From 70b983d0909a332d357da9bb07adc263059bce19 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 5 Nov 2021 16:28:08 -0400 Subject: [PATCH 01/16] wip: consolidation, simplification --- packages/core/src/bumpframe.rs | 2 - packages/core/src/childiter.rs | 2 +- packages/core/src/component.rs | 9 +- packages/core/src/coroutines.rs | 17 - packages/core/src/diff.rs | 93 +++-- packages/core/src/heuristics.rs | 50 --- packages/core/src/hooklist.rs | 33 +- packages/core/src/hooks.rs | 130 +++--- packages/core/src/lib.rs | 15 +- packages/core/src/nodes.rs | 193 +++++---- packages/core/src/resources.rs | 6 +- packages/core/src/scheduler.rs | 226 +++++------ packages/core/src/scope.rs | 542 ++++++++++++------------- packages/core/src/tasks.rs | 39 -- packages/core/src/test_dom.rs | 2 +- packages/core/src/util.rs | 53 --- packages/core/src/virtual_dom.rs | 652 ++++++++++++++++++++++++++++--- 17 files changed, 1228 insertions(+), 836 deletions(-) delete mode 100644 packages/core/src/coroutines.rs delete mode 100644 packages/core/src/heuristics.rs delete mode 100644 packages/core/src/tasks.rs diff --git a/packages/core/src/bumpframe.rs b/packages/core/src/bumpframe.rs index ac1c66189..b4facf39f 100644 --- a/packages/core/src/bumpframe.rs +++ b/packages/core/src/bumpframe.rs @@ -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, diff --git a/packages/core/src/childiter.rs b/packages/core/src/childiter.rs index 0dc047caf..b7b6a70ea 100644 --- a/packages/core/src/childiter.rs +++ b/packages/core/src/childiter.rs @@ -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 diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index e249bfae1..8bcc8bd35 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -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>, } 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 } } diff --git a/packages/core/src/coroutines.rs b/packages/core/src/coroutines.rs deleted file mode 100644 index 490a0acab..000000000 --- a/packages/core/src/coroutines.rs +++ /dev/null @@ -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>>, -} - -impl CoroutineScheduler { - pub fn new() -> Self { - CoroutineScheduler { - futures: FuturesUnordered::new(), - } - } -} diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index 654048267..f59d89490 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -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 _); } } diff --git a/packages/core/src/heuristics.rs b/packages/core/src/heuristics.rs deleted file mode 100644 index a8069c95e..000000000 --- a/packages/core/src/heuristics.rs +++ /dev/null @@ -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, -} - -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(&mut self, fc: FC, heuristic: Heuristic) { - let g = fc as FcSlot; - let e = self.heuristics.entry(g); - } - - fn get_recommendation(&mut self, fc: FC) -> &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(); -} diff --git a/packages/core/src/hooklist.rs b/packages/core/src/hooklist.rs index ed4eb49dd..03bce78b4 100644 --- a/packages/core/src/hooklist.rs +++ b/packages/core/src/hooklist.rs @@ -1,10 +1,9 @@ use std::{ any::Any, - cell::{Cell, RefCell, UnsafeCell}, + cell::{Cell, RefCell}, }; -type UnsafeInnerHookState = UnsafeCell>; -type HookCleanup = Box)>; +use bumpalo::Bump; /// An abstraction over internally stored data using a hook-based memory layout. /// @@ -15,7 +14,8 @@ type HookCleanup = Box)>; /// Todo: this could use its very own bump arena, but that might be a tad overkill #[derive(Default)] pub(crate) struct HookList { - vals: RefCell>, + arena: Bump, + vals: RefCell>, idx: Cell, } @@ -23,7 +23,7 @@ impl HookList { pub(crate) fn next(&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::() }) } @@ -38,10 +38,9 @@ impl HookList { self.idx.set(0); } - pub(crate) fn push_hook(&self, new: T, cleanup: HookCleanup) { - self.vals - .borrow_mut() - .push((UnsafeCell::new(Box::new(new)), cleanup)) + pub(crate) fn push_hook(&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() } } diff --git a/packages/core/src/hooks.rs b/packages/core/src/hooks.rs index b18a445c1..54a5ca5a5 100644 --- a/packages/core/src/hooks.rs +++ b/packages/core/src/hooks.rs @@ -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) -where - Out: 'static, - Fut: Future + 'static, - Init: FnOnce() -> Fut + 'src, -{ - struct TaskHook { - handle: TaskHandle, - task_dump: Rc>>, - value: Option, - } +// /// 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) +// where +// Out: 'static, +// Fut: Future + 'static, +// Init: FnOnce() -> Fut + 'src, +// { +// struct TaskHook { +// handle: TaskHandle, +// task_dump: Rc>>, +// value: Option, +// } - 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>>>, -} +// pub(crate) struct SuspenseHook { +// pub handle: TaskHandle, +// pub value: Rc>>>, +// } #[derive(Clone, Copy)] pub struct NodeRef<'src, T: 'static>(&'src RefCell>); @@ -183,5 +183,5 @@ impl<'a, T> Deref for NodeRef<'a, T> { } pub fn use_node_ref(cx: Context) -> NodeRef { - cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }, |_| {}) + cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }) } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index d023b2c37..41bd45ceb 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -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 { diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 1cb06a4d6..fc7b7f76e 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -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>, - 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 bool + 'src>>, - - pub(crate) drop_props: RefCell>>, - 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>, + pub(crate) comparator: Option bool + 'src>>, +} + +pub enum VCompCaller<'src> { + Borrowed(BumpBox<'src, dyn for<'b> Fn(&'b ScopeInner) -> Element<'b> + 'src>), + Owned(Box Fn(&'b ScopeInner) -> Element<'b>>), } pub struct VSuspended<'a> { @@ -473,7 +478,7 @@ impl<'a> NodeFactory<'a> { } } - pub fn component

