From d063a199391383288e807ac03d333091bac6ba60 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 13 Feb 2021 02:49:10 -0500 Subject: [PATCH] Feat: wire up some of the changelist for diff --- packages/core/src/changelist.rs | 671 +++++++++++++++++++++++++++++++ packages/core/src/lib.rs | 1 + packages/core/src/virtual_dom.rs | 46 ++- packages/web/Cargo.toml | 2 + packages/web/src/interpreter.rs | 381 ++++++++++++++++++ packages/web/src/lib.rs | 2 +- 6 files changed, 1088 insertions(+), 15 deletions(-) create mode 100644 packages/core/src/changelist.rs create mode 100644 packages/web/src/interpreter.rs diff --git a/packages/core/src/changelist.rs b/packages/core/src/changelist.rs new file mode 100644 index 000000000..254293e67 --- /dev/null +++ b/packages/core/src/changelist.rs @@ -0,0 +1,671 @@ +//! Changelist +//! ---------- +//! +//! This module exposes the "changelist" object which allows 3rd party implementors to handle diffs to the virtual dom. +//! +//! # Design +//! --- +//! In essence, the changelist object connects a diff of two vdoms to the actual edits required to update the output renderer. +//! +//! This abstraction relies on the assumption that the final renderer accepts a tree of elements. For most target platforms, +//! this is an appropriate abstraction . +//! +//! During the diff phase, the change list is built. Once the diff phase is over, the change list is finished and returned back +//! to the renderer. The renderer is responsible for propogating the updates (a stream of u32) to the final display. +//! +//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom +//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the +//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object. +//! +//! + +use crate::innerlude::Listener; + +/// Renderers need to implement the interpreter trait +/// +/// +/// +/// +/// +trait Inrerpreter { + fn set_text(&mut self, text: &str); + fn remove_self_and_next_siblings(&mut self); + fn replace_with(&mut self); + fn set_attribute(&mut self, name: &str, value: &str); + fn remove_attribute(&mut self, name: &str); + fn push_reverse_child(&mut self, n: u32); + fn pop_push_child(&mut self, n: u32); + fn pop(&mut self); + fn append_child(&mut self); + fn create_text_node(&mut self, text: &str); + fn create_element(&mut self, tag_name: &str); + fn new_event_listener(&mut self, event_type: &str, a: u32, b: u32); + fn update_event_listener(&mut self, event_type: &str, a: u32, b: u32); + fn remove_event_listener(&mut self, event_type: &str); + fn create_element_ns(&mut self, tag_name: &str, ns: &str); + fn save_children_to_temporaries(&mut self, temp: u32, start: u32, end: u32); + // fn save_children_to_temporaries(&mut self, mut temp: u32, start: u32, end: u32); + fn push_child(&mut self, n: u32); + fn push_temporary(&mut self, temp: u32); + fn insert_before(&mut self); + fn pop_push_reverse_child(&mut self, n: u32); + fn remove_child(&mut self, n: u32); + fn set_class(&mut self, class_name: &str); + // fn save_template(&mut self, id: CacheId); + // fn push_template(&mut self, id: CacheId); +} + +pub struct ChangeList<'src> { + traversal: Traversal, + next_temporary: u32, + forcing_new_listeners: bool, + emitter: &'src mut dyn Inrerpreter, +} + +/// Traversal methods. +impl ChangeList<'_> { + pub fn go_down(&mut self) { + self.traversal.down(); + } + + pub fn go_down_to_child(&mut self, index: usize) { + self.traversal.down(); + self.traversal.sibling(index); + } + + pub fn go_down_to_reverse_child(&mut self, index: usize) { + self.traversal.down(); + self.traversal.reverse_sibling(index); + } + + pub fn go_up(&mut self) { + self.traversal.up(); + } + + pub fn go_to_sibling(&mut self, index: usize) { + self.traversal.sibling(index); + } + + pub fn go_to_temp_sibling(&mut self, temp: u32) { + self.traversal.up(); + self.traversal.down_to_temp(temp); + } + + pub fn go_down_to_temp_child(&mut self, temp: u32) { + self.traversal.down_to_temp(temp); + } + + pub fn commit_traversal(&mut self) { + if self.traversal.is_committed() { + return; + } + + for mv in self.traversal.commit() { + // match mv { + // MoveTo::Parent => { + // // debug!("emit: pop"); + // self.emitter.pop(); + // } + // MoveTo::Child(n) => { + // // debug!("emit: push_child({})", n); + // self.emitter.push_child(n); + // } + // MoveTo::ReverseChild(n) => { + // // debug!("emit: push_reverse_child({})", n); + // self.emitter.push_reverse_child(n); + // } + // MoveTo::Sibling(n) => { + // // debug!("emit: pop_push_child({})", n); + // self.emitter.pop_push_child(n); + // } + // MoveTo::ReverseSibling(n) => { + // // debug!("emit: pop_push_reverse_child({})", n); + // self.emitter.pop_push_reverse_child(n); + // } + // MoveTo::TempChild(temp) => { + // // debug!("emit: push_temporary({})", temp); + // self.emitter.push_temporary(temp); + // } + // } + } + } + + pub fn traversal_is_committed(&self) -> bool { + self.traversal.is_committed() + } +} + +impl ChangeList<'_> { + pub fn next_temporary(&self) -> u32 { + self.next_temporary + } + + pub fn set_next_temporary(&mut self, next_temporary: u32) { + self.next_temporary = next_temporary; + } + + pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 { + debug_assert!(self.traversal_is_committed()); + debug_assert!(start < end); + let temp_base = self.next_temporary; + // debug!( + // "emit: save_children_to_temporaries({}, {}, {})", + // temp_base, start, end + // ); + self.next_temporary = temp_base + (end - start) as u32; + self.emitter + .save_children_to_temporaries(temp_base, start as u32, end as u32); + temp_base + } + + pub fn push_temporary(&self, temp: u32) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: push_temporary({})", temp); + self.emitter.push_temporary(temp); + } + + pub fn remove_child(&self, child: usize) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: remove_child({})", child); + self.emitter.remove_child(child as u32); + } + + pub fn insert_before(&self) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: insert_before()"); + self.emitter.insert_before(); + } + + pub fn ensure_string(&mut self, string: &str) -> StringKey { + todo!() + // self.strings.ensure_string(string, &self.emitter) + } + + pub fn set_text(&self, text: &str) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: set_text({:?})", text); + self.emitter.set_text(text); + // .set_text(text.as_ptr() as u32, text.len() as u32); + } + + pub fn remove_self_and_next_siblings(&self) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: remove_self_and_next_siblings()"); + self.emitter.remove_self_and_next_siblings(); + } + + pub fn replace_with(&self) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: replace_with()"); + self.emitter.replace_with(); + } + + pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) { + debug_assert!(self.traversal_is_committed()); + todo!() + // if name == "class" && !is_namespaced { + // let class_id = self.ensure_string(value); + // // debug!("emit: set_class({:?})", value); + // self.emitter.set_class(class_id.into()); + // } else { + // let name_id = self.ensure_string(name); + // let value_id = self.ensure_string(value); + // // debug!("emit: set_attribute({:?}, {:?})", name, value); + // self.state + // .emitter + // .set_attribute(name_id.into(), value_id.into()); + // } + } + + pub fn remove_attribute(&mut self, name: &str) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: remove_attribute({:?})", name); + let name_id = self.ensure_string(name); + self.emitter.remove_attribute(name_id.into()); + } + + pub fn append_child(&self) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: append_child()"); + self.emitter.append_child(); + } + + pub fn create_text_node(&self, text: &str) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: create_text_node({:?})", text); + self.emitter.create_text_node(text); + } + + pub fn create_element(&mut self, tag_name: &str) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: create_element({:?})", tag_name); + let tag_name_id = self.ensure_string(tag_name); + self.emitter.create_element(tag_name_id.into()); + } + + pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns); + let tag_name_id = self.ensure_string(tag_name); + let ns_id = self.ensure_string(ns); + self.emitter + .create_element_ns(tag_name_id.into(), ns_id.into()); + } + + pub fn push_force_new_listeners(&mut self) -> bool { + let old = self.forcing_new_listeners; + self.forcing_new_listeners = true; + old + } + + pub fn pop_force_new_listeners(&mut self, previous: bool) { + debug_assert!(self.forcing_new_listeners); + self.forcing_new_listeners = previous; + } + + pub fn new_event_listener(&mut self, listener: &Listener) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: new_event_listener({:?})", listener); + let (a, b) = listener.get_callback_parts(); + debug_assert!(a != 0); + let event_id = self.ensure_string(listener.event); + self.emitter.new_event_listener(event_id.into(), a, b); + } + + pub fn update_event_listener(&mut self, listener: &Listener) { + debug_assert!(self.traversal_is_committed()); + + if self.forcing_new_listeners { + self.new_event_listener(listener); + return; + } + + // debug!("emit: update_event_listener({:?})", listener); + let (a, b) = listener.get_callback_parts(); + debug_assert!(a != 0); + let event_id = self.ensure_string(listener.event); + self.emitter.update_event_listener(event_id.into(), a, b); + } + + pub fn remove_event_listener(&mut self, event: &str) { + debug_assert!(self.traversal_is_committed()); + // debug!("emit: remove_event_listener({:?})", event); + let event_id = self.ensure_string(event); + self.emitter.remove_event_listener(event_id.into()); + } + + // #[inline] + // pub fn has_template(&mut self, id: CacheId) -> bool { + // self.templates.contains(&id) + // } + + // pub fn save_template(&mut self, id: CacheId) { + // debug_assert!(self.traversal_is_committed()); + // debug_assert!(!self.has_template(id)); + // // debug!("emit: save_template({:?})", id); + // self.templates.insert(id); + // self.emitter.save_template(id.into()); + // } + + // pub fn push_template(&mut self, id: CacheId) { + // debug_assert!(self.traversal_is_committed()); + // debug_assert!(self.has_template(id)); + // // debug!("emit: push_template({:?})", id); + // self.emitter.push_template(id.into()); + // } +} + +// Keeps track of where we are moving in a DOM tree, and shortens traversal +// paths between mutations to their minimal number of operations. + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub enum MoveTo { + /// Move from the current node up to its parent. + Parent, + + /// Move to the current node's n^th child. + Child(u32), + + /// Move to the current node's n^th from last child. + ReverseChild(u32), + + /// Move to the n^th sibling. Not relative from the current + /// location. Absolute indexed within all of the current siblings. + Sibling(u32), + + /// Move to the n^th from last sibling. Not relative from the current + /// location. Absolute indexed within all of the current siblings. + ReverseSibling(u32), + + /// Move down to the given saved temporary child. + TempChild(u32), +} + +#[derive(Debug)] +pub struct Traversal { + uncommitted: Vec, +} + +impl Traversal { + /// Construct a new `Traversal` with its internal storage backed by the + /// given bump arena. + pub fn new() -> Traversal { + Traversal { + uncommitted: Vec::with_capacity(32), + } + } + + /// Move the traversal up in the tree. + pub fn up(&mut self) { + match self.uncommitted.last() { + Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => { + self.uncommitted.pop(); + self.uncommitted.push(MoveTo::Parent); + } + Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => { + self.uncommitted.pop(); + // And we're back at the parent. + } + _ => { + self.uncommitted.push(MoveTo::Parent); + } + } + } + + /// Move the traversal down in the tree to the first child of the current + /// node. + pub fn down(&mut self) { + if let Some(&MoveTo::Parent) = self.uncommitted.last() { + self.uncommitted.pop(); + self.sibling(0); + } else { + self.uncommitted.push(MoveTo::Child(0)); + } + } + + /// Move the traversal to the n^th sibling. + pub fn sibling(&mut self, index: usize) { + let index = index as u32; + match self.uncommitted.last_mut() { + Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => { + *n = index; + } + Some(MoveTo::ReverseSibling(_)) => { + self.uncommitted.pop(); + self.uncommitted.push(MoveTo::Sibling(index)); + } + Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => { + self.uncommitted.pop(); + self.uncommitted.push(MoveTo::Child(index)) + } + _ => { + self.uncommitted.push(MoveTo::Sibling(index)); + } + } + } + + /// Move the the n^th from last sibling. + pub fn reverse_sibling(&mut self, index: usize) { + let index = index as u32; + match self.uncommitted.last_mut() { + Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => { + *n = index; + } + Some(MoveTo::Sibling(_)) => { + self.uncommitted.pop(); + self.uncommitted.push(MoveTo::ReverseSibling(index)); + } + Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => { + self.uncommitted.pop(); + self.uncommitted.push(MoveTo::ReverseChild(index)) + } + _ => { + self.uncommitted.push(MoveTo::ReverseSibling(index)); + } + } + } + + /// Go to the given saved temporary. + pub fn down_to_temp(&mut self, temp: u32) { + match self.uncommitted.last() { + Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => { + self.uncommitted.pop(); + } + Some(MoveTo::Parent) + | Some(MoveTo::TempChild(_)) + | Some(MoveTo::Child(_)) + | Some(MoveTo::ReverseChild(_)) + | None => { + // Can't remove moves to parents since we rely on their stack + // pops. + } + } + self.uncommitted.push(MoveTo::TempChild(temp)); + } + + /// Are all the traversal's moves committed? That is, are there no moves + /// that have *not* been committed yet? + #[inline] + pub fn is_committed(&self) -> bool { + self.uncommitted.is_empty() + } + + /// Commit this traversals moves and return the optimized path from the last + /// commit. + #[inline] + pub fn commit(&mut self) -> Moves { + Moves { + inner: self.uncommitted.drain(..), + } + } + + #[inline] + pub fn reset(&mut self) { + self.uncommitted.clear(); + } +} + +pub struct Moves<'a> { + inner: std::vec::Drain<'a, MoveTo>, +} + +impl Iterator for Moves<'_> { + type Item = MoveTo; + + #[inline] + fn next(&mut self) -> Option { + self.inner.next() + } +} + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn test_traversal() { + fn t(f: F) -> Box + where + F: 'static + FnMut(&mut Traversal), + { + Box::new(f) as _ + } + + for (mut traverse, expected_moves) in vec![ + ( + t(|t| { + t.down(); + }), + vec![MoveTo::Child(0)], + ), + ( + t(|t| { + t.up(); + }), + vec![MoveTo::Parent], + ), + ( + t(|t| { + t.sibling(42); + }), + vec![MoveTo::Sibling(42)], + ), + ( + t(|t| { + t.down(); + t.up(); + }), + vec![], + ), + ( + t(|t| { + t.down(); + t.sibling(2); + t.up(); + }), + vec![], + ), + ( + t(|t| { + t.down(); + t.sibling(3); + }), + vec![MoveTo::Child(3)], + ), + ( + t(|t| { + t.down(); + t.sibling(4); + t.sibling(8); + }), + vec![MoveTo::Child(8)], + ), + ( + t(|t| { + t.sibling(1); + t.sibling(1); + }), + vec![MoveTo::Sibling(1)], + ), + ( + t(|t| { + t.reverse_sibling(3); + }), + vec![MoveTo::ReverseSibling(3)], + ), + ( + t(|t| { + t.down(); + t.reverse_sibling(3); + }), + vec![MoveTo::ReverseChild(3)], + ), + ( + t(|t| { + t.down(); + t.reverse_sibling(3); + t.up(); + }), + vec![], + ), + ( + t(|t| { + t.down(); + t.reverse_sibling(3); + t.reverse_sibling(6); + }), + vec![MoveTo::ReverseChild(6)], + ), + ( + t(|t| { + t.up(); + t.reverse_sibling(3); + t.reverse_sibling(6); + }), + vec![MoveTo::Parent, MoveTo::ReverseSibling(6)], + ), + ( + t(|t| { + t.up(); + t.sibling(3); + t.sibling(6); + }), + vec![MoveTo::Parent, MoveTo::Sibling(6)], + ), + ( + t(|t| { + t.sibling(3); + t.sibling(6); + t.up(); + }), + vec![MoveTo::Parent], + ), + ( + t(|t| { + t.reverse_sibling(3); + t.reverse_sibling(6); + t.up(); + }), + vec![MoveTo::Parent], + ), + ( + t(|t| { + t.down(); + t.down_to_temp(3); + }), + vec![MoveTo::Child(0), MoveTo::TempChild(3)], + ), + ( + t(|t| { + t.down_to_temp(3); + t.sibling(5); + }), + vec![MoveTo::Child(5)], + ), + ( + t(|t| { + t.down_to_temp(3); + t.reverse_sibling(5); + }), + vec![MoveTo::ReverseChild(5)], + ), + ( + t(|t| { + t.down_to_temp(3); + t.up(); + }), + vec![], + ), + ( + t(|t| { + t.sibling(2); + t.up(); + t.down_to_temp(3); + }), + vec![MoveTo::Parent, MoveTo::TempChild(3)], + ), + ( + t(|t| { + t.up(); + t.down_to_temp(3); + }), + vec![MoveTo::Parent, MoveTo::TempChild(3)], + ), + ] { + let mut traversal = Traversal::new(); + traverse(&mut traversal); + let actual_moves: Vec<_> = traversal.commit().collect(); + assert_eq!(actual_moves, expected_moves); + } + } +} + +#[derive(Clone, Copy, Debug, PartialEq, Eq)] +pub struct StringKey(u32); + +impl From for u32 { + #[inline] + fn from(key: StringKey) -> u32 { + key.0 + } +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 92285048e..0537b95fa 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -65,6 +65,7 @@ //! - dioxus-liveview (SSR + StringRenderer) //! +pub mod changelist; pub mod component; pub mod context; pub mod debug_renderer; diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index aad8ca429..325341bff 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -8,6 +8,7 @@ use std::{ cell::{RefCell, UnsafeCell}, future::Future, marker::PhantomData, + rc::Rc, sync::atomic::AtomicUsize, }; @@ -21,11 +22,14 @@ pub struct VirtualDom { /// The index of the root component. base_scope: Index, - /// Components generate lifecycle events - event_queue: Vec, // wrap in a lock or something so + /// + /// + /// + /// + event_queue: Rc>>, - // mark the root props with P, even though they're held by the root component - _p: PhantomData

