浏览代码

feat: dyn scope

Jonathan Kelley 4 年之前
父节点
当前提交
89f2290
共有 2 个文件被更改,包括 184 次插入144 次删除
  1. 152 111
      packages/core/src/scope.rs
  2. 32 33
      packages/core/src/virtual_dom.rs

+ 152 - 111
packages/core/src/scope.rs

@@ -11,22 +11,33 @@ use std::{
     ops::Deref,
 };
 
+pub trait Properties: PartialEq {}
+impl Properties for () {}
+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`.
 ///
 /// 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 struct Scope<P: Properties> {
     // Map to the parent
     pub parent: Option<ScopeIdx>,
 
-    // lying, cheating reference >:(
-    pub props: Box<dyn std::any::Any>,
-
     // our own index
     pub myidx: ScopeIdx,
 
+    pub caller: FC<P>,
+
+    pub props: P,
+
     // ==========================
     // slightly unsafe stuff
     // ==========================
@@ -41,65 +52,63 @@ pub struct Scope {
 
     // Unsafety:
     // - is self-refenrential and therefore needs to point into the bump
-    //
     // Stores references into the listeners attached to the vnodes
     // NEEDS TO BE PRIVATE
     listeners: RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
-
-    // Unsafety
-    // - is a raw ptr because we need to compare
-    pub caller: *const (),
 }
 
-impl Scope {
-    // create a new scope from a function
-    pub fn new<'a, P1, P2: 'static>(
-        f: FC<P1>,
-        props: P1,
-        myidx: ScopeIdx,
-        parent: Option<ScopeIdx>,
-    ) -> Self {
-        let hook_arena = typed_arena::Arena::new();
-        let hooks = RefCell::new(Vec::new());
-
-        // Capture the caller
-        let caller = f as *const ();
-
-        let listeners = Default::default();
-
-        let old_frame = BumpFrame {
-            bump: Bump::new(),
-            head_node: VNode::text(""),
-        };
-
-        let new_frame = BumpFrame {
-            bump: Bump::new(),
-            head_node: VNode::text(""),
-        };
+// 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<P: Properties + 'static>(
+    caller: FC<P>,
+    props: P,
+    myidx: ScopeIdx,
+    parent: Option<ScopeIdx>,
+) -> Box<dyn Scoped> {
+    let hook_arena = typed_arena::Arena::new();
+    let hooks = RefCell::new(Vec::new());
+
+    let listeners = Default::default();
+
+    let old_frame = BumpFrame {
+        bump: Bump::new(),
+        head_node: VNode::text(""),
+    };
 
-        let frames = ActiveFrame::from_frames(old_frame, new_frame);
+    let new_frame = BumpFrame {
+        bump: Bump::new(),
+        head_node: VNode::text(""),
+    };
 
-        // box the props
-        let props = Box::new(props);
+    let frames = ActiveFrame::from_frames(old_frame, new_frame);
+
+    Box::new(Scope {
+        myidx,
+        hook_arena,
+        hooks,
+        caller,
+        frames,
+        listeners,
+        parent,
+        props,
+    })
+}
 
-        let props = unsafe { std::mem::transmute::<_, Box<P2>>(props) };
+impl<P: Properties + 'static> Scoped for Scope<P> {
+    fn run(&mut self) {
+        self.run()
+    }
 
-        Self {
-            myidx,
-            hook_arena,
-            hooks,
-            caller,
-            frames,
-            listeners,
-            parent,
-            props,
-        }
+    fn compare_props(&self, new: &Any) -> bool {
+        new.downcast_ref::<P>()
+            .map(|f| &self.props == f)
+            .expect("Props should not be of a different type")
     }
 
     // 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
-    pub fn call_listener(&mut self, trigger: EventTrigger) {
+    fn call_listener(&mut self, trigger: EventTrigger) {
         let EventTrigger {
             listener_id,
             event: source,
@@ -126,26 +135,23 @@ impl Scope {
             self.listeners.borrow_mut().drain(..);
         }
     }
-}
 
-pub struct RawComponent {
-    // used as a memoization strategy
-    comparator: *const Box<dyn Fn(&Box<dyn Any>) -> bool>,
-
-    // used to actually run the component
-    // encapsulates props
-    runner: *const Box<dyn Fn(Context) -> DomTree>,
+    fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
+        self.frames.current_head_node()
+    }
 
-    // the actual FC<T>
-    raw: *const (),
+    fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
+        self.frames.prev_head_node()
+    }
 }
 
-impl Scope {
+impl<P: Properties> Scope<P> {
     /// 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, PLocked: Sized + 'static>(&'bump mut self) {
+    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();
@@ -165,44 +171,46 @@ impl Scope {
             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<PLocked>>(self.caller);
-            let props = self.props.downcast_ref::<PLocked>().unwrap();
-
-            // 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");
-        }
+        // 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<PLocked>>(self.caller);
+        // let props = self.props.downcast_ref::<PLocked>().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");
+        // }
     }
 }
 
@@ -221,17 +229,18 @@ fn retrieve_listeners(node: &VNode<'static>, listeners: &mut Vec<&Listener>) {
 // ==========================
 // 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()
-    }
-}
+// impl<P: Properties> Scope<P> {
+//     /// 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
@@ -323,8 +332,8 @@ mod tests {
         let props = ();
         let parent = None;
         let mut nodes = generational_arena::Arena::new();
-        nodes.insert_with(|f| {
-            let scope = Scope::new::<(), ()>(example, props, f, parent);
+        nodes.insert_with(|myidx| {
+            let scope = create_scoped(example, props, myidx, parent);
         });
     }
 
@@ -390,3 +399,35 @@ mod tests {
         let props = ExampleProps { name: &source_text };
     }
 }
+
+#[cfg(asd)]
+mod old {
+
+    /// The ComponentCaller struct is an opaque object that encapsultes the memoization and running functionality for FC
+    ///
+    /// It's opaque because during the diffing mechanism, the type of props is sealed away in a closure. This makes it so
+    /// scope doesn't need to be generic
+    pub struct ComponentCaller {
+        // used as a memoization strategy
+        comparator: Box<dyn Fn(&Box<dyn Any>) -> bool>,
+
+        // used to actually run the component
+        // encapsulates props
+        runner: Box<dyn Fn(Context) -> DomTree>,
+
+        props_type: TypeId,
+
+        // the actual FC<T>
+        raw: *const (),
+    }
+
+    impl ComponentCaller {
+        fn new<P>(props: P) -> Self {
+            let comparator = Box::new(|f| false);
+            todo!();
+            // Self { comparator }
+        }
+
+        fn update_props<P>(props: P) {}
+    }
+}

+ 32 - 33
packages/core/src/virtual_dom.rs

@@ -1,6 +1,7 @@
 // use crate::{changelist::EditList, nodes::VNode};
 
-use crate::innerlude::*;
+use crate::scope::{create_scoped, Scoped};
+use crate::{innerlude::*, scope::Properties};
 use bumpalo::Bump;
 use generational_arena::Arena;
 use std::{
@@ -18,8 +19,12 @@ use std::{
 /// Differences are converted into patches which a renderer can use to draw the UI.
 pub struct VirtualDom {
     /// All mounted components are arena allocated to make additions, removals, and references easy to work with
-    /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
-    pub(crate) components: Arena<Scope>,
+    /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
+    ///
+    /// 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 soem heuristics on optimizing layout
+    pub(crate) components: Arena<Box<dyn Scoped>>,
 
     /// The index of the root component.
     /// Will not be ready if the dom is fresh
@@ -49,7 +54,7 @@ impl VirtualDom {
     ///
     /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
     /// to toss out the entire tree.
-    pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
+    pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
         let mut components = Arena::new();
 
         let event_queue = RefCell::new(VecDeque::new());
@@ -57,8 +62,7 @@ impl VirtualDom {
         // 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| Scope::new::<_, P>(root, root_props, id, None));
+        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!
@@ -93,7 +97,7 @@ impl VirtualDom {
             .get_mut(self.base_scope)
             .expect("Root should always exist");
 
-        component.run::<()>();
+        component.run();
 
         diff_machine.diff_node(component.old_frame(), component.new_frame());
 
@@ -138,22 +142,8 @@ impl VirtualDom {
         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
-        //     .get_mut(self.base_scope)
-        //     .expect("Root should always exist");
-
-        component.run::<()>();
-
+        component.run();
         diff_machine.diff_node(component.old_frame(), component.new_frame());
-        // diff_machine.diff_node(
-        //     component.old_frame(),
-        //     component.new_frame(),
-        //     Some(self.base_scope),
-        // );
 
         Ok(diff_machine.consume())
         // Err(crate::error::Error::NoEvent)
@@ -201,18 +191,27 @@ impl VirtualDom {
     /// 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.
-    pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<LifecycleEvent> {
-        // Ensure the props match
-        if TypeId::of::<P>() != self._root_prop_type {
-            return Err(Error::WrongProps);
-        }
+    /// 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<P: 'static>(
+        &mut self,
+        updater: impl FnOnce(&mut P),
+    ) -> Result<LifecycleEvent> {
+        todo!()
+        // // pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<LifecycleEvent> {
+        // // Ensure the props match
+        // if TypeId::of::<P>() != self._root_prop_type {
+        //     return Err(Error::WrongProps);
+        // }
 
-        Ok(LifecycleEvent {
-            event_type: LifecycleType::PropsChanged {
-                props: Box::new(new_props),
-                component: self.base_scope,
-            },
-        })
+        // Ok(LifecycleEvent {
+        //     event_type: LifecycleType::PropsChanged {
+        //         props: Box::new(new_props),
+        //         component: self.base_scope,
+        //     },
+        // })
     }
 }