Jelajahi Sumber

wip: merge in some code from the other branch

Jonathan Kelley 4 tahun lalu
induk
melakukan
7790750349

+ 2 - 1
packages/core-macro/examples/prop_test.rs

@@ -2,8 +2,9 @@ fn main() {}
 
 pub mod dioxus {
     pub mod prelude {
-        pub trait Properties {
+        pub unsafe trait Properties {
             type Builder;
+            const CAN_BE_MEMOIZED: bool;
             fn builder() -> Self::Builder;
         }
     }

+ 2 - 1
packages/core-macro/src/props/mod.rs

@@ -679,8 +679,9 @@ Finally, call `.build()` to create the instance of `{name}`.
                     }
                 }
 
-                impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
+                unsafe impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
                     type Builder = #builder_name #generics_with_empty;
+                    const CAN_BE_MEMOIZED: bool = true;
                     fn builder() -> Self::Builder {
                         #name::builder()
                     }

+ 9 - 2
packages/core/src/arena.rs

@@ -9,9 +9,9 @@ use generational_arena::Arena;
 use crate::innerlude::*;
 
 #[derive(Clone)]
-pub struct ScopeArena(Rc<RefCell<ScopeArenaInner>>);
+pub struct ScopeArena(pub Rc<RefCell<ScopeArenaInner>>);
 
