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
|
2021-07-01 18:14:59 +00:00
|
|
|
//! - The [`NodeFactory`] object for lazyily exposing the `Context` API to the nodebuilder API
|
2021-05-16 06:06:02 +00:00
|
|
|
//! - 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.
|
|
|
|
|
2021-07-09 05:26:15 +00:00
|
|
|
use crate::hooklist::HookList;
|
2021-05-16 06:58:57 +00:00
|
|
|
use crate::{arena::ScopeArena, innerlude::*};
|
2021-07-09 05:26:15 +00:00
|
|
|
use appendlist::AppendList;
|
2021-05-15 16:03:08 +00:00
|
|
|
use bumpalo::Bump;
|
2021-06-26 01:15:33 +00:00
|
|
|
use futures::FutureExt;
|
2021-06-30 02:44:21 +00:00
|
|
|
use slotmap::DefaultKey;
|
|
|
|
use slotmap::SlotMap;
|
2021-07-09 05:26:15 +00:00
|
|
|
use std::marker::PhantomData;
|
2021-03-18 22:54:26 +00:00
|
|
|
use std::{
|
2021-05-17 21:59:10 +00:00
|
|
|
any::{Any, TypeId},
|
2021-06-23 05:44:48 +00:00
|
|
|
cell::{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,
|
2021-06-01 22:33:15 +00:00
|
|
|
ops::Deref,
|
2021-05-16 06:06:02 +00:00
|
|
|
pin::Pin,
|
2021-05-28 16:56:21 +00:00
|
|
|
rc::{Rc, Weak},
|
2021-03-18 22:54:26 +00:00
|
|
|
};
|
2021-06-30 02:44:21 +00:00
|
|
|
pub type ScopeIdx = DefaultKey;
|
|
|
|
// pub type ScopeIdx = generational_arena::Index;
|
2021-03-12 21:58:30 +00:00
|
|
|
|
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-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-03-12 21:58:30 +00:00
|
|
|
|
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
|
2021-03-12 21:58:30 +00:00
|
|
|
#[doc(hidden)]
|
2021-06-07 18:14:49 +00:00
|
|
|
_root_caller: Rc<OpaqueComponent>,
|
|
|
|
// _root_caller: Rc<OpaqueComponent<'static>>,
|
2021-06-26 01:15:33 +00:00
|
|
|
/// Type of the original cx. This is stored as TypeId so VirtualDom does not need to be generic.
|
2021-05-16 06:06:02 +00:00
|
|
|
///
|
|
|
|
/// 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.
|
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-06-27 02:13:57 +00:00
|
|
|
/// The `RealDomNode` is an ID handle that corresponds to a foreign DOM node.
|
|
|
|
///
|
|
|
|
/// "u64" was chosen for two reasons
|
|
|
|
/// - 0 cost hashing
|
|
|
|
/// - use with slotmap and other versioned slot arenas
|
2021-06-23 05:44:48 +00:00
|
|
|
#[derive(Clone, Copy, Debug, PartialEq)]
|
2021-06-27 02:13:57 +00:00
|
|
|
pub struct RealDomNode(pub u64);
|
2021-06-20 05:52:32 +00:00
|
|
|
impl RealDomNode {
|
2021-06-27 02:13:57 +00:00
|
|
|
pub fn new(id: u64) -> Self {
|
2021-06-22 21:20:54 +00:00
|
|
|
Self(id)
|
|
|
|
}
|
2021-06-20 05:52:32 +00:00
|
|
|
pub fn empty() -> Self {
|
2021-06-27 02:13:57 +00:00
|
|
|
Self(u64::MIN)
|
2021-06-20 05:52:32 +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
|
|
|
// ======================================
|
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-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
|
|
|
|
///
|
2021-06-26 01:15:33 +00:00
|
|
|
/// let dom = VirtualDom::new(|cx| cx.render(rsx!{ div {"hello world"} }));
|
2021-05-16 06:06:02 +00:00
|
|
|
///
|
|
|
|
/// // or pass in...
|
|
|
|
///
|
2021-06-26 01:15:33 +00:00
|
|
|
/// let root = |cx| {
|
|
|
|
/// cx.render(rsx!{
|
2021-05-16 06:06:02 +00:00
|
|
|
/// div {"hello world"}
|
|
|
|
/// })
|
|
|
|
/// }
|
|
|
|
/// let dom = VirtualDom::new(root);
|
|
|
|
///
|
|
|
|
/// // or directly from a fn
|
|
|
|
///
|
2021-06-26 01:15:33 +00:00
|
|
|
/// fn Example(cx: Context<()>) -> VNode {
|
|
|
|
/// cx.render(rsx!{ div{"hello world"} })
|
2021-05-16 06:06:02 +00:00
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// let dom = VirtualDom::new(Example);
|
|
|
|
/// ```
|
2021-06-23 05:44:48 +00:00
|
|
|
pub fn new(root: FC<()>) -> Self {
|
2021-03-09 05:58:20 +00:00
|
|
|
Self::new_with_props(root, ())
|
2021-02-03 07:26:04 +00:00
|
|
|
}
|
2021-03-12 19:27:32 +00:00
|
|
|
|
2021-06-26 01:15:33 +00:00
|
|
|
/// Start a new VirtualDom instance with a dependent cx.
|
2021-02-03 07:26:04 +00:00
|
|
|
/// 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
|
|
|
|
///
|
2021-06-26 01:15:33 +00:00
|
|
|
/// let dom = VirtualDom::new(|cx| cx.render(rsx!{ div {"hello world"} }));
|
2021-05-16 06:06:02 +00:00
|
|
|
///
|
|
|
|
/// // or pass in...
|
|
|
|
///
|
2021-06-26 01:15:33 +00:00
|
|
|
/// let root = |cx| {
|
|
|
|
/// cx.render(rsx!{
|
2021-05-16 06:06:02 +00:00
|
|
|
/// div {"hello world"}
|
|
|
|
/// })
|
|
|
|
/// }
|
|
|
|
/// let dom = VirtualDom::new(root);
|
|
|
|
///
|
|
|
|
/// // or directly from a fn
|
|
|
|
///
|
2021-06-26 01:15:33 +00:00
|
|
|
/// fn Example(cx: Context, props: &SomeProps) -> VNode {
|
|
|
|
/// cx.render(rsx!{ div{"hello world"} })
|
2021-05-16 06:06:02 +00:00
|
|
|
/// }
|
|
|
|
///
|
|
|
|
/// let dom = VirtualDom::new(Example);
|
|
|
|
/// ```
|
2021-06-23 05:44:48 +00:00
|
|
|
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
2021-06-30 02:44:21 +00:00
|
|
|
let components = ScopeArena::new(SlotMap::new());
|
2021-02-07 03:19:56 +00:00
|
|
|
|
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.
|
2021-06-07 18:14:49 +00:00
|
|
|
let _root_caller: Rc<OpaqueComponent> = Rc::new(move |scope| {
|
|
|
|
// let _root_caller: Rc<OpaqueComponent<'static>> = Rc::new(move |scope| {
|
2021-06-02 15:07:30 +00:00
|
|
|
// the lifetime of this closure is just as long as the lifetime on the scope reference
|
|
|
|
// this closure moves root props (which is static) into this closure
|
|
|
|
let props = unsafe { &*(&root_props as *const _) };
|
2021-07-09 05:26:15 +00:00
|
|
|
root(Context {
|
|
|
|
props,
|
|
|
|
scope,
|
|
|
|
tasks: todo!(),
|
|
|
|
})
|
2021-06-01 22:33:15 +00:00
|
|
|
});
|
2021-03-12 21:58:30 +00:00
|
|
|
|
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-06-07 18:14:49 +00:00
|
|
|
|
2021-05-16 06:06:02 +00:00
|
|
|
let base_scope = components
|
2021-05-18 05:16:43 +00:00
|
|
|
.with(|arena| {
|
2021-06-30 02:44:21 +00:00
|
|
|
arena.insert_with_key(move |myidx| {
|
2021-06-07 18:14:49 +00:00
|
|
|
let event_channel = _event_queue.new_channel(0, myidx);
|
2021-06-03 17:57:41 +00:00
|
|
|
Scope::new(caller_ref, myidx, None, 0, event_channel, link, &[])
|
2021-05-18 05:16:43 +00:00
|
|
|
})
|
|
|
|
})
|
|
|
|
.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-12 08:07:35 +00:00
|
|
|
}
|
2021-06-20 05:52:32 +00:00
|
|
|
}
|
2021-02-03 07:26:04 +00:00
|
|
|
|
2021-06-20 05:52:32 +00:00
|
|
|
// ======================================
|
|
|
|
// Private Methods for the VirtualDom
|
|
|
|
// ======================================
|
|
|
|
impl VirtualDom {
|
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-06-03 17:57:41 +00:00
|
|
|
/// Currently this doesn't do what we want it to do
|
2021-06-28 16:05:17 +00:00
|
|
|
pub fn rebuild<'s, Dom: RealDom<'s>>(&'s mut self, realdom: &mut Dom) -> Result<()> {
|
2021-06-07 18:14:49 +00:00
|
|
|
let mut diff_machine = DiffMachine::new(
|
2021-06-20 06:16:42 +00:00
|
|
|
realdom,
|
2021-06-28 16:05:17 +00:00
|
|
|
&self.components,
|
2021-06-07 18:14:49 +00:00
|
|
|
self.base_scope,
|
|
|
|
self.event_queue.clone(),
|
|
|
|
);
|
2021-03-12 20:41:36 +00:00
|
|
|
|
2021-06-07 18:14:49 +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-06-03 17:57:41 +00:00
|
|
|
|
2021-06-07 18:14:49 +00:00
|
|
|
let update = &base.event_channel;
|
|
|
|
update();
|
2021-05-16 06:06:02 +00:00
|
|
|
|
2021-06-20 06:16:42 +00:00
|
|
|
self.progress_completely(&mut diff_machine)?;
|
2021-03-05 20:02:36 +00:00
|
|
|
|
2021-06-20 05:52:32 +00:00
|
|
|
Ok(())
|
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
|
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
|
|
|
//
|
2021-05-16 06:58:57 +00:00
|
|
|
// A good project would be to remove all unsafe from this crate and move the unsafety into safer abstractions.
|
2021-06-28 16:05:17 +00:00
|
|
|
pub fn progress_with_event<'s, Dom: RealDom<'s>>(
|
|
|
|
&'s mut self,
|
|
|
|
realdom: &'_ mut Dom,
|
2021-06-23 05:44:48 +00:00
|
|
|
trigger: EventTrigger,
|
2021-06-20 05:52:32 +00:00
|
|
|
) -> Result<()> {
|
2021-06-23 05:44:48 +00:00
|
|
|
let id = trigger.component_id.clone();
|
2021-05-15 16:03:08 +00:00
|
|
|
|
2021-06-23 05:44:48 +00:00
|
|
|
self.components.try_get_mut(id)?.call_listener(trigger)?;
|
2021-02-13 07:49:10 +00:00
|
|
|
|
2021-06-28 16:05:17 +00:00
|
|
|
let mut diff_machine =
|
|
|
|
DiffMachine::new(realdom, &self.components, id, self.event_queue.clone());
|
2021-06-07 18:14:49 +00:00
|
|
|
|
2021-06-20 06:16:42 +00:00
|
|
|
self.progress_completely(&mut diff_machine)?;
|
2021-05-16 06:06:02 +00:00
|
|
|
|
2021-06-20 05:52:32 +00:00
|
|
|
Ok(())
|
2021-05-16 06:06:02 +00:00
|
|
|
}
|
|
|
|
|
2021-05-16 06:58:57 +00:00
|
|
|
/// 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.
|
2021-06-28 16:05:17 +00:00
|
|
|
pub(crate) fn progress_completely<'a, 'bump, Dom: RealDom<'bump>>(
|
|
|
|
&'bump self,
|
|
|
|
diff_machine: &'_ mut DiffMachine<'a, 'bump, Dom>,
|
2021-05-16 06:58:57 +00:00
|
|
|
) -> Result<()> {
|
2021-05-16 06:06:02 +00:00
|
|
|
// Add this component to the list of components that need to be difed
|
2021-06-07 18:14:49 +00:00
|
|
|
// #[allow(unused_assignments)]
|
|
|
|
// let mut cur_height: u32 = 0;
|
2021-05-15 16:03:08 +00:00
|
|
|
|
|
|
|
// Now, there are events in the queue
|
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
|
|
|
|
2021-06-17 03:37:55 +00:00
|
|
|
// Order the nodes by their height, we want the nodes with the smallest depth on top
|
2021-05-15 16:03:08 +00:00
|
|
|
// This prevents us from running the same component multiple times
|
|
|
|
updates.sort_unstable();
|
|
|
|
|
2021-06-07 18:14:49 +00:00
|
|
|
log::debug!("There are: {:#?} updates to be processed", updates.len());
|
|
|
|
|
2021-05-15 16:03:08 +00:00
|
|
|
// Iterate through the triggered nodes (sorted by height) and begin to diff them
|
|
|
|
for update in updates.drain(..) {
|
2021-06-07 18:14:49 +00:00
|
|
|
log::debug!("Running updates for: {:#?}", update);
|
2021-05-15 16:03:08 +00:00
|
|
|
// 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
|
2021-06-07 18:14:49 +00:00
|
|
|
if diff_machine.seen_nodes.contains(&update.idx) {
|
2021-05-15 16:03:08 +00:00
|
|
|
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
|
2021-06-07 18:14:49 +00:00
|
|
|
diff_machine.seen_nodes.insert(update.idx.clone());
|
2021-05-15 16:03:08 +00:00
|
|
|
|
2021-05-16 06:58:57 +00:00
|
|
|
// 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-06-07 18:14:49 +00:00
|
|
|
// let inner: &'s mut _ = unsafe { &mut *self.components.0.borrow().arena.get() };
|
|
|
|
// let cur_component = inner.get_mut(update.idx).unwrap();
|
2021-05-15 16:03:08 +00:00
|
|
|
|
2021-05-18 14:36:17 +00:00
|
|
|
cur_component.run_scope()?;
|
2021-06-17 03:37:55 +00:00
|
|
|
// diff_machine.change_list.load_known_root(1);
|
2021-05-15 16:03:08 +00:00
|
|
|
|
2021-06-22 21:20:54 +00:00
|
|
|
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
|
|
|
|
// let (old, new) = cur_component.get_frames_mut();
|
2021-06-20 06:16:42 +00:00
|
|
|
diff_machine.diff_node(old, new);
|
2021-05-15 16:03:08 +00:00
|
|
|
|
2021-06-07 18:14:49 +00:00
|
|
|
// cur_height = cur_component.height;
|
2021-05-15 16:03:08 +00:00
|
|
|
|
2021-06-07 18:14:49 +00:00
|
|
|
// log::debug!(
|
|
|
|
// "Processing update: {:#?} with height {}",
|
|
|
|
// &update.idx,
|
|
|
|
// cur_height
|
|
|
|
// );
|
2021-05-15 16:03:08 +00:00
|
|
|
}
|
|
|
|
|
2021-05-16 06:06:02 +00:00
|
|
|
Ok(())
|
2021-05-15 16:03:08 +00:00
|
|
|
}
|
2021-06-15 14:02:46 +00:00
|
|
|
|
|
|
|
pub fn base_scope(&self) -> &Scope {
|
2021-06-26 07:06:29 +00:00
|
|
|
let idx = self.base_scope;
|
|
|
|
self.components.try_get(idx).unwrap()
|
2021-06-15 14:02:46 +00:00
|
|
|
}
|
2021-04-05 01:47:53 +00:00
|
|
|
}
|
|
|
|
|
2021-05-28 04:28:09 +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
|
2021-06-07 18:14:49 +00:00
|
|
|
pub(crate) descendents: RefCell<HashSet<ScopeIdx>>,
|
2021-06-03 17:57:41 +00:00
|
|
|
|
|
|
|
child_nodes: &'static [VNode<'static>],
|
2021-05-18 14:36:17 +00:00
|
|
|
|
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
|
2021-05-18 14:36:17 +00:00
|
|
|
pub arena_idx: ScopeIdx,
|
2021-05-15 16:03:08 +00:00
|
|
|
|
|
|
|
pub height: u32,
|
|
|
|
|
2021-06-03 17:57:41 +00:00
|
|
|
pub event_channel: Rc<dyn Fn() + 'static>,
|
2021-05-16 06:06:02 +00:00
|
|
|
|
2021-06-07 18:14:49 +00:00
|
|
|
pub caller: Weak<OpaqueComponent>,
|
2021-06-26 05:29:51 +00:00
|
|
|
|
2021-05-15 16:03:08 +00:00
|
|
|
// ==========================
|
|
|
|
// slightly unsafe stuff
|
|
|
|
// ==========================
|
|
|
|
// an internal, highly efficient storage of vnodes
|
2021-05-16 06:58:57 +00:00
|
|
|
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-07-09 05:26:15 +00:00
|
|
|
hooks: HookList,
|
|
|
|
// hooks: RefCell<Vec<Hook>>,
|
2021-07-07 17:51:55 +00:00
|
|
|
pub(crate) listener_idx: Cell<usize>,
|
|
|
|
|
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
|
2021-07-08 14:17:51 +00:00
|
|
|
pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
|
2021-06-26 01:15:33 +00:00
|
|
|
|
2021-07-09 03:25:27 +00:00
|
|
|
pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
|
2021-07-08 14:17:51 +00:00
|
|
|
// pub(crate) listeners: RefCell<nohash_hasher::IntMap<u32, *const dyn FnMut(VirtualEvent)>>,
|
|
|
|
// pub(crate) listeners: RefCell<Vec<*const dyn FnMut(VirtualEvent)>>,
|
|
|
|
// pub(crate) listeners: RefCell<Vec<*const dyn FnMut(VirtualEvent)>>,
|
|
|
|
// NoHashMap<RealDomNode, <*const dyn FnMut(VirtualEvent)>
|
|
|
|
// pub(crate) listeners: RefCell<Vec<*const dyn FnMut(VirtualEvent)>>
|
2021-05-15 16:03:08 +00:00
|
|
|
}
|
|
|
|
|
2021-06-03 16:02:46 +00:00
|
|
|
// We need to pin the hook so it doesn't move as we initialize the list of hooks
|
2021-07-09 05:26:15 +00:00
|
|
|
type Hook = Box<dyn std::any::Any>;
|
2021-06-03 17:57:41 +00:00
|
|
|
type EventChannel = Rc<dyn Fn()>;
|
2021-06-03 16:02:46 +00:00
|
|
|
|
2021-05-15 16:03:08 +00:00
|
|
|
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
|
2021-06-07 18:14:49 +00:00
|
|
|
pub fn new<'creator_node>(
|
|
|
|
caller: Weak<OpaqueComponent>,
|
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-06-03 17:57:41 +00:00
|
|
|
event_channel: EventChannel,
|
2021-05-18 05:16:43 +00:00
|
|
|
arena_link: ScopeArena,
|
2021-06-03 17:57:41 +00:00
|
|
|
child_nodes: &'creator_node [VNode<'creator_node>],
|
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::<
|
2021-06-07 18:14:49 +00:00
|
|
|
Weak<OpaqueComponent>,
|
|
|
|
Weak<OpaqueComponent>,
|
|
|
|
// Weak<OpaqueComponent<'creator_node>>,
|
|
|
|
// Weak<OpaqueComponent<'static>>,
|
2021-05-16 06:06:02 +00:00
|
|
|
>(caller)
|
|
|
|
};
|
2021-05-15 16:03:08 +00:00
|
|
|
|
2021-06-08 18:00:29 +00:00
|
|
|
let child_nodes = unsafe { std::mem::transmute(child_nodes) };
|
|
|
|
|
2021-05-15 16:03:08 +00:00
|
|
|
Self {
|
2021-06-23 05:44:48 +00:00
|
|
|
child_nodes,
|
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-06-03 17:57:41 +00:00
|
|
|
event_channel,
|
2021-05-18 05:16:43 +00:00
|
|
|
arena_link,
|
2021-07-07 17:51:55 +00:00
|
|
|
listener_idx: Default::default(),
|
2021-05-26 05:40:30 +00:00
|
|
|
frames: ActiveFrame::new(),
|
|
|
|
hooks: Default::default(),
|
|
|
|
shared_contexts: Default::default(),
|
|
|
|
listeners: Default::default(),
|
2021-06-03 17:57:41 +00:00
|
|
|
descendents: Default::default(),
|
2021-06-26 01:15:33 +00:00
|
|
|
suspended_tasks: Default::default(),
|
2021-05-15 16:03:08 +00:00
|
|
|
}
|
|
|
|
}
|
2021-05-16 06:06:02 +00:00
|
|
|
|
2021-06-07 18:14:49 +00:00
|
|
|
pub fn update_caller<'creator_node>(&mut self, caller: Weak<OpaqueComponent>) {
|
|
|
|
// 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::<
|
2021-06-07 18:14:49 +00:00
|
|
|
Weak<OpaqueComponent>,
|
|
|
|
Weak<OpaqueComponent>,
|
|
|
|
// Weak<OpaqueComponent<'creator_node>>,
|
|
|
|
// Weak<OpaqueComponent<'static>>,
|
2021-05-16 06:06:02 +00:00
|
|
|
>(caller)
|
|
|
|
};
|
2021-05-15 16:03:08 +00:00
|
|
|
|
|
|
|
self.caller = broken_caller;
|
|
|
|
}
|
|
|
|
|
2021-07-05 05:11:49 +00:00
|
|
|
pub fn update_children<'creator_node>(
|
|
|
|
&mut self,
|
|
|
|
child_nodes: &'creator_node [VNode<'creator_node>],
|
|
|
|
) {
|
|
|
|
let child_nodes = unsafe { std::mem::transmute(child_nodes) };
|
|
|
|
self.child_nodes = child_nodes;
|
|
|
|
}
|
|
|
|
|
2021-05-15 16:03:08 +00:00
|
|
|
/// 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();
|
|
|
|
|
2021-05-31 22:55:56 +00:00
|
|
|
// Remove all the outdated listeners
|
2021-06-23 05:44:48 +00:00
|
|
|
self.listeners.borrow_mut().clear();
|
2021-05-31 22:55:56 +00:00
|
|
|
|
2021-07-09 05:26:15 +00:00
|
|
|
unsafe { self.hooks.reset() };
|
2021-07-07 17:51:55 +00:00
|
|
|
self.listener_idx.set(0);
|
2021-05-18 14:36:17 +00:00
|
|
|
|
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-06-07 18:14:49 +00:00
|
|
|
let c2: &OpaqueComponent = caller.as_ref();
|
|
|
|
let c3: &OpaqueComponent = unsafe { std::mem::transmute(c2) };
|
2021-06-02 15:07:30 +00:00
|
|
|
|
2021-07-07 17:51:55 +00:00
|
|
|
self.frames.cur_frame_mut().head_node = unsafe { self.own_vnodes(c3) };
|
2021-05-16 06:06:02 +00:00
|
|
|
|
2021-05-15 16:03:08 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-06-02 15:07:30 +00:00
|
|
|
// this is its own function so we can preciesly control how lifetimes flow
|
2021-06-07 18:14:49 +00:00
|
|
|
unsafe fn own_vnodes<'a>(&'a self, f: &OpaqueComponent) -> VNode<'static> {
|
2021-06-02 15:07:30 +00:00
|
|
|
let new_head: VNode<'a> = f(self);
|
|
|
|
let out: VNode<'static> = std::mem::transmute(new_head);
|
|
|
|
out
|
|
|
|
}
|
|
|
|
|
2021-05-15 16:03:08 +00:00
|
|
|
// 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-06-23 05:44:48 +00:00
|
|
|
real_node_id,
|
|
|
|
event,
|
|
|
|
..
|
2021-05-15 16:03:08 +00:00
|
|
|
} = trigger;
|
2021-06-22 21:20:54 +00:00
|
|
|
|
|
|
|
// todo: implement scanning for outdated events
|
2021-06-23 05:44:48 +00:00
|
|
|
|
|
|
|
// Convert the raw ptr into an actual object
|
|
|
|
// This operation is assumed to be safe
|
|
|
|
|
|
|
|
log::debug!("Calling listeners! {:?}", self.listeners.borrow().len());
|
2021-07-08 14:17:51 +00:00
|
|
|
let mut listners = self.listeners.borrow_mut();
|
2021-06-23 05:44:48 +00:00
|
|
|
let (_, listener) = listners
|
|
|
|
.iter()
|
|
|
|
.find(|(domptr, _)| {
|
|
|
|
let p = unsafe { &**domptr };
|
|
|
|
p.get() == real_node_id
|
|
|
|
})
|
|
|
|
.expect(&format!(
|
|
|
|
"Failed to find real node with ID {:?}",
|
|
|
|
real_node_id
|
|
|
|
));
|
|
|
|
|
|
|
|
// TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster!
|
2021-05-15 16:03:08 +00:00
|
|
|
unsafe {
|
2021-07-08 14:17:51 +00:00
|
|
|
let mut listener_fn = &mut **listener;
|
2021-05-16 06:06:02 +00:00
|
|
|
listener_fn(event);
|
2021-04-05 01:47:53 +00:00
|
|
|
}
|
2021-06-23 05:44:48 +00:00
|
|
|
|
2021-05-15 16:03:08 +00:00
|
|
|
Ok(())
|
|
|
|
}
|
|
|
|
|
2021-07-07 18:37:31 +00:00
|
|
|
pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
2021-05-15 16:03:08 +00:00
|
|
|
self.frames.current_head_node()
|
|
|
|
}
|
|
|
|
|
2021-07-07 18:37:31 +00:00
|
|
|
pub(crate) fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
2021-05-15 16:03:08 +00:00
|
|
|
self.frames.prev_head_node()
|
2021-04-05 01:47:53 +00:00
|
|
|
}
|
2021-05-15 16:03:08 +00:00
|
|
|
|
2021-07-07 18:37:31 +00:00
|
|
|
pub(crate) fn cur_frame(&self) -> &BumpFrame {
|
2021-05-15 16:03:08 +00:00
|
|
|
self.frames.cur_frame()
|
|
|
|
}
|
2021-06-26 07:06:29 +00:00
|
|
|
|
2021-07-07 18:37:31 +00:00
|
|
|
pub(crate) fn root<'a>(&'a self) -> &'a VNode<'a> {
|
2021-06-26 07:06:29 +00:00
|
|
|
&self.frames.cur_frame().head_node
|
|
|
|
}
|
2021-07-07 19:07:46 +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(cx: Context<Props>) -> VNode {
|
|
|
|
/// html! {
|
|
|
|
/// <div> "Hello, {cx.name}" </div>
|
|
|
|
/// }
|
|
|
|
/// }
|
|
|
|
/// ```
|
|
|
|
// todo: force lifetime of source into T as a valid lifetime too
|
|
|
|
// it's definitely possible, just needs some more messing around
|
|
|
|
pub struct Context<'src, T> {
|
|
|
|
pub props: &'src T,
|
|
|
|
pub scope: &'src Scope,
|
2021-07-09 05:26:15 +00:00
|
|
|
pub tasks: &'src AppendList<&'src mut DTask>,
|
|
|
|
// pub task: &'src RefCell<Vec<&'src mut >>,
|
2021-07-07 19:07:46 +00:00
|
|
|
}
|
2021-07-09 05:26:15 +00:00
|
|
|
pub type DTask = Pin<Box<dyn Future<Output = ()>>>;
|
|
|
|
// // pub task: &'src RefCell<Option<&'src mut Pin<Box<dyn Future<Output = ()>>>>>,
|
|
|
|
// pub task: Option<()>, // pub task: &'src RefCell<Option<&'src mut Pin<Box<dyn Future<Output = ()>>>>>,
|
|
|
|
// pub task: &'src RefCell<Option<&'src mut Pin<Box<dyn Future<Output = ()>>>>>
|
2021-07-07 19:07:46 +00:00
|
|
|
|
|
|
|
impl<'src, T> Copy for Context<'src, T> {}
|
|
|
|
impl<'src, T> Clone for Context<'src, T> {
|
|
|
|
fn clone(&self) -> Self {
|
|
|
|
Self {
|
|
|
|
props: self.props,
|
|
|
|
scope: self.scope,
|
2021-07-09 05:26:15 +00:00
|
|
|
tasks: todo!(),
|
2021-07-07 19:07:46 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<'a, T> Deref for Context<'a, T> {
|
|
|
|
type Target = &'a T;
|
|
|
|
|
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&self.props
|
|
|
|
}
|
|
|
|
}
|
2021-06-03 16:02:46 +00:00
|
|
|
|
2021-07-07 19:07:46 +00:00
|
|
|
impl<'src, P> Context<'src, P> {
|
2021-05-16 06:06:02 +00:00
|
|
|
/// Access the children elements passed into the component
|
2021-07-07 19:07:46 +00:00
|
|
|
pub fn children(&self) -> &'src [VNode<'src>] {
|
2021-06-03 17:57:41 +00:00
|
|
|
// We're re-casting the nodes back out
|
|
|
|
// They don't really have a static lifetime
|
|
|
|
unsafe {
|
2021-07-07 19:07:46 +00:00
|
|
|
let scope = self.scope;
|
2021-06-03 17:57:41 +00:00
|
|
|
let nodes = scope.child_nodes;
|
|
|
|
nodes
|
|
|
|
}
|
2021-05-16 06:06:02 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// Create a subscription that schedules a future render for the reference component
|
2021-07-07 18:37:31 +00:00
|
|
|
pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
|
2021-07-07 19:07:46 +00:00
|
|
|
self.scope.event_channel.clone()
|
2021-05-16 06:06:02 +00:00
|
|
|
}
|
|
|
|
|
2021-07-07 18:37:31 +00:00
|
|
|
pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
|
2021-06-26 01:15:33 +00:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
2021-07-07 18:37:31 +00:00
|
|
|
pub fn schedule_layout_effect(&self) {
|
2021-06-26 01:15:33 +00:00
|
|
|
todo!()
|
|
|
|
}
|
|
|
|
|
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-06-26 01:15:33 +00:00
|
|
|
/// fn Component(cx: Context<()>) -> 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
|
2021-06-26 01:15:33 +00:00
|
|
|
/// cx.render(lazy_tree)
|
2021-05-16 06:06:02 +00:00
|
|
|
/// }
|
|
|
|
///```
|
2021-07-07 19:07:46 +00:00
|
|
|
pub fn render<F: FnOnce(NodeFactory<'src>) -> VNode<'src>>(
|
|
|
|
self,
|
2021-06-01 22:33:15 +00:00
|
|
|
lazy_nodes: LazyNodes<'src, F>,
|
|
|
|
) -> VNode<'src> {
|
2021-07-07 19:07:46 +00:00
|
|
|
let scope_ref = self.scope;
|
2021-07-07 17:51:55 +00:00
|
|
|
let listener_id = &scope_ref.listener_idx;
|
|
|
|
lazy_nodes.into_vnode(NodeFactory {
|
|
|
|
scope_ref,
|
|
|
|
listener_id,
|
2021-06-03 17:57:41 +00:00
|
|
|
})
|
2021-05-31 22:55:56 +00:00
|
|
|
}
|
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())),
|
2021-06-01 22:33:15 +00:00
|
|
|
/// |state| state.clone(),
|
2021-05-16 06:06:02 +00:00
|
|
|
/// |_| {},
|
|
|
|
/// )
|
|
|
|
/// }
|
|
|
|
/// ```
|
2021-07-07 19:07:46 +00:00
|
|
|
pub fn use_hook<InternalHookState: 'static, Output: 'src>(
|
|
|
|
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
|
2021-06-01 22:33:15 +00:00
|
|
|
runner: impl FnOnce(&'src mut InternalHookState) -> Output,
|
2021-05-16 06:06:02 +00:00
|
|
|
|
|
|
|
// 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 {
|
|
|
|
// If the idx is the same as the hook length, then we need to add the current hook
|
2021-07-09 05:26:15 +00:00
|
|
|
if self.scope.hooks.is_finished() {
|
2021-05-16 06:06:02 +00:00
|
|
|
let new_state = initializer();
|
2021-07-09 05:26:15 +00:00
|
|
|
self.scope.hooks.push(Box::new(new_state));
|
2021-05-16 06:06:02 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 05:26:15 +00:00
|
|
|
let state = self.scope.hooks.next::<InternalHookState>().expect(
|
2021-05-16 06:06:02 +00:00
|
|
|
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
|
|
|
"###,
|
|
|
|
);
|
|
|
|
|
2021-07-09 05:26:15 +00:00
|
|
|
runner(state)
|
2021-05-16 06:06:02 +00:00
|
|
|
}
|
2021-05-17 21:59:10 +00:00
|
|
|
|
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.
|
|
|
|
///
|
|
|
|
///
|
|
|
|
///
|
2021-07-07 19:07:46 +00:00
|
|
|
pub fn use_create_context<T: 'static>(&self, init: impl Fn() -> T) {
|
|
|
|
let mut scope = self.scope;
|
2021-06-26 01:15:33 +00:00
|
|
|
let mut cxs = scope.shared_contexts.borrow_mut();
|
2021-05-18 05:16:43 +00:00
|
|
|
let ty = TypeId::of::<T>();
|
|
|
|
|
2021-05-18 14:36:17 +00:00
|
|
|
let is_initialized = self.use_hook(
|
2021-05-18 05:16:43 +00:00
|
|
|
|| false,
|
|
|
|
|s| {
|
2021-05-18 14:36:17 +00:00
|
|
|
let i = s.clone();
|
2021-05-18 05:16:43 +00:00
|
|
|
*s = true;
|
|
|
|
i
|
|
|
|
},
|
|
|
|
|_| {},
|
|
|
|
);
|
2021-05-17 21:59:10 +00:00
|
|
|
|
2021-06-26 01:15:33 +00:00
|
|
|
match (is_initialized, cxs.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) => {
|
2021-05-18 14:36:17 +00:00
|
|
|
log::debug!("Initializing context...");
|
2021-06-26 01:15:33 +00:00
|
|
|
cxs.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!
|
2021-07-07 19:07:46 +00:00
|
|
|
pub fn use_context<T: 'static>(self) -> &'src Rc<T> {
|
2021-05-26 05:40:30 +00:00
|
|
|
self.try_use_context().unwrap()
|
|
|
|
}
|
|
|
|
|
2021-05-31 22:55:56 +00:00
|
|
|
/// Uses a context, storing the cached value around
|
2021-07-07 19:07:46 +00:00
|
|
|
pub fn try_use_context<T: 'static>(self) -> Result<&'src Rc<T>> {
|
2021-05-31 22:55:56 +00:00
|
|
|
struct UseContextHook<C> {
|
|
|
|
par: Option<Rc<C>>,
|
|
|
|
we: Option<Weak<C>>,
|
|
|
|
}
|
2021-05-26 05:40:30 +00:00
|
|
|
|
2021-05-31 22:55:56 +00:00
|
|
|
self.use_hook(
|
|
|
|
move || UseContextHook {
|
|
|
|
par: None as Option<Rc<T>>,
|
|
|
|
we: None as Option<Weak<T>>,
|
|
|
|
},
|
|
|
|
move |hook| {
|
2021-07-07 19:07:46 +00:00
|
|
|
let mut scope = Some(self.scope);
|
2021-05-26 05:40:30 +00:00
|
|
|
|
2021-05-31 22:55:56 +00:00
|
|
|
if let Some(we) = &hook.we {
|
|
|
|
if let Some(re) = we.upgrade() {
|
|
|
|
hook.par = Some(re);
|
|
|
|
return Ok(hook.par.as_ref().unwrap());
|
|
|
|
}
|
|
|
|
}
|
2021-05-18 05:16:43 +00:00
|
|
|
|
2021-05-31 22:55:56 +00:00
|
|
|
let ty = TypeId::of::<T>();
|
|
|
|
while let Some(inner) = scope {
|
|
|
|
log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx);
|
|
|
|
let shared_contexts = inner.shared_contexts.borrow();
|
|
|
|
|
2021-06-26 01:15:33 +00:00
|
|
|
if let Some(shared_cx) = shared_contexts.get(&ty) {
|
|
|
|
log::debug!("found matching cx");
|
|
|
|
let rc = shared_cx
|
2021-05-31 22:55:56 +00:00
|
|
|
.clone()
|
|
|
|
.downcast::<T>()
|
|
|
|
.expect("Should not fail, already validated the type from the hashmap");
|
|
|
|
|
|
|
|
hook.we = Some(Rc::downgrade(&rc));
|
|
|
|
hook.par = Some(rc);
|
|
|
|
return Ok(hook.par.as_ref().unwrap());
|
|
|
|
} else {
|
|
|
|
match inner.parent {
|
|
|
|
Some(parent_id) => {
|
|
|
|
let parent = inner
|
|
|
|
.arena_link
|
|
|
|
.try_get(parent_id)
|
|
|
|
.map_err(|_| Error::FatalInternal("Failed to find parent"))?;
|
|
|
|
|
|
|
|
scope = Some(parent);
|
|
|
|
}
|
|
|
|
None => return Err(Error::MissingSharedContext),
|
|
|
|
}
|
2021-05-18 05:16:43 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-05-31 22:55:56 +00:00
|
|
|
Err(Error::MissingSharedContext)
|
|
|
|
},
|
|
|
|
|_| {},
|
|
|
|
)
|
2021-05-17 21:59:10 +00:00
|
|
|
}
|
2021-06-15 14:02:46 +00:00
|
|
|
|
2021-07-07 19:07:46 +00:00
|
|
|
pub fn suspend<Output: 'src, Fut: FnOnce(SuspendedContext, Output) -> VNode<'src> + 'src>(
|
2021-07-07 18:37:31 +00:00
|
|
|
&'src self,
|
2021-06-26 05:29:51 +00:00
|
|
|
fut: &'src mut Pin<Box<dyn Future<Output = Output> + 'static>>,
|
|
|
|
callback: Fut,
|
2021-06-26 01:15:33 +00:00
|
|
|
) -> VNode<'src> {
|
2021-06-26 05:29:51 +00:00
|
|
|
match fut.now_or_never() {
|
|
|
|
Some(out) => {
|
2021-06-26 01:15:33 +00:00
|
|
|
let suspended_cx = SuspendedContext {};
|
2021-06-26 05:29:51 +00:00
|
|
|
let nodes = callback(suspended_cx, out);
|
2021-06-26 01:15:33 +00:00
|
|
|
return nodes;
|
|
|
|
}
|
|
|
|
None => {
|
|
|
|
// we need to register this task
|
2021-06-27 02:13:57 +00:00
|
|
|
VNode::Suspended {
|
|
|
|
real: Cell::new(RealDomNode::empty()),
|
|
|
|
}
|
2021-06-26 01:15:33 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2021-07-09 03:25:27 +00:00
|
|
|
|
|
|
|
/// `submit_task` will submit the future to be polled.
|
|
|
|
/// Dioxus will step its internal event loop if the future returns if the future completes while waiting.
|
|
|
|
///
|
|
|
|
/// Tasks can't return anything, but they can be controlled with the returned handle
|
|
|
|
///
|
|
|
|
/// Tasks will only run until the component renders again. If you want your task to last longer than one frame, you'll need
|
|
|
|
/// to store it somehow.
|
|
|
|
///
|
|
|
|
pub fn submit_task(
|
|
|
|
&self,
|
2021-07-09 05:26:15 +00:00
|
|
|
mut task: &'src mut Pin<Box<dyn Future<Output = ()> + 'static>>,
|
2021-07-09 03:25:27 +00:00
|
|
|
) -> TaskHandle {
|
2021-07-09 05:26:15 +00:00
|
|
|
self.tasks.push(task);
|
|
|
|
// let mut g = self.task.borrow_mut();
|
|
|
|
// *g = Some(task);
|
2021-07-09 03:25:27 +00:00
|
|
|
// the pointer to the task is stable - we guarantee stability of all &'src references
|
2021-07-09 05:26:15 +00:00
|
|
|
// let task_ptr = task as *mut _;
|
2021-07-09 03:25:27 +00:00
|
|
|
|
2021-07-09 05:26:15 +00:00
|
|
|
TaskHandle { _p: PhantomData {} }
|
2021-07-09 03:25:27 +00:00
|
|
|
}
|
2021-06-26 01:15:33 +00:00
|
|
|
}
|
|
|
|
|
2021-07-09 05:26:15 +00:00
|
|
|
pub struct TaskHandle<'src> {
|
|
|
|
_p: PhantomData<&'src ()>,
|
|
|
|
}
|
2021-06-26 01:15:33 +00:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct SuspendedContext {}
|
|
|
|
|
2021-07-07 17:51:55 +00:00
|
|
|
// impl SuspendedContext {
|
|
|
|
// pub fn render<'a, F: for<'b, 'src> FnOnce(&'b NodeFactory<'src>) -> VNode<'src>>(
|
|
|
|
// &self,
|
|
|
|
// lazy_nodes: LazyNodes<F>,
|
|
|
|
// ) -> VNode {
|
|
|
|
// todo!()
|
|
|
|
// }
|
|
|
|
// }
|
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"
|
2021-06-07 18:14:49 +00:00
|
|
|
pub type OpaqueComponent = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
|
2021-05-18 05:16:43 +00:00
|
|
|
|
2021-06-03 17:57:41 +00:00
|
|
|
#[derive(PartialEq, Debug, Clone, Default)]
|
2021-06-07 18:14:49 +00:00
|
|
|
pub struct EventQueue(pub Rc<RefCell<Vec<HeightMarker>>>);
|
2021-05-18 05:16:43 +00:00
|
|
|
|
|
|
|
impl EventQueue {
|
2021-06-03 17:57:41 +00:00
|
|
|
pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc<dyn Fn()> {
|
2021-05-18 05:16:43 +00:00
|
|
|
let inner = self.clone();
|
2021-06-03 17:57:41 +00:00
|
|
|
let marker = HeightMarker { height, idx };
|
2021-06-07 18:14:49 +00:00
|
|
|
Rc::new(move || {
|
|
|
|
log::debug!("channel updated {:#?}", marker);
|
|
|
|
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)]
|
2021-06-07 18:14:49 +00:00
|
|
|
pub struct HeightMarker {
|
2021-05-18 05:16:43 +00:00
|
|
|
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
|
|
|
#[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-06-07 18:14:49 +00:00
|
|
|
pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
|
2021-05-18 05:16:43 +00:00
|
|
|
Self {
|
|
|
|
generation: 0.into(),
|
|
|
|
frames: [a, b],
|
|
|
|
}
|
2021-05-16 06:06:02 +00:00
|
|
|
}
|
|
|
|
|
2021-06-07 18:14:49 +00:00
|
|
|
pub fn cur_frame(&self) -> &BumpFrame {
|
2021-05-18 05:16:43 +00:00
|
|
|
match *self.generation.borrow() & 1 == 0 {
|
|
|
|
true => &self.frames[0],
|
|
|
|
false => &self.frames[1],
|
|
|
|
}
|
|
|
|
}
|
2021-06-07 18:14:49 +00:00
|
|
|
pub fn cur_frame_mut(&mut self) -> &mut BumpFrame {
|
2021-05-18 05:16:43 +00:00
|
|
|
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-17 21:59:10 +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-17 21:59:10 +00:00
|
|
|
|
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-17 21:59:10 +00:00
|
|
|
|
2021-06-07 18:14:49 +00:00
|
|
|
pub fn next(&mut self) -> &mut BumpFrame {
|
2021-05-18 05:16:43 +00:00
|
|
|
*self.generation.borrow_mut() += 1;
|
|
|
|
|
|
|
|
if *self.generation.borrow() % 2 == 0 {
|
|
|
|
&mut self.frames[0]
|
|
|
|
} else {
|
|
|
|
&mut self.frames[1]
|
|
|
|
}
|
2021-05-17 21:59:10 +00:00
|
|
|
}
|
2021-05-16 06:06:02 +00:00
|
|
|
}
|