, + #[doc(hidden)] // mark the root props with P, even though they're held by the root component + _root_props: PhantomData

, } impl VirtualDom<()> { @@ -61,19 +65,19 @@ impl VirtualDom

{ let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props); // Create an event queue with a mount for the base scope - let event_queue = vec![first_event]; + let event_queue = Rc::new(RefCell::new(vec![first_event])); Self { components, base_scope, event_queue, - _p: PhantomData {}, + _root_props: PhantomData {}, } } /// With access to the virtual dom, schedule an update to the Root component's props pub fn update_props(&mut self, new_props: P) { - self.event_queue.push(LifecycleEvent { + self.event_queue.borrow_mut().push(LifecycleEvent { event_type: LifecycleType::PropsChanged { props: Box::new(new_props), }, @@ -86,8 +90,8 @@ impl VirtualDom

{ /// Takes a bump arena to allocate into, making the diff phase as fast as possible /// pub fn progress(&mut self) -> Result<()> { - let event = self.event_queue.pop().ok_or(Error::NoEvent)?; - process_event(self, event) + let event = self.event_queue.borrow_mut().pop().ok_or(Error::NoEvent)?; + process_event(&mut self.components, event) } /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered. @@ -125,9 +129,21 @@ impl VirtualDom

{ .as_ref(); // Run the callback - // Theoretically, this should + // This should cause internal state to progress, dumping events into the event queue + // todo: integrate this with a tracing mechanism exposed to a dev tool listener(); + // Run through our events, tagging which Indexes are receiving updates + // Prop updates take prescedence over subscription updates + // Run all prop updates *first* as they will cascade into children. + // *then* run the non-prop updates that were not already covered by props + let mut events = self.event_queue.borrow_mut(); + + // for now, just naively process each event in the queue + for event in events.drain(..) { + process_event(&mut self.components, event)?; + } + Ok(()) } @@ -143,11 +159,12 @@ impl VirtualDom

{ /// /// /// -fn process_event( - dom: &mut VirtualDom

, +fn process_event( + // dom: &mut VirtualDom

, + components: &mut Arena, LifecycleEvent { index, event_type }: LifecycleEvent, ) -> Result<()> { - let scope = dom.components.get(index).ok_or(Error::NoEvent)?; + let scope = components.get(index).ok_or(Error::NoEvent)?; match event_type { // Component needs to be mounted to the virtual dom @@ -176,7 +193,8 @@ fn process_event( // Component was removed from the DOM // Run any destructors and cleanup for the hooks and the dump the component LifecycleType::Removed {} => { - let f = dom.components.remove(index); + let f = components.remove(index); + // let f = dom.components.remove(index); } // Component was messaged via the internal subscription service diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index 7959cffb5..34026213f 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -13,6 +13,8 @@ wasm-bindgen = "0.2.70" lazy_static = "1.4.0" wasm-bindgen-futures = "0.4.20" futures = "0.3.12" +wasm-logger = "0.2.0" +log = "0.4.14" # html-validation = { path = "../html-validation", version = "0.1.1" } [dependencies.web-sys] diff --git a/packages/web/src/interpreter.rs b/packages/web/src/interpreter.rs new file mode 100644 index 000000000..7929ca46d --- /dev/null +++ b/packages/web/src/interpreter.rs @@ -0,0 +1,381 @@ +use crate::cached_set::CacheId; +use crate::{Element, EventsTrampoline}; +use fxhash::FxHashMap; +use log::{debug, info, log}; +use wasm_bindgen::{closure::Closure, JsCast}; +use web_sys::{window, Document, Event, Node}; + +#[derive(Debug)] +pub(crate) struct ChangeListInterpreter { + container: Element, + stack: Stack, + temporaries: FxHashMap, + templates: FxHashMap, + callback: Option>, + document: Document, +} + +#[derive(Debug, Default)] +struct Stack { + list: Vec, +} + +impl Stack { + pub fn with_capacity(cap: usize) -> Self { + Stack { + list: Vec::with_capacity(cap), + } + } + + pub fn push(&mut self, node: Node) { + debug!("stack-push: {:?}", node); + self.list.push(node); + } + + pub fn pop(&mut self) -> Node { + let res = self.list.pop().unwrap(); + debug!("stack-pop: {:?}", res); + + res + } + + pub fn clear(&mut self) { + self.list.clear(); + } + + pub fn top(&self) -> &Node { + &self.list[self.list.len() - 1] + } +} + +impl ChangeListInterpreter { + pub fn new(container: Element) -> Self { + let document = window() + .expect("must have access to the window") + .document() + .expect("must have access to the Document"); + + Self { + container, + stack: Stack::with_capacity(20), + temporaries: Default::default(), + templates: Default::default(), + callback: None, + document, + } + } + + pub fn unmount(&mut self) { + self.stack.clear(); + self.temporaries.clear(); + self.templates.clear(); + } + + pub fn start(&mut self) { + if let Some(child) = self.container.first_child() { + self.stack.push(child); + } + } + + pub fn reset(&mut self) { + self.stack.clear(); + self.temporaries.clear(); + } + + pub fn get_template(&self, id: CacheId) -> Option<&Node> { + self.templates.get(&id) + } + + pub fn init_events_trampoline(&mut self, mut trampoline: EventsTrampoline) { + self.callback = Some(Closure::wrap(Box::new(move |event: &web_sys::Event| { + let target = event + .target() + .expect("missing target") + .dyn_into::() + .expect("not a valid element"); + let typ = event.type_(); + let a: u32 = target + .get_attribute(&format!("dodrio-a-{}", typ)) + .and_then(|v| v.parse().ok()) + .unwrap_or_default(); + + let b: u32 = target + .get_attribute(&format!("dodrio-b-{}", typ)) + .and_then(|v| v.parse().ok()) + .unwrap_or_default(); + + // get a and b from the target + trampoline(event.clone(), a, b); + }) as Box)); + } + + // 0 + pub fn set_text(&mut self, text: &str) { + self.stack.top().set_text_content(Some(text)); + } + + // 1 + pub fn remove_self_and_next_siblings(&mut self) { + let node = self.stack.pop(); + let mut sibling = node.next_sibling(); + + while let Some(inner) = sibling { + let temp = inner.next_sibling(); + if let Some(sibling) = inner.dyn_ref::() { + sibling.remove(); + } + sibling = temp; + } + if let Some(node) = node.dyn_ref::() { + node.remove(); + } + } + + // 2 + pub fn replace_with(&mut self) { + let new_node = self.stack.pop(); + let old_node = self.stack.pop(); + + if old_node.has_type::() { + old_node + .dyn_ref::() + .unwrap() + .replace_with_with_node_1(&new_node) + .unwrap(); + } else if old_node.has_type::() { + old_node + .dyn_ref::() + .unwrap() + .replace_with_with_node_1(&new_node) + .unwrap(); + } else if old_node.has_type::() { + old_node + .dyn_ref::() + .unwrap() + .replace_with_with_node_1(&new_node) + .unwrap(); + } else { + panic!("Cannot replace node: {:?}", old_node); + } + + self.stack.push(new_node); + } + + // 3 + pub fn set_attribute(&mut self, name: &str, value: &str) { + let node = self.stack.top(); + + if let Some(node) = node.dyn_ref::() { + node.set_attribute(name, value).unwrap(); + + // Some attributes are "volatile" and don't work through `setAttribute`. + // TODO: + // if name == "value" { + // node.set_value(value); + // } + // if name == "checked" { + // node.set_checked(true); + // } + // if name == "selected" { + // node.set_selected(true); + // } + } + } + + // 4 + pub fn remove_attribute(&mut self, name: &str) { + let node = self.stack.top(); + if let Some(node) = node.dyn_ref::() { + node.remove_attribute(name).unwrap(); + + // Some attributes are "volatile" and don't work through `removeAttribute`. + // TODO: + // if name == "value" { + // node.set_value(""); + // } + // if name == "checked" { + // node.set_checked(false); + // } + // if name == "selected" { + // node.set_selected(false); + // } + } + } + + // 5 + pub fn push_reverse_child(&mut self, n: u32) { + let parent = self.stack.top(); + let children = parent.child_nodes(); + let child = children.get(children.length() - n - 1).unwrap(); + self.stack.push(child); + } + + // 6 + pub fn pop_push_child(&mut self, n: u32) { + self.stack.pop(); + let parent = self.stack.top(); + let children = parent.child_nodes(); + let child = children.get(n).unwrap(); + self.stack.push(child); + } + + // 7 + pub fn pop(&mut self) { + self.stack.pop(); + } + + // 8 + pub fn append_child(&mut self) { + let child = self.stack.pop(); + self.stack.top().append_child(&child).unwrap(); + } + + // 9 + pub fn create_text_node(&mut self, text: &str) { + self.stack.push( + self.document + .create_text_node(text) + .dyn_into::() + .unwrap(), + ); + } + + // 10 + pub fn create_element(&mut self, tag_name: &str) { + let el = self + .document + .create_element(tag_name) + .unwrap() + .dyn_into::() + .unwrap(); + self.stack.push(el); + } + + // 11 + pub fn new_event_listener(&mut self, event_type: &str, a: u32, b: u32) { + let el = self.stack.top(); + + let el = el + .dyn_ref::() + .expect(&format!("not an element: {:?}", el)); + el.add_event_listener_with_callback( + event_type, + self.callback.as_ref().unwrap().as_ref().unchecked_ref(), + ) + .unwrap(); + debug!("adding attributes: {}, {}", a, b); + el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) + .unwrap(); + el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) + .unwrap(); + } + + // 12 + pub fn update_event_listener(&mut self, event_type: &str, a: u32, b: u32) { + if let Some(el) = self.stack.top().dyn_ref::() { + el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string()) + .unwrap(); + el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string()) + .unwrap(); + } + } + + // 13 + pub fn remove_event_listener(&mut self, event_type: &str) { + if let Some(el) = self.stack.top().dyn_ref::() { + el.remove_event_listener_with_callback( + event_type, + self.callback.as_ref().unwrap().as_ref().unchecked_ref(), + ) + .unwrap(); + } + } + + // 16 + pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) { + let el = self + .document + .create_element_ns(Some(ns), tag_name) + .unwrap() + .dyn_into::() + .unwrap(); + self.stack.push(el); + } + + // 17 + pub fn save_children_to_temporaries(&mut self, mut temp: u32, start: u32, end: u32) { + let parent = self.stack.top(); + let children = parent.child_nodes(); + for i in start..end { + self.temporaries.insert(temp, children.get(i).unwrap()); + temp += 1; + } + } + + // 18 + pub fn push_child(&mut self, n: u32) { + let parent = self.stack.top(); + let child = parent.child_nodes().get(n).unwrap(); + self.stack.push(child); + } + + // 19 + pub fn push_temporary(&mut self, temp: u32) { + let t = self.temporaries.get(&temp).unwrap().clone(); + self.stack.push(t); + } + + // 20 + pub fn insert_before(&mut self) { + let before = self.stack.pop(); + let after = self.stack.pop(); + after + .parent_node() + .unwrap() + .insert_before(&before, Some(&after)) + .unwrap(); + self.stack.push(before); + } + + // 21 + pub fn pop_push_reverse_child(&mut self, n: u32) { + self.stack.pop(); + let parent = self.stack.top(); + let children = parent.child_nodes(); + let child = children.get(children.length() - n - 1).unwrap(); + self.stack.push(child); + } + + // 22 + pub fn remove_child(&mut self, n: u32) { + let parent = self.stack.top(); + if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::() { + child.remove(); + } + } + + // 23 + pub fn set_class(&mut self, class_name: &str) { + if let Some(el) = self.stack.top().dyn_ref::() { + el.set_class_name(class_name); + } + } + + // 24 + pub fn save_template(&mut self, id: CacheId) { + let template = self.stack.top(); + let t = template.clone_node_with_deep(true).unwrap(); + self.templates.insert(id, t); + } + + // 25 + pub fn push_template(&mut self, id: CacheId) { + let template = self.get_template(id).unwrap(); + let t = template.clone_node_with_deep(true).unwrap(); + self.stack.push(t); + } + + pub fn has_template(&self, id: CacheId) -> bool { + self.templates.contains_key(&id) + } +} diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 34fddfd7a..2cedd1372 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -24,7 +24,7 @@ use dioxus_core::{ }; use futures::{channel::mpsc, future, SinkExt, StreamExt}; use mpsc::UnboundedSender; - +pub mod interpreter; /// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM. /// Under the hood, we leverage WebSys and interact directly with the DOM