-struct ScopeArenaInner {
+pub struct ScopeArenaInner {
     pub(crate) arena: UnsafeCell<Arena<Scope>>,
     locks: HashMap<ScopeIdx, MutStatus>,
 }
@@ -61,6 +61,13 @@ impl ScopeArena {
         // todo!()
     }
 
+    pub fn try_remove(&mut self, id: ScopeIdx) -> Result<Scope> {
+        let inner = unsafe { &mut *self.0.borrow().arena.get() };
+        inner
+            .remove(id)
+            .ok_or_else(|| Error::FatalInternal("Scope not found"))
+    }
+
     unsafe fn inner_unchecked<'s>() -> &'s mut Arena<Scope> {
         todo!()
     }

+ 4 - 2
packages/core/src/component.rs

@@ -9,8 +9,9 @@ use crate::innerlude::FC;
 
 pub type ScopeIdx = generational_arena::Index;
 
-pub trait Properties: PartialEq {
+pub unsafe trait Properties: PartialEq {
     type Builder;
+    const CAN_BE_MEMOIZED: bool;
     fn builder() -> Self::Builder;
 }
 
@@ -21,7 +22,8 @@ impl EmptyBuilder {
     }
 }
 
-impl Properties for () {
+unsafe impl Properties for () {
+    const CAN_BE_MEMOIZED: bool = true;
     type Builder = EmptyBuilder;
 
     fn builder() -> Self::Builder {

+ 50 - 48
packages/core/src/diff.rs

@@ -120,7 +120,7 @@ impl<'a> DiffMachine<'a> {
         When re-entering, we reuse the EditList in DiffState
         */
         match (old, new) {
-            (VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => {
+            (VNode::Text(old_text), VNode::Text(new_text)) => {
                 if old_text != new_text {
                     self.change_list.commit_traversal();
                     self.change_list.set_text(new_text);
@@ -157,30 +157,32 @@ impl<'a> DiffMachine<'a> {
                 // self.change_list.commit_traversal();
                 if cold.user_fc == cnew.user_fc {
                     // todo: create a stable addr
-                    let caller = Rc::downgrade(&cnew.caller);
-                    let id = cold.stable_addr.borrow().unwrap();
-                    *cnew.stable_addr.borrow_mut() = Some(id);
-                    *cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow();
-
-                    let scope = Rc::downgrade(&cold.ass_scope);
-                    self.lifecycle_events
-                        .push_back(LifeCycleEvent::PropsChanged {
-                            caller,
-                            root_id: id,
-                            stable_scope_addr: scope,
-                        });
+                    // let caller = Rc::downgrade(&cnew.caller);
+                    // let id = cold.stable_addr.borrow().unwrap();
+                    // *cnew.stable_addr.borrow_mut() = Some(id);
+                    // *cnew.ass_scope.borrow_mut() = *cold.ass_scope.borrow();
+
+                    // let scope = Rc::downgrade(&cold.ass_scope);
+                    todo!()
+                    // self.lifecycle_events
+                    //     .push_back(LifeCycleEvent::PropsChanged {
+                    //         caller,
+                    //         root_id: id,
+                    //         stable_scope_addr: scope,
+                    //     });
                 } else {
-                    let caller = Rc::downgrade(&cnew.caller);
-                    let id = cold.stable_addr.borrow().unwrap();
-                    let old_scope = Rc::downgrade(&cold.ass_scope);
-                    let new_scope = Rc::downgrade(&cnew.ass_scope);
-
-                    self.lifecycle_events.push_back(LifeCycleEvent::Replace {
-                        caller,
-                        root_id: id,
-                        old_scope,
-                        new_scope,
-                    });
+                    // let caller = Rc::downgrade(&cnew.caller);
+                    // let id = cold.stable_addr.borrow().unwrap();
+                    // let old_scope = Rc::downgrade(&cold.ass_scope);
+                    // let new_scope = Rc::downgrade(&cnew.ass_scope);
+
+                    todo!()
+                    // self.lifecycle_events.push_back(LifeCycleEvent::Replace {
+                    //     caller,
+                    //     root_id: id,
+                    //     old_scope,
+                    //     new_scope,
+                    // });
                 }
             }
 
@@ -217,7 +219,7 @@ impl<'a> DiffMachine<'a> {
     fn create(&mut self, node: &VNode<'a>) {
         debug_assert!(self.change_list.traversal_is_committed());
         match node {
-            VNode::Text(VText { text }) => {
+            VNode::Text(text) => {
                 self.change_list.create_text_node(text);
             }
             VNode::Element(&VElement {
@@ -252,7 +254,7 @@ impl<'a> DiffMachine<'a> {
                 // text content, and finally (3) append the text node to this
                 // parent.
                 if children.len() == 1 {
-                    if let VNode::Text(VText { text }) = children[0] {
+                    if let VNode::Text(text) = children[0] {
                         self.change_list.set_text(text);
                         return;
                     }
@@ -268,18 +270,20 @@ impl<'a> DiffMachine<'a> {
             todo: integrate re-entrace
             */
             VNode::Component(component) => {
-                self.change_list
-                    .create_text_node("placeholder for vcomponent");
-
-                let id = get_id();
-                *component.stable_addr.as_ref().borrow_mut() = Some(id);
-                self.change_list.save_known_root(id);
-                let scope = Rc::downgrade(&component.ass_scope);
-                self.lifecycle_events.push_back(LifeCycleEvent::Mount {
-                    caller: Rc::downgrade(&component.caller),
-                    root_id: id,
-                    stable_scope_addr: scope,
-                });
+                todo!()
+                // self.change_list
+                //     .create_text_node("placeholder for vcomponent");
+
+                // let id = get_id();
+                // *component.stable_addr.as_ref().borrow_mut() = Some(id);
+                // self.change_list.save_known_root(id);
+                // let scope = Rc::downgrade(&component.ass_scope);
+                // todo!()
+                // self.lifecycle_events.push_back(LifeCycleEvent::Mount {
+                //     caller: Rc::downgrade(&component.caller),
+                //     root_id: id,
+                //     stable_scope_addr: scope,
+                // });
             }
             VNode::Suspended => {
                 todo!("Creation of VNode::Suspended not yet supported")
@@ -422,14 +426,11 @@ impl<'a> DiffMachine<'a> {
 
         if new.len() == 1 {
             match (old.first(), &new[0]) {
-                (
-                    Some(&VNode::Text(VText { text: old_text })),
-                    &VNode::Text(VText { text: new_text }),
-                ) if old_text == new_text => {
+                (Some(&VNode::Text(old_text)), &VNode::Text(new_text)) if old_text == new_text => {
                     // Don't take this fast path...
                 }
 
-                (_, &VNode::Text(VText { text })) => {
+                (_, &VNode::Text(text)) => {
                     self.change_list.commit_traversal();
                     self.change_list.set_text(text);
                     // for o in old {
@@ -979,11 +980,12 @@ impl<'a> DiffMachine<'a> {
                 // self.change_list
                 //     .create_text_node("placeholder for vcomponent");
 
-                let root_id = vcomp.stable_addr.as_ref().borrow().unwrap();
-                self.lifecycle_events.push_back(LifeCycleEvent::Remove {
-                    root_id,
-                    stable_scope_addr: Rc::downgrade(&vcomp.ass_scope),
-                })
+                todo!()
+                // let root_id = vcomp.stable_addr.as_ref().borrow().unwrap();
+                // self.lifecycle_events.push_back(LifeCycleEvent::Remove {
+                //     root_id,
+                //     stable_scope_addr: Rc::downgrade(&vcomp.ass_scope),
+                // })
                 // let id = get_id();
                 // *component.stable_addr.as_ref().borrow_mut() = Some(id);
                 // self.change_list.save_known_root(id);

+ 2 - 0
packages/core/src/lib.rs

@@ -86,6 +86,8 @@ pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and su
 pub mod builder {
     pub use super::nodebuilder::*;
 }
+pub mod scope;
+pub mod support;
 
 // types used internally that are important
 pub(crate) mod innerlude {

+ 47 - 53
packages/core/src/nodes.rs

@@ -27,7 +27,7 @@ pub enum VNode<'src> {
     Element(&'src VElement<'src>),
 
     /// A text node (node type `TEXT_NODE`).
-    Text(VText<'src>),
+    Text(&'src str),
 
     /// A fragment is a "virtual position" in the DOM
     /// Fragments may have children and keys
@@ -46,11 +46,11 @@ pub enum VNode<'src> {
 impl<'a> Clone for VNode<'a> {
     fn clone(&self) -> Self {
         match self {
-            VNode::Element(el) => VNode::Element(el),
-            VNode::Text(origi) => VNode::Text(VText { text: origi.text }),
-            VNode::Fragment(frag) => VNode::Fragment(frag),
+            VNode::Element(element) => VNode::Element(element),
+            VNode::Text(text) => VNode::Text(text),
+            VNode::Fragment(fragment) => VNode::Fragment(fragment),
+            VNode::Component(component) => VNode::Component(component),
             VNode::Suspended => VNode::Suspended,
-            VNode::Component(c) => VNode::Component(c),
         }
     }
 }
@@ -86,7 +86,7 @@ impl<'a> VNode<'a> {
     /// Construct a new text node with the given text.
     #[inline]
     pub fn text(text: &'a str) -> VNode<'a> {
-        VNode::Text(VText { text })
+        VNode::Text(text)
     }
 
     pub fn text_args(bump: &'a Bump, args: Arguments) -> VNode<'a> {
@@ -210,47 +210,31 @@ impl<'a> NodeKey<'a> {
     }
 }
 
-#[derive(Debug, PartialEq)]
-pub struct VText<'bump> {
-    pub text: &'bump str,
-}
-
-impl<'b> Clone for VText<'b> {
-    fn clone(&self) -> Self {
-        Self { text: self.text }
-    }
-}
-
-impl<'a> VText<'a> {
-    // / Create an new `VText` instance with the specified text.
-    pub fn new(text: &'a str) -> Self {
-        VText { text: text.into() }
-    }
-}
-
 // ==============================
 //   Custom components
 // ==============================
 
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
-pub type StableScopeAddres = RefCell<Option<u32>>;
-pub type VCompAssociatedScope = RefCell<Option<ScopeIdx>>;
+pub type StableScopeAddres = Option<u32>;
+pub type VCompAssociatedScope = Option<ScopeIdx>;
 
 pub struct VComponent<'src> {
     pub key: NodeKey<'src>,
 
-    pub stable_addr: Rc<StableScopeAddres>,
-    pub ass_scope: Rc<VCompAssociatedScope>,
+    pub stable_addr: RefCell<StableScopeAddres>,
+    pub ass_scope: RefCell<VCompAssociatedScope>,
 
     // pub comparator: Rc<dyn Fn(&VComponent) -> bool + 'src>,
     pub caller: Rc<dyn Fn(&Scope) -> VNode + 'src>,
 
     pub children: &'src [VNode<'src>],
 
+    pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
+
     // a pointer into the bump arena (given by the 'src lifetime)
     // raw_props: Box<dyn Any>,
-    // raw_props: *const (),
+    raw_props: *const (),
 
     // a pointer to the raw fn typ
     pub user_fc: *const (),
@@ -264,7 +248,8 @@ impl<'a> VComponent<'a> {
     // 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<P: Properties>(
+    pub fn new<P: Properties + 'a>(
+        bump: &'a Bump,
         component: FC<P>,
         // props: bumpalo::boxed::Box<'a, P>,
         props: P,
@@ -273,25 +258,32 @@ impl<'a> VComponent<'a> {
         // pub fn new<P: Properties + 'a>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
         // let bad_props = unsafe { transmogrify(props) };
         let caller_ref = component as *const ();
-
-        // let raw_props = props as *const P as *const ();
-
-        // 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.user_fc {
-        //         let g = other.raw_ctx.downcast_ref::<P>().unwrap();
-        //         // let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
-        //         &props == g
-        //     } else {
-        //         false
-        //     }
-        // };
+        let props = bump.alloc(props);
+
+        let raw_props = props as *const P as *const ();
+
+        let comparator: Option<&dyn Fn(&VComponent) -> bool> = {
+            if P::CAN_BE_MEMOIZED {
+                Some(bump.alloc(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.user_fc {
+                        // let g = other.raw_ctx.downcast_ref::<P>().unwrap();
+                        let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
+                        &props == &real_other
+                    } else {
+                        false
+                    }
+                }))
+            } else {
+                None
+            }
+        };
 
         // let prref: &'a P = props.as_ref();
 
@@ -313,16 +305,18 @@ impl<'a> VComponent<'a> {
             None => NodeKey(None),
         };
 
+        // raw_props: Box::new(props),
+        // comparator: Rc::new(props_comparator),
         Self {
             key,
-            ass_scope: Rc::new(RefCell::new(None)),
+            ass_scope: RefCell::new(None),
             user_fc: caller_ref,
-            // raw_props: Box::new(props),
+            comparator,
+            raw_props,
             _p: PhantomData,
             children: &[],
             caller,
-            // comparator: Rc::new(props_comparator),
-            stable_addr: Rc::new(RefCell::new(None)),
+            stable_addr: RefCell::new(None),
         }
     }
 }

+ 2 - 0
packages/core/src/patch.rs

@@ -146,6 +146,7 @@ pub struct EditMachine<'lock> {
     pub traversal: Traversal,
     next_temporary: u32,
     forcing_new_listeners: bool,
+    pub cur_height: u32,
 
     // // if the current node is a "known" node
     // // any actions that modify this node should update the mapping
@@ -158,6 +159,7 @@ impl<'lock> EditMachine<'lock> {
         Self {
             // current_known: None,
             traversal: Traversal::new(),
+            cur_height: 0,
             next_temporary: 0,
             forcing_new_listeners: false,
             emitter: EditList::<'lock>::default(),

+ 488 - 0
packages/core/src/scope.rs

@@ -0,0 +1,488 @@
+use crate::{arena::ScopeArena, innerlude::*};
+use bumpalo::Bump;
+use generational_arena::Arena;
+use std::{
+    any::{Any, TypeId},
+    cell::RefCell,
+    collections::{HashMap, HashSet, VecDeque},
+    fmt::Debug,
+    future::Future,
+    ops::Deref,
+    pin::Pin,
+    rc::{Rc, Weak},
+};
+
+/// 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 {
+    // The parent's scope ID
+    pub parent: Option<ScopeIdx>,
+
+    // IDs of children that this scope has created
+    // This enables us to drop the children and their children when this scope is destroyed
+    pub(crate) descendents: RefCell<HashSet<ScopeIdx>>,
+
+    pub(crate) child_nodes: &'static [VNode<'static>],
+
+    // A reference to the list of components.
+    // This lets us traverse the component list whenever we need to access our parent or children.
+    pub(crate) arena_link: ScopeArena,
+
+    pub shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
+
+    // Our own ID accessible from the component map
+    pub arena_idx: ScopeIdx,
+
+    pub height: u32,
+
+    pub event_channel: Rc<dyn Fn() + 'static>,
+
+    // pub event_queue: EventQueue,
+    pub caller: Weak<OpaqueComponent<'static>>,
+
+    pub hookidx: RefCell<usize>,
+
+    // ==========================
+    // slightly unsafe stuff
+    // ==========================
+    // an internal, highly efficient storage of vnodes
+    pub frames: ActiveFrame,
+
+    // 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
+    hooks: RefCell<Vec<Hook>>,
+
+    // 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
+    pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
+}
+
+// We need to pin the hook so it doesn't move as we initialize the list of hooks
+type Hook = Pin<Box<dyn std::any::Any>>;
+type EventChannel = Rc<dyn Fn()>;
+
+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
+    //
+    // Scopes cannot be made anywhere else except for this file
+    // Therefore, their lifetimes are connected exclusively to the virtual dom
+    pub(crate) fn new<'creator_node>(
+        caller: Weak<OpaqueComponent<'creator_node>>,
+        arena_idx: ScopeIdx,
+        parent: Option<ScopeIdx>,
+        height: u32,
+        event_channel: EventChannel,
+        arena_link: ScopeArena,
+        child_nodes: &'creator_node [VNode<'creator_node>],
+    ) -> Self {
+        log::debug!(
+            "New scope created, height is {}, idx is {:?}",
+            height,
+            arena_idx
+        );
+
+        // The function to run this scope is actually located in the parent's bump arena.
+        // Every time the parent is updated, that function is invalidated via double-buffering wiping the old frame.
+        // If children try to run this invalid caller, it *will* result in UB.
+        //
+        // During the lifecycle progression process, this caller will need to be updated. Right now,
+        // until formal safety abstractions are implemented, we will just use unsafe to "detach" the caller
+        // lifetime from the bump arena, exposing ourselves to this potential for invalidation. Truthfully,
+        // this is a bit of a hack, but will remain this way until we've figured out a cleaner solution.
+        //
+        // Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction.
+        let caller = unsafe {
+            std::mem::transmute::<
+                Weak<OpaqueComponent<'creator_node>>,
+                Weak<OpaqueComponent<'static>>,
+            >(caller)
+        };
+
+        Self {
+            child_nodes: &[],
+            caller,
+            parent,
+            arena_idx,
+            height,
+            event_channel,
+            arena_link,
+            frames: ActiveFrame::new(),
+            hooks: Default::default(),
+            shared_contexts: Default::default(),
+            listeners: Default::default(),
+            hookidx: Default::default(),
+            descendents: Default::default(),
+        }
+    }
+
+    pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
+        let broken_caller = unsafe {
+            std::mem::transmute::<
+                Weak<OpaqueComponent<'creator_node>>,
+                Weak<OpaqueComponent<'static>>,
+            >(caller)
+        };
+
+        self.caller = broken_caller;
+    }
+
+    /// 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_scope<'sel>(&'sel mut self) -> Result<()> {
+        // Cycle to the next frame and then reset it
+        // This breaks any latent references, invalidating every pointer referencing into it.
+        self.frames.next().bump.reset();
+
+        // Remove all the outdated listeners
+        //
+        self.listeners
+            .try_borrow_mut()
+            .ok()
+            .ok_or(Error::FatalInternal("Borrowing listener failed"))?
+            .drain(..);
+
+        *self.hookidx.borrow_mut() = 0;
+
+        let caller = self
+            .caller
+            .upgrade()
+            .ok_or(Error::FatalInternal("Failed to get caller"))?;
+
+        // Cast the caller ptr from static to one with our own reference
+        let c2: &OpaqueComponent<'static> = caller.as_ref();
+        let c3: &OpaqueComponent<'_> = unsafe { std::mem::transmute(c2) };
+
+        let unsafe_head = unsafe { self.own_vnodes(c3) };
+
+        self.frames.cur_frame_mut().head_node = unsafe_head;
+
+        Ok(())
+    }
+
+    // this is its own function so we can preciesly control how lifetimes flow
+    unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent<'a>) -> VNode<'static> {
+        let new_head: VNode<'a> = f(self);
+        let out: VNode<'static> = std::mem::transmute(new_head);
+        out
+    }
+
+    // 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) -> Result<()> {
+        let EventTrigger {
+            listener_id, event, ..
+        } = trigger;
+        //
+        unsafe {
+            // Convert the raw ptr into an actual object
+            // This operation is assumed to be safe
+            let listener_fn = self
+                .listeners
+                .try_borrow()
+                .ok()
+                .ok_or(Error::FatalInternal("Borrowing listener failed"))?
+                .get(listener_id as usize)
+                .ok_or(Error::FatalInternal("Event should exist if triggered"))?
+                .as_ref()
+                .ok_or(Error::FatalInternal("Raw event ptr is invalid"))?;
+
+            // Run the callback with the user event
+            listener_fn(event);
+        }
+        Ok(())
+    }
+
+    pub fn next_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()
+    }
+
+    pub fn cur_frame(&self) -> &BumpFrame {
+        self.frames.cur_frame()
+    }
+}
+
+/// Components in Dioxus use the "Context" object to interact with their lifecycle.
+/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
+///
+/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
+///
+/// ```ignore
+/// #[derive(Properties)]
+/// struct Props {
+///     name: String
+///
+/// }
+///
+/// fn example(ctx: Context, props: &Props -> VNode {
+///     html! {
+///         <div> "Hello, {ctx.ctx.name}" </div>
+///     }
+/// }
+/// ```
+// todo: force lifetime of source into T as a valid lifetime too
+// it's definitely possible, just needs some more messing around
+
+pub struct Context<'src, T> {
+    pub props: &'src T,
+    pub scope: &'src Scope,
+}
+
+impl<'src, T> Copy for Context<'src, T> {}
+impl<'src, T> Clone for Context<'src, T> {
+    fn clone(&self) -> Self {
+        Self {
+            props: self.props,
+            scope: self.scope,
+        }
+    }
+}
+
+impl<'a, T> Deref for Context<'a, T> {
+    type Target = &'a T;
+
+    fn deref(&self) -> &Self::Target {
+        &self.props
+    }
+}
+
+impl<'src, T> Scoped<'src> for Context<'src, T> {
+    fn get_scope(&self) -> &'src Scope {
+        self.scope
+    }
+}
+
+pub trait Scoped<'src>: Sized {
+    fn get_scope(&self) -> &'src Scope;
+
+    /// Access the children elements passed into the component
+    fn children(&self) -> &'src [VNode<'src>] {
+        // We're re-casting the nodes back out
+        // They don't really have a static lifetime
+        unsafe {
+            let scope = self.get_scope();
+            let nodes: &'src [VNode<'static>] = scope.child_nodes;
+
+            // cast the lifetime back correctly
+            std::mem::transmute(nodes)
+        }
+    }
+
+    /// Create a subscription that schedules a future render for the reference component
+    fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
+        self.get_scope().event_channel.clone()
+    }
+
+    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
+    ///
+    /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
+    ///
+    /// ## Example
+    ///
+    /// ```ignore
+    /// fn Component(ctx: Context<()>) -> VNode {
+    ///     // Lazy assemble the VNode tree
+    ///     let lazy_tree = html! {<div> "Hello World" </div>};
+    ///     
+    ///     // Actually build the tree and allocate it
+    ///     ctx.render(lazy_tree)
+    /// }
+    ///```
+    fn render<'a, F: for<'b> FnOnce(&'b NodeCtx<'src>) -> VNode<'src> + 'src + 'a>(
+        self,
+        lazy_nodes: LazyNodes<'src, F>,
+    ) -> VNode<'src> {
+        lazy_nodes.into_vnode(&NodeCtx {
+            scope_ref: self.get_scope(),
+            listener_id: 0.into(),
+        })
+    }
+
+    // impl<'scope> Context<'scope> {
+    /// Store a value between renders
+    ///
+    /// - Initializer: closure used to create the initial hook state
+    /// - Runner: closure used to output a value every time the hook is used
+    /// - Cleanup: closure used to teardown the hook once the dom is cleaned up
+    ///
+    /// ```ignore
+    /// // use_ref is the simplest way of storing a value between renders
+    /// pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
+    ///     use_hook(
+    ///         || Rc::new(RefCell::new(initial_value())),
+    ///         |state| state.clone(),
+    ///         |_| {},
+    ///     )
+    /// }
+    /// ```
+    fn use_hook<InternalHookState: 'static, Output: 'src>(
+        &self,
+
+        // The closure that builds the hook state
+        initializer: impl FnOnce() -> InternalHookState,
+
+        // The closure that takes the hookstate and returns some value
+        runner: impl FnOnce(&'src mut InternalHookState) -> Output,
+
+        // The closure that cleans up whatever mess is left when the component gets torn down
+        // TODO: add this to the "clean up" group for when the component is dropped
+        _cleanup: impl FnOnce(InternalHookState),
+    ) -> Output {
+        let scope = self.get_scope();
+
+        let idx = *scope.hookidx.borrow();
+
+        // Grab out the hook list
+        let mut hooks = scope.hooks.borrow_mut();
+
+        // If the idx is the same as the hook length, then we need to add the current hook
+        if idx >= hooks.len() {
+            let new_state = initializer();
+            hooks.push(Box::pin(new_state));
+        }
+
+        *scope.hookidx.borrow_mut() += 1;
+
+        let stable_ref = hooks
+            .get_mut(idx)
+            .expect("Should not fail, idx is validated")
+            .as_mut();
+
+        let pinned_state = unsafe { Pin::get_unchecked_mut(stable_ref) };
+
+        let internal_state = pinned_state.downcast_mut::<InternalHookState>().expect(
+            r###"
+Unable to retrive the hook that was initialized in this index.
+Consult the `rules of hooks` to understand how to use hooks properly.
+
+You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
+Any function prefixed with "use" should not be called conditionally.
+            "###,
+        );
+
+        // We extend the lifetime of the internal state
+        runner(unsafe { &mut *(internal_state as *mut _) })
+    }
+
+    /// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
+    ///
+    /// This is a hook, so it may not be called conditionally!
+    ///
+    /// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
+    /// so don't put it in a conditional.
+    ///
+    /// When the component is dropped, so is the context. Be aware of this behavior when consuming
+    /// the context via Rc/Weak.
+    ///
+    ///
+    ///
+    fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
+        let scope = self.get_scope();
+        let mut ctxs = scope.shared_contexts.borrow_mut();
+        let ty = TypeId::of::<T>();
+
+        let is_initialized = self.use_hook(
+            || false,
+            |s| {
+                let i = s.clone();
+                *s = true;
+                i
+            },
+            |_| {},
+        );
+
+        match (is_initialized, ctxs.contains_key(&ty)) {
+            // Do nothing, already initialized and already exists
+            (true, true) => {}
+
+            // Needs to be initialized
+            (false, false) => {
+                log::debug!("Initializing context...");
+                ctxs.insert(ty, Rc::new(init()));
+            }
+
+            _ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
+        }
+    }
+
+    /// There are hooks going on here!
+    fn use_context<T: 'static>(&self) -> &'src Rc<T> {
+        self.try_use_context().unwrap()
+    }
+
+    /// Uses a context, storing the cached value around
+    fn try_use_context<T: 'static>(&self) -> Result<&'src Rc<T>> {
+        struct UseContextHook<C> {
+            par: Option<Rc<C>>,
+            we: Option<Weak<C>>,
+        }
+
+        self.use_hook(
+            move || UseContextHook {
+                par: None as Option<Rc<T>>,
+                we: None as Option<Weak<T>>,
+            },
+            move |hook| {
+                let scope = self.get_scope();
+                let mut scope = Some(scope);
+
+                if let Some(we) = &hook.we {
+                    if let Some(re) = we.upgrade() {
+                        hook.par = Some(re);
+                        return Ok(hook.par.as_ref().unwrap());
+                    }
+                }
+
+                let ty = TypeId::of::<T>();
+                while let Some(inner) = scope {
+                    log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx);
+                    let shared_contexts = inner.shared_contexts.borrow();
+
+                    if let Some(shared_ctx) = shared_contexts.get(&ty) {
+                        log::debug!("found matching ctx");
+                        let rc = shared_ctx
+                            .clone()
+                            .downcast::<T>()
+                            .expect("Should not fail, already validated the type from the hashmap");
+
+                        hook.we = Some(Rc::downgrade(&rc));
+                        hook.par = Some(rc);
+                        return Ok(hook.par.as_ref().unwrap());
+                    } else {
+                        match inner.parent {
+                            Some(parent_id) => {
+                                let parent = inner
+                                    .arena_link
+                                    .try_get(parent_id)
+                                    .map_err(|_| Error::FatalInternal("Failed to find parent"))?;
+
+                                scope = Some(parent);
+                            }
+                            None => return Err(Error::MissingSharedContext),
+                        }
+                    }
+                }
+
+                Err(Error::MissingSharedContext)
+            },
+            |_| {},
+        )
+    }
+}

+ 166 - 0
packages/core/src/support.rs

@@ -0,0 +1,166 @@
+pub use crate::scope::*;
+use crate::{arena::ScopeArena, innerlude::*};
+use bumpalo::Bump;
+use generational_arena::Arena;
+use std::{
+    any::{Any, TypeId},
+    cell::RefCell,
+    collections::{HashMap, HashSet, VecDeque},
+    fmt::Debug,
+    future::Future,
+    ops::Deref,
+    pin::Pin,
+    rc::{Rc, Weak},
+};
+// We actually allocate the properties for components in their parent's properties
+// We then expose a handle to use those props for render in the form of "OpaqueComponent"
+pub(crate) type OpaqueComponent<'e> = dyn Fn(&'e Scope) -> VNode<'e> + 'e;
+// pub(crate) type OpaqueComponent<'e> = dyn for<'b> Fn(&'b Scope) -> VNode<'b> + 'e;
+
+#[derive(PartialEq, Debug, Clone, Default)]
+pub(crate) struct EventQueue(pub Rc<RefCell<Vec<HeightMarker>>>);
+
+impl EventQueue {
+    pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc<dyn Fn()> {
+        let inner = self.clone();
+        let marker = HeightMarker { height, idx };
+        Rc::new(move || inner.0.as_ref().borrow_mut().push(marker))
+    }
+}
+
+/// A helper type that lets scopes be ordered by their height
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub(crate) struct HeightMarker {
+    pub idx: ScopeIdx,
+    pub height: u32,
+}
+
+impl Ord for HeightMarker {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        self.height.cmp(&other.height)
+    }
+}
+
+impl PartialOrd for HeightMarker {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        Some(self.cmp(other))
+    }
+}
+
+// NodeCtx is used to build VNodes in the component's memory space.
+// This struct adds metadata to the final VNode about listeners, attributes, and children
+#[derive(Clone)]
+pub struct NodeCtx<'a> {
+    pub scope_ref: &'a Scope,
+    pub listener_id: RefCell<usize>,
+}
+
+impl<'a> NodeCtx<'a> {
+    pub fn bump(&self) -> &'a Bump {
+        &self.scope_ref.cur_frame().bump
+    }
+}
+
+impl Debug for NodeCtx<'_> {
+    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+        Ok(())
+    }
+}
+
+#[derive(Debug, PartialEq, Hash)]
+pub struct ContextId {
+    // Which component is the scope in
+    original: ScopeIdx,
+
+    // What's the height of the scope
+    height: u32,
+
+    // Which scope is it (in order)
+    id: u32,
+}
+
+pub struct ActiveFrame {
+    // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
+    pub generation: RefCell<usize>,
+
+    // The double-buffering situation that we will use
+    pub frames: [BumpFrame; 2],
+}
+
+pub struct BumpFrame {
+    pub bump: Bump,
+    pub head_node: VNode<'static>,
+}
+
+impl ActiveFrame {
+    pub fn new() -> Self {
+        Self::from_frames(
+            BumpFrame {
+                bump: Bump::new(),
+                head_node: VNode::text(""),
+            },
+            BumpFrame {
+                bump: Bump::new(),
+                head_node: VNode::text(""),
+            },
+        )
+    }
+
+    pub(crate) fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
+        Self {
+            generation: 0.into(),
+            frames: [a, b],
+        }
+    }
+
+    pub(crate) fn cur_frame(&self) -> &BumpFrame {
+        match *self.generation.borrow() & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        }
+    }
+    pub(crate) fn cur_frame_mut(&mut self) -> &mut BumpFrame {
+        match *self.generation.borrow() & 1 == 0 {
+            true => &mut self.frames[0],
+            false => &mut self.frames[1],
+        }
+    }
+
+    pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
+        let raw_node = match *self.generation.borrow() & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        };
+
+        // Give out our self-referential item with our own borrowed lifetime
+        unsafe {
+            let unsafe_head = &raw_node.head_node;
+            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
+            safe_node
+        }
+    }
+
+    pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
+        let raw_node = match *self.generation.borrow() & 1 != 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        };
+
+        // Give out our self-referential item with our own borrowed lifetime
+        unsafe {
+            let unsafe_head = &raw_node.head_node;
+            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
+            safe_node
+        }
+    }
+
+    pub(crate) fn next(&mut self) -> &mut BumpFrame {
+        *self.generation.borrow_mut() += 1;
+
+        if *self.generation.borrow() % 2 == 0 {
+            &mut self.frames[0]
+        } else {
+            &mut self.frames[1]
+        }
+    }
+}

+ 0 - 4
packages/core/src/virtual_dom.rs

@@ -191,10 +191,6 @@ impl VirtualDom {
 
         // Ok(diff_machine.consume())
     }
-
-    pub fn base_scope(&self) -> &Scope {
-        todo!()
-    }
 }
 
 // ======================================

