Procházet zdrojové kódy

wip: before scheduler simplify

Jonathan Kelley před 3 roky
rodič
revize
c16b92e

+ 1 - 1
packages/core/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-  "rust-analyzer.inlayHints.enable": true
+  "rust-analyzer.inlayHints.enable": false
 }

+ 43 - 44
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<dyn Fn() + 'static> {
         self.scope.memoized_updater.clone()
     }
 
-    pub fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
+    /// 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<dyn Fn(ScopeId)> {
         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<T: 'static>(self, val: T) {
-        self.scope
-            .shared_contexts
-            .borrow_mut()
-            .insert(TypeId::of::<T>(), 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<T: 'static>(self) -> Option<Rc<T>> {
-        let getter = &self.scope.shared.get_shared_context;
-        let ty = TypeId::of::<T>();
-        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::<SharedState>();
+    ///     let state = cx.use_consume_state::<SharedState>();
     ///     rsx!(cx, div { "hello {state.0}" })
     /// }
     /// ```
-    pub fn provide_state<T, F>(self, init: F) -> &'src Rc<T>
+    pub fn use_provide_state<T, F>(self, init: F) -> &'src Rc<T>
     where
         T: 'static,
         F: FnOnce() -> T,
     {
-        //
-        let ty = TypeId::of::<T>();
-        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) => {}
-
-            // Needs to be initialized
-            (false, false) => self.add_shared_state(init()),
-
-            _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
-        };
+        if !is_initialized {
+            self.scope
+            .shared_contexts
+            .borrow_mut()
+            .insert(TypeId::of::<T>(), 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"
+                );
+            });
+        }
 
-        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<T: 'static>(self) -> Option<&'src Rc<T>> {
+    pub fn use_consume_state<T: 'static>(self) -> Option<&'src Rc<T>> {
         struct UseContextHook<C>(Option<Rc<C>>);
         self.use_hook(
-            move |_| UseContextHook(self.try_consume_shared_state::<T>()),
+            move |_| {
+                let getter = &self.scope.shared.get_shared_context;
+                let ty = TypeId::of::<T>();
+                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::<State>().unwrap())),
+            );
         }
 
         const ERR_MSG: &str = r###"

+ 13 - 4
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<Vec<UnsafeCell<Box<dyn Any>>>>,
+    vals: RefCell<Vec<(UnsafeCell<Box<dyn Any>>, Box<dyn FnOnce(&mut dyn Any)>)>>,
     idx: Cell<usize>,
 }
 
@@ -20,7 +20,7 @@ impl HookList {
     pub(crate) fn next<T: 'static>(&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::<T>()
         })
     }
@@ -35,8 +35,10 @@ impl HookList {
         self.idx.set(0);
     }
 
-    pub(crate) fn push<T: 'static>(&self, new: T) {
-        self.vals.borrow_mut().push(UnsafeCell::new(Box::new(new)))
+    pub(crate) fn push_hook<T: 'static>(&self, new: T, cleanup: Box<dyn FnOnce(&mut dyn Any)>) {
+        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()));
+    }
 }

+ 10 - 2
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<T>);
+pub struct NodeRef<'src, T: 'static>(&'src RefCell<Option<T>>);
+
+impl<'a, T> Deref for NodeRef<'a, T> {
+    type Target = RefCell<Option<T>>;
+    fn deref(&self) -> &Self::Target {
+        self.0
+    }
+}
 
 pub fn use_node_ref<T, P>(cx: Context<P>) -> NodeRef<T> {
     cx.use_hook(

+ 8 - 0
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 {

+ 2 - 2
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::<MySharedState>()?;
+        let shared = cx.use_consume_state::<MySharedState>()?;
         rsx!(cx, "Hello, {shared.0}")
     };