Ver código fonte

Feat: event loop

Jonathan Kelley 4 anos atrás
pai
commit
ea2aa4b
39 arquivos alterados com 502 adições e 294 exclusões
  1. 3 2
      Cargo.toml
  2. 0 0
      old/recoil/Cargo.toml
  3. 0 0
      old/recoil/src/lib.rs
  4. 0 0
      old/redux/Cargo.toml
  5. 0 0
      old/redux/src/lib.rs
  6. 0 0
      old/virtual-dom-rs/CHANGELOG.md
  7. 0 0
      old/virtual-dom-rs/Cargo.toml
  8. 0 0
      old/virtual-dom-rs/README.md
  9. 0 0
      old/virtual-dom-rs/src/diff/diff_test_case.rs
  10. 0 0
      old/virtual-dom-rs/src/diff/mod.rs
  11. 0 0
      old/virtual-dom-rs/src/dom_updater.rs
  12. 0 0
      old/virtual-dom-rs/src/lib.rs
  13. 0 0
      old/virtual-dom-rs/src/patch/apply_patches.rs
  14. 0 0
      old/virtual-dom-rs/src/patch/mod.rs
  15. 0 0
      old/virtual-dom-rs/tests/closures.rs
  16. 0 0
      old/virtual-dom-rs/tests/create_element.rs
  17. 0 0
      old/virtual-dom-rs/tests/diff_patch.rs
  18. 0 0
      old/virtual-dom-rs/tests/diff_patch_test_case/mod.rs
  19. 0 0
      old/virtual-dom-rs/tests/dom_updater.rs
  20. 0 0
      old/virtual-dom-rs/tests/events.rs
  21. 1 1
      packages/core/.vscode/settings.json
  22. 1 1
      packages/core/src/component.rs
  23. 1 1
      packages/core/src/context.rs
  24. 33 12
      packages/core/src/diff.rs
  25. 23 24
      packages/core/src/error.rs
  26. 13 0
      packages/core/src/events.rs
  27. 2 2
      packages/core/src/hooks.rs
  28. 2 2
      packages/core/src/lib.rs
  29. 3 5
      packages/core/src/nodebuilder.rs
  30. 6 6
      packages/core/src/nodes.rs
  31. 17 13
      packages/core/src/scope.rs
  32. 3 0
      packages/core/src/validation.rs
  33. 63 31
      packages/core/src/virtual_dom.rs
  34. 4 2
      packages/web/Cargo.toml
  35. 21 0
      packages/web/examples/hello.rs
  36. 94 192
      packages/web/src/lib.rs
  37. 211 0
      packages/web/src/old/lib.rs
  38. 0 0
      packages/web/src/old/virtual_node_test_utils.rs
  39. 1 0
      packages/web/src/util.rs

+ 3 - 2
Cargo.toml

