diff --git a/.vscode/settings.json b/.vscode/settings.json index 80f51cff8..2f418701b 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,3 +1,3 @@ { - "rust-analyzer.inlayHints.enable": false + "rust-analyzer.inlayHints.enable": true } \ No newline at end of file diff --git a/packages/core/src/error.rs b/packages/core/src/error.rs index 83f528c02..2de19607f 100644 --- a/packages/core/src/error.rs +++ b/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, diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 929da5c47..6614f106a 100644 --- a/packages/core/src/events.rs +++ b/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, diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index bab7481bf..49cb54068 100644 --- a/packages/core/src/virtual_dom.rs +++ b/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 { /// 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, /// The index of the root component. + /// Will not be ready if the dom is fresh base_scope: Index, event_queue: Rc>>, - // 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(root: FC

, 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)); + // 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 a new mount event with no root container - // let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props); + // evaluate the component, pushing any updates its generates into the lifecycle queue + // todo! - // // Create an event queue with a mount for the base scope - // let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect())); + let _root_prop_type = TypeId::of::

(); + let diff_bump = Bump::new(); - // let _root_prop_type = TypeId::of::

(); - - // Self { - // components, - // base_scope, - // event_queue, - // _root_prop_type, - // } - } - - /// With access to the virtual dom, schedule an update to the Root component's props - pub fn update_props(&mut self, new_props: P) -> Result<()> { - // Ensure the props match - if TypeId::of::

() != 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 { - 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. /// + /// 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> { + pub fn progress_with_event(&mut self, evt: EventTrigger) -> Result> { 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(diff_machine.consume()) + } + + /// 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 } => {} + + // The parent for this component generated new props and the component needs update + LifecycleType::PropsChanged { props, component } => {} + + // Component was messaged via the internal subscription service + LifecycleType::Callback { component } => {} + } Ok(()) } - /// 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)?; - - 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); - } - - // 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); - } - - // Component was messaged via the internal subscription service - LifecycleType::Messaged => { - // - } - - // Event from renderer was fired with a given listener ID - // - LifecycleType::Callback { listener_id } => {} - - // 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(&mut self, new_props: P) -> Result { + // Ensure the props match + if TypeId::of::

() != 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, + to: Index, under: usize, props: Box, }, + + // Parent was evalauted causing new props to generate PropsChanged { props: Box, + 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(which: Index, to: Option, under: usize, props: P) -> Self { - Self { - component_index: which, - event_type: LifecycleType::Mount { - to, - under, - props: Box::new(props), - }, + fn index(&self) -> Option { + match &self.event_type { + LifecycleType::Mount { to, under, props } => None, + + LifecycleType::PropsChanged { component, .. } + | LifecycleType::Callback { component } => Some(component.clone()), } } } diff --git a/packages/web/examples/weather.rs b/packages/web/examples/weather.rs index a03723c7b..d785fc5cc 100644 --- a/packages/web/examples/weather.rs +++ b/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! {

diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index bf2f476a4..0aa52dbc6 100644 --- a/packages/web/src/lib.rs +++ b/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; + pub async fn run(&mut self) -> dioxus_core::error::Result<()> { + // Connect the listeners to the event loop + let (mut sender, mut receiver) = mpsc::unbounded::(); - // Progress the mount of the root component - internal_dom - .progress() - .expect("Progressing the root failed :("); + // Send the start event + sender + .send(EventTrigger::start_event()) + .await + .expect("Should not fail"); - // set up the channels to connect listeners to the event loop - let (sender, mut receiver) = mpsc::unbounded::(); - - // 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"); - // }) - // }; - // } + 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;