Procházet zdrojové kódy

wip: bottom up dropping

Jonathan Kelley před 3 roky
rodič
revize
f2334c17be

+ 86 - 0
examples/borrowed.rs

@@ -0,0 +1,86 @@
+#![allow(non_snake_case)]
+//! Example: Extremely nested borrowing
+//! -----------------------------------
+//!
+//! Dioxus manages borrow lifetimes for you. This means any child may borrow from its parent. However, it is not possible
+//! to hand out an &mut T to children - all props are consumed by &P, so you'd only get an &&mut T.
+//!
+//! How does it work?
+//!
+//! Dioxus will manually drop closures and props - things that borrow data before the component is ran again. This is done
+//! "bottom up" from the lowest child all the way to the initiating parent. As it traverses each listener and prop, the
+//! drop implementation is manually called, freeing any memory and ensuring that memory is not leaked.
+//!
+//! We cannot drop from the parent to the children - if the drop implementation modifies the data, downstream references
+//! might be broken since we take an &mut T and and &T to the data. Instead, we work bottom up, making sure to remove any
+//! potential references to the data before finally giving out an &mut T. This prevents us from mutably aliasing the data,
+//! and is proven to be safe with MIRI.
+
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus::desktop::launch(App, |c| c);
+}
+
+fn App<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
+    let text: &'a mut Vec<String> = cx.use_hook(|_| vec![String::from("abc=def")], |f| f, |_| {});
+
+    let first = text.get_mut(0).unwrap();
+
+    cx.render(rsx! {
+        div {
+            Child1 {
+                text: first
+            }
+        }
+    })
+}
+
+#[derive(Props)]
+struct C1Props<'a> {
+    text: &'a mut String,
+}
+
+impl<'a> Drop for C1Props<'a> {
+    fn drop(&mut self) {}
+}
+
+fn Child1<'a>(cx: Context<'a, C1Props>) -> DomTree<'a> {
+    let (left, right) = cx.text.split_once("=").unwrap();
+
+    cx.render(rsx! {
+        div {
+            Child2 { text: left  }
+            Child2 { text: right  }
+        }
+    })
+}
+
+#[derive(Props)]
+struct C2Props<'a> {
+    text: &'a str,
+}
+impl<'a> Drop for C2Props<'a> {
+    fn drop(&mut self) {
+        todo!()
+    }
+}
+
+fn Child2<'a>(cx: Context<'a, C2Props>) -> DomTree<'a> {
+    cx.render(rsx! {
+        Child3 {
+            text: cx.text
+        }
+    })
+}
+
+#[derive(Props)]
+struct C3Props<'a> {
+    text: &'a str,
+}
+
+fn Child3<'a>(cx: Context<'a, C3Props>) -> DomTree<'a> {
+    cx.render(rsx! {
+        div { "{cx.text}"}
+    })
+}

+ 7 - 0
packages/core-macro/README.md

@@ -0,0 +1,7 @@
+# core-macro
+
+This crate implements these macros:
+- `format_args_f`: for f-string interpolation inside of text blocks
+- `Props`: derive macro for typed-builder with props configurations
+- 
+- 

+ 3 - 0
packages/core/Cargo.toml

@@ -39,9 +39,12 @@ smallvec = "1.6.1"
 
 slab = "0.4.3"
 
+futures-channel = "0.3.16"
+
 
 [dev-dependencies]
 anyhow = "1.0.42"
+async-std = { version = "1.9.0", features = ["attributes"] }
 dioxus-html = { path = "../html" }
 
 

+ 29 - 22
packages/core/src/arena.rs

@@ -33,19 +33,22 @@ impl ElementId {
 }
 
 type Shared<T> = Rc<RefCell<T>>;
+type TaskReceiver = futures_channel::mpsc::UnboundedReceiver<EventTrigger>;
+type TaskSender = futures_channel::mpsc::UnboundedSender<EventTrigger>;
 
 /// These are resources shared among all the components and the virtualdom itself
 #[derive(Clone)]
 pub struct SharedResources {
     pub components: Rc<UnsafeCell<Slab<Scope>>>,
 
-    pub event_queue: Shared<Vec<HeightMarker>>,
+    pub(crate) heuristics: Shared<HeuristicsEngine>,
 
-    pub events: Shared<Vec<EventTrigger>>,
+    ///
+    pub task_sender: TaskSender,
 
-    pub(crate) heuristics: Shared<HeuristicsEngine>,
+    pub task_receiver: Shared<TaskReceiver>,
 
-    pub tasks: Shared<FuturesUnordered<FiberTask>>,
+    pub async_tasks: Shared<FuturesUnordered<FiberTask>>,
 
     /// We use a SlotSet to keep track of the keys that are currently being used.
     /// However, we don't store any specific data since the "mirror"
@@ -63,30 +66,39 @@ impl SharedResources {
         // elements are super cheap - the value takes no space
         let raw_elements = Slab::with_capacity(2000);
 
-        let event_queue = Rc::new(RefCell::new(Vec::new()));
-        let tasks = Vec::new();
+        // let pending_events = Rc::new(RefCell::new(Vec::new()));
+
+        let (sender, receiver) = futures_channel::mpsc::unbounded();
+
         let heuristics = HeuristicsEngine::new();
 
+        // we allocate this task setter once to save us from having to allocate later
         let task_setter = {
-            let queue = event_queue.clone();
+            let queue = sender.clone();
             let components = components.clone();
             Rc::new(move |idx: ScopeId| {
                 let comps = unsafe { &*components.get() };
 
                 if let Some(scope) = comps.get(idx.0) {
-                    queue.borrow_mut().push(HeightMarker {
-                        height: scope.height,
-                        idx,
-                    })
+                    queue
+                        .unbounded_send(EventTrigger::new(
+                            VirtualEvent::ScheduledUpdate {
+                                height: scope.height,
+                            },
+                            idx,
+                            None,
+                            EventPriority::High,
+                        ))
+                        .expect("The event queu receiver should *never* be dropped");
                 }
-            })
+            }) as Rc<dyn Fn(ScopeId)>
         };
 
         Self {
-            event_queue,
             components,
-            tasks: Rc::new(RefCell::new(FuturesUnordered::new())),
-            events: Rc::new(RefCell::new(tasks)),
+            async_tasks: Rc::new(RefCell::new(FuturesUnordered::new())),
+            task_receiver: Rc::new(RefCell::new(receiver)),
+            task_sender: sender,
             heuristics: Rc::new(RefCell::new(heuristics)),
             raw_elements: Rc::new(RefCell::new(raw_elements)),
             task_setter,
@@ -136,12 +148,7 @@ impl SharedResources {
 
     /// return the id, freeing the space of the original node
     pub fn collect_garbage(&self, id: ElementId) {
-        let mut r: RefMut<Slab<()>> = self.raw_elements.borrow_mut();
-        r.remove(id.0);
-    }
-
-    pub fn borrow_queue(&self) -> RefMut<Vec<HeightMarker>> {
-        self.event_queue.borrow_mut()
+        self.raw_elements.borrow_mut().remove(id.0);
     }
 
     pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
@@ -157,7 +164,7 @@ impl SharedResources {
     }
 
     pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
-        self.tasks.borrow_mut().push(task);
+        self.async_tasks.borrow_mut().push(task);
         TaskHandle {}
     }
 }

+ 58 - 146
packages/core/src/diff.rs

@@ -65,10 +65,11 @@
 //!  - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
 
 use crate::{arena::SharedResources, innerlude::*};
+use futures_util::Future;
 use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
 
-use std::{any::Any, cell::Cell, cmp::Ordering};
+use std::{any::Any, cell::Cell, cmp::Ordering, pin::Pin};
 use DomEdit::*;
 
 /// Instead of having handles directly over nodes, Dioxus uses simple u32 as node IDs.
@@ -79,16 +80,32 @@ use DomEdit::*;
 /// nodes as the diffing algorithm descenes through the tree. This means that whatever is on top of the stack will receive
 /// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any
 /// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
-pub trait RealDom<'a> {
+pub trait RealDom {
     fn raw_node_as_any(&self) -> &mut dyn Any;
-}
 
