diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index bfedf6e5c..a80951f6a 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -1,34 +1,19 @@ -//! An example that shows how to: -//! create a scope, -//! render a component, -//! change some data -//! render it again -//! consume the diffs and write that to a renderer - use dioxus_core::prelude::*; fn main() -> Result<(), ()> { let p1 = Props { name: "bob".into() }; - let _vdom = VirtualDom::new_with_props(Example, p1); - // vdom.progress()?; + let mut vdom = VirtualDom::new_with_props(Example, p1); + vdom.update_props(|p: &mut Props| {}); Ok(()) } -#[derive(Debug)] +#[derive(Debug, PartialEq)] struct Props { name: String, } -// impl Properties for Props { -// fn call(&self, ptr: *const ()) {} - -// // fn new() -> Self { -// // todo!() -// // } -// } - static Example: FC = |ctx, _props| { ctx.render(html! {
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 7710db6d1..2199f7755 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -32,10 +32,14 @@ //! //! More info on how to improve this diffing algorithm: //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ -use crate::innerlude::*; +use crate::{ + innerlude::*, + scope::{create_scoped, Scoped}, +}; use bumpalo::Bump; use fxhash::{FxHashMap, FxHashSet}; -use std::cmp::Ordering; +use generational_arena::Arena; +use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc}; /// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while /// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event. @@ -49,20 +53,23 @@ use std::cmp::Ordering; /// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components /// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via /// subscriptions and props changes. -pub struct DiffMachine<'a> { +pub struct DiffMachine<'a, 'b> { pub change_list: EditMachine<'a>, - immediate_queue: Vec, - diffed: FxHashSet, - need_to_diff: FxHashSet, + + pub vdom: &'b VirtualDom, + pub cur_idx: ScopeIdx, + pub diffed: FxHashSet, + pub need_to_diff: FxHashSet, } -impl<'a> DiffMachine<'a> { - pub fn new(bump: &'a Bump) -> Self { +impl<'a, 'b> DiffMachine<'a, 'b> { + pub fn new(vdom: &'b VirtualDom, bump: &'a Bump, idx: ScopeIdx) -> Self { Self { + cur_idx: idx, change_list: EditMachine::new(bump), - immediate_queue: Vec::new(), diffed: FxHashSet::default(), need_to_diff: FxHashSet::default(), + vdom, } } @@ -71,6 +78,7 @@ impl<'a> DiffMachine<'a> { } pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) { + // pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) { /* For each valid case, we "commit traversal", meaning we save this current position in the tree. Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later. @@ -120,7 +128,45 @@ impl<'a> DiffMachine<'a> { todo!("Usage of component VNode not currently supported"); } - (_, VNode::Component(_)) | (VNode::Component(_), _) => { + (_, VNode::Component(new)) => { + // let VComponent { + // props, + // props_type, + // comp, + // caller, + // assigned_scope, + // .. + // } = *new; + + // make the component + // let idx = unsafe { + // // let vdom = &mut *self.vdom; + // vdom.insert_with(|f| { + // todo!() + // // + // // create_scoped(caller, props, myidx, parent) + // }) + // }; + + // we have no stable reference to work from + // push the lifecycle event onto the queue + // self.lifecycle_events + // .borrow_mut() + // .push_back(LifecycleEvent { + // event_type: LifecycleType::Mount { + // props: new.props, + // to: self.cur_idx, + // }, + // }); + // we need to associaote this new component with a scope... + + // self.change_list.save_known_root(id) + self.change_list.commit_traversal(); + + // push the current + } + + (VNode::Component(old), _) => { todo!("Usage of component VNode not currently supported"); } @@ -137,7 +183,8 @@ impl<'a> DiffMachine<'a> { // [... node] // // The change list stack is left unchanged. - fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) { + fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) { + // fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) { if !old.is_empty() || !new.is_empty() { self.change_list.commit_traversal(); } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 535ebf79d..8b04e95ae 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -296,11 +296,13 @@ mod vtext { /// Virtual Components for custom user-defined components /// Only supports the functional syntax mod vcomponent { - use crate::innerlude::{Context, FC}; - use std::{any::TypeId, marker::PhantomData}; + use crate::innerlude::{Context, ScopeIdx, FC}; + use std::{any::TypeId, cell::RefCell, marker::PhantomData, rc::Rc}; use super::DomTree; + pub type StableScopeAddres = Rc>>; + #[derive(Debug)] pub struct VComponent<'src> { _p: PhantomData<&'src ()>, @@ -308,6 +310,11 @@ mod vcomponent { pub(crate) props_type: TypeId, pub(crate) comp: *const (), pub(crate) caller: Caller, + + // once a component gets mounted, its parent gets a stable address. + // this way we can carry the scope index from between renders + // genius, really! + pub assigned_scope: StableScopeAddres, } pub struct Caller(Box DomTree>); @@ -323,10 +330,10 @@ mod vcomponent { // - perform comparisons when diffing (memoization) // - pub fn new

