Kaynağa Gözat

Feat: context api wired up

Jonathan Kelley 4 yıl önce
ebeveyn
işleme
24805a0

+ 14 - 13
packages/core/examples/contextapi.rs

@@ -11,20 +11,21 @@ struct Props {
 
 #[allow(unused)]
 static Example: FC<Props> = |ctx, props| {
-    let value = ctx.use_context(|c: &SomeContext| c.items.last().unwrap());
+    todo!()
+    // let value = ctx.use_context(|c: &SomeContext| c.items.last().unwrap());
 
-    ctx.render(LazyNodes::new(move |bump| {
-        builder::ElementBuilder::new(bump, "button")
-            .on("click", move |_| {
-                println!("Value is {}", props.name);
-                println!("Value is {}", value.as_str());
-                println!("Value is {}", *value);
-            })
-            .on("click", move |_| {
-                println!("Value is {}", props.name);
-            })
-            .finish()
-    }))
+    // ctx.render(LazyNodes::new(move |bump| {
+    //     builder::ElementBuilder::new(bump, "button")
+    //         .on("click", move |_| {
+    //             println!("Value is {}", props.name);
+    //             println!("Value is {}", value.as_str());
+    //             println!("Value is {}", *value);
+    //         })
+    //         .on("click", move |_| {
+    //             println!("Value is {}", props.name);
+    //         })
+    //         .finish()
+    // }))
     // ctx.render(html! {
     //     <div>
     //         <button onclick={move |_| println!("Value is {}", value)} />

+ 11 - 4
packages/core/src/arena.rs

@@ -1,10 +1,17 @@
-use std::{cell::UnsafeCell, collections::HashMap};
+use std::{
+    cell::{RefCell, UnsafeCell},
+    collections::HashMap,
+    rc::Rc,
+};
 
 use generational_arena::Arena;
 
 use crate::innerlude::*;
 
-pub struct ScopeArena {
+#[derive(Clone)]
+pub struct ScopeArena(Rc<RefCell<ScopeArenaInner>>);
+
+struct ScopeArenaInner {
     pub(crate) arena: UnsafeCell<Arena<Scope>>,
     locks: HashMap<ScopeIdx, MutStatus>,
 }
@@ -16,10 +23,10 @@ enum MutStatus {
 
 impl ScopeArena {
     pub fn new(arena: Arena<Scope>) -> Self {
-        Self {
+        ScopeArena(Rc::new(RefCell::new(ScopeArenaInner {
             arena: UnsafeCell::new(arena),
             locks: Default::default(),
-        }
+        })))
     }
 
     pub fn try_get(&self, idx: ScopeIdx) -> Result<&Scope> {

+ 7 - 7
packages/core/src/context.rs

@@ -55,14 +55,14 @@ impl<T> Deref for RemoteState<T> {
     }
 }
 
-impl<'a> crate::virtual_dom::Context<'a> {
-    // impl<'a, P> super::Context<'a, P> {
-    pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
-        todo!()
-    }
+// impl<'a> crate::virtual_dom::Context<'a> {
+//     // impl<'a, P> super::Context<'a, P> {
+//     pub fn use_context<I, O>(&'a self, _narrow: impl Fn(&'_ I) -> &'_ O) -> RemoteState<O> {
+//         todo!()
+//     }
 
-    pub fn create_context<T: 'static>(&self, creator: impl FnOnce() -> T) {}
-}
+//     pub fn create_context<T: 'static>(&self, creator: impl FnOnce() -> T) {}
+// }
 
 /// # SAFETY ALERT
 ///

+ 3 - 0
packages/core/src/error.rs

@@ -10,6 +10,9 @@ pub enum Error {
     #[error("Fatal Internal Error: {0}")]
     FatalInternal(&'static str),
 
+    #[error("Context is missing")]
+    MissingSharedContext,
+
     #[error("No event to progress")]
     NoEvent,
 

+ 1 - 1
packages/core/src/events.rs

@@ -80,7 +80,7 @@ pub mod on {
                         let bump = &c.bump();
                         Listener {
                             event: stringify!($name),
-                            id: *c.idx.borrow(),
+                            id: *c.listener_id.borrow(),
                             scope: c.scope_ref.myidx,
                             callback: bump.alloc(move |evt: VirtualEvent| match evt {
                                 VirtualEvent::$eventdata(event) => callback(event),

+ 2 - 2
packages/core/src/nodebuilder.rs

@@ -341,7 +341,7 @@ where
         let listener = Listener {
             event,
             callback: bump.alloc(callback),
-            id: *self.ctx.idx.borrow(),
+            id: *self.ctx.listener_id.borrow(),
             scope: self.ctx.scope_ref.myidx,
         };
         self.add_listener(listener)
@@ -351,7 +351,7 @@ where
         self.listeners.push(listener);
 
         // bump the context id forward
-        *self.ctx.idx.borrow_mut() += 1;
+        *self.ctx.listener_id.borrow_mut() += 1;
 
         // Add this listener to the context list
         // This casts the listener to a self-referential pointer

+ 267 - 199
packages/core/src/virtual_dom.rs

@@ -5,7 +5,8 @@
 //! navigate the innerworkings of the Dom. We try to keep these main mechanics in this file to limit
 //! the possible exposed API surface (keep fields private). This particular implementation of VDOM
 //! is extremely efficient, but relies on some unsafety under the hood to do things like manage
-//! micro-heaps for components.
+//! micro-heaps for components. We are currently working on refactoring the safety out into safe(r)
+//! abstractions, but current tests (MIRI and otherwise) show no issues with the current implementation.
 //!
 //! Included is:
 //! - The [`VirtualDom`] itself
@@ -23,7 +24,8 @@ use bumpalo::Bump;
 use generational_arena::Arena;
 use std::{
     any::{Any, TypeId},
-    cell::{RefCell, UnsafeCell},
+    borrow::{Borrow, BorrowMut},
+    cell::RefCell,
     collections::{HashMap, HashSet},
     fmt::Debug,
     future::Future,
@@ -31,8 +33,6 @@ use std::{
     rc::{Rc, Weak},
 };
 
-pub use support::*;
-
 /// 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.
 pub struct VirtualDom {
@@ -44,16 +44,12 @@ pub struct VirtualDom {
     pub components: ScopeArena,
 
     /// The index of the root component
-    /// Should always be the first (gen0, id0)
+    /// Should always be the first (gen=0, id=0)
     pub base_scope: ScopeIdx,
 
     /// All components dump their updates into a queue to be processed
     pub(crate) event_queue: EventQueue,
 
-    /// Global contexts shared within the VirtualDOM
-    /// These are anchored to individual scopes, making them inaccessible if a context is created from a sibiling
-    pub(crate) contexts: HashMap<ContextId, Box<dyn Any>>,
-
     /// a strong allocation to the "caller" for the original component and its props
     #[doc(hidden)]
     _root_caller: Rc<OpaqueComponent<'static>>,
@@ -134,7 +130,7 @@ impl VirtualDom {
     /// let dom = VirtualDom::new(Example);
     /// ```
     pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
-        let mut components = Arena::new();
+        let mut components = ScopeArena::new(Arena::new());
 
         // Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
         // Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
@@ -149,20 +145,25 @@ impl VirtualDom {
 
         // Make the first scope
         // We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM
+        let link = components.clone();
         let base_scope = components
-            .insert_with(move |myidx| Scope::new(caller_ref, myidx, None, 0, _event_queue));
+            .with(|arena| {
+                arena.insert_with(move |myidx| {
+                    Scope::new(caller_ref, myidx, None, 0, _event_queue, link)
+                })
+            })
+            .unwrap();
 
         Self {
             _root_caller,
             base_scope,
             event_queue,
-            contexts: Default::default(),
-            components: ScopeArena::new(components),
+            components,
             _root_prop_type: TypeId::of::<P>(),
         }
     }
 
-    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom. from scratch
+    /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
     pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
         let mut diff_machine = DiffMachine::new();
 
@@ -171,8 +172,8 @@ impl VirtualDom {
         // Instead, this is done on top-level component
 
         let base = self.components.try_get(self.base_scope)?;
-        let update = self.event_queue.schedule_update(base);
-        update();
+        let immediate_update = self.event_queue.schedule_update(base);
+        immediate_update();
 
         self.progress_completely(&mut diff_machine)?;
 
@@ -317,6 +318,7 @@ impl VirtualDom {
                                     None,
                                     cur_height + 1,
                                     self.event_queue.clone(),
+                                    self.components.clone(),
                                 )
                             })
                         })?;
@@ -437,6 +439,12 @@ pub struct Scope {
     // The parent's scope ID
     pub parent: Option<ScopeIdx>,
 
+    // A reference to the list of components.
+    // This lets us traverse the component list whenever we need to access our parent or children.
+    arena_link: ScopeArena,
+
+    pub shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
+
     // Our own ID accessible from the component map
     pub myidx: ScopeIdx,
 
@@ -444,12 +452,10 @@ pub struct Scope {
 
     pub event_queue: EventQueue,
 
-    // A list of children
-    // TODO, repalce the hashset with a faster hash function
-    pub children: HashSet<ScopeIdx>,
-
     pub caller: Weak<OpaqueComponent<'static>>,
 
+    pub hookidx: RefCell<usize>,
+
     // ==========================
     // slightly unsafe stuff
     // ==========================
@@ -460,7 +466,7 @@ pub struct Scope {
     // 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(crate) hooks: RefCell<Vec<Hook>>,
+    hooks: RefCell<Vec<Hook>>,
 
     // Unsafety:
     // - is self-refenrential and therefore needs to point into the bump
@@ -483,6 +489,7 @@ impl Scope {
         parent: Option<ScopeIdx>,
         height: u32,
         event_queue: EventQueue,
+        arena_link: ScopeArena,
     ) -> Self {
         log::debug!(
             "New scope created, height is {}, idx is {:?}",
@@ -490,8 +497,14 @@ impl Scope {
             myidx
         );
 
-        // The Componet has a lifetime that's "stuck" to its original allocation.
-        // We need to "break" this reference and manually manage the lifetime.
+        // 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 broken_caller = unsafe {
@@ -502,15 +515,17 @@ impl Scope {
         };
 
         Self {
+            shared_contexts: Default::default(),
             caller: broken_caller,
             hooks: RefCell::new(Vec::new()),
             frames: ActiveFrame::new(),
-            children: HashSet::new(),
             listeners: Default::default(),
+            hookidx: Default::default(),
             parent,
             myidx,
             height,
             event_queue,
+            arena_link,
         }
     }
 
@@ -529,17 +544,11 @@ impl Scope {
     /// 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<'b>(&'b mut self) -> Result<()> {
+    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();
 
-        let ctx = Context {
-            idx: 0.into(),
-            _p: std::marker::PhantomData {},
-            scope: self,
-        };
-
         let caller = self
             .caller
             .upgrade()
@@ -547,8 +556,10 @@ impl Scope {
 
         let new_head = unsafe {
             // Cast the caller ptr from static to one with our own reference
-            std::mem::transmute::<&OpaqueComponent<'static>, &OpaqueComponent<'b>>(caller.as_ref())
-        }(ctx);
+            std::mem::transmute::<&OpaqueComponent<'static>, &OpaqueComponent<'sel>>(
+                caller.as_ref(),
+            )
+        }(&self);
 
         self.frames.cur_frame_mut().head_node = new_head.root;
 
@@ -604,92 +615,6 @@ impl Scope {
     }
 }
 
-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(""),
-            },
-        )
-    }
-
-    fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
-        Self {
-            generation: 0.into(),
-            frames: [a, b],
-        }
-    }
-
-    fn cur_frame(&self) -> &BumpFrame {
-        match *self.generation.borrow() & 1 == 0 {
-            true => &self.frames[0],
-            false => &self.frames[1],
-        }
-    }
-    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
-        }
-    }
-
-    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]
-        }
-    }
-}
-
 /// 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.
 ///
@@ -710,16 +635,9 @@ impl ActiveFrame {
 /// ```
 // 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> {
-    pub idx: RefCell<usize>,
-
-    // pub scope: ScopeIdx,
-    pub scope: &'src Scope,
+pub type Context<'src> = &'src Scope;
 
-    pub _p: std::marker::PhantomData<&'src ()>,
-}
-
-impl<'a> Context<'a> {
+impl Scope {
     /// Access the children elements passed into the component
     pub fn children(&self) -> Vec<VNode> {
         todo!("Children API not yet implemented for component Context")
@@ -727,21 +645,26 @@ impl<'a> Context<'a> {
 
     /// Create a subscription that schedules a future render for the reference component
     pub fn schedule_update(&self) -> impl Fn() -> () {
-        self.scope.event_queue.schedule_update(&self.scope)
+        self.event_queue.schedule_update(&self)
     }
 
     /// Create a suspended component from a future.
     ///
     /// When the future completes, the component will be renderered
-    pub fn suspend<F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
-        &self,
+    pub fn suspend<'a, F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
+        &'a self,
         _fut: impl Future<Output = LazyNodes<'a, F>>,
     ) -> VNode<'a> {
         todo!()
     }
 }
 
-impl<'scope> Context<'scope> {
+// ================================================
+//       Render Implementation for Components
+// ================================================
+//
+impl Scope {
+    // impl<'scope> Context<'scope> {
     /// 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.
@@ -749,7 +672,7 @@ impl<'scope> Context<'scope> {
     /// ## Example
     ///
     /// ```ignore
-    /// fn Component(ctx: Context<Props>) -> VNode {
+    /// fn Component(ctx: Context, props: &()) -> VNode {
     ///     // Lazy assemble the VNode tree
     ///     let lazy_tree = html! {<div> "Hello World" </div>};
     ///     
@@ -757,13 +680,13 @@ impl<'scope> Context<'scope> {
     ///     ctx.render(lazy_tree)
     /// }
     ///```
-    pub fn render<F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
-        &self,
+    pub fn render<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
+        &'scope self,
         lazy_nodes: LazyNodes<'scope, F>,
     ) -> DomTree {
         let ctx = NodeCtx {
-            scope_ref: self.scope,
-            idx: 0.into(),
+            scope_ref: self,
+            listener_id: 0.into(),
         };
 
         DomTree {
@@ -774,10 +697,15 @@ impl<'scope> Context<'scope> {
     }
 }
 
+// ================================================
+//       Hooks Implementation for Components
+// ================================================
+
 // 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>>;
 
-impl<'scope> Context<'scope> {
+impl Scope {
+    // impl<'scope> Context<'scope> {
     /// Store a value between renders
     ///
     /// - Initializer: closure used to create the initial hook state
@@ -794,8 +722,8 @@ impl<'scope> Context<'scope> {
     ///     )
     /// }
     /// ```
-    pub fn use_hook<'c, InternalHookState: 'static, Output: 'scope>(
-        &'c self,
+    pub fn use_hook<'scope, InternalHookState: 'static, Output: 'scope>(
+        &'scope self,
 
         // The closure that builds the hook state
         initializer: impl FnOnce() -> InternalHookState,
@@ -807,10 +735,10 @@ impl<'scope> Context<'scope> {
         // TODO: add this to the "clean up" group for when the component is dropped
         _cleanup: impl FnOnce(InternalHookState),
     ) -> Output {
-        let idx = *self.idx.borrow();
+        let idx = *self.hookidx.borrow();
 
         // Grab out the hook list
-        let mut hooks = self.scope.hooks.borrow_mut();
+        let mut hooks = self.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() {
@@ -818,7 +746,7 @@ impl<'scope> Context<'scope> {
             hooks.push(Box::pin(new_state));
         }
 
-        *self.idx.borrow_mut() += 1;
+        *self.hookidx.borrow_mut() += 1;
 
         let stable_ref = hooks
             .get_mut(idx)
@@ -839,88 +767,228 @@ impl<'scope> Context<'scope> {
         // We extend the lifetime of the internal state
         runner(unsafe { &mut *(internal_state as *mut _) })
     }
+}
 
-    fn create_context_provider<T: 'static>(&self, init: impl Fn() -> T) {}
+// ================================================
+//   Context API Implementation for Components
+// ================================================
+impl Scope {
+    pub fn create_context<T: 'static>(&self, init: impl Fn() -> T) {
+        let mut ctxs = self.shared_contexts.borrow_mut();
+        let ty = TypeId::of::<T>();
+
+        let initialized = self.use_hook(
+            || false,
+            |s| {
+                let i = *s;
+                *s = true;
+                i
+            },
+            |_| {},
+        );
 
-    fn try_consume_context<T: 'static>(&self) -> Result<&T> {
-        todo!()
+        match (initialized, ctxs.contains_key(&ty)) {
+            // Do nothing, already initialized and already exists
+            (true, true) => {}
+
+            // Needs to be initialized
+            (false, false) => {
+                ctxs.insert(ty, Rc::new(init())).unwrap();
+            }
+
+            (false, true) => panic!("Cannot initialize two contexts of the same type"),
+            (true, false) => panic!("Implementation failure resulted in missing context"),
+        }
+    }
+
+    pub fn try_use_context<T: 'static>(&self) -> Result<Rc<T>> {
+        let ty = TypeId::of::<T>();
+
+        let mut scope = Some(self);
+
+        while let Some(inner) = scope {
+            let shared_contexts = inner.shared_contexts.borrow();
+            if let Some(shared_ctx) = shared_contexts.get(&ty) {
+                return Ok(shared_ctx.clone().downcast().unwrap());
+            } else {
+                match inner.parent {
+                    Some(parid) => {
+                        let parent = inner
+                            .arena_link
+                            .try_get(parid)
+                            .map_err(|_| Error::FatalInternal("Failed to find parent"))?;
+
+                        scope = Some(parent);
+                    }
+                    None => return Err(Error::MissingSharedContext),
+                }
+            }
+        }
+
+        Err(Error::MissingSharedContext)
     }
 
-    fn consume_context<T: 'static>(&self) -> &T {
-        self.try_consume_context().unwrap()
+    pub fn use_context<T: 'static>(&self) -> Rc<T> {
+        self.try_use_context().unwrap()
     }
 }
 
-mod support {
-    use super::*;
+// ==================================================================================
+//                Supporting structs for the above abstractions
+// ==================================================================================
 
-    // 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<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + 'a;
-
-    #[derive(Debug, Default, Clone)]
-    pub struct EventQueue(pub(crate) Rc<RefCell<Vec<HeightMarker>>>);
-
-    impl EventQueue {
-        pub fn schedule_update(&self, source: &Scope) -> impl Fn() {
-            let inner = self.clone();
-            let marker = HeightMarker {
-                height: source.height,
-                idx: source.myidx,
-            };
-            move || inner.0.as_ref().borrow_mut().push(marker)
-        }
+// 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<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + 'a;
+
+#[derive(Debug, Default, Clone)]
+pub struct EventQueue(pub(crate) Rc<RefCell<Vec<HeightMarker>>>);
+
+impl EventQueue {
+    pub fn schedule_update(&self, source: &Scope) -> impl Fn() {
+        let inner = self.clone();
+        let marker = HeightMarker {
+            height: source.height,
+            idx: source.myidx,
+        };
+        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,
+}
 
-    /// 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 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))
     }
+}
 
-    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 DomTree 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(""),
+            },
+        )
     }
 
-    // NodeCtx is used to build VNodes in the component's memory space.
-    // This struct adds metadata to the final DomTree about listeners, attributes, and children
-    #[derive(Clone)]
-    pub struct NodeCtx<'a> {
-        pub scope_ref: &'a Scope,
-        pub idx: RefCell<usize>,
+    fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
+        Self {
+            generation: 0.into(),
+            frames: [a, b],
+        }
     }
 
-    impl<'a> NodeCtx<'a> {
-        pub fn bump(&self) -> &'a Bump {
-            &self.scope_ref.cur_frame().bump
+    fn cur_frame(&self) -> &BumpFrame {
+        match *self.generation.borrow() & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        }
+    }
+    fn cur_frame_mut(&mut self) -> &mut BumpFrame {
+        match *self.generation.borrow() & 1 == 0 {
+            true => &mut self.frames[0],
+            false => &mut self.frames[1],
         }
     }
 
-    impl Debug for NodeCtx<'_> {
-        fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-            Ok(())
+    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
         }
     }
 
-    #[derive(Debug, PartialEq, Hash)]
-    pub struct ContextId {
-        // Which component is the scope in
-        original: ScopeIdx,
+    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],
+        };
 
-        // What's the height of the scope
-        height: u32,
+        // 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
+        }
+    }
 
-        // Which scope is it (in order)
-        id: u32,
+    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]
+        }
     }
 }