diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 3e31e62d4..cdeac3839 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -454,7 +454,6 @@ impl<'bump> DiffState<'bump> { // TODO: // add noderefs to current noderef list Noderefs // add effects to current effect list Effects - let new_component = self.scopes.get_scope(&new_idx).unwrap(); log::debug!( @@ -633,17 +632,26 @@ impl<'bump> DiffState<'bump> { ) { let scope_addr = old.associated_scope.get().unwrap(); + log::debug!( + "Diffing components. old_scope: {:?}, old_addr: {:?}, new_addr: {:?}", + scope_addr, + old.user_fc, + new.user_fc + ); + // Make sure we're dealing with the same component (by function pointer) if old.user_fc == new.user_fc { - log::debug!("Diffing component {:?} - {:?}", new.user_fc, scope_addr); - // self.stack.scope_stack.push(scope_addr); // Make sure the new component vnode is referencing the right scope id new.associated_scope.set(Some(scope_addr)); // make sure the component's caller function is up to date - let scope = unsafe { self.scopes.get_scope_mut(&scope_addr).unwrap() }; + let scope = unsafe { + self.scopes + .get_scope_mut(&scope_addr) + .expect(&format!("could not find {:?}", scope_addr)) + }; scope.caller = unsafe { std::mem::transmute(new.caller) }; // React doesn't automatically memoize, but we do. @@ -1164,7 +1172,6 @@ impl<'bump> DiffState<'bump> { } VNode::Component(el) => { let scope_id = el.associated_scope.get().unwrap(); - // let scope = self.scopes.get_scope(&scope_id).unwrap(); search_node = Some(self.scopes.root_node(&scope_id)); } } @@ -1198,7 +1205,13 @@ impl<'bump> DiffState<'bump> { fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) { match old { - VNode::Text(_) | VNode::Element(_) | VNode::Anchor(_) | VNode::Suspended(_) => { + VNode::Element(el) => { + let id = old.try_mounted_id().expect(&format!("broke on {:?}", old)); + self.mutations.replace_with(id, nodes_created as u32); + self.remove_nodes(el.children, false); + } + + VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => { let id = old.try_mounted_id().expect(&format!("broke on {:?}", old)); self.mutations.replace_with(id, nodes_created as u32); } @@ -1211,6 +1224,11 @@ impl<'bump> DiffState<'bump> { VNode::Component(c) => { let node = self.scopes.fin_head(&c.associated_scope.get().unwrap()); self.replace_node(node, nodes_created); + + let scope_id = c.associated_scope.get().unwrap(); + println!("replacing c {:?} ", scope_id); + log::debug!("Destroying scope {:?}", scope_id); + self.scopes.try_remove(&scope_id).unwrap(); } VNode::Linked(l) => { @@ -1276,16 +1294,20 @@ impl<'bump> DiffState<'bump> { } VNode::Component(c) => { - let scope_id = c.associated_scope.get().unwrap(); - let root = self.scopes.root_node(&scope_id); - self.remove_nodes(Some(root), gen_muts); - log::debug!("Destroying scope {:?}", scope_id); - self.scopes.try_remove(&scope_id).unwrap(); + self.destroy_vomponent(c, gen_muts); } } } } + fn destroy_vomponent(&mut self, vc: &VComponent, gen_muts: bool) { + let scope_id = vc.associated_scope.get().unwrap(); + let root = self.scopes.root_node(&scope_id); + self.remove_nodes(Some(root), gen_muts); + log::debug!("Destroying scope {:?}", scope_id); + self.scopes.try_remove(&scope_id).unwrap(); + } + /// Adds a listener closure to a scope during diff. fn attach_listener_to_scope(&mut self, listener: &'bump Listener<'bump>, scope: &Scope) { let long_listener = unsafe { std::mem::transmute(listener) }; diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index f9b236a23..d6bf3fda2 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -27,7 +27,7 @@ pub struct LazyNodes<'a, 'b> { inner: StackNodeStorage<'a, 'b>, } -type StackHeapSize = [usize; 8]; +type StackHeapSize = [usize; 16]; enum StackNodeStorage<'a, 'b> { Stack(LazyStack), @@ -88,21 +88,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> { let max_size = mem::size_of::(); if stored_size > max_size { - log::debug!( - "lazy nodes was too large to fit into stack. falling back to heap. max: {}, actual {:?}", - max_size, - stored_size - ); - Self { inner: StackNodeStorage::Heap(Box::new(val)), } } else { - log::debug!( - "lazy nodes fits on stack! max: {}, actual: {:?}", - max_size, - stored_size - ); let mut buf: StackHeapSize = StackHeapSize::default(); assert!(info.len() + round_to_words(size) <= buf.as_ref().len()); diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 95ee93422..5176b0fba 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -499,7 +499,7 @@ impl Scope { } } - pub fn root_node(&self) -> &VNode { + pub fn root_node<'a>(&'a self) -> &'a VNode<'a> { let node = *self.wip_frame().nodes.borrow().get(0).unwrap(); unsafe { std::mem::transmute(&*node) } } diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index a08e0ea41..c3c85d5d0 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -19,9 +19,10 @@ pub struct Heuristic { // has an internal heuristics engine to pre-allocate arenas to the right size pub(crate) struct ScopeArena { bump: Bump, - scopes: RefCell>, + scope_counter: Cell, + scopes: RefCell>, pub heuristics: RefCell>, - free_scopes: RefCell>, + free_scopes: RefCell>, nodes: RefCell>>, pub(crate) sender: UnboundedSender, } @@ -29,8 +30,9 @@ pub(crate) struct ScopeArena { impl ScopeArena { pub fn new(sender: UnboundedSender) -> Self { Self { + scope_counter: Cell::new(0), bump: Bump::new(), - scopes: RefCell::new(Vec::new()), + scopes: RefCell::new(FxHashMap::default()), heuristics: RefCell::new(FxHashMap::default()), free_scopes: RefCell::new(Vec::new()), nodes: RefCell::new(Slab::new()), @@ -39,17 +41,17 @@ impl ScopeArena { } pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> { - unsafe { self.scopes.borrow().get(id.0).map(|f| &**f) } + unsafe { self.scopes.borrow().get(id).map(|f| &**f) } } // this is unsafe pub unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> { - self.scopes.borrow().get(id.0).map(|f| *f) + self.scopes.borrow().get(id).map(|f| *f) } // this is unsafe pub unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> { - self.scopes.borrow().get(id.0).map(|s| &mut **s) + self.scopes.borrow().get(id).map(|s| &mut **s) } pub fn new_with_key( @@ -60,80 +62,128 @@ impl ScopeArena { height: u32, subtree: u32, ) -> ScopeId { - let scope_id = ScopeId(self.scopes.borrow().len()); + let new_scope_id = ScopeId(self.scope_counter.get()); + self.scope_counter.set(self.scope_counter.get() + 1); - let (node_capacity, hook_capacity) = { - let heuristics = self.heuristics.borrow(); - if let Some(heuristic) = heuristics.get(&fc_ptr) { - (heuristic.node_arena_size, heuristic.hook_arena_size) - } else { - (0, 0) - } - }; + // + // + if let Some(old_scope) = self.free_scopes.borrow_mut().pop() { + let scope = unsafe { &mut *old_scope }; + log::debug!( + "reusing scope {:?} as {:?}", + scope.our_arena_idx, + new_scope_id + ); - let mut frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)]; + scope.caller = caller; + scope.parent_scope = parent_scope; + scope.height = height; + scope.subtree = Cell::new(subtree); + scope.our_arena_idx = new_scope_id; - frames[0].nodes.get_mut().push({ - let vnode = frames[0] - .bump - .alloc(VNode::Text(frames[0].bump.alloc(VText { - dom_id: Default::default(), - is_static: false, - text: "", - }))); - unsafe { std::mem::transmute(vnode as *mut VNode) } - }); + scope.frames[0].nodes.get_mut().push({ + let vnode = scope.frames[0] + .bump + .alloc(VNode::Text(scope.frames[0].bump.alloc(VText { + dom_id: Default::default(), + is_static: false, + text: "", + }))); + unsafe { std::mem::transmute(vnode as *mut VNode) } + }); - frames[1].nodes.get_mut().push({ - let vnode = frames[1] - .bump - .alloc(VNode::Text(frames[1].bump.alloc(VText { - dom_id: Default::default(), - is_static: false, - text: "", - }))); - unsafe { std::mem::transmute(vnode as *mut VNode) } - }); + scope.frames[1].nodes.get_mut().push({ + let vnode = scope.frames[1] + .bump + .alloc(VNode::Text(scope.frames[1].bump.alloc(VText { + dom_id: Default::default(), + is_static: false, + text: "", + }))); + unsafe { std::mem::transmute(vnode as *mut VNode) } + }); - let mut new_scope = Scope { - sender: self.sender.clone(), - our_arena_idx: scope_id, - parent_scope, - height, - frames, - subtree: Cell::new(subtree), - is_subtree_root: Cell::new(false), + let r = self.scopes.borrow_mut().insert(new_scope_id, scope); - caller, - generation: 0.into(), + assert!(r.is_none()); - hooks: HookList::new(hook_capacity), - shared_contexts: Default::default(), - - items: RefCell::new(SelfReferentialItems { - listeners: Default::default(), - borrowed_props: Default::default(), - suspended_nodes: Default::default(), - tasks: Default::default(), - pending_effects: Default::default(), - }), - }; - - if let Some(id) = self.free_scopes.borrow_mut().pop() { - let scope = unsafe { self.get_scope_mut(&id) }.unwrap(); - std::mem::swap(&mut new_scope, scope); - id + new_scope_id } else { - let scope = self.bump.alloc(new_scope); - self.scopes.borrow_mut().push(scope); - scope_id + let (node_capacity, hook_capacity) = { + let heuristics = self.heuristics.borrow(); + if let Some(heuristic) = heuristics.get(&fc_ptr) { + (heuristic.node_arena_size, heuristic.hook_arena_size) + } else { + (0, 0) + } + }; + + let mut frames = [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)]; + + frames[0].nodes.get_mut().push({ + let vnode = frames[0] + .bump + .alloc(VNode::Text(frames[0].bump.alloc(VText { + dom_id: Default::default(), + is_static: false, + text: "", + }))); + unsafe { std::mem::transmute(vnode as *mut VNode) } + }); + + frames[1].nodes.get_mut().push({ + let vnode = frames[1] + .bump + .alloc(VNode::Text(frames[1].bump.alloc(VText { + dom_id: Default::default(), + is_static: false, + text: "", + }))); + unsafe { std::mem::transmute(vnode as *mut VNode) } + }); + + let scope = self.bump.alloc(Scope { + sender: self.sender.clone(), + our_arena_idx: new_scope_id, + parent_scope, + height, + frames, + subtree: Cell::new(subtree), + is_subtree_root: Cell::new(false), + + caller, + generation: 0.into(), + + hooks: HookList::new(hook_capacity), + shared_contexts: Default::default(), + + items: RefCell::new(SelfReferentialItems { + listeners: Default::default(), + borrowed_props: Default::default(), + suspended_nodes: Default::default(), + tasks: Default::default(), + pending_effects: Default::default(), + }), + }); + + dbg!(self.scopes.borrow()); + + let r = self.scopes.borrow_mut().insert(new_scope_id, scope); + + assert!(r.is_none()); + // .expect(&format!("scope shouldnt exist, {:?}", new_scope_id)); + + new_scope_id } } pub fn try_remove(&self, id: &ScopeId) -> Option<()> { self.ensure_drop_safety(id); - let mut scope = unsafe { &mut *self.get_scope_raw(id)? }; + log::debug!("removing scope {:?}", id); + println!("removing scope {:?}", id); + + let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() }; // we're just reusing scopes so we need to clear it out scope.hooks.clear(); @@ -143,6 +193,12 @@ impl ScopeArena { scope.is_subtree_root.set(false); scope.subtree.set(0); + scope.frames[0].nodes.get_mut().clear(); + scope.frames[1].nodes.get_mut().clear(); + + scope.frames[0].bump.reset(); + scope.frames[1].bump.reset(); + let SelfReferentialItems { borrowed_props, listeners, @@ -157,7 +213,8 @@ impl ScopeArena { suspended_nodes.clear(); tasks.clear(); - self.free_scopes.borrow_mut().push(*id); + self.free_scopes.borrow_mut().push(scope); + Some(()) } @@ -218,11 +275,9 @@ impl ScopeArena { } pub(crate) fn run_scope(&self, id: &ScopeId) -> bool { - let scope = unsafe { - &mut *self - .get_scope_mut(id) - .expect("The base scope should never be moved") - }; + let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") }; + + log::debug!("found scope, about to run: {:?}", id); // Cycle to the next frame and then reset it // This breaks any latent references, invalidating every pointer referencing into it. @@ -285,8 +340,8 @@ impl ScopeArena { // The head of the bumpframe is the first linked NodeLink pub fn wip_head(&self, id: &ScopeId) -> &VNode { let scope = self.get_scope(id).unwrap(); - let wip_frame = scope.wip_frame(); - let nodes = wip_frame.nodes.borrow(); + let frame = scope.wip_frame(); + let nodes = frame.nodes.borrow(); let node: &VNode = unsafe { &**nodes.get(0).unwrap() }; unsafe { std::mem::transmute::<&VNode, &VNode>(node) } } @@ -294,8 +349,8 @@ impl ScopeArena { // The head of the bumpframe is the first linked NodeLink pub fn fin_head(&self, id: &ScopeId) -> &VNode { let scope = self.get_scope(id).unwrap(); - let wip_frame = scope.fin_frame(); - let nodes = wip_frame.nodes.borrow(); + let frame = scope.fin_frame(); + let nodes = frame.nodes.borrow(); let node: &VNode = unsafe { &**nodes.get(0).unwrap() }; unsafe { std::mem::transmute::<&VNode, &VNode>(node) } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 19fa6ca3b..c90fa0119 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -401,13 +401,13 @@ impl VirtualDom { if let Some(element) = event.mounted_dom_id { log::info!("Calling listener {:?}, {:?}", event.scope_id, element); - let scope = self.scopes.get_scope(&event.scope_id).unwrap(); + if let Some(scope) = self.scopes.get_scope(&event.scope_id) { + // TODO: bubble properly here + scope.call_listener(event, element); - // TODO: bubble properly here - scope.call_listener(event, element); - - while let Ok(Some(dirty_scope)) = self.receiver.try_next() { - self.pending_messages.push_front(dirty_scope); + while let Ok(Some(dirty_scope)) = self.receiver.try_next() { + self.pending_messages.push_front(dirty_scope); + } } } else { log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event); diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 3c52214f5..b5568d74f 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -220,3 +220,96 @@ fn components_generate() { ] ); } + +#[test] +fn component_swap() { + // simple_logger::init(); + static App: FC<()> = |cx, _| { + let mut render_phase = use_state(cx, || 0); + render_phase += 1; + + cx.render(match *render_phase { + 0 => rsx!( + div { + NavBar {} + Dashboard {} + } + ), + 1 => rsx!( + div { + NavBar {} + Results {} + } + ), + 2 => rsx!( + div { + NavBar {} + Dashboard {} + } + ), + 3 => rsx!( + div { + NavBar {} + Results {} + } + ), + 4 => rsx!( + div { + NavBar {} + Dashboard {} + } + ), + _ => rsx!("blah"), + }) + }; + + static NavBar: FC<()> = |cx, _| { + println!("running navbar"); + cx.render(rsx! { + h1 { + "NavBar" + {(0..3).map(|f| rsx!(NavLink {}))} + } + }) + }; + + static NavLink: FC<()> = |cx, _| { + println!("running navlink"); + cx.render(rsx! { + h1 { + "NavLink" + } + }) + }; + + static Dashboard: FC<()> = |cx, _| { + println!("running dashboard"); + cx.render(rsx! { + div { + "dashboard" + } + }) + }; + + static Results: FC<()> = |cx, _| { + println!("running results"); + cx.render(rsx! { + div { + "results" + } + }) + }; + + let mut dom = VirtualDom::new(App); + let edits = dom.rebuild(); + dbg!(&edits); + + let edits = dom.work_with_deadline(|| false); + dbg!(&edits); + let edits = dom.work_with_deadline(|| false); + dbg!(&edits); + let edits = dom.work_with_deadline(|| false); + dbg!(&edits); + let edits = dom.work_with_deadline(|| false); + dbg!(&edits); +} diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index f9856eeaa..56d0fab77 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -17,6 +17,8 @@ pub(crate) struct ProvidedStateInner { impl ProvidedStateInner { pub(crate) fn notify_consumers(&mut self) { for consumer in self.consumers.iter() { + println!("notifiying {:?}", consumer); + // log::debug("notifiying {:?}", consumer); (self.notify_any)(*consumer); } } diff --git a/packages/router/src/lib.rs b/packages/router/src/lib.rs index 378935546..3aee5a684 100644 --- a/packages/router/src/lib.rs +++ b/packages/router/src/lib.rs @@ -61,7 +61,7 @@ impl RouterService { /// This hould only be used once per app /// /// You can manually parse the route if you want, but the derived `parse` method on `Routable` will also work just fine -pub fn use_router(cx: Context, cfg: impl FnOnce(&str) -> R) -> Option { +pub fn use_router(cx: Context, cfg: impl FnOnce(&str) -> R) -> Option<&R> { // for the web, attach to the history api cx.use_hook( |f| { @@ -88,30 +88,24 @@ pub fn use_router(cx: Context, cfg: impl FnOnce(&str) -> R) -> Opti |f| { // }, - |f| { - // - }, ); - let router = use_router_service::(cx)?; - Some(cfg(router.get_current_route())) + todo!() + // let router = use_router_service::(cx)?; + // Some(cfg(router.get_current_route())) } pub fn use_router_service(cx: Context) -> Option<&Rc>> { - cx.use_hook( - |_| cx.consume_state::>(), - |f| f.as_ref(), - |f| {}, - ) + cx.use_hook(|_| cx.consume_state::>(), |f| f.as_ref()) } #[derive(Props)] -pub struct LinkProps<'a, R: Routable> { +pub struct LinkProps { to: R, - children: ScopeChildren<'a>, + children: Element, } -pub fn Link<'a, R: Routable>((cx, props): Scope<'a, LinkProps<'a, R>>) -> Element { +pub fn Link<'a, R: Routable>(cx: Context, props: &LinkProps) -> Element { let service = use_router_service::(cx)?; cx.render(rsx! { a { diff --git a/src/lib.rs b/src/lib.rs index c581914b0..bc980a259 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -185,6 +185,9 @@ pub use dioxus_mobile as mobile; #[cfg(feature = "desktop")] pub use dioxus_desktop as desktop; +#[cfg(feature = "router")] +pub use dioxus_router as router; + pub mod debug {} pub mod prelude {