diff --git a/packages/core/src/arbitrary_value.rs b/packages/core/src.old/arbitrary_value.rs
similarity index 100%
rename from packages/core/src/arbitrary_value.rs
rename to packages/core/src.old/arbitrary_value.rs
diff --git a/packages/core/src.old/diff.rs b/packages/core/src.old/diff.rs
new file mode 100644
index 000000000..e605345e1
--- /dev/null
+++ b/packages/core/src.old/diff.rs
@@ -0,0 +1,1246 @@
+#![warn(clippy::pedantic)]
+#![allow(clippy::cast_possible_truncation)]
+
+//! This module contains the stateful [`DiffState`] and all methods to diff [`VNode`]s, their properties, and their children.
+//!
+//! The [`DiffState`] calculates the diffs between the old and new frames, updates the new nodes, and generates a set
+//! of mutations for the renderer to apply.
+//!
+//! ## Notice:
+//!
+//! The inspiration and code for this module was originally taken from Dodrio (@fitzgen) and then modified to support
+//! Components, Fragments, Suspense, `SubTree` memoization, incremental diffing, cancellation, pausing, priority
+//! scheduling, and additional batching operations.
+//!
+//! ## Implementation Details:
+//!
+//! ### IDs for elements
+//! --------------------
+//! All nodes are addressed by their IDs.
+//! We don't necessarily require that DOM changes happen instantly during the diffing process, so the implementor may choose
+//! to batch nodes if it is more performant for their application. The element IDs are indices into the internal element
+//! array. The expectation is that implementors will use the ID as an index into a Vec of real nodes, allowing for passive
+//! garbage collection as the [`crate::VirtualDom`] replaces old nodes.
+//!
+//! When new vnodes are created through `cx.render`, they won't know which real node they correspond to. During diffing,
+//! we always make sure to copy over the ID. If we don't do this properly, the [`ElementId`] will be populated incorrectly
+//! and brick the user's page.
+//!
+//! ### Fragment Support
+//! --------------------
+//! Fragments (nodes without a parent) are supported through a combination of "replace with" and anchor vnodes. Fragments
+//! can be particularly challenging when they are empty, so the anchor node lets us "reserve" a spot for the empty
+//! fragment to be replaced with when it is no longer empty. This is guaranteed by logic in the [`crate::innerlude::NodeFactory`] - it is
+//! impossible to craft a fragment with 0 elements - they must always have at least a single placeholder element. Adding
+//! "dummy" nodes _is_ inefficient, but it makes our diffing algorithm faster and the implementation is completely up to
+//! the platform.
+//!
+//! Other implementations either don't support fragments or use a "child + sibling" pattern to represent them. Our code is
+//! vastly simpler and more performant when we can just create a placeholder element while the fragment has no children.
+//!
+//! ## Subtree Memoization
+//! -----------------------
+//! We also employ "subtree memoization" which saves us from having to check trees which hold no dynamic content. We can
+//! detect if a subtree is "static" by checking if its children are "static". Since we dive into the tree depth-first, the
+//! calls to "create" propagate this information upwards. Structures like the one below are entirely static:
+//! ```rust, ignore
+//! rsx!( div { class: "hello world", "this node is entirely static" } )
+//! ```
+//! Because the subtrees won't be diffed, their "real node" data will be stale (invalid), so it's up to the reconciler to
+//! track nodes created in a scope and clean up all relevant data. Support for this is currently WIP and depends on comp-time
+//! hashing of the subtree from the rsx! macro. We do a very limited form of static analysis via static string pointers as
+//! a way of short-circuiting the most expensive checks.
+//!
+//! ## Bloom Filter and Heuristics
+//! ------------------------------
+//! For all components, we employ some basic heuristics to speed up allocations and pre-size bump arenas. The heuristics are
+//! currently very rough, but will get better as time goes on. The information currently tracked includes the size of a
+//! bump arena after first render, the number of hooks, and the number of nodes in the tree.
+//!
+//! ## Garbage Collection
+//! ---------------------
+//! Dioxus uses a passive garbage collection system to clean up old nodes once the work has been completed. This garbage
+//! collection is done internally once the main diffing work is complete. After the "garbage" is collected, Dioxus will then
+//! start to re-use old keys for new nodes. This results in a passive memory management system that is very efficient.
+//!
+//! The IDs used by the key/map are just an index into a Vec. This means that Dioxus will drive the key allocation strategy
+//! so the client only needs to maintain a simple list of nodes. By default, Dioxus will not manually clean up old nodes
+//! for the client. As new nodes are created, old nodes will be over-written.
+//!
+//! ## Further Reading and Thoughts
+//! ----------------------------
+//! There are more ways of increasing diff performance here that are currently not implemented.
+//! - Strong memoization of subtrees.
+//! - Guided diffing.
+//! - Certain web-dom-specific optimizations.
+//!
+//! More info on how to improve this diffing algorithm:
+//! -
+
+use crate::{
+ innerlude::{
+ AnyProps, ElementId, Renderer, ScopeArena, ScopeId, TemplateNode, VComponent, VElement,
+ VFragment, VNode, VTemplate, VText,
+ },
+ AttributeValue,
+};
+use fxhash::{FxHashMap, FxHashSet};
+use smallvec::{smallvec, SmallVec};
+
+pub(crate) struct DiffState<'a, 'bump, R: Renderer<'bump>> {
+ pub(crate) scopes: &'bump ScopeArena,
+ pub(crate) mutations: &'a mut R,
+ pub(crate) force_diff: bool,
+ pub(crate) element_stack: SmallVec<[ElementId; 10]>,
+ pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
+}
+
+impl<'a, 'b, R: Renderer<'b>> DiffState<'a, 'b, R> {
+ pub fn new(scopes: &'b ScopeArena, renderer: &'a mut R) -> Self {
+ Self {
+ scopes,
+ mutations: renderer,
+ force_diff: false,
+ element_stack: smallvec![],
+ scope_stack: smallvec![],
+ }
+ }
+
+ pub fn diff_scope(&mut self, scopeid: ScopeId) {
+ let (old, new) = (self.scopes.wip_head(scopeid), self.scopes.fin_head(scopeid));
+ let scope = self.scopes.get_scope(scopeid).unwrap();
+
+ self.scope_stack.push(scopeid);
+ self.element_stack.push(scope.container);
+ self.diff_node(old, new);
+ self.element_stack.pop();
+ self.scope_stack.pop();
+
+ self.mutations.mark_dirty_scope(scopeid);
+ }
+
+ pub fn diff_node(&mut self, old_node: &'b VNode<'b>, new_node: &'b VNode<'b>) {
+ use VNode::{Component, Element, Fragment, Template, Text};
+
+ // Same node by ref, no need to diff.
+ if std::ptr::eq(old_node, new_node) {
+ return;
+ }
+
+ match (old_node, new_node) {
+ (Text(old), Text(new)) => self.diff_text(old, new, old_node, new_node),
+ (Element(old), Element(new)) => self.diff_element(old, new, old_node, new_node),
+ (Component(old), Component(new)) => self.diff_component(old_node, new_node, *old, *new),
+ (Fragment(old), Fragment(new)) => self.diff_fragment(old, new),
+ (Template(old), Template(new)) => self.diff_templates(old, new, old_node, new_node),
+
+ (
+ Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_),
+ Component(_) | Text(_) | Element(_) | Template(_) | Fragment(_),
+ ) => self.replace_node(old_node, new_node),
+ }
+ }
+
+ pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize {
+ match node {
+ VNode::Text(vtext) => self.create_text(vtext, node),
+ VNode::Element(element) => self.create_element(element, node),
+ VNode::Fragment(frag) => self.create_fragment(frag),
+ VNode::Component(component) => self.create_component_node(*component),
+ VNode::Template(template) => self.create_template_node(template, node),
+ }
+ }
+
+ fn create_text(&mut self, text: &'b VText<'b>, node: &'b VNode<'b>) -> usize {
+ let real_id = self.scopes.reserve_node(node);
+ text.id.set(Some(real_id));
+ self.mutations.create_text_node(text.text, real_id);
+ 1
+ }
+
+ fn create_element(&mut self, element: &'b VElement<'b>, node: &'b VNode<'b>) -> usize {
+ let VElement {
+ tag: tag_name,
+ listeners,
+ attributes,
+ children,
+ namespace,
+ id: dom_id,
+ parent: parent_id,
+ ..
+ } = &element;
+
+ parent_id.set(self.element_stack.last().copied());
+
+ let real_id = self.scopes.reserve_node(node);
+
+ dom_id.set(Some(real_id));
+
+ self.element_stack.push(real_id);
+ {
+ self.mutations.create_element(tag_name, *namespace, real_id);
+
+ let cur_scope_id = self.current_scope();
+
+ for listener in listeners.iter() {
+ listener.mounted_node.set(real_id);
+ self.mutations.new_event_listener(listener, cur_scope_id);
+ }
+
+ for attr in attributes.iter() {
+ self.mutations
+ .set_attribute(attr.name, attr.value, attr.namespace, real_id);
+ }
+
+ if !children.is_empty() {
+ self.create_and_append_children(children);
+ }
+ }
+ self.element_stack.pop();
+
+ 1
+ }
+
+ fn create_fragment(&mut self, frag: &'b VFragment<'b>) -> usize {
+ self.create_children(frag.children)
+ }
+
+ fn create_component_node(&mut self, vcomponent: &'b VComponent<'b>) -> usize {
+ let parent_idx = self.current_scope();
+
+ // the component might already exist - if it does, we need to reuse it
+ // this makes figure out when to drop the component more complicated
+ let new_idx = if let Some(idx) = vcomponent.scope.get() {
+ assert!(self.scopes.get_scope(idx).is_some());
+ idx
+ } else {
+ // Insert a new scope into our component list
+ let props: Box = vcomponent.props.borrow_mut().take().unwrap();
+ let props: Box = unsafe { std::mem::transmute(props) };
+ self.scopes.new_with_key(
+ vcomponent.user_fc,
+ props,
+ Some(parent_idx),
+ self.element_stack.last().copied().unwrap(),
+ 0,
+ )
+ };
+
+ // Actually initialize the caller's slot with the right address
+ vcomponent.scope.set(Some(new_idx));
+
+ log::trace!(
+ "created component \"{}\", id: {:?} parent {:?}",
+ vcomponent.fn_name,
+ new_idx,
+ parent_idx,
+ );
+
+ // if vcomponent.can_memoize {
+ // // todo: implement promotion logic. save us from boxing props that we don't need
+ // } else {
+ // // track this component internally so we know the right drop order
+ // }
+
+ self.enter_scope(new_idx);
+
+ let created = {
+ // Run the scope for one iteration to initialize it
+ self.scopes.run_scope(new_idx);
+ self.mutations.mark_dirty_scope(new_idx);
+
+ // Take the node that was just generated from running the component
+ let nextnode = self.scopes.fin_head(new_idx);
+ self.create_node(nextnode)
+ };
+
+ self.leave_scope();
+
+ created
+ }
+
+ pub(crate) fn diff_text(
+ &mut self,
+ old: &'b VText<'b>,
+ new: &'b VText<'b>,
+ _old_node: &'b VNode<'b>,
+ new_node: &'b VNode<'b>,
+ ) {
+ // if the node is comming back not assigned, that means it was borrowed but removed
+ let root = match old.id.get() {
+ Some(id) => id,
+ None => self.scopes.reserve_node(new_node),
+ };
+
+ if old.text != new.text {
+ self.mutations.set_text(new.text, root);
+ }
+
+ self.scopes.update_node(new_node, root);
+
+ new.id.set(Some(root));
+ }
+
+ fn diff_templates(
+ &mut self,
+ old: &'b VTemplate<'b>,
+ new: &'b VTemplate<'b>,
+ old_node: &'b VNode<'b>,
+ new_node: &'b VNode<'b>,
+ ) {
+ if old.template.id != new.template.id {
+ return self.replace_node(old_node, new_node);
+ }
+
+ // if they're the same, just diff the dynamic nodes directly
+ for (left, right) in old.dynamic_nodes.iter().zip(new.dynamic_nodes.iter()) {
+ self.diff_node(&left.node, &right.node);
+ }
+
+ for (left, right) in old.dynamic_attrs.iter().zip(new.dynamic_attrs.iter()) {
+ let id = left.mounted_element.get();
+ right.mounted_element.set(id);
+
+ for (left, right) in right.attrs.iter().zip(left.attrs.iter()) {
+ if right.value != left.value || right.volatile {
+ self.mutations
+ .set_attribute(right.name, right.value, right.namespace, id);
+ }
+ }
+
+ // There's not really any diffing that needs to happen for listeners
+ for listener in right.listeners {
+ listener.mounted_node.set(id);
+ }
+ }
+ }
+
+ fn create_static_template_nodes(&mut self, node: &'b TemplateNode, id: ElementId) {
+ match *node {
+ TemplateNode::Element {
+ tag,
+ attrs,
+ children,
+ namespace,
+ } => {
+ self.mutations.create_element(tag, namespace, id);
+ for attr in attrs {
+ self.mutations.set_attribute(
+ attr.name,
+ AttributeValue::Text(attr.value),
+ attr.namespace,
+ id,
+ );
+ }
+ for child in children.iter() {
+ self.create_static_template_nodes(child, id);
+ }
+ self.mutations.append_children(children.len() as u32);
+ }
+ TemplateNode::Text(text) => self.mutations.create_text_node(text, id),
+ TemplateNode::Dynamic(_) => self.mutations.create_placeholder(id),
+ }
+ }
+
+ /// Create the template from scratch using instructions, cache it, and then use the instructions to build it
+ ///
+ /// This would be way easier if the ID could just be unique *after* cloning
+ ///
+ /// If we traversed the template
+ fn create_template_node(&mut self, template: &'b VTemplate<'b>, node: &'b VNode<'b>) -> usize {
+ // Reserve a single node for all the template nodes to reuse
+ template.node_id.set(self.scopes.reserve_node(node));
+
+ // Save the template if it doesn't exist
+ // todo: use &mut cache instead of borrowed cache
+ let mut templates = self.scopes.template_cache.borrow_mut();
+ if !templates.contains(&template.template) {
+ template
+ .template
+ .roots
+ .into_iter()
+ .for_each(|node| self.create_static_template_nodes(node, template.node_id.get()));
+
+ self.mutations
+ .save(template.template.id, template.template.roots.len() as u32);
+
+ templates.insert(template.template);
+ }
+
+ // Walk the roots backwards, creating nodes and assigning IDs
+ let mut dynamic_attrs = template.dynamic_attrs.iter().peekable();
+ let mut dynamic_nodes = template.dynamic_nodes.iter().peekable();
+
+ let mut on_stack = 0;
+ for (root_idx, root) in template.template.roots.iter().enumerate() {
+ on_stack += match root {
+ TemplateNode::Dynamic(id) => self.create_node(&template.dynamic_nodes[*id].node),
+ TemplateNode::Element { .. } | TemplateNode::Text(_) => 1,
+ };
+
+ // we're on top of a node that has a dynamic attribute for a descndent
+ // Set that attribute now before the stack gets in a weird state
+ // Roots may not be more than 255 nodes long, enforced by the macro
+ while let Some(loc) = dynamic_attrs.next_if(|attr| attr.pathway[0] == root_idx as u8) {
+ // Attach all the elementIDs to the nodes with dynamic content
+ let id = self.scopes.reserve_node(node);
+ self.mutations.assign_id(&loc.pathway[1..], id);
+ loc.mounted_element.set(id);
+
+ for attr in loc.attrs {
+ self.mutations
+ .set_attribute(attr.name, attr.value, attr.namespace, id);
+ }
+
+ for listener in loc.listeners {
+ listener.mounted_node.set(id);
+ }
+ }
+
+ while let Some(dyn_node) = dynamic_nodes.next_if(|f| f.pathway[0] == root_idx as u8) {
+ // we're on top of a node that has a dynamic node for a descndent
+ // Set that node now
+ // Roots may not be more than 255 nodes long, enforced by the macro
+ if dyn_node.pathway[0] == root_idx as u8 {
+ let created = self.create_node(&dyn_node.node);
+
+ self.mutations
+ .replace_descendant(&dyn_node.pathway[1..], created as u32);
+ }
+ }
+ }
+
+ on_stack
+ }
+
+ fn diff_element(
+ &mut self,
+ old: &'b VElement<'b>,
+ new: &'b VElement<'b>,
+ old_node: &'b VNode<'b>,
+ new_node: &'b VNode<'b>,
+ ) {
+ // if the node is comming back not assigned, that means it was borrowed but removed
+ let root = match old.id.get() {
+ Some(id) => id,
+ None => self.scopes.reserve_node(new_node),
+ };
+
+ // If the element type is completely different, the element needs to be re-rendered completely
+ // This is an optimization React makes due to how users structure their code
+ //
+ // This case is rather rare (typically only in non-keyed lists)
+ if new.tag != old.tag || new.namespace != old.namespace {
+ self.replace_node(old_node, new_node);
+ return;
+ }
+
+ self.scopes.update_node(new_node, root);
+
+ new.id.set(Some(root));
+ new.parent.set(old.parent.get());
+
+ // todo: attributes currently rely on the element on top of the stack, but in theory, we only need the id of the
+ // element to modify its attributes.
+ // it would result in fewer instructions if we just set the id directly.
+ // it would also clean up this code some, but that's not very important anyways
+
+ // Diff Attributes
+ //
+ // It's extraordinarily rare to have the number/order of attributes change
+ // In these cases, we just completely erase the old set and make a new set
+ //
+ // TODO: take a more efficient path than this
+ if old.attributes.len() == new.attributes.len() {
+ for (old_attr, new_attr) in old.attributes.iter().zip(new.attributes.iter()) {
+ if old_attr.value != new_attr.value || new_attr.volatile {
+ self.mutations.set_attribute(
+ new_attr.name,
+ new_attr.value,
+ new_attr.namespace,
+ root,
+ );
+ }
+ }
+ } else {
+ for attribute in old.attributes {
+ self.mutations.remove_attribute(attribute, root);
+ }
+ for attribute in new.attributes {
+ self.mutations.set_attribute(
+ attribute.name,
+ attribute.value,
+ attribute.namespace,
+ root,
+ );
+ }
+ }
+
+ // Diff listeners
+ //
+ // It's extraordinarily rare to have the number/order of listeners change
+ // In the cases where the listeners change, we completely wipe the data attributes and add new ones
+ //
+ // We also need to make sure that all listeners are properly attached to the parent scope (fix_listener)
+ //
+ // TODO: take a more efficient path than this
+ let cur_scope_id = self.current_scope();
+
+ if old.listeners.len() == new.listeners.len() {
+ for (old_l, new_l) in old.listeners.iter().zip(new.listeners.iter()) {
+ new_l.mounted_node.set(old_l.mounted_node.get());
+ if old_l.event != new_l.event {
+ self.mutations.remove_event_listener(old_l.event, root);
+ self.mutations.new_event_listener(new_l, cur_scope_id);
+ }
+ }
+ } else {
+ for listener in old.listeners {
+ self.mutations.remove_event_listener(listener.event, root);
+ }
+ for listener in new.listeners {
+ listener.mounted_node.set(root);
+ self.mutations.new_event_listener(listener, cur_scope_id);
+ }
+ }
+
+ match (old.children.len(), new.children.len()) {
+ (0, 0) => {}
+ (0, _) => {
+ self.mutations.push_root(root);
+ let created = self.create_children(new.children);
+ self.mutations.append_children(created as u32);
+ self.mutations.pop_root();
+ }
+ (_, _) => self.diff_children(old.children, new.children),
+ };
+ }
+
+ fn diff_component(
+ &mut self,
+ old_node: &'b VNode<'b>,
+ new_node: &'b VNode<'b>,
+ old: &'b VComponent<'b>,
+ new: &'b VComponent<'b>,
+ ) {
+ let scope_addr = old
+ .scope
+ .get()
+ .expect("existing component nodes should have a scope");
+
+ // Make sure we're dealing with the same component (by function pointer)
+ if old.user_fc == new.user_fc {
+ self.enter_scope(scope_addr);
+ {
+ // Make sure the new component vnode is referencing the right scope id
+ new.scope.set(Some(scope_addr));
+
+ // make sure the component's caller function is up to date
+ let scope = self
+ .scopes
+ .get_scope(scope_addr)
+ .unwrap_or_else(|| panic!("could not find {:?}", scope_addr));
+
+ // take the new props out regardless
+ // when memoizing, push to the existing scope if memoization happens
+ let new_props = new
+ .props
+ .borrow_mut()
+ .take()
+ .expect("new component props should exist");
+
+ let should_diff = {
+ if old.can_memoize {
+ // safety: we trust the implementation of "memoize"
+ let props_are_the_same = unsafe {
+ let new_ref = new_props.as_ref();
+ scope.props.borrow().as_ref().unwrap().memoize(new_ref)
+ };
+ !props_are_the_same || self.force_diff
+ } else {
+ true
+ }
+ };
+
+ if should_diff {
+ let _old_props = scope
+ .props
+ .replace(unsafe { std::mem::transmute(Some(new_props)) });
+
+ // this should auto drop the previous props
+ self.scopes.run_scope(scope_addr);
+ self.mutations.mark_dirty_scope(scope_addr);
+
+ self.diff_node(
+ self.scopes.wip_head(scope_addr),
+ self.scopes.fin_head(scope_addr),
+ );
+ } else {
+ // memoization has taken place
+ drop(new_props);
+ };
+ }
+ self.leave_scope();
+ } else {
+ self.replace_node(old_node, new_node);
+ }
+ }
+
+ fn diff_fragment(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
+ todo!()
+ // // This is the case where options or direct vnodes might be used.
+ // // In this case, it's faster to just skip ahead to their diff
+ // if old.children.len() == 1 && new.children.len() == 1 {
+ // if !std::ptr::eq(old, new) {
+ // self.diff_node(&old.children[0], &new.children[0]);
+ // }
+ // return;
+ // }
+
+ // debug_assert!(!old.children.is_empty());
+ // debug_assert!(!new.children.is_empty());
+
+ // self.diff_children(old.children, new.children);
+ }
+
+ // Diff the given set of old and new children.
+ //
+ // The parent must be on top of the change list stack when this function is
+ // entered:
+ //
+ // [... parent]
+ //
+ // the change list stack is in the same state when this function returns.
+ //
+ // If old no anchors are provided, then it's assumed that we can freely append to the parent.
+ //
+ // Remember, non-empty lists does not mean that there are real elements, just that there are virtual elements.
+ //
+ // Fragment nodes cannot generate empty children lists, so we can assume that when a list is empty, it belongs only
+ // to an element, and appending makes sense.
+ fn diff_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+ if std::ptr::eq(old, new) {
+ return;
+ }
+
+ // Remember, fragments can never be empty (they always have a single child)
+ match (old, new) {
+ ([], []) => {}
+ ([], _) => self.create_and_append_children(new),
+ (_, []) => self.remove_nodes(old, true),
+ _ => {
+ let new_is_keyed = new[0].key().is_some();
+ let old_is_keyed = old[0].key().is_some();
+
+ debug_assert!(
+ new.iter().all(|n| n.key().is_some() == new_is_keyed),
+ "all siblings must be keyed or all siblings must be non-keyed"
+ );
+ debug_assert!(
+ old.iter().all(|o| o.key().is_some() == old_is_keyed),
+ "all siblings must be keyed or all siblings must be non-keyed"
+ );
+
+ if new_is_keyed && old_is_keyed {
+ self.diff_keyed_children(old, new);
+ } else {
+ self.diff_non_keyed_children(old, new);
+ }
+ }
+ }
+ }
+
+ // Diff children that are not keyed.
+ //
+ // The parent must be on the top of the change list stack when entering this
+ // function:
+ //
+ // [... parent]
+ //
+ // the change list stack is in the same state when this function returns.
+ fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+ use std::cmp::Ordering;
+
+ // Handled these cases in `diff_children` before calling this function.
+ debug_assert!(!new.is_empty());
+ debug_assert!(!old.is_empty());
+
+ match old.len().cmp(&new.len()) {
+ Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
+ Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
+ Ordering::Equal => {}
+ }
+
+ for (new, old) in new.iter().zip(old.iter()) {
+ self.diff_node(old, new);
+ }
+ }
+
+ // Diffing "keyed" children.
+ //
+ // With keyed children, we care about whether we delete, move, or create nodes
+ // versus mutate existing nodes in place. Presumably there is some sort of CSS
+ // transition animation that makes the virtual DOM diffing algorithm
+ // observable. By specifying keys for nodes, we know which virtual DOM nodes
+ // must reuse (or not reuse) the same physical DOM nodes.
+ //
+ // This is loosely based on Inferno's keyed patching implementation. However, we
+ // have to modify the algorithm since we are compiling the diff down into change
+ // list instructions that will be executed later, rather than applying the
+ // changes to the DOM directly as we compare virtual DOMs.
+ //
+ // https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
+ //
+ // The stack is empty upon entry.
+ fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+ if cfg!(debug_assertions) {
+ let mut keys = fxhash::FxHashSet::default();
+ let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
+ keys.clear();
+ for child in children {
+ let key = child.key();
+ debug_assert!(
+ key.is_some(),
+ "if any sibling is keyed, all siblings must be keyed"
+ );
+ keys.insert(key);
+ }
+ debug_assert_eq!(
+ children.len(),
+ keys.len(),
+ "keyed siblings must each have a unique key"
+ );
+ };
+ assert_unique_keys(old);
+ assert_unique_keys(new);
+ }
+
+ // First up, we diff all the nodes with the same key at the beginning of the
+ // children.
+ //
+ // `shared_prefix_count` is the count of how many nodes at the start of
+ // `new` and `old` share the same keys.
+ let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
+ Some(count) => count,
+ None => return,
+ };
+
+ // Ok, we now hopefully have a smaller range of children in the middle
+ // within which to re-order nodes with the same keys, remove old nodes with
+ // now-unused keys, and create new nodes with fresh keys.
+
+ let old_middle = &old[left_offset..(old.len() - right_offset)];
+ let new_middle = &new[left_offset..(new.len() - right_offset)];
+
+ debug_assert!(
+ !((old_middle.len() == new_middle.len()) && old_middle.is_empty()),
+ "keyed children must have the same number of children"
+ );
+
+ if new_middle.is_empty() {
+ // remove the old elements
+ self.remove_nodes(old_middle, true);
+ } else if old_middle.is_empty() {
+ // there were no old elements, so just create the new elements
+ // we need to find the right "foothold" though - we shouldn't use the "append" at all
+ if left_offset == 0 {
+ // insert at the beginning of the old list
+ let foothold = &old[old.len() - right_offset];
+ self.create_and_insert_before(new_middle, foothold);
+ } else if right_offset == 0 {
+ // insert at the end the old list
+ let foothold = old.last().unwrap();
+ self.create_and_insert_after(new_middle, foothold);
+ } else {
+ // inserting in the middle
+ let foothold = &old[left_offset - 1];
+ self.create_and_insert_after(new_middle, foothold);
+ }
+ } else {
+ self.diff_keyed_middle(old_middle, new_middle);
+ }
+ }
+
+ /// Diff both ends of the children that share keys.
+ ///
+ /// Returns a left offset and right offset of that indicates a smaller section to pass onto the middle diffing.
+ ///
+ /// If there is no offset, then this function returns None and the diffing is complete.
+ fn diff_keyed_ends(
+ &mut self,
+ old: &'b [VNode<'b>],
+ new: &'b [VNode<'b>],
+ ) -> Option<(usize, usize)> {
+ let mut left_offset = 0;
+
+ for (old, new) in old.iter().zip(new.iter()) {
+ // abort early if we finally run into nodes with different keys
+ if old.key() != new.key() {
+ break;
+ }
+ self.diff_node(old, new);
+ left_offset += 1;
+ }
+
+ // If that was all of the old children, then create and append the remaining
+ // new children and we're finished.
+ if left_offset == old.len() {
+ self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
+ return None;
+ }
+
+ // And if that was all of the new children, then remove all of the remaining
+ // old children and we're finished.
+ if left_offset == new.len() {
+ self.remove_nodes(&old[left_offset..], true);
+ return None;
+ }
+
+ // if the shared prefix is less than either length, then we need to walk backwards
+ let mut right_offset = 0;
+ for (old, new) in old.iter().rev().zip(new.iter().rev()) {
+ // abort early if we finally run into nodes with different keys
+ if old.key() != new.key() {
+ break;
+ }
+ self.diff_node(old, new);
+ right_offset += 1;
+ }
+
+ Some((left_offset, right_offset))
+ }
+
+ // The most-general, expensive code path for keyed children diffing.
+ //
+ // We find the longest subsequence within `old` of children that are relatively
+ // ordered the same way in `new` (via finding a longest-increasing-subsequence
+ // of the old child's index within `new`). The children that are elements of
+ // this subsequence will remain in place, minimizing the number of DOM moves we
+ // will have to do.
+ //
+ // Upon entry to this function, the change list stack must be empty.
+ //
+ // This function will load the appropriate nodes onto the stack and do diffing in place.
+ //
+ // Upon exit from this function, it will be restored to that same self.
+ #[allow(clippy::too_many_lines)]
+ fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+ /*
+ 1. Map the old keys into a numerical ordering based on indices.
+ 2. Create a map of old key to its index
+ 3. Map each new key to the old key, carrying over the old index.
+ - IE if we have ABCD becomes BACD, our sequence would be 1,0,2,3
+ - if we have ABCD to ABDE, our sequence would be 0,1,3,MAX because E doesn't exist
+
+ now, we should have a list of integers that indicates where in the old list the new items map to.
+
+ 4. Compute the LIS of this list
+ - this indicates the longest list of new children that won't need to be moved.
+
+ 5. Identify which nodes need to be removed
+ 6. Identify which nodes will need to be diffed
+
+ 7. Going along each item in the new list, create it and insert it before the next closest item in the LIS.
+ - if the item already existed, just move it to the right place.
+
+ 8. Finally, generate instructions to remove any old children.
+ 9. Generate instructions to finally diff children that are the same between both
+ */
+
+ // 0. Debug sanity checks
+ // Should have already diffed the shared-key prefixes and suffixes.
+ debug_assert_ne!(new.first().map(VNode::key), old.first().map(VNode::key));
+ debug_assert_ne!(new.last().map(VNode::key), old.last().map(VNode::key));
+
+ // 1. Map the old keys into a numerical ordering based on indices.
+ // 2. Create a map of old key to its index
+ // IE if the keys were A B C, then we would have (A, 1) (B, 2) (C, 3).
+ let old_key_to_old_index = old
+ .iter()
+ .enumerate()
+ .map(|(i, o)| (o.key().unwrap(), i))
+ .collect::>();
+
+ let mut shared_keys = FxHashSet::default();
+
+ // 3. Map each new key to the old key, carrying over the old index.
+ let new_index_to_old_index = new
+ .iter()
+ .map(|node| {
+ let key = node.key().unwrap();
+ if let Some(&index) = old_key_to_old_index.get(&key) {
+ shared_keys.insert(key);
+ index
+ } else {
+ u32::MAX as usize
+ }
+ })
+ .collect::>();
+
+ // If none of the old keys are reused by the new children, then we remove all the remaining old children and
+ // create the new children afresh.
+ if shared_keys.is_empty() {
+ if let Some(first_old) = old.get(0) {
+ self.remove_nodes(&old[1..], true);
+ let nodes_created = self.create_children(new);
+ self.replace_inner(first_old, nodes_created);
+ } else {
+ // I think this is wrong - why are we appending?
+ // only valid of the if there are no trailing elements
+ self.create_and_append_children(new);
+ }
+ return;
+ }
+
+ // remove any old children that are not shared
+ // todo: make this an iterator
+ for child in old {
+ let key = child.key().unwrap();
+ if !shared_keys.contains(&key) {
+ self.remove_nodes([child], true);
+ }
+ }
+
+ // 4. Compute the LIS of this list
+ let mut lis_sequence = Vec::default();
+ lis_sequence.reserve(new_index_to_old_index.len());
+
+ let mut predecessors = vec![0; new_index_to_old_index.len()];
+ let mut starts = vec![0; new_index_to_old_index.len()];
+
+ longest_increasing_subsequence::lis_with(
+ &new_index_to_old_index,
+ &mut lis_sequence,
+ |a, b| a < b,
+ &mut predecessors,
+ &mut starts,
+ );
+
+ // the lis comes out backwards, I think. can't quite tell.
+ lis_sequence.sort_unstable();
+
+ // if a new node gets u32 max and is at the end, then it might be part of our LIS (because u32 max is a valid LIS)
+ if lis_sequence.last().map(|f| new_index_to_old_index[*f]) == Some(u32::MAX as usize) {
+ lis_sequence.pop();
+ }
+
+ for idx in &lis_sequence {
+ self.diff_node(&old[new_index_to_old_index[*idx]], &new[*idx]);
+ }
+
+ let mut nodes_created = 0;
+
+ // add mount instruction for the first items not covered by the lis
+ let last = *lis_sequence.last().unwrap();
+ if last < (new.len() - 1) {
+ for (idx, new_node) in new[(last + 1)..].iter().enumerate() {
+ let new_idx = idx + last + 1;
+ let old_index = new_index_to_old_index[new_idx];
+ if old_index == u32::MAX as usize {
+ nodes_created += self.create_node(new_node);
+ } else {
+ self.diff_node(&old[old_index], new_node);
+ nodes_created += self.push_all_real_nodes(new_node);
+ }
+ }
+
+ self.mutations.insert_after(
+ self.find_last_element(&new[last]).unwrap(),
+ nodes_created as u32,
+ );
+ nodes_created = 0;
+ }
+
+ // for each spacing, generate a mount instruction
+ let mut lis_iter = lis_sequence.iter().rev();
+ let mut last = *lis_iter.next().unwrap();
+ for next in lis_iter {
+ if last - next > 1 {
+ for (idx, new_node) in new[(next + 1)..last].iter().enumerate() {
+ let new_idx = idx + next + 1;
+ let old_index = new_index_to_old_index[new_idx];
+ if old_index == u32::MAX as usize {
+ nodes_created += self.create_node(new_node);
+ } else {
+ self.diff_node(&old[old_index], new_node);
+ nodes_created += self.push_all_real_nodes(new_node);
+ }
+ }
+
+ self.mutations.insert_before(
+ self.find_first_element(&new[last]).unwrap(),
+ nodes_created as u32,
+ );
+
+ nodes_created = 0;
+ }
+ last = *next;
+ }
+
+ // add mount instruction for the last items not covered by the lis
+ let first_lis = *lis_sequence.first().unwrap();
+ if first_lis > 0 {
+ for (idx, new_node) in new[..first_lis].iter().enumerate() {
+ let old_index = new_index_to_old_index[idx];
+ if old_index == u32::MAX as usize {
+ nodes_created += self.create_node(new_node);
+ } else {
+ self.diff_node(&old[old_index], new_node);
+ nodes_created += self.push_all_real_nodes(new_node);
+ }
+ }
+
+ self.mutations.insert_before(
+ self.find_first_element(&new[first_lis]).unwrap(),
+ nodes_created as u32,
+ );
+ }
+ }
+
+ fn replace_node(&mut self, old: &'b VNode<'b>, new: &'b VNode<'b>) {
+ let nodes_created = self.create_node(new);
+ self.replace_inner(old, nodes_created);
+ }
+
+ fn replace_inner(&mut self, old: &'b VNode<'b>, nodes_created: usize) {
+ match old {
+ VNode::Element(el) => {
+ let id = old
+ .try_mounted_id()
+ .unwrap_or_else(|| panic!("broke on {:?}", old));
+
+ self.mutations.replace_with(id, nodes_created as u32);
+ self.remove_nodes(el.children, false);
+ self.scopes.collect_garbage(id);
+ }
+ VNode::Text(_) => {
+ let id = old
+ .try_mounted_id()
+ .unwrap_or_else(|| panic!("broke on {:?}", old));
+
+ self.mutations.replace_with(id, nodes_created as u32);
+ self.scopes.collect_garbage(id);
+ }
+ VNode::Fragment(f) => {
+ self.replace_inner(&f.children[0], nodes_created);
+ self.remove_nodes(f.children.iter().skip(1), true);
+ }
+ VNode::Component(c) => {
+ let scope_id = c.scope.get().unwrap();
+ let node = self.scopes.fin_head(scope_id);
+
+ self.enter_scope(scope_id);
+ {
+ self.replace_inner(node, nodes_created);
+ let scope = self.scopes.get_scope(scope_id).unwrap();
+ c.scope.set(None);
+ let props = scope.props.take().unwrap();
+ c.props.borrow_mut().replace(props);
+ self.scopes.try_remove(scope_id);
+ }
+
+ self.leave_scope();
+ }
+ VNode::Template(c) => {
+ todo!()
+ // // let ids = c.root_keys.as_slice_of_cells();
+
+ // self.mutations
+ // .replace_with(ids[0].get(), nodes_created as u32);
+ // self.scopes.collect_garbage(ids[0].get());
+
+ // for id in ids.iter().skip(1) {
+ // self.mutations.remove(id.get());
+ // self.scopes.collect_garbage(id.get());
+ // }
+ }
+ }
+ }
+
+ pub fn remove_nodes(&mut self, nodes: impl IntoIterator>, gen_muts: bool) {
+ for node in nodes {
+ match node {
+ VNode::Text(t) => {
+ // this check exists because our null node will be removed but does not have an ID
+ if let Some(id) = t.id.get() {
+ self.scopes.collect_garbage(id);
+ t.id.set(None);
+
+ if gen_muts {
+ self.mutations.remove(id);
+ }
+ }
+ }
+ // VNode::Placeholder(a) => {
+ // let id = a.id.get().unwrap();
+ // self.scopes.collect_garbage(id);
+ // a.id.set(None);
+
+ // if gen_muts {
+ // self.mutations.remove(id);
+ // }
+ // }
+ VNode::Element(e) => {
+ let id = e.id.get().unwrap();
+
+ if gen_muts {
+ self.mutations.remove(id);
+ }
+
+ self.scopes.collect_garbage(id);
+ e.id.set(None);
+
+ self.remove_nodes(e.children, false);
+ }
+
+ VNode::Fragment(f) => {
+ self.remove_nodes(f.children, gen_muts);
+ }
+ VNode::Component(c) => {
+ self.enter_scope(c.scope.get().unwrap());
+ {
+ let scope_id = c.scope.get().unwrap();
+ let root = self.scopes.root_node(scope_id);
+ self.remove_nodes([root], gen_muts);
+
+ let scope = self.scopes.get_scope(scope_id).unwrap();
+ c.scope.set(None);
+
+ let props = scope.props.take().unwrap();
+ c.props.borrow_mut().replace(props);
+ self.scopes.try_remove(scope_id);
+ }
+ self.leave_scope();
+ }
+
+ VNode::Template(c) => {}
+ }
+ }
+ }
+
+ fn create_children(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+ let mut created = 0;
+ for node in nodes {
+ created += self.create_node(node);
+ }
+ created
+ }
+
+ fn create_and_append_children(&mut self, nodes: &'b [VNode<'b>]) {
+ let created = self.create_children(nodes);
+ self.mutations.append_children(created as u32);
+ }
+
+ fn create_and_insert_after(&mut self, nodes: &'b [VNode<'b>], after: &'b VNode<'b>) {
+ let created = self.create_children(nodes);
+ let last = self.find_last_element(after).unwrap();
+ self.mutations.insert_after(last, created as u32);
+ }
+
+ fn create_and_insert_before(&mut self, nodes: &'b [VNode<'b>], before: &'b VNode<'b>) {
+ let created = self.create_children(nodes);
+ let first = self.find_first_element(before).unwrap();
+ self.mutations.insert_before(first, created as u32);
+ }
+
+ fn current_scope(&self) -> ScopeId {
+ self.scope_stack.last().copied().expect("no current scope")
+ }
+
+ fn enter_scope(&mut self, scope: ScopeId) {
+ self.scope_stack.push(scope);
+ }
+
+ fn leave_scope(&mut self) {
+ self.scope_stack.pop();
+ }
+
+ fn find_last_element(&self, vnode: &'b VNode<'b>) -> Option {
+ let mut search_node = Some(vnode);
+ loop {
+ match &search_node.take().unwrap() {
+ VNode::Text(t) => break t.id.get(),
+ VNode::Element(t) => break t.id.get(),
+ VNode::Fragment(frag) => search_node = frag.children.last(),
+ VNode::Component(el) => {
+ let scope_id = el.scope.get().unwrap();
+ search_node = Some(self.scopes.root_node(scope_id));
+ }
+ VNode::Template(template) => match &template.template.roots[0] {
+ TemplateNode::Text(_) | TemplateNode::Element { .. } => {
+ break Some(template.root_ids.last().unwrap().get());
+ }
+ TemplateNode::Dynamic(el) => {
+ search_node = Some(&template.dynamic_nodes[*el].node)
+ }
+ },
+ }
+ }
+ }
+
+ fn find_first_element(&self, vnode: &'b VNode<'b>) -> Option {
+ let mut search_node = Some(vnode);
+ loop {
+ match &search_node.take().expect("search node to have an ID") {
+ VNode::Text(t) => break t.id.get(),
+ VNode::Element(t) => break t.id.get(),
+ VNode::Fragment(frag) => search_node = Some(&frag.children[0]),
+ VNode::Component(el) => {
+ let scope = el.scope.get().expect("element to have a scope assigned");
+ search_node = Some(self.scopes.root_node(scope));
+ }
+ VNode::Template(template) => match &template.template.roots[0] {
+ TemplateNode::Text(_) | TemplateNode::Element { .. } => {
+ break Some(template.root_ids[0].get());
+ }
+ TemplateNode::Dynamic(el) => {
+ search_node = Some(&template.dynamic_nodes[*el].node)
+ }
+ },
+ }
+ }
+ }
+
+ // recursively push all the nodes of a tree onto the stack and return how many are there
+ fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
+ match node {
+ VNode::Text(_) | VNode::Element(_) => {
+ self.mutations.push_root(node.mounted_id());
+ 1
+ }
+
+ VNode::Fragment(frag) => {
+ let mut added = 0;
+ for child in frag.children {
+ added += self.push_all_real_nodes(child);
+ }
+ added
+ }
+ VNode::Component(c) => {
+ let scope_id = c.scope.get().unwrap();
+ let root = self.scopes.root_node(scope_id);
+ self.push_all_real_nodes(root)
+ }
+
+ VNode::Template(template) => {
+ let mut added = 0;
+ for (idx, root) in template.template.roots.iter().enumerate() {
+ match root {
+ TemplateNode::Text(_) | TemplateNode::Element { .. } => {
+ self.mutations.push_root(template.root_ids[idx].get());
+ added += 1;
+ }
+ TemplateNode::Dynamic(did) => {
+ added += self.push_all_real_nodes(&template.dynamic_nodes[*did].node);
+ }
+ }
+ }
+ added
+ }
+ }
+ }
+
+ pub(crate) fn diff_placeholder(&self, old_node: &VNode, new_node: &VNode) {
+ todo!()
+ }
+}
diff --git a/packages/core/src/events.rs b/packages/core/src.old/events.rs
similarity index 100%
rename from packages/core/src/events.rs
rename to packages/core/src.old/events.rs
diff --git a/packages/core/src/lazynodes.rs b/packages/core/src.old/lazynodes.rs
similarity index 100%
rename from packages/core/src/lazynodes.rs
rename to packages/core/src.old/lazynodes.rs
diff --git a/packages/core/src.old/lib.rs b/packages/core/src.old/lib.rs
new file mode 100644
index 000000000..c909e11f7
--- /dev/null
+++ b/packages/core/src.old/lib.rs
@@ -0,0 +1,131 @@
+#![allow(non_snake_case)]
+#![doc = include_str!("../README.md")]
+#![warn(missing_docs)]
+
+pub(crate) mod diff;
+pub(crate) mod events;
+pub(crate) mod lazynodes;
+pub(crate) mod mutations;
+pub(crate) mod nodes;
+pub(crate) mod properties;
+pub(crate) mod scopes;
+pub(crate) mod virtual_dom;
+
+pub(crate) mod innerlude {
+ pub use crate::events::*;
+ pub use crate::lazynodes::*;
+ pub use crate::mutations::*;
+ pub use crate::nodes::*;
+ pub use crate::properties::*;
+ pub use crate::scopes::*;
+ pub use crate::virtual_dom::*;
+
+ /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
+ ///
+ /// Any [`None`] [`Element`] will automatically be coerced into a placeholder [`VNode`] with the [`VNode::Placeholder`] variant.
+ pub type Element<'a> = Option>;
+
+ /// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
+ ///
+ /// Components can be used in other components with two syntax options:
+ /// - lowercase as a function call with named arguments (rust style)
+ /// - uppercase as an element (react style)
+ ///
+ /// ## Rust-Style
+ ///
+ /// ```rust, ignore
+ /// fn example(cx: Scope) -> Element {
+ /// // ...
+ /// }
+ ///
+ /// rsx!(
+ /// example()
+ /// )
+ /// ```
+ /// ## React-Style
+ /// ```rust, ignore
+ /// fn Example(cx: Scope) -> Element {
+ /// // ...
+ /// }
+ ///
+ /// rsx!(
+ /// Example {}
+ /// )
+ /// ```
+ pub type Component
= fn(Scope
) -> Element;
+
+ /// A list of attributes
+ pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
+}
+
+pub use crate::innerlude::{
+ AnyAttributeValue, AnyEvent, Attribute, AttributeValue, Component, Element, ElementId,
+ EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, NodeFactory, Properties, Renderer,
+ SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, Template, TemplateAttribute, TemplateNode,
+ UiEvent, UserEvent, VComponent, VElement, VNode, VTemplate, VText, VirtualDom,
+};
+
+/// The purpose of this module is to alleviate imports of many common types
+///
+/// This includes types like [`Scope`], [`Element`], and [`Component`].
+pub mod prelude {
+ pub use crate::innerlude::{
+ fc_to_builder, Attributes, Component, Element, EventHandler, LazyNodes, NodeFactory,
+ Properties, Scope, ScopeId, ScopeState, Template, VNode, VirtualDom,
+ };
+}
+
+pub mod exports {
+ //! Important dependencies that are used by the rest of the library
+ //! Feel free to just add the dependencies in your own Crates.toml
+ pub use bumpalo;
+ pub use futures_channel;
+}
+
+/// Functions that wrap unsafe functionality to prevent us from misusing it at the callsite
+pub(crate) mod unsafe_utils {
+ use crate::VNode;
+
+ pub(crate) unsafe fn extend_vnode<'a, 'b>(node: &'a VNode<'a>) -> &'b VNode<'b> {
+ std::mem::transmute(node)
+ }
+}
+
+#[macro_export]
+/// A helper macro for using hooks in async environements.
+///
+/// # Usage
+///
+///
+/// ```ignore
+/// let (data) = use_ref(&cx, || {});
+///
+/// let handle_thing = move |_| {
+/// to_owned![data]
+/// cx.spawn(async move {
+/// // do stuff
+/// });
+/// }
+/// ```
+macro_rules! to_owned {
+ ($($es:ident),+) => {$(
+ #[allow(unused_mut)]
+ let mut $es = $es.to_owned();
+ )*}
+}
+
+/// get the code location of the code that called this function
+#[macro_export]
+macro_rules! get_line_num {
+ () => {
+ concat!(
+ file!(),
+ ":",
+ line!(),
+ ":",
+ column!(),
+ ":",
+ env!("CARGO_MANIFEST_DIR")
+ )
+ };
+}
diff --git a/packages/core/src.old/mutations.rs b/packages/core/src.old/mutations.rs
new file mode 100644
index 000000000..1bd88ad65
--- /dev/null
+++ b/packages/core/src.old/mutations.rs
@@ -0,0 +1,98 @@
+//! Instructions returned by the VirtualDOM on how to modify the Real DOM.
+//!
+//! This module contains an internal API to generate these instructions.
+//!
+//! Beware that changing code in this module will break compatibility with
+//! interpreters for these types of DomEdits.
+
+use crate::innerlude::*;
+
+/// A renderer for Dioxus to modify during diffing
+///
+/// The renderer should implement a Stack Machine. IE each call to the below methods are modifications to the renderer's
+/// internal stack for creating and modifying nodes.
+///
+/// Dioxus guarantees that the stack is always in a valid state.
+pub trait Renderer<'a> {
+ /// Load this element onto the stack
+ fn push_root(&mut self, root: ElementId);
+ /// Pop the topmost element from the stack
+ fn pop_root(&mut self);
+ /// Replace the given element with the next m elements on the stack
+ fn replace_with(&mut self, root: ElementId, m: u32);
+
+ /// Insert the next m elements on the stack after the given element
+ fn insert_after(&mut self, root: ElementId, n: u32);
+ /// Insert the next m elements on the stack before the given element
+ fn insert_before(&mut self, root: ElementId, n: u32);
+ /// Append the next n elements on the stack to the n+1 element on the stack
+ fn append_children(&mut self, n: u32);
+
+ /// Create a new element with the given text and ElementId
+ fn create_text_node(&mut self, text: &'a str, root: ElementId);
+ /// Create an element with the given tag name, optional namespace, and ElementId
+ /// Note that namespaces do not cascade down the tree, so the renderer must handle this if it implements namespaces
+ fn create_element(&mut self, tag: &'static str, ns: Option<&'static str>, id: ElementId);
+ /// Create a hidden element to be used later for replacement.
+ /// Used in suspense, lists, and other places where we need to hide a node before it is ready to be shown.
+ /// This is up to the renderer to implement, but it should not be visible to the user.
+ fn create_placeholder(&mut self, id: ElementId);
+
+ /// Remove the targeted node from the DOM
+ fn remove(&mut self, root: ElementId);
+ /// Remove an attribute from an existing element
+ fn remove_attribute(&mut self, attribute: &Attribute, root: ElementId);
+ /// Remove all the children of the given element
+ fn remove_children(&mut self, root: ElementId);
+
+ /// Attach a new listener to the dom
+ fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId);
+ /// Remove an existing listener from the dom
+ fn remove_event_listener(&mut self, event: &'static str, root: ElementId);
+
+ /// Set the text content of a node
+ fn set_text(&mut self, text: &'a str, root: ElementId);
+ /// Set an attribute on an element
+ fn set_attribute(
+ &mut self,
+ name: &'static str,
+ value: AttributeValue<'a>,
+ namespace: Option<&'a str>,
+ root: ElementId,
+ );
+
+ /// General statistics for doing things that extend outside of the renderer
+ fn mark_dirty_scope(&mut self, scope: ScopeId);
+
+ /// Save the current n nodes to the ID to be loaded later
+ fn save(&mut self, id: &'static str, num: u32);
+ /// Loads a set of saved nodes from the ID into a scratch space
+ fn load(&mut self, id: &'static str, index: u32);
+ /// Assign the element on the stack's descendent the given ID
+ fn assign_id(&mut self, descendent: &'static [u8], id: ElementId);
+ /// Replace the given element of the topmost element with the next m elements on the stack
+ /// Is essentially a combination of assign_id and replace_with
+ fn replace_descendant(&mut self, descendent: &'static [u8], m: u32);
+}
+
+/*
+div {
+ div {
+ div {
+ div {}
+ }
+ }
+}
+
+push_child(0)
+push_child(1)
+push_child(3)
+push_child(4)
+pop
+pop
+
+clone_node(0)
+set_node(el, [1,2,3,4])
+set_attribute("class", "foo")
+append_child(1)
+*/
diff --git a/packages/core/src/nodes.old.rs b/packages/core/src.old/nodes.old.rs
similarity index 100%
rename from packages/core/src/nodes.old.rs
rename to packages/core/src.old/nodes.old.rs
diff --git a/packages/core/src/nodes/arbitrary_value.rs b/packages/core/src.old/nodes/arbitrary_value.rs
similarity index 100%
rename from packages/core/src/nodes/arbitrary_value.rs
rename to packages/core/src.old/nodes/arbitrary_value.rs
diff --git a/packages/core/src/nodes/component.rs b/packages/core/src.old/nodes/component.rs
similarity index 100%
rename from packages/core/src/nodes/component.rs
rename to packages/core/src.old/nodes/component.rs
diff --git a/packages/core/src/nodes/element.rs b/packages/core/src.old/nodes/element.rs
similarity index 100%
rename from packages/core/src/nodes/element.rs
rename to packages/core/src.old/nodes/element.rs
diff --git a/packages/core/src/nodes/factory.rs b/packages/core/src.old/nodes/factory.rs
similarity index 100%
rename from packages/core/src/nodes/factory.rs
rename to packages/core/src.old/nodes/factory.rs
diff --git a/packages/core/src/nodes/fragment.rs b/packages/core/src.old/nodes/fragment.rs
similarity index 100%
rename from packages/core/src/nodes/fragment.rs
rename to packages/core/src.old/nodes/fragment.rs
diff --git a/packages/core/src/nodes/mod.rs b/packages/core/src.old/nodes/mod.old.rs
similarity index 100%
rename from packages/core/src/nodes/mod.rs
rename to packages/core/src.old/nodes/mod.old.rs
diff --git a/packages/core/src.old/nodes/mod.rs b/packages/core/src.old/nodes/mod.rs
new file mode 100644
index 000000000..343f73325
--- /dev/null
+++ b/packages/core/src.old/nodes/mod.rs
@@ -0,0 +1,88 @@
+use std::{cell::Cell, num::NonZeroUsize};
+
+/// A reference to a template along with any context needed to hydrate it
+pub struct VTemplate<'a> {
+ // The ID assigned for the root of this template
+ pub node_id: Cell,
+
+ pub template: &'static Template,
+
+ /// All the dynamic nodes for a template
+ pub dynamic_nodes: &'a [DynamicNode<'a>],
+
+ pub dynamic_attrs: &'a [AttributeLocation<'a>],
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Template {
+ pub id: &'static str,
+
+ pub root: TemplateNode<'static>,
+
+ // todo: locations of dynamic nodes
+ pub node_pathways: &'static [&'static [u8]],
+
+ // todo: locations of dynamic nodes
+ pub attr_pathways: &'static [&'static [u8]],
+}
+
+/// A weird-ish variant of VNodes with way more limited types
+#[derive(Debug, Clone, Copy)]
+pub enum TemplateNode<'a> {
+ /// A simple element
+ Element {
+ tag: &'a str,
+ namespace: Option<&'a str>,
+ attrs: &'a [TemplateAttribute<'a>],
+ children: &'a [TemplateNode<'a>],
+ },
+ Text(&'a str),
+ Dynamic(usize),
+ DynamicText(usize),
+}
+
+pub enum DynamicNode<'a> {
+ // Anything declared in component form
+ // IE in caps or with underscores
+ Component {
+ name: &'static str,
+ },
+
+ // Comes in with string interpolation or from format_args, include_str, etc
+ Text {
+ id: Cell,
+ value: &'static str,
+ },
+
+ // Anything that's coming in as an iterator
+ Fragment {
+ children: &'a [VTemplate<'a>],
+ },
+}
+
+#[derive(Debug)]
+pub struct TemplateAttribute<'a> {
+ pub name: &'static str,
+ pub value: &'a str,
+ pub namespace: Option<&'static str>,
+ pub volatile: bool,
+}
+
+pub struct AttributeLocation<'a> {
+ pub mounted_element: Cell,
+ pub attrs: &'a [Attribute<'a>],
+}
+
+#[derive(Debug)]
+pub struct Attribute<'a> {
+ pub name: &'static str,
+ pub value: &'a str,
+ pub namespace: Option<&'static str>,
+}
+
+#[test]
+fn what_are_the_sizes() {
+ dbg!(std::mem::size_of::());
+ dbg!(std::mem::size_of::());
+ dbg!(std::mem::size_of::());
+}
diff --git a/packages/core/src/nodes/placeholder.rs b/packages/core/src.old/nodes/placeholder.rs
similarity index 100%
rename from packages/core/src/nodes/placeholder.rs
rename to packages/core/src.old/nodes/placeholder.rs
diff --git a/packages/core/src/nodes/suspense.rs b/packages/core/src.old/nodes/suspense.rs
similarity index 100%
rename from packages/core/src/nodes/suspense.rs
rename to packages/core/src.old/nodes/suspense.rs
diff --git a/packages/core/src/nodes/template.rs b/packages/core/src.old/nodes/template.rs
similarity index 100%
rename from packages/core/src/nodes/template.rs
rename to packages/core/src.old/nodes/template.rs
diff --git a/packages/core/src/nodes/text.rs b/packages/core/src.old/nodes/text.rs
similarity index 100%
rename from packages/core/src/nodes/text.rs
rename to packages/core/src.old/nodes/text.rs
diff --git a/packages/core/src/properties.rs b/packages/core/src.old/properties.rs
similarity index 100%
rename from packages/core/src/properties.rs
rename to packages/core/src.old/properties.rs
diff --git a/packages/core/src.old/scopes.rs b/packages/core/src.old/scopes.rs
new file mode 100644
index 000000000..be14cc4b2
--- /dev/null
+++ b/packages/core/src.old/scopes.rs
@@ -0,0 +1,1012 @@
+use crate::{innerlude::*, unsafe_utils::extend_vnode};
+use bumpalo::Bump;
+use futures_channel::mpsc::UnboundedSender;
+use fxhash::FxHashMap;
+use slab::Slab;
+use std::{
+ any::{Any, TypeId},
+ borrow::Borrow,
+ cell::{Cell, RefCell},
+ collections::{HashMap, HashSet},
+ future::Future,
+ pin::Pin,
+ rc::Rc,
+ sync::Arc,
+};
+
+/// for traceability, we use the raw fn pointer to identify the function
+/// we also get the component name, but that's not necessarily unique in the app
+pub(crate) type ComponentPtr = *mut std::os::raw::c_void;
+
+pub(crate) 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
+//
+// has an internal heuristics engine to pre-allocate arenas to the right size
+pub(crate) struct ScopeArena {
+ pub scope_gen: Cell,
+ pub bump: Bump,
+ pub scopes: RefCell>,
+ pub heuristics: RefCell>,
+ pub free_scopes: RefCell>,
+ pub nodes: RefCell>>,
+ pub tasks: Rc,
+ pub template_cache: RefCell>>,
+}
+
+impl ScopeArena {
+ pub(crate) fn new(sender: UnboundedSender) -> Self {
+ let bump = Bump::new();
+
+ // allocate a container for the root element
+ // this will *never* show up in the diffing process
+ // todo: figure out why this is necessary. i forgot. whoops.
+ let el = bump.alloc(VElement {
+ tag: "root",
+ namespace: None,
+ key: None,
+ id: Cell::new(Some(ElementId(0))),
+ parent: Default::default(),
+ listeners: &[],
+ attributes: &[],
+ children: &[],
+ });
+
+ let node = bump.alloc(VNode::Element(el));
+ let mut nodes = Slab::new();
+ let root_id = nodes.insert(unsafe { std::mem::transmute(node as *const _) });
+
+ debug_assert_eq!(root_id, 0);
+
+ Self {
+ scope_gen: Cell::new(0),
+ bump,
+ scopes: RefCell::new(FxHashMap::default()),
+ heuristics: RefCell::new(FxHashMap::default()),
+ free_scopes: RefCell::new(Vec::new()),
+ nodes: RefCell::new(nodes),
+ tasks: Rc::new(TaskQueue {
+ tasks: RefCell::new(FxHashMap::default()),
+ task_map: RefCell::new(FxHashMap::default()),
+ gen: Cell::new(0),
+ sender,
+ }),
+ template_cache: RefCell::new(HashSet::new()),
+ }
+ }
+
+ /// Safety:
+ /// - Obtaining a mutable reference to any Scope is unsafe
+ /// - Scopes use interior mutability when sharing data into components
+ pub(crate) fn get_scope(&self, id: ScopeId) -> Option<&ScopeState> {
+ unsafe { self.scopes.borrow().get(&id).map(|f| &**f) }
+ }
+
+ pub(crate) fn get_scope_raw(&self, id: ScopeId) -> Option<*mut ScopeState> {
+ self.scopes.borrow().get(&id).copied()
+ }
+
+ pub(crate) fn new_with_key(
+ &self,
+ fc_ptr: ComponentPtr,
+ vcomp: Box,
+ parent_scope: Option,
+ container: ElementId,
+ subtree: u32,
+ ) -> ScopeId {
+ // Increment the ScopeId system. ScopeIDs are never reused
+ let new_scope_id = ScopeId(self.scope_gen.get());
+ self.scope_gen.set(self.scope_gen.get() + 1);
+
+ // Get the height of the scope
+ let height = parent_scope
+ .and_then(|id| self.get_scope(id).map(|scope| scope.height + 1))
+ .unwrap_or_default();
+
+ let parent_scope = parent_scope.and_then(|f| self.get_scope_raw(f));
+
+ /*
+ This scopearena aggressively reuses old scopes when possible.
+ We try to minimize the new allocations for props/arenas.
+
+ However, this will probably lead to some sort of fragmentation.
+ I'm not exactly sure how to improve this today.
+ */
+ if let Some(old_scope) = self.free_scopes.borrow_mut().pop() {
+ // reuse the old scope
+ let scope = unsafe { &mut *old_scope };
+
+ scope.container = container;
+ scope.our_arena_idx = new_scope_id;
+ scope.parent_scope = parent_scope;
+ scope.height = height;
+ scope.fnptr = fc_ptr;
+ scope.props.get_mut().replace(vcomp);
+ scope.subtree.set(subtree);
+ scope.frames[0].reset();
+ scope.frames[1].reset();
+ scope.shared_contexts.get_mut().clear();
+ scope.items.get_mut().listeners.clear();
+ scope.items.get_mut().borrowed_props.clear();
+ scope.hook_idx.set(0);
+ scope.hook_vals.get_mut().clear();
+
+ let any_item = self.scopes.borrow_mut().insert(new_scope_id, scope);
+ debug_assert!(any_item.is_none());
+ } else {
+ // else create a new scope
+ let (node_capacity, hook_capacity) = self
+ .heuristics
+ .borrow()
+ .get(&fc_ptr)
+ .map(|h| (h.node_arena_size, h.hook_arena_size))
+ .unwrap_or_default();
+
+ self.scopes.borrow_mut().insert(
+ new_scope_id,
+ self.bump.alloc(ScopeState {
+ container,
+ our_arena_idx: new_scope_id,
+ parent_scope,
+ height,
+ fnptr: fc_ptr,
+ props: RefCell::new(Some(vcomp)),
+ frames: [BumpFrame::new(node_capacity), BumpFrame::new(node_capacity)],
+
+ // todo: subtrees
+ subtree: Cell::new(0),
+ is_subtree_root: Cell::default(),
+
+ generation: 0.into(),
+
+ tasks: self.tasks.clone(),
+ shared_contexts: RefCell::default(),
+
+ items: RefCell::new(SelfReferentialItems {
+ listeners: Vec::default(),
+ borrowed_props: Vec::default(),
+ }),
+
+ hook_arena: Bump::new(),
+ hook_vals: RefCell::new(Vec::with_capacity(hook_capacity)),
+ hook_idx: Cell::default(),
+ }),
+ );
+ }
+
+ new_scope_id
+ }
+
+ // Removes a scope and its descendents from the arena
+ pub fn try_remove(&self, id: ScopeId) {
+ self.ensure_drop_safety(id);
+
+ // Dispose of any ongoing tasks
+ let mut tasks = self.tasks.tasks.borrow_mut();
+ let mut task_map = self.tasks.task_map.borrow_mut();
+ if let Some(cur_tasks) = task_map.remove(&id) {
+ for task in cur_tasks {
+ tasks.remove(&task);
+ }
+ }
+
+ // Safety:
+ // - ensure_drop_safety ensures that no references to this scope are in use
+ // - this raw pointer is removed from the map
+ let scope = unsafe { &mut *self.scopes.borrow_mut().remove(&id).unwrap() };
+ scope.reset();
+
+ self.free_scopes.borrow_mut().push(scope);
+ }
+
+ pub fn reserve_node<'a>(&self, node: &'a VNode<'a>) -> ElementId {
+ let mut els = self.nodes.borrow_mut();
+ let entry = els.vacant_entry();
+ let key = entry.key();
+ let id = ElementId(key);
+ let node = unsafe { extend_vnode(node) };
+ entry.insert(node as *const _);
+ id
+ }
+
+ pub fn update_node<'a>(&self, node: &'a VNode<'a>, id: ElementId) {
+ let node = unsafe { extend_vnode(node) };
+ *self.nodes.borrow_mut().get_mut(id.0).unwrap() = node;
+ }
+
+ pub fn collect_garbage(&self, id: ElementId) {
+ self.nodes.borrow_mut().remove(id.0);
+ }
+
+ /// 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(&self, scope_id: ScopeId) {
+ if let Some(scope) = self.get_scope(scope_id) {
+ let mut items = scope.items.borrow_mut();
+
+ // 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)
+ // recursively call ensure_drop_safety on all children
+ items.borrowed_props.drain(..).for_each(|comp| {
+ if let Some(scope_id) = comp.scope.get() {
+ self.ensure_drop_safety(scope_id);
+ }
+ drop(comp.props.take());
+ });
+
+ // Now that all the references are gone, we can safely drop our own references in our listeners.
+ items
+ .listeners
+ .drain(..)
+ .for_each(|listener| drop(listener.callback.borrow_mut().take()));
+ }
+ }
+
+ pub(crate) fn run_scope(&self, id: ScopeId) {
+ // 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(id);
+
+ // todo: we *know* that this is aliased by the contents of the scope itself
+ let scope = unsafe { &mut *self.get_scope_raw(id).expect("could not find scope") };
+
+ log::trace!("running scope {:?} symbol: {:?}", id, scope.fnptr);
+
+ // 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
+ scope.hook_idx.set(0);
+
+ {
+ // Safety:
+ // - We've dropped all references to the wip bump frame with "ensure_drop_safety"
+ unsafe { scope.reset_wip_frame() };
+
+ let items = scope.items.borrow();
+
+ // guarantee that we haven't screwed up - there should be no latent references anywhere
+ debug_assert!(items.listeners.is_empty());
+ debug_assert!(items.borrowed_props.is_empty());
+ }
+
+ /*
+ If the component returns None, then we fill in a placeholder node. This will wipe what was there.
+ An alternate approach is to leave the Real Dom the same, but that can lead to safety issues and a lot more checks.
+
+ Instead, we just treat the `None` as a shortcut to placeholder.
+ If the developer wants to prevent a scope from updating, they should control its memoization instead.
+
+ Also, the way we implement hooks allows us to cut rendering short before the next hook is recalled.
+ I'm not sure if React lets you abort the component early, but we let you do that.
+ */
+
+ let props = scope.props.borrow();
+ let render = props.as_ref().unwrap();
+ if let Some(node) = render.render(scope) {
+ let frame = scope.wip_frame();
+ let node = frame.bump.alloc(node);
+ frame.node.set(unsafe { extend_vnode(node) });
+ } else {
+ let frame = scope.wip_frame();
+ let node = frame.bump.alloc(VNode::Text(frame.bump.alloc(VText {
+ id: Cell::default(),
+ text: "asd",
+ })));
+ frame.node.set(unsafe { extend_vnode(node) });
+ }
+
+ // make the "wip frame" contents the "finished frame"
+ // any future dipping into completed nodes after "render" will go through "fin head"
+ scope.cycle_frame();
+ }
+
+ pub fn call_listener_with_bubbling(&self, event: &UserEvent, element: ElementId) {
+ let nodes = self.nodes.borrow();
+ let mut cur_el = Some(element);
+
+ let state = Rc::new(BubbleState::new());
+
+ while let Some(id) = cur_el.take() {
+ if let Some(el) = nodes.get(id.0) {
+ let real_el = unsafe { &**el };
+
+ if let VNode::Element(real_el) = real_el {
+ for listener in real_el.listeners.borrow().iter() {
+ if listener.event == event.name {
+ if state.canceled.get() {
+ // stop bubbling if canceled
+ return;
+ }
+
+ let mut cb = listener.callback.borrow_mut();
+ if let Some(cb) = cb.as_mut() {
+ // todo: arcs are pretty heavy to clone
+ // we really want to convert arc to rc
+ // unfortunately, the SchedulerMsg must be send/sync to be sent across threads
+ // we could convert arc to rc internally or something
+ (cb)(AnyEvent {
+ bubble_state: state.clone(),
+ data: event.data.clone(),
+ });
+ }
+
+ if !event.bubbles {
+ return;
+ }
+ }
+ }
+
+ cur_el = real_el.parent.get();
+ }
+ }
+ }
+ }
+
+ // The head of the bumpframe is the first linked NodeLink
+ pub fn wip_head(&self, id: ScopeId) -> &VNode {
+ let scope = self.get_scope(id).unwrap();
+ let frame = scope.wip_frame();
+ let node = unsafe { &*frame.node.get() };
+ unsafe { extend_vnode(node) }
+ }
+
+ // The head of the bumpframe is the first linked NodeLink
+ pub fn fin_head(&self, id: ScopeId) -> &VNode {
+ let scope = self.get_scope(id).unwrap();
+ let frame = scope.fin_frame();
+ let node = unsafe { &*frame.node.get() };
+ unsafe { extend_vnode(node) }
+ }
+
+ pub fn root_node(&self, id: ScopeId) -> &VNode {
+ self.fin_head(id)
+ }
+
+ // this is totally okay since all our nodes are always in a valid state
+ pub fn get_element(&self, id: ElementId) -> Option<&VNode> {
+ self.nodes
+ .borrow()
+ .get(id.0)
+ .copied()
+ .map(|ptr| unsafe { extend_vnode(&*ptr) })
+ }
+}
+
+/// 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.
+///
+/// For the most part, the only method you should be using regularly is `render`.
+///
+/// ## Example
+///
+/// ```ignore
+/// #[derive(Props)]
+/// struct ExampleProps {
+/// name: String
+/// }
+///
+/// fn Example(cx: Scope) -> Element {
+/// cx.render(rsx!{ div {"Hello, {cx.props.name}"} })
+/// }
+/// ```
+pub struct Scope<'a, P = ()> {
+ /// The internal ScopeState for this component
+ pub scope: &'a ScopeState,
+
+ /// The props for this component
+ pub props: &'a P,
+}
+
+impl
Copy for Scope<'_, P> {}
+impl
Clone for Scope<'_, P> {
+ fn clone(&self) -> Self {
+ Self {
+ scope: self.scope,
+ props: self.props,
+ }
+ }
+}
+
+impl<'a, P> std::ops::Deref for Scope<'a, P> {
+ // rust will auto deref again to the original 'a lifetime at the call site
+ type Target = &'a ScopeState;
+ fn deref(&self) -> &Self::Target {
+ &self.scope
+ }
+}
+
+/// A component's unique identifier.
+///
+/// `ScopeId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`ScopeID`]s will never be reused
+/// once a component has been unmounted.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
+pub struct ScopeId(pub usize);
+
+/// A task's unique identifier.
+///
+/// `TaskId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`TaskID`]s will never be reused
+/// once a Task has been completed.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub struct TaskId {
+ /// The global ID of the task
+ pub id: usize,
+
+ /// The original scope that this task was scheduled in
+ pub scope: ScopeId,
+}
+
+/// Every component in Dioxus is represented by a `ScopeState`.
+///
+/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
+///
+/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
+/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
+///
+/// We expose the `Scope` type so downstream users can traverse the Dioxus [`VirtualDom`] for whatever
+/// use case they might have.
+pub struct ScopeState {
+ pub(crate) parent_scope: Option<*mut ScopeState>,
+ pub(crate) container: ElementId,
+ pub(crate) our_arena_idx: ScopeId,
+ pub(crate) height: u32,
+ pub(crate) fnptr: ComponentPtr,
+
+ // todo: subtrees
+ pub(crate) is_subtree_root: Cell,
+ pub(crate) subtree: Cell,
+ pub(crate) props: RefCell