Skip to content

Commit

Permalink
feat!: store the composition as nodes in the runtime (#105)
Browse files Browse the repository at this point in the history
* feat!: impl Iterator for Composer to step through re-compositions

* refactor: pass lints

* fix: composer example

* fix: drop scopes in reverse order to keep current behavior and remove Compose impl for RefMap

* feat: update compose impl for tuples

* fix: reborrow composables inside a tuple

* fix: change pending queue to FIFO and pass lints

* refactor!: make Runtime private and clean up

* feat: update Memo composable

* refactor: remove unused flags from ScopeData

* feat: update Option and Result

* feat: store the composition as nodes in the runtime

* feat!: step through the composition with Composer::try_compose

* feat: update FromIter composable

* feat: update DynCompose and track parent keys

* fix: drop child key on removal in Option and skip allocating Nodes for ()

* refactor: clean up
  • Loading branch information
matthunz authored Dec 5, 2024
1 parent f6341ac commit 9ef73eb
Show file tree
Hide file tree
Showing 7 changed files with 552 additions and 295 deletions.
3 changes: 3 additions & 0 deletions examples/core/composer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
91 changes: 57 additions & 34 deletions src/compose/dyn_compose.rs
Original file line number Diff line number Diff line change
@@ -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> {
Expand All @@ -15,22 +21,64 @@ pub struct DynCompose<'a> {
compose: UnsafeCell<Option<Box<dyn AnyCompose + 'a>>>,
}

#[derive(Clone, Copy)]
struct DynComposeState {
compose: Box<dyn AnyCompose>,
key: DefaultKey,
data_id: TypeId,
}

impl Compose for DynCompose<'_> {
fn compose(cx: Scope<Self>) -> impl Compose {
cx.is_container.set(true);
let state: &Cell<Option<DynComposeState>> = 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<dyn AnyCompose> = 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<Option<DynComposeState>> = 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<dyn AnyCompose> = 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
Expand All @@ -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<dyn AnyCompose> = 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);
}
}
91 changes: 47 additions & 44 deletions src/compose/from_iter.rs
Original file line number Diff line number Diff line change
@@ -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."]
Expand All @@ -24,46 +27,39 @@ where
C: Compose,
{
fn compose(cx: Scope<Self>) -> impl Compose {
cx.is_container.set(true);

let states: &RefCell<Vec<AnyItemState>> = use_ref(&cx, || RefCell::new(Vec::new()));
let mut states = states.borrow_mut();

if cx.is_parent_changed() {
let mut items: Vec<Option<_>> = 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<ItemState<Item>> =
unsafe { mem::transmute(any_state.boxed.take().unwrap()) };
drop(state);
},
});
}
} else {
states.truncate(items.len());
let mut items: Vec<Option<_>> = 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<ItemState<Item>> =
unsafe { mem::transmute(any_state.boxed.take().unwrap()) };
drop(state);
},
});
}
} else {
states.truncate(items.len());
}

for state in &mut *states {
let state: &mut ItemState<Item> =
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 {
Expand All @@ -73,24 +69,32 @@ where
let any_compose: Box<dyn AnyCompose> = Box::new(compose);
let any_compose: Box<dyn AnyCompose> = 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());
}
}
}
Expand All @@ -115,8 +119,7 @@ where

struct ItemState<T> {
item: T,
compose: Option<Box<dyn AnyCompose>>,
scope: ScopeData<'static>,
key: Option<DefaultKey>,
}

struct AnyItemState {
Expand Down
53 changes: 48 additions & 5 deletions src/compose/memo.rs
Original file line number Diff line number Diff line change
@@ -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.
///
Expand Down Expand Up @@ -35,19 +37,60 @@ where
C: Compose,
{
fn compose(cx: Scope<Self>) -> 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<Cow<'static, str>> {
Expand Down
Loading

0 comments on commit 9ef73eb

Please sign in to comment.