Просмотр исходного кода

feat: static node infrastructure and ssr changes

Jonathan Kelley 4 лет назад
Родитель
Сommit
9abb047

+ 10 - 6
Cargo.toml

@@ -9,24 +9,27 @@ description = "Core functionality for Dioxus - a concurrent renderer-agnostic Vi
 
 [dependencies]
 dioxus-core = { path="./packages/core", version="0.1.0" }
-dioxus-core-macro = { path="./packages/core-macro", version="0.1.0" }
+dioxus-core-macro = { path="./packages/core-macro", version="0.1.0", optional=true }
 dioxus-html = { path="./packages/html", optional=true }
 dioxus-web = { path="./packages/web", optional=true }
 dioxus-webview = { path="./packages/webview", optional=true }
-dioxus-hooks = { path="./packages/hooks", version="0.0.0" }
+dioxus-hooks = { path="./packages/hooks", version="0.0.0", optional=true }
 
 
 [features]
-default = ["ssr", "hooks", "router", "core", "atoms", "html", "web", "desktop"]
+default = ["macro", "ssr", "hooks", "router", "core", "atoms", "html", "web", "desktop"]
 atoms = []
-core = []
+macro = ["dioxus-core-macro"]
 ssr = []
-hooks = []
+hooks = ["dioxus-hooks"]
 router = []
 html = ["dioxus-html"]
 web = ["dioxus-web"]
 desktop = ["dioxus-webview"]
 
+# Core just includes dioxus-core (the only required package)
+core = []
+
 
 [profile.dev]
 split-debuginfo = "unpacked"
@@ -41,6 +44,7 @@ separator = "0.4.1"
 serde = { version="1.0.126", features=["derive"] }
 surf = "2.2.0"
 env_logger = "*"