( + pub fn component( &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

is the same by function pointer - // - Because FC

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

is the same by function pointer + // // - Because FC

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

= 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> { Some(lazy) => lazy.call(cx), None => VNode::Fragment(VFragment { children: &[], - is_static: false, key: None, }), } diff --git a/packages/core/src/resources.rs b/packages/core/src/resources.rs index 4163e30d3..df931dcda 100644 --- a/packages/core/src/resources.rs +++ b/packages/core/src/resources.rs @@ -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 { + pub fn try_remove(&self, id: &ScopeId) -> Option { let inner = unsafe { &mut *self.components.get() }; Some(inner.remove(id.0)) // .try_remove(id.0) diff --git a/packages/core/src/scheduler.rs b/packages/core/src/scheduler.rs index f6c5ee018..21503fffd 100644 --- a/packages/core/src/scheduler.rs +++ b/packages/core/src/scheduler.rs @@ -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>, - pub cur_subtree: Rc>, pub sender: UnboundedSender, - pub schedule_any_immediate: Rc, - pub submit_task: Rc 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, pub receiver: UnboundedReceiver, @@ -145,104 +144,70 @@ pub(crate) struct Scheduler { // Every component that has futures that need to be polled pub pending_futures: FxHashSet, - // In-flight futures - pub async_tasks: FuturesUnordered, - // // scheduler stuff // pub current_priority: EventPriority, pub ui_events: VecDeque, pub pending_immediates: VecDeque, - pub pending_tasks: VecDeque, - pub batched_events: VecDeque, pub garbage_scopes: HashSet, pub dirty_scopes: IndexSet, + pub saved_state: Option>, + 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, receiver: UnboundedReceiver, + 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 { + 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(()), + } + } +} diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index f48163710..27bffd90f 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -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) -> VNode { -/// html! { -///

"Hello, {cx.name}"
-/// } +/// fn Example((cx, props): Scope) -> 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>>, - pub(crate) borrowed_props: RefCell>>, - pub(crate) suspended_nodes: RefCell>>, - - pub(crate) tasks: RefCell>>>, - pub(crate) pending_effects: RefCell>>, + pub(crate) items: RefCell>, // 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>>, - // 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, - 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>, + pub(crate) tasks: Vec>>, + pub(crate) pending_effects: Vec>, +} + 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, + 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 { + 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 { - 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>>; - -/// 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 Fn(&'b ScopeInner) -> Element<'b>>, - our_arena_idx: ScopeId, - parent_idx: Option, - 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::>(); - - // 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 { - 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 { + 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 { - 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) + '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::().unwrap(); - cleanup(s); - }), - ); + self.hooks.push_hook(initializer(self.hooks.len())); } runner(self.hooks.next::().expect(HOOK_ERR_MSG)) diff --git a/packages/core/src/tasks.rs b/packages/core/src/tasks.rs deleted file mode 100644 index 95540d153..000000000 --- a/packages/core/src/tasks.rs +++ /dev/null @@ -1,39 +0,0 @@ -use crate::innerlude::*; -use futures_channel::mpsc::UnboundedSender; - -pub struct TaskHandle { - pub(crate) sender: UnboundedSender, - 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() - } -} diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index aab7f3373..332a23402 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -13,7 +13,7 @@ impl TestDom { pub fn new() -> TestDom { let bump = Bump::new(); let (sender, receiver) = futures_channel::mpsc::unbounded::(); - let scheduler = Scheduler::new(sender, receiver); + let scheduler = Scheduler::new(sender, receiver, 10, 100); TestDom { bump, scheduler } } diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs index 1ddd3a51c..fad669026 100644 --- a/packages/core/src/util.rs +++ b/packages/core/src/util.rs @@ -11,59 +11,6 @@ pub fn type_name_of(_: T) -> &'static str { std::any::type_name::() } -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 { - 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 diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 6945520e4..c0d3054a2 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -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, @@ -65,6 +77,49 @@ pub struct VirtualDom { // we need to keep the allocation around, but we don't necessarily use it _root_caller: Box, + + // /// 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, + + pub receiver: UnboundedReceiver, + + // Garbage stored + pub pending_garbage: FxHashSet, + + // Every component that has futures that need to be polled + pub pending_futures: FxHashSet, + + pub ui_events: VecDeque, + + pub pending_immediates: VecDeque, + + pub batched_events: VecDeque, + + pub garbage_scopes: HashSet, + + pub dirty_scopes: IndexSet, + + pub saved_state: Option>, + + 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, receiver: UnboundedReceiver, ) -> Self { - let root_fc = Box::new(root); + let mut component_arena = Bump::new(); - let root_props: Rc = Rc::new(root_props); + // let root_fc = Box::new(root); - let props = root_props.clone(); + // let root_props: Rc = Rc::new(root_props); - let root_caller: Box Element> = - Box::new(move |scope: &ScopeInner| { - let props = props.downcast_ref::

().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 Element> = + // Box::new(move |scope: &ScopeInner| { + // let props = props.downcast_ref::

().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 = 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> { - 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> { - self.scheduler.work_with_deadline(deadline) + self.work_with_deadline(deadline) } pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender { - 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, + ) -> 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> { + /* + 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::>::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> { + 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 { + 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(()), } } } From b8d98698c6056a330fe62f52833504fb1c728bbb Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 5 Nov 2021 17:15:59 -0400 Subject: [PATCH 02/16] wip: remove resource pool in favor of raw ptrs --- packages/core/src/lib.rs | 2 + packages/core/src/nodes.rs | 4 +- packages/core/src/scope.rs | 112 +++++++++++++------------------ packages/core/src/scopearena.rs | 76 +++++++++++++++++++++ packages/core/src/test_dom.rs | 15 +++-- packages/core/src/virtual_dom.rs | 31 +++++---- 6 files changed, 150 insertions(+), 90 deletions(-) create mode 100644 packages/core/src/scopearena.rs diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 41bd45ceb..d63b155ed 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -23,6 +23,7 @@ pub(crate) mod lazynodes; pub(crate) mod mutations; pub(crate) mod nodes; pub(crate) mod scope; +pub(crate) mod scopearena; pub(crate) mod test_dom; pub(crate) mod threadsafe; pub(crate) mod util; @@ -43,6 +44,7 @@ pub(crate) mod innerlude { pub use crate::mutations::*; pub use crate::nodes::*; pub use crate::scope::*; + pub use crate::scopearena::*; pub use crate::test_dom::*; pub use crate::threadsafe::*; pub use crate::util::*; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index fc7b7f76e..362eaa274 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -157,7 +157,7 @@ impl<'src> VNode<'src> { pub fn children(&self) -> &[VNode<'src>] { match &self { VNode::Fragment(f) => &f.children, - VNode::Component(c) => &c.children, + VNode::Component(c) => todo!("children are not accessible through this"), _ => &[], } } @@ -319,7 +319,7 @@ pub struct Listener<'bump> { pub struct VComponent<'src> { pub key: Option<&'src str>, - pub associated_scope: Cell>, + pub associated_scope: Cell>, // Function pointer to the FC that was used to generate this component pub user_fc: *const (), diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 27bffd90f..026a53e65 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -1,5 +1,6 @@ use crate::innerlude::*; +use futures_channel::mpsc::UnboundedSender; use fxhash::FxHashMap; use std::{ any::{Any, TypeId}, @@ -42,9 +43,13 @@ pub type Context<'a> = &'a ScopeInner; /// use case they might have. pub struct ScopeInner { // Book-keeping about our spot in the arena - pub(crate) parent_idx: Option, + // yes, a raw pointer + // it's bump allocated so it's stable + // the safety of parent pointers is guaranteed by the logic in this crate + pub(crate) parent_scope: Option<*mut ScopeInner>, pub(crate) our_arena_idx: ScopeId, pub(crate) height: u32, + pub(crate) subtree: Cell, pub(crate) is_subtree_root: Cell, @@ -67,7 +72,7 @@ pub struct ScopeInner { // todo: move this into a centralized place - is more memory efficient pub(crate) shared_contexts: RefCell>>, - pub(crate) shared: EventChannel, + pub(crate) sender: UnboundedSender, } pub struct SelfReferentialItems<'a> { @@ -78,47 +83,11 @@ pub struct SelfReferentialItems<'a> { pub(crate) pending_effects: Vec>, } +pub struct ScopeVcomp { + // important things +} + 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, - 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. /// @@ -131,7 +100,7 @@ impl ScopeInner { /// /// 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) { + pub(crate) fn ensure_drop_safety(&mut self) { // 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 @@ -147,13 +116,13 @@ impl ScopeInner { .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 scope = unsafe { &mut *scope_id }; - todo!("drop the component's props"); - // let mut drop_props = comp.drop_props.borrow_mut().take().unwrap(); - // drop_props(); - } + scope.ensure_drop_safety(); + + 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. @@ -204,7 +173,8 @@ impl ScopeInner { } // run the list of effects - pub(crate) fn run_effects(&mut self, pool: &ResourcePool) { + pub(crate) fn run_effects(&mut self) { + // pub(crate) fn run_effects(&mut self, pool: &ResourcePool) { for mut effect in self.items.get_mut().pending_effects.drain(..) { effect(); } @@ -213,11 +183,11 @@ impl ScopeInner { /// 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 { + pub(crate) fn run_scope<'sel>(&'sel mut self) -> 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); + self.ensure_drop_safety(); // Safety: // - We dropped the listeners, so no more &mut T can be used while these are held @@ -356,7 +326,10 @@ impl ScopeInner { /// assert_eq!(base.parent(), None); /// ``` pub fn parent(&self) -> Option { - self.parent_idx + match self.parent_scope { + Some(p) => Some(unsafe { &*p }.our_arena_idx), + None => None, + } } /// Get the ID of this Scope within this Dioxus VirtualDOM. @@ -381,7 +354,7 @@ impl ScopeInner { /// ## Notice: you should prefer using prepare_update and get_scope_id pub fn schedule_update(&self) -> Rc { // pub fn schedule_update(&self) -> Rc { - let chan = self.shared.sender.clone(); + let chan = self.sender.clone(); let id = self.scope_id(); Rc::new(move || { chan.unbounded_send(SchedulerMsg::Immediate(id)); @@ -394,7 +367,7 @@ impl ScopeInner { /// /// This method should be used when you want to schedule an update for a component pub fn schedule_update_any(&self) -> Rc { - let chan = self.shared.sender.clone(); + let chan = self.sender.clone(); Rc::new(move |id| { chan.unbounded_send(SchedulerMsg::Immediate(id)); }) @@ -411,8 +384,7 @@ impl ScopeInner { /// /// `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 + self.sender .unbounded_send(SchedulerMsg::Immediate(id)) .unwrap(); } @@ -508,7 +480,7 @@ impl ScopeInner { /// rsx!(cx, div { "hello {state.0}" }) /// } /// ``` - pub fn provide_state(self, value: T) + pub fn provide_state(&self, value: T) where T: 'static, { @@ -520,11 +492,21 @@ impl ScopeInner { } /// Try to retrieve a SharedState with type T from the any parent Scope. - pub fn consume_state(self) -> Option> { - let getter = &self.shared.get_shared_context; - let ty = TypeId::of::(); - let idx = self.our_arena_idx; - getter(idx, ty).map(|f| f.downcast().unwrap()) + pub fn consume_state(&self) -> Option> { + if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::()) { + Some(shared.clone().downcast::().unwrap()) + } else { + let mut search_parent = self.parent_scope; + + while let Some(parent_ptr) = search_parent { + let parent = unsafe { &*parent_ptr }; + if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::()) { + return Some(shared.clone().downcast::().unwrap()); + } + search_parent = parent.parent_scope; + } + None + } } /// Create a new subtree with this scope as the root of the subtree. @@ -542,7 +524,7 @@ impl ScopeInner { /// rsx!(cx, div { "Subtree {id}"}) /// }; /// ``` - pub fn create_subtree(self) -> Option { + pub fn create_subtree(&self) -> Option { self.new_subtree() } @@ -559,7 +541,7 @@ impl ScopeInner { /// rsx!(cx, div { "Subtree {id}"}) /// }; /// ``` - pub fn get_current_subtree(self) -> u32 { + pub fn get_current_subtree(&self) -> u32 { self.subtree() } diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs new file mode 100644 index 000000000..e202723c7 --- /dev/null +++ b/packages/core/src/scopearena.rs @@ -0,0 +1,76 @@ +use std::cell::{Cell, RefCell}; + +use bumpalo::{boxed::Box as BumpBox, Bump}; +use futures_channel::mpsc::UnboundedSender; + +use crate::innerlude::*; + +// a slab-like arena with stable references even when new scopes are allocated +// uses a bump arena as a backing +// +// has an internal heuristics engine to pre-allocate arenas to the right size +pub(crate) struct ScopeArena { + bump: Bump, + scopes: Vec<*mut ScopeInner>, + free_scopes: Vec, +} + +impl ScopeArena { + pub fn new() -> Self { + Self { + bump: Bump::new(), + scopes: Vec::new(), + free_scopes: Vec::new(), + } + } + + pub fn new_with_key( + &mut self, + fc_ptr: *const (), + vcomp: &VComponent, + parent_scope: Option<*mut ScopeInner>, + height: u32, + subtree: u32, + sender: UnboundedSender, + ) -> ScopeId { + if let Some(id) = self.free_scopes.pop() { + // have already called drop on it - the slot is still chillin tho + let scope = unsafe { &mut *self.scopes[id.0 as usize] }; + + todo!("override the scope contents"); + id + } else { + let id = ScopeId(self.scopes.len()); + + let vcomp = unsafe { std::mem::transmute(vcomp as *const VComponent) }; + + let new_scope = ScopeInner { + sender, + parent_scope, + our_arena_idx: id, + 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(), + }), + }; + + let stable = self.bump.alloc(new_scope); + self.scopes.push(stable); + id + } + } + + // scopes never get dropepd +} diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index 332a23402..206276efc 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -6,15 +6,16 @@ use crate::nodes::IntoVNode; pub struct TestDom { bump: Bump, - scheduler: Scheduler, + // scheduler: Scheduler, } impl TestDom { pub fn new() -> TestDom { let bump = Bump::new(); let (sender, receiver) = futures_channel::mpsc::unbounded::(); - let scheduler = Scheduler::new(sender, receiver, 10, 100); - TestDom { bump, scheduler } + todo!() + // let scheduler = Scheduler::new(sender, receiver, 10, 100); + // TestDom { bump, scheduler } } pub fn new_factory(&self) -> NodeFactory { @@ -32,7 +33,7 @@ impl TestDom { pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { let mutations = Mutations::new(); - let mut machine = DiffMachine::new(mutations, &self.scheduler.pool); + let mut machine = DiffMachine::new(mutations, todo!()); machine.stack.push(DiffInstruction::Diff { new, old }); machine.mutations } @@ -40,7 +41,7 @@ impl TestDom { pub fn create<'a>(&'a self, left: Option>) -> Mutations<'a> { let old = self.bump.alloc(self.render_direct(left)); - let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool); + let mut machine = DiffMachine::new(Mutations::new(), todo!()); machine.stack.create_node(old, MountType::Append); @@ -56,14 +57,14 @@ impl TestDom { ) -> (Mutations<'a>, Mutations<'a>) { let (old, new) = (self.render(left), self.render(right)); - let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool); + let mut machine = DiffMachine::new(Mutations::new(), todo!()); machine.stack.create_node(old, MountType::Append); machine.work(|| false); let create_edits = machine.mutations; - let mut machine = DiffMachine::new(Mutations::new(), &self.scheduler.pool); + let mut machine = DiffMachine::new(Mutations::new(), todo!()); machine.stack.push(DiffInstruction::Diff { old, new }); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index c0d3054a2..239474334 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -78,16 +78,7 @@ pub struct VirtualDom { // we need to keep the allocation around, but we don't necessarily use it _root_caller: Box, - // /// 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 scopes: ScopeArena, pub heuristics: FxHashMap, @@ -192,7 +183,17 @@ impl VirtualDom { sender: UnboundedSender, receiver: UnboundedReceiver, ) -> Self { - let mut component_arena = Bump::new(); + let mut scopes = ScopeArena::new(); + + let base_scope = scopes.new_with_key( + // + root as _, + boxed_comp.as_ref(), + None, + 0, + 0, + sender.clone(), + ); // let root_fc = Box::new(root); @@ -239,13 +240,11 @@ impl VirtualDom { // }); Self { - scheduler: todo!(), + scopes, base_scope: todo!(), root_fc: todo!(), root_props: todo!(), _root_caller: todo!(), - component_arena: todo!(), - free_components: todo!(), heuristics: todo!(), receiver, pending_garbage: todo!(), @@ -340,7 +339,7 @@ impl VirtualDom { /// apply_edits(edits); /// ``` pub fn rebuild(&mut self) -> Mutations { - self.rebuild(self.base_scope) + // self.rebuild(self.base_scope) } /// Compute a manual diff of the VirtualDOM between states. @@ -856,7 +855,7 @@ impl VirtualDom { /// 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 { + pub fn rebuild_inner(&mut self, base_scope: ScopeId) -> Mutations { let mut shared = self.clone(); let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared); From 95bd17e38fc936dcc9383d0ba8beac5ed64b41eb Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Fri, 5 Nov 2021 18:02:44 -0400 Subject: [PATCH 03/16] wip: more raw ptrs --- packages/core/README.md | 16 + packages/core/src/diff.rs | 7 +- packages/core/src/events.rs | 1125 ----------- packages/core/src/lib.rs | 6 +- packages/core/src/{ => old}/childiter.rs | 0 packages/core/src/{ => old}/scheduler.rs | 0 packages/core/src/{ => old}/threadsafe.rs | 0 packages/core/src/scope.rs | 52 - packages/core/src/scopearena.rs | 12 + packages/core/src/test_dom.rs | 8 +- packages/core/src/virtual_dom.rs | 249 ++- packages/html/Cargo.toml | 5 +- packages/html/src/attrval.rs | 33 - packages/html/src/elements.rs | 1122 +++++++++++ packages/html/src/events.rs | 1123 +++++++++++ packages/html/src/global_attributes.rs | 922 +++++++++ packages/html/src/lib.rs | 2190 +-------------------- 17 files changed, 3335 insertions(+), 3535 deletions(-) rename packages/core/src/{ => old}/childiter.rs (100%) rename packages/core/src/{ => old}/scheduler.rs (100%) rename packages/core/src/{ => old}/threadsafe.rs (100%) delete mode 100644 packages/html/src/attrval.rs create mode 100644 packages/html/src/elements.rs create mode 100644 packages/html/src/events.rs create mode 100644 packages/html/src/global_attributes.rs diff --git a/packages/core/README.md b/packages/core/README.md index f02ec114c..45059cd89 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -40,3 +40,19 @@ We have big goals for Dioxus. The final implementation must: - Be "live". Components should be able to be both server rendered and client rendered without needing frontend APIs. - Be modular. Components and hooks should be work anywhere without worrying about target platform. + + +## Safety + +Dioxus deals with arenas, lifetimes, asynchronous tasks, custom allocators, pinning, and a lot more foundational low-level work that is very difficult to implement with 0 unsafe. + +If you don't want to use a crate that uses unsafe, then this crate is not for you. + +however, we are always interested in decreasing the scope of the core VirtualDom to make it easier to review. + +We'd also be happy to welcome PRs that can eliminate unsafe code while still upholding the numerous variants required to execute certain features. + +There's a few invariants that are very important: + +- References to `ScopeInner` and `Props` passed into components are *always* valid for as long as the component exists. Even if the scope backing is resized to fit more scopes, the scope has to stay the same place in memory. + diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index f59d89490..f22e0d617 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -105,7 +105,6 @@ use DomEdit::*; /// Funnily enough, this stack machine's entire job is to create instructions for another stack machine to execute. It's /// stack machines all the way down! pub(crate) struct DiffMachine<'bump> { - pub vdom: &'bump ResourcePool, pub mutations: Mutations<'bump>, pub stack: DiffStack<'bump>, pub seen_scopes: FxHashSet, @@ -137,10 +136,9 @@ impl<'a> SavedDiffWork<'a> { std::mem::transmute(self) } - pub unsafe fn promote<'b>(self, vdom: &'b ResourcePool) -> DiffMachine<'b> { + pub unsafe fn promote<'b>(self) -> DiffMachine<'b> { let extended: SavedDiffWork<'b> = std::mem::transmute(self); DiffMachine { - vdom, cfg: DiffCfg::default(), mutations: extended.mutations, stack: extended.stack, @@ -150,12 +148,11 @@ impl<'a> SavedDiffWork<'a> { } impl<'bump> DiffMachine<'bump> { - pub(crate) fn new(mutations: Mutations<'bump>, shared: &'bump ResourcePool) -> Self { + pub(crate) fn new(mutations: Mutations<'bump>) -> Self { Self { mutations, cfg: DiffCfg::default(), stack: DiffStack::new(), - vdom: shared, seen_scopes: FxHashSet::default(), } } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 23d415562..70635aa2e 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -14,8 +14,6 @@ use std::{ fmt::Debug, }; -pub use on::*; - #[derive(Debug)] pub struct UserEvent { /// The originator of the event trigger @@ -84,1126 +82,3 @@ pub enum EventPriority { /// This is considered "idle" work or "background" work. Low = 0, } - -pub mod on { - use super::*; - macro_rules! event_directory { - ( $( - $( #[$attr:meta] )* - $wrapper:ident: [ - // $eventdata:ident($wrapper:ident): [ - $( - $( #[$method_attr:meta] )* - $name:ident - )* - ]; - )* ) => { - $( - $( - $(#[$method_attr])* - pub fn $name<'a, F>( - c: NodeFactory<'a>, - mut callback: F, - ) -> Listener<'a> - where F: FnMut($wrapper) + 'a - { - let bump = &c.bump(); - - // we can't allocate unsized in bumpalo's box, so we need to craft the box manually - // safety: this is essentially the same as calling Box::new() but manually - // The box is attached to the lifetime of the bumpalo allocator - let cb: &mut dyn FnMut(Box) = bump.alloc(move |evt: Box| { - let event = evt.downcast::<$wrapper>().unwrap(); - callback(*event) - }); - - let callback: BumpBox) + 'a> = unsafe { BumpBox::from_raw(cb) }; - - // ie oncopy - let event_name = stringify!($name); - - // ie copy - let shortname: &'static str = &event_name[2..]; - - Listener { - event: shortname, - mounted_node: Cell::new(None), - callback: RefCell::new(Some(callback)), - } - } - )* - )* - }; - } - - // The Dioxus Synthetic event system - // todo: move these into the html event system. dioxus accepts *any* event, so having these here doesn't make sense. - event_directory! { - ClipboardEvent: [ - /// Called when "copy" - oncopy - - /// oncut - oncut - - /// onpaste - onpaste - ]; - - CompositionEvent: [ - /// oncompositionend - oncompositionend - - /// oncompositionstart - oncompositionstart - - /// oncompositionupdate - oncompositionupdate - ]; - - KeyboardEvent: [ - /// onkeydown - onkeydown - - /// onkeypress - onkeypress - - /// onkeyup - onkeyup - ]; - - FocusEvent: [ - /// onfocus - onfocus - - /// onblur - onblur - ]; - - FormEvent: [ - /// onchange - onchange - - /// oninput handler - oninput - - /// oninvalid - oninvalid - - /// onreset - onreset - - /// onsubmit - onsubmit - ]; - - /// A synthetic event that wraps a web-style [`MouseEvent`](https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent) - /// - /// - /// The MouseEvent interface represents events that occur due to the user interacting with a pointing device (such as a mouse). - /// - /// ## Trait implementation: - /// ```rust - /// fn alt_key(&self) -> bool; - /// fn button(&self) -> i16; - /// fn buttons(&self) -> u16; - /// fn client_x(&self) -> i32; - /// fn client_y(&self) -> i32; - /// fn ctrl_key(&self) -> bool; - /// fn meta_key(&self) -> bool; - /// fn page_x(&self) -> i32; - /// fn page_y(&self) -> i32; - /// fn screen_x(&self) -> i32; - /// fn screen_y(&self) -> i32; - /// fn shift_key(&self) -> bool; - /// fn get_modifier_state(&self, key_code: &str) -> bool; - /// ``` - /// - /// ## Event Handlers - /// - [`onclick`] - /// - [`oncontextmenu`] - /// - [`ondoubleclick`] - /// - [`ondrag`] - /// - [`ondragend`] - /// - [`ondragenter`] - /// - [`ondragexit`] - /// - [`ondragleave`] - /// - [`ondragover`] - /// - [`ondragstart`] - /// - [`ondrop`] - /// - [`onmousedown`] - /// - [`onmouseenter`] - /// - [`onmouseleave`] - /// - [`onmousemove`] - /// - [`onmouseout`] - /// - [`onmouseover`] - /// - [`onmouseup`] - MouseEvent: [ - /// Execute a callback when a button is clicked. - /// - /// ## Description - /// - /// An element receives a click event when a pointing device button (such as a mouse's primary mouse button) - /// is both pressed and released while the pointer is located inside the element. - /// - /// - Bubbles: Yes - /// - Cancelable: Yes - /// - Interface: [`MouseEvent`] - /// - /// If the button is pressed on one element and the pointer is moved outside the element before the button - /// is released, the event is fired on the most specific ancestor element that contained both elements. - /// `click` fires after both the `mousedown` and `mouseup` events have fired, in that order. - /// - /// ## Example - /// ``` - /// rsx!( button { "click me", onclick: move |_| log::info!("Clicked!`") } ) - /// ``` - /// - /// ## Reference - /// - https://www.w3schools.com/tags/ev_onclick.asp - /// - https://developer.mozilla.org/en-US/docs/Web/API/Element/click_event - onclick - - /// oncontextmenu - oncontextmenu - - /// ondoubleclick - ondoubleclick - - /// ondrag - ondrag - - /// ondragend - ondragend - - /// ondragenter - ondragenter - - /// ondragexit - ondragexit - - /// ondragleave - ondragleave - - /// ondragover - ondragover - - /// ondragstart - ondragstart - - /// ondrop - ondrop - - /// onmousedown - onmousedown - - /// onmouseenter - onmouseenter - - /// onmouseleave - onmouseleave - - /// onmousemove - onmousemove - - /// onmouseout - onmouseout - - /// - onscroll - - /// onmouseover - /// - /// Triggered when the users's mouse hovers over an element. - onmouseover - - /// onmouseup - onmouseup - ]; - - PointerEvent: [ - /// pointerdown - onpointerdown - - /// pointermove - onpointermove - - /// pointerup - onpointerup - - /// pointercancel - onpointercancel - - /// gotpointercapture - ongotpointercapture - - /// lostpointercapture - onlostpointercapture - - /// pointerenter - onpointerenter - - /// pointerleave - onpointerleave - - /// pointerover - onpointerover - - /// pointerout - onpointerout - ]; - - SelectionEvent: [ - /// onselect - onselect - ]; - - TouchEvent: [ - /// ontouchcancel - ontouchcancel - - /// ontouchend - ontouchend - - /// ontouchmove - ontouchmove - - /// ontouchstart - ontouchstart - ]; - - WheelEvent: [ - /// - onwheel - ]; - - MediaEvent: [ - ///abort - onabort - - ///canplay - oncanplay - - ///canplaythrough - oncanplaythrough - - ///durationchange - ondurationchange - - ///emptied - onemptied - - ///encrypted - onencrypted - - ///ended - onended - - ///error - onerror - - ///loadeddata - onloadeddata - - ///loadedmetadata - onloadedmetadata - - ///loadstart - onloadstart - - ///pause - onpause - - ///play - onplay - - ///playing - onplaying - - ///progress - onprogress - - ///ratechange - onratechange - - ///seeked - onseeked - - ///seeking - onseeking - - ///stalled - onstalled - - ///suspend - onsuspend - - ///timeupdate - ontimeupdate - - ///volumechange - onvolumechange - - ///waiting - onwaiting - ]; - - AnimationEvent: [ - /// onanimationstart - onanimationstart - - /// onanimationend - onanimationend - - /// onanimationiteration - onanimationiteration - ]; - - TransitionEvent: [ - /// - ontransitionend - ]; - - ToggleEvent: [ - /// - ontoggle - ]; - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct ClipboardEvent( - // DOMDataTransfer clipboardData - ); - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct CompositionEvent { - pub data: String, - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct KeyboardEvent { - pub char_code: u32, - - /// Identify which "key" was entered. - /// - /// This is the best method to use for all languages. They key gets mapped to a String sequence which you can match on. - /// The key isn't an enum because there are just so many context-dependent keys. - /// - /// A full list on which keys to use is available at: - /// - /// - /// # Example - /// - /// ```rust - /// match event.key().as_str() { - /// "Esc" | "Escape" => {} - /// "ArrowDown" => {} - /// "ArrowLeft" => {} - /// _ => {} - /// } - /// ``` - /// - pub key: String, - - /// Get the key code as an enum Variant. - /// - /// This is intended for things like arrow keys, escape keys, function keys, and other non-international keys. - /// To match on unicode sequences, use the [`key`] method - this will return a string identifier instead of a limited enum. - /// - /// - /// ## Example - /// - /// ```rust - /// use dioxus::KeyCode; - /// match event.key_code() { - /// KeyCode::Escape => {} - /// KeyCode::LeftArrow => {} - /// KeyCode::RightArrow => {} - /// _ => {} - /// } - /// ``` - /// - pub key_code: KeyCode, - - /// Indicate if the `alt` modifier key was pressed during this keyboard event - pub alt_key: bool, - - /// Indicate if the `ctrl` modifier key was pressed during this keyboard event - pub ctrl_key: bool, - - /// Indicate if the `meta` modifier key was pressed during this keyboard event - pub meta_key: bool, - - /// Indicate if the `shift` modifier key was pressed during this keyboard event - pub shift_key: bool, - - pub locale: String, - - pub location: usize, - - pub repeat: bool, - - pub which: usize, - // get_modifier_state: bool, - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct FocusEvent {/* DOMEventInner: Send + SyncTarget relatedTarget */} - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct FormEvent { - pub value: String, - /* DOMEvent: Send + SyncTarget relatedTarget */ - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct MouseEvent { - pub alt_key: bool, - pub button: i16, - pub buttons: u16, - pub client_x: i32, - pub client_y: i32, - pub ctrl_key: bool, - pub meta_key: bool, - pub page_x: i32, - pub page_y: i32, - pub screen_x: i32, - pub screen_y: i32, - pub shift_key: bool, - // fn get_modifier_state(&self, key_code: &str) -> bool; - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct PointerEvent { - // Mouse only - pub alt_key: bool, - pub button: i16, - pub buttons: u16, - pub client_x: i32, - pub client_y: i32, - pub ctrl_key: bool, - pub meta_key: bool, - pub page_x: i32, - pub page_y: i32, - pub screen_x: i32, - pub screen_y: i32, - pub shift_key: bool, - pub pointer_id: i32, - pub width: i32, - pub height: i32, - pub pressure: f32, - pub tangential_pressure: f32, - pub tilt_x: i32, - pub tilt_y: i32, - pub twist: i32, - pub pointer_type: String, - pub is_primary: bool, - // pub get_modifier_state: bool, - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct SelectionEvent {} - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct TouchEvent { - pub alt_key: bool, - pub ctrl_key: bool, - pub meta_key: bool, - pub shift_key: bool, - // get_modifier_state: bool, - // changedTouches: DOMTouchList, - // targetTouches: DOMTouchList, - // touches: DOMTouchList, - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct WheelEvent { - pub delta_mode: u32, - pub delta_x: f64, - pub delta_y: f64, - pub delta_z: f64, - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct MediaEvent {} - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct ImageEvent { - pub load_error: bool, - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct AnimationEvent { - pub animation_name: String, - pub pseudo_element: String, - pub elapsed_time: f32, - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct TransitionEvent { - pub property_name: String, - pub pseudo_element: String, - pub elapsed_time: f32, - } - - #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] - #[derive(Debug)] - pub struct ToggleEvent {} -} - -#[cfg_attr( - feature = "serialize", - derive(serde_repr::Serialize_repr, serde_repr::Deserialize_repr) -)] -#[derive(Clone, Copy, Debug)] -#[repr(u8)] -pub enum KeyCode { - // That key has no keycode, = 0 - // break, = 3 - // backspace / delete, = 8 - // tab, = 9 - // clear, = 12 - // enter, = 13 - // shift, = 16 - // ctrl, = 17 - // alt, = 18 - // pause/break, = 19 - // caps lock, = 20 - // hangul, = 21 - // hanja, = 25 - // escape, = 27 - // conversion, = 28 - // non-conversion, = 29 - // spacebar, = 32 - // page up, = 33 - // page down, = 34 - // end, = 35 - // home, = 36 - // left arrow, = 37 - // up arrow, = 38 - // right arrow, = 39 - // down arrow, = 40 - // select, = 41 - // print, = 42 - // execute, = 43 - // Print Screen, = 44 - // insert, = 45 - // delete, = 46 - // help, = 47 - // 0, = 48 - // 1, = 49 - // 2, = 50 - // 3, = 51 - // 4, = 52 - // 5, = 53 - // 6, = 54 - // 7, = 55 - // 8, = 56 - // 9, = 57 - // :, = 58 - // semicolon (firefox), equals, = 59 - // <, = 60 - // equals (firefox), = 61 - // ß, = 63 - // @ (firefox), = 64 - // a, = 65 - // b, = 66 - // c, = 67 - // d, = 68 - // e, = 69 - // f, = 70 - // g, = 71 - // h, = 72 - // i, = 73 - // j, = 74 - // k, = 75 - // l, = 76 - // m, = 77 - // n, = 78 - // o, = 79 - // p, = 80 - // q, = 81 - // r, = 82 - // s, = 83 - // t, = 84 - // u, = 85 - // v, = 86 - // w, = 87 - // x, = 88 - // y, = 89 - // z, = 90 - // Windows Key / Left ⌘ / Chromebook Search key, = 91 - // right window key, = 92 - // Windows Menu / Right ⌘, = 93 - // sleep, = 95 - // numpad 0, = 96 - // numpad 1, = 97 - // numpad 2, = 98 - // numpad 3, = 99 - // numpad 4, = 100 - // numpad 5, = 101 - // numpad 6, = 102 - // numpad 7, = 103 - // numpad 8, = 104 - // numpad 9, = 105 - // multiply, = 106 - // add, = 107 - // numpad period (firefox), = 108 - // subtract, = 109 - // decimal point, = 110 - // divide, = 111 - // f1, = 112 - // f2, = 113 - // f3, = 114 - // f4, = 115 - // f5, = 116 - // f6, = 117 - // f7, = 118 - // f8, = 119 - // f9, = 120 - // f10, = 121 - // f11, = 122 - // f12, = 123 - // f13, = 124 - // f14, = 125 - // f15, = 126 - // f16, = 127 - // f17, = 128 - // f18, = 129 - // f19, = 130 - // f20, = 131 - // f21, = 132 - // f22, = 133 - // f23, = 134 - // f24, = 135 - // f25, = 136 - // f26, = 137 - // f27, = 138 - // f28, = 139 - // f29, = 140 - // f30, = 141 - // f31, = 142 - // f32, = 143 - // num lock, = 144 - // scroll lock, = 145 - // airplane mode, = 151 - // ^, = 160 - // !, = 161 - // ؛ (arabic semicolon), = 162 - // #, = 163 - // $, = 164 - // ù, = 165 - // page backward, = 166 - // page forward, = 167 - // refresh, = 168 - // closing paren (AZERTY), = 169 - // *, = 170 - // ~ + * key, = 171 - // home key, = 172 - // minus (firefox), mute/unmute, = 173 - // decrease volume level, = 174 - // increase volume level, = 175 - // next, = 176 - // previous, = 177 - // stop, = 178 - // play/pause, = 179 - // e-mail, = 180 - // mute/unmute (firefox), = 181 - // decrease volume level (firefox), = 182 - // increase volume level (firefox), = 183 - // semi-colon / ñ, = 186 - // equal sign, = 187 - // comma, = 188 - // dash, = 189 - // period, = 190 - // forward slash / ç, = 191 - // grave accent / ñ / æ / ö, = 192 - // ?, / or °, = 193 - // numpad period (chrome), = 194 - // open bracket, = 219 - // back slash, = 220 - // close bracket / å, = 221 - // single quote / ø / ä, = 222 - // `, = 223 - // left or right ⌘ key (firefox), = 224 - // altgr, = 225 - // < /git >, left back slash, = 226 - // GNOME Compose Key, = 230 - // ç, = 231 - // XF86Forward, = 233 - // XF86Back, = 234 - // non-conversion, = 235 - // alphanumeric, = 240 - // hiragana/katakana, = 242 - // half-width/full-width, = 243 - // kanji, = 244 - // unlock trackpad (Chrome/Edge), = 251 - // toggle touchpad, = 255 - NA = 0, - Break = 3, - Backspace = 8, - Tab = 9, - Clear = 12, - Enter = 13, - Shift = 16, - Ctrl = 17, - Alt = 18, - Pause = 19, - CapsLock = 20, - // hangul, = 21 - // hanja, = 25 - Escape = 27, - // conversion, = 28 - // non-conversion, = 29 - Space = 32, - PageUp = 33, - PageDown = 34, - End = 35, - Home = 36, - LeftArrow = 37, - UpArrow = 38, - RightArrow = 39, - DownArrow = 40, - // select, = 41 - // print, = 42 - // execute, = 43 - // Print Screen, = 44 - Insert = 45, - Delete = 46, - // help, = 47 - Num0 = 48, - Num1 = 49, - Num2 = 50, - Num3 = 51, - Num4 = 52, - Num5 = 53, - Num6 = 54, - Num7 = 55, - Num8 = 56, - Num9 = 57, - // :, = 58 - // semicolon (firefox), equals, = 59 - // <, = 60 - // equals (firefox), = 61 - // ß, = 63 - // @ (firefox), = 64 - A = 65, - B = 66, - C = 67, - D = 68, - E = 69, - F = 70, - G = 71, - H = 72, - I = 73, - J = 74, - K = 75, - L = 76, - M = 77, - N = 78, - O = 79, - P = 80, - Q = 81, - R = 82, - S = 83, - T = 84, - U = 85, - V = 86, - W = 87, - X = 88, - Y = 89, - Z = 90, - LeftWindow = 91, - RightWindow = 92, - SelectKey = 93, - Numpad0 = 96, - Numpad1 = 97, - Numpad2 = 98, - Numpad3 = 99, - Numpad4 = 100, - Numpad5 = 101, - Numpad6 = 102, - Numpad7 = 103, - Numpad8 = 104, - Numpad9 = 105, - Multiply = 106, - Add = 107, - Subtract = 109, - DecimalPoint = 110, - Divide = 111, - F1 = 112, - F2 = 113, - F3 = 114, - F4 = 115, - F5 = 116, - F6 = 117, - F7 = 118, - F8 = 119, - F9 = 120, - F10 = 121, - F11 = 122, - F12 = 123, - // f13, = 124 - // f14, = 125 - // f15, = 126 - // f16, = 127 - // f17, = 128 - // f18, = 129 - // f19, = 130 - // f20, = 131 - // f21, = 132 - // f22, = 133 - // f23, = 134 - // f24, = 135 - // f25, = 136 - // f26, = 137 - // f27, = 138 - // f28, = 139 - // f29, = 140 - // f30, = 141 - // f31, = 142 - // f32, = 143 - NumLock = 144, - ScrollLock = 145, - // airplane mode, = 151 - // ^, = 160 - // !, = 161 - // ؛ (arabic semicolon), = 162 - // #, = 163 - // $, = 164 - // ù, = 165 - // page backward, = 166 - // page forward, = 167 - // refresh, = 168 - // closing paren (AZERTY), = 169 - // *, = 170 - // ~ + * key, = 171 - // home key, = 172 - // minus (firefox), mute/unmute, = 173 - // decrease volume level, = 174 - // increase volume level, = 175 - // next, = 176 - // previous, = 177 - // stop, = 178 - // play/pause, = 179 - // e-mail, = 180 - // mute/unmute (firefox), = 181 - // decrease volume level (firefox), = 182 - // increase volume level (firefox), = 183 - Semicolon = 186, - EqualSign = 187, - Comma = 188, - Dash = 189, - Period = 190, - ForwardSlash = 191, - GraveAccent = 192, - // ?, / or °, = 193 - // numpad period (chrome), = 194 - OpenBracket = 219, - BackSlash = 220, - CloseBraket = 221, - SingleQuote = 222, - // `, = 223 - // left or right ⌘ key (firefox), = 224 - // altgr, = 225 - // < /git >, left back slash, = 226 - // GNOME Compose Key, = 230 - // ç, = 231 - // XF86Forward, = 233 - // XF86Back, = 234 - // non-conversion, = 235 - // alphanumeric, = 240 - // hiragana/katakana, = 242 - // half-width/full-width, = 243 - // kanji, = 244 - // unlock trackpad (Chrome/Edge), = 251 - // toggle touchpad, = 255 - #[cfg_attr(feature = "serialize", serde(other))] - Unknown, -} - -impl KeyCode { - pub fn from_raw_code(i: u8) -> Self { - use KeyCode::*; - match i { - 8 => Backspace, - 9 => Tab, - 13 => Enter, - 16 => Shift, - 17 => Ctrl, - 18 => Alt, - 19 => Pause, - 20 => CapsLock, - 27 => Escape, - 33 => PageUp, - 34 => PageDown, - 35 => End, - 36 => Home, - 37 => LeftArrow, - 38 => UpArrow, - 39 => RightArrow, - 40 => DownArrow, - 45 => Insert, - 46 => Delete, - 48 => Num0, - 49 => Num1, - 50 => Num2, - 51 => Num3, - 52 => Num4, - 53 => Num5, - 54 => Num6, - 55 => Num7, - 56 => Num8, - 57 => Num9, - 65 => A, - 66 => B, - 67 => C, - 68 => D, - 69 => E, - 70 => F, - 71 => G, - 72 => H, - 73 => I, - 74 => J, - 75 => K, - 76 => L, - 77 => M, - 78 => N, - 79 => O, - 80 => P, - 81 => Q, - 82 => R, - 83 => S, - 84 => T, - 85 => U, - 86 => V, - 87 => W, - 88 => X, - 89 => Y, - 90 => Z, - 91 => LeftWindow, - 92 => RightWindow, - 93 => SelectKey, - 96 => Numpad0, - 97 => Numpad1, - 98 => Numpad2, - 99 => Numpad3, - 100 => Numpad4, - 101 => Numpad5, - 102 => Numpad6, - 103 => Numpad7, - 104 => Numpad8, - 105 => Numpad9, - 106 => Multiply, - 107 => Add, - 109 => Subtract, - 110 => DecimalPoint, - 111 => Divide, - 112 => F1, - 113 => F2, - 114 => F3, - 115 => F4, - 116 => F5, - 117 => F6, - 118 => F7, - 119 => F8, - 120 => F9, - 121 => F10, - 122 => F11, - 123 => F12, - 144 => NumLock, - 145 => ScrollLock, - 186 => Semicolon, - 187 => EqualSign, - 188 => Comma, - 189 => Dash, - 190 => Period, - 191 => ForwardSlash, - 192 => GraveAccent, - 219 => OpenBracket, - 220 => BackSlash, - 221 => CloseBraket, - 222 => SingleQuote, - _ => Unknown, - } - } - - // get the raw code - pub fn raw_code(&self) -> u32 { - *self as u32 - } -} - -pub(crate) fn event_meta(event: &UserEvent) -> (bool, EventPriority) { - use EventPriority::*; - - match event.name { - // clipboard - "copy" | "cut" | "paste" => (true, Medium), - - // Composition - "compositionend" | "compositionstart" | "compositionupdate" => (true, Low), - - // Keyboard - "keydown" | "keypress" | "keyup" => (true, High), - - // Focus - "focus" | "blur" => (true, Low), - - // Form - "change" | "input" | "invalid" | "reset" | "submit" => (true, Medium), - - // Mouse - "click" | "contextmenu" | "doubleclick" | "drag" | "dragend" | "dragenter" | "dragexit" - | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown" | "mouseenter" - | "mouseleave" | "mouseout" | "mouseover" | "mouseup" => (true, High), - - "mousemove" => (false, Medium), - - // Pointer - "pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture" - | "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => { - (true, Medium) - } - - // Selection - "select" | "touchcancel" | "touchend" => (true, Medium), - - // Touch - "touchmove" | "touchstart" => (true, Medium), - - // Wheel - "scroll" | "wheel" => (false, Medium), - - // Media - "abort" | "canplay" | "canplaythrough" | "durationchange" | "emptied" | "encrypted" - | "ended" | "error" | "loadeddata" | "loadedmetadata" | "loadstart" | "pause" | "play" - | "playing" | "progress" | "ratechange" | "seeked" | "seeking" | "stalled" | "suspend" - | "timeupdate" | "volumechange" | "waiting" => (true, Medium), - - // Animation - "animationstart" | "animationend" | "animationiteration" => (true, Medium), - - // Transition - "transitionend" => (true, Medium), - - // Toggle - "toggle" => (true, Medium), - - _ => (true, Low), - } -} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index d63b155ed..322f0d48a 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -25,7 +25,6 @@ pub(crate) mod nodes; pub(crate) mod scope; pub(crate) mod scopearena; pub(crate) mod test_dom; -pub(crate) mod threadsafe; pub(crate) mod util; pub(crate) mod virtual_dom; @@ -46,7 +45,6 @@ pub(crate) mod innerlude { pub use crate::scope::*; pub use crate::scopearena::*; pub use crate::test_dom::*; - pub use crate::threadsafe::*; pub use crate::util::*; pub use crate::virtual_dom::*; @@ -56,8 +54,8 @@ pub(crate) mod innerlude { pub use crate::innerlude::{ Context, DioxusElement, DomEdit, Element, ElementId, EventPriority, LazyNodes, MountType, - Mutations, NodeFactory, Properties, ScopeChildren, ScopeId, TestDom, ThreadsafeVirtualDom, - UserEvent, VNode, VirtualDom, FC, + Mutations, NodeFactory, Properties, ScopeChildren, ScopeId, TestDom, UserEvent, VNode, + VirtualDom, FC, }; pub mod prelude { diff --git a/packages/core/src/childiter.rs b/packages/core/src/old/childiter.rs similarity index 100% rename from packages/core/src/childiter.rs rename to packages/core/src/old/childiter.rs diff --git a/packages/core/src/scheduler.rs b/packages/core/src/old/scheduler.rs similarity index 100% rename from packages/core/src/scheduler.rs rename to packages/core/src/old/scheduler.rs diff --git a/packages/core/src/threadsafe.rs b/packages/core/src/old/threadsafe.rs similarity index 100% rename from packages/core/src/threadsafe.rs rename to packages/core/src/old/threadsafe.rs diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 026a53e65..477032566 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -180,58 +180,6 @@ impl ScopeInner { } } - /// 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) -> 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(); - - // 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 { todo!() // if self.is_subtree_root.get() { diff --git a/packages/core/src/scopearena.rs b/packages/core/src/scopearena.rs index e202723c7..715e7ae3a 100644 --- a/packages/core/src/scopearena.rs +++ b/packages/core/src/scopearena.rs @@ -5,6 +5,14 @@ use futures_channel::mpsc::UnboundedSender; use crate::innerlude::*; +pub type FcSlot = *const (); +// pub heuristics: FxHashMap, + +pub struct Heuristic { + hook_arena_size: usize, + node_arena_size: usize, +} + // a slab-like arena with stable references even when new scopes are allocated // uses a bump arena as a backing // @@ -24,6 +32,10 @@ impl ScopeArena { } } + pub fn get_mut(&mut self, id: &ScopeId) -> Option<&mut ScopeInner> { + unsafe { Some(&mut *self.scopes[id.0]) } + } + pub fn new_with_key( &mut self, fc_ptr: *const (), diff --git a/packages/core/src/test_dom.rs b/packages/core/src/test_dom.rs index 206276efc..63a0e2cbb 100644 --- a/packages/core/src/test_dom.rs +++ b/packages/core/src/test_dom.rs @@ -33,7 +33,7 @@ impl TestDom { pub fn diff<'a>(&'a self, old: &'a VNode<'a>, new: &'a VNode<'a>) -> Mutations<'a> { let mutations = Mutations::new(); - let mut machine = DiffMachine::new(mutations, todo!()); + let mut machine = DiffMachine::new(mutations); machine.stack.push(DiffInstruction::Diff { new, old }); machine.mutations } @@ -41,7 +41,7 @@ impl TestDom { pub fn create<'a>(&'a self, left: Option>) -> Mutations<'a> { let old = self.bump.alloc(self.render_direct(left)); - let mut machine = DiffMachine::new(Mutations::new(), todo!()); + let mut machine = DiffMachine::new(Mutations::new()); machine.stack.create_node(old, MountType::Append); @@ -57,14 +57,14 @@ impl TestDom { ) -> (Mutations<'a>, Mutations<'a>) { let (old, new) = (self.render(left), self.render(right)); - let mut machine = DiffMachine::new(Mutations::new(), todo!()); + let mut machine = DiffMachine::new(Mutations::new()); machine.stack.create_node(old, MountType::Append); machine.work(|| false); let create_edits = machine.mutations; - let mut machine = DiffMachine::new(Mutations::new(), todo!()); + let mut machine = DiffMachine::new(Mutations::new()); machine.stack.push(DiffInstruction::Diff { old, new }); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 239474334..73102a37e 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -80,12 +80,8 @@ pub struct VirtualDom { pub scopes: ScopeArena, - pub heuristics: FxHashMap, - pub receiver: UnboundedReceiver, - - // Garbage stored - pub pending_garbage: FxHashSet, + pub sender: UnboundedSender, // Every component that has futures that need to be polled pub pending_futures: FxHashSet, @@ -94,10 +90,6 @@ pub struct VirtualDom { pub pending_immediates: VecDeque, - pub batched_events: VecDeque, - - pub garbage_scopes: HashSet, - pub dirty_scopes: IndexSet, pub saved_state: Option>, @@ -173,7 +165,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 + /// Launch the VirtualDom, but provide your own channel for receiving and sending messages into the scheduler /// /// 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. @@ -188,74 +180,36 @@ impl VirtualDom { let base_scope = scopes.new_with_key( // root as _, - boxed_comp.as_ref(), + todo!(), + // boxed_comp.as_ref(), None, 0, 0, sender.clone(), ); - // let root_fc = Box::new(root); - - // let root_props: Rc = Rc::new(root_props); - - // let props = root_props.clone(); - - // let mut root_caller: Box Element> = - // Box::new(move |scope: &ScopeInner| { - // let props = props.downcast_ref::

().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 _) }; - - // // 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 { scopes, - base_scope: todo!(), + base_scope, + receiver, + sender, + root_fc: todo!(), root_props: todo!(), _root_caller: 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!(), + + pending_futures: Default::default(), + dirty_scopes: Default::default(), + + saved_state: Some(SavedDiffWork { + mutations: Mutations::new(), + stack: DiffStack::new(), + seen_scopes: Default::default(), + }), + in_progress: false, } } @@ -264,12 +218,14 @@ 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.pool.get_scope(&self.base_scope).unwrap() + todo!() + // self.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.pool.get_scope(&id) + pub fn get_scope(&self, id: &ScopeId) -> Option<&ScopeInner> { + todo!() + // self.get_scope(&id) } /// Update the root props of this VirtualDOM. @@ -297,7 +253,7 @@ impl VirtualDom { 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.pool); + root_scope.ensure_drop_safety(); let mut root_props: Rc = Rc::new(root_props); @@ -339,6 +295,7 @@ impl VirtualDom { /// apply_edits(edits); /// ``` pub fn rebuild(&mut self) -> Mutations { + todo!() // self.rebuild(self.base_scope) } @@ -440,7 +397,7 @@ impl VirtualDom { } pub fn get_event_sender(&self) -> futures_channel::mpsc::UnboundedSender { - self.pool.channel.sender.clone() + self.sender.clone() } /// Waits for the scheduler to have work @@ -481,13 +438,6 @@ impl VirtualDom { } } -pub type FcSlot = *const (); - -pub struct Heuristic { - hook_arena_size: usize, - node_arena_size: usize, -} - /* Welcome to Dioxus's cooperative, priority-based scheduler. @@ -670,19 +620,15 @@ impl VirtualDom { 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 machine = unsafe { saved_state.promote() }; 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.retain(|id| self.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 = self.get_scope(a).unwrap().height; + let h2 = self.get_scope(b).unwrap().height; h1.cmp(&h2).reverse() }); @@ -692,15 +638,14 @@ impl VirtualDom { 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 }); - } + // if let Some(component) = self.get_scope_mut(&scopeid) { + if self.run_scope(&scopeid) { + 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 }); } + // } } } } @@ -856,19 +801,17 @@ impl VirtualDom { /// /// Typically used to kickstart the VirtualDOM after initialization. pub fn rebuild_inner(&mut self, base_scope: ScopeId) -> Mutations { - let mut shared = self.clone(); - let mut diff_machine = DiffMachine::new(Mutations::new(), &mut shared); + let mut diff_machine = DiffMachine::new(Mutations::new()); // 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) { + if self.run_scope(&base_scope) { diff_machine .stack .create_node(cur_component.frames.fin_head(), MountType::Append); @@ -889,14 +832,13 @@ impl VirtualDom { 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); + if self.run_scope(&base_scope) { + let mut diff_machine = DiffMachine::new(Mutations::new()); diff_machine.cfg.force_diff = true; diff_machine.diff_scope(base_scope); diff_machine.mutations @@ -904,36 +846,93 @@ impl VirtualDom { Mutations::new() } } -} -impl Future for VirtualDom { - type Output = (); + pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&mut ScopeInner> { + self.scopes.get_mut(id) + } - fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { - let mut all_pending = true; + pub fn run_scope(&mut self, id: &ScopeId) -> bool { + let scope = self + .get_scope_mut(id) + .expect("The base scope should never be moved"); - for fut in self.pending_futures.iter() { - let scope = self - .pool - .get_scope_mut(&fut) - .expect("Scope should never be moved"); + // 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 + scope.ensure_drop_safety(); - 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 => {} - } - } - } + // 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 { scope.hooks.reset() }; - match all_pending { - true => Poll::Pending, - false => Poll::Ready(()), + // Safety: + // - We've dropped all references to the wip bump frame + unsafe { scope.frames.reset_wip_frame() }; + + let items = scope.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 = scope.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(scope) { + let new_head = builder.into_vnode(NodeFactory { + bump: &scope.frames.wip_frame().bump, + }); + log::debug!("Render is successful"); + + // the user's component succeeded. We can safely cycle to the next frame + scope.frames.wip_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; + scope.frames.cycle_frame(); + + true + } else { + false } } } + +// impl<'a> Future for PollAllTasks<'a> { +// type Output = (); + +// fn poll(self: Pin<&mut Self>, cx: &mut std::task::Context<'_>) -> Poll { +// 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(()), +// } +// } +// } diff --git a/packages/html/Cargo.toml b/packages/html/Cargo.toml index 20ef160d4..b5d7e4993 100644 --- a/packages/html/Cargo.toml +++ b/packages/html/Cargo.toml @@ -9,7 +9,4 @@ description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Vir # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -dioxus-core = { path = "../core", version = "0.1.2" } - -[dev-dependencies] -scraper = "0.12.0" +dioxus-core = { path = "../core", version = "0.1.3" } diff --git a/packages/html/src/attrval.rs b/packages/html/src/attrval.rs deleted file mode 100644 index 9ac1f1373..000000000 --- a/packages/html/src/attrval.rs +++ /dev/null @@ -1,33 +0,0 @@ -//! This module is not included anywhere. -//! -//! It is a prototype for a system that supports non-string attribute values. - -trait AsAttributeValue: Sized { - fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a>; -} -enum AttributeValue<'a> { - Int(i32), - Float(f32), - Str(&'a str), - Bool(bool), -} -impl<'b> AsAttributeValue for Arguments<'b> { - fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> { - todo!() - } -} -impl AsAttributeValue for &'static str { - fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> { - todo!() - } -} -impl AsAttributeValue for f32 { - fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> { - todo!() - } -} -impl AsAttributeValue for i32 { - fn into_attribute_value<'a>(self, cx: NodeFactory<'a>) -> AttributeValue<'a> { - todo!() - } -} diff --git a/packages/html/src/elements.rs b/packages/html/src/elements.rs new file mode 100644 index 000000000..f27f11ba1 --- /dev/null +++ b/packages/html/src/elements.rs @@ -0,0 +1,1122 @@ +macro_rules! builder_constructors { + ( + $( + $(#[$attr:meta])* + $name:ident { + $( + $(#[$attr_method:meta])* + $fil:ident: $vil:ident, + )* + }; + )* + ) => { + $( + #[allow(non_camel_case_types)] + $(#[$attr])* + pub struct $name; + + impl DioxusElement for $name { + const TAG_NAME: &'static str = stringify!($name); + const NAME_SPACE: Option<&'static str> = None; + } + + impl GlobalAttributes for $name {} + + impl $name { + $( + $(#[$attr_method])* + pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr(stringify!($fil), val, None, false) + } + )* + } + )* + }; + + ( $( + $(#[$attr:meta])* + $name:ident <> $namespace:tt { + $($fil:ident: $vil:ident,)* + }; + )* ) => { + $( + #[allow(non_camel_case_types)] + $(#[$attr])* + pub struct $name; + + impl DioxusElement for $name { + const TAG_NAME: &'static str = stringify!($name); + const NAME_SPACE: Option<&'static str> = Some($namespace); + } + + impl SvgAttributes for $name {} + + impl $name { + $( + pub fn $fil<'a>(&self, cx: NodeFactory<'a>, val: Arguments) -> Attribute<'a> { + cx.attr(stringify!($fil), val, Some(stringify!($namespace)), false) + } + )* + } + )* + }; +} + +// Organized in the same order as +// https://developer.mozilla.org/en-US/docs/Web/HTML/Element +// +// Does not include obsolete elements. +// +// This namespace represents a collection of modern HTML-5 compatiable elements. +// +// This list does not include obsolete, deprecated, experimental, or poorly supported elements. +builder_constructors! { + // Document metadata + + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/base) + /// element. + /// + base { + href: Uri, + target: Target, + }; + + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/head) + /// element. + head {}; + + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/link) + /// element. + link { + // as: Mime, + crossorigin: CrossOrigin, + href: Uri, + hreflang: LanguageTag, + media: String, // FIXME media query + rel: LinkType, + sizes: String, // FIXME + title: String, // FIXME + r#type: Mime, + integrity: String, + }; + + /// Build a + /// [``](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/meta) + /// element. + meta { + charset: String, // FIXME IANA standard names + content: String, + http_equiv: HTTPEquiv, + name: Metadata, + }; + + /// Build a + /// [`