From bcbb93b69764dbff0d6130dc0f32bcf58ad7807f Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Wed, 17 Feb 2021 20:56:53 -0500 Subject: [PATCH] Feat: remove our use of ouroborous. Instead, we're just going stick with unsafely transmuting the static lifetime when accessing the active frame. This is a bit scarier, but greatly simplifies containing the VNode. --- packages/core/examples/step.rs | 2 +- packages/core/src/context.rs | 10 +- packages/core/src/lib.rs | 14 +- packages/core/src/scope.rs | 219 ++++++++++++++----------------- packages/core/src/virtual_dom.rs | 31 ++--- 5 files changed, 126 insertions(+), 150 deletions(-) diff --git a/packages/core/examples/step.rs b/packages/core/examples/step.rs index 74d2c0c6b..681946238 100644 --- a/packages/core/examples/step.rs +++ b/packages/core/examples/step.rs @@ -5,7 +5,7 @@ //! render it again //! consume the diffs and write that to a renderer -use dioxus_core::{prelude::*, scope::Scope}; +use dioxus_core::prelude::*; fn main() -> Result<(), ()> { let p1 = Props { name: "bob".into() }; diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index fc78a165f..4ea1dd1a5 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -30,15 +30,10 @@ use std::{ pub struct Context<'src> { pub idx: AtomicUsize, - pub(crate) scope: &'src Scope, - /// Direct access to the properties used to create this component. - // pub props: &'src PropType, - // Borrowed from scope pub(crate) arena: &'src typed_arena::Arena, pub(crate) hooks: &'src RefCell>, pub(crate) bump: &'src Bump, - // pub(crate) components: &'src generational_arena::Arena, // holder for the src lifetime // todo @jon remove this @@ -74,7 +69,7 @@ impl<'a> Context<'a> { /// } ///``` pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> { - lazy_nodes(self.bump.borrow()) + lazy_nodes(self.bump) } pub fn callback(&self, f: impl Fn(()) + 'static) {} @@ -105,8 +100,7 @@ pub mod hooks { } impl<'a> Context<'a> { - // impl<'a, P> Context<'a> { - // impl<'a, P> Context<'a, P> { + /// TODO: @jon, rework this so we dont have to use unsafe to make hooks and then return them /// use_hook provides a way to store data between renders for functional components. /// todo @jon: ensure the hook arena is stable with pin or is stable by default pub fn use_hook<'internal, 'scope, InternalHookState: 'static, Output: 'internal>( diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index bb66209ad..0c572da82 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -87,19 +87,19 @@ pub mod builder { // types used internally that are important pub(crate) mod innerlude { - pub use crate::component::{Component, Properties}; + pub(crate) use crate::component::{Component, Properties}; use crate::context::hooks::Hook; - pub use crate::context::Context; - pub use crate::error::{Error, Result}; + pub(crate) use crate::context::Context; + pub(crate) use crate::error::{Error, Result}; use crate::nodes; - pub use crate::scope::Scope; - pub use crate::virtual_dom::VirtualDom; - pub use nodes::*; + pub(crate) use crate::scope::Scope; + pub(crate) use crate::virtual_dom::VirtualDom; + pub(crate) use nodes::*; // pub use nodes::iterables::IterableNodes; /// This type alias is an internal way of abstracting over the static functions that represent components. - pub type FC

= for<'a> fn(Context<'a>, &'a P) -> &'a VNode<'a>; + pub type FC

= for<'a> fn(Context<'a>, &'a P) -> VNode<'a>; // pub type FC

= for<'a> fn(Context<'a, P>) -> VNode<'a>; // TODO @Jon, fix this diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index fa36f0093..8e6a7b757 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -15,77 +15,13 @@ use std::{ todo, }; -pub struct BumpContainer(pub UnsafeCell); -impl BumpContainer { - fn new() -> Self { - Self(UnsafeCell::new(Bump::new())) - } -} - -impl Deref for BumpContainer { - type Target = Bump; - - fn deref(&self) -> &Self::Target { - todo!() - // self.0.borrow() - } -} -impl DerefMut for BumpContainer { - fn deref_mut(&mut self) -> &mut Self::Target { - todo!() - // self.0.borrow_mut() - } -} -unsafe impl StableAddress for BumpContainer {} - -#[ouroboros::self_referencing] -pub struct BumpFrame { - pub bump: BumpContainer, - - #[covariant] - #[borrows(bump)] - pub head_node: &'this VNode<'this>, -} - -pub struct ActiveFrame { - pub idx: AtomicUsize, - pub frames: [BumpFrame; 2], -} - -impl ActiveFrame { - fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { - Self { - idx: 0.into(), - frames: [a, b], - } - } - - fn next(&self) -> &BumpFrame { - self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed); - match cur % 1 { - 1 => &self.frames[1], - 0 => &self.frames[0], - _ => unreachable!("mod cannot by non-zero"), - } - } - // fn next(&self) -> &BumpFrame { - // self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed); - // let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed); - // match cur % 2_usize { - // 1 => &self.frames[1], - // 0 => &self.frames[0], - // } - // } -} - /// Every component in Dioxus is represented by a `Scope`. /// /// Scopes contain the state for hooks, the component's props, and other lifecycle information. /// /// 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(crate) struct Scope { // TODO @Jon // These hooks are actually references into the hook arena // These two could be combined with "OwningRef" to remove unsafe usage @@ -107,6 +43,8 @@ pub struct Scope { // IE Which listeners need to be woken up? pub listeners: Vec>, + pub props: Box, + // pub props_type: TypeId, pub caller: *const i32, @@ -128,7 +66,11 @@ pub struct Scope { impl Scope { // create a new scope from a function - pub(crate) fn new(f: FC, parent: Option) -> Self { + pub(crate) fn new( + f: FC, + props: impl Properties + 'static, + parent: Option, + ) -> Self { // Capture the props type let props_type = TypeId::of::(); let hook_arena = typed_arena::Arena::new(); @@ -137,24 +79,20 @@ impl Scope { // Capture the caller let caller = f as *const i32; - // Create the two buffers the componetn will render into - // There will always be an "old" and "new" - let listeners = Vec::new(); - let new_frame = BumpFrameBuilder { - bump: BumpContainer::new(), - head_node_builder: |bump| bump.alloc(VNode::text("")), - } - .build(); + let old_frame = BumpFrame { + bump: Bump::new(), + head_node: VNode::text(""), + }; - let old_frame = BumpFrameBuilder { - bump: BumpContainer::new(), - head_node_builder: |bump| bump.alloc(VNode::text("")), - } - .build(); + let new_frame = BumpFrame { + bump: Bump::new(), + head_node: VNode::text(""), + }; let frames = ActiveFrame::from_frames(old_frame, new_frame); + let props = Box::new(props); Self { hook_arena, @@ -164,64 +102,107 @@ impl Scope { frames, listeners, parent, + props, } } + /// Update this component's props with a new set of props + /// + /// + pub(crate) fn update_props( + &mut self, + new_props: Box

