From 40b469292e023e53fba3c37071468707fe0a77ed Mon Sep 17 00:00:00 2001 From: Chris <1731074+ccbrown@users.noreply.github.com> Date: Tue, 10 Dec 2024 18:02:26 -0500 Subject: [PATCH] feat: make async functions send+sync (#38) * feat: make async functions send+sync * simplify test --- packages/iocraft-macros/src/lib.rs | 4 ++-- packages/iocraft-macros/tests/component.rs | 10 ++++----- packages/iocraft-macros/tests/element.rs | 8 +++---- packages/iocraft-macros/tests/props.rs | 4 ++-- .../tests/with_layout_style_props.rs | 2 +- packages/iocraft/src/component.rs | 6 ++--- packages/iocraft/src/context.rs | 14 ++++++------ packages/iocraft/src/element.rs | 8 +++---- packages/iocraft/src/handler.rs | 6 ++--- packages/iocraft/src/hook.rs | 2 +- .../iocraft/src/hooks/use_async_handler.rs | 4 ++-- packages/iocraft/src/props.rs | 8 ++++++- packages/iocraft/src/render.rs | 22 +++++++++++++------ packages/iocraft/src/terminal.rs | 2 +- 14 files changed, 57 insertions(+), 43 deletions(-) diff --git a/packages/iocraft-macros/src/lib.rs b/packages/iocraft-macros/src/lib.rs index 3123b0e..da25303 100644 --- a/packages/iocraft-macros/src/lib.rs +++ b/packages/iocraft-macros/src/lib.rs @@ -531,12 +531,12 @@ impl ToTokens for ParsedComponent { /// ``` /// # use iocraft::prelude::*; /// #[derive(Default, Props)] -/// struct MyGenericComponentProps { +/// struct MyGenericComponentProps { /// items: Vec, /// } /// /// #[component] -/// fn MyGenericComponent( +/// fn MyGenericComponent( /// _props: &MyGenericComponentProps, /// ) -> impl Into> { /// element!(Box) diff --git a/packages/iocraft-macros/tests/component.rs b/packages/iocraft-macros/tests/component.rs index d3a9778..7de4899 100644 --- a/packages/iocraft-macros/tests/component.rs +++ b/packages/iocraft-macros/tests/component.rs @@ -29,20 +29,20 @@ fn MyComponentWithHooksRef(_hooks: &mut Hooks) -> impl Into> } #[derive(Props)] -struct MyGenericProps { +struct MyGenericProps { foo: [T; U], } #[component] -fn MyComponentWithGenericProps( +fn MyComponentWithGenericProps( _props: &mut MyGenericProps, ) -> impl Into> { element!(Box) } -fn check_component_traits() {} +fn check_component_traits() {} -fn check_component_traits_with_generic() { +fn check_component_traits_with_generic() { check_component_traits::>(); } @@ -51,7 +51,7 @@ fn MyComponentWithGenericPropsWhereClause( _props: &mut MyGenericProps, ) -> impl Into> where - T: 'static, + T: Send + Sync + 'static, { element!(Box) } diff --git a/packages/iocraft-macros/tests/element.rs b/packages/iocraft-macros/tests/element.rs index 3896c71..0d47b6d 100644 --- a/packages/iocraft-macros/tests/element.rs +++ b/packages/iocraft-macros/tests/element.rs @@ -35,16 +35,16 @@ impl Component for MyContainer { } } -struct MyGenericComponent { - _marker: std::marker::PhantomData<*const T>, +struct MyGenericComponent { + _marker: std::marker::PhantomData<&'static T>, } #[derive(Default, Props)] -struct MyGenericComponentProps { +struct MyGenericComponentProps { items: Vec, } -impl Component for MyGenericComponent { +impl Component for MyGenericComponent { type Props<'a> = MyGenericComponentProps; fn new(_props: &Self::Props<'_>) -> Self { diff --git a/packages/iocraft-macros/tests/props.rs b/packages/iocraft-macros/tests/props.rs index 2a72f62..04aa2f9 100644 --- a/packages/iocraft-macros/tests/props.rs +++ b/packages/iocraft-macros/tests/props.rs @@ -25,11 +25,11 @@ struct StructWithLifetimeAndConsts<'lt, const N: usize, const M: usize> { } #[derive(Props)] -struct StructWithTypeGeneric { +struct StructWithTypeGeneric { foo: T, } #[derive(Props)] -struct StructWithLifetimeAndTypeGeneric<'lt, T> { +struct StructWithLifetimeAndTypeGeneric<'lt, T: Sync> { foo: &'lt T, } diff --git a/packages/iocraft-macros/tests/with_layout_style_props.rs b/packages/iocraft-macros/tests/with_layout_style_props.rs index d24105d..2f26fa2 100644 --- a/packages/iocraft-macros/tests/with_layout_style_props.rs +++ b/packages/iocraft-macros/tests/with_layout_style_props.rs @@ -15,7 +15,7 @@ struct MyPropsWithLifetime<'lt> { #[with_layout_style_props] #[derive(Default, Props)] -struct MyPropsWithTypeGeneric { +struct MyPropsWithTypeGeneric { foo: Option, } diff --git a/packages/iocraft/src/component.rs b/packages/iocraft/src/component.rs index 08a3934..3790614 100644 --- a/packages/iocraft/src/component.rs +++ b/packages/iocraft/src/component.rs @@ -28,7 +28,7 @@ impl ComponentHelper { } #[doc(hidden)] -pub trait ComponentHelperExt: Any { +pub trait ComponentHelperExt: Any + Send + Sync { fn new_component(&self, props: AnyProps) -> Box; fn update_component( &self, @@ -70,7 +70,7 @@ impl ComponentHelperExt for ComponentHelper { /// /// Most users will not need to implement this trait directly. This is only required for new, low /// level component type definitions. Instead, the [`component`](macro@crate::component) macro should be used. -pub trait Component: Any + Unpin { +pub trait Component: Any + Send + Sync + Unpin { /// The type of properties that the component accepts. type Props<'a>: Props where @@ -103,7 +103,7 @@ impl ElementType for C { } #[doc(hidden)] -pub trait AnyComponent: Any + Unpin { +pub trait AnyComponent: Any + Send + Sync + Unpin { fn update(&mut self, props: AnyProps, hooks: Hooks, updater: &mut ComponentUpdater); fn draw(&mut self, drawer: &mut ComponentDrawer<'_>); fn poll_change(self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<()>; diff --git a/packages/iocraft/src/context.rs b/packages/iocraft/src/context.rs index 3860921..f211095 100644 --- a/packages/iocraft/src/context.rs +++ b/packages/iocraft/src/context.rs @@ -29,31 +29,31 @@ impl SystemContext { pub enum Context<'a> { /// Provides the context via a mutable reference. Children will be able to get mutable or /// immutable references to the context. - Mut(&'a mut dyn Any), + Mut(&'a mut (dyn Any + Send + Sync)), /// Provides the context via an immutable reference. Children will not be able to get a mutable /// reference to the context. - Ref(&'a dyn Any), + Ref(&'a (dyn Any + Send + Sync)), /// Provides the context via an owned value. Children will be able to get mutable or immutable /// references to the context. - Owned(Box), + Owned(Box<(dyn Any + Send + Sync)>), } impl<'a> Context<'a> { /// Creates a new context from an owned value. Children will be able to get mutable or /// immutable references to the context. - pub fn owned(context: T) -> Self { + pub fn owned(context: T) -> Self { Context::Owned(Box::new(context)) } /// Creates a new context from a mutable reference. Children will be able to get mutable or /// immutable references to the context. - pub fn from_mut(context: &'a mut T) -> Self { + pub fn from_mut(context: &'a mut T) -> Self { Context::Mut(context) } /// Creates a new context from an immutable reference. Children will not be able to get a /// mutable reference to the context. - pub fn from_ref(context: &'a T) -> Self { + pub fn from_ref(context: &'a T) -> Self { Context::Ref(context) } @@ -91,7 +91,7 @@ pub struct ContextStack<'a> { } impl<'a> ContextStack<'a> { - pub(crate) fn root(root_context: &'a mut dyn Any) -> Self { + pub(crate) fn root(root_context: &'a mut (dyn Any + Send + Sync)) -> Self { Self { contexts: vec![RefCell::new(Context::Mut(root_context))], } diff --git a/packages/iocraft/src/element.rs b/packages/iocraft/src/element.rs index a0229d7..6e83b1e 100644 --- a/packages/iocraft/src/element.rs +++ b/packages/iocraft/src/element.rs @@ -12,7 +12,7 @@ use std::{ future::Future, hash::Hash, io::{self, stderr, stdout, IsTerminal, Write}, - rc::Rc, + sync::Arc, }; /// Used by the `element!` macro to extend a collection with elements. @@ -60,12 +60,12 @@ where /// Used to identify an element within the scope of its parent. This is used to minimize the number /// of times components are destroyed and recreated from render-to-render. #[derive(Clone, Hash, PartialEq, Eq, Debug)] -pub struct ElementKey(Rc>); +pub struct ElementKey(Arc>); impl ElementKey { /// Constructs a new key. - pub fn new(key: K) -> Self { - Self(Rc::new(Box::new(key))) + pub fn new(key: K) -> Self { + Self(Arc::new(Box::new(key))) } } diff --git a/packages/iocraft/src/handler.rs b/packages/iocraft/src/handler.rs index 7554ffb..40fa677 100644 --- a/packages/iocraft/src/handler.rs +++ b/packages/iocraft/src/handler.rs @@ -7,7 +7,7 @@ use std::{ /// /// Any function that takes a single argument and returns `()` can be converted into a `Handler`, /// and it can be invoked using function call syntax. -pub struct Handler<'a, T>(bool, Box); +pub struct Handler<'a, T>(bool, Box); impl<'a, T> Handler<'a, T> { /// Returns `true` if the handler was default-initialized. @@ -29,7 +29,7 @@ impl<'a, T> Default for Handler<'a, T> { impl<'a, T, F> From for Handler<'a, T> where - F: FnMut(T) + Send + 'a, + F: FnMut(T) + Send + Sync + 'a, { fn from(f: F) -> Self { Self(true, Box::new(f)) @@ -37,7 +37,7 @@ where } impl<'a, T: 'a> Deref for Handler<'a, T> { - type Target = dyn FnMut(T) + Send + 'a; + type Target = dyn FnMut(T) + Send + Sync + 'a; fn deref(&self) -> &Self::Target { &self.1 diff --git a/packages/iocraft/src/hook.rs b/packages/iocraft/src/hook.rs index 77fa437..266ab9c 100644 --- a/packages/iocraft/src/hook.rs +++ b/packages/iocraft/src/hook.rs @@ -10,7 +10,7 @@ use std::{ /// /// Hooks are created by implementing this trait. All methods have default implementations, so /// you only need to implement the ones you care about. -pub trait Hook: Unpin { +pub trait Hook: Unpin + Send { /// Called to determine if the hook has caused a change which requires its component to be /// redrawn. fn poll_change(self: Pin<&mut Self>, _cx: &mut Context) -> Poll<()> { diff --git a/packages/iocraft/src/hooks/use_async_handler.rs b/packages/iocraft/src/hooks/use_async_handler.rs index b1cefb3..771c14f 100644 --- a/packages/iocraft/src/hooks/use_async_handler.rs +++ b/packages/iocraft/src/hooks/use_async_handler.rs @@ -21,14 +21,14 @@ pub trait UseAsyncHandler: private::Sealed { /// resulting future to completion. fn use_async_handler(&mut self, f: Fun) -> Handler<'static, T> where - Fun: FnMut(T) -> Fut + Send + 'static, + Fun: FnMut(T) -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static; } impl UseAsyncHandler for Hooks<'_, '_> { fn use_async_handler(&mut self, mut f: Fun) -> Handler<'static, T> where - Fun: FnMut(T) -> Fut + Send + 'static, + Fun: FnMut(T) -> Fut + Send + Sync + 'static, Fut: Future + Send + 'static, { let handler_impl_state = self.use_hook(UseAsyncHandlerImpl::default).state.clone(); diff --git a/packages/iocraft/src/props.rs b/packages/iocraft/src/props.rs index 935d1e3..72d3a48 100644 --- a/packages/iocraft/src/props.rs +++ b/packages/iocraft/src/props.rs @@ -60,7 +60,7 @@ use std::marker::PhantomData; /// implemented for a type that is not actually covariant, then the safety of the program is /// compromised. You can use the [`#[derive(Props)]`](derive@crate::Props) macro to implement this trait safely. If the /// type is not actually covariant, the derive macro will give you an error at compile-time. -pub unsafe trait Props {} +pub unsafe trait Props: Send + Sync {} #[doc(hidden)] #[derive(Clone, Copy, iocraft_macros::Props, Default)] @@ -89,6 +89,12 @@ pub struct AnyProps<'a> { _marker: PhantomData<&'a mut ()>, } +// SAFETY: Safe because `Props` must be `Send` and `Sync`. +unsafe impl Send for AnyProps<'_> {} + +// SAFETY: Safe because `Props` must be `Sync`. +unsafe impl Sync for AnyProps<'_> {} + impl<'a> AnyProps<'a> { pub(crate) fn owned(props: T) -> Self { let raw = Box::into_raw(Box::new(props)); diff --git a/packages/iocraft/src/render.rs b/packages/iocraft/src/render.rs index eb7322f..9546129 100644 --- a/packages/iocraft/src/render.rs +++ b/packages/iocraft/src/render.rs @@ -275,7 +275,7 @@ impl<'a> ComponentDrawer<'a> { } } -type MeasureFunc = Box>, Size, &Style) -> Size>; +type MeasureFunc = Box>, Size, &Style) -> Size + Send>; #[derive(Default)] pub(crate) struct LayoutEngineNodeContext { @@ -405,11 +405,7 @@ impl<'a> Tree<'a> { if self.system_context.should_exit() || term.received_ctrl_c() { break; } - select( - self.root_component.wait().boxed_local(), - term.wait().boxed_local(), - ) - .await; + select(self.root_component.wait().boxed(), term.wait().boxed()).await; if term.received_ctrl_c() { break; } @@ -470,10 +466,11 @@ where #[cfg(test)] mod tests { + use super::*; use crate::prelude::*; - use futures::stream::StreamExt; use macro_rules_attribute::apply; use smol_macros::test; + use std::future::Future; #[derive(Default, Props)] struct MyInnerComponentProps { @@ -531,6 +528,17 @@ mod tests { assert_eq!(actual, expected); } + async fn await_send_future> + Send>(f: F) { + f.await.unwrap(); + } + + // Make sure terminal_render_loop can be sent across threads. + #[apply(test!)] + async fn test_terminal_render_loop_send() { + let (term, _output) = Terminal::mock(MockTerminalConfig::default()); + await_send_future(terminal_render_loop(element!(MyComponent), term)).await; + } + #[component] fn FullWidthComponent() -> impl Into> { element! { diff --git a/packages/iocraft/src/terminal.rs b/packages/iocraft/src/terminal.rs index bff4a5d..0637d30 100644 --- a/packages/iocraft/src/terminal.rs +++ b/packages/iocraft/src/terminal.rs @@ -111,7 +111,7 @@ impl Stream for TerminalEvents { } } -trait TerminalImpl: Write { +trait TerminalImpl: Write + Send { fn width(&self) -> Option; fn is_raw_mode_enabled(&self) -> bool; fn clear_canvas(&mut self) -> io::Result<()>;