dioxus/packages/core/src/virtual_dom.rs

274 lines
9.6 KiB
Rust
Raw Normal View History

2021-02-15 04:39:46 +00:00
// use crate::{changelist::EditList, nodes::VNode};
2021-02-24 06:32:50 +00:00
2021-03-05 04:57:25 +00:00
use crate::scope::{create_scoped, Scoped};
use crate::{innerlude::*, scope::Properties};
2021-02-03 07:26:04 +00:00
use bumpalo::Bump;
2021-03-03 07:27:26 +00:00
use generational_arena::Arena;
use std::{
2021-03-03 07:27:26 +00:00
any::TypeId,
2021-02-17 15:53:55 +00:00
borrow::BorrowMut,
cell::{RefCell, UnsafeCell},
2021-02-17 15:53:55 +00:00
collections::{vec_deque, VecDeque},
future::Future,
2021-02-07 19:59:34 +00:00
marker::PhantomData,
rc::Rc,
sync::atomic::AtomicUsize,
};
2021-02-03 07:26:04 +00:00
/// 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 {
2021-02-03 07:26:04 +00:00
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
2021-03-05 04:57:25 +00:00
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
///
/// eventually, come up with a better datastructure that reuses boxes for known P types
/// like a generational typemap bump arena
/// -> IE a cache line for each P type with soem heuristics on optimizing layout
pub(crate) components: Arena<Box<dyn Scoped>>,
2021-02-03 07:26:04 +00:00
2021-02-07 22:38:17 +00:00
/// The index of the root component.
2021-02-24 06:31:19 +00:00
/// Will not be ready if the dom is fresh
2021-03-03 07:27:26 +00:00
base_scope: ScopeIdx,
2021-03-04 17:03:22 +00:00
event_queue: RefCell<VecDeque<LifecycleEvent>>,
2021-02-03 07:26:04 +00:00
2021-02-24 06:31:19 +00:00
// todo: encapsulate more state into this so we can better reuse it
diff_bump: Bump,
2021-03-04 17:03:22 +00:00
// Type of the original props. This is done so VirtualDom does not need to be generic.
#[doc(hidden)]
_root_prop_type: std::any::TypeId,
2021-02-03 07:26:04 +00:00
}
impl VirtualDom {
2021-02-03 07:26:04 +00:00
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
///
/// This means that the root component must either consumes its own context, or statics are used to generate the page.
/// The root component can access things like routing in its context.
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
2021-02-03 07:26:04 +00:00
}
/// Start a new VirtualDom instance with a dependent props.
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
///
/// 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.
2021-03-05 04:57:25 +00:00
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
2021-02-24 06:31:19 +00:00
let mut components = Arena::new();
2021-03-04 17:03:22 +00:00
let event_queue = RefCell::new(VecDeque::new());
// Create a reference to the component in the arena
2021-02-24 06:31:19 +00:00
// 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
2021-03-05 04:57:25 +00:00
let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
2021-02-07 22:38:17 +00:00
2021-02-24 06:31:19 +00:00
// evaluate the component, pushing any updates its generates into the lifecycle queue
// todo!
2021-02-24 06:31:19 +00:00
let _root_prop_type = TypeId::of::<P>();
let diff_bump = Bump::new();
2021-02-03 07:26:04 +00:00
2021-02-24 06:31:19 +00:00
Self {
components,
base_scope,
event_queue,
diff_bump,
_root_prop_type,
}
}
2021-02-03 07:26:04 +00:00
2021-02-24 15:12:26 +00:00
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
///
///
2021-02-24 08:51:26 +00:00
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");
2021-03-05 04:57:25 +00:00
component.run();
2021-02-24 08:51:26 +00:00
2021-03-03 07:27:26 +00:00
diff_machine.diff_node(component.old_frame(), component.new_frame());
2021-02-24 08:51:26 +00:00
Ok(diff_machine.consume())
}
2021-02-12 21:11:33 +00:00
/// 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
/// dom to completion, tagging components that need updates, compressing events together, and finally emitting a single
/// change list.
///
/// If implementing an external renderer, this is the perfect method to combine with an async event loop that waits on
/// listeners.
///
2021-02-24 06:31:19 +00:00
/// 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.
2021-02-12 21:11:33 +00:00
///
2021-02-24 06:31:19 +00:00
/// ```ignore
/// let (sender, receiver) = channel::new();
/// sender.send(EventTrigger::start());
2021-02-12 21:11:33 +00:00
///
2021-02-24 06:31:19 +00:00
/// let mut dom = VirtualDom::new();
/// dom.suspense_handler(|event| sender.send(event));
2021-02-12 21:11:33 +00:00
///
2021-02-24 06:31:19 +00:00
/// while let Ok(diffs) = dom.progress_with_event(receiver.recv().await) {
/// render(diffs);
/// }
2021-02-12 21:11:33 +00:00
///
/// ```
2021-02-24 08:51:26 +00:00
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
2021-02-12 21:11:33 +00:00
let component = self
.components
2021-03-05 00:20:55 +00:00
.get_mut(event.component_id)
2021-02-12 21:11:33 +00:00
.expect("Component should exist if an event was triggered");
2021-03-05 00:20:55 +00:00
component.call_listener(event);
2021-02-12 21:11:33 +00:00
// 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);
2021-03-05 04:57:25 +00:00
component.run();
2021-03-03 07:27:26 +00:00
diff_machine.diff_node(component.old_frame(), component.new_frame());
Ok(diff_machine.consume())
// Err(crate::error::Error::NoEvent)
2021-02-24 08:51:26 +00:00
// Mark dirty components. Descend from the highest node until all dirty nodes are updated.
// let mut affected_components = Vec::new();
2021-02-24 06:31:19 +00:00
// while let Some(event) = self.pop_event() {
// if let Some(component_idx) = event.index() {
// affected_components.push(component_idx);
// }
// self.process_lifecycle(event)?;
// }
// todo!()
2021-02-12 21:11:33 +00:00
}
2021-02-17 15:53:55 +00:00
/// Using mutable access to the Virtual Dom, progress a given lifecycle event
2021-02-24 06:31:19 +00:00
fn process_lifecycle(&mut self, LifecycleEvent { event_type }: LifecycleEvent) -> Result<()> {
2021-02-17 15:53:55 +00:00
match event_type {
// Component needs to be mounted to the virtual dom
2021-02-24 08:51:26 +00:00
LifecycleType::Mount {
to: _,
under: _,
props: _,
} => {}
2021-02-17 15:53:55 +00:00
// The parent for this component generated new props and the component needs update
2021-02-24 08:51:26 +00:00
LifecycleType::PropsChanged {
props: _,
component: _,
} => {}
2021-02-17 15:53:55 +00:00
// Component was messaged via the internal subscription service
2021-02-24 06:32:50 +00:00
LifecycleType::Callback { component: _ } => {}
2021-02-24 06:31:19 +00:00
}
2021-02-24 06:31:19 +00:00
Ok(())
}
2021-02-24 08:51:26 +00:00
/// Pop the top event of the internal lifecycle event queu
pub fn pop_event(&self) -> Option<LifecycleEvent> {
2021-03-04 17:03:22 +00:00
self.event_queue.borrow_mut().pop_front()
2021-02-24 08:51:26 +00:00
}
2021-02-24 06:31:19 +00:00
/// 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.
2021-03-05 04:57:25 +00:00
/// todo
/// change this to accept a modification closure, so the user gets 0-cost access to the props item.
/// Good for cases where the props is changed remotely (or something similar) and building a whole new props item would
/// be wasteful
pub fn update_props<P: 'static>(
&mut self,
updater: impl FnOnce(&mut P),
) -> Result<LifecycleEvent> {
todo!()
// // 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);
// }
2021-02-12 21:11:33 +00:00
2021-03-05 04:57:25 +00:00
// Ok(LifecycleEvent {
// event_type: LifecycleType::PropsChanged {
// props: Box::new(new_props),
// component: self.base_scope,
// },
// })
}
2021-02-03 07:26:04 +00:00
}
pub struct LifecycleEvent {
pub event_type: LifecycleType,
}
pub enum LifecycleType {
2021-02-24 06:31:19 +00:00
// Component needs to be mounted, but its scope doesn't exist yet
Mount {
2021-03-03 07:27:26 +00:00
to: ScopeIdx,
under: usize,
2021-02-21 02:59:16 +00:00
props: Box<dyn std::any::Any>,
},
2021-02-24 06:31:19 +00:00
// Parent was evalauted causing new props to generate
PropsChanged {
2021-02-21 02:59:16 +00:00
props: Box<dyn std::any::Any>,
2021-03-03 07:27:26 +00:00
component: ScopeIdx,
},
2021-02-24 06:31:19 +00:00
// Hook for the subscription API
Callback {
2021-03-03 07:27:26 +00:00
component: ScopeIdx,
},
}
impl LifecycleEvent {
2021-03-03 07:27:26 +00:00
fn index(&self) -> Option<ScopeIdx> {
2021-02-24 06:31:19 +00:00
match &self.event_type {
2021-02-24 08:51:26 +00:00
LifecycleType::Mount {
to: _,
under: _,
props: _,
} => None,
2021-02-24 06:31:19 +00:00
LifecycleType::PropsChanged { component, .. }
| LifecycleType::Callback { component } => Some(component.clone()),
}
}
}
2021-02-24 08:51:26 +00:00
mod tests {
use super::*;
#[test]
fn start_dom() {
let mut dom = VirtualDom::new(|ctx, props| {
todo!()
// ctx.render(|ctx| {
// use crate::builder::*;
// let bump = ctx.bump();
// div(bump).child(text("hello, world")).finish()
// })
2021-02-24 08:51:26 +00:00
});
let edits = dom.rebuild().unwrap();
println!("{:#?}", edits);
}
}