diff --git a/packages/core/Cargo.toml b/packages/core/Cargo.toml index f779e6ba2..827968655 100644 --- a/packages/core/Cargo.toml +++ b/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" } diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index d6af9b051..024ad5375 100644 --- a/packages/core/src/arena.rs +++ b/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 !"); } } diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs index feaa276e4..b0a463c9e 100644 --- a/packages/core/src/create.rs +++ b/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, diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index c0b14d0bd..28ffb2404 100644 --- a/packages/core/src/diff.rs +++ b/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 => {} diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index 020b307d9..72b5662fd 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -11,8 +11,13 @@ //! The logic for this was borrowed from . 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) -> Option> + 'b>), + inner: SmallBox 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(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(val: F) -> Self - where - F: FnMut(Option<&'a ScopeState>) -> Option> + 'b, - { - let mut ptr: *const _ = &val as &dyn FnMut(Option<&'a ScopeState>) -> Option>; - - 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::(), - "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::() <= std::mem::align_of::(), - "TODO: Enforce alignment >{} (requires {})", - std::mem::align_of::(), - std::mem::align_of::() - ); - - let info = &words[1..]; - let data = words[0] as *mut (); - let size = mem::size_of::(); - - let stored_size = info.len() * mem::size_of::() + size; - let max_size = mem::size_of::(); - - 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::(); - - 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>>() - / mem::size_of::() - - 1; - - let info_ofs = data.len() - info_size; - - let g: *mut dyn FnMut(Option<&'a ScopeState>) -> Option> = - 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>>() - / mem::size_of::() - - 1; - - let info_ofs = data.len() - info_size; - - let g: *mut dyn FnMut(Option<&ScopeState>) -> Option> = - 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(ptr: &mut T) -> &mut [usize] { - assert!(mem::size_of::() % mem::size_of::() == 0); - let words = mem::size_of::() / mem::size_of::(); - // 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(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::() - 1) / mem::size_of::() -} diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 3687cd956..2343c4548 100644 --- a/packages/core/src/mutations.rs +++ b/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::()); + println!("scope: {}", std::mem::size_of::()); +} diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs index e13b0fd45..f8fdfb595 100644 --- a/packages/core/src/scheduler/wait.rs +++ b/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"); } } } diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index e94818fee..b4cc54c19 100644 --- a/packages/core/src/scope_arena.rs +++ b/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(), diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index bb2776861..54402f159 100644 --- a/packages/core/src/scopes.rs +++ b/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>, pub(crate) hook_idx: Cell, - pub(crate) shared_contexts: RefCell>>, + pub(crate) shared_contexts: RefCell>>, pub(crate) tasks: Rc, - pub(crate) spawned_tasks: HashSet, + pub(crate) spawned_tasks: FxHashSet, pub(crate) props: Option>>, pub(crate) placeholder: Cell>, @@ -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() }) } diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index a7046636b..643de2caf 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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>, + pub(crate) templates: FxHashMap>, pub(crate) scopes: Slab>, pub(crate) dirty_scopes: BTreeSet, pub(crate) scheduler: Rc, @@ -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) -> 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 { diff --git a/packages/hooks/src/usefuture.rs b/packages/hooks/src/usefuture.rs index a95fc3221..1eae035be 100644 --- a/packages/hooks/src/usefuture.rs +++ b/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() } } diff --git a/packages/interpreter/Cargo.toml b/packages/interpreter/Cargo.toml index 4eb7410bd..c00d85733 100644 --- a/packages/interpreter/Cargo.toml +++ b/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 = [] diff --git a/packages/interpreter/src/bindings.rs b/packages/interpreter/src/bindings.rs index e4ffdb7a7..b3e59233a 100644 --- a/packages/interpreter/src/bindings.rs +++ b/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); } diff --git a/packages/interpreter/src/interpreter.js b/packages/interpreter/src/interpreter.js index 13414b6e2..d90d8a306 100644 --- a/packages/interpreter/src/interpreter.js +++ b/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]); diff --git a/packages/interpreter/src/lib.rs b/packages/interpreter/src/lib.rs index 076a89197..c4b5440a4 100644 --- a/packages/interpreter/src/lib.rs +++ b/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; diff --git a/packages/interpreter/src/sledgehammer_bindings.rs b/packages/interpreter/src/sledgehammer_bindings.rs new file mode 100644 index 000000000..9be487c58 --- /dev/null +++ b/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, 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, 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, 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, value: &str, ns: &str) { + "{node = nodes[$id$]; SetAttributeInner(node, $field$, $value$, $ns$);}" + } + fn remove_attribute(id: u32, field: &str, ns: &str) { + 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);}" + } +} diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index cf248df95..7cb137845 100644 --- a/packages/web/src/dom.rs +++ b/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, - root: Element, + templates: FxHashMap, + 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 = + 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) { 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.RemoveEventListener(name, id.0 as u32), - Remove { id } => i.Remove(id.0 as u32), - PushRoot { id } => i.PushRoot(id.0 as u32), + RemoveEventListener { name, id } => { + i.remove_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8) + } + Remove { id } => i.remove(id.0 as u32), + PushRoot { id } => i.push_root(id.0 as u32), } } + edits.clear(); + i.flush(); } }