Skip to content

Commit

Permalink
feat: store the composition as nodes in the runtime
Browse files Browse the repository at this point in the history
  • Loading branch information
matthunz committed Dec 5, 2024
1 parent f6341ac commit 2de7b1b
Show file tree
Hide file tree
Showing 3 changed files with 119 additions and 30 deletions.
2 changes: 2 additions & 0 deletions examples/core/composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,6 @@ fn main() {
composer.try_compose().unwrap();

assert_eq!(composer.try_compose(), Err(TryComposeError::Pending));

dbg!(composer);
}
74 changes: 51 additions & 23 deletions src/compose/mod.rs
Original file line number Diff line number Diff line change
@@ -1,11 +1,16 @@
use crate::{data::Data, use_context, use_ref, Scope, ScopeData, ScopeState};
use crate::{
composer::{Node, Runtime},
data::Data,
use_context, use_ref, Scope, ScopeData, ScopeState,
};
use alloc::borrow::Cow;
use core::{
any::TypeId,
cell::{RefCell, UnsafeCell},
error::Error as StdError,
mem,
};
use std::{cell::Cell, rc::Rc};

mod catch;
pub use self::catch::{catch, Catch};
Expand Down Expand Up @@ -207,6 +212,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<Cow<'static, str>>;
}

impl<C> AnyCompose for C
Expand All @@ -226,6 +233,10 @@ where
}

unsafe fn any_compose(&self, state: &ScopeData) {
if typeid::of::<C>() == typeid::of::<()>() {
return;
}

// Reset the hook index.
state.hook_idx.set(0);

Expand All @@ -243,12 +254,7 @@ where
// Safety: This cell is only accessed by this composable.
let cell = unsafe { &mut *cell.get() };

if typeid::of::<C>() == typeid::of::<()>() {
return;
}

// Scope for this composable's content.
let child_state = use_ref(&cx, ScopeData::default);
let child_key_cell = use_ref(&cx, || Cell::new(None));

if cell.is_none()
|| cx.is_changed.take()
Expand All @@ -262,33 +268,55 @@ where
}
}

let mut child = C::compose(cx);
let child = C::compose(cx);
let child: Box<dyn AnyCompose> = Box::new(child);
let mut child: Box<dyn AnyCompose> = unsafe { mem::transmute(child) };

cx.is_parent_changed.set(false);

*child_state.contexts.borrow_mut() = cx.contexts.borrow().clone();
child_state
.contexts
.borrow_mut()
.values
.extend(cx.child_contexts.borrow().values.clone());

child_state.is_parent_changed.set(true);
let rt = Runtime::current();
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<dyn AnyCompose> = Box::new(child);
let boxed: Box<dyn AnyCompose> = mem::transmute(boxed);
*cell = Some(boxed);
let child_key = nodes.insert(Rc::new(Node {
compose: RefCell::new(child),
scope: ScopeData::default(),
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_state.is_parent_changed.set(true);
}
}
} else {
let rt = Runtime::current();
let nodes = rt.nodes.borrow();

let child_state = &nodes[child_key_cell.get().unwrap()].scope;
child_state.is_parent_changed.set(false);
}
}

let child = cell.as_mut().unwrap();
(*child).any_compose(child_state);
fn name(&self) -> Option<Cow<'static, str>> {
C::name()
}
}
73 changes: 66 additions & 7 deletions src/composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ use core::{
any::TypeId,
cell::{Cell, RefCell},
error::Error,
fmt,
future::Future,
pin::Pin,
task::{Context, Poll, Waker},
Expand All @@ -19,6 +20,12 @@ use tokio::sync::RwLock;

type RuntimeFuture = Pin<Box<dyn Future<Output = ()>>>;

pub(crate) struct Node {
pub(crate) compose: RefCell<Box<dyn AnyCompose>>,
pub(crate) scope: ScopeData<'static>,
pub(crate) children: RefCell<Vec<DefaultKey>>,
}

/// Runtime for a [`Composer`].
#[derive(Clone)]
pub struct Runtime {
Expand All @@ -36,6 +43,12 @@ pub struct Runtime {
pub(crate) lock: Arc<RwLock<()>>,

pub(crate) waker: RefCell<Option<Waker>>,

pub(crate) nodes: Rc<RefCell<SlotMap<DefaultKey, Rc<Node>>>>,

pub(crate) current_key: Rc<Cell<DefaultKey>>,

pub(crate) root: DefaultKey,
}

impl Runtime {
Expand Down Expand Up @@ -114,8 +127,6 @@ impl PartialEq for TryComposeError {

/// Composer for composable content.
pub struct Composer {
compose: Box<dyn AnyCompose>,
scope_state: Box<ScopeData<'static>>,
rt: Runtime,
task_queue: Arc<SegQueue<DefaultKey>>,
update_queue: Rc<SegQueue<Box<dyn FnMut()>>>,
Expand All @@ -131,17 +142,24 @@ 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(Box::new(content)),
scope: ScopeData::default(),
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(),
update_queue: update_queue.clone(),
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,
},
task_queue,
update_queue,
Expand All @@ -155,7 +173,9 @@ impl Composer {

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::<CatchContext>(),
Rc::new(CatchContext::new(move |error| {
error_cell_handle.set(Some(error));
Expand Down Expand Up @@ -195,8 +215,10 @@ impl Composer {
#[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) };
unsafe { root.compose.borrow().any_compose(&root.scope) };

error_cell
.take()
Expand All @@ -221,6 +243,43 @@ impl Composer {
}
}

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()
}
}

struct Debugger<'a> {
nodes: &'a SlotMap<DefaultKey, Rc<Node>>,
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,
});
}

dbg_tuple.finish()
}
}

#[cfg(all(test, feature = "rt"))]
mod tests {
use crate::{
Expand Down

0 comments on commit 2de7b1b

Please sign in to comment.