Przeglądaj źródła

feat: mutations

Jonathan Kelley 3 lat temu
rodzic
commit
fac4233

+ 1 - 0
packages/core-macro/examples/prop_test.rs

@@ -4,6 +4,7 @@ pub mod dioxus {
     pub mod prelude {
         pub trait Properties {
             type Builder;
+            const IS_STATIC: bool;
             fn builder() -> Self::Builder;
             unsafe fn memoize(&self, other: &Self) -> bool;
         }

+ 6 - 0
packages/core-macro/src/props/mod.rs

@@ -662,6 +662,11 @@ Finally, call `.build()` to create the instance of `{name}`.
                 false => quote! { self == other },
             };
 
+            let is_static = match are_there_generics {
+                true => quote! { false  },
+                false => quote! { true },
+            };
+
             Ok(quote! {
                 impl #impl_generics #name #ty_generics #where_clause {
                     #[doc = #builder_method_doc]
@@ -693,6 +698,7 @@ Finally, call `.build()` to create the instance of `{name}`.
 
                 impl #impl_generics dioxus::prelude::Properties for #name #ty_generics{
                     type Builder = #builder_name #generics_with_empty;
+                    const IS_STATIC: bool = #is_static;
                     fn builder() -> Self::Builder {
                         #name::builder()
                     }

+ 1 - 0
packages/core/examples/borrowed.rs

@@ -67,6 +67,7 @@ impl PartialEq for ChildProps {
 }
 impl Properties for ChildProps {
     type Builder = ();
+    const IS_STATIC: bool = true;
     fn builder() -> Self::Builder {
         ()
     }

+ 15 - 4
packages/core/src/arena.rs

@@ -66,8 +66,6 @@ impl SharedResources {
         // elements are super cheap - the value takes no space
         let raw_elements = Slab::with_capacity(2000);
 
-        // let pending_events = Rc::new(RefCell::new(Vec::new()));
-
         let (sender, receiver) = futures_channel::mpsc::unbounded();
 
         let heuristics = HeuristicsEngine::new();
@@ -106,8 +104,8 @@ impl SharedResources {
     }
 
     /// this is unsafe because the caller needs to track which other scopes it's already using
-    pub unsafe fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
-        let inner = &*self.components.get();
+    pub fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
+        let inner = unsafe { &*self.components.get() };
         inner.get(idx.0)
     }
 
@@ -167,6 +165,19 @@ impl SharedResources {
         self.async_tasks.borrow_mut().push(task);
         TaskHandle {}
     }
+
+    pub fn make_trigger_key(&self, trigger: &EventTrigger) -> EventKey {
+        let height = self
+            .get_scope(trigger.originator)
+            .map(|f| f.height)
+            .unwrap();
+
+        EventKey {
+            height,
+            originator: trigger.originator,
+            priority: trigger.priority,
+        }
+    }
 }
 
 pub struct TaskHandle {}

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

@@ -41,7 +41,7 @@ use crate::innerlude::{Context, DomTree, LazyNodes, FC};
 /// ```
 pub trait Properties: Sized {
     type Builder;
-    const IS_STATIC: bool = false;
+    const IS_STATIC: bool;
     fn builder() -> Self::Builder;
 
     /// Memoization can only happen if the props are 'static

+ 41 - 62
packages/core/src/diff.rs

@@ -69,43 +69,13 @@ use futures_util::Future;
 use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
 use smallvec::{smallvec, SmallVec};
 
-use std::{any::Any, cell::Cell, cmp::Ordering, pin::Pin};
+use std::{any::Any, cell::Cell, cmp::Ordering, marker::PhantomData, pin::Pin};
 use DomEdit::*;
 
-/// Instead of having handles directly over nodes, Dioxus uses simple u32 as node IDs.
-/// The expectation is that the underlying renderer will mainain their Nodes in vec where the ids are the index. This allows
-/// for a form of passive garbage collection where nodes aren't immedately cleaned up.
-///
-/// The "RealDom" abstracts over the... real dom. The RealDom trait assumes that the renderer maintains a stack of real
-/// 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 {
-    fn raw_node_as_any(&self) -> &mut dyn Any;
-
-    /// 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: &'r mut Vec<DomEdit<'bump>>,
+    pub edits: Mutations<'bump>,
 
     pub scope_stack: SmallVec<[ScopeId; 5]>,
 
@@ -114,11 +84,13 @@ pub struct DiffMachine<'r, 'bump> {
     // will be used later for garbage collection
     // we check every seen node and then schedule its eventual deletion
     pub seen_scopes: FxHashSet<ScopeId>,
+
+    _r: PhantomData<&'r ()>,
 }
 
 impl<'r, 'bump> DiffMachine<'r, 'bump> {
     pub(crate) fn new(
-        edits: &'r mut Vec<DomEdit<'bump>>,
+        edits: Mutations<'bump>,
         cur_scope: ScopeId,
         shared: &'bump SharedResources,
     ) -> Self {
@@ -128,6 +100,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
             vdom: shared,
             diffed: FxHashSet::default(),
             seen_scopes: FxHashSet::default(),
+            _r: PhantomData,
         }
     }
 
@@ -136,15 +109,16 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
     ///
     /// This will PANIC if any component elements are passed in.
     pub fn new_headless(
-        edits: &'r mut Vec<DomEdit<'bump>>,
+        // edits: &'r mut Vec<DomEdit<'bump>>,
         shared: &'bump SharedResources,
     ) -> Self {
         Self {
-            edits,
+            edits: Mutations { edits: Vec::new() },
             scope_stack: smallvec![ScopeId(0)],
             vdom: shared,
             diffed: FxHashSet::default(),
             seen_scopes: FxHashSet::default(),
+            _r: PhantomData,
         }
     }
 
@@ -211,13 +185,13 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
                 if old.attributes.len() == new.attributes.len() {
                     for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
                         if old_attr.value != new_attr.value {
-                            please_commit(&mut self.edits);
+                            please_commit(&mut self.edits.edits);
                             self.edit_set_attribute(new_attr);
                         }
                     }
                 } else {
                     // TODO: provide some sort of report on how "good" the diffing was
-                    please_commit(&mut self.edits);
+                    please_commit(&mut self.edits.edits);
                     for attribute in old.attributes {
                         self.edit_remove_attribute(attribute);
                     }
@@ -238,7 +212,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
                 if old.listeners.len() == new.listeners.len() {
                     for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
                         if old_l.event != new_l.event {
-                            please_commit(&mut self.edits);
+                            please_commit(&mut self.edits.edits);
                             self.edit_remove_event_listener(old_l.event);
                             self.edit_new_event_listener(new_l, cur_scope);
                         }
@@ -246,7 +220,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
                         self.fix_listener(new_l);
                     }
                 } else {
-                    please_commit(&mut self.edits);
+                    please_commit(&mut self.edits.edits);
                     for listener in old.listeners {
                         self.edit_remove_event_listener(listener.event);
                     }
@@ -421,6 +395,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
                 dom_id.set(Some(real_id));
 
                 let cur_scope = self.current_scope().unwrap();
+
                 listeners.iter().for_each(|listener| {
                     self.fix_listener(listener);
                     listener.mounted_node.set(Some(real_id));
@@ -464,7 +439,6 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
             }
 
             VNodeKind::Component(vcomponent) => {
-                log::debug!("Mounting a new component");
                 let caller = vcomponent.caller.clone();
 
                 let parent_idx = self.scope_stack.last().unwrap().clone();
@@ -486,9 +460,17 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
                 // Actually initialize the caller's slot with the right address
                 vcomponent.ass_scope.set(Some(new_idx));
 
+                if !vcomponent.can_memoize {
+                    let cur_scope = self.get_scope_mut(&parent_idx).unwrap();
+                    let extended = *vcomponent as *const VComponent;
+                    let extended: *const VComponent<'static> =
+                        unsafe { std::mem::transmute(extended) };
+                    cur_scope.borrowed_props.borrow_mut().push(extended);
+                }
+
                 // TODO:
-                //  Noderefs
-                //  Effects
+                //  add noderefs to current noderef list Noderefs
+                //  add effects to current effect list Effects
 
                 let new_component = self.get_scope_mut(&new_idx).unwrap();
 
@@ -501,9 +483,6 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
                         // failed to run. this is the first time the component ran, and it failed
                         // we manually set its head node to an empty fragment
                         panic!("failing components not yet implemented");
-
-                        // new_component.frames.head
-                        // self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
                     }
                 }
 
@@ -1367,42 +1346,42 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
     // Navigation
     pub(crate) fn edit_push_root(&mut self, root: ElementId) {
         let id = root.as_u64();
-        self.edits.push(PushRoot { id });
+        self.edits.edits.push(PushRoot { id });
     }
 
     pub(crate) fn edit_pop(&mut self) {
-        self.edits.push(PopRoot {});
+        self.edits.edits.push(PopRoot {});
     }
 
     // Add Nodes to the dom
     // add m nodes from the stack
     pub(crate) fn edit_append_children(&mut self, many: u32) {
-        self.edits.push(AppendChildren { many });
+        self.edits.edits.push(AppendChildren { many });
     }
 
     // replace the n-m node on the stack with the m nodes
     // ends with the last element of the chain on the top of the stack
     pub(crate) fn edit_replace_with(&mut self, n: u32, m: u32) {
-        self.edits.push(ReplaceWith { n, m });
+        self.edits.edits.push(ReplaceWith { n, m });
     }
 
     pub(crate) fn edit_insert_after(&mut self, n: u32) {
-        self.edits.push(InsertAfter { n });
+        self.edits.edits.push(InsertAfter { n });
     }
 
     pub(crate) fn edit_insert_before(&mut self, n: u32) {
-        self.edits.push(InsertBefore { n });
+        self.edits.edits.push(InsertBefore { n });
     }
 
     // Remove Nodesfrom the dom
     pub(crate) fn edit_remove(&mut self) {
-        self.edits.push(Remove);
+        self.edits.edits.push(Remove);
     }
 
     // Create
     pub(crate) fn edit_create_text_node(&mut self, text: &'bump str, id: ElementId) {
         let id = id.as_u64();
-        self.edits.push(CreateTextNode { text, id });
+        self.edits.edits.push(CreateTextNode { text, id });
     }
 
     pub(crate) fn edit_create_element(
@@ -1413,15 +1392,15 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
     ) {
         let id = id.as_u64();
         match ns {
-            Some(ns) => self.edits.push(CreateElementNs { id, ns, tag }),
-            None => self.edits.push(CreateElement { id, tag }),
+            Some(ns) => self.edits.edits.push(CreateElementNs { id, ns, tag }),
+            None => self.edits.edits.push(CreateElement { id, tag }),
         }
     }
 
     // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
     pub(crate) fn edit_create_placeholder(&mut self, id: ElementId) {
         let id = id.as_u64();
-        self.edits.push(CreatePlaceholder { id });
+        self.edits.edits.push(CreatePlaceholder { id });
     }
 
     // events
@@ -1434,7 +1413,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
 
         let element_id = mounted_node.get().unwrap().as_u64();
 
-        self.edits.push(NewEventListener {
+        self.edits.edits.push(NewEventListener {
             scope,
             event_name: event,
             mounted_node_id: element_id,
@@ -1442,12 +1421,12 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
     }
 
     pub(crate) fn edit_remove_event_listener(&mut self, event: &'static str) {
-        self.edits.push(RemoveEventListener { event });
+        self.edits.edits.push(RemoveEventListener { event });
     }
 
     // modify
     pub(crate) fn edit_set_text(&mut self, text: &'bump str) {
-        self.edits.push(SetText { text });
+        self.edits.edits.push(SetText { text });
     }
 
     pub(crate) fn edit_set_attribute(&mut self, attribute: &'bump Attribute) {
@@ -1461,7 +1440,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
         // field: &'static str,
         // value: &'bump str,
         // ns: Option<&'static str>,
-        self.edits.push(SetAttribute {
+        self.edits.edits.push(SetAttribute {
             field: name,
             value,
             ns: *namespace,
@@ -1484,7 +1463,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
         // field: &'static str,
         // value: &'bump str,
         // ns: Option<&'static str>,
-        self.edits.push(SetAttribute {
+        self.edits.edits.push(SetAttribute {
             field: name,
             value,
             ns: Some(namespace),
@@ -1493,7 +1472,7 @@ impl<'r, 'bump> DiffMachine<'r, 'bump> {
 
     pub(crate) fn edit_remove_attribute(&mut self, attribute: &Attribute) {
         let name = attribute.name;
-        self.edits.push(RemoveAttribute { name });
+        self.edits.edits.push(RemoveAttribute { name });
     }
 }
 

+ 7 - 15
packages/core/src/events.rs

@@ -28,21 +28,15 @@ pub struct EventTrigger {
     /// The priority of the event
     pub priority: EventPriority,
 }
-impl EventTrigger {
-    pub fn make_key(&self) -> EventKey {
-        EventKey {
-            originator: self.originator,
-            priority: self.priority,
-        }
-    }
-}
 
-#[derive(PartialEq, Eq)]
+#[derive(PartialEq, Eq, Clone, Copy)]
 pub struct EventKey {
     /// The originator of the event trigger
     pub originator: ScopeId,
     /// The priority of the event
     pub priority: EventPriority,
+    /// The height of the scope (used for ordering)
+    pub height: u32,
     // TODO: add the time that the event was queued
 }
 
@@ -117,9 +111,6 @@ pub enum VirtualEvent {
     /// be processed.
     GarbageCollection,
 
-    ///
-    DiffComponent,
-
     /// A type of "immediate" event scheduled by components
     ScheduledUpdate {
         height: u32,
@@ -135,9 +126,11 @@ pub enum VirtualEvent {
     // 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 {},
+    AsyncEvent {
+        should_rerender: bool,
+    },
 
-    // Suspense events are a type of async event
+    // Suspense events are a type of async event generated when suspended nodes are ready to be processed.
     //
     // they have the lowest priority
     SuspenseEvent {
@@ -188,7 +181,6 @@ impl std::fmt::Debug for VirtualEvent {
             VirtualEvent::ScheduledUpdate { .. } => "SetStateEvent",
             VirtualEvent::AsyncEvent { .. } => "AsyncEvent",
             VirtualEvent::SuspenseEvent { .. } => "SuspenseEvent",
-            VirtualEvent::DiffComponent { .. } => "DiffComponent",
         };
 
         f.debug_struct("VirtualEvent").field("type", &name).finish()

+ 3 - 1
packages/core/src/hooks.rs

@@ -127,7 +127,9 @@ where
                 *slot.as_ref().borrow_mut() = Some(output);
                 updater(update_id);
                 EventTrigger {
-                    event: VirtualEvent::AsyncEvent {},
+                    event: VirtualEvent::AsyncEvent {
+                        should_rerender: false,
+                    },
                     originator,
                     priority: EventPriority::Low,
                     real_node_id: None,

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

@@ -12,8 +12,8 @@
 
 pub use crate::innerlude::{
     format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventPriority,
-    EventTrigger, LazyNodes, NodeFactory, Properties, RealDom, ScopeId, SuspendedContext, VNode,
-    VNodeKind, VirtualDom, VirtualEvent, FC,
+    EventTrigger, LazyNodes, NodeFactory, Properties, ScopeId, SuspendedContext, VNode, VNodeKind,
+    VirtualDom, VirtualEvent, FC,
 };
 
 pub mod prelude {

+ 21 - 14
packages/core/src/nodes.rs

@@ -152,15 +152,16 @@ pub struct VComponent<'src> {
 
     pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
 
-    pub(crate) drop_props: Option<&'src dyn FnOnce()>,
+    pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
 
     pub is_static: bool,
 
+    pub can_memoize: bool,
+
     // a pointer into the bump arena (given by the 'src lifetime)
     pub(crate) raw_props: *const (),
 
     // a pointer to the raw fn typ
-    // pub(crate) user_fc: BumpB\,
     pub(crate) user_fc: *const (),
 }
 
@@ -365,20 +366,19 @@ impl<'a> NodeFactory<'a> {
         P: Properties + 'a,
         V: 'a + AsRef<[VNode<'a>]>,
     {
-        // TODO
-        // It's somewhat wrong to go about props like this
+        let bump = self.bump();
 
         // We don't want the fat part of the fat pointer
         // This function does static dispatch so we don't need any VTable stuff
-        let children: &'a V = self.bump().alloc(children);
+        let children: &'a V = bump.alloc(children);
         let children = children.as_ref();
 
-        let props = self.bump().alloc(props);
+        let props = bump.alloc(props);
 
         let raw_props = props as *mut P as *mut ();
         let user_fc = component as *const ();
 
-        let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(self.bump().alloc_with(|| {
+        let comparator: Option<&dyn Fn(&VComponent) -> bool> = Some(bump.alloc_with(|| {
             move |other: &VComponent| {
                 if user_fc == other.user_fc {
                     // Safety
@@ -404,13 +404,19 @@ impl<'a> NodeFactory<'a> {
         }));
 
         // create a closure to drop the props
-        let drop_props: Option<&dyn FnOnce()> = Some(self.bump().alloc_with(|| {
+        let mut has_dropped = false;
+        let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
             move || unsafe {
-                let real_other = raw_props as *mut _ as *mut P;
-                let b = BumpBox::from_raw(real_other);
-                std::mem::drop(b);
+                if !has_dropped {
+                    let real_other = raw_props as *mut _ as *mut P;
+                    let b = BumpBox::from_raw(real_other);
+                    std::mem::drop(b);
+
+                    has_dropped = true;
+                }
             }
-        }));
+        });
+        let drop_props = unsafe { BumpBox::from_raw(drop_props) };
 
         let is_static = children.len() == 0 && P::IS_STATIC && key.is_none();
 
@@ -418,14 +424,15 @@ impl<'a> NodeFactory<'a> {
 
         VNode {
             key,
-            kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
+            kind: VNodeKind::Component(bump.alloc_with(|| VComponent {
                 user_fc,
                 comparator,
                 raw_props,
                 children,
                 caller: NodeFactory::create_component_caller(component, raw_props),
                 is_static,
-                drop_props,
+                drop_props: RefCell::new(Some(drop_props)),
+                can_memoize: P::IS_STATIC,
                 ass_scope: Cell::new(None),
             })),
         }

+ 48 - 20
packages/core/src/scope.rs

@@ -37,6 +37,7 @@ pub struct Scope {
 
     // Listeners
     pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
+    pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
 
     // State
     pub(crate) hooks: HookList,
@@ -94,6 +95,7 @@ impl Scope {
             hooks: Default::default(),
             shared_contexts: Default::default(),
             listeners: Default::default(),
+            borrowed_props: Default::default(),
             descendents: Default::default(),
             pending_garbage: Default::default(),
         }
@@ -114,27 +116,8 @@ impl Scope {
         // 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
-        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");
-        }
-
-        log::debug!("reset okay");
 
-        // make sure we call the drop implementation on all the listeners
-        // this is important to not leak memory
-        self.listeners
-            .borrow_mut()
-            .drain(..)
-            .map(|li| unsafe { &*li })
-            .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
+        self.ensure_drop_safety();
 
         // Safety:
         // - We dropped the listeners, so no more &mut T can be used while these are held
@@ -164,6 +147,51 @@ impl Scope {
         }
     }
 
+    /// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
+    /// causuing UB in our tree.
+    ///
+    /// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
+    /// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
+    /// any possible references to the data in the hook list.
+    ///
+    /// Refrences to hook data can only be stored in listeners and component props. During diffing, we make sure to log
+    /// all listeners and borrowed props so we can clear them here.
+    fn ensure_drop_safety(&mut self) {
+        // make sure all garabge is collected before trying to proceed with anything else
+        debug_assert!(
+            self.pending_garbage.borrow().is_empty(),
+            "clean up your garabge please"
+        );
+
+        // 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
+        let vdom = &self.vdom;
+        self.borrowed_props
+            .get_mut()
+            .drain(..)
+            .map(|li| unsafe { &*li })
+            .for_each(|comp| {
+                // First drop the component's undropped references
+                let scope_id = comp.ass_scope.get().unwrap();
+                let scope = unsafe { vdom.get_scope_mut(scope_id) }.unwrap();
+                scope.ensure_drop_safety();
+
+                // Now, drop our own reference
+                let mut dropper = comp.drop_props.borrow_mut().take().unwrap();
+                dropper();
+            });
+
+        // Now that all the references are gone, we can safely drop our own references in our listeners.
+        self.listeners
+            .get_mut()
+            .drain(..)
+            .map(|li| unsafe { &*li })
+            .for_each(|listener| {
+                listener.callback.borrow_mut().take();
+            });
+    }
+
     // A safe wrapper around calling listeners
     // calling listeners will invalidate the list of listeners
     // The listener list will be completely drained because the next frame will write over previous listeners

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

@@ -26,16 +26,3 @@ pub fn empty_cell() -> Cell<Option<ElementId>> {
 //         Some(self.cmp(other))
 //     }
 // }
-
-pub struct DebugDom {}
-impl DebugDom {
-    pub fn new() -> Self {
-        Self {}
-    }
-}
-
-impl RealDom for DebugDom {
-    fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-        todo!()
-    }
-}

+ 263 - 119
packages/core/src/virtual_dom.rs

@@ -29,7 +29,7 @@ use std::any::Any;
 
 use std::any::TypeId;
 use std::cell::{Ref, RefCell, RefMut};
-use std::collections::{BTreeMap, BTreeSet, BinaryHeap};
+use std::collections::{BTreeMap, BTreeSet, BinaryHeap, HashSet};
 use std::pin::Pin;
 
 /// An integrated virtual node system that progresses events and diffs UI trees.
@@ -53,6 +53,8 @@ pub struct VirtualDom {
     /// Should always be the first (gen=0, id=0)
     pub base_scope: ScopeId,
 
+    active_fibers: Vec<Fiber<'static>>,
+
     pending_events: BTreeMap<EventKey, EventTrigger>,
 
     // for managing the props that were used to create the dom
@@ -144,6 +146,7 @@ impl VirtualDom {
             base_scope,
             _root_props: root_props,
             shared: components,
+            active_fibers: Vec::new(),
             pending_events: BTreeMap::new(),
             _root_prop_type: TypeId::of::<P>(),
         }
@@ -195,8 +198,10 @@ impl VirtualDom {
     /// 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);
+    pub fn rebuild<'s>(&'s mut self) -> Result<Vec<DomEdit<'s>>> {
+        let mut edits = Vec::new();
+        let mutations = Mutations { edits: Vec::new() };
+        let mut diff_machine = DiffMachine::new(mutations, self.base_scope, &self.shared);
 
         let cur_component = diff_machine
             .get_scope_mut(&self.base_scope)
@@ -214,7 +219,7 @@ impl VirtualDom {
             );
         }
 
-        Ok(())
+        Ok(edits)
     }
 
     async fn select_next_event(&mut self) -> Option<EventTrigger> {
@@ -222,8 +227,9 @@ impl VirtualDom {
 
         // 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);
+            log::info!("retrieving event from receiver");
+            let key = self.shared.make_trigger_key(&trigger);
+            self.pending_events.insert(key, trigger);
         }
 
         if self.pending_events.is_empty() {
@@ -242,83 +248,172 @@ impl VirtualDom {
                 futures_util::future::Either::Right((trigger, _)) => trigger,
             }
             .unwrap();
-            self.pending_events.insert(trigger.make_key(), trigger);
+            let key = self.shared.make_trigger_key(&trigger);
+            self.pending_events.insert(key, trigger);
         }
 
+        // pop the most important event off
+        let key = self.pending_events.keys().next().unwrap().clone();
+        let trigger = self.pending_events.remove(&key).unwrap();
+
+        Some(trigger)
+    }
+
+    /// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
+    ///
+    /// This method will not wait for any suspended tasks, completely skipping over
+    pub fn run_immediate<'s>(&'s mut self) -> Result<Mutations<'s>> {
+        //
+
         todo!()
+    }
 
-        // let trigger = self.select_next_event().unwrap();
-        // trigger
+    /// Runs the virtualdom with no time limit.
+    ///
+    /// If there are pending tasks, they will be progressed before returning. This is useful when rendering an application
+    /// that has suspended nodes or suspended tasks. Be warned - any async tasks running forever will prevent this method
+    /// from completing. Consider using `run` and specifing a deadline.
+    pub async fn run_unbounded<'s>(&'s mut self) -> Result<Mutations<'s>> {
+        self.run_with_deadline(|| false).await
     }
 
-    // the cooperartive, fiber-based scheduler
-    // is "awaited" and will always return some edits for the real dom to apply
-    //
-    // Right now, we'll happily partially render component trees
-    //
-    // 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<()> {
+    /// Run the virtualdom with a time limit.
+    ///
+    /// 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 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.
+    ///
+    /// Due to platform differences in how time is handled, this method accepts a closure that must return true 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.
+    ///
+    /// The deadline is checked 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.
+    ///
+    /// # Example
+    ///
+    /// ```no_run
+    /// let mut dom = VirtualDom::new(|cx| cx.render(rsx!( div {"hello"} )));
+    /// loop {
+    ///     let started = std::time::Instant::now();
+    ///     let deadline = move || std::time::Instant::now() - started > std::time::Duration::from_millis(16);
+    ///     
+    ///     let mutations = dom.run_with_deadline(deadline).await;
+    ///     apply_mutations(mutations);
+    /// }
+    /// ```
+    pub async fn run_with_deadline<'s>(
+        &'s mut self,
+        mut deadline_exceeded: impl FnMut() -> bool,
+    ) -> Result<Mutations<'s>> {
         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);
+        let mut mutations = Mutations { edits: Vec::new() };
+
+        let mut diff_machine = DiffMachine::new(mutations, cur_component, &self.shared);
+
+        let must_be_re_rendered = HashSet::<ScopeId>::new();
+
+        let mut receiver = self.shared.task_receiver.borrow_mut();
+
+        //
 
         loop {
-            let trigger = self.select_next_event().await.unwrap();
+            if deadline_exceeded() {
+                break;
+            }
 
-            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();
+            /*
+            Strategy:
+            1. Check if there are any events in the receiver.
+            2. If there are, process them and create a new fiber.
+            3. If there are no events, then choose a fiber to work on.
+            4. If there are no fibers, then wait for the next event from the receiver.
+            5. While processing a fiber, periodically check if we're out of time
+            6. If we are almost out of time, then commit our edits to the realdom
+            7. Whenever a fiber is finished, immediately commit it. (IE so deadlines can be infinite if unsupported)
 
-                    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::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);
-                                }
-                            }
+            The user of this method will loop over "run", waiting for edits and then committing them IE
 
-                            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);
-                                }
-                            }
-                        }
-                    }
 
-                    for scope in scopes_to_kill {
-                        // oy kill em
-                        log::debug!("should be removing scope {:#?}", scope);
+            task::spawn_local(async {
+                let vdom = VirtualDom::new(App);
+                loop {
+                    let deadline = wait_for_idle().await;
+                    let mutations = vdom.run_with_deadline(deadline);
+                    realdom.apply_edits(mutations.edits);
+                    realdom.apply_refs(mutations.refs);
+                }
+            });
+
+
+            let vdom = VirtualDom::new(App);
+            let realdom = WebsysDom::new(App);
+            loop {
+                let deadline = wait_for_idle().await;
+                let mutations = vdom.run_with_deadline(deadline);
+
+                realdom.apply_edits(mutations.edits);
+                realdom.apply_refs(mutations.refs);
+            }
+
+
+
+            ```
+            task::spawn_local(async move {
+                let vdom = VirtualDom::new(App);
+                loop {
+                    let mutations = vdom.run_with_deadline(16);
+                    realdom.apply_edits(mutations.edits)?;
+                    realdom.apply_refs(mutations.refs)?;
+                }
+            });
+
+            event_loop.run(move |event, _, flow| {
+
+            });
+            ```
+            */
+            let mut receiver = self.shared.task_receiver.borrow_mut();
+
+            // match receiver.try_next() {}
+
+            let trigger = receiver.next().await.unwrap();
+
+            match &trigger.event {
+                // If any user event is received, then we run the listener and let it dump "needs updates" into the queue
+                //
+                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);
+                        }
                     }
                 }
 
@@ -363,74 +458,93 @@ impl VirtualDom {
                     }
                 }
 
+                // 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::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
+
+                                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);
+                            }
+                        }
+                    }
+
+                    for scope in scopes_to_kill {
+                        // oy kill em
+                        log::debug!("should be removing scope {:#?}", scope);
+                    }
+                }
+
                 // 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);
+                            todo!();
+                            // 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!");
                         }
                     }
                 }
-
-                VirtualEvent::DiffComponent => {
-                    diff_machine.diff_scope(trigger.originator)?;
-                }
-
-                // 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;
-            }
+            // //
+            // if realdom.must_commit() {
+            //     // commit these edits and then wait for the next idle period
+            //     realdom.commit_edits(&mut diff_machine.edits).await;
+            // }
         }
 
-        Ok(())
+        Ok(diff_machine.edits)
     }
 
     pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<EventTrigger> {
-        todo!()
-        // std::rc::Rc::new(move |_| {
-        //     //
-        // })
-        // todo()
+        self.shared.task_sender.clone()
     }
 }
 
@@ -439,8 +553,38 @@ impl VirtualDom {
 unsafe impl Sync for VirtualDom {}
 unsafe impl Send for VirtualDom {}
 
-fn select_next_event(
-    pending_events: &mut BTreeMap<EventKey, EventTrigger>,
-) -> Option<EventTrigger> {
-    None
+struct Fiber<'a> {
+    trigger: EventTrigger,
+
+    // scopes that haven't been updated yet
+    pending_scopes: Vec<ScopeId>,
+
+    pending_nodes: Vec<*const VNode<'a>>,
+
+    // WIP edits
+    edits: Vec<DomEdit<'a>>,
+
+    started: bool,
+
+    completed: bool,
+}
+
+impl Fiber<'_> {
+    fn new(trigger: EventTrigger) -> Self {
+        Self {
+            trigger,
+            pending_scopes: Vec::new(),
+            pending_nodes: Vec::new(),
+            edits: Vec::new(),
+            started: false,
+            completed: false,
+        }
+    }
+}
+
+/// The "Mutations" object holds the changes that need to be made to the DOM.
+pub struct Mutations<'s> {
+    // todo: apply node refs
+    // todo: apply effects
+    pub edits: Vec<DomEdit<'s>>,
 }

+ 0 - 1
packages/core/tests/diffing.rs

@@ -11,7 +11,6 @@ use dioxus::{
     arena::SharedResources,
     diff::{CreateMeta, DiffMachine},
     prelude::*,
-    util::DebugDom,
     DomEdit,
 };
 use dioxus_core as dioxus;

+ 32 - 0
packages/core/tests/eventsystem.rs

@@ -0,0 +1,32 @@
+use bumpalo::Bump;
+
+use anyhow::{Context, Result};
+use dioxus::{
+    arena::SharedResources,
+    diff::{CreateMeta, DiffMachine},
+    prelude::*,
+    DomEdit,
+};
+use dioxus_core as dioxus;
+use dioxus_html as dioxus_elements;
+
+#[async_std::test]
+async fn event_queue_works() {
+    static App: FC<()> = |cx| {
+        cx.render(rsx! {
+            div { "hello world" }
+        })
+    };
+
+    let mut dom = VirtualDom::new(App);
+    let edits = dom.rebuild().unwrap();
+
+    async_std::task::spawn_local(async move {
+        match dom.run_unbounded().await {
+            Err(_) => todo!(),
+            Ok(mutations) => {
+                //
+            }
+        }
+    });
+}

+ 20 - 20
packages/desktop/src/lib.rs

@@ -88,26 +88,26 @@ impl<T: Properties + 'static> WebviewRenderer<T> {
 
         let channel = vir.get_event_sender();
         struct WebviewBridge {}
-        impl RealDom for WebviewBridge {
-            fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
-                todo!()
-            }
-
-            fn must_commit(&self) -> bool {
-                false
-            }
-
-            fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
-
-            fn wait_until_ready<'s>(
-                &'s mut self,
-            ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
-                //
-                Box::pin(async {
-                    //
-                })
-            }
-        }
+        // impl RealDom for WebviewBridge {
+        //     fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
+        //         todo!()
+        //     }
+
+        //     fn must_commit(&self) -> bool {
+        //         false
+        //     }
+
+        //     fn commit_edits<'a>(&mut self, edits: &mut Vec<DomEdit<'a>>) {}
+
+        //     fn wait_until_ready<'s>(
+        //         &'s mut self,
+        //     ) -> std::pin::Pin<Box<dyn std::future::Future<Output = ()> + 's>> {
+        //         //
+        //         Box::pin(async {
+        //             //
+        //         })
+        //     }
+        // }
 
         let mut real_dom = WebviewBridge {};
         // async_std::task::spawn_local(vir.run(&mut real_dom));

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

@@ -96,7 +96,7 @@ pub async fn run_with_props<T: Properties + 'static>(
         Rc::new(move |event| tasks.unbounded_send(event).unwrap()),
     );
 
-    dom.run(&mut websys_dom).await?;
+    dom.run_with_deadline(&mut websys_dom).await?;
 
     Ok(())
 }