2021-02-15 04:39:46 +00:00
|
|
|
// use crate::{changelist::EditList, nodes::VNode};
|
2021-02-24 06:32:50 +00:00
|
|
|
|
2021-03-12 21:58:30 +00:00
|
|
|
use crate::{error::Error, innerlude::*};
|
2021-03-12 19:27:32 +00:00
|
|
|
use crate::{patch::Edit, scope::Scope};
|
2021-03-03 07:27:26 +00:00
|
|
|
use generational_arena::Arena;
|
2021-03-18 22:54:26 +00:00
|
|
|
use std::{
|
|
|
|
any::TypeId,
|
|
|
|
borrow::{Borrow, BorrowMut},
|
2021-03-29 16:31:47 +00:00
|
|
|
cell::RefCell,
|
|
|
|
collections::{BTreeMap, BTreeSet},
|
2021-03-18 22:54:26 +00:00
|
|
|
rc::{Rc, Weak},
|
|
|
|
};
|
2021-03-12 21:58:30 +00:00
|
|
|
use thiserror::private::AsDynError;
|
|
|
|
|
|
|
|
// We actually allocate the properties for components in their parent's properties
|
|
|
|
// We then expose a handle to use those props for render in the form of "OpaqueComponent"
|
2021-03-18 22:54:26 +00:00
|
|
|
pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + 'a;
|
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.
|
2021-02-13 08:19:35 +00:00
|
|
|
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.
|
2021-03-29 16:31:47 +00:00
|
|
|
pub components: Arena<Scope>,
|
2021-03-12 21:58:30 +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-29 16:31:47 +00:00
|
|
|
pub base_scope: ScopeIdx,
|
|
|
|
|
|
|
|
pub(crate) update_schedule: Rc<RefCell<Vec<HierarchyMarker>>>,
|
2021-02-24 06:31:19 +00:00
|
|
|
|
2021-03-12 21:58:30 +00:00
|
|
|
// a strong allocation to the "caller" for the original props
|
|
|
|
#[doc(hidden)]
|
|
|
|
_root_caller: Rc<OpaqueComponent<'static>>,
|
2021-03-12 19:27:32 +00:00
|
|
|
|
2021-03-04 17:03:22 +00:00
|
|
|
// Type of the original props. This is done so VirtualDom does not need to be generic.
|
2021-02-13 08:19:35 +00:00
|
|
|
#[doc(hidden)]
|
|
|
|
_root_prop_type: std::any::TypeId,
|
2021-02-03 07:26:04 +00:00
|
|
|
}
|
|
|
|
|
2021-02-13 08:19:35 +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.
|
2021-03-09 05:58:20 +00:00
|
|
|
pub fn new(root: FC<()>) -> Self {
|
|
|
|
Self::new_with_props(root, ())
|
2021-02-03 07:26:04 +00:00
|
|
|
}
|
2021-03-12 19:27:32 +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();
|
2021-02-07 03:19:56 +00:00
|
|
|
|
2021-03-18 22:54:26 +00:00
|
|
|
// let prr = Rc::new(root_props);
|
|
|
|
|
2021-03-12 21:58:30 +00:00
|
|
|
// the root is kept around with a "hard" allocation
|
2021-03-18 22:54:26 +00:00
|
|
|
let root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| {
|
|
|
|
//
|
|
|
|
// let p2 = &root_props;
|
|
|
|
// let p2 = prr.clone();
|
|
|
|
root(ctx, &root_props)
|
|
|
|
});
|
2021-03-12 21:58:30 +00:00
|
|
|
|
|
|
|
// we then expose this to the component with a weak allocation
|
|
|
|
let weak_caller: Weak<OpaqueComponent> = Rc::downgrade(&root_caller);
|
|
|
|
|
|
|
|
let base_scope = components.insert_with(move |myidx| Scope::new(weak_caller, myidx, None));
|
2021-02-07 22:38:17 +00:00
|
|
|
|
2021-03-11 17:27:01 +00:00
|
|
|
Self {
|
|
|
|
components,
|
2021-03-12 21:58:30 +00:00
|
|
|
_root_caller: root_caller,
|
2021-03-11 17:27:01 +00:00
|
|
|
base_scope,
|
2021-03-29 16:31:47 +00:00
|
|
|
update_schedule: Rc::new(RefCell::new(Vec::new())),
|
2021-03-11 17:27:01 +00:00
|
|
|
_root_prop_type: TypeId::of::<P>(),
|
|
|
|
}
|
2021-02-12 08:07:35 +00:00
|
|
|
}
|
2021-02-03 07:26:04 +00:00
|
|
|
|
2021-03-12 21:58:30 +00:00
|
|
|
// consume the top of the diff machine event cycle and dump edits into the edit list
|
|
|
|
pub fn step(&mut self, event: LifeCycleEvent) -> Result<()> {
|
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
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
|
|
|
// Diff from the top
|
2021-03-16 15:03:59 +00:00
|
|
|
let mut diff_machine = DiffMachine::new();
|
2021-03-11 17:27:01 +00:00
|
|
|
|
2021-03-16 15:03:59 +00:00
|
|
|
let very_unsafe_components = &mut self.components as *mut generational_arena::Arena<Scope>;
|
2021-03-29 16:31:47 +00:00
|
|
|
let component = self
|
2021-03-12 22:21:06 +00:00
|
|
|
.components
|
2021-03-12 21:58:30 +00:00
|
|
|
.get_mut(self.base_scope)
|
2021-03-12 22:21:06 +00:00
|
|
|
.ok_or_else(|| Error::FatalInternal("Acquring base component should never fail"))?;
|
|
|
|
component.run_scope()?;
|
2021-03-16 15:03:59 +00:00
|
|
|
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
2021-03-11 17:27:01 +00:00
|
|
|
|
2021-03-29 16:31:47 +00:00
|
|
|
// log::debug!("New events generated: {:#?}", diff_machine.lifecycle_events);
|
2021-03-12 20:41:36 +00:00
|
|
|
// 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
|
2021-03-14 00:11:06 +00:00
|
|
|
LifeCycleEvent::Mount { caller, id, scope } => {
|
2021-03-12 20:41:36 +00:00
|
|
|
log::debug!("Mounting a new component");
|
|
|
|
|
|
|
|
// 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 {
|
2021-03-14 00:11:06 +00:00
|
|
|
let p = &mut *(very_unsafe_components);
|
2021-03-12 20:41:36 +00:00
|
|
|
|
|
|
|
// todo, hook up the parent/child indexes properly
|
2021-03-14 00:11:06 +00:00
|
|
|
let idx = p.insert_with(|f| Scope::new(caller, f, None));
|
|
|
|
let c = p.get_mut(idx).unwrap();
|
|
|
|
|
|
|
|
let real_scope = scope.upgrade().unwrap();
|
|
|
|
*real_scope.as_ref().borrow_mut() = Some(idx);
|
|
|
|
c.run_scope()?;
|
|
|
|
diff_machine.change_list.load_known_root(id);
|
|
|
|
diff_machine.diff_node(c.old_frame(), c.new_frame());
|
2021-03-12 20:41:36 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-14 00:11:06 +00:00
|
|
|
LifeCycleEvent::PropsChanged { caller, id, scope } => {
|
2021-03-29 16:31:47 +00:00
|
|
|
log::debug!("PROPS CHANGED");
|
2021-03-14 00:11:06 +00:00
|
|
|
let idx = scope.upgrade().unwrap().as_ref().borrow().unwrap();
|
|
|
|
unsafe {
|
|
|
|
let p = &mut *(very_unsafe_components);
|
2021-03-18 22:54:26 +00:00
|
|
|
let c = p.get_mut(idx).unwrap();
|
2021-03-15 06:16:03 +00:00
|
|
|
c.update_caller(caller);
|
2021-03-14 00:11:06 +00:00
|
|
|
c.run_scope()?;
|
|
|
|
diff_machine.change_list.load_known_root(id);
|
|
|
|
diff_machine.diff_node(c.old_frame(), c.new_frame());
|
|
|
|
}
|
2021-03-12 20:41:36 +00:00
|
|
|
// break 'render;
|
|
|
|
}
|
2021-03-14 00:11:06 +00:00
|
|
|
LifeCycleEvent::SameProps { caller, id, scope } => {
|
2021-03-29 16:31:47 +00:00
|
|
|
log::debug!("SAME PROPS RECEIVED");
|
2021-03-12 20:41:36 +00:00
|
|
|
//
|
|
|
|
// break 'render;
|
|
|
|
}
|
|
|
|
LifeCycleEvent::Remove => {
|
|
|
|
//
|
|
|
|
// break 'render;
|
|
|
|
}
|
2021-03-14 00:11:06 +00:00
|
|
|
LifeCycleEvent::Replace { caller, id, .. } => {}
|
2021-03-12 20:41:36 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// } 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-02-13 07:49:10 +00:00
|
|
|
|
2021-03-11 17:27:01 +00:00
|
|
|
component.call_listener(event);
|
2021-03-29 16:31:47 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
-> call listener
|
|
|
|
-> sort through accumulated queue by the top
|
|
|
|
-> run each component, running its children
|
|
|
|
-> add new updates to the tree of updates (these are dirty)
|
|
|
|
->
|
|
|
|
*/
|
|
|
|
// component.run_scope()?;
|
2021-02-12 08:07:35 +00:00
|
|
|
|
2021-03-15 00:33:37 +00:00
|
|
|
// let mut diff_machine = DiffMachine::new();
|
2021-03-11 17:27:01 +00:00
|
|
|
// let mut diff_machine = DiffMachine::new(&self.diff_bump);
|
|
|
|
|
2021-03-14 00:11:06 +00:00
|
|
|
// diff_machine.diff_node(component.old_frame(), component.new_frame());
|
2021-03-11 00:42:10 +00:00
|
|
|
|
2021-03-14 00:11:06 +00:00
|
|
|
// Ok(diff_machine.consume())
|
|
|
|
self.rebuild()
|
2021-03-11 17:27:01 +00:00
|
|
|
}
|
|
|
|
}
|
2021-03-29 16:31:47 +00:00
|
|
|
|
|
|
|
#[derive(Debug, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
pub struct HierarchyMarker {
|
|
|
|
height: u32,
|
|
|
|
}
|