+async-std = { version="1.9.0", features=["attributes"] }
 
 [workspace]
 members = [
@@ -50,7 +54,7 @@ members = [
     "packages/web",
     "packages/webview",
     "packages/hooks",
+    "packages/ssr",
     # "packages/atoms",
-    # "packages/ssr",
     # "packages/docsite",
 ]

+ 5 - 11
README.md

@@ -36,14 +36,6 @@ If you know React, then you already know Dioxus.
 - Powerful and simple integrated state management
 - Rust! (enums, static types, modules, efficiency)
 
-### Key Differentiators
-
-- Immutability by default
-- Built-in suspense system
-- Integrations for isomorphic apps
-- Skip diffing altogether with signal API
-- Extremely portable without runtime requirements
-
 ## Get Started with...
 
 <table style="width:100%" align="center">
@@ -121,13 +113,15 @@ Dioxus is heavily inspired by React, but we want your transition to feel like an
 | Controlled Inputs       | ✅      | ✅     | stateful wrappers around inputs                       |
 | CSS/Inline Styles       | ✅      | ✅     | syntax for inline styles/attribute groups             |
 | Custom elements         | ✅      | ✅     | Define new element primitives                         |
-| Compile-time correct    | ✅      | ✅     | Throw errors on invalid template layouts              |
-| 1st class global state  | ✅      | ❓     | redux/recoil/mobx on top of context                   |
 | Suspense                | 🛠      | ✅     | schedule future render from future/promise            |
 | Cooperative Scheduling  | 🛠      | ✅     | Prioritize important events over non-important events |
+| NodeRef                 | 🛠      | ✅     | gain direct access to nodes [1]                       |
 | Fine-grained reactivity | 🛠      | ❓     | Skip diffing for fine-grain updates                   |
+| Compile-time correct    | ✅      | ❓     | Throw errors on invalid template layouts              |
 | Runs natively           | ✅      | ❓     | runs as a portable binary w/o a runtime (Node)        |
-| NodeRef                 | 🛠      | ✅     | gain direct access to nodes [1]                       |
+| 1st class global state  | ✅      | ❓     | redux/recoil/mobx on top of context                   |
+| Subtree Memoization     | ✅      | ❓     | skip diffing static element subtrees                  |
+| Heuristic Engine        | ✅      | ❓     | track component memory usage to minimize allocations  |
 
 - [1] Currently blocked until we figure out a cross-platform way of exposing an imperative Node API.
 

+ 14 - 12
examples/async.rs

@@ -2,7 +2,7 @@
 //!
 //! The example from the README.md
 
-use std::pin::Pin;
+use std::{pin::Pin, time::Duration};
 
 use dioxus::prelude::*;
 use futures::Future;
@@ -19,23 +19,25 @@ struct DogApi {
 
 const ENDPOINT: &str = "https://dog.ceo/api/breeds/image/random";
 
-struct Ex(Pin<Box<dyn Future<Output = ()> + 'static>>);
 static App: FC<()> = |cx| {
-    // let mut count = use_state(cx, || 0);
+    let mut count = use_state(cx, || 0);
     let mut fut = cx.use_hook(
         move || {
-            Ex(Box::pin(async {
+            Box::pin(async {
                 //
+                let mut tick = 0;
                 loop {
-                    match surf::get(ENDPOINT).recv_json::<DogApi>().await {
-                        Ok(_) => (),
-                        Err(_) => (),
-                    }
+                    async_std::task::sleep(Duration::from_millis(250)).await;
+                    log::debug!("ticking forward... {}", tick);
+                    tick += 1;
+                    // match surf::get(ENDPOINT).recv_json::<DogApi>().await {
+                    //     Ok(_) => (),
+                    //     Err(_) => (),
+                    // }
                 }
-            })
-                as Pin<Box<dyn Future<Output = ()> + 'static>>)
+            }) as Pin<Box<dyn Future<Output = ()> + 'static>>
         },
-        |h| &mut h.0,
+        |h| h,
         |_| {},
     );
 
@@ -43,7 +45,7 @@ static App: FC<()> = |cx| {
 
     cx.render(rsx! {
         div {
-
+            h1 {"it's working somewhat properly"}
         }
     })
 };

+ 1 - 1
packages/core/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
-    "rust-analyzer.inlayHints.enable": true
+    "rust-analyzer.inlayHints.enable": false
 }

+ 8 - 9
packages/core/Cargo.toml

@@ -31,20 +31,19 @@ log = "0.4"
 # # Serialize the Edits for use in Webview/Liveview instances
 serde = { version="1", features=["derive"], optional=true }
 
-smallvec = "1.6.1"
-
 # Backs scopes and unique keys
 slotmap = "1.0.3"
 
-# backs the fiber system for suspended components
-# todo: would like to use something smaller or just roll our own futures manually
-futures = "0.3.15"
+appendlist = "1.4.0"
 
+futures-util = "0.3.15"
 
-async-std = { version="1.9.0", features=["attributes"] }
-appendlist = "1.4.0"
+[dev-dependencies]
+dioxus-html = { path="../html" }
+dioxus-hooks = { path="../hooks" }
+env_logger = "*"
+# async-std = { version="1.9.0", features=["attributes"] }
 
 [features]
-default = ["serialize"]
-# default = []
+default = []
 serialize = ["slotmap/serde", "serde"]

+ 2 - 2
packages/core/examples/async.rs

@@ -1,7 +1,7 @@
 use std::pin::Pin;
 
 use dioxus_core::prelude::*;
-use futures::Future;
+use std::future::Future;
 
 fn main() {}
 
@@ -18,7 +18,7 @@ const App: FC<()> = |cx| {
     );
     // let g = unsafe { Pin::new_unchecked(fut) };
 
-    cx.submit_task(fut);
+    // cx.submit_task(fut);
 
     todo!()
 };

+ 2 - 3
packages/core/src/bumpframe.rs

@@ -2,7 +2,6 @@ use crate::hooklist::HookList;
 use crate::{arena::SharedArena, innerlude::*};
 use appendlist::AppendList;
 use bumpalo::Bump;
-use futures::FutureExt;
 use slotmap::DefaultKey;
 use slotmap::SlotMap;
 use std::marker::PhantomData;
@@ -34,11 +33,11 @@ impl ActiveFrame {
         Self::from_frames(
             BumpFrame {
                 bump: Bump::new(),
-                head_node: VNode::text(""),
+                head_node: VNode::static_text(""),
             },
             BumpFrame {
                 bump: Bump::new(),
-                head_node: VNode::text(""),
+                head_node: VNode::static_text(""),
             },
         )
     }

+ 164 - 0
packages/core/src/childiter.rs

@@ -0,0 +1,164 @@
+/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
+///
+/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
+/// "InsertBefore".
+struct RealChildIterator<'a> {
+    scopes: &'a SharedArena,
+
+    // Heuristcally we should never bleed into 5 completely nested fragments/components
+    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
+    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
+}
+
+impl<'a> RealChildIterator<'a> {
+    fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
+        Self {
+            scopes,
+            stack: smallvec::smallvec![(0, starter)],
+        }
+    }
+}
+
+// impl<'a> DoubleEndedIterator for ChildIterator<'a> {
+//     fn next_back(&mut self) -> Option<Self::Item> {
+//         todo!()
+//     }
+// }
+
+impl<'a> Iterator for RealChildIterator<'a> {
+    type Item = &'a VNode<'a>;
+
+    fn next(&mut self) -> Option<&'a VNode<'a>> {
+        let mut should_pop = false;
+        let mut returned_node = None;
+        let mut should_push = None;
+
+        while returned_node.is_none() {
+            if let Some((count, node)) = self.stack.last_mut() {
+                match node {
+                    // We can only exit our looping when we get "real" nodes
+                    VNode::Element(_) | VNode::Text(_) => {
+                        // We've recursed INTO an element/text
+                        // We need to recurse *out* of it and move forward to the next
+                        should_pop = true;
+                        returned_node = Some(&**node);
+                    }
+
+                    // If we get a fragment we push the next child
+                    VNode::Fragment(frag) => {
+                        let _count = *count as usize;
+                        if _count >= frag.children.len() {
+                            should_pop = true;
+                        } else {
+                            should_push = Some(&frag.children[_count]);
+                        }
+                    }
+
+                    // Immediately abort suspended nodes - can't do anything with them yet
+                    // VNode::Suspended => should_pop = true,
+                    VNode::Suspended { real } => todo!(),
+
+                    // For components, we load their root and push them onto the stack
+                    VNode::Component(sc) => {
+                        let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
+
+                        // Simply swap the current node on the stack with the root of the component
+                        *node = scope.root();
+                    }
+                }
+            } else {
+                // If there's no more items on the stack, we're done!
+                return None;
+            }
+
+            if should_pop {
+                self.stack.pop();
+                if let Some((id, _)) = self.stack.last_mut() {
+                    *id += 1;
+                }
+                should_pop = false;
+            }
+
+            if let Some(push) = should_push {
+                self.stack.push((0, push));
+                should_push = None;
+            }
+        }
+
+        returned_node
+    }
+}
+
+mod tests {
+    use super::*;
+    use crate as dioxus;
+    use crate::innerlude::*;
+    use crate::util::DebugDom;
+    use dioxus_core_macro::*;
+
+    // #[test]
+    // fn test_child_iterator() {
+    //     static App: FC<()> = |cx| {
+    //         cx.render(rsx! {
+    //             Fragment {
+    //                 div {}
+    //                 h1 {}
+    //                 h2 {}
+    //                 h3 {}
+    //                 Fragment {
+    //                     "internal node"
+    //                     div {
+    //                         "baller text shouldn't show up"
+    //                     }
+    //                     p {
+
+    //                     }
+    //                     Fragment {
+    //                         Fragment {
+    //                             "wow you really like framgents"
+    //                             Fragment {
+    //                                 "why are you like this"
+    //                                 Fragment {
+    //                                     "just stop now please"
+    //                                     Fragment {
+    //                                         "this hurts"
+    //                                         Fragment {
+    //                                             "who needs this many fragments?????"
+    //                                             Fragment {
+    //                                                 "just... fine..."
+    //                                                 Fragment {
+    //                                                     "no"
+    //                                                 }
+    //                                             }
+    //                                         }
+    //                                     }
+    //                                 }
+    //                             }
+    //                         }
+    //                     }
+    //                 }
+    //                 "my text node 1"
+    //                 "my text node 2"
+    //                 "my text node 3"
+    //                 "my text node 4"
+    //             }
+    //         })
+    //     };
+    //     let mut dom = VirtualDom::new(App);
+    //     let mut renderer = DebugDom::new();
+    //     dom.rebuild(&mut renderer).unwrap();
+    //     let starter = dom.base_scope().root();
+    //     let ite = RealChildIterator::new(starter, &dom.components);
+    //     for child in ite {
+    //         match child {
+    //             VNode::Element(el) => println!("Found: Element {}", el.tag_name),
+    //             VNode::Text(t) => println!("Found: Text {:?}", t.text),
+
+    //             // These would represent failing cases.
+    //             VNode::Fragment(_) => panic!("Found: Fragment"),
+    //             VNode::Suspended { real } => panic!("Found: Suspended"),
+    //             VNode::Component(_) => panic!("Found: Component"),
+    //         }
+    //     }
+    // }
+}

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

@@ -9,6 +9,7 @@ use crate::innerlude::FC;
 
 pub trait Properties: Sized {
     type Builder;
+    const IS_STATIC: bool = false;
     fn builder() -> Self::Builder;
 
     /// Memoization can only happen if the props are 'static
@@ -19,6 +20,7 @@ pub trait Properties: Sized {
 
 impl Properties for () {
     type Builder = EmptyBuilder;
+    const IS_STATIC: bool = true;
     fn builder() -> Self::Builder {
         EmptyBuilder {}
     }

+ 30 - 31
packages/core/src/context.rs

@@ -2,7 +2,6 @@ use crate::hooklist::HookList;
 use crate::{arena::SharedArena, innerlude::*};
 use appendlist::AppendList;
 use bumpalo::Bump;
-use futures::FutureExt;
 use slotmap::DefaultKey;
 use slotmap::SlotMap;
 use std::marker::PhantomData;
@@ -40,11 +39,9 @@ use std::{
 pub struct Context<'src, T> {
     pub props: &'src T,
     pub scope: &'src Scope,
-    pub tasks: &'src AppendList<&'src mut DTask<'src>>,
+    pub tasks: &'src RefCell<Vec<&'src mut PinnedTask>>,
 }
-
-pub type DTask<'s> = dyn Future<Output = ()> + 's;
-// pub type DTask = Pin<Box<dyn Future<Output = ()>>>;
+pub type PinnedTask = Pin<Box<dyn Future<Output = ()>>>;
 
 impl<'src, T> Copy for Context<'src, T> {}
 impl<'src, T> Clone for Context<'src, T> {
@@ -133,36 +130,34 @@ impl<'src, P> Context<'src, P> {
     ///     )
     /// }
     /// ```
-    pub fn use_hook<InternalHookState: 'static, Output: 'src>(
+    pub fn use_hook<State, Output, Init, Run, Cleanup>(
         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 {
+        initializer: Init,
+        runner: Run,
+        cleanup: Cleanup,
+    ) -> Output
+    where
+        State: 'static,
+        Output: 'src,
+        Init: FnOnce() -> State,
+        Run: FnOnce(&'src mut State) -> Output,
+        Cleanup: FnOnce(State),
+    {
         // If the idx is the same as the hook length, then we need to add the current hook
         if self.scope.hooks.at_end() {
             let new_state = initializer();
             self.scope.hooks.push(new_state);
         }
 
-        let state = self.scope.hooks.next::<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.
-            "###,
-        );
+        const ERR_MSG: &str = 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.
+"###;
 
-        runner(state)
+        runner(self.scope.hooks.next::<State>().expect(ERR_MSG))
     }
 
     /// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
@@ -178,8 +173,7 @@ impl<'src, P> Context<'src, P> {
     ///
     ///
     pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
-        let mut scope = self.scope;
-        let mut cxs = scope.shared_contexts.borrow_mut();
+        let mut cxs = self.scope.shared_contexts.borrow_mut();
         let ty = TypeId::of::<T>();
 
         let is_initialized = self.use_hook(
@@ -274,6 +268,7 @@ impl<'src, P> Context<'src, P> {
         fut: &'src mut Pin<Box<dyn Future<Output = Output> + 'static>>,
         callback: Fut,
     ) -> VNode<'src> {
+        use futures_util::FutureExt;
         match fut.now_or_never() {
             Some(out) => {
                 let suspended_cx = SuspendedContext {};
@@ -290,6 +285,10 @@ impl<'src, P> Context<'src, P> {
     }
 
     /// `submit_task` will submit the future to be polled.
+    ///
+    /// This is useful when you have some async task that needs to be progressed.
+    ///
+    /// ## Explanation
     /// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
     ///
     /// Tasks can't return anything, but they can be controlled with the returned handle
@@ -299,8 +298,8 @@ impl<'src, P> Context<'src, P> {
     ///
     ///
     ///
-    pub fn submit_task(&self, mut task: &'src mut DTask<'src>) -> TaskHandle {
-        self.tasks.push(task);
+    pub fn submit_task(&self, task: &'src mut PinnedTask) -> TaskHandle {
+        self.tasks.borrow_mut().push(task);
 
         TaskHandle { _p: PhantomData {} }
     }

+ 143 - 237
packages/core/src/diff.rs

@@ -5,14 +5,31 @@
 //! ------
 //!
 //! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
-//! Components, Fragments, Suspense, and additional batching operations.
+//! Components, Fragments, Suspense, SubTree memoization, and additional batching operations.
 //!
-//! Implementation Details:
-//! -----------------------
+//! ## Implementation Details:
+//!
+//! ### IDs for elements
 //!
 //! All nodes are addressed by their IDs. The RealDom provides an imperative interface for making changes to these nodes.
 //! We don't necessarily require that DOM changes happen instnatly during the diffing process, so the implementor may choose
-//! to batch nodes if it is more performant for their application. We care about an ID size of u32
+//! to batch nodes if it is more performant for their application. The expectation is that renderers use a Slotmap for nodes
+//! whose keys can be converted to u64 on FFI boundaries.
+//!
+//! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we
+//! always make sure to copy over the ID. If we don't do this properly, the realdomnode will be populated incorrectly and
+//! brick the user's page.
+//!
+//! ## Subtree Memoization
+//! We also employ "subtree memoization" which saves us from having to check trees which take no dynamic content. We can
+//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
+//! calls to "create" propogate this information upwards. Structures like the one below are entirely static:
+//! ```rust
+//! rsx!( div { class: "hello world", "this node is entirely static" } )
+//! ```
+//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so its up to the reconciler to
+//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP
+//!
 //!
 //!
 //! Further Reading and Thoughts
@@ -25,10 +42,7 @@
 use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue};
 use fxhash::{FxHashMap, FxHashSet};
 
-use std::{
-    any::Any,
-    rc::{Rc, Weak},
-};
+use std::any::Any;
 
 /// The accompanying "real dom" exposes an imperative API for controlling the UI layout
 ///
@@ -46,8 +60,10 @@ pub trait RealDom<'a> {
     fn push_root(&mut self, root: RealDomNode);
 
     // Add Nodes to the dom
-    fn append_child(&mut self);
-    fn replace_with(&mut self);
+    // add m nodes from the stack
+    fn append_children(&mut self, many: u32);
+    // replace the n-m node on the stack with the m nodes
+    fn replace_with(&mut self, many: u32);
 
     // Remove Nodesfrom the dom
     fn remove(&mut self);
@@ -101,7 +117,10 @@ pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
     pub seen_nodes: FxHashSet<ScopeIdx>,
 }
 
-impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
+impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
+where
+    Dom: RealDom<'bump>,
+{
     pub fn new(
         dom: &'real mut Dom,
         components: &'bump SharedArena,
@@ -141,6 +160,10 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
             // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
             // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
             (VNode::Text(old), VNode::Text(new)) => {
+                if old.is_static {
+                    log::debug!("encountered static text node: {:?}", old.text);
+                }
+
                 if old.text != new.text {
                     self.dom.push_root(old.dom_id.get());
                     log::debug!("Text has changed {}, {}", old.text, new.text);
@@ -156,8 +179,8 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
                 // In Dioxus, this is less likely to occur unless through a fragment
                 if new.tag_name != old.tag_name || new.namespace != old.namespace {
                     self.dom.push_root(old.dom_id.get());
-                    self.create(new_node);
-                    self.dom.replace_with();
+                    let meta = self.create(new_node);
+                    self.dom.replace_with(meta.added_to_stack);
                     return;
                 }
                 new.dom_id.set(old.dom_id.get());
@@ -181,7 +204,7 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
                     // make sure the component's caller function is up to date
                     let scope = self.components.try_get_mut(scope_id.unwrap()).unwrap();
                     // .with_scope(scope_id.unwrap(), |scope| {
-                    scope.caller = Rc::downgrade(&new.caller);
+                    scope.caller = new.caller.clone();
 
                     // ack - this doesn't work on its own!
                     scope.update_children(new.children);
@@ -265,17 +288,17 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
             // in the case where the old node was a fragment but the new nodes are text,
             (VNode::Fragment(_) | VNode::Component(_), VNode::Element(_) | VNode::Text(_)) => {
                 // find the first real element int the old node
-                let mut iter = RealChildIterator::new(old_node, self.components);
-                if let Some(first) = iter.next() {
-                    // replace the very first node with the creation of the element or text
-                } else {
-                    // there are no real elements in the old fragment...
-                    // We need to load up the next real
-                }
-                if let VNode::Component(old) = old_node {
-                    // schedule this component's destructor to be run
-                    todo!()
-                }
+                // let mut iter = RealChildIterator::new(old_node, self.components);
+                // if let Some(first) = iter.next() {
+                //     // replace the very first node with the creation of the element or text
+                // } else {
+                //     // there are no real elements in the old fragment...
+                //     // We need to load up the next real
+                // }
+                // if let VNode::Component(old) = old_node {
+                //     // schedule this component's destructor to be run
+                //     todo!()
+                // }
             }
             // In the case where real nodes are being replaced by potentially
             (VNode::Element(_) | VNode::Text(_), VNode::Fragment(new)) => {
@@ -283,11 +306,11 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
             }
             (VNode::Text(_), VNode::Element(_)) => {
                 self.create(new_node);
-                self.dom.replace_with();
+                self.dom.replace_with(1);
             }
             (VNode::Element(_), VNode::Text(_)) => {
                 self.create(new_node);
-                self.dom.replace_with();
+                self.dom.replace_with(1);
             }
 
             _ => {
@@ -295,7 +318,28 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
             }
         }
     }
+}
+
+// When we create new nodes, we need to propagate some information back up the call chain.
+// This gives the caller some information on how to handle things like insertins, appending, and subtree discarding.
+struct CreateMeta {
+    is_static: bool,
+    added_to_stack: u32,
+}
 
+impl CreateMeta {
+    fn new(is_static: bool, added_to_tack: u32) -> Self {
+        Self {
+            is_static,
+            added_to_stack: added_to_tack,
+        }
+    }
+}
+
+impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
+where
+    Dom: RealDom<'bump>,
+{
     // Emit instructions to create the given virtual node.
     //
     // The change list stack may have any shape upon entering this function:
@@ -305,15 +349,22 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
     // When this function returns, the new node is on top of the change list stack:
     //
     //     [... node]
-    fn create(&mut self, node: &'bump VNode<'bump>) {
-        // debug_assert!(self.dom.traversal_is_committed());
-        log::warn!("Creating node!");
+    fn create(&mut self, node: &'bump VNode<'bump>) -> CreateMeta {
+        log::warn!("Creating node! ... {:#?}", node);
         match node {
             VNode::Text(text) => {
                 let real_id = self.dom.create_text_node(text.text);
                 text.dom_id.set(real_id);
+                CreateMeta::new(text.is_static, 1)
             }
             VNode::Element(el) => {
+                // we have the potential to completely eliminate working on this node in the future(!)
+                //
+                // This can only be done if all of the elements properties (attrs, children, listeners, etc) are static
+                // While creating these things, keep track if we can memoize this element.
+                // At the end, we'll set this flag on the element to skip it
+                let mut is_static: bool = true;
+
                 let VElement {
                     key,
                     tag_name,
@@ -322,22 +373,27 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
                     children,
                     namespace,
                     dom_id,
+                    is_static: el_is_static,
                 } = el;
-                // log::info!("Creating {:#?}", node);
+
                 let real_id = if let Some(namespace) = namespace {
                     self.dom.create_element(tag_name, Some(namespace))
                 } else {
                     self.dom.create_element(tag_name, None)
                 };
-                el.dom_id.set(real_id);
+                dom_id.set(real_id);
 
                 listeners.iter().enumerate().for_each(|(idx, listener)| {
+                    listener.mounted_node.set(real_id);
                     self.dom
                         .new_event_listener(listener.event, listener.scope, idx, real_id);
-                    listener.mounted_node.set(real_id);
+
+                    // if the node has an event listener, then it must be visited ?
+                    is_static = false;
                 });
 
                 for attr in *attributes {
+                    is_static = is_static && attr.is_static;
                     self.dom.set_attribute(&attr.name, &attr.value, *namespace);
                 }
 
@@ -359,28 +415,26 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
                 // }
 
                 for child in *children {
-                    self.create(child);
-                    if let VNode::Fragment(_) = child {
-                        // do nothing
-                        // fragments append themselves
-                    } else {
-                        self.dom.append_child();
-                    }
+                    let child_meta = self.create(child);
+                    is_static = is_static && child_meta.is_static;
+
+                    // append whatever children were generated by this call
+                    self.dom.append_children(child_meta.added_to_stack);
                 }
-            }
 
-            VNode::Component(component) => {
-                // let real_id = self.dom.create_placeholder();
+                if is_static {
+                    log::debug!("created a static node {:#?}", node);
+                } else {
+                    log::debug!("created a dynamic node {:#?}", node);
+                }
 
-                // let root_id = next_id();
-                // self.dom.save_known_root(root_id);
+                el_is_static.set(is_static);
+                CreateMeta::new(is_static, 1)
+            }
 
+            VNode::Component(vcomponent) => {
                 log::debug!("Mounting a new component");
-                let caller: Weak<WrappedCaller> = Rc::downgrade(&component.caller);
-
-                // We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
-                // those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
-                // However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
+                let caller = vcomponent.caller.clone();
 
                 let parent_idx = self.cur_idx;
 
@@ -398,50 +452,69 @@ impl<'real, 'bump, Dom: RealDom<'bump>> DiffMachine<'real, 'bump, Dom> {
                                 height,
                                 self.event_queue.new_channel(height, new_idx),
                                 self.components.clone(),
-                                component.children,
+                                vcomponent.children,
+                                self.task_queue.new_submitter(),
                             )
                         })
                     })
                     .unwrap();
 
-                {
-                    let cur_component = self.components.try_get_mut(idx).unwrap();
-                    let mut ch = cur_component.descendents.borrow_mut();
-                    ch.insert(idx);
-                    std::mem::drop(ch);
-                }
-
-                // yaaaaay lifetimes out of thin air
-                // really tho, we're merging the frame lifetimes together
+                // This code is supposed to insert the new idx into the parent's descendent list, but it doesn't really work.
+                // This is mostly used for cleanup - to remove old scopes when components are destroyed.
+                // TODO
+                //
+                // self.components
+                //     .try_get_mut(idx)
+                //     .unwrap()
+                //     .descendents
+                //     .borrow_mut()
+                //     .insert(idx);
+
+                // TODO: abstract this unsafe into the arena abstraction
                 let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() };
                 let new_component = inner.get_mut(idx).unwrap();
 
                 // Actually initialize the caller's slot with the right address
-                component.ass_scope.set(Some(idx));
+                vcomponent.ass_scope.set(Some(idx));
 
                 // Run the scope for one iteration to initialize it
                 new_component.run_scope().unwrap();
 
-                // And then run the diff algorithm
-                let _real_id = self.dom.create_placeholder();
+                // By using "diff_node" instead of "create", we delegate the mutations to the child
+                // However, "diff_node" always expects a real node on the stack, so we put a placeholder so it knows where to start.
+                //
+                // TODO: we need to delete (IE relcaim this node, otherwise the arena will grow infinitely)
+                let _ = self.dom.create_placeholder();
                 self.diff_node(new_component.old_frame(), new_component.next_frame());
 
                 // Finally, insert this node as a seen node.
                 self.seen_nodes.insert(idx);
+
+                // Virtual Components don't result in new nodes on the stack
+                // However, we can skip them from future diffing if they take no children, have no props, take no key, etc.
+                CreateMeta::new(vcomponent.is_static, 0)
             }
 
-            // we go the the "known root" but only operate on a sibling basis
+            // Fragments are the only nodes that can contain dynamic content (IE through curlies or iterators).
+            // We can never ignore their contents, so the prescence of a fragment indicates that we need always diff them.
             VNode::Fragment(frag) => {
-                // create the children directly in the space
-                for child in frag.children {
-                    self.create(child);
-                    self.dom.append_child();
+                let mut nodes_added = 0;
+                for child in frag.children.iter().rev() {
+                    // different types of nodes will generate different amounts on the stack
+                    // nested fragments will spew a ton of nodes onto the stack
+                    // TODO: make sure that our order (.rev) makes sense in a nested situation
+                    let new_meta = self.create(child);
+                    nodes_added += new_meta.added_to_stack;
                 }
+
+                // Never ignore
+                CreateMeta::new(false, nodes_added)
             }
 
             VNode::Suspended { real } => {
                 let id = self.dom.create_placeholder();
                 real.set(id);
+                CreateMeta::new(false, 1)
             }
         }
     }
@@ -1171,11 +1244,9 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
     //
     // When this function returns, the change list stack is in the same state.
     pub fn create_and_append_children(&mut self, new: &'bump [VNode<'bump>]) {
-        // debug_assert!(self.dom.traversal_is_committed());
         for child in new {
-            // self.create_and_append(node, parent)
-            self.create(child);
-            self.dom.append_child();
+            let meta = self.create(child);
+            self.dom.append_children(meta.added_to_stack);
         }
     }
 
@@ -1227,168 +1298,3 @@ enum KeyedPrefixResult {
     // the beginning of `new` and `old` we already processed.
     MoreWorkToDo(usize),
 }
-
-/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
-///
-/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
-/// "InsertBefore".
-struct RealChildIterator<'a> {
-    scopes: &'a SharedArena,
-
-    // Heuristcally we should never bleed into 5 completely nested fragments/components
-    // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
-    stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
-}
-
-impl<'a> RealChildIterator<'a> {
-    fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
-        Self {
-            scopes,
-            stack: smallvec::smallvec![(0, starter)],
-        }
-    }
-}
-
-// impl<'a> DoubleEndedIterator for ChildIterator<'a> {
-//     fn next_back(&mut self) -> Option<Self::Item> {
-//         todo!()
-//     }
-// }
-
-impl<'a> Iterator for RealChildIterator<'a> {
-    type Item = &'a VNode<'a>;
-
-    fn next(&mut self) -> Option<&'a VNode<'a>> {
-        let mut should_pop = false;
-        let mut returned_node = None;
-        let mut should_push = None;
-
-        while returned_node.is_none() {
-            if let Some((count, node)) = self.stack.last_mut() {
-                match node {
-                    // We can only exit our looping when we get "real" nodes
-                    VNode::Element(_) | VNode::Text(_) => {
-                        // We've recursed INTO an element/text
-                        // We need to recurse *out* of it and move forward to the next
-                        should_pop = true;
-                        returned_node = Some(&**node);
-                    }
-
-                    // If we get a fragment we push the next child
-                    VNode::Fragment(frag) => {
-                        let _count = *count as usize;
-                        if _count >= frag.children.len() {
-                            should_pop = true;
-                        } else {
-                            should_push = Some(&frag.children[_count]);
-                        }
-                    }
-
-                    // Immediately abort suspended nodes - can't do anything with them yet
-                    // VNode::Suspended => should_pop = true,
-                    VNode::Suspended { real } => todo!(),
-
-                    // For components, we load their root and push them onto the stack
-                    VNode::Component(sc) => {
-                        let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
-
-                        // Simply swap the current node on the stack with the root of the component
-                        *node = scope.root();
-                    }
-                }
-            } else {
-                // If there's no more items on the stack, we're done!
-                return None;
-            }
-
-            if should_pop {
-                self.stack.pop();
-                if let Some((id, _)) = self.stack.last_mut() {
-                    *id += 1;
-                }
-                should_pop = false;
-            }
-
-            if let Some(push) = should_push {
-                self.stack.push((0, push));
-                should_push = None;
-            }
-        }
-
-        returned_node
-    }
-}
-
-mod tests {
-    use super::*;
-    use crate as dioxus;
-    use crate::innerlude::*;
-    use crate::util::DebugDom;
-    use dioxus_core_macro::*;
-
-    // #[test]
-    // fn test_child_iterator() {
-    //     static App: FC<()> = |cx| {
-    //         cx.render(rsx! {
-    //             Fragment {
-    //                 div {}
-    //                 h1 {}
-    //                 h2 {}
-    //                 h3 {}
-    //                 Fragment {
-    //                     "internal node"
-    //                     div {
-    //                         "baller text shouldn't show up"
-    //                     }
-    //                     p {
-
-    //                     }
-    //                     Fragment {
-    //                         Fragment {
-    //                             "wow you really like framgents"
-    //                             Fragment {
-    //                                 "why are you like this"
-    //                                 Fragment {
-    //                                     "just stop now please"
-    //                                     Fragment {
-    //                                         "this hurts"
-    //                                         Fragment {
-    //                                             "who needs this many fragments?????"
-    //                                             Fragment {
-    //                                                 "just... fine..."
-    //                                                 Fragment {
-    //                                                     "no"
-    //                                                 }
-    //                                             }
-    //                                         }
-    //                                     }
-    //                                 }
-    //                             }
-    //                         }
-    //                     }
-    //                 }
-    //                 "my text node 1"
-    //                 "my text node 2"
-    //                 "my text node 3"
-    //                 "my text node 4"
-    //             }
-    //         })
-    //     };
-    //     let mut dom = VirtualDom::new(App);
-    //     let mut renderer = DebugDom::new();
-    //     dom.rebuild(&mut renderer).unwrap();
-    //     let starter = dom.base_scope().root();
-    //     let ite = RealChildIterator::new(starter, &dom.components);
-    //     for child in ite {
-    //         match child {
-    //             VNode::Element(el) => println!("Found: Element {}", el.tag_name),
-    //             VNode::Text(t) => println!("Found: Text {:?}", t.text),
-
-    //             // These would represent failing cases.
-    //             VNode::Fragment(_) => panic!("Found: Fragment"),
-    //             VNode::Suspended { real } => panic!("Found: Suspended"),
-    //             VNode::Component(_) => panic!("Found: Component"),
-    //         }
-    //     }
-    // }
-}

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

@@ -4,7 +4,7 @@
 //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
 //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
 
-use std::{ops::Deref, rc::Rc};
+use std::{cell::Cell, ops::Deref, rc::Rc};
 
 use crate::innerlude::{RealDomNode, ScopeIdx};
 

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

@@ -43,6 +43,7 @@ pub(crate) mod innerlude {
     pub use crate::nodebuilder::*;
     pub use crate::nodes::*;
     pub use crate::scope::*;
+    pub use crate::tasks::*;
     pub use crate::util::*;
     pub use crate::virtual_dom::*;
 
@@ -62,6 +63,7 @@ pub mod prelude {
     pub use crate::styles::{AsAttr, StyleBuilder};
 
     pub use crate::util::RealDomNode;
+    pub use crate::virtual_dom::VirtualDom;
     pub use nodes::*;
 
     pub use crate::nodebuilder::LazyNodes;

+ 77 - 158
packages/core/src/nodebuilder.rs

@@ -308,7 +308,7 @@ where
     /// let my_div: VNode = builder.finish();
     /// ```
     #[inline]
-    pub fn finish(self) -> VNode<'a> {
+    pub fn finish(mut self) -> VNode<'a> {
         let bump = self.cx.bump();
 
         let children: &'a Children = bump.alloc(self.children);
@@ -317,6 +317,23 @@ where
         let listeners: &'a Listeners = bump.alloc(self.listeners);
         let listeners: &'a [Listener<'a>] = listeners.as_ref();
 
+        for listener in listeners {
+            // bump the context id forward
+            let id = self.cx.listener_id.get();
+            self.cx.listener_id.set(id + 1);
+
+            // Add this listener to the context list
+            // This casts the listener to a self-referential pointer
+            // This is okay because the bump arena is stable
+
+            // TODO: maybe not add it into CTX immediately
+            let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(listener) };
+            self.cx.scope_ref.listeners.borrow_mut().push((
+                r.mounted_node as *const _ as *mut _,
+                r.callback as *const _ as *mut _,
+            ));
+        }
+
         let attributes: &'a Attributes = bump.alloc(self.attributes);
         let attributes: &'a [Attribute<'a>] = attributes.as_ref();
 
@@ -338,57 +355,57 @@ where
     Attributes: 'a + AsRef<[Attribute<'a>]>,
     Children: 'a + AsRef<[VNode<'a>]>,
 {
-    /// Add a new event listener to this element.
-    ///
-    /// The `event` string specifies which event will be listened for. The
-    /// `callback` function is the function that will be invoked if the
-    /// specified event occurs.
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// use dioxus::{builder::*, bumpalo::Bump};
-    ///
-    /// let b = Bump::new();
-    ///
-    /// // A button that does something when clicked!
-    /// let my_button = button(&b)
-    ///     .on("click", |event| {
-    ///         // ...
-    ///     })
-    ///     .finish();
-    /// ```
-    pub fn on(self, event: &'static str, callback: impl FnMut(VirtualEvent) + 'a) -> Self {
-        let bump = &self.cx.bump();
-        let listener = Listener {
-            event,
-            callback: bump.alloc(callback),
-            scope: self.cx.scope_ref.arena_idx,
-            mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
-        };
-        self.add_listener(listener)
-    }
-
-    pub fn add_listener(mut self, listener: Listener<'a>) -> Self {
-        self.listeners.push(listener);
-
-        // bump the context id forward
-        let id = self.cx.listener_id.get();
-        self.cx.listener_id.set(id + 1);
-
-        // Add this listener to the context list
-        // This casts the listener to a self-referential pointer
-        // This is okay because the bump arena is stable
-        self.listeners.last().map(|g| {
-            let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) };
-            self.cx.scope_ref.listeners.borrow_mut().push((
-                r.mounted_node as *const _ as *mut _,
-                r.callback as *const _ as *mut _,
-            ));
-        });
-
-        self
-    }
+    // / Add a new event listener to this element.
+    // /
+    // / The `event` string specifies which event will be listened for. The
+    // / `callback` function is the function that will be invoked if the
+    // / specified event occurs.
+    // /
+    // / # Example
+    // /
+    // / ```no_run
+    // / use dioxus::{builder::*, bumpalo::Bump};
+    // /
+    // / let b = Bump::new();
+    // /
+    // / // A button that does something when clicked!
+    // / let my_button = button(&b)
+    // /     .on("click", |event| {
+    // /         // ...
+    // /     })
+    // /     .finish();
+    // / ```
+    // pub fn on(self, event: &'static str, callback: impl FnMut(VirtualEvent) + 'a) -> Self {
+    //     let bump = &self.cx.bump();
+    //     let listener = Listener {
+    //         event,
+    //         callback: bump.alloc(callback),
+    //         scope: self.cx.scope_ref.arena_idx,
+    //         mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
+    //     };
+    //     self.add_listener(listener)
+    // }
+
+    // pub fn add_listener(mut self, listener: Listener<'a>) -> Self {
+    //     self.listeners.push(listener);
+
+    //     // bump the context id forward
+    //     let id = self.cx.listener_id.get();
+    //     self.cx.listener_id.set(id + 1);
+
+    //     // Add this listener to the context list
+    //     // This casts the listener to a self-referential pointer
+    //     // This is okay because the bump arena is stable
+    //     self.listeners.last().map(|g| {
+    //         let r = unsafe { std::mem::transmute::<&Listener<'a>, &Listener<'static>>(g) };
+    //         self.cx.scope_ref.listeners.borrow_mut().push((
+    //             r.mounted_node as *const _ as *mut _,
+    //             r.callback as *const _ as *mut _,
+    //         ));
+    //     });
+
+    //     self
+    // }
 }
 
 impl<'a, 'b, Listeners, Children>
@@ -410,61 +427,16 @@ where
     /// let my_div = div(&b).attr("id", "my-div").finish();
     /// ```
     pub fn attr(mut self, name: &'static str, args: std::fmt::Arguments) -> Self {
-        let value = match args.as_str() {
-            Some(static_str) => static_str,
-            None => {
-                use bumpalo::core_alloc::fmt::Write;
-                let mut s = bumpalo::collections::String::new_in(self.cx.bump());
-                s.write_fmt(args).unwrap();
-                s.into_bump_str()
-            }
-        };
+        let (value, is_static) = raw_text(self.cx.bump(), args);
 
         self.attributes.push(Attribute {
             name,
             value,
+            is_static,
             namespace: None,
         });
         self
     }
-
-    /// Conditionally add a "boolean-style" attribute to this element.
-    ///
-    /// If the `should_add` parameter is true, then adds an attribute with the
-    /// given `name` and an empty string value. If the `should_add` parameter is
-    /// false, then the attribute is not added.
-    ///
-    /// This method is useful for attributes whose semantics are defined by
-    /// whether or not the attribute is present or not, and whose value is
-    /// ignored. Example attributes like this include:
-    ///
-    /// * `checked`
-    /// * `hidden`
-    /// * `selected`
-    ///
-    /// # Example
-    ///
-    /// ```no_run
-    /// use dioxus::{builder::*, bumpalo::Bump};
-    /// use js_sys::Math;
-    ///
-    /// let b = Bump::new();
-    ///
-    /// // Create the `<div>` that is randomly hidden 50% of the time.
-    /// let my_div = div(&b)
-    ///     .bool_attr("hidden", Math::random() >= 0.5)
-    ///     .finish();
-    /// ```
-    pub fn bool_attr(mut self, name: &'static str, should_add: bool) -> Self {
-        if should_add {
-            self.attributes.push(Attribute {
-                name,
-                value: "",
-                namespace: None,
-            });
-        }
-        self
-    }
 }
 
 impl<'a, 'b, Listeners, Attributes>
@@ -618,72 +590,18 @@ impl IntoVNode<'_> for Option<()> {
     }
 }
 
