wip: consolidation, simplification
This commit is contained in:
1 parent
b56ea6c9a9
commit
70b983d090
17 files changed
+1228
-836
No files matched your search
@@ -28,7 +28,6 @@ impl ActiveFrame {
|
||||
head_node: VNode::Fragment(VFragment {
|
||||
key: None,
|
||||
children: &[],
|
||||
is_static: false,
|
||||
}),
|
||||
bump: b1,
|
||||
|
||||
@@ -39,7 +38,6 @@ impl ActiveFrame {
|
||||
head_node: VNode::Fragment(VFragment {
|
||||
key: None,
|
||||
children: &[],
|
||||
is_static: false,
|
||||
}),
|
||||
bump: b2,
|
||||
|
||||
|
||||
@@ -62,7 +62,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
|
||||
VNode::Component(sc) => {
|
||||
let scope = self
|
||||
.scopes
|
||||
.get_scope(sc.associated_scope.get().unwrap())
|
||||
.get_scope(&sc.associated_scope.get().unwrap())
|
||||
.unwrap();
|
||||
|
||||
// Simply swap the current node on the stack with the root of the component
|
||||
|
||||
@@ -5,10 +5,7 @@
|
||||
//! if the type supports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
|
||||
//! that ensures compile-time required and optional fields on cx.
|
||||
|
||||
use crate::{
|
||||
innerlude::{Context, Element, VAnchor, VFragment, VNode},
|
||||
LazyNodes, ScopeChildren,
|
||||
};
|
||||
use crate::innerlude::{Context, Element, LazyNodes, ScopeChildren};
|
||||
/// A component is a wrapper around a Context and some Props that share a lifetime
|
||||
///
|
||||
///
|
||||
@@ -51,7 +48,7 @@ pub struct FragmentBuilder<'a, const BUILT: bool> {
|
||||
children: Option<ScopeChildren<'a>>,
|
||||
}
|
||||
impl<'a> FragmentBuilder<'a, false> {
|
||||
pub fn children(mut self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
|
||||
pub fn children(self, children: ScopeChildren<'a>) -> FragmentBuilder<'a, true> {
|
||||
FragmentBuilder {
|
||||
children: Some(children),
|
||||
}
|
||||
@@ -75,7 +72,7 @@ impl<'a> Properties for FragmentProps<'a> {
|
||||
FragmentBuilder { children: None }
|
||||
}
|
||||
|
||||
unsafe fn memoize(&self, other: &Self) -> bool {
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
//! Coroutines are just a "futures unordered" buffer for tasks that can be submitted through the use_coroutine hook.
|
||||
//!
|
||||
//! The idea here is to move *coroutine* support as a layer on top of *tasks*
|
||||
|
||||
use futures_util::{stream::FuturesUnordered, Future};
|
||||
|
||||
pub struct CoroutineScheduler {
|
||||
futures: FuturesUnordered<Box<dyn Future<Output = ()>>>,
|
||||
}
|
||||
|
||||
impl CoroutineScheduler {
|
||||
pub fn new() -> Self {
|
||||
CoroutineScheduler {
|
||||
futures: FuturesUnordered::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
+61
-32
@@ -169,7 +169,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
|
||||
pub fn diff_scope(&mut self, id: ScopeId) {
|
||||
if let Some(component) = self.vdom.get_scope_mut(id) {
|
||||
if let Some(component) = self.vdom.get_scope_mut(&id) {
|
||||
let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
self.stack.push(DiffInstruction::Diff { new, old });
|
||||
self.work(|| false);
|
||||
@@ -189,7 +189,10 @@ impl<'bump> DiffMachine<'bump> {
|
||||
DiffInstruction::Diff { old, new } => self.diff_node(old, new),
|
||||
DiffInstruction::Create { node } => self.create_node(node),
|
||||
DiffInstruction::Mount { and } => self.mount(and),
|
||||
DiffInstruction::PrepareMove { node } => self.prepare_move_node(node),
|
||||
DiffInstruction::PrepareMove { node } => {
|
||||
let num_on_stack = self.push_all_nodes(node);
|
||||
self.stack.add_child_count(num_on_stack);
|
||||
}
|
||||
DiffInstruction::PopScope => self.stack.pop_off_scope(),
|
||||
};
|
||||
|
||||
@@ -202,10 +205,29 @@ impl<'bump> DiffMachine<'bump> {
|
||||
true
|
||||
}
|
||||
|
||||
fn prepare_move_node(&mut self, node: &'bump VNode<'bump>) {
|
||||
for el in RealChildIterator::new(node, self.vdom) {
|
||||
self.mutations.push_root(el.mounted_id());
|
||||
self.stack.add_child_count(1);
|
||||
// recursively push all the nodes of a tree onto the stack and return how many are there
|
||||
fn push_all_nodes(&mut self, node: &'bump VNode<'bump>) -> usize {
|
||||
match node {
|
||||
VNode::Text(_) | VNode::Anchor(_) | VNode::Suspended(_) => {
|
||||
self.mutations.push_root(node.mounted_id());
|
||||
1
|
||||
}
|
||||
|
||||
VNode::Fragment(_) | VNode::Component(_) => node
|
||||
.children()
|
||||
.iter()
|
||||
.map(|child| self.push_all_nodes(child))
|
||||
.sum(),
|
||||
|
||||
VNode::Element(el) => {
|
||||
let mut num_on_stack = 0;
|
||||
for child in el.children.iter() {
|
||||
num_on_stack += self.push_all_nodes(child);
|
||||
}
|
||||
self.mutations.push_root(el.dom_id.get().unwrap());
|
||||
|
||||
num_on_stack + 1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -307,7 +329,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
self.stack.add_child_count(1);
|
||||
|
||||
if let Some(cur_scope_id) = self.stack.current_scope() {
|
||||
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
|
||||
let scope = self.vdom.get_scope_mut(&cur_scope_id).unwrap();
|
||||
|
||||
listeners.iter().for_each(|listener| {
|
||||
self.attach_listener_to_scope(listener, scope);
|
||||
@@ -332,18 +354,18 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
|
||||
fn create_component_node(&mut self, vcomponent: &'bump VComponent<'bump>) {
|
||||
let caller = vcomponent.caller;
|
||||
// let caller = vcomponent.caller;
|
||||
|
||||
let parent_idx = self.stack.current_scope().unwrap();
|
||||
|
||||
let shared = self.vdom.channel.clone();
|
||||
|
||||
// Insert a new scope into our component list
|
||||
let parent_scope = self.vdom.get_scope(parent_idx).unwrap();
|
||||
let parent_scope = self.vdom.get_scope(&parent_idx).unwrap();
|
||||
|
||||
let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
|
||||
ScopeInner::new(
|
||||
caller,
|
||||
vcomponent,
|
||||
new_idx,
|
||||
Some(parent_idx),
|
||||
parent_scope.height + 1,
|
||||
@@ -356,17 +378,18 @@ impl<'bump> DiffMachine<'bump> {
|
||||
vcomponent.associated_scope.set(Some(new_idx));
|
||||
|
||||
if !vcomponent.can_memoize {
|
||||
let cur_scope = self.vdom.get_scope_mut(parent_idx).unwrap();
|
||||
let cur_scope = self.vdom.get_scope_mut(&parent_idx).unwrap();
|
||||
let extended = vcomponent as *const VComponent;
|
||||
let extended: *const VComponent<'static> = unsafe { std::mem::transmute(extended) };
|
||||
cur_scope.borrowed_props.borrow_mut().push(extended);
|
||||
|
||||
cur_scope.items.get_mut().borrowed_props.push(extended);
|
||||
}
|
||||
|
||||
// TODO:
|
||||
// add noderefs to current noderef list Noderefs
|
||||
// add effects to current effect list Effects
|
||||
|
||||
let new_component = self.vdom.get_scope_mut(new_idx).unwrap();
|
||||
let new_component = self.vdom.get_scope_mut(&new_idx).unwrap();
|
||||
|
||||
log::debug!(
|
||||
"initializing component {:?} with height {:?}",
|
||||
@@ -494,7 +517,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
//
|
||||
// TODO: take a more efficient path than this
|
||||
if let Some(cur_scope_id) = self.stack.current_scope() {
|
||||
let scope = self.vdom.get_scope(cur_scope_id).unwrap();
|
||||
let scope = self.vdom.get_scope_mut(&cur_scope_id).unwrap();
|
||||
|
||||
if old.listeners.len() == new.listeners.len() {
|
||||
for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
|
||||
@@ -549,19 +572,21 @@ impl<'bump> DiffMachine<'bump> {
|
||||
new.associated_scope.set(Some(scope_addr));
|
||||
|
||||
// make sure the component's caller function is up to date
|
||||
let scope = self.vdom.get_scope_mut(scope_addr).unwrap();
|
||||
scope.update_scope_dependencies(new.caller);
|
||||
let scope = self.vdom.get_scope_mut(&scope_addr).unwrap();
|
||||
scope.update_vcomp(new);
|
||||
|
||||
// React doesn't automatically memoize, but we do.
|
||||
let props_are_the_same = old.comparator.unwrap();
|
||||
let props_are_the_same = todo!("reworking component memoization");
|
||||
// let props_are_the_same = todo!("reworking component memoization");
|
||||
// let props_are_the_same = old.comparator.unwrap();
|
||||
|
||||
if self.cfg.force_diff || !props_are_the_same(new) {
|
||||
let succeeded = scope.run_scope(self.vdom);
|
||||
// if self.cfg.force_diff || !props_are_the_same(new) {
|
||||
// let succeeded = scope.run_scope(self.vdom);
|
||||
|
||||
if succeeded {
|
||||
self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
|
||||
}
|
||||
}
|
||||
// if succeeded {
|
||||
// self.diff_node(scope.frames.wip_head(), scope.frames.fin_head());
|
||||
// }
|
||||
// }
|
||||
|
||||
self.stack.scope_stack.pop();
|
||||
} else {
|
||||
@@ -1038,7 +1063,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
VNode::Component(el) => {
|
||||
let scope_id = el.associated_scope.get().unwrap();
|
||||
let scope = self.vdom.get_scope(scope_id).unwrap();
|
||||
let scope = self.vdom.get_scope(&scope_id).unwrap();
|
||||
search_node = Some(scope.root_node());
|
||||
}
|
||||
}
|
||||
@@ -1056,7 +1081,7 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
VNode::Component(el) => {
|
||||
let scope_id = el.associated_scope.get().unwrap();
|
||||
let scope = self.vdom.get_scope(scope_id).unwrap();
|
||||
let scope = self.vdom.get_scope(&scope_id).unwrap();
|
||||
search_node = Some(scope.root_node());
|
||||
}
|
||||
VNode::Text(t) => break t.dom_id.get(),
|
||||
@@ -1131,12 +1156,12 @@ impl<'bump> DiffMachine<'bump> {
|
||||
|
||||
VNode::Component(c) => {
|
||||
let scope_id = c.associated_scope.get().unwrap();
|
||||
let scope = self.vdom.get_scope_mut(scope_id).unwrap();
|
||||
let scope = self.vdom.get_scope_mut(&scope_id).unwrap();
|
||||
let root = scope.root_node();
|
||||
self.remove_nodes(Some(root), gen_muts);
|
||||
|
||||
log::debug!("Destroying scope {:?}", scope_id);
|
||||
let mut s = self.vdom.try_remove(scope_id).unwrap();
|
||||
let mut s = self.vdom.try_remove(&scope_id).unwrap();
|
||||
s.hooks.clear_hooks();
|
||||
}
|
||||
}
|
||||
@@ -1161,23 +1186,27 @@ impl<'bump> DiffMachine<'bump> {
|
||||
}
|
||||
|
||||
/// Adds a listener closure to a scope during diff.
|
||||
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &ScopeInner) {
|
||||
let mut queue = scope.listeners.borrow_mut();
|
||||
fn attach_listener_to_scope<'a>(&mut self, listener: &'a Listener<'a>, scope: &mut ScopeInner) {
|
||||
let long_listener: &'a Listener<'static> = unsafe { std::mem::transmute(listener) };
|
||||
queue.push(long_listener as *const _)
|
||||
scope
|
||||
.items
|
||||
.get_mut()
|
||||
.listeners
|
||||
.push(long_listener as *const _)
|
||||
}
|
||||
|
||||
fn attach_suspended_node_to_scope(&mut self, suspended: &'bump VSuspended) {
|
||||
if let Some(scope) = self
|
||||
.stack
|
||||
.current_scope()
|
||||
.and_then(|id| self.vdom.get_scope_mut(id))
|
||||
.and_then(|id| self.vdom.get_scope_mut(&id))
|
||||
{
|
||||
// safety: this lifetime is managed by the logic on scope
|
||||
let extended: &VSuspended<'static> = unsafe { std::mem::transmute(suspended) };
|
||||
scope
|
||||
.items
|
||||
.get_mut()
|
||||
.suspended_nodes
|
||||
.borrow_mut()
|
||||
.insert(suspended.task_id, extended as *const _);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,50 +0,0 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use fxhash::FxHashMap;
|
||||
|
||||
use crate::FC;
|
||||
|
||||
/// Provides heuristics to the "SharedResources" object for improving allocation performance.
|
||||
///
|
||||
/// This heuristics engine records the memory footprint of bump arenas and hook lists for each component. These records are
|
||||
/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on
|
||||
/// that would slow down the diffing and initialization process.
|
||||
///
|
||||
///
|
||||
pub struct HeuristicsEngine {
|
||||
heuristics: FxHashMap<FcSlot, Heuristic>,
|
||||
}
|
||||
|
||||
pub type FcSlot = *const ();
|
||||
|
||||
pub struct Heuristic {
|
||||
hooks: u32,
|
||||
bump_size: u64,
|
||||
}
|
||||
|
||||
impl HeuristicsEngine {
|
||||
pub(crate) fn new() -> Self {
|
||||
Self {
|
||||
heuristics: FxHashMap::default(),
|
||||
}
|
||||
}
|
||||
|
||||
fn recommend<T>(&mut self, fc: FC<T>, heuristic: Heuristic) {
|
||||
let g = fc as FcSlot;
|
||||
let e = self.heuristics.entry(g);
|
||||
}
|
||||
|
||||
fn get_recommendation<T>(&mut self, fc: FC<T>) -> &Heuristic {
|
||||
let id = fc as FcSlot;
|
||||
|
||||
self.heuristics.entry(id).or_insert(Heuristic {
|
||||
bump_size: 100,
|
||||
hooks: 10,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn types_work() {
|
||||
let engine = HeuristicsEngine::new();
|
||||
}
|
||||
@@ -1,10 +1,9 @@
|
||||
use std::{
|
||||
any::Any,
|
||||
cell::{Cell, RefCell, UnsafeCell},
|
||||
cell::{Cell, RefCell},
|
||||
};
|
||||
|
||||
type UnsafeInnerHookState = UnsafeCell<Box<dyn Any>>;
|
||||
type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
|
||||
use bumpalo::Bump;
|
||||
|
||||
/// An abstraction over internally stored data using a hook-based memory layout.
|
||||
///
|
||||
@@ -15,7 +14,8 @@ type HookCleanup = Box<dyn FnOnce(Box<dyn Any>)>;
|
||||
/// Todo: this could use its very own bump arena, but that might be a tad overkill
|
||||
#[derive(Default)]
|
||||
pub(crate) struct HookList {
|
||||
vals: RefCell<Vec<(UnsafeInnerHookState, HookCleanup)>>,
|
||||
arena: Bump,
|
||||
vals: RefCell<Vec<*mut dyn Any>>,
|
||||
idx: Cell<usize>,
|
||||
}
|
||||
|
||||
@@ -23,7 +23,7 @@ impl HookList {
|
||||
pub(crate) fn next<T: 'static>(&self) -> Option<&mut T> {
|
||||
self.vals.borrow().get(self.idx.get()).and_then(|inn| {
|
||||
self.idx.set(self.idx.get() + 1);
|
||||
let raw_box = unsafe { &mut *inn.0.get() };
|
||||
let raw_box = unsafe { &mut **inn };
|
||||
raw_box.downcast_mut::<T>()
|
||||
})
|
||||
}
|
||||
@@ -38,10 +38,9 @@ impl HookList {
|
||||
self.idx.set(0);
|
||||
}
|
||||
|
||||
pub(crate) fn push_hook<T: 'static>(&self, new: T, cleanup: HookCleanup) {
|
||||
self.vals
|
||||
.borrow_mut()
|
||||
.push((UnsafeCell::new(Box::new(new)), cleanup))
|
||||
pub(crate) fn push_hook<T: 'static>(&self, new: T) {
|
||||
let val = self.arena.alloc(new);
|
||||
self.vals.borrow_mut().push(val)
|
||||
}
|
||||
|
||||
pub(crate) fn len(&self) -> usize {
|
||||
@@ -57,10 +56,16 @@ impl HookList {
|
||||
}
|
||||
|
||||
pub fn clear_hooks(&mut self) {
|
||||
log::debug!("clearing hooks...");
|
||||
self.vals
|
||||
.borrow_mut()
|
||||
.drain(..)
|
||||
.for_each(|(state, cleanup)| cleanup(state.into_inner()));
|
||||
self.vals.borrow_mut().drain(..).for_each(|state| {
|
||||
let as_mut = unsafe { &mut *state };
|
||||
let boxed = unsafe { bumpalo::boxed::Box::from_raw(as_mut) };
|
||||
drop(boxed);
|
||||
});
|
||||
}
|
||||
|
||||
/// Get the ammount of memory a hooklist uses
|
||||
/// Used in heuristics
|
||||
pub fn get_hook_arena_size(&self) -> usize {
|
||||
self.arena.allocated_bytes()
|
||||
}
|
||||
}
|
||||
+65
-65
@@ -13,73 +13,73 @@ use crate::innerlude::*;
|
||||
use futures_util::FutureExt;
|
||||
use std::{any::Any, cell::RefCell, future::Future, ops::Deref, rc::Rc};
|
||||
|
||||
/// Awaits the given task, forcing the component to re-render when the value is ready.
|
||||
///
|
||||
/// Returns the handle to the task and the value (if it is ready, else None).
|
||||
///
|
||||
/// ```
|
||||
/// static Example: FC<()> = |(cx, props)| {
|
||||
/// let (task, value) = use_task(|| async {
|
||||
/// timer::sleep(Duration::from_secs(1)).await;
|
||||
/// "Hello World"
|
||||
/// });
|
||||
///
|
||||
/// match contents {
|
||||
/// Some(contents) => rsx!(cx, div { "{title}" }),
|
||||
/// None => rsx!(cx, div { "Loading..." }),
|
||||
/// }
|
||||
/// };
|
||||
/// ```
|
||||
pub fn use_coroutine<'src, Out, Fut, Init>(
|
||||
cx: Context<'src>,
|
||||
task_initializer: Init,
|
||||
) -> (&'src TaskHandle, &'src Option<Out>)
|
||||
where
|
||||
Out: 'static,
|
||||
Fut: Future<Output = Out> + 'static,
|
||||
Init: FnOnce() -> Fut + 'src,
|
||||
{
|
||||
struct TaskHook<T> {
|
||||
handle: TaskHandle,
|
||||
task_dump: Rc<RefCell<Option<T>>>,
|
||||
value: Option<T>,
|
||||
}
|
||||
// /// Awaits the given task, forcing the component to re-render when the value is ready.
|
||||
// ///
|
||||
// /// Returns the handle to the task and the value (if it is ready, else None).
|
||||
// ///
|
||||
// /// ```
|
||||
// /// static Example: FC<()> = |(cx, props)| {
|
||||
// /// let (task, value) = use_task(|| async {
|
||||
// /// timer::sleep(Duration::from_secs(1)).await;
|
||||
// /// "Hello World"
|
||||
// /// });
|
||||
// ///
|
||||
// /// match contents {
|
||||
// /// Some(contents) => rsx!(cx, div { "{title}" }),
|
||||
// /// None => rsx!(cx, div { "Loading..." }),
|
||||
// /// }
|
||||
// /// };
|
||||
// /// ```
|
||||
// pub fn use_coroutine<'src, Out, Fut, Init>(
|
||||
// cx: Context<'src>,
|
||||
// task_initializer: Init,
|
||||
// ) -> (&'src TaskHandle, &'src Option<Out>)
|
||||
// where
|
||||
// Out: 'static,
|
||||
// Fut: Future<Output = Out> + 'static,
|
||||
// Init: FnOnce() -> Fut + 'src,
|
||||
// {
|
||||
// struct TaskHook<T> {
|
||||
// handle: TaskHandle,
|
||||
// task_dump: Rc<RefCell<Option<T>>>,
|
||||
// value: Option<T>,
|
||||
// }
|
||||
|
||||
todo!()
|
||||
// todo!()
|
||||
|
||||
// // whenever the task is complete, save it into th
|
||||
// cx.use_hook(
|
||||
// move |_| {
|
||||
// let task_fut = task_initializer();
|
||||
// // // whenever the task is complete, save it into th
|
||||
// // cx.use_hook(
|
||||
// // move |_| {
|
||||
// // let task_fut = task_initializer();
|
||||
|
||||
// let task_dump = Rc::new(RefCell::new(None));
|
||||
// // let task_dump = Rc::new(RefCell::new(None));
|
||||
|
||||
// let slot = task_dump.clone();
|
||||
// // let slot = task_dump.clone();
|
||||
|
||||
// let updater = cx.schedule_update_any();
|
||||
// let originator = cx.scope.our_arena_idx;
|
||||
// // let updater = cx.schedule_update_any();
|
||||
// // let originator = cx.scope.our_arena_idx;
|
||||
|
||||
// let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
|
||||
// *slot.as_ref().borrow_mut() = Some(output);
|
||||
// updater(originator);
|
||||
// originator
|
||||
// })));
|
||||
// // let handle = cx.submit_task(Box::pin(task_fut.then(move |output| async move {
|
||||
// // *slot.as_ref().borrow_mut() = Some(output);
|
||||
// // updater(originator);
|
||||
// // originator
|
||||
// // })));
|
||||
|
||||
// TaskHook {
|
||||
// task_dump,
|
||||
// value: None,
|
||||
// handle,
|
||||
// }
|
||||
// },
|
||||
// |hook| {
|
||||
// if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
|
||||
// hook.value = Some(val);
|
||||
// }
|
||||
// (&hook.handle, &hook.value)
|
||||
// },
|
||||
// |_| {},
|
||||
// )
|
||||
}
|
||||
// // TaskHook {
|
||||
// // task_dump,
|
||||
// // value: None,
|
||||
// // handle,
|
||||
// // }
|
||||
// // },
|
||||
// // |hook| {
|
||||
// // if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
|
||||
// // hook.value = Some(val);
|
||||
// // }
|
||||
// // (&hook.handle, &hook.value)
|
||||
// // },
|
||||
// // |_| {},
|
||||
// // )
|
||||
// }
|
||||
|
||||
/// Asynchronously render new nodes once the given future has completed.
|
||||
///
|
||||
@@ -167,10 +167,10 @@ where
|
||||
// )
|
||||
}
|
||||
|
||||
pub(crate) struct SuspenseHook {
|
||||
pub handle: TaskHandle,
|
||||
pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
|
||||
}
|
||||
// pub(crate) struct SuspenseHook {
|
||||
// pub handle: TaskHandle,
|
||||
// pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
|
||||
// }
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct NodeRef<'src, T: 'static>(&'src RefCell<Option<T>>);
|
||||
@@ -183,5 +183,5 @@ impl<'a, T> Deref for NodeRef<'a, T> {
|
||||
}
|
||||
|
||||
pub fn use_node_ref<T, P>(cx: Context) -> NodeRef<T> {
|
||||
cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {})
|
||||
cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f })
|
||||
}
|
||||
@@ -13,22 +13,16 @@ Navigating this crate:
|
||||
Some utilities
|
||||
*/
|
||||
pub(crate) mod bumpframe;
|
||||
pub(crate) mod childiter;
|
||||
pub(crate) mod component;
|
||||
pub(crate) mod coroutines;
|
||||
pub(crate) mod diff;
|
||||
pub(crate) mod diff_stack;
|
||||
pub(crate) mod events;
|
||||
pub(crate) mod heuristics;
|
||||
pub(crate) mod hooklist;
|
||||
pub(crate) mod hooks;
|
||||
pub(crate) mod lazynodes;
|
||||
pub(crate) mod mutations;
|
||||
pub(crate) mod nodes;
|
||||
pub(crate) mod resources;
|
||||
pub(crate) mod scheduler;
|
||||
pub(crate) mod scope;
|
||||
pub(crate) mod tasks;
|
||||
pub(crate) mod test_dom;
|
||||
pub(crate) mod threadsafe;
|
||||
pub(crate) mod util;
|
||||
@@ -39,21 +33,16 @@ pub mod debug_dom;
|
||||
|
||||
pub(crate) mod innerlude {
|
||||
pub(crate) use crate::bumpframe::*;
|
||||
pub(crate) use crate::childiter::*;
|
||||
pub use crate::component::*;
|
||||
pub(crate) use crate::diff::*;
|
||||
pub use crate::diff_stack::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::heuristics::*;
|
||||
pub(crate) use crate::hooklist::*;
|
||||
pub use crate::hooks::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::*;
|
||||
pub(crate) use crate::resources::*;
|
||||
pub use crate::scheduler::*;
|
||||
pub use crate::scope::*;
|
||||
pub use crate::tasks::*;
|
||||
pub use crate::test_dom::*;
|
||||
pub use crate::threadsafe::*;
|
||||
pub use crate::util::*;
|
||||
@@ -65,8 +54,8 @@ pub(crate) mod innerlude {
|
||||
|
||||
pub use crate::innerlude::{
|
||||
Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType,
|
||||
Mutations, NodeFactory, Properties, SchedulerMsg, ScopeChildren, ScopeId, TaskHandle, TestDom,
|
||||
ThreadsafeVirtualDom, UserEvent, VNode, VirtualDom, FC,
|
||||
Mutations, NodeFactory, Properties, ScopeChildren, ScopeId, TestDom, ThreadsafeVirtualDom,
|
||||
UserEvent, VNode, VirtualDom, FC,
|
||||
};
|
||||
|
||||
pub mod prelude {
|
||||
|
||||
+110
-83
@@ -154,6 +154,14 @@ impl<'src> VNode<'src> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn children(&self) -> &[VNode<'src>] {
|
||||
match &self {
|
||||
VNode::Fragment(f) => &f.children,
|
||||
VNode::Component(c) => &c.children,
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
// Create an "owned" version of the vnode.
|
||||
pub fn decouple(&self) -> VNode<'src> {
|
||||
match self {
|
||||
@@ -165,7 +173,6 @@ impl<'src> VNode<'src> {
|
||||
VNode::Fragment(f) => VNode::Fragment(VFragment {
|
||||
children: f.children,
|
||||
key: f.key,
|
||||
is_static: f.is_static,
|
||||
}),
|
||||
}
|
||||
}
|
||||
@@ -209,8 +216,6 @@ pub struct VFragment<'src> {
|
||||
pub key: Option<&'src str>,
|
||||
|
||||
pub children: &'src [VNode<'src>],
|
||||
|
||||
pub is_static: bool,
|
||||
}
|
||||
|
||||
/// An element like a "div" with children, listeners, and attributes.
|
||||
@@ -316,21 +321,21 @@ pub struct VComponent<'src> {
|
||||
|
||||
pub associated_scope: Cell<Option<ScopeId>>,
|
||||
|
||||
pub is_static: bool,
|
||||
|
||||
// Function pointer to the FC that was used to generate this component
|
||||
pub user_fc: *const (),
|
||||
|
||||
pub(crate) caller: BumpBox<'src, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'src>,
|
||||
|
||||
pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
|
||||
|
||||
pub(crate) drop_props: RefCell<Option<BumpBox<'src, dyn FnMut()>>>,
|
||||
|
||||
pub(crate) can_memoize: bool,
|
||||
|
||||
// Raw pointer into the bump arena for the props of the component
|
||||
pub(crate) raw_props: *const (),
|
||||
|
||||
// during the "teardown" process we'll take the caller out so it can be dropped properly
|
||||
pub(crate) caller: Option<VCompCaller<'src>>,
|
||||
pub(crate) comparator: Option<BumpBox<'src, dyn Fn(&VComponent) -> bool + 'src>>,
|
||||
}
|
||||
|
||||
pub enum VCompCaller<'src> {
|
||||
Borrowed(BumpBox<'src, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'src>),
|
||||
Owned(Box<dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>),
|
||||
}
|
||||
|
||||
pub struct VSuspended<'a> {
|
||||
@@ -473,7 +478,7 @@ impl<'a> NodeFactory<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn component<P>(
|
||||
pub fn component<P, P1>(
|
||||
&self,
|
||||
component: fn(Scope<'a, P>) -> Element<'a>,
|
||||
props: P,
|
||||
@@ -482,85 +487,109 @@ impl<'a> NodeFactory<'a> {
|
||||
where
|
||||
P: Properties + 'a,
|
||||
{
|
||||
/*
|
||||
our strategy:
|
||||
- unsafe hackery
|
||||
- lol
|
||||
|
||||
- we don't want to hit the global allocator
|
||||
- allocate into our bump arena
|
||||
- if the props aren't static, then we convert them into a box which we pass off between renders
|
||||
*/
|
||||
|
||||
let bump = self.bump();
|
||||
let props = bump.alloc(props);
|
||||
let raw_props = props as *mut P as *mut ();
|
||||
|
||||
// let p = BumpBox::new_in(x, a)
|
||||
|
||||
// the best place to allocate the props are the other component's arena
|
||||
// the second best place is the global allocator
|
||||
|
||||
// // if the props are static
|
||||
// let boxed = if P::IS_STATIC {
|
||||
// todo!()
|
||||
// } else {
|
||||
// todo!()
|
||||
// }
|
||||
|
||||
// let caller = Box::new(|f: &ScopeInner| -> Element {
|
||||
// //
|
||||
// component((f, &props))
|
||||
// });
|
||||
|
||||
let user_fc = component as *const ();
|
||||
|
||||
let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
|
||||
move |other: &VComponent| {
|
||||
if user_fc == other.user_fc {
|
||||
// Safety
|
||||
// - We guarantee that FC<P> is the same by function pointer
|
||||
// - Because FC<P> is the same, then P must be the same (even with generics)
|
||||
// - Non-static P are autoderived to memoize as false
|
||||
// - This comparator is only called on a corresponding set of bumpframes
|
||||
let props_memoized = unsafe {
|
||||
let real_other: &P = &*(other.raw_props as *const _ as *const P);
|
||||
props.memoize(real_other)
|
||||
};
|
||||
// let comparator: &mut dyn Fn(&VComponent) -> bool = bump.alloc_with(|| {
|
||||
// move |other: &VComponent| {
|
||||
// if user_fc == other.user_fc {
|
||||
// // Safety
|
||||
// // - We guarantee that FC<P> is the same by function pointer
|
||||
// // - Because FC<P> is the same, then P must be the same (even with generics)
|
||||
// // - Non-static P are autoderived to memoize as false
|
||||
// // - This comparator is only called on a corresponding set of bumpframes
|
||||
// let props_memoized = unsafe {
|
||||
// let real_other: &P = &*(other.raw_props as *const _ as *const P);
|
||||
// props.memoize(real_other)
|
||||
// };
|
||||
|
||||
// It's only okay to memoize if there are no children and the props can be memoized
|
||||
// Implementing memoize is unsafe and done automatically with the props trait
|
||||
props_memoized
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
});
|
||||
let comparator = Some(unsafe { BumpBox::from_raw(comparator) });
|
||||
|
||||
let drop_props = {
|
||||
// create a closure to drop the props
|
||||
let mut has_dropped = false;
|
||||
|
||||
let drop_props: &mut dyn FnMut() = bump.alloc_with(|| {
|
||||
move || unsafe {
|
||||
log::debug!("dropping props!");
|
||||
if !has_dropped {
|
||||
let real_other = raw_props as *mut _ as *mut P;
|
||||
let b = BumpBox::from_raw(real_other);
|
||||
std::mem::drop(b);
|
||||
|
||||
has_dropped = true;
|
||||
} else {
|
||||
panic!("Drop props called twice - this is an internal failure of Dioxus");
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let drop_props = unsafe { BumpBox::from_raw(drop_props) };
|
||||
|
||||
RefCell::new(Some(drop_props))
|
||||
};
|
||||
// // It's only okay to memoize if there are no children and the props can be memoized
|
||||
// // Implementing memoize is unsafe and done automatically with the props trait
|
||||
// props_memoized
|
||||
// } else {
|
||||
// false
|
||||
// }
|
||||
// }
|
||||
// });
|
||||
// let comparator = Some(unsafe { BumpBox::from_raw(comparator) });
|
||||
|
||||
let key = key.map(|f| self.raw_text(f).0);
|
||||
|
||||
let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
|
||||
bump.alloc(move |scope: &ScopeInner| -> Element {
|
||||
log::debug!("calling component renderr {:?}", scope.our_arena_idx);
|
||||
let props: &'_ P = unsafe { &*(raw_props as *const P) };
|
||||
let caller = match P::IS_STATIC {
|
||||
true => {
|
||||
// it just makes sense to box the props
|
||||
let boxed_props: Box<P> = Box::new(props);
|
||||
let props_we_know_are_static = todo!();
|
||||
VCompCaller::Owned(Box::new(|f| {
|
||||
//
|
||||
|
||||
let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
|
||||
let s: Scope<'a, P> = (scp, props);
|
||||
let p = todo!();
|
||||
|
||||
let res: Element = component(s);
|
||||
unsafe { std::mem::transmute(res) }
|
||||
});
|
||||
todo!()
|
||||
}))
|
||||
}
|
||||
false => VCompCaller::Borrowed({
|
||||
//
|
||||
|
||||
let caller = unsafe { BumpBox::from_raw(caller) };
|
||||
todo!()
|
||||
// let caller = bump.alloc()
|
||||
}),
|
||||
};
|
||||
|
||||
VNode::Component(bump.alloc(VComponent {
|
||||
user_fc,
|
||||
comparator,
|
||||
raw_props,
|
||||
caller,
|
||||
is_static: P::IS_STATIC,
|
||||
key,
|
||||
can_memoize: P::IS_STATIC,
|
||||
drop_props,
|
||||
associated_scope: Cell::new(None),
|
||||
}))
|
||||
todo!()
|
||||
// let caller: &'a mut dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> =
|
||||
// bump.alloc(move |scope: &ScopeInner| -> Element {
|
||||
// log::debug!("calling component renderr {:?}", scope.our_arena_idx);
|
||||
// let props: &'_ P = unsafe { &*(raw_props as *const P) };
|
||||
|
||||
// let scp: &'a ScopeInner = unsafe { std::mem::transmute(scope) };
|
||||
// let s: Scope<'a, P> = (scp, props);
|
||||
|
||||
// let res: Element = component(s);
|
||||
// unsafe { std::mem::transmute(res) }
|
||||
// });
|
||||
|
||||
// let caller = unsafe { BumpBox::from_raw(caller) };
|
||||
|
||||
// VNode::Component(bump.alloc(VComponent {
|
||||
// user_fc,
|
||||
// comparator,
|
||||
// raw_props,
|
||||
// caller,
|
||||
// is_static: P::IS_STATIC,
|
||||
// key,
|
||||
// can_memoize: P::IS_STATIC,
|
||||
// drop_props,
|
||||
// associated_scope: Cell::new(None),
|
||||
// }))
|
||||
}
|
||||
|
||||
pub fn fragment_from_iter(
|
||||
@@ -604,7 +633,6 @@ impl<'a> NodeFactory<'a> {
|
||||
VNode::Fragment(VFragment {
|
||||
children,
|
||||
key: None,
|
||||
is_static: false,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -684,7 +712,6 @@ impl<'a> IntoVNode<'a> for Option<LazyNodes<'a, '_>> {
|
||||
Some(lazy) => lazy.call(cx),
|
||||
None => VNode::Fragment(VFragment {
|
||||
children: &[],
|
||||
is_static: false,
|
||||
key: None,
|
||||
}),
|
||||
}
|
||||
|
||||
@@ -32,18 +32,18 @@ pub(crate) struct ResourcePool {
|
||||
|
||||
impl ResourcePool {
|
||||
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||
pub fn get_scope(&self, idx: ScopeId) -> Option<&ScopeInner> {
|
||||
pub fn get_scope(&self, idx: &ScopeId) -> Option<&ScopeInner> {
|
||||
let inner = unsafe { &*self.components.get() };
|
||||
inner.get(idx.0)
|
||||
}
|
||||
|
||||
/// this is unsafe because the caller needs to track which other scopes it's already using
|
||||
pub fn get_scope_mut(&self, idx: ScopeId) -> Option<&mut ScopeInner> {
|
||||
pub fn get_scope_mut(&self, idx: &ScopeId) -> Option<&mut ScopeInner> {
|
||||
let inner = unsafe { &mut *self.components.get() };
|
||||
inner.get_mut(idx.0)
|
||||
}
|
||||
|
||||
pub fn try_remove(&self, id: ScopeId) -> Option<ScopeInner> {
|
||||
pub fn try_remove(&self, id: &ScopeId) -> Option<ScopeInner> {
|
||||
let inner = unsafe { &mut *self.components.get() };
|
||||
Some(inner.remove(id.0))
|
||||
// .try_remove(id.0)
|
||||
|
||||
+105
-121
@@ -68,14 +68,17 @@ do anything too arduous from onInput.
|
||||
|
||||
For the rest, we defer to the rIC period and work down each queue from high to low.
|
||||
*/
|
||||
use crate::heuristics::*;
|
||||
|
||||
use crate::innerlude::*;
|
||||
use bumpalo::Bump;
|
||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
|
||||
use fxhash::FxHashMap;
|
||||
use fxhash::FxHashSet;
|
||||
use indexmap::IndexSet;
|
||||
use slab::Slab;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, UnsafeCell},
|
||||
@@ -85,11 +88,7 @@ use std::{
|
||||
|
||||
#[derive(Clone)]
|
||||
pub(crate) struct EventChannel {
|
||||
pub task_counter: Rc<Cell<u64>>,
|
||||
pub cur_subtree: Rc<Cell<u32>>,
|
||||
pub sender: UnboundedSender<SchedulerMsg>,
|
||||
pub schedule_any_immediate: Rc<dyn Fn(ScopeId)>,
|
||||
pub submit_task: Rc<dyn Fn(FiberTask) -> TaskHandle>,
|
||||
pub get_shared_context: GetSharedContext,
|
||||
}
|
||||
|
||||
@@ -101,16 +100,6 @@ pub enum SchedulerMsg {
|
||||
|
||||
// setstate
|
||||
Immediate(ScopeId),
|
||||
|
||||
// tasks
|
||||
Task(TaskMsg),
|
||||
}
|
||||
|
||||
pub enum TaskMsg {
|
||||
ToggleTask(u64),
|
||||
PauseTask(u64),
|
||||
ResumeTask(u64),
|
||||
DropTask(u64),
|
||||
}
|
||||
|
||||
/// The scheduler holds basically everything around "working"
|
||||
@@ -126,16 +115,26 @@ pub enum TaskMsg {
|
||||
///
|
||||
/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
|
||||
///
|
||||
/// There's a lot of raw pointers here...
|
||||
///
|
||||
/// Since we're building self-referential structures for each component, we need to make sure that the referencs stay stable
|
||||
/// The best way to do that is a bump allocator.
|
||||
///
|
||||
///
|
||||
///
|
||||
pub(crate) struct Scheduler {
|
||||
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
||||
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||
///
|
||||
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
||||
/// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
||||
pub pool: ResourcePool,
|
||||
// /// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
||||
// /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||
// ///
|
||||
// /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
||||
// /// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
||||
// pub pool: ResourcePool,
|
||||
//
|
||||
pub component_arena: Bump,
|
||||
|
||||
pub heuristics: HeuristicsEngine,
|
||||
pub free_components: VecDeque<*mut ScopeInner>,
|
||||
|
||||
pub heuristics: FxHashMap<FcSlot, Heuristic>,
|
||||
|
||||
pub receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
|
||||
@@ -145,104 +144,70 @@ pub(crate) struct Scheduler {
|
||||
// Every component that has futures that need to be polled
|
||||
pub pending_futures: FxHashSet<ScopeId>,
|
||||
|
||||
// In-flight futures
|
||||
pub async_tasks: FuturesUnordered<FiberTask>,
|
||||
|
||||
// // scheduler stuff
|
||||
// pub current_priority: EventPriority,
|
||||
pub ui_events: VecDeque<UserEvent>,
|
||||
|
||||
pub pending_immediates: VecDeque<ScopeId>,
|
||||
|
||||
pub pending_tasks: VecDeque<UserEvent>,
|
||||
|
||||
pub batched_events: VecDeque<UserEvent>,
|
||||
|
||||
pub garbage_scopes: HashSet<ScopeId>,
|
||||
|
||||
pub dirty_scopes: IndexSet<ScopeId>,
|
||||
|
||||
pub saved_state: Option<SavedDiffWork<'static>>,
|
||||
|
||||
pub in_progress: bool,
|
||||
}
|
||||
|
||||
pub type FcSlot = *const ();
|
||||
|
||||
pub struct Heuristic {
|
||||
hook_arena_size: usize,
|
||||
node_arena_size: usize,
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
pub(crate) fn new(
|
||||
sender: UnboundedSender<SchedulerMsg>,
|
||||
receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
component_capacity: usize,
|
||||
element_capacity: usize,
|
||||
) -> Self {
|
||||
/*
|
||||
Preallocate 2000 elements and 100 scopes to avoid dynamic allocation.
|
||||
Perhaps this should be configurable from some external config?
|
||||
*/
|
||||
let components = Rc::new(UnsafeCell::new(Slab::with_capacity(100)));
|
||||
let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(2000)));
|
||||
|
||||
let heuristics = HeuristicsEngine::new();
|
||||
|
||||
let task_counter = Rc::new(Cell::new(0));
|
||||
let cur_subtree = Rc::new(Cell::new(0));
|
||||
// let components = Rc::new(UnsafeCell::new(Slab::with_capacity(component_capacity)));
|
||||
let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(element_capacity)));
|
||||
|
||||
let channel = EventChannel {
|
||||
cur_subtree,
|
||||
task_counter: task_counter.clone(),
|
||||
sender: sender.clone(),
|
||||
schedule_any_immediate: {
|
||||
let sender = sender.clone();
|
||||
Rc::new(move |id| {
|
||||
//
|
||||
log::debug!("scheduling immediate! {:?}", id);
|
||||
sender.unbounded_send(SchedulerMsg::Immediate(id)).unwrap()
|
||||
})
|
||||
},
|
||||
// todo: we want to get the futures out of the scheduler message
|
||||
// the scheduler message should be send/sync
|
||||
submit_task: {
|
||||
Rc::new(move |fiber_task| {
|
||||
let task_id = task_counter.get();
|
||||
task_counter.set(task_id + 1);
|
||||
|
||||
todo!();
|
||||
// sender
|
||||
// .unbounded_send(SchedulerMsg::Task(TaskMsg::SubmitTask(
|
||||
// fiber_task, task_id,
|
||||
// )))
|
||||
// .unwrap();
|
||||
TaskHandle {
|
||||
our_id: task_id,
|
||||
sender: sender.clone(),
|
||||
}
|
||||
})
|
||||
},
|
||||
get_shared_context: {
|
||||
let components = components.clone();
|
||||
Rc::new(move |id, ty| {
|
||||
let components = unsafe { &*components.get() };
|
||||
let mut search: Option<&ScopeInner> = components.get(id.0);
|
||||
while let Some(inner) = search.take() {
|
||||
if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
|
||||
return Some(shared.clone());
|
||||
} else {
|
||||
search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
todo!()
|
||||
// let components = components.clone();
|
||||
// Rc::new(move |id, ty| {
|
||||
// let components = unsafe { &*components.get() };
|
||||
// let mut search: Option<&ScopeInner> = components.get(id.0);
|
||||
// while let Some(inner) = search.take() {
|
||||
// if let Some(shared) = inner.shared_contexts.borrow().get(&ty) {
|
||||
// return Some(shared.clone());
|
||||
// } else {
|
||||
// search = inner.parent_idx.map(|id| components.get(id.0)).flatten();
|
||||
// }
|
||||
// }
|
||||
// None
|
||||
// })
|
||||
},
|
||||
};
|
||||
|
||||
let pool = ResourcePool {
|
||||
components,
|
||||
raw_elements,
|
||||
channel,
|
||||
};
|
||||
|
||||
let async_tasks = FuturesUnordered::new();
|
||||
|
||||
// push a task that would never resolve - prevents us from immediately aborting the scheduler
|
||||
async_tasks.push(Box::pin(async {
|
||||
std::future::pending::<()>().await;
|
||||
ScopeId(0)
|
||||
}) as FiberTask);
|
||||
// let pool = ResourcePool {
|
||||
// components,
|
||||
// raw_elements,
|
||||
// channel,
|
||||
// };
|
||||
|
||||
let saved_state = SavedDiffWork {
|
||||
mutations: Mutations::new(),
|
||||
@@ -251,22 +216,15 @@ impl Scheduler {
|
||||
};
|
||||
|
||||
Self {
|
||||
pool,
|
||||
|
||||
// pool,
|
||||
receiver,
|
||||
|
||||
async_tasks,
|
||||
|
||||
pending_garbage: FxHashSet::default(),
|
||||
|
||||
heuristics,
|
||||
|
||||
ui_events: VecDeque::new(),
|
||||
|
||||
pending_immediates: VecDeque::new(),
|
||||
|
||||
pending_tasks: VecDeque::new(),
|
||||
|
||||
batched_events: VecDeque::new(),
|
||||
|
||||
garbage_scopes: HashSet::new(),
|
||||
@@ -275,6 +233,8 @@ impl Scheduler {
|
||||
dirty_scopes: Default::default(),
|
||||
saved_state: Some(saved_state),
|
||||
in_progress: false,
|
||||
|
||||
heuristics: todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -282,7 +242,7 @@ impl Scheduler {
|
||||
pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
|
||||
let (discrete, priority) = event_meta(&event);
|
||||
|
||||
if let Some(scope) = self.pool.get_scope_mut(event.scope) {
|
||||
if let Some(scope) = self.get_scope_mut(&event.scope) {
|
||||
if let Some(element) = event.mounted_dom_id {
|
||||
// TODO: bubble properly here
|
||||
scope.call_listener(event, element);
|
||||
@@ -308,7 +268,7 @@ impl Scheduler {
|
||||
|
||||
fn prepare_work(&mut self) {
|
||||
// while let Some(trigger) = self.ui_events.pop_back() {
|
||||
// if let Some(scope) = self.pool.get_scope_mut(trigger.scope) {}
|
||||
// if let Some(scope) = self.get_scope_mut(&trigger.scope) {}
|
||||
// }
|
||||
}
|
||||
|
||||
@@ -328,15 +288,9 @@ impl Scheduler {
|
||||
unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
|
||||
self.saved_state.take().unwrap().extend()
|
||||
}
|
||||
pub fn handle_task(&mut self, evt: TaskMsg) {
|
||||
//
|
||||
}
|
||||
|
||||
pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
|
||||
match msg {
|
||||
//
|
||||
SchedulerMsg::Task(msg) => todo!(),
|
||||
|
||||
SchedulerMsg::Immediate(_) => todo!(),
|
||||
|
||||
SchedulerMsg::UiEvent(event) => {
|
||||
@@ -344,7 +298,7 @@ impl Scheduler {
|
||||
|
||||
let (discrete, priority) = event_meta(&event);
|
||||
|
||||
if let Some(scope) = self.pool.get_scope_mut(event.scope) {
|
||||
if let Some(scope) = self.get_scope_mut(&event.scope) {
|
||||
if let Some(element) = event.mounted_dom_id {
|
||||
// TODO: bubble properly here
|
||||
scope.call_listener(event, element);
|
||||
@@ -374,19 +328,19 @@ impl Scheduler {
|
||||
let saved_state = unsafe { self.load_work() };
|
||||
|
||||
// We have to split away some parts of ourself - current lane is borrowed mutably
|
||||
let shared = self.pool.clone();
|
||||
let shared = self.clone();
|
||||
let mut machine = unsafe { saved_state.promote(&shared) };
|
||||
|
||||
let mut ran_scopes = FxHashSet::default();
|
||||
|
||||
if machine.stack.is_empty() {
|
||||
let shared = self.pool.clone();
|
||||
let shared = self.clone();
|
||||
|
||||
self.dirty_scopes
|
||||
.retain(|id| shared.get_scope(*id).is_some());
|
||||
.retain(|id| shared.get_scope(id).is_some());
|
||||
self.dirty_scopes.sort_by(|a, b| {
|
||||
let h1 = shared.get_scope(*a).unwrap().height;
|
||||
let h2 = shared.get_scope(*b).unwrap().height;
|
||||
let h1 = shared.get_scope(a).unwrap().height;
|
||||
let h2 = shared.get_scope(b).unwrap().height;
|
||||
h1.cmp(&h2).reverse()
|
||||
});
|
||||
|
||||
@@ -396,8 +350,8 @@ impl Scheduler {
|
||||
ran_scopes.insert(scopeid);
|
||||
log::debug!("about to run scope {:?}", scopeid);
|
||||
|
||||
if let Some(component) = self.pool.get_scope_mut(scopeid) {
|
||||
if component.run_scope(&self.pool) {
|
||||
if let Some(component) = self.get_scope_mut(&scopeid) {
|
||||
if component.run_scope(&self) {
|
||||
let (old, new) =
|
||||
(component.frames.wip_head(), component.frames.fin_head());
|
||||
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
@@ -494,7 +448,6 @@ impl Scheduler {
|
||||
while self.has_any_work() {
|
||||
while let Ok(Some(msg)) = self.receiver.try_next() {
|
||||
match msg {
|
||||
SchedulerMsg::Task(t) => todo!(),
|
||||
SchedulerMsg::Immediate(im) => {
|
||||
self.dirty_scopes.insert(im);
|
||||
}
|
||||
@@ -506,7 +459,7 @@ impl Scheduler {
|
||||
|
||||
// switch our priority, pop off any work
|
||||
while let Some(event) = self.ui_events.pop_front() {
|
||||
if let Some(scope) = self.pool.get_scope_mut(event.scope) {
|
||||
if let Some(scope) = self.get_scope_mut(&event.scope) {
|
||||
if let Some(element) = event.mounted_dom_id {
|
||||
log::info!("Calling listener {:?}, {:?}", event.scope, element);
|
||||
|
||||
@@ -519,7 +472,6 @@ impl Scheduler {
|
||||
self.dirty_scopes.insert(im);
|
||||
}
|
||||
SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
|
||||
SchedulerMsg::Task(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -562,19 +514,19 @@ impl Scheduler {
|
||||
///
|
||||
/// Typically used to kickstart the VirtualDOM after initialization.
|
||||
pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations {
|
||||
let mut shared = self.pool.clone();
|
||||
let mut shared = self.clone();
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
|
||||
|
||||
// TODO: drain any in-flight work
|
||||
let cur_component = self
|
||||
.pool
|
||||
.get_scope_mut(base_scope)
|
||||
.get_scope_mut(&base_scope)
|
||||
.expect("The base scope should never be moved");
|
||||
|
||||
log::debug!("rebuild {:?}", base_scope);
|
||||
|
||||
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
|
||||
if cur_component.run_scope(&self.pool) {
|
||||
if cur_component.run_scope(&self) {
|
||||
diff_machine
|
||||
.stack
|
||||
.create_node(cur_component.frames.fin_head(), MountType::Append);
|
||||
@@ -596,13 +548,13 @@ impl Scheduler {
|
||||
pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
|
||||
let cur_component = self
|
||||
.pool
|
||||
.get_scope_mut(base_scope)
|
||||
.get_scope_mut(&base_scope)
|
||||
.expect("The base scope should never be moved");
|
||||
|
||||
log::debug!("hard diff {:?}", base_scope);
|
||||
|
||||
if cur_component.run_scope(&self.pool) {
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self.pool);
|
||||
if cur_component.run_scope(&self) {
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self);
|
||||
diff_machine.cfg.force_diff = true;
|
||||
diff_machine.diff_scope(base_scope);
|
||||
diff_machine.mutations
|
||||
@@ -611,3 +563,35 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for Scheduler {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut all_pending = true;
|
||||
|
||||
for fut in self.pending_futures.iter() {
|
||||
let scope = self
|
||||
.pool
|
||||
.get_scope_mut(&fut)
|
||||
.expect("Scope should never be moved");
|
||||
|
||||
let items = scope.items.get_mut();
|
||||
for task in items.tasks.iter_mut() {
|
||||
let t = task.as_mut();
|
||||
let g = unsafe { Pin::new_unchecked(t) };
|
||||
match g.poll(cx) {
|
||||
Poll::Ready(r) => {
|
||||
all_pending = false;
|
||||
}
|
||||
Poll::Pending => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
match all_pending {
|
||||
true => Poll::Pending,
|
||||
false => Poll::Ready(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
+258
-284
@@ -6,36 +6,27 @@ use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashMap,
|
||||
future::Future,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::{innerlude::*, lazynodes::LazyNodes};
|
||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
use std::ops::Deref;
|
||||
|
||||
/// Components in Dioxus use the "Context" object to interact with their lifecycle.
|
||||
///
|
||||
/// This lets components access props, schedule updates, integrate hooks, and expose shared state.
|
||||
///
|
||||
/// Note: all of these methods are *imperative* - they do not act as hooks! They are meant to be used by hooks
|
||||
/// to provide complex behavior. For instance, calling "add_shared_state" on every render is considered a leak. This method
|
||||
/// exists for the `use_provide_state` hook to provide a shared state object.
|
||||
///
|
||||
/// For the most part, the only method you should be using regularly is `render`.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ```ignore
|
||||
/// #[derive(Properties)]
|
||||
/// struct Props {
|
||||
/// #[derive(Props)]
|
||||
/// struct ExampleProps {
|
||||
/// name: String
|
||||
/// }
|
||||
///
|
||||
/// fn example(cx: Context<Props>) -> VNode {
|
||||
/// html! {
|
||||
/// <div> "Hello, {cx.name}" </div>
|
||||
/// }
|
||||
/// fn Example((cx, props): Scope<Props>) -> Element {
|
||||
/// cx.render(rsx!{ div {"Hello, {props.name}"} })
|
||||
/// }
|
||||
/// ```
|
||||
pub type Context<'a> = &'a ScopeInner;
|
||||
@@ -59,7 +50,8 @@ pub struct ScopeInner {
|
||||
|
||||
// Nodes
|
||||
pub(crate) frames: ActiveFrame,
|
||||
pub(crate) caller: BumpBox<'static, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
|
||||
|
||||
pub(crate) vcomp: *const VComponent<'static>,
|
||||
|
||||
/*
|
||||
we care about:
|
||||
@@ -67,12 +59,7 @@ pub struct ScopeInner {
|
||||
- borrowed props (and how to drop them when the parent is dropped)
|
||||
- suspended nodes (and how to call their callback when their associated tasks are complete)
|
||||
*/
|
||||
pub(crate) listeners: RefCell<Vec<*const Listener<'static>>>,
|
||||
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
|
||||
pub(crate) suspended_nodes: RefCell<FxHashMap<u64, *const VSuspended<'static>>>,
|
||||
|
||||
pub(crate) tasks: RefCell<Vec<BumpBox<'static, dyn Future<Output = ()>>>>,
|
||||
pub(crate) pending_effects: RefCell<Vec<BumpBox<'static, dyn FnMut()>>>,
|
||||
pub(crate) items: RefCell<SelfReferentialItems<'static>>,
|
||||
|
||||
// State
|
||||
pub(crate) hooks: HookList,
|
||||
@@ -80,15 +67,221 @@ pub struct ScopeInner {
|
||||
// todo: move this into a centralized place - is more memory efficient
|
||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
|
||||
|
||||
// whenever set_state is called, we fire off a message to the scheduler
|
||||
// this closure _is_ the method called by schedule_update that marks this component as dirty
|
||||
pub(crate) memoized_updater: Rc<dyn Fn()>,
|
||||
|
||||
pub(crate) shared: EventChannel,
|
||||
}
|
||||
|
||||
/// Public interface for Scopes.
|
||||
pub struct SelfReferentialItems<'a> {
|
||||
pub(crate) listeners: Vec<*const Listener<'a>>,
|
||||
pub(crate) borrowed_props: Vec<*const VComponent<'a>>,
|
||||
pub(crate) suspended_nodes: FxHashMap<u64, *const VSuspended<'a>>,
|
||||
pub(crate) tasks: Vec<BumpBox<'a, dyn Future<Output = ()>>>,
|
||||
pub(crate) pending_effects: Vec<BumpBox<'a, dyn FnMut()>>,
|
||||
}
|
||||
|
||||
impl ScopeInner {
|
||||
// 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
|
||||
pub(crate) fn new(
|
||||
vcomp: &VComponent,
|
||||
our_arena_idx: ScopeId,
|
||||
parent_idx: Option<ScopeId>,
|
||||
height: u32,
|
||||
subtree: u32,
|
||||
shared: EventChannel,
|
||||
) -> Self {
|
||||
let vcomp = unsafe { std::mem::transmute(vcomp as *const VComponent) };
|
||||
|
||||
Self {
|
||||
shared,
|
||||
parent_idx,
|
||||
our_arena_idx,
|
||||
height,
|
||||
subtree: Cell::new(subtree),
|
||||
is_subtree_root: Cell::new(false),
|
||||
frames: ActiveFrame::new(),
|
||||
vcomp,
|
||||
|
||||
hooks: Default::default(),
|
||||
shared_contexts: Default::default(),
|
||||
|
||||
items: RefCell::new(SelfReferentialItems {
|
||||
listeners: Default::default(),
|
||||
borrowed_props: Default::default(),
|
||||
suspended_nodes: Default::default(),
|
||||
tasks: Default::default(),
|
||||
pending_effects: Default::default(),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
|
||||
/// causing UB in our tree.
|
||||
///
|
||||
/// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
|
||||
/// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
|
||||
/// any possible references to the data in the hook list.
|
||||
///
|
||||
/// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
|
||||
/// all listeners and borrowed props so we can clear them here.
|
||||
///
|
||||
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
|
||||
/// be dropped.
|
||||
pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
|
||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||
// run the hooks (which hold an &mut Reference)
|
||||
// right now, we don't drop
|
||||
self.items
|
||||
.get_mut()
|
||||
.borrowed_props
|
||||
.drain(..)
|
||||
.map(|li| unsafe { &*li })
|
||||
.for_each(|comp| {
|
||||
// First drop the component's undropped references
|
||||
let scope_id = comp
|
||||
.associated_scope
|
||||
.get()
|
||||
.expect("VComponents should be associated with a valid Scope");
|
||||
|
||||
if let Some(scope) = pool.get_scope_mut(&scope_id) {
|
||||
scope.ensure_drop_safety(pool);
|
||||
|
||||
todo!("drop the component's props");
|
||||
// let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
|
||||
// drop_props();
|
||||
}
|
||||
});
|
||||
|
||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||
self.items
|
||||
.get_mut()
|
||||
.listeners
|
||||
.drain(..)
|
||||
.map(|li| unsafe { &*li })
|
||||
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
|
||||
}
|
||||
|
||||
/// A safe wrapper around calling listeners
|
||||
pub(crate) fn call_listener(&mut self, event: UserEvent, element: ElementId) {
|
||||
let listners = &mut self.items.get_mut().listeners;
|
||||
|
||||
let raw_listener = listners.iter().find(|lis| {
|
||||
let search = unsafe { &***lis };
|
||||
if search.event == event.name {
|
||||
let search_id = search.mounted_node.get();
|
||||
search_id.map(|f| f == element).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(raw_listener) = raw_listener {
|
||||
let listener = unsafe { &**raw_listener };
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
if let Some(cb) = cb.as_mut() {
|
||||
(cb)(event.event);
|
||||
}
|
||||
} else {
|
||||
log::warn!("An event was triggered but there was no listener to handle it");
|
||||
}
|
||||
}
|
||||
|
||||
// General strategy here is to load up the appropriate suspended task and then run it.
|
||||
// Suspended nodes cannot be called repeatedly.
|
||||
pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
|
||||
let mut nodes = &mut self.items.get_mut().suspended_nodes;
|
||||
|
||||
if let Some(suspended) = nodes.remove(&task_id) {
|
||||
let sus: &'a VSuspended<'static> = unsafe { &*suspended };
|
||||
let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
|
||||
let mut boxed = sus.callback.borrow_mut().take().unwrap();
|
||||
let new_node: Element<'a> = boxed();
|
||||
}
|
||||
}
|
||||
|
||||
// run the list of effects
|
||||
pub(crate) fn run_effects(&mut self, pool: &ResourcePool) {
|
||||
for mut effect in self.items.get_mut().pending_effects.drain(..) {
|
||||
effect();
|
||||
}
|
||||
}
|
||||
|
||||
/// Render this component.
|
||||
///
|
||||
/// Returns true if the scope completed successfully and false if running failed (IE a None error was propagated).
|
||||
pub(crate) fn run_scope<'sel>(&'sel mut self, pool: &ResourcePool) -> bool {
|
||||
// Cycle to the next frame and then reset it
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
// Remove all the outdated listeners
|
||||
self.ensure_drop_safety(pool);
|
||||
|
||||
// Safety:
|
||||
// - We dropped the listeners, so no more &mut T can be used while these are held
|
||||
// - All children nodes that rely on &mut T are replaced with a new reference
|
||||
unsafe { self.hooks.reset() };
|
||||
|
||||
// Safety:
|
||||
// - We've dropped all references to the wip bump frame
|
||||
unsafe { self.frames.reset_wip_frame() };
|
||||
|
||||
let items = self.items.get_mut();
|
||||
|
||||
// just forget about our suspended nodes while we're at it
|
||||
items.suspended_nodes.clear();
|
||||
|
||||
// guarantee that we haven't screwed up - there should be no latent references anywhere
|
||||
debug_assert!(items.listeners.is_empty());
|
||||
debug_assert!(items.suspended_nodes.is_empty());
|
||||
debug_assert!(items.borrowed_props.is_empty());
|
||||
|
||||
log::debug!("Borrowed stuff is successfully cleared");
|
||||
|
||||
// temporarily cast the vcomponent to the right lifetime
|
||||
let vcomp = self.load_vcomp();
|
||||
|
||||
let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = todo!();
|
||||
|
||||
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
|
||||
if let Some(builder) = render(self) {
|
||||
let new_head = builder.into_vnode(NodeFactory {
|
||||
bump: &self.frames.wip_frame().bump,
|
||||
});
|
||||
log::debug!("Render is successful");
|
||||
|
||||
// the user's component succeeded. We can safely cycle to the next frame
|
||||
self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
||||
self.frames.cycle_frame();
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn new_subtree(&self) -> Option<u32> {
|
||||
todo!()
|
||||
// if self.is_subtree_root.get() {
|
||||
// None
|
||||
// } else {
|
||||
// let cur = self.shared.cur_subtree.get();
|
||||
// self.shared.cur_subtree.set(cur + 1);
|
||||
// Some(cur)
|
||||
// }
|
||||
}
|
||||
|
||||
pub(crate) fn update_vcomp(&mut self, vcomp: &VComponent) {
|
||||
let f: *const _ = vcomp;
|
||||
self.vcomp = unsafe { std::mem::transmute(f) };
|
||||
}
|
||||
|
||||
pub(crate) fn load_vcomp<'a>(&'a mut self) -> &'a VComponent<'a> {
|
||||
unsafe { std::mem::transmute(&*self.vcomp) }
|
||||
}
|
||||
|
||||
/// Get the root VNode for this Scope.
|
||||
///
|
||||
/// This VNode is the "entrypoint" VNode. If the component renders multiple nodes, then this VNode will be a fragment.
|
||||
@@ -128,16 +321,6 @@ impl ScopeInner {
|
||||
self.subtree.get()
|
||||
}
|
||||
|
||||
pub(crate) fn new_subtree(&self) -> Option<u32> {
|
||||
if self.is_subtree_root.get() {
|
||||
None
|
||||
} else {
|
||||
let cur = self.shared.cur_subtree.get();
|
||||
self.shared.cur_subtree.set(cur + 1);
|
||||
Some(cur)
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the height of this Scope - IE the number of scopes above it.
|
||||
///
|
||||
/// A Scope with a height of `0` is the root scope - there are no other scopes above it.
|
||||
@@ -192,233 +375,17 @@ impl ScopeInner {
|
||||
pub fn scope_id(&self) -> ScopeId {
|
||||
self.our_arena_idx
|
||||
}
|
||||
}
|
||||
|
||||
// The type of closure that wraps calling components
|
||||
/// The type of task that gets sent to the task scheduler
|
||||
/// Submitting a fiber task returns a handle to that task, which can be used to wake up suspended nodes
|
||||
pub type FiberTask = Pin<Box<dyn Future<Output = ScopeId>>>;
|
||||
|
||||
/// Private interface for Scopes.
|
||||
impl ScopeInner {
|
||||
// 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
|
||||
pub(crate) fn new(
|
||||
caller: BumpBox<dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>>,
|
||||
our_arena_idx: ScopeId,
|
||||
parent_idx: Option<ScopeId>,
|
||||
height: u32,
|
||||
subtree: u32,
|
||||
shared: EventChannel,
|
||||
) -> Self {
|
||||
let schedule_any_update = shared.schedule_any_immediate.clone();
|
||||
|
||||
let memoized_updater = Rc::new(move || schedule_any_update(our_arena_idx));
|
||||
|
||||
// wipe away the associated lifetime - we are going to manually manage the one-way lifetime graph
|
||||
let caller = unsafe { std::mem::transmute(caller) };
|
||||
|
||||
Self {
|
||||
memoized_updater,
|
||||
shared,
|
||||
caller,
|
||||
parent_idx,
|
||||
our_arena_idx,
|
||||
height,
|
||||
subtree: Cell::new(subtree),
|
||||
is_subtree_root: Cell::new(false),
|
||||
tasks: Default::default(),
|
||||
frames: ActiveFrame::new(),
|
||||
hooks: Default::default(),
|
||||
|
||||
pending_effects: Default::default(),
|
||||
suspended_nodes: Default::default(),
|
||||
shared_contexts: Default::default(),
|
||||
listeners: Default::default(),
|
||||
borrowed_props: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn update_scope_dependencies(
|
||||
&mut self,
|
||||
caller: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b>,
|
||||
) {
|
||||
log::debug!("Updating scope dependencies {:?}", self.our_arena_idx);
|
||||
let caller = caller as *const _;
|
||||
self.caller = unsafe { std::mem::transmute(caller) };
|
||||
}
|
||||
|
||||
/// This method cleans up any references to data held within our hook list. This prevents mutable aliasing from
|
||||
/// causing UB in our tree.
|
||||
///
|
||||
/// This works by cleaning up our references from the bottom of the tree to the top. The directed graph of components
|
||||
/// essentially forms a dependency tree that we can traverse from the bottom to the top. As we traverse, we remove
|
||||
/// any possible references to the data in the hook list.
|
||||
///
|
||||
/// References to hook data can only be stored in listeners and component props. During diffing, we make sure to log
|
||||
/// all listeners and borrowed props so we can clear them here.
|
||||
///
|
||||
/// This also makes sure that drop order is consistent and predictable. All resources that rely on being dropped will
|
||||
/// be dropped.
|
||||
pub(crate) fn ensure_drop_safety(&mut self, pool: &ResourcePool) {
|
||||
// make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
|
||||
// run the hooks (which hold an &mut Reference)
|
||||
// right now, we don't drop
|
||||
self.borrowed_props
|
||||
.get_mut()
|
||||
.drain(..)
|
||||
.map(|li| unsafe { &*li })
|
||||
.for_each(|comp| {
|
||||
// First drop the component's undropped references
|
||||
let scope_id = comp
|
||||
.associated_scope
|
||||
.get()
|
||||
.expect("VComponents should be associated with a valid Scope");
|
||||
|
||||
if let Some(scope) = pool.get_scope_mut(scope_id) {
|
||||
scope.ensure_drop_safety(pool);
|
||||
|
||||
let mut drop_props = comp.drop_props.borrow_mut().take().unwrap();
|
||||
drop_props();
|
||||
}
|
||||
});
|
||||
|
||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||
self.listeners
|
||||
.get_mut()
|
||||
.drain(..)
|
||||
.map(|li| unsafe { &*li })
|
||||
.for_each(|listener| drop(listener.callback.borrow_mut().take()));
|
||||
}
|
||||
|
||||
/// A safe wrapper around calling listeners
|
||||
pub(crate) fn call_listener(&mut self, event: UserEvent, element: ElementId) {
|
||||
let listners = self.listeners.borrow_mut();
|
||||
|
||||
let raw_listener = listners.iter().find(|lis| {
|
||||
let search = unsafe { &***lis };
|
||||
if search.event == event.name {
|
||||
let search_id = search.mounted_node.get();
|
||||
search_id.map(|f| f == element).unwrap_or(false)
|
||||
} else {
|
||||
false
|
||||
}
|
||||
});
|
||||
|
||||
if let Some(raw_listener) = raw_listener {
|
||||
let listener = unsafe { &**raw_listener };
|
||||
let mut cb = listener.callback.borrow_mut();
|
||||
if let Some(cb) = cb.as_mut() {
|
||||
(cb)(event.event);
|
||||
}
|
||||
} else {
|
||||
log::warn!("An event was triggered but there was no listener to handle it");
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
General strategy here is to load up the appropriate suspended task and then run it.
|
||||
Suspended nodes cannot be called repeatedly.
|
||||
*/
|
||||
pub(crate) fn call_suspended_node<'a>(&'a mut self, task_id: u64) {
|
||||
let mut nodes = self.suspended_nodes.borrow_mut();
|
||||
|
||||
if let Some(suspended) = nodes.remove(&task_id) {
|
||||
let sus: &'a VSuspended<'static> = unsafe { &*suspended };
|
||||
let sus: &'a VSuspended<'a> = unsafe { std::mem::transmute(sus) };
|
||||
let mut boxed = sus.callback.borrow_mut().take().unwrap();
|
||||
let new_node: Element<'a> = boxed();
|
||||
}
|
||||
}
|
||||
|
||||
// run the list of effects
|
||||
pub(crate) fn run_effects(&mut self, pool: &ResourcePool) {
|
||||
todo!()
|
||||
// let mut effects = self.frames.effects.borrow_mut();
|
||||
// let mut effects = effects.drain(..).collect::<Vec<_>>();
|
||||
|
||||
// for effect in effects {
|
||||
// let effect = unsafe { &*effect };
|
||||
// let effect = effect.as_ref();
|
||||
|
||||
// let mut effect = effect.borrow_mut();
|
||||
// let mut effect = effect.as_mut();
|
||||
|
||||
// effect.run(pool);
|
||||
// }
|
||||
}
|
||||
|
||||
/// Render this component.
|
||||
///
|
||||
/// Returns true if the scope completed successfully and false if running failed (IE a None error was propagated).
|
||||
pub(crate) fn run_scope<'sel>(&'sel mut self, pool: &ResourcePool) -> bool {
|
||||
// Cycle to the next frame and then reset it
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
// Remove all the outdated listeners
|
||||
self.ensure_drop_safety(pool);
|
||||
|
||||
// Safety:
|
||||
// - We dropped the listeners, so no more &mut T can be used while these are held
|
||||
// - All children nodes that rely on &mut T are replaced with a new reference
|
||||
unsafe { self.hooks.reset() };
|
||||
|
||||
// Safety:
|
||||
// - We've dropped all references to the wip bump frame
|
||||
unsafe { self.frames.reset_wip_frame() };
|
||||
|
||||
// just forget about our suspended nodes while we're at it
|
||||
self.suspended_nodes.get_mut().clear();
|
||||
|
||||
// guarantee that we haven't screwed up - there should be no latent references anywhere
|
||||
debug_assert!(self.listeners.borrow().is_empty());
|
||||
debug_assert!(self.suspended_nodes.borrow().is_empty());
|
||||
debug_assert!(self.borrowed_props.borrow().is_empty());
|
||||
|
||||
log::debug!("Borrowed stuff is successfully cleared");
|
||||
|
||||
// Cast the caller ptr from static to one with our own reference
|
||||
let render: &dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> = unsafe { &*self.caller };
|
||||
|
||||
// Todo: see if we can add stronger guarantees around internal bookkeeping and failed component renders.
|
||||
if let Some(builder) = render(self) {
|
||||
let new_head = builder.into_vnode(NodeFactory {
|
||||
bump: &self.frames.wip_frame().bump,
|
||||
});
|
||||
log::debug!("Render is successful");
|
||||
|
||||
// the user's component succeeded. We can safely cycle to the next frame
|
||||
self.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
|
||||
self.frames.cycle_frame();
|
||||
|
||||
true
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
///
|
||||
/// ## Notice: you should prefer using prepare_update and get_scope_id
|
||||
pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
|
||||
self.memoized_updater.clone()
|
||||
}
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
///
|
||||
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
|
||||
pub fn needs_update(&self) {
|
||||
(self.memoized_updater)()
|
||||
}
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
///
|
||||
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
|
||||
pub fn needs_update_any(&self, id: ScopeId) {
|
||||
(self.shared.schedule_any_immediate)(id)
|
||||
// pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
|
||||
let chan = self.shared.sender.clone();
|
||||
let id = self.scope_id();
|
||||
Rc::new(move || {
|
||||
chan.unbounded_send(SchedulerMsg::Immediate(id));
|
||||
})
|
||||
}
|
||||
|
||||
/// Schedule an update for any component given its ScopeId.
|
||||
@@ -427,7 +394,27 @@ impl ScopeInner {
|
||||
///
|
||||
/// This method should be used when you want to schedule an update for a component
|
||||
pub fn schedule_update_any(&self) -> Rc<dyn Fn(ScopeId)> {
|
||||
self.shared.schedule_any_immediate.clone()
|
||||
let chan = self.shared.sender.clone();
|
||||
Rc::new(move |id| {
|
||||
chan.unbounded_send(SchedulerMsg::Immediate(id));
|
||||
})
|
||||
}
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
///
|
||||
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
|
||||
pub fn needs_update(&self) {
|
||||
self.needs_update_any(self.scope_id())
|
||||
}
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
///
|
||||
/// `ScopeId` is not unique for the lifetime of the VirtualDom - a ScopeId will be reused if a component is unmounted.
|
||||
pub fn needs_update_any(&self, id: ScopeId) {
|
||||
self.shared
|
||||
.sender
|
||||
.unbounded_send(SchedulerMsg::Immediate(id))
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
@@ -475,8 +462,9 @@ impl ScopeInner {
|
||||
// erase the 'src lifetime for self-referential storage
|
||||
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
|
||||
|
||||
self.pending_effects.borrow_mut().push(self_ref_fut);
|
||||
self.pending_effects.borrow().len() - 1
|
||||
let mut items = self.items.borrow_mut();
|
||||
items.pending_effects.push(self_ref_fut);
|
||||
items.pending_effects.len() - 1
|
||||
}
|
||||
|
||||
/// Pushes the future onto the poll queue to be polled
|
||||
@@ -491,8 +479,9 @@ impl ScopeInner {
|
||||
// erase the 'src lifetime for self-referential storage
|
||||
let self_ref_fut = unsafe { std::mem::transmute(boxed_fut) };
|
||||
|
||||
self.tasks.borrow_mut().push(self_ref_fut);
|
||||
self.tasks.borrow().len() - 1
|
||||
let mut items = self.items.borrow_mut();
|
||||
items.tasks.push(self_ref_fut);
|
||||
items.tasks.len() - 1
|
||||
}
|
||||
|
||||
/// This method enables the ability to expose state to children further down the VirtualDOM Tree.
|
||||
@@ -580,8 +569,9 @@ impl ScopeInner {
|
||||
///
|
||||
/// - 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
|
||||
///
|
||||
/// To "cleanup" the hook, implement `Drop` on the stored hook value. Whenever the component is dropped, the hook
|
||||
/// will be dropped as well.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -591,32 +581,16 @@ impl ScopeInner {
|
||||
/// use_hook(
|
||||
/// || Rc::new(RefCell::new(initial_value())),
|
||||
/// |state| state,
|
||||
/// |_| {},
|
||||
/// )
|
||||
/// }
|
||||
/// ```
|
||||
pub fn use_hook<'src, State, Output, Init, Run, Cleanup>(
|
||||
pub fn use_hook<'src, State: 'static, Output: 'src>(
|
||||
&'src self,
|
||||
initializer: Init,
|
||||
runner: Run,
|
||||
cleanup: Cleanup,
|
||||
) -> Output
|
||||
where
|
||||
State: 'static,
|
||||
Output: 'src,
|
||||
Init: FnOnce(usize) -> State,
|
||||
Run: FnOnce(&'src mut State) -> Output,
|
||||
Cleanup: FnOnce(Box<State>) + 'static,
|
||||
{
|
||||
// If the idx is the same as the hook length, then we need to add the current hook
|
||||
initializer: impl FnOnce(usize) -> State,
|
||||
runner: impl FnOnce(&'src mut State) -> Output,
|
||||
) -> Output {
|
||||
if self.hooks.at_end() {
|
||||
self.hooks.push_hook(
|
||||
initializer(self.hooks.len()),
|
||||
Box::new(|raw| {
|
||||
let s = raw.downcast::<State>().unwrap();
|
||||
cleanup(s);
|
||||
}),
|
||||
);
|
||||
self.hooks.push_hook(initializer(self.hooks.len()));
|
||||
}
|
||||
|
||||
runner(self.hooks.next::<State>().expect(HOOK_ERR_MSG))
|
||||
|
||||
@@ -1,39 +0,0 @@
|
||||
use crate::innerlude::*;
|
||||
use futures_channel::mpsc::UnboundedSender;
|
||||
|
||||
pub struct TaskHandle {
|
||||
pub(crate) sender: UnboundedSender<SchedulerMsg>,
|
||||
pub(crate) our_id: u64,
|
||||
}
|
||||
|
||||
impl TaskHandle {
|
||||
/// Toggles this coroutine off/on.
|
||||
///
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn toggle(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn resume(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::Task(TaskMsg::ResumeTask(self.our_id)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn stop(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
/// This method is not synchronous - your task will not stop immediately.
|
||||
pub fn restart(&self) {
|
||||
self.sender
|
||||
.unbounded_send(SchedulerMsg::Task(TaskMsg::ToggleTask(self.our_id)))
|
||||
.unwrap()
|
||||
}
|
||||
}
|
||||
@@ -13,7 +13,7 @@ impl TestDom {
|
||||
pub fn new() -> TestDom {
|
||||
let bump = Bump::new();
|
||||
let (sender, receiver) = futures_channel::mpsc::unbounded::<SchedulerMsg>();
|
||||
let scheduler = Scheduler::new(sender, receiver);
|
||||
let scheduler = Scheduler::new(sender, receiver, 10, 100);
|
||||
TestDom { bump, scheduler }
|
||||
}
|
||||
|
||||
|
||||
@@ -11,59 +11,6 @@ pub fn type_name_of<T>(_: T) -> &'static str {
|
||||
std::any::type_name::<T>()
|
||||
}
|
||||
|
||||
use std::future::Future;
|
||||
use std::pin::Pin;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
// use crate::task::{Context, Poll};
|
||||
|
||||
/// Cooperatively gives up a timeslice to the task scheduler.
|
||||
///
|
||||
/// Calling this function will move the currently executing future to the back
|
||||
/// of the execution queue, making room for other futures to execute. This is
|
||||
/// especially useful after running CPU-intensive operations inside a future.
|
||||
///
|
||||
/// See also [`task::spawn_blocking`].
|
||||
///
|
||||
/// [`task::spawn_blocking`]: fn.spawn_blocking.html
|
||||
///
|
||||
/// # Examples
|
||||
///
|
||||
/// Basic usage:
|
||||
///
|
||||
/// ```
|
||||
/// # async_std::task::block_on(async {
|
||||
/// #
|
||||
/// use async_std::task;
|
||||
///
|
||||
/// task::yield_now().await;
|
||||
/// #
|
||||
/// # })
|
||||
/// ```
|
||||
#[inline]
|
||||
pub async fn yield_now() {
|
||||
YieldNow(false).await
|
||||
}
|
||||
|
||||
struct YieldNow(bool);
|
||||
|
||||
impl Future for YieldNow {
|
||||
type Output = ();
|
||||
|
||||
// The futures executor is implemented as a FIFO queue, so all this future
|
||||
// does is re-schedule the future back to the end of the queue, giving room
|
||||
// for other futures to progress.
|
||||
fn poll(mut self: Pin<&mut Self>, cx: &mut Context<'_>) -> Poll<Self::Output> {
|
||||
if !self.0 {
|
||||
self.0 = true;
|
||||
cx.waker().wake_by_ref();
|
||||
Poll::Pending
|
||||
} else {
|
||||
Poll::Ready(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A component's unique identifier.
|
||||
///
|
||||
/// `ScopeId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||
|
||||
@@ -19,10 +19,24 @@
|
||||
//! This module includes just the barebones for a complete VirtualDOM API.
|
||||
//! Additional functionality is defined in the respective files.
|
||||
|
||||
use crate::innerlude::*;
|
||||
use bumpalo::Bump;
|
||||
use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||
use futures_util::{pin_mut, stream::FuturesUnordered, Future, FutureExt, StreamExt};
|
||||
use fxhash::FxHashMap;
|
||||
use fxhash::FxHashSet;
|
||||
use indexmap::IndexSet;
|
||||
use slab::Slab;
|
||||
use std::pin::Pin;
|
||||
use std::task::Poll;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, UnsafeCell},
|
||||
collections::{HashSet, VecDeque},
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
use crate::innerlude::*;
|
||||
use std::{any::Any, rc::Rc};
|
||||
|
||||
/// An integrated virtual node system that progresses events and diffs UI trees.
|
||||
///
|
||||
@@ -55,8 +69,6 @@ use std::{any::Any, rc::Rc};
|
||||
/// }
|
||||
/// ```
|
||||
pub struct VirtualDom {
|
||||
scheduler: Scheduler,
|
||||
|
||||
base_scope: ScopeId,
|
||||
|
||||
root_fc: Box<dyn Any>,
|
||||
@@ -65,6 +77,49 @@ pub struct VirtualDom {
|
||||
|
||||
// we need to keep the allocation around, but we don't necessarily use it
|
||||
_root_caller: Box<dyn Any>,
|
||||
|
||||
// /// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
||||
// /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||
// ///
|
||||
// /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
|
||||
// /// and rusts's guarantees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
|
||||
// pub pool: ResourcePool,
|
||||
//
|
||||
pub component_arena: Bump,
|
||||
|
||||
pub free_components: VecDeque<*mut ScopeInner>,
|
||||
|
||||
pub heuristics: FxHashMap<FcSlot, Heuristic>,
|
||||
|
||||
pub receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
|
||||
// Garbage stored
|
||||
pub pending_garbage: FxHashSet<ScopeId>,
|
||||
|
||||
// Every component that has futures that need to be polled
|
||||
pub pending_futures: FxHashSet<ScopeId>,
|
||||
|
||||
pub ui_events: VecDeque<UserEvent>,
|
||||
|
||||
pub pending_immediates: VecDeque<ScopeId>,
|
||||
|
||||
pub batched_events: VecDeque<UserEvent>,
|
||||
|
||||
pub garbage_scopes: HashSet<ScopeId>,
|
||||
|
||||
pub dirty_scopes: IndexSet<ScopeId>,
|
||||
|
||||
pub saved_state: Option<SavedDiffWork<'static>>,
|
||||
|
||||
pub in_progress: bool,
|
||||
}
|
||||
|
||||
pub enum SchedulerMsg {
|
||||
// events from the host
|
||||
UiEvent(UserEvent),
|
||||
|
||||
// setstate
|
||||
Immediate(ScopeId),
|
||||
}
|
||||
|
||||
impl VirtualDom {
|
||||
@@ -127,7 +182,7 @@ impl VirtualDom {
|
||||
Self::new_with_props_and_scheduler(root, root_props, sender, receiver)
|
||||
}
|
||||
|
||||
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler.
|
||||
/// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the
|
||||
///
|
||||
/// This is useful when the VirtualDom must be driven from outside a thread and it doesn't make sense to wait for the
|
||||
/// VirtualDom to be created just to retrieve its channel receiver.
|
||||
@@ -137,33 +192,71 @@ impl VirtualDom {
|
||||
sender: UnboundedSender<SchedulerMsg>,
|
||||
receiver: UnboundedReceiver<SchedulerMsg>,
|
||||
) -> Self {
|
||||
let root_fc = Box::new(root);
|
||||
let mut component_arena = Bump::new();
|
||||
|
||||
let root_props: Rc<dyn Any> = Rc::new(root_props);
|
||||
// let root_fc = Box::new(root);
|
||||
|
||||
let props = root_props.clone();
|
||||
// let root_props: Rc<dyn Any> = Rc::new(root_props);
|
||||
|
||||
let root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
|
||||
Box::new(move |scope: &ScopeInner| {
|
||||
let props = props.downcast_ref::<P>().unwrap();
|
||||
let node = root((scope, props));
|
||||
// cast into the right lifetime
|
||||
unsafe { std::mem::transmute(node) }
|
||||
});
|
||||
let caller = unsafe { bumpalo::boxed::Box::from_raw(root_caller.as_mut() as *mut _) };
|
||||
// let props = root_props.clone();
|
||||
|
||||
let scheduler = Scheduler::new(sender, receiver);
|
||||
// let mut root_caller: Box<dyn Fn(&ScopeInner) -> Element> =
|
||||
// Box::new(move |scope: &ScopeInner| {
|
||||
// let props = props.downcast_ref::<P>().unwrap();
|
||||
// let node = root((scope, props));
|
||||
// // cast into the right lifetime
|
||||
// unsafe { std::mem::transmute(node) }
|
||||
// });
|
||||
|
||||
let base_scope = scheduler.pool.insert_scope_with_key(|myidx| {
|
||||
ScopeInner::new(caller, myidx, None, 0, 0, scheduler.pool.channel.clone())
|
||||
});
|
||||
// let caller = unsafe { bumpalo::boxed::Box::from_raw(root_caller.as_mut() as *mut _) };
|
||||
|
||||
// // todo make the memory footprint congifurable
|
||||
// let scheduler = Scheduler::new(sender, receiver, 100, 2000);
|
||||
|
||||
// let vcomp = VComponent {
|
||||
// key: todo!(),
|
||||
// associated_scope: todo!(),
|
||||
// user_fc: root as *const _,
|
||||
// can_memoize: todo!(),
|
||||
// raw_props: todo!(),
|
||||
// // drop_props: todo!(),
|
||||
// // caller,
|
||||
// comparator: todo!(),
|
||||
// caller: todo!(),
|
||||
// };
|
||||
|
||||
// let boxed_comp = Box::new(vcomp);
|
||||
|
||||
// let base_scope = pool.insert_scope_with_key(|myidx| {
|
||||
// ScopeInner::new(
|
||||
// boxed_comp.as_ref(),
|
||||
// myidx,
|
||||
// None,
|
||||
// 0,
|
||||
// 0,
|
||||
// pool.channel.clone(),
|
||||
// )
|
||||
// });
|
||||
|
||||
Self {
|
||||
_root_caller: Box::new(root_caller),
|
||||
root_fc,
|
||||
base_scope,
|
||||
scheduler,
|
||||
root_props,
|
||||
scheduler: todo!(),
|
||||
base_scope: todo!(),
|
||||
root_fc: todo!(),
|
||||
root_props: todo!(),
|
||||
_root_caller: todo!(),
|
||||
component_arena: todo!(),
|
||||
free_components: todo!(),
|
||||
heuristics: todo!(),
|
||||
receiver,
|
||||
pending_garbage: todo!(),
|
||||
pending_futures: todo!(),
|
||||
ui_events: todo!(),
|
||||
pending_immediates: todo!(),
|
||||
batched_events: todo!(),
|
||||
garbage_scopes: todo!(),
|
||||
dirty_scopes: todo!(),
|
||||
saved_state: todo!(),
|
||||
in_progress: todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,12 +265,12 @@ impl VirtualDom {
|
||||
/// This is useful for traversing the tree from the root for heuristics or alternsative renderers that use Dioxus
|
||||
/// directly.
|
||||
pub fn base_scope(&self) -> &ScopeInner {
|
||||
self.scheduler.pool.get_scope(self.base_scope).unwrap()
|
||||
self.pool.get_scope(&self.base_scope).unwrap()
|
||||
}
|
||||
|
||||
/// Get the [`Scope`] for a component given its [`ScopeId`]
|
||||
pub fn get_scope(&self, id: ScopeId) -> Option<&ScopeInner> {
|
||||
self.scheduler.pool.get_scope(id)
|
||||
self.pool.get_scope(&id)
|
||||
}
|
||||
|
||||
/// Update the root props of this VirtualDOM.
|
||||
@@ -202,10 +295,10 @@ impl VirtualDom {
|
||||
where
|
||||
P: 'static,
|
||||
{
|
||||
let root_scope = self.scheduler.pool.get_scope_mut(self.base_scope).unwrap();
|
||||
let root_scope = self.pool.get_scope_mut(&self.base_scope).unwrap();
|
||||
|
||||
// Pre-emptively drop any downstream references of the old props
|
||||
root_scope.ensure_drop_safety(&self.scheduler.pool);
|
||||
root_scope.ensure_drop_safety(&self.pool);
|
||||
|
||||
let mut root_props: Rc<dyn Any> = Rc::new(root_props);
|
||||
|
||||
@@ -221,8 +314,6 @@ impl VirtualDom {
|
||||
std::mem::transmute(root((scope, props)))
|
||||
});
|
||||
|
||||
root_scope.update_scope_dependencies(&root_caller);
|
||||
|
||||
drop(root_props);
|
||||
|
||||
Some(self.rebuild())
|
||||
@@ -249,7 +340,7 @@ impl VirtualDom {
|
||||
/// apply_edits(edits);
|
||||
/// ```
|
||||
pub fn rebuild(&mut self) -> Mutations {
|
||||
self.scheduler.rebuild(self.base_scope)
|
||||
self.rebuild(self.base_scope)
|
||||
}
|
||||
|
||||
/// Compute a manual diff of the VirtualDOM between states.
|
||||
@@ -287,7 +378,7 @@ impl VirtualDom {
|
||||
/// let edits = dom.diff();
|
||||
/// ```
|
||||
pub fn diff(&mut self) -> Mutations {
|
||||
self.scheduler.hard_diff(self.base_scope)
|
||||
self.hard_diff(self.base_scope)
|
||||
}
|
||||
|
||||
/// Runs the virtualdom immediately, not waiting for any suspended nodes to complete.
|
||||
@@ -295,8 +386,8 @@ impl VirtualDom {
|
||||
/// This method will not wait for any suspended nodes to complete. If there is no pending work, then this method will
|
||||
/// return "None"
|
||||
pub fn run_immediate(&mut self) -> Option<Vec<Mutations>> {
|
||||
if self.scheduler.has_any_work() {
|
||||
Some(self.scheduler.work_sync())
|
||||
if self.has_any_work() {
|
||||
Some(self.work_sync())
|
||||
} else {
|
||||
None
|
||||
}
|
||||
@@ -346,18 +437,18 @@ impl VirtualDom {
|
||||
///
|
||||
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
||||
pub fn run_with_deadline(&mut self, deadline: impl FnMut() -> bool) -> Vec<Mutations<'_>> {
|
||||
self.scheduler.work_with_deadline(deadline)
|
||||
self.work_with_deadline(deadline)
|
||||
}
|
||||
|
||||
pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
|
||||
self.scheduler.pool.channel.sender.clone()
|
||||
self.pool.channel.sender.clone()
|
||||
}
|
||||
|
||||
/// Waits for the scheduler to have work
|
||||
/// This lets us poll async tasks during idle periods without blocking the main thread.
|
||||
pub async fn wait_for_work(&mut self) {
|
||||
// todo: poll the events once even if there is work to do to prevent starvation
|
||||
if self.scheduler.has_any_work() {
|
||||
if self.has_any_work() {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -365,28 +456,485 @@ impl VirtualDom {
|
||||
|
||||
// Wait for any new events if we have nothing to do
|
||||
|
||||
let tasks_fut = self.scheduler.async_tasks.next();
|
||||
let scheduler_fut = self.scheduler.receiver.next();
|
||||
// let tasks_fut = self.async_tasks.next();
|
||||
// let scheduler_fut = self.receiver.next();
|
||||
|
||||
use futures_util::future::{select, Either};
|
||||
match select(tasks_fut, scheduler_fut).await {
|
||||
// poll the internal futures
|
||||
Either::Left((_id, _)) => {
|
||||
// use futures_util::future::{select, Either};
|
||||
// match select(tasks_fut, scheduler_fut).await {
|
||||
// // poll the internal futures
|
||||
// Either::Left((_id, _)) => {
|
||||
// //
|
||||
// }
|
||||
|
||||
// // wait for an external event
|
||||
// Either::Right((msg, _)) => match msg.unwrap() {
|
||||
// SchedulerMsg::Task(t) => {
|
||||
// self.handle_task(t);
|
||||
// }
|
||||
// SchedulerMsg::Immediate(im) => {
|
||||
// self.dirty_scopes.insert(im);
|
||||
// }
|
||||
// SchedulerMsg::UiEvent(evt) => {
|
||||
// self.ui_events.push_back(evt);
|
||||
// }
|
||||
// },
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
pub type FcSlot = *const ();
|
||||
|
||||
pub struct Heuristic {
|
||||
hook_arena_size: usize,
|
||||
node_arena_size: usize,
|
||||
}
|
||||
|
||||
/*
|
||||
Welcome to Dioxus's cooperative, priority-based scheduler.
|
||||
|
||||
I hope you enjoy your stay.
|
||||
|
||||
Some essential reading:
|
||||
- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L197-L200
|
||||
- https://github.com/facebook/react/blob/main/packages/scheduler/src/forks/Scheduler.js#L440
|
||||
- https://github.com/WICG/is-input-pending
|
||||
- https://web.dev/rail/
|
||||
- https://indepth.dev/posts/1008/inside-fiber-in-depth-overview-of-the-new-reconciliation-algorithm-in-react
|
||||
|
||||
# What's going on?
|
||||
|
||||
Dioxus is a framework for "user experience" - not just "user interfaces." Part of the "experience" is keeping the UI
|
||||
snappy and "jank free" even under heavy work loads. Dioxus already has the "speed" part figured out - but there's no
|
||||
point in being "fast" if you can't also be "responsive."
|
||||
|
||||
As such, Dioxus can manually decide on what work is most important at any given moment in time. With a properly tuned
|
||||
priority system, Dioxus can ensure that user interaction is prioritized and committed as soon as possible (sub 100ms).
|
||||
The controller responsible for this priority management is called the "scheduler" and is responsible for juggling many
|
||||
different types of work simultaneously.
|
||||
|
||||
# How does it work?
|
||||
|
||||
Per the RAIL guide, we want to make sure that A) inputs are handled ASAP and B) animations are not blocked.
|
||||
React-three-fiber is a testament to how amazing this can be - a ThreeJS scene is threaded in between work periods of
|
||||
React, and the UI still stays snappy!
|
||||
|
||||
While it's straightforward to run code ASAP and be as "fast as possible", what's not _not_ straightforward is how to do
|
||||
this while not blocking the main thread. The current prevailing thought is to stop working periodically so the browser
|
||||
has time to paint and run animations. When the browser is finished, we can step in and continue our work.
|
||||
|
||||
React-Fiber uses the "Fiber" concept to achieve a pause-resume functionality. This is worth reading up on, but not
|
||||
necessary to understand what we're doing here. In Dioxus, our DiffMachine is guided by DiffInstructions - essentially
|
||||
"commands" that guide the Diffing algorithm through the tree. Our "diff_scope" method is async - we can literally pause
|
||||
our DiffMachine "mid-sentence" (so to speak) by just stopping the poll on the future. The DiffMachine periodically yields
|
||||
so Rust's async machinery can take over, allowing us to customize when exactly to pause it.
|
||||
|
||||
React's "should_yield" method is more complex than ours, and I assume we'll move in that direction as Dioxus matures. For
|
||||
now, Dioxus just assumes a TimeoutFuture, and selects! on both the Diff algorithm and timeout. If the DiffMachine finishes
|
||||
before the timeout, then Dioxus will work on any pending work in the interim. If there is no pending work, then the changes
|
||||
are committed, and coroutines are polled during the idle period. However, if the timeout expires, then the DiffMachine
|
||||
future is paused and saved (self-referentially).
|
||||
|
||||
# Priority System
|
||||
|
||||
So far, we've been able to thread our Dioxus work between animation frames - the main thread is not blocked! But that
|
||||
doesn't help us _under load_. How do we still stay snappy... even if we're doing a lot of work? Well, that's where
|
||||
priorities come into play. The goal with priorities is to schedule shorter work as a "high" priority and longer work as
|
||||
a "lower" priority. That way, we can interrupt long-running low-priority work with short-running high-priority work.
|
||||
|
||||
React's priority system is quite complex.
|
||||
|
||||
There are 5 levels of priority and 2 distinctions between UI events (discrete, continuous). I believe React really only
|
||||
uses 3 priority levels and "idle" priority isn't used... Regardless, there's some batching going on.
|
||||
|
||||
For Dioxus, we're going with a 4 tier priority system:
|
||||
- Sync: Things that need to be done by the next frame, like TextInput on controlled elements
|
||||
- High: for events that block all others - clicks, keyboard, and hovers
|
||||
- Medium: for UI events caused by the user but not directly - scrolls/forms/focus (all other events)
|
||||
- Low: set_state called asynchronously, and anything generated by suspense
|
||||
|
||||
In "Sync" state, we abort our "idle wait" future, and resolve the sync queue immediately and escape. Because we completed
|
||||
work before the next rAF, any edits can be immediately processed before the frame ends. Generally though, we want to leave
|
||||
as much time to rAF as possible. "Sync" is currently only used by onInput - we'll leave some docs telling people not to
|
||||
do anything too arduous from onInput.
|
||||
|
||||
For the rest, we defer to the rIC period and work down each queue from high to low.
|
||||
*/
|
||||
|
||||
/// The scheduler holds basically everything around "working"
|
||||
///
|
||||
/// Each scope has the ability to lightly interact with the scheduler (IE, schedule an update) but ultimately the scheduler calls the components.
|
||||
///
|
||||
/// In Dioxus, the scheduler provides 4 priority levels - each with their own "DiffMachine". The DiffMachine state can be saved if the deadline runs
|
||||
/// out.
|
||||
///
|
||||
/// Saved DiffMachine state can be self-referential, so we need to be careful about how we save it. All self-referential data is a link between
|
||||
/// pending DiffInstructions, Mutations, and their underlying Scope. It's okay for us to be self-referential with this data, provided we don't priority
|
||||
/// task shift to a higher priority task that needs mutable access to the same scopes.
|
||||
///
|
||||
/// We can prevent this safety issue from occurring if we track which scopes are invalidated when starting a new task.
|
||||
///
|
||||
/// There's a lot of raw pointers here...
|
||||
///
|
||||
/// Since we're building self-referential structures for each component, we need to make sure that the referencs stay stable
|
||||
/// The best way to do that is a bump allocator.
|
||||
///
|
||||
///
|
||||
///
|
||||
impl VirtualDom {
|
||||
// returns true if the event is discrete
|
||||
pub fn handle_ui_event(&mut self, event: UserEvent) -> bool {
|
||||
let (discrete, priority) = event_meta(&event);
|
||||
|
||||
if let Some(scope) = self.get_scope_mut(&event.scope) {
|
||||
if let Some(element) = event.mounted_dom_id {
|
||||
// TODO: bubble properly here
|
||||
scope.call_listener(event, element);
|
||||
|
||||
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
|
||||
//
|
||||
// self.add_dirty_scope(dirty_scope, trigger.priority)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// use EventPriority::*;
|
||||
|
||||
// match priority {
|
||||
// Immediate => todo!(),
|
||||
// High => todo!(),
|
||||
// Medium => todo!(),
|
||||
// Low => todo!(),
|
||||
// }
|
||||
|
||||
discrete
|
||||
}
|
||||
|
||||
fn prepare_work(&mut self) {
|
||||
// while let Some(trigger) = self.ui_events.pop_back() {
|
||||
// if let Some(scope) = self.get_scope_mut(&trigger.scope) {}
|
||||
// }
|
||||
}
|
||||
|
||||
// nothing to do, no events on channels, no work
|
||||
pub fn has_any_work(&self) -> bool {
|
||||
!(self.dirty_scopes.is_empty() && self.ui_events.is_empty())
|
||||
}
|
||||
|
||||
/// re-balance the work lanes, ensuring high-priority work properly bumps away low priority work
|
||||
fn balance_lanes(&mut self) {}
|
||||
|
||||
fn save_work(&mut self, lane: SavedDiffWork) {
|
||||
let saved: SavedDiffWork<'static> = unsafe { std::mem::transmute(lane) };
|
||||
self.saved_state = Some(saved);
|
||||
}
|
||||
|
||||
unsafe fn load_work(&mut self) -> SavedDiffWork<'static> {
|
||||
self.saved_state.take().unwrap().extend()
|
||||
}
|
||||
|
||||
pub fn handle_channel_msg(&mut self, msg: SchedulerMsg) {
|
||||
match msg {
|
||||
SchedulerMsg::Immediate(_) => todo!(),
|
||||
|
||||
SchedulerMsg::UiEvent(event) => {
|
||||
//
|
||||
|
||||
let (discrete, priority) = event_meta(&event);
|
||||
|
||||
if let Some(scope) = self.get_scope_mut(&event.scope) {
|
||||
if let Some(element) = event.mounted_dom_id {
|
||||
// TODO: bubble properly here
|
||||
scope.call_listener(event, element);
|
||||
|
||||
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
|
||||
//
|
||||
// self.add_dirty_scope(dirty_scope, trigger.priority)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
discrete;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Load the current lane, and work on it, periodically checking in if the deadline has been reached.
|
||||
///
|
||||
/// Returns true if the lane is finished before the deadline could be met.
|
||||
pub fn work_on_current_lane(
|
||||
&mut self,
|
||||
deadline_reached: impl FnMut() -> bool,
|
||||
mutations: &mut Vec<Mutations>,
|
||||
) -> bool {
|
||||
// Work through the current subtree, and commit the results when it finishes
|
||||
// When the deadline expires, give back the work
|
||||
let saved_state = unsafe { self.load_work() };
|
||||
|
||||
// We have to split away some parts of ourself - current lane is borrowed mutably
|
||||
let shared = self.clone();
|
||||
let mut machine = unsafe { saved_state.promote(&shared) };
|
||||
|
||||
let mut ran_scopes = FxHashSet::default();
|
||||
|
||||
if machine.stack.is_empty() {
|
||||
let shared = self.clone();
|
||||
|
||||
self.dirty_scopes
|
||||
.retain(|id| shared.get_scope(id).is_some());
|
||||
self.dirty_scopes.sort_by(|a, b| {
|
||||
let h1 = shared.get_scope(a).unwrap().height;
|
||||
let h2 = shared.get_scope(b).unwrap().height;
|
||||
h1.cmp(&h2).reverse()
|
||||
});
|
||||
|
||||
if let Some(scopeid) = self.dirty_scopes.pop() {
|
||||
log::info!("handling dirty scope {:?}", scopeid);
|
||||
if !ran_scopes.contains(&scopeid) {
|
||||
ran_scopes.insert(scopeid);
|
||||
log::debug!("about to run scope {:?}", scopeid);
|
||||
|
||||
if let Some(component) = self.get_scope_mut(&scopeid) {
|
||||
if component.run_scope(&self) {
|
||||
let (old, new) =
|
||||
(component.frames.wip_head(), component.frames.fin_head());
|
||||
// let (old, new) = (component.frames.wip_head(), component.frames.fin_head());
|
||||
machine.stack.scope_stack.push(scopeid);
|
||||
machine.stack.push(DiffInstruction::Diff { new, old });
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let work_completed = machine.work(deadline_reached);
|
||||
|
||||
// log::debug!("raw edits {:?}", machine.mutations.edits);
|
||||
|
||||
let mut machine: DiffMachine<'static> = unsafe { std::mem::transmute(machine) };
|
||||
// let mut saved = machine.save();
|
||||
|
||||
if work_completed {
|
||||
for node in machine.seen_scopes.drain() {
|
||||
// self.dirty_scopes.clear();
|
||||
// self.ui_events.clear();
|
||||
self.dirty_scopes.remove(&node);
|
||||
// self.dirty_scopes.remove(&node);
|
||||
}
|
||||
|
||||
// wait for an external event
|
||||
Either::Right((msg, _)) => match msg.unwrap() {
|
||||
SchedulerMsg::Task(t) => {
|
||||
self.scheduler.handle_task(t);
|
||||
let mut new_mutations = Mutations::new();
|
||||
|
||||
for edit in machine.mutations.edits.drain(..) {
|
||||
new_mutations.edits.push(edit);
|
||||
}
|
||||
|
||||
// for edit in saved.edits.drain(..) {
|
||||
// new_mutations.edits.push(edit);
|
||||
// }
|
||||
|
||||
// std::mem::swap(&mut new_mutations, &mut saved.mutations);
|
||||
|
||||
mutations.push(new_mutations);
|
||||
|
||||
// log::debug!("saved edits {:?}", mutations);
|
||||
|
||||
let mut saved = machine.save();
|
||||
self.save_work(saved);
|
||||
true
|
||||
|
||||
// self.save_work(saved);
|
||||
// false
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// The primary workhorse of the VirtualDOM.
|
||||
///
|
||||
/// Uses some fairly complex logic to schedule what work should be produced.
|
||||
///
|
||||
/// Returns a list of successful mutations.
|
||||
pub fn work_with_deadline<'a>(
|
||||
&'a mut self,
|
||||
mut deadline: impl FnMut() -> bool,
|
||||
) -> Vec<Mutations<'a>> {
|
||||
/*
|
||||
Strategy:
|
||||
- When called, check for any UI events that might've been received since the last frame.
|
||||
- Dump all UI events into a "pending discrete" queue and a "pending continuous" queue.
|
||||
|
||||
- If there are any pending discrete events, then elevate our priority level. If our priority level is already "high,"
|
||||
then we need to finish the high priority work first. If the current work is "low" then analyze what scopes
|
||||
will be invalidated by this new work. If this interferes with any in-flight medium or low work, then we need
|
||||
to bump the other work out of the way, or choose to process it so we don't have any conflicts.
|
||||
'static components have a leg up here since their work can be re-used among multiple scopes.
|
||||
"High priority" is only for blocking! Should only be used on "clicks"
|
||||
|
||||
- If there are no pending discrete events, then check for continuous events. These can be completely batched
|
||||
|
||||
- we batch completely until we run into a discrete event
|
||||
- all continuous events are batched together
|
||||
- so D C C C C C would be two separate events - D and C. IE onclick and onscroll
|
||||
- D C C C C C C D C C C D would be D C D C D in 5 distinct phases.
|
||||
|
||||
- !listener bubbling is not currently implemented properly and will need to be implemented somehow in the future
|
||||
- we need to keep track of element parents to be able to traverse properly
|
||||
|
||||
|
||||
Open questions:
|
||||
- what if we get two clicks from the component during the same slice?
|
||||
- should we batch?
|
||||
- react says no - they are continuous
|
||||
- but if we received both - then we don't need to diff, do we? run as many as we can and then finally diff?
|
||||
*/
|
||||
let mut committed_mutations = Vec::<Mutations<'static>>::new();
|
||||
|
||||
while self.has_any_work() {
|
||||
while let Ok(Some(msg)) = self.receiver.try_next() {
|
||||
match msg {
|
||||
SchedulerMsg::Immediate(im) => {
|
||||
self.dirty_scopes.insert(im);
|
||||
}
|
||||
SchedulerMsg::UiEvent(evt) => {
|
||||
self.ui_events.push_back(evt);
|
||||
}
|
||||
}
|
||||
SchedulerMsg::Immediate(im) => {
|
||||
self.scheduler.dirty_scopes.insert(im);
|
||||
}
|
||||
|
||||
// switch our priority, pop off any work
|
||||
while let Some(event) = self.ui_events.pop_front() {
|
||||
if let Some(scope) = self.get_scope_mut(&event.scope) {
|
||||
if let Some(element) = event.mounted_dom_id {
|
||||
log::info!("Calling listener {:?}, {:?}", event.scope, element);
|
||||
|
||||
// TODO: bubble properly here
|
||||
scope.call_listener(event, element);
|
||||
|
||||
while let Ok(Some(dirty_scope)) = self.receiver.try_next() {
|
||||
match dirty_scope {
|
||||
SchedulerMsg::Immediate(im) => {
|
||||
self.dirty_scopes.insert(im);
|
||||
}
|
||||
SchedulerMsg::UiEvent(e) => self.ui_events.push_back(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
SchedulerMsg::UiEvent(evt) => {
|
||||
self.scheduler.ui_events.push_back(evt);
|
||||
}
|
||||
|
||||
let work_complete = self.work_on_current_lane(&mut deadline, &mut committed_mutations);
|
||||
|
||||
if !work_complete {
|
||||
return committed_mutations;
|
||||
}
|
||||
}
|
||||
|
||||
committed_mutations
|
||||
}
|
||||
|
||||
/// Work the scheduler down, not polling any ongoing tasks.
|
||||
///
|
||||
/// Will use the standard priority-based scheduling, batching, etc, but just won't interact with the async reactor.
|
||||
pub fn work_sync<'a>(&'a mut self) -> Vec<Mutations<'a>> {
|
||||
let mut committed_mutations = Vec::new();
|
||||
|
||||
while let Ok(Some(msg)) = self.receiver.try_next() {
|
||||
self.handle_channel_msg(msg);
|
||||
}
|
||||
|
||||
if !self.has_any_work() {
|
||||
return committed_mutations;
|
||||
}
|
||||
|
||||
while self.has_any_work() {
|
||||
self.prepare_work();
|
||||
self.work_on_current_lane(|| false, &mut committed_mutations);
|
||||
}
|
||||
|
||||
committed_mutations
|
||||
}
|
||||
|
||||
/// Restart the entire VirtualDOM from scratch, wiping away any old state and components.
|
||||
///
|
||||
/// Typically used to kickstart the VirtualDOM after initialization.
|
||||
pub fn rebuild(&mut self, base_scope: ScopeId) -> Mutations {
|
||||
let mut shared = self.clone();
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared);
|
||||
|
||||
// TODO: drain any in-flight work
|
||||
let cur_component = self
|
||||
.pool
|
||||
.get_scope_mut(&base_scope)
|
||||
.expect("The base scope should never be moved");
|
||||
|
||||
log::debug!("rebuild {:?}", base_scope);
|
||||
|
||||
// We run the component. If it succeeds, then we can diff it and add the changes to the dom.
|
||||
if cur_component.run_scope(&self) {
|
||||
diff_machine
|
||||
.stack
|
||||
.create_node(cur_component.frames.fin_head(), MountType::Append);
|
||||
|
||||
diff_machine.stack.scope_stack.push(base_scope);
|
||||
|
||||
diff_machine.work(|| false);
|
||||
} else {
|
||||
// todo: should this be a hard error?
|
||||
log::warn!(
|
||||
"Component failed to run successfully during rebuild.
|
||||
This does not result in a failed rebuild, but indicates a logic failure within your app."
|
||||
);
|
||||
}
|
||||
|
||||
unsafe { std::mem::transmute(diff_machine.mutations) }
|
||||
}
|
||||
|
||||
pub fn hard_diff(&mut self, base_scope: ScopeId) -> Mutations {
|
||||
let cur_component = self
|
||||
.pool
|
||||
.get_scope_mut(&base_scope)
|
||||
.expect("The base scope should never be moved");
|
||||
|
||||
log::debug!("hard diff {:?}", base_scope);
|
||||
|
||||
if cur_component.run_scope(&self) {
|
||||
let mut diff_machine = DiffMachine::new(Mutations::new(), &mut self);
|
||||
diff_machine.cfg.force_diff = true;
|
||||
diff_machine.diff_scope(base_scope);
|
||||
diff_machine.mutations
|
||||
} else {
|
||||
Mutations::new()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Future for VirtualDom {
|
||||
type Output = ();
|
||||
|
||||
fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll<Self::Output> {
|
||||
let mut all_pending = true;
|
||||
|
||||
for fut in self.pending_futures.iter() {
|
||||
let scope = self
|
||||
.pool
|
||||
.get_scope_mut(&fut)
|
||||
.expect("Scope should never be moved");
|
||||
|
||||
let items = scope.items.get_mut();
|
||||
for task in items.tasks.iter_mut() {
|
||||
let t = task.as_mut();
|
||||
let g = unsafe { Pin::new_unchecked(t) };
|
||||
match g.poll(cx) {
|
||||
Poll::Ready(r) => {
|
||||
all_pending = false;
|
||||
}
|
||||
Poll::Pending => {}
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
match all_pending {
|
||||
true => Poll::Pending,
|
||||
false => Poll::Ready(()),
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user