diff --git a/packages/core-macro/src/lib.rs b/packages/core-macro/src/lib.rs index ce36fb83c..a218ccc4d 100644 --- a/packages/core-macro/src/lib.rs +++ b/packages/core-macro/src/lib.rs @@ -202,6 +202,8 @@ pub fn rsx(s: TokenStream) -> TokenStream { /// Home, /// #[at("/secure")] /// Secure, +/// #[at("/profile/{id}")] +/// Profile(u32), /// #[at("/404")] /// NotFound, /// } diff --git a/packages/core-macro/src/router.rs b/packages/core-macro/src/router.rs index 684d2dc7d..2ab9f3a17 100644 --- a/packages/core-macro/src/router.rs +++ b/packages/core-macro/src/router.rs @@ -200,11 +200,11 @@ pub fn routable_derive_impl(input: Routable) -> TokenStream { ); quote! { - ::std::thread_local! { - #[doc(hidden)] - #[allow(non_upper_case_globals)] - static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None); - } + // ::std::thread_local! { + // #[doc(hidden)] + // #[allow(non_upper_case_globals)] + // static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None); + // } #[automatically_derived] impl ::dioxus::router::Routable for #ident { @@ -219,29 +219,30 @@ pub fn routable_derive_impl(input: Routable) -> TokenStream { #not_found_route } - fn current_route() -> ::std::option::Option { - #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow())) - } + // fn current_route() -> ::std::option::Option { + // #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow())) + // } fn recognize(pathname: &str) -> ::std::option::Option { - ::std::thread_local! { - static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>(); - } - let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname)); - { - let route = ::std::clone::Clone::clone(&route); - #cache_thread_local_ident.with(move |val| { - *val.borrow_mut() = route; - }); - } - route + todo!() + // ::std::thread_local! { + // static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>(); + // } + // let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname)); + // { + // let route = ::std::clone::Clone::clone(&route); + // #cache_thread_local_ident.with(move |val| { + // *val.borrow_mut() = route; + // }); + // } + // route } - fn cleanup() { - #cache_thread_local_ident.with(move |val| { - *val.borrow_mut() = ::std::option::Option::None; - }); - } + // fn cleanup() { + // #cache_thread_local_ident.with(move |val| { + // *val.borrow_mut() = ::std::option::Option::None; + // }); + // } } } } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index f01e11bf7..959b17251 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -36,7 +36,7 @@ impl FragmentBuilder { /// /// ## Example /// -/// ```rust +/// ```rust, ignore /// fn App(cx: Context, props: &()) -> Element { /// cx.render(rsx!{ /// CustomCard { @@ -82,7 +82,7 @@ impl Properties for FragmentProps { /// /// ## Example /// -/// ```rust +/// ```rust, ignore /// rsx!{ /// Fragment { key: "abc" } /// } @@ -118,7 +118,7 @@ pub fn Fragment(cx: Context, props: &FragmentProps) -> Element { /// ## Example /// /// For props that are 'static: -/// ```rust ignore +/// ```rust, ignore ignore /// #[derive(Props, PartialEq)] /// struct MyProps { /// data: String @@ -127,7 +127,7 @@ pub fn Fragment(cx: Context, props: &FragmentProps) -> Element { /// /// For props that borrow: /// -/// ```rust ignore +/// ```rust, ignore ignore /// #[derive(Props)] /// struct MyProps<'a >{ /// data: &'a str diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index cdeac3839..3810ad6b8 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -54,7 +54,7 @@ //! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can //! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the //! calls to "create" propagate this information upwards. Structures like the one below are entirely static: -//! ```rust +//! ```rust, ignore //! rsx!( div { class: "hello world", "this node is entirely static" } ) //! ``` //! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to @@ -147,6 +147,7 @@ pub(crate) enum DiffInstruction<'a> { }, PopScope, + PopElement, } #[derive(Debug, Clone, Copy)] @@ -162,30 +163,32 @@ pub(crate) struct DiffStack<'bump> { pub(crate) instructions: Vec>, nodes_created_stack: SmallVec<[usize; 10]>, pub scope_stack: SmallVec<[ScopeId; 5]>, + pub element_stack: SmallVec<[ElementId; 10]>, } impl<'bump> DiffStack<'bump> { - pub fn new() -> Self { + fn new() -> Self { Self { instructions: Vec::with_capacity(1000), nodes_created_stack: smallvec![], scope_stack: smallvec![], + element_stack: smallvec![], } } - pub fn pop(&mut self) -> Option> { + fn pop(&mut self) -> Option> { self.instructions.pop() } - pub fn pop_off_scope(&mut self) { + fn pop_off_scope(&mut self) { self.scope_stack.pop(); } - pub fn push(&mut self, instruction: DiffInstruction<'bump>) { + pub(crate) fn push(&mut self, instruction: DiffInstruction<'bump>) { self.instructions.push(instruction) } - pub fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) { + fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) { self.nodes_created_stack.push(0); self.instructions.push(DiffInstruction::Mount { and }); @@ -195,36 +198,36 @@ impl<'bump> DiffStack<'bump> { } } - pub fn push_subtree(&mut self) { + fn push_subtree(&mut self) { self.nodes_created_stack.push(0); self.instructions.push(DiffInstruction::Mount { and: MountType::Append, }); } - pub fn push_nodes_created(&mut self, count: usize) { + fn push_nodes_created(&mut self, count: usize) { self.nodes_created_stack.push(count); } - pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) { + pub(crate) fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) { self.nodes_created_stack.push(0); self.instructions.push(DiffInstruction::Mount { and }); self.instructions.push(DiffInstruction::Create { node }); } - pub fn add_child_count(&mut self, count: usize) { + fn add_child_count(&mut self, count: usize) { *self.nodes_created_stack.last_mut().unwrap() += count; } - pub fn pop_nodes_created(&mut self) -> usize { + fn pop_nodes_created(&mut self) -> usize { self.nodes_created_stack.pop().unwrap() } - pub fn current_scope(&self) -> Option { + fn current_scope(&self) -> Option { self.scope_stack.last().copied() } - pub fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) { + fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) { // Push the new scope onto the stack self.scope_stack.push(idx); @@ -263,10 +266,13 @@ impl<'bump> DiffState<'bump> { self.stack.add_child_count(num_on_stack); } DiffInstruction::PopScope => self.stack.pop_off_scope(), + DiffInstruction::PopElement => { + self.stack.element_stack.pop(); + } }; if deadline_expired() { - log::debug!("Deadline expired before we could finished!"); + log::debug!("Deadline expired before we could finish!"); return false; } } @@ -390,11 +396,18 @@ impl<'bump> DiffState<'bump> { children, namespace, dom_id, + parent_id, .. } = element; - let real_id = self.scopes.reserve_node(node); + // set the parent ID for event bubbling + self.stack.instructions.push(DiffInstruction::PopElement); + let parent = self.stack.element_stack.last().unwrap(); + parent_id.set(Some(*parent)); + // set the id of the element + let real_id = self.scopes.reserve_node(node); + self.stack.element_stack.push(real_id); dom_id.set(Some(real_id)); self.mutations.create_element(tag_name, *namespace, real_id); @@ -438,9 +451,11 @@ impl<'bump> DiffState<'bump> { let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) }; let fc_ptr = vcomponent.user_fc; - let new_idx = self - .scopes - .new_with_key(fc_ptr, caller, parent_scope, height, subtree); + let container = *self.stack.element_stack.last().unwrap(); + + let new_idx = + self.scopes + .new_with_key(fc_ptr, caller, parent_scope, container, height, subtree); // Actually initialize the caller's slot with the right address vcomponent.associated_scope.set(Some(new_idx)); @@ -650,20 +665,18 @@ impl<'bump> DiffState<'bump> { let scope = unsafe { self.scopes .get_scope_mut(&scope_addr) - .expect(&format!("could not find {:?}", scope_addr)) + .unwrap_or_else(|| panic!("could not find {:?}", scope_addr)) }; scope.caller = unsafe { std::mem::transmute(new.caller) }; // React doesn't automatically memoize, but we do. let props_are_the_same = old.comparator.unwrap(); - if self.force_diff || !props_are_the_same(new) { - if self.scopes.run_scope(&scope_addr) { - self.diff_node( - self.scopes.wip_head(&scope_addr), - self.scopes.fin_head(&scope_addr), - ); - } + if (self.force_diff || !props_are_the_same(new)) && self.scopes.run_scope(&scope_addr) { + self.diff_node( + self.scopes.wip_head(&scope_addr), + self.scopes.fin_head(&scope_addr), + ); } self.stack.scope_stack.pop(); @@ -1206,13 +1219,19 @@ impl<'bump> DiffState<'bump> { fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) { match old { VNode::Element(el) => { - let id = old.try_mounted_id().expect(&format!("broke on {:?}", old)); + let id = old + .try_mounted_id() + .unwrap_or_else(|| panic!("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)); + let id = old + .try_mounted_id() + .unwrap_or_else(|| panic!("broke on {:?}", old)); + self.mutations.replace_with(id, nodes_created as u32); } @@ -1226,7 +1245,6 @@ impl<'bump> DiffState<'bump> { 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(); } diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index d6bf3fda2..a9fbcf3fa 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -20,7 +20,7 @@ use std::mem; /// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode. /// /// -/// ```rust +/// ```rust, ignore /// LazyNodes::new(|f| f.element("div", [], [], [] None)) /// ``` pub struct LazyNodes<'a, 'b> { @@ -47,7 +47,14 @@ impl<'a, 'b> LazyNodes<'a, 'b> { fac.map(inner) }; - unsafe { LazyNodes::new_inner(val) } + // miri does not know how to work with mucking directly into bytes + if cfg!(miri) { + Self { + inner: StackNodeStorage::Heap(Box::new(val)), + } + } else { + unsafe { LazyNodes::new_inner(val) } + } } unsafe fn new_inner(val: F) -> Self @@ -212,6 +219,7 @@ fn round_to_words(len: usize) -> usize { fn it_works() { let bump = bumpalo::Bump::new(); + #[cfg(not(miri))] simple_logger::init().unwrap(); let factory = NodeFactory { bump: &bump }; @@ -232,9 +240,10 @@ fn it_drops() { use std::rc::Rc; let bump = bumpalo::Bump::new(); + #[cfg(not(miri))] simple_logger::init().unwrap(); - // let factory = NodeFactory { scope: &bump }; + let factory = NodeFactory { bump: &bump }; struct DropInner { id: i32, @@ -246,7 +255,7 @@ fn it_drops() { } let val = Rc::new(10); - { + let caller = { let it = (0..10) .map(|i| { let val = val.clone(); @@ -259,16 +268,13 @@ fn it_drops() { }) .collect::>(); - let caller = NodeFactory::annotate_lazy(|f| { + NodeFactory::annotate_lazy(|f| { log::debug!("main closure"); f.fragment_from_iter(it) }) - .unwrap(); - } + .unwrap() + }; - // let nodes = caller.call(factory); - - // dbg!(nodes); - - dbg!(Rc::strong_count(&val)); + let _ = caller.call(factory); + assert_eq!(Rc::strong_count(&val), 1); } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 62c3ab837..55464d9dc 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -25,7 +25,7 @@ pub enum VNode<'src> { /// /// # Example /// - /// ``` + /// ```rust, ignore /// let mut vdom = VirtualDom::new(); /// let node = vdom.render_vnode(rsx!( "hello" )); /// @@ -41,7 +41,7 @@ pub enum VNode<'src> { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// let mut vdom = VirtualDom::new(); /// /// let node = vdom.render_vnode(rsx!{ @@ -66,7 +66,7 @@ pub enum VNode<'src> { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// rsx!{ /// a {} /// link {} @@ -81,7 +81,7 @@ pub enum VNode<'src> { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// fn Example(cx: Context, props: &()) -> Element { /// todo!() /// } @@ -100,7 +100,7 @@ pub enum VNode<'src> { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// /// /// ``` @@ -112,7 +112,7 @@ pub enum VNode<'src> { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// let mut vdom = VirtualDom::new(); /// /// let node = vdom.render_vnode(rsx!( Fragment {} )); @@ -133,7 +133,7 @@ pub enum VNode<'src> { /// an `rsx!` call. /// /// # Example - /// ```rust + /// ```rust, ignore /// let mut vdom = VirtualDom::new(); /// /// let node: NodeLink = vdom.render_vnode(rsx!( "hello" )); @@ -202,7 +202,7 @@ impl<'src> VNode<'src> { VNode::Linked(c) => VNode::Linked(NodeLink { scope_id: c.scope_id.clone(), link_idx: c.link_idx.clone(), - node: c.node.clone(), + node: c.node, }), } } @@ -282,6 +282,7 @@ pub struct VElement<'a> { pub dom_id: Cell>, + // Keep the parent id around so we can bubble events through the tree pub parent_id: Cell>, pub listeners: &'a [Listener<'a>], @@ -310,7 +311,7 @@ impl Debug for VElement<'_> { /// /// This trait provides the ability to use custom elements in the `rsx!` macro. /// -/// ```rust +/// ```rust, ignore /// struct my_element; /// /// impl DioxusElement for my_element { @@ -368,7 +369,6 @@ pub struct Listener<'bump> { pub(crate) callback: RefCell) + 'bump>>>, } -pub type VCompCaller<'src> = BumpBox<'src, dyn Fn(Context) -> Element + 'src>; /// Virtual Components for custom user-defined components /// Only supports the functional syntax pub struct VComponent<'src> { @@ -381,7 +381,7 @@ pub struct VComponent<'src> { pub(crate) can_memoize: bool, - pub(crate) hard_allocation: Cell>, + pub(crate) _hard_allocation: Cell>, // Raw pointer into the bump arena for the props of the component pub(crate) bump_props: *const (), @@ -426,16 +426,6 @@ impl PartialEq for NodeLink { self.node == other.node } } -impl NodeLink { - // we don't want to let users clone NodeLinks - pub(crate) fn clone_inner(&self) -> Self { - Self { - link_idx: self.link_idx.clone(), - scope_id: self.scope_id.clone(), - node: self.node.clone(), - } - } -} /// This struct provides an ergonomic API to quickly build VNodes. /// @@ -453,7 +443,7 @@ impl<'a> NodeFactory<'a> { #[inline] pub fn bump(&self) -> &'a bumpalo::Bump { - &self.bump + self.bump } /// Directly pass in text blocks without the need to use the format_args macro. @@ -591,14 +581,13 @@ impl<'a> NodeFactory<'a> { // - Because FC

is the same, then P must be the same (even with generics) // - Non-static P are autoderived to memoize as false // - This comparator is only called on a corresponding set of bumpframes - let props_memoized = unsafe { - let real_other: &P = &*(other.bump_props as *const _ as *const P); - props.memoize(real_other) - }; - + // // It's only okay to memoize if there are no children and the props can be memoized // Implementing memoize is unsafe and done automatically with the props trait - props_memoized + unsafe { + let real_other: &P = &*(other.bump_props as *const _ as *const P); + props.memoize(real_other) + } } else { false } @@ -633,8 +622,7 @@ impl<'a> NodeFactory<'a> { let caller: &'a mut dyn Fn(&'a Scope) -> Element = bump.alloc(move |scope: &Scope| -> Element { let props: &'_ P = unsafe { &*(bump_props as *const P) }; - let res = component(scope, props); - res + component(scope, props) }); let can_memoize = P::IS_STATIC; @@ -648,7 +636,7 @@ impl<'a> NodeFactory<'a> { can_memoize, drop_props, associated_scope: Cell::new(None), - hard_allocation: Cell::new(None), + _hard_allocation: Cell::new(None), })) } @@ -782,7 +770,7 @@ impl Debug for NodeFactory<'_> { /// /// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out. /// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type: -/// ``` +/// ```rust, ignore /// impl IntoIterator> /// ``` /// diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 5176b0fba..c8668a26e 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -43,12 +43,8 @@ pub type Context<'a> = &'a Scope; /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever /// use case they might have. pub struct Scope { - // safety: - // - // pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed - // this is just a bit of a hack to not need an Rc to the ScopeArena. - // todo: replace this will ScopeId and provide a connection to scope arena directly pub(crate) parent_scope: Option<*mut Scope>, + pub(crate) container: ElementId, pub(crate) our_arena_idx: ScopeId, @@ -60,23 +56,14 @@ pub struct Scope { pub(crate) generation: Cell, - // The double-buffering situation that we will use pub(crate) frames: [BumpFrame; 2], pub(crate) caller: *const dyn Fn(&Scope) -> Element, - /* - we care about: - - listeners (and how to call them when an event is triggered) - - borrowed props (and how to drop them when the parent is dropped) - - suspended nodes (and how to call their callback when their associated tasks are complete) - */ pub(crate) items: RefCell>, - // State pub(crate) hooks: HookList, - // todo: move this into a centralized place - is more memory efficient pub(crate) shared_contexts: RefCell>>, pub(crate) sender: UnboundedSender, @@ -108,7 +95,7 @@ impl Scope { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// @@ -126,7 +113,7 @@ impl Scope { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// @@ -146,7 +133,7 @@ impl Scope { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// @@ -155,10 +142,7 @@ impl Scope { /// assert_eq!(base.parent(), None); /// ``` pub fn parent(&self) -> Option { - match self.parent_scope { - Some(p) => Some(unsafe { &*p }.our_arena_idx), - None => None, - } + self.parent_scope.map(|p| unsafe { &*p }.our_arena_idx) } /// Get the ID of this Scope within this Dioxus VirtualDOM. @@ -167,7 +151,7 @@ impl Scope { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} })); /// dom.rebuild(); /// let base = dom.base_scope(); @@ -303,7 +287,7 @@ impl Scope { /// /// # Example /// - /// ``` + /// ```rust, ignore /// struct SharedState(&'static str); /// /// static App: FC<()> = |cx, props|{ @@ -311,7 +295,7 @@ impl Scope { /// rsx!(cx, Child {}) /// } /// - /// static Child: FC<()> = |cx, props|{ + /// static Child: FC<()> = |cx, props| { /// let state = cx.consume_state::(); /// rsx!(cx, div { "hello {state.0}" }) /// } @@ -351,7 +335,7 @@ impl Scope { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// fn App(cx: Context, props: &()) -> Element { /// todo!(); /// rsx!(cx, div { "Subtree {id}"}) @@ -375,7 +359,7 @@ impl Scope { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// fn App(cx: Context, props: &()) -> Element { /// let id = cx.get_current_subtree(); /// rsx!(cx, div { "Subtree {id}"}) @@ -437,6 +421,13 @@ impl Scope { } } + pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame { + match self.generation.get() & 1 == 0 { + true => &mut self.frames[0], + false => &mut self.frames[1], + } + } + pub(crate) fn fin_frame(&self) -> &BumpFrame { match self.generation.get() & 1 == 1 { true => &self.frames[0], @@ -444,14 +435,17 @@ impl Scope { } } - pub unsafe fn reset_wip_frame(&self) { + /// Reset this component's frame + /// + /// # Safety: + /// This method breaks every reference of VNodes in the current frame. + pub(crate) unsafe fn reset_wip_frame(&mut self) { // todo: unsafecell or something - let bump = self.wip_frame() as *const _ as *mut Bump; - let g = &mut *bump; - g.reset(); + let bump = self.wip_frame_mut(); + bump.bump.reset(); } - pub fn cycle_frame(&self) { + pub(crate) fn cycle_frame(&self) { self.generation.set(self.generation.get() + 1); } @@ -485,7 +479,7 @@ impl Scope { let mut nodes = &mut self.items.get_mut().suspended_nodes; if let Some(suspended) = nodes.remove(&task_id) { - let sus: &'a VSuspended<'static> = unsafe { &*suspended }; + let sus: &'a VSuspended<'static> = suspended; let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) }; let mut boxed = sus.callback.borrow_mut().take().unwrap(); let new_node: Element = boxed(); diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index c3c85d5d0..e6f3f8f20 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -28,45 +28,65 @@ pub(crate) struct ScopeArena { } impl ScopeArena { - pub fn new(sender: UnboundedSender) -> Self { + pub(crate) fn new(sender: UnboundedSender) -> Self { + let bump = Bump::new(); + + // allocate a container for the root element + // this will *never* show up in the diffing process + let el = bump.alloc(VElement { + tag_name: "root", + namespace: None, + key: None, + dom_id: Cell::new(Some(ElementId(0))), + parent_id: Default::default(), + listeners: &[], + attributes: &[], + children: &[], + }); + + let node = bump.alloc(VNode::Element(el)); + let mut nodes = Slab::new(); + let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) }); + debug_assert_eq!(root_id, 0); + Self { scope_counter: Cell::new(0), - bump: Bump::new(), + bump, scopes: RefCell::new(FxHashMap::default()), heuristics: RefCell::new(FxHashMap::default()), free_scopes: RefCell::new(Vec::new()), - nodes: RefCell::new(Slab::new()), + nodes: RefCell::new(nodes), sender, } } - pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> { + /// Safety: + /// - Obtaining a mutable refernece to any Scope is unsafe + /// - Scopes use interior mutability when sharing data into components + pub(crate) fn get_scope(&self, id: &ScopeId) -> Option<&Scope> { 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).map(|f| *f) + pub(crate) unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> { + self.scopes.borrow().get(id).copied() } - // this is unsafe - pub unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> { + pub(crate) unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> { self.scopes.borrow().get(id).map(|s| &mut **s) } - pub fn new_with_key( + pub(crate) fn new_with_key( &self, fc_ptr: *const (), caller: *const dyn Fn(&Scope) -> Element, parent_scope: Option<*mut Scope>, + container: ElementId, height: u32, subtree: u32, ) -> ScopeId { let new_scope_id = ScopeId(self.scope_counter.get()); self.scope_counter.set(self.scope_counter.get() + 1); - // - // if let Some(old_scope) = self.free_scopes.borrow_mut().pop() { let scope = unsafe { &mut *old_scope }; log::debug!( @@ -80,6 +100,7 @@ impl ScopeArena { scope.height = height; scope.subtree = Cell::new(subtree); scope.our_arena_idx = new_scope_id; + scope.container = container; scope.frames[0].nodes.get_mut().push({ let vnode = scope.frames[0] @@ -103,9 +124,8 @@ impl ScopeArena { unsafe { std::mem::transmute(vnode as *mut VNode) } }); - let r = self.scopes.borrow_mut().insert(new_scope_id, scope); - - assert!(r.is_none()); + let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope); + debug_assert!(any_item.is_none()); new_scope_id } else { @@ -144,6 +164,7 @@ impl ScopeArena { let scope = self.bump.alloc(Scope { sender: self.sender.clone(), + container, our_arena_idx: new_scope_id, parent_scope, height, @@ -166,12 +187,8 @@ impl ScopeArena { }), }); - 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)); + let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope); + debug_assert!(any_item.is_none()); new_scope_id } @@ -181,9 +198,11 @@ impl ScopeArena { self.ensure_drop_safety(id); log::debug!("removing scope {:?}", id); - println!("removing scope {:?}", id); - let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() }; + // Safety: + // - ensure_drop_safety ensures that no references to this scope are in use + // - this raw pointer is removed from the map + 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(); @@ -229,6 +248,11 @@ impl ScopeArena { id } + pub fn update_reservation(&self, node: &VNode, id: ElementId) { + let node = unsafe { std::mem::transmute::<*const VNode, *const VNode>(node) }; + *self.nodes.borrow_mut().get_mut(id.0).unwrap() = node; + } + pub fn collect_garbage(&self, id: ElementId) { self.nodes.borrow_mut().remove(id.0); } @@ -275,15 +299,15 @@ impl ScopeArena { } pub(crate) fn run_scope(&self, id: &ScopeId) -> bool { - 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. // Remove all the outdated listeners self.ensure_drop_safety(id); + let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") }; + + log::debug!("found scope, about to run: {:?}", id); + // Safety: // - We dropped the listeners, so no more &mut T can be used while these are held // - All children nodes that rely on &mut T are replaced with a new reference diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index c90fa0119..b937f4586 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -18,7 +18,7 @@ use std::{any::Any, collections::VecDeque}; /// /// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`]. /// -/// ```rust +/// ```rust, ignore /// #[derive(Props, PartialEq)] /// struct AppProps { /// title: String @@ -33,7 +33,7 @@ use std::{any::Any, collections::VecDeque}; /// /// Components may be composed to make complex apps. /// -/// ```rust +/// ```rust, ignore /// fn App(cx: Context, props: &AppProps) -> Element { /// cx.render(rsx!( /// NavBar { routes: ROUTES } @@ -46,14 +46,14 @@ use std::{any::Any, collections::VecDeque}; /// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to /// draw the UI. /// -/// ```rust +/// ```rust, ignore /// let mut vdom = VirtualDom::new(App); /// let edits = vdom.rebuild(); /// ``` /// /// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler. /// -/// ```rust +/// ```rust, ignore /// let channel = vdom.get_scheduler_channel(); /// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent { /// // ... @@ -62,7 +62,7 @@ use std::{any::Any, collections::VecDeque}; /// /// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom. /// -/// ```rust +/// ```rust, ignore /// vdom.wait_for_work().await; /// ``` /// @@ -70,7 +70,7 @@ use std::{any::Any, collections::VecDeque}; /// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be /// handled by the renderer. /// -/// ```rust +/// ```rust, ignore /// let mutations = vdom.work_with_deadline(|| false); /// for edit in mutations { /// apply(edit); @@ -81,7 +81,7 @@ use std::{any::Any, collections::VecDeque}; /// /// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above. /// -/// ```rust +/// ```rust, ignore /// fn App(cx: Context, props: &()) -> Element { /// cx.render(rsx!{ /// div { "Hello World" } @@ -106,7 +106,7 @@ use std::{any::Any, collections::VecDeque}; pub struct VirtualDom { base_scope: ScopeId, - _root_caller: *mut dyn Fn(&Scope) -> Element, + _root_props: Box, scopes: Box, @@ -134,7 +134,7 @@ impl VirtualDom { /// /// /// # Example - /// ``` + /// ```rust, ignore /// fn Example(cx: Context, props: &()) -> Element { /// cx.render(rsx!( div { "hello world" } )) /// } @@ -158,7 +158,7 @@ impl VirtualDom { /// /// /// # Example - /// ``` + /// ```rust, ignore /// #[derive(PartialEq, Props)] /// struct SomeProps { /// name: &'static str @@ -173,7 +173,7 @@ impl VirtualDom { /// /// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it. /// - /// ```rust + /// ```rust, ignore /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" }); /// let mutations = dom.rebuild(); /// ``` @@ -194,9 +194,9 @@ impl VirtualDom { ) -> Self { let scopes = ScopeArena::new(sender.clone()); - let caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) }); - let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller); - let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0); + let mut caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) }); + let caller_ref: *mut dyn Fn(&Scope) -> Element = caller.as_mut() as *mut _; + let base_scope = scopes.new_with_key(root as _, caller_ref, None, ElementId(0), 0, 0); let pending_messages = VecDeque::new(); let mut dirty_scopes = IndexSet::new(); @@ -206,8 +206,7 @@ impl VirtualDom { scopes: Box::new(scopes), base_scope, receiver, - // todo: clean this up manually? - _root_caller: caller_ref, + _root_props: caller, pending_messages, pending_futures: Default::default(), dirty_scopes, @@ -239,7 +238,7 @@ impl VirtualDom { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// /// /// ``` @@ -251,7 +250,7 @@ impl VirtualDom { /// /// # Example /// - /// ```rust + /// ```rust, ignore /// /// /// ``` @@ -359,7 +358,7 @@ impl VirtualDom { /// /// # Example /// - /// ```no_run + /// ```rust, ignore /// fn App(cx: Context, props: &()) -> Element { /// cx.render(rsx!( div {"hello"} )) /// } @@ -394,6 +393,7 @@ impl VirtualDom { while let Some(msg) = self.pending_messages.pop_back() { match msg { + // TODO: Suspsense SchedulerMsg::Immediate(id) => { self.dirty_scopes.insert(id); } @@ -447,6 +447,8 @@ impl VirtualDom { self.scopes.fin_head(&scopeid), ); diff_state.stack.scope_stack.push(scopeid); + let scope = scopes.get_scope(&scopeid).unwrap(); + diff_state.stack.element_stack.push(scope.container); diff_state.stack.push(DiffInstruction::Diff { new, old }); } } @@ -458,7 +460,6 @@ impl VirtualDom { let DiffState { mutations, seen_scopes, - stack, .. } = diff_state; @@ -490,7 +491,7 @@ impl VirtualDom { /// All state stored in components will be completely wiped away. /// /// # Example - /// ``` + /// ```rust, ignore /// static App: FC<()> = |cx, props| cx.render(rsx!{ "hello world" }); /// let mut dom = VirtualDom::new(); /// let edits = dom.rebuild(); @@ -506,6 +507,7 @@ impl VirtualDom { .stack .create_node(self.scopes.fin_head(&scope_id), MountType::Append); + diff_state.stack.element_stack.push(ElementId(0)); diff_state.stack.scope_stack.push(scope_id); diff_state.work(|| false); @@ -523,7 +525,7 @@ impl VirtualDom { /// /// /// # Example - /// ```rust + /// ```rust, ignore /// #[derive(PartialEq, Props)] /// struct AppProps { /// value: Shared<&'static str>, diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index fdb9786a6..fce8d3f15 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -36,16 +36,16 @@ fn test_original_diff() { assert_eq!( mutations.edits, [ - CreateElement { - root: 0, - tag: "div" - }, CreateElement { root: 1, tag: "div" }, - CreateTextNode { + CreateElement { root: 2, + tag: "div" + }, + CreateTextNode { + root: 3, text: "Hello, world!" }, AppendChildren { many: 1 }, @@ -81,32 +81,32 @@ fn create() { assert_eq!( mutations.edits, [ - CreateElement { - root: 0, - tag: "div" - }, CreateElement { root: 1, tag: "div" }, - CreateTextNode { - root: 2, - text: "Hello, world!" - }, CreateElement { - root: 3, + root: 2, tag: "div" }, + CreateTextNode { + root: 3, + text: "Hello, world!" + }, CreateElement { root: 4, tag: "div" }, - CreateTextNode { + CreateElement { root: 5, - text: "hello" + tag: "div" }, CreateTextNode { root: 6, + text: "hello" + }, + CreateTextNode { + root: 7, text: "world" }, AppendChildren { many: 2 }, @@ -136,29 +136,29 @@ fn create_list() { mutations.edits, [ CreateElement { - root: 0, - tag: "div" - }, - CreateTextNode { root: 1, - text: "hello" + tag: "div" }, - AppendChildren { many: 1 }, - CreateElement { + CreateTextNode { root: 2, - tag: "div" - }, - CreateTextNode { - root: 3, text: "hello" }, AppendChildren { many: 1 }, CreateElement { - root: 4, + root: 3, tag: "div" }, CreateTextNode { + root: 4, + text: "hello" + }, + AppendChildren { many: 1 }, + CreateElement { root: 5, + tag: "div" + }, + CreateTextNode { + root: 6, text: "hello" }, AppendChildren { many: 1 }, @@ -185,10 +185,6 @@ fn create_simple() { assert_eq!( mutations.edits, [ - CreateElement { - root: 0, - tag: "div" - }, CreateElement { root: 1, tag: "div" @@ -201,6 +197,10 @@ fn create_simple() { root: 3, tag: "div" }, + CreateElement { + root: 4, + tag: "div" + }, AppendChildren { many: 4 }, ] ); @@ -234,39 +234,39 @@ fn create_components() { assert_eq!( mutations.edits, [ - CreateElement { root: 0, tag: "h1" }, + CreateElement { root: 1, tag: "h1" }, CreateElement { - root: 1, + root: 2, tag: "div" }, CreateTextNode { - root: 2, + root: 3, text: "abc1" }, AppendChildren { many: 1 }, - CreateElement { root: 3, tag: "p" }, - CreateElement { root: 4, tag: "h1" }, + CreateElement { root: 4, tag: "p" }, + CreateElement { root: 5, tag: "h1" }, CreateElement { - root: 5, + root: 6, tag: "div" }, CreateTextNode { - root: 6, + root: 7, text: "abc2" }, AppendChildren { many: 1 }, - CreateElement { root: 7, tag: "p" }, - CreateElement { root: 8, tag: "h1" }, + CreateElement { root: 8, tag: "p" }, + CreateElement { root: 9, tag: "h1" }, CreateElement { - root: 9, + root: 10, tag: "div" }, CreateTextNode { - root: 10, + root: 11, text: "abc3" }, AppendChildren { many: 1 }, - CreateElement { root: 11, tag: "p" }, + CreateElement { root: 12, tag: "p" }, AppendChildren { many: 9 }, ] ); @@ -286,15 +286,15 @@ fn anchors() { mutations.edits, [ CreateElement { - root: 0, + root: 1, tag: "div" }, CreateTextNode { - root: 1, + root: 2, text: "hello" }, AppendChildren { many: 1 }, - CreatePlaceholder { root: 2 }, + CreatePlaceholder { root: 3 }, AppendChildren { many: 2 }, ] ); diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index b5568d74f..3bb4a526b 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -18,8 +18,6 @@ type Shared = Arc>; #[test] fn manual_diffing() { - test_logging::set_up_logging(IS_LOGGING_ENABLED); - struct AppProps { value: Shared<&'static str>, } @@ -73,9 +71,9 @@ fn events_generate() { let mut channel = dom.get_scheduler_channel(); assert!(dom.has_any_work()); - let edits = dom.work_with_deadline(|| false); + let edits = dom.rebuild(); assert_eq!( - edits[0].edits, + edits.edits, [ CreateElement { tag: "div", @@ -100,6 +98,7 @@ fn events_generate() { root: 3, }, AppendChildren { many: 2 }, + AppendChildren { many: 1 }, ] ) } diff --git a/packages/core/tests/set_state_batch.rs b/packages/core/tests/set_state_batch.rs deleted file mode 100644 index fc897102e..000000000 --- a/packages/core/tests/set_state_batch.rs +++ /dev/null @@ -1,41 +0,0 @@ -use futures_util::StreamExt; - -/* -furtures_channel provides us some batching simply due to how Rust's async works. - -Any hook that uses schedule_update is simply deferring to unbounded_send. Multiple -unbounded_sends can be linked together in succession provided there isn't an "await" -between them. Our internal batching mechanism simply waits for the "schedule_update" -to fire and then pulls any messages off the unbounded_send queue. - -Additionally, due to how our "time slicing" works we'll always come back and check -in for new work if the deadline hasn't expired. On average, our deadline should be -about 10ms, which is way more than enough for diffing/creating to happen. -*/ -#[async_std::test] -async fn batch() { - let (sender, mut recver) = futures_channel::mpsc::unbounded::(); - - let _handle = async_std::task::spawn(async move { - let _msg = recver.next().await; - while let Ok(msg) = recver.try_next() { - println!("{:#?}", msg); - } - let _msg = recver.next().await; - while let Ok(msg) = recver.try_next() { - println!("{:#?}", msg); - } - }); - - sender.unbounded_send(1).unwrap(); - sender.unbounded_send(2).unwrap(); - sender.unbounded_send(3).unwrap(); - sender.unbounded_send(4).unwrap(); - - async_std::task::sleep(std::time::Duration::from_millis(100)).await; - - sender.unbounded_send(5).unwrap(); - sender.unbounded_send(6).unwrap(); - sender.unbounded_send(7).unwrap(); - sender.unbounded_send(8).unwrap(); -} diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 9aa805918..b7fef7643 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -88,17 +88,3 @@ fn child_components() { let edits = vdom.rebuild(); dbg!(edits); } - -#[test] -fn suspended_works() { - todo!() - // static App: FC<()> = |cx, props| { - // let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!()); - // // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"}); - // cx.render(rsx!("hello" { title })) - // }; - - // let mut vdom = VirtualDom::new(App); - // let edits = vdom.rebuild(); - // dbg!(edits); -} diff --git a/packages/hooks/src/use_shared_state.rs b/packages/hooks/src/use_shared_state.rs index 56d0fab77..f9856eeaa 100644 --- a/packages/hooks/src/use_shared_state.rs +++ b/packages/hooks/src/use_shared_state.rs @@ -17,8 +17,6 @@ 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/src/lib.rs b/src/lib.rs index bc980a259..93c7d33b1 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -193,7 +193,7 @@ pub mod debug {} pub mod prelude { //! A glob import that includes helper types like FC, rsx!, html!, and required traits pub use dioxus_core::prelude::*; - pub use dioxus_core_macro::{format_args_f, rsx, Props}; + pub use dioxus_core_macro::{format_args_f, rsx, Props, Routable}; pub use dioxus_elements::{GlobalAttributes, SvgAttributes}; pub use dioxus_hooks::*; pub use dioxus_html as dioxus_elements;