Explorar o código

Merge pull request #36 from DioxusLabs/jk/stack_dst

feat: event bubbling
Jonathan Kelley %!s(int64=3) %!d(string=hai) anos
pai
achega
34c9978ad1

+ 143 - 0
README.md

@@ -199,6 +199,149 @@ You shouldn't use Dioxus if:
 - You want to support browsers where Wasm or asm.js are not supported.
 
 
+## Show me some examples!
+
+In our collection of examples, guides, and tutorials, we have:
+- The book (an introductory course)
+- The guide (an in-depth analysis of everything in Dioxus)
+- The reference (a collection of examples with heavy documentation)
+- The general examples
+- The platform-specific examples (web, ssr, desktop, mobile, server)
+  
+Here's what a few common tasks look like in Dioxus:
+
+Nested components with children and internal state:
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  cx.render(rsx!( Toggle { "Toggle me" } ))
+}
+
+#[derive(PartialEq, Props)]
+struct ToggleProps { children: Element }
+
+fn Toggle(cx: Context, props: &ToggleProps) -> Element {
+  let mut toggled = use_state(cx, || false);
+  cx.render(rsx!{
+    div {
+      {&props.children}
+      button { onclick: move |_| toggled.set(true),
+        {toggled.and_then(|| "On").or_else(|| "Off")}
+      }
+    }
+  })
+}
+```
+
+Controlled inputs:
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  let value = use_state(cx, String::new);
+  cx.render(rsx!( 
+    input {
+      "type": "text",
+      value: "{value}",
+      oninput: move |evt| value.set(evt.value.clone())
+    }
+  ))
+}
+```
+
+Lists and Conditional rendering:
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  let list = (0..10).map(|i| {
+    rsx!(li { key: "{i}", "Value: {i}" })
+  });
+  
+  let title = match list.len() {
+    0 => rsx!("Not enough"),
+    _ => rsx!("Plenty!"),
+  };
+
+  if should_show {
+    cx.render(rsx!( 
+      {title}
+      ul { {list} } 
+    ))
+  } else {
+    None
+  }
+}
+```
+
+Tiny components:
+```rust
+static App: FC<()> = |cx, _| rsx!(cx, div {"hello world!"});
+```
+
+Borrowed prop contents:
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  let name = use_state(cx, || String::from("example"));
+  rsx!(cx, Child { title: name.as_str() })
+}
+
+#[derive(Props)]
+struct ChildProps<'a> { title: &'a str }
+
+fn Child(cx: Context, props: &ChildProps) -> Element {
+  rsx!(cx, "Hello {props.title}")
+}
+```
+
+Global State
+```rust
+struct GlobalState { name: String }
+
+fn App(cx: Context, props: &()) -> Element {
+  use_provide_shared_state(cx, || GlobalState { name: String::from("Toby") })
+  rsx!(cx, Leaf {})
+}
+
+fn Leaf(cx: Context, props: &()) -> Element {
+  let state = use_consume_shared_state::<GlobalState>(cx)?;
+  rsx!(cx, "Hello {state.name}")
+}
+```
+
+Router (inspired by Yew-Router)
+```rust
+#[derive(PartialEq, Clone,  Hash, Eq, Routable)]
+enum Route {
+  #[at("/")]
+  Home,
+  #[at("/post/{id}")]
+  Post(id)
+}
+
+fn App(cx: Context, props: &()) -> Element {
+  let route = use_router(cx, Route::parse);
+  cx.render(rsx!(div {
+    {match route {
+      Route::Home => rsx!( Home {} ),
+      Route::Post(id) => rsx!( Post { id: id })
+    }}
+  }))  
+}
+```
+
+Suspense 
+```rust
+fn App(cx: Context, props: &()) -> Element {
+  let doggo = use_suspense(cx,
+    || async { reqwest::get("https://dog.ceo/api/breeds/image/random").await.unwrap().json::<Response>().await.unwrap() },
+    |response| cx.render(rsx!( img { src: "{response.message}" }))
+  );
+  
+  cx.render(rsx!{
+    div {
+      "One doggo coming right up:"
+      {doggo}
+    }
+  })
+}
+```
+
 ## License
 
 This project is licensed under the [MIT license].

+ 14 - 3
examples/tasks.rs

@@ -2,6 +2,8 @@
 //!
 //! The example from the README.md.
 
+use std::time::Duration;
+
 use dioxus::prelude::*;
 fn main() {
     dioxus::desktop::launch(App, |c| c);
@@ -10,14 +12,23 @@ fn main() {
 static App: FC<()> = |cx, props| {
     let mut count = use_state(cx, || 0);
 
-    cx.push_task(async {
-        panic!("polled future");
-        //
+    cx.push_task(async move {
+        tokio::time::sleep(Duration::from_millis(100)).await;
+        println!("setting count");
+        count += 1;
+        // count.set(10);
+        // *count += 1;
+        // let c = count.get() + 1;
+        // count.set(c);
     });
 
     cx.render(rsx! {
         div {
             h1 { "High-Five counter: {count}" }
+            // button {
+            //     onclick: move |_| count +=1 ,
+            //     "Click me!"
+            // }
         }
     })
 };

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

@@ -202,6 +202,8 @@ pub fn rsx(s: TokenStream) -> TokenStream {
 ///     Home,
 ///     #[at("/secure")]
 ///     Secure,
+///     #[at("/profile/{id}")]
+///     Profile(u32),
 ///     #[at("/404")]
 ///     NotFound,
 /// }

+ 25 - 24
packages/core-macro/src/router.rs

@@ -200,11 +200,11 @@ pub fn routable_derive_impl(input: Routable) -> TokenStream {
     );
 
     quote! {
-        ::std::thread_local! {
-            #[doc(hidden)]
-            #[allow(non_upper_case_globals)]
-            static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None);
-        }
+        // ::std::thread_local! {
+        //     #[doc(hidden)]
+        //     #[allow(non_upper_case_globals)]
+        //     static #cache_thread_local_ident: ::std::cell::RefCell<::std::option::Option<#ident>> = ::std::cell::RefCell::new(::std::option::Option::None);
+        // }
 
         #[automatically_derived]
         impl ::dioxus::router::Routable for #ident {
@@ -219,29 +219,30 @@ pub fn routable_derive_impl(input: Routable) -> TokenStream {
                 #not_found_route
             }
 
-            fn current_route() -> ::std::option::Option<Self> {
-                #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow()))
-            }
+            // fn current_route() -> ::std::option::Option<Self> {
+            //     #cache_thread_local_ident.with(|val| ::std::clone::Clone::clone(&*val.borrow()))
+            // }
 
             fn recognize(pathname: &str) -> ::std::option::Option<Self> {
-                ::std::thread_local! {
-                    static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
-                }
-                let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
-                {
-                    let route = ::std::clone::Clone::clone(&route);
-                    #cache_thread_local_ident.with(move |val| {
-                        *val.borrow_mut() = route;
-                    });
-                }
-                route
+                todo!()
+                // ::std::thread_local! {
+                //     static ROUTER: ::dioxus::router::__macro::Router = ::dioxus::router::__macro::build_router::<#ident>();
+                // }
+                // let route = ROUTER.with(|router| ::dioxus::router::__macro::recognize_with_router(router, pathname));
+                // {
+                //     let route = ::std::clone::Clone::clone(&route);
+                //     #cache_thread_local_ident.with(move |val| {
+                //         *val.borrow_mut() = route;
+                //     });
+                // }
+                // route
             }
 
-            fn cleanup() {
-                #cache_thread_local_ident.with(move |val| {
-                    *val.borrow_mut() = ::std::option::Option::None;
-                });
-            }
+            // fn cleanup() {
+            //     #cache_thread_local_ident.with(move |val| {
+            //         *val.borrow_mut() = ::std::option::Option::None;
+            //     });
+            // }
         }
     }
 }

+ 1 - 0
packages/core/Cargo.toml

@@ -39,6 +39,7 @@ indexmap = "1.7.0"
 
 # Serialize the Edits for use in Webview/Liveview instances
 serde = { version = "1", features = ["derive"], optional = true }
+backtrace = "0.3.63"
 
 [dev-dependencies]
 anyhow = "1.0.42"

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

