Forráskód Böngészése

work on optimizing web implementation

Evan Almloff 2 éve
szülő
commit
6102902387

+ 2 - 0
packages/core/Cargo.toml

@@ -35,6 +35,8 @@ indexmap = "1.7"
 serde = { version = "1", features = ["derive"], optional = true }
 anyhow = "1.0.66"
 
+smallbox = "0.8.1"
+
 [dev-dependencies]
 tokio = { version = "*", features = ["full"] }
 dioxus = { path = "../dioxus" }

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

@@ -69,7 +69,6 @@ impl VirtualDom {
             )
         }
 
-        println!("reclaiming {:?}", el);
         self.elements.try_remove(el.0)
     }
 
@@ -95,9 +94,7 @@ impl VirtualDom {
         // Drop all the hooks once the children are dropped
         // this means we'll drop hooks bottom-up
         for hook in scope.hook_list.get_mut().drain(..) {
-            println!("dropping hook !");
             drop(unsafe { BumpBox::from_raw(hook) });
-            println!("hook dropped !");
         }
     }
 

+ 0 - 5
packages/core/src/create.rs

@@ -388,11 +388,6 @@ impl<'b> VirtualDom {
         // Set the placeholder of the scope
         self.scopes[scope.0].placeholder.set(Some(new_id));
 
-        println!(
-            "assigning id {:?} to path {:?}, template: {:?}",
-            new_id, &template.template.node_paths, template.template
-        );
-
         // Since the placeholder is already in the DOM, we don't create any new nodes
         self.mutations.push(AssignId {
             id: new_id,

+ 1 - 17
packages/core/src/diff.rs

@@ -61,17 +61,10 @@ impl<'b> VirtualDom {
     }
 
     fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
-        println!(
-            "diffing node {:#?},\n\n{:#?}",
-            left_template.template.name, right_template.template.name
-        );
-
         if left_template.template.name != right_template.template.name {
             return self.light_diff_templates(left_template, right_template);
         }
 
-        println!("diffing attributs...");
-
         for (left_attr, right_attr) in left_template
             .dynamic_attrs
             .iter()
@@ -106,8 +99,6 @@ impl<'b> VirtualDom {
             }
         }
 
-        println!("diffing dyn nodes...");
-
         for (idx, (left_node, right_node)) in left_template
             .dynamic_nodes
             .iter()
@@ -133,8 +124,6 @@ impl<'b> VirtualDom {
             };
         }
 
-        println!("applying roots...");
-
         // Make sure the roots get transferred over
         for (left, right) in left_template
             .root_ids