(comp: FC

, props: P) -> Self { - let caller = move |ctx: Context| { - let t = comp(ctx, &props); - t - }; + // let caller = move |ctx: Context| { + // let t = comp(ctx, &props); + // t + // }; // let _caller = comp as *const (); // let _props = Box::new(props); diff --git a/packages/core/src/patch.rs b/packages/core/src/patch.rs index a2ff0e292..5999e55bf 100644 --- a/packages/core/src/patch.rs +++ b/packages/core/src/patch.rs @@ -126,6 +126,7 @@ pub struct EditMachine<'src> { pub traversal: Traversal, next_temporary: u32, forcing_new_listeners: bool, + pub emitter: EditList<'src>, } @@ -214,10 +215,6 @@ impl<'a> EditMachine<'a> { debug_assert!(self.traversal_is_committed()); debug_assert!(start < end); let temp_base = self.next_temporary; - // debug!( - // "emit: save_children_to_temporaries({}, {}, {})", - // temp_base, start, end - // ); self.next_temporary = temp_base + (end - start) as u32; self.emitter.push(Edit::SaveChildrenToTemporaries { temp: temp_base, @@ -367,6 +364,11 @@ impl<'a> EditMachine<'a> { // debug!("emit: remove_event_listener({:?})", event); } + pub fn save_known_root(&mut self, id: ScopeIdx) { + log::debug!("emit: save_known_root({:?})", id); + self.emitter.push(Edit::MakeKnown { node: id }) + } + // pub fn save_template(&mut self, id: CacheId) { // debug_assert!(self.traversal_is_committed()); // debug_assert!(!self.has_template(id)); diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index d63c5a5a1..7de06a73e 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -12,11 +12,14 @@ use std::{ }; pub trait Properties: PartialEq {} -impl Properties for () {} +// just for now +impl Properties for T {} + pub trait Scoped { fn run(&mut self); fn compare_props(&self, new: &dyn std::any::Any) -> bool; fn call_listener(&mut self, trigger: EventTrigger); + fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>; fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>; } @@ -46,6 +49,7 @@ pub struct Scope { // These hooks are actually references into the hook arena // These two could be combined with "OwningRef" to remove unsafe usage + // or we could dedicate a tiny bump arena just for them // could also use ourborous pub hooks: RefCell>, pub hook_arena: typed_arena::Arena, @@ -95,8 +99,51 @@ pub fn create_scoped( } impl Scoped for Scope

{ - fn run(&mut self) { - self.run() + /// Create a new context and run the component with references from the Virtual Dom + /// This function downcasts the function pointer based on the stored props_type + /// + /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) + fn run<'bump>(&'bump mut self) { + let frame = { + let frame = self.frames.next(); + frame.bump.reset(); + frame + }; + + let node_slot = std::rc::Rc::new(RefCell::new(None)); + + let ctx: Context<'bump> = Context { + arena: &self.hook_arena, + hooks: &self.hooks, + bump: &frame.bump, + idx: 0.into(), + _p: PhantomData {}, + final_nodes: node_slot.clone(), + scope: self.myidx, + listeners: &self.listeners, + }; + + // Note that the actual modification of the vnode head element occurs during this call + // let _: DomTree = caller(ctx, props); + let _: DomTree = (self.caller)(ctx, &self.props); + + /* + SAFETY ALERT + + DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS. + KEEPING THIS STATIC REFERENCE CAN LEAD TO UB. + + Some things to note: + - The VNode itself is bound to the lifetime, but it itself is owned by scope. + - The VNode has a private API and can only be used from accessors. + - Public API cannot drop or destructure VNode + */ + + frame.head_node = node_slot + .deref() + .borrow_mut() + .take() + .expect("Viewing did not happen"); } fn compare_props(&self, new: &Any) -> bool { @@ -145,103 +192,10 @@ impl Scoped for Scope

{ } } -impl Scope

{ - /// Create a new context and run the component with references from the Virtual Dom - /// This function downcasts the function pointer based on the stored props_type - /// - /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized) - pub fn run<'bump>(&'bump mut self) { - // pub fn run<'bump, PLocked: Sized + 'static>(&'bump mut self) { - let frame = { - let frame = self.frames.next(); - frame.bump.reset(); - frame - }; - - let node_slot = std::rc::Rc::new(RefCell::new(None)); - - let ctx: Context<'bump> = Context { - arena: &self.hook_arena, - hooks: &self.hooks, - bump: &frame.bump, - idx: 0.into(), - _p: PhantomData {}, - final_nodes: node_slot.clone(), - scope: self.myidx, - listeners: &self.listeners, - }; - - // unsafe { - /* - SAFETY ALERT - - This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html - We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called, - we transmute the function back using the props as reference. - - we could do a better check to make sure that the TypeID is correct before casting - -- - This is safe because we check that the generic type matches before casting. - */ - // we use plocked to be able to remove the borrowed lifetime - // these lifetimes could be very broken, so we need to dynamically manage them - // let caller = std::mem::transmute::<*const (), FC>(self.caller); - // let props = self.props.downcast_ref::().unwrap(); - let caller = self.caller; - let props = &self.props; - - // Note that the actual modification of the vnode head element occurs during this call - let _: DomTree = caller(ctx, props); - - /* - SAFETY ALERT - - DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS. - KEEPING THIS STATIC REFERENCE CAN LEAD TO UB. - - Some things to note: - - The VNode itself is bound to the lifetime, but it itself is owned by scope. - - The VNode has a private API and can only be used from accessors. - - Public API cannot drop or destructure VNode - */ - // the nodes we care about have been unsafely extended to a static lifetime in context - frame.head_node = node_slot - .deref() - .borrow_mut() - .take() - .expect("Viewing did not happen"); - // } - } -} - -fn retrieve_listeners(node: &VNode<'static>, listeners: &mut Vec<&Listener>) { - if let VNode::Element(el) = *node { - for listener in el.listeners { - // let g = listener as *const Listener; - listeners.push(listener); - } - for child in el.children { - retrieve_listeners(child, listeners); - } - } -} - // ========================== // Active-frame related code // ========================== -// impl Scope

{ -// /// Accessor to get the root node and its children (safely)\ -// /// Scope is self-referntial, so we are forced to use the 'static lifetime to cheat -// pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { -// self.frames.current_head_node() -// } - -// pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { -// self.frames.prev_head_node() -// } -// } - // todo, do better with the active frame stuff // somehow build this vnode with a lifetime tied to self // This root node has "static" lifetime, but it's really not static. diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 9b0285b36..10b1d2d95 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,7 +1,10 @@ // use crate::{changelist::EditList, nodes::VNode}; -use crate::scope::{create_scoped, Scoped}; use crate::{innerlude::*, scope::Properties}; +use crate::{ + patch::Edit, + scope::{create_scoped, Scoped}, +}; use bumpalo::Bump; use generational_arena::Arena; use std::{ @@ -25,19 +28,31 @@ pub struct VirtualDom { /// like a generational typemap bump arena /// -> IE a cache line for each P type with soem heuristics on optimizing layout pub(crate) components: Arena>, - + // pub(crate) components: Rc>>>, /// The index of the root component. /// Will not be ready if the dom is fresh - base_scope: ScopeIdx, - - event_queue: RefCell>, - - // todo: encapsulate more state into this so we can better reuse it - diff_bump: Bump, + pub(crate) base_scope: ScopeIdx, // Type of the original props. This is done so VirtualDom does not need to be generic. #[doc(hidden)] _root_prop_type: std::any::TypeId, + // ====================== + // DIFF RELATED ITEMs + // ====================== + // // todo: encapsulate more state into this so we can better reuse it + pub(crate) diff_bump: Bump, + // // be very very very very very careful + // pub change_list: EditMachine<'static>, + + // // vdom: &'a VirtualDom, + // vdom: *mut Arena>, + + // // vdom: Rc>>>, + // pub cur_idx: ScopeIdx, + + // // todo + // // do an indexmap sorted by height + // dirty_nodes: fxhash::FxHashSet, } impl VirtualDom { @@ -57,51 +72,53 @@ impl VirtualDom { pub fn new_with_props(root: FC

, root_props: P) -> Self { let mut components = Arena::new(); - let event_queue = RefCell::new(VecDeque::new()); - // Create a reference to the component in the arena // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist // This puts the dom in a usable state on creation, rather than being potentially invalid let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None)); - // evaluate the component, pushing any updates its generates into the lifecycle queue - // todo! - - let _root_prop_type = TypeId::of::

(); - let diff_bump = Bump::new(); - - Self { - components, - base_scope, - event_queue, - diff_bump, - _root_prop_type, - } + todo!() + // Self { + // // components: RefCell::new(components), + // components: components, + // // components: Rc::new(RefCell::new(components)), + // base_scope, + // // event_queue: RefCell::new(VecDeque::new()), + // diff_bump: Bump::new(), + // _root_prop_type: TypeId::of::

(), + // } } /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom. - /// - /// - pub fn rebuild(&mut self) -> Result> { + + // pub fn rebuild<'s>(&'s mut self) -> Result<> { + // pub fn rebuild<'s>(&'s mut self) -> Result>>> { + pub fn rebuild<'s>(&'s mut self) -> Result> { // Reset and then build a new diff machine // The previous edit list cannot be around while &mut is held // Make sure variance doesnt break this self.diff_bump.reset(); - let mut diff_machine = DiffMachine::new(&self.diff_bump); - // this is still a WIP - // we'll need to re-fecth all the scopes that were changed and build the diff machine - // fetch the component again - let component = self - .components + self.components .get_mut(self.base_scope) - .expect("Root should always exist"); + .expect("Root should always exist") + .run(); - component.run(); + let b = Bump::new(); - diff_machine.diff_node(component.old_frame(), component.new_frame()); + let mut diff_machine = DiffMachine::new(self, &b, self.base_scope); + // let mut diff_machine = DiffMachine::new(self, &self.diff_bump, self.base_scope); - Ok(diff_machine.consume()) + todo!() + // let component = self.components.get(self.base_scope).unwrap(); + + // diff_machine.diff_node(component.old_frame(), component.new_frame()); + + // let edits = diff_machine.consume(); + + // self.diff_bump = b; + + // Ok(edits) } /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered. @@ -129,23 +146,32 @@ impl VirtualDom { /// /// ``` pub fn progress_with_event(&mut self, event: EventTrigger) -> Result> { - let component = self - .components - .get_mut(event.component_id) - .expect("Component should exist if an event was triggered"); + // self.components + // .borrow_mut() + // .get_mut(event.component_id) + // .map(|f| { + // f.call_listener(event); + // f + // }) + // .map(|f| f.run()) + // .expect("Borrowing should not fail"); - component.call_listener(event); + // component.call_listener(event); + // .expect("Component should exist if an event was triggered"); // Reset and then build a new diff machine // The previous edit list cannot be around while &mut is held // Make sure variance doesnt break this - self.diff_bump.reset(); - let mut diff_machine = DiffMachine::new(&self.diff_bump); + // self.diff_bump.reset(); + // let mut diff_machine = DiffMachine::new(&mut self, event.component_id); + // let mut diff_machine = + // DiffMachine::new(&self.diff_bump, &mut self.components, event.component_id); - component.run(); - diff_machine.diff_node(component.old_frame(), component.new_frame()); + // component.run(); + // diff_machine.diff_node(component.old_frame(), component.new_frame()); - Ok(diff_machine.consume()) + todo!() + // Ok(diff_machine.consume()) // Err(crate::error::Error::NoEvent) // Mark dirty components. Descend from the highest node until all dirty nodes are updated. // let mut affected_components = Vec::new(); @@ -159,115 +185,8 @@ impl VirtualDom { // todo!() } - - /// Using mutable access to the Virtual Dom, progress a given lifecycle event - fn process_lifecycle(&mut self, LifecycleEvent { event_type }: LifecycleEvent) -> Result<()> { - match event_type { - // Component needs to be mounted to the virtual dom - LifecycleType::Mount { - to: _, - under: _, - props: _, - } => {} - - // The parent for this component generated new props and the component needs update - LifecycleType::PropsChanged { - props: _, - component: _, - } => {} - - // Component was messaged via the internal subscription service - LifecycleType::Callback { component: _ } => {} - } - - Ok(()) - } - - /// Pop the top event of the internal lifecycle event queu - pub fn pop_event(&self) -> Option { - self.event_queue.borrow_mut().pop_front() - } - - /// With access to the virtual dom, schedule an update to the Root component's props. - /// This generates the appropriate Lifecycle even. It's up to the renderer to actually feed this lifecycle event - /// back into the event system to get an edit list. - /// todo - /// change this to accept a modification closure, so the user gets 0-cost access to the props item. - /// Good for cases where the props is changed remotely (or something similar) and building a whole new props item would - /// be wasteful - pub fn update_props( - &mut self, - updater: impl FnOnce(&mut P), - ) -> Result { - todo!() - // // pub fn update_props(&mut self, new_props: P) -> Result { - // // Ensure the props match - // if TypeId::of::

() != self._root_prop_type { - // return Err(Error::WrongProps); - // } - - // Ok(LifecycleEvent { - // event_type: LifecycleType::PropsChanged { - // props: Box::new(new_props), - // component: self.base_scope, - // }, - // }) - } } -pub struct LifecycleEvent { - pub event_type: LifecycleType, -} - -pub enum LifecycleType { - // Component needs to be mounted, but its scope doesn't exist yet - Mount { - to: ScopeIdx, - under: usize, - props: Box, - }, - - // Parent was evalauted causing new props to generate - PropsChanged { - props: Box, - component: ScopeIdx, - }, - - // Hook for the subscription API - Callback { - component: ScopeIdx, - }, -} - -impl LifecycleEvent { - fn index(&self) -> Option { - match &self.event_type { - LifecycleType::Mount { - to: _, - under: _, - props: _, - } => None, - - LifecycleType::PropsChanged { component, .. } - | LifecycleType::Callback { component } => Some(component.clone()), - } - } -} - -mod tests { - use super::*; - - #[test] - fn start_dom() { - let mut dom = VirtualDom::new(|ctx, props| { - todo!() - // ctx.render(|ctx| { - // use crate::builder::*; - // let bump = ctx.bump(); - // div(bump).child(text("hello, world")).finish() - // }) - }); - let edits = dom.rebuild().unwrap(); - println!("{:#?}", edits); - } -} +// struct LockedEdits<'src> { +// edits: +// }