+ 15 - 18
packages/recoil/src/lib.rs

@@ -32,7 +32,7 @@ mod traits {
     // Atoms, selectors, and their family variants are readable
     pub trait Readable<T: AtomValue>: Sized + Copy {
         fn use_read<'a, P: 'static>(self, ctx: Context<'a, P>) -> &'a T {
-            hooks::use_read(ctx, self)
+            hooks::use_read(&ctx, self)
         }
 
         // This returns a future of the value
@@ -95,9 +95,9 @@ mod atoms {
             const EXAMPLE_ATOM: Atom<i32> = |_| 10;
 
             // ensure that atoms are both read and write
-            let _ = use_read(ctx, &EXAMPLE_ATOM);
-            let _ = use_read_write(ctx, &EXAMPLE_ATOM);
-            let _ = use_write(ctx, &EXAMPLE_ATOM);
+            let _ = use_read(&ctx, &EXAMPLE_ATOM);
+            let _ = use_read_write(&ctx, &EXAMPLE_ATOM);
+            let _ = use_write(&ctx, &EXAMPLE_ATOM);
         }
     }
 }
@@ -161,7 +161,7 @@ mod atomfamily {
 
         fn test(ctx: Context<()>) {
             let title = Titles.select(&10).use_read(ctx);
-            let t2 = use_read(ctx, &Titles.select(&10));
+            let t2 = use_read(&ctx, &Titles.select(&10));
         }
     }
 }
