dioxus/packages/core/src/virtual_dom.rs

1115 lines
41 KiB
Rust
Raw Normal View History

2021-05-16 06:06:02 +00:00
//! # VirtualDOM Implementation for Rust
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
//!
//! In this file, multiple items are defined. This file is big, but should be documented well to
//! navigate the innerworkings of the Dom. We try to keep these main mechanics in this file to limit
//! the possible exposed API surface (keep fields private). This particular implementation of VDOM
//! is extremely efficient, but relies on some unsafety under the hood to do things like manage
2021-05-18 05:16:43 +00:00
//! micro-heaps for components. We are currently working on refactoring the safety out into safe(r)
//! abstractions, but current tests (MIRI and otherwise) show no issues with the current implementation.
2021-05-16 06:06:02 +00:00
//!
//! Included is:
//! - The [`VirtualDom`] itself
//! - The [`Scope`] object for mangning component lifecycle
//! - The [`ActiveFrame`] object for managing the Scope`s microheap
//! - The [`Context`] object for exposing VirtualDOM API to components
//! - The [`NodeCtx`] object for lazyily exposing the `Context` API to the nodebuilder API
//! - The [`Hook`] object for exposing state management in components.
//!
//! This module includes just the barebones for a complete VirtualDOM API.
//! Additional functionality is defined in the respective files.
use crate::{arena::ScopeArena, innerlude::*};
2021-05-15 16:03:08 +00:00
use bumpalo::Bump;
2021-03-03 07:27:26 +00:00
use generational_arena::Arena;
2021-03-18 22:54:26 +00:00
use std::{
any::{Any, TypeId},
2021-05-18 05:16:43 +00:00
cell::RefCell,
2021-05-19 14:14:02 +00:00
collections::{HashMap, HashSet, VecDeque},
2021-05-16 06:06:02 +00:00
fmt::Debug,
future::Future,
pin::Pin,
2021-05-27 21:57:59 +00:00
// rc::{Rc, Weak},
sync::{Arc, Weak},
2021-03-18 22:54:26 +00:00
};
2021-05-27 21:57:59 +00:00
type Rc<T> = Arc<T>;
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.
2021-05-15 16:03:08 +00:00
///
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
2021-05-16 07:01:36 +00:00
pub components: ScopeArena,
2021-05-16 06:06:02 +00:00
/// The index of the root component
2021-05-18 05:16:43 +00:00
/// Should always be the first (gen=0, id=0)
2021-03-29 16:31:47 +00:00
pub base_scope: ScopeIdx,
2021-05-15 16:03:08 +00:00
/// All components dump their updates into a queue to be processed
2021-05-16 06:06:02 +00:00
pub(crate) event_queue: EventQueue,
2021-02-24 06:31:19 +00:00
2021-05-16 06:06:02 +00:00
/// a strong allocation to the "caller" for the original component and its props
#[doc(hidden)]
2021-05-16 06:06:02 +00:00
_root_caller: Rc<OpaqueComponent<'static>>,
2021-05-16 06:06:02 +00:00
/// Type of the original props. This is stored as TypeId so VirtualDom does not need to be generic.
///
/// Whenver props need to be updated, an Error will be thrown if the new props do not
/// match the props used to create the VirtualDom.
#[doc(hidden)]
_root_prop_type: std::any::TypeId,
2021-02-03 07:26:04 +00:00
}
2021-05-15 16:03:08 +00:00
// ======================================
2021-05-16 06:06:02 +00:00
// Public Methods for the VirtualDom
2021-05-15 16:03:08 +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-05-16 06:06:02 +00:00
///
/// As an end-user, you'll want to use the Renderer's "new" method instead of this method.
/// Directly creating the VirtualDOM is only useful when implementing a new renderer.
///
///
/// ```ignore
/// // Directly from a closure
///
/// let dom = VirtualDom::new(|ctx, _| ctx.render(rsx!{ div {"hello world"} }));
///
/// // or pass in...
///
/// let root = |ctx, _| {
/// ctx.render(rsx!{
/// div {"hello world"}
/// })
/// }
/// let dom = VirtualDom::new(root);
///
/// // or directly from a fn
///
/// fn Example(ctx: Context, props: &()) -> DomTree {
/// ctx.render(rsx!{ div{"hello world"} })
/// }
///
/// let dom = VirtualDom::new(Example);
/// ```
2021-05-27 21:57:59 +00:00
pub fn new(root: impl Fn(Context, &()) -> DomTree + 'static) -> 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-05-16 06:06:02 +00:00
///
/// ```ignore
/// // Directly from a closure
///
/// let dom = VirtualDom::new(|ctx, props| ctx.render(rsx!{ div {"hello world"} }));
///
/// // or pass in...
///
/// let root = |ctx, props| {
/// ctx.render(rsx!{
/// div {"hello world"}
/// })
/// }
/// let dom = VirtualDom::new(root);
///
/// // or directly from a fn
///
/// fn Example(ctx: Context, props: &SomeProps) -> DomTree {
/// ctx.render(rsx!{ div{"hello world"} })
/// }
///
/// let dom = VirtualDom::new(Example);
/// ```
2021-05-27 21:57:59 +00:00
pub fn new_with_props<P: Properties + 'static>(
root: impl for<'a> Fn(Context<'a>, &'a P) -> DomTree + 'static,
root_props: P,
) -> Self {
2021-05-26 05:40:30 +00:00
let components = ScopeArena::new(Arena::new());
2021-05-16 06:06:02 +00:00
// Normally, a component would be passed as a child in the RSX macro which automatically produces OpaqueComponents
// Here, we need to make it manually, using an RC to force the Weak reference to stick around for the main scope.
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |ctx| root(ctx, &root_props));
2021-05-16 06:06:02 +00:00
// Create a weak reference to the OpaqueComponent for the root scope to use as its render function
let caller_ref = Rc::downgrade(&_root_caller);
// Build a funnel for hooks to send their updates into. The `use_hook` method will call into the update funnel.
let event_queue = EventQueue::default();
let _event_queue = event_queue.clone();
// Make the first scope
// We don't run the component though, so renderers will need to call "rebuild" when they initialize their DOM
2021-05-18 05:16:43 +00:00
let link = components.clone();
2021-05-16 06:06:02 +00:00
let base_scope = components
2021-05-18 05:16:43 +00:00
.with(|arena| {
arena.insert_with(move |myidx| {
Scope::new(caller_ref, myidx, None, 0, _event_queue, link)
})
})
.unwrap();
2021-02-07 22:38:17 +00:00
2021-03-11 17:27:01 +00:00
Self {
2021-05-16 06:06:02 +00:00
_root_caller,
base_scope,
event_queue,
2021-05-18 05:16:43 +00:00
components,
2021-03-11 17:27:01 +00:00
_root_prop_type: TypeId::of::<P>(),
}
}
2021-02-03 07:26:04 +00:00
2021-05-18 05:16:43 +00:00
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
2021-03-05 20:02:36 +00:00
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
2021-05-16 06:06:02 +00:00
let mut diff_machine = DiffMachine::new();
2021-05-16 06:06:02 +00:00
// Schedule an update and then immediately call it on the root component
// This is akin to a hook being called from a listener and requring a re-render
// Instead, this is done on top-level component
let base = self.components.try_get(self.base_scope)?;
2021-05-18 05:16:43 +00:00
let immediate_update = self.event_queue.schedule_update(base);
immediate_update();
2021-05-16 06:06:02 +00:00
self.progress_completely(&mut diff_machine)?;
2021-03-05 20:02:36 +00:00
2021-05-16 06:06:02 +00:00
Ok(diff_machine.consume())
2021-02-24 08:51:26 +00:00
}
pub fn base_scope(&self) -> &Scope {
todo!()
}
2021-05-15 16:03:08 +00:00
}
2021-02-24 08:51:26 +00:00
2021-05-16 06:06:02 +00:00
// ======================================
// Private Methods for the VirtualDom
// ======================================
2021-05-15 16:03:08 +00:00
impl VirtualDom {
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
2021-05-15 16:03:08 +00:00
/// listeners, something like this:
///
/// ```ignore
/// while let Ok(event) = receiver.recv().await {
/// let edits = self.internal_dom.progress_with_event(event)?;
/// for edit in &edits {
/// patch_machine.handle_edit(edit);
/// }
/// }
/// ```
2021-02-12 21:11:33 +00:00
///
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-05-15 16:03:08 +00:00
//
// Developer notes:
// ----
// This method has some pretty complex safety guarantees to uphold.
// We interact with bump arenas, raw pointers, and use UnsafeCell to get a partial borrow of the arena.
// The final EditList has edits that pull directly from the Bump Arenas which add significant complexity
// in crafting a 100% safe solution with traditional lifetimes. Consider this method to be internally unsafe
// but the guarantees provide a safe, fast, and efficient abstraction for the VirtualDOM updating framework.
2021-05-16 06:06:02 +00:00
//
// A good project would be to remove all unsafe from this crate and move the unsafety into safer abstractions.
2021-05-15 16:03:08 +00:00
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList> {
let id = event.component_id.clone();
self.components.try_get_mut(id)?.call_listener(event)?;
2021-05-15 16:03:08 +00:00
let mut diff_machine = DiffMachine::new();
2021-05-16 06:06:02 +00:00
self.progress_completely(&mut diff_machine)?;
Ok(diff_machine.consume())
}
/// Consume the event queue, descending depth-first.
/// Only ever run each component once.
///
/// The DiffMachine logs its progress as it goes which might be useful for certain types of renderers.
pub(crate) fn progress_completely<'s>(
&'s mut self,
diff_machine: &'_ mut DiffMachine<'s>,
) -> Result<()> {
2021-05-16 06:06:02 +00:00
// Add this component to the list of components that need to be difed
#[allow(unused_assignments)]
2021-05-16 06:06:02 +00:00
let mut cur_height: u32 = 0;
2021-05-15 16:03:08 +00:00
// Now, there are events in the queue
let mut seen_nodes = HashSet::<ScopeIdx>::new();
2021-05-16 06:06:02 +00:00
let mut updates = self.event_queue.0.as_ref().borrow_mut();
2021-05-15 16:03:08 +00:00
// Order the nodes by their height, we want the biggest nodes on the top
// This prevents us from running the same component multiple times
updates.sort_unstable();
// Iterate through the triggered nodes (sorted by height) and begin to diff them
for update in updates.drain(..) {
// Make sure this isn't a node we've already seen, we don't want to double-render anything
// If we double-renderer something, this would cause memory safety issues
if seen_nodes.contains(&update.idx) {
continue;
}
2021-03-29 16:31:47 +00:00
2021-05-15 16:03:08 +00:00
// Now, all the "seen nodes" are nodes that got notified by running this listener
seen_nodes.insert(update.idx.clone());
// Start a new mutable borrow to components
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified
2021-05-15 16:03:08 +00:00
2021-05-19 14:14:02 +00:00
let cur_component = self.components.try_get_mut(update.idx).unwrap();
2021-05-15 16:03:08 +00:00
cur_component.run_scope()?;
2021-05-15 16:03:08 +00:00
diff_machine.diff_node(cur_component.old_frame(), cur_component.next_frame());
2021-05-15 16:03:08 +00:00
cur_height = cur_component.height;
2021-05-15 16:03:08 +00:00
2021-05-16 06:06:02 +00:00
log::debug!(
"Processing update: {:#?} with height {}",
&update.idx,
cur_height
);
2021-05-15 16:03:08 +00:00
// Now, the entire subtree has been invalidated. We need to descend depth-first and process
// any updates that the diff machine has proprogated into the component lifecycle queue
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 to build up the subtree
LifeCycleEvent::Mount {
caller,
root_id: id,
stable_scope_addr,
} => {
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.
// Insert a new scope into our component list
let idx = self.components.with(|components| {
components.insert_with(|f| {
Scope::new(
caller,
f,
Some(cur_component.arena_idx),
cur_height + 1,
self.event_queue.clone(),
2021-05-18 05:16:43 +00:00
self.components.clone(),
)
})
})?;
2021-05-15 16:03:08 +00:00
2021-05-19 14:14:02 +00:00
{
let cur_component = self.components.try_get_mut(update.idx).unwrap();
let mut ch = cur_component.children.borrow_mut();
ch.insert(idx);
std::mem::drop(ch);
}
2021-05-15 16:03:08 +00:00
// Grab out that component
let new_component = self.components.try_get_mut(idx).unwrap();
2021-05-15 16:03:08 +00:00
// Actually initialize the caller's slot with the right address
*stable_scope_addr.upgrade().unwrap().as_ref().borrow_mut() = Some(idx);
// Run the scope for one iteration to initialize it
new_component.run_scope()?;
2021-05-15 16:03:08 +00:00
// Navigate the diff machine to the right point in the output dom
diff_machine.change_list.load_known_root(id);
// And then run the diff algorithm
diff_machine
.diff_node(new_component.old_frame(), new_component.next_frame());
2021-05-15 16:03:08 +00:00
// Finally, insert this node as a seen node.
seen_nodes.insert(idx);
}
// A component has remained in the same location but its properties have changed
// We need to process this component and then dump the output lifecycle events into the queue
LifeCycleEvent::PropsChanged {
caller,
root_id,
stable_scope_addr,
} => {
log::debug!("Updating a component after its props have changed");
2021-05-15 16:03:08 +00:00
// Get the stable index to the target component
// This *should* exist due to guarantees in the diff algorithm
let idx = stable_scope_addr
.upgrade()
.unwrap()
.as_ref()
.borrow()
.unwrap();
2021-03-11 00:42:10 +00:00
2021-05-15 16:03:08 +00:00
// Grab out that component
let component = self.components.try_get_mut(idx).unwrap();
2021-05-15 16:03:08 +00:00
// We have to move the caller over or running the scope will fail
component.update_caller(caller);
// Run the scope
component.run_scope()?;
// Navigate the diff machine to the right point in the output dom
diff_machine.change_list.load_known_root(root_id);
// And then run the diff algorithm
diff_machine.diff_node(component.old_frame(), component.next_frame());
// Finally, insert this node as a seen node.
seen_nodes.insert(idx);
}
// A component's parent has updated, but its properties did not change.
// This means the caller ptr is invalidated and needs to be updated, but the component itself does not need to be re-ran
LifeCycleEvent::SameProps {
caller,
stable_scope_addr,
2021-05-16 06:06:02 +00:00
..
2021-05-15 16:03:08 +00:00
} => {
// In this case, the parent made a new DomTree that resulted in the same props for us
// However, since our caller is located in a Bump frame, we need to update the caller pointer (which is now invalid)
log::debug!("Received the same props");
// Get the stable index to the target component
// This *should* exist due to guarantees in the diff algorithm
let idx = stable_scope_addr
.upgrade()
.unwrap()
.as_ref()
.borrow()
.unwrap();
// Grab out that component
let component = self.components.try_get_mut(idx).unwrap();
2021-05-15 16:03:08 +00:00
// We have to move the caller over or running the scope will fail
component.update_caller(caller);
// This time, we will not add it to our seen nodes since we did not actually run it
}
LifeCycleEvent::Remove {
root_id,
stable_scope_addr,
} => {
2021-05-19 14:14:02 +00:00
let id = stable_scope_addr
.upgrade()
.unwrap()
.as_ref()
.borrow()
.unwrap();
log::warn!("Removing node {:#?}", id);
// This would normally be recursive but makes sense to do linear to
let mut children_to_remove = VecDeque::new();
children_to_remove.push_back(id);
// Accumulate all the child components that need to be removed
while let Some(child_id) = children_to_remove.pop_back() {
let comp = self.components.try_get(child_id).unwrap();
let children = comp.children.borrow();
for child in children.iter() {
children_to_remove.push_front(*child);
}
log::debug!("Removing component: {:#?}", child_id);
self.components
.with(|components| components.remove(child_id).unwrap())
.unwrap();
}
2021-05-15 16:03:08 +00:00
}
2021-05-19 14:14:02 +00:00
2021-05-15 16:03:08 +00:00
LifeCycleEvent::Replace {
caller,
root_id: id,
..
} => {
unimplemented!("This feature (Replace) is unimplemented")
}
}
}
}
2021-05-16 06:06:02 +00:00
Ok(())
2021-05-15 16:03:08 +00:00
}
2021-04-05 01:47:53 +00:00
}
// TODO!
//
// These impls are actually wrong. The DOM needs to have a mutex implemented.
unsafe impl Sync for VirtualDom {}
unsafe impl Send for VirtualDom {}
2021-05-15 16:03:08 +00:00
/// Every component in Dioxus is represented by a `Scope`.
///
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
///
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
pub struct Scope {
// The parent's scope ID
pub parent: Option<ScopeIdx>,
2021-05-19 14:14:02 +00:00
// IDs of children that this scope has created
// This enables us to drop the children and their children when this scope is destroyed
pub children: RefCell<HashSet<ScopeIdx>>,
2021-05-18 05:16:43 +00:00
// A reference to the list of components.
// This lets us traverse the component list whenever we need to access our parent or children.
arena_link: ScopeArena,
pub shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
2021-05-15 16:03:08 +00:00
// Our own ID accessible from the component map
pub arena_idx: ScopeIdx,
2021-05-15 16:03:08 +00:00
pub height: u32,
2021-05-16 06:06:02 +00:00
pub event_queue: EventQueue,
2021-05-15 16:03:08 +00:00
pub caller: Weak<OpaqueComponent<'static>>,
2021-05-18 05:16:43 +00:00
pub hookidx: RefCell<usize>,
2021-05-15 16:03:08 +00:00
// ==========================
// slightly unsafe stuff
// ==========================
// an internal, highly efficient storage of vnodes
pub frames: ActiveFrame,
2021-05-15 16:03:08 +00:00
// These hooks are actually references into the hook arena
// These two could be combined with "OwningRef" to remove unsafe usage
// or we could dedicate a tiny bump arena just for them
// could also use ourborous
2021-05-18 05:16:43 +00:00
hooks: RefCell<Vec<Hook>>,
2021-05-15 16:03:08 +00:00
// Unsafety:
// - is self-refenrential and therefore needs to point into the bump
// Stores references into the listeners attached to the vnodes
// NEEDS TO BE PRIVATE
pub(crate) listeners: RefCell<Vec<*const dyn Fn(VirtualEvent)>>,
}
impl Scope {
// we are being created in the scope of an existing component (where the creator_node lifetime comes into play)
// we are going to break this lifetime by force in order to save it on ourselves.
// To make sure that the lifetime isn't truly broken, we receive a Weak RC so we can't keep it around after the parent dies.
// This should never happen, but is a good check to keep around
//
// Scopes cannot be made anywhere else except for this file
// Therefore, their lifetimes are connected exclusively to the virtual dom
fn new<'creator_node>(
caller: Weak<OpaqueComponent<'creator_node>>,
2021-05-26 05:40:30 +00:00
arena_idx: ScopeIdx,
2021-05-15 16:03:08 +00:00
parent: Option<ScopeIdx>,
height: u32,
2021-05-16 06:06:02 +00:00
event_queue: EventQueue,
2021-05-18 05:16:43 +00:00
arena_link: ScopeArena,
2021-05-15 16:03:08 +00:00
) -> Self {
log::debug!(
"New scope created, height is {}, idx is {:?}",
height,
2021-05-26 05:40:30 +00:00
arena_idx
2021-05-15 16:03:08 +00:00
);
2021-05-16 06:06:02 +00:00
2021-05-18 05:16:43 +00:00
// The function to run this scope is actually located in the parent's bump arena.
// Every time the parent is updated, that function is invalidated via double-buffering wiping the old frame.
// If children try to run this invalid caller, it *will* result in UB.
//
// During the lifecycle progression process, this caller will need to be updated. Right now,
// until formal safety abstractions are implemented, we will just use unsafe to "detach" the caller
// lifetime from the bump arena, exposing ourselves to this potential for invalidation. Truthfully,
// this is a bit of a hack, but will remain this way until we've figured out a cleaner solution.
2021-05-16 06:06:02 +00:00
//
// Not the best solution, so TODO on removing this in favor of a dedicated resource abstraction.
2021-05-26 05:40:30 +00:00
let caller = unsafe {
2021-05-16 06:06:02 +00:00
std::mem::transmute::<
Weak<OpaqueComponent<'creator_node>>,
Weak<OpaqueComponent<'static>>,
>(caller)
};
2021-05-15 16:03:08 +00:00
Self {
2021-05-26 05:40:30 +00:00
caller,
2021-05-15 16:03:08 +00:00
parent,
2021-05-26 05:40:30 +00:00
arena_idx,
2021-05-15 16:03:08 +00:00
height,
2021-05-16 06:06:02 +00:00
event_queue,
2021-05-18 05:16:43 +00:00
arena_link,
2021-05-26 05:40:30 +00:00
frames: ActiveFrame::new(),
hooks: Default::default(),
shared_contexts: Default::default(),
listeners: Default::default(),
hookidx: Default::default(),
children: Default::default(),
2021-05-15 16:03:08 +00:00
}
}
2021-05-16 06:06:02 +00:00
2021-05-15 16:03:08 +00:00
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent<'creator_node>>) {
2021-05-16 06:06:02 +00:00
let broken_caller = unsafe {
std::mem::transmute::<
Weak<OpaqueComponent<'creator_node>>,
Weak<OpaqueComponent<'static>>,
>(caller)
};
2021-05-15 16:03:08 +00:00
self.caller = broken_caller;
}
/// Create a new context and run the component with references from the Virtual Dom
/// This function downcasts the function pointer based on the stored props_type
///
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
2021-05-18 05:16:43 +00:00
pub fn run_scope<'sel>(&'sel mut self) -> Result<()> {
2021-05-16 06:06:02 +00:00
// Cycle to the next frame and then reset it
// This breaks any latent references, invalidating every pointer referencing into it.
2021-05-15 16:03:08 +00:00
self.frames.next().bump.reset();
*self.hookidx.borrow_mut() = 0;
2021-05-16 06:06:02 +00:00
let caller = self
.caller
.upgrade()
.ok_or(Error::FatalInternal("Failed to get caller"))?;
2021-05-15 16:03:08 +00:00
2021-05-26 05:40:30 +00:00
// Cast the caller ptr from static to one with our own reference
2021-05-15 16:03:08 +00:00
let new_head = unsafe {
2021-05-18 05:16:43 +00:00
std::mem::transmute::<&OpaqueComponent<'static>, &OpaqueComponent<'sel>>(
caller.as_ref(),
)
}(&self);
2021-05-15 16:03:08 +00:00
self.frames.cur_frame_mut().head_node = new_head.root;
2021-05-16 06:06:02 +00:00
2021-05-15 16:03:08 +00:00
Ok(())
}
// A safe wrapper around calling listeners
// calling listeners will invalidate the list of listeners
// The listener list will be completely drained because the next frame will write over previous listeners
pub fn call_listener(&mut self, trigger: EventTrigger) -> Result<()> {
let EventTrigger {
2021-05-16 06:06:02 +00:00
listener_id, event, ..
2021-05-15 16:03:08 +00:00
} = trigger;
unsafe {
// Convert the raw ptr into an actual object
// This operation is assumed to be safe
2021-05-16 06:06:02 +00:00
let listener_fn = self
.listeners
.try_borrow()
.ok()
2021-05-26 05:40:30 +00:00
.ok_or(Error::FatalInternal("Borrowing listener failed"))?
2021-05-15 16:03:08 +00:00
.get(listener_id as usize)
2021-05-16 06:06:02 +00:00
.ok_or(Error::FatalInternal("Event should exist if triggered"))?
2021-04-05 01:47:53 +00:00
.as_ref()
2021-05-16 06:06:02 +00:00
.ok_or(Error::FatalInternal("Raw event ptr is invalid"))?;
2021-05-15 16:03:08 +00:00
2021-05-16 06:06:02 +00:00
// Run the callback with the user event
listener_fn(event);
2021-05-15 16:03:08 +00:00
// drain all the event listeners
// if we don't, then they'll stick around and become invalid
// big big big big safety issue
2021-05-16 06:06:02 +00:00
self.listeners
.try_borrow_mut()
.ok()
.ok_or(Error::FatalInternal("Borrowing listener failed"))?
.drain(..);
2021-04-05 01:47:53 +00:00
}
2021-05-15 16:03:08 +00:00
Ok(())
}
pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.current_head_node()
}
pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.prev_head_node()
2021-04-05 01:47:53 +00:00
}
2021-05-15 16:03:08 +00:00
pub fn cur_frame(&self) -> &BumpFrame {
self.frames.cur_frame()
}
}
2021-05-16 06:06:02 +00:00
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
/// This lets components schedule updates, integrate hooks, and expose their context via the context api.
///
/// Properties passed down from the parent component are also directly accessible via the exposed "props" field.
///
/// ```ignore
/// #[derive(Properties)]
/// struct Props {
/// name: String
///
/// }
///
/// fn example(ctx: Context, props: &Props -> VNode {
/// html! {
/// <div> "Hello, {ctx.props.name}" </div>
/// }
/// }
/// ```
// todo: force lifetime of source into T as a valid lifetime too
// it's definitely possible, just needs some more messing around
2021-05-18 05:16:43 +00:00
pub type Context<'src> = &'src Scope;
2021-05-16 06:06:02 +00:00
2021-05-18 05:16:43 +00:00
impl Scope {
2021-05-16 06:06:02 +00:00
/// Access the children elements passed into the component
pub fn children(&self) -> Vec<VNode> {
todo!("Children API not yet implemented for component Context")
}
/// Create a subscription that schedules a future render for the reference component
pub fn schedule_update(&self) -> impl Fn() -> () {
2021-05-18 05:16:43 +00:00
self.event_queue.schedule_update(&self)
2021-05-16 06:06:02 +00:00
}
/// Create a suspended component from a future.
///
/// When the future completes, the component will be renderered
2021-05-18 05:16:43 +00:00
pub fn suspend<'a, F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a>(
&'a self,
2021-05-16 06:06:02 +00:00
_fut: impl Future<Output = LazyNodes<'a, F>>,
) -> VNode<'a> {
todo!()
}
}
2021-05-18 05:16:43 +00:00
// ================================================
// Render Implementation for Components
// ================================================
//
impl Scope {
// impl<'scope> Context<'scope> {
2021-05-16 06:06:02 +00:00
/// Take a lazy VNode structure and actually build it with the context of the VDom's efficient VNode allocator.
///
/// This function consumes the context and absorb the lifetime, so these VNodes *must* be returned.
///
/// ## Example
///
/// ```ignore
2021-05-18 05:16:43 +00:00
/// fn Component(ctx: Context, props: &()) -> VNode {
2021-05-16 06:06:02 +00:00
/// // Lazy assemble the VNode tree
/// let lazy_tree = html! {<div> "Hello World" </div>};
///
/// // Actually build the tree and allocate it
/// ctx.render(lazy_tree)
/// }
///```
2021-05-18 05:16:43 +00:00
pub fn render<'scope, F: for<'b> FnOnce(&'b NodeCtx<'scope>) -> VNode<'scope> + 'scope>(
&'scope self,
2021-05-16 06:06:02 +00:00
lazy_nodes: LazyNodes<'scope, F>,
) -> DomTree {
let ctx = NodeCtx {
2021-05-18 05:16:43 +00:00
scope_ref: self,
listener_id: 0.into(),
2021-05-16 06:06:02 +00:00
};
DomTree {
root: unsafe {
std::mem::transmute::<VNode<'scope>, VNode<'static>>(lazy_nodes.into_vnode(&ctx))
},
}
2021-05-16 06:06:02 +00:00
}
}
2021-05-18 05:16:43 +00:00
// ================================================
// Hooks Implementation for Components
// ================================================
// We need to pin the hook so it doesn't move as we initialize the list of hooks
2021-05-16 06:06:02 +00:00
type Hook = Pin<Box<dyn std::any::Any>>;
2021-05-18 05:16:43 +00:00
impl Scope {
// impl<'scope> Context<'scope> {
2021-05-16 06:06:02 +00:00
/// Store a value between renders
///
/// - Initializer: closure used to create the initial hook state
/// - Runner: closure used to output a value every time the hook is used
/// - Cleanup: closure used to teardown the hook once the dom is cleaned up
///
/// ```ignore
/// // use_ref is the simplest way of storing a value between renders
/// pub fn use_ref<T: 'static>(initial_value: impl FnOnce() -> T + 'static) -> Rc<RefCell<T>> {
/// use_hook(
/// || Rc::new(RefCell::new(initial_value())),
/// |state, _| state.clone(),
/// |_| {},
/// )
/// }
/// ```
2021-05-18 05:16:43 +00:00
pub fn use_hook<'scope, InternalHookState: 'static, Output: 'scope>(
&'scope self,
2021-05-16 06:06:02 +00:00
// The closure that builds the hook state
initializer: impl FnOnce() -> InternalHookState,
// The closure that takes the hookstate and returns some value
runner: impl FnOnce(&'scope mut InternalHookState) -> Output,
// The closure that cleans up whatever mess is left when the component gets torn down
// TODO: add this to the "clean up" group for when the component is dropped
_cleanup: impl FnOnce(InternalHookState),
) -> Output {
2021-05-18 05:16:43 +00:00
let idx = *self.hookidx.borrow();
2021-05-16 06:06:02 +00:00
// Grab out the hook list
2021-05-18 05:16:43 +00:00
let mut hooks = self.hooks.borrow_mut();
2021-05-16 06:06:02 +00:00
// If the idx is the same as the hook length, then we need to add the current hook
if idx >= hooks.len() {
let new_state = initializer();
hooks.push(Box::pin(new_state));
}
2021-05-18 05:16:43 +00:00
*self.hookidx.borrow_mut() += 1;
2021-05-16 06:06:02 +00:00
let stable_ref = hooks
.get_mut(idx)
.expect("Should not fail, idx is validated")
.as_mut();
let pinned_state = unsafe { Pin::get_unchecked_mut(stable_ref) };
let internal_state = pinned_state.downcast_mut::<InternalHookState>().expect(
r###"
2021-05-26 05:40:30 +00:00
Unable to retrive the hook that was initialized in this index.
Consult the `rules of hooks` to understand how to use hooks properly.
2021-05-16 06:06:02 +00:00
2021-05-26 05:40:30 +00:00
You likely used the hook in a conditional. Hooks rely on consistent ordering between renders.
Any function prefixed with "use" should not be called conditionally.
2021-05-16 06:06:02 +00:00
"###,
);
// We extend the lifetime of the internal state
runner(unsafe { &mut *(internal_state as *mut _) })
}
2021-05-18 05:16:43 +00:00
}
2021-05-18 05:16:43 +00:00
// ================================================
// Context API Implementation for Components
// ================================================
impl Scope {
2021-05-26 05:40:30 +00:00
/// This hook enables the ability to expose state to children further down the VirtualDOM Tree.
///
/// This is a hook, so it may not be called conditionally!
///
/// The init method is ran *only* on first use, otherwise it is ignored. However, it uses hooks (ie `use`)
/// so don't put it in a conditional.
///
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
/// the context via Rc/Weak.
///
///
///
pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
2021-05-18 05:16:43 +00:00
let mut ctxs = self.shared_contexts.borrow_mut();
let ty = TypeId::of::<T>();
let is_initialized = self.use_hook(
2021-05-18 05:16:43 +00:00
|| false,
|s| {
let i = s.clone();
2021-05-18 05:16:43 +00:00
*s = true;
i
},
|_| {},
);
match (is_initialized, ctxs.contains_key(&ty)) {
2021-05-18 05:16:43 +00:00
// Do nothing, already initialized and already exists
(true, true) => {}
// Needs to be initialized
(false, false) => {
log::debug!("Initializing context...");
ctxs.insert(ty, Rc::new(init()));
2021-05-18 05:16:43 +00:00
}
2021-05-26 05:40:30 +00:00
_ => debug_assert!(false, "Cannot initialize two contexts of the same type"),
2021-05-18 05:16:43 +00:00
}
}
2021-05-26 05:40:30 +00:00
/// There are hooks going on here!
pub fn use_context<T: 'static>(&self) -> Rc<T> {
self.try_use_context().unwrap()
}
///
2021-05-18 05:16:43 +00:00
pub fn try_use_context<T: 'static>(&self) -> Result<Rc<T>> {
let ty = TypeId::of::<T>();
let mut scope = Some(self);
2021-05-26 05:40:30 +00:00
let cached_root = use_ref(self, || None as Option<Weak<T>>);
// Try to provide the cached version without having to re-climb the tree
if let Some(ptr) = cached_root.borrow().as_ref() {
if let Some(pt) = ptr.clone().upgrade() {
return Ok(pt);
} else {
/*
failed to upgrade the weak is strange
this means the root dropped the context (the scope was killed)
The main idea here is to prevent memory leaks where parents should be cleaning up their own memory.
However, this behavior allows receivers/providers to move around in the hierarchy.
This works because we climb the tree if upgrading the Rc failed.
*/
}
}
2021-05-18 05:16:43 +00:00
while let Some(inner) = scope {
log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx);
2021-05-18 05:16:43 +00:00
let shared_contexts = inner.shared_contexts.borrow();
if let Some(shared_ctx) = shared_contexts.get(&ty) {
2021-05-26 05:40:30 +00:00
let rc = shared_ctx
2021-05-27 21:57:59 +00:00
.downcast_ref()
2021-05-26 05:40:30 +00:00
.expect("Should not fail, already validated the type from the hashmap");
*cached_root.borrow_mut() = Some(Rc::downgrade(&rc));
2021-05-27 21:57:59 +00:00
return Ok(rc.clone());
2021-05-18 05:16:43 +00:00
} else {
match inner.parent {
2021-05-26 05:40:30 +00:00
Some(parent_id) => {
2021-05-18 05:16:43 +00:00
let parent = inner
.arena_link
2021-05-26 05:40:30 +00:00
.try_get(parent_id)
2021-05-18 05:16:43 +00:00
.map_err(|_| Error::FatalInternal("Failed to find parent"))?;
scope = Some(parent);
}
None => return Err(Error::MissingSharedContext),
}
}
}
Err(Error::MissingSharedContext)
}
2021-05-16 06:06:02 +00:00
}
2021-05-18 05:16:43 +00:00
// ==================================================================================
// Supporting structs for the above abstractions
// ==================================================================================
2021-05-16 06:06:02 +00:00
2021-05-18 05:16:43 +00:00
// 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"
pub(crate) type OpaqueComponent<'a> = dyn for<'b> Fn(Context<'b>) -> DomTree + 'a;
#[derive(Debug, Default, Clone)]
pub struct EventQueue(pub(crate) Rc<RefCell<Vec<HeightMarker>>>);
impl EventQueue {
pub fn schedule_update(&self, source: &Scope) -> impl Fn() {
let inner = self.clone();
let marker = HeightMarker {
height: source.height,
idx: source.arena_idx,
2021-05-18 05:16:43 +00:00
};
move || inner.0.as_ref().borrow_mut().push(marker)
2021-05-16 06:06:02 +00:00
}
2021-05-18 05:16:43 +00:00
}
/// A helper type that lets scopes be ordered by their height
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct HeightMarker {
pub idx: ScopeIdx,
pub height: u32,
}
2021-05-16 06:06:02 +00:00
2021-05-18 05:16:43 +00:00
impl Ord for HeightMarker {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.height.cmp(&other.height)
2021-05-16 06:06:02 +00:00
}
2021-05-18 05:16:43 +00:00
}
2021-05-16 06:06:02 +00:00
2021-05-18 05:16:43 +00:00
impl PartialOrd for HeightMarker {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
2021-05-16 06:06:02 +00:00
}
2021-05-18 05:16:43 +00:00
}
2021-05-16 06:06:02 +00:00
2021-05-18 05:16:43 +00:00
// NodeCtx is used to build VNodes in the component's memory space.
// This struct adds metadata to the final DomTree about listeners, attributes, and children
#[derive(Clone)]
pub struct NodeCtx<'a> {
pub scope_ref: &'a Scope,
pub listener_id: RefCell<usize>,
}
impl<'a> NodeCtx<'a> {
pub fn bump(&self) -> &'a Bump {
&self.scope_ref.cur_frame().bump
}
}
impl Debug for NodeCtx<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
Ok(())
}
}
#[derive(Debug, PartialEq, Hash)]
pub struct ContextId {
// Which component is the scope in
original: ScopeIdx,
// What's the height of the scope
height: u32,
// Which scope is it (in order)
id: u32,
}
pub struct ActiveFrame {
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
pub generation: RefCell<usize>,
// The double-buffering situation that we will use
pub frames: [BumpFrame; 2],
}
pub struct BumpFrame {
pub bump: Bump,
pub head_node: VNode<'static>,
}
impl ActiveFrame {
pub fn new() -> Self {
Self::from_frames(
BumpFrame {
bump: Bump::new(),
head_node: VNode::text(""),
},
BumpFrame {
bump: Bump::new(),
head_node: VNode::text(""),
},
)
2021-05-16 06:06:02 +00:00
}
2021-05-18 05:16:43 +00:00
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
Self {
generation: 0.into(),
frames: [a, b],
}
2021-05-16 06:06:02 +00:00
}
2021-05-18 05:16:43 +00:00
fn cur_frame(&self) -> &BumpFrame {
match *self.generation.borrow() & 1 == 0 {
true => &self.frames[0],
false => &self.frames[1],
}
}
fn cur_frame_mut(&mut self) -> &mut BumpFrame {
match *self.generation.borrow() & 1 == 0 {
true => &mut self.frames[0],
false => &mut self.frames[1],
2021-05-16 06:06:02 +00:00
}
}
2021-05-18 05:16:43 +00:00
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
let raw_node = match *self.generation.borrow() & 1 == 0 {
true => &self.frames[0],
false => &self.frames[1],
};
// Give out our self-referential item with our own borrowed lifetime
unsafe {
let unsafe_head = &raw_node.head_node;
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
safe_node
2021-05-16 06:06:02 +00:00
}
}
2021-05-18 05:16:43 +00:00
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
let raw_node = match *self.generation.borrow() & 1 != 0 {
true => &self.frames[0],
false => &self.frames[1],
};
2021-05-18 05:16:43 +00:00
// Give out our self-referential item with our own borrowed lifetime
unsafe {
let unsafe_head = &raw_node.head_node;
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
safe_node
}
}
2021-05-18 05:16:43 +00:00
fn next(&mut self) -> &mut BumpFrame {
*self.generation.borrow_mut() += 1;
if *self.generation.borrow() % 2 == 0 {
&mut self.frames[0]
} else {
&mut self.frames[1]
}
}
2021-05-16 06:06:02 +00:00
}
mod tests {
2021-05-15 16:03:08 +00:00
use super::*;
#[test]
fn simulate() {
let dom = VirtualDom::new(|ctx, props| {
//
ctx.render(rsx! {
div {
}
})
});
// let root = dom.components.get(dom.base_scope).unwrap();
}
2021-05-27 21:57:59 +00:00
// ensure the virtualdom is send + sync
// needed for use in async/await contexts
fn is_send_sync() {
fn check_send<T: Send>(a: T) -> T {
todo!()
}
fn check_sync<T: Sync>(a: T) -> T {
todo!()
}
let _ = check_send(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}})));
let _ = check_sync(VirtualDom::new(|ctx, props| ctx.render(rsx! { div {}})));
}
2021-05-15 16:03:08 +00:00
}