From 2559740463d298d27a058607c5d80196dc736c11 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 7 Nov 2021 20:59:09 -0500 Subject: [PATCH 1/3] wip: clean up, use old approach to components --- packages/core/benches/jsframework.rs | 4 +- packages/core/examples/props_expand.rs | 9 +++ packages/core/examples/works.rs | 6 +- packages/core/src/component.rs | 38 +-------- packages/core/src/diff.rs | 5 +- packages/core/src/diff_stack.rs | 4 - packages/core/src/lib.rs | 8 +- packages/core/src/nodes.rs | 64 +++++++--------- packages/core/src/scope.rs | 24 ++---- packages/core/src/scopearena.rs | 75 +++++++++++++++--- packages/core/src/util.rs | 4 - packages/core/src/virtual_dom.rs | 102 ++++--------------------- packages/core/tests/borrowedstate.rs | 6 +- packages/core/tests/create_dom.rs | 35 ++++----- packages/core/tests/display_vdom.rs | 6 +- packages/core/tests/lifecycle.rs | 4 +- packages/core/tests/sharedstate.rs | 4 +- packages/core/tests/vdom_rebuild.rs | 29 +++---- 18 files changed, 180 insertions(+), 247 deletions(-) create mode 100644 packages/core/examples/props_expand.rs diff --git a/packages/core/benches/jsframework.rs b/packages/core/benches/jsframework.rs index d0c8aae77..a36d4a1e5 100644 --- a/packages/core/benches/jsframework.rs +++ b/packages/core/benches/jsframework.rs @@ -24,7 +24,7 @@ criterion_group!(mbenches, create_rows); criterion_main!(mbenches); fn create_rows(c: &mut Criterion) { - static App: FC<()> = |(cx, _)| { + static App: FC<()> = |cx, _| { let mut rng = SmallRng::from_entropy(); let rows = (0..10_000_usize).map(|f| { let label = Label::new(&mut rng); @@ -58,7 +58,7 @@ struct RowProps { row_id: usize, label: Label, } -fn Row((cx, props): Scope) -> Element { +fn Row(cx: Context, props: &RowProps) -> Element { let [adj, col, noun] = props.label.0; cx.render(rsx! { tr { diff --git a/packages/core/examples/props_expand.rs b/packages/core/examples/props_expand.rs new file mode 100644 index 000000000..29d41129e --- /dev/null +++ b/packages/core/examples/props_expand.rs @@ -0,0 +1,9 @@ +use dioxus_core as dioxus; +use dioxus_core_macro::*; + +fn main() {} + +#[derive(Props)] +struct ChildProps<'a> { + name: &'a str, +} diff --git a/packages/core/examples/works.rs b/packages/core/examples/works.rs index 1e4655589..fa02c3613 100644 --- a/packages/core/examples/works.rs +++ b/packages/core/examples/works.rs @@ -7,7 +7,7 @@ fn main() { let _ = VirtualDom::new(Parent); } -fn Parent((cx, _): Scope<()>) -> Element { +fn Parent(cx: Context, props: &()) -> Element { let value = cx.use_hook(|_| String::new(), |f| &*f); cx.render(rsx! { @@ -22,7 +22,7 @@ struct ChildProps<'a> { name: &'a str, } -fn Child((cx, props): Scope) -> Element { +fn Child(cx: Context, props: &ChildProps) -> Element { cx.render(rsx! { div { h1 { "it's nested" } @@ -36,7 +36,7 @@ struct Grandchild<'a> { name: &'a str, } -fn Child2((cx, props): Scope) -> Element { +fn Child2(cx: Context, props: &Grandchild) -> Element { cx.render(rsx! { div { "Hello {props.name}!" } }) diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index fceabb196..b3f82bf18 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -6,39 +6,6 @@ //! that ensures compile-time required and optional fields on cx. use crate::innerlude::{Context, Element, LazyNodes, ScopeChildren}; -/// A component is a wrapper around a Context and some Props that share a lifetime -/// -/// -/// # Example -/// -/// With memoized state: -/// ```rust -/// struct State {} -/// -/// fn Example((cx, props): Scope) -> DomTree { -/// // ... -/// } -/// ``` -/// -/// With borrowed state: -/// ```rust -/// struct State<'a> { -/// name: &'a str -/// } -/// -/// fn Example<'a>((cx, props): Scope<'a, State>) -> DomTree<'a> { -/// // ... -/// } -/// ``` -/// -/// With owned state as a closure: -/// ```rust -/// static Example: FC<()> = |(cx, props)| { -/// // ... -/// }; -/// ``` -/// -pub type Scope<'a, T> = (Context<'a>, &'a T); pub struct FragmentProps<'a> { children: ScopeChildren<'a>, @@ -99,7 +66,7 @@ impl<'a> Properties for FragmentProps<'a> { /// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it. /// #[allow(non_upper_case_globals, non_snake_case)] -pub fn Fragment<'a>((cx, props): Scope<'a, FragmentProps<'a>>) -> Element { +pub fn Fragment<'a>(cx: Context<'a>, props: &'a FragmentProps<'a>) -> Element { cx.render(Some(LazyNodes::new(|f| { f.fragment_from_iter(&props.children) }))) @@ -170,6 +137,7 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { +pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &T) -> Element) -> T::Builder { + // pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { T::builder() } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 98ba2f3fd..2fd66d014 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -90,6 +90,7 @@ use crate::innerlude::*; use fxhash::{FxHashMap, FxHashSet}; +use slab::Slab; use DomEdit::*; /// Our DiffMachine is an iterative tree differ. @@ -609,7 +610,7 @@ impl<'bump> ScopeArena { // make sure the component's caller function is up to date let scope = self.get_scope(&scope_addr).unwrap(); - scope.update_vcomp(new); + let mut items = scope.items.borrow_mut(); // React doesn't automatically memoize, but we do. let props_are_the_same = todo!("reworking component memoization"); @@ -1280,7 +1281,7 @@ impl<'bump> ScopeArena { } /// Adds a listener closure to a scope during diff. - fn attach_listener_to_scope(&'bump self, listener: &'bump Listener<'bump>, scope: &ScopeState) { + fn attach_listener_to_scope(&'bump self, listener: &'bump Listener<'bump>, scope: &Scope) { let long_listener = unsafe { std::mem::transmute(listener) }; scope.items.borrow_mut().listeners.push(long_listener) } diff --git a/packages/core/src/diff_stack.rs b/packages/core/src/diff_stack.rs index 8685824a1..8eaa42a5c 100644 --- a/packages/core/src/diff_stack.rs +++ b/packages/core/src/diff_stack.rs @@ -49,10 +49,6 @@ impl<'bump> DiffStack<'bump> { } } - pub fn is_empty(&self) -> bool { - self.instructions.is_empty() - } - pub fn pop(&mut self) -> Option> { self.instructions.pop() } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f8b632917..6c9ef181e 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -40,7 +40,7 @@ pub(crate) mod innerlude { pub use crate::virtual_dom::*; pub type Element = Option; - pub type FC

= for<'a> fn(Scope<'a, P>) -> Element; + pub type FC

= for<'a> fn(Context<'a>, &'a P) -> Element; } pub use crate::innerlude::{ @@ -50,9 +50,11 @@ pub use crate::innerlude::{ }; pub mod prelude { - pub use crate::component::{fc_to_builder, Fragment, Properties, Scope}; + pub use crate::component::{fc_to_builder, Fragment, Properties}; pub use crate::innerlude::Context; - pub use crate::innerlude::{DioxusElement, Element, LazyNodes, NodeFactory, ScopeChildren, FC}; + pub use crate::innerlude::{ + DioxusElement, Element, LazyNodes, NodeFactory, Scope, ScopeChildren, FC, + }; pub use crate::nodes::VNode; pub use crate::VirtualDom; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index d6988c503..0e245ff81 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::{ - innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId, ScopeState}, + innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId}, lazynodes::LazyNodes, }; use bumpalo::{boxed::Box as BumpBox, Bump}; @@ -344,31 +344,29 @@ 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> { pub key: Option<&'src str>, pub associated_scope: Cell>, - // pub associated_scope: Cell>, // Function pointer to the FC that was used to generate this component pub user_fc: *const (), + pub(crate) can_memoize: bool, + pub(crate) hard_allocation: Cell>, + // Raw pointer into the bump arena for the props of the component - pub(crate) raw_props: *const (), + pub(crate) bump_props: *const (), // during the "teardown" process we'll take the caller out so it can be dropped properly pub(crate) caller: Option>, pub(crate) comparator: Option bool + 'src>>, } -pub enum VCompCaller<'src> { - Borrowed(BumpBox<'src, dyn for<'b> Fn(&'b ScopeState) -> Element + 'src>), - Owned(Box Fn(&'b ScopeState) -> Element>), -} - pub struct VSuspended<'a> { pub task_id: u64, pub dom_id: Cell>, @@ -511,7 +509,7 @@ impl<'a> NodeFactory<'a> { pub fn component

( &self, - component: fn(Scope<'a, P>) -> Element, + component: fn(Context<'a>, &'a P) -> Element, props: P, key: Option, ) -> VNode<'a> @@ -530,18 +528,8 @@ impl<'a> NodeFactory<'a> { let bump = self.bump(); - // let p = BumpBox::new_in(x, a) - - // the best place to allocate the props are the other component's arena - // the second best place is the global allocator - - // // if the props are static - // let boxed = if P::IS_STATIC { - // todo!() - // } else { - // todo!() - // } - + // later, we'll do a hard allocation + let raw_ptr = bump.alloc(props); // let caller = Box::new(|f: &ScopeInner| -> Element { // // // component((f, &props)) @@ -574,26 +562,26 @@ impl<'a> NodeFactory<'a> { let key = key.map(|f| self.raw_text(f).0); - let caller = match P::IS_STATIC { - true => { - // it just makes sense to box the props - let boxed_props: Box

= Box::new(props); - let props_we_know_are_static = todo!(); - VCompCaller::Owned(Box::new(|f| { - // + // let caller = match P::IS_STATIC { + // true => { + // // it just makes sense to box the props + // let boxed_props: Box

= Box::new(props); + // let props_we_know_are_static = todo!(); + // VCompCaller::Owned(Box::new(|f| { + // // - let p = todo!(); + // let p = todo!(); - todo!() - })) - } - false => VCompCaller::Borrowed({ - // + // todo!() + // })) + // } + // false => VCompCaller::Borrowed({ + // // - todo!() - // let caller = bump.alloc() - }), - }; + // todo!() + // // let caller = bump.alloc() + // }), + // }; todo!() // let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index fd46f7476..3a826b8c7 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -30,7 +30,7 @@ use bumpalo::{boxed::Box as BumpBox, Bump}; /// cx.render(rsx!{ div {"Hello, {props.name}"} }) /// } /// ``` -pub type Context<'a> = &'a ScopeState; +pub type Context<'a> = &'a Scope; /// Every component in Dioxus is represented by a `Scope`. /// @@ -41,7 +41,7 @@ pub type Context<'a> = &'a ScopeState; /// /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever /// use case they might have. -pub struct ScopeState { +pub struct Scope { // Book-keeping about our spot in the arena // safety: @@ -49,7 +49,7 @@ pub struct ScopeState { // 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 ScopeState>, + pub(crate) parent_scope: Option<*mut Scope>, pub(crate) our_arena_idx: ScopeId, @@ -62,8 +62,6 @@ pub struct ScopeState { // The double-buffering situation that we will use pub(crate) frames: [Bump; 2], - pub(crate) vcomp: *const VComponent<'static>, - pub(crate) old_root: RefCell>, pub(crate) new_root: RefCell>, @@ -89,6 +87,8 @@ pub struct SelfReferentialItems<'a> { pub(crate) cached_nodes_old: Vec>, pub(crate) cached_nodes_new: Vec>, + pub(crate) caller: &'a dyn Fn(&Scope) -> Element, + pub(crate) generation: Cell, pub(crate) listeners: Vec<&'a Listener<'a>>, @@ -99,7 +99,7 @@ pub struct SelfReferentialItems<'a> { } // Public methods exposed to libraries and components -impl ScopeState { +impl Scope { /// Get the root VNode for this Scope. /// /// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment. @@ -440,7 +440,7 @@ Functions prefixed with "use" should never be called conditionally. "###; // Important internal methods -impl ScopeState { +impl Scope { /// Give out our self-referential item with our own borrowed lifetime pub(crate) fn fin_head<'b>(&'b self) -> &'b VNode<'b> { todo!() @@ -527,14 +527,4 @@ impl ScopeState { // Some(cur) // } } - - pub(crate) fn update_vcomp(&self, vcomp: &VComponent) { - let f: *const _ = vcomp; - todo!() - // self.vcomp = unsafe { std::mem::transmute(f) }; - } - - pub(crate) fn load_vcomp<'a>(&'a mut self) -> &'a VComponent<'a> { - unsafe { std::mem::transmute(&*self.vcomp) } - } } diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index 48732841c..a19600504 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -1,3 +1,4 @@ +use slab::Slab; use std::cell::{Cell, RefCell}; use bumpalo::{boxed::Box as BumpBox, Bump}; @@ -19,7 +20,7 @@ pub struct Heuristic { // has an internal heuristics engine to pre-allocate arenas to the right size pub(crate) struct ScopeArena { bump: Bump, - scopes: Vec<*mut ScopeState>, + scopes: Vec<*mut Scope>, free_scopes: Vec, pub(crate) sender: UnboundedSender, } @@ -30,19 +31,20 @@ impl ScopeArena { bump: Bump::new(), scopes: Vec::new(), free_scopes: Vec::new(), + sender, } } - pub fn get_scope(&self, id: &ScopeId) -> Option<&ScopeState> { + pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> { unsafe { Some(&*self.scopes[id.0]) } } pub fn new_with_key( &mut self, fc_ptr: *const (), - vcomp: &VComponent, - parent_scope: Option<*mut ScopeState>, + caller: *mut dyn Fn(&Scope) -> Element, + parent_scope: Option<*mut Scope>, height: u32, subtree: u32, ) -> ScopeId { @@ -55,9 +57,10 @@ impl ScopeArena { } else { let id = ScopeId(self.scopes.len()); - let vcomp = unsafe { std::mem::transmute(vcomp as *const VComponent) }; + // cast off the lifetime + let caller = unsafe { std::mem::transmute(caller) }; - let new_scope = ScopeState { + let new_scope = Scope { sender: self.sender.clone(), parent_scope, our_arena_idx: id, @@ -65,7 +68,6 @@ impl ScopeArena { subtree: Cell::new(subtree), is_subtree_root: Cell::new(false), frames: [Bump::default(), Bump::default()], - vcomp, hooks: Default::default(), shared_contexts: Default::default(), @@ -78,7 +80,8 @@ impl ScopeArena { pending_effects: Default::default(), cached_nodes_old: Default::default(), generation: Default::default(), - cached_nodes_new: todo!(), + cached_nodes_new: Default::default(), + caller, }), old_root: todo!(), new_root: todo!(), @@ -90,7 +93,7 @@ impl ScopeArena { } } - pub fn try_remove(&self, id: &ScopeId) -> Option { + pub fn try_remove(&self, id: &ScopeId) -> Option { todo!() } @@ -154,4 +157,58 @@ impl ScopeArena { .map(|li| unsafe { &*li }) .for_each(|listener| drop(listener.callback.borrow_mut().take())); } + + pub(crate) fn run_scope(&self, id: &ScopeId) -> bool { + let scope = self + .get_scope(id) + .expect("The base scope should never be moved"); + + // 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); + + // 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 + unsafe { scope.hooks.reset() }; + + // Safety: + // - We've dropped all references to the wip bump frame with "ensure_drop_safety" + unsafe { scope.reset_wip_frame() }; + + let mut items = scope.items.borrow_mut(); + + // just forget about our suspended nodes while we're at it + items.suspended_nodes.clear(); + + // guarantee that we haven't screwed up - there should be no latent references anywhere + debug_assert!(items.listeners.is_empty()); + debug_assert!(items.suspended_nodes.is_empty()); + debug_assert!(items.borrowed_props.is_empty()); + + log::debug!("Borrowed stuff is successfully cleared"); + + // temporarily cast the vcomponent to the right lifetime + // let vcomp = scope.load_vcomp(); + + let render: &dyn Fn(&Scope) -> Element = todo!(); + + // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. + if let Some(key) = render(scope) { + // todo!("attach the niode"); + // let new_head = builder.into_vnode(NodeFactory { + // bump: &scope.frames.wip_frame().bump, + // }); + // log::debug!("Render is successful"); + + // the user's component succeeded. We can safely cycle to the next frame + // scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; + // scope.frames.cycle_frame(); + + true + } else { + false + } + } } diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index fad669026..b0da2c425 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -7,10 +7,6 @@ pub fn empty_cell() -> Cell> { Cell::new(None) } -pub fn type_name_of(_: T) -> &'static str { - std::any::type_name::() -} - /// A component's unique identifier. /// /// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 59eed261c..9b164d128 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -20,21 +20,13 @@ //! Additional functionality is defined in the respective files. use crate::innerlude::*; -use bumpalo::Bump; use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender}; -use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt}; -use fxhash::FxHashMap; +use futures_util::{Future, StreamExt}; use fxhash::FxHashSet; use indexmap::IndexSet; -use slab::Slab; use std::pin::Pin; use std::task::Poll; -use std::{ - any::{Any, TypeId}, - cell::{Cell, UnsafeCell}, - collections::{HashSet, VecDeque}, - rc::Rc, -}; +use std::{any::Any, collections::VecDeque}; /// An integrated virtual node system that progresses events and diffs UI trees. /// @@ -69,10 +61,7 @@ use std::{ pub struct VirtualDom { base_scope: ScopeId, - _root_props: Rc, - - // we need to keep the allocation around, but we don't necessarily use it - _root_caller: Box, + _root_caller: *mut dyn Fn(&Scope) -> Element, pub(crate) scopes: ScopeArena, @@ -156,30 +145,22 @@ impl VirtualDom { sender: UnboundedSender, receiver: UnboundedReceiver, ) -> Self { - let mut scopes = ScopeArena::new(sender); + let mut scopes = ScopeArena::new(sender.clone()); - let base_scope = scopes.new_with_key( - // - root as _, - todo!(), - // boxed_comp.as_ref(), - None, - 0, - 0, - ); + let caller = Box::new(move |f: &Scope| -> Element { root(f, &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); Self { scopes, base_scope, receiver, - sender, - - _root_props: todo!(), - _root_caller: todo!(), - + // todo: clean this up manually? + _root_caller: caller_ref, pending_messages: VecDeque::new(), pending_futures: Default::default(), dirty_scopes: Default::default(), + sender, } } @@ -189,7 +170,7 @@ impl VirtualDom { /// directly. /// /// # Example - pub fn base_scope(&self) -> &ScopeState { + pub fn base_scope(&self) -> &Scope { self.get_scope(&self.base_scope).unwrap() } @@ -199,7 +180,7 @@ impl VirtualDom { /// /// /// - pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a ScopeState> { + pub fn get_scope<'a>(&'a self, id: &ScopeId) -> Option<&'a Scope> { self.scopes.get_scope(id) } @@ -402,7 +383,7 @@ impl VirtualDom { log::debug!("about to run scope {:?}", scopeid); - if self.run_scope(&scopeid) { + if self.scopes.run_scope(&scopeid) { let scope = self.scopes.get_scope(&scopeid).unwrap(); let (old, new) = (scope.wip_head(), scope.fin_head()); diff_state.stack.scope_stack.push(scopeid); @@ -503,7 +484,7 @@ impl VirtualDom { pub fn hard_diff<'a>(&'a mut self, scope_id: &ScopeId) -> Option> { log::debug!("hard diff {:?}", scope_id); - if self.run_scope(scope_id) { + if self.scopes.run_scope(scope_id) { let mut diff_machine = DiffState::new(Mutations::new()); diff_machine.force_diff = true; @@ -515,61 +496,6 @@ impl VirtualDom { None } } - - fn run_scope(&self, id: &ScopeId) -> bool { - let scope = self - .scopes - .get_scope(id) - .expect("The base scope should never be moved"); - - // 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.scopes.ensure_drop_safety(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 - unsafe { scope.hooks.reset() }; - - // Safety: - // - We've dropped all references to the wip bump frame with "ensure_drop_safety" - unsafe { scope.reset_wip_frame() }; - - let mut items = scope.items.borrow_mut(); - - // just forget about our suspended nodes while we're at it - items.suspended_nodes.clear(); - - // guarantee that we haven't screwed up - there should be no latent references anywhere - debug_assert!(items.listeners.is_empty()); - debug_assert!(items.suspended_nodes.is_empty()); - debug_assert!(items.borrowed_props.is_empty()); - - log::debug!("Borrowed stuff is successfully cleared"); - - // temporarily cast the vcomponent to the right lifetime - // let vcomp = scope.load_vcomp(); - - let render: &dyn Fn(&ScopeState) -> Element = todo!(); - - // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. - if let Some(key) = render(scope) { - // todo!("attach the niode"); - // let new_head = builder.into_vnode(NodeFactory { - // bump: &scope.frames.wip_frame().bump, - // }); - // log::debug!("Render is successful"); - - // the user's component succeeded. We can safely cycle to the next frame - // scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; - // scope.frames.cycle_frame(); - - true - } else { - false - } - } } pub enum SchedulerMsg { diff --git a/packages/core/tests/borrowedstate.rs b/packages/core/tests/borrowedstate.rs index b12e411a2..692a8f8db 100644 --- a/packages/core/tests/borrowedstate.rs +++ b/packages/core/tests/borrowedstate.rs @@ -10,7 +10,7 @@ fn test_borrowed_state() { let _ = VirtualDom::new(Parent); } -fn Parent((cx, _): Scope<()>) -> Element { +fn Parent(cx: Context, props: &()) -> Element { let value = cx.use_hook(|_| String::new(), |f| &*f); cx.render(rsx! { @@ -28,7 +28,7 @@ struct ChildProps<'a> { name: &'a str, } -fn Child((cx, props): Scope) -> Element { +fn Child(cx: Context, props: &ChildProps) -> Element { cx.render(rsx! { div { h1 { "it's nested" } @@ -42,7 +42,7 @@ struct Grandchild<'a> { name: &'a str, } -fn Child2((cx, props): Scope) -> Element { +fn Child2(cx: Context, props: &Grandchild) -> Element { cx.render(rsx! { div { "Hello {props.name}!" } }) diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 1d7e0b9bf..49a10fb21 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -21,7 +21,7 @@ fn new_dom(app: FC

, props: P) -> VirtualDom { #[test] fn test_original_diff() { - static APP: FC<()> = |(cx, props)| { + static APP: FC<()> = |cx, props| { cx.render(rsx! { div { div { @@ -57,7 +57,7 @@ fn test_original_diff() { #[test] fn create() { - static APP: FC<()> = |(cx, props)| { + static APP: FC<()> = |cx, props| { cx.render(rsx! { div { div { @@ -120,7 +120,7 @@ fn create() { #[test] fn create_list() { - static APP: FC<()> = |(cx, props)| { + static APP: FC<()> = |cx, props| { cx.render(rsx! { {(0..3).map(|f| rsx!{ div { "hello" @@ -169,7 +169,7 @@ fn create_list() { #[test] fn create_simple() { - static APP: FC<()> = |(cx, props)| { + static APP: FC<()> = |cx, props| { cx.render(rsx! { div {} div {} @@ -207,7 +207,7 @@ fn create_simple() { } #[test] fn create_components() { - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { cx.render(rsx! { Child { "abc1" } Child { "abc2" } @@ -220,7 +220,7 @@ fn create_components() { children: ScopeChildren<'a>, } - fn Child<'a>((cx, props): Scope<'a, ChildProps<'a>>) -> Element { + fn Child<'a>(cx: Context<'a>, props: &ChildProps<'a>) -> Element { cx.render(rsx! { h1 {} div { {&props.children} } @@ -273,7 +273,7 @@ fn create_components() { } #[test] fn anchors() { - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { cx.render(rsx! { {true.then(|| rsx!{ div { "hello" } })} {false.then(|| rsx!{ div { "goodbye" } })} @@ -302,17 +302,18 @@ fn anchors() { #[test] fn suspended() { - static App: FC<()> = |(cx, props)| { - let val = use_suspense(cx, || async {}, |p| todo!()); + todo!() + // static App: FC<()> = |cx, props| { + // let val = use_suspense(cx, || async {}, |p| todo!()); - cx.render(rsx! { {val} }) - }; + // cx.render(rsx! { {val} }) + // }; - let mut dom = new_dom(App, ()); - let mutations = dom.rebuild(); + // let mut dom = new_dom(App, ()); + // let mutations = dom.rebuild(); - assert_eq!( - mutations.edits, - [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },] - ); + // assert_eq!( + // mutations.edits, + // [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 },] + // ); } diff --git a/packages/core/tests/display_vdom.rs b/packages/core/tests/display_vdom.rs index c7acc712c..ee32f27c0 100644 --- a/packages/core/tests/display_vdom.rs +++ b/packages/core/tests/display_vdom.rs @@ -13,7 +13,7 @@ mod test_logging; #[test] fn please_work() { - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { cx.render(rsx! { div { hidden: "true" @@ -27,7 +27,7 @@ fn please_work() { }) }; - static Child: FC<()> = |(cx, props)| { + static Child: FC<()> = |cx, props| { cx.render(rsx! { div { "child" } }) @@ -35,6 +35,4 @@ fn please_work() { let mut dom = VirtualDom::new(App); dom.rebuild(); - - println!("{}", dom); } diff --git a/packages/core/tests/lifecycle.rs b/packages/core/tests/lifecycle.rs index 3b9c05cca..1e65f8f58 100644 --- a/packages/core/tests/lifecycle.rs +++ b/packages/core/tests/lifecycle.rs @@ -20,7 +20,7 @@ fn manual_diffing() { value: Shared<&'static str>, } - static App: FC = |(cx, props)| { + static App: FC = |cx, props| { let val = props.value.lock().unwrap(); cx.render(rsx! { div { "{val}" } }) }; @@ -37,7 +37,7 @@ fn manual_diffing() { *value.lock().unwrap() = "goodbye"; - let edits = dom.diff(); + let edits = dom.rebuild(); log::debug!("edits: {:?}", edits); } diff --git a/packages/core/tests/sharedstate.rs b/packages/core/tests/sharedstate.rs index 7bb9e6501..5a2e8d042 100644 --- a/packages/core/tests/sharedstate.rs +++ b/packages/core/tests/sharedstate.rs @@ -13,12 +13,12 @@ mod test_logging; fn shared_state_test() { struct MySharedState(&'static str); - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { cx.provide_state(MySharedState("world!")); cx.render(rsx!(Child {})) }; - static Child: FC<()> = |(cx, props)| { + static Child: FC<()> = |cx, props| { let shared = cx.consume_state::()?; cx.render(rsx!("Hello, {shared.0}")) }; diff --git a/packages/core/tests/vdom_rebuild.rs b/packages/core/tests/vdom_rebuild.rs index 79aaad112..9aa805918 100644 --- a/packages/core/tests/vdom_rebuild.rs +++ b/packages/core/tests/vdom_rebuild.rs @@ -17,7 +17,7 @@ use dioxus_html as dioxus_elements; #[test] fn app_runs() { - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { // cx.render(rsx!( div{"hello"} )) }; @@ -28,7 +28,7 @@ fn app_runs() { #[test] fn fragments_work() { - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { cx.render(rsx!( div{"hello"} div{"goodbye"} @@ -42,7 +42,7 @@ fn fragments_work() { #[test] fn lists_work() { - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} {(0..6).map(|f| rsx!(span{ "{f}" }))} @@ -55,7 +55,7 @@ fn lists_work() { #[test] fn conditional_rendering() { - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} {true.then(|| rsx!(span{ "a" }))} @@ -72,13 +72,13 @@ fn conditional_rendering() { #[test] fn child_components() { - static App: FC<()> = |(cx, props)| { + static App: FC<()> = |cx, props| { cx.render(rsx!( {true.then(|| rsx!(Child { }))} {false.then(|| rsx!(Child { }))} )) }; - static Child: FC<()> = |(cx, props)| { + static Child: FC<()> = |cx, props| { cx.render(rsx!( h1 {"hello"} h1 {"goodbye"} @@ -91,13 +91,14 @@ fn child_components() { #[test] fn suspended_works() { - 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 })) - }; + 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); + // let mut vdom = VirtualDom::new(App); + // let edits = vdom.rebuild(); + // dbg!(edits); } From 15c31d545a84f05fe17254009dc275aac93e7574 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 7 Nov 2021 22:36:57 -0500 Subject: [PATCH 2/3] wip: fix rebuild --- packages/core/src/diff.rs | 24 +++--- packages/core/src/nodes.rs | 19 +++-- packages/core/src/scope.rs | 115 ++++++++++++------------- packages/core/src/scopearena.rs | 142 ++++++++++++++++++++++++------- packages/core/src/virtual_dom.rs | 27 ++++-- 5 files changed, 207 insertions(+), 120 deletions(-) diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 2fd66d014..4f923af24 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -125,11 +125,13 @@ impl<'bump> DiffState<'bump> { impl<'bump> ScopeArena { pub fn diff_scope(&'bump self, state: &mut DiffState<'bump>, id: &ScopeId) { - if let Some(component) = self.get_scope(id) { - let (old, new) = (component.wip_head(), component.fin_head()); - state.stack.push(DiffInstruction::Diff { new, old }); - self.work(state, || false); - } + // if let Some(component) = self.get_scope(id) { + + let (old, new) = (self.wip_head(id), self.fin_head(id)); + + state.stack.push(DiffInstruction::Diff { old, new }); + self.work(state, || false); + // } } /// Progress the diffing for this "fiber" @@ -1146,8 +1148,8 @@ impl<'bump> ScopeArena { } VNode::Component(el) => { let scope_id = el.associated_scope.get().unwrap(); - let scope = self.get_scope(&scope_id).unwrap(); - search_node = Some(scope.root_node()); + // let scope = self.get_scope(&scope_id).unwrap(); + search_node = Some(self.root_node(&scope_id)); } } } @@ -1164,8 +1166,8 @@ impl<'bump> ScopeArena { } VNode::Component(el) => { let scope_id = el.associated_scope.get().unwrap(); - let scope = self.get_scope(&scope_id).unwrap(); - search_node = Some(scope.root_node()); + // let scope = self.get_scope(&scope_id).unwrap(); + search_node = Some(self.root_node(&scope_id)); } VNode::Linked(link) => { todo!("linked") @@ -1249,8 +1251,8 @@ impl<'bump> ScopeArena { VNode::Component(c) => { let scope_id = c.associated_scope.get().unwrap(); - let scope = self.get_scope(&scope_id).unwrap(); - let root = scope.root_node(); + // let scope = self.get_scope(&scope_id).unwrap(); + let root = self.root_node(&scope_id); self.remove_nodes(state, Some(root), gen_muts); log::debug!("Destroying scope {:?}", scope_id); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0e245ff81..9a240f63b 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -20,7 +20,9 @@ use std::{ /// /// It is used during the diffing/rendering process as a runtime key into an existing set of nodes. The "render" key /// is essentially a unique key to guarantee safe usage of the Node. +#[derive(Clone, Debug)] pub struct NodeLink { + pub(crate) link_idx: usize, pub(crate) gen_id: u32, pub(crate) scope_id: ScopeId, } @@ -198,6 +200,7 @@ impl<'src> VNode<'src> { VNode::Linked(c) => VNode::Linked(NodeLink { gen_id: c.gen_id, scope_id: c.scope_id, + link_idx: c.link_idx, }), } } @@ -207,20 +210,22 @@ impl Debug for VNode<'_> { fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { match &self { VNode::Element(el) => s - .debug_struct("VElement") + .debug_struct("VNode::VElement") .field("name", &el.tag_name) .field("key", &el.key) .finish(), - VNode::Text(t) => write!(s, "VText {{ text: {} }}", t.text), - VNode::Anchor(_) => write!(s, "VAnchor"), + VNode::Text(t) => write!(s, "VNode::VText {{ text: {} }}", t.text), + VNode::Anchor(_) => write!(s, "VNode::VAnchor"), - VNode::Fragment(frag) => write!(s, "VFragment {{ children: {:?} }}", frag.children), - VNode::Suspended { .. } => write!(s, "VSuspended"), - VNode::Component(comp) => write!(s, "VComponent {{ fc: {:?}}}", comp.user_fc), + VNode::Fragment(frag) => { + write!(s, "VNode::VFragment {{ children: {:?} }}", frag.children) + } + VNode::Suspended { .. } => write!(s, "VNode::VSuspended"), + VNode::Component(comp) => write!(s, "VNode::VComponent {{ fc: {:?}}}", comp.user_fc), VNode::Linked(c) => write!( s, - "VCached {{ gen_id: {}, scope_id: {:?} }}", + "VNode::VCached {{ gen_id: {}, scope_id: {:?} }}", c.gen_id, c.scope_id ), } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 3a826b8c7..789946016 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -59,12 +59,16 @@ pub struct Scope { pub(crate) is_subtree_root: Cell, + pub(crate) generation: Cell, + // The double-buffering situation that we will use - pub(crate) frames: [Bump; 2], + pub(crate) frames: [BumpFrame; 2], pub(crate) old_root: RefCell>, pub(crate) new_root: RefCell>, + pub(crate) caller: *mut dyn Fn(&Scope) -> Element, + /* we care about: - listeners (and how to call them when an event is triggered) @@ -83,14 +87,6 @@ pub struct Scope { } pub struct SelfReferentialItems<'a> { - // nodes stored by "cx.render" - pub(crate) cached_nodes_old: Vec>, - pub(crate) cached_nodes_new: Vec>, - - pub(crate) caller: &'a dyn Fn(&Scope) -> Element, - - pub(crate) generation: Cell, - pub(crate) listeners: Vec<&'a Listener<'a>>, pub(crate) borrowed_props: Vec<&'a VComponent<'a>>, pub(crate) suspended_nodes: FxHashMap>, @@ -100,25 +96,6 @@ pub struct SelfReferentialItems<'a> { // Public methods exposed to libraries and components impl Scope { - /// Get the root VNode for this Scope. - /// - /// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment. - /// - /// # Example - /// ```rust - /// let mut dom = VirtualDom::new(|(cx, props)|cx.render(rsx!{ div {} })); - /// dom.rebuild(); - /// - /// let base = dom.base_scope(); - /// - /// if let VNode::VElement(node) = base.root_node() { - /// assert_eq!(node.tag_name, "div"); - /// } - /// ``` - pub fn root_node(&self) -> &VNode { - self.fin_head() - } - /// Get the subtree ID that this scope belongs to. /// /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route @@ -239,7 +216,7 @@ impl Scope { /// /// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted. pub fn bump(&self) -> &Bump { - &self.wip_frame() + &self.wip_frame().bump } /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator. @@ -258,18 +235,18 @@ impl Scope { /// } ///``` pub fn render<'src>(&'src self, lazy_nodes: Option>) -> Option { - let bump = &self.wip_frame(); + let bump = &self.wip_frame().bump; let factory = NodeFactory { bump }; let node = lazy_nodes.map(|f| f.call(factory))?; - self.items - .borrow_mut() - .cached_nodes_old - .push(unsafe { std::mem::transmute(node) }); + let idx = self + .wip_frame() + .add_node(unsafe { std::mem::transmute(node) }); Some(NodeLink { - gen_id: self.items.borrow().generation.get(), + gen_id: self.generation.get(), scope_id: self.our_arena_idx, + link_idx: idx, }) } @@ -427,11 +404,7 @@ impl Scope { self.hooks.push_hook(initializer(self.hooks.len())); } - runner(self.hooks.next::().expect(HOOK_ERR_MSG)) - } -} - -const HOOK_ERR_MSG: &str = r###" + const HOOK_ERR_MSG: &str = r###" Unable to retrieve the hook that was initialized at this index. Consult the `rules of hooks` to understand how to use hooks properly. @@ -439,29 +412,24 @@ You likely used the hook in a conditional. Hooks rely on consistent ordering bet Functions prefixed with "use" should never be called conditionally. "###; + runner(self.hooks.next::().expect(HOOK_ERR_MSG)) + } +} + // Important internal methods impl Scope { - /// Give out our self-referential item with our own borrowed lifetime - pub(crate) fn fin_head<'b>(&'b self) -> &'b VNode<'b> { - todo!() - // let cur_head = &self.finished_frame().head_node; - // unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) } - } - - /// Give out our self-referential item with our own borrowed lifetime - pub(crate) fn wip_head<'b>(&'b self) -> &'b VNode<'b> { - todo!() - // let cur_head = &self.wip_frame().head_node; - // unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) } - } - /// The "work in progress frame" represents the frame that is currently being worked on. - pub(crate) fn wip_frame(&self) -> &Bump { - todo!() - // match self.cur_generation.get() & 1 == 0 { - // true => &self.frames[0], - // false => &self.frames[1], - // } + pub(crate) fn wip_frame(&self) -> &BumpFrame { + match self.generation.get() & 1 == 0 { + true => &self.frames[0], + false => &self.frames[1], + } + } + pub(crate) fn fin_frame(&self) -> &BumpFrame { + match self.generation.get() & 1 == 1 { + true => &self.frames[0], + false => &self.frames[1], + } } pub unsafe fn reset_wip_frame(&self) { @@ -469,8 +437,10 @@ impl Scope { let bump = self.wip_frame() as *const _ as *mut Bump; let g = &mut *bump; g.reset(); + } - // self.wip_frame_mut().bump.reset() + pub fn cycle_frame(&self) { + self.generation.set(self.generation.get() + 1); } /// A safe wrapper around calling listeners @@ -528,3 +498,26 @@ impl Scope { // } } } + +pub struct BumpFrame { + pub bump: Bump, + pub nodes: RefCell>>, +} +impl BumpFrame { + pub fn new() -> Self { + let bump = Bump::new(); + + let node = &*bump.alloc(VText { + text: "asd", + dom_id: Default::default(), + is_static: false, + }); + let nodes = RefCell::new(vec![VNode::Text(unsafe { std::mem::transmute(node) })]); + Self { bump, nodes } + } + fn add_node(&self, node: VNode<'static>) -> usize { + let mut nodes = self.nodes.borrow_mut(); + nodes.push(node); + nodes.len() - 1 + } +} diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index a19600504..8f9050159 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -1,5 +1,8 @@ use slab::Slab; -use std::cell::{Cell, RefCell}; +use std::{ + borrow::BorrowMut, + cell::{Cell, RefCell}, +}; use bumpalo::{boxed::Box as BumpBox, Bump}; use futures_channel::mpsc::UnboundedSender; @@ -22,6 +25,7 @@ pub(crate) struct ScopeArena { bump: Bump, scopes: Vec<*mut Scope>, free_scopes: Vec, + nodes: RefCell>>, pub(crate) sender: UnboundedSender, } @@ -31,7 +35,7 @@ impl ScopeArena { bump: Bump::new(), scopes: Vec::new(), free_scopes: Vec::new(), - + nodes: RefCell::new(Slab::new()), sender, } } @@ -55,41 +59,49 @@ impl ScopeArena { todo!("override the scope contents"); id } else { - let id = ScopeId(self.scopes.len()); + let scope_id = ScopeId(self.scopes.len()); - // cast off the lifetime - let caller = unsafe { std::mem::transmute(caller) }; + let old_root = NodeLink { + link_idx: 0, + gen_id: 0, + scope_id, + }; + let new_root = NodeLink { + link_idx: 0, + gen_id: 0, + scope_id, + }; let new_scope = Scope { sender: self.sender.clone(), parent_scope, - our_arena_idx: id, + our_arena_idx: scope_id, height, subtree: Cell::new(subtree), is_subtree_root: Cell::new(false), - frames: [Bump::default(), Bump::default()], + frames: [BumpFrame::new(), BumpFrame::new()], hooks: Default::default(), shared_contexts: Default::default(), + caller, + generation: 0.into(), + old_root: RefCell::new(Some(old_root)), + new_root: RefCell::new(Some(new_root)), + // old_root: RefCell::new(Some(old_root)), + // new_root: RefCell::new(None), items: RefCell::new(SelfReferentialItems { listeners: Default::default(), borrowed_props: Default::default(), suspended_nodes: Default::default(), tasks: Default::default(), pending_effects: Default::default(), - cached_nodes_old: Default::default(), - generation: Default::default(), - cached_nodes_new: Default::default(), - caller, }), - old_root: todo!(), - new_root: todo!(), }; let stable = self.bump.alloc(new_scope); self.scopes.push(stable); - id + scope_id } } @@ -98,8 +110,20 @@ impl ScopeArena { } pub fn reserve_node(&self, node: &VNode) -> ElementId { - todo!() - // self.node_reservations.insert(id); + let mut els = self.nodes.borrow_mut(); + let entry = els.vacant_entry(); + let key = entry.key(); + let id = ElementId(key); + let node = node as *const _; + let node = unsafe { std::mem::transmute(node) }; + entry.insert(node); + id + + // let nodes = self.nodes.borrow_mut(); + // let id = nodes.insert(()); + // let node_id = ElementId(id); + // node = Some(node_id); + // node_id } pub fn collect_garbage(&self, id: ElementId) { @@ -177,38 +201,90 @@ impl ScopeArena { // - We've dropped all references to the wip bump frame with "ensure_drop_safety" unsafe { scope.reset_wip_frame() }; - let mut items = scope.items.borrow_mut(); + { + let mut items = scope.items.borrow_mut(); - // just forget about our suspended nodes while we're at it - items.suspended_nodes.clear(); + // just forget about our suspended nodes while we're at it + items.suspended_nodes.clear(); - // guarantee that we haven't screwed up - there should be no latent references anywhere - debug_assert!(items.listeners.is_empty()); - debug_assert!(items.suspended_nodes.is_empty()); - debug_assert!(items.borrowed_props.is_empty()); + // guarantee that we haven't screwed up - there should be no latent references anywhere + debug_assert!(items.listeners.is_empty()); + debug_assert!(items.suspended_nodes.is_empty()); + debug_assert!(items.borrowed_props.is_empty()); - log::debug!("Borrowed stuff is successfully cleared"); + log::debug!("Borrowed stuff is successfully cleared"); - // temporarily cast the vcomponent to the right lifetime - // let vcomp = scope.load_vcomp(); + // temporarily cast the vcomponent to the right lifetime + // let vcomp = scope.load_vcomp(); + } - let render: &dyn Fn(&Scope) -> Element = todo!(); + let render: &dyn Fn(&Scope) -> Element = unsafe { &*scope.caller }; // Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders. + scope.wip_frame().nodes.borrow_mut().clear(); if let Some(key) = render(scope) { - // todo!("attach the niode"); - // let new_head = builder.into_vnode(NodeFactory { - // bump: &scope.frames.wip_frame().bump, - // }); - // log::debug!("Render is successful"); + dbg!(key); + + dbg!(&scope.wip_frame().nodes.borrow_mut()); + // let mut old = scope.old_root.borrow_mut(); + // let mut new = scope.new_root.borrow_mut(); + + // let new_old = new.clone(); + // *old = new_old; + // *new = Some(key); + + // dbg!(&old); + // dbg!(&new); // the user's component succeeded. We can safely cycle to the next frame // scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; - // scope.frames.cycle_frame(); + scope.cycle_frame(); true } else { false } } + + 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 node = nodes.get(0).unwrap(); + unsafe { std::mem::transmute(node) } + // let root = scope.old_root.borrow(); + // let link = root.as_ref().unwrap(); + // dbg!(link); + + // // for now, components cannot pass portals through each other + // assert_eq!(link.scope_id, *id); + // // assert_eq!(link.gen_id, scope.generation.get() - 1); + + // // let items = scope.items.borrow(); + // let nodes = scope.wip_frame().nodes.borrow(); + // let node = nodes.get(link.link_idx).unwrap(); + // unsafe { std::mem::transmute(node) } + } + + 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 node = nodes.get(0).unwrap(); + unsafe { std::mem::transmute(node) } + // let scope = self.get_scope(id).unwrap(); + // let root = scope.new_root.borrow(); + // let link = root.as_ref().unwrap(); + + // // for now, components cannot pass portals through each other + // assert_eq!(link.scope_id, *id); + // // assert_eq!(link.gen_id, scope.generation.get()); + + // let nodes = scope.fin_frame().nodes.borrow(); + // let node = nodes.get(link.link_idx).unwrap(); + // unsafe { std::mem::transmute(node) } + } + pub fn root_node(&self, id: &ScopeId) -> &VNode { + self.wip_head(id) + } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 9b164d128..6eb620d9e 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -384,8 +384,10 @@ impl VirtualDom { log::debug!("about to run scope {:?}", scopeid); if self.scopes.run_scope(&scopeid) { - let scope = self.scopes.get_scope(&scopeid).unwrap(); - let (old, new) = (scope.wip_head(), scope.fin_head()); + let (old, new) = ( + self.scopes.wip_head(&scopeid), + self.scopes.fin_head(&scopeid), + ); diff_state.stack.scope_stack.push(scopeid); diff_state.stack.push(DiffInstruction::Diff { new, old }); } @@ -438,13 +440,20 @@ impl VirtualDom { /// apply_edits(edits); /// ``` pub fn rebuild(&mut self) -> Mutations { - // todo: I think we need to append a node or something - // diff_machine - // .stack - // .create_node(cur_component.frames.fin_head(), MountType::Append); + let mut diff_machine = DiffState::new(Mutations::new()); - let scope = self.base_scope; - self.hard_diff(&scope).unwrap() + let scope_id = self.base_scope; + if self.scopes.run_scope(&scope_id) { + diff_machine + .stack + .create_node(self.scopes.fin_head(&scope_id), MountType::Append); + + diff_machine.stack.scope_stack.push(scope_id); + + self.scopes.work(&mut diff_machine, || false); + } + + diff_machine.mutations } /// Compute a manual diff of the VirtualDOM between states. @@ -491,6 +500,8 @@ impl VirtualDom { self.scopes.diff_scope(&mut diff_machine, scope_id); + dbg!(&diff_machine.mutations); + Some(diff_machine.mutations) } else { None From fffc7ea061922120cd2288982a310a87691948cd Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sun, 7 Nov 2021 22:45:41 -0500 Subject: [PATCH 3/3] wip: working on re-enabling components --- packages/core/examples/works.rs | 1 + packages/core/src/component.rs | 2 +- packages/core/src/nodes.rs | 143 ++++++++++++++---------------- packages/core/tests/create_dom.rs | 8 +- 4 files changed, 75 insertions(+), 79 deletions(-) diff --git a/packages/core/examples/works.rs b/packages/core/examples/works.rs index fa02c3613..45104daa0 100644 --- a/packages/core/examples/works.rs +++ b/packages/core/examples/works.rs @@ -13,6 +13,7 @@ fn Parent(cx: Context, props: &()) -> Element { cx.render(rsx! { div { Child { name: value } + Fragment { "asd" } } }) } diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index b3f82bf18..82818ab62 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -137,7 +137,7 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. -pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &T) -> Element) -> T::Builder { +pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &'a T) -> Element) -> T::Builder { // pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { T::builder() } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 9a240f63b..a81aac3b6 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -531,89 +531,84 @@ impl<'a> NodeFactory<'a> { - if the props aren't static, then we convert them into a box which we pass off between renders */ + // let bump = self.bump(); + + // // later, we'll do a hard allocation + // let raw_ptr = bump.alloc(props); let bump = self.bump(); - - // later, we'll do a hard allocation - let raw_ptr = bump.alloc(props); - // let caller = Box::new(|f: &ScopeInner| -> Element { - // // - // component((f, &props)) - // }); - + let props = bump.alloc(props); + let bump_props = props as *mut P as *mut (); let user_fc = component as *const (); - // let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| { - // move |other: &VComponent| { - // if user_fc == other.user_fc { - // // Safety - // // - We guarantee that FC

is the same by function pointer - // // - 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.raw_props as *const _ as *const P); - // props.memoize(real_other) - // }; + let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| { + move |other: &VComponent| { + if user_fc == other.user_fc { + // Safety + // - We guarantee that FC

is the same by function pointer + // - 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 - // } else { - // false - // } - // } - // }); - // let comparator = Some(unsafe { BumpBox::from_raw(comparator) }); + // 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 + } else { + false + } + } + }); + + let drop_props = { + // create a closure to drop the props + let mut has_dropped = false; + + let drop_props: &mut dyn FnMut() = bump.alloc_with(|| { + move || unsafe { + log::debug!("dropping props!"); + if !has_dropped { + let real_other = bump_props as *mut _ as *mut P; + let b = BumpBox::from_raw(real_other); + std::mem::drop(b); + + has_dropped = true; + } else { + panic!("Drop props called twice - this is an internal failure of Dioxus"); + } + } + }); + + let drop_props = unsafe { BumpBox::from_raw(drop_props) }; + + RefCell::new(Some(drop_props)) + }; let key = key.map(|f| self.raw_text(f).0); - // let caller = match P::IS_STATIC { - // true => { - // // it just makes sense to box the props - // let boxed_props: Box

= Box::new(props); - // let props_we_know_are_static = todo!(); - // VCompCaller::Owned(Box::new(|f| { - // // + let caller: &'a mut dyn Fn(&Scope) -> Element = + bump.alloc(move |scope: &Scope| -> Element { + log::debug!("calling component renderr {:?}", scope.our_arena_idx); + let props: &'_ P = unsafe { &*(bump_props as *const P) }; + let res = component(scope, props); + // let res = component((Context { scope }, props)); + unsafe { std::mem::transmute(res) } + }); - // let p = todo!(); + let can_memoize = P::IS_STATIC; - // todo!() - // })) - // } - // false => VCompCaller::Borrowed({ - // // - - // todo!() - // // let caller = bump.alloc() - // }), - // }; - - todo!() - // let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = - // bump.alloc(move |scope: &ScopeInner| -> Element { - // log::debug!("calling component renderr {:?}", scope.our_arena_idx); - // let props: &'_ P = unsafe { &*(raw_props as *const P) }; - - // let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) }; - // let s: Scope<'a, P> = (scp, props); - - // let res: Element = component(s); - // unsafe { std::mem::transmute(res) } - // }); - - // let caller = unsafe { BumpBox::from_raw(caller) }; - - // VNode::Component(bump.alloc(VComponent { - // user_fc, - // comparator, - // raw_props, - // caller, - // is_static: P::IS_STATIC, - // key, - // can_memoize: P::IS_STATIC, - // drop_props, - // associated_scope: Cell::new(None), - // })) + VNode::Component(bump.alloc(VComponent { + user_fc, + comparator, + bump_props, + caller, + key, + can_memoize, + drop_props, + associated_scope: Cell::new(None), + })) } pub fn listener( diff --git a/packages/core/tests/create_dom.rs b/packages/core/tests/create_dom.rs index 49a10fb21..00e3debec 100644 --- a/packages/core/tests/create_dom.rs +++ b/packages/core/tests/create_dom.rs @@ -64,10 +64,10 @@ fn create() { "Hello, world!" div { div { - // Fragment { - // "hello" - // "world" - // } + Fragment { + "hello" + "world" + } } } }