diff --git a/examples/core/composer.rs b/examples/core/composer.rs index b0f45ec..050c8a1 100644 --- a/examples/core/composer.rs +++ b/examples/core/composer.rs @@ -35,7 +35,10 @@ fn main() { .unwrap(); let mut composer = Composer::new(App); + composer.try_compose().unwrap(); assert_eq!(composer.try_compose(), Err(TryComposeError::Pending)); + + dbg!(composer); } diff --git a/src/compose/dyn_compose.rs b/src/compose/dyn_compose.rs index f324ce5..19b863d 100644 --- a/src/compose/dyn_compose.rs +++ b/src/compose/dyn_compose.rs @@ -1,6 +1,12 @@ -use super::AnyCompose; +use slotmap::DefaultKey; + +use super::{drop_node, AnyCompose, Node, Runtime}; use crate::{compose::Compose, use_ref, Scope, ScopeData}; use core::{any::TypeId, cell::UnsafeCell, mem}; +use std::{ + cell::{Cell, RefCell}, + rc::Rc, +}; /// Create a new dynamically-typed composable. pub fn dyn_compose<'a>(content: impl Compose + 'a) -> DynCompose<'a> { @@ -15,22 +21,64 @@ pub struct DynCompose<'a> { compose: UnsafeCell>>, } +#[derive(Clone, Copy)] struct DynComposeState { - compose: Box, + key: DefaultKey, data_id: TypeId, } impl Compose for DynCompose<'_> { fn compose(cx: Scope) -> impl Compose { - cx.is_container.set(true); + let state: &Cell> = use_ref(&cx, || Cell::new(None)); + + let rt = Runtime::current(); + let mut nodes = rt.nodes.borrow_mut(); + + if let Some(state) = state.get() { + let compose: &mut dyn AnyCompose = unsafe { &mut *cx.me().compose.get() } + .as_deref_mut() + .unwrap(); + let mut compose: Box = unsafe { mem::transmute(compose) }; + let data_id = compose.data_id(); + + if data_id == state.data_id { + let mut last = nodes[state.key].compose.borrow_mut(); + unsafe { compose.reborrow(last.as_ptr_mut()) }; + + rt.pending.borrow_mut().push_back(state.key); + + return; + } else { + drop_node(&mut nodes, state.key); + } + } - let cell: &UnsafeCell> = use_ref(&cx, || UnsafeCell::new(None)); - let cell = unsafe { &mut *cell.get() }; + let Some(compose) = unsafe { &mut *cx.me().compose.get() }.take() else { + if let Some(state) = state.get() { + rt.pending.borrow_mut().push_back(state.key); + } + + return; + }; + let compose: Box = unsafe { mem::transmute(compose) }; + let data_id = compose.data_id(); - let inner = unsafe { &mut *cx.me().compose.get() }; + let key = nodes.insert(Rc::new(Node { + compose: RefCell::new(crate::composer::ComposePtr::Boxed(compose)), + scope: ScopeData::default(), + parent: Some(rt.current_key.get()), + children: RefCell::new(Vec::new()), + })); + state.set(Some(DynComposeState { key, data_id })); - let child_state = use_ref(&cx, || UnsafeCell::new(ScopeData::default())); - let child_state = unsafe { &mut *child_state.get() }; + nodes + .get(rt.current_key.get()) + .unwrap() + .children + .borrow_mut() + .push(key); + + let child_state = &nodes[key].scope; *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone(); child_state @@ -39,31 +87,6 @@ impl Compose for DynCompose<'_> { .values .extend(cx.child_contexts.borrow().values.clone()); - child_state - .is_parent_changed - .set(cx.is_parent_changed.get()); - - if let Some(any_compose) = inner.take() { - let mut compose: Box = unsafe { mem::transmute(any_compose) }; - - if let Some(state) = cell { - if state.data_id != compose.data_id() { - *child_state = ScopeData::default(); - state.compose = compose; - } else { - let ptr = (*state.compose).as_ptr_mut(); - unsafe { - compose.reborrow(ptr); - } - } - } else { - *cell = Some(DynComposeState { - data_id: compose.data_id(), - compose, - }) - } - } - - unsafe { cell.as_mut().unwrap().compose.any_compose(child_state) } + rt.pending.borrow_mut().push_back(key); } } diff --git a/src/compose/from_iter.rs b/src/compose/from_iter.rs index 264def8..e4a5c6b 100644 --- a/src/compose/from_iter.rs +++ b/src/compose/from_iter.rs @@ -1,6 +1,9 @@ -use super::AnyCompose; +use slotmap::DefaultKey; + +use super::{AnyCompose, Node, Runtime}; use crate::{compose::Compose, data::Data, use_ref, Scope, ScopeData, Signal}; use core::{cell::RefCell, mem}; +use std::rc::Rc; /// Composable from an iterator, created with [`from_iter`]. #[must_use = "Composables do nothing unless composed or returned from other composables."] @@ -24,46 +27,39 @@ where C: Compose, { fn compose(cx: Scope) -> impl Compose { - cx.is_container.set(true); - let states: &RefCell> = use_ref(&cx, || RefCell::new(Vec::new())); let mut states = states.borrow_mut(); - if cx.is_parent_changed() { - let mut items: Vec> = cx.me().iter.clone().into_iter().map(Some).collect(); - - if items.len() >= states.len() { - for item in &mut items[states.len()..] { - let item = item.take().unwrap(); - - let state = ItemState { - item, - compose: None, - scope: ScopeData::default(), - }; - - let state = Box::new(state); - let boxed: Box<()> = unsafe { mem::transmute(state) }; - - states.push(AnyItemState { - boxed: Some(boxed), - drop: |any_state| { - let state: Box> = - unsafe { mem::transmute(any_state.boxed.take().unwrap()) }; - drop(state); - }, - }); - } - } else { - states.truncate(items.len()); + let mut items: Vec> = cx.me().iter.clone().into_iter().map(Some).collect(); + + let rt = Runtime::current(); + let mut nodes = rt.nodes.borrow_mut(); + + if items.len() >= states.len() { + for item in &mut items[states.len()..] { + let item = item.take().unwrap(); + + let state = ItemState { item, key: None }; + let boxed = Box::new(state); + let boxed: Box<()> = unsafe { mem::transmute(boxed) }; + states.push(AnyItemState { + boxed: Some(boxed), + drop: |any_state| { + let state: Box> = + unsafe { mem::transmute(any_state.boxed.take().unwrap()) }; + drop(state); + }, + }); } + } else { + states.truncate(items.len()); } for state in &mut *states { let state: &mut ItemState = unsafe { mem::transmute(state.boxed.as_deref_mut().unwrap()) }; - if state.compose.is_none() || cx.is_parent_changed() { + if state.key.is_none() { let item_ref: &Item = &state.item; let item_ref: &Item = unsafe { mem::transmute(item_ref) }; let compose = (cx.me().make_item)(Signal { @@ -73,24 +69,32 @@ where let any_compose: Box = Box::new(compose); let any_compose: Box = unsafe { mem::transmute(any_compose) }; - state.compose = Some(any_compose); + let key = nodes.insert(Rc::new(Node { + compose: RefCell::new(crate::composer::ComposePtr::Boxed(any_compose)), + scope: ScopeData::default(), + parent: Some(rt.current_key.get()), + children: RefCell::new(Vec::new()), + })); + nodes + .get(rt.current_key.get()) + .unwrap() + .children + .borrow_mut() + .push(key); + + state.key = Some(key); } - *state.scope.contexts.borrow_mut() = cx.contexts.borrow().clone(); - state - .scope + let node = nodes.get(state.key.unwrap()).unwrap().clone(); + + *node.scope.contexts.borrow_mut() = cx.contexts.borrow().clone(); + node.scope .contexts .borrow_mut() .values .extend(cx.child_contexts.borrow().values.clone()); - state - .scope - .is_parent_changed - .set(cx.is_parent_changed.get()); - - let compose = state.compose.as_ref().unwrap(); - unsafe { compose.any_compose(&state.scope) } + rt.pending.borrow_mut().push_back(state.key.unwrap()); } } } @@ -115,8 +119,7 @@ where struct ItemState { item: T, - compose: Option>, - scope: ScopeData<'static>, + key: Option, } struct AnyItemState { diff --git a/src/compose/memo.rs b/src/compose/memo.rs index 38c91d2..e6eceb4 100644 --- a/src/compose/memo.rs +++ b/src/compose/memo.rs @@ -1,6 +1,8 @@ -use crate::{compose::Compose, data::Data, use_ref, Scope, Signal}; +use super::{AnyCompose, Node, Runtime}; +use crate::{compose::Compose, data::Data, use_ref, Scope, ScopeData}; use alloc::borrow::Cow; use core::cell::RefCell; +use std::{mem, rc::Rc}; /// Create a new memoized composable. /// @@ -35,19 +37,60 @@ where C: Compose, { fn compose(cx: Scope) -> impl Compose { + let rt = Runtime::current(); + + let mut is_init = false; + let child_key = use_ref(&cx, || { + is_init = true; + + let mut nodes = rt.nodes.borrow_mut(); + + let ptr: *const dyn AnyCompose = + unsafe { mem::transmute(&cx.me().content as *const dyn AnyCompose) }; + let child_key = nodes.insert(Rc::new(Node { + compose: RefCell::new(crate::composer::ComposePtr::Ptr(ptr)), + scope: ScopeData::default(), + parent: Some(rt.current_key.get()), + children: RefCell::new(Vec::new()), + })); + + nodes + .get(rt.current_key.get()) + .unwrap() + .children + .borrow_mut() + .push(child_key); + + let child_state = &nodes[child_key].scope; + + *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone(); + child_state + .contexts + .borrow_mut() + .values + .extend(cx.child_contexts.borrow().values.clone()); + + child_key + }); + + if !is_init { + let last = rt.nodes.borrow().get(*child_key).unwrap().clone(); + let ptr: *const dyn AnyCompose = + unsafe { mem::transmute(&cx.me().content as *const dyn AnyCompose) }; + *last.compose.borrow_mut() = crate::composer::ComposePtr::Ptr(ptr); + } + let last = use_ref(&cx, RefCell::default); let mut last = last.borrow_mut(); if let Some(last) = &mut *last { if cx.me().dependency != *last { *last = cx.me().dependency.clone(); - cx.is_parent_changed.set(true); + rt.pending.borrow_mut().push_back(*child_key); } } else { *last = Some(cx.me().dependency.clone()); - cx.is_parent_changed.set(true); + rt.pending.borrow_mut().push_back(*child_key); } - - unsafe { Signal::map_unchecked(cx.me(), |me| &me.content) } } fn name() -> Option> { diff --git a/src/compose/mod.rs b/src/compose/mod.rs index e7cb352..668f777 100644 --- a/src/compose/mod.rs +++ b/src/compose/mod.rs @@ -1,4 +1,8 @@ -use crate::{data::Data, use_context, use_ref, Scope, ScopeData, ScopeState}; +use crate::{ + composer::{ComposePtr, Node, Runtime}, + data::Data, + use_context, use_ref, Scope, ScopeData, ScopeState, +}; use alloc::borrow::Cow; use core::{ any::TypeId, @@ -6,6 +10,8 @@ use core::{ error::Error as StdError, mem, }; +use slotmap::{DefaultKey, SlotMap}; +use std::{cell::Cell, rc::Rc}; mod catch; pub use self::catch::{catch, Catch}; @@ -63,41 +69,76 @@ impl Compose for () { } } -impl Compose for &C { - fn compose(cx: Scope) -> impl Compose { - unsafe { - (**cx.me()).any_compose(&cx); - } - } -} - impl Compose for Option { fn compose(cx: Scope) -> impl Compose { - cx.is_container.set(true); + let child_key = use_ref(&cx, || Cell::new(None)); - let state_cell: &RefCell> = use_ref(&cx, || RefCell::new(None)); - let mut state_cell = state_cell.borrow_mut(); + let rt = Runtime::current(); + let mut nodes = rt.nodes.borrow_mut(); if let Some(content) = &*cx.me() { - if let Some(state) = &*state_cell { - state.is_parent_changed.set(cx.is_parent_changed.get()); - unsafe { - content.any_compose(state); - } + if let Some(key) = child_key.get() { + let last = nodes.get_mut(key).unwrap(); + + let ptr = content as *const dyn AnyCompose; + let ptr: *const dyn AnyCompose = unsafe { mem::transmute(ptr) }; + + *last.compose.borrow_mut() = ComposePtr::Ptr(ptr); + + rt.pending.borrow_mut().push_back(key); } else { - let mut state = ScopeData::default(); - state.contexts = cx.contexts.clone(); - *state_cell = Some(state); - unsafe { - content.any_compose(state_cell.as_ref().unwrap()); - } + let ptr: *const dyn AnyCompose = + unsafe { mem::transmute(content as *const dyn AnyCompose) }; + let key = nodes.insert(Rc::new(Node { + compose: RefCell::new(crate::composer::ComposePtr::Ptr(ptr)), + scope: ScopeData::default(), + parent: Some(rt.current_key.get()), + children: RefCell::new(Vec::new()), + })); + child_key.set(Some(key)); + + nodes + .get(rt.current_key.get()) + .unwrap() + .children + .borrow_mut() + .push(key); + + let child_state = &nodes[key].scope; + + *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone(); + child_state + .contexts + .borrow_mut() + .values + .extend(cx.child_contexts.borrow().values.clone()); + + rt.pending.borrow_mut().push_back(key); } - } else { - *state_cell = None; + } else if let Some(key) = child_key.get() { + child_key.set(None); + + drop_node(&mut nodes, key); } } } +// TODO replace with non-recursive algorithm. +fn drop_node(nodes: &mut SlotMap>, key: DefaultKey) { + let node = nodes[key].clone(); + if let Some(parent) = node.parent { + let parent = nodes.get_mut(parent).unwrap(); + parent.children.borrow_mut().retain(|&x| x != key); + } + + let children = node.children.borrow().clone(); + for key in children { + drop_node(nodes, key) + } + + nodes.remove(key); +} + /// Composable error. /// /// This can be handled by a parent composable with [`Catch`]. @@ -120,31 +161,58 @@ impl Compose for Result { fn compose(cx: Scope) -> impl Compose { let catch_cx = use_context::(&cx).unwrap(); - cx.is_container.set(true); + let child_key = use_ref(&cx, || Cell::new(None)); - let state_cell: &RefCell> = use_ref(&cx, || RefCell::new(None)); - let mut state_cell = state_cell.borrow_mut(); + let rt = Runtime::current(); + let mut nodes = rt.nodes.borrow_mut(); match &*cx.me() { Ok(content) => { - if let Some(state) = &*state_cell { - state.is_parent_changed.set(cx.is_parent_changed.get()); - unsafe { - content.any_compose(state); - } + if let Some(key) = child_key.get() { + let last = nodes.get_mut(key).unwrap(); + + let ptr = content as *const dyn AnyCompose; + let ptr: *const dyn AnyCompose = unsafe { mem::transmute(ptr) }; + + *last.compose.borrow_mut() = ComposePtr::Ptr(ptr); + + rt.pending.borrow_mut().push_back(key); } else { - let mut state = ScopeData::default(); - state.contexts = cx.contexts.clone(); - *state_cell = Some(state); - unsafe { - content.any_compose(state_cell.as_ref().unwrap()); - } + let ptr: *const dyn AnyCompose = + unsafe { mem::transmute(content as *const dyn AnyCompose) }; + let key = nodes.insert(Rc::new(Node { + compose: RefCell::new(crate::composer::ComposePtr::Ptr(ptr)), + scope: ScopeData::default(), + parent: Some(rt.current_key.get()), + children: RefCell::new(Vec::new()), + })); + child_key.set(Some(key)); + + nodes + .get(rt.current_key.get()) + .unwrap() + .children + .borrow_mut() + .push(key); + + let child_state = &nodes[key].scope; + + *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone(); + child_state + .contexts + .borrow_mut() + .values + .extend(cx.child_contexts.borrow().values.clone()); + + rt.pending.borrow_mut().push_back(key); } } Err(error) => { - *state_cell = None; + if let Some(key) = child_key.get() { + drop_node(&mut nodes, key); + } - (catch_cx.f)((error.make_error)()); + (catch_cx.f)((error.make_error)()) } } } @@ -166,23 +234,50 @@ macro_rules! impl_tuples { impl<$($t: Compose),*> Compose for ($($t,)*) { fn compose(cx: Scope) -> impl Compose { - cx.is_container.set(true); + let rt = Runtime::current(); $( - let state = use_ref(&cx, || { - ScopeData::default() + let mut is_init = false; + + let child_key = use_ref(&cx, || { + is_init = true; + + let mut nodes = rt.nodes.borrow_mut(); + + let ptr: *const dyn AnyCompose = unsafe { mem::transmute(&cx.me().$idx as *const dyn AnyCompose) }; + let child_key = nodes.insert(Rc::new(Node { + compose: RefCell::new(crate::composer::ComposePtr::Ptr(ptr)), + scope: ScopeData::default(), + parent: Some(rt.current_key.get()), + children: RefCell::new(Vec::new()), + })); + + nodes + .get(rt.current_key.get()) + .unwrap() + .children + .borrow_mut() + .push(child_key); + + let child_state = &nodes[child_key].scope; + + *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone(); + child_state + .contexts + .borrow_mut() + .values + .extend(cx.child_contexts.borrow().values.clone()); + + child_key }); - *state.contexts.borrow_mut() = cx.contexts.borrow().clone(); - state - .contexts - .borrow_mut() - .values - .extend(cx.child_contexts.borrow().values.clone()); - - state.is_parent_changed.set(cx.is_parent_changed.get()); + if !is_init { + let last = rt.nodes.borrow().get(*child_key).unwrap().clone(); + let ptr: *const dyn AnyCompose = unsafe { mem::transmute(&cx.me().$idx as *const dyn AnyCompose) }; + *last.compose.borrow_mut() = crate::composer::ComposePtr::Ptr(ptr); + } - unsafe { cx.me().$idx.any_compose(state) } + rt.pending.borrow_mut().push_back(*child_key); )* } } @@ -207,6 +302,8 @@ pub(crate) trait AnyCompose { /// Safety: The caller must ensure `&self` is valid for the lifetime of `state`. unsafe fn any_compose(&self, state: &ScopeData); + + fn name(&self) -> Option>; } impl AnyCompose for C @@ -243,52 +340,65 @@ where // Safety: This cell is only accessed by this composable. let cell = unsafe { &mut *cell.get() }; - if typeid::of::() == typeid::of::<()>() { - return; - } + let child_key_cell = use_ref(&cx, || Cell::new(None)); - // Scope for this composable's content. - let child_state = use_ref(&cx, ScopeData::default); + let rt = Runtime::current(); - if cell.is_none() - || cx.is_changed.take() - || cx.is_parent_changed.get() - || cx.is_container.get() - { + if cell.is_none() { #[cfg(feature = "tracing")] - if !cx.is_container.get() { - if let Some(name) = C::name() { - tracing::trace!("Compose: {}", name); - } + if let Some(name) = C::name() { + tracing::trace!("Compose: {}", name); } - let mut child = C::compose(cx); + let child = C::compose(cx); - cx.is_parent_changed.set(false); + if child.data_id() == typeid::of::<()>() { + return; + } - *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone(); - child_state - .contexts - .borrow_mut() - .values - .extend(cx.child_contexts.borrow().values.clone()); + let child: Box = Box::new(child); + let mut child: Box = unsafe { mem::transmute(child) }; - child_state.is_parent_changed.set(true); + let mut nodes = rt.nodes.borrow_mut(); unsafe { - if let Some(ref mut content) = cell { - child.reborrow((**content).as_ptr_mut()); + if let Some(key) = child_key_cell.get() { + let last = nodes.get_mut(key).unwrap(); + child.reborrow(last.compose.borrow_mut().as_ptr_mut()); } else { - let boxed: Box = Box::new(child); - let boxed: Box = mem::transmute(boxed); - *cell = Some(boxed); + let child_key = nodes.insert(Rc::new(Node { + compose: RefCell::new(crate::composer::ComposePtr::Boxed(child)), + scope: ScopeData::default(), + parent: Some(rt.current_key.get()), + children: RefCell::new(Vec::new()), + })); + child_key_cell.set(Some(child_key)); + + nodes + .get(rt.current_key.get()) + .unwrap() + .children + .borrow_mut() + .push(child_key); + + let child_state = &nodes[child_key].scope; + + *child_state.contexts.borrow_mut() = cx.contexts.borrow().clone(); + child_state + .contexts + .borrow_mut() + .values + .extend(cx.child_contexts.borrow().values.clone()); } } - } else { - child_state.is_parent_changed.set(false); } - let child = cell.as_mut().unwrap(); - (*child).any_compose(child_state); + if let Some(key) = child_key_cell.get() { + rt.pending.borrow_mut().push_back(key); + } + } + + fn name(&self) -> Option> { + C::name() } } diff --git a/src/composer.rs b/src/composer.rs index 139cb8d..5263482 100644 --- a/src/composer.rs +++ b/src/composer.rs @@ -7,21 +7,74 @@ use core::{ any::TypeId, cell::{Cell, RefCell}, error::Error, + fmt, future::Future, + mem, pin::Pin, task::{Context, Poll, Waker}, }; use crossbeam_queue::SegQueue; use slotmap::{DefaultKey, SlotMap}; +use std::collections::VecDeque; #[cfg(feature = "executor")] use tokio::sync::RwLock; type RuntimeFuture = Pin>>; +pub(crate) enum ComposePtr { + Boxed(Box), + Ptr(*const dyn AnyCompose), +} + +impl AnyCompose for ComposePtr { + fn data_id(&self) -> TypeId { + match self { + ComposePtr::Boxed(compose) => compose.data_id(), + ComposePtr::Ptr(ptr) => unsafe { (**ptr).data_id() }, + } + } + + fn as_ptr_mut(&mut self) -> *mut () { + match self { + ComposePtr::Boxed(compose) => compose.as_ptr_mut(), + ComposePtr::Ptr(ptr) => *ptr as *mut (), + } + } + + unsafe fn reborrow(&mut self, ptr: *mut ()) { + match self { + ComposePtr::Boxed(compose) => compose.reborrow(ptr), + ComposePtr::Ptr(_) => {} + } + } + + unsafe fn any_compose(&self, state: &ScopeData) { + match self { + ComposePtr::Boxed(compose) => compose.any_compose(state), + ComposePtr::Ptr(ptr) => (**ptr).any_compose(state), + } + } + + fn name(&self) -> Option> { + match self { + ComposePtr::Boxed(compose) => compose.name(), + ComposePtr::Ptr(_) => None, + } + } +} + +// Safety: `scope` must be dropped before `compose`. +pub(crate) struct Node { + pub(crate) compose: RefCell, + pub(crate) scope: ScopeData<'static>, + pub(crate) parent: Option, + pub(crate) children: RefCell>, +} + /// Runtime for a [`Composer`]. #[derive(Clone)] -pub struct Runtime { +pub(crate) struct Runtime { /// Local task stored on this runtime. pub(crate) tasks: Rc>>, @@ -36,6 +89,14 @@ pub struct Runtime { pub(crate) lock: Arc>, pub(crate) waker: RefCell>, + + pub(crate) nodes: Rc>>>, + + pub(crate) current_key: Rc>, + + pub(crate) root: DefaultKey, + + pub(crate) pending: Rc>>, } impl Runtime { @@ -108,14 +169,12 @@ pub enum TryComposeError { impl PartialEq for TryComposeError { fn eq(&self, other: &Self) -> bool { - core::mem::discriminant(self) == core::mem::discriminant(other) + mem::discriminant(self) == mem::discriminant(other) } } /// Composer for composable content. pub struct Composer { - compose: Box, - scope_state: Box>, rt: Runtime, task_queue: Arc>, update_queue: Rc>>, @@ -131,10 +190,15 @@ impl Composer { let task_queue = Arc::new(SegQueue::new()); let update_queue = Rc::new(SegQueue::new()); - let scope_data = ScopeData::default(); + let mut nodes = SlotMap::new(); + let root_key = nodes.insert(Rc::new(Node { + compose: RefCell::new(ComposePtr::Boxed(Box::new(content))), + scope: ScopeData::default(), + parent: None, + children: RefCell::new(Vec::new()), + })); + Self { - compose: Box::new(content), - scope_state: Box::new(scope_data), rt: Runtime { tasks: Rc::new(RefCell::new(SlotMap::new())), task_queue: task_queue.clone(), @@ -142,6 +206,10 @@ impl Composer { waker: RefCell::new(None), #[cfg(feature = "executor")] lock, + nodes: Rc::new(RefCell::new(nodes)), + current_key: Rc::new(Cell::new(root_key)), + root: root_key, + pending: Rc::new(RefCell::new(VecDeque::new())), }, task_queue, update_queue, @@ -151,11 +219,66 @@ impl Composer { /// Try to immediately compose the content in this composer. pub fn try_compose(&mut self) -> Result<(), TryComposeError> { + let mut is_pending = true; + + for res in self.by_ref() { + res.map_err(TryComposeError::Error)?; + + is_pending = false; + } + + if is_pending { + Err(TryComposeError::Pending) + } else { + Ok(()) + } + } + + /// Poll a composition of the content in this composer. + pub fn poll_compose(&mut self, cx: &mut Context) -> Poll>> { + *self.rt.waker.borrow_mut() = Some(cx.waker().clone()); + + match self.try_compose() { + Ok(()) => Poll::Ready(Ok(())), + Err(TryComposeError::Pending) => Poll::Pending, + Err(TryComposeError::Error(error)) => Poll::Ready(Err(error)), + } + } + + /// Compose the content of this composer. + pub async fn compose(&mut self) -> Result<(), Box> { + futures::future::poll_fn(|cx| self.poll_compose(cx)).await + } +} + +impl Drop for Composer { + fn drop(&mut self) { + let node = self.rt.nodes.borrow()[self.rt.root].clone(); + drop_recursive(&self.rt, self.rt.root, node) + } +} + +fn drop_recursive(rt: &Runtime, key: DefaultKey, node: Rc) { + let children = node.children.borrow().clone(); + for child_key in children { + let child = rt.nodes.borrow()[child_key].clone(); + drop_recursive(rt, child_key, child) + } + + rt.nodes.borrow_mut().remove(key); +} + +impl Iterator for Composer { + type Item = Result<(), Box>; + + fn next(&mut self) -> Option { self.rt.enter(); let error_cell = Rc::new(Cell::new(None)); let error_cell_handle = error_cell.clone(); - self.scope_state.contexts.borrow_mut().values.insert( + + let root = self.rt.nodes.borrow().get(self.rt.root).unwrap().clone(); + root.scope.contexts.borrow_mut().values.insert( TypeId::of::(), Rc::new(CatchContext::new(move |error| { error_cell_handle.set(Some(error)); @@ -163,61 +286,81 @@ impl Composer { ); if !self.is_initial { - let mut is_ready = false; - - while let Some(key) = self.task_queue.pop() { - let waker = Waker::from(Arc::new(TaskWaker { - key, - waker: self.rt.waker.borrow().clone(), - queue: self.rt.task_queue.clone(), - })); - let mut cx = Context::from_waker(&waker); - - let mut tasks = self.rt.tasks.borrow_mut(); - let task = tasks.get_mut(key).unwrap(); - let _ = task.as_mut().poll(&mut cx); - - is_ready = true; - } + let key_cell = self.rt.pending.borrow_mut().pop_front(); + if let Some(key) = key_cell { + self.rt.current_key.set(key); + + let node = self.rt.nodes.borrow().get(key).unwrap().clone(); + + // Safety: `self.compose` is guaranteed to live as long as `self.scope_state`. + unsafe { node.compose.borrow().any_compose(&node.scope) }; + } else { + while let Some(key) = self.task_queue.pop() { + let waker = Waker::from(Arc::new(TaskWaker { + key, + waker: self.rt.waker.borrow().clone(), + queue: self.rt.task_queue.clone(), + })); + let mut cx = Context::from_waker(&waker); + + let mut tasks = self.rt.tasks.borrow_mut(); + let task = tasks.get_mut(key).unwrap(); + let _ = task.as_mut().poll(&mut cx); + } - while let Some(mut update) = self.update_queue.pop() { - update(); - is_ready = true; - } + while let Some(mut update) = self.update_queue.pop() { + update(); + } - if !is_ready { - return Err(TryComposeError::Pending); + return None; } } else { self.is_initial = false; - } - #[cfg(feature = "tracing")] - tracing::trace!("Start composition"); + self.rt.current_key.set(self.rt.root); - // Safety: `self.compose` is guaranteed to live as long as `self.scope_state`. - unsafe { self.compose.any_compose(&self.scope_state) }; + // Safety: `self.compose` is guaranteed to live as long as `self.scope_state`. + unsafe { root.compose.borrow().any_compose(&root.scope) }; + } - error_cell - .take() - .map(|error| Err(TryComposeError::Error(error))) - .unwrap_or_else(|| Ok(())) + Some(error_cell.take().map(Err).unwrap_or(Ok(()))) } +} - /// Poll a composition of the content in this composer. - pub fn poll_compose(&mut self, cx: &mut Context) -> Poll>> { - *self.rt.waker.borrow_mut() = Some(cx.waker().clone()); +impl fmt::Debug for Composer { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + f.debug_struct("Composer") + .field( + "nodes", + &Debugger { + nodes: &self.rt.nodes.borrow(), + key: self.rt.root, + }, + ) + .finish() + } +} - match self.try_compose() { - Ok(()) => Poll::Ready(Ok(())), - Err(TryComposeError::Pending) => Poll::Pending, - Err(TryComposeError::Error(error)) => Poll::Ready(Err(error)), +struct Debugger<'a> { + nodes: &'a SlotMap>, + key: DefaultKey, +} + +impl fmt::Debug for Debugger<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + let node = &self.nodes[self.key]; + let name = node.compose.borrow().name().unwrap_or_default(); + + let mut dbg_tuple = f.debug_tuple(&name); + + for child in &*node.children.borrow() { + dbg_tuple.field(&Debugger { + nodes: self.nodes, + key: *child, + }); } - } - /// Compose the content of this composer. - pub async fn compose(&mut self) -> Result<(), Box> { - futures::future::poll_fn(|cx| self.poll_compose(cx)).await + dbg_tuple.finish() } } @@ -240,9 +383,10 @@ mod tests { impl Compose for Counter { fn compose(cx: Scope) -> impl Compose { - cx.me().x.set(cx.me().x.get() + 1); + let updater = use_mut(&cx, || ()); + SignalMut::set(updater, ()); - cx.set_changed(); + cx.me().x.set(cx.me().x.get() + 1); } } diff --git a/src/lib.rs b/src/lib.rs index bb29102..664f7b6 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -126,6 +126,7 @@ use core::{ pin::Pin, ptr::NonNull, }; +use slotmap::DefaultKey; use thiserror::Error; #[cfg(not(feature = "std"))] @@ -320,22 +321,6 @@ impl fmt::Display for RefMap<'_, T> { unsafe impl Data for RefMap<'_, T> {} -impl Compose for RefMap<'_, C> { - fn compose(cx: Scope) -> impl Compose { - cx.is_container.set(true); - - let state = use_ref(&cx, || { - let mut state = ScopeData::default(); - state.contexts = cx.contexts.clone(); - state - }); - - state.is_parent_changed.set(cx.is_parent_changed.get()); - - unsafe { (**cx.me()).any_compose(state) } - } -} - /// Mapped immutable reference to a value of type `T`. /// /// This can be created with [`Signal::map`]. @@ -365,16 +350,12 @@ unsafe impl Data for MapUnchecked<'_, T> {} impl Compose for MapUnchecked<'_, C> { fn compose(cx: Scope) -> impl Compose { - cx.is_container.set(true); - let state = use_ref(&cx, || { let mut state = ScopeData::default(); state.contexts = cx.contexts.clone(); state }); - state.is_parent_changed.set(cx.is_parent_changed.get()); - // Safety: The `Map` is dereferenced every re-compose, so it's guranteed not to point to // an invalid memory location (e.g. an `Option` that previously returned `Some` is now `None`). unsafe { (*cx.me().map).any_compose(state) } @@ -458,8 +439,8 @@ pub struct SignalMut<'a, T> { /// Pointer to the boxed value. ptr: NonNull, - /// Pointer to the scope's `is_changed` flag. - scope_is_changed: *const Cell, + /// Key to this signal's scope. + scope_key: DefaultKey, /// Pointer to this value's generation. generation: *const Cell, @@ -476,13 +457,10 @@ impl<'a, T: 'static> SignalMut<'a, T> { /// Queue an update to this value, triggering an update to the component owning this value. pub fn update(me: Self, f: impl FnOnce(&mut T) + Send + 'static) { - let is_changed = UnsafeWrap(me.scope_is_changed); + let scope_key = me.scope_key; Self::with(me, move |value| { - let is_changed = is_changed; - // Set this scope as changed. - // Safety: the pointer to this scope is guranteed to outlive `me`. - unsafe { (*is_changed.0).set(true) }; + Runtime::current().pending.borrow_mut().push_back(scope_key); f(value) }) @@ -615,15 +593,6 @@ pub struct ScopeData<'a> { /// Current hook index. hook_idx: Cell, - /// `true` if this scope is changed. - is_changed: Cell, - - /// `true` if an ancestor to this scope is changed. - is_parent_changed: Cell, - - /// `true` if this scope contains a container composable. - is_container: Cell, - /// Context values stored in this scope. contexts: RefCell, @@ -640,44 +609,6 @@ pub struct ScopeData<'a> { _marker: PhantomData<&'a fn(ScopeData<'a>) -> ScopeData<'a>>, } -impl ScopeData<'_> { - /// Returns `true` if this is this scope's state has changed since the last re-composition. - pub fn is_changed(&self) -> bool { - self.is_changed.get() - } - - /// Set this scope as changed during the next re-composition. - pub fn set_changed(&self) { - let is_changed = UnsafeWrap(&self.is_changed as *const Cell); - - Runtime::current().update(move || { - let is_changed = is_changed; - let is_changed = unsafe { &*is_changed.0 }; - is_changed.set(true); - }); - } - - /// Returns `true` if an ancestor to this scope is changed. - pub fn is_parent_changed(&self) -> bool { - self.is_parent_changed.get() - } - - /// Returns `true` if this scope contains a container composable. - /// - /// A container composable will be re-composed during every re-composition of the tree. - /// This is useful for low-level composables that manage child state or external elements. - pub fn is_container(&self) -> bool { - self.is_container.get() - } - - /// Set this scope as a container composable. - /// - /// See [`ScopeData::is_container`] for more. - pub fn set_is_container(&self) { - self.is_container.set(true) - } -} - impl Drop for ScopeData<'_> { fn drop(&mut self) { for idx in &*self.drops.borrow() { @@ -771,7 +702,7 @@ pub fn use_mut(cx: ScopeState, make_value: impl FnOnce() -> T) -> Si SignalMut { ptr: unsafe { NonNull::new_unchecked(&mut state.value as *mut _) }, - scope_is_changed: &cx.is_changed, + scope_key: Runtime::current().current_key.get(), generation: &state.generation, _marker: PhantomData, }