-/// Construct a text VNode.
-///
-/// This is `dioxus`'s virtual DOM equivalent of `document.createTextVNode`.
-///
-/// # Example
-///
-/// ```no_run
-/// use dioxus::builder::*;
-///
-/// let my_text = text("hello, dioxus!");
-/// ```
-#[inline]
-pub fn text<'a>(contents: &'a str) -> VNode<'a> {
-    VNode::text(contents)
-}
-
-pub fn text2<'a>(contents: bumpalo::collections::String<'a>) -> VNode<'a> {
-    let f: &'a str = contents.into_bump_str();
-    VNode::text(f)
-}
-
-pub fn text3<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> VNode<'a> {
-    // This is a cute little optimization
-    //
-    // We use format_args! on everything. However, not all textnodes have dynamic content, and thus are completely static.
-    // we can just short-circuit to the &'static str version instead of having to allocate in the bump arena.
-    //
-    // In the most general case, this prevents the need for any string allocations for simple code IE:
-    // ```
-    //  div {"abc"}
-    // ```
-    VNode::text(raw_text(bump, args))
-}
-
-pub fn raw_text<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> &'a str {
+pub fn raw_text<'a>(bump: &'a bumpalo::Bump, args: std::fmt::Arguments) -> (&'a str, bool) {
     match args.as_str() {
-        Some(static_str) => static_str,
+        Some(static_str) => (static_str, true),
         None => {
             use bumpalo::core_alloc::fmt::Write;
             let mut s = bumpalo::collections::String::new_in(bump);
             s.write_fmt(args).unwrap();
-            s.into_bump_str()
+            (s.into_bump_str(), false)
         }
     }
 }
 