@@ -3,14 +3,15 @@ members = [
     # Built-in
     "packages/dioxus",
     "packages/core",
-    "packages/recoil",
-    "packages/redux",
     "packages/core-macro",
     "packages/router",
     "packages/ssr",
     "packages/webview",
+    "packages/web",
     "packages/livehost",
     "packages/vscode-ext",
+    # "packages/recoil",
+    # "packages/redux",
     # "packages/macro",
     # TODO @Jon, share the validation code
     # "packages/web",

+ 0 - 0
packages/recoil/Cargo.toml → old/recoil/Cargo.toml


+ 0 - 0
packages/recoil/src/lib.rs → old/recoil/src/lib.rs


+ 0 - 0
packages/redux/Cargo.toml → old/redux/Cargo.toml


+ 0 - 0
packages/redux/src/lib.rs → old/redux/src/lib.rs


+ 0 - 0
packages/virtual-dom-rs/CHANGELOG.md → old/virtual-dom-rs/CHANGELOG.md


+ 0 - 0
packages/virtual-dom-rs/Cargo.toml → old/virtual-dom-rs/Cargo.toml


+ 0 - 0
packages/virtual-dom-rs/README.md → old/virtual-dom-rs/README.md


+ 0 - 0
packages/virtual-dom-rs/src/diff/diff_test_case.rs → old/virtual-dom-rs/src/diff/diff_test_case.rs


+ 0 - 0
packages/virtual-dom-rs/src/diff/mod.rs → old/virtual-dom-rs/src/diff/mod.rs


+ 0 - 0
packages/virtual-dom-rs/src/dom_updater.rs → old/virtual-dom-rs/src/dom_updater.rs


+ 0 - 0
packages/virtual-dom-rs/src/lib.rs → old/virtual-dom-rs/src/lib.rs


+ 0 - 0
packages/virtual-dom-rs/src/patch/apply_patches.rs → old/virtual-dom-rs/src/patch/apply_patches.rs


+ 0 - 0
packages/virtual-dom-rs/src/patch/mod.rs → old/virtual-dom-rs/src/patch/mod.rs


+ 0 - 0
packages/virtual-dom-rs/tests/closures.rs → old/virtual-dom-rs/tests/closures.rs


+ 0 - 0
packages/virtual-dom-rs/tests/create_element.rs → old/virtual-dom-rs/tests/create_element.rs


+ 0 - 0
packages/virtual-dom-rs/tests/diff_patch.rs → old/virtual-dom-rs/tests/diff_patch.rs


+ 0 - 0
packages/virtual-dom-rs/tests/diff_patch_test_case/mod.rs → old/virtual-dom-rs/tests/diff_patch_test_case/mod.rs


+ 0 - 0
packages/virtual-dom-rs/tests/dom_updater.rs → old/virtual-dom-rs/tests/dom_updater.rs


+ 0 - 0
packages/virtual-dom-rs/tests/events.rs → old/virtual-dom-rs/tests/events.rs


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

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

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

@@ -2,7 +2,7 @@
 //! for components to be used within Nodes.
 //!
 
-use crate::inner::*;
+use crate::innerlude::*;
 
 /// The `Component` trait refers to any struct or funciton that can be used as a component
 /// We automatically implement Component for FC<T>

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

@@ -1,5 +1,5 @@
 use crate::prelude::*;
-use crate::{inner::Scope, nodes::VNode};
+use crate::{innerlude::Scope, nodes::VNode};
 use bumpalo::Bump;
 use hooks::Hook;
 use std::{

+ 33 - 12
packages/core/src/diff.rs

@@ -1,15 +1,4 @@
-use crate::prelude::VNode;
-
-// use crate::{
-//     cached_set::{CacheId, CachedSet},
-//     change_list::ChangeListBuilder,
-//     events::EventsRegistry,
-//     node::{Attribute, ElementNode, Listener, Node, NodeKind, TextNode},
-// };
-// use fxhash::{FxHashMap, FxHashSet};
-// use std::cmp::Ordering;
-// use std::u32;
-// use wasm_bindgen::UnwrapThrowExt;
+use crate::innerlude::{VNode, VText};
 
 // Diff the `old` node with the `new` node. Emits instructions to modify a
 // physical DOM node that reflects `old` into something that reflects `new`.
@@ -28,6 +17,38 @@ pub(crate) fn diff(
     new: &VNode,
     // cached_roots: &mut FxHashSet<CacheId>,
 ) {
+    match (old, new) {
+        // This case occurs when we generate two text nodes that are the sa
+        (VNode::Text(VText { text: old_text }), VNode::Text(VText { text: new_text })) => {
+            //
+            if old_text != new_text {}
+        }
+
+        // compare elements
+        // if different, schedule different types of update
+        (VNode::Element(_), VNode::Element(_)) => {}
+        (VNode::Element(_), VNode::Text(_)) => {}
+        (VNode::Element(_), VNode::Component(_)) => {}
+        (VNode::Text(_), VNode::Element(_)) => {}
+        (VNode::Text(_), VNode::Component(_)) => {}
+
+        (VNode::Component(_), VNode::Element(_)) => {}
+        (VNode::Component(_), VNode::Text(_)) => {}
+
+        // No immediate change to dom. If props changed though, queue a "props changed" update
+        // However, mark these for a
+        (VNode::Component(_), VNode::Component(_)) => {}
+        //
+        (VNode::Suspended, _) | (_, VNode::Suspended) => {
+            todo!("Suspended components not currently available")
+        } // (VNode::Element(_), VNode::Suspended) => {}
+          // (VNode::Text(_), VNode::Suspended) => {}
+          // (VNode::Component(_), VNode::Suspended) => {}
+          // (VNode::Suspended, VNode::Element(_)) => {}
+          // (VNode::Suspended, VNode::Text(_)) => {}
+          // (VNode::Suspended, VNode::Suspended) => {}
+          // (VNode::Suspended, VNode::Component(_)) => {}
+    }
     todo!()
     // match (&new.kind, &old.kind) {
     //     (

+ 23 - 24
packages/core/src/error.rs

@@ -7,38 +7,37 @@ pub enum Error {
     #[error("No event to progress")]
     NoEvent,
 
-    #[error("Out of compute credits")]
-    OutOfCredits,
+    // #[error("Out of compute credits")]
+    // OutOfCredits,
 
-    /// Used when errors need to propogate but are too unique to be typed
-    #[error("{0}")]
-    Unique(String),
+    // /// Used when errors need to propogate but are too unique to be typed
+    // #[error("{0}")]
+    // Unique(String),
 
-    #[error("GraphQL error: {0}")]
-    GraphQL(String),
+    // #[error("GraphQL error: {0}")]
+    // GraphQL(String),
 
-    // TODO(haze): Remove, or make a better error. This is pretty much useless
-    #[error("GraphQL response mismatch. Got {found} but expected {expected}")]
-    GraphQLMisMatch {
-        expected: &'static str,
-        found: String,
-    },
+    // // TODO(haze): Remove, or make a better error. This is pretty much useless
+    // #[error("GraphQL response mismatch. Got {found} but expected {expected}")]
+    // GraphQLMisMatch {
+    //     expected: &'static str,
+    //     found: String,
+    // },
 
-    #[error("Difference detected in SystemTime! {0}")]
-    SystemTime(#[from] std::time::SystemTimeError),
+    // #[error("Difference detected in SystemTime! {0}")]
+    // SystemTime(#[from] std::time::SystemTimeError),
 
-    #[error("Failed to parse Integer")]
-    ParseInt(#[from] std::num::ParseIntError),
+    // #[error("Failed to parse Integer")]
+    // ParseInt(#[from] std::num::ParseIntError),
 
-    #[error("")]
-    MissingAuthentication,
+    // #[error("")]
+    // MissingAuthentication,
 
-    #[error("Failed to create experiment run: {0}")]
-    FailedToCreateExperimentRun(String),
-
-    #[error("Could not find shared behavior with ID {0}")]
-    MissingSharedBehavior(String),
+    // #[error("Failed to create experiment run: {0}")]
+    // FailedToCreateExperimentRun(String),
 
+    // #[error("Could not find shared behavior with ID {0}")]
+    // MissingSharedBehavior(String),
     #[error("I/O Error: {0}")]
     IO(#[from] std::io::Error),
 }

+ 13 - 0
packages/core/src/events.rs

@@ -4,6 +4,19 @@
 //! 3rd party renderers are responsible for forming this virtual events from events
 //!
 //! The goal here is to provide a consistent event interface across all renderer types
+use generational_arena::Index;
+
+pub struct EventTrigger {
+    pub component_id: Index,
+    pub listener_id: u32,
+    pub event: VirtualEvent,
+}
+
+impl EventTrigger {
+    pub fn new() -> Self {
+        todo!()
+    }
+}
 
 pub enum VirtualEvent {
     ClipboardEvent,

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

@@ -9,7 +9,7 @@ pub use use_ref_def::use_ref;
 pub use use_state_def::use_state;
 
 mod use_state_def {
-    use crate::inner::*;
+    use crate::innerlude::*;
     use std::{cell::RefCell, ops::DerefMut, rc::Rc};
 
     struct UseState<T: 'static> {
@@ -80,7 +80,7 @@ mod use_state_def {
 }
 
 mod use_ref_def {
-    use crate::inner::*;
+    use crate::innerlude::*;
     use std::{cell::RefCell, ops::DerefMut};
 
     pub struct UseRef<T: 'static> {

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

@@ -83,7 +83,7 @@ pub mod builder {
 }
 
 // types used internally that are important
-pub(crate) mod inner {
+pub(crate) mod innerlude {
     pub use crate::component::{Component, Properties};
     use crate::context::hooks::Hook;
     pub use crate::context::Context;
@@ -121,7 +121,7 @@ pub mod prelude {
 
     // pub use nodes::iterables::IterableNodes;
     /// This type alias is an internal way of abstracting over the static functions that represent components.
-    pub use crate::inner::FC;
+    pub use crate::innerlude::FC;
 
     // TODO @Jon, fix this
     // hack the VNode type until VirtualNode is fixed in the macro crate

+ 3 - 5
packages/core/src/nodebuilder.rs

@@ -1071,11 +1071,9 @@ pub fn attr<'a>(name: &'a str, value: &'a str) -> Attribute<'a> {
 /// ```
 pub fn on<'a, 'b, F: 'static>(
     bump: &'a Bump,
-    event: &'a str,
-    callback: impl Fn(()) + 'static,
-) -> Listener<'a>
-// F: Fn(()) + 'b,
-{
+    event: &'static str,
+    callback: impl Fn(()) + 'a,
+) -> Listener<'a> {
     Listener {
         event,
         callback: bump.alloc(callback),

+ 6 - 6
packages/core/src/nodes.rs

@@ -174,11 +174,11 @@ mod velement {
     }
 
     /// An event listener.
-    pub struct Listener<'a> {
+    pub struct Listener<'bump> {
         /// The type of event to listen for.
-        pub(crate) event: &'a str,
+        pub(crate) event: &'static str,
         /// The callback to invoke when the event happens.
-        pub(crate) callback: &'a (dyn Fn(())),
+        pub(crate) callback: &'bump (dyn Fn(())),
     }
 
     /// The key for keyed children.
@@ -226,8 +226,8 @@ mod velement {
 
 mod vtext {
     #[derive(PartialEq)]
-    pub struct VText<'a> {
-        pub text: &'a str,
+    pub struct VText<'bump> {
+        pub text: &'bump str,
     }
 
     impl<'a> VText<'a> {
@@ -245,7 +245,7 @@ mod vtext {
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 mod vcomponent {
-    use crate::inner::{Properties, FC};
+    use crate::innerlude::{Properties, FC};
     use std::{any::TypeId, fmt, future::Future, marker::PhantomData};
 
     use super::VNode;

+ 17 - 13
packages/core/src/scope.rs

@@ -1,4 +1,4 @@
-use crate::inner::*;
+use crate::innerlude::*;
 use crate::nodes::VNode;
 use crate::{context::hooks::Hook, diff::diff};
 use bumpalo::Bump;
@@ -19,26 +19,26 @@ pub struct Scope {
     // These hooks are actually references into the hook arena
     // These two could be combined with "OwningRef" to remove unsafe usage
     // could also use ourborous
-    hooks: RefCell<Vec<*mut Hook>>,
-    hook_arena: typed_arena::Arena<Hook>,
+    pub hooks: RefCell<Vec<*mut Hook>>,
+    pub hook_arena: typed_arena::Arena<Hook>,
 
     // Map to the parent
-    parent: Option<Index>,
+    pub parent: Option<Index>,
 
     // todo, do better with the active frame stuff
-    frames: [Bump; 2],
+    pub frames: [Bump; 2],
 
     // somehow build this vnode with a lifetime tied to self
-    cur_node: *mut VNode<'static>,
+    pub cur_node: *mut VNode<'static>,
 
-    active_frame: u8,
+    pub active_frame: u8,
 
     // IE Which listeners need to be woken up?
-    listeners: Vec<Box<dyn Fn()>>,
+    pub listeners: Vec<Box<dyn Fn()>>,
 
     //
-    props_type: TypeId,
-    caller: *const i32,
+    pub props_type: TypeId,
+    pub caller: *const i32,
 }
 
 impl Scope {
@@ -76,7 +76,7 @@ impl Scope {
     /// Create a new context and run the component with references from the Virtual Dom
     /// This function downcasts the function pointer based on the stored props_type
     ///
-    /// Props is ?Sized because we borrow the props
+    /// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
     pub(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) {
         let bump = &self.frames[0];
 
@@ -96,7 +96,7 @@ impl Scope {
         We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
         we transmute the function back using the props as reference.
 
-        we could do a better check to make sure that the TypeID is correct
+        we could do a better check to make sure that the TypeID is correct before casting
         --
         This is safe because we check that the generic type matches before casting.
         */
@@ -104,7 +104,11 @@ impl Scope {
         let new_nodes = caller(ctx, props);
         let old_nodes: &mut VNode<'static> = unsafe { &mut *self.cur_node };
 
-        // perform the diff, dumping into the change list
+        // TODO: Iterate through the new nodes
+        // move any listeners into ourself
+
+        // perform the diff, dumping into the mutable change list
+        // this doesnt perform any "diff compression" where an event and a re-render
         crate::diff::diff(old_nodes, &new_nodes);
     }
 }

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

@@ -13,6 +13,9 @@ mod validation {
     // look them up by their unique id.
     // When the DomUpdater sees that the element no longer exists it will drop all of it's
     // Rc'd Closures for those events.
+    // It doesn't quite make sense to keep this here, perhaps just in the html crate?
+    // Dioxus itself shouldn't be concerned with the attribute names
+    // a ftk!
     static SELF_CLOSING_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
         [
             "area", "base", "br", "col", "hr", "img", "input", "link", "meta", "param", "command",

+ 63 - 31
packages/core/src/virtual_dom.rs

@@ -1,29 +1,5 @@
-/*
-The Dioxus Virtual Dom integrates an event system and virtual nodes to create reactive user interfaces.
-
-The Dioxus VDom uses the same underlying mechanics as Dodrio (double buffering, bump dom, etc).
-Instead of making the allocator very obvious, we choose to parametrize over the DomTree trait. For our purposes,
-the DomTree trait is simply an abstraction over a lazy dom builder, much like the iterator trait.
-
-This means we can accept DomTree anywhere as well as return it. All components therefore look like this:
-```ignore
-function Component(ctx: Context<()>) -> VNode {
-    ctx.view(html! {<div> "hello world" </div>})
-}
-```
-It's not quite as sexy as statics, but there's only so much you can do. The goal is to get statics working with the FC macro,
-so types don't get in the way of you and your component writing. Fortunately, this is all generic enough to be split out
-into its own lib (IE, lazy loading wasm chunks by function (exciting stuff!))
-
-```ignore
-#[fc] // gets translated into a function.
-static Component: FC = |ctx| {
-    ctx.view(html! {<div> "hello world" </div>})
-}
-```
-*/
-use crate::inner::*;
 use crate::nodes::VNode;
+use crate::{events::EventTrigger, innerlude::*};
 use any::Any;
 use bumpalo::Bump;
 use generational_arena::{Arena, Index};
@@ -46,13 +22,12 @@ pub struct VirtualDom<P: Properties> {
     base_scope: Index,
 
     /// Components generate lifecycle events
-    event_queue: Vec<LifecycleEvent>,
+    event_queue: Vec<LifecycleEvent>, // wrap in a lock or something so
 
     // mark the root props with P, even though they're held by the root component
     _p: PhantomData<P>,
 }
 
-/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
 impl VirtualDom<()> {
     /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     ///
@@ -63,7 +38,6 @@ impl VirtualDom<()> {
     }
 }
 
-/// Implement the VirtualDom for any Properties
 impl<P: Properties + 'static> VirtualDom<P> {
     /// 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.
@@ -97,15 +71,69 @@ impl<P: Properties + 'static> VirtualDom<P> {
         }
     }
 
+    /// 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 {
+            event_type: LifecycleType::PropsChanged {
+                props: Box::new(new_props),
+            },
+            index: self.base_scope,
+        });
+    }
+
     /// Pop an event off the event queue and process it
+    /// Update the root props, and progress
+    /// 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)
     }
 
-    /// Update the root props, causing a full event cycle
-    pub fn update_props(&mut self, new_props: P) {}
+    /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
+    ///  
+    /// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
+    /// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single
+    /// change list.
+    ///
+    /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
+    /// listeners.
+    ///
+    /// ```ignore
+    ///
+    ///
+    ///
+    ///
+    /// ```
+    pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<()> {
+        let EventTrigger {
+            component_id,
+            listener_id,
+            event,
+        } = evt;
+
+        let component = self
+            .components
+            .get(component_id)
+            // todo: update this with a dedicated error type so implementors know what went wrong
+            .expect("Component should exist if an event was triggered");
+
+        let listener = component
+            .listeners
+            .get(listener_id as usize)
+            .expect("Listener should exist if it was triggered")
+            .as_ref();
+
+        // Run the callback
+        // Theoretically, this should
+        listener();
+
+        Ok(())
+    }
+
+    pub async fn progress_completely(&mut self) -> Result<()> {
+        Ok(())
+    }
 }
 
 /// Using mutable access to the Virtual Dom, progress a given lifecycle event
@@ -159,6 +187,9 @@ fn process_event<P: Properties>(
         // Event from renderer was fired with a given listener ID
         //
         LifecycleType::Callback { listener_id } => {}
+
+        // Run any post-render callbacks on a component
+        LifecycleType::Rendered => {}
     }
 
     Ok(())
@@ -182,6 +213,7 @@ pub enum LifecycleType {
     PropsChanged {
         props: Box<dyn Properties>,
     },
+    Rendered,
     Mounted,
     Removed,
     Messaged,

+ 4 - 2
packages/web/Cargo.toml

@@ -9,9 +9,11 @@ edition = "2018"
 [dependencies]
 dioxus-core = { path = "../core" }
 js-sys = "0.3"
-wasm-bindgen = "0.2.69"
-html-validation = { path = "../html-validation", version = "0.1.1" }
+wasm-bindgen = "0.2.70"
 lazy_static = "1.4.0"
+wasm-bindgen-futures = "0.4.20"
+futures = "0.3.12"
+# html-validation = { path = "../html-validation", version = "0.1.1" }
 
 [dependencies.web-sys]
 version = "0.3"

+ 21 - 0
packages/web/examples/hello.rs

@@ -0,0 +1,21 @@
+use dioxus_core::prelude::*;
+use dioxus_web::WebsysRenderer;
+
+fn main() {
+    // todo: set this up so the websys render can spawn itself rather than having to wrap it
+    // almost like bundling an executor with the wasm version
+    wasm_bindgen_futures::spawn_local(async {
+        WebsysRenderer::new(Example)
+            .run()
+            .await
+            .expect("Dioxus Failed! This should *not* happen!")
+    });
+}
+
+static Example: FC<()> = |ctx, props| {
+    ctx.view(html! {
+        <div>
+            "Hello world!"
+        </div>
+    })
+};

+ 94 - 192
packages/web/src/lib.rs

@@ -1,211 +1,113 @@
-use std::{collections::HashMap, fmt, rc::Rc};
-use web_sys::{self, Element, EventTarget, Node, Text};
-
-use dioxus_core::prelude::{VElement, VNode, VText, VirtualNode};
-use std::ops::Deref;
-use std::sync::Mutex;
-use wasm_bindgen::JsCast;
-use wasm_bindgen::JsValue;
-
-pub struct DomRenderer {}
-
-// Used to uniquely identify elements that contain closures so that the DomUpdater can
-// look them up by their unique id.
-// When the DomUpdater sees that the element no longer exists it will drop all of it's
-// Rc'd Closures for those events.
-use lazy_static::lazy_static;
-lazy_static! {
-    static ref ELEM_UNIQUE_ID: Mutex<u32> = Mutex::new(0);
+//! Dioxus WebSys
+//!
+//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser.
+//!
+//! While `VNode` supports "to_string" directly, it renders child components as the RSX! macro tokens. For custom components,
+//! an external renderer is needed to progress the component lifecycles. The `WebsysRenderer` shows how to use the Virtual DOM
+//! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
+//! `render` method.
+//!
+//! ```ignore
+//! fn main() {
+//!     let renderer = WebsysRenderer::<()>::new(|_| html! {<div> "Hello world" </div>});
+//!     let output = renderer.render();
+//!     assert_eq!(output, "<div>Hello World</div>");
+//! }
+//! ```
+//!
+//! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
+//!
+
+use dioxus_core::{
+    events::EventTrigger,
+    prelude::{Properties, VNode, VirtualDom, FC},
+};
+use futures::{channel::mpsc, future, SinkExt, StreamExt};
+use mpsc::UnboundedSender;
+
+/// 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
+
+///
+pub struct WebsysRenderer<T: Properties> {
+    internal_dom: VirtualDom<T>,
 }
 
-fn create_unique_identifier() -> u32 {
-    let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap();
-    *elem_unique_id += 1;
-    *elem_unique_id
-}
-
-/// A node along with all of the closures that were created for that
-/// node's events and all of it's child node's events.
-pub struct CreatedNode<T> {
-    /// A `Node` or `Element` that was created from a `VirtualNode`
-    pub node: T,
-    /// A map of a node's unique identifier along with all of the Closures for that node.
+/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
+impl WebsysRenderer<()> {
+    /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     ///
-    /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
-    /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
-    /// memory.
-    pub closures: HashMap<u32, Vec<DynClosure>>,
-}
-
-impl<T> CreatedNode<T> {
-    pub fn without_closures<N: Into<T>>(node: N) -> Self {
-        CreatedNode {
-            node: node.into(),
-            closures: HashMap::with_capacity(0),
-        }
+    /// This means that the root component must either consumes its own context, or statics are used to generate the page.
+    /// The root component can access things like routing in its context.
+    pub fn new(root: FC<()>) -> Self {
+        Self::new_with_props(root, ())
     }
 }
 
-impl<T> Deref for CreatedNode<T> {
-    type Target = T;
-    fn deref(&self) -> &Self::Target {
-        &self.node
+impl<T: Properties + 'static> WebsysRenderer<T> {
+    /// Create a new text-renderer instance from a functional component root.
+    /// Automatically progresses the creation of the VNode tree to completion.
+    ///
+    /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
+    pub fn new_with_props(root: FC<T>, root_props: T) -> Self {
+        Self::from_vdom(VirtualDom::new_with_props(root, root_props))
     }
-}
 
-impl From<CreatedNode<Element>> for CreatedNode<Node> {
-    fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
-        CreatedNode {
-            node: other.node.into(),
-            closures: other.closures,
-        }
+    /// Create a new text renderer from an existing Virtual DOM.
+    /// This will progress the existing VDom's events to completion.
+    pub fn from_vdom(dom: VirtualDom<T>) -> Self {
+        Self { internal_dom: dom }
     }
-}
-
-//----------------------------------
-// Create nodes for the VNode types
-// ---------------------------------
-
-/// Return a `Text` element from a `VirtualNode`, typically right before adding it
-/// into the DOM.
-pub fn create_text_node(text_node: &VText) -> Text {
-    let document = web_sys::window().unwrap().document().unwrap();
-    document.create_text_node(&text_node.text)
-}
-
-/// Build a DOM element by recursively creating DOM nodes for this element and it's
-/// children, it's children's children, etc.
-pub fn create_element_node(velement: &VElement) -> CreatedNode<Element> {
-    let document = web_sys::window().unwrap().document().unwrap();
-
-    let element = if html_validation::is_svg_namespace(&velement.tag) {
-        document
-            .create_element_ns(Some("http://www.w3.org/2000/svg"), &velement.tag)
-            .unwrap()
-    } else {
-        document.create_element(&velement.tag).unwrap()
-    };
 
-    let mut closures = HashMap::new();
-
-    velement.attrs.iter().for_each(|(name, value)| {
-        if name == "unsafe_inner_html" {
-            element.set_inner_html(value);
-
-            return;
+    /// Run the renderer, progressing any events that crop up
+    /// Yield on event handlers
+    /// If the dom errors out, self is consumed and the dom is torn down
+    pub async fn run(self) -> dioxus_core::error::Result<()> {
+        let WebsysRenderer { mut internal_dom } = self;
+
+        // Progress the mount of the root component
+        internal_dom
+            .progress()
+            .expect("Progressing the root failed :(");
+
+        // set up the channels to connect listeners to the event loop
+        let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
+
+        // Iterate through the nodes, attaching the closure and sender to the listener
+        {
+            let mut remote_sender = sender.clone();
+            let f = move || {
+                let event = EventTrigger::new();
+                wasm_bindgen_futures::spawn_local(async move {
+                    remote_sender
+                        .send(event)
+                        .await
+                        .expect("Updating receiver failed");
+                })
+            };
         }
 
-        element
-            .set_attribute(name, value)
-            .expect("Set element attribute in create element");
-    });
-
-    todo!("Support events properly in web ");
-    // if velement.events.0.len() > 0 {
-    //     let unique_id = create_unique_identifier();
-
-    //     element
-    //         .set_attribute("data-vdom-id".into(), &unique_id.to_string())
-    //         .expect("Could not set attribute on element");
-
-    //     closures.insert(unique_id, vec![]);
-
-    //     velement.events.0.iter().for_each(|(onevent, callback)| {
-    //         // onclick -> click
-    //         let event = &onevent[2..];
-
-    //         let current_elem: &EventTarget = element.dyn_ref().unwrap();
-
-    //         current_elem
-    //             .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
-    //             .unwrap();
-
-    //         closures
-    //             .get_mut(&unique_id)
-    //             .unwrap()
-    //             .push(Rc::clone(callback));
-    //     });
-    // }
-
-    let mut previous_node_was_text = false;
-
-    velement.children.iter().for_each(|child| {
-        match child {
-            VNode::Text(text_node) => {
-                let current_node = element.as_ref() as &web_sys::Node;
-
-                // We ensure that the text siblings are patched by preventing the browser from merging
-                // neighboring text nodes. Originally inspired by some of React's work from 2016.
-                //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
-                //  -> https://github.com/facebook/react/pull/5753
-                //
-                // `ptns` = Percy text node separator
-                if previous_node_was_text {
-                    let separator = document.create_comment("ptns");
-                    current_node
-                        .append_child(separator.as_ref() as &web_sys::Node)
-                        .unwrap();
-                }
-
-                current_node
-                    .append_child(&create_text_node(&text_node))
-                    // .append_child(&text_node.create_text_node())
-                    .unwrap();
-
-                previous_node_was_text = true;
-            }
-            VNode::Element(element_node) => {
-                previous_node_was_text = false;
-
-                let child = create_element_node(&element_node);
-                // let child = element_node.create_element_node();
-                let child_elem: Element = child.node;
-
-                closures.extend(child.closures);
-
-                element.append_child(&child_elem).unwrap();
-            }
-
-            VNode::Component(component) => {
-                //
-                todo!("Support components in the web properly");
+        // Event loop waits for the receiver to finish up
+        // TODO! Connect the sender to the virtual dom's suspense system
+        // Suspense is basically an external event that can force renders to specific nodes
+        while let Some(event) = receiver.next().await {
+            // event is triggered
+            // relevant listeners are ran
+            // internal state is modified, components are tagged for changes
+
+            match internal_dom.progress_with_event(event).await {
+                Err(_) => {}
+                Ok(_) => render_diffs(),
             }
+            // waiting for next event to arrive from the external triggers
         }
-    });
-
-    todo!("Support events properly in web ");
-    // if let Some(on_create_elem) = velement.events.0.get("on_create_elem") {
-    //     let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
-    //     on_create_elem
-    //         .call1(&wasm_bindgen::JsValue::NULL, &element)
-    //         .unwrap();
-    // }
 
-    CreatedNode {
-        node: element,
-        closures,
+        Ok(())
     }
 }
 
-/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
-/// any Closure regardless of the arguments.
-pub type DynClosure = Rc<dyn AsRef<JsValue>>;
-
-/// We need a custom implementation of fmt::Debug since JsValue doesn't
-/// implement debug.
-pub struct Events(pub HashMap<String, DynClosure>);
+/// 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
+fn attach_listeners<P: Properties>(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom<P>) {}
 
-impl PartialEq for Events {
-    // TODO: What should happen here..? And why?
-    fn eq(&self, _rhs: &Self) -> bool {
-        true
-    }
-}
-
-impl fmt::Debug for Events {
-    // Print out all of the event names for this VirtualNode
-    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
-        let events: String = self.0.keys().map(|key| " ".to_string() + key).collect();
-        write!(f, "{}", events)
-    }
-}
+fn render_diffs() {}

+ 211 - 0
packages/web/src/old/lib.rs

@@ -0,0 +1,211 @@
+use std::{collections::HashMap, fmt, rc::Rc};
+use web_sys::{self, Element, EventTarget, Node, Text};
+
+use dioxus_core::prelude::{VElement, VNode, VText, VirtualNode};
+use std::ops::Deref;
+use std::sync::Mutex;
+use wasm_bindgen::JsCast;
+use wasm_bindgen::JsValue;
+
+pub struct DomRenderer {}
+
+// Used to uniquely identify elements that contain closures so that the DomUpdater can
+// look them up by their unique id.
+// When the DomUpdater sees that the element no longer exists it will drop all of it's
+// Rc'd Closures for those events.
+use lazy_static::lazy_static;
+lazy_static! {
+    static ref ELEM_UNIQUE_ID: Mutex<u32> = Mutex::new(0);
+}
+
+fn create_unique_identifier() -> u32 {
+    let mut elem_unique_id = ELEM_UNIQUE_ID.lock().unwrap();
+    *elem_unique_id += 1;
+    *elem_unique_id
+}
+
+/// A node along with all of the closures that were created for that
+/// node's events and all of it's child node's events.
+pub struct CreatedNode<T> {
+    /// A `Node` or `Element` that was created from a `VirtualNode`
+    pub node: T,
+    /// A map of a node's unique identifier along with all of the Closures for that node.
+    ///
+    /// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
+    /// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
+    /// memory.
+    pub closures: HashMap<u32, Vec<DynClosure>>,
+}
+
+impl<T> CreatedNode<T> {
+    pub fn without_closures<N: Into<T>>(node: N) -> Self {
+        CreatedNode {
+            node: node.into(),
+            closures: HashMap::with_capacity(0),
+        }
+    }
+}
+
+impl<T> Deref for CreatedNode<T> {
+    type Target = T;
+    fn deref(&self) -> &Self::Target {
+        &self.node
+    }
+}
+
+impl From<CreatedNode<Element>> for CreatedNode<Node> {
+    fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
+        CreatedNode {
+            node: other.node.into(),
+            closures: other.closures,
+        }
+    }
+}
+
+//----------------------------------
+// Create nodes for the VNode types
+// ---------------------------------
+
+/// Return a `Text` element from a `VirtualNode`, typically right before adding it
+/// into the DOM.
+pub fn create_text_node(text_node: &VText) -> Text {
+    let document = web_sys::window().unwrap().document().unwrap();
+    document.create_text_node(&text_node.text)
+}
+
+/// Build a DOM element by recursively creating DOM nodes for this element and it's
+/// children, it's children's children, etc.
+pub fn create_element_node(velement: &VElement) -> CreatedNode<Element> {
+    let document = web_sys::window().unwrap().document().unwrap();
+
+    let element = if html_validation::is_svg_namespace(&velement.tag) {
+        document
+            .create_element_ns(Some("http://www.w3.org/2000/svg"), &velement.tag)
+            .unwrap()
+    } else {
+        document.create_element(&velement.tag).unwrap()
+    };
+
+    let mut closures = HashMap::new();
+
+    velement.attrs.iter().for_each(|(name, value)| {
+        if name == "unsafe_inner_html" {
+            element.set_inner_html(value);
+
+            return;
+        }
+
+        element
+            .set_attribute(name, value)
+            .expect("Set element attribute in create element");
+    });
+
+    todo!("Support events properly in web ");
+    // if velement.events.0.len() > 0 {
+    //     let unique_id = create_unique_identifier();
+
+    //     element
+    //         .set_attribute("data-vdom-id".into(), &unique_id.to_string())
+    //         .expect("Could not set attribute on element");
+
+    //     closures.insert(unique_id, vec![]);
+
+    //     velement.events.0.iter().for_each(|(onevent, callback)| {
+    //         // onclick -> click
+    //         let event = &onevent[2..];
+
+    //         let current_elem: &EventTarget = element.dyn_ref().unwrap();
+
+    //         current_elem
+    //             .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
+    //             .unwrap();
+
+    //         closures
+    //             .get_mut(&unique_id)
+    //             .unwrap()
+    //             .push(Rc::clone(callback));
+    //     });
+    // }
+
+    let mut previous_node_was_text = false;
+
+    velement.children.iter().for_each(|child| {
+        match child {
+            VNode::Text(text_node) => {
+                let current_node = element.as_ref() as &web_sys::Node;
+
+                // We ensure that the text siblings are patched by preventing the browser from merging
+                // neighboring text nodes. Originally inspired by some of React's work from 2016.
+                //  -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
+                //  -> https://github.com/facebook/react/pull/5753
+                //
+                // `ptns` = Percy text node separator
+                if previous_node_was_text {
+                    let separator = document.create_comment("ptns");
+                    current_node
+                        .append_child(separator.as_ref() as &web_sys::Node)
+                        .unwrap();
+                }
+
+                current_node
+                    .append_child(&create_text_node(&text_node))
+                    // .append_child(&text_node.create_text_node())
+                    .unwrap();
+
+                previous_node_was_text = true;
+            }
+            VNode::Element(element_node) => {
+                previous_node_was_text = false;
+
+                let child = create_element_node(&element_node);
+                // let child = element_node.create_element_node();
+                let child_elem: Element = child.node;
+
+                closures.extend(child.closures);
+
+                element.append_child(&child_elem).unwrap();
+            }
+
+            VNode::Component(component) => {
+                //
+                todo!("Support components in the web properly");
+            }
+        }
+    });
+
+    todo!("Support events properly in web ");
+    // if let Some(on_create_elem) = velement.events.0.get("on_create_elem") {
+    //     let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
+    //     on_create_elem
+    //         .call1(&wasm_bindgen::JsValue::NULL, &element)
+    //         .unwrap();
+    // }
+
+    CreatedNode {
+        node: element,
+        closures,
+    }
+}
+
+/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
+/// any Closure regardless of the arguments.
+pub type DynClosure = Rc<dyn AsRef<JsValue>>;
+
+/// We need a custom implementation of fmt::Debug since JsValue doesn't
+/// implement debug.
+pub struct Events(pub HashMap<String, DynClosure>);
+
+impl PartialEq for Events {
+    // TODO: What should happen here..? And why?
+    fn eq(&self, _rhs: &Self) -> bool {
+        true
+    }
+}
+
+impl fmt::Debug for Events {
+    // Print out all of the event names for this VirtualNode
+    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+        let events: String = self.0.keys().map(|key| " ".to_string() + key).collect();
+        write!(f, "{}", events)
+    }
+}

+ 0 - 0
packages/web/src/virtual_node_test_utils.rs → packages/web/src/old/virtual_node_test_utils.rs


+ 1 - 0
packages/web/src/util.rs

@@ -0,0 +1 @@
+//! Utilities specific to websys