Quellcode durchsuchen

Feat: fix internal lifecycle

Jonathan Kelley vor 4 Jahren
Ursprung
Commit
5204862bc2

+ 1 - 1
.vscode/settings.json

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

+ 3 - 0
packages/core/src/error.rs

@@ -10,6 +10,9 @@ pub enum Error {
     #[error("Wrong Properties Type")]
     WrongProps,
 
+    #[error("Base scope has not been mounted yet")]
+    NotMounted,
+
     // #[error("Out of compute credits")]
     // OutOfCredits,
 

+ 6 - 1
packages/core/src/events.rs

@@ -16,12 +16,17 @@ 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
-    InitiateEvent,
+    StartEvent,
 
     // Real events
     ClipboardEvent,

+ 74 - 145
packages/core/src/virtual_dom.rs

@@ -23,20 +23,19 @@ use std::{
 /// An integrated virtual node system that progresses events and diffs UI trees.
 /// Differences are converted into patches which a renderer can use to draw the UI.
 pub struct VirtualDom {
-    // pub struct VirtualDom<P: Properties> {
     /// All mounted components are arena allocated to make additions, removals, and references easy to work with
     /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
     pub(crate) components: Arena<Scope>,
 
     /// The index of the root component.
+    /// Will not be ready if the dom is fresh
     base_scope: Index,
 
     event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
 
-    // Mark the root props with P, even though they're held by the root component
-    // This is done so we don't have a "generic" vdom, making it easier to hold references to it, especially when the holders
-    // don't care about the generic props type
-    // Most implementations that use the VirtualDom won't care about the root props anyways.
+    // todo: encapsulate more state into this so we can better reuse it
+    diff_bump: Bump,
+
     #[doc(hidden)]
     _root_prop_type: std::any::TypeId,
 }
@@ -56,75 +55,28 @@ impl VirtualDom {
     /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
     /// to toss out the entire tree.
     pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
-        // 1. Create the component arena
-        // 2. Create the base scope (can never be removed)
-        // 3. Create the lifecycle queue
-        // 4. Create the event queue
+        let mut components = Arena::new();
 
-        todo!();
-        // Arena allocate all the components
-        // This should make it *really* easy to store references in events and such
-        // let mut components = Arena::new();
+        let event_queue = Rc::new(RefCell::new(VecDeque::new()));
 
         // Create a reference to the component in the arena
-        // let base_scope = components.insert(Scope::new(root, None));
-
-        // // Create a new mount event with no root container
-        // let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props);
+        // Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
+        // This puts the dom in a usable state on creation, rather than being potentially invalid
+        let base_scope = components.insert(Scope::new::<_, P>(root, root_props, None));
 
-        // // Create an event queue with a mount for the base scope
-        // let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect()));
+        // evaluate the component, pushing any updates its generates into the lifecycle queue
+        // todo!
 
-        // let _root_prop_type = TypeId::of::<P>();
-
-        // Self {
-        //     components,
-        //     base_scope,
-        //     event_queue,
-        //     _root_prop_type,
-        // }
-    }
+        let _root_prop_type = TypeId::of::<P>();
+        let diff_bump = Bump::new();
 
-    /// With access to the virtual dom, schedule an update to the Root component's props
-    pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<()> {
-        // Ensure the props match
-        if TypeId::of::<P>() != self._root_prop_type {
-            return Err(Error::WrongProps);
+        Self {
+            components,
+            base_scope,
+            event_queue,
+            diff_bump,
+            _root_prop_type,
         }
-
-        self.event_queue
-            .as_ref()
-            .borrow_mut()
-            .push_back(LifecycleEvent {
-                event_type: LifecycleType::PropsChanged {
-                    props: Box::new(new_props),
-                },
-                component_index: self.base_scope,
-            });
-
-        Ok(())
-    }
-
-    /// Schedule a future update for a component from outside the vdom!
-    ///
-    /// This lets services external to the virtual dom interact directly with the component and event system.
-    pub fn queue_update() {}
-
-    pub fn start(&mut self) -> Result<EditList> {
-        todo!()
-    }
-
-    /// Pop an event off the event queue and process it
-    /// Update the root props, and progress
-    /// Takes a bump arena to allocate into, making the diff phase as fast as possible
-    pub fn progress(&mut self) -> Result<()> {
-        let event = self
-            .event_queue
-            .as_ref()
-            .borrow_mut()
-            .pop_front()
-            .ok_or(Error::NoEvent)?;
-        self.process_lifecycle(event)
     }
 
     /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
@@ -136,14 +88,22 @@ impl VirtualDom {
     /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
     /// listeners.
     ///
-    /// ```ignore
+    /// 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);
+    /// }
     ///
     /// ```
-    pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<()> {
-        // pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
+    pub fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
         let EventTrigger {
             component_id,
             listener_id,
@@ -173,124 +133,93 @@ impl VirtualDom {
         // *then* run the non-prop updates that were not already covered by props
 
         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
         } {
-            affected_components.push(event.component_index);
+            if let Some(component_idx) = event.index() {
+                affected_components.push(component_idx);
+            }
             self.process_lifecycle(event)?;
         }
 
-        let diff_bump = Bump::new();
-        let diff_machine = DiffMachine::new(&diff_bump);
+        // 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(())
+        Ok(diff_machine.consume())
     }
 
     /// Using mutable access to the Virtual Dom, progress a given lifecycle event
-    ///
-    ///
-    ///
-    ///
-    ///
-    ///
-    fn process_lifecycle(
-        &mut self,
-        LifecycleEvent {
-            component_index: index,
-            event_type,
-        }: LifecycleEvent,
-    ) -> Result<()> {
-        let scope = self.components.get_mut(index).ok_or(Error::NoEvent)?;
-
+    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 } => {
-                if let Some(other) = to {
-                    // mount to another component
-                } else {
-                    // mount to the root
-                }
-
-                // let g = props.as_ref();
-                // scope.run(g);
-                // scope.run(runner, props, dom);
-            }
+            LifecycleType::Mount { to, under, props } => {}
 
             // The parent for this component generated new props and the component needs update
-            LifecycleType::PropsChanged { props } => {
-                //
-            }
-
-            // Component was successfully mounted to the dom
-            LifecycleType::Mounted {} => {
-                //
-            }
-
-            // Component was removed from the DOM
-            // Run any destructors and cleanup for the hooks and the dump the component
-            LifecycleType::Removed {} => {
-                let f = self.components.remove(index);
-                // let f = dom.components.remove(index);
-            }
+            LifecycleType::PropsChanged { props, component } => {}
 
             // Component was messaged via the internal subscription service
-            LifecycleType::Messaged => {
-                //
-            }
+            LifecycleType::Callback { component } => {}
+        }
 
-            // Event from renderer was fired with a given listener ID
-            //
-            LifecycleType::Callback { listener_id } => {}
+        Ok(())
+    }
 
-            // Run any post-render callbacks on a component
-            LifecycleType::Rendered => {}
+    /// 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.
+    pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<LifecycleEvent> {
+        // Ensure the props match
+        if TypeId::of::<P>() != self._root_prop_type {
+            return Err(Error::WrongProps);
         }
 
-        Ok(())
+        Ok(LifecycleEvent {
+            event_type: LifecycleType::PropsChanged {
+                props: Box::new(new_props),
+                component: self.base_scope,
+            },
+        })
     }
 }
 
 pub struct LifecycleEvent {
-    pub component_index: Index,
     pub event_type: LifecycleType,
 }
 
-/// The internal lifecycle event system is managed by these
-/// Right now, we box the properties and but them in the enum
-/// Later, we could directly call the chain of children without boxing
-/// We could try to reuse the boxes somehow
 pub enum LifecycleType {
+    // Component needs to be mounted, but its scope doesn't exist yet
     Mount {
-        to: Option<Index>,
+        to: Index,
         under: usize,
         props: Box<dyn std::any::Any>,
     },
+
+    // Parent was evalauted causing new props to generate
     PropsChanged {
         props: Box<dyn std::any::Any>,
+        component: Index,
     },
-    Rendered,
-    Mounted,
-    Removed,
-    Messaged,
+
+    // Hook for the subscription API
     Callback {
-        listener_id: i32,
+        component: Index,
     },
 }
 
 impl LifecycleEvent {
-    // helper method for shortcutting to the enum type
-    // probably not necessary
-    fn mount<P: 'static>(which: Index, to: Option<Index>, under: usize, props: P) -> Self {
-        Self {
-            component_index: which,
-            event_type: LifecycleType::Mount {
-                to,
-                under,
-                props: Box::new(props),
-            },
+    fn index(&self) -> Option<Index> {
+        match &self.event_type {
+            LifecycleType::Mount { to, under, props } => None,
+
+            LifecycleType::PropsChanged { component, .. }
+            | LifecycleType::Callback { component } => Some(component.clone()),
         }
     }
 }

+ 8 - 1
packages/web/examples/weather.rs

@@ -2,13 +2,20 @@
 //!
 //!
 //!
-use dioxus_core::prelude::bumpalo::Bump;
 use dioxus_core::prelude::*;
 use dioxus_web::*;
 
 fn main() {
     wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
     console_error_panic_hook::set_once();
+    // todo: wire up the component macro macro
+    // div! { class = "asd" onclick = move |_| {}
+    //     div! { class = "asd" onclick = move |_| {} }
+    //     h1! { "123456" "123456" "123456" }
+    //     h2! { "123456" "123456" "123456" }
+    //     h3! { "123456" "123456" "123456" }
+    // };
+
     WebsysRenderer::simple_render(html! {
         <div>
             <div class="flex items-center justify-center flex-col">

+ 54 - 28
packages/web/src/lib.rs

@@ -26,7 +26,10 @@ use dioxus_core::{
     events::EventTrigger,
     prelude::{bumpalo::Bump, html, DiffMachine, VNode, VirtualDom, FC},
 };
-use futures::{channel::mpsc, future, SinkExt, StreamExt};
+use futures::{
+    channel::mpsc::{self, Sender},
+    future, SinkExt, StreamExt,
+};
 use mpsc::UnboundedSender;
 pub mod interpreter;
 use interpreter::PatchMachine;
@@ -36,7 +39,6 @@ use interpreter::PatchMachine;
 ///
 pub struct WebsysRenderer {
     internal_dom: VirtualDom,
-    // Map of handlers
 }
 
 impl WebsysRenderer {
@@ -63,31 +65,17 @@ impl WebsysRenderer {
 
     /// Run the renderer, progressing any events that crop up
     /// Yield on event handlers
-    /// If the dom errors out, self is consumed and the dom is torn down
-    pub async fn run(self) -> dioxus_core::error::Result<()> {
-        let WebsysRenderer { mut internal_dom } = self;
-
-        // Progress the mount of the root component
-        internal_dom
-            .progress()
-            .expect("Progressing the root failed :(");
-
-        // set up the channels to connect listeners to the event loop
-        let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
-
-        // Iterate through the nodes, attaching the closure and sender to the listener
-        // {
-        //     let mut remote_sender = sender.clone();
-        //     let listener = move || {
-        //         let event = EventTrigger::new();
-        //         wasm_bindgen_futures::spawn_local(async move {
-        //             remote_sender
-        //                 .send(event)
-        //                 .await
-        //                 .expect("Updating receiver failed");
-        //         })
-        //     };
-        // }
+    pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
+        // Connect the listeners to the event loop
+        let (mut sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
+
+        // Send the start event
+        sender
+            .send(EventTrigger::start_event())
+            .await
+            .expect("Should not fail");
+
+        prepare_websys_dom();
 
         // Event loop waits for the receiver to finish up
         // TODO! Connect the sender to the virtual dom's suspense system
@@ -97,7 +85,7 @@ impl WebsysRenderer {
             // relevant listeners are ran
             // internal state is modified, components are tagged for changes
 
-            match internal_dom.progress_with_event(event).await {
+            match self.internal_dom.progress_with_event(event) {
                 Err(_) => {}
                 Ok(_) => {} // Ok(_) => render_diffs(),
             }
@@ -174,6 +162,44 @@ impl WebsysRenderer {
     }
 }
 
+fn prepare_websys_dom() {
+    // Initialize the container on the dom
+    // Hook up the body as the root component to render tinto
+    let window = web_sys::window().expect("should have access to the Window");
+    let document = window
+        .document()
+        .expect("should have access to the Document");
+    let body = document.body().unwrap();
+
+    // Build a dummy div
+    let container: &Element = body.as_ref();
+    container.set_inner_html("");
+    container
+        .append_child(
+            document
+                .create_element("div")
+                .expect("should create element OK")
+                .as_ref(),
+        )
+        .expect("should append child OK");
+}
+
+// Progress the mount of the root component
+
+// Iterate through the nodes, attaching the closure and sender to the listener
+// {
+//     let mut remote_sender = sender.clone();
+//     let listener = move || {
+//         let event = EventTrigger::new();
+//         wasm_bindgen_futures::spawn_local(async move {
+//             remote_sender
+//                 .send(event)
+//                 .await
+//                 .expect("Updating receiver failed");
+//         })
+//     };
+// }
+
 #[cfg(test)]
 mod tests {
     use std::env;