@@ -178,7 +167,6 @@ impl<'b> VirtualDom {
     ) {
         // Replace components that have different render fns
         if left.render_fn != right.render_fn {
-            dbg!(&left, &right);
             let created = self.create_component_node(right_template, right, idx);
             let head = unsafe {
                 self.scopes[left.scope.get().unwrap().0]
@@ -396,8 +384,6 @@ impl<'b> VirtualDom {
             "all siblings must be keyed or all siblings must be non-keyed"
         );
 
-        println!("Diffing fragment {:?}, {:?}", old.len(), new.len());
-
         if new_is_keyed && old_is_keyed {
             self.diff_keyed_children(old, new);
         } else {
@@ -420,9 +406,7 @@ impl<'b> VirtualDom {
         debug_assert!(!new.is_empty());
         debug_assert!(!old.is_empty());
 
-        println!("Diffing non keyed children");
-
-        match dbg!(old.len().cmp(&new.len())) {
+        match old.len().cmp(&new.len()) {
             Ordering::Greater => self.remove_nodes(&old[new.len()..]),
             Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
             Ordering::Equal => {}

+ 15 - 198
packages/core/src/lazynodes.rs

@@ -11,8 +11,13 @@
 //! The logic for this was borrowed from <https://docs.rs/stack_dst/0.6.1/stack_dst/>. Unfortunately, this crate does not
 //! support non-static closures, so we've implemented the core logic of `ValueA` in this module.
 
+use smallbox::{
+    smallbox,
+    space::{S1, S16, S4, S8},
+    SmallBox,
+};
+
 use crate::{innerlude::VNode, ScopeState};
-use std::mem;
 
 /// A concrete type provider for closures that build [`VNode`] structures.
 ///
@@ -24,14 +29,7 @@ use std::mem;
 /// LazyNodes::new(|f| f.element("div", [], [], [] None))
 /// ```
 pub struct LazyNodes<'a, 'b> {
-    inner: StackNodeStorage<'a, 'b>,
-}
-
-type StackHeapSize = [usize; 16];
-
-enum StackNodeStorage<'a, 'b> {
-    Stack(LazyStack),
-    Heap(Box<dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b>),
+    inner: SmallBox<dyn FnMut(&'a ScopeState) -> VNode<'a> + 'b, S16>,
 }
 
 impl<'a, 'b> LazyNodes<'a, 'b> {
@@ -44,114 +42,11 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
         // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
         let mut slot = Some(val);
 
-        let val = move |fac: Option<&'a ScopeState>| {
-            fac.map(
-                slot.take()
-                    .expect("LazyNodes closure to be called only once"),
-            )
-        };
-
-        // miri does not know how to work with mucking directly into bytes
-        // just use a heap allocated type when miri is running
-        if cfg!(miri) {
-            Self {
-                inner: StackNodeStorage::Heap(Box::new(val)),
-            }
-        } else {
-            unsafe { LazyNodes::new_inner(val) }
-        }
-    }
-
-    /// Create a new [`LazyNodes`] closure, but force it onto the heap.
-    pub fn new_boxed<F>(inner: F) -> Self
-    where
-        F: FnOnce(&'a ScopeState) -> VNode<'a> + 'b,
-    {
-        // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch
-        let mut slot = Some(inner);
-
         Self {
-            inner: StackNodeStorage::Heap(Box::new(move |fac: Option<&'a ScopeState>| {
-                fac.map(
-                    slot.take()
-                        .expect("LazyNodes closure to be called only once"),
-                )
-            })),
-        }
-    }
-
-    unsafe fn new_inner<F>(val: F) -> Self
-    where
-        F: FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> + 'b,
-    {
-        let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>;
-
-        assert_eq!(
-            ptr as *const u8, &val as *const _ as *const u8,
-            "MISUSE: Closure returned different pointer"
-        );
-        assert_eq!(
-            std::mem::size_of_val(&*ptr),
-            std::mem::size_of::<F>(),
-            "MISUSE: Closure returned a subset pointer"
-        );
-
-        let words = ptr_as_slice(&mut ptr);
-        assert!(
-            words[0] == &val as *const _ as usize,
-            "BUG: Pointer layout is not (data_ptr, info...)"
-        );
-
-        // - Ensure that Self is aligned same as data requires
-        assert!(
-            std::mem::align_of::<F>() <= std::mem::align_of::<Self>(),
-            "TODO: Enforce alignment >{} (requires {})",
-            std::mem::align_of::<Self>(),
-            std::mem::align_of::<F>()
-        );
-
-        let info = &words[1..];
-        let data = words[0] as *mut ();
-        let size = mem::size_of::<F>();
-
-        let stored_size = info.len() * mem::size_of::<usize>() + size;
-        let max_size = mem::size_of::<StackHeapSize>();
-
-        if stored_size > max_size {
-            Self {
-                inner: StackNodeStorage::Heap(Box::new(val)),
-            }
-        } else {
-            let mut buf: StackHeapSize = StackHeapSize::default();
-
-            assert!(info.len() + round_to_words(size) <= buf.as_ref().len());
-
-            // Place pointer information at the end of the region
-            // - Allows the data to be at the start for alignment purposes
-            {
-                let info_ofs = buf.as_ref().len() - info.len();
-                let info_dst = &mut buf.as_mut()[info_ofs..];
-                for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) {
-                    *d = *v;
-                }
-            }
-
-            let src_ptr = data as *const u8;
-            let dataptr = buf.as_mut_ptr().cast::<u8>();
-
-            for i in 0..size {
-                *dataptr.add(i) = *src_ptr.add(i);
-            }
-
-            std::mem::forget(val);
-
-            Self {
-                inner: StackNodeStorage::Stack(LazyStack {
-                    _align: [],
-                    buf,
-                    dropped: false,
-                }),
-            }
+            inner: smallbox!(move |f| {
+                let val = slot.take().expect("cannot call LazyNodes twice");
+                val(f)
+            }),
         }
     }
 
@@ -163,88 +58,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
     /// let node = f.call(cac);
     /// ```
     #[must_use]
-    pub fn call(self, f: &'a ScopeState) -> VNode<'a> {
-        match self.inner {
-            StackNodeStorage::Heap(mut lazy) => {
-                lazy(Some(f)).expect("Closure should not be called twice")
-            }
-            StackNodeStorage::Stack(mut stack) => stack.call(f),
+    pub fn call(mut self, f: &'a ScopeState) -> VNode<'a> {
+        if self.inner.is_heap() {
+            panic!();
         }
+        (self.inner)(f)
     }
 }
-
-struct LazyStack {
-    _align: [u64; 0],
-    buf: StackHeapSize,
-    dropped: bool,
-}
-
-impl LazyStack {
-    fn call<'a>(&mut self, f: &'a ScopeState) -> VNode<'a> {
-        let LazyStack { buf, .. } = self;
-        let data = buf.as_ref();
-
-        let info_size =
-            mem::size_of::<*mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>>>()
-                / mem::size_of::<usize>()
-                - 1;
-
-        let info_ofs = data.len() - info_size;
-
-        let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option<VNode<'a>> =
-            unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
-
-        self.dropped = true;
-
-        let clos = unsafe { &mut *g };
-        clos(Some(f)).unwrap()
-    }
-}
-impl Drop for LazyStack {
-    fn drop(&mut self) {
-        if !self.dropped {
-            let LazyStack { buf, .. } = self;
-            let data = buf.as_ref();
-
-            let info_size =
-                mem::size_of::<*mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>>>()
-                    / mem::size_of::<usize>()
-                    - 1;
-
-            let info_ofs = data.len() - info_size;
-
-            let g: *mut dyn FnMut(Option<&ScopeState>) -> Option<VNode<'_>> =
-                unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) };
-
-            self.dropped = true;
-
-            let clos = unsafe { &mut *g };
-            clos(None);
-        }
-    }
-}
-
-/// Obtain mutable access to a pointer's words
-fn ptr_as_slice<T>(ptr: &mut T) -> &mut [usize] {
-    assert!(mem::size_of::<T>() % mem::size_of::<usize>() == 0);
-    let words = mem::size_of::<T>() / mem::size_of::<usize>();
-    // SAFE: Points to valid memory (a raw pointer)
-    unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) }
-}
-
-/// Re-construct a fat pointer
-unsafe fn make_fat_ptr<T: ?Sized>(data_ptr: usize, meta_vals: &[usize]) -> *mut T {
-    let mut rv = mem::MaybeUninit::<*mut T>::uninit();
-    {
-        let s = ptr_as_slice(&mut rv);
-        s[0] = data_ptr;
-        s[1..].copy_from_slice(meta_vals);
-    }
-    let rv = rv.assume_init();
-    assert_eq!(rv as *const (), data_ptr as *const ());
-    rv
-}
-
-fn round_to_words(len: usize) -> usize {
-    (len + mem::size_of::<usize>() - 1) / mem::size_of::<usize>()
-}

+ 7 - 1
packages/core/src/mutations.rs

@@ -1,6 +1,6 @@
 use fxhash::FxHashSet;
 
-use crate::{arena::ElementId, ScopeId, Template};
+use crate::{arena::ElementId, ScopeId, ScopeState, Template};
 
 /// A container for all the relevant steps to modify the Real DOM
 ///
@@ -257,3 +257,9 @@ pub enum Mutation<'a> {
         id: ElementId,
     },
 }
