From e3d9db0847ecc4ff5156929adb6d22e324f93df3 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Mon, 7 Jun 2021 14:14:49 -0400 Subject: [PATCH] wip: successfully building --- packages/core/.vscode/settings.json | 2 +- packages/core/src/arena.rs | 18 + packages/core/src/component.rs | 43 ++- packages/core/src/diff.rs | 272 ++++++++++------ packages/core/src/lib.rs | 2 +- packages/core/src/nodebuilder.rs | 14 +- packages/core/src/nodes.rs | 56 +++- packages/core/src/scope.rs | 488 ---------------------------- packages/core/src/support.rs | 1 - packages/core/src/virtual_dom.rs | 284 ++++------------ packages/recoil/src/lib.rs | 2 +- 11 files changed, 355 insertions(+), 827 deletions(-) delete mode 100644 packages/core/src/scope.rs diff --git a/packages/core/.vscode/settings.json b/packages/core/.vscode/settings.json index 80f51cff8..2f418701b 100644 --- a/packages/core/.vscode/settings.json +++ b/packages/core/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } \ No newline at end of file diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index 6f197fa60..210a04c4e 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -61,6 +61,24 @@ impl ScopeArena { // todo!() } + pub fn with_scope<'b, O: 'static>( + &'b self, + id: ScopeIdx, + f: impl FnOnce(&'b mut Scope) -> O, + ) -> Result { + todo!() + } + + // return a bumpframe with a lifetime attached to the arena borrow + // this is useful for merging lifetimes + pub fn with_scope_vnode<'b>( + &self, + id: ScopeIdx, + f: impl FnOnce(&mut Scope) -> &VNode<'b>, + ) -> Result<&VNode<'b>> { + todo!() + } + pub fn try_remove(&mut self, id: ScopeIdx) -> Result { let inner = unsafe { &mut *self.0.borrow().arena.get() }; inner diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index dc972c173..ed11d308a 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -9,7 +9,7 @@ use crate::innerlude::FC; pub type ScopeIdx = generational_arena::Index; -pub unsafe trait Properties: PartialEq { +pub unsafe trait Properties: PartialEq + Sized { type Builder; const CAN_BE_MEMOIZED: bool; fn builder() -> Self::Builder; @@ -36,7 +36,9 @@ pub fn fc_to_builder(_f: FC) -> T::Builder { } mod testing { - use std::any::Any; + use std::{any::Any, ops::Deref}; + + use crate::innerlude::VNode; // trait PossibleProps { // type POut: PartialEq; @@ -137,6 +139,43 @@ mod testing { // blah(10, "asd"); } } + + struct Inner<'a> { + a: String, + b: i32, + c: &'a str, + } + + struct Custom<'a, P: 'a> { + inner: &'a P, + // inner: *const (), + _p: std::marker::PhantomData<&'a P>, + } + + impl<'a, P> Custom<'a, P> { + fn props(&self) -> &P { + todo!() + } + } + + // impl

Deref for Custom

{ + // type Target = Inner; + + // fn deref(&self) -> &Self::Target { + // unsafe { &*self.inner } + // } + // } + + fn test2<'a>(a: Custom<'a, Inner<'a>>) -> VNode { + let r = a.inner; + + todo!() + // let g = a.props(); + // todo!() + // let g = &a.a; + } + + fn is_comp() {} } mod style {} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 80d0fd2ee..b28c4724b 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -59,52 +59,29 @@ use std::{ /// subscriptions and props changes. pub struct DiffMachine<'a> { pub create_diffs: bool, + pub cur_idx: ScopeIdx, pub change_list: EditMachine<'a>, pub diffed: FxHashSet, - pub lifecycle_events: VecDeque>, - pub vdom: ScopeArena, -} -pub enum LifeCycleEvent<'a> { - Mount { - caller: Weak VNode + 'a>, - stable_scope_addr: Weak, - root_id: u32, - }, - PropsChanged { - caller: Weak VNode + 'a>, - stable_scope_addr: Weak, - root_id: u32, - }, - SameProps { - caller: Weak VNode + 'a>, - stable_scope_addr: Weak, - root_id: u32, - }, - Replace { - caller: Weak VNode + 'a>, - old_scope: Weak, - new_scope: Weak, - root_id: u32, - }, - Remove { - stable_scope_addr: Weak, - root_id: u32, - }, + pub components: ScopeArena, + pub event_queue: EventQueue, + pub seen_nodes: FxHashSet, } static COUNTER: AtomicU32 = AtomicU32::new(1); -fn get_id() -> u32 { +fn next_id() -> u32 { COUNTER.fetch_add(1, std::sync::atomic::Ordering::Relaxed) } impl<'a> DiffMachine<'a> { - pub fn new(vdom: ScopeArena) -> Self { + pub fn new(components: ScopeArena, cur_idx: ScopeIdx, event_queue: EventQueue) -> Self { Self { - vdom, + components, + cur_idx, + event_queue, create_diffs: true, - lifecycle_events: VecDeque::new(), change_list: EditMachine::new(), diffed: FxHashSet::default(), + seen_nodes: FxHashSet::default(), } } @@ -112,14 +89,14 @@ impl<'a> DiffMachine<'a> { self.change_list.emitter } - pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) { + pub fn diff_node(&mut self, old_node: &VNode<'a>, new_node: &VNode<'a>) { // pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) { /* For each valid case, we "commit traversal", meaning we save this current position in the tree. Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later. When re-entering, we reuse the EditList in DiffState */ - match (old, new) { + match (old_node, new_node) { (VNode::Text(old_text), VNode::Text(new_text)) => { if old_text != new_text { self.change_list.commit_traversal(); @@ -129,13 +106,13 @@ impl<'a> DiffMachine<'a> { (VNode::Text(_), VNode::Element(_)) => { self.change_list.commit_traversal(); - self.create(new); + self.create(new_node); self.change_list.replace_with(); } (VNode::Element(_), VNode::Text(_)) => { self.change_list.commit_traversal(); - self.create(new); + self.create(new_node); self.change_list.replace_with(); } @@ -153,57 +130,84 @@ impl<'a> DiffMachine<'a> { } (VNode::Component(cold), VNode::Component(cnew)) => { - // todo!("should not happen") - // self.change_list.commit_traversal(); + // Make sure we're dealing with the same component (by function pointer) if cold.user_fc == cnew.user_fc { - // todo: create a stable addr - // let caller = Rc::downgrade(&cnew.caller); - // let id = cold.stable_addr.borrow().unwrap(); - // *cnew.stable_addr.borrow_mut() = Some(id); - // *cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow(); + // Make sure the new component vnode is referencing the right scope id + let scope_id = cold.ass_scope.borrow().clone(); + *cnew.ass_scope.borrow_mut() = scope_id; - // let scope = Rc::downgrade(&cold.ass_scope); - todo!() - // self.lifecycle_events - // .push_back(LifeCycleEvent::PropsChanged { - // caller, - // root_id: id, - // stable_scope_addr: scope, - // }); + // make sure the component's caller function is up to date + self.components + .with_scope(scope_id.unwrap(), |scope| { + scope.caller = Rc::downgrade(&cnew.caller) + }) + .unwrap(); + + // React doesn't automatically memoize, but we do. + // The cost is low enough to make it worth checking + let should_render = match cold.comparator { + Some(comparator) => comparator(cnew), + None => true, + }; + + if should_render { + self.change_list.commit_traversal(); + self.components + .with_scope(scope_id.unwrap(), |f| { + f.run_scope().unwrap(); + }) + .unwrap(); + // diff_machine.change_list.load_known_root(root_id); + // run the scope + // + } else { + // Component has memoized itself and doesn't need to be re-rendered. + // We still need to make sure the child's props are up-to-date. + // Don't commit traversal + } } else { - // let caller = Rc::downgrade(&cnew.caller); - // let id = cold.stable_addr.borrow().unwrap(); - // let old_scope = Rc::downgrade(&cold.ass_scope); - // let new_scope = Rc::downgrade(&cnew.ass_scope); + // A new component has shown up! We need to destroy the old node - todo!() - // self.lifecycle_events.push_back(LifeCycleEvent::Replace { - // caller, - // root_id: id, - // old_scope, - // new_scope, - // }); + // Wipe the old one and plant the new one + self.change_list.commit_traversal(); + self.create(new_node); + self.change_list.replace_with(); + + // Now we need to remove the old scope and all of its descendents + let old_scope = cold.ass_scope.borrow().as_ref().unwrap().clone(); + self.destroy_scopes(old_scope); } } // todo: knock out any listeners - (_, VNode::Component(_new)) => { + (_, VNode::Component(_)) => { self.change_list.commit_traversal(); + self.create(new_node); + self.change_list.replace_with(); } + // A component is being torn down in favor of a non-component node (VNode::Component(_old), _) => { - todo!("Usage of component VNode not currently supported"); + self.change_list.commit_traversal(); + self.create(new_node); + self.change_list.replace_with(); + + // Destroy the original scope and any of its children + self.destroy_scopes(_old.ass_scope.borrow().unwrap()); } + // Anything suspended is not enabled ATM (VNode::Suspended, _) | (_, VNode::Suspended) => { todo!("Suspended components not currently available") } - (VNode::Fragment(_), VNode::Fragment(_)) => { + // (VNode::Fragment(_), VNode::Fragment(_)) => { + // todo!("Fragments not currently supported in diffing") + // } + // Fragments are special + (VNode::Fragment(_), _) | (_, VNode::Fragment(_)) => { todo!("Fragments not currently supported in diffing") } - (_, VNode::Fragment(_)) => todo!("Fragments not currently supported in diffing"), - (VNode::Fragment(_), _) => todo!("Fragments not currently supported in diffing"), } } @@ -266,28 +270,74 @@ impl<'a> DiffMachine<'a> { } } - /* - todo: integrate re-entrace - */ VNode::Component(component) => { - todo!() - // self.change_list - // .create_text_node("placeholder for vcomponent"); + self.change_list + .create_text_node("placeholder for vcomponent"); - // let id = get_id(); - // *component.stable_addr.as_ref().borrow_mut() = Some(id); - // self.change_list.save_known_root(id); - // let scope = Rc::downgrade(&component.ass_scope); - // todo!() - // self.lifecycle_events.push_back(LifeCycleEvent::Mount { - // caller: Rc::downgrade(&component.caller), - // root_id: id, - // stable_scope_addr: scope, - // }); + let root_id = next_id(); + self.change_list.save_known_root(root_id); + + log::debug!("Mounting a new component"); + let caller: Weak = Rc::downgrade(&component.caller); + + // We're modifying the component arena while holding onto references into the assoiated bump arenas of its children + // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas. + // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime. + + // Insert a new scope into our component list + let idx = self + .components + .with(|components| { + components.insert_with(|new_idx| { + let cur_idx = self.cur_idx; + let cur_scope = self.components.try_get(cur_idx).unwrap(); + let height = cur_scope.height + 1; + Scope::new( + caller, + new_idx, + Some(cur_idx), + height, + self.event_queue.new_channel(height, new_idx), + self.components.clone(), + &[], + ) + }) + }) + .unwrap(); + + { + let cur_component = self.components.try_get_mut(idx).unwrap(); + let mut ch = cur_component.descendents.borrow_mut(); + ch.insert(idx); + std::mem::drop(ch); + } + + // yaaaaay lifetimes out of thin air + // really tho, we're merging the frame lifetimes together + let inner: &'a mut _ = unsafe { &mut *self.components.0.borrow().arena.get() }; + let new_component = inner.get_mut(idx).unwrap(); + + // Actually initialize the caller's slot with the right address + *component.ass_scope.borrow_mut() = Some(idx); + + // Run the scope for one iteration to initialize it + new_component.run_scope().unwrap(); + + // // Navigate the diff machine to the right point in the output dom + // self.change_list.load_known_root(id); + // let root_id = component.stable_addr + + // And then run the diff algorithm + self.diff_node(new_component.old_frame(), new_component.next_frame()); + + // Finally, insert this node as a seen node. + self.seen_nodes.insert(idx); } VNode::Suspended => { todo!("Creation of VNode::Suspended not yet supported") } + + // we go the the "known root" but only operate on a sibling basis VNode::Fragment(frag) => { // todo!("Cannot current create fragments") @@ -295,6 +345,38 @@ impl<'a> DiffMachine<'a> { } } + /// Destroy a scope and all of its descendents. + /// + /// Calling this will run the destuctors on all hooks in the tree. + /// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered. + fn destroy_scopes(&mut self, old_scope: ScopeIdx) { + let mut nodes_to_delete = vec![old_scope]; + let mut scopes_to_explore = vec![old_scope]; + + // explore the scope tree breadth first + while let Some(scope_id) = scopes_to_explore.pop() { + // If we're planning on deleting this node, then we don't need to both rendering it + self.seen_nodes.insert(scope_id); + let scope = self.components.try_get(scope_id).unwrap(); + for child in scope.descendents.borrow().iter() { + // Add this node to be explored + scopes_to_explore.push(child.clone()); + + // Also add it for deletion + nodes_to_delete.push(child.clone()); + } + } + + // Delete all scopes that we found as part of this subtree + for node in nodes_to_delete { + log::debug!("Removing scope {:#?}", node); + let _scope = self.components.try_remove(node).unwrap(); + // do anything we need to do to delete the scope + // I think we need to run the destructors on the hooks + // TODO + } + } + // Diff event listeners between `old` and `new`. // // The listeners' node must be on top of the change list stack: @@ -303,7 +385,6 @@ impl<'a> DiffMachine<'a> { // // The change list stack is left unchanged. fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) { - // fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) { if !old.is_empty() || !new.is_empty() { self.change_list.commit_traversal(); } @@ -325,26 +406,15 @@ impl<'a> DiffMachine<'a> { .update_event_listener(event_type, new_l.scope, new_l.id) } - // if let Some(scope) = self.current_idx { - // let cb = CbIdx::from_gi_index(scope, l_idx); - // self.change_list - // .update_event_listener(event_type, new_l.scope, new_l.id); - // } - continue 'outer1; } } - // if let Some(scope) = self.current_idx { - // let cb = CbIdx::from_gi_index(scope, l_idx); self.change_list .new_event_listener(event_type, new_l.scope, new_l.id); - // } } 'outer2: for old_l in old { - // registry.remove(old_l); - for new_l in new { if new_l.event == old_l.event { continue 'outer2; @@ -369,6 +439,9 @@ impl<'a> DiffMachine<'a> { ) { // Do O(n^2) passes to add/update and remove attributes, since // there are almost always very few attributes. + // + // The "fast" path is when the list of attributes name is identical and in the same order + // With the Rsx and Html macros, this will almost always be the case 'outer: for new_attr in new { if new_attr.is_volatile() { self.change_list.commit_traversal(); @@ -386,6 +459,8 @@ impl<'a> DiffMachine<'a> { ); } continue 'outer; + } else { + // names are different, a varying order of attributes has arrived } } @@ -433,12 +508,10 @@ impl<'a> DiffMachine<'a> { (_, &VNode::Text(text)) => { self.change_list.commit_traversal(); self.change_list.set_text(text); - // for o in old { - // registry.remove_subtree(o); - // } return; } + // todo: any more optimizations (_, _) => {} } } @@ -465,6 +538,7 @@ impl<'a> DiffMachine<'a> { if new_is_keyed && old_is_keyed { let t = self.change_list.next_temporary(); + self.diff_keyed_children(old, new); self.change_list.set_next_temporary(t); } else { self.diff_non_keyed_children(old, new); diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 53ea356b4..f16b633f7 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -86,7 +86,7 @@ pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and su pub mod builder { pub use super::nodebuilder::*; } -pub mod scope; + pub mod support; // types used internally that are important diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index aa1a0ac57..fd95a05bd 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -650,7 +650,7 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { Attribute { name, value } } -pub fn virtual_child<'a, T: Properties>( +pub fn virtual_child<'a, T: Properties + 'a>( ctx: &NodeCtx<'a>, f: FC, props: T, @@ -658,9 +658,11 @@ pub fn virtual_child<'a, T: Properties>( ) -> VNode<'a> { // currently concerned about if props have a custom drop implementation // might override it with the props macro - todo!() - // VNode::Component( - // ctx.bump() - // .alloc(crate::nodes::VComponent::new(f, props, key)), - // ) + // todo!() + VNode::Component( + ctx.bump() + .alloc(crate::nodes::VComponent::new(ctx.bump(), f, props, key)), + // ctx.bump() + // .alloc(crate::nodes::VComponent::new(f, props, key)), + ) } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index f6d02becc..44c32b4a1 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -226,7 +226,7 @@ pub struct VComponent<'src> { pub ass_scope: RefCell, // pub comparator: Rc bool + 'src>, - pub caller: Rc VNode + 'src>, + pub caller: Rc VNode>, pub children: &'src [VNode<'src>], @@ -287,18 +287,24 @@ impl<'a> VComponent<'a> { // let prref: &'a P = props.as_ref(); - let caller: Rc VNode> = Rc::new(move |scope| { - // - // let props2 = bad_props; - // props.as_ref() - // let ctx = Context { - // props: prref, - // scope, - // }; - todo!() - // component(ctx) - }); - // let caller = Rc::new(create_closure(component, raw_props)); + // let r = create_closure(component, raw_props); + // let caller: Rc Fn(&'g Scope) -> VNode<'g>> = Rc::new(move |scope| { + // // r(scope); + // // + // // let props2 = bad_props; + // // props.as_ref(); + // // let ctx = Context { + // // props: prref, + // // scope, + // // }; + // // let ctx: Context<'g, P> = todo!(); + // // todo!() + // // let r = component(ctx); + // todo!() + // }); + let caller = create_closure(component, raw_props); + + // let caller: Rc VNode> = Rc::new(create_closure(component, raw_props)); let key = match key { Some(key) => NodeKey::new(key), @@ -321,6 +327,30 @@ impl<'a> VComponent<'a> { } } +type Captured<'a> = Rc Fn(&'r Scope) -> VNode<'r> + 'a>; + +fn create_closure<'a, P: Properties + 'a>( + component: FC

, + raw_props: *const (), +) -> Rc Fn(&'r Scope) -> VNode<'r>> { + // ) -> impl for<'r> Fn(&'r Scope) -> VNode<'r> { + let g: Captured = Rc::new(move |scp: &Scope| -> VNode { + // cast back into the right lifetime + let safe_props: &'_ P = unsafe { &*(raw_props as *const P) }; + // let ctx: Context = todo!(); + let ctx: Context

= Context { + props: safe_props, + scope: scp, + }; + + let g = component(ctx); + let g2 = unsafe { std::mem::transmute(g) }; + g2 + }); + let r: Captured<'static> = unsafe { std::mem::transmute(g) }; + r +} + pub struct VFragment<'src> { pub key: NodeKey<'src>, pub children: &'src [VNode<'src>], diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs deleted file mode 100644 index 8018d7308..000000000 --- a/packages/core/src/scope.rs +++ /dev/null @@ -1,488 +0,0 @@ -use crate::{arena::ScopeArena, innerlude::*}; -use bumpalo::Bump; -use generational_arena::Arena; -use std::{ - any::{Any, TypeId}, - cell::RefCell, - collections::{HashMap, HashSet, VecDeque}, - fmt::Debug, - future::Future, - ops::Deref, - pin::Pin, - rc::{Rc, Weak}, -}; - -/// Every component in Dioxus is represented by a `Scope`. -/// -/// Scopes contain the state for hooks, the component's props, and other lifecycle information. -/// -/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. -/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. -pub struct Scope { - // The parent's scope ID - pub parent: Option, - - // IDs of children that this scope has created - // This enables us to drop the children and their children when this scope is destroyed - pub(crate) descendents: RefCell>, - - pub(crate) child_nodes: &'static [VNode<'static>], - - // A reference to the list of components. - // This lets us traverse the component list whenever we need to access our parent or children. - pub(crate) arena_link: ScopeArena, - - pub shared_contexts: RefCell>>, - - // Our own ID accessible from the component map - pub arena_idx: ScopeIdx, - - pub height: u32, - - pub event_channel: Rc, - - // pub event_queue: EventQueue, - pub caller: Weak>, - - pub hookidx: RefCell, - - // ========================== - // slightly unsafe stuff - // ========================== - // an internal, highly efficient storage of vnodes - pub frames: ActiveFrame, - - // These hooks are actually references into the hook arena - // These two could be combined with "OwningRef" to remove unsafe usage - // or we could dedicate a tiny bump arena just for them - // could also use ourborous - hooks: RefCell>, - - // Unsafety: - // - is self-refenrential and therefore needs to point into the bump - // Stores references into the listeners attached to the vnodes - // NEEDS TO BE PRIVATE - pub(crate) listeners: RefCell>, -} - -// We need to pin the hook so it doesn't move as we initialize the list of hooks -type Hook = Pin>; -type EventChannel = Rc; - -impl Scope { - // we are being created in the scope of an existing component (where the creator_node lifetime comes into play) - // we are going to break this lifetime by force in order to save it on ourselves. - // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies. - // This should never happen, but is a good check to keep around - // - // Scopes cannot be made anywhere else except for this file - // Therefore, their lifetimes are connected exclusively to the virtual dom - pub(crate) fn new<'creator_node>( - caller: Weak>, - arena_idx: ScopeIdx, - parent: Option, - height: u32, - event_channel: EventChannel, - arena_link: ScopeArena, - child_nodes: &'creator_node [VNode<'creator_node>], - ) -> Self { - log::debug!( - "New scope created, height is {}, idx is {:?}", - height, - arena_idx - ); - - // The function to run this scope is actually located in the parent's bump arena. - // Every time the parent is updated, that function is invalidated via double-buffering wiping the old frame. - // If children try to run this invalid caller, it *will* result in UB. - // - // During the lifecycle progression process, this caller will need to be updated. Right now, - // until formal safety abstractions are implemented, we will just use unsafe to "detach" the caller - // lifetime from the bump arena, exposing ourselves to this potential for invalidation. Truthfully, - // this is a bit of a hack, but will remain this way until we've figured out a cleaner solution. - // - // Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction. - let caller = unsafe { - std::mem::transmute::< - Weak>, - Weak>, - >(caller) - }; - - Self { - child_nodes: &[], - caller, - parent, - arena_idx, - height, - event_channel, - arena_link, - frames: ActiveFrame::new(), - hooks: Default::default(), - shared_contexts: Default::default(), - listeners: Default::default(), - hookidx: Default::default(), - descendents: Default::default(), - } - } - - pub fn update_caller<'creator_node>(&mut self, caller: Weak>) { - let broken_caller = unsafe { - std::mem::transmute::< - Weak>, - Weak>, - >(caller) - }; - - self.caller = broken_caller; - } - - /// Create a new context and run the component with references from the Virtual Dom - /// This function downcasts the function pointer based on the stored props_type - /// - /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) - pub fn run_scope<'sel>(&'sel mut self) -> Result<()> { - // Cycle to the next frame and then reset it - // This breaks any latent references, invalidating every pointer referencing into it. - self.frames.next().bump.reset(); - - // Remove all the outdated listeners - // - self.listeners - .try_borrow_mut() - .ok() - .ok_or(Error::FatalInternal("Borrowing listener failed"))? - .drain(..); - - *self.hookidx.borrow_mut() = 0; - - let caller = self - .caller - .upgrade() - .ok_or(Error::FatalInternal("Failed to get caller"))?; - - // Cast the caller ptr from static to one with our own reference - let c2: &OpaqueComponent<'static> = caller.as_ref(); - let c3: &OpaqueComponent<'_> = unsafe { std::mem::transmute(c2) }; - - let unsafe_head = unsafe { self.own_vnodes(c3) }; - - self.frames.cur_frame_mut().head_node = unsafe_head; - - Ok(()) - } - - // this is its own function so we can preciesly control how lifetimes flow - unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> { - let new_head: VNode<'a> = f(self); - let out: VNode<'static> = std::mem::transmute(new_head); - out - } - - // A safe wrapper around calling listeners - // calling listeners will invalidate the list of listeners - // The listener list will be completely drained because the next frame will write over previous listeners - pub fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> { - let EventTrigger { - listener_id, event, .. - } = trigger; - // - unsafe { - // Convert the raw ptr into an actual object - // This operation is assumed to be safe - let listener_fn = self - .listeners - .try_borrow() - .ok() - .ok_or(Error::FatalInternal("Borrowing listener failed"))? - .get(listener_id as usize) - .ok_or(Error::FatalInternal("Event should exist if triggered"))? - .as_ref() - .ok_or(Error::FatalInternal("Raw event ptr is invalid"))?; - - // Run the callback with the user event - listener_fn(event); - } - Ok(()) - } - - pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { - self.frames.current_head_node() - } - - pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { - self.frames.prev_head_node() - } - - pub fn cur_frame(&self) -> &BumpFrame { - self.frames.cur_frame() - } -} - -/// Components in Dioxus use the "Context" object to interact with their lifecycle. -/// This lets components schedule updates, integrate hooks, and expose their context via the context api. -/// -/// Properties passed down from the parent component are also directly accessible via the exposed "props" field. -/// -/// ```ignore -/// #[derive(Properties)] -/// struct Props { -/// name: String -/// -/// } -/// -/// fn example(ctx: Context, props: &Props -> VNode { -/// html! { -///

"Hello, {ctx.ctx.name}"
-/// } -/// } -/// ``` -// todo: force lifetime of source into T as a valid lifetime too -// it's definitely possible, just needs some more messing around - -pub struct Context<'src, T> { - pub props: &'src T, - pub scope: &'src Scope, -} - -impl<'src, T> Copy for Context<'src, T> {} -impl<'src, T> Clone for Context<'src, T> { - fn clone(&self) -> Self { - Self { - props: self.props, - scope: self.scope, - } - } -} - -impl<'a, T> Deref for Context<'a, T> { - type Target = &'a T; - - fn deref(&self) -> &Self::Target { - &self.props - } -} - -impl<'src, T> Scoped<'src> for Context<'src, T> { - fn get_scope(&self) -> &'src Scope { - self.scope - } -} - -pub trait Scoped<'src>: Sized { - fn get_scope(&self) -> &'src Scope; - - /// Access the children elements passed into the component - fn children(&self) -> &'src [VNode<'src>] { - // We're re-casting the nodes back out - // They don't really have a static lifetime - unsafe { - let scope = self.get_scope(); - let nodes: &'src [VNode<'static>] = scope.child_nodes; - - // cast the lifetime back correctly - std::mem::transmute(nodes) - } - } - - /// Create a subscription that schedules a future render for the reference component - fn schedule_update(&self) -> Rc { - self.get_scope().event_channel.clone() - } - - /// 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. - /// - /// ## Example - /// - /// ```ignore - /// fn Component(ctx: Context<()>) -> VNode { - /// // Lazy assemble the VNode tree - /// let lazy_tree = html! {
"Hello World"
}; - /// - /// // Actually build the tree and allocate it - /// ctx.render(lazy_tree) - /// } - ///``` - fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>( - self, - lazy_nodes: LazyNodes<'src, F>, - ) -> VNode<'src> { - lazy_nodes.into_vnode(&NodeCtx { - scope_ref: self.get_scope(), - listener_id: 0.into(), - }) - } - - // impl<'scope> Context<'scope> { - /// Store a value between renders - /// - /// - Initializer: closure used to create the initial hook state - /// - Runner: closure used to output a value every time the hook is used - /// - Cleanup: closure used to teardown the hook once the dom is cleaned up - /// - /// ```ignore - /// // use_ref is the simplest way of storing a value between renders - /// pub fn use_ref(initial_value: impl FnOnce() -> T + 'static) -> Rc> { - /// use_hook( - /// || Rc::new(RefCell::new(initial_value())), - /// |state| state.clone(), - /// |_| {}, - /// ) - /// } - /// ``` - fn use_hook( - &self, - - // The closure that builds the hook state - initializer: impl FnOnce() -> InternalHookState, - - // The closure that takes the hookstate and returns some value - runner: impl FnOnce(&'src mut InternalHookState) -> Output, - - // The closure that cleans up whatever mess is left when the component gets torn down - // TODO: add this to the "clean up" group for when the component is dropped - _cleanup: impl FnOnce(InternalHookState), - ) -> Output { - let scope = self.get_scope(); - - let idx = *scope.hookidx.borrow(); - - // Grab out the hook list - let mut hooks = scope.hooks.borrow_mut(); - - // If the idx is the same as the hook length, then we need to add the current hook - if idx >= hooks.len() { - let new_state = initializer(); - hooks.push(Box::pin(new_state)); - } - - *scope.hookidx.borrow_mut() += 1; - - let stable_ref = hooks - .get_mut(idx) - .expect("Should not fail, idx is validated") - .as_mut(); - - let pinned_state = unsafe { Pin::get_unchecked_mut(stable_ref) }; - - let internal_state = pinned_state.downcast_mut::().expect( - r###" -Unable to retrive the hook that was initialized in this index. -Consult the `rules of hooks` to understand how to use hooks properly. - -You likely used the hook in a conditional. Hooks rely on consistent ordering between renders. -Any function prefixed with "use" should not be called conditionally. - "###, - ); - - // We extend the lifetime of the internal state - runner(unsafe { &mut *(internal_state as *mut _) }) - } - - /// This hook enables the ability to expose state to children further down the VirtualDOM Tree. - /// - /// This is a hook, so it may not be called conditionally! - /// - /// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`) - /// so don't put it in a conditional. - /// - /// When the component is dropped, so is the context. Be aware of this behavior when consuming - /// the context via Rc/Weak. - /// - /// - /// - fn use_create_context(&self, init: impl Fn() -> T) { - let scope = self.get_scope(); - let mut ctxs = scope.shared_contexts.borrow_mut(); - let ty = TypeId::of::(); - - let is_initialized = self.use_hook( - || false, - |s| { - let i = s.clone(); - *s = true; - i - }, - |_| {}, - ); - - match (is_initialized, ctxs.contains_key(&ty)) { - // Do nothing, already initialized and already exists - (true, true) => {} - - // Needs to be initialized - (false, false) => { - log::debug!("Initializing context..."); - ctxs.insert(ty, Rc::new(init())); - } - - _ => debug_assert!(false, "Cannot initialize two contexts of the same type"), - } - } - - /// There are hooks going on here! - fn use_context(&self) -> &'src Rc { - self.try_use_context().unwrap() - } - - /// Uses a context, storing the cached value around - fn try_use_context(&self) -> Result<&'src Rc> { - struct UseContextHook { - par: Option>, - we: Option>, - } - - self.use_hook( - move || UseContextHook { - par: None as Option>, - we: None as Option>, - }, - move |hook| { - let scope = self.get_scope(); - let mut scope = Some(scope); - - if let Some(we) = &hook.we { - if let Some(re) = we.upgrade() { - hook.par = Some(re); - return Ok(hook.par.as_ref().unwrap()); - } - } - - let ty = TypeId::of::(); - while let Some(inner) = scope { - log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx); - let shared_contexts = inner.shared_contexts.borrow(); - - if let Some(shared_ctx) = shared_contexts.get(&ty) { - log::debug!("found matching ctx"); - let rc = shared_ctx - .clone() - .downcast::() - .expect("Should not fail, already validated the type from the hashmap"); - - hook.we = Some(Rc::downgrade(&rc)); - hook.par = Some(rc); - return Ok(hook.par.as_ref().unwrap()); - } else { - match inner.parent { - Some(parent_id) => { - let parent = inner - .arena_link - .try_get(parent_id) - .map_err(|_| Error::FatalInternal("Failed to find parent"))?; - - scope = Some(parent); - } - None => return Err(Error::MissingSharedContext), - } - } - } - - Err(Error::MissingSharedContext) - }, - |_| {}, - ) - } -} diff --git a/packages/core/src/support.rs b/packages/core/src/support.rs index 2cbdd57b2..a68ae57d4 100644 --- a/packages/core/src/support.rs +++ b/packages/core/src/support.rs @@ -1,4 +1,3 @@ -pub use crate::scope::*; use crate::{arena::ScopeArena, innerlude::*}; use bumpalo::Bump; use generational_arena::Arena; diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 20d590021..9114780d0 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -52,8 +52,8 @@ pub struct VirtualDom { /// a strong allocation to the "caller" for the original component and its props #[doc(hidden)] - _root_caller: Rc>, - + _root_caller: Rc, + // _root_caller: Rc>, /// Type of the original ctx. This is stored as TypeId so VirtualDom does not need to be generic. /// /// Whenver props need to be updated, an Error will be thrown if the new props do not @@ -137,7 +137,8 @@ impl VirtualDom { // Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents // Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope. - let _root_caller: Rc> = Rc::new(move |scope| { + let _root_caller: Rc = Rc::new(move |scope| { + // let _root_caller: Rc> = Rc::new(move |scope| { // the lifetime of this closure is just as long as the lifetime on the scope reference // this closure moves root props (which is static) into this closure let props = unsafe { &*(&root_props as *const _) }; @@ -154,10 +155,11 @@ impl VirtualDom { // Make the first scope // We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM let link = components.clone(); - let event_channel = Rc::new(move || {}); + let base_scope = components .with(|arena| { arena.insert_with(move |myidx| { + let event_channel = _event_queue.new_channel(0, myidx); Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[]) }) }) @@ -175,21 +177,24 @@ impl VirtualDom { /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch /// Currently this doesn't do what we want it to do pub fn rebuild<'s>(&'s mut self) -> Result> { - todo!() - // let mut diff_machine = DiffMachine::new(); + let mut diff_machine = DiffMachine::new( + self.components.clone(), + self.base_scope, + self.event_queue.clone(), + ); - // // Schedule an update and then immediately call it on the root component - // // This is akin to a hook being called from a listener and requring a re-render - // // Instead, this is done on top-level component + // Schedule an update and then immediately call it on the root component + // This is akin to a hook being called from a listener and requring a re-render + // Instead, this is done on top-level component - // let base = self.components.try_get(self.base_scope)?; + let base = self.components.try_get(self.base_scope)?; - // let update = &base.event_channel; - // update(); + let update = &base.event_channel; + update(); - // self.progress_completely(&mut diff_machine)?; + self.progress_completely(&mut diff_machine)?; - // Ok(diff_machine.consume()) + Ok(diff_machine.consume()) } } @@ -245,7 +250,9 @@ impl VirtualDom { self.components.try_get_mut(id)?.call_listener(event)?; - let mut diff_machine = DiffMachine::new(self.components.clone()); + let mut diff_machine = + DiffMachine::new(self.components.clone(), id, self.event_queue.clone()); + self.progress_completely(&mut diff_machine)?; Ok(diff_machine.consume()) @@ -260,212 +267,48 @@ impl VirtualDom { diff_machine: &'_ mut DiffMachine<'s>, ) -> Result<()> { // Add this component to the list of components that need to be difed - #[allow(unused_assignments)] - let mut cur_height: u32 = 0; + // #[allow(unused_assignments)] + // let mut cur_height: u32 = 0; // Now, there are events in the queue - let mut seen_nodes = HashSet::::new(); let mut updates = self.event_queue.0.as_ref().borrow_mut(); // Order the nodes by their height, we want the biggest nodes on the top // This prevents us from running the same component multiple times updates.sort_unstable(); + log::debug!("There are: {:#?} updates to be processed", updates.len()); + // Iterate through the triggered nodes (sorted by height) and begin to diff them for update in updates.drain(..) { + log::debug!("Running updates for: {:#?}", update); // Make sure this isn't a node we've already seen, we don't want to double-render anything // If we double-renderer something, this would cause memory safety issues - if seen_nodes.contains(&update.idx) { + if diff_machine.seen_nodes.contains(&update.idx) { continue; } // Now, all the "seen nodes" are nodes that got notified by running this listener - seen_nodes.insert(update.idx.clone()); + diff_machine.seen_nodes.insert(update.idx.clone()); // Start a new mutable borrow to components // We are guaranteeed that this scope is unique because we are tracking which nodes have modified let cur_component = self.components.try_get_mut(update.idx).unwrap(); + // let inner: &'s mut _ = unsafe { &mut *self.components.0.borrow().arena.get() }; + // let cur_component = inner.get_mut(update.idx).unwrap(); cur_component.run_scope()?; diff_machine.diff_node(cur_component.old_frame(), cur_component.next_frame()); - cur_height = cur_component.height; + // cur_height = cur_component.height; - log::debug!( - "Processing update: {:#?} with height {}", - &update.idx, - cur_height - ); - - // Now, the entire subtree has been invalidated. We need to descend depth-first and process - // any updates that the diff machine has proprogated into the component lifecycle queue - while let Some(event) = diff_machine.lifecycle_events.pop_front() { - match event { - // A new component has been computed from the diffing algorithm - // create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it - // this will flood the lifecycle queue with new updates to build up the subtree - LifeCycleEvent::Mount { - caller, - root_id: id, - stable_scope_addr, - } => { - log::debug!("Mounting a new component"); - - // We're modifying the component arena while holding onto references into the assoiated bump arenas of its children - // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas. - // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime. - - // Insert a new scope into our component list - let idx = self.components.with(|components| { - components.insert_with(|new_idx| { - let height = cur_height + 1; - Scope::new( - caller, - new_idx, - Some(cur_component.arena_idx), - height, - self.event_queue.new_channel(height, new_idx), - self.components.clone(), - &[], - ) - }) - })?; - - { - let cur_component = self.components.try_get_mut(update.idx).unwrap(); - let mut ch = cur_component.descendents.borrow_mut(); - ch.insert(idx); - std::mem::drop(ch); - } - - // Grab out that component - let new_component = self.components.try_get_mut(idx).unwrap(); - - // Actually initialize the caller's slot with the right address - *stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx); - - // Run the scope for one iteration to initialize it - new_component.run_scope()?; - - // Navigate the diff machine to the right point in the output dom - diff_machine.change_list.load_known_root(id); - - // And then run the diff algorithm - diff_machine - .diff_node(new_component.old_frame(), new_component.next_frame()); - - // Finally, insert this node as a seen node. - seen_nodes.insert(idx); - } - - // A component has remained in the same location but its properties have changed - // We need to process this component and then dump the output lifecycle events into the queue - LifeCycleEvent::PropsChanged { - caller, - root_id, - stable_scope_addr, - } => { - log::debug!("Updating a component after its props have changed"); - - // Get the stable index to the target component - // This *should* exist due to guarantees in the diff algorithm - let idx = stable_scope_addr - .upgrade() - .unwrap() - .as_ref() - .borrow() - .unwrap(); - - // Grab out that component - let component = self.components.try_get_mut(idx).unwrap(); - - // We have to move the caller over or running the scope will fail - component.update_caller(caller); - - // Run the scope - component.run_scope()?; - - // Navigate the diff machine to the right point in the output dom - diff_machine.change_list.load_known_root(root_id); - - // And then run the diff algorithm - diff_machine.diff_node(component.old_frame(), component.next_frame()); - - // Finally, insert this node as a seen node. - seen_nodes.insert(idx); - } - - // A component's parent has updated, but its properties did not change. - // This means the caller ptr is invalidated and needs to be updated, but the component itself does not need to be re-ran - LifeCycleEvent::SameProps { - caller, - stable_scope_addr, - .. - } => { - // In this case, the parent made a new VNode that resulted in the same props for us - // However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid) - log::debug!("Received the same props"); - - // Get the stable index to the target component - // This *should* exist due to guarantees in the diff algorithm - let idx = stable_scope_addr - .upgrade() - .unwrap() - .as_ref() - .borrow() - .unwrap(); - - // Grab out that component - let component = self.components.try_get_mut(idx).unwrap(); - - // We have to move the caller over or running the scope will fail - component.update_caller(caller); - - // This time, we will not add it to our seen nodes since we did not actually run it - } - - LifeCycleEvent::Remove { - root_id, - stable_scope_addr, - } => { - let id = stable_scope_addr - .upgrade() - .unwrap() - .as_ref() - .borrow() - .unwrap(); - - log::warn!("Removing node {:#?}", id); - - // This would normally be recursive but makes sense to do linear to - let mut children_to_remove = VecDeque::new(); - children_to_remove.push_back(id); - - // Accumulate all the child components that need to be removed - while let Some(child_id) = children_to_remove.pop_back() { - let comp = self.components.try_get(child_id).unwrap(); - let children = comp.descendents.borrow(); - for child in children.iter() { - children_to_remove.push_front(*child); - } - log::debug!("Removing component: {:#?}", child_id); - self.components - .with(|components| components.remove(child_id).unwrap()) - .unwrap(); - } - } - - LifeCycleEvent::Replace { - caller, - root_id: id, - .. - } => { - unimplemented!("This feature (Replace) is unimplemented") - } - } - } + // log::debug!( + // "Processing update: {:#?} with height {}", + // &update.idx, + // cur_height + // ); } Ok(()) @@ -489,7 +332,7 @@ pub struct Scope { // IDs of children that this scope has created // This enables us to drop the children and their children when this scope is destroyed - descendents: RefCell>, + pub(crate) descendents: RefCell>, child_nodes: &'static [VNode<'static>], @@ -507,8 +350,8 @@ pub struct Scope { pub event_channel: Rc, // pub event_queue: EventQueue, - pub caller: Weak>, - + pub caller: Weak, + // pub caller: Weak>, pub hookidx: RefCell, // ========================== @@ -542,8 +385,9 @@ impl Scope { // // Scopes cannot be made anywhere else except for this file // Therefore, their lifetimes are connected exclusively to the virtual dom - fn new<'creator_node>( - caller: Weak>, + pub fn new<'creator_node>( + caller: Weak, + // caller: Weak>, arena_idx: ScopeIdx, parent: Option, height: u32, @@ -569,8 +413,10 @@ impl Scope { // Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction. let caller = unsafe { std::mem::transmute::< - Weak>, - Weak>, + Weak, + Weak, + // Weak>, + // Weak>, >(caller) }; @@ -591,11 +437,14 @@ impl Scope { } } - pub fn update_caller<'creator_node>(&mut self, caller: Weak>) { + pub fn update_caller<'creator_node>(&mut self, caller: Weak) { + // pub fn update_caller<'creator_node>(&mut self, caller: Weak>) { let broken_caller = unsafe { std::mem::transmute::< - Weak>, - Weak>, + Weak, + Weak, + // Weak>, + // Weak>, >(caller) }; @@ -627,8 +476,10 @@ impl Scope { .ok_or(Error::FatalInternal("Failed to get caller"))?; // Cast the caller ptr from static to one with our own reference - let c2: &OpaqueComponent<'static> = caller.as_ref(); - let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) }; + let c2: &OpaqueComponent = caller.as_ref(); + let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) }; + // let c2: &OpaqueComponent<'static> = caller.as_ref(); + // let c3: &OpaqueComponent<'sel> = unsafe { std::mem::transmute(c2) }; let unsafe_head = unsafe { self.own_vnodes(c3) }; @@ -638,7 +489,7 @@ impl Scope { } // this is its own function so we can preciesly control how lifetimes flow - unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> { + unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent) -> VNode<'static> { let new_head: VNode<'a> = f(self); let out: VNode<'static> = std::mem::transmute(new_head); out @@ -956,22 +807,25 @@ Any function prefixed with "use" should not be called conditionally. // We actually allocate the properties for components in their parent's properties // We then expose a handle to use those props for render in the form of "OpaqueComponent" -pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e; +pub type OpaqueComponent = dyn for<'b> Fn(&'b Scope) -> VNode<'b>; #[derive(PartialEq, Debug, Clone, Default)] -pub(crate) struct EventQueue(pub Rc>>); +pub struct EventQueue(pub Rc>>); impl EventQueue { pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc { let inner = self.clone(); let marker = HeightMarker { height, idx }; - Rc::new(move || inner.0.as_ref().borrow_mut().push(marker)) + Rc::new(move || { + log::debug!("channel updated {:#?}", marker); + inner.0.as_ref().borrow_mut().push(marker) + }) } } /// A helper type that lets scopes be ordered by their height #[derive(Debug, Clone, Copy, PartialEq, Eq)] -pub(crate) struct HeightMarker { +pub struct HeightMarker { pub idx: ScopeIdx, pub height: u32, } @@ -1047,20 +901,20 @@ impl ActiveFrame { ) } - fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { + pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { Self { generation: 0.into(), frames: [a, b], } } - fn cur_frame(&self) -> &BumpFrame { + pub fn cur_frame(&self) -> &BumpFrame { match *self.generation.borrow() & 1 == 0 { true => &self.frames[0], false => &self.frames[1], } } - fn cur_frame_mut(&mut self) -> &mut BumpFrame { + pub fn cur_frame_mut(&mut self) -> &mut BumpFrame { match *self.generation.borrow() & 1 == 0 { true => &mut self.frames[0], false => &mut self.frames[1], @@ -1095,7 +949,7 @@ impl ActiveFrame { } } - fn next(&mut self) -> &mut BumpFrame { + pub fn next(&mut self) -> &mut BumpFrame { *self.generation.borrow_mut() += 1; if *self.generation.borrow() % 2 == 0 { diff --git a/packages/recoil/src/lib.rs b/packages/recoil/src/lib.rs index 7fc684a14..634d9442e 100644 --- a/packages/recoil/src/lib.rs +++ b/packages/recoil/src/lib.rs @@ -368,7 +368,7 @@ mod root { mod hooks { use super::*; - use dioxus_core::{hooks::use_ref, prelude::Context, scope::Scoped}; + use dioxus_core::{hooks::use_ref, prelude::Context, virtual_dom::Scoped}; pub fn use_init_recoil_root

(ctx: Context

, cfg: impl Fn(())) { ctx.use_create_context(move || RefCell::new(RecoilRoot::new()))