@@ -36,11 +36,11 @@ impl<const A: bool> FragmentBuilder<A> {
 ///
 /// ## Example
 ///
-/// ```rust
+/// ```rust, ignore
 /// fn App(cx: Context, props: &()) -> Element {
 ///     cx.render(rsx!{
 ///         CustomCard {
-///             h1 {}
+///             h1 {}2
 ///             p {}
 ///         }
 ///     })
@@ -82,7 +82,7 @@ impl Properties for FragmentProps {
 ///
 /// ## Example
 ///
-/// ```rust
+/// ```rust, ignore
 /// rsx!{
 ///     Fragment { key: "abc" }
 /// }
@@ -118,7 +118,7 @@ pub fn Fragment(cx: Context, props: &FragmentProps) -> Element {
 /// ## Example
 ///
 /// For props that are 'static:
-/// ```rust ignore
+/// ```rust, ignore ignore
 /// #[derive(Props, PartialEq)]
 /// struct MyProps {
 ///     data: String
@@ -127,7 +127,7 @@ pub fn Fragment(cx: Context, props: &FragmentProps) -> Element {
 ///
 /// For props that borrow:
 ///
-/// ```rust ignore
+/// ```rust, ignore ignore
 /// #[derive(Props)]
 /// struct MyProps<'a >{
 ///     data: &'a str

+ 81 - 56
packages/core/src/diff.rs

@@ -54,7 +54,7 @@
 //! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can
 //! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
 //! calls to "create" propagate this information upwards. Structures like the one below are entirely static:
-//! ```rust
+//! ```rust, ignore
 //! rsx!( div { class: "hello world", "this node is entirely static" } )
 //! ```
 //! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to
@@ -147,6 +147,7 @@ pub(crate) enum DiffInstruction<'a> {
     },
 
     PopScope,
+    PopElement,
 }
 
 #[derive(Debug, Clone, Copy)]
@@ -162,30 +163,32 @@ pub(crate) struct DiffStack<'bump> {
     pub(crate) instructions: Vec<DiffInstruction<'bump>>,
     nodes_created_stack: SmallVec<[usize; 10]>,
     pub scope_stack: SmallVec<[ScopeId; 5]>,
+    pub element_stack: SmallVec<[ElementId; 10]>,
 }
 
 impl<'bump> DiffStack<'bump> {
-    pub fn new() -> Self {
+    fn new() -> Self {
         Self {
             instructions: Vec::with_capacity(1000),
             nodes_created_stack: smallvec![],
             scope_stack: smallvec![],
+            element_stack: smallvec![],
         }
     }
 
-    pub fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
+    fn pop(&mut self) -> Option<DiffInstruction<'bump>> {
         self.instructions.pop()
     }
 
-    pub fn pop_off_scope(&mut self) {
+    fn pop_off_scope(&mut self) {
         self.scope_stack.pop();
     }
 
-    pub fn push(&mut self, instruction: DiffInstruction<'bump>) {
+    pub(crate) fn push(&mut self, instruction: DiffInstruction<'bump>) {
         self.instructions.push(instruction)
     }
 
-    pub fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
+    fn create_children(&mut self, children: &'bump [VNode<'bump>], and: MountType<'bump>) {
         self.nodes_created_stack.push(0);
         self.instructions.push(DiffInstruction::Mount { and });
 
@@ -195,36 +198,36 @@ impl<'bump> DiffStack<'bump> {
         }
     }
 
-    pub fn push_subtree(&mut self) {
+    fn push_subtree(&mut self) {
         self.nodes_created_stack.push(0);
         self.instructions.push(DiffInstruction::Mount {
             and: MountType::Append,
         });
     }
 
-    pub fn push_nodes_created(&mut self, count: usize) {
+    fn push_nodes_created(&mut self, count: usize) {
         self.nodes_created_stack.push(count);
     }
 
-    pub fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
+    pub(crate) fn create_node(&mut self, node: &'bump VNode<'bump>, and: MountType<'bump>) {
         self.nodes_created_stack.push(0);
         self.instructions.push(DiffInstruction::Mount { and });
         self.instructions.push(DiffInstruction::Create { node });
     }
 
-    pub fn add_child_count(&mut self, count: usize) {
+    fn add_child_count(&mut self, count: usize) {
         *self.nodes_created_stack.last_mut().unwrap() += count;
     }
 
-    pub fn pop_nodes_created(&mut self) -> usize {
+    fn pop_nodes_created(&mut self) -> usize {
         self.nodes_created_stack.pop().unwrap()
     }
 
-    pub fn current_scope(&self) -> Option<ScopeId> {
+    fn current_scope(&self) -> Option<ScopeId> {
         self.scope_stack.last().copied()
     }
 
-    pub fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
+    fn create_component(&mut self, idx: ScopeId, node: &'bump VNode<'bump>) {
         // Push the new scope onto the stack
         self.scope_stack.push(idx);
 
@@ -241,7 +244,8 @@ impl<'bump> DiffState<'bump> {
         let (old, new) = (self.scopes.wip_head(id), self.scopes.fin_head(id));
         self.stack.push(DiffInstruction::Diff { old, new });
         self.stack.scope_stack.push(*id);
-        self.stack.push_nodes_created(0);
+        let scope = self.scopes.get_scope(id).unwrap();
+        self.stack.element_stack.push(scope.container);
         self.work(|| false);
     }
 
@@ -263,10 +267,13 @@ impl<'bump> DiffState<'bump> {
                     self.stack.add_child_count(num_on_stack);
                 }
                 DiffInstruction::PopScope => self.stack.pop_off_scope(),
+                DiffInstruction::PopElement => {
+                    self.stack.element_stack.pop();
+                }
             };
 
             if deadline_expired() {
-                log::debug!("Deadline expired before we could finished!");
+                log::debug!("Deadline expired before we could finish!");
                 return false;
             }
         }
@@ -358,6 +365,7 @@ impl<'bump> DiffState<'bump> {
 
     fn create_text_node(&mut self, vtext: &'bump VText<'bump>, node: &'bump VNode<'bump>) {
         let real_id = self.scopes.reserve_node(node);
+
         self.mutations.create_text_node(vtext.text, real_id);
         vtext.dom_id.set(Some(real_id));
         self.stack.add_child_count(1);
@@ -390,11 +398,19 @@ impl<'bump> DiffState<'bump> {
             children,
             namespace,
             dom_id,
+            parent_id,
             ..
         } = element;
 
-        let real_id = self.scopes.reserve_node(node);
+        // set the parent ID for event bubbling
+        self.stack.instructions.push(DiffInstruction::PopElement);
 
+        let parent = self.stack.element_stack.last().unwrap();
+        parent_id.set(Some(*parent));
+
+        // set the id of the element
+        let real_id = self.scopes.reserve_node(node);
+        self.stack.element_stack.push(real_id);
         dom_id.set(Some(real_id));
 
         self.mutations.create_element(tag_name, *namespace, real_id);
@@ -438,9 +454,13 @@ impl<'bump> DiffState<'bump> {
         let caller = unsafe { std::mem::transmute(vcomponent.caller as *const _) };
         let fc_ptr = vcomponent.user_fc;
 
-        let new_idx = self
-            .scopes
-            .new_with_key(fc_ptr, caller, parent_scope, height, subtree);
+        let container = *self.stack.element_stack.last().unwrap();
+
+        let new_idx =
+            self.scopes
+                .new_with_key(fc_ptr, caller, parent_scope, container, height, subtree);
+
+        log::debug!("container for scope {:?} is {:?}", new_idx, container);
 
         // Actually initialize the caller's slot with the right address
         vcomponent.associated_scope.set(Some(new_idx));
@@ -495,7 +515,9 @@ impl<'bump> DiffState<'bump> {
         use VNode::*;
         match (old_node, new_node) {
             // Check the most common cases first
-            (Text(old), Text(new)) => self.diff_text_nodes(old, new),
+            (Text(old), Text(new)) => {
+                self.diff_text_nodes(old, new, old_node, new_node);
+            }
             (Component(old), Component(new)) => {
                 self.diff_component_nodes(old_node, new_node, old, new)
             }
@@ -517,11 +539,18 @@ impl<'bump> DiffState<'bump> {
         }
     }
 
-    fn diff_text_nodes(&mut self, old: &'bump VText<'bump>, new: &'bump VText<'bump>) {
+    fn diff_text_nodes(
+        &mut self,
+        old: &'bump VText<'bump>,
+        new: &'bump VText<'bump>,
+        _old_node: &'bump VNode<'bump>,
+        new_node: &'bump VNode<'bump>,
+    ) {
         if let Some(root) = old.dom_id.get() {
             if old.text != new.text {
                 self.mutations.set_text(new.text, root.as_u64());
             }
+            self.scopes.update_node(new_node, root);
 
             new.dom_id.set(Some(root));
         }
@@ -551,7 +580,10 @@ impl<'bump> DiffState<'bump> {
             return;
         }
 
+        self.scopes.update_node(new_node, root);
+
         new.dom_id.set(Some(root));
+        new.parent_id.set(old.parent_id.get());
 
         // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
         // element to modify its attributes.
@@ -617,8 +649,12 @@ impl<'bump> DiffState<'bump> {
             self.mutations.edits.push(PushRoot {
                 root: root.as_u64(),
             });
+            self.stack.element_stack.push(root);
+            self.stack.instructions.push(DiffInstruction::PopElement);
             self.stack.create_children(new.children, MountType::Append);
         } else {
+            self.stack.element_stack.push(root);
+            self.stack.instructions.push(DiffInstruction::PopElement);
             self.diff_children(old.children, new.children);
         }
     }
@@ -650,20 +686,18 @@ impl<'bump> DiffState<'bump> {
             let scope = unsafe {
                 self.scopes
                     .get_scope_mut(&scope_addr)
-                    .expect(&format!("could not find {:?}", scope_addr))
+                    .unwrap_or_else(|| panic!("could not find {:?}", scope_addr))
             };
             scope.caller = unsafe { std::mem::transmute(new.caller) };
 
             // React doesn't automatically memoize, but we do.
             let props_are_the_same = old.comparator.unwrap();
 
-            if self.force_diff || !props_are_the_same(new) {
-                if self.scopes.run_scope(&scope_addr) {
-                    self.diff_node(
-                        self.scopes.wip_head(&scope_addr),
-                        self.scopes.fin_head(&scope_addr),
-                    );
-                }
+            if (self.force_diff || !props_are_the_same(new)) && self.scopes.run_scope(&scope_addr) {
+                self.diff_node(
+                    self.scopes.wip_head(&scope_addr),
+                    self.scopes.fin_head(&scope_addr),
+                );
             }
 
             self.stack.scope_stack.pop();
@@ -856,13 +890,7 @@ impl<'bump> DiffState<'bump> {
             Some(count) => count,
             None => return,
         };
-        // log::debug!(
-        //     "Left offset, right offset, {}, {}",
-        //     left_offset,
-        //     right_offset,
-        // );
 
-        // log::debug!("stack before lo is {:#?}", self.stack.instructions);
         // Ok, we now hopefully have a smaller range of children in the middle
         // within which to re-order nodes with the same keys, remove old nodes with
         // now-unused keys, and create new nodes with fresh keys.
@@ -911,8 +939,6 @@ impl<'bump> DiffState<'bump> {
         } else {
             self.diff_keyed_middle(old_middle, new_middle);
         }
-
-        log::debug!("stack after km is {:#?}", self.stack.instructions);
     }
 
     /// Diff both ends of the children that share keys.
@@ -1039,15 +1065,6 @@ impl<'bump> DiffState<'bump> {
         // If none of the old keys are reused by the new children, then we remove all the remaining old children and
         // create the new children afresh.
         if shared_keys.is_empty() {
-            log::debug!(
-                "no shared keys, replacing and creating many with many, {:#?}, {:#?}",
-                old,
-                new
-            );
-            log::debug!("old_key_to_old_index, {:#?}", old_key_to_old_index);
-            log::debug!("new_index_to_old_index, {:#?}", new_index_to_old_index);
-            log::debug!("shared_keys, {:#?}", shared_keys);
-
             if let Some(first_old) = old.get(0) {
                 self.remove_nodes(&old[1..], true);
                 self.stack
@@ -1206,13 +1223,21 @@ impl<'bump> DiffState<'bump> {
     fn replace_node(&mut self, old: &'bump VNode<'bump>, nodes_created: usize) {
         match old {
             VNode::Element(el) => {
-                let id = old.try_mounted_id().expect(&format!("broke on {:?}", old));
+                let id = old
+                    .try_mounted_id()
+                    .unwrap_or_else(|| panic!("broke on {:?}", old));
+
+                log::debug!("element parent is {:?}", el.parent_id.get());
+
                 self.mutations.replace_with(id, nodes_created as u32);
                 self.remove_nodes(el.children, false);
             }
 
             VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
-                let id = old.try_mounted_id().expect(&format!("broke on {:?}", old));
+                let id = old
+                    .try_mounted_id()
+                    .unwrap_or_else(|| panic!("broke on {:?}", old));
+
                 self.mutations.replace_with(id, nodes_created as u32);
             }
 
@@ -1226,7 +1251,6 @@ impl<'bump> DiffState<'bump> {
                 self.replace_node(node, nodes_created);
 
                 let scope_id = c.associated_scope.get().unwrap();
-                println!("replacing c {:?} ", scope_id);
                 log::debug!("Destroying scope {:?}", scope_id);
                 self.scopes.try_remove(&scope_id).unwrap();
             }
@@ -1320,13 +1344,14 @@ impl<'bump> DiffState<'bump> {
             .current_scope()
             .and_then(|id| self.scopes.get_scope(&id))
         {
-            // safety: this lifetime is managed by the logic on scope
-            let extended = unsafe { std::mem::transmute(suspended) };
-            scope
-                .items
-                .borrow_mut()
-                .suspended_nodes
-                .insert(suspended.task_id, extended);
+            todo!()
+            // // safety: this lifetime is managed by the logic on scope
+            // let extended = unsafe { std::mem::transmute(suspended) };
+            // scope
+            //     .items
+            //     .borrow_mut()
+            //     .suspended_nodes
+            //     .insert(suspended.task_id, extended);
         }
     }
 }

+ 16 - 16
packages/core/src/lazynodes.rs

@@ -20,7 +20,7 @@ use std::mem;
 /// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode.
 ///
 ///
-/// ```rust
+/// ```rust, ignore
 /// LazyNodes::new(|f| f.element("div", [], [], [] None))
 /// ```
 pub struct LazyNodes<'a, 'b> {
@@ -47,7 +47,14 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
             fac.map(inner)
         };
 
-        unsafe { LazyNodes::new_inner(val) }
+        // miri does not know how to work with mucking directly into bytes
+        if cfg!(miri) {
+            Self {
+                inner: StackNodeStorage::Heap(Box::new(val)),
+            }
+        } else {
+            unsafe { LazyNodes::new_inner(val) }
+        }
     }
 
     unsafe fn new_inner<F>(val: F) -> Self
@@ -212,8 +219,6 @@ fn round_to_words(len: usize) -> usize {
 fn it_works() {
     let bump = bumpalo::Bump::new();
 
-    simple_logger::init().unwrap();
-
     let factory = NodeFactory { bump: &bump };
 
     let caller = NodeFactory::annotate_lazy(|f| {
@@ -232,9 +237,7 @@ fn it_drops() {
     use std::rc::Rc;
     let bump = bumpalo::Bump::new();
 
-    simple_logger::init().unwrap();
-
-    // let factory = NodeFactory { scope: &bump };
+    let factory = NodeFactory { bump: &bump };
 
     struct DropInner {
         id: i32,
@@ -246,7 +249,7 @@ fn it_drops() {
     }
     let val = Rc::new(10);
 
-    {
+    let caller = {
         let it = (0..10)
             .map(|i| {
                 let val = val.clone();
@@ -259,16 +262,13 @@ fn it_drops() {
             })
             .collect::<Vec<_>>();
 
-        let caller = NodeFactory::annotate_lazy(|f| {
+        NodeFactory::annotate_lazy(|f| {
             log::debug!("main closure");
             f.fragment_from_iter(it)
         })
-        .unwrap();
-    }
-
-    // let nodes = caller.call(factory);
-
-    // dbg!(nodes);
+        .unwrap()
+    };
 
-    dbg!(Rc::strong_count(&val));
+    let _ = caller.call(factory);
+    assert_eq!(Rc::strong_count(&val), 1);
 }

+ 87 - 102
packages/core/src/mutations.rs

@@ -5,28 +5,111 @@
 use crate::innerlude::*;
 use std::{any::Any, fmt::Debug};
 
+/// ## Mutations
+///
+/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
+/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
+/// applied the edits.
+///
+/// Mutations are the only link between the RealDOM and the VirtualDOM.
 pub struct Mutations<'a> {
     pub edits: Vec<DomEdit<'a>>,
-    pub noderefs: Vec<NodeRefMutation<'a>>,
+    pub refs: Vec<NodeRefMutation<'a>>,
     pub effects: Vec<&'a dyn FnMut()>,
 }
+
 impl Debug for Mutations<'_> {
     fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
         f.debug_struct("Mutations")
             .field("edits", &self.edits)
-            .field("noderefs", &self.noderefs)
-            // .field("effects", &self.effects)
+            .field("noderefs", &self.refs)
             .finish()
     }
 }
 
+/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
+/// network or through FFI boundaries.
+#[derive(Debug, PartialEq)]
+#[cfg_attr(
+    feature = "serialize",
+    derive(serde::Serialize, serde::Deserialize),
+    serde(tag = "type")
+)]
+pub enum DomEdit<'bump> {
+    PushRoot {
+        root: u64,
+    },
+    PopRoot,
+
+    AppendChildren {
+        many: u32,
+    },
+
+    // "Root" refers to the item directly
+    // it's a waste of an instruction to push the root directly
+    ReplaceWith {
+        root: u64,
+        m: u32,
+    },
+    InsertAfter {
+        root: u64,
+        n: u32,
+    },
+    InsertBefore {
+        root: u64,
+        n: u32,
+    },
+    Remove {
+        root: u64,
+    },
+    CreateTextNode {
+        text: &'bump str,
+        root: u64,
+    },
+    CreateElement {
+        tag: &'bump str,
+        root: u64,
+    },
+    CreateElementNs {
+        tag: &'bump str,
+        root: u64,
+        ns: &'static str,
+    },
+    CreatePlaceholder {
+        root: u64,
+    },
+    NewEventListener {
+        event_name: &'static str,
+        scope: ScopeId,
+        root: u64,
+    },
+    RemoveEventListener {
+        root: u64,
+        event: &'static str,
+    },
+    SetText {
+        root: u64,
+        text: &'bump str,
+    },
+    SetAttribute {
+        root: u64,
+        field: &'static str,
+        value: &'bump str,
+        ns: Option<&'bump str>,
+    },
+    RemoveAttribute {
+        root: u64,
+        name: &'static str,
+    },
+}
+
 use DomEdit::*;
 
 impl<'a> Mutations<'a> {
     pub(crate) fn new() -> Self {
         Self {
             edits: Vec::new(),
-            noderefs: Vec::new(),
+            refs: Vec::new(),
             effects: Vec::new(),
         }
     }
@@ -156,101 +239,3 @@ impl<'a> NodeRefMutation<'a> {
             .and_then(|f| f.downcast_mut::<T>())
     }
 }
-
-/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the
-/// network or through FFI boundaries.
-#[derive(Debug, PartialEq)]
-#[cfg_attr(
-    feature = "serialize",
-    derive(serde::Serialize, serde::Deserialize),
-    serde(tag = "type")
-)]
-pub enum DomEdit<'bump> {
-    PushRoot {
-        root: u64,
-    },
-    PopRoot,
-
-    AppendChildren {
-        many: u32,
-    },
-
-    // "Root" refers to the item directly
-    // it's a waste of an instruction to push the root directly
-    ReplaceWith {
-        root: u64,
-        m: u32,
-    },
-    InsertAfter {
-        root: u64,
-        n: u32,
-    },
-    InsertBefore {
-        root: u64,
-        n: u32,
-    },
-    Remove {
-        root: u64,
-    },
-    CreateTextNode {
-        text: &'bump str,
-        root: u64,
-    },
-    CreateElement {
-        tag: &'bump str,
-        root: u64,
-    },
-    CreateElementNs {
-        tag: &'bump str,
-        root: u64,
-        ns: &'static str,
-    },
-    CreatePlaceholder {
-        root: u64,
-    },
-    NewEventListener {
-        event_name: &'static str,
-        scope: ScopeId,
-        root: u64,
-    },
-    RemoveEventListener {
-        root: u64,
-        event: &'static str,
-    },
-    SetText {
-        root: u64,
-        text: &'bump str,
-    },
-    SetAttribute {
-        root: u64,
-        field: &'static str,
-        value: &'bump str,
-        ns: Option<&'bump str>,
-    },
-    RemoveAttribute {
-        root: u64,
-        name: &'static str,
-    },
-}
-impl DomEdit<'_> {
-    pub fn is(&self, id: &'static str) -> bool {
-        match self {
-            DomEdit::InsertAfter { .. } => id == "InsertAfter",
-            DomEdit::InsertBefore { .. } => id == "InsertBefore",
-            DomEdit::PushRoot { .. } => id == "PushRoot",
-            DomEdit::PopRoot => id == "PopRoot",
-            DomEdit::AppendChildren { .. } => id == "AppendChildren",
-            DomEdit::ReplaceWith { .. } => id == "ReplaceWith",
-            DomEdit::Remove { .. } => id == "Remove",
-            DomEdit::CreateTextNode { .. } => id == "CreateTextNode",
-            DomEdit::CreateElement { .. } => id == "CreateElement",
-            DomEdit::CreateElementNs { .. } => id == "CreateElementNs",
-            DomEdit::CreatePlaceholder { .. } => id == "CreatePlaceholder",
-            DomEdit::NewEventListener { .. } => id == "NewEventListener",
-            DomEdit::RemoveEventListener { .. } => id == "RemoveEventListener",
-            DomEdit::SetText { .. } => id == "SetText",
-            DomEdit::SetAttribute { .. } => id == "SetAttribute",
-            DomEdit::RemoveAttribute { .. } => id == "RemoveAttribute",
-        }
-    }
-}

+ 50 - 70
packages/core/src/nodes.rs

@@ -12,6 +12,7 @@ use std::{
     any::Any,
     cell::{Cell, RefCell},
     fmt::{Arguments, Debug, Formatter},
+    sync::Arc,
 };
 
 /// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
@@ -25,7 +26,7 @@ pub enum VNode<'src> {
     ///
     /// # Example
     ///
-    /// ```
+    /// ```rust, ignore
     /// let mut vdom = VirtualDom::new();
     /// let node = vdom.render_vnode(rsx!( "hello" ));
     ///
@@ -41,7 +42,7 @@ pub enum VNode<'src> {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// let mut vdom = VirtualDom::new();
     ///
     /// let node = vdom.render_vnode(rsx!{
@@ -66,7 +67,7 @@ pub enum VNode<'src> {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// rsx!{
     ///     a {}
     ///     link {}
@@ -81,7 +82,7 @@ pub enum VNode<'src> {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// fn Example(cx: Context, props: &()) -> Element {
     ///     todo!()
     /// }
@@ -100,11 +101,11 @@ pub enum VNode<'src> {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     ///
     ///
     /// ```
-    Suspended(&'src VSuspended<'src>),
+    Suspended(&'src VSuspended),
 
     /// Anchors are a type of placeholder VNode used when fragments don't contain any children.
     ///
@@ -112,7 +113,7 @@ pub enum VNode<'src> {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// let mut vdom = VirtualDom::new();
     ///
     /// let node = vdom.render_vnode(rsx!( Fragment {} ));
@@ -133,7 +134,7 @@ pub enum VNode<'src> {
     /// an `rsx!` call.
     ///
     /// # Example
-    /// ```rust
+    /// ```rust, ignore
     /// let mut vdom = VirtualDom::new();
     ///
     /// let node: NodeLink = vdom.render_vnode(rsx!( "hello" ));
@@ -202,7 +203,7 @@ impl<'src> VNode<'src> {
             VNode::Linked(c) => VNode::Linked(NodeLink {
                 scope_id: c.scope_id.clone(),
                 link_idx: c.link_idx.clone(),
-                node: c.node.clone(),
+                node: c.node,
             }),
         }
     }
@@ -260,9 +261,7 @@ pub struct VAnchor {
 /// A bump-allocated string slice and metadata.
 pub struct VText<'src> {
     pub text: &'src str,
-
     pub dom_id: Cell<Option<ElementId>>,
-
     pub is_static: bool,
 }
 
@@ -275,19 +274,12 @@ pub struct VFragment<'src> {
 /// An element like a "div" with children, listeners, and attributes.
 pub struct VElement<'a> {
     pub tag_name: &'static str,
-
     pub namespace: Option<&'static str>,
-
     pub key: Option<&'a str>,
-
     pub dom_id: Cell<Option<ElementId>>,
-
     pub parent_id: Cell<Option<ElementId>>,
-
     pub listeners: &'a [Listener<'a>],
-
     pub attributes: &'a [Attribute<'a>],
-
     pub children: &'a [VNode<'a>],
 }
 
@@ -310,7 +302,7 @@ impl Debug for VElement<'_> {
 ///
 /// This trait provides the ability to use custom elements in the `rsx!` macro.
 ///
-/// ```rust
+/// ```rust, ignore
 /// struct my_element;
 ///
 /// impl DioxusElement for my_element {
@@ -365,10 +357,10 @@ pub struct Listener<'bump> {
     pub event: &'static str,
 
     /// The actual callback that the user specified
-    pub(crate) callback: RefCell<Option<BumpBox<'bump, dyn FnMut(Box<dyn Any + Send>) + 'bump>>>,
+    pub(crate) callback:
+        RefCell<Option<BumpBox<'bump, dyn FnMut(std::sync::Arc<dyn Any + Send + Sync>) + 'bump>>>,
 }
 
-pub type VCompCaller<'src> = BumpBox<'src, dyn Fn(Context) -> Element + 'src>;
 /// Virtual Components for custom user-defined components
 /// Only supports the functional syntax
 pub struct VComponent<'src> {
@@ -381,7 +373,7 @@ pub struct VComponent<'src> {
 
     pub(crate) can_memoize: bool,
 
-    pub(crate) hard_allocation: Cell<Option<*const ()>>,
+    pub(crate) _hard_allocation: Cell<Option<*const ()>>,
 
     // Raw pointer into the bump arena for the props of the component
     pub(crate) bump_props: *const (),
@@ -395,12 +387,11 @@ pub struct VComponent<'src> {
     pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
 }
 
-pub struct VSuspended<'a> {
-    pub task_id: u64,
+pub struct VSuspended {
+    pub task_id: usize,
+    pub scope: Cell<Option<ScopeId>>,
     pub dom_id: Cell<Option<ElementId>>,
-
-    #[allow(clippy::type_complexity)]
-    pub callback: RefCell<Option<BumpBox<'a, dyn FnMut() -> Element + 'a>>>,
+    pub parent: Cell<Option<ElementId>>,
 }
 
 /// A cached node is a "pointer" to a "rendered" node in a particular scope
@@ -426,16 +417,6 @@ impl PartialEq for NodeLink {
         self.node == other.node
     }
 }
-impl NodeLink {
-    // we don't want to let users clone NodeLinks
-    pub(crate) fn clone_inner(&self) -> Self {
-        Self {
-            link_idx: self.link_idx.clone(),
-            scope_id: self.scope_id.clone(),
-            node: self.node.clone(),
-        }
-    }
-}
 
 /// This struct provides an ergonomic API to quickly build VNodes.
 ///
@@ -453,7 +434,7 @@ impl<'a> NodeFactory<'a> {
 
     #[inline]
     pub fn bump(&self) -> &'a bumpalo::Bump {
-        &self.bump
+        self.bump
     }
 
     /// Directly pass in text blocks without the need to use the format_args macro.
@@ -591,14 +572,13 @@ impl<'a> NodeFactory<'a> {
                     // - Because FC<P> is the same, then P must be the same (even with generics)
                     // - Non-static P are autoderived to memoize as false
                     // - This comparator is only called on a corresponding set of bumpframes
-                    let props_memoized = unsafe {
-                        let real_other: &P = &*(other.bump_props as *const _ as *const P);
-                        props.memoize(real_other)
-                    };
-
+                    //
                     // It's only okay to memoize if there are no children and the props can be memoized
                     // Implementing memoize is unsafe and done automatically with the props trait
-                    props_memoized
+                    unsafe {
+                        let real_other: &P = &*(other.bump_props as *const _ as *const P);
+                        props.memoize(real_other)
+                    }
                 } else {
                     false
                 }
@@ -633,8 +613,7 @@ impl<'a> NodeFactory<'a> {
         let caller: &'a mut dyn Fn(&'a Scope) -> Element =
             bump.alloc(move |scope: &Scope| -> Element {
                 let props: &'_ P = unsafe { &*(bump_props as *const P) };
-                let res = component(scope, props);
-                res
+                component(scope, props)
             });
 
         let can_memoize = P::IS_STATIC;
@@ -648,14 +627,14 @@ impl<'a> NodeFactory<'a> {
             can_memoize,
             drop_props,
             associated_scope: Cell::new(None),
-            hard_allocation: Cell::new(None),
+            _hard_allocation: Cell::new(None),
         }))
     }
 
     pub fn listener(
         self,
         event: &'static str,
-        callback: BumpBox<'a, dyn FnMut(Box<dyn Any + Send>) + 'a>,
+        callback: BumpBox<'a, dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a>,
     ) -> Listener<'a> {
         Listener {
             mounted_node: Cell::new(None),
@@ -664,9 +643,9 @@ impl<'a> NodeFactory<'a> {
         }
     }
 
-    pub fn fragment_from_iter(
+    pub fn fragment_from_iter<'b, 'c>(
         self,
-        node_iter: impl IntoIterator<Item = impl IntoVNode<'a>>,
+        node_iter: impl IntoIterator<Item = impl IntoVNode<'a> + 'c> + 'b,
     ) -> VNode<'a> {
         let bump = self.bump;
         let mut nodes = bumpalo::collections::Vec::new_in(bump);
@@ -683,24 +662,25 @@ impl<'a> NodeFactory<'a> {
 
         let children = nodes.into_bump_slice();
 
-        // TODO
-        // We need a dedicated path in the rsx! macro that will trigger the "you need keys" warning
-        //
-        // if cfg!(debug_assertions) {
-        //     if children.len() > 1 {
-        //         if children.last().unwrap().key().is_none() {
-        //             log::error!(
-        //                 r#"
-        // Warning: Each child in an array or iterator should have a unique "key" prop.
-        // Not providing a key will lead to poor performance with lists.
-        // See docs.rs/dioxus for more information.
-        // ---
-        // To help you identify where this error is coming from, we've generated a backtrace.
-        //                         "#,
-        //             );
-        //         }
-        //     }
-        // }
+        // todo: add a backtrace
+        if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none()
+        {
+            // todo: rsx! calls get turned into fragments which means they always trip this error
+            //
+            //
+            // use backtrace::Backtrace;
+            // let bt = Backtrace::new();
+
+            log::error!(
+                r#"
+                Warning: Each child in an array or iterator should have a unique "key" prop.
+                Not providing a key will lead to poor performance with lists.
+                See docs.rs/dioxus for more information.
+                -------------
+                "#,
+                // bt
+            );
+        }
 
         VNode::Fragment(VFragment {
             children,
@@ -782,7 +762,7 @@ impl Debug for NodeFactory<'_> {
 ///
 /// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out.
 /// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type:
-/// ```
+/// ```rust, ignore
 /// impl IntoIterator<Item = impl IntoVNode<'a>>
 /// ```
 ///
@@ -841,7 +821,7 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
     }
 }
 
-impl<'a> IntoVNode<'a> for LazyNodes<'a, '_> {
+impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
     fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
         self.call(cx)
     }

+ 154 - 163
packages/core/src/scope.rs

@@ -43,12 +43,8 @@ pub type Context<'a> = &'a Scope;
 /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
 /// use case they might have.
 pub struct Scope {
-    // safety:
-    //
-    // pointers to scopes are *always* valid since they are bump allocated and never freed until this scope is also freed
-    // this is just a bit of a hack to not need an Rc to the ScopeArena.
-    // todo: replace this will ScopeId and provide a connection to scope arena directly
     pub(crate) parent_scope: Option<*mut Scope>,
+    pub(crate) container: ElementId,
 
     pub(crate) our_arena_idx: ScopeId,
 
@@ -60,23 +56,14 @@ pub struct Scope {
 
     pub(crate) generation: Cell<u32>,
 
-    // The double-buffering situation that we will use
     pub(crate) frames: [BumpFrame; 2],
 
     pub(crate) caller: *const dyn Fn(&Scope) -> Element,
 
-    /*
-    we care about:
-    - listeners (and how to call them when an event is triggered)
-    - borrowed props (and how to drop them when the parent is dropped)
-    - suspended nodes (and how to call their callback when their associated tasks are complete)
-    */
     pub(crate) items: RefCell<SelfReferentialItems<'static>>,
 
-    // State
     pub(crate) hooks: HookList,
 
-    // todo: move this into a centralized place - is more memory efficient
     pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
 
     pub(crate) sender: UnboundedSender<SchedulerMsg>,
@@ -85,7 +72,7 @@ pub struct Scope {
 pub struct SelfReferentialItems<'a> {
     pub(crate) listeners: Vec<&'a Listener<'a>>,
     pub(crate) borrowed_props: Vec<&'a VComponent<'a>>,
-    pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended<'a>>,
+    pub(crate) suspended_nodes: FxHashMap<u64, &'a VSuspended>,
     pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
     pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
 }
@@ -108,7 +95,7 @@ impl Scope {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     ///
@@ -120,13 +107,39 @@ impl Scope {
         self.subtree.get()
     }
 
+    /// Create a new subtree with this scope as the root of the subtree.
+    ///
+    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
+    /// the mutations to the correct window/portal/subtree.
+    ///
+    /// This method
+    ///
+    /// # Example
+    ///
+    /// ```rust, ignore
+    /// fn App(cx: Context, props: &()) -> Element {
+    ///     todo!();
+    ///     rsx!(cx, div { "Subtree {id}"})
+    /// };
+    /// ```
+    pub fn create_subtree(&self) -> Option<u32> {
+        if self.is_subtree_root.get() {
+            None
+        } else {
+            todo!()
+            // let cur = self.subtree().get();
+            // self.shared.cur_subtree.set(cur + 1);
+            // Some(cur)
+        }
+    }
+
     /// Get the height of this Scope - IE the number of scopes above it.
     ///
     /// A Scope with a height of `0` is the root scope - there are no other scopes above it.
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     ///
@@ -146,7 +159,7 @@ impl Scope {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     ///
@@ -155,10 +168,7 @@ impl Scope {
     /// assert_eq!(base.parent(), None);
     /// ```
     pub fn parent(&self) -> Option<ScopeId> {
-        match self.parent_scope {
-            Some(p) => Some(unsafe { &*p }.our_arena_idx),
-            None => None,
-        }
+        self.parent_scope.map(|p| unsafe { &*p }.our_arena_idx)
     }
 
     /// Get the ID of this Scope within this Dioxus VirtualDOM.
@@ -167,7 +177,7 @@ impl Scope {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// let mut dom = VirtualDom::new(|cx, props|cx.render(rsx!{ div {} }));
     /// dom.rebuild();
     /// let base = dom.base_scope();
@@ -186,6 +196,7 @@ impl Scope {
         let chan = self.sender.clone();
         let id = self.scope_id();
         Rc::new(move || {
+            log::debug!("set on channel an update for scope {:?}", id);
             let _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
         })
     }
@@ -223,75 +234,6 @@ impl Scope {
         &self.wip_frame().bump
     }
 
-    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
-    ///
-    /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
-    ///
-    /// ## Example
-    ///
-    /// ```ignore
-    /// fn Component(cx: Scope, props: &Props) -> Element {
-    ///     // Lazy assemble the VNode tree
-    ///     let lazy_nodes = rsx!("hello world");
-    ///
-    ///     // Actually build the tree and allocate it
-    ///     cx.render(lazy_tree)
-    /// }
-    ///```
-    pub fn render<'src>(&'src self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
-        let frame = self.wip_frame();
-        let bump = &frame.bump;
-        let factory = NodeFactory { bump };
-        let node = lazy_nodes.map(|f| f.call(factory))?;
-        let node = bump.alloc(node);
-
-        let node_ptr = node as *mut _;
-        let node_ptr = unsafe { std::mem::transmute(node_ptr) };
-
-        let link = NodeLink {
-            scope_id: Cell::new(Some(self.our_arena_idx)),
-            link_idx: Cell::new(0),
-            node: node_ptr,
-        };
-
-        Some(link)
-    }
-
-    /// Push an effect to be ran after the component has been successfully mounted to the dom
-    /// Returns the effect's position in the stack
-    pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
-        // this is some tricker to get around not being able to actually call fnonces
-        let mut slot = Some(effect);
-        let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
-
-        // wrap it in a type that will actually drop the contents
-        let boxed_fut = unsafe { BumpBox::from_raw(fut) };
-
-        // erase the 'src lifetime for self-referential storage
-        let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
-
-        let mut items = self.items.borrow_mut();
-        items.pending_effects.push(self_ref_fut);
-        items.pending_effects.len() - 1
-    }
-
-    /// Pushes the future onto the poll queue to be polled
-    /// The future is forcibly dropped if the component is not ready by the next render
-    pub fn push_task<'src>(&'src self, fut: impl Future<Output = ()> + 'src) -> usize {
-        // allocate the future
-        let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
-
-        // wrap it in a type that will actually drop the contents
-        let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
-
-        // erase the 'src lifetime for self-referential storage
-        let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
-
-        let mut items = self.items.borrow_mut();
-        items.tasks.push(self_ref_fut);
-        items.tasks.len() - 1
-    }
-
     /// This method enables the ability to expose state to children further down the VirtualDOM Tree.
     ///
     /// This is a "fundamental" operation and should only be called during initialization of a hook.
@@ -303,7 +245,7 @@ impl Scope {
     ///
     /// # Example
     ///
-    /// ```
+    /// ```rust, ignore
     /// struct SharedState(&'static str);
     ///
     /// static App: FC<()> = |cx, props|{
@@ -311,7 +253,7 @@ impl Scope {
     ///     rsx!(cx, Child {})
     /// }
     ///
-    /// static Child: FC<()> = |cx, props|{
+    /// static Child: FC<()> = |cx, props| {
     ///     let state = cx.consume_state::<SharedState>();
     ///     rsx!(cx, div { "hello {state.0}" })
     /// }
@@ -342,47 +284,111 @@ impl Scope {
         }
     }
 
-    /// Create a new subtree with this scope as the root of the subtree.
+    /// Push an effect to be ran after the component has been successfully mounted to the dom
+    /// Returns the effect's position in the stack
+    pub fn push_effect<'src>(&'src self, effect: impl FnOnce() + 'src) -> usize {
+        // this is some tricker to get around not being able to actually call fnonces
+        let mut slot = Some(effect);
+        let fut: &mut dyn FnMut() = self.bump().alloc(move || slot.take().unwrap()());
+
+        // wrap it in a type that will actually drop the contents
+        let boxed_fut = unsafe { BumpBox::from_raw(fut) };
+
+        // erase the 'src lifetime for self-referential storage
+        let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
+
+        let mut items = self.items.borrow_mut();
+        items.pending_effects.push(self_ref_fut);
+        items.pending_effects.len() - 1
+    }
+
+    /// Pushes the future onto the poll queue to be polled
+    /// The future is forcibly dropped if the component is not ready by the next render
+    pub fn push_task<'src, F: Future<Output = ()>>(
+        &'src self,
+        fut: impl FnOnce() -> F + 'src,
+    ) -> usize
+    where
+        F::Output: 'src,
+        F: 'src,
+    {
+        self.sender
+            .unbounded_send(SchedulerMsg::TaskPushed(self.our_arena_idx))
+            .unwrap();
+
+        // allocate the future
+        let fut = fut();
+        let fut: &mut dyn Future<Output = ()> = self.bump().alloc(fut);
+
+        // wrap it in a type that will actually drop the contents
+        let boxed_fut: BumpBox<dyn Future<Output = ()>> = unsafe { BumpBox::from_raw(fut) };
+
+        // erase the 'src lifetime for self-referential storage
+        let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
+
+        let mut items = self.items.borrow_mut();
+
+        items.tasks.push(self_ref_fut);
+        items.tasks.len() - 1
+    }
+
+    /// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
     ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
+    /// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
     ///
-    /// This method
+    /// ## Example
     ///
-    /// # Example
+    /// ```ignore
+    /// fn Component(cx: Scope, props: &Props) -> Element {
+    ///     // Lazy assemble the VNode tree
+    ///     let lazy_nodes = rsx!("hello world");
     ///
-    /// ```rust
-    /// fn App(cx: Context, props: &()) -> Element {
-    ///     todo!();
-    ///     rsx!(cx, div { "Subtree {id}"})
-    /// };
-    /// ```
-    pub fn create_subtree(&self) -> Option<u32> {
-        if self.is_subtree_root.get() {
-            None
-        } else {
-            todo!()
-            // let cur = self.subtree().get();
-            // self.shared.cur_subtree.set(cur + 1);
-            // Some(cur)
-        }
+    ///     // Actually build the tree and allocate it
+    ///     cx.render(lazy_tree)
+    /// }
+    ///```
+    pub fn render<'src>(&'src self, lazy_nodes: Option<LazyNodes<'src, '_>>) -> Option<NodeLink> {
+        let frame = self.wip_frame();
+        let bump = &frame.bump;
+        let factory = NodeFactory { bump };
+        let node = lazy_nodes.map(|f| f.call(factory))?;
+        let node = bump.alloc(node);
+
+        let node_ptr = node as *mut _;
+        let node_ptr = unsafe { std::mem::transmute(node_ptr) };
+
+        let link = NodeLink {
+            scope_id: Cell::new(Some(self.our_arena_idx)),
+            link_idx: Cell::new(0),
+            node: node_ptr,
+        };
+
+        Some(link)
     }
 
-    /// Get the subtree ID that this scope belongs to.
-    ///
-    /// Each component has its own subtree ID - the root subtree has an ID of 0. This ID is used by the renderer to route
-    /// the mutations to the correct window/portal/subtree.
-    ///
-    /// # Example
-    ///
-    /// ```rust
-    /// fn App(cx: Context, props: &()) -> Element {
-    ///     let id = cx.get_current_subtree();
-    ///     rsx!(cx, div { "Subtree {id}"})
-    /// };
-    /// ```
-    pub fn get_current_subtree(&self) -> u32 {
-        self.subtree()
+    pub fn suspend<'src, F: Future<Output = Element> + 'src>(
+        &'src self,
+        mut fut: impl FnMut() -> F,
+    ) -> Option<VNode> {
+        let channel = self.sender.clone();
+        let node_fut = fut();
+
+        let scope = self.scope_id();
+
+        // self.push_task(move || {
+        //
+        // async move {
+        //     //
+        //     let r = node_fut.await;
+        //     if let Some(node) = r {
+        //         channel
+        //             .unbounded_send(SchedulerMsg::Suspended { node, scope })
+        //             .unwrap();
+        //     }
+        // }
+        // });
+
+        todo!()
     }
 
     /// Store a value between renders
@@ -437,6 +443,13 @@ impl Scope {
         }
     }
 
+    pub(crate) fn wip_frame_mut(&mut self) -> &mut BumpFrame {
+        match self.generation.get() & 1 == 0 {
+            true => &mut self.frames[0],
+            false => &mut self.frames[1],
+        }
+    }
+
     pub(crate) fn fin_frame(&self) -> &BumpFrame {
         match self.generation.get() & 1 == 1 {
             true => &self.frames[0],
@@ -444,51 +457,29 @@ impl Scope {
         }
     }
 
-    pub unsafe fn reset_wip_frame(&self) {
+    /// Reset this component's frame
+    ///
+    /// # Safety:
+    /// This method breaks every reference of VNodes in the current frame.
+    pub(crate) unsafe fn reset_wip_frame(&mut self) {
         // todo: unsafecell or something
-        let bump = self.wip_frame() as *const _ as *mut Bump;
-        let g = &mut *bump;
-        g.reset();
+        let bump = self.wip_frame_mut();
+        bump.bump.reset();
     }
 
-    pub fn cycle_frame(&self) {
+    pub(crate) fn cycle_frame(&self) {
         self.generation.set(self.generation.get() + 1);
     }
 
-    /// A safe wrapper around calling listeners
-    pub(crate) fn call_listener(&self, event: UserEvent, element: ElementId) {
-        let listners = &mut self.items.borrow_mut().listeners;
-
-        let listener = listners.iter().find(|lis| {
-            let search = lis;
-            if search.event == event.name {
-                let search_id = search.mounted_node.get();
-                search_id.map(|f| f == element).unwrap_or(false)
-            } else {
-                false
-            }
-        });
-
-        if let Some(listener) = listener {
-            let mut cb = listener.callback.borrow_mut();
-            if let Some(cb) = cb.as_mut() {
-                (cb)(event.event);
-            }
-        } else {
-            log::warn!("An event was triggered but there was no listener to handle it");
-        }
-    }
-
     // General strategy here is to load up the appropriate suspended task and then run it.
     // Suspended nodes cannot be called repeatedly.
     pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
         let mut nodes = &mut self.items.get_mut().suspended_nodes;
 
         if let Some(suspended) = nodes.remove(&task_id) {
-            let sus: &'a VSuspended<'static> = unsafe { &*suspended };
-            let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
-            let mut boxed = sus.callback.borrow_mut().take().unwrap();
-            let new_node: Element = boxed();
+            let sus: &'a VSuspended = suspended;
+            // let mut boxed = sus.callback.borrow_mut().take().unwrap();
+            // let new_node: Element = boxed();
         }
     }
 
@@ -499,7 +490,7 @@ impl Scope {
         }
     }
 
-    pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
+    pub fn root_node(&self) -> &VNode {
         let node = *self.wip_frame().nodes.borrow().get(0).unwrap();
         unsafe { std::mem::transmute(&*node) }
     }

+ 88 - 35
packages/core/src/scopearena.rs

@@ -1,8 +1,11 @@
 use bumpalo::Bump;
 use futures_channel::mpsc::UnboundedSender;
-use fxhash::FxHashMap;
+use fxhash::{FxHashMap, FxHashSet};
 use slab::Slab;
-use std::cell::{Cell, RefCell};
+use std::{
+    borrow::Borrow,
+    cell::{Cell, RefCell},
+};
 
 use crate::innerlude::*;
 
@@ -19,8 +22,9 @@ pub struct Heuristic {
 // has an internal heuristics engine to pre-allocate arenas to the right size
 pub(crate) struct ScopeArena {
     bump: Bump,
+    pub pending_futures: RefCell<FxHashSet<ScopeId>>,
     scope_counter: Cell<usize>,
-    scopes: RefCell<FxHashMap<ScopeId, *mut Scope>>,
+    pub scopes: RefCell<FxHashMap<ScopeId, *mut Scope>>,
     pub heuristics: RefCell<FxHashMap<FcSlot, Heuristic>>,
     free_scopes: RefCell<Vec<*mut Scope>>,
     nodes: RefCell<Slab<*const VNode<'static>>>,
@@ -28,45 +32,68 @@ pub(crate) struct ScopeArena {
 }
 
 impl ScopeArena {
-    pub fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
+    pub(crate) fn new(sender: UnboundedSender<SchedulerMsg>) -> Self {
+        let bump = Bump::new();
+
+        // allocate a container for the root element
+        // this will *never* show up in the diffing process
+        let el = bump.alloc(VElement {
+            tag_name: "root",
+            namespace: None,
+            key: None,
+            dom_id: Cell::new(Some(ElementId(0))),
+            parent_id: Default::default(),
+            listeners: &[],
+            attributes: &[],
+            children: &[],
+        });
+
+        let node = bump.alloc(VNode::Element(el));
+        let mut nodes = Slab::new();
+        let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) });
+        debug_assert_eq!(root_id, 0);
+
         Self {
             scope_counter: Cell::new(0),
-            bump: Bump::new(),
+            bump,
+            pending_futures: RefCell::new(FxHashSet::default()),
             scopes: RefCell::new(FxHashMap::default()),
             heuristics: RefCell::new(FxHashMap::default()),
             free_scopes: RefCell::new(Vec::new()),
-            nodes: RefCell::new(Slab::new()),
+            nodes: RefCell::new(nodes),
             sender,
         }
     }
 
-    pub fn get_scope(&self, id: &ScopeId) -> Option<&Scope> {
+    /// Safety:
+    /// - Obtaining a mutable refernece to any Scope is unsafe
+    /// - Scopes use interior mutability when sharing data into components
+    pub(crate) fn get_scope(&self, id: &ScopeId) -> Option<&Scope> {
         unsafe { self.scopes.borrow().get(id).map(|f| &**f) }
     }
 
-    // this is unsafe
-    pub unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> {
-        self.scopes.borrow().get(id).map(|f| *f)
+    pub(crate) unsafe fn get_scope_raw(&self, id: &ScopeId) -> Option<*mut Scope> {
+        self.scopes.borrow().get(id).copied()
     }
-    // this is unsafe
 
-    pub unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> {
+    pub(crate) unsafe fn get_scope_mut(&self, id: &ScopeId) -> Option<&mut Scope> {
         self.scopes.borrow().get(id).map(|s| &mut **s)
     }
 
-    pub fn new_with_key(
+    pub(crate) fn new_with_key(
         &self,
         fc_ptr: *const (),
         caller: *const dyn Fn(&Scope) -> Element,
         parent_scope: Option<*mut Scope>,
+        container: ElementId,
         height: u32,
         subtree: u32,
     ) -> ScopeId {
         let new_scope_id = ScopeId(self.scope_counter.get());
         self.scope_counter.set(self.scope_counter.get() + 1);
 
-        //
-        //
+        log::debug!("new scope {:?} with parent {:?}", new_scope_id, container);
+
         if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
             let scope = unsafe { &mut *old_scope };
             log::debug!(
@@ -80,6 +107,7 @@ impl ScopeArena {
             scope.height = height;
             scope.subtree = Cell::new(subtree);
             scope.our_arena_idx = new_scope_id;
+            scope.container = container;
 
             scope.frames[0].nodes.get_mut().push({
                 let vnode = scope.frames[0]
@@ -103,11 +131,8 @@ impl ScopeArena {
                 unsafe { std::mem::transmute(vnode as *mut VNode) }
             });
 
-            let r = self.scopes.borrow_mut().insert(new_scope_id, scope);
-
-            assert!(r.is_none());
-
-            new_scope_id
+            let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
+            debug_assert!(any_item.is_none());
         } else {
             let (node_capacity, hook_capacity) = {
                 let heuristics = self.heuristics.borrow();
@@ -144,6 +169,7 @@ impl ScopeArena {
 
             let scope = self.bump.alloc(Scope {
                 sender: self.sender.clone(),
+                container,
                 our_arena_idx: new_scope_id,
                 parent_scope,
                 height,
@@ -166,24 +192,22 @@ impl ScopeArena {
                 }),
             });
 
-            dbg!(self.scopes.borrow());
-
-            let r = self.scopes.borrow_mut().insert(new_scope_id, scope);
-
-            assert!(r.is_none());
-            // .expect(&format!("scope shouldnt exist, {:?}", new_scope_id));
-
-            new_scope_id
+            let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
+            debug_assert!(any_item.is_none());
         }
+
+        new_scope_id
     }
 
     pub fn try_remove(&self, id: &ScopeId) -> Option<()> {
         self.ensure_drop_safety(id);
 
         log::debug!("removing scope {:?}", id);
-        println!("removing scope {:?}", id);
 
-        let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
+        // Safety:
+        // - ensure_drop_safety ensures that no references to this scope are in use
+        // - this raw pointer is removed from the map
+        let scope = unsafe { &mut *self.scopes.borrow_mut().remove(id).unwrap() };
 
         // we're just reusing scopes so we need to clear it out
         scope.hooks.clear();
@@ -229,6 +253,11 @@ impl ScopeArena {
         id
     }
 
+    pub fn update_node(&self, node: &VNode, id: ElementId) {
+        let node = unsafe { std::mem::transmute::<*const VNode, *const VNode>(node) };
+        *self.nodes.borrow_mut().get_mut(id.0).unwrap() = node;
+    }
+
     pub fn collect_garbage(&self, id: ElementId) {
         self.nodes.borrow_mut().remove(id.0);
     }
@@ -275,15 +304,15 @@ impl ScopeArena {
     }
 
     pub(crate) fn run_scope(&self, id: &ScopeId) -> bool {
-        let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
-
-        log::debug!("found scope, about to run: {:?}", id);
-
         // Cycle to the next frame and then reset it
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
         self.ensure_drop_safety(id);
 
+        let scope = unsafe { &mut *self.get_scope_mut(id).expect("could not find scope") };
+
+        log::debug!("found scope, about to run: {:?}", id);
+
         // Safety:
         // - We dropped the listeners, so no more &mut T can be used while these are held
         // - All children nodes that rely on &mut T are replaced with a new reference
@@ -325,7 +354,7 @@ impl ScopeArena {
             debug_assert_eq!(scope.wip_frame().nodes.borrow().len(), 1);
 
             if !scope.items.borrow().tasks.is_empty() {
-                // self.
+                self.pending_futures.borrow_mut().insert(*id);
             }
 
             // make the "wip frame" contents the "finished frame"
@@ -337,6 +366,30 @@ impl ScopeArena {
         }
     }
 
+    pub fn call_listener_with_bubbling(&self, event: UserEvent, element: ElementId) {
+        let nodes = self.nodes.borrow();
+        let mut cur_el = Some(element);
+
+        while let Some(id) = cur_el.take() {
+            if let Some(el) = nodes.get(id.0) {
+                let real_el = unsafe { &**el };
+                if let VNode::Element(real_el) = real_el {
+                    //
+                    for listener in real_el.listeners.borrow().iter() {
+                        if listener.event == event.name {
+                            let mut cb = listener.callback.borrow_mut();
+                            if let Some(cb) = cb.as_mut() {
+                                (cb)(event.data.clone());
+                            }
+                        }
+                    }
+
+                    cur_el = real_el.parent_id.get();
+                }
+            }
+        }
+    }
+
     // The head of the bumpframe is the first linked NodeLink
     pub fn wip_head(&self, id: &ScopeId) -> &VNode {
         let scope = self.get_scope(id).unwrap();

+ 174 - 161
packages/core/src/virtual_dom.rs

@@ -7,7 +7,9 @@ use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
 use futures_util::{Future, StreamExt};
 use fxhash::FxHashSet;
 use indexmap::IndexSet;
+use smallvec::SmallVec;
 use std::pin::Pin;
+use std::sync::Arc;
 use std::task::Poll;
 use std::{any::Any, collections::VecDeque};
 
@@ -18,7 +20,7 @@ use std::{any::Any, collections::VecDeque};
 ///
 /// Components are defined as simple functions that take [`Context`] and a [`Properties`] type and return an [`Element`].  
 ///
-/// ```rust
+/// ```rust, ignore
 /// #[derive(Props, PartialEq)]
 /// struct AppProps {
 ///     title: String
@@ -33,7 +35,7 @@ use std::{any::Any, collections::VecDeque};
 ///
 /// Components may be composed to make complex apps.
 ///
-/// ```rust
+/// ```rust, ignore
 /// fn App(cx: Context, props: &AppProps) -> Element {
 ///     cx.render(rsx!(
 ///         NavBar { routes: ROUTES }
@@ -46,14 +48,14 @@ use std::{any::Any, collections::VecDeque};
 /// To start an app, create a [`VirtualDom`] and call [`VirtualDom::rebuild`] to get the list of edits required to
 /// draw the UI.
 ///
-/// ```rust
+/// ```rust, ignore
 /// let mut vdom = VirtualDom::new(App);
 /// let edits = vdom.rebuild();
 /// ```
 ///
 /// To inject UserEvents into the VirtualDom, call [`VirtualDom::get_scheduler_channel`] to get access to the scheduler.
 ///
-/// ```rust
+/// ```rust, ignore
 /// let channel = vdom.get_scheduler_channel();
 /// channel.send_unbounded(SchedulerMsg::UserEvent(UserEvent {
 ///     // ...
@@ -62,7 +64,7 @@ use std::{any::Any, collections::VecDeque};
 ///
 /// While waiting for UserEvents to occur, call [`VirtualDom::wait_for_work`] to poll any futures inside the VirtualDom.
 ///
-/// ```rust
+/// ```rust, ignore
 /// vdom.wait_for_work().await;
 /// ```
 ///
@@ -70,7 +72,7 @@ use std::{any::Any, collections::VecDeque};
 /// current UI trees. This will return a [`Mutations`] object that contains Edits, Effects, and NodeRefs that need to be
 /// handled by the renderer.
 ///
-/// ```rust
+/// ```rust, ignore
 /// let mutations = vdom.work_with_deadline(|| false);
 /// for edit in mutations {
 ///     apply(edit);
@@ -81,7 +83,7 @@ use std::{any::Any, collections::VecDeque};
 ///
 /// Putting everything together, you can build an event loop around Dioxus by using the methods outlined above.
 ///
-/// ```rust
+/// ```rust, ignore
 /// fn App(cx: Context, props: &()) -> Element {
 ///     cx.render(rsx!{
 ///         div { "Hello World" }
@@ -106,7 +108,7 @@ use std::{any::Any, collections::VecDeque};
 pub struct VirtualDom {
     base_scope: ScopeId,
 
-    _root_caller: *mut dyn Fn(&Scope) -> Element,
+    _root_props: Box<dyn Any>,
 
     scopes: Box<ScopeArena>,
 
@@ -114,8 +116,6 @@ pub struct VirtualDom {
 
     sender: UnboundedSender<SchedulerMsg>,
 
-    pending_futures: FxHashSet<ScopeId>,
-
     pending_messages: VecDeque<SchedulerMsg>,
 
     dirty_scopes: IndexSet<ScopeId>,
@@ -134,7 +134,7 @@ impl VirtualDom {
     ///
     ///
     /// # Example
-    /// ```
+    /// ```rust, ignore
     /// fn Example(cx: Context, props: &()) -> Element  {
     ///     cx.render(rsx!( div { "hello world" } ))
     /// }
@@ -158,7 +158,7 @@ impl VirtualDom {
     ///
     ///
     /// # Example
-    /// ```
+    /// ```rust, ignore
     /// #[derive(PartialEq, Props)]
     /// struct SomeProps {
     ///     name: &'static str
@@ -173,7 +173,7 @@ impl VirtualDom {
     ///
     /// Note: the VirtualDOM is not progressed on creation. You must either "run_with_deadline" or use "rebuild" to progress it.
     ///
-    /// ```rust
+    /// ```rust, ignore
     /// let mut dom = VirtualDom::new_with_props(Example, SomeProps { name: "jane" });
     /// let mutations = dom.rebuild();
     /// ```
@@ -194,9 +194,9 @@ impl VirtualDom {
     ) -> Self {
         let scopes = ScopeArena::new(sender.clone());
 
-        let caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) });
-        let caller_ref: *mut dyn Fn(&Scope) -> Element = Box::into_raw(caller);
-        let base_scope = scopes.new_with_key(root as _, caller_ref, None, 0, 0);
+        let mut caller = Box::new(move |scp: &Scope| -> Element { root(scp, &root_props) });
+        let caller_ref: *mut dyn Fn(&Scope) -> Element = caller.as_mut() as *mut _;
+        let base_scope = scopes.new_with_key(root as _, caller_ref, None, ElementId(0), 0, 0);
 
         let pending_messages = VecDeque::new();
         let mut dirty_scopes = IndexSet::new();
@@ -206,10 +206,8 @@ impl VirtualDom {
             scopes: Box::new(scopes),
             base_scope,
             receiver,
-            // todo: clean this up manually?
-            _root_caller: caller_ref,
+            _root_props: caller,
             pending_messages,
-            pending_futures: Default::default(),
             dirty_scopes,
             sender,
         }
@@ -239,7 +237,7 @@ impl VirtualDom {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     ///
     ///
     /// ```
@@ -251,7 +249,7 @@ impl VirtualDom {
     ///
     /// # Example
     ///
-    /// ```rust
+    /// ```rust, ignore
     ///
     ///
     /// ```
@@ -262,104 +260,79 @@ impl VirtualDom {
     /// Waits for the scheduler to have work
     /// This lets us poll async tasks during idle periods without blocking the main thread.
     pub async fn wait_for_work(&mut self) {
-        // todo: poll the events once even if there is work to do to prevent starvation
-
-        // if there's no futures in the virtualdom, just wait for a scheduler message and put it into the queue to be processed
-        if self.pending_futures.is_empty() {
-            self.pending_messages
-                .push_front(self.receiver.next().await.unwrap());
-        } else {
-            struct PollTasks<'a> {
-                pending_futures: &'a FxHashSet<ScopeId>,
-                scopes: &'a ScopeArena,
+        loop {
+            if !self.dirty_scopes.is_empty() && self.pending_messages.is_empty() {
+                break;
             }
 
-            impl<'a> Future for PollTasks<'a> {
-                type Output = ();
-
-                fn poll(
-                    self: Pin<&mut Self>,
-                    cx: &mut std::task::Context<'_>,
-                ) -> Poll<Self::Output> {
-                    let mut all_pending = true;
-
-                    // Poll every scope manually
-                    for fut in self.pending_futures.iter() {
-                        let scope = self
-                            .scopes
-                            .get_scope(fut)
-                            .expect("Scope should never be moved");
-
-                        let mut items = scope.items.borrow_mut();
-                        for task in items.tasks.iter_mut() {
-                            let task = task.as_mut();
-
-                            // todo: does this make sense?
-                            // I don't usually write futures by hand
-                            // I think the futures neeed to be pinned using bumpbox or something
-                            // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
-                            let unpinned = unsafe { Pin::new_unchecked(task) };
-
-                            if unpinned.poll(cx).is_ready() {
-                                all_pending = false
-                            }
-                        }
-                    }
+            if self.pending_messages.is_empty() {
+                if self.scopes.pending_futures.borrow().is_empty() {
+                    self.pending_messages
+                        .push_front(self.receiver.next().await.unwrap());
+                } else {
+                    use futures_util::future::{select, Either};
 
-                    // Resolve the future if any singular task is ready
-                    match all_pending {
-                        true => Poll::Pending,
-                        false => Poll::Ready(()),
+                    match select(PollTasks(&mut self.scopes), self.receiver.next()).await {
+                        Either::Left((_, _)) => {}
+                        Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
                     }
                 }
             }
 
-            // Poll both the futures and the scheduler message queue simulataneously
-            use futures_util::future::{select, Either};
-
-            let scheduler_fut = self.receiver.next();
-            let tasks_fut = PollTasks {
-                pending_futures: &self.pending_futures,
-                scopes: &self.scopes,
-            };
-
-            match select(tasks_fut, scheduler_fut).await {
-                // Futures don't generate work
-                Either::Left((_, _)) => {}
+            while let Ok(Some(msg)) = self.receiver.try_next() {
+                self.pending_messages.push_front(msg);
+            }
 
-                // Save these messages in FIFO to be processed later
-                Either::Right((msg, _)) => self.pending_messages.push_front(msg.unwrap()),
+            if let Some(msg) = self.pending_messages.pop_back() {
+                match msg {
+                    // just keep looping, the task is now saved but we should actually poll it
+                    SchedulerMsg::TaskPushed(id) => {
+                        self.scopes.pending_futures.borrow_mut().insert(id);
+                    }
+                    SchedulerMsg::UiEvent(event) => {
+                        if let Some(element) = event.element {
+                            self.scopes.call_listener_with_bubbling(event, element);
+                        }
+                    }
+                    SchedulerMsg::Immediate(s) => {
+                        self.dirty_scopes.insert(s);
+                    }
+                    SchedulerMsg::Suspended { scope } => todo!(),
+                }
             }
         }
     }
 
     /// Run the virtualdom with a deadline.
     ///
-    /// This method will progress async tasks until the deadline is reached. If tasks are completed before the deadline,
-    /// and no tasks are pending, this method will return immediately. If tasks are still pending, then this method will
-    /// exhaust the deadline working on them.
+    /// This method will perform any outstanding diffing work and try to return as many mutations as possible before the
+    /// deadline is reached. This method accepts a closure that returns `true` if the deadline has been reached. To wrap
+    /// your future into a deadline, consider the `now_or_never` method from `future_utils`.
     ///
-    /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
-    /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
+    /// ```rust, ignore
+    /// let mut vdom = VirtualDom::new(App);
     ///
-    /// Due to platform differences in how time is handled, this method accepts a future that resolves when the deadline
-    /// is exceeded. However, the deadline won't be met precisely, so you might want to build some wiggle room into the
-    /// deadline closure manually.
+    /// let timeout = TimeoutFuture::from_ms(16);
+    /// let deadline = || (&mut timeout).now_or_never();
     ///
-    /// The deadline is polled before starting to diff components. This strikes a balance between the overhead of checking
-    /// the deadline and just completing the work. However, if an individual component takes more than 16ms to render, then
-    /// the screen will "jank" up. In debug, this will trigger an alert.
+    /// let mutations = vdom.work_with_deadline(deadline);
+    /// ```
+    ///
+    /// This method is useful when needing to schedule the virtualdom around other tasks on the main thread to prevent
+    /// "jank". It will try to finish whatever work it has by the deadline to free up time for other work.
     ///
-    /// If there are no in-flight fibers when this method is called, it will await any possible tasks, aborting early if
-    /// the provided deadline future resolves.
+    /// If the work is not finished by the deadline, Dioxus will store it for later and return when work_with_deadline
+    /// is called again. This means you can ensure some level of free time on the VirtualDom's thread during the work phase.
     ///
     /// For use in the web, it is expected that this method will be called to be executed during "idle times" and the
     /// mutations to be applied during the "paint times" IE "animation frames". With this strategy, it is possible to craft
     /// entirely jank-free applications that perform a ton of work.
     ///
+    /// In general use, Dioxus is plenty fast enough to not need to worry about this.
+    ///
     /// # Example
     ///
-    /// ```no_run
+    /// ```rust, ignore
     /// fn App(cx: Context, props: &()) -> Element {
     ///     cx.render(rsx!( div {"hello"} ))
     /// }
@@ -375,58 +348,19 @@ impl VirtualDom {
     ///     apply_mutations(mutations);
     /// }
     /// ```
-    ///
-    /// ## Mutations
-    ///
-    /// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
-    /// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
-    /// applied the edits.
-    ///
-    /// Mutations are the only link between the RealDOM and the VirtualDOM.
-    ///
     pub fn work_with_deadline(&mut self, mut deadline: impl FnMut() -> bool) -> Vec<Mutations> {
         let mut committed_mutations = vec![];
 
-        while self.has_any_work() {
-            while let Ok(Some(msg)) = self.receiver.try_next() {
-                self.pending_messages.push_front(msg);
-            }
-
-            while let Some(msg) = self.pending_messages.pop_back() {
-                match msg {
-                    SchedulerMsg::Immediate(id) => {
-                        self.dirty_scopes.insert(id);
-                    }
-                    SchedulerMsg::UiEvent(event) => {
-                        if let Some(element) = event.mounted_dom_id {
-                            log::info!("Calling listener {:?}, {:?}", event.scope_id, element);
-
-                            if let Some(scope) = self.scopes.get_scope(&event.scope_id) {
-                                // TODO: bubble properly here
-                                scope.call_listener(event, element);
-
-                                while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
-                                    self.pending_messages.push_front(dirty_scope);
-                                }
-                            }
-                        } else {
-                            log::debug!("User event without a targetted ElementId. Not currently supported.\nUnsure how to proceed. {:?}", event);
-                        }
-                    }
-                }
-            }
-
+        while !self.dirty_scopes.is_empty() {
+            log::debug!("working with deadline");
             let scopes = &self.scopes;
             let mut diff_state = DiffState::new(scopes);
 
             let mut ran_scopes = FxHashSet::default();
 
-            // todo: the 2021 version of rust will let us not have to force the borrow
-            // let scopes = &self.scopes;
             // Sort the scopes by height. Theoretically, we'll de-duplicate scopes by height
             self.dirty_scopes
                 .retain(|id| scopes.get_scope(id).is_some());
-
             self.dirty_scopes.sort_by(|a, b| {
                 let h1 = scopes.get_scope(a).unwrap().height;
                 let h2 = scopes.get_scope(b).unwrap().height;
@@ -434,31 +368,27 @@ impl VirtualDom {
             });
 
             if let Some(scopeid) = self.dirty_scopes.pop() {
-                log::info!("handling dirty scope {:?}", scopeid);
-
                 if !ran_scopes.contains(&scopeid) {
                     ran_scopes.insert(scopeid);
 
-                    log::debug!("about to run scope {:?}", scopeid);
-
                     if self.scopes.run_scope(&scopeid) {
                         let (old, new) = (
                             self.scopes.wip_head(&scopeid),
                             self.scopes.fin_head(&scopeid),
                         );
-                        diff_state.stack.scope_stack.push(scopeid);
                         diff_state.stack.push(DiffInstruction::Diff { new, old });
+                        diff_state.stack.scope_stack.push(scopeid);
+
+                        let scope = scopes.get_scope(&scopeid).unwrap();
+                        diff_state.stack.element_stack.push(scope.container);
                     }
                 }
             }
 
-            let work_completed = diff_state.work(&mut deadline);
-
-            if work_completed {
+            if diff_state.work(&mut deadline) {
                 let DiffState {
                     mutations,
                     seen_scopes,
-                    stack,
                     ..
                 } = diff_state;
 
@@ -466,13 +396,9 @@ impl VirtualDom {
                     self.dirty_scopes.remove(&scope);
                 }
 
-                // // I think the stack should be empty at the end of diffing?
-                // debug_assert_eq!(stack.scope_stack.len(), 1);
-
                 committed_mutations.push(mutations);
             } else {
                 // leave the work in an incomplete state
-                log::debug!("don't have a mechanism to pause work (yet)");
                 return committed_mutations;
             }
         }
@@ -490,7 +416,7 @@ impl VirtualDom {
     /// All state stored in components will be completely wiped away.
     ///
     /// # Example
-    /// ```
+    /// ```rust, ignore
     /// static App: FC<()> = |cx, props| cx.render(rsx!{ "hello world" });
     /// let mut dom = VirtualDom::new();
     /// let edits = dom.rebuild();
@@ -506,6 +432,7 @@ impl VirtualDom {
                 .stack
                 .create_node(self.scopes.fin_head(&scope_id), MountType::Append);
 
+            diff_state.stack.element_stack.push(ElementId(0));
             diff_state.stack.scope_stack.push(scope_id);
 
             diff_state.work(|| false);
@@ -521,9 +448,8 @@ impl VirtualDom {
     /// In this case, every component will be diffed, even if their props are memoized. This method is intended to be used
     /// to force an update of the DOM when the state of the app is changed outside of the app.
     ///
-    ///
     /// # Example
-    /// ```rust
+    /// ```rust, ignore
     /// #[derive(PartialEq, Props)]
     /// struct AppProps {
     ///     value: Shared<&'static str>,
@@ -535,12 +461,7 @@ impl VirtualDom {
     /// };
     ///
     /// let value = Rc::new(RefCell::new("Hello"));
-    /// let mut dom = VirtualDom::new_with_props(
-    ///     App,
-    ///     AppProps {
-    ///         value: value.clone(),
-    ///     },
-    /// );
+    /// let mut dom = VirtualDom::new_with_props(App, AppProps { value: value.clone(), });
     ///
     /// let _ = dom.rebuild();
     ///
@@ -574,6 +495,7 @@ impl VirtualDom {
     pub fn diff_vnodes<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> {
         let mut machine = DiffState::new(&self.scopes);
         machine.stack.push(DiffInstruction::Diff { new, old });
+        machine.stack.element_stack.push(ElementId(0));
         machine.stack.scope_stack.push(self.base_scope);
         machine.work(|| false);
         machine.mutations
@@ -585,6 +507,7 @@ impl VirtualDom {
     pub fn create_vnodes<'a>(&'a self, left: Option<LazyNodes<'a, '_>>) -> Mutations<'a> {
         let nodes = self.render_vnodes(left);
         let mut machine = DiffState::new(&self.scopes);
+        machine.stack.element_stack.push(ElementId(0));
         machine.stack.create_node(nodes, MountType::Append);
         machine.work(|| false);
         machine.mutations
@@ -602,11 +525,13 @@ impl VirtualDom {
 
         let mut create = DiffState::new(&self.scopes);
         create.stack.scope_stack.push(self.base_scope);
+        create.stack.element_stack.push(ElementId(0));
         create.stack.create_node(old, MountType::Append);
         create.work(|| false);
 
         let mut edit = DiffState::new(&self.scopes);
-        create.stack.scope_stack.push(self.base_scope);
+        edit.stack.scope_stack.push(self.base_scope);
+        edit.stack.element_stack.push(ElementId(0));
         edit.stack.push(DiffInstruction::Diff { old, new });
         edit.work(&mut || false);
 
@@ -620,17 +545,53 @@ pub enum SchedulerMsg {
 
     // setstate
     Immediate(ScopeId),
+
+    // an async task pushed from an event handler (or just spawned)
+    TaskPushed(ScopeId),
+
+    Suspended { scope: ScopeId },
 }
 
+/// User Events are events that are shuttled from the renderer into the VirtualDom trhough the scheduler channel.
+///
+/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
+/// where each listener is checked and fired if the event name matches.
+///
+/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
+/// attempting to downcast the event data.
+///
+/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
+/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
+///
+/// # Example
+/// ```rust
+/// fn App(cx: Context, props: &()) -> Element {
+///     rsx!(cx, div {
+///         onclick: move |_| println!("Clicked!")
+///     })
+/// }
+///
+/// let mut dom = VirtualDom::new(App);
+/// let mut scheduler = dom.get_scheduler_channel();
+/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
+///     UserEvent {
+///         scope_id: None,
+///         priority: EventPriority::Medium,
+///         name: "click",
+///         element: Some(ElementId(0)),
+///         data: Arc::new(ClickEvent { .. })
+///     }
+/// )).unwrap();
+/// ```
 #[derive(Debug)]
 pub struct UserEvent {
     /// The originator of the event trigger
-    pub scope_id: ScopeId,
+    pub scope_id: Option<ScopeId>,
 
     pub priority: EventPriority,
 
     /// The optional real node associated with the trigger
-    pub mounted_dom_id: Option<ElementId>,
+    pub element: Option<ElementId>,
 
     /// The event type IE "onclick" or "onmouseover"
     ///
@@ -638,7 +599,7 @@ pub struct UserEvent {
     pub name: &'static str,
 
     /// Event Data
-    pub event: Box<dyn Any + Send>,
+    pub data: Arc<dyn Any + Send + Sync>,
 }
 
 /// Priority of Event Triggers.
@@ -692,3 +653,55 @@ pub enum EventPriority {
     /// This is considered "idle" work or "background" work.
     Low = 0,
 }
+
+struct PollTasks<'a>(&'a mut ScopeArena);
+
+impl<'a> Future for PollTasks<'a> {
+    type Output = ();
+
+    fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
+        let mut all_pending = true;
+
+        let mut unfinished_tasks: SmallVec<[_; 10]> = smallvec::smallvec![];
+        let mut scopes_to_clear: SmallVec<[_; 10]> = smallvec::smallvec![];
+
+        // Poll every scope manually
+        for fut in self.0.pending_futures.borrow().iter() {
+            let scope = self.0.get_scope(fut).expect("Scope should never be moved");
+
+            let mut items = scope.items.borrow_mut();
+
+            // really this should just be retain_mut but that doesn't exist yet
+            while let Some(mut task) = items.tasks.pop() {
+                // todo: does this make sense?
+                // I don't usually write futures by hand
+                // I think the futures neeed to be pinned using bumpbox or something
+                // right now, they're bump allocated so this shouldn't matter anyway - they're not going to move
+                let task_mut = task.as_mut();
+                let unpinned = unsafe { Pin::new_unchecked(task_mut) };
+
+                if unpinned.poll(cx).is_ready() {
+                    all_pending = false
+                } else {
+                    unfinished_tasks.push(task);
+                }
+            }
+
+            if unfinished_tasks.is_empty() {
+                scopes_to_clear.push(*fut);
+            }
+
+            items.tasks.extend(unfinished_tasks.drain(..));
+        }
+
+        for scope in scopes_to_clear {
+            self.0.pending_futures.borrow_mut().remove(&scope);
+        }
+
+        // Resolve the future if any singular task is ready
+        match all_pending {
+            true => Poll::Pending,
+            false => Poll::Ready(()),
+        }
+    }
+}

+ 35 - 35
packages/core/tests/create_dom.rs

@@ -37,15 +37,15 @@ fn test_original_diff() {
         mutations.edits,
         [
             CreateElement {
-                root: 0,
+                root: 1,
                 tag: "div"
             },
             CreateElement {
-                root: 1,
+                root: 2,
                 tag: "div"
             },
             CreateTextNode {
-                root: 2,
+                root: 3,
                 text: "Hello, world!"
             },
             AppendChildren { many: 1 },
@@ -82,31 +82,31 @@ fn create() {
         mutations.edits,
         [
             CreateElement {
-                root: 0,
+                root: 1,
                 tag: "div"
             },
             CreateElement {
-                root: 1,
+                root: 2,
                 tag: "div"
             },
             CreateTextNode {
-                root: 2,
+                root: 3,
                 text: "Hello, world!"
             },
             CreateElement {
-                root: 3,
+                root: 4,
                 tag: "div"
             },
             CreateElement {
-                root: 4,
+                root: 5,
                 tag: "div"
             },
             CreateTextNode {
-                root: 5,
+                root: 6,
                 text: "hello"
             },
             CreateTextNode {
-                root: 6,
+                root: 7,
                 text: "world"
             },
             AppendChildren { many: 2 },
@@ -136,29 +136,29 @@ fn create_list() {
         mutations.edits,
         [
             CreateElement {
-                root: 0,
+                root: 1,
                 tag: "div"
             },
             CreateTextNode {
-                root: 1,
+                root: 2,
                 text: "hello"
             },
             AppendChildren { many: 1 },
             CreateElement {
-                root: 2,
+                root: 3,
                 tag: "div"
             },
             CreateTextNode {
-                root: 3,
+                root: 4,
                 text: "hello"
             },
             AppendChildren { many: 1 },
             CreateElement {
-                root: 4,
+                root: 5,
                 tag: "div"
             },
             CreateTextNode {
-                root: 5,
+                root: 6,
                 text: "hello"
             },
             AppendChildren { many: 1 },
@@ -185,10 +185,6 @@ fn create_simple() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement {
-                root: 0,
-                tag: "div"
-            },
             CreateElement {
                 root: 1,
                 tag: "div"
@@ -201,6 +197,10 @@ fn create_simple() {
                 root: 3,
                 tag: "div"
             },
+            CreateElement {
+                root: 4,
+                tag: "div"
+            },
             AppendChildren { many: 4 },
         ]
     );
@@ -234,39 +234,39 @@ fn create_components() {
     assert_eq!(
         mutations.edits,
         [
-            CreateElement { root: 0, tag: "h1" },
+            CreateElement { root: 1, tag: "h1" },
             CreateElement {
-                root: 1,
+                root: 2,
                 tag: "div"
             },
             CreateTextNode {
-                root: 2,
+                root: 3,
                 text: "abc1"
             },
             AppendChildren { many: 1 },
-            CreateElement { root: 3, tag: "p" },
-            CreateElement { root: 4, tag: "h1" },
+            CreateElement { root: 4, tag: "p" },
+            CreateElement { root: 5, tag: "h1" },
             CreateElement {
-                root: 5,
+                root: 6,
                 tag: "div"
             },
             CreateTextNode {
-                root: 6,
+                root: 7,
                 text: "abc2"
             },
             AppendChildren { many: 1 },
-            CreateElement { root: 7, tag: "p" },
-            CreateElement { root: 8, tag: "h1" },
+            CreateElement { root: 8, tag: "p" },
+            CreateElement { root: 9, tag: "h1" },
             CreateElement {
-                root: 9,
+                root: 10,
                 tag: "div"
             },
             CreateTextNode {
-                root: 10,
+                root: 11,
                 text: "abc3"
             },
             AppendChildren { many: 1 },
-            CreateElement { root: 11, tag: "p" },
+            CreateElement { root: 12, tag: "p" },
             AppendChildren { many: 9 },
         ]
     );
@@ -286,15 +286,15 @@ fn anchors() {
         mutations.edits,
         [
             CreateElement {
-                root: 0,
+                root: 1,
                 tag: "div"
             },
             CreateTextNode {
-                root: 1,
+                root: 2,
                 text: "hello"
             },
             AppendChildren { many: 1 },
-            CreatePlaceholder { root: 2 },
+            CreatePlaceholder { root: 3 },
             AppendChildren { many: 2 },
         ]
     );

+ 89 - 89
packages/core/tests/diffing.rs

@@ -32,11 +32,11 @@ fn html_and_rsx_generate_the_same_output() {
         create.edits,
         [
             CreateElement {
-                root: 0,
+                root: 1,
                 tag: "div"
             },
             CreateTextNode {
-                root: 1,
+                root: 2,
                 text: "Hello world"
             },
             AppendChildren { many: 1 },
@@ -48,7 +48,7 @@ fn html_and_rsx_generate_the_same_output() {
         change.edits,
         [SetText {
             text: "Goodbye world",
-            root: 1
+            root: 2
         },]
     );
 }
@@ -68,29 +68,29 @@ fn fragments_create_properly() {
         create.edits,
         [
             CreateElement {
-                root: 0,
+                root: 1,
                 tag: "div"
             },
             CreateTextNode {
-                root: 1,
+                root: 2,
                 text: "Hello a"
             },
             AppendChildren { many: 1 },
             CreateElement {
-                root: 2,
+                root: 3,
                 tag: "div"
             },
             CreateTextNode {
-                root: 3,
+                root: 4,
                 text: "Hello b"
             },
             AppendChildren { many: 1 },
             CreateElement {
-                root: 4,
+                root: 5,
                 tag: "div"
             },
             CreateTextNode {
-                root: 5,
+                root: 6,
                 text: "Hello c"
             },
             AppendChildren { many: 1 },
@@ -111,16 +111,16 @@ fn empty_fragments_create_anchors() {
 
     assert_eq!(
         create.edits,
-        [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
+        [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
     );
     assert_eq!(
         change.edits,
         [
             CreateElement {
-                root: 1,
+                root: 2,
                 tag: "div"
             },
-            ReplaceWith { m: 1, root: 0 }
+            ReplaceWith { m: 1, root: 1 }
         ]
     );
 }
@@ -136,15 +136,11 @@ fn empty_fragments_create_many_anchors() {
     let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         create.edits,
-        [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
+        [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
     );
     assert_eq!(
         change.edits,
         [
-            CreateElement {
-                root: 1,
-                tag: "div"
-            },
             CreateElement {
                 root: 2,
                 tag: "div"
@@ -161,7 +157,11 @@ fn empty_fragments_create_many_anchors() {
                 root: 5,
                 tag: "div"
             },
-            ReplaceWith { m: 5, root: 0 }
+            CreateElement {
+                root: 6,
+                tag: "div"
+            },
+            ReplaceWith { m: 5, root: 1 }
         ]
     );
 }
@@ -182,39 +182,39 @@ fn empty_fragments_create_anchors_with_many_children() {
     let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         create.edits,
-        [CreatePlaceholder { root: 0 }, AppendChildren { many: 1 }]
+        [CreatePlaceholder { root: 1 }, AppendChildren { many: 1 }]
     );
     assert_eq!(
         change.edits,
         [
             CreateElement {
-                root: 1,
+                root: 2,
                 tag: "div"
             },
             CreateTextNode {
                 text: "hello: 0",
-                root: 2
+                root: 3
             },
             AppendChildren { many: 1 },
             CreateElement {
-                root: 3,
+                root: 4,
                 tag: "div"
             },
             CreateTextNode {
                 text: "hello: 1",
-                root: 4
+                root: 5
             },
             AppendChildren { many: 1 },
             CreateElement {
-                root: 5,
+                root: 6,
                 tag: "div"
             },
             CreateTextNode {
                 text: "hello: 2",
-                root: 6
+                root: 7
             },
             AppendChildren { many: 1 },
-            ReplaceWith { m: 3, root: 0 }
+            ReplaceWith { m: 3, root: 1 }
         ]
     );
 }
@@ -236,21 +236,21 @@ fn many_items_become_fragment() {
         create.edits,
         [
             CreateElement {
-                root: 0,
+                root: 1,
                 tag: "div"
             },
             CreateTextNode {
                 text: "hello",
-                root: 1
+                root: 2
             },
             AppendChildren { many: 1 },
             CreateElement {
-                root: 2,
+                root: 3,
                 tag: "div"
             },
             CreateTextNode {
                 text: "hello",
-                root: 3
+                root: 4
             },
             AppendChildren { many: 1 },
             AppendChildren { many: 2 },
@@ -261,9 +261,9 @@ fn many_items_become_fragment() {
     assert_eq!(
         change.edits,
         [
-            Remove { root: 2 },
-            CreatePlaceholder { root: 3 },
-            ReplaceWith { root: 0, m: 1 },
+            Remove { root: 3 },
+            CreatePlaceholder { root: 4 },
+            ReplaceWith { root: 1, m: 1 },
         ]
     );
 }
@@ -308,15 +308,15 @@ fn two_fragments_with_differrent_elements_are_differet() {
         changes.edits,
         [
             // create the new h1s
-            CreateElement { tag: "h1", root: 3 },
             CreateElement { tag: "h1", root: 4 },
             CreateElement { tag: "h1", root: 5 },
-            InsertAfter { root: 1, n: 3 },
-            // replace the divs with new h1s
             CreateElement { tag: "h1", root: 6 },
-            ReplaceWith { root: 0, m: 1 },
+            InsertAfter { root: 2, n: 3 },
+            // replace the divs with new h1s
             CreateElement { tag: "h1", root: 7 },
             ReplaceWith { root: 1, m: 1 },
+            CreateElement { tag: "h1", root: 8 },
+            ReplaceWith { root: 2, m: 1 },
         ]
     );
 }
@@ -339,10 +339,6 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
     assert_eq!(
         create.edits,
         [
-            CreateElement {
-                root: 0,
-                tag: "div"
-            },
             CreateElement {
                 root: 1,
                 tag: "div"
@@ -359,20 +355,24 @@ fn two_fragments_with_differrent_elements_are_differet_shorter() {
                 root: 4,
                 tag: "div"
             },
-            CreateElement { root: 5, tag: "p" },
+            CreateElement {
+                root: 5,
+                tag: "div"
+            },
+            CreateElement { root: 6, tag: "p" },
             AppendChildren { many: 6 },
         ]
     );
     assert_eq!(
         change.edits,
         [
-            Remove { root: 2 },
             Remove { root: 3 },
             Remove { root: 4 },
-            CreateElement { root: 6, tag: "h1" },
-            ReplaceWith { root: 0, m: 1 },
+            Remove { root: 5 },
             CreateElement { root: 7, tag: "h1" },
             ReplaceWith { root: 1, m: 1 },
+            CreateElement { root: 8, tag: "h1" },
+            ReplaceWith { root: 2, m: 1 },
         ]
     );
 }
@@ -396,14 +396,14 @@ fn two_fragments_with_same_elements_are_differet() {
         create.edits,
         [
             CreateElement {
-                root: 0,
+                root: 1,
                 tag: "div"
             },
             CreateElement {
-                root: 1,
+                root: 2,
                 tag: "div"
             },
-            CreateElement { root: 2, tag: "p" },
+            CreateElement { root: 3, tag: "p" },
             AppendChildren { many: 3 },
         ]
     );
@@ -411,18 +411,18 @@ fn two_fragments_with_same_elements_are_differet() {
         change.edits,
         [
             CreateElement {
-                root: 3,
+                root: 4,
                 tag: "div"
             },
             CreateElement {
-                root: 4,
+                root: 5,
                 tag: "div"
             },
             CreateElement {
-                root: 5,
+                root: 6,
                 tag: "div"
             },
-            InsertAfter { root: 1, n: 3 },
+            InsertAfter { root: 2, n: 3 },
         ]
     );
 }
@@ -444,7 +444,7 @@ fn keyed_diffing_order() {
     let (create, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
-        [Remove { root: 2 }, Remove { root: 3 }, Remove { root: 4 },]
+        [Remove { root: 3 }, Remove { root: 4 }, Remove { root: 5 },]
     );
 }
 
@@ -469,7 +469,7 @@ fn keyed_diffing_out_of_order() {
     log::debug!("{:?}", &changes);
     assert_eq!(
         changes.edits,
-        [PushRoot { root: 6 }, InsertBefore { root: 4, n: 1 }]
+        [PushRoot { root: 7 }, InsertBefore { root: 5, n: 1 }]
     );
 }
 
@@ -494,9 +494,9 @@ fn keyed_diffing_out_of_order_adds() {
     assert_eq!(
         change.edits,
         [
+            PushRoot { root: 5 },
             PushRoot { root: 4 },
-            PushRoot { root: 3 },
-            InsertBefore { n: 2, root: 0 }
+            InsertBefore { n: 2, root: 1 }
         ]
     );
 }
@@ -521,9 +521,9 @@ fn keyed_diffing_out_of_order_adds_2() {
     assert_eq!(
         change.edits,
         [
-            PushRoot { root: 3 },
             PushRoot { root: 4 },
-            InsertBefore { n: 2, root: 0 }
+            PushRoot { root: 5 },
+            InsertBefore { n: 2, root: 1 }
         ]
     );
 }
@@ -549,9 +549,9 @@ fn keyed_diffing_out_of_order_adds_3() {
     assert_eq!(
         change.edits,
         [
+            PushRoot { root: 5 },
             PushRoot { root: 4 },
-            PushRoot { root: 3 },
-            InsertBefore { n: 2, root: 1 }
+            InsertBefore { n: 2, root: 2 }
         ]
     );
 }
@@ -577,9 +577,9 @@ fn keyed_diffing_out_of_order_adds_4() {
     assert_eq!(
         change.edits,
         [
+            PushRoot { root: 5 },
             PushRoot { root: 4 },
-            PushRoot { root: 3 },
-            InsertBefore { n: 2, root: 2 }
+            InsertBefore { n: 2, root: 3 }
         ]
     );
 }
@@ -604,7 +604,7 @@ fn keyed_diffing_out_of_order_adds_5() {
     let (_, change) = dom.diff_lazynodes(left, right);
     assert_eq!(
         change.edits,
-        [PushRoot { root: 4 }, InsertBefore { n: 1, root: 3 }]
+        [PushRoot { root: 5 }, InsertBefore { n: 1, root: 4 }]
     );
 }
 
@@ -629,14 +629,14 @@ fn keyed_diffing_additions() {
         change.edits,
         [
             CreateElement {
-                root: 5,
+                root: 6,
                 tag: "div"
             },
             CreateElement {
-                root: 6,
+                root: 7,
                 tag: "div"
             },
-            InsertAfter { n: 2, root: 4 }
+            InsertAfter { n: 2, root: 5 }
         ]
     );
 }
@@ -665,16 +665,16 @@ fn keyed_diffing_additions_and_moves_on_ends() {
             // create 11, 12
             CreateElement {
                 tag: "div",
-                root: 4
+                root: 5
             },
             CreateElement {
                 tag: "div",
-                root: 5
+                root: 6
             },
-            InsertAfter { root: 2, n: 2 },
+            InsertAfter { root: 3, n: 2 },
             // move 7 to the front
-            PushRoot { root: 3 },
-            InsertBefore { root: 0, n: 1 }
+            PushRoot { root: 4 },
+            InsertBefore { root: 1, n: 1 }
         ]
     );
 }
@@ -704,26 +704,26 @@ fn keyed_diffing_additions_and_moves_in_middle() {
             // create 13, 17
             CreateElement {
                 tag: "div",
-                root: 4
+                root: 5
             },
             CreateElement {
                 tag: "div",
-                root: 5
+                root: 6
             },
-            InsertBefore { root: 1, n: 2 },
+            InsertBefore { root: 2, n: 2 },
             // create 11, 12
             CreateElement {
                 tag: "div",
-                root: 6
+                root: 7
             },
             CreateElement {
                 tag: "div",
-                root: 7
+                root: 8
             },
-            InsertBefore { root: 2, n: 2 },
+            InsertBefore { root: 3, n: 2 },
             // move 7
-            PushRoot { root: 3 },
-            InsertBefore { root: 0, n: 1 }
+            PushRoot { root: 4 },
+            InsertBefore { root: 1, n: 1 }
         ]
     );
 }
@@ -751,22 +751,22 @@ fn controlled_keyed_diffing_out_of_order() {
         changes.edits,
         [
             // move 4 to after 6
-            PushRoot { root: 0 },
-            InsertAfter { n: 1, root: 2 },
+            PushRoot { root: 1 },
+            InsertAfter { n: 1, root: 3 },
             // remove 7
 
             // create 9 and insert before 6
             CreateElement {
-                root: 4,
+                root: 5,
                 tag: "div"
             },
-            InsertBefore { n: 1, root: 2 },
+            InsertBefore { n: 1, root: 3 },
             // create 0 and insert before 5
             CreateElement {
-                root: 5,
+                root: 6,
                 tag: "div"
             },
-            InsertBefore { n: 1, root: 1 },
+            InsertBefore { n: 1, root: 2 },
         ]
     );
 }
@@ -793,12 +793,12 @@ fn controlled_keyed_diffing_out_of_order_max_test() {
         changes.edits,
         [
             CreateElement {
-                root: 5,
+                root: 6,
                 tag: "div"
             },
-            InsertBefore { n: 1, root: 2 },
-            PushRoot { root: 3 },
-            InsertBefore { n: 1, root: 0 },
+            InsertBefore { n: 1, root: 3 },
+            PushRoot { root: 4 },
+            InsertBefore { n: 1, root: 1 },
         ]
     );
 }

+ 24 - 26
packages/core/tests/lifecycle.rs

@@ -18,8 +18,6 @@ type Shared<T> = Arc<Mutex<T>>;
 
 #[test]
 fn manual_diffing() {
-    test_logging::set_up_logging(IS_LOGGING_ENABLED);
-
     struct AppProps {
         value: Shared<&'static str>,
     }
@@ -73,33 +71,34 @@ fn events_generate() {
     let mut channel = dom.get_scheduler_channel();
     assert!(dom.has_any_work());
 
-    let edits = dom.work_with_deadline(|| false);
+    let edits = dom.rebuild();
     assert_eq!(
-        edits[0].edits,
+        edits.edits,
         [
             CreateElement {
                 tag: "div",
-                root: 0,
+                root: 1,
             },
             NewEventListener {
                 event_name: "click",
                 scope: ScopeId(0),
-                root: 0,
+                root: 1,
             },
             CreateElement {
                 tag: "div",
-                root: 1,
+                root: 2,
             },
             CreateTextNode {
                 text: "nested",
-                root: 2,
+                root: 3,
             },
             AppendChildren { many: 1 },
             CreateTextNode {
                 text: "Click me!",
-                root: 3,
+                root: 4,
             },
             AppendChildren { many: 2 },
+            AppendChildren { many: 1 },
         ]
     )
 }
@@ -124,7 +123,6 @@ fn components_generate() {
     };
 
     static Child: FC<()> = |cx, _| {
-        println!("running child");
         cx.render(rsx! {
             h1 {}
         })
@@ -137,7 +135,7 @@ fn components_generate() {
         [
             CreateTextNode {
                 text: "Text0",
-                root: 0,
+                root: 1,
             },
             AppendChildren { many: 1 },
         ]
@@ -149,9 +147,9 @@ fn components_generate() {
         [
             CreateElement {
                 tag: "div",
-                root: 1,
+                root: 2,
             },
-            ReplaceWith { root: 0, m: 1 },
+            ReplaceWith { root: 1, m: 1 },
         ]
     );
 
@@ -161,9 +159,9 @@ fn components_generate() {
         [
             CreateTextNode {
                 text: "Text2",
-                root: 2,
+                root: 3,
             },
-            ReplaceWith { root: 1, m: 1 },
+            ReplaceWith { root: 2, m: 1 },
         ]
     );
 
@@ -171,15 +169,15 @@ fn components_generate() {
     assert_eq!(
         edits.edits,
         [
-            CreateElement { tag: "h1", root: 3 },
-            ReplaceWith { root: 2, m: 1 },
+            CreateElement { tag: "h1", root: 4 },
+            ReplaceWith { root: 3, m: 1 },
         ]
     );
 
     let edits = dom.hard_diff(&ScopeId(0)).unwrap();
     assert_eq!(
         edits.edits,
-        [CreatePlaceholder { root: 4 }, ReplaceWith { root: 3, m: 1 },]
+        [CreatePlaceholder { root: 5 }, ReplaceWith { root: 4, m: 1 },]
     );
 
     let edits = dom.hard_diff(&ScopeId(0)).unwrap();
@@ -188,9 +186,9 @@ fn components_generate() {
         [
             CreateTextNode {
                 text: "text 3",
-                root: 5,
+                root: 6,
             },
-            ReplaceWith { root: 4, m: 1 },
+            ReplaceWith { root: 5, m: 1 },
         ]
     );
 
@@ -200,13 +198,13 @@ fn components_generate() {
         [
             CreateTextNode {
                 text: "text 0",
-                root: 6,
+                root: 7,
             },
             CreateTextNode {
                 text: "text 1",
-                root: 7,
+                root: 8,
             },
-            ReplaceWith { root: 5, m: 2 },
+            ReplaceWith { root: 6, m: 2 },
         ]
     );
 
@@ -214,9 +212,9 @@ fn components_generate() {
     assert_eq!(
         edits.edits,
         [
-            CreateElement { tag: "h1", root: 8 },
-            ReplaceWith { root: 6, m: 1 },
-            Remove { root: 7 },
+            CreateElement { tag: "h1", root: 9 },
+            ReplaceWith { root: 7, m: 1 },
+            Remove { root: 8 },
         ]
     );
 }

+ 0 - 41
packages/core/tests/set_state_batch.rs

@@ -1,41 +0,0 @@
-use futures_util::StreamExt;
-
-/*
-furtures_channel provides us some batching simply due to how Rust's async works.
-
-Any hook that uses schedule_update is simply deferring to unbounded_send. Multiple
-unbounded_sends can be linked together in succession provided there isn't an "await"
-between them. Our internal batching mechanism simply waits for the "schedule_update"
-to fire and then pulls any messages off the unbounded_send queue.
-
-Additionally, due to how our "time slicing" works we'll always come back and check
-in for new work if the deadline hasn't expired. On average, our deadline should be
-about 10ms, which is way more than enough for diffing/creating to happen.
-*/
-#[async_std::test]
-async fn batch() {
-    let (sender, mut recver) = futures_channel::mpsc::unbounded::<i32>();
-
-    let _handle = async_std::task::spawn(async move {
-        let _msg = recver.next().await;
-        while let Ok(msg) = recver.try_next() {
-            println!("{:#?}", msg);
-        }
-        let _msg = recver.next().await;
-        while let Ok(msg) = recver.try_next() {
-            println!("{:#?}", msg);
-        }
-    });
-
-    sender.unbounded_send(1).unwrap();
-    sender.unbounded_send(2).unwrap();
-    sender.unbounded_send(3).unwrap();
-    sender.unbounded_send(4).unwrap();
-
-    async_std::task::sleep(std::time::Duration::from_millis(100)).await;
-
-    sender.unbounded_send(5).unwrap();
-    sender.unbounded_send(6).unwrap();
-    sender.unbounded_send(7).unwrap();
-    sender.unbounded_send(8).unwrap();
-}

+ 1 - 1
packages/core/tests/sharedstate.rs

@@ -30,7 +30,7 @@ fn shared_state_test() {
         edits,
         [
             CreateTextNode {
-                root: 0,
+                root: 1,
                 text: "Hello, world!"
             },
             AppendChildren { many: 1 },

+ 23 - 22
packages/core/tests/vdom_rebuild.rs

@@ -11,19 +11,18 @@
 //! Don't have a good way to validate, everything is done manually ATM
 
 use dioxus::prelude::*;
+use dioxus::DomEdit;
 use dioxus_core as dioxus;
 use dioxus_core_macro::*;
 use dioxus_html as dioxus_elements;
+use DomEdit::*;
 
 #[test]
 fn app_runs() {
-    static App: FC<()> = |cx, props| {
-        //
-        cx.render(rsx!( div{"hello"} ))
-    };
+    static App: FC<()> = |cx, props| rsx!(cx, div{"hello"} );
+
     let mut vdom = VirtualDom::new(App);
     let edits = vdom.rebuild();
-    dbg!(edits);
 }
 
 #[test]
@@ -65,9 +64,25 @@ fn conditional_rendering() {
     let mut vdom = VirtualDom::new(App);
 
     let mutations = vdom.rebuild();
-    dbg!(&mutations);
-    // the "false" fragment should generate an empty placeholder to re-visit
-    assert!(mutations.edits[mutations.edits.len() - 2].is("CreatePlaceholder"));
+    assert_eq!(
+        mutations.edits,
+        [
+            CreateElement { root: 1, tag: "h1" },
+            CreateTextNode {
+                root: 2,
+                text: "hello"
+            },
+            AppendChildren { many: 1 },
+            CreateElement {
+                root: 3,
+                tag: "span"
+            },
+            CreateTextNode { root: 4, text: "a" },
+            AppendChildren { many: 1 },
+            CreatePlaceholder { root: 5 },
+            AppendChildren { many: 3 },
+        ]
+    )
 }
 
 #[test]
@@ -88,17 +103,3 @@ fn child_components() {
     let edits = vdom.rebuild();
     dbg!(edits);
 }
-
-#[test]
-fn suspended_works() {
-    todo!()
-    // static App: FC<()> = |cx, props| {
-    //     let title = use_suspense(cx, || async { "bob" }, move |cx, f| todo!());
-    //     // let title = use_suspense(cx, || async { "bob" }, move |cx, f| rsx! { "{f}"});
-    //     cx.render(rsx!("hello" { title }))
-    // };
-
-    // let mut vdom = VirtualDom::new(App);
-    // let edits = vdom.rebuild();
-    // dbg!(edits);
-}

+ 1 - 0
packages/desktop/Cargo.toml

@@ -22,6 +22,7 @@ tokio = { version = "1.12.0", features = [
     "sync",
     "rt-multi-thread",
     "rt",
+    "time",
 ], optional = true, default-features = false }
 dioxus-core-macro = { path = "../core-macro" }
 dioxus-html = { path = "../html", features = ["serialize"] }

+ 34 - 0
packages/desktop/examples/async.rs

@@ -0,0 +1,34 @@
+//! Example: README.md showcase
+//!
+//! The example from the README.md.
+
+use std::time::Duration;
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_hooks::*;
+use dioxus_html as dioxus_elements;
+
+fn main() {
+    dioxus_desktop::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx, props| {
+    let mut count = use_state(cx, || 0);
+
+    cx.push_task(|| async move {
+        tokio::time::sleep(Duration::from_millis(100)).await;
+        count += 1;
+    });
+
+    cx.render(rsx! {
+        div {
+            h1 { "High-Five counter: {count}" }
+            button {
+                onclick: move |_| count.set(0),
+                "Click me!"
+            }
+        }
+    })
+};

+ 23 - 23
packages/desktop/src/events.rs

@@ -11,7 +11,7 @@ use dioxus_html::on::*;
 struct ImEvent {
     event: String,
     mounted_dom_id: u64,
-    scope: u64,
+    // scope: u64,
     contents: serde_json::Value,
 }
 
@@ -20,11 +20,11 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
     let ImEvent {
         event,
         mounted_dom_id,
-        scope,
+        // scope,
         contents,
     } = ims.into_iter().next().unwrap();
 
-    let scope_id = ScopeId(scope as usize);
+    // let scope_id = ScopeId(scope as usize);
     let mounted_dom_id = Some(ElementId(mounted_dom_id as usize));
 
     let name = event_name_from_typ(&event);
@@ -33,75 +33,75 @@ pub fn trigger_from_serialized(val: serde_json::Value) -> UserEvent {
     UserEvent {
         name,
         priority: EventPriority::Low,
-        scope_id,
-        mounted_dom_id,
-        event,
+        scope_id: None,
+        element: mounted_dom_id,
+        data: event,
     }
 }
 
-fn make_synthetic_event(name: &str, val: serde_json::Value) -> Box<dyn Any + Send> {
+fn make_synthetic_event(name: &str, val: serde_json::Value) -> Arc<dyn Any + Send + Sync> {
     match name {
         "copy" | "cut" | "paste" => {
             //
-            Box::new(ClipboardEvent {})
+            Arc::new(ClipboardEvent {})
         }
         "compositionend" | "compositionstart" | "compositionupdate" => {
-            Box::new(serde_json::from_value::<CompositionEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<CompositionEvent>(val).unwrap())
         }
         "keydown" | "keypress" | "keyup" => {
             let evt = serde_json::from_value::<KeyboardEvent>(val).unwrap();
-            Box::new(evt)
+            Arc::new(evt)
         }
         "focus" | "blur" => {
             //
-            Box::new(FocusEvent {})
+            Arc::new(FocusEvent {})
         }
 
         // todo: these handlers might get really slow if the input box gets large and allocation pressure is heavy
         // don't have a good solution with the serialized event problem
         "change" | "input" | "invalid" | "reset" | "submit" => {
-            Box::new(serde_json::from_value::<FormEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<FormEvent>(val).unwrap())
         }
 
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
-            Box::new(serde_json::from_value::<MouseEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<MouseEvent>(val).unwrap())
         }
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
-            Box::new(serde_json::from_value::<PointerEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<PointerEvent>(val).unwrap())
         }
         "select" => {
             //
-            Box::new(serde_json::from_value::<SelectionEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<SelectionEvent>(val).unwrap())
         }
 
         "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
-            Box::new(serde_json::from_value::<TouchEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<TouchEvent>(val).unwrap())
         }
 
-        "scroll" => Box::new(()),
+        "scroll" => Arc::new(()),
 
-        "wheel" => Box::new(serde_json::from_value::<WheelEvent>(val).unwrap()),
+        "wheel" => Arc::new(serde_json::from_value::<WheelEvent>(val).unwrap()),
 
         "animationstart" | "animationend" | "animationiteration" => {
-            Box::new(serde_json::from_value::<AnimationEvent>(val).unwrap())
+            Arc::new(serde_json::from_value::<AnimationEvent>(val).unwrap())
         }
 
-        "transitionend" => Box::new(serde_json::from_value::<TransitionEvent>(val).unwrap()),
+        "transitionend" => Arc::new(serde_json::from_value::<TransitionEvent>(val).unwrap()),
 
         "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted"
         | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play"
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
         | "timeupdate" | "volumechange" | "waiting" => {
             //
-            Box::new(MediaEvent {})
+            Arc::new(MediaEvent {})
         }
 
-        "toggle" => Box::new(ToggleEvent {}),
+        "toggle" => Arc::new(ToggleEvent {}),
 
-        _ => Box::new(()),
+        _ => Arc::new(()),
     }
 }
 

+ 235 - 302
packages/desktop/src/index.js

@@ -1,4 +1,213 @@
 
+function serialize_event(event) {
+  switch (event.type) {
+    case "copy":
+    case "cut":
+    case "past":
+      return {};
+
+    case "compositionend":
+    case "compositionstart":
+    case "compositionupdate":
+      return {
+        data: event.data
+      }
+
+    case "keydown":
+    case "keypress":
+    case "keyup":
+      return {
+        char_code: event.charCode,
+        key: event.key,
+        alt_key: event.altKey,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        key_code: event.keyCode,
+        shift_key: event.shiftKey,
+        locale: "locale",
+        location: event.location,
+        repeat: event.repeat,
+        which: event.which,
+        // locale: event.locale,
+      }
+
+    case "focus":
+    case "blur":
+      return {};
+
+    case "change":
+      let target = event.target;
+      let value;
+      if (target.type === "checkbox" || target.type === "radio") {
+        value = target.checked ? "true" : "false";
+      } else {
+        value = target.value ?? target.textContent;
+      }
+
+      return {
+        value: value
+      };
+
+    case "input":
+    case "invalid":
+    case "reset":
+    case "submit": {
+      let target = event.target;
+      let value = target.value ?? target.textContent;
+      return {
+        value: value
+      };
+    }
+
+    case "click":
+    case "contextmenu":
+    case "doubleclick":
+    case "drag":
+    case "dragend":
+    case "dragenter":
+    case "dragexit":
+    case "dragleave":
+    case "dragover":
+    case "dragstart":
+    case "drop":
+    case "mousedown":
+    case "mouseenter":
+    case "mouseleave":
+    case "mousemove":
+    case "mouseout":
+    case "mouseover":
+    case "mouseup":
+      return {
+        alt_key: event.altKey,
+        button: event.button,
+        buttons: event.buttons,
+        client_x: event.clientX,
+        client_y: event.clientY,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        page_x: event.pageX,
+        page_y: event.pageY,
+        screen_x: event.screenX,
+        screen_y: event.screenY,
+        shift_key: event.shiftKey,
+      }
+
+    case "pointerdown":
+    case "pointermove":
+    case "pointerup":
+    case "pointercancel":
+    case "gotpointercapture":
+    case "lostpointercapture":
+    case "pointerenter":
+    case "pointerleave":
+    case "pointerover":
+    case "pointerout":
+      return {
+        alt_key: event.altKey,
+        button: event.button,
+        buttons: event.buttons,
+        client_x: event.clientX,
+        client_y: event.clientY,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        page_x: event.pageX,
+        page_y: event.pageY,
+        screen_x: event.screenX,
+        screen_y: event.screenY,
+        shift_key: event.shiftKey,
+        pointer_id: event.pointerId,
+        width: event.width,
+        height: event.height,
+        pressure: event.pressure,
+        tangential_pressure: event.tangentialPressure,
+        tilt_x: event.tiltX,
+        tilt_y: event.tiltY,
+        twist: event.twist,
+        pointer_type: event.pointerType,
+        is_primary: event.isPrimary,
+      }
+
+    case "select":
+      return {};
+
+    case "touchcancel":
+    case "touchend":
+    case "touchmove":
+    case "touchstart":
+      return {
+        alt_key: event.altKey,
+        ctrl_key: event.ctrlKey,
+        meta_key: event.metaKey,
+        shift_key: event.shiftKey,
+
+        // changed_touches: event.changedTouches,
+        // target_touches: event.targetTouches,
+        // touches: event.touches,
+      }
+
+    case "scroll":
+      return {};
+
+    case "wheel":
+      return {
+        delta_x: event.deltaX,
+        delta_y: event.deltaY,
+        delta_z: event.deltaZ,
+        delta_mode: event.deltaMode,
+      };
+
+    case "animationstart":
+    case "animationend":
+    case "animationiteration":
+      return {
+        animation_name: event.animationName,
+        elapsed_time: event.elapsedTime,
+        pseudo_element: event.pseudoElement,
+      };
+
+    case "transitionend":
+      return {
+        property_name: event.propertyName,
+        elapsed_time: event.elapsedTime,
+        pseudo_element: event.pseudoElement,
+      };
+
+    case "abort":
+    case "canplay":
+    case "canplaythrough":
+    case "durationchange":
+    case "emptied":
+    case "encrypted":
+    case "ended":
+    case "error":
+    case "loadeddata":
+    case "loadedmetadata":
+    case "loadstart":
+    case "pause":
+    case "play":
+    case "playing":
+    case "progress":
+    case "ratechange":
+    case "seeked":
+    case "seeking":
+    case "stalled":
+    case "suspend":
+    case "timeupdate":
+    case "volumechange":
+    case "waiting":
+      return {};
+
+    case "toggle":
+      return {};
+
+    default:
+      return {};
+  }
+
+
+}
+
+
 class Interpreter {
   constructor(root) {
     this.root = root;
@@ -39,14 +248,9 @@ class Interpreter {
   }
 
   ReplaceWith(edit) {
-    // console.log(edit);
     let root = this.nodes[edit.root];
     let els = this.stack.splice(this.stack.length - edit.m);
 
-    // console.log(root);
-    // console.log(els);
-
-
     root.replaceWith(...els);
   }
 
@@ -79,6 +283,7 @@ class Interpreter {
     const tagName = edit.tag;
     const el = document.createElement(tagName);
     this.nodes[edit.root] = el;
+    el.setAttribute("dioxus-id", edit.root);
     this.stack.push(el);
   }
 
@@ -110,14 +315,14 @@ class Interpreter {
       this.root.addEventListener(event_name, (event) => {
         // console.log("CLICKED");
         const target = event.target;
-        const val = target.getAttribute(`dioxus-event-${event_name}`);
-        if (val == null) {
+        const real_id = target.getAttribute(`dioxus-id`);
+        if (real_id == null) {
           return;
         }
 
-        const fields = val.split(".");
-        const scope_id = parseInt(fields[0]);
-        const real_id = parseInt(fields[1]);
+        // const fields = val.split(".");
+        // const scope_id = parseInt(fields[0]);
+        // const real_id = parseInt(fields[1]);
 
         // // console.log(`parsed event with scope_id ${scope_id} and real_id ${real_id}`);
 
@@ -125,26 +330,12 @@ class Interpreter {
         let contents = serialize_event(event);
         let evt = {
           event: event_name,
-          scope: scope_id,
-          mounted_dom_id: real_id,
+          // scope: scope_id,
+          mounted_dom_id: parseInt(real_id),
           contents: contents,
         };
-        // console.log(evt);
-        rpc.call('user_event', evt).then((reply) => {
-          // console.log("reply received", reply);
-
-          this.stack.push(this.root);
-
-          reply.map((reply) => {
-            let edits = reply.edits;
-            for (let x = 0; x < edits.length; x++) {
-              let edit = edits[x];
-              let f = this[edit.type];
-              f.call(this, edit);
-            }
-          });
-
-        })
+
+        rpc.call('user_event', evt);
       });
     }
   }
@@ -201,286 +392,28 @@ class Interpreter {
     }
   }
 
-}
-
-async function initialize() {
-  let root = window.document.getElementById("_dioxusroot");
-  const interpreter = new Interpreter(root);
-
-  const reply = await rpc.call('initiate');
-
-  let pre_rendered = reply.pre_rendered;
-  if (pre_rendered !== undefined) {
-    root.innerHTML = pre_rendered;
-  }
+  handleEdits(edits) {
 
-  const edits = reply.edits;
+    // console.log("handling raw edits", rawedits);
+    // let edits = JSON.parse(rawedits);
+    // console.log("handling  edits", edits);
 
-  apply_edits(edits, interpreter);
-}
-
-function apply_edits(edits, interpreter) {
-  // console.log(edits);
-  for (let x = 0; x < edits.length; x++) {
-    let edit = edits[x];
-    let f = interpreter[edit.type];
-    f.call(interpreter, edit);
-  }
-
-  // // console.log("stack completed: ", interpreter.stack);
-}
-
-function serialize_event(event) {
-  let serializer = SerializeMap[event.type];
-  if (serializer === undefined) {
-    return {};
-  } else {
-    return serializer(event);
-  }
-}
+    this.stack.push(this.root);
 
-const SerializeMap = {
-  "copy": serialize_clipboard,
-  "cut": serialize_clipboard,
-  "paste": serialize_clipboard,
-
-  "compositionend": serialize_composition,
-  "compositionstart": serialize_composition,
-  "compositionupdate": serialize_composition,
-
-  "keydown": serialize_keyboard,
-  "keypress": serialize_keyboard,
-  "keyup": serialize_keyboard,
-
-  "focus": serialize_focus,
-  "blur": serialize_focus,
-
-  // "change": serialize_form,
-  "change": serialize_change,
-
-  "input": serialize_form,
-  "invalid": serialize_form,
-  "reset": serialize_form,
-  "submit": serialize_form,
-
-  "click": serialize_mouse,
-  "contextmenu": serialize_mouse,
-  "doubleclick": serialize_mouse,
-  "drag": serialize_mouse,
-  "dragend": serialize_mouse,
-  "dragenter": serialize_mouse,
-  "dragexit": serialize_mouse,
-  "dragleave": serialize_mouse,
-  "dragover": serialize_mouse,
-  "dragstart": serialize_mouse,
-  "drop": serialize_mouse,
-  "mousedown": serialize_mouse,
-  "mouseenter": serialize_mouse,
-  "mouseleave": serialize_mouse,
-  "mousemove": serialize_mouse,
-  "mouseout": serialize_mouse,
-  "mouseover": serialize_mouse,
-  "mouseup": serialize_mouse,
-
-  "pointerdown": serialize_pointer,
-  "pointermove": serialize_pointer,
-  "pointerup": serialize_pointer,
-  "pointercancel": serialize_pointer,
-  "gotpointercapture": serialize_pointer,
-  "lostpointercapture": serialize_pointer,
-  "pointerenter": serialize_pointer,
-  "pointerleave": serialize_pointer,
-  "pointerover": serialize_pointer,
-  "pointerout": serialize_pointer,
-
-  "select": serialize_selection,
-
-  "touchcancel": serialize_touch,
-  "touchend": serialize_touch,
-  "touchmove": serialize_touch,
-  "touchstart": serialize_touch,
-
-  "scroll": serialize_scroll,
-
-  "wheel": serialize_wheel,
-
-  "animationstart": serialize_animation,
-  "animationend": serialize_animation,
-  "animationiteration": serialize_animation,
-
-  "transitionend": serialize_transition,
-
-  "abort": serialize_media,
-  "canplay": serialize_media,
-  "canplaythrough": serialize_media,
-  "durationchange": serialize_media,
-  "emptied": serialize_media,
-  "encrypted": serialize_media,
-  "ended": serialize_media,
-  "error": serialize_media,
-  "loadeddata": serialize_media,
-  "loadedmetadata": serialize_media,
-  "loadstart": serialize_media,
-  "pause": serialize_media,
-  "play": serialize_media,
-  "playing": serialize_media,
-  "progress": serialize_media,
-  "ratechange": serialize_media,
-  "seeked": serialize_media,
-  "seeking": serialize_media,
-  "stalled": serialize_media,
-  "suspend": serialize_media,
-  "timeupdate": serialize_media,
-  "volumechange": serialize_media,
-  "waiting": serialize_media,
-
-  "toggle": serialize_toggle
-}
-
-function serialize_clipboard(_event) {
-  return {};
-}
-function serialize_composition(event) {
-  return {
-    data: event.data
-  }
-}
-function serialize_keyboard(event) {
-  return {
-    char_code: event.charCode,
-    key: event.key,
-    alt_key: event.altKey,
-    ctrl_key: event.ctrlKey,
-    meta_key: event.metaKey,
-    key_code: event.keyCode,
-    shift_key: event.shiftKey,
-    locale: "locale",
-    // locale: event.locale,
-    location: event.location,
-    repeat: event.repeat,
-    which: event.which,
-  }
-}
-function serialize_focus(_event) {
-  return {}
-}
-
-function serialize_change(event) {
-  let target = event.target;
-  let value;
-  if (target.type === "checkbox" || target.type === "radio") {
-    value = target.checked ? "true" : "false";
-  } else {
-    value = target.value ?? target.textContent;
-  }
-
-  return {
-    value: value
-  }
-}
-function serialize_form(event) {
-  let target = event.target;
-  let value = target.value ?? target.textContent;
-  return {
-    value: value
-  }
-}
-function serialize_mouse(event) {
-  return {
-    alt_key: event.altKey,
-    button: event.button,
-    buttons: event.buttons,
-    client_x: event.clientX,
-    client_y: event.clientY,
-    ctrl_key: event.ctrlKey,
-    meta_key: event.metaKey,
-    page_x: event.pageX,
-    page_y: event.pageY,
-    screen_x: event.screenX,
-    screen_y: event.screenY,
-    shift_key: event.shiftKey,
-  }
-}
-
-function serialize_pointer(event) {
-  return {
-    alt_key: event.altKey,
-    button: event.button,
-    buttons: event.buttons,
-    client_x: event.clientX,
-    client_y: event.clientY,
-    ctrl_key: event.ctrlKey,
-    meta_key: event.metaKey,
-    page_x: event.pageX,
-    page_y: event.pageY,
-    screen_x: event.screenX,
-    screen_y: event.screenY,
-    shift_key: event.shiftKey,
-    pointer_id: event.pointerId,
-    width: event.width,
-    height: event.height,
-    pressure: event.pressure,
-    tangential_pressure: event.tangentialPressure,
-    tilt_x: event.tiltX,
-    tilt_y: event.tiltY,
-    twist: event.twist,
-    pointer_type: event.pointerType,
-    is_primary: event.isPrimary,
-  }
-}
-
-function serialize_selection(event) {
-  return {}
-}
-
-function serialize_touch(event) {
-  return {
-    alt_key: event.altKey,
-    ctrl_key: event.ctrlKey,
-    meta_key: event.metaKey,
-    shift_key: event.shiftKey,
-
-    // changed_touches: event.changedTouches,
-    // target_touches: event.targetTouches,
-    // touches: event.touches,
-  }
-}
-function serialize_scroll(event) {
-  return {}
-}
-
-function serialize_wheel(event) {
-  return {
-    delta_x: event.deltaX,
-    delta_y: event.deltaY,
-    delta_z: event.deltaZ,
-    delta_mode: event.deltaMode,
-  }
-}
-
-function serialize_animation(event) {
-  return {
-    animation_name: event.animationName,
-    elapsed_time: event.elapsedTime,
-    pseudo_element: event.pseudoElement,
-  }
-}
-
-function serialize_transition(event) {
-  return {
-    property_name: event.propertyName,
-    elapsed_time: event.elapsedTime,
-    pseudo_element: event.pseudoElement,
+    for (let x = 0; x < edits.length; x++) {
+      let edit = edits[x];
+      let f = this[edit.type];
+      f.call(this, edit);
+    }
   }
 }
 
-function serialize_media(event) {
-  return {}
-}
+function main() {
+  let root = window.document.getElementById("_dioxusroot");
+  window.interpreter = new Interpreter(root);
+  console.log(window.interpreter);
 
-function serialize_toggle(event) {
-  return {}
+  rpc.call('initialize');
 }
 
-
-initialize();
+main()

+ 121 - 158
packages/desktop/src/lib.rs

@@ -5,7 +5,7 @@
 
 use std::borrow::BorrowMut;
 use std::cell::{Cell, RefCell};
-use std::collections::HashMap;
+use std::collections::{HashMap, VecDeque};
 use std::ops::{Deref, DerefMut};
 use std::rc::Rc;
 use std::sync::atomic::AtomicBool;
@@ -20,10 +20,10 @@ pub use wry;
 
 use wry::application::accelerator::{Accelerator, SysMods};
 use wry::application::event::{ElementState, Event, StartCause, WindowEvent};
-use wry::application::event_loop::{self, ControlFlow, EventLoop};
+use wry::application::event_loop::{self, ControlFlow, EventLoop, EventLoopWindowTarget};
 use wry::application::keyboard::{Key, KeyCode, ModifiersState};
 use wry::application::menu::{MenuBar, MenuItem, MenuItemAttributes};
-use wry::application::window::Fullscreen;
+use wry::application::window::{Fullscreen, WindowId};
 use wry::webview::{WebView, WebViewBuilder};
 use wry::{
     application::menu,
@@ -59,12 +59,6 @@ enum RpcEvent<'a> {
     Initialize { edits: Vec<DomEdit<'a>> },
 }
 
-#[derive(Debug)]
-enum BridgeEvent {
-    Initialize(serde_json::Value),
-    Update(serde_json::Value),
-}
-
 #[derive(Serialize)]
 struct Response<'a> {
     pre_rendered: Option<String>,
@@ -88,111 +82,29 @@ pub fn run<T: 'static + Send + Sync>(
 
     // All of our webview windows are stored in a way that we can look them up later
     // The "DesktopContext" will provide functionality for spawning these windows
-    let mut webviews = HashMap::new();
+    let mut webviews = HashMap::<WindowId, WebView>::new();
     let event_loop = EventLoop::new();
 
     let props_shared = Cell::new(Some(props));
 
     // create local modifier state
-    let mut modifiers = ModifiersState::default();
+    let modifiers = ModifiersState::default();
 
     let quit_hotkey = Accelerator::new(SysMods::Cmd, KeyCode::KeyQ);
 
-    event_loop.run(move |event, event_loop, control_flow| {
+    let edit_queue = Arc::new(RwLock::new(VecDeque::new()));
+    let is_ready: Arc<AtomicBool> = Default::default();
+
+    event_loop.run(move |window_event, event_loop, control_flow| {
         *control_flow = ControlFlow::Wait;
 
-        match event {
+        match window_event {
             Event::NewEvents(StartCause::Init) => {
-                // create main menubar menu
-                let mut menu_bar_menu = MenuBar::new();
-
-                // create `first_menu`
-                let mut first_menu = MenuBar::new();
-
-                first_menu.add_native_item(MenuItem::About("Todos".to_string()));
-                first_menu.add_native_item(MenuItem::Services);
-                first_menu.add_native_item(MenuItem::Separator);
-                first_menu.add_native_item(MenuItem::Hide);
-                first_menu.add_native_item(MenuItem::HideOthers);
-                first_menu.add_native_item(MenuItem::ShowAll);
-
-                first_menu.add_native_item(MenuItem::Quit);
-                first_menu.add_native_item(MenuItem::CloseWindow);
-
-                // create second menu
-                let mut second_menu = MenuBar::new();
-
-                // second_menu.add_submenu("Sub menu", true, my_sub_menu);
-                second_menu.add_native_item(MenuItem::Copy);
-                second_menu.add_native_item(MenuItem::Paste);
-                second_menu.add_native_item(MenuItem::SelectAll);
-
-                menu_bar_menu.add_submenu("First menu", true, first_menu);
-                menu_bar_menu.add_submenu("Second menu", true, second_menu);
-
-                let window = WindowBuilder::new()
-                    .with_maximized(true)
-                    .with_menu(menu_bar_menu)
-                    .with_title("Dioxus App")
-                    .build(event_loop)
-                    .unwrap();
+                let window = create_window(event_loop);
                 let window_id = window.id();
-
-                let (event_tx, event_rx) = tokio::sync::mpsc::unbounded_channel();
-                let my_props = props_shared.take().unwrap();
-
-                let sender = launch_vdom_with_tokio(root, my_props, event_tx);
-
-                let locked_receiver = Rc::new(RefCell::new(event_rx));
-
-                let webview = WebViewBuilder::new(window)
-                    .unwrap()
-                    .with_url("wry://index.html")
-                    .unwrap()
-                    .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
-                        let mut rx = (*locked_receiver).borrow_mut();
-                        match req.method.as_str() {
-                            "initiate" => {
-                                if let Ok(BridgeEvent::Initialize(edits)) = rx.try_recv() {
-                                    Some(RpcResponse::new_result(req.id.take(), Some(edits)))
-                                } else {
-                                    None
-                                }
-                            }
-                            "user_event" => {
-                                let event = events::trigger_from_serialized(req.params.unwrap());
-                                log::debug!("User event: {:?}", event);
-
-                                sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
-
-                                if let Some(BridgeEvent::Update(edits)) = rx.blocking_recv() {
-                                    log::info!("bridge received message");
-                                    Some(RpcResponse::new_result(req.id.take(), Some(edits)))
-                                } else {
-                                    log::info!("none received message");
-                                    None
-                                }
-                            }
-                            _ => None,
-                        }
-                    })
-                    // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
-                    // For now, we only serve two pieces of content which get included as bytes into the final binary.
-                    .with_custom_protocol("wry".into(), move |request| {
-                        let path = request.uri().replace("wry://", "");
-                        let (data, meta) = match path.as_str() {
-                            "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"),
-                            "index.html/index.js" => {
-                                (include_bytes!("./index.js").to_vec(), "text/javascript")
-                            }
-                            _ => unimplemented!("path {}", path),
-                        };
-
-                        wry::http::ResponseBuilder::new().mimetype(meta).body(data)
-                    })
-                    .build()
-                    .unwrap();
-
+                let sender =
+                    launch_vdom_with_tokio(root, props_shared.take().unwrap(), edit_queue.clone());
+                let webview = create_webview(window, is_ready.clone(), sender);
                 webviews.insert(window_id, webview);
             }
 
@@ -206,29 +118,17 @@ pub fn run<T: 'static + Send + Sync>(
                         *control_flow = ControlFlow::Exit;
                     }
                 }
-                // catch only pressed event
+                WindowEvent::Moved(pos) => {
+                    //
+                }
+
                 WindowEvent::KeyboardInput { event, .. } => {
-                    log::debug!("keybowrd input");
                     if quit_hotkey.matches(&modifiers, &event.physical_key) {
-                        log::debug!("quitting");
-
                         webviews.remove(&window_id);
                         if webviews.is_empty() {
                             *control_flow = ControlFlow::Exit;
                         }
                     }
-
-                    // println!(
-                    //     "KeyEvent:  `Shift` + `1` | logical_key: {:?}",
-                    //     &event.logical_key
-                    // );
-                    // we can match manually without `Accelerator`
-
-                    // else if event.key_without_modifiers() == Key::Character("1")
-                    //     && modifiers.is_empty()
-                    // {
-                    //     println!("KeyEvent: `1`");
-                    // }
                 }
 
                 WindowEvent::Resized(_) | WindowEvent::Moved(_) => {
@@ -240,7 +140,18 @@ pub fn run<T: 'static + Send + Sync>(
                 _ => {}
             },
 
-            Event::MainEventsCleared => {}
+            Event::MainEventsCleared => {
+                // I hate this ready hack but it's needed to wait for the "onload" to occur
+                // We can't run any initializion scripts because the window isn't ready yet?
+                if is_ready.load(std::sync::atomic::Ordering::Relaxed) {
+                    let mut queue = edit_queue.write().unwrap();
+                    let (id, view) = webviews.iter_mut().next().unwrap();
+                    while let Some(edit) = queue.pop_back() {
+                        view.evaluate_script(&format!("window.interpreter.handleEdits({})", edit))
+                            .unwrap();
+                    }
+                }
+            }
             Event::Resumed => {}
             Event::Suspended => {}
             Event::LoopDestroyed => {}
@@ -250,19 +161,11 @@ pub fn run<T: 'static + Send + Sync>(
     })
 }
 
-pub fn start<P: 'static + Send>(
-    root: FC<P>,
-    config_builder: impl for<'a, 'b> FnOnce(&'b mut DesktopConfig<'a>) -> &'b mut DesktopConfig<'a>,
-) -> ((), ()) {
-    //
-    ((), ())
-}
-
 // Create a new tokio runtime on a dedicated thread and then launch the apps VirtualDom.
 pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
     root: FC<P>,
     props: P,
-    event_tx: tokio::sync::mpsc::UnboundedSender<BridgeEvent>,
+    edit_queue: Arc<RwLock<VecDeque<String>>>,
 ) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
     let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
     let return_sender = sender.clone();
@@ -275,47 +178,107 @@ pub(crate) fn launch_vdom_with_tokio<P: Send + 'static>(
             .unwrap();
 
         runtime.block_on(async move {
-            let mut vir = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
-            let _ = vir.get_scheduler_channel();
+            let mut dom = VirtualDom::new_with_props_and_scheduler(root, props, sender, receiver);
+
+            let edits = dom.rebuild();
 
-            let edits = vir.rebuild();
+            edit_queue
+                .write()
+                .unwrap()
+                .push_front(serde_json::to_string(&edits.edits).unwrap());
 
-            // the receiving end expects something along these lines
-            #[derive(Serialize)]
-            struct Evt<'a> {
-                edits: Vec<DomEdit<'a>>,
+            loop {
+                dom.wait_for_work().await;
+                let mut muts = dom.work_with_deadline(|| false);
+                while let Some(edit) = muts.pop() {
+                    edit_queue
+                        .write()
+                        .unwrap()
+                        .push_front(serde_json::to_string(&edit.edits).unwrap());
+                }
             }
+        })
+    });
 
-            let edit_string = serde_json::to_value(Evt { edits: edits.edits }).unwrap();
+    return_sender
+}
 
-            event_tx
-                .send(BridgeEvent::Initialize(edit_string))
-                .expect("Sending should not fail");
+fn build_menu() -> MenuBar {
+    // create main menubar menu
+    let mut menu_bar_menu = MenuBar::new();
 
-            loop {
-                vir.wait_for_work().await;
-                // we're running on our own thread, so we don't need to worry about blocking anything
-                // todo: maybe we want to schedule ourselves in
-                // on average though, the virtualdom running natively is stupid fast
+    // create `first_menu`
+    let mut first_menu = MenuBar::new();
 
-                let mut muts = vir.work_with_deadline(|| false);
+    first_menu.add_native_item(MenuItem::About("Todos".to_string()));
+    first_menu.add_native_item(MenuItem::Services);
+    first_menu.add_native_item(MenuItem::Separator);
+    first_menu.add_native_item(MenuItem::Hide);
+    first_menu.add_native_item(MenuItem::HideOthers);
+    first_menu.add_native_item(MenuItem::ShowAll);
 
-                log::debug!("finished running with deadline");
+    first_menu.add_native_item(MenuItem::Quit);
+    first_menu.add_native_item(MenuItem::CloseWindow);
 
-                let mut edits = vec![];
+    // create second menu
+    let mut second_menu = MenuBar::new();
 
-                while let Some(edit) = muts.pop() {
-                    let edit_string = serde_json::to_value(Evt { edits: edit.edits })
-                        .expect("serializing edits should never fail");
-                    edits.push(edit_string);
-                }
+    // second_menu.add_submenu("Sub menu", true, my_sub_menu);
+    second_menu.add_native_item(MenuItem::Copy);
+    second_menu.add_native_item(MenuItem::Paste);
+    second_menu.add_native_item(MenuItem::SelectAll);
+
+    menu_bar_menu.add_submenu("First menu", true, first_menu);
+    menu_bar_menu.add_submenu("Second menu", true, second_menu);
+
+    menu_bar_menu
+}
 
-                event_tx
-                    .send(BridgeEvent::Update(serde_json::Value::Array(edits)))
-                    .expect("Sending should not fail");
+fn create_window(event_loop: &EventLoopWindowTarget<()>) -> Window {
+    WindowBuilder::new()
+        .with_maximized(true)
+        .with_menu(build_menu())
+        .with_title("Dioxus App")
+        .build(event_loop)
+        .unwrap()
+}
+
+fn create_webview(
+    window: Window,
+    is_ready: Arc<AtomicBool>,
+    sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
+) -> WebView {
+    WebViewBuilder::new(window)
+        .unwrap()
+        .with_url("wry://index.html")
+        .unwrap()
+        .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
+            match req.method.as_str() {
+                "user_event" => {
+                    let event = events::trigger_from_serialized(req.params.unwrap());
+                    log::debug!("User event: {:?}", event);
+                    sender.unbounded_send(SchedulerMsg::UiEvent(event)).unwrap();
+                }
+                "initialize" => {
+                    is_ready.store(true, std::sync::atomic::Ordering::Relaxed);
+                }
+                _ => {}
             }
-        })
-    });
 
-    return_sender
+            None
+        })
+        // Any content that that uses the `wry://` scheme will be shuttled through this handler as a "special case"
+        // For now, we only serve two pieces of content which get included as bytes into the final binary.
+        .with_custom_protocol("wry".into(), move |request| {
+            let path = request.uri().replace("wry://", "");
+            let (data, meta) = match path.as_str() {
+                "index.html" => (include_bytes!("./index.html").to_vec(), "text/html"),
+                "index.html/index.js" => (include_bytes!("./index.js").to_vec(), "text/javascript"),
+                _ => unimplemented!("path {}", path),
+            };
+
+            wry::http::ResponseBuilder::new().mimetype(meta).body(data)
+        })
+        .build()
+        .unwrap()
 }

+ 0 - 0
packages/desktop/src/serialize.js


+ 0 - 2
packages/hooks/src/use_shared_state.rs

@@ -17,8 +17,6 @@ pub(crate) struct ProvidedStateInner<T> {
 impl<T> ProvidedStateInner<T> {
     pub(crate) fn notify_consumers(&mut self) {
         for consumer in self.consumers.iter() {
-            println!("notifiying {:?}", consumer);
-            // log::debug("notifiying {:?}", consumer);
             (self.notify_any)(*consumer);
         }
     }

+ 4 - 1
packages/hooks/src/usestate.rs

@@ -126,7 +126,10 @@ impl<'a, T: 'static> UseState<'a, T> {
 
     pub fn setter(&self) -> Rc<dyn Fn(T)> {
         let slot = self.inner.wip.clone();
-        Rc::new(move |new| *slot.borrow_mut() = Some(new))
+        Rc::new(move |new| {
+            //
+            *slot.borrow_mut() = Some(new);
+        })
     }
 
     pub fn for_async(&self) -> AsyncUseState<T> {

+ 5 - 4
packages/html/src/events.rs

@@ -5,6 +5,7 @@ use std::any::Any;
 
 pub mod on {
     use super::*;
+    use std::sync::Arc;
     macro_rules! event_directory {
         ( $(
             $( #[$attr:meta] )*
@@ -23,19 +24,19 @@ pub mod on {
                         c: NodeFactory<'a>,
                         mut callback: F,
                     ) -> Listener<'a>
-                        where F: FnMut($wrapper) + 'a
+                        where F: FnMut(Arc<$wrapper>) + 'a
                     {
                         let bump = &c.bump();
 
                         // we can't allocate unsized in bumpalo's box, so we need to craft the box manually
                         // safety: this is essentially the same as calling Box::new() but manually
                         // The box is attached to the lifetime of the bumpalo allocator
-                        let cb: &mut dyn FnMut(Box<dyn Any + Send>) = bump.alloc(move |evt: Box<dyn Any + Send>| {
+                        let cb: &mut dyn FnMut(Arc<dyn Any + Send + Sync>) = bump.alloc(move |evt: Arc<dyn Any + Send + Sync>| {
                             let event = evt.downcast::<$wrapper>().unwrap();
-                            callback(*event)
+                            callback(event)
                         });
 
-                        let callback: BumpBox<dyn FnMut(Box<dyn Any + Send>) + 'a> = unsafe { BumpBox::from_raw(cb) };
+                        let callback: BumpBox<dyn FnMut(Arc<dyn Any + Send + Sync>) + 'a> = unsafe { BumpBox::from_raw(cb) };
 
                         // ie oncopy
                         let event_name = stringify!($name);

+ 29 - 0
packages/web/examples/async.rs

@@ -0,0 +1,29 @@
+//! Example: README.md showcase
+//!
+//! The example from the README.md.
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_hooks::use_state;
+use dioxus_html as dioxus_elements;
+use dioxus_web;
+use gloo_timers::future::TimeoutFuture;
+
+fn main() {
+    dioxus_web::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx, props| {
+    let mut count = use_state(cx, || 0);
+
+    cx.push_task(|| async move {
+        TimeoutFuture::new(100).await;
+        count += 1;
+    });
+
+    rsx!(cx, div {
+        h3 { "High-Five counter: {count}" }
+        button { onclick: move |_| count.set(0), "Reset!" }
+    })
+};

+ 40 - 0
packages/web/examples/suspense.rs

@@ -0,0 +1,40 @@
+#![allow(non_upper_case_globals)]
+
+//! Example: README.md showcase
+//!
+//! The example from the README.md.
+
+use dioxus::prelude::*;
+use dioxus_core as dioxus;
+use dioxus_core_macro::*;
+use dioxus_html as dioxus_elements;
+use dioxus_web;
+
+fn main() {
+    dioxus_web::launch(App, |c| c);
+}
+
+static App: FC<()> = |cx, _| {
+    let doggo = cx.suspend(|| async move {
+        #[derive(serde::Deserialize)]
+        struct Doggo {
+            message: String,
+        }
+
+        let src = reqwest::get("https://dog.ceo/api/breeds/image/random")
+            .await
+            .expect("Failed to fetch doggo")
+            .json::<Doggo>()
+            .await
+            .expect("Failed to parse doggo")
+            .message;
+
+        rsx!(cx, img { src: "{src}" })
+    });
+
+    rsx!(cx, div {
+        h1 {"One doggo coming right up"}
+        button { onclick: move |_| cx.needs_update(), "Get a new doggo" }
+        {doggo}
+    })
+};

+ 26 - 24
packages/web/src/dom.rs

@@ -9,7 +9,7 @@
 
 use dioxus_core::{DomEdit, ElementId, SchedulerMsg, ScopeId, UserEvent};
 use fxhash::FxHashMap;
-use std::{any::Any, fmt::Debug, rc::Rc};
+use std::{any::Any, fmt::Debug, rc::Rc, sync::Arc};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{
     CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
@@ -92,7 +92,7 @@ impl WebsysDom {
     //                 .as_ref()
     //                 .unwrap()
     //                 .clone();
-    //             bla.set(Box::new(node)).unwrap();
+    //             bla.set(Arc::new(node)).unwrap();
     //         }
     //     }
     // }
@@ -286,14 +286,16 @@ impl WebsysDom {
         } else {
             let trigger = self.sender_callback.clone();
 
-            let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
+            let c: Box<dyn FnMut(&Event)> = Box::new(move |event: &web_sys::Event| {
                 // "Result" cannot be received from JS
                 // Instead, we just build and immediately execute a closure that returns result
                 match decode_trigger(event) {
                     Ok(synthetic_event) => trigger.as_ref()(SchedulerMsg::UiEvent(synthetic_event)),
                     Err(e) => log::error!("Error decoding Dioxus event attribute. {:#?}", e),
                 };
-            }) as Box<dyn FnMut(&Event)>);
+            });
+
+            let handler = Closure::wrap(c);
 
             self.root
                 .add_event_listener_with_callback(event, (&handler).as_ref().unchecked_ref())
@@ -483,21 +485,21 @@ unsafe impl Sync for DioxusWebsysEvent {}
 
 // todo: some of these events are being casted to the wrong event type.
 // We need tests that simulate clicks/etc and make sure every event type works.
-fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send> {
+fn virtual_event_from_websys_event(event: web_sys::Event) -> Arc<dyn Any + Send + Sync> {
     use dioxus_html::on::*;
     use dioxus_html::KeyCode;
     // use dioxus_core::events::on::*;
     match event.type_().as_str() {
-        "copy" | "cut" | "paste" => Box::new(ClipboardEvent {}),
+        "copy" | "cut" | "paste" => Arc::new(ClipboardEvent {}),
         "compositionend" | "compositionstart" | "compositionupdate" => {
             let evt: &web_sys::CompositionEvent = event.dyn_ref().unwrap();
-            Box::new(CompositionEvent {
+            Arc::new(CompositionEvent {
                 data: evt.data().unwrap_or_default(),
             })
         }
         "keydown" | "keypress" | "keyup" => {
             let evt: &web_sys::KeyboardEvent = event.dyn_ref().unwrap();
-            Box::new(KeyboardEvent {
+            Arc::new(KeyboardEvent {
                 alt_key: evt.alt_key(),
                 char_code: evt.char_code(),
                 key: evt.key(),
@@ -513,7 +515,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
         }
         "focus" | "blur" => {
             //
-            Box::new(FocusEvent {})
+            Arc::new(FocusEvent {})
         }
         // "change" => SyntheticEvent::GenericEvent(DioxusEvent::new((), DioxusWebsysEvent(event))),
 
@@ -558,13 +560,13 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
                 })
                 .expect("only an InputElement or TextAreaElement or an element with contenteditable=true can have an oninput event listener");
 
-            Box::new(FormEvent { value })
+            Arc::new(FormEvent { value })
         }
         "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit"
         | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter"
         | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
             let evt: &web_sys::MouseEvent = event.dyn_ref().unwrap();
-            Box::new(MouseEvent {
+            Arc::new(MouseEvent {
                 alt_key: evt.alt_key(),
                 button: evt.button(),
                 buttons: evt.buttons(),
@@ -582,7 +584,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
         "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
         | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
             let evt: &web_sys::PointerEvent = event.dyn_ref().unwrap();
-            Box::new(PointerEvent {
+            Arc::new(PointerEvent {
                 alt_key: evt.alt_key(),
                 button: evt.button(),
                 buttons: evt.buttons(),
@@ -608,11 +610,11 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
                 // get_modifier_state: evt.get_modifier_state(),
             })
         }
-        "select" => Box::new(SelectionEvent {}),
+        "select" => Arc::new(SelectionEvent {}),
 
         "touchcancel" | "touchend" | "touchmove" | "touchstart" => {
             let evt: &web_sys::TouchEvent = event.dyn_ref().unwrap();
-            Box::new(TouchEvent {
+            Arc::new(TouchEvent {
                 alt_key: evt.alt_key(),
                 ctrl_key: evt.ctrl_key(),
                 meta_key: evt.meta_key(),
@@ -620,11 +622,11 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
             })
         }
 
-        "scroll" => Box::new(()),
+        "scroll" => Arc::new(()),
 
         "wheel" => {
             let evt: &web_sys::WheelEvent = event.dyn_ref().unwrap();
-            Box::new(WheelEvent {
+            Arc::new(WheelEvent {
                 delta_x: evt.delta_x(),
                 delta_y: evt.delta_y(),
                 delta_z: evt.delta_z(),
@@ -634,7 +636,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
 
         "animationstart" | "animationend" | "animationiteration" => {
             let evt: &web_sys::AnimationEvent = event.dyn_ref().unwrap();
-            Box::new(AnimationEvent {
+            Arc::new(AnimationEvent {
                 elapsed_time: evt.elapsed_time(),
                 animation_name: evt.animation_name(),
                 pseudo_element: evt.pseudo_element(),
@@ -643,7 +645,7 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
 
         "transitionend" => {
             let evt: &web_sys::TransitionEvent = event.dyn_ref().unwrap();
-            Box::new(TransitionEvent {
+            Arc::new(TransitionEvent {
                 elapsed_time: evt.elapsed_time(),
                 property_name: evt.property_name(),
                 pseudo_element: evt.pseudo_element(),
@@ -655,15 +657,15 @@ fn virtual_event_from_websys_event(event: web_sys::Event) -> Box<dyn Any + Send>
         | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend"
         | "timeupdate" | "volumechange" | "waiting" => {
             //
-            Box::new(MediaEvent {})
+            Arc::new(MediaEvent {})
         }
 
         "toggle" => {
             //
-            Box::new(ToggleEvent {})
+            Arc::new(ToggleEvent {})
         }
 
-        _ => Box::new(()),
+        _ => Arc::new(()),
     }
 }
 
@@ -701,9 +703,9 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<UserEvent> {
 
     Ok(UserEvent {
         name: event_name_from_typ(&typ),
-        event: virtual_event_from_websys_event(event.clone()),
-        mounted_dom_id: Some(ElementId(real_id as usize)),
-        scope_id: ScopeId(triggered_scope as usize),
+        data: virtual_event_from_websys_event(event.clone()),
+        element: Some(ElementId(real_id as usize)),
+        scope_id: Some(ScopeId(triggered_scope as usize)),
         priority: dioxus_core::EventPriority::Medium,
     })
 }

+ 1 - 1
src/lib.rs

@@ -193,7 +193,7 @@ pub mod debug {}
 pub mod prelude {
     //! A glob import that includes helper types like FC, rsx!, html!, and required traits
     pub use dioxus_core::prelude::*;
-    pub use dioxus_core_macro::{format_args_f, rsx, Props};
+    pub use dioxus_core_macro::{format_args_f, rsx, Props, Routable};
     pub use dioxus_elements::{GlobalAttributes, SvgAttributes};
     pub use dioxus_hooks::*;
     pub use dioxus_html as dioxus_elements;