瀏覽代碼

Feat: event loop

Jonathan Kelley 4 年之前
父節點
當前提交
ea2aa4b
共有 39 個文件被更改,包括 502 次插入294 次删除
  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
     # Built-in
     "packages/dioxus",
     "packages/dioxus",
     "packages/core",
     "packages/core",
-    "packages/recoil",
-    "packages/redux",
     "packages/core-macro",
     "packages/core-macro",
     "packages/router",
     "packages/router",
     "packages/ssr",
     "packages/ssr",
     "packages/webview",
     "packages/webview",
+    "packages/web",
     "packages/livehost",
     "packages/livehost",
     "packages/vscode-ext",
     "packages/vscode-ext",
+    # "packages/recoil",
+    # "packages/redux",
     # "packages/macro",
     # "packages/macro",
     # TODO @Jon, share the validation code
     # TODO @Jon, share the validation code
     # "packages/web",
     # "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.
 //! 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
 /// The `Component` trait refers to any struct or funciton that can be used as a component
 /// We automatically implement Component for FC<T>
 /// We automatically implement Component for FC<T>

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

@@ -1,5 +1,5 @@
 use crate::prelude::*;
 use crate::prelude::*;
-use crate::{inner::Scope, nodes::VNode};
+use crate::{innerlude::Scope, nodes::VNode};
 use bumpalo::Bump;
 use bumpalo::Bump;
 use hooks::Hook;
 use hooks::Hook;
 use std::{
 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
 // Diff the `old` node with the `new` node. Emits instructions to modify a
 // physical DOM node that reflects `old` into something that reflects `new`.
 // physical DOM node that reflects `old` into something that reflects `new`.
@@ -28,6 +17,38 @@ pub(crate) fn diff(
     new: &VNode,
     new: &VNode,
     // cached_roots: &mut FxHashSet<CacheId>,
     // 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!()
     todo!()
     // match (&new.kind, &old.kind) {
     // match (&new.kind, &old.kind) {
     //     (
     //     (

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

@@ -7,38 +7,37 @@ pub enum Error {
     #[error("No event to progress")]
     #[error("No event to progress")]
     NoEvent,
     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}")]
     #[error("I/O Error: {0}")]
     IO(#[from] std::io::Error),
     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
 //! 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
 //! 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 {
 pub enum VirtualEvent {
     ClipboardEvent,
     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;
 pub use use_state_def::use_state;
 
 
 mod use_state_def {
 mod use_state_def {
-    use crate::inner::*;
+    use crate::innerlude::*;
     use std::{cell::RefCell, ops::DerefMut, rc::Rc};
     use std::{cell::RefCell, ops::DerefMut, rc::Rc};
 
 
     struct UseState<T: 'static> {
     struct UseState<T: 'static> {
@@ -80,7 +80,7 @@ mod use_state_def {
 }
 }
 
 
 mod use_ref_def {
 mod use_ref_def {
-    use crate::inner::*;
+    use crate::innerlude::*;
     use std::{cell::RefCell, ops::DerefMut};
     use std::{cell::RefCell, ops::DerefMut};
 
 
     pub struct UseRef<T: 'static> {
     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
 // types used internally that are important
-pub(crate) mod inner {
+pub(crate) mod innerlude {
     pub use crate::component::{Component, Properties};
     pub use crate::component::{Component, Properties};
     use crate::context::hooks::Hook;
     use crate::context::hooks::Hook;
     pub use crate::context::Context;
     pub use crate::context::Context;
@@ -121,7 +121,7 @@ pub mod prelude {
 
 
     // pub use nodes::iterables::IterableNodes;
     // pub use nodes::iterables::IterableNodes;
     /// This type alias is an internal way of abstracting over the static functions that represent components.
     /// 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
     // TODO @Jon, fix this
     // hack the VNode type until VirtualNode is fixed in the macro crate
     // 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>(
 pub fn on<'a, 'b, F: 'static>(
     bump: &'a Bump,
     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 {
     Listener {
         event,
         event,
         callback: bump.alloc(callback),
         callback: bump.alloc(callback),

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

@@ -174,11 +174,11 @@ mod velement {
     }
     }
 
 
     /// An event listener.
     /// An event listener.
-    pub struct Listener<'a> {
+    pub struct Listener<'bump> {
         /// The type of event to listen for.
         /// 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.
         /// 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.
     /// The key for keyed children.
@@ -226,8 +226,8 @@ mod velement {
 
 
 mod vtext {
 mod vtext {
     #[derive(PartialEq)]
     #[derive(PartialEq)]
-    pub struct VText<'a> {
-        pub text: &'a str,
+    pub struct VText<'bump> {
+        pub text: &'bump str,
     }
     }
 
 
     impl<'a> VText<'a> {
     impl<'a> VText<'a> {
@@ -245,7 +245,7 @@ mod vtext {
 /// Virtual Components for custom user-defined components
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 /// Only supports the functional syntax
 mod vcomponent {
 mod vcomponent {
-    use crate::inner::{Properties, FC};
+    use crate::innerlude::{Properties, FC};
     use std::{any::TypeId, fmt, future::Future, marker::PhantomData};
     use std::{any::TypeId, fmt, future::Future, marker::PhantomData};
 
 
     use super::VNode;
     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::nodes::VNode;
 use crate::{context::hooks::Hook, diff::diff};
 use crate::{context::hooks::Hook, diff::diff};
 use bumpalo::Bump;
 use bumpalo::Bump;
@@ -19,26 +19,26 @@ pub struct Scope {
     // These hooks are actually references into the hook arena
     // These hooks are actually references into the hook arena
     // These two could be combined with "OwningRef" to remove unsafe usage
     // These two could be combined with "OwningRef" to remove unsafe usage
     // could also use ourborous
     // 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
     // Map to the parent
-    parent: Option<Index>,
+    pub parent: Option<Index>,
 
 
     // todo, do better with the active frame stuff
     // 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
     // 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?
     // 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 {
 impl Scope {
@@ -76,7 +76,7 @@ impl Scope {
     /// Create a new context and run the component with references from the Virtual Dom
     /// 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
     /// 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) {
     pub(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) {
         let bump = &self.frames[0];
         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 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 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.
         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 new_nodes = caller(ctx, props);
         let old_nodes: &mut VNode<'static> = unsafe { &mut *self.cur_node };
         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);
         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.
     // look them up by their unique id.
     // When the DomUpdater sees that the element no longer exists it will drop all of it's
     // When the DomUpdater sees that the element no longer exists it will drop all of it's
     // Rc'd Closures for those events.
     // 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(|| {
     static SELF_CLOSING_TAGS: Lazy<HashSet<&'static str>> = Lazy::new(|| {
         [
         [
             "area", "base", "br", "col", "hr", "img", "input", "link", "meta", "param", "command",
             "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::nodes::VNode;
+use crate::{events::EventTrigger, innerlude::*};
 use any::Any;
 use any::Any;
 use bumpalo::Bump;
 use bumpalo::Bump;
 use generational_arena::{Arena, Index};
 use generational_arena::{Arena, Index};
@@ -46,13 +22,12 @@ pub struct VirtualDom<P: Properties> {
     base_scope: Index,
     base_scope: Index,
 
 
     /// Components generate lifecycle events
     /// 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
     // mark the root props with P, even though they're held by the root component
     _p: PhantomData<P>,
     _p: PhantomData<P>,
 }
 }
 
 
-/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
 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.
     ///
     ///
@@ -63,7 +38,6 @@ impl VirtualDom<()> {
     }
     }
 }
 }
 
 
-/// Implement the VirtualDom for any Properties
 impl<P: Properties + 'static> VirtualDom<P> {
 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.
@@ -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
     /// 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<()> {
     pub fn progress(&mut self) -> Result<()> {
         let event = self.event_queue.pop().ok_or(Error::NoEvent)?;
         let event = self.event_queue.pop().ok_or(Error::NoEvent)?;
-
         process_event(self, event)
         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
 /// 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
         // Event from renderer was fired with a given listener ID
         //
         //
         LifecycleType::Callback { listener_id } => {}
         LifecycleType::Callback { listener_id } => {}
+
+        // Run any post-render callbacks on a component
+        LifecycleType::Rendered => {}
     }
     }
 
 
     Ok(())
     Ok(())
@@ -182,6 +213,7 @@ pub enum LifecycleType {
     PropsChanged {
     PropsChanged {
         props: Box<dyn Properties>,
         props: Box<dyn Properties>,
     },
     },
+    Rendered,
     Mounted,
     Mounted,
     Removed,
     Removed,
     Messaged,
     Messaged,

+ 4 - 2
packages/web/Cargo.toml

@@ -9,9 +9,11 @@ edition = "2018"
 [dependencies]
 [dependencies]
 dioxus-core = { path = "../core" }
 dioxus-core = { path = "../core" }
 js-sys = "0.3"
 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"
 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]
 [dependencies.web-sys]
 version = "0.3"
 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