-/// Construct an attribute for an element.
-///
-/// # Example
-///
-/// This example creates the `id="my-id"` for some element like `<div
-/// id="my-id"/>`.
-///
-/// ```no_run
-/// use dioxus::builder::*;
-///
-/// let my_id_attr = attr("id", "my-id");
-/// ```
-pub fn attr<'a>(name: &'static str, value: &'a str) -> Attribute<'a> {
-    Attribute {
-        name,
-        value,
-        namespace: None,
-    }
-}
-
 pub fn virtual_child<'a, T: Properties + 'a>(
     cx: NodeFactory<'a>,
     f: FC<T>,
@@ -726,7 +644,7 @@ impl<'a> NodeFactory<'a> {
 
     /// Create some text that's allocated along with the other vnodes
     pub fn text(&self, args: Arguments) -> VNode<'a> {
-        text3(self.bump(), args)
+        VNode::text(self.bump(), args)
     }
 
     /// Create an element builder
@@ -749,10 +667,11 @@ impl<'a> NodeFactory<'a> {
         val: Arguments,
         namespace: Option<&'static str>,
     ) -> Attribute<'a> {
-        let value = raw_text(self.bump(), val);
+        let (value, is_static) = raw_text(self.bump(), val);
         Attribute {
             name,
             value,
+            is_static,
             namespace,
         }
     }

