diff --git a/packages/core/src/component.rs b/packages/core/src/component.rs index 82818ab62..8a10c2083 100644 --- a/packages/core/src/component.rs +++ b/packages/core/src/component.rs @@ -138,6 +138,5 @@ impl EmptyBuilder { /// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern /// to initialize a component's props. pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Context<'a>, &'a T) -> Element) -> T::Builder { - // pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder { T::builder() } diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs index b066c9ac4..4c68aed42 100644 --- a/packages/core/src/diff.rs +++ b/packages/core/src/diff.rs @@ -124,32 +124,6 @@ impl<'bump> DiffState<'bump> { force_diff: false, } } - - pub fn try_remove(&self, id: &ScopeId) -> Option { - todo!() - } - - pub fn reserve_node(&self, node: &VNode) -> ElementId { - todo!() - // let mut els = self.nodes.borrow_mut(); - // let entry = els.vacant_entry(); - // let key = entry.key(); - // let id = ElementId(key); - // let node = node as *const _; - // let node = unsafe { std::mem::transmute(node) }; - // entry.insert(node); - // id - - // let nodes = self.nodes.borrow_mut(); - // let id = nodes.insert(()); - // let node_id = ElementId(id); - // node = Some(node_id); - // node_id - } - - pub fn collect_garbage(&self, id: ElementId) { - todo!() - } } impl<'bump> DiffState<'bump> { @@ -283,14 +257,14 @@ impl<'bump> DiffState<'bump> { } fn create_text_node(&mut self, vtext: &'bump VText<'bump>, node: &'bump VNode<'bump>) { - let real_id = self.reserve_node(node); + let real_id = self.scopes.reserve_node(node); self.mutations.create_text_node(vtext.text, real_id); vtext.dom_id.set(Some(real_id)); self.stack.add_child_count(1); } fn create_suspended_node(&mut self, suspended: &'bump VSuspended, node: &'bump VNode<'bump>) { - let real_id = self.reserve_node(node); + let real_id = self.scopes.reserve_node(node); self.mutations.create_placeholder(real_id); suspended.dom_id.set(Some(real_id)); @@ -300,7 +274,7 @@ impl<'bump> DiffState<'bump> { } fn create_anchor_node(&mut self, anchor: &'bump VAnchor, node: &'bump VNode<'bump>) { - let real_id = self.reserve_node(node); + let real_id = self.scopes.reserve_node(node); self.mutations.create_placeholder(real_id); anchor.dom_id.set(Some(real_id)); self.stack.add_child_count(1); @@ -317,7 +291,7 @@ impl<'bump> DiffState<'bump> { .. } = element; - let real_id = self.reserve_node(node); + let real_id = self.scopes.reserve_node(node); dom_id.set(Some(real_id)); @@ -1142,7 +1116,7 @@ impl<'bump> DiffState<'bump> { match node { VNode::Text(t) => { let id = t.dom_id.get().unwrap(); - self.collect_garbage(id); + self.scopes.collect_garbage(id); if gen_muts { self.mutations.remove(id.as_u64()); @@ -1150,7 +1124,7 @@ impl<'bump> DiffState<'bump> { } VNode::Suspended(s) => { let id = s.dom_id.get().unwrap(); - self.collect_garbage(id); + self.scopes.collect_garbage(id); if gen_muts { self.mutations.remove(id.as_u64()); @@ -1158,7 +1132,7 @@ impl<'bump> DiffState<'bump> { } VNode::Anchor(a) => { let id = a.dom_id.get().unwrap(); - self.collect_garbage(id); + self.scopes.collect_garbage(id); if gen_muts { self.mutations.remove(id.as_u64()); @@ -1189,7 +1163,7 @@ impl<'bump> DiffState<'bump> { self.remove_nodes(Some(root), gen_muts); log::debug!("Destroying scope {:?}", scope_id); - let mut s = self.try_remove(&scope_id).unwrap(); + let mut s = self.scopes.try_remove(&scope_id).unwrap(); s.hooks.clear_hooks(); } } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 6c9ef181e..8af6fd940 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -22,7 +22,6 @@ pub(crate) mod nodes; pub(crate) mod scope; pub(crate) mod scopearena; pub(crate) mod test_dom; -pub(crate) mod util; pub(crate) mod virtual_dom; pub(crate) mod innerlude { @@ -36,7 +35,6 @@ pub(crate) mod innerlude { pub use crate::scope::*; pub use crate::scopearena::*; pub use crate::test_dom::*; - pub use crate::util::*; pub use crate::virtual_dom::*; pub type Element = Option; diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index d0efc3257..1acfdb844 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::{ - innerlude::{empty_cell, Context, Element, ElementId, Properties, Scope, ScopeId}, + innerlude::{Context, Element, Properties, Scope, ScopeId}, lazynodes::LazyNodes, }; use bumpalo::{boxed::Box as BumpBox, Bump}; @@ -232,6 +232,28 @@ impl Debug for VNode<'_> { } } +/// An Element's unique identifier. +/// +/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is +/// unmounted, then the `ElementId` will be reused for a new component. +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ElementId(pub usize); +impl std::fmt::Display for ElementId { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.0) + } +} + +impl ElementId { + pub fn as_u64(self) -> u64 { + self.0 as u64 + } +} + +fn empty_cell() -> Cell> { + Cell::new(None) +} + /// A placeholder node only generated when Fragments don't have any children. pub struct VAnchor { pub dom_id: Cell>, diff --git a/packages/core/src/old/bumpframe.rs b/packages/core/src/old/bumpframe.rs deleted file mode 100644 index 1c1994e8d..000000000 --- a/packages/core/src/old/bumpframe.rs +++ /dev/null @@ -1,106 +0,0 @@ -use crate::innerlude::*; -use bumpalo::Bump; -use std::cell::Cell; - -pub(crate) struct ActiveFrame { - pub cur_generation: Cell, - - // The double-buffering situation that we will use - pub frames: [BumpFrame; 2], -} - -impl ActiveFrame { - pub fn new() -> Self { - let b1 = Bump::new(); - let b2 = Bump::new(); - - let frame_a = BumpFrame { - bump: b1, - generation: 0.into(), - }; - let frame_b = BumpFrame { - bump: b2, - generation: 0.into(), - }; - - Self { - frames: [frame_a, frame_b], - cur_generation: 0.into(), - } - } - - pub unsafe fn reset_wip_frame(&self) { - // todo: unsafecell or something - let bump = self.wip_frame() as *const _ as *mut BumpFrame; - let g = &mut *bump; - g.bump.reset(); - - // self.wip_frame_mut().bump.reset() - } - - /// The "work in progress frame" represents the frame that is currently being worked on. - pub fn wip_frame(&self) -> &BumpFrame { - match self.cur_generation.get() & 1 == 0 { - true => &self.frames[0], - false => &self.frames[1], - } - } - - pub fn wip_frame_mut(&mut self) -> &mut BumpFrame { - match self.cur_generation.get() & 1 == 0 { - true => &mut self.frames[0], - false => &mut self.frames[1], - } - } - - /// The finished frame represents the frame that has been "finished" and cannot be modified again - pub fn finished_frame(&self) -> &BumpFrame { - match self.cur_generation.get() & 1 == 1 { - true => &self.frames[0], - false => &self.frames[1], - } - } - - // /// Give out our self-referential item with our own borrowed lifetime - // pub fn fin_head<'b>(&'b self) -> &'b VNode<'b> { - // let cur_head = &self.finished_frame().head_node; - // unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) } - // } - - // /// Give out our self-referential item with our own borrowed lifetime - // pub fn wip_head<'b>(&'b self) -> &'b VNode<'b> { - // let cur_head = &self.wip_frame().head_node; - // unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) } - // } - - pub fn cycle_frame(&mut self) { - self.cur_generation.set(self.cur_generation.get() + 1); - } -} - -#[cfg(test)] -mod tests { - //! These tests are bad. I don't have a good way of properly testing the ActiveFrame stuff - use super::*; - - #[test] - fn test_bump_frame() { - let mut frames = ActiveFrame::new(); - - // just cycle a few times and make sure we get the right frames out - for _ in 0..5 { - let fin = frames.finished_frame(); - let wip = frames.wip_frame(); - assert_eq!(wip._name, "wip"); - assert_eq!(fin._name, "fin"); - frames.cycle_frame(); - - let fin = frames.finished_frame(); - let wip = frames.wip_frame(); - assert_eq!(wip._name, "fin"); - assert_eq!(fin._name, "wip"); - frames.cycle_frame(); - } - assert_eq!(frames.cur_generation.get(), 10); - } -} diff --git a/packages/core/src/old/childiter.rs b/packages/core/src/old/childiter.rs deleted file mode 100644 index b7b6a70ea..000000000 --- a/packages/core/src/old/childiter.rs +++ /dev/null @@ -1,93 +0,0 @@ -use crate::innerlude::*; - -/// This iterator iterates through a list of virtual children and only returns real children (Elements, Text, Anchors). -/// -/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like -/// "InsertBefore". -pub(crate) struct RealChildIterator<'a> { - scopes: &'a ResourcePool, - - // Heuristically we should never bleed into 4 completely nested fragments/components - // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane - // TODO: use const generics instead of the 4 estimation - stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 4]>, -} - -impl<'a> RealChildIterator<'a> { - pub fn new(starter: &'a VNode<'a>, scopes: &'a ResourcePool) -> Self { - Self { - scopes, - stack: smallvec::smallvec![(0, starter)], - } - } -} - -impl<'a> Iterator for RealChildIterator<'a> { - type Item = &'a VNode<'a>; - - fn next(&mut self) -> Option<&'a VNode<'a>> { - let mut should_pop = false; - let mut returned_node: Option<&'a VNode<'a>> = None; - let mut should_push = None; - - while returned_node.is_none() { - if let Some((count, node)) = self.stack.last_mut() { - match &node { - // We can only exit our looping when we get "real" nodes - // This includes fragments and components when they're empty (have a single root) - VNode::Element(_) | VNode::Text(_) | VNode::Suspended(_) | VNode::Anchor(_) => { - // We've recursed INTO an element/text - // We need to recurse *out* of it and move forward to the next - should_pop = true; - returned_node = Some(node); - } - - // If we get a fragment we push the next child - VNode::Fragment(frag) => { - let subcount = *count as usize; - - if frag.children.is_empty() { - should_pop = true; - returned_node = Some(node); - } - - if subcount >= frag.children.len() { - should_pop = true; - } else { - should_push = Some(&frag.children[subcount]); - } - } - - // For components, we load their root and push them onto the stack - VNode::Component(sc) => { - let scope = self - .scopes - .get_scope(&sc.associated_scope.get().unwrap()) - .unwrap(); - - // Simply swap the current node on the stack with the root of the component - *node = scope.frames.fin_head(); - } - } - } else { - // If there's no more items on the stack, we're done! - return None; - } - - if should_pop { - self.stack.pop(); - if let Some((id, _)) = self.stack.last_mut() { - *id += 1; - } - should_pop = false; - } - - if let Some(push) = should_push { - self.stack.push((0, push)); - should_push = None; - } - } - - returned_node - } -} diff --git a/packages/core/src/old/debug_dom.rs b/packages/core/src/old/debug_dom.rs deleted file mode 100644 index 0ffa4214a..000000000 --- a/packages/core/src/old/debug_dom.rs +++ /dev/null @@ -1,131 +0,0 @@ -use crate::{innerlude::ScopeState, virtual_dom::VirtualDom, VNode}; - -impl std::fmt::Display for VirtualDom { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - let base = self.base_scope(); - let root = base.root_node(); - - let renderer = ScopeRenderer { - show_fragments: false, - skip_components: false, - - _scope: base, - _pre_render: false, - _newline: true, - _indent: true, - _max_depth: usize::MAX, - }; - - renderer.render(self, root, f, 0) - } -} - -/// render the scope to a string using the rsx! syntax -pub(crate) struct ScopeRenderer<'a> { - pub skip_components: bool, - pub show_fragments: bool, - pub _scope: &'a ScopeState, - pub _pre_render: bool, - pub _newline: bool, - pub _indent: bool, - pub _max_depth: usize, -} - -// this is more or less a debug tool, but it'll render the entire tree to the terminal -impl<'a> ScopeRenderer<'a> { - pub fn render( - &self, - vdom: &VirtualDom, - node: &VNode, - f: &mut std::fmt::Formatter, - il: u16, - ) -> std::fmt::Result { - const INDENT: &str = " "; - let write_indent = |_f: &mut std::fmt::Formatter, le| { - for _ in 0..le { - write!(_f, "{}", INDENT).unwrap(); - } - }; - - match &node { - VNode::Linked(_) => { - write!(f, "Linked").unwrap(); - } - VNode::Text(text) => { - write_indent(f, il); - writeln!(f, "\"{}\"", text.text)? - } - VNode::Anchor(_anchor) => { - write_indent(f, il); - writeln!(f, "Anchor {{}}")?; - } - VNode::Element(el) => { - write_indent(f, il); - writeln!(f, "{} {{", el.tag_name)?; - // write!(f, "element: {}", el.tag_name)?; - let mut attr_iter = el.attributes.iter().peekable(); - - while let Some(attr) = attr_iter.next() { - match attr.namespace { - None => { - // - write_indent(f, il + 1); - writeln!(f, "{}: \"{}\"", attr.name, attr.value)? - } - - Some(ns) => { - // write the opening tag - write_indent(f, il + 1); - write!(f, " {}:\"", ns)?; - let mut cur_ns_el = attr; - 'ns_parse: loop { - write!(f, "{}:{};", cur_ns_el.name, cur_ns_el.value)?; - match attr_iter.peek() { - Some(next_attr) if next_attr.namespace == Some(ns) => { - cur_ns_el = attr_iter.next().unwrap(); - } - _ => break 'ns_parse, - } - } - // write the closing tag - write!(f, "\"")?; - } - } - } - - for child in el.children { - self.render(vdom, child, f, il + 1)?; - } - write_indent(f, il); - - writeln!(f, "}}")?; - } - VNode::Fragment(frag) => { - if self.show_fragments { - write_indent(f, il); - writeln!(f, "Fragment {{")?; - for child in frag.children { - self.render(vdom, child, f, il + 1)?; - } - write_indent(f, il); - writeln!(f, "}}")?; - } else { - for child in frag.children { - self.render(vdom, child, f, il)?; - } - } - } - VNode::Component(vcomp) => { - let idx = vcomp.associated_scope.get().unwrap(); - if !self.skip_components { - let new_node = vdom.get_scope(&idx).unwrap().root_node(); - self.render(vdom, new_node, f, il)?; - } - } - VNode::Suspended { .. } => { - // we can't do anything with suspended nodes - } - } - Ok(()) - } -} diff --git a/packages/core/src/old/events.rs b/packages/core/src/old/events.rs deleted file mode 100644 index 6038cbcb6..000000000 --- a/packages/core/src/old/events.rs +++ /dev/null @@ -1,15 +0,0 @@ -//! An event system that's less confusing than Traits + RC; -//! This should hopefully make it easier to port to other platforms. -//! -//! Unfortunately, it is less efficient than the original, but hopefully it's negligible. - -use crate::{ - innerlude::Listener, - innerlude::{ElementId, NodeFactory, ScopeId}, -}; -use bumpalo::boxed::Box as BumpBox; -use std::{ - any::Any, - cell::{Cell, RefCell}, - fmt::Debug, -}; diff --git a/packages/core/src/old/hooks.rs b/packages/core/src/old/hooks.rs deleted file mode 100644 index 349011dae..000000000 --- a/packages/core/src/old/hooks.rs +++ /dev/null @@ -1,187 +0,0 @@ -//! Built-in hooks -//! -//! This module contains all the low-level built-in hooks that require first party support to work. -//! -//! Hooks: -//! - [`use_hook`] -//! - [`use_state_provider`] -//! - [`use_state_consumer`] -//! - [`use_task`] -//! - [`use_suspense`] - -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, -// } - -// todo!() - -// // // 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 slot = task_dump.clone(); - -// // 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 -// // }))); - -// // 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. -/// -/// # Easda -/// -/// -/// -/// -/// # Example -/// -/// -pub fn use_suspense<'src, Out, Fut, Cb>( - cx: Context<'src>, - task_initializer: impl FnOnce() -> Fut, - user_callback: Cb, -) -> Element -where - Fut: Future, - Out: 'static, - Cb: FnMut(&Out) -> Element + 'src, -{ - /* - General strategy: - - Create a slot for the future to dump its output into - - Create a new future feeding off the user's future that feeds the output into that slot - - Submit that future as a task - - Take the task handle id and attach that to our suspended node - - when the hook runs, check if the value exists - - if it does, then we can render the node directly - - if it doesn't, then we render a suspended node along with with the callback and task id - */ - todo!() - // cx.use_hook( - // move |_| { - // let value = Rc::new(RefCell::new(None)); - // let slot = value.clone(); - // let originator = cx.scope.our_arena_idx; - - // let handle = cx.submit_task(Box::pin(task_initializer().then( - // move |output| async move { - // *slot.borrow_mut() = Some(Box::new(output) as Box); - // originator - // }, - // ))); - - // SuspenseHook { handle, value } - // }, - // move |hook| { - // // If the value exists, just run the callback to get the contents - // // if the value doesn't exist, we want to render a suspended node with an associated callback - // if let Some(value) = hook.value.borrow().as_ref() { - // let out = value.downcast_ref::().unwrap(); - // user_callback(out) - // } else { - // let value = hook.value.clone(); - - // let id = hook.handle.our_id; - - // let bump = cx.bump(); - - // use bumpalo::boxed::Box as BumpBox; - - // let f: &mut dyn FnMut() -> Element<'src> = bump.alloc(move || { - // let val = value.borrow(); - - // let out = val - // .as_ref() - // .unwrap() - // .as_ref() - // .downcast_ref::() - // .unwrap(); - - // user_callback(out) - // }); - // let callback = unsafe { BumpBox::from_raw(f) }; - - // Some(VNode::Suspended(bump.alloc(VSuspended { - // dom_id: empty_cell(), - // task_id: id, - // callback: RefCell::new(Some(callback)), - // }))) - // } - // }, - // |_| {}, - // ) -} - -// pub(crate) struct SuspenseHook { -// pub handle: TaskHandle, -// pub value: Rc>>>, -// } - -#[derive(Clone, Copy)] -pub struct NodeRef<'src, T: 'static>(&'src RefCell>); - -impl<'a, T> Deref for NodeRef<'a, T> { - type Target = RefCell>; - fn deref(&self) -> &Self::Target { - self.0 - } -} - -pub fn use_node_ref(cx: Context) -> NodeRef { - cx.use_hook(|_| RefCell::new(None), |f| NodeRef { 0: f }) -} diff --git a/packages/core/src/old/noderef.rs b/packages/core/src/old/noderef.rs deleted file mode 100644 index 16db8fe0d..000000000 --- a/packages/core/src/old/noderef.rs +++ /dev/null @@ -1,119 +0,0 @@ -// let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap(); - -// let mut garbage_list = scope.consume_garbage(); - -// let mut scopes_to_kill = Vec::new(); -// while let Some(node) = garbage_list.pop() { -// match &node.kind { -// VNodeKind::Text(_) => { -// self.shared.collect_garbage(node.direct_id()); -// } -// VNodeKind::Anchor(_) => { -// self.shared.collect_garbage(node.direct_id()); -// } -// VNodeKind::Suspended(_) => { -// self.shared.collect_garbage(node.direct_id()); -// } - -// VNodeKind::Element(el) => { -// self.shared.collect_garbage(node.direct_id()); -// for child in el.children { -// garbage_list.push(child); -// } -// } - -// VNodeKind::Fragment(frag) => { -// for child in frag.children { -// garbage_list.push(child); -// } -// } - -// VNodeKind::Component(comp) => { -// // TODO: run the hook destructors and then even delete the scope - -// let scope_id = comp.ass_scope.get().unwrap(); -// let scope = self.get_scope(scope_id).unwrap(); -// let root = scope.root(); -// garbage_list.push(root); -// scopes_to_kill.push(scope_id); -// } -// } -// } - -// for scope in scopes_to_kill { -// // oy kill em -// log::debug!("should be removing scope {:#?}", scope); -// } - -// // On the primary event queue, there is no batching, we take them off one-by-one -// let trigger = match receiver.try_next() { -// Ok(Some(trigger)) => trigger, -// _ => { -// // Continuously poll the future pool and the event receiver for work -// let mut tasks = self.shared.async_tasks.borrow_mut(); -// let tasks_tasks = tasks.next(); - -// // if the new event generates work more important than our current fiber, we should consider switching -// // only switch if it impacts different scopes. -// let mut ui_receiver = self.shared.ui_event_receiver.borrow_mut(); -// let ui_reciv_task = ui_receiver.next(); - -// // right now, this polling method will only catch batched set_states that don't get awaited. -// // However, in the future, we might be interested in batching set_states across await points -// let immediate_tasks = (); - -// futures_util::pin_mut!(tasks_tasks); -// futures_util::pin_mut!(ui_reciv_task); - -// // Poll the event receiver and the future pool for work -// // Abort early if our deadline has ran out -// let mut deadline = (&mut deadline_future).fuse(); - -// let trig = futures_util::select! { -// trigger = tasks_tasks => trigger, -// trigger = ui_reciv_task => trigger, - -// // abort if we're out of time -// _ = deadline => { return Ok(diff_machine.mutations); } -// }; - -// trig.unwrap() -// } -// }; - -// async fn select_next_event(&mut self) -> Option { -// let mut receiver = self.shared.task_receiver.borrow_mut(); - -// // drain the in-flight events so that we can sort them out with the current events -// while let Ok(Some(trigger)) = receiver.try_next() { -// log::info!("retrieving event from receiver"); -// let key = self.shared.make_trigger_key(&trigger); -// self.pending_events.insert(key, trigger); -// } - -// if self.pending_events.is_empty() { -// // Continuously poll the future pool and the event receiver for work -// let mut tasks = self.shared.async_tasks.borrow_mut(); -// let tasks_tasks = tasks.next(); - -// let mut receiver = self.shared.task_receiver.borrow_mut(); -// let reciv_task = receiver.next(); - -// futures_util::pin_mut!(tasks_tasks); -// futures_util::pin_mut!(reciv_task); - -// let trigger = match futures_util::future::select(tasks_tasks, reciv_task).await { -// futures_util::future::Either::Left((trigger, _)) => trigger, -// futures_util::future::Either::Right((trigger, _)) => trigger, -// } -// .unwrap(); -// let key = self.shared.make_trigger_key(&trigger); -// self.pending_events.insert(key, trigger); -// } - -// // pop the most important event off -// let key = self.pending_events.keys().next().unwrap().clone(); -// let trigger = self.pending_events.remove(&key).unwrap(); - -// Some(trigger) -// } diff --git a/packages/core/src/old/resources.rs b/packages/core/src/old/resources.rs deleted file mode 100644 index df931dcda..000000000 --- a/packages/core/src/old/resources.rs +++ /dev/null @@ -1,77 +0,0 @@ -use crate::innerlude::*; -use slab::Slab; - -use std::{cell::UnsafeCell, rc::Rc}; -#[derive(Clone)] -pub(crate) struct ResourcePool { - /* - This *has* to be an UnsafeCell. - - Each BumpFrame and Scope is located in this Slab - and we'll need mutable access to a scope while holding on to - its bumpframe contents immutably. - - However, all of the interaction with this Slab is done in this module and the Diff module, so it should be fairly - simple to audit. - - Wrapped in Rc so the "get_shared_context" closure can walk the tree (immutably!) - */ - pub components: Rc>>, - - /* - Yes, a slab of "nil". We use this for properly ordering ElementIDs - all we care about is the allocation strategy - that slab uses. The slab essentially just provides keys for ElementIDs that we can re-use in a Vec on the client. - - This just happened to be the simplest and most efficient way to implement a deterministic keyed map with slot reuse. - - In the future, we could actually store a pointer to the VNode instead of nil to provide O(1) lookup for VNodes... - */ - pub raw_elements: Rc>>>, - - pub channel: EventChannel, -} - -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> { - 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> { - let inner = unsafe { &mut *self.components.get() }; - inner.get_mut(idx.0) - } - - pub fn try_remove(&self, id: &ScopeId) -> Option { - let inner = unsafe { &mut *self.components.get() }; - Some(inner.remove(id.0)) - // .try_remove(id.0) - // .ok_or_else(|| Error::FatalInternal("Scope not found")) - } - - pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId { - let els = unsafe { &mut *self.raw_elements.get() }; - let entry = els.vacant_entry(); - let key = entry.key(); - let id = ElementId(key); - let node = node as *const _; - let node = unsafe { std::mem::transmute(node) }; - entry.insert(node); - id - } - - /// return the id, freeing the space of the original node - pub fn collect_garbage(&self, id: ElementId) { - let els = unsafe { &mut *self.raw_elements.get() }; - els.remove(id.0); - } - - pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> ScopeInner) -> ScopeId { - let g = unsafe { &mut *self.components.get() }; - let entry = g.vacant_entry(); - let id = ScopeId(entry.key()); - entry.insert(f(id)); - id - } -} diff --git a/packages/core/src/old/scheduler.rs b/packages/core/src/old/scheduler.rs deleted file mode 100644 index 21503fffd..000000000 --- a/packages/core/src/old/scheduler.rs +++ /dev/null @@ -1,597 +0,0 @@ -/* -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. -*/ - -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, -}; - -#[derive(Clone)] -pub(crate) struct EventChannel { - pub sender: UnboundedSender, - pub get_shared_context: GetSharedContext, -} - -pub type GetSharedContext = Rc Option>>; - -pub enum SchedulerMsg { - // events from the host - UiEvent(UserEvent), - - // setstate - Immediate(ScopeId), -} - -/// 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. -/// -/// -/// -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, - // - 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, - - // // scheduler stuff - // pub current_priority: EventPriority, - 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 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(component_capacity))); - let raw_elements = Rc::new(UnsafeCell::new(Slab::with_capacity(element_capacity))); - - let channel = EventChannel { - sender: sender.clone(), - get_shared_context: { - 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 saved_state = SavedDiffWork { - mutations: Mutations::new(), - stack: DiffStack::new(), - seen_scopes: Default::default(), - }; - - Self { - // pool, - receiver, - - pending_garbage: FxHashSet::default(), - - ui_events: VecDeque::new(), - - pending_immediates: VecDeque::new(), - - batched_events: VecDeque::new(), - - garbage_scopes: HashSet::new(), - - pending_futures: Default::default(), - dirty_scopes: Default::default(), - saved_state: Some(saved_state), - in_progress: false, - - heuristics: todo!(), - } - } - - // 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); - } - - 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); - } - } - } - - // 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), - } - } - } - } - } - - 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 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/old/threadsafe.rs b/packages/core/src/old/threadsafe.rs deleted file mode 100644 index 813c1cee4..000000000 --- a/packages/core/src/old/threadsafe.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! A threadsafe wrapper for the VirtualDom -//! -//! This is an experimental module, and must be explicitly opted-into. -//! -//! It's not guaranteed that this module produces safe results, so use at your own peril. -//! -//! The only real "right" answer to a Send VirtualDom is by ensuring all hook data is Send -//! -//! -use std::sync::{Arc, Mutex, MutexGuard}; - -use crate::VirtualDom; - -/// A threadsafe wrapper for the Dioxus VirtualDom. -/// -/// The Dioxus VirtualDom is not normally `Send` because user code can contain non-`Send` types. However, it is important -/// to have a VirtualDom that is `Send` when used in server-side code since very few web frameworks support non-send -/// handlers. -/// -/// To address this, we have the `ThreadsafeVirtualDom` type which is a threadsafe wrapper for the VirtualDom. To access -/// the VirtualDom, it must be first unlocked using the `lock` method. This locks the VirtualDom through a mutex and -/// prevents any user code from leaking out. It is not possible to acquire any non-`Send` types from inside the VirtualDom. -/// -/// The only way data may be accessed through the VirtualDom is from the "root props" method or by accessing a `Scope` -/// directly. Even then, it's not possible to access any hook data. This means that non-Send types are only "in play" -/// while the VirtualDom is locked with a non-Send marker. -/// -/// Calling "wait for work" on the ThreadsafeVirtualDom does indeed work, because this method only accesses `Send` types. -/// Otherwise, the VirtualDom must be unlocked on the current thread to modify any data. -/// -/// Dioxus does have the concept of local tasks and non-local tasks. -/// -/// For the ThreadsafeVirtualDom, non-Send tasks are not ran - and will error out during a Debug build if one is submitted. -/// -/// -/// -/// When Tasks are submitted to a thread-local executor, -/// -pub struct ThreadsafeVirtualDom { - inner: Arc>, -} - -impl ThreadsafeVirtualDom { - pub fn new(inner: VirtualDom) -> Self { - let inner = Arc::new(Mutex::new(inner)); - Self { inner } - } - - pub fn lock(&self) -> Option { - let locked = self.inner.lock().unwrap(); - Some(VirtualDomGuard { guard: locked }) - } -} - -unsafe impl Send for ThreadsafeVirtualDom {} - -pub struct VirtualDomGuard<'a> { - guard: MutexGuard<'a, VirtualDom>, -} - -impl<'a> std::ops::Deref for VirtualDomGuard<'a> { - type Target = MutexGuard<'a, VirtualDom>; - - fn deref(&self) -> &Self::Target { - &self.guard - } -} - -impl<'a> std::ops::DerefMut for VirtualDomGuard<'a> { - fn deref_mut(&mut self) -> &mut Self::Target { - &mut self.guard - } -} diff --git a/packages/core/src/scope.rs b/packages/core/src/scope.rs index 2e1cfd3ed..895b4ca99 100644 --- a/packages/core/src/scope.rs +++ b/packages/core/src/scope.rs @@ -94,6 +94,14 @@ pub struct SelfReferentialItems<'a> { pub(crate) pending_effects: Vec>, } +/// 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 +/// unmounted, then the `ScopeId` will be reused for a new component. +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] +pub struct ScopeId(pub usize); + // Public methods exposed to libraries and components impl Scope { /// Get the subtree ID that this scope belongs to. diff --git a/packages/core/src/util.rs b/packages/core/src/util.rs deleted file mode 100644 index b0da2c425..000000000 --- a/packages/core/src/util.rs +++ /dev/null @@ -1,34 +0,0 @@ -use std::cell::Cell; -use std::fmt::Display; - -// create a cell with a "none" value -#[inline] -pub fn empty_cell() -> Cell> { - Cell::new(None) -} - -/// 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 -/// unmounted, then the `ScopeId` will be reused for a new component. -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct ScopeId(pub usize); - -/// An Element's unique identifier. -/// -/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is -/// unmounted, then the `ElementId` will be reused for a new component. -#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)] -pub struct ElementId(pub usize); -impl Display for ElementId { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.0) - } -} - -impl ElementId { - pub fn as_u64(self) -> u64 { - self.0 as u64 - } -} diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 07f8e6220..bea4c2686 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -447,18 +447,13 @@ impl VirtualDom { let scope_id = self.base_scope; if self.scopes.run_scope(&scope_id) { - todo!(); - // diff_state - // .stack - // .create_node(self.scopes.fin_head(&scope_id), MountType::Append); + diff_state + .stack + .create_node(self.scopes.fin_head(&scope_id), MountType::Append); - // diff_state.stack.scope_stack.push(scope_id); + diff_state.stack.scope_stack.push(scope_id); let work_completed = diff_state.work(|| false); - // let scopes = &mut self.scopes; - // - // let work_completed = crate::diff::work(&mut diff_state, || false); - // self.scopes.work(&mut diff_machine, || false); } diff_state.mutations