dioxus/packages/core/src/virtual_dom.rs

203 lines
8 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
use crate::innerlude::*;
use crate::{patch::Edit, scope::Scope};
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::{
any::TypeId,
cell::RefCell,
rc::{Rc, Weak},
};
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
2021-03-11 17:27:01 +00:00
/// -> IE a cache line for each P type with some heuristics on optimizing layout
pub(crate) components: Arena<Scope>,
2021-03-11 17:27:01 +00:00
// pub(crate) components: RefCell<Arena<Box<dyn Scoped>>>,
2021-03-05 20:02:36 +00:00
// pub(crate) components: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
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-05 20:02:36 +00:00
pub(crate) base_scope: ScopeIdx,
2021-02-24 06:31:19 +00:00
pub(crate) root_caller: Rc<dyn Fn(Context) -> DomTree + 'static>,
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-03-05 20:02:36 +00:00
// ======================
// DIFF RELATED ITEMs
// ======================
// // todo: encapsulate more state into this so we can better reuse it
pub(crate) diff_bump: Bump,
// // be very very very very very careful
// pub change_list: EditMachine<'static>,
// // vdom: &'a VirtualDom,
// vdom: *mut Arena<Box<dyn Scoped>>,
// // vdom: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
// pub cur_idx: ScopeIdx,
// // todo
// // do an indexmap sorted by height
// dirty_nodes: fxhash::FxHashSet<ScopeIdx>,
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
}
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-11 17:27:01 +00:00
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let mut components = Arena::new();
// the root is kept around
let root_caller: Rc<dyn Fn(Context) -> DomTree + 'static> =
Rc::new(move |ctx| root(ctx, &root_props));
let weak_caller: Weak<dyn Fn(Context) -> DomTree + 'static> = Rc::downgrade(&root_caller);
let base_scope = components.insert_with(move |id| Scope::new(weak_caller, id, None));
2021-02-07 22:38:17 +00:00
2021-03-11 17:27:01 +00:00
Self {
components,
root_caller,
2021-03-11 17:27:01 +00:00
base_scope,
diff_bump: Bump::new(),
_root_prop_type: TypeId::of::<P>(),
}
}
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-03-05 20:02:36 +00:00
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
2021-03-11 17:27:01 +00:00
log::debug!("rebuilding...");
2021-02-24 08:51:26 +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
2021-03-11 17:27:01 +00:00
// bump.reset();
// Diff from the top
let mut diff_machine = DiffMachine::new(); // partial borrow
{
let component = self
.components
.get_mut(self.base_scope)
.expect("failed to acquire base scope");
component.run();
}
// get raw pointer to the arena
let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
2021-03-11 17:27:01 +00:00
{
let component = self
.components
.get(self.base_scope)
.expect("failed to acquire base scope");
diff_machine.diff_node(component.old_frame(), component.new_frame());
}
// chew down the the lifecycle events until all dirty nodes are computed
while let Some(event) = diff_machine.lifecycle_events.pop_front() {
match event {
// A new component has been computed from the diffing algorithm
// create a new component in the arena, run it, move the diffing machine to this new spot, and then diff it
// this will flood the lifecycle queue with new updates
LifeCycleEvent::Mount { caller, id } => {
log::debug!("Mounting a new component");
diff_machine.change_list.load_known_root(id);
// We're modifying the component arena while holding onto references into the assoiated bump arenas of its children
// those references are stable, even if the component arena moves around in memory, thanks to the bump arenas.
// However, there is no way to convey this to rust, so we need to use unsafe to pierce through the lifetime.
unsafe {
let p = &mut *(very_unsafe_components);
// todo, hook up the parent/child indexes properly
let idx = p.insert_with(|f| Scope::new(caller, f, None));
let c = p.get_mut(idx).unwrap();
c.run();
diff_machine.diff_node(c.old_frame(), c.new_frame());
}
}
LifeCycleEvent::PropsChanged => {
//
// break 'render;
}
LifeCycleEvent::SameProps => {
//
// break 'render;
}
LifeCycleEvent::Remove => {
//
// break 'render;
}
}
// } else {
// break 'render;
// }
}
2021-03-05 20:02:36 +00:00
2021-03-11 17:27:01 +00:00
let edits: Vec<Edit<'s>> = diff_machine.consume();
2021-03-08 02:28:20 +00:00
Ok(edits)
2021-02-24 08:51:26 +00:00
}
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-03-11 17:27:01 +00:00
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
let component = self
.components
.get_mut(event.component_id)
.expect("Borrowing should not fail");
2021-03-11 17:27:01 +00:00
component.call_listener(event);
component.run();
2021-03-11 17:27:01 +00:00
let mut diff_machine = DiffMachine::new();
// let mut diff_machine = DiffMachine::new(&self.diff_bump);
diff_machine.diff_node(component.old_frame(), component.new_frame());
2021-03-11 00:42:10 +00:00
2021-03-11 17:27:01 +00:00
Ok(diff_machine.consume())
}
}