, + ) -> crate::error::Result<()> { + Ok(()) + } + /// 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(crate) fn run<'a, 'bump, P: Properties + ?Sized>(&'bump mut self, props: &'a P) { - // I really wanted to do this safely, but I don't think we can. - // We want to reset the bump before writing into it. This requires &mut to the bump - // Ouroborous lets us borrow with self, but the heads (IE the source) cannot be changed while the ref is live + pub(crate) fn run<'bump, P: Properties + Sized + 'static>(&'bump mut self) { + let frame = { + let frame = self.frames.next(); + frame.bump.reset(); + frame + }; - // n.b, there might be a better way of doing this active frame stuff - perhaps swapping - let frame = self.frames.next(); + let ctx: Context<'bump> = Context { + arena: &self.hook_arena, + hooks: &self.hooks, + bump: &frame.bump, + idx: 0.into(), + _p: PhantomData {}, + }; - frame.with_bump(|bump_container| { - let bump: &mut Bump = unsafe { &mut *bump_container.0.get() }; - bump.reset(); + /* + SAFETY ALERT - let bump = &*bump; + 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. - let ctx: Context<'bump> = Context { - scope: &*self, - _p: PhantomData {}, - arena: &self.hook_arena, - hooks: &self.hooks, - idx: 0.into(), - bump, - }; + 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. + */ - /* - SAFETY ALERT + let caller = unsafe { std::mem::transmute::<*const i32, FC

>(self.caller) }; + let nodes: VNode<'bump> = caller(ctx, self.props.downcast_ref::

().unwrap()); - 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. + /* + SAFETY ALERT - 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. - */ - let caller = unsafe { std::mem::transmute::<*const i32, FC

>(self.caller) }; - let nodes: &'bump VNode = caller(ctx, props); - }); + DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS. + KEEPING THIS STATIC REFERENCE CAN LEAD TO UB. - // let new_nodes = caller(ctx, props); - // let r = new_nodes as *const _; - // self.old_root = self.new_root; - // self.new_root = new_nodes as *const _; + 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 + */ - // let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node }; + let unsafe_node = unsafe { std::mem::transmute::, VNode<'static>>(nodes) }; + frame.head_node = unsafe_node; - // TODO: Iterate through the new nodes - // move any listeners into ourself + todo!() + } - // perform the diff, dumping into the mutable change list - // this doesnt perform any "diff compression" where an event and a re-render - // crate::diff::diff(old_nodes, &new_nodes); + /// 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 current_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> { + todo!() + } + pub fn prev_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> { todo!() } } + +pub struct BumpFrame { + pub bump: Bump, + pub head_node: VNode<'static>, +} + +pub struct ActiveFrame { + pub idx: AtomicUsize, + pub frames: [BumpFrame; 2], +} + +impl ActiveFrame { + fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { + Self { + idx: 0.into(), + frames: [a, b], + } + } + + fn next(&mut self) -> &mut BumpFrame { + self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed); + let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed); + match cur % 1 { + 1 => &mut self.frames[1], + 0 => &mut self.frames[0], + _ => unreachable!("mod cannot by non-zero"), + } + } +} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 78639d61b..264a76aaf 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -56,27 +56,28 @@ impl VirtualDom { // 3. Create the lifecycle queue // 4. Create the event queue + todo!(); // Arena allocate all the components // This should make it *really* easy to store references in events and such - let mut components = Arena::new(); + // let mut components = Arena::new(); // Create a reference to the component in the arena - let base_scope = components.insert(Scope::new(root, None)); + // let base_scope = components.insert(Scope::new(root, None)); - // Create a new mount event with no root container - let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props); + // // Create a new mount event with no root container + // let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props); - // Create an event queue with a mount for the base scope - let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect())); + // // Create an event queue with a mount for the base scope + // let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect())); - let _root_prop_type = TypeId::of::

(); + // let _root_prop_type = TypeId::of::

(); - Self { - components, - base_scope, - event_queue, - _root_prop_type, - } + // Self { + // components, + // base_scope, + // event_queue, + // _root_prop_type, + // } } /// With access to the virtual dom, schedule an update to the Root component's props @@ -207,8 +208,8 @@ impl VirtualDom { // mount to the root } - let g = props.as_ref(); - scope.run(g); + // let g = props.as_ref(); + // scope.run(g); // scope.run(runner, props, dom); }