Parcourir la source

Feat: wire up rebuild

Jonathan Kelley il y a 4 ans
Parent
commit
06ae4fc178

+ 1 - 1
.vscode/settings.json

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

+ 1 - 3
packages/core/examples/contextapi.rs

@@ -1,6 +1,4 @@
-
-
-use builder::{button};
+use builder::button;
 use dioxus_core::prelude::*;
 
 fn main() {}

+ 11 - 9
packages/core/src/context.rs

@@ -1,10 +1,11 @@
+use crate::nodes::VNode;
 use crate::prelude::*;
-use crate::{nodes::VNode};
 use bumpalo::Bump;
 use hooks::Hook;
+use log::info;
 use std::{
-    any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
-    sync::atomic::AtomicUsize,
+    any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData, ops::Deref,
+    rc::Rc, sync::atomic::AtomicUsize,
 };
 
 /// Components in Dioxus use the "Context" object to interact with their lifecycle.
@@ -35,6 +36,8 @@ pub struct Context<'src> {
     pub(crate) hooks: &'src RefCell<Vec<*mut Hook>>,
     pub(crate) bump: &'src Bump,
 
+    pub(crate) final_nodes: Rc<RefCell<Option<VNode<'static>>>>,
+
     // holder for the src lifetime
     // todo @jon remove this
     pub _p: std::marker::PhantomData<&'src ()>,