+
+#[test]
+fn sizes() {
+    println!("mutations: {}", std::mem::size_of::<Mutation>());
+    println!("scope: {}", std::mem::size_of::<ScopeState>());
+}

+ 0 - 16
packages/core/src/scheduler/wait.rs

@@ -40,8 +40,6 @@ impl VirtualDom {
     }
 
     pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
-        println!("suspense notified");
-
         let leaf = self
             .scheduler
             .leaves
@@ -67,13 +65,6 @@ impl VirtualDom {
             // safety: we're not going to modify the suspense context but we don't want to make a clone of it
             let fiber = self.acquire_suspense_boundary(leaf.scope_id);
 
-            println!("ready pool");
-
-            println!(
-                "Existing mutations {:?}, scope {:?}",
-                fiber.mutations, fiber.id
-            );
-
             let scope = &mut self.scopes[scope_id.0];
             let arena = scope.current_frame();
 
@@ -106,16 +97,9 @@ impl VirtualDom {
                 std::mem::swap(&mut self.mutations, mutations);
 
                 if fiber.waiting_on.borrow().is_empty() {
-                    println!("fiber is finished!");
                     self.finished_fibers.push(fiber.id);
-                } else {
-                    println!("fiber is not finished {:?}", fiber.waiting_on);
                 }
-            } else {
-                println!("nodes arent right");
             }
-        } else {
-            println!("not ready");
         }
     }
 }

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

