2021-02-15 04:39:46 +00:00
|
|
|
// use crate::{changelist::EditList, nodes::VNode};
|
2021-02-21 02:59:16 +00:00
|
|
|
use crate::{
|
|
|
|
changelist::{self, EditList},
|
|
|
|
dodriodiff::DiffMachine,
|
|
|
|
nodes::VNode,
|
|
|
|
};
|
2021-02-12 21:11:33 +00:00
|
|
|
use crate::{events::EventTrigger, innerlude::*};
|
2021-02-07 21:21:38 +00:00
|
|
|
use any::Any;
|
2021-02-03 07:26:04 +00:00
|
|
|
use bumpalo::Bump;
|
2021-02-19 01:04:25 +00:00
|
|
|
use changelist::EditMachine;
|
2021-02-07 03:19:56 +00:00
|
|
|
use generational_arena::{Arena, Index};
|
|
|
|
use std::{
|
2021-02-07 21:21:38 +00:00
|
|
|
any::{self, TypeId},
|
2021-02-17 15:53:55 +00:00
|
|
|
borrow::BorrowMut,
|
2021-02-07 03:19:56 +00:00
|
|
|
cell::{RefCell, UnsafeCell},
|
2021-02-17 15:53:55 +00:00
|
|
|
collections::{vec_deque, VecDeque},
|
2021-02-07 03:19:56 +00:00
|
|
|
future::Future,
|
2021-02-07 19:59:34 +00:00
|
|
|
marker::PhantomData,
|
2021-02-13 07:49:10 +00:00
|
|
|
rc::Rc,
|
2021-02-07 03:19:56 +00:00
|
|
|
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.
|
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
|
|
|
|
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
2021-02-12 08:07:35 +00:00
|
|
|
pub(crate) components: Arena<Scope>,
|
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-02-07 03:19:56 +00:00
|
|
|
base_scope: Index,
|
|
|
|
|
2021-02-17 15:53:55 +00:00
|
|
|
event_queue: Rc<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-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-02-03 19:07:07 +00:00
|
|
|
pub fn new(root: FC<()>) -> Self {
|
2021-02-07 03:19:56 +00:00
|
|
|
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-02-21 02:59:16 +00:00
|
|
|
pub fn new_with_props<P: 'static>(root: FC<P>, root_props: P) -> Self {
|
2021-02-24 06:31:19 +00:00
|
|
|
let mut components = Arena::new();
|
2021-02-07 03:19:56 +00:00
|
|
|
|
2021-02-24 06:31:19 +00:00
|
|
|
let event_queue = Rc::new(RefCell::new(VecDeque::new()));
|
2021-02-07 03:19:56 +00:00
|
|
|
|
|
|
|
// 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
|
|
|
|
let base_scope = components.insert(Scope::new::<_, P>(root, root_props, 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-07 03:19:56 +00:00
|
|
|
|
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-13 08:19:35 +00:00
|
|
|
}
|
2021-02-12 08:07:35 +00:00
|
|
|
}
|
2021-02-03 07:26:04 +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-02-24 06:31:19 +00:00
|
|
|
pub fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
|
2021-02-12 21:11:33 +00:00
|
|
|
let EventTrigger {
|
|
|
|
component_id,
|
|
|
|
listener_id,
|
|
|
|
event,
|
|
|
|
} = evt;
|
|
|
|
|
|
|
|
let component = self
|
|
|
|
.components
|
|
|
|
.get(component_id)
|
|
|
|
// todo: update this with a dedicated error type so implementors know what went wrong
|
|
|
|
.expect("Component should exist if an event was triggered");
|
|
|
|
|
|
|
|
let listener = component
|
|
|
|
.listeners
|
|
|
|
.get(listener_id as usize)
|
|
|
|
.expect("Listener should exist if it was triggered")
|
|
|
|
.as_ref();
|
|
|
|
|
|
|
|
// Run the callback
|
2021-02-13 07:49:10 +00:00
|
|
|
// This should cause internal state to progress, dumping events into the event queue
|
|
|
|
// todo: integrate this with a tracing mechanism exposed to a dev tool
|
2021-02-12 21:11:33 +00:00
|
|
|
listener();
|
|
|
|
|
2021-02-13 07:49:10 +00:00
|
|
|
// Run through our events, tagging which Indexes are receiving updates
|
|
|
|
// Prop updates take prescedence over subscription updates
|
|
|
|
// Run all prop updates *first* as they will cascade into children.
|
|
|
|
// *then* run the non-prop updates that were not already covered by props
|
|
|
|
|
2021-02-17 15:53:55 +00:00
|
|
|
let mut affected_components = Vec::new();
|
2021-02-24 06:31:19 +00:00
|
|
|
|
2021-02-17 15:53:55 +00:00
|
|
|
// 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
|
|
|
|
} {
|
2021-02-24 06:31:19 +00:00
|
|
|
if let Some(component_idx) = event.index() {
|
|
|
|
affected_components.push(component_idx);
|
|
|
|
}
|
2021-02-19 01:04:25 +00:00
|
|
|
self.process_lifecycle(event)?;
|
2021-02-13 07:49:10 +00:00
|
|
|
}
|
|
|
|
|
2021-02-24 06:31:19 +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 diff_machine = DiffMachine::new(&self.diff_bump);
|
2021-02-17 15:53:55 +00:00
|
|
|
|
2021-02-24 06:31:19 +00:00
|
|
|
Ok(diff_machine.consume())
|
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 06:31:19 +00:00
|
|
|
LifecycleType::Mount { to, under, props } => {}
|
2021-02-07 03:19:56 +00:00
|
|
|
|
2021-02-17 15:53:55 +00:00
|
|
|
// The parent for this component generated new props and the component needs update
|
2021-02-24 06:31:19 +00:00
|
|
|
LifecycleType::PropsChanged { props, component } => {}
|
2021-02-07 03:19:56 +00:00
|
|
|
|
2021-02-17 15:53:55 +00:00
|
|
|
// Component was messaged via the internal subscription service
|
2021-02-24 06:31:19 +00:00
|
|
|
LifecycleType::Callback { component } => {}
|
|
|
|
}
|
2021-02-07 03:19:56 +00:00
|
|
|
|
2021-02-24 06:31:19 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
2021-02-12 08:07:35 +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.
|
|
|
|
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-17 15:53:55 +00:00
|
|
|
}
|
2021-02-12 21:11:33 +00:00
|
|
|
|
2021-02-24 06:31:19 +00:00
|
|
|
Ok(LifecycleEvent {
|
|
|
|
event_type: LifecycleType::PropsChanged {
|
|
|
|
props: Box::new(new_props),
|
|
|
|
component: self.base_scope,
|
|
|
|
},
|
|
|
|
})
|
2021-02-07 03:19:56 +00:00
|
|
|
}
|
2021-02-03 07:26:04 +00:00
|
|
|
}
|
|
|
|
|
2021-02-07 03:19:56 +00:00
|
|
|
pub struct LifecycleEvent {
|
|
|
|
pub event_type: LifecycleType,
|
|
|
|
}
|
2021-02-12 08:07:35 +00:00
|
|
|
|
2021-02-07 03:19:56 +00:00
|
|
|
pub enum LifecycleType {
|
2021-02-24 06:31:19 +00:00
|
|
|
// Component needs to be mounted, but its scope doesn't exist yet
|
2021-02-12 08:07:35 +00:00
|
|
|
Mount {
|
2021-02-24 06:31:19 +00:00
|
|
|
to: Index,
|
2021-02-12 08:07:35 +00:00
|
|
|
under: usize,
|
2021-02-21 02:59:16 +00:00
|
|
|
props: Box<dyn std::any::Any>,
|
2021-02-12 08:07:35 +00:00
|
|
|
},
|
2021-02-24 06:31:19 +00:00
|
|
|
|
|
|
|
// Parent was evalauted causing new props to generate
|
2021-02-12 08:07:35 +00:00
|
|
|
PropsChanged {
|
2021-02-21 02:59:16 +00:00
|
|
|
props: Box<dyn std::any::Any>,
|
2021-02-24 06:31:19 +00:00
|
|
|
component: Index,
|
2021-02-12 08:07:35 +00:00
|
|
|
},
|
2021-02-24 06:31:19 +00:00
|
|
|
|
|
|
|
// Hook for the subscription API
|
2021-02-12 08:07:35 +00:00
|
|
|
Callback {
|
2021-02-24 06:31:19 +00:00
|
|
|
component: Index,
|
2021-02-12 08:07:35 +00:00
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
impl LifecycleEvent {
|
2021-02-24 06:31:19 +00:00
|
|
|
fn index(&self) -> Option<Index> {
|
|
|
|
match &self.event_type {
|
|
|
|
LifecycleType::Mount { to, under, props } => None,
|
|
|
|
|
|
|
|
LifecycleType::PropsChanged { component, .. }
|
|
|
|
| LifecycleType::Callback { component } => Some(component.clone()),
|
2021-02-12 08:07:35 +00:00
|
|
|
}
|
|
|
|
}
|
2021-02-07 03:19:56 +00:00
|
|
|
}
|