@@ -368,7 +368,7 @@ mod root {
 
 mod hooks {
     use super::*;
-    use dioxus_core::{hooks::use_ref, prelude::Context};
+    use dioxus_core::{hooks::use_ref, prelude::Context, scope::Scoped};
 
     pub fn use_init_recoil_root<P>(ctx: Context<P>, cfg: impl Fn(())) {
         ctx.use_create_context(move || RefCell::new(RecoilRoot::new()))
@@ -381,12 +381,12 @@ mod hooks {
     ///
     /// You can use this method to create controllers that perform much more complex actions than set/get
     /// However, be aware that "getting" values through this hook will not subscribe the component to any updates.
-    pub fn use_recoil_api<'a, P>(ctx: Context<'a, P>) -> &Rc<RecoilContext> {
+    pub fn use_recoil_api<'a>(ctx: &impl Scoped<'a>) -> &'a Rc<RecoilContext> {
         ctx.use_context::<RecoilContext>()
     }
 
-    pub fn use_write<'a, T: AtomValue, P>(
-        ctx: Context<'a, P>,
+    pub fn use_write<'a, T: AtomValue>(
+        ctx: &impl Scoped<'a>,
         // todo: this shouldn't need to be static
         writable: impl Writable<T>,
     ) -> &'a Rc<dyn Fn(T)> {
@@ -412,10 +412,10 @@ mod hooks {
     /// Read the atom and get the Rc directly to the Atom's slot
     /// This is useful if you need the memoized Atom value. However, Rc<T> is not as easy to
     /// work with as
-    pub fn use_read_raw<'a, T: AtomValue, P: 'static>(
-        ctx: Context<'a, P>,
+    pub fn use_read_raw<'a, T: AtomValue>(
+        ctx: &impl Scoped<'a>,
         readable: impl Readable<T>,
-    ) -> &Rc<T> {
+    ) -> &'a Rc<T> {
         struct ReadHook<T> {
             value: Rc<T>,
             consumer_id: u32,
@@ -449,10 +449,7 @@ mod hooks {
     }
 
     ///
-    pub fn use_read<'a, T: AtomValue, P: 'static>(
-        ctx: Context<'a, P>,
-        readable: impl Readable<T>,
-    ) -> &'a T {
+    pub fn use_read<'a, T: AtomValue>(ctx: &impl Scoped<'a>, readable: impl Readable<T>) -> &'a T {
         use_read_raw(ctx, readable).as_ref()
     }
 
@@ -471,8 +468,8 @@ mod hooks {
     /// // equivalent to:
     /// let (title, set_title) = (use_read(ctx, &Title), use_write(ctx, &Title));
     /// ```
-    pub fn use_read_write<'a, T: AtomValue + 'static, P: 'static>(
-        ctx: Context<'a, P>,
+    pub fn use_read_write<'a, T: AtomValue + 'static>(
+        ctx: &impl Scoped<'a>,
         writable: impl Writable<T>,
     ) -> (&'a T, &'a Rc<dyn Fn(T)>) {
         (use_read(ctx, writable), use_write(ctx, writable))