Browse Source

Feat: remove generic paramter on VDOM

This makes storage of VDoms easier, and removes static dispatch on any function that relies on VDOMs.
Jonathan Kelley 4 years ago
parent
commit
4c291a0efd

+ 1 - 1
.vscode/settings.json

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

+ 36 - 28
packages/core/src/changelist.rs

@@ -19,7 +19,7 @@
 //!
 //!
 //!
 //!
 
 
-use crate::innerlude::Listener;
+use crate::innerlude::{Listener, VirtualDom};
 
 
 /// Renderers need to implement the interpreter trait
 /// Renderers need to implement the interpreter trait
 ///
 ///
@@ -60,6 +60,7 @@ pub struct ChangeList<'src> {
     next_temporary: u32,
     next_temporary: u32,
     forcing_new_listeners: bool,
     forcing_new_listeners: bool,
     emitter: &'src mut dyn Inrerpreter,
     emitter: &'src mut dyn Inrerpreter,
+    domlock: &'src VirtualDom,
 }
 }
 
 
 /// Traversal methods.
 /// Traversal methods.
@@ -158,19 +159,19 @@ impl ChangeList<'_> {
         temp_base
         temp_base
     }
     }
 
 
-    pub fn push_temporary(&self, temp: u32) {
+    pub fn push_temporary(&mut self, temp: u32) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: push_temporary({})", temp);
         // debug!("emit: push_temporary({})", temp);
         self.emitter.push_temporary(temp);
         self.emitter.push_temporary(temp);
     }
     }
 
 
-    pub fn remove_child(&self, child: usize) {
+    pub fn remove_child(&mut self, child: usize) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: remove_child({})", child);
         // debug!("emit: remove_child({})", child);
         self.emitter.remove_child(child as u32);
         self.emitter.remove_child(child as u32);
     }
     }
 
 
-    pub fn insert_before(&self) {
+    pub fn insert_before(&mut self) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: insert_before()");
         // debug!("emit: insert_before()");
         self.emitter.insert_before();
         self.emitter.insert_before();
@@ -181,20 +182,20 @@ impl ChangeList<'_> {
         // self.strings.ensure_string(string, &self.emitter)
         // self.strings.ensure_string(string, &self.emitter)
     }
     }
 
 
-    pub fn set_text(&self, text: &str) {
+    pub fn set_text(&mut self, text: &str) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: set_text({:?})", text);
         // debug!("emit: set_text({:?})", text);
         self.emitter.set_text(text);
         self.emitter.set_text(text);
         // .set_text(text.as_ptr() as u32, text.len() as u32);
         // .set_text(text.as_ptr() as u32, text.len() as u32);
     }
     }
 
 
-    pub fn remove_self_and_next_siblings(&self) {
+    pub fn remove_self_and_next_siblings(&mut self) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: remove_self_and_next_siblings()");
         // debug!("emit: remove_self_and_next_siblings()");
         self.emitter.remove_self_and_next_siblings();
         self.emitter.remove_self_and_next_siblings();
     }
     }
 
 
-    pub fn replace_with(&self) {
+    pub fn replace_with(&mut self) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: replace_with()");
         // debug!("emit: replace_with()");
         self.emitter.replace_with();
         self.emitter.replace_with();
@@ -218,38 +219,42 @@ impl ChangeList<'_> {
     }
     }
 
 
     pub fn remove_attribute(&mut self, name: &str) {
     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());
+        // todo!("figure out how to get this working with ensure string");
+        self.emitter.remove_attribute(name);
+        // 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) {
+    pub fn append_child(&mut self) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: append_child()");
         // debug!("emit: append_child()");
         self.emitter.append_child();
         self.emitter.append_child();
     }
     }
 
 