+ 43 - 14
packages/core/src/nodes.rs

@@ -7,7 +7,7 @@ use crate::{
     arena::SharedArena,
     events::VirtualEvent,
     innerlude::{Context, Properties, RealDom, RealDomNode, Scope, ScopeIdx, FC},
-    nodebuilder::{text3, NodeFactory},
+    nodebuilder::NodeFactory,
 };
 use appendlist::AppendList;
 use bumpalo::Bump;
@@ -39,7 +39,7 @@ pub enum VNode<'src> {
 
     /// A "suspended component"
     /// This is a masqeurade over an underlying future that needs to complete
-    /// When the future is completed, the VNode will then trigger a render
+    /// When the future is completed, the VNode will then trigger a render and the `real` field gets populated
     Suspended { real: Cell<RealDomNode> },
 
     /// A User-defined componen node (node type COMPONENT_NODE)
@@ -84,6 +84,7 @@ impl<'old, 'new> VNode<'old> {
                     },
                     namespace: el.namespace.clone(),
                     dom_id: el.dom_id.clone(),
+                    is_static: el.is_static.clone(),
                 };
 
                 VNode::Element(new.alloc_with(move || new_el))
@@ -121,21 +122,33 @@ impl<'a> VNode<'a> {
             children,
             namespace,
             dom_id: Cell::new(RealDomNode::empty()),
+            is_static: Cell::new(false),
         });
         VNode::Element(element)
     }
 
