From c16b92ee7ee3a02153d651db1a84c7dc781c26da Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 19 Sep 2021 22:10:00 -0400 Subject: [PATCH] wip: before scheduler simplify --- packages/core/.vscode/settings.json | 2 +- packages/core/src/context.rs | 87 ++++++++++++++--------------- packages/core/src/hooklist.rs | 17 ++++-- packages/core/src/hooks.rs | 12 +++- packages/core/src/scheduler.rs | 8 +++ packages/core/tests/sharedstate.rs | 4 +- 6 files changed, 77 insertions(+), 53 deletions(-) diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 1c68f97cc..79505b018 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": true + "rust-analyzer.inlayHints.enable": false } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index 3e86a3c88..7915601ca 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -98,15 +98,26 @@ impl<'src, P> Context<'src, P> { /// Create a subscription that schedules a future render for the reference component /// /// ## Notice: you should prefer using prepare_update and get_scope_id - /// pub fn schedule_update(&self) -> Rc { self.scope.memoized_updater.clone() } - pub fn prepare_update(&self) -> Rc { + /// Schedule an update for any component given its ScopeId. + /// + /// A component's ScopeId can be obtained from `use_hook` or the [`Context::scope_id`] method. + /// + /// This method should be used when you want to schedule an update for a component + pub fn schedule_update_any(&self) -> Rc { self.scope.shared.schedule_any_immediate.clone() } + /// Get the [`ScopeId`] of a mounted component. + /// + /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted. + pub fn scope_id(&self) -> ScopeId { + self.scope.our_arena_idx + } + /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. /// /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned. @@ -151,24 +162,6 @@ impl<'src, P> Context<'src, P> { (self.scope.shared.submit_task)(task) } - /// Add a state globally accessible to child components via tree walking - pub fn add_shared_state(self, val: T) { - self.scope - .shared_contexts - .borrow_mut() - .insert(TypeId::of::(), Rc::new(val)) - .map(|_| { - log::warn!("A shared state was replaced with itself. This is does not result in a panic, but is probably not what you are trying to do"); - }); - } - - pub fn try_consume_shared_state(self) -> Option> { - let getter = &self.scope.shared.get_shared_context; - let ty = TypeId::of::(); - let idx = self.scope.our_arena_idx; - getter(idx, ty).map(|f| f.downcast().expect("TypeID already validated")) - } - /// This hook enables the ability to expose state to children further down the VirtualDOM Tree. /// /// This is a hook, so it should not be called conditionally! @@ -185,45 +178,44 @@ impl<'src, P> Context<'src, P> { /// struct SharedState(&'static str); /// /// static App: FC<()> = |cx| { - /// cx.provide_state(|| SharedState("world")); + /// cx.use_provide_state(|| SharedState("world")); /// rsx!(cx, Child {}) /// } /// /// static Child: FC<()> = |cx| { - /// let state = cx.consume_state::(); + /// let state = cx.use_consume_state::(); /// rsx!(cx, div { "hello {state.0}" }) /// } /// ``` - pub fn provide_state(self, init: F) -> &'src Rc + pub fn use_provide_state(self, init: F) -> &'src Rc where T: 'static, F: FnOnce() -> T, { - // - let ty = TypeId::of::(); - let contains_key = self.scope.shared_contexts.borrow().contains_key(&ty); - let is_initialized = self.use_hook( |_| false, |s| { - let i = s.clone(); + let i = *s; *s = true; i }, |_| {}, ); - match (is_initialized, contains_key) { - // Do nothing, already initialized and already exists - (true, true) => {} + if !is_initialized { + self.scope + .shared_contexts + .borrow_mut() + .insert(TypeId::of::(), Rc::new(init())) + .map(|_| { + log::warn!( + "A shared state was replaced with itself. \ + This is does not result in a panic, but is probably not what you are trying to do" + ); + }); + } - // Needs to be initialized - (false, false) => self.add_shared_state(init()), - - _ => debug_assert!(false, "Cannot initialize two contexts of the same type"), - }; - - self.consume_state().unwrap() + self.use_consume_state().unwrap() } /// Uses a context, storing the cached value around @@ -231,10 +223,15 @@ impl<'src, P> Context<'src, P> { /// If a context is not found on the first search, then this call will be "dud", always returning "None" even if a /// context was added later. This allows using another hook as a fallback /// - pub fn consume_state(self) -> Option<&'src Rc> { + pub fn use_consume_state(self) -> Option<&'src Rc> { struct UseContextHook(Option>); self.use_hook( - move |_| UseContextHook(self.try_consume_shared_state::()), + move |_| { + let getter = &self.scope.shared.get_shared_context; + let ty = TypeId::of::(); + let idx = self.scope.our_arena_idx; + UseContextHook(getter(idx, ty).map(|f| f.downcast().unwrap())) + }, move |hook| hook.0.as_ref(), |_| {}, ) @@ -262,19 +259,21 @@ impl<'src, P> Context<'src, P> { self, initializer: Init, runner: Run, - _cleanup: Cleanup, + cleanup: Cleanup, ) -> Output where State: 'static, Output: 'src, Init: FnOnce(usize) -> State, Run: FnOnce(&'src mut State) -> Output, - Cleanup: FnOnce(State), + Cleanup: FnOnce(&mut State) + 'static, { // If the idx is the same as the hook length, then we need to add the current hook if self.scope.hooks.at_end() { - let new_state = initializer(self.scope.hooks.len()); - self.scope.hooks.push(new_state); + self.scope.hooks.push_hook( + initializer(self.scope.hooks.len()), + Box::new(|raw| cleanup(raw.downcast_mut::().unwrap())), + ); } const ERR_MSG: &str = r###" diff --git a/packages/core/src/hooklist.rs b/packages/core/src/hooklist.rs index af3487723..bea11a82a 100644 --- a/packages/core/src/hooklist.rs +++ b/packages/core/src/hooklist.rs @@ -12,7 +12,7 @@ use std::{ /// Todo: this could use its very own bump arena, but that might be a tad overkill #[derive(Default)] pub(crate) struct HookList { - vals: RefCell>>>, + vals: RefCell>, Box)>>, idx: Cell, } @@ -20,7 +20,7 @@ impl HookList { pub(crate) fn next(&self) -> Option<&mut T> { self.vals.borrow().get(self.idx.get()).and_then(|inn| { self.idx.set(self.idx.get() + 1); - let raw_box = unsafe { &mut *inn.get() }; + let raw_box = unsafe { &mut *inn.0.get() }; raw_box.downcast_mut::() }) } @@ -35,8 +35,10 @@ impl HookList { self.idx.set(0); } - pub(crate) fn push(&self, new: T) { - self.vals.borrow_mut().push(UnsafeCell::new(Box::new(new))) + pub(crate) fn push_hook(&self, new: T, cleanup: Box) { + self.vals + .borrow_mut() + .push((UnsafeCell::new(Box::new(new)), cleanup)) } pub(crate) fn len(&self) -> usize { @@ -50,4 +52,11 @@ impl HookList { pub(crate) fn at_end(&self) -> bool { self.cur_idx() >= self.len() } + + pub(crate) fn cleanup_hooks(&mut self) { + self.vals + .borrow_mut() + .drain(..) + .for_each(|(mut state, cleanup)| cleanup(state.get_mut())); + } } diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index 24f8ff8e7..85cf782ff 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -15,6 +15,7 @@ use std::{ any::{Any, TypeId}, cell::RefCell, future::Future, + ops::Deref, rc::Rc, }; @@ -47,7 +48,7 @@ where let slot = task_dump.clone(); - let updater = cx.prepare_update(); + let updater = cx.schedule_update_any(); let originator = cx.scope.our_arena_idx; let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move { @@ -188,7 +189,14 @@ impl<'src> SuspendedContext<'src> { } #[derive(Clone, Copy)] -pub struct NodeRef<'src, T: 'static>(&'src RefCell); +pub struct NodeRef<'src, T: 'static>(&'src RefCell>); + +impl<'a, T> Deref for NodeRef<'a, T> { + type Target = RefCell>; + fn deref(&self) -> &Self::Target { + self.0 + } +} pub fn use_node_ref(cx: Context

) -> NodeRef { cx.use_hook( diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index dfcac8519..c29b163a2 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -657,9 +657,17 @@ impl TaskHandle { } } +/// A component's unique identifier. +/// +/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is +/// unmounted, then the `ScopeId` will be reused for a new component. #[derive(serde::Serialize, serde::Deserialize, Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ScopeId(pub usize); +/// An Element's unique identifier. +/// +/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is +/// unmounted, then the `ElementId` will be reused for a new component. #[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] pub struct ElementId(pub usize); impl Display for ElementId { diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 059549db5..a5e2b1dad 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -16,12 +16,12 @@ fn shared_state_test() { struct MySharedState(&'static str); static App: FC<()> = |cx| { - cx.provide_state(|| MySharedState("world!")); + cx.use_provide_state(|| MySharedState("world!")); rsx!(cx, Child {}) }; static Child: FC<()> = |cx| { - let shared = cx.consume_state::()?; + let shared = cx.use_consume_state::()?; rsx!(cx, "Hello, {shared.0}") };