-    pub fn create_text_node(&self, text: &str) {
+    pub fn create_text_node(&mut self, text: &str) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: create_text_node({:?})", text);
         // debug!("emit: create_text_node({:?})", text);
         self.emitter.create_text_node(text);
         self.emitter.create_text_node(text);
     }
     }
 
 
     pub fn create_element(&mut self, tag_name: &str) {
     pub fn create_element(&mut self, tag_name: &str) {
-        debug_assert!(self.traversal_is_committed());
+        // debug_assert!(self.traversal_is_committed());
         // debug!("emit: create_element({:?})", tag_name);
         // debug!("emit: create_element({:?})", tag_name);
-        let tag_name_id = self.ensure_string(tag_name);
-        self.emitter.create_element(tag_name_id.into());
+        // let tag_name_id = self.ensure_string(tag_name);
+        self.emitter.create_element(tag_name);
+        // self.emitter.create_element(tag_name_id.into());
     }
     }
 
 
     pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
     pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
         // 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());
+        // let tag_name_id = self.ensure_string(tag_name);
+        // let ns_id = self.ensure_string(ns);
+        self.emitter.create_element_ns(tag_name, ns);
+        // self.emitter
+        //     .create_element_ns(tag_name_id.into(), ns_id.into());
     }
     }
 
 
     pub fn push_force_new_listeners(&mut self) -> bool {
     pub fn push_force_new_listeners(&mut self) -> bool {
@@ -265,11 +270,12 @@ impl ChangeList<'_> {
 
 
     pub fn new_event_listener(&mut self, listener: &Listener) {
     pub fn new_event_listener(&mut self, listener: &Listener) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
+        todo!("Event listener not wired up yet");
         // debug!("emit: new_event_listener({:?})", listener);
         // 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);
+        // 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) {
     pub fn update_event_listener(&mut self, listener: &Listener) {
@@ -281,17 +287,19 @@ impl ChangeList<'_> {
         }
         }
 
 
         // debug!("emit: update_event_listener({:?})", listener);
         // debug!("emit: update_event_listener({:?})", listener);
-        let (a, b) = listener.get_callback_parts();
-        debug_assert!(a != 0);
+        todo!("Event listener not wired up yet");
+        // let (a, b) = listener.get_callback_parts();
+        // debug_assert!(a != 0);
         let event_id = self.ensure_string(listener.event);
         let event_id = self.ensure_string(listener.event);
-        self.emitter.update_event_listener(event_id.into(), a, b);
+        // self.emitter.update_event_listener(event_id.into(), a, b);
     }
     }
 
 
     pub fn remove_event_listener(&mut self, event: &str) {
     pub fn remove_event_listener(&mut self, event: &str) {
         debug_assert!(self.traversal_is_committed());
         debug_assert!(self.traversal_is_committed());
         // debug!("emit: remove_event_listener({:?})", event);
         // debug!("emit: remove_event_listener({:?})", event);
         let event_id = self.ensure_string(event);
         let event_id = self.ensure_string(event);
-        self.emitter.remove_event_listener(event_id.into());
+        todo!("Event listener not wired up yet");
+        // self.emitter.remove_event_listener(event_id.into());
     }
     }
 
 
     // #[inline]
     // #[inline]

+ 4 - 4
packages/core/src/debug_renderer.rs

@@ -5,12 +5,12 @@
 
 
 use crate::prelude::{Properties, VirtualDom};
 use crate::prelude::{Properties, VirtualDom};
 
 
-pub struct DebugRenderer<'a, P: Properties> {
-    vdom: &'a mut VirtualDom<P>,
+pub struct DebugRenderer {
+    vdom: VirtualDom,
 }
 }
 
 