-    /// Construct a new text node with the given text.
-    #[inline]
-    pub fn text(text: &'a str) -> VNode<'a> {
+    pub fn static_text(text: &'static str) -> VNode {
         VNode::Text(VText {
             text,
+            is_static: true,
             dom_id: Cell::new(RealDomNode::empty()),
         })
     }
-
-    pub fn text_args(bump: &'a Bump, args: Arguments) -> VNode<'a> {
-        text3(bump, args)
+    /// Construct a new text node with the given text.
+    pub fn text(bump: &'a Bump, args: Arguments) -> VNode<'a> {
+        match args.as_str() {
+            Some(text) => VNode::static_text(text),
+            None => {
+                use bumpalo::core_alloc::fmt::Write;
+                let mut s = bumpalo::collections::String::new_in(bump);
+                s.write_fmt(args).unwrap();
+                VNode::Text(VText {
+                    text: s.into_bump_str(),
+                    is_static: false,
+                    dom_id: Cell::new(RealDomNode::empty()),
+                })
+            }
+        }
     }
 
     #[inline]
@@ -191,6 +204,7 @@ impl Debug for VNode<'_> {
 #[derive(Clone)]
 pub struct VText<'src> {
     pub text: &'src str,
+    pub is_static: bool,
     pub dom_id: Cell<RealDomNode>,
 }
 
@@ -208,6 +222,7 @@ pub struct VElement<'a> {
     pub children: &'a [VNode<'a>],
     pub namespace: Option<&'static str>,
     pub dom_id: Cell<RealDomNode>,
+    pub is_static: Cell<bool>,
 }
 
 /// An attribute on a DOM node, such as `id="my-thing"` or
@@ -216,6 +231,7 @@ pub struct VElement<'a> {
 pub struct Attribute<'a> {
     pub name: &'static str,
     pub value: &'a str,
+    pub is_static: bool,
 
     /// If an attribute is "namespaced", then it belongs to a group
     /// The most common namespace is the "style" namespace
@@ -336,6 +352,8 @@ pub struct VComponent<'src> {
 
     pub comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
 
+    pub is_static: bool,
+
     // a pointer into the bump arena (given by the 'src lifetime)
     // raw_props: Box<dyn Any>,
     raw_props: *const (),
@@ -402,6 +420,15 @@ impl<'a> VComponent<'a> {
             None => NodeKey(None),
         };
 
+        let caller = create_component_caller(component, raw_props);
+
+        // If the component does not have children, has no props (we can't memoize props), and has no no key, then we don't
+        // need to bother diffing it in the future
+        //
+        // This is more of an optimization to prevent unnecessary descending through the tree during diffing, rather than
+        // actually speeding up the diff process itself
+        let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
+
         Self {
             user_fc,
             comparator,
@@ -409,7 +436,8 @@ impl<'a> VComponent<'a> {
             children,
             ass_scope: Cell::new(None),
             key,
-            caller: create_closure(component, raw_props),
+            caller,
+            is_static,
             mounted_root: Cell::new(RealDomNode::empty()),
         }
     }
@@ -417,14 +445,14 @@ impl<'a> VComponent<'a> {
 
 type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
 
-fn create_closure<'a, P: 'a>(
+pub fn create_component_caller<'a, P: 'a>(
     user_component: FC<P>,
     raw_props: *const (),
 ) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
     let g: Captured = Rc::new(move |scp: &Scope| -> VNode {
         // cast back into the right lifetime
         let safe_props: &'_ P = unsafe { &*(raw_props as *const P) };
-        let tasks = AppendList::new();
+        let tasks = RefCell::new(Vec::new());
         let cx: Context<P> = Context {
             props: safe_props,
             scope: scp,
@@ -433,11 +461,12 @@ fn create_closure<'a, P: 'a>(
 
         let g = user_component(cx);
 
-        // collect the submitted tasks
-        println!("tasks submittted: {:#?}", tasks.len());
-        // log::debug!("tasks submittted: {:#?}", tasks.len());
+        for task in tasks.borrow_mut().drain(..) {
+            scp.submit_task(task);
+        }
 
         let g2 = unsafe { std::mem::transmute(g) };
+
         g2
     });
     let r: Captured<'static> = unsafe { std::mem::transmute(g) };

+ 42 - 28
packages/core/src/scope.rs

@@ -2,10 +2,10 @@ use crate::hooklist::HookList;
 use crate::{arena::SharedArena, innerlude::*};
 use appendlist::AppendList;
 use bumpalo::Bump;
-use futures::FutureExt;
 use slotmap::DefaultKey;
 use slotmap::SlotMap;
 use std::marker::PhantomData;
+use std::sync::Arc;
 use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
@@ -14,7 +14,7 @@ use std::{
     future::Future,
     ops::Deref,
     pin::Pin,
-    rc::{Rc, Weak},
+    rc::Rc,
 };
 
 // We need to pin the hook so it doesn't move as we initialize the list of hooks
@@ -51,7 +51,7 @@ pub struct Scope {
 
     pub event_channel: Rc<dyn Fn() + 'static>,
 
-    pub caller: Weak<WrappedCaller>,
+    pub caller: Rc<WrappedCaller>,
 
     // ==========================
     // slightly unsafe stuff
@@ -73,6 +73,8 @@ pub struct Scope {
     // NEEDS TO BE PRIVATE
     pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
 
+    pub task_submitter: TaskSubmitter,
+
     pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
 }
 
@@ -85,13 +87,14 @@ impl Scope {
     // Scopes cannot be made anywhere else except for this file
     // Therefore, their lifetimes are connected exclusively to the virtual dom
     pub fn new<'creator_node>(
-        caller: Weak<WrappedCaller>,
+        caller: Rc<WrappedCaller>,
         arena_idx: ScopeIdx,
         parent: Option<ScopeIdx>,
         height: u32,
         event_channel: EventChannel,
         arena_link: SharedArena,
         child_nodes: &'creator_node [VNode<'creator_node>],
+        task_submitter: TaskSubmitter,
     ) -> Self {
         log::debug!(
             "New scope created, height is {}, idx is {:?}",
@@ -109,6 +112,7 @@ impl Scope {
             height,
             event_channel,
             arena_link,
+            task_submitter,
             listener_idx: Default::default(),
             frames: ActiveFrame::new(),
             hooks: Default::default(),
@@ -119,7 +123,7 @@ impl Scope {
         }
     }
 
-    pub fn update_caller<'creator_node>(&mut self, caller: Weak<WrappedCaller>) {
+    pub fn update_caller<'creator_node>(&mut self, caller: Rc<WrappedCaller>) {
         self.caller = caller;
     }
 
@@ -136,19 +140,15 @@ impl Scope {
         // This breaks any latent references, invalidating every pointer referencing into it.
         self.frames.next().bump.reset();
 
+        log::debug!("clearing listeners!");
         // Remove all the outdated listeners
         self.listeners.borrow_mut().clear();
 
         unsafe { self.hooks.reset() };
         self.listener_idx.set(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 c3: &WrappedCaller = caller.as_ref();
+        let c3: &WrappedCaller = self.caller.as_ref();
 
         self.frames.cur_frame_mut().head_node = unsafe { self.call_user_component(c3) };
 
@@ -176,28 +176,42 @@ impl Scope {
         // Convert the raw ptr into an actual object
         // This operation is assumed to be safe
 
-        log::debug!("Calling listeners! {:?}", self.listeners.borrow().len());
+        log::debug!(
+            "There are  {:?} listeners associated with this scope {:#?}",
+            self.listeners.borrow().len(),
+            self.arena_idx
+        );
+
         let mut listners = self.listeners.borrow_mut();
-        let (_, listener) = listners
-            .iter()
-            .find(|(domptr, _)| {
-                let p = unsafe { &**domptr };
-                p.get() == real_node_id.expect("realdomnode not found, propery handling of true virtual events not managed")
-            })
-            .expect(&format!(
-                "Failed to find real node with ID {:?}",
-                real_node_id
-            ));
-
-        // TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster!
-        unsafe {
-            let mut listener_fn = &mut **listener;
-            listener_fn(event);
+
+        // let listener = listners.get(trigger);
+        let raw_listener = listners.iter().find(|(domptr, _)| {
+            let search = unsafe { &**domptr };
+            let search_id = search.get();
+            log::info!("searching listener {:#?}", search_id);
+            match real_node_id {
+                Some(e) => search_id == e,
+                None => false,
+            }
+        });
+
+        match raw_listener {
+            Some((_node, listener)) => unsafe {
+                // TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster!
+                let listener_fn = &mut **listener;
+                listener_fn(event);
+            },
+            None => todo!(),
         }
 
         Ok(())
     }
 
+    pub fn submit_task(&self, task: &mut Pin<Box<dyn Future<Output = ()>>>) {
+        log::debug!("Task submitted into scope");
+        (self.task_submitter)(DTask::new(task, self.arena_idx));
+    }
+
     pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
         self.frames.current_head_node()
     }
@@ -210,7 +224,7 @@ impl Scope {
         self.frames.cur_frame()
     }
 
-    pub(crate) fn root<'a>(&'a self) -> &'a VNode<'a> {
+    pub fn root<'a>(&'a self) -> &'a VNode<'a> {
         &self.frames.cur_frame().head_node
     }
 }

+ 1 - 4
packages/core/src/styles.rs

@@ -12,10 +12,7 @@
 //!
 //!
 
-use crate::{
-    innerlude::Attribute,
-    nodebuilder::{text3, NodeFactory},
-};
+use crate::{innerlude::Attribute, nodebuilder::NodeFactory};
 
 pub struct StyleBuilder;
 pub trait AsAttr<'a> {

+ 50 - 42
packages/core/src/tasks.rs

@@ -16,14 +16,16 @@ use std::{
     task::{Context, Poll},
 };
 
-use futures::{Future, Stream, StreamExt};
+use futures_util::{Future, Stream, StreamExt};
 use slotmap::{DefaultKey, SlotMap};
 
 use crate::{events::EventTrigger, prelude::ScopeIdx};
 
+pub type TaskSubmitter = Arc<dyn Fn(DTask)>;
+
 pub struct TaskQueue {
     slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>,
-    submitter: Arc<dyn Fn(DTask)>,
+    submitter: TaskSubmitter,
 }
 
 impl TaskQueue {
@@ -34,11 +36,16 @@ impl TaskQueue {
 
         let submitter = Arc::new(move |task| {
             let mut slots = slots2.write().unwrap();
+            log::debug!("Task submitted into global task queue");
             slots.insert(task);
         });
         Self { slots, submitter }
     }
 
+    pub fn new_submitter(&self) -> TaskSubmitter {
+        self.submitter.clone()
+    }
+
     pub fn submit_task(&mut self, task: DTask) -> TaskHandle {
         let key = self.slots.write().unwrap().insert(task);
         TaskHandle { key }
@@ -126,43 +133,44 @@ impl DTask {
     }
 }
 
-mod tests {
-    use std::time::Duration;
-
-    use super::*;
-    use bumpalo::Bump;
-
-    #[async_std::test]
-    async fn example() {
-        let bump = Bump::new();
-        type RawTask = Pin<Box<dyn Future<Output = ()>>>;
-        // build the three
-        let f1 = bump.alloc(Box::pin(async {
-            //
-            async_std::task::sleep(Duration::from_secs(3)).await;
-            println!("3 sec")
-        }) as RawTask);
-
-        let f2 = bump.alloc(Box::pin(async {
-            //
-            async_std::task::sleep(Duration::from_secs(2)).await;
-            println!("2 sec")
-        }) as RawTask);
-
-        let f3 = bump.alloc(Box::pin(async {
-            //
-            async_std::task::sleep(Duration::from_secs(1)).await;
-            println!("1 sec");
-        }) as RawTask);
-
-        let mut queue = TaskQueue::new();
-        queue.submit_task(DTask::debug_new(f1));
-        queue.submit_task(DTask::debug_new(f2));
-        queue.submit_task(DTask::debug_new(f3));
-
-        while !queue.is_empty() {
-            let next = queue.next().await;
-            println!("Event received {:#?}", next);
-        }
-    }
-}
+// #[cfg(test)]
+// mod tests {
+//     use std::time::Duration;
+
+//     use super::*;
+//     use bumpalo::Bump;
+
+//     #[async_std::test]
+//     async fn example() {
+//         let bump = Bump::new();
+//         type RawTask = Pin<Box<dyn Future<Output = ()>>>;
+//         // build the three
+//         let f1 = bump.alloc(Box::pin(async {
+//             //
+//             async_std::task::sleep(Duration::from_secs(3)).await;
+//             println!("3 sec")
+//         }) as RawTask);
+
+//         let f2 = bump.alloc(Box::pin(async {
+//             //
+//             async_std::task::sleep(Duration::from_secs(2)).await;
+//             println!("2 sec")
+//         }) as RawTask);
+
+//         let f3 = bump.alloc(Box::pin(async {
+//             //
+//             async_std::task::sleep(Duration::from_secs(1)).await;
+//             println!("1 sec");
+//         }) as RawTask);
+
+//         let mut queue = TaskQueue::new();
+//         queue.submit_task(DTask::debug_new(f1));
+//         queue.submit_task(DTask::debug_new(f2));
+//         queue.submit_task(DTask::debug_new(f3));
+
+//         while !queue.is_empty() {
+//             let next = queue.next().await;
+//             println!("Event received {:#?}", next);
+//         }
+//     }
+// }

+ 73 - 3
packages/core/src/util.rs

@@ -4,6 +4,8 @@ use std::{
     vec::Drain,
 };
 
+use futures_util::StreamExt;
+
 use crate::innerlude::*;
 
 #[derive(PartialEq, Debug, Clone, Default)]
@@ -67,18 +69,28 @@ impl RealDomNode {
 
 pub struct DebugDom {
     counter: u64,
+    logging: bool,
 }
 impl DebugDom {
     pub fn new() -> Self {
-        Self { counter: 0 }
+        Self {
+            counter: 0,
+            logging: false,
+        }
+    }
+    pub fn with_logging_enabled() -> Self {
+        Self {
+            counter: 0,
+            logging: true,
+        }
     }
 }
 impl<'a> RealDom<'a> for DebugDom {
     fn push_root(&mut self, root: RealDomNode) {}
 
-    fn append_child(&mut self) {}
+    fn append_children(&mut self, many: u32) {}
 
-    fn replace_with(&mut self) {}
+    fn replace_with(&mut self, many: u32) {}
 
     fn remove(&mut self) {}
 
@@ -120,3 +132,61 @@ impl<'a> RealDom<'a> for DebugDom {
         todo!()
     }
 }
+
+async fn launch_demo(app: FC<()>) {
+    let mut dom = VirtualDom::new(app);
+    let mut real_dom = DebugDom::new();
+    dom.rebuild(&mut real_dom).unwrap();
+
+    while let Some(evt) = dom.tasks.next().await {
+        //
+        log::debug!("Event triggered! {:#?}", evt);
+    }
+}
+
+// #[cfg(test)]
+// mod tests {
+//     use super::*;
+//     use crate as dioxus;
+//     use std::{pin::Pin, time::Duration};
+
+//     use crate::builder::DioxusElement;
+//     use dioxus::prelude::*;
+//     use futures::Future;
+
+//     #[async_std::test]
+//     async fn async_tick() {
+//         static App: FC<()> = |cx| {
+//             // let mut count = use_state(cx, || 0);
+//             let mut fut = cx.use_hook(
+//                 move || {
+//                     Box::pin(async {
+//                         //
+//                         let mut tick: i32 = 0;
+//                         loop {
+//                             async_std::task::sleep(Duration::from_millis(250)).await;
+//                             log::debug!("ticking forward... {}", tick);
+//                             tick += 1;
+//                             // match surf::get(ENDPOINT).recv_json::<DogApi>().await {
+//                             //     Ok(_) => (),
+//                             //     Err(_) => (),
+//                             // }
+//                         }
+//                     }) as Pin<Box<dyn Future<Output = ()> + 'static>>
+//                 },
+//                 |h| h,
+//                 |_| {},
+//             );
+
+//             cx.submit_task(fut);
+
+//             cx.render(LazyNodes::new(move |f| {
+//                 f.text(format_args!("it's sorta working"))
+//             }))
+//         };
+
+//         std::env::set_var("RUST_LOG", "debug");
+//         env_logger::init();
+//         launch_demo(App).await;
+//     }
+// }

+ 48 - 38
packages/core/src/virtual_dom.rs

@@ -24,6 +24,9 @@ use crate::{arena::SharedArena, innerlude::*};
 use appendlist::AppendList;
 use slotmap::DefaultKey;
 use slotmap::SlotMap;
+use std::any::Any;
+use std::cell::RefCell;
+use std::pin::Pin;
 use std::{any::TypeId, fmt::Debug, rc::Rc};
 
 pub type ScopeIdx = DefaultKey;
@@ -45,11 +48,9 @@ pub struct VirtualDom {
     /// All components dump their updates into a queue to be processed
     pub(crate) event_queue: EventQueue,
 
-    pub(crate) tasks: TaskQueue,
+    pub tasks: TaskQueue,
 
-    /// a strong allocation to the "caller" for the original component and its props
-    #[doc(hidden)]
-    _root_caller: Rc<WrappedCaller>,
+    root_props: std::pin::Pin<Box<dyn std::any::Any>>,
 
     /// Type of the original cx. This is stored as TypeId so VirtualDom does not need to be generic.
     ///
@@ -129,59 +130,36 @@ impl VirtualDom {
     pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
         let components = SharedArena::new(SlotMap::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.
-        let _root_caller: Rc<WrappedCaller> = Rc::new(move |scope: &Scope| {
-            // let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
-            // the lifetime of this closure is just as long as the lifetime on the scope reference
-            // this closure moves root props (which is static) into this closure
-            let props = unsafe { &*(&root_props as *const _) };
-            let tasks = AppendList::new();
-            let t2 = &tasks;
-
-            let cx = Context {
-                props,
-                scope,
-                tasks: t2,
-            };
-            let nodes = root(cx);
-
-            log::debug!("There were {:?} tasks submitted", tasks.len());
-            // cast a weird lifetime to shake the appendlist thing
-            // TODO: move all of this into the same logic that governs other components
-            // we want to wrap everything in a dioxus root component
-            unsafe { std::mem::transmute(nodes) }
-            // std::mem::drop(tasks);
-            //
-            // nodes
-        });
-
-        // Create a weak reference to the OpaqueComponent for the root scope to use as its render function
-        let caller_ref = Rc::downgrade(&_root_caller);
+        let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
+        let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
 
         // Build a funnel for hooks to send their updates into. The `use_hook` method will call into the update funnel.
         let event_queue = EventQueue::default();
         let _event_queue = event_queue.clone();
 
-        // 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 tasks = TaskQueue::new();
+        let submitter = tasks.new_submitter();
+
         let base_scope = components
             .with(|arena| {
                 arena.insert_with_key(move |myidx| {
                     let event_channel = _event_queue.new_channel(0, myidx);
-                    Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[])
+                    let caller = crate::nodes::create_component_caller(root, props_ptr as *const _);
+                    Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter)
                 })
             })
             .unwrap();
 
+        log::debug!("base scope is {:#?}", base_scope);
+
         Self {
-            _root_caller,
             base_scope,
             event_queue,
             components,
-            tasks: TaskQueue::new(),
+            root_props,
+            tasks,
             _root_prop_type: TypeId::of::<P>(),
         }
     }
@@ -191,6 +169,38 @@ impl VirtualDom {
 // Private Methods for the VirtualDom
 // ======================================
 impl VirtualDom {
+    /// Rebuilds the VirtualDOM from scratch, but uses a "dummy" RealDom.
+    ///
+    /// Used in contexts where a real copy of the  structure doesn't matter, and the VirtualDOM is the source of truth.
+    ///
+    /// ## Why?
+    ///
+    /// This method uses the `DebugDom` under the hood - essentially making the VirtualDOM's diffing patches a "no-op".
+    ///
+    /// SSR takes advantage of this by using Dioxus itself as the source of truth, and rendering from the tree directly.
+    pub fn rebuild_in_place(&mut self) -> Result<()> {
+        let mut realdom = DebugDom::new();
+        let mut diff_machine = DiffMachine::new(
+            &mut realdom,
+            &self.components,
+            self.base_scope,
+            self.event_queue.clone(),
+            &self.tasks,
+        );
+
+        // Schedule an update and then immediately call it on the root component
+        // This is akin to a hook being called from a listener and requring a re-render
+        // Instead, this is done on top-level component
+        let base = self.components.try_get(self.base_scope)?;
+
+        let update = &base.event_channel;
+        update();
+
+        self.progress_completely(&mut diff_machine)?;
+
+        Ok(())
+    }
+
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
     /// Currently this doesn't do what we want it to do
     pub fn rebuild<'s, Dom: RealDom<'s>>(&'s mut self, realdom: &mut Dom) -> Result<()> {

+ 1 - 1
packages/hooks/Cargo.toml

@@ -7,4 +7,4 @@ edition = "2018"
 # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
 
 [dependencies]
-dioxus-core = { path = "../core" }
+dioxus-core = { path="../core" }

+ 8 - 0
packages/hooks/src/usestate.rs

@@ -24,6 +24,14 @@ use std::{
 /// Additionally, a ton of std::ops traits are implemented for the `UseState` wrapper, meaning any mutative type operations
 /// will automatically be called on the WIP value.
 ///
+/// ## Combinators
+///
+/// On top of the methods to set/get state, `use_state` also supports fancy combinators to extend its functionality:
+/// - `.classic()` and `.split()`  convert the hook into the classic React-style hook
+///     ```rust
+///     let (state, set_state) = use_state(cx, || 10).split()
+///     ```
+///
 ///
 /// Usage:
 /// ```ignore

+ 1 - 0
packages/ssr/Cargo.toml

@@ -11,6 +11,7 @@ dioxus-core = { path = "../core", version = "0.1.0", features = ["serialize"] }
 
 
 [dev-dependencies]
+dioxus-html = { path = "../html" }
 tide-websockets = "*"
 thiserror = "1.0.23"
 log = "0.4.13"

+ 18 - 0
packages/ssr/README.md

@@ -0,0 +1,18 @@
+# Dioxus SSR
+
+Render a Dioxus VirtualDOM to a string.
+
+```rust
+// Our app:
+const App: FC<()> = |cx| cx.render(rsx!(div {"hello world!"}));
+
+// Build the virtualdom from our app
+let mut vdom = VirtualDOM::new(App);
+
+// This runs components, lifecycles, etc. without needing a physical dom. Some features (like noderef) won't work.
+vdom.rebuild_in_place();
+
+// Render the entire virtualdom from the root
+let text = dioxus_ssr::render_root(&vdom);
+assert_eq!(text, "<div>hello world!</div>")
+```

+ 2 - 101
packages/ssr/index.html

@@ -1,102 +1,3 @@
-<div title="About W3Schools">
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 0
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 1
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 2
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 3
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 4
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 5
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 6
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 7
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 8
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 9
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 10
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 11
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 12
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 13
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 14
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 15
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 16
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 17
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 18
-        </p>
-    </div>
-    <div title="About W3Schools" style="color:blue;text-align:center" class="About W3Schools">
-        <p title="About W3Schools">
-            Hello world!: 19
-        </p>
-    </div>
+<div>
+hello world!
 </div>

+ 103 - 54
packages/ssr/src/lib.rs

@@ -7,79 +7,128 @@
 //! an external renderer is needed to progress the component lifecycles. The `TextRenderer` shows how to use the Virtual DOM
 //! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
 //! `render` method.
-//!
-//! ```ignore
-//! fn main() {
-//!     let renderer = TextRenderer::<()>::new(|_| html! {<div> "Hello world" </div>});
-//!     let output = renderer.render();
-//!     assert_eq!(output, "<div>Hello World</div>");
-//! }
-//! ```
-//!
-//! The `TextRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
-//!
 
-use dioxus_core::prelude::{VNode, FC};
-pub mod tostring;
+use std::fmt::{Arguments, Display, Formatter};
 
-pub mod prelude {
-    pub use dioxus_core::prelude::*;
+use dioxus_core::prelude::*;
+use dioxus_core::{nodes::VNode, prelude::ScopeIdx, virtual_dom::VirtualDom};
+use std::io::{BufWriter, Result, Write};
+
+pub fn render_root(vdom: &VirtualDom) -> String {
+    format!("{:}", TextRenderer::new(vdom))
 }
 
-/// The `TextRenderer` provides a way of rendering a Dioxus Virtual DOM to a String.
+/// A configurable text renderer for the Dioxus VirtualDOM.
+///
 ///
+/// ## Details
 ///
+/// This uses the `Formatter` infrastructure so you can write into anything that supports `write_fmt`. We can't accept
+/// any generic writer, so you need to "Display" the text renderer. This is done through `format!` or `format_args!`
 ///
-pub struct TextRenderer<T> {
-    _root_type: std::marker::PhantomData<T>,
+///
+///
+///
+pub struct TextRenderer<'a> {
+    vdom: &'a VirtualDom,
 }
 
-impl<T> TextRenderer<T> {
-    /// Create a new text-renderer instance from a functional component root.
-    /// Automatically progresses the creation of the VNode tree to completion.
-    ///
-    /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
-    pub fn new(root: FC<T>) -> Self {
-        Self {
-            _root_type: std::marker::PhantomData {},
-        }
+impl<'a> TextRenderer<'a> {
+    fn new(vdom: &'a VirtualDom) -> Self {
+        Self { vdom }
     }
 
-    /// Create a new text renderer from an existing Virtual DOM.
-    /// This will progress the existing VDom's events to completion.
-    pub fn from_vdom() -> Self {
-        todo!()
+    fn html_render(&self, node: &VNode, f: &mut std::fmt::Formatter) -> std::fmt::Result {
+        match node {
+            VNode::Element(el) => {
+                write!(f, "<{}", el.tag_name)?;
+                for attr in el.attributes {
+                    write!(f, " {}=\"{}\"", attr.name, attr.value)?;
+                }
+                write!(f, ">\n")?;
+                for child in el.children {
+                    self.html_render(child, f)?;
+                }
+                write!(f, "\n</{}>", el.tag_name)?;
+                Ok(())
+            }
+            VNode::Text(text) => write!(f, "{}", text.text),
+            VNode::Suspended { .. } => todo!(),
+            VNode::Component(vcomp) => {
+                todo!()
+                // let id = vcomp.ass_scope.borrow().unwrap();
+                // let id = vcomp.ass_scope.as_ref().borrow().unwrap();
+                // let new_node = dom
+                //     .components
+                //     .try_get(id)
+                //     .unwrap()
+                //     .frames
+                //     .current_head_node();
+                // html_render(&dom, new_node, f)
+            }
+            VNode::Fragment(_) => todo!(),
+        }
     }
+}
 
-    /// Pass new args to the root function
-    pub fn update(&mut self, new_val: T) {
-        todo!()
+impl Display for TextRenderer<'_> {
+    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
+        let root = self.vdom.base_scope();
+        let root_node = root.root();
+        self.html_render(root_node, f)
     }
+}
 
-    /// Modify the root function in place, forcing a re-render regardless if the props changed
-    pub fn update_mut(&mut self, modifier: impl Fn(&mut T)) {
-        todo!()
-    }
+#[cfg(test)]
+mod tests {
+    use super::*;
 
-    /// Immediately render a VNode to string
-    pub fn to_text(root: VNode) -> String {
-        todo!()
-    }
+    use dioxus_core as dioxus;
+    use dioxus_html as dioxus_elements;
 
-    /// Render the virtual DOM to a string
-    pub fn render(&self) -> String {
-        let mut buffer = String::new();
+    const SIMPLE_APP: FC<()> = |cx| {
+        //
+        cx.render(rsx!(div {
+            "hello world!"
+        }))
+    };
 
-        // iterate through the internal patch queue of virtual dom, and apply them to the buffer
-        /*
-         */
-        todo!()
+    const SLIGHTLY_MORE_COMPLEX: FC<()> = |cx| {
+        cx.render(rsx! {
+            div {
+                title: "About W3Schools"
+                {(0..20).map(|f| rsx!{
+                    div {
+                        title: "About W3Schools"
+                        style: "color:blue;text-align:center"
+                        class: "About W3Schools"
+                        p {
+                            title: "About W3Schools"
+                            "Hello world!: {f}"
+                        }
+                    }
+                })}
+            }
+        })
+    };
+
+    #[test]
+    fn test_to_string_works() {
+        let mut dom = VirtualDom::new(SIMPLE_APP);
+        dom.rebuild_in_place().expect("failed to run virtualdom");
+        dbg!(render_root(&dom));
     }
 
-    /// Render VDom to an existing buffer
-    /// TODO @Jon, support non-string buffers to actually make this useful
-    /// Currently, this only supports overwriting an existing buffer, instead of just
-    pub fn render_mut(&self, buf: &mut String) {
-        todo!()
+    #[test]
+    fn test_write_to_file() {
+        use std::fs::File;
+
+        let mut file = File::create("index.html").unwrap();
+
+        let mut dom = VirtualDom::new(SIMPLE_APP);
+        dom.rebuild_in_place().expect("failed to run virtualdom");
+
+        file.write_fmt(format_args!("{}", TextRenderer::new(&dom)))
+            .unwrap();
     }
 }

+ 0 - 89
packages/ssr/src/tostring.rs

@@ -1,89 +0,0 @@
-use std::fmt::Display;
-
-use dioxus_core::prelude::*;
-use dioxus_core::{nodes::VNode, prelude::ScopeIdx, virtual_dom::VirtualDom};
-
-struct SsrRenderer {
-    dom: VirtualDom,
-}
-
-impl Display for SsrRenderer {
-    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
-        let node = self.dom.base_scope().frames.current_head_node();
-
-        html_render(&self.dom, node, f)
-    }
-}
-
-// recursively walk the tree
-fn html_render(
-    dom: &VirtualDom,
-    node: &VNode,
-    f: &mut std::fmt::Formatter<'_>,
-) -> std::fmt::Result {
-    match node {
-        VNode::Element(el) => {
-            write!(f, "<{}", el.tag_name)?;
-            for attr in el.attributes {
-                write!(f, " {}=\"{}\"", attr.name, attr.value)?;
-            }
-            write!(f, ">\n")?;
-            for child in el.children {
-                html_render(dom, child, f)?;
-            }
-            write!(f, "\n</{}>", el.tag_name)?;
-            Ok(())
-        }
-        VNode::Text(text) => write!(f, "{}", text),
-        VNode::Suspended => todo!(),
-        VNode::Component(vcomp) => {
-            let id = vcomp.ass_scope.borrow().unwrap();
-            // let id = vcomp.ass_scope.as_ref().borrow().unwrap();
-            let new_node = dom
-                .components
-                .try_get(id)
-                .unwrap()
-                .frames
-                .current_head_node();
-            html_render(&dom, new_node, f)
-        }
-        VNode::Fragment(_) => todo!(),
-    }
-}
-
-#[test]
-fn test_serialize() {
-    let mut dom = VirtualDom::new(|cx| {
-        //
-        //
-        //
-        cx.render(rsx! {
-            div {
-                title: "About W3Schools"
-                {(0..20).map(|f| rsx!{
-                    div {
-                        title: "About W3Schools"
-                        // style: "color:blue;text-align:center"
-                        class: "About W3Schools"
-                        p {
-                            title: "About W3Schools"
-                            "Hello world!: {f}"
-                        }
-                    }
-                })}
-            }
-        })
-    });
-
-    dom.rebuild();
-    let renderer = SsrRenderer { dom };
-
-    use std::fs::File;
-    use std::io::prelude::*;
-
-    let mut file = File::create("index.html").unwrap();
-    let buf = renderer.to_string();
-    // dbg!(buf);
-    file.write(buf.as_bytes());
-    // dbg!(renderer.to_string());
-}

+ 1 - 1
packages/web/.vscode/settings.json

@@ -1,3 +1,3 @@
 {
     "rust-analyzer.inlayHints.enable": false
-}
+}

+ 6 - 2
packages/web/Cargo.toml

@@ -23,11 +23,12 @@ generational-arena = "0.2.8"
 wasm-bindgen-test = "0.3.21"
 once_cell = "1.7.2"
 atoms = { path="../atoms" }
-
 async-channel = "1.6.1"
 anyhow = "1.0.41"
 slotmap = "1.0.3"
 
+futures-util = "0.3.15"
+
 [dependencies.web-sys]
 version = "0.3.50"
 features = [
@@ -72,6 +73,9 @@ crate-type = ["cdylib", "rlib"]
 
 [dev-dependencies]
 im-rc = "15.0.0"
-rand = { version="0.8.4", features=["small_rng"] }
+# rand = { version="0.8.4", features=["small_rng"] }
 separator = "0.4.1"
 uuid = { version="0.8.2", features=["v4", "wasm-bindgen"] }
+dioxus-hooks = { path="../hooks" }
+gloo-timers = { version="0.2.1", features=["futures"] }
+# wasm-timer = "0.2.5"

+ 71 - 0
packages/web/examples/async_web.rs

@@ -0,0 +1,71 @@
+//! Basic example that renders a simple VNode to the browser.
+
+use dioxus::events::on::MouseEvent;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_hooks::*;
+use dioxus_html as dioxus_elements;
+// use wasm_timer;
+
+use std::future::Future;
+
+use std::{pin::Pin, time::Duration};
+
+use dioxus::prelude::*;
+
+use dioxus_web::*;
+
+fn main() {
+    // Setup logging
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+
+    // Run the app
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
+}
+
+static App: FC<()> = |cx| {
+    // let mut count = use_state(cx, || 0);
+    let fut = cx.use_hook(
+        move || {
+            Box::pin(async {
+                let mut tick: i32 = 0;
+                log::debug!("yeet!");
+                loop {
+                    gloo_timers::future::TimeoutFuture::new(250).await;
+                    log::debug!("ticking forward... {}", tick);
+                    tick += 1;
+                }
+            }) as Pin<Box<dyn Future<Output = ()> + 'static>>
+        },
+        |h| h,
+        |_| {},
+    );
+
+    cx.submit_task(fut);
+
+    let state = use_state(cx, || 0);
+    cx.render(rsx! {
+        div {
+            section { class: "py-12 px-4 text-center"
+                div { class: "w-full max-w-2xl mx-auto"
+                    span { class: "text-sm font-semibold"
+                        "count: {state}"
+                    }
+                    div {
+                        button {
+                            onclick: move |_| state.set(state + 1)
+                            "incr"
+                        }
+                        br {}
+                        br {}
+                        button {
+                            onclick: move |_| state.set(state - 1)
+                            "decr"
+                        }
+                    }
+                }
+            }
+        }
+    })
+};

+ 52 - 0
packages/web/examples/blah.rs

@@ -0,0 +1,52 @@
+//! Basic example that renders a simple VNode to the browser.
+
+use dioxus::events::on::MouseEvent;
+use dioxus_core as dioxus;
+use dioxus_core::prelude::*;
+use dioxus_hooks::*;
+use dioxus_html as dioxus_elements;
+// use wasm_timer;
+
+use std::future::Future;
+
+use std::{pin::Pin, time::Duration};
+
+use dioxus::prelude::*;
+
+use dioxus_web::*;
+
+fn main() {
+    // Setup logging
+    wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
+    console_error_panic_hook::set_once();
+
+    // Run the app
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(App));
+}
+
+static App: FC<()> = |cx| {
+    let mut state = use_state(cx, || 0);
+    cx.render(rsx! {
+        div {
+            section { class: "py-12 px-4 text-center"
+                div { class: "w-full max-w-2xl mx-auto"
+                    span { class: "text-sm font-semibold"
+                        "static subtree"
+                    }
+                }
+            }
+            section { class: "py-12 px-4 text-center"
+                div { class: "w-full max-w-2xl mx-auto"
+                    span { class: "text-sm font-semibold"
+                        "dynamic subtree {state}"
+                    }
+                    div {
+                        button { onclick: move |_| state+=1, "incr" }
+                        br {}
+                        button { onclick: move |_| state-=1, "decr" }
+                    }
+                }
+            }
+        }
+    })
+};

+ 33 - 2
packages/web/src/lib.rs

@@ -3,6 +3,8 @@
 //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
 
 use dioxus::prelude::{Context, Properties, VNode};
+use futures_util::{pin_mut, Stream, StreamExt};
+
 use fxhash::FxHashMap;
 use web_sys::{window, Document, Element, Event, Node};
 // use futures::{channel::mpsc, SinkExt, StreamExt};
@@ -70,12 +72,41 @@ impl WebsysRenderer {
 
         self.internal_dom.rebuild(&mut websys_dom)?;
 
-        while let Some(trigger) = websys_dom.wait_for_event().await {
+        log::info!("Going into event loop");
+        loop {
+            let trigger = {
+                let real_queue = websys_dom.wait_for_event();
+                let task_queue = (&mut self.internal_dom.tasks).next();
+
+                pin_mut!(real_queue);
+                pin_mut!(task_queue);
+
+                match futures_util::future::select(real_queue, task_queue).await {
+                    futures_util::future::Either::Left((trigger, _)) => trigger,
+                    futures_util::future::Either::Right((trigger, _)) => trigger,
+                }
+            };
+
+            log::info!("event received");
             let root_node = body_element.first_child().unwrap();
             websys_dom.stack.push(root_node.clone());
             self.internal_dom
-                .progress_with_event(&mut websys_dom, trigger)?;
+                .progress_with_event(&mut websys_dom, trigger.unwrap())?;
+
+            // let t2 = self.internal_dom.tasks.next();
+            // futures::select! {
+            //     trigger = t1 => {
+            //         log::info!("event received");
+            //         let root_node = body_element.first_child().unwrap();
+            //         websys_dom.stack.push(root_node.clone());
+            //         self.internal_dom
+            //             .progress_with_event(&mut websys_dom, trigger)?;
+            //     },
+            //     () = t2 => {}
+            // };
         }
+        // while let Some(trigger) = websys_dom.wait_for_event().await {
+        // }
 
         Ok(()) // should actually never return from this, should be an error, rustc just cant see it
     }

+ 14 - 11
packages/web/src/new.rs

@@ -78,7 +78,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
         self.stack.push(domnode.clone());
     }
 
-    fn append_child(&mut self) {
+    fn append_children(&mut self, many: u32) {
         log::debug!("Called [`append_child`]");
         let child = self.stack.pop();
 
@@ -99,7 +99,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
         self.stack.top().append_child(&child).unwrap();
     }
 
-    fn replace_with(&mut self) {
+    fn replace_with(&mut self, many: u32) {
         log::debug!("Called [`replace_with`]");
         let new_node = self.stack.pop();
         let old_node = self.stack.pop();
@@ -193,15 +193,16 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
         &mut self,
         event: &'static str,
         scope: dioxus_core::prelude::ScopeIdx,
-        el_id: usize,
+        _element_id: usize,
         real_id: RealDomNode,
     ) {
+        let (_on, event) = event.split_at(2);
         let event = wasm_bindgen::intern(event);
         log::debug!(
             "Called [`new_event_listener`]: {}, {:?}, {}, {:?}",
             event,
             scope,
-            el_id,
+            _element_id,
             real_id
         );
         // attach the correct attributes to the element
@@ -215,10 +216,11 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
             .dyn_ref::<Element>()
             .expect(&format!("not an element: {:?}", el));
 
-        let gi_id = scope.data().as_ffi();
+        let scope_id = scope.data().as_ffi();
         el.set_attribute(
             &format!("dioxus-event-{}", event),
-            &format!("{}.{}.{}", gi_id, el_id, real_id.0),
+            &format!("{}.{}", scope_id, real_id.0),
+            // &format!("{}.{}.{}", gi_id, el_id, real_id.0),
         )
         .unwrap();
 
@@ -566,19 +568,20 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
         .next()
         .and_then(|f| f.parse::<u64>().ok())
         .context("failed to parse gi id")?;
-    let el_id = fields
-        .next()
-        .and_then(|f| f.parse::<usize>().ok())
-        .context("failed to parse el id")?;
+    // let el_id = fields
+    //     .next()
+    //     .and_then(|f| f.parse::<usize>().ok())
+    //     .context("failed to parse el id")?;
     let real_id = fields
         .next()
         .and_then(|f| f.parse::<u64>().ok().map(RealDomNode::new))
         .context("failed to parse real id")?;
 
     // Call the trigger
-    log::debug!("decoded gi_id: {}, li_idx: {}", gi_id, el_id);
+    log::debug!("decoded scope_id: {}, node_id: {:#?}", gi_id, real_id);
 
     let triggered_scope: ScopeIdx = KeyData::from_ffi(gi_id).into();
+    log::debug!("Triggered scope is {:#?}", triggered_scope);
     Ok(EventTrigger::new(
         virtual_event_from_websys_event(event),
         triggered_scope,

+ 2 - 2
packages/webview/src/dom.rs

@@ -37,11 +37,11 @@ impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
         self.edits.push(PushRoot { root: root.0 });
     }
 
-    fn append_child(&mut self) {
+    fn append_children(&mut self, many: u32) {
         self.edits.push(AppendChild);
     }
 
-    fn replace_with(&mut self) {
+    fn replace_with(&mut self, many: u32) {
         self.edits.push(ReplaceWith);
     }