diff --git a/packages/core/examples/borrowed.rs b/packages/core/examples/borrowed.rs index 011c84ec7..f9304a8dd 100644 --- a/packages/core/examples/borrowed.rs +++ b/packages/core/examples/borrowed.rs @@ -71,14 +71,9 @@ impl PartialEq for ChildProps<'_> { impl<'a> Properties for ChildProps<'a> { type Builder = ChildPropsBuilder<'a, ((), ())>; - type StaticOutput = ChildProps<'static>; fn builder() -> ::Builder { ChildProps::builder() } - - unsafe fn into_static(self) -> Self::StaticOutput { - unsafe { std::mem::transmute(self) } - } } fn ChildItem(_ctx: Context, _props: &ChildProps) -> DomTree { diff --git a/packages/core/examples/fc.rs b/packages/core/examples/fc.rs index eaa385c1a..0e6440bcf 100644 --- a/packages/core/examples/fc.rs +++ b/packages/core/examples/fc.rs @@ -27,12 +27,7 @@ fn main() {} impl Properties for ExampleProps { type Builder = ExamplePropsBuilder<((),)>; - type StaticOutput = ExampleProps; fn builder() -> Self::Builder { ExampleProps::builder() } - - unsafe fn into_static(self) -> Self { - self - } } diff --git a/packages/core/examples/nested.rs b/packages/core/examples/nested.rs index 440864e69..a96bc9240 100644 --- a/packages/core/examples/nested.rs +++ b/packages/core/examples/nested.rs @@ -12,12 +12,12 @@ static Header: FC<()> = |ctx, props| { let handler1 = move || println!("Value is {}", inner.current()); - ctx.render(|bump| { - builder::ElementBuilder::new(bump, "div") + ctx.render(|c| { + builder::ElementBuilder::new(c, "div") .child(VNode::Component(VComponent::new( Bottom, // - &mut (), + c.bump.alloc(()), ))) .finish() }) diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index 09c14e188..eefc250a4 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -27,12 +27,7 @@ static Example: FC = |ctx, _props| { // toodo: derive this impl Properties for SomeProps { type Builder = SomePropsBuilder<((),)>; - type StaticOutput = SomeProps; fn builder() -> Self::Builder { SomeProps::builder() } - - unsafe fn into_static(self) -> Self { - self - } } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 74209774d..7ddbeca3d 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -32,7 +32,7 @@ //! //! 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::*, scope::Scoped}; +use crate::{innerlude::*, scope::Scope}; use bumpalo::Bump; use fxhash::{FxHashMap, FxHashSet}; use generational_arena::Arena; @@ -60,12 +60,15 @@ use std::{ pub struct DiffMachine<'a> { pub change_list: EditMachine<'a>, pub diffed: FxHashSet, - pub lifecycle_events: VecDeque, + pub lifecycle_events: VecDeque>, } -#[derive(Debug)] -pub enum LifeCycleEvent { - Mount { caller: Caller, id: Uuid }, +// #[derive(Debug)] +pub enum LifeCycleEvent<'a> { + Mount { + caller: Rc Fn(Context<'r>) -> DomTree + 'a>, + id: Uuid, + }, PropsChanged, SameProps, Remove, @@ -260,6 +263,7 @@ impl<'a> DiffMachine<'a> { self.change_list .create_text_node("placeholder for vcomponent"); let id = uuid::Uuid::new_v4(); + *component.stable_addr.as_ref().borrow_mut() = Some(id); self.change_list.save_known_root(id); self.lifecycle_events.push_back(LifeCycleEvent::Mount { caller: component.caller.clone(), diff --git a/packages/core/src/nodebuilder.rs b/packages/core/src/nodebuilder.rs index 16c40b856..0ce2eaa38 100644 --- a/packages/core/src/nodebuilder.rs +++ b/packages/core/src/nodebuilder.rs @@ -521,8 +521,9 @@ pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> { // } // } -pub fn virtual_child<'a, T: Properties>(ctx: &NodeCtx<'a>, f: FC, p: T) -> VNode<'a> { - VNode::Component(crate::nodes::VComponent::new(f, ctx.bump.alloc(p))) +pub fn virtual_child<'a, T: Properties + 'a>(ctx: &NodeCtx<'a>, f: FC, p: T) -> VNode<'a> { + let propsd: &'a mut _ = ctx.bump.alloc(p); + VNode::Component(crate::nodes::VComponent::new(f, propsd)) } trait Bany { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 5490e3974..3c1023db4 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -264,35 +264,40 @@ impl<'a> VText<'a> { /// Virtual Components for custom user-defined components /// Only supports the functional syntax -pub type StableScopeAddres = Rc>>; +pub type StableScopeAddres = Rc>>; -#[derive(Debug, Clone)] pub struct VComponent<'src> { pub stable_addr: StableScopeAddres, - pub raw_props: Rc<*mut dyn Any>, - // pub comparator: Comparator, - pub caller: Caller, - pub caller_ref: *const (), + + pub comparator: Rc bool + 'src>, + pub caller: Rc Fn(Context<'r>) -> DomTree + 'src>, + + // a pointer into the bump arena (given by the 'src lifetime) + raw_props: *const (), + + // a pointer to the raw fn typ + caller_ref: *const (), _p: PhantomData<&'src ()>, } -pub trait PropsComparator {} -#[derive(Clone)] -// pub struct Comparator(pub Rc bool>); -pub struct Comparator(pub Rc bool>); -impl std::fmt::Debug for Comparator { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) +impl std::fmt::Debug for VComponent<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + todo!() } } +// pub struct Comparator(pub Rc bool>); +// impl std::fmt::Debug for Comparator { +// fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// Ok(()) +// } +// } -#[derive(Clone)] -pub struct Caller(pub Rc DomTree>); -impl std::fmt::Debug for Caller { - fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} +// pub struct Caller(pub Rc DomTree>); +// impl std::fmt::Debug for Caller { +// fn fmt(&self, _f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { +// Ok(()) +// } +// } impl<'a> VComponent<'a> { // use the type parameter on props creation and move it into a portable context @@ -300,36 +305,40 @@ impl<'a> VComponent<'a> { // - perform comparisons when diffing (memoization) // TODO: lift the requirement that props need to be static // we want them to borrow references... maybe force implementing a "to_static_unsafe" trait - pub fn new(caller: FC

, props: &mut P) -> Self { - let caller_ref = caller as *const (); + pub fn new(component: FC

, props: &'a P) -> Self { + let caller_ref = component as *const (); - let props = Rc::new(props); + let raw_props = props as *const P as *const (); - // used for memoization - let p1 = props.clone(); - - // let props_comparator = move |new_props| -> bool { - // false - // let p = new_props.downcast_ref::().expect(""); - // let r = unsafe { std::mem::transmute::<_, &P>(p) }; - // &r == p1.as_ref() - // }; - - // used for actually rendering the custom component - let p2 = props.clone(); - let caller = move |ctx: Context| -> DomTree { - // cast back into the right lifetime - caller(ctx, p2.as_ref()) + let props_comparator = move |other: &VComponent| { + // Safety: + // We are guaranteed that the props will be of the same type because + // there is no way to create a VComponent other than this `new` method. + // + // Therefore, if the render functions are identical (by address), then so will be + // props type paramter (because it is the same render function). Therefore, we can be + // sure + if caller_ref == other.caller_ref { + let real_other = unsafe { &*(other.raw_props as *const _ as *const P) }; + real_other == props + } else { + false + } }; - todo!() - // Self { - // caller_ref, - // raw_props: props, - // _p: PhantomData, - // comparator: Comparator(Rc::new(props_comparator)), - // caller: Caller(Rc::new(caller)), - // stable_addr: Rc::new(RefCell::new(None)), - // } + let caller = move |ctx: Context| -> DomTree { + // cast back into the right lifetime + let safe_props: &P = unsafe { &*(raw_props as *const P) }; + component(ctx, props) + }; + + Self { + caller_ref, + raw_props: props as *const P as *const _, + _p: PhantomData, + caller: Rc::new(caller), + comparator: Rc::new(props_comparator), + stable_addr: Rc::new(RefCell::new(None)), + } } } diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index dbfb526e6..a8dce6bfa 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -1,15 +1,22 @@ use crate::innerlude::*; use bumpalo::Bump; -use std::{any::Any, cell::RefCell, collections::HashSet, marker::PhantomData, ops::Deref}; +use std::{ + any::Any, + cell::RefCell, + collections::HashSet, + marker::PhantomData, + ops::Deref, + rc::{Rc, Weak}, +}; -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>; -} +// 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>; +// } /// Every component in Dioxus is represented by a `Scope`. /// @@ -17,7 +24,8 @@ pub trait Scoped { /// /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. -pub struct Scope { +pub struct Scope { + // pub struct Scope { // Map to the parent pub parent: Option, @@ -27,12 +35,15 @@ pub struct Scope { // pub children: HashSet, + pub caller: Weak DomTree + 'static>, + // // the props // pub props: P, // and the actual render function - pub caller: Caller, - _p: std::marker::PhantomData

, + // pub caller: *const dyn Fn(Context) -> DomTree, + // _p: std::marker::PhantomData

, + // _p: std::marker::PhantomData

, // pub raw_caller: FC

, // ========================== @@ -57,35 +68,62 @@ pub struct Scope { // instead of having it as a trait method, we use a single function // todo: do the unsafety magic stuff to erase the type of p -pub fn create_scoped( - // raw_f: FC

, - caller: Caller, +// pub fn create_scoped( +// // raw_f: FC

, +// // caller: Caller, - // props: P, - myidx: ScopeIdx, - parent: Option, -) -> Box { - Box::new(Scope::<()> { - // raw_caller: raw_f, - _p: Default::default(), - caller, - myidx, - hook_arena: typed_arena::Arena::new(), - hooks: RefCell::new(Vec::new()), - frames: ActiveFrame::new(), - children: HashSet::new(), - listeners: Default::default(), - parent, - // props, - }) -} +// // props: P, +// myidx: ScopeIdx, +// parent: Option, +// ) -> Box { +// Box::new(Scope::<()> { +// // raw_caller: raw_f, +// _p: Default::default(), +// // caller, +// myidx, +// hook_arena: typed_arena::Arena::new(), +// hooks: RefCell::new(Vec::new()), +// frames: ActiveFrame::new(), +// children: HashSet::new(), +// listeners: Default::default(), +// parent, +// // props, +// }) +// } -impl Scoped for Scope

{ +impl Scope { + // we are being created in the scope of an existing component (where the creator_node lifetime comes into play) + // we are going to break this lifetime by force in order to save it on ourselves. + // To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies. + // This should never happen, but is a good check to keep around + pub fn new<'creator_node>( + caller: Weak DomTree + 'creator_node>, + myidx: ScopeIdx, + parent: Option, + ) -> Self { + // caller has been broken free + // however, it's still weak, so if the original Rc gets killed, we can't touch it + let broken_caller: Weak DomTree + 'static> = + unsafe { std::mem::transmute(caller) }; + + Self { + caller: broken_caller, + hook_arena: typed_arena::Arena::new(), + hooks: RefCell::new(Vec::new()), + frames: ActiveFrame::new(), + children: HashSet::new(), + listeners: Default::default(), + parent, + myidx, + } + } + + // impl Scoped for 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) - fn run<'bump>(&'bump mut self) { + pub fn run<'bump>(&'bump mut self) { let frame = { let frame = self.frames.next(); frame.bump.reset(); @@ -105,8 +143,9 @@ impl Scoped for Scope

{ listeners: &self.listeners, }; + todo!() // Note that the actual modification of the vnode head element occurs during this call - let _: DomTree = (self.caller.0.as_ref())(ctx); + // let _: DomTree = (self.caller.0.as_ref())(ctx); // let _: DomTree = (self.raw_caller)(ctx, &self.props); /* @@ -121,14 +160,14 @@ impl Scoped for Scope

{ - Public API cannot drop or destructure VNode */ - frame.head_node = node_slot - .deref() - .borrow_mut() - .take() - .expect("Viewing did not happen"); + // frame.head_node = node_slot + // .deref() + // .borrow_mut() + // .take() + // .expect("Viewing did not happen"); } - // fn compare_props(&self, new: &dyn Any) -> bool { + // pub fn compare_props(&self, new: &dyn Any) -> bool { // new.downcast_ref::

() // .map(|f| &self.props == f) // .expect("Props should not be of a different type") @@ -137,7 +176,7 @@ impl Scoped for Scope

{ // A safe wrapper around calling listeners // calling listeners will invalidate the list of listeners // The listener list will be completely drained because the next frame will write over previous listeners - fn call_listener(&mut self, trigger: EventTrigger) { + pub fn call_listener(&mut self, trigger: EventTrigger) { let EventTrigger { listener_id, event: source, @@ -165,11 +204,11 @@ impl Scoped for Scope

{ } } - fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { + pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { self.frames.current_head_node() } - fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { + pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { self.frames.prev_head_node() } } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index f2fe3a6ca..5e8391d19 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -1,13 +1,14 @@ // use crate::{changelist::EditList, nodes::VNode}; use crate::innerlude::*; -use crate::{ - patch::Edit, - scope::{create_scoped, Scoped}, -}; +use crate::{patch::Edit, scope::Scope}; use bumpalo::Bump; use generational_arena::Arena; -use std::{any::TypeId, cell::RefCell, rc::Rc}; +use std::{ + any::TypeId, + cell::RefCell, + rc::{Rc, Weak}, +}; /// An integrated virtual node system that progresses events and diffs UI trees. /// Differences are converted into patches which a renderer can use to draw the UI. @@ -18,13 +19,15 @@ pub struct VirtualDom { /// eventually, come up with a better datastructure that reuses boxes for known P types /// like a generational typemap bump arena /// -> IE a cache line for each P type with some heuristics on optimizing layout - pub(crate) components: Arena>, + pub(crate) components: Arena, // pub(crate) components: RefCell>>, // pub(crate) components: Rc>>>, /// The index of the root component. /// Will not be ready if the dom is fresh pub(crate) base_scope: ScopeIdx, + pub(crate) root_caller: Rc DomTree + 'static>, + // Type of the original props. This is done so VirtualDom does not need to be generic. #[doc(hidden)] _root_prop_type: std::any::TypeId, @@ -55,6 +58,7 @@ impl VirtualDom { pub fn new(root: FC<()>) -> Self { Self::new_with_props(root, ()) } + /// Start a new VirtualDom instance with a dependent props. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders. /// @@ -63,18 +67,15 @@ impl VirtualDom { pub fn new_with_props(root: FC

, root_props: P) -> Self { let mut components = Arena::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 - let caller = Caller(Rc::new(move |ctx| { - // - root(ctx, &root_props) - // DomTree {} - })); - let base_scope = components.insert_with(|id| create_scoped(caller, id, None)); - // let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None)); + // the root is kept around + let root_caller: Rc DomTree + 'static> = + Rc::new(move |ctx| root(ctx, &root_props)); + let weak_caller: Weak DomTree + 'static> = Rc::downgrade(&root_caller); + let base_scope = components.insert_with(move |id| Scope::new(weak_caller, id, None)); Self { components, + root_caller, base_scope, diff_bump: Bump::new(), _root_prop_type: TypeId::of::

(), diff --git a/packages/web/examples/infer.rs b/packages/web/examples/infer.rs index 3737cec3a..0bc4f1541 100644 --- a/packages/web/examples/infer.rs +++ b/packages/web/examples/infer.rs @@ -60,13 +60,7 @@ static Example2: FC = |ctx, props| { impl Properties for ExampleProps { type Builder = ExamplePropsBuilder<((),)>; - type StaticOutput = ExampleProps; fn builder() -> Self::Builder { ExampleProps::builder() } - - - unsafe fn into_static(self) -> Self::StaticOutput { - todo!() - } }