-impl<'a, P: Properties> DebugRenderer<'a, P> {
-    pub fn new(vdom: &'a mut VirtualDom<P>) -> Self {
+impl DebugRenderer {
+    pub fn new(vdom: VirtualDom) -> Self {
         Self { vdom }
         Self { vdom }
     }
     }
 
 

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

@@ -7,6 +7,9 @@ pub enum Error {
     #[error("No event to progress")]
     #[error("No event to progress")]
     NoEvent,
     NoEvent,
 
 
+    #[error("Wrong Properties Type")]
+    WrongProps,
+
     // #[error("Out of compute credits")]
     // #[error("Out of compute credits")]
     // OutOfCredits,
     // OutOfCredits,
 
 

+ 32 - 12
packages/core/src/virtual_dom.rs

@@ -1,4 +1,4 @@
-use crate::nodes::VNode;
+use crate::{changelist::ChangeList, nodes::VNode};
 use crate::{events::EventTrigger, innerlude::*};
 use crate::{events::EventTrigger, innerlude::*};
 use any::Any;
 use any::Any;
 use bumpalo::Bump;
 use bumpalo::Bump;
@@ -14,7 +14,8 @@ use std::{
 
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
 /// An integrated virtual node system that progresses events and diffs UI trees.
 /// Differences are converted into patches which a renderer can use to draw the UI.
 /// Differences are converted into patches which a renderer can use to draw the UI.
-pub struct VirtualDom<P: Properties> {
+pub struct VirtualDom {
+    // pub struct VirtualDom<P: Properties> {
     /// All mounted components are arena allocated to make additions, removals, and references easy to work with
     /// All mounted components are arena allocated to make additions, removals, and references easy to work with
     /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
     /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
     pub(crate) components: Arena<Scope>,
     pub(crate) components: Arena<Scope>,
@@ -28,11 +29,15 @@ pub struct VirtualDom<P: Properties> {
     ///
     ///
     event_queue: Rc<RefCell<Vec<LifecycleEvent>>>,
     event_queue: Rc<RefCell<Vec<LifecycleEvent>>>,
 
 
-    #[doc(hidden)] // mark the root props with P, even though they're held by the root component
-    _root_props: PhantomData<P>,
+    // Mark the root props with P, even though they're held by the root component
+    // This is done so we don't have a "generic" vdom, making it easier to hold references to it, especially when the holders
+    // don't care about the generic props type
+    // Most implementations that use the VirtualDom won't care about the root props anyways.
+    #[doc(hidden)]
+    _root_prop_type: std::any::TypeId,
 }
 }
 
 
-impl VirtualDom<()> {
+impl VirtualDom {
     /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     ///
     ///
     /// This means that the root component must either consumes its own context, or statics are used to generate the page.
     /// This means that the root component must either consumes its own context, or statics are used to generate the page.
@@ -42,13 +47,14 @@ impl VirtualDom<()> {
     }
     }
 }
 }
 
 
-impl<P: Properties + 'static> VirtualDom<P> {
+impl VirtualDom {
+    // impl<P: Properties + 'static> VirtualDom<P> {
     /// Start a new VirtualDom instance with a dependent props.
     /// Start a new VirtualDom instance with a dependent props.
     /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
     /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
     ///
     ///
     /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
     /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
     /// to toss out the entire tree.
     /// to toss out the entire tree.
-    pub fn new_with_props(root: FC<P>, root_props: P) -> Self {
+    pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
         // 1. Create the component arena
         // 1. Create the component arena
         // 2. Create the base scope (can never be removed)
         // 2. Create the base scope (can never be removed)
         // 3. Create the lifecycle queue
         // 3. Create the lifecycle queue
@@ -67,28 +73,41 @@ impl<P: Properties + 'static> VirtualDom<P> {
         // Create an event queue with a mount for the base scope
         // Create an event queue with a mount for the base scope
         let event_queue = Rc::new(RefCell::new(vec![first_event]));
         let event_queue = Rc::new(RefCell::new(vec![first_event]));
 
 
+        let _root_prop_type = TypeId::of::<P>();
+
         Self {
         Self {
             components,
             components,
             base_scope,
             base_scope,
             event_queue,
             event_queue,
-            _root_props: PhantomData {},
+            _root_prop_type,
         }
         }
     }
     }
 
 
     /// With access to the virtual dom, schedule an update to the Root component's props
     /// With access to the virtual dom, schedule an update to the Root component's props
-    pub fn update_props(&mut self, new_props: P) {
+    pub fn update_props<P: Properties + 'static>(&mut self, new_props: P) -> Result<()> {
+        // Ensure the props match
+        if TypeId::of::<P>() != self._root_prop_type {
+            return Err(Error::WrongProps);
+        }
+
         self.event_queue.borrow_mut().push(LifecycleEvent {
         self.event_queue.borrow_mut().push(LifecycleEvent {
             event_type: LifecycleType::PropsChanged {
             event_type: LifecycleType::PropsChanged {
                 props: Box::new(new_props),
                 props: Box::new(new_props),
             },
             },
             index: self.base_scope,
             index: self.base_scope,
         });
         });
+
+        Ok(())
     }
     }
 
 
+    /// Schedule a future update for a component from outside the vdom!
+    ///
+    /// This lets services external to the virtual dom interact directly with the component and event system.
+    pub fn queue_update() {}
+
     /// Pop an event off the event queue and process it
     /// Pop an event off the event queue and process it
     /// Update the root props, and progress
     /// Update the root props, and progress
     /// Takes a bump arena to allocate into, making the diff phase as fast as possible
     /// Takes a bump arena to allocate into, making the diff phase as fast as possible
-    ///
     pub fn progress(&mut self) -> Result<()> {
     pub fn progress(&mut self) -> Result<()> {
         let event = self.event_queue.borrow_mut().pop().ok_or(Error::NoEvent)?;
         let event = self.event_queue.borrow_mut().pop().ok_or(Error::NoEvent)?;
         process_event(&mut self.components, event)
         process_event(&mut self.components, event)
@@ -109,7 +128,7 @@ impl<P: Properties + 'static> VirtualDom<P> {
     ///
     ///
     ///
     ///
     /// ```
     /// ```
-    pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<()> {
+    pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<ChangeList<'_>> {
         let EventTrigger {
         let EventTrigger {
             component_id,
             component_id,
             listener_id,
             listener_id,
@@ -144,7 +163,8 @@ impl<P: Properties + 'static> VirtualDom<P> {
             process_event(&mut self.components, event)?;
             process_event(&mut self.components, event)?;
         }
         }
 
 
-        Ok(())
+        todo!()
+        // Ok(())
     }
     }
 
 
     pub async fn progress_completely(&mut self) -> Result<()> {
     pub async fn progress_completely(&mut self) -> Result<()> {

+ 2 - 0
packages/web/Cargo.toml

@@ -15,6 +15,7 @@ wasm-bindgen-futures = "0.4.20"
 futures = "0.3.12"
 futures = "0.3.12"
 wasm-logger = "0.2.0"
 wasm-logger = "0.2.0"
 log = "0.4.14"
 log = "0.4.14"
+fxhash = "0.2.1"
 # html-validation = { path = "../html-validation", version = "0.1.1" }
 # html-validation = { path = "../html-validation", version = "0.1.1" }
 
 
 [dependencies.web-sys]
 [dependencies.web-sys]
@@ -33,4 +34,5 @@ features = [
     "Event",
     "Event",
     "MouseEvent",
     "MouseEvent",
     "InputEvent",
     "InputEvent",
+    "DocumentType",
 ]
 ]

+ 29 - 24
packages/web/src/interpreter.rs

@@ -1,9 +1,12 @@
-use crate::cached_set::CacheId;
-use crate::{Element, EventsTrampoline};
+// use crate::cached_set::CacheId;
+// use crate::{Element, EventsTrampoline};
 use fxhash::FxHashMap;
 use fxhash::FxHashMap;
 use log::{debug, info, log};
 use log::{debug, info, log};
 use wasm_bindgen::{closure::Closure, JsCast};
 use wasm_bindgen::{closure::Closure, JsCast};
-use web_sys::{window, Document, Event, Node};
+use web_sys::{window, Document, Element, Event, Node};
+
+#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
+pub(crate) struct CacheId(u32);
 
 
 #[derive(Debug)]
 #[derive(Debug)]
 pub(crate) struct ChangeListInterpreter {
 pub(crate) struct ChangeListInterpreter {
@@ -86,27 +89,29 @@ impl ChangeListInterpreter {
         self.templates.get(&id)
         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::<Element>()
-                .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<dyn FnMut(&Event)>));
+    pub fn init_events_trampoline(&mut self, mut trampoline: ()) {
+        todo!("Event trampoline not a thing anymore")
+        // 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::<Element>()
+        //         .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<dyn FnMut(&Event)>));
     }
     }
 
 
     // 0
     // 0

+ 8 - 8
packages/web/src/lib.rs

@@ -29,12 +29,12 @@ pub mod interpreter;
 /// Under the hood, we leverage WebSys and interact directly with the DOM
 /// Under the hood, we leverage WebSys and interact directly with the DOM
 
 
 ///
 ///
-pub struct WebsysRenderer<T: Properties> {
-    internal_dom: VirtualDom<T>,
+pub struct WebsysRenderer {
+    internal_dom: VirtualDom,
 }
 }
 
 
 /// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
 /// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
-impl WebsysRenderer<()> {
+impl WebsysRenderer {
     /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     ///
     ///
     /// This means that the root component must either consumes its own context, or statics are used to generate the page.
     /// This means that the root component must either consumes its own context, or statics are used to generate the page.
@@ -44,18 +44,18 @@ impl WebsysRenderer<()> {
     }
     }
 }
 }
 
 
-impl<T: Properties + 'static> WebsysRenderer<T> {
+impl WebsysRenderer {
     /// Create a new text-renderer instance from a functional component root.
     /// Create a new text-renderer instance from a functional component root.
     /// Automatically progresses the creation of the VNode tree to completion.
     /// Automatically progresses the creation of the VNode tree to completion.
     ///
     ///
     /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
     /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
-    pub fn new_with_props(root: FC<T>, root_props: T) -> Self {
+    pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
         Self::from_vdom(VirtualDom::new_with_props(root, root_props))
         Self::from_vdom(VirtualDom::new_with_props(root, root_props))
     }
     }
 
 
     /// Create a new text renderer from an existing Virtual DOM.
     /// Create a new text renderer from an existing Virtual DOM.
     /// This will progress the existing VDom's events to completion.
     /// This will progress the existing VDom's events to completion.
-    pub fn from_vdom(dom: VirtualDom<T>) -> Self {
+    pub fn from_vdom(dom: VirtualDom) -> Self {
         Self { internal_dom: dom }
         Self { internal_dom: dom }
     }
     }
 
 
@@ -76,7 +76,7 @@ impl<T: Properties + 'static> WebsysRenderer<T> {
         // Iterate through the nodes, attaching the closure and sender to the listener
         // Iterate through the nodes, attaching the closure and sender to the listener
         {
         {
             let mut remote_sender = sender.clone();
             let mut remote_sender = sender.clone();
-            let f = move || {
+            let listener = move || {
                 let event = EventTrigger::new();
                 let event = EventTrigger::new();
                 wasm_bindgen_futures::spawn_local(async move {
                 wasm_bindgen_futures::spawn_local(async move {
                     remote_sender
                     remote_sender
@@ -108,6 +108,6 @@ impl<T: Properties + 'static> WebsysRenderer<T> {
 
 
 /// For any listeners in the tree, attach the sender closure.
 /// For any listeners in the tree, attach the sender closure.
 /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
 /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
-fn attach_listeners<P: Properties>(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom<P>) {}
+fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
 
 
 fn render_diffs() {}
 fn render_diffs() {}