-pub struct DiffMachine<'real, 'bump> {
-    pub real_dom: &'real dyn RealDom<'bump>,
+    /// Essentially "are we out of time to do more work?"
+    /// Right now, defaults to "no" giving us unlimited time to process work.
+    /// This will lead to blocking behavior in the UI, so implementors will want to override this.
+    fn must_commit(&self) -> bool {
+        false
+    }
+
+    fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
+
+    // pause the virtualdom until the main loop is ready to process more work
+    fn wait_until_ready<'s>(&'s mut self) -> Pin<Box<dyn Future<Output = ()> + 's>> {
+        //
+        Box::pin(async {
+            //
+        })
+    }
+}
 
+pub struct DiffMachine<'r, 'bump> {
+    // pub real_dom: &'real dyn RealDom,
     pub vdom: &'bump SharedResources,
 
-    pub edits: &'real mut Vec<DomEdit<'bump>>,
+    pub edits: &'r mut Vec<DomEdit<'bump>>,
 
     pub scope_stack: SmallVec<[ScopeId; 5]>,
 
@@ -99,15 +116,13 @@ pub struct DiffMachine<'real, 'bump> {
     pub seen_scopes: FxHashSet<ScopeId>,
 }
 
-impl<'real, 'bump> DiffMachine<'real, 'bump> {
+impl<'r, 'bump> DiffMachine<'r, 'bump> {
     pub(crate) fn new(
-        edits: &'real mut Vec<DomEdit<'bump>>,
-        dom: &'real dyn RealDom<'bump>,
+        edits: &'r mut Vec<DomEdit<'bump>>,
         cur_scope: ScopeId,
         shared: &'bump SharedResources,
     ) -> Self {
         Self {
-            real_dom: dom,
             edits,
             scope_stack: smallvec![cur_scope],
             vdom: shared,
@@ -121,12 +136,10 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
     ///
     /// This will PANIC if any component elements are passed in.
     pub fn new_headless(
-        edits: &'real mut Vec<DomEdit<'bump>>,
-        dom: &'real dyn RealDom<'bump>,
+        edits: &'r mut Vec<DomEdit<'bump>>,
         shared: &'bump SharedResources,
     ) -> Self {
         Self {
-            real_dom: dom,
             edits,
             scope_stack: smallvec![ScopeId(0)],
             vdom: shared,
@@ -135,6 +148,14 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
         }
     }
 
+    //
+    pub fn diff_scope(&mut self, id: ScopeId) -> Result<()> {
+        let component = self.get_scope_mut(&id).ok_or_else(|| Error::NotMounted)?;
+        let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
+        self.diff_node(old, new);
+        Ok(())
+    }
+
     // Diff the `old` node with the `new` node. Emits instructions to modify a
     // physical DOM node that reflects `old` into something that reflects `new`.
     //
@@ -396,11 +417,7 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
                 } = el;
 
                 let real_id = self.vdom.reserve_node();
-                if let Some(namespace) = namespace {
-                    self.edit_create_element(tag_name, Some(namespace), real_id)
-                } else {
-                    self.edit_create_element(tag_name, None, real_id)
-                };
+                self.edit_create_element(tag_name, *namespace, real_id);
                 dom_id.set(Some(real_id));
 
                 let cur_scope = self.current_scope().unwrap();
@@ -1451,6 +1468,29 @@ impl<'real, 'bump> DiffMachine<'real, 'bump> {
         });
     }
 
+    pub(crate) fn edit_set_attribute_ns(
+        &mut self,
+        attribute: &'bump Attribute,
+        namespace: &'bump str,
+    ) {
+        let Attribute {
+            name,
+            value,
+            is_static,
+            is_volatile,
+            // namespace,
+            ..
+        } = attribute;
+        // field: &'static str,
+        // value: &'bump str,
+        // ns: Option<&'static str>,
+        self.edits.push(SetAttribute {
+            field: name,
+            value,
+            ns: Some(namespace),
+        });
+    }
+
     pub(crate) fn edit_remove_attribute(&mut self, attribute: &Attribute) {
         let name = attribute.name;
         self.edits.push(RemoveAttribute { name });
@@ -1627,131 +1667,3 @@ fn compare_strs(a: &str, b: &str) -> bool {
         true
     }
 }
-
-// // Now we will iterate from the end of the new children back to the
-// // beginning, diffing old children we are reusing and if they aren't in the
-// // LIS moving them to their new destination, or creating new children. Note
-// // that iterating in reverse order lets us use `Node.prototype.insertBefore`
-// // to move/insert children.
-// //
-// // But first, we ensure that we have a child on the change list stack that
-// // we can `insertBefore`. We handle this once before looping over `new`
-// // children, so that we don't have to keep checking on every loop iteration.
-// if shared_suffix_count > 0 {
-//     // There is a shared suffix after these middle children. We will be
-//     // inserting before that shared suffix, so add the first child of that
-//     // shared suffix to the change list stack.
-//     //
-//     // [... parent]
-
-//     // TODO
-
-//     // self.edits
-//     //     .go_down_to_child(old_shared_suffix_start - removed_count);
-//     // [... parent first_child_of_shared_suffix]
-// } else {
-//     // There is no shared suffix coming after these middle children.
-//     // Therefore we have to process the last child in `new` and move it to
-//     // the end of the parent's children if it isn't already there.
-//     let last_index = new.len() - 1;
-//     // uhhhh why an unwrap?
-//     let last = new.last().unwrap();
-//     // let last = new.last().unwrap_throw();
-//     new = &new[..new.len() - 1];
-//     if shared_keys.contains(&last.key()) {
-//         let old_index = new_index_to_old_index[last_index];
-//         let temp = old_index_to_temp[old_index];
-//         // [... parent]
-//         // self.go_down_to_temp_child(temp);
-//         // [... parent last]
-//         self.diff_node(&old[old_index], last);
-
-//         if new_index_is_in_lis.contains(&last_index) {
-//             // Don't move it, since it is already where it needs to be.
-//         } else {
-//             // self.commit_traversal();
-//             // [... parent last]
-//             // self.append_child();
-//             // [... parent]
-//             // self.go_down_to_temp_child(temp);
-//             // [... parent last]
-//         }
-//     } else {
-//         // self.commit_traversal();
-//         // [... parent]
-//         let meta = self.create_vnode(last);
-
-//         // [... parent last]
-//         // self.append_child();
-//         // [... parent]
-//         // self.go_down_to_reverse_child(0);
-//         // [... parent last]
-//     }
-// }
-
-// for (new_index, new_child) in new.iter().enumerate().rev() {
-//     let old_index = new_index_to_old_index[new_index];
-//     if old_index == u32::MAX as usize {
-//         debug_assert!(!shared_keys.contains(&new_child.key()));
-//         // self.commit_traversal();
-//         // [... parent successor]
-//         let meta = self.create_vnode(new_child);
-//         // [... parent successor new_child]
-//         self.edit_insert_after(meta.added_to_stack);
-//         // self.insert_before();
-//         // [... parent new_child]
-//     } else {
-//         debug_assert!(shared_keys.contains(&new_child.key()));
-//         let temp = old_index_to_temp[old_index];
-//         debug_assert_ne!(temp, u32::MAX);
-
-//         if new_index_is_in_lis.contains(&new_index) {
-//             // [... parent successor]
-//             // self.go_to_temp_sibling(temp);
-//             // [... parent new_child]
-//         } else {
-//             // self.commit_traversal();
-//             // [... parent successor]
-//             // self.push_temporary(temp);
-//             // [... parent successor new_child]
-//             // self.insert_before();
-//             // [... parent new_child]
-//         }
-
-//         self.diff_node(&old[old_index], new_child);
-//     }
-// }
-
-// Save each of the old children whose keys are reused in the new
-// children
-// let reused_children = vec![];
-// let mut old_index_to_temp = vec![u32::MAX; old.len()];
-// let mut start = 0;
-// loop {
-//     let end = (start..old.len())
-//         .find(|&i| {
-//             let key = old[i].key();
-//             !shared_keys.contains(&key)
-//         })
-//         .unwrap_or(old.len());
-
-//     if end - start > 0 {
-//         // self.commit_traversal();
-//         // let mut t = 5;
-//         let mut t = self.save_children_to_temporaries(
-//             shared_prefix_count + start,
-//             shared_prefix_count + end,
-//         );
-//         for i in start..end {
-//             old_index_to_temp[i] = t;
-//             t += 1;
-//         }
-//     }
-
-//     debug_assert!(end <= old.len());
-//     if end == old.len() {
-//         break;
-//     } else {
-//         start = end + 1;
-//     }
-// }

+ 67 - 34
packages/core/src/events.rs

@@ -10,7 +10,7 @@ use std::{
 };
 
 use crate::{
-    innerlude::{ElementId, HeightMarker, ScopeId},
+    innerlude::{ElementId, ScopeId},
     VNode,
 };
 
@@ -28,18 +28,35 @@ pub struct EventTrigger {
     /// The priority of the event
     pub priority: EventPriority,
 }
-
 impl EventTrigger {
-    pub fn new_from_task(originator: ScopeId, hook_idx: usize) -> Self {
-        Self {
-            originator,
-            event: VirtualEvent::AsyncEvent { hook_idx },
-            priority: EventPriority::Low,
-            real_node_id: None,
+    pub fn make_key(&self) -> EventKey {
+        EventKey {
+            originator: self.originator,
+            priority: self.priority,
         }
     }
 }
 
+#[derive(PartialEq, Eq)]
+pub struct EventKey {
+    /// The originator of the event trigger
+    pub originator: ScopeId,
+    /// The priority of the event
+    pub priority: EventPriority,
+    // TODO: add the time that the event was queued
+}
+
+impl PartialOrd for EventKey {
+    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+        todo!()
+    }
+}
+impl Ord for EventKey {
+    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+        todo!()
+    }
+}
+
 /// Priority of Event Triggers.
 ///
 /// Internally, Dioxus will abort work that's taking too long if new, more important, work arrives. Unlike React, Dioxus
@@ -47,7 +64,7 @@ impl EventTrigger {
 /// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well.
 ///
 /// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes.
-#[derive(Debug)]
+#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)]
 pub enum EventPriority {
     /// Garbage collection is a type of work than can be scheduled around other work, but must be completed in a specific
     /// order. The GC must be run for a component before any other future work for that component is run. Otherwise,
@@ -92,6 +109,45 @@ impl EventTrigger {
 }
 
 pub enum VirtualEvent {
+    /// Generated during diffing to signal that a component's nodes to be given back
+    ///
+    /// Typically has a high priority
+    ///
+    /// If an event is scheduled for a component that has "garbage", that garabge will be cleaned up before the event can
+    /// be processed.
+    GarbageCollection,
+
+    ///
+    DiffComponent,
+
+    /// A type of "immediate" event scheduled by components
+    ScheduledUpdate {
+        height: u32,
+    },
+
+    // Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
+    //
+    // Async events don't necessarily propagate into a scope being ran. It's up to the event itself
+    // to force an update for itself.
+    //
+    // Most async events should have a low priority.
+    //
+    // This type exists for the task/concurrency system to signal that a task is ready.
+    // However, this does not necessarily signal that a scope must be re-ran, so the hook implementation must cause its
+    // own re-run.
+    AsyncEvent {},
+
+    // Suspense events are a type of async event
+    //
+    // they have the lowest priority
+    SuspenseEvent {
+        hook_idx: usize,
+        domnode: Rc<Cell<Option<ElementId>>>,
+    },
+
+    // image event has conflicting method types
+    // ImageEvent(event_data::ImageEvent),
+
     // Real events
     ClipboardEvent(on::ClipboardEvent),
     CompositionEvent(on::CompositionEvent),
@@ -108,30 +164,6 @@ pub enum VirtualEvent {
     ToggleEvent(on::ToggleEvent),
     MouseEvent(on::MouseEvent),
     PointerEvent(on::PointerEvent),
-
-    GarbageCollection,
-
-    // image event has conflicting method types
-    // ImageEvent(event_data::ImageEvent),
-    SetStateEvent {
-        height: HeightMarker,
-    },
-
-    // Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
-    //
-    // Async events don't necessarily propagate into a scope being ran. It's up to the event itself
-    // to force an update for itself.
-    AsyncEvent {
-        hook_idx: usize,
-    },
-
-    // These are more intrusive than the rest
-    SuspenseEvent {
-        hook_idx: usize,
-        domnode: Rc<Cell<Option<ElementId>>>,
-    },
-    // TOOD: make garbage collection its own dedicated event
-    // GarbageCollection {}
 }
 
 impl std::fmt::Debug for VirtualEvent {
@@ -153,9 +185,10 @@ impl std::fmt::Debug for VirtualEvent {
             VirtualEvent::MouseEvent(_) => "MouseEvent",
             VirtualEvent::PointerEvent(_) => "PointerEvent",
             VirtualEvent::GarbageCollection => "GarbageCollection",
-            VirtualEvent::SetStateEvent { .. } => "SetStateEvent",
+            VirtualEvent::ScheduledUpdate { .. } => "SetStateEvent",
             VirtualEvent::AsyncEvent { .. } => "AsyncEvent",
             VirtualEvent::SuspenseEvent { .. } => "SuspenseEvent",
+            VirtualEvent::DiffComponent { .. } => "DiffComponent",
         };
 
         f.debug_struct("VirtualEvent").field("type", &name).finish()

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

@@ -111,7 +111,7 @@ where
 
     // whenever the task is complete, save it into th
     cx.use_hook(
-        move |hook_idx| {
+        move |_| {
             let task_fut = task_initializer();
 
             let task_dump = Rc::new(RefCell::new(None));
@@ -127,7 +127,7 @@ where
                 *slot.as_ref().borrow_mut() = Some(output);
                 updater(update_id);
                 EventTrigger {
-                    event: VirtualEvent::AsyncEvent { hook_idx },
+                    event: VirtualEvent::AsyncEvent {},
                     originator,
                     priority: EventPriority::Low,
                     real_node_id: None,

+ 0 - 1
packages/core/src/nodes.rs

@@ -359,7 +359,6 @@ impl<'a> NodeFactory<'a> {
         component: FC<P>,
         props: P,
         key: Option<Arguments>,
-        // key: Option<&'a str>,
         children: V,
     ) -> VNode<'a>
     where

+ 10 - 9
packages/core/src/scope.rs

@@ -115,6 +115,8 @@ impl Scope {
         // This breaks any latent references, invalidating every pointer referencing into it.
         // Remove all the outdated listeners
         if !self.pending_garbage.borrow().is_empty() {
+            // We have some garbage to clean up
+            log::error!("Cleaning up garabge");
             panic!("cannot run scope while garbage is pending! Please clean up your mess first");
         }
 
@@ -122,18 +124,17 @@ impl Scope {
 
         // make sure we call the drop implementation on all the listeners
         // this is important to not leak memory
-        for listener in self
-            .listeners
+        self.listeners
             .borrow_mut()
             .drain(..)
             .map(|li| unsafe { &*li })
-        {
-            let mut cb = listener.callback.borrow_mut();
-            match cb.take() {
-                Some(val) => std::mem::drop(val),
-                None => log::info!("no callback to drop. component must be broken"),
-            };
-        }
+            .for_each(|listener| {
+                listener.callback.borrow_mut().take();
+            });
+
+        // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
+        // run the hooks (which hold an &mut Referrence)
+        // right now, we don't drop
 
         // Safety:
         // - We dropped the listeners, so no more &mut T can be used while these are held

+ 17 - 17
packages/core/src/util.rs

@@ -8,24 +8,24 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
     Cell::new(None)
 }
 
-/// A helper type that lets scopes be ordered by their height
-#[derive(Debug, Clone, Copy, PartialEq, Eq)]
-pub struct HeightMarker {
-    pub idx: ScopeId,
-    pub height: u32,
-}
+// /// A helper type that lets scopes be ordered by their height
+// #[derive(Debug, Clone, Copy, PartialEq, Eq)]
+// pub struct HeightMarker {
+//     pub idx: ScopeId,
+//     pub height: u32,
+// }
 
-impl Ord for HeightMarker {
-    fn cmp(&self, other: &Self) -> std::cmp::Ordering {
-        self.height.cmp(&other.height)
-    }
-}
+// impl Ord for HeightMarker {
+//     fn cmp(&self, other: &Self) -> std::cmp::Ordering {
+//         self.height.cmp(&other.height)
+//     }
+// }
 
-impl PartialOrd for HeightMarker {
-    fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
-        Some(self.cmp(other))
-    }
-}
+// impl PartialOrd for HeightMarker {
+//     fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
+//         Some(self.cmp(other))
+//     }
+// }
 
 pub struct DebugDom {}
 impl DebugDom {
@@ -34,7 +34,7 @@ impl DebugDom {
     }
 }
 
-impl<'a> RealDom<'a> for DebugDom {
+impl RealDom for DebugDom {
     fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
         todo!()
     }

+ 213 - 198
packages/core/src/virtual_dom.rs

@@ -18,8 +18,9 @@
 //!
 //! This module includes just the barebones for a complete VirtualDOM API.
 //! Additional functionality is defined in the respective files.
-
+#![allow(unreachable_code)]
 use futures_util::StreamExt;
+use fxhash::FxHashMap;
 
 use crate::hooks::{SuspendedContext, SuspenseHook};
 use crate::{arena::SharedResources, innerlude::*};
@@ -28,6 +29,7 @@ use std::any::Any;
 
 use std::any::TypeId;
 use std::cell::{Ref, RefCell, RefMut};
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
 use std::pin::Pin;
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
@@ -51,7 +53,7 @@ pub struct VirtualDom {
     /// Should always be the first (gen=0, id=0)
     pub base_scope: ScopeId,
 
-    pub triggers: RefCell<Vec<EventTrigger>>,
+    pending_events: BTreeMap<EventKey, EventTrigger>,
 
     // for managing the props that were used to create the dom
     #[doc(hidden)]
@@ -61,9 +63,6 @@ pub struct VirtualDom {
     _root_props: std::pin::Pin<Box<dyn std::any::Any>>,
 }
 
-// ======================================
-// Public Methods for the VirtualDom
-// ======================================
 impl VirtualDom {
     /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
     ///
@@ -145,14 +144,14 @@ impl VirtualDom {
             base_scope,
             _root_props: root_props,
             shared: components,
-            triggers: Default::default(),
+            pending_events: BTreeMap::new(),
             _root_prop_type: TypeId::of::<P>(),
         }
     }
 
     pub fn launch_in_place(root: FC<()>) -> Self {
         let mut s = Self::new(root);
-        s.rebuild_in_place();
+        s.rebuild_in_place().unwrap();
         s
     }
 
@@ -160,10 +159,18 @@ impl VirtualDom {
     ///
     pub fn launch_with_props_in_place<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
         let mut s = Self::new_with_props(root, root_props);
-        s.rebuild_in_place();
+        s.rebuild_in_place().unwrap();
         s
     }
 
+    pub fn base_scope(&self) -> &Scope {
+        unsafe { self.shared.get_scope(self.base_scope).unwrap() }
+    }
+
+    pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> {
+        unsafe { self.shared.get_scope(id) }
+    }
+
     /// Rebuilds the VirtualDOM from scratch, but uses a "dummy" RealDom.
     ///
     /// Used in contexts where a real copy of the  structure doesn't matter, and the VirtualDOM is the source of truth.
@@ -174,21 +181,22 @@ impl VirtualDom {
     ///
     /// SSR takes advantage of this by using Dioxus itself as the source of truth, and rendering from the tree directly.
     pub fn rebuild_in_place(&mut self) -> Result<Vec<DomEdit>> {
-        let mut realdom = DebugDom::new();
-        let mut edits = Vec::new();
-        self.rebuild(&mut realdom, &mut edits)?;
-        Ok(edits)
+        todo!();
+        // let mut realdom = DebugDom::new();
+        // let mut edits = Vec::new();
+        // self.rebuild(&mut realdom, &mut edits)?;
+        // Ok(edits)
     }
 
     /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
     ///
     /// The diff machine expects the RealDom's stack to be the root of the application
-    pub fn rebuild<'s>(
-        &'s mut self,
-        realdom: &'_ mut dyn RealDom<'s>,
-        edits: &'_ mut Vec<DomEdit<'s>>,
-    ) -> Result<()> {
-        let mut diff_machine = DiffMachine::new(edits, realdom, self.base_scope, &self.shared);
+    ///
+    /// Events like garabge collection, application of refs, etc are not handled by this method and can only be progressed
+    /// through "run"
+    ///
+    pub fn rebuild<'s>(&'s mut self, edits: &'_ mut Vec<DomEdit<'s>>) -> Result<()> {
+        let mut diff_machine = DiffMachine::new(edits, self.base_scope, &self.shared);
 
         let cur_component = diff_machine
             .get_scope_mut(&self.base_scope)
@@ -209,219 +217,220 @@ impl VirtualDom {
         Ok(())
     }
 
-    ///
-    ///
-    ///
-    ///
-    ///
-    pub fn queue_event(&self, trigger: EventTrigger) {
-        let mut triggers = self.triggers.borrow_mut();
-        triggers.push(trigger);
+    async fn select_next_event(&mut self) -> Option<EventTrigger> {
+        let mut receiver = self.shared.task_receiver.borrow_mut();
+
+        // drain the in-flight events so that we can sort them out with the current events
+        while let Ok(Some(trigger)) = receiver.try_next() {
+            log::info!("scooping event from receiver");
+            self.pending_events.insert(trigger.make_key(), trigger);
+        }
+
+        if self.pending_events.is_empty() {
+            // Continuously poll the future pool and the event receiver for work
+            let mut tasks = self.shared.async_tasks.borrow_mut();
+            let tasks_tasks = tasks.next();
+
+            let mut receiver = self.shared.task_receiver.borrow_mut();
+            let reciv_task = receiver.next();
+
+            futures_util::pin_mut!(tasks_tasks);
+            futures_util::pin_mut!(reciv_task);
+
+            let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await {
+                futures_util::future::Either::Left((trigger, _)) => trigger,
+                futures_util::future::Either::Right((trigger, _)) => trigger,
+            }
+            .unwrap();
+            self.pending_events.insert(trigger.make_key(), trigger);
+        }
+
+        todo!()
+
+        // let trigger = self.select_next_event().unwrap();
+        // trigger
     }
 
-    /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
-    ///  
-    /// Given a synthetic event, the component that triggered the event, and the index of the callback, this runs the virtual
-    /// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single
-    /// change list.
-    ///
-    /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
-    /// listeners, something like this:
-    ///
-    /// ```ignore
-    /// while let Ok(event) = receiver.recv().await {
-    ///     let edits = self.internal_dom.progress_with_event(event)?;
-    ///     for edit in &edits {
-    ///         patch_machine.handle_edit(edit);
-    ///     }
-    /// }
-    /// ```
-    ///
-    /// Note: this method is not async and does not provide suspense-like functionality. It is up to the renderer to provide the
-    /// executor and handlers for suspense as show in the example.
-    ///
-    /// ```ignore
-    /// let (sender, receiver) = channel::new();
-    /// sender.send(EventTrigger::start());
-    ///
-    /// let mut dom = VirtualDom::new();
-    /// dom.suspense_handler(|event| sender.send(event));
-    ///
-    /// while let Ok(diffs) = dom.progress_with_event(receiver.recv().await) {
-    ///     render(diffs);
-    /// }
-    ///
-    /// ```
+    // the cooperartive, fiber-based scheduler
+    // is "awaited" and will always return some edits for the real dom to apply
     //
-    // Developer notes:
-    // ----
-    // This method has some pretty complex safety guarantees to uphold.
-    // We interact with bump arenas, raw pointers, and use UnsafeCell to get a partial borrow of the arena.
-    // The final EditList has edits that pull directly from the Bump Arenas which add significant complexity
-    // in crafting a 100% safe solution with traditional lifetimes. Consider this method to be internally unsafe
-    // but the guarantees provide a safe, fast, and efficient abstraction for the VirtualDOM updating framework.
+    // Right now, we'll happily partially render component trees
     //
-    // A good project would be to remove all unsafe from this crate and move the unsafety into safer abstractions.
-    pub async fn progress_with_event<'a, 's>(
-        &'s mut self,
-        realdom: &'a mut dyn RealDom<'s>,
-        edits: &'a mut Vec<DomEdit<'s>>,
-    ) -> Result<()> {
-        let trigger = self.triggers.borrow_mut().pop().expect("failed");
-
-        let mut diff_machine = DiffMachine::new(edits, realdom, trigger.originator, &self.shared);
-
-        match &trigger.event {
-            // When a scope gets destroyed during a diff, it gets its own garbage collection event
-            // However, an old scope might be attached
-            VirtualEvent::GarbageCollection => {
-                let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
-
-                let mut garbage_list = scope.consume_garbage();
-
-                while let Some(node) = garbage_list.pop() {
-                    match &node.kind {
-                        VNodeKind::Text(_) => {
-                            //
-                            self.shared.collect_garbage(node.direct_id())
-                        }
-                        VNodeKind::Anchor(anchor) => {
-                            //
-                        }
+    // Normally, you would descend through the tree depth-first, but we actually descend breadth-first.
+    pub async fn run(&mut self, realdom: &mut dyn RealDom) -> Result<()> {
+        let cur_component = self.base_scope;
+        let mut edits = Vec::new();
+        let resources = self.shared.clone();
+
+        let mut diff_machine = DiffMachine::new(&mut edits, cur_component, &resources);
 
-                        VNodeKind::Element(el) => {
-                            self.shared.collect_garbage(node.direct_id());
-                            for child in el.children {
-                                garbage_list.push(child);
+        loop {
+            let trigger = self.select_next_event().await.unwrap();
+
+            match &trigger.event {
+                // Collecting garabge is not currently interruptible.
+                //
+                // In the future, it could be though
+                VirtualEvent::GarbageCollection => {
+                    let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
+
+                    let mut garbage_list = scope.consume_garbage();
+
+                    let mut scopes_to_kill = Vec::new();
+                    while let Some(node) = garbage_list.pop() {
+                        match &node.kind {
+                            VNodeKind::Text(_) => {
+                                self.shared.collect_garbage(node.direct_id());
+                            }
+                            VNodeKind::Anchor(_) => {
+                                self.shared.collect_garbage(node.direct_id());
+                            }
+                            VNodeKind::Suspended(_) => {
+                                self.shared.collect_garbage(node.direct_id());
                             }
-                        }
 
-                        VNodeKind::Fragment(frag) => {
-                            for child in frag.children {
-                                garbage_list.push(child);
+                            VNodeKind::Element(el) => {
+                                self.shared.collect_garbage(node.direct_id());
+                                for child in el.children {
+                                    garbage_list.push(child);
+                                }
+                            }
+
+                            VNodeKind::Fragment(frag) => {
+                                for child in frag.children {
+                                    garbage_list.push(child);
+                                }
+                            }
+
+                            VNodeKind::Component(comp) => {
+                                // TODO: run the hook destructors and then even delete the scope
+                                // TODO: allow interruption here
+                                if !realdom.must_commit() {
+                                    let scope_id = comp.ass_scope.get().unwrap();
+                                    let scope = self.get_scope(scope_id).unwrap();
+                                    let root = scope.root();
+                                    garbage_list.push(root);
+                                    scopes_to_kill.push(scope_id);
+                                }
                             }
                         }
-                        VNodeKind::Component(comp) => {
-                            // run the destructors
-                            todo!();
-                        }
-                        VNodeKind::Suspended(node) => {
-                            // make sure the task goes away
-                            todo!();
-                        }
+                    }
+
+                    for scope in scopes_to_kill {
+                        // oy kill em
+                        log::debug!("should be removing scope {:#?}", scope);
                     }
                 }
-            }
 
-            // Nothing yet
-            VirtualEvent::AsyncEvent { .. } => {}
+                VirtualEvent::AsyncEvent { .. } => {
+                    // we want to progress these events
+                    // However, there's nothing we can do for these events, they must generate their own events.
+                }
 
-            // Suspense Events! A component's suspended node is updated
-            VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
-                // Safety: this handler is the only thing that can mutate shared items at this moment in tim
-                let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
+                // Suspense Events! A component's suspended node is updated
+                VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
+                    // Safety: this handler is the only thing that can mutate shared items at this moment in tim
+                    let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
 
-                // safety: we are sure that there are no other references to the inner content of suspense hooks
-                let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
+                    // safety: we are sure that there are no other references to the inner content of suspense hooks
+                    let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
 
-                let cx = Context { scope, props: &() };
-                let scx = SuspendedContext { inner: cx };
+                    let cx = Context { scope, props: &() };
+                    let scx = SuspendedContext { inner: cx };
 
-                // generate the new node!
-                let nodes: Option<VNode<'s>> = (&hook.callback)(scx);
-                match nodes {
-                    None => {
-                        log::warn!("Suspense event came through, but there was no mounted node to update >:(");
-                    }
-                    Some(nodes) => {
-                        // todo!("using the wrong frame");
-                        let nodes = scope.frames.finished_frame().bump.alloc(nodes);
+                    // generate the new node!
+                    let nodes: Option<VNode> = (&hook.callback)(scx);
+                    match nodes {
+                        None => {
+                            log::warn!(
+                            "Suspense event came through, but there were no generated nodes >:(."
+                        );
+                        }
+                        Some(nodes) => {
+                            // allocate inside the finished frame - not the WIP frame
+                            let nodes = scope.frames.finished_frame().bump.alloc(nodes);
 
-                        // push the old node's root onto the stack
-                        let real_id = domnode.get().ok_or(Error::NotMounted)?;
-                        diff_machine.edit_push_root(real_id);
+                            // push the old node's root onto the stack
+                            let real_id = domnode.get().ok_or(Error::NotMounted)?;
+                            diff_machine.edit_push_root(real_id);
 
-                        // push these new nodes onto the diff machines stack
-                        let meta = diff_machine.create_vnode(&*nodes);
+                            // push these new nodes onto the diff machines stack
+                            let meta = diff_machine.create_vnode(&*nodes);
 
-                        // replace the placeholder with the new nodes we just pushed on the stack
-                        diff_machine.edit_replace_with(1, meta.added_to_stack);
+                            // replace the placeholder with the new nodes we just pushed on the stack
+                            diff_machine.edit_replace_with(1, meta.added_to_stack);
+                        }
                     }
                 }
-            }
 
-            // This is the "meat" of our cooperative scheduler
-            // As updates flow in, we re-evalute the event queue and decide if we should be switching the type of work
-            //
-            // We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished
-            _ => {
-                diff_machine
-                    .get_scope_mut(&trigger.originator)
-                    .map(|f| f.call_listener(trigger));
-
-                // Now, there are events in the queue
-                let mut updates = self.shared.borrow_queue();
-
-                // Order the nodes by their height, we want the nodes with the smallest depth on top
-                // This prevents us from running the same component multiple times
-                updates.sort_unstable();
-
-                log::debug!("There are: {:#?} updates to be processed", updates.len());
-
-                // Iterate through the triggered nodes (sorted by height) and begin to diff them
-                for update in updates.drain(..) {
-                    log::debug!("Running updates for: {:#?}", update);
-
-                    // Make sure this isn't a node we've already seen, we don't want to double-render anything
-                    // If we double-renderer something, this would cause memory safety issues
-                    if diff_machine.seen_scopes.contains(&update.idx) {
-                        log::debug!("Skipping update for: {:#?}", update);
-                        continue;
+                // Run the component
+                VirtualEvent::ScheduledUpdate { height: u32 } => {
+                    let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
+
+                    match scope.run_scope() {
+                        Ok(_) => {
+                            let event = VirtualEvent::DiffComponent;
+                            let trigger = EventTrigger {
+                                event,
+                                originator: trigger.originator,
+                                priority: EventPriority::High,
+                                real_node_id: None,
+                            };
+                            self.shared.task_sender.unbounded_send(trigger);
+                        }
+                        Err(_) => {
+                            log::error!("failed to run this component!");
+                        }
                     }
+                }
 
-                    // Start a new mutable borrow to components
-                    // We are guaranteeed that this scope is unique because we are tracking which nodes have modified in the diff machine
-                    let cur_component = diff_machine
-                        .get_scope_mut(&update.idx)
-                        .expect("Failed to find scope or borrow would be aliasing");
-
-                    // Now, all the "seen nodes" are nodes that got notified by running this listener
-                    diff_machine.seen_scopes.insert(update.idx.clone());
+                VirtualEvent::DiffComponent => {
+                    diff_machine.diff_scope(trigger.originator)?;
+                }
 
-                    if cur_component.run_scope().is_ok() {
-                        let (old, new) = (
-                            cur_component.frames.wip_head(),
-                            cur_component.frames.fin_head(),
-                        );
-                        diff_machine.diff_node(old, new);
+                // Process any user-facing events just by calling their listeners
+                // This performs no actual work - but the listeners themselves will cause insert new work
+                VirtualEvent::ClipboardEvent(_)
+                | VirtualEvent::CompositionEvent(_)
+                | VirtualEvent::KeyboardEvent(_)
+                | VirtualEvent::FocusEvent(_)
+                | VirtualEvent::FormEvent(_)
+                | VirtualEvent::SelectionEvent(_)
+                | VirtualEvent::TouchEvent(_)
+                | VirtualEvent::UIEvent(_)
+                | VirtualEvent::WheelEvent(_)
+                | VirtualEvent::MediaEvent(_)
+                | VirtualEvent::AnimationEvent(_)
+                | VirtualEvent::TransitionEvent(_)
+                | VirtualEvent::ToggleEvent(_)
+                | VirtualEvent::MouseEvent(_)
+                | VirtualEvent::PointerEvent(_) => {
+                    let scope_id = &trigger.originator;
+                    let scope = unsafe { self.shared.get_scope_mut(*scope_id) };
+                    match scope {
+                        Some(scope) => scope.call_listener(trigger)?,
+                        None => {
+                            log::warn!("No scope found for event: {:#?}", scope_id);
+                        }
                     }
                 }
             }
+
+            if realdom.must_commit() || self.pending_events.is_empty() {
+                realdom.commit_edits(&mut diff_machine.edits);
+                realdom.wait_until_ready().await;
+            }
         }
 
         Ok(())
     }
 
-    pub async fn wait_for_event(&mut self) -> Option<EventTrigger> {
-        let r = self.shared.tasks.clone();
-        let mut r = r.borrow_mut();
-        let gh = r.next().await;
-
-        gh
-    }
-
-    pub fn any_pending_events(&self) -> bool {
-        let r = self.shared.tasks.clone();
-        let r = r.borrow();
-        !r.is_empty()
-    }
-
-    pub fn base_scope(&self) -> &Scope {
-        unsafe { self.shared.get_scope(self.base_scope).unwrap() }
-    }
-
-    pub fn get_scope(&self, id: ScopeId) -> Option<&Scope> {
-        unsafe { self.shared.get_scope(id) }
+    pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
+        todo!()
+        // std::rc::Rc::new(move |_| {
+        //     //
+        // })
+        // todo()
     }
 }
 
@@ -429,3 +438,9 @@ impl VirtualDom {
 // These impls are actually wrong. The DOM needs to have a mutex implemented.
 unsafe impl Sync for VirtualDom {}
 unsafe impl Send for VirtualDom {}
+
+fn select_next_event(
+    pending_events: &mut BTreeMap<EventKey, EventTrigger>,
+) -> Option<EventTrigger> {
+    None
+}

+ 19 - 0
packages/core/tests/channels.rs

@@ -0,0 +1,19 @@
+use futures_channel::mpsc::unbounded;
+
+#[async_std::test]
+async fn channels() {
+    let (sender, mut receiver) = unbounded::<u32>();
+
+    // drop(sender);
+
+    match receiver.try_next() {
+        Ok(a) => {
+            dbg!(a);
+        }
+        Err(no) => {
+            dbg!(no);
+        }
+    }
+
+    sender.unbounded_send(1).unwrap();
+}

+ 4 - 7
packages/core/tests/diffing.rs

@@ -41,8 +41,7 @@ impl TestDom {
 
     fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Vec<DomEdit<'a>> {
         let mut edits = Vec::new();
-        let dom = DebugDom::new();
-        let mut machine = DiffMachine::new_headless(&mut edits, &dom, &self.resources);
+        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
         machine.diff_node(old, new);
         edits
     }
@@ -53,9 +52,8 @@ impl TestDom {
     {
         let old = self.bump.alloc(self.render(left));
         let mut edits = Vec::new();
-        let dom = DebugDom::new();
 
-        let mut machine = DiffMachine::new_headless(&mut edits, &dom, &self.resources);
+        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
         let meta = machine.create_vnode(old);
         (meta, edits)
     }
@@ -74,13 +72,12 @@ impl TestDom {
         let new = self.bump.alloc(self.render(right));
 
         let mut create_edits = Vec::new();
-        let dom = DebugDom::new();
 
-        let mut machine = DiffMachine::new_headless(&mut create_edits, &dom, &self.resources);
+        let mut machine = DiffMachine::new_headless(&mut create_edits, &self.resources);
         machine.create_vnode(old);
 
         let mut edits = Vec::new();
-        let mut machine = DiffMachine::new_headless(&mut edits, &dom, &self.resources);
+        let mut machine = DiffMachine::new_headless(&mut edits, &self.resources);
         machine.diff_node(old, new);
         (create_edits, edits)
     }

+ 1 - 1
packages/desktop/src/dom.rs

@@ -29,7 +29,7 @@ impl WebviewDom<'_> {
     //     self.registry
     // }
 }
-impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
+impl RealDom for WebviewDom<'_> {
     fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
         todo!()
         // self.edits.push(PushRoot { root });

+ 104 - 151
packages/desktop/src/lib.rs

@@ -5,6 +5,7 @@ use std::sync::{Arc, RwLock};
 
 use cfg::DesktopConfig;
 use dioxus_core::*;
+use serde::{Deserialize, Serialize};
 pub use wry;
 
 use wry::application::event::{Event, WindowEvent};
@@ -83,102 +84,114 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
 
         let window = window.build(&event_loop)?;
 
-        let vir = VirtualDom::new_with_props(root, props);
-
-        // todo: combine these or something
-        let vdom = Arc::new(RwLock::new(vir));
-        // let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
-
-        let webview = WebViewBuilder::new(window)?
-            .with_url(&format!("data:text/html,{}", HTML_CONTENT))?
-            .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
-                match req.method.as_str() {
-                    "initiate" => {
-                        let edits = if let Some(edits) = &redits {
-                            serde_json::to_value(edits).unwrap()
-                        } else {
-                            let mut lock = vdom.write().unwrap();
-                            // let mut reg_lock = registry.write().unwrap();
-
-                            // Create the thin wrapper around the registry to collect the edits into
-                            let mut real = dom::WebviewDom::new();
-                            let pre = pre_rendered.clone();
-
-                            let response = match pre {
-                                Some(content) => {
-                                    lock.rebuild_in_place().unwrap();
-
-                                    Response {
-                                        edits: Vec::new(),
-                                        pre_rendered: Some(content),
-                                    }
-                                }
-                                None => {
-                                    //
-                                    let edits = {
-                                        let mut edits = Vec::new();
-                                        lock.rebuild(&mut real, &mut edits).unwrap();
-                                        edits
-                                    };
-                                    Response {
-                                        edits,
-                                        pre_rendered: None,
-                                    }
-                                }
-                            };
+        let mut vir = VirtualDom::new_with_props(root, props);
 
-                            serde_json::to_value(&response).unwrap()
-                        };
-
-                        // Return the edits into the webview runtime
-                        Some(RpcResponse::new_result(req.id.take(), Some(edits)))
-                    }
-                    "user_event" => {
-                        log::debug!("User event received");
-
-                        // let registry = registry.clone();
-                        let vdom = vdom.clone();
-                        let response = async_std::task::block_on(async move {
-                            let mut lock = vdom.write().unwrap();
-                            // let mut reg_lock = registry.write().unwrap();
-
-                            // a deserialized event
-                            let data = req.params.unwrap();
-                            log::debug!("Data: {:#?}", data);
-                            let event = trigger_from_serialized(data);
-
-                            lock.queue_event(event);
-
-                            // Create the thin wrapper around the registry to collect the edits into
-                            let mut real = dom::WebviewDom::new();
+        let channel = vir.get_event_sender();
+        struct WebviewBridge {}
+        impl RealDom for WebviewBridge {
+            fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
+                todo!()
+            }
 
-                            // Serialize the edit stream
-                            //
-                            let mut edits = Vec::new();
-                            lock.progress_with_event(&mut real, &mut edits)
-                                .await
-                                .expect("failed to progress");
+            fn must_commit(&self) -> bool {
+                false
+            }
 
-                            let response = Response {
-                                edits,
-                                pre_rendered: None,
-                            };
-                            let response = serde_json::to_value(&response).unwrap();
+            fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
 
-                            // Give back the registry into its slot
-                            // *reg_lock = Some(real.consume());
+            fn wait_until_ready<'s>(
+                &'s mut self,
+            ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
+                //
+                Box::pin(async {
+                    //
+                })
+            }
+        }
 
-                            // Return the edits into the webview runtime
-                            Some(RpcResponse::new_result(req.id.take(), Some(response)))
-                        });
+        let mut real_dom = WebviewBridge {};
+        // async_std::task::spawn_local(vir.run(&mut real_dom));
 
-                        response
+        // todo: combine these or something
+        let vdom = Arc::new(RwLock::new(vir));
+        // let registry = Arc::new(RwLock::new(Some(WebviewRegistry::new())));
 
-                        // spawn a task to clean up the garbage
-                    }
-                    _ => todo!("this message failed"),
-                }
-            })
+        let webview = WebViewBuilder::new(window)?
+            .with_url(&format!("data:text/html,{}", HTML_CONTENT))?
+            // .with_rpc_handler(move |_window: &Window, mut req: RpcRequest| {
+            //     match req.method.as_str() {
+            //         "initiate" => {
+            //             let edits = if let Some(edits) = &redits {
+            //                 serde_json::to_value(edits).unwrap()
+            //             } else {
+            //                 let mut lock = vdom.write().unwrap();
+            //                 // let mut reg_lock = registry.write().unwrap();
+            //                 // Create the thin wrapper around the registry to collect the edits into
+            //                 let mut real = dom::WebviewDom::new();
+            //                 let pre = pre_rendered.clone();
+            //                 let response = match pre {
+            //                     Some(content) => {
+            //                         lock.rebuild_in_place().unwrap();
+            //                         Response {
+            //                             edits: Vec::new(),
+            //                             pre_rendered: Some(content),
+            //                         }
+            //                     }
+            //                     None => {
+            //                         //
+            //                         let edits = {
+            //                             // let mut edits = Vec::new();
+            //                             todo!()
+            //                             // lock.rebuild(&mut real, &mut edits).unwrap();
+            //                             // edits
+            //                         };
+            //                         Response {
+            //                             edits,
+            //                             pre_rendered: None,
+            //                         }
+            //                     }
+            //                 };
+            //                 serde_json::to_value(&response).unwrap()
+            //             };
+            //             // Return the edits into the webview runtime
+            //             Some(RpcResponse::new_result(req.id.take(), Some(edits)))
+            //         }
+            //         "user_event" => {
+            //             log::debug!("User event received");
+            //             // let registry = registry.clone();
+            //             let vdom = vdom.clone();
+            //             let response = async_std::task::block_on(async move {
+            //                 let mut lock = vdom.write().unwrap();
+            //                 // let mut reg_lock = registry.write().unwrap();
+            //                 // a deserialized event
+            //                 let data = req.params.unwrap();
+            //                 log::debug!("Data: {:#?}", data);
+            //                 let event = trigger_from_serialized(data);
+            //                 // lock.queue_event(event);
+            //                 // Create the thin wrapper around the registry to collect the edits into
+            //                 let mut real = dom::WebviewDom::new();
+            //                 // Serialize the edit stream
+            //                 //
+            //                 let mut edits = Vec::new();
+            //                 // lock.run(&mut real, &mut edits)
+            //                 //     .await
+            //                 //     .expect("failed to progress");
+            //                 let response = Response {
+            //                     edits,
+            //                     pre_rendered: None,
+            //                 };
+            //                 let response = serde_json::to_value(&response).unwrap();
+            //                 // Give back the registry into its slot
+            //                 // *reg_lock = Some(real.consume());
+            //                 // Return the edits into the webview runtime
+            //                 Some(RpcResponse::new_result(req.id.take(), Some(response)))
+            //             });
+            //             response
+            //             // spawn a task to clean up the garbage
+            //         }
+            //         _ => todo!("this message failed"),
+            //     }
+            // })
             .build()?;
 
         event_loop.run(move |event, _, control_flow| {
@@ -194,33 +207,17 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
                     }
                     _ => {}
                 },
+
                 Event::MainEventsCleared => {
                     webview.resize();
                     // window.request_redraw();
                 }
 
-                _ => {} // Event::WindowEvent { event, .. } => {
-                        //     //
-                        //     match event {
-                        //         WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
-                        //         _ => {}
-                        //     }
-                        // }
-                        // _ => {
-                        //     // let _ = webview.resize();
-                        // }
+                _ => {}
             }
         });
     }
 
-    /// Create a new text-renderer instance from a functional component root.
-    /// Automatically progresses the creation of the VNode tree to completion.
-    ///
-    /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
-    // pub fn new(root: FC<T>, builder: impl FnOnce() -> WVResult<WebView<'static, ()>>) -> Self {
-    //     Self { root }
-    // }
-
     /// Create a new text renderer from an existing Virtual DOM.
     /// This will progress the existing VDom's events to completion.
     pub fn from_vdom() -> Self {
@@ -237,47 +234,3 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
         todo!()
     }
 }
-
-use serde::{Deserialize, Serialize};
-use serde_json::Value;
-
-// use crate::dom::WebviewRegistry;
-
-#[derive(Debug, Serialize, Deserialize)]
-struct MessageParameters {
-    message: String,
-}
-
-fn HANDLER(window: &Window, mut req: RpcRequest) -> Option<RpcResponse> {
-    let mut response = None;
-    if &req.method == "fullscreen" {
-        if let Some(params) = req.params.take() {
-            if let Ok(mut args) = serde_json::from_value::<Vec<bool>>(params) {
-                if !args.is_empty() {
-                    if args.swap_remove(0) {
-                        window.set_fullscreen(Some(Fullscreen::Borderless(None)));
-                    } else {
-                        window.set_fullscreen(None);
-                    }
-                };
-                response = Some(RpcResponse::new_result(req.id.take(), None));
-            }
-        }
-    } else if &req.method == "send-parameters" {
-        if let Some(params) = req.params.take() {
-            if let Ok(mut args) = serde_json::from_value::<Vec<MessageParameters>>(params) {
-                let result = if !args.is_empty() {
-                    let msg = args.swap_remove(0);
-                    Some(Value::String(format!("Hello, {}!", msg.message)))
-                } else {
-                    // NOTE: in the real-world we should send an error response here!
-                    None
-                };
-                // Must always send a response as this is a `call()`
-                response = Some(RpcResponse::new_result(req.id.take(), result));
-            }
-        }
-    }
-
-    response
-}

+ 1 - 1
packages/mobile/src/dom.rs

@@ -30,7 +30,7 @@ impl WebviewDom<'_> {
         self.registry
     }
 }
-impl<'bump> RealDom<'bump> for WebviewDom<'bump> {
+impl<'bump> RealDom for WebviewDom<'bump> {
     fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
         todo!()
         // self.edits.push(PushRoot { root });

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

@@ -118,7 +118,7 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
                                         // Serialize the edit stream
                                         let edits = {
                                             let mut edits = Vec::new();
-                                            lock.rebuild(&mut real, &mut edits).unwrap();
+                                            lock.rebuild(&mut edits).unwrap();
                                             serde_json::to_value(edits).unwrap()
                                         };
 
@@ -140,7 +140,7 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
                                     // Serialize the edit stream
                                     let edits = {
                                         let mut edits = Vec::new();
-                                        lock.rebuild(&mut real, &mut edits).unwrap();
+                                        lock.rebuild(&mut edits).unwrap();
                                         serde_json::to_value(edits).unwrap()
                                     };
 

+ 1 - 0
packages/web/Cargo.toml

@@ -33,6 +33,7 @@ features = [
     "Attr",
     "Document",
     "Element",
+    "CssStyleDeclaration",
     "HtmlElement",
     "HtmlInputElement",
     "HtmlSelectElement",

+ 10 - 1
packages/web/src/cfg.rs

@@ -1,9 +1,13 @@
 pub struct WebConfig {
     pub(crate) hydrate: bool,
+    pub(crate) rootname: String,
 }
 impl Default for WebConfig {
     fn default() -> Self {
-        Self { hydrate: false }
+        Self {
+            hydrate: false,
+            rootname: "dioxusroot".to_string(),
+        }
     }
 }
 impl WebConfig {
@@ -18,4 +22,9 @@ impl WebConfig {
         self.hydrate = f;
         self
     }
+
+    pub fn rootname(mut self, name: impl Into<String>) -> Self {
+        self.rootname = name.into();
+        self
+    }
 }

+ 20 - 43
packages/web/src/dom.rs

@@ -7,8 +7,8 @@ use dioxus_core::{
 use fxhash::FxHashMap;
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{
-    window, Document, Element, Event, HtmlElement, HtmlInputElement, HtmlOptionElement, Node,
-    NodeList,
+    window, CssStyleDeclaration, Document, Element, Event, HtmlElement, HtmlInputElement,
+    HtmlOptionElement, Node, NodeList,
 };
 
 use crate::{nodeslab::NodeSlab, WebConfig};
@@ -23,9 +23,7 @@ pub struct WebsysDom {
 
     root: Element,
 
-    event_receiver: async_channel::Receiver<EventTrigger>,
-
-    trigger: Arc<dyn Fn(EventTrigger)>,
+    sender_callback: Rc<dyn Fn(EventTrigger)>,
 
     // map of listener types to number of those listeners
     // This is roughly a delegater
@@ -40,19 +38,9 @@ pub struct WebsysDom {
     last_node_was_text: bool,
 }
 impl WebsysDom {
-    pub fn new(root: Element, cfg: WebConfig) -> Self {
+    pub fn new(root: Element, cfg: WebConfig, sender_callback: Rc<dyn Fn(EventTrigger)>) -> Self {
         let document = load_document();
 
-        let (sender, receiver) = async_channel::unbounded::<EventTrigger>();
-
-        let sender_callback = Arc::new(move |ev| {
-            let c = sender.clone();
-            wasm_bindgen_futures::spawn_local(async move {
-                log::debug!("sending event through channel");
-                c.send(ev).await.unwrap();
-            });
-        });
-
         let mut nodes = NodeSlab::new(2000);
         let mut listeners = FxHashMap::default();
 
@@ -67,12 +55,11 @@ impl WebsysDom {
                 let el: &Element = node.dyn_ref::<Element>().unwrap();
                 let id: String = el.get_attribute("dio_el").unwrap();
                 let id = id.parse::<usize>().unwrap();
-
-                // this autoresizes the vector if needed
                 nodes[id] = Some(node);
             }
 
             // Load all the event listeners into our listener register
+            // TODO
         }
 
         let mut stack = Stack::with_capacity(10);
@@ -84,18 +71,12 @@ impl WebsysDom {
             nodes,
             listeners,
             document,
-            event_receiver: receiver,
-            trigger: sender_callback,
+            sender_callback,
             root,
             last_node_was_text: false,
         }
     }
 
-    pub async fn wait_for_event(&mut self) -> Option<EventTrigger> {
-        let v = self.event_receiver.recv().await.unwrap();
-        Some(v)
-    }
-
     pub fn process_edits(&mut self, edits: &mut Vec<DomEdit>) {
         for edit in edits.drain(..) {
             log::info!("Handling edit: {:#?}", edit);
@@ -310,7 +291,7 @@ impl WebsysDom {
         if let Some(entry) = self.listeners.get_mut(event) {
             entry.0 += 1;
         } else {
-            let trigger = self.trigger.clone();
+            let trigger = self.sender_callback.clone();
 
             let handler = Closure::wrap(Box::new(move |event: &web_sys::Event| {
                 // "Result" cannot be received from JS
@@ -339,25 +320,21 @@ impl WebsysDom {
     }
 
     fn set_attribute(&mut self, name: &str, value: &str, ns: Option<&str>) {
-        if name == "class" {
+        if let Some(el) = self.stack.top().dyn_ref::<Element>() {
             match ns {
-                Some("http://www.w3.org/2000/svg") => {
-                    //
-                    if let Some(el) = self.stack.top().dyn_ref::<web_sys::SvgElement>() {
-                        let r: web_sys::SvgAnimatedString = el.class_name();
-                        r.set_base_val(value);
-                        // el.set_class_name(value);
-                    }
-                }
-                _ => {
-                    if let Some(el) = self.stack.top().dyn_ref::<Element>() {
-                        el.set_class_name(value);
-                    }
+                // inline style support
+                Some("style") => {
+                    let el = el.dyn_ref::<HtmlElement>().unwrap();
+                    let style_dc: CssStyleDeclaration = el.style();
+                    style_dc.set_property(name, value).unwrap();
                 }
+                _ => el.set_attribute(name, value).unwrap(),
             }
-        } else {
-            if let Some(el) = self.stack.top().dyn_ref::<Element>() {
-                el.set_attribute(name, value).unwrap();
+            match name {
+                "value" => {}
+                "checked" => {}
+                "selected" => {}
+                _ => {}
             }
         }
     }
@@ -418,7 +395,7 @@ impl WebsysDom {
     }
 }
 
-impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
+impl<'a> dioxus_core::diff::RealDom for WebsysDom {
     // fn request_available_node(&mut self) -> ElementId {
     //     let key = self.nodes.insert(None);
     //     log::debug!("making new key: {:#?}", key);

+ 22 - 38
packages/web/src/lib.rs

@@ -2,6 +2,8 @@
 //! --------------
 //! This crate implements a renderer of the Dioxus Virtual DOM for the web browser using Websys.
 
+use std::rc::Rc;
+
 pub use crate::cfg::WebConfig;
 use crate::dom::load_document;
 use dioxus::prelude::{Context, Properties, VNode};
@@ -77,50 +79,25 @@ pub async fn run_with_props<T: Properties + 'static>(
 ) -> Result<()> {
     let mut dom = VirtualDom::new_with_props(root, root_props);
 
-    // let tasks = dom.shared.tasks.clone();
-
-    let root_el = load_document().get_element_by_id("dioxusroot").unwrap();
-    let mut websys_dom = dom::WebsysDom::new(root_el, cfg);
-
-    let mut edits = Vec::new();
-    dom.rebuild(&mut websys_dom, &mut edits)?;
-    websys_dom.process_edits(&mut edits);
+    let root_el = load_document().get_element_by_id(&cfg.rootname).unwrap();
 
-    log::info!("Going into event loop");
+    let tasks = dom.get_event_sender();
 
-    // #[allow(unreachable_code)]
-    loop {
-        let trigger = {
-            let real_queue = websys_dom.wait_for_event();
-            if dom.any_pending_events() {
-                log::info!("tasks is not empty, waiting for either tasks or event system");
-                let mut task = dom.wait_for_event();
+    let mut real = RealDomWebsys {};
 
-                pin_mut!(real_queue);
-                pin_mut!(task);
-
-                match futures_util::future::select(real_queue, task).await {
-                    futures_util::future::Either::Left((trigger, _)) => trigger,
-                    futures_util::future::Either::Right((trigger, _)) => trigger,
-                }
-            } else {
-                log::info!("tasks is empty, waiting for dom event to trigger soemthing");
-                real_queue.await
-            }
-        };
+    // initialize the virtualdom first
+    if cfg.hydrate {
+        dom.rebuild_in_place()?;
+    }
 
-        if let Some(real_trigger) = trigger {
-            log::info!("event received");
+    let mut websys_dom = dom::WebsysDom::new(
+        root_el,
+        cfg,
+        Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
+    );
 
-            dom.queue_event(real_trigger);
+    dom.run(&mut websys_dom).await?;
 
-            let mut edits = Vec::new();
-            dom.progress_with_event(&mut websys_dom, &mut edits).await?;
-            websys_dom.process_edits(&mut edits);
-        }
-    }
-
-    // should actually never return from this, should be an error, rustc just cant see it
     Ok(())
 }
 
@@ -128,3 +105,10 @@ struct HydrationNode {
     id: usize,
     node: Node,
 }
+
+struct RealDomWebsys {}
+impl dioxus::RealDom for RealDomWebsys {
+    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
+        todo!()
+    }
+}