@@ -73,10 +76,10 @@ impl<'a> Context<'a> {
     /// }
     ///```
     pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> DomTree {
-        // pub fn view(self, lazy_nodes: impl for<'b> FnOnce(&'b Bump) -> VNode<'b> + 'a + 'p) -> DomTree {
-        // pub fn view<'p>(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a + 'p) -> DomTree {
-        // pub fn view(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> VNode<'a> {
-        let _g = lazy_nodes(self.bump);
+        let safe_nodes = lazy_nodes(self.bump);
+        let unsafe_nodes = unsafe { std::mem::transmute::<VNode<'a>, VNode<'static>>(safe_nodes) };
+        self.final_nodes.deref().borrow_mut().replace(unsafe_nodes);
+        info!("lazy nodes have been generated");
         DomTree {}
     }
 
@@ -186,9 +189,8 @@ mod context_api {
     //! a failure of implementation.
     //!
     //!
-    
 
-    use std::{ops::Deref};
+    use std::ops::Deref;
 
     pub struct RemoteState<T> {
         inner: *const T,

+ 36 - 25
packages/core/src/events.rs

@@ -16,34 +16,45 @@ impl EventTrigger {
     pub fn new() -> Self {
         todo!()
     }
-
-    /// Create a new "start" event that boots up the virtual dom if it is paused
-    pub fn start_event() -> Self {
-        todo!()
-    }
 }
 
 pub enum VirtualEvent {
-    // the event to drain the current lifecycle queue
-    // Used to initate the dom
-    StartEvent,
-
     // Real events
-    ClipboardEvent,
-    CompositionEvent,
-    KeyboardEvent,
-    FocusEvent,
-    FormEvent,
-    GenericEvent,
-    MouseEvent,
-    PointerEvent,
-    SelectionEvent,
-    TouchEvent,
-    UIEvent,
-    WheelEvent,
-    MediaEvent,
-    ImageEvent,
-    AnimationEvent,
-    TransitionEvent,
+    ClipboardEvent(ClipboardEvent),
+    CompositionEvent(CompositionEvent),
+    KeyboardEvent(KeyboardEvent),
+    FocusEvent(FocusEvent),
+    FormEvent(FormEvent),
+    GenericEvent(GenericEvent),
+    MouseEvent(MouseEvent),
+    PointerEvent(PointerEvent),
+    SelectionEvent(SelectionEvent),
+    TouchEvent(TouchEvent),
+    UIEvent(UIEvent),
+    WheelEvent(WheelEvent),
+    MediaEvent(MediaEvent),
+    ImageEvent(ImageEvent),
+    AnimationEvent(AnimationEvent),
+    TransitionEvent(TransitionEvent),
+
     OtherEvent,
 }
+
+// these should reference the underlying event
+
+pub struct ClipboardEvent {}
+pub struct CompositionEvent {}
+pub struct KeyboardEvent {}
+pub struct FocusEvent {}
+pub struct FormEvent {}
+pub struct GenericEvent {}
+pub struct MouseEvent {}
+pub struct PointerEvent {}
+pub struct SelectionEvent {}
+pub struct TouchEvent {}
+pub struct UIEvent {}
+pub struct WheelEvent {}
+pub struct MediaEvent {}
+pub struct ImageEvent {}
+pub struct AnimationEvent {}
+pub struct TransitionEvent {}

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

@@ -88,7 +88,7 @@ pub mod builder {
 // types used internally that are important
 pub(crate) mod innerlude {
     // pub(crate) use crate::component::Properties;
-    
+
     pub(crate) use crate::context::Context;
     pub(crate) use crate::error::{Error, Result};
     use crate::nodes;
@@ -101,9 +101,7 @@ pub(crate) mod innerlude {
 
     pub type FC<P> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
 
-    mod fc2 {
-        
-    }
+    mod fc2 {}
     // pub type FC<'a, P: 'a> = for<'scope> fn(Context<'scope>, &'scope P) -> DomTree;
     // pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'scope P) -> DomTree;
     // pub type FC<P> = for<'scope, 'r> fn(Context<'scope>, &'r P) -> VNode<'scope>;

+ 65 - 67
packages/core/src/scope.rs

@@ -12,6 +12,7 @@ use std::{
     marker::PhantomData,
     ops::{Deref, DerefMut},
     sync::atomic::AtomicUsize,
+    sync::atomic::Ordering,
     todo,
 };
 
@@ -40,34 +41,23 @@ pub struct Scope {
 
     // lying, cheating reference >:(
     pub props: Box<dyn std::any::Any>,
-    // pub props: Box<dyn Properties>,
 
-    //
     // pub props_type: TypeId,
     pub caller: *const (),
 }
 
 impl Scope {
     // create a new scope from a function
-    pub fn new<'a, P1, P2: 'static>(
-        // pub fn new<'a, P: Properties, PFree: P + 'a, PLocked: P + 'static>(
-        f: FC<P1>,
-        props: P1,
-        parent: Option<Index>,
-    ) -> Self
-// where
-    //     PFree: 'a,
-    //     PLocked: 'static,
-    {
-        // Capture the props type
-        // let props_type = TypeId::of::<P>();
+    pub fn new<'a, P1, P2: 'static>(f: FC<P1>, props: P1, parent: Option<Index>) -> Self {
         let hook_arena = typed_arena::Arena::new();
         let hooks = RefCell::new(Vec::new());
 
         // Capture the caller
         let caller = f as *const ();
 
-        let listeners = Vec::new();
+        let listeners: Vec<Box<dyn Fn()>> = vec![Box::new(|| {
+            log::info!("Base listener called");
+        })];
 
         let old_frame = BumpFrame {
             bump: Bump::new(),
@@ -84,16 +74,11 @@ impl Scope {
         // box the props
         let props = Box::new(props);
 
-        // erase the lifetime
-        // we'll manage this with dom lifecycle
-
         let props = unsafe { std::mem::transmute::<_, Box<P2>>(props) };
 
-        // todo!()
         Self {
             hook_arena,
             hooks,
-            // props_type,
             caller,
             frames,
             listeners,
@@ -102,13 +87,6 @@ impl Scope {
         }
     }
 
-    /// Update this component's props with a new set of props, remotely
-    ///
-    ///
-    pub(crate) fn update_props<'a, P>(&self, _new_props: P) -> crate::error::Result<()> {
-        Ok(())
-    }
-
     /// Create a new context and run the component with references from the Virtual Dom
     /// This function downcasts the function pointer based on the stored props_type
     ///
@@ -120,67 +98,67 @@ impl Scope {
             frame
         };
 
+        let node_slot = std::rc::Rc::new(RefCell::new(None));
         let ctx: Context<'bump> = Context {
             arena: &self.hook_arena,
             hooks: &self.hooks,
             bump: &frame.bump,
             idx: 0.into(),
             _p: PhantomData {},
+            final_nodes: node_slot.clone(),
         };
 
         unsafe {
+            /*
+            SAFETY ALERT
+
+            This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
+            We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
+            we transmute the function back using the props as reference.
+
+            we could do a better check to make sure that the TypeID is correct before casting
+            --
+            This is safe because we check that the generic type matches before casting.
+            */
             // we use plocked to be able to remove the borrowed lifetime
             // these lifetimes could be very broken, so we need to dynamically manage them
             let caller = std::mem::transmute::<*const (), FC<PLocked>>(self.caller);
             let props = self.props.downcast_ref::<PLocked>().unwrap();
+
+            // Note that the actual modification of the vnode head element occurs during this call
             let _nodes: DomTree = caller(ctx, props);
-            todo!("absorb domtree into self")
-            // let nodes: VNode<'bump> = caller(ctx, props);
 
-            // let unsafe_node = std::mem::transmute::<VNode<'bump>, VNode<'static>>(nodes);
-            // frame.head_node = unsafe_node;
+            /*
+            SAFETY ALERT
+
+            DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
+            KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
+
+            Some things to note:
+            - The VNode itself is bound to the lifetime, but it itself is owned by scope.
+            - The VNode has a private API and can only be used from accessors.
+            - Public API cannot drop or destructure VNode
+            */
+            // the nodes we care about have been unsafely extended to a static lifetime in context
+            frame.head_node = node_slot
+                .deref()
+                .borrow_mut()
+                .take()
+                .expect("Viewing did not happen");
         }
-        /*
-        SAFETY ALERT
-
-        This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
-        We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
-        we transmute the function back using the props as reference.
-
-        we could do a better check to make sure that the TypeID is correct before casting
-        --
-        This is safe because we check that the generic type matches before casting.
-        */
-
-        /*
-        SAFETY ALERT
-
-        DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
-        KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
-
-        Some things to note:
-        - The VNode itself is bound to the lifetime, but it itself is owned by scope.
-        - The VNode has a private API and can only be used from accessors.
-        - Public API cannot drop or destructure VNode
-        */
     }
 
     /// Accessor to get the root node and its children (safely)\
     /// Scope is self-referntial, so we are forced to use the 'static lifetime to cheat
-    pub fn current_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> {
+    pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
         self.frames.current_head_node()
     }
 
-    pub fn prev_root_node<'bump>(&'bump self) -> &'bump VNode<'bump> {
-        todo!()
+    pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
+        self.frames.prev_head_node()
     }
 }
 
-pub struct BumpFrame {
-    pub bump: Bump,
-    pub head_node: VNode<'static>,
-}
-
 // todo, do better with the active frame stuff
 // somehow build this vnode with a lifetime tied to self
 // This root node has  "static" lifetime, but it's really not static.
@@ -192,6 +170,11 @@ pub struct ActiveFrame {
     pub frames: [BumpFrame; 2],
 }
 
+pub struct BumpFrame {
+    pub bump: Bump,
+    pub head_node: VNode<'static>,
+}
+
 impl ActiveFrame {
     fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
         Self {
@@ -201,8 +184,23 @@ impl ActiveFrame {
     }
 
     fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
-        let cur_idx = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed) % 1;
-        let raw_node = &self.frames[cur_idx];
+        let raw_node = match self.idx.borrow().load(Ordering::Relaxed) & 1 != 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        };
+        unsafe {
+            let unsafe_head = &raw_node.head_node;
+            let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
+            safe_node
+        }
+    }
+
+    fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
+        let raw_node = match self.idx.borrow().load(Ordering::Relaxed) & 1 == 0 {
+            true => &self.frames[0],
+            false => &self.frames[1],
+        };
+
         unsafe {
             let unsafe_head = &raw_node.head_node;
             let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
@@ -211,8 +209,8 @@ impl ActiveFrame {
     }
 
     fn next(&mut self) -> &mut BumpFrame {
-        self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
-        let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
+        self.idx.fetch_add(1, Ordering::Relaxed);
+        let cur = self.idx.borrow().load(Ordering::Relaxed);
         match cur % 1 {
             1 => &mut self.frames[1],
             0 => &mut self.frames[0],
@@ -276,7 +274,7 @@ mod tests {
         })
     }
 
-    fn child_example(ctx: Context, props: &ExampleProps) -> DomTree {
+    fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree {
         ctx.view(move |b| {
             div(b)
                 .child(text(props.name))

+ 69 - 27
packages/core/src/virtual_dom.rs

@@ -78,6 +78,34 @@ impl VirtualDom {
         }
     }
 
+    //// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom
+    pub fn rebuild(&mut self) -> Result<EditList<'_>> {
+        // Reset and then build a new diff machine
+        // The previous edit list cannot be around while &mut is held
+        // Make sure variance doesnt break this
+        self.diff_bump.reset();
+        let mut diff_machine = DiffMachine::new(&self.diff_bump);
+
+        // this is still a WIP
+        // we'll need to re-fecth all the scopes that were changed and build the diff machine
+        // fetch the component again
+        let component = self
+            .components
+            .get_mut(self.base_scope)
+            .expect("Root should always exist");
+
+        component.run::<()>();
+
+        let old = component.old_frame();
+        let new = component.new_frame();
+        dbg!(old);
+        dbg!(new);
+
+        diff_machine.diff_node(old, new);
+
+        Ok(diff_machine.consume())
+    }
+
     /// 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
@@ -102,17 +130,16 @@ impl VirtualDom {
     /// }
     ///
     /// ```
-    pub fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
+    pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
         let EventTrigger {
             component_id,
             listener_id,
             event: _,
-        } = evt;
+        } = event;
 
         let component = self
             .components
             .get(component_id)
-            // todo: update this with a dedicated error type so implementors know what went wrong
             .expect("Component should exist if an event was triggered");
 
         let listener = component
@@ -121,47 +148,37 @@ impl VirtualDom {
             .expect("Listener should exist if it was triggered")
             .as_ref();
 
-        // Run the callback
-        // This should cause internal state to progress, dumping events into the event queue
-        // todo: integrate this with a tracing mechanism exposed to a dev tool
+        // Run the callback with the user event
         listener();
 
-        // Run through our events, tagging which Indexes are receiving updates
-        // Prop updates take prescedence over subscription updates
-        // Run all prop updates *first* as they will cascade into children.
-        // *then* run the non-prop updates that were not already covered by props
-
+        // Mark dirty components. Descend from the highest node until all dirty nodes are updated.
         let mut affected_components = Vec::new();
 
-        // It's essentially draining the vec, but with some dancing to release the RefMut
-        // We also want to be able to push events into the queue from processing the event
-        while let Some(event) = {
-            let new_evt = self.event_queue.as_ref().borrow_mut().pop_front();
-            new_evt
-        } {
+        while let Some(event) = self.pop_event() {
             if let Some(component_idx) = event.index() {
                 affected_components.push(component_idx);
             }
             self.process_lifecycle(event)?;
         }
 
-        // Reset and then build a new diff machine
-        // The previous edit list cannot be around while &mut is held
-        // Make sure variance doesnt break this
-        self.diff_bump.reset();
-        let diff_machine = DiffMachine::new(&self.diff_bump);
-
-        Ok(diff_machine.consume())
+        todo!()
     }
 
     /// Using mutable access to the Virtual Dom, progress a given lifecycle event
     fn process_lifecycle(&mut self, LifecycleEvent { event_type }: LifecycleEvent) -> Result<()> {
         match event_type {
             // Component needs to be mounted to the virtual dom
-            LifecycleType::Mount { to: _, under: _, props: _ } => {}
+            LifecycleType::Mount {
+                to: _,
+                under: _,
+                props: _,
+            } => {}
 
             // The parent for this component generated new props and the component needs update
-            LifecycleType::PropsChanged { props: _, component: _ } => {}
+            LifecycleType::PropsChanged {
+                props: _,
+                component: _,
+            } => {}
 
             // Component was messaged via the internal subscription service
             LifecycleType::Callback { component: _ } => {}
@@ -170,6 +187,11 @@ impl VirtualDom {
         Ok(())
     }
 
+    /// Pop the top event of the internal lifecycle event queu
+    pub fn pop_event(&self) -> Option<LifecycleEvent> {
+        self.event_queue.as_ref().borrow_mut().pop_front()
+    }
+
     /// With access to the virtual dom, schedule an update to the Root component's props.
     /// This generates the appropriate Lifecycle even. It's up to the renderer to actually feed this lifecycle event
     /// back into the event system to get an edit list.
@@ -215,10 +237,30 @@ pub enum LifecycleType {
 impl LifecycleEvent {
     fn index(&self) -> Option<Index> {
         match &self.event_type {
-            LifecycleType::Mount { to: _, under: _, props: _ } => None,
+            LifecycleType::Mount {
+                to: _,
+                under: _,
+                props: _,
+            } => None,
 
             LifecycleType::PropsChanged { component, .. }
             | LifecycleType::Callback { component } => Some(component.clone()),
         }
     }
 }
+
+mod tests {
+    use super::*;
+
+    #[test]
+    fn start_dom() {
+        let mut dom = VirtualDom::new(|ctx, props| {
+            ctx.view(|bump| {
+                use crate::builder::*;
+                div(bump).child(text("hello,    world")).finish()
+            })
+        });
+        let edits = dom.rebuild().unwrap();
+        println!("{:#?}", edits);
+    }
+}

+ 2 - 7
packages/web/examples/hello.rs

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

+ 2 - 2
packages/web/examples/weather.rs

@@ -9,7 +9,7 @@ fn main() {
     wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
     console_error_panic_hook::set_once();
 
-    WebsysRenderer::new(|ctx, _| {
+    wasm_bindgen_futures::spawn_local(WebsysRenderer::start(|ctx, _| {
         ctx.view(html! {
             <div>
                 <div class="flex items-center justify-center flex-col">
@@ -43,5 +43,5 @@ fn main() {
                 </div>
             </div>
         })
-    });
+    }));
 }

+ 2 - 2
packages/web/src/interpreter.rs

@@ -1,6 +1,6 @@
 use dioxus_core::changelist::Edit;
 use fxhash::FxHashMap;
-use log::{debug, info, log};
+use log::{debug};
 use wasm_bindgen::{closure::Closure, JsCast};
 use web_sys::{window, Document, Element, Event, Node};
 
@@ -95,7 +95,7 @@ impl PatchMachine {
         self.templates.get(&id)
     }
 
-    pub fn init_events_trampoline(&mut self, mut trampoline: ()) {
+    pub fn init_events_trampoline(&mut self, _trampoline: ()) {
         todo!("Event trampoline not a thing anymore")
         // pub fn init_events_trampoline(&mut self, mut trampoline: EventsTrampoline) {
         // self.callback = Some(Closure::wrap(Box::new(move |event: &web_sys::Event| {

+ 19 - 23
packages/web/src/lib.rs

@@ -5,20 +5,14 @@
 use fxhash::FxHashMap;
 use web_sys::{window, Document, Element, Event, Node};
 
-use dioxus::prelude::VElement;
-
 pub use dioxus_core as dioxus;
 use dioxus_core::{
     events::EventTrigger,
-    prelude::{bumpalo::Bump, html, DiffMachine, VNode, VirtualDom, FC},
-};
-use futures::{
-    channel::mpsc::{self, Sender},
-    future, SinkExt, StreamExt,
+    prelude::{VirtualDom, FC},
 };
-use mpsc::UnboundedSender;
+use futures::{channel::mpsc, SinkExt, StreamExt};
+
 pub mod interpreter;
-use interpreter::PatchMachine;
 
 /// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
 /// Under the hood, we leverage WebSys and interact directly with the DOM
@@ -27,7 +21,7 @@ pub struct WebsysRenderer {
     internal_dom: VirtualDom,
 
     // this should be a component index
-    event_map: FxHashMap<(u32, u32), u32>,
+    _event_map: FxHashMap<(u32, u32), u32>,
 }
 
 impl WebsysRenderer {
@@ -57,34 +51,36 @@ impl WebsysRenderer {
     pub fn from_vdom(dom: VirtualDom) -> Self {
         Self {
             internal_dom: dom,
-            event_map: FxHashMap::default(),
+            _event_map: FxHashMap::default(),
         }
     }
 
     pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
-        let (mut sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
-
-        sender
-            .send(EventTrigger::start_event())
-            .await
-            .expect("Should not fail");
+        let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
 
         let body = prepare_websys_dom();
-        let mut patch_machine = PatchMachine::new(body.clone());
+        let mut patch_machine = interpreter::PatchMachine::new(body.clone());
         let root_node = body.first_child().unwrap();
         patch_machine.stack.push(root_node);
 
+        self.internal_dom
+            .rebuild()?
+            .into_iter()
+            .for_each(|edit| patch_machine.handle_edit(&edit));
+
         // Event loop waits for the receiver to finish up
         // TODO! Connect the sender to the virtual dom's suspense system
         // Suspense is basically an external event that can force renders to specific nodes
         while let Some(event) = receiver.next().await {
-            let diffs = self.internal_dom.progress_with_event(event)?;
-            for edit in &diffs {
-                patch_machine.handle_edit(edit);
-            }
+            self.internal_dom
+                .progress_with_event(event)?
+                .into_iter()
+                .for_each(|edit| {
+                    patch_machine.handle_edit(&edit);
+                });
         }
 
-        Ok(()) // should actually never return from this, should be an error
+        Ok(()) // should actually never return from this, should be an error, rustc just cant see it
     }
 }