Feat: fix internal lifecycle

This commit is contained in:
Jonathan Kelley committed 2021-02-24 01:31:19 -05:00
1 parent edf09c1892
commit 5204862bc2
6 files changed
+152 -182

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": false "rust-analyzer.inlayHints.enable": true
} }
+3
View File
@@ -10,6 +10,9 @@ pub enum Error {
#[error("Wrong Properties Type")] #[error("Wrong Properties Type")]
WrongProps, WrongProps,
#[error("Base scope has not been mounted yet")]
NotMounted,
// #[error("Out of compute credits")] // #[error("Out of compute credits")]
// OutOfCredits, // OutOfCredits,
+6 -1
View File
@@ -16,12 +16,17 @@ impl EventTrigger {
pub fn new() -> Self { pub fn new() -> Self {
todo!() 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 { pub enum VirtualEvent {
// the event to drain the current lifecycle queue // the event to drain the current lifecycle queue
// Used to initate the dom // Used to initate the dom
InitiateEvent, StartEvent,
// Real events // Real events
ClipboardEvent, ClipboardEvent,
+82 -153
View File
@@ -23,20 +23,19 @@ use std::{
/// An integrated virtual node system that progresses events and diffs UI trees. /// 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. /// Differences are converted into patches which a renderer can use to draw the UI.
pub struct VirtualDom { pub struct VirtualDom {
// pub struct VirtualDom<P: Properties> {
/// All mounted components are arena allocated to make additions, removals, and references easy to work with /// 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. /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
pub(crate) components: Arena<Scope>, pub(crate) components: Arena<Scope>,
/// The index of the root component. /// The index of the root component.
/// Will not be ready if the dom is fresh
base_scope: Index, base_scope: Index,
event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>, event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
// Mark the root props with P, even though they're held by the root component // todo: encapsulate more state into this so we can better reuse it
// This is done so we don't have a "generic" vdom, making it easier to hold references to it, especially when the holders diff_bump: Bump,
// don't care about the generic props type
// Most implementations that use the VirtualDom won't care about the root props anyways.
#[doc(hidden)] #[doc(hidden)]
_root_prop_type: std::any::TypeId, _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 /// 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. /// to toss out the entire tree.
pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self { pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
// 1. Create the component arena let mut components = Arena::new();
// 2. Create the base scope (can never be removed)
// 3. Create the lifecycle queue
// 4. Create the event queue
todo!(); let event_queue = Rc::new(RefCell::new(VecDeque::new()));
// Arena allocate all the components
// This should make it *really* easy to store references in events and such
// let mut components = Arena::new();
// Create a reference to the component in the arena // 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 // evaluate the component, pushing any updates its generates into the lifecycle queue
// let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props); // todo!
// // Create an event queue with a mount for the base scope let _root_prop_type = TypeId::of::<P>();
// let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect())); let diff_bump = Bump::new();
// let _root_prop_type = TypeId::of::<P>(); Self {
components,
// Self { base_scope,
// components, event_queue,
// base_scope, diff_bump,
// event_queue, _root_prop_type,
// _root_prop_type,
// }
}
/// 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.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. /// 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 /// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
/// listeners. /// 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 /// ```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 fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
// pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
let EventTrigger { let EventTrigger {
component_id, component_id,
listener_id, listener_id,
@@ -173,124 +133,93 @@ impl VirtualDom {
// *then* run the non-prop updates that were not already covered by props // *then* run the non-prop updates that were not already covered by props
let mut affected_components = Vec::new(); let mut affected_components = Vec::new();
// It's essentially draining the vec, but with some dancing to release the RefMut // 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 // We also want to be able to push events into the queue from processing the event
while let Some(event) = { while let Some(event) = {
let new_evt = self.event_queue.as_ref().borrow_mut().pop_front(); let new_evt = self.event_queue.as_ref().borrow_mut().pop_front();
new_evt new_evt
} { } {
affected_components.push(event.component_index); if let Some(component_idx) = event.index() {
affected_components.push(component_idx);
}
self.process_lifecycle(event)?; self.process_lifecycle(event)?;
} }
let diff_bump = Bump::new(); // Reset and then build a new diff machine
let diff_machine = DiffMachine::new(&diff_bump); // 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(()) Ok(())
} }
/// Using mutable access to the Virtual Dom, progress a given lifecycle event /// 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);
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 => {}
} }
Ok(()) Ok(LifecycleEvent {
event_type: LifecycleType::PropsChanged {
props: Box::new(new_props),
component: self.base_scope,
},
})
} }
} }
pub struct LifecycleEvent { pub struct LifecycleEvent {
pub component_index: Index,
pub event_type: LifecycleType, 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 { pub enum LifecycleType {
// Component needs to be mounted, but its scope doesn't exist yet
Mount { Mount {
to: Option<Index>, to: Index,
under: usize, under: usize,
props: Box<dyn std::any::Any>, props: Box<dyn std::any::Any>,
}, },
// Parent was evalauted causing new props to generate
PropsChanged { PropsChanged {
props: Box<dyn std::any::Any>, props: Box<dyn std::any::Any>,
component: Index,
}, },
Rendered,
Mounted, // Hook for the subscription API
Removed,
Messaged,
Callback { Callback {
listener_id: i32, component: Index,
}, },
} }
impl LifecycleEvent { impl LifecycleEvent {
// helper method for shortcutting to the enum type fn index(&self) -> Option<Index> {
// probably not necessary match &self.event_type {
fn mount<P: 'static>(which: Index, to: Option<Index>, under: usize, props: P) -> Self { LifecycleType::Mount { to, under, props } => None,
Self {
component_index: which, LifecycleType::PropsChanged { component, .. }
event_type: LifecycleType::Mount { | LifecycleType::Callback { component } => Some(component.clone()),
to,
under,
props: Box::new(props),
},
} }
} }
} }
+8 -1
View File
@@ -2,13 +2,20 @@
//! //!
//! //!
//! //!
use dioxus_core::prelude::bumpalo::Bump;
use dioxus_core::prelude::*; use dioxus_core::prelude::*;
use dioxus_web::*; use dioxus_web::*;
fn main() { fn main() {
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug)); wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
console_error_panic_hook::set_once(); 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! { WebsysRenderer::simple_render(html! {
<div> <div>
<div class="flex items-center justify-center flex-col"> <div class="flex items-center justify-center flex-col">
+52 -26
View File
@@ -26,7 +26,10 @@ use dioxus_core::{
events::EventTrigger, events::EventTrigger,
prelude::{bumpalo::Bump, html, DiffMachine, VNode, VirtualDom, FC}, 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; use mpsc::UnboundedSender;
pub mod interpreter; pub mod interpreter;
use interpreter::PatchMachine; use interpreter::PatchMachine;
@@ -36,7 +39,6 @@ use interpreter::PatchMachine;
/// ///
pub struct WebsysRenderer { pub struct WebsysRenderer {
internal_dom: VirtualDom, internal_dom: VirtualDom,
// Map of handlers
} }
impl WebsysRenderer { impl WebsysRenderer {
@@ -63,31 +65,17 @@ impl WebsysRenderer {
/// Run the renderer, progressing any events that crop up /// Run the renderer, progressing any events that crop up
/// Yield on event handlers /// Yield on event handlers
/// If the dom errors out, self is consumed and the dom is torn down pub async fn run(&mut self) -> dioxus_core::error::Result<()> {
pub async fn run(self) -> dioxus_core::error::Result<()> { // Connect the listeners to the event loop
let WebsysRenderer { mut internal_dom } = self; let (mut sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
// Progress the mount of the root component // Send the start event
internal_dom sender
.progress() .send(EventTrigger::start_event())
.expect("Progressing the root failed :("); .await
.expect("Should not fail");
// set up the channels to connect listeners to the event loop prepare_websys_dom();
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");
// })
// };
// }
// Event loop waits for the receiver to finish up // Event loop waits for the receiver to finish up
// TODO! Connect the sender to the virtual dom's suspense system // TODO! Connect the sender to the virtual dom's suspense system
@@ -97,7 +85,7 @@ impl WebsysRenderer {
// relevant listeners are ran // relevant listeners are ran
// internal state is modified, components are tagged for changes // 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(_) => {} Err(_) => {}
Ok(_) => {} // Ok(_) => render_diffs(), 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)] #[cfg(test)]
mod tests { mod tests {
use std::env; use std::env;