@@ -30,8 +30,8 @@ impl VirtualDom {
             height,
             props: Some(props),
             placeholder: Default::default(),
-            node_arena_1: BumpFrame::new(50),
-            node_arena_2: BumpFrame::new(50),
+            node_arena_1: BumpFrame::new(0),
+            node_arena_2: BumpFrame::new(0),
             spawned_tasks: Default::default(),
             render_cnt: Default::default(),
             hook_arena: Default::default(),

+ 31 - 4
packages/core/src/scopes.rs

@@ -10,6 +10,7 @@ use crate::{
     Attribute, AttributeValue, Element, Event, Properties, TaskId,
 };
 use bumpalo::{boxed::Box as BumpBox, Bump};
+use fxhash::{FxHashMap, FxHashSet};
 use std::{
     any::{Any, TypeId},
     cell::{Cell, RefCell},
@@ -81,10 +82,10 @@ pub struct ScopeState {
     pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
     pub(crate) hook_idx: Cell<usize>,
 
-    pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
+    pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
 
     pub(crate) tasks: Rc<Scheduler>,
-    pub(crate) spawned_tasks: HashSet<TaskId>,
+    pub(crate) spawned_tasks: FxHashSet<TaskId>,
 
     pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
     pub(crate) placeholder: Cell<Option<ElementId>>,
@@ -383,9 +384,35 @@ impl<'src> ScopeState {
     /// Uses the currently active [`Bump`] allocator
     pub fn raw_text(&'src self, args: Arguments) -> &'src str {
         args.as_str().unwrap_or_else(|| {
-            use bumpalo::core_alloc::fmt::Write;
             let mut str_buf = bumpalo::collections::String::new_in(self.bump());
-            str_buf.write_fmt(args).unwrap();
+            struct Wrapper<'a, 'b>(&'a mut bumpalo::collections::Vec<'b, u8>);
+
+            impl<'a, 'b> std::fmt::Write for Wrapper<'a, 'b> {
+                fn write_str(&mut self, s: &str) -> std::fmt::Result {
+                    unsafe {
+                        copy(s, self.0);
+                    }
+                    Ok(())
+                }
+                fn write_char(&mut self, c: char) -> std::fmt::Result {
+                    self.0.push(c as u8);
+                    Ok(())
+                }
+            }
+
+            unsafe fn copy<'a>(s: &str, buf: &mut bumpalo::collections::Vec<'a, u8>) {
+                let old_len = buf.len();
+                buf.reserve(s.len());
+                let ptr = buf.as_mut_ptr().add(old_len);
+                let bytes = s.as_bytes();
+                let str_ptr = bytes.as_ptr();
+                for o in 0..s.len() {
+                    *ptr.add(o) = *str_ptr.add(o);
+                }
+                buf.set_len(old_len + s.len());
+            }
+
+            let _ = unsafe { std::fmt::write(&mut Wrapper(str_buf.as_mut_vec()), args) };
             str_buf.into_bump_str()
         })
     }

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

@@ -14,6 +14,7 @@ use crate::{
     AttributeValue, Element, Event, Scope, SuspenseContext,
 };
 use futures_util::{pin_mut, StreamExt};
+use fxhash::FxHashMap;
 use slab::Slab;
 use std::{
     any::Any,
@@ -148,7 +149,7 @@ use std::{
 /// }
 /// ```
 pub struct VirtualDom {
-    pub(crate) templates: HashMap<TemplateId, Template<'static>>,
+    pub(crate) templates: FxHashMap<TemplateId, Template<'static>>,
     pub(crate) scopes: Slab<Box<ScopeState>>,
     pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
     pub(crate) scheduler: Rc<Scheduler>,
@@ -362,8 +363,6 @@ impl VirtualDom {
             let target_path = el_ref.path;
 
             for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
-                println!("{:?} \n {:?} \n {:?}", attr, name, element);
-
                 let this_path = template.template.attr_paths[idx];
 
                 // listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
@@ -385,8 +384,6 @@ impl VirtualDom {
                 }
             }
 
-            println!("calling listeners: {:?}", listeners);
-
             // Now that we've accumulated all the parent attributes for the target element, call them in reverse order
             // We check the bubble state between each call to see if the event has been stopped from bubbling
             for listener in listeners.drain(..).rev() {
@@ -403,8 +400,6 @@ impl VirtualDom {
 
             parent_path = template.parent.and_then(|id| self.elements.get(id.0));
         }
-
-        println!("all listeners exhausted");
     }
 
     /// Wait for the scheduler to have any work.
@@ -527,7 +522,6 @@ impl VirtualDom {
     ///
     /// If no suspense trees are present
     pub async fn render_with_deadline(&mut self, deadline: impl Future<Output = ()>) -> Mutations {
-        println!("render with deadline");
         pin_mut!(deadline);
 
         loop {
@@ -538,11 +532,11 @@ impl VirtualDom {
 
                 self.mutations
                     .templates
-                    .extend(context.mutations.borrow_mut().templates.drain(..));
+                    .append(&mut context.mutations.borrow_mut().templates);
 
                 self.mutations
                     .edits
-                    .extend(context.mutations.borrow_mut().edits.drain(..));
+                    .append(&mut context.mutations.borrow_mut().edits);
 
                 // TODO: count how many nodes are on the stack?
                 self.mutations.push(Mutation::ReplaceWith {

+ 0 - 1
packages/hooks/src/usefuture.rs

@@ -67,7 +67,6 @@ where
             match waker.borrow().as_ref() {
                 Some(waker) => waker.wake_by_ref(),
                 None => {
-                    println!("scheduling update");
                     // schedule_update()
                 }
             }

+ 2 - 1
packages/interpreter/Cargo.toml

@@ -17,7 +17,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
 wasm-bindgen = { version = "0.2.79", optional = true }
 js-sys = { version = "0.3.56", optional = true }
 web-sys = { version = "0.3.56", optional = true, features = ["Element", "Node"] }
-
+sledgehammer_bindgen = { version = "0.1.1" }
+sledgehammer_utils = { version = "0.1.0" }
 
 [features]
 default = []

+ 3 - 0
packages/interpreter/src/bindings.rs

@@ -73,4 +73,7 @@ extern "C" {
 
     #[wasm_bindgen(method)]
     pub fn PushRoot(this: &Interpreter, id: u32);
+
+    #[wasm_bindgen(method)]
+    pub fn AppendChildren(this: &Interpreter, id: u32, m: u32);
 }

+ 2 - 2
packages/interpreter/src/interpreter.js

@@ -92,8 +92,8 @@ export class Interpreter {
   PopRoot() {
     this.stack.pop();
   }
-  AppendChildren(many) {
-    let root = this.stack[this.stack.length - (1 + many)];
+  AppendChildren(id, many) {
+    let root = this.nodes[id];
     let to_add = this.stack.splice(this.stack.length - many);
     for (let i = 0; i < many; i++) {
       root.appendChild(to_add[i]);

+ 5 - 0
packages/interpreter/src/lib.rs

@@ -1,5 +1,10 @@
 pub static INTERPRETER_JS: &str = include_str!("./interpreter.js");
 
+#[cfg(feature = "web")]
+mod sledgehammer_bindings;
+#[cfg(feature = "web")]
+pub use sledgehammer_bindings::*;
+
 #[cfg(feature = "web")]
 mod bindings;
 

+ 221 - 0
packages/interpreter/src/sledgehammer_bindings.rs

@@ -0,0 +1,221 @@
+use js_sys::Function;
+use sledgehammer_bindgen::bindgen;
+use web_sys::Node;
+
+#[bindgen]
+mod js {
+    const JS: &str = r#"
+    class ListenerMap {
+        constructor(root) {
+            // bubbling events can listen at the root element
+            this.global = {};
+            // non bubbling events listen at the element the listener was created at
+            this.local = {};
+            this.root = null;
+            this.handler = null;
+        }
+
+        create(event_name, element, bubbles) {
+            if (bubbles) {
+                if (this.global[event_name] === undefined) {
+                    this.global[event_name] = {};
+                    this.global[event_name].active = 1;
+                    this.root.addEventListener(event_name, this.handler);
+                } else {
+                    this.global[event_name].active++;
+                }
+            }
+            else {
+                const id = element.getAttribute("data-dioxus-id");
+                if (!this.local[id]) {
+                    this.local[id] = {};
+                }
+                element.addEventListener(event_name, this.handler);
+            }
+        }
+
+        remove(element, event_name, bubbles) {
+            if (bubbles) {
+                this.global[event_name].active--;
+                if (this.global[event_name].active === 0) {
+                    this.root.removeEventListener(event_name, this.global[event_name].callback);
+                    delete this.global[event_name];
+                }
+            }
+            else {
+                const id = element.getAttribute("data-dioxus-id");
+                delete this.local[id][event_name];
+                if (this.local[id].length === 0) {
+                    delete this.local[id];
+                }
+                element.removeEventListener(event_name, this.handler);
+            }
+        }
+
+        removeAllNonBubbling(element) {
+            const id = element.getAttribute("data-dioxus-id");
+            delete this.local[id];
+        }
+    }
+    function SetAttributeInner(node, field, value, ns) {
+        const name = field;
+        if (ns === "style") {
+            // ????? why do we need to do this
+            if (node.style === undefined) {
+                node.style = {};
+            }
+            node.style[name] = value;
+        } else if (ns !== null && ns !== undefined && ns !== "") {
+            node.setAttributeNS(ns, name, value);
+        } else {
+            switch (name) {
+                case "value":
+                    if (value !== node.value) {
+                        node.value = value;
+                    }
+                    break;
+                case "checked":
+                    node.checked = value === "true";
+                    break;
+                case "selected":
+                    node.selected = value === "true";
+                    break;
+                case "dangerous_inner_html":
+                    node.innerHTML = value;
+                    break;
+                default:
+                    // https://github.com/facebook/react/blob/8b88ac2592c5f555f315f9440cbb665dd1e7457a/packages/react-dom/src/shared/DOMProperty.js#L352-L364
+                    if (value === "false" && bool_attrs.hasOwnProperty(name)) {
+                        node.removeAttribute(name);
+                    } else {
+                        node.setAttribute(name, value);
+                    }
+            }
+        }
+    }
+    function LoadChild(ptr, len) {
+        // iterate through each number and get that child
+        node = stack[stack.length - 1];
+        ptr_end = ptr + len;
+        for (; ptr < ptr_end; ptr++) {
+            end = m.getUint8(ptr);
+            for (node = node.firstChild; end > 0; end--) {
+                node = node.nextSibling;
+            }
+        }
+        return node;
+    }
+    const listeners = new ListenerMap();
+    let nodes = [];
+    let stack = [];
+    const templates = {};
+    let node, els, end, ptr_end, k;
+    export function save_template(nodes, tmpl_id) {
+        templates[tmpl_id] = nodes;
+    }
+    export function set_node(id, node) {
+        nodes[id] = node;
+    }
+    export function initilize(root, handler) {
+        listeners.handler = handler;
+        nodes = [root];
+        stack = [root];
+        listeners.root = root;
+    }
+    function AppendChildren(id, many){
+        root = nodes[id];
+        els = stack.splice(stack.length-many);
+        for (k = 0; k < many; k++) {
+            root.appendChild(els[k]);
+        }
+    }
+    "#;
+
+    extern "C" {
+        #[wasm_bindgen]
+        pub fn save_template(nodes: Vec<Node>, tmpl_id: u32);
+
+        #[wasm_bindgen]
+        pub fn set_node(id: u32, node: Node);
+
+        #[wasm_bindgen]
+        pub fn initilize(root: Node, handler: &Function);
+    }
+
+    fn mount_to_root() {
+        "{AppendChildren(root, stack.length-1);}"
+    }
+    fn push_root(root: u32) {
+        "{stack.push(nodes[$root$]);}"
+    }
+    fn append_children(id: u32, many: u32) {
+        "{AppendChildren($id$, $many$);}"
+    }
+    fn pop_root() {
+        "{stack.pop();}"
+    }
+    fn replace_with(id: u32, n: u32) {
+        "{root = nodes[$id$]; els = stack.splice(stack.length-$n$); if (root.listening) { listeners.removeAllNonBubbling(root); } root.replaceWith(...els);}"
+    }
+    fn insert_after(id: u32, n: u32) {
+        "{nodes[$id$].after(...stack.splice(stack.length-$n$));}"
+    }
+    fn insert_before(id: u32, n: u32) {
+        "{nodes[$id$].before(...stack.splice(stack.length-$n$));}"
+    }
+    fn remove(id: u32) {
+        "{node = nodes[$id$]; if (node !== undefined) { if (node.listening) { listeners.removeAllNonBubbling(node); } node.remove(); }}"
+    }
+    fn create_raw_text(text: &str) {
+        "{stack.push(document.createTextNode($text$));}"
+    }
+    fn create_text_node(text: &str, id: u32) {
+        "{node = document.createTextNode($text$); nodes[$id$] = node; stack.push(node);}"
+    }
+    fn create_placeholder(id: u32) {
+        "{node = document.createElement('pre'); node.hidden = true; stack.push(node); nodes[$id$] = node;}"
+    }
+    fn new_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
+        r#"node = nodes[id]; if(node.listening){node.listening += 1;}else{node.listening = 1;} node.setAttribute('data-dioxus-id', `\${id}`); listeners.create($event_name$, node, $bubbles$);"#
+    }
+    fn remove_event_listener(event_name: &str<u8, evt>, id: u32, bubbles: u8) {
+        "{node = nodes[$id$]; node.listening -= 1; node.removeAttribute('data-dioxus-id'); listeners.remove(node, $event_name$, $bubbles$);}"
+    }
+    fn set_text(id: u32, text: &str) {
+        "{nodes[$id$].textContent = $text$;}"
+    }
+    fn set_attribute(id: u32, field: &str<u8, attr>, value: &str, ns: &str<u8, ns_cache>) {
+        "{node = nodes[$id$]; SetAttributeInner(node, $field$, $value$, $ns$);}"
+    }
+    fn remove_attribute(id: u32, field: &str<u8, attr>, ns: &str<u8, ns_cache>) {
+        r#"{name = $field$;
+        node = this.nodes[$id$];
+        if (ns == "style") {
+            node.style.removeProperty(name);
+        } else if (ns !== null && ns !== undefined && ns !== "") {
+            node.removeAttributeNS(ns, name);
+        } else if (name === "value") {
+            node.value = "";
+        } else if (name === "checked") {
+            node.checked = false;
+        } else if (name === "selected") {
+            node.selected = false;
+        } else if (name === "dangerous_inner_html") {
+            node.innerHTML = "";
+        } else {
+            node.removeAttribute(name);
+        }}"#
+    }
+    fn assign_id(ptr: u32, len: u8, id: u32) {
+        "{nodes[$id$] = LoadChild($ptr$, $len$);}"
+    }
+    fn hydrate_text(ptr: u32, len: u8, value: &str, id: u32) {
+        "{node = LoadChild($ptr$, $len$); node.textContent = $value$; nodes[$id$] = node;}"
+    }
+    fn replace_placeholder(ptr: u32, len: u8, n: u32) {
+        "{els = stack.splice(stack.length - $n$); node = LoadChild($ptr$, $len$); node.replaceWith(...els);}"
+    }
+    fn load_template(tmpl_id: u32, index: u32, id: u32) {
+        "{node = templates[$tmpl_id$][$index$].cloneNode(true); nodes[$id$] = node; stack.push(node);}"
+    }
+}

+ 57 - 38
packages/web/src/dom.rs

@@ -7,22 +7,22 @@
 //! - tests to ensure dyn_into works for various event types.
 //! - Partial delegation?>
 
-use dioxus_core::{ElementId, Mutation, Mutations, Template, TemplateAttribute, TemplateNode};
+use dioxus_core::{Mutation, Template, TemplateAttribute, TemplateNode};
 use dioxus_html::{event_bubbles, CompositionData, FormData};
-use dioxus_interpreter_js::Interpreter;
+use dioxus_interpreter_js::{save_template, Channel};
 use futures_channel::mpsc;
-use js_sys::Function;
+use rustc_hash::{FxHashMap, FxHashSet};
 use std::{any::Any, rc::Rc};
-use wasm_bindgen::{closure::Closure, JsCast, JsValue};
+use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{Document, Element, Event, HtmlElement};
 
 use crate::Config;
 
 pub struct WebsysDom {
     document: Document,
-    interpreter: Interpreter,
-    handler: Closure<dyn FnMut(&Event)>,
-    root: Element,
+    templates: FxHashMap<String, u32>,
+    max_template_id: u32,
+    interpreter: Channel,
 }
 
 impl WebsysDom {
@@ -34,19 +34,25 @@ impl WebsysDom {
             Some(root) => root,
             None => document.create_element("body").ok().unwrap(),
         };
+        let interpreter = Channel::default();
 
+        let handler: Closure<dyn FnMut(&Event)> =
+            Closure::wrap(Box::new(move |event: &web_sys::Event| {
+                let _ = event_channel.unbounded_send(event.clone());
+            }));
+
+        dioxus_interpreter_js::initilize(root.unchecked_into(), handler.as_ref().unchecked_ref());
+        handler.forget();
         Self {
             document,
-            interpreter: Interpreter::new(root.clone()),
-            root,
-            handler: Closure::wrap(Box::new(move |event: &web_sys::Event| {
-                let _ = event_channel.unbounded_send(event.clone());
-            })),
+            interpreter,
+            templates: FxHashMap::default(),
+            max_template_id: 0,
         }
     }
 
     pub fn mount(&mut self) {
-        self.interpreter.MountToRoot();
+        self.interpreter.mount_to_root();
     }
 
     pub fn load_templates(&mut self, templates: &[Template]) {
@@ -59,7 +65,10 @@ impl WebsysDom {
                 roots.push(self.create_template_node(root))
             }
 
-            self.interpreter.SaveTemplate(roots, template.name);
+            self.templates
+                .insert(template.name.to_owned(), self.max_template_id);
+            save_template(roots, self.max_template_id);
+            self.max_template_id += 1
         }
     }
 
@@ -113,41 +122,51 @@ impl WebsysDom {
 
     pub fn apply_edits(&mut self, mut edits: Vec<Mutation>) {
         use Mutation::*;
-        let i = &self.interpreter;
-        for edit in edits.drain(..) {
+        let i = &mut self.interpreter;
+        for edit in &edits {
             match edit {
-                AssignId { path, id } => i.AssignId(path, id.0 as u32),
-                CreatePlaceholder { id } => i.CreatePlaceholder(id.0 as u32),
-                CreateTextNode { value, id } => i.CreateTextNode(value.into(), id.0 as u32),
-                HydrateText { path, value, id } => i.HydrateText(path, value, id.0 as u32),
-                LoadTemplate { name, index, id } => i.LoadTemplate(name, index as u32, id.0 as u32),
-                ReplaceWith { id, m } => i.ReplaceWith(id.0 as u32, m as u32),
-                ReplacePlaceholder { path, m } => i.ReplacePlaceholder(path, m as u32),
-                InsertAfter { id, m } => i.InsertAfter(id.0 as u32, m as u32),
-                InsertBefore { id, m } => i.InsertBefore(id.0 as u32, m as u32),
+                AppendChildren { id, m } => i.append_children(id.0 as u32, *m as u32),
+                AssignId { path, id } => {
+                    i.assign_id(path.as_ptr() as u32, path.len() as u8, id.0 as u32)
+                }
+                CreatePlaceholder { id } => i.create_placeholder(id.0 as u32),
+                CreateTextNode { value, id } => i.create_text_node(value, id.0 as u32),
+                HydrateText { path, value, id } => {
+                    i.hydrate_text(path.as_ptr() as u32, path.len() as u8, value, id.0 as u32)
+                }
+                LoadTemplate { name, index, id } => {
+                    if let Some(tmpl_id) = self.templates.get(*name) {
+                        i.load_template(*tmpl_id, *index as u32, id.0 as u32)
+                    }
+                }
+                ReplaceWith { id, m } => i.replace_with(id.0 as u32, *m as u32),
+                ReplacePlaceholder { path, m } => {
+                    i.replace_placeholder(path.as_ptr() as u32, path.len() as u8, *m as u32)
+                }
+                InsertAfter { id, m } => i.insert_after(id.0 as u32, *m as u32),
+                InsertBefore { id, m } => i.insert_before(id.0 as u32, *m as u32),
                 SetAttribute {
                     name,
                     value,
                     id,
                     ns,
-                } => i.SetAttribute(id.0 as u32, name, value.into(), ns),
+                } => i.set_attribute(id.0 as u32, name, value, ns.unwrap_or_default()),
                 SetBoolAttribute { name, value, id } => {
-                    i.SetBoolAttribute(id.0 as u32, name, value)
+                    i.set_attribute(id.0 as u32, name, if *value { "true" } else { "false" }, "")
                 }
-                SetText { value, id } => i.SetText(id.0 as u32, value.into()),
-                NewEventListener { name, scope, id } => {
-                    self.interpreter.NewEventListener(
-                        name,
-                        id.0 as u32,
-                        self.handler.as_ref().unchecked_ref(),
-                        event_bubbles(&name[2..]),
-                    );
+                SetText { value, id } => i.set_text(id.0 as u32, value),
+                NewEventListener { name, scope: _, id } => {
+                    i.new_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8);
+                }
+                RemoveEventListener { name, id } => {
+                    i.remove_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8)
                 }
-                RemoveEventListener { name, id } => i.RemoveEventListener(name, id.0 as u32),
-                Remove { id } => i.Remove(id.0 as u32),
-                PushRoot { id } => i.PushRoot(id.0 as u32),
+                Remove { id } => i.remove(id.0 as u32),
+                PushRoot { id } => i.push_root(id.0 as u32),
             }
         }
+        edits.clear();
+        i.flush();
     }
 }