Skip to content

Commit

Permalink
feat: make async functions send+sync (#38)
Browse files Browse the repository at this point in the history
* feat: make async functions send+sync

* simplify test
  • Loading branch information
ccbrown authored Dec 10, 2024
1 parent 7a1b364 commit 40b4692
Show file tree
Hide file tree
Showing 14 changed files with 57 additions and 43 deletions.
4 changes: 2 additions & 2 deletions packages/iocraft-macros/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -531,12 +531,12 @@ impl ToTokens for ParsedComponent {
/// ```
/// # use iocraft::prelude::*;
/// #[derive(Default, Props)]
/// struct MyGenericComponentProps<T> {
/// struct MyGenericComponentProps<T: Send + Sync> {
/// items: Vec<T>,
/// }
///
/// #[component]
/// fn MyGenericComponent<T: 'static>(
/// fn MyGenericComponent<T: Send + Sync + 'static>(
/// _props: &MyGenericComponentProps<T>,
/// ) -> impl Into<AnyElement<'static>> {
/// element!(Box)
Expand Down
10 changes: 5 additions & 5 deletions packages/iocraft-macros/tests/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -29,20 +29,20 @@ fn MyComponentWithHooksRef(_hooks: &mut Hooks) -> impl Into<AnyElement<'static>>
}

#[derive(Props)]
struct MyGenericProps<T, const U: usize> {
struct MyGenericProps<T: Send + Sync, const U: usize> {
foo: [T; U],
}

#[component]
fn MyComponentWithGenericProps<T: 'static, const U: usize>(
fn MyComponentWithGenericProps<T: Send + Sync + 'static, const U: usize>(
_props: &mut MyGenericProps<T, U>,
) -> impl Into<AnyElement<'static>> {
element!(Box)
}

fn check_component_traits<T: Sync + Send>() {}
fn check_component_traits<T: Send + Sync>() {}

fn check_component_traits_with_generic<T: 'static, const U: usize>() {
fn check_component_traits_with_generic<T: Send + Sync + 'static, const U: usize>() {
check_component_traits::<MyComponentWithGenericProps<T, U>>();
}

Expand All @@ -51,7 +51,7 @@ fn MyComponentWithGenericPropsWhereClause<T, const U: usize>(
_props: &mut MyGenericProps<T, U>,
) -> impl Into<AnyElement<'static>>
where
T: 'static,
T: Send + Sync + 'static,
{
element!(Box)
}
8 changes: 4 additions & 4 deletions packages/iocraft-macros/tests/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,16 +35,16 @@ impl Component for MyContainer {
}
}

struct MyGenericComponent<T> {
_marker: std::marker::PhantomData<*const T>,
struct MyGenericComponent<T: Sync + 'static> {
_marker: std::marker::PhantomData<&'static T>,
}

#[derive(Default, Props)]
struct MyGenericComponentProps<T> {
struct MyGenericComponentProps<T: Send + Sync> {
items: Vec<T>,
}

impl<T: 'static> Component for MyGenericComponent<T> {
impl<T: Send + Sync + 'static> Component for MyGenericComponent<T> {
type Props<'a> = MyGenericComponentProps<T>;

fn new(_props: &Self::Props<'_>) -> Self {
Expand Down
4 changes: 2 additions & 2 deletions packages/iocraft-macros/tests/props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -25,11 +25,11 @@ struct StructWithLifetimeAndConsts<'lt, const N: usize, const M: usize> {
}

#[derive(Props)]
struct StructWithTypeGeneric<T> {
struct StructWithTypeGeneric<T: Send + Sync> {
foo: T,
}

#[derive(Props)]
struct StructWithLifetimeAndTypeGeneric<'lt, T> {
struct StructWithLifetimeAndTypeGeneric<'lt, T: Sync> {
foo: &'lt T,
}
2 changes: 1 addition & 1 deletion packages/iocraft-macros/tests/with_layout_style_props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ struct MyPropsWithLifetime<'lt> {

#[with_layout_style_props]
#[derive(Default, Props)]
struct MyPropsWithTypeGeneric<T> {
struct MyPropsWithTypeGeneric<T: Send + Sync> {
foo: Option<T>,
}

Expand Down
6 changes: 3 additions & 3 deletions packages/iocraft/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ impl<C: Component> ComponentHelper<C> {
}

#[doc(hidden)]
pub trait ComponentHelperExt: Any {
pub trait ComponentHelperExt: Any + Send + Sync {
fn new_component(&self, props: AnyProps) -> Box<dyn AnyComponent>;
fn update_component(
&self,
Expand Down Expand Up @@ -70,7 +70,7 @@ impl<C: Component> ComponentHelperExt for ComponentHelper<C> {
///
/// 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
Expand Down Expand Up @@ -103,7 +103,7 @@ impl<C: Component> 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<()>;
Expand Down
14 changes: 7 additions & 7 deletions packages/iocraft/src/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn Any>),
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<T: Any>(context: T) -> Self {
pub fn owned<T: Any + Send + Sync>(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<T: Any>(context: &'a mut T) -> Self {
pub fn from_mut<T: Any + Send + Sync>(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<T: Any>(context: &'a T) -> Self {
pub fn from_ref<T: Any + Send + Sync>(context: &'a T) -> Self {
Context::Ref(context)
}

Expand Down Expand Up @@ -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))],
}
Expand Down
8 changes: 4 additions & 4 deletions packages/iocraft/src/element.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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<Box<dyn AnyHash>>);
pub struct ElementKey(Arc<Box<dyn AnyHash + Send + Sync>>);

impl ElementKey {
/// Constructs a new key.
pub fn new<K: Debug + Hash + Eq + 'static>(key: K) -> Self {
Self(Rc::new(Box::new(key)))
pub fn new<K: Debug + Hash + Eq + Send + Sync + 'static>(key: K) -> Self {
Self(Arc::new(Box::new(key)))
}
}

Expand Down
6 changes: 3 additions & 3 deletions packages/iocraft/src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<dyn FnMut(T) + Send + 'a>);
pub struct Handler<'a, T>(bool, Box<dyn FnMut(T) + Send + Sync + 'a>);

impl<'a, T> Handler<'a, T> {
/// Returns `true` if the handler was default-initialized.
Expand All @@ -29,15 +29,15 @@ impl<'a, T> Default for Handler<'a, T> {

impl<'a, T, F> From<F> 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))
}
}

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
Expand Down
2 changes: 1 addition & 1 deletion packages/iocraft/src/hook.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<()> {
Expand Down
4 changes: 2 additions & 2 deletions packages/iocraft/src/hooks/use_async_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,14 @@ pub trait UseAsyncHandler: private::Sealed {
/// resulting future to completion.
fn use_async_handler<T, Fun, Fut>(&mut self, f: Fun) -> Handler<'static, T>
where
Fun: FnMut(T) -> Fut + Send + 'static,
Fun: FnMut(T) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static;
}

impl UseAsyncHandler for Hooks<'_, '_> {
fn use_async_handler<T, Fun, Fut>(&mut self, mut f: Fun) -> Handler<'static, T>
where
Fun: FnMut(T) -> Fut + Send + 'static,
Fun: FnMut(T) -> Fut + Send + Sync + 'static,
Fut: Future<Output = ()> + Send + 'static,
{
let handler_impl_state = self.use_hook(UseAsyncHandlerImpl::default).state.clone();
Expand Down
8 changes: 7 additions & 1 deletion packages/iocraft/src/props.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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)]
Expand Down Expand Up @@ -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<T: Props + 'a>(props: T) -> Self {
let raw = Box::into_raw(Box::new(props));
Expand Down
22 changes: 15 additions & 7 deletions packages/iocraft/src/render.rs
Original file line number Diff line number Diff line change
Expand Up @@ -275,7 +275,7 @@ impl<'a> ComponentDrawer<'a> {
}
}

type MeasureFunc = Box<dyn Fn(Size<Option<f32>>, Size<AvailableSpace>, &Style) -> Size<f32>>;
type MeasureFunc = Box<dyn Fn(Size<Option<f32>>, Size<AvailableSpace>, &Style) -> Size<f32> + Send>;

#[derive(Default)]
pub(crate) struct LayoutEngineNodeContext {
Expand Down Expand Up @@ -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;
}
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -531,6 +528,17 @@ mod tests {
assert_eq!(actual, expected);
}

async fn await_send_future<F: Future<Output = io::Result<()>> + 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<AnyElement<'static>> {
element! {
Expand Down
2 changes: 1 addition & 1 deletion packages/iocraft/src/terminal.rs
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,7 @@ impl Stream for TerminalEvents {
}
}

trait TerminalImpl: Write {
trait TerminalImpl: Write + Send {
fn width(&self) -> Option<u16>;
fn is_raw_mode_enabled(&self) -> bool;
fn clear_canvas(&mut self) -> io::Result<()>;
Expand Down

0 comments on commit 40b4692

Please sign in to comment.