diff --git a/packages/core/src.old/arbitrary_value.rs b/packages/core/src.old/arbitrary_value.rs deleted file mode 100644 index b62d03f93..000000000 --- a/packages/core/src.old/arbitrary_value.rs +++ /dev/null @@ -1,237 +0,0 @@ -use std::{any::Any, fmt::Formatter}; - -/// Possible values for an attribute -// trying to keep values at 3 bytes -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serialize", serde(untagged))] -#[derive(Clone, Debug, PartialEq)] -#[allow(missing_docs)] -pub enum AttributeValue<'a> { - Text(&'a str), - Float32(f32), - Float64(f64), - Int32(i32), - Int64(i64), - Uint32(u32), - Uint64(u64), - Bool(bool), - - Vec3Float(f32, f32, f32), - Vec3Int(i32, i32, i32), - Vec3Uint(u32, u32, u32), - - Vec4Float(f32, f32, f32, f32), - Vec4Int(i32, i32, i32, i32), - Vec4Uint(u32, u32, u32, u32), - - Bytes(&'a [u8]), - // Any(ArbitraryAttributeValue<'a>), -} - -// todo -#[allow(missing_docs)] -impl<'a> AttributeValue<'a> { - pub fn is_truthy(&self) -> bool { - match self { - AttributeValue::Text(t) => *t == "true", - AttributeValue::Bool(t) => *t, - _ => false, - } - } - - pub fn is_falsy(&self) -> bool { - match self { - AttributeValue::Text(t) => *t == "false", - AttributeValue::Bool(t) => !(*t), - _ => false, - } - } -} - -impl<'a> std::fmt::Display for AttributeValue<'a> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - AttributeValue::Text(a) => write!(f, "{}", a), - AttributeValue::Float32(a) => write!(f, "{}", a), - AttributeValue::Float64(a) => write!(f, "{}", a), - AttributeValue::Int32(a) => write!(f, "{}", a), - AttributeValue::Int64(a) => write!(f, "{}", a), - AttributeValue::Uint32(a) => write!(f, "{}", a), - AttributeValue::Uint64(a) => write!(f, "{}", a), - AttributeValue::Bool(a) => write!(f, "{}", a), - AttributeValue::Vec3Float(_, _, _) => todo!(), - AttributeValue::Vec3Int(_, _, _) => todo!(), - AttributeValue::Vec3Uint(_, _, _) => todo!(), - AttributeValue::Vec4Float(_, _, _, _) => todo!(), - AttributeValue::Vec4Int(_, _, _, _) => todo!(), - AttributeValue::Vec4Uint(_, _, _, _) => todo!(), - AttributeValue::Bytes(a) => write!(f, "{:?}", a), - // AttributeValue::Any(a) => write!(f, "{:?}", a), - } - } -} - -#[derive(Clone, Copy)] -#[allow(missing_docs)] -pub struct ArbitraryAttributeValue<'a> { - pub value: &'a dyn Any, - pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool, -} - -// pub trait AnyAttrValue {} - -// impl PartialEq for ArbitraryAttributeValue<'_> { -// fn eq(&self, other: &Self) -> bool { -// (self.cmp)(self.value, other.value) -// } -// } - -impl std::fmt::Debug for ArbitraryAttributeValue<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("ArbitraryAttributeValue").finish() - } -} - -#[cfg(feature = "serialize")] -impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> { - fn serialize(&self, _serializer: S) -> Result - where - S: serde::Serializer, - { - panic!("ArbitraryAttributeValue should not be serialized") - } -} -#[cfg(feature = "serialize")] -impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> { - fn deserialize(_deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - panic!("ArbitraryAttributeValue is not deserializable!") - } -} -#[cfg(feature = "serialize")] -impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> { - fn deserialize(_deserializer: D) -> Result - where - D: serde::Deserializer<'de>, - { - panic!("ArbitraryAttributeValue is not deserializable!") - } -} - -// todo -#[allow(missing_docs)] -impl<'a> AttributeValue<'a> { - pub fn as_text(&self) -> Option<&'a str> { - match self { - AttributeValue::Text(s) => Some(s), - _ => None, - } - } - - pub fn as_float32(&self) -> Option { - match self { - AttributeValue::Float32(f) => Some(*f), - _ => None, - } - } - - pub fn as_float64(&self) -> Option { - match self { - AttributeValue::Float64(f) => Some(*f), - _ => None, - } - } - - pub fn as_int32(&self) -> Option { - match self { - AttributeValue::Int32(i) => Some(*i), - _ => None, - } - } - - pub fn as_int64(&self) -> Option { - match self { - AttributeValue::Int64(i) => Some(*i), - _ => None, - } - } - - pub fn as_uint32(&self) -> Option { - match self { - AttributeValue::Uint32(i) => Some(*i), - _ => None, - } - } - - pub fn as_uint64(&self) -> Option { - match self { - AttributeValue::Uint64(i) => Some(*i), - _ => None, - } - } - - pub fn as_bool(&self) -> Option { - match self { - AttributeValue::Bool(b) => Some(*b), - _ => None, - } - } - - pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> { - match self { - AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)), - _ => None, - } - } - - pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> { - match self { - AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)), - _ => None, - } - } - - pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> { - match self { - AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)), - _ => None, - } - } - - pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> { - match self { - AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)), - _ => None, - } - } - - pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> { - match self { - AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)), - _ => None, - } - } - - pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> { - match self { - AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)), - _ => None, - } - } - - pub fn as_bytes(&self) -> Option<&[u8]> { - match self { - AttributeValue::Bytes(b) => Some(b), - _ => None, - } - } - - // pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> { - // match self { - // AttributeValue::Any(a) => Some(a), - // _ => None, - // } - // } -} diff --git a/packages/core/src.old/diff.rs b/packages/core/src.old/diff.rs deleted file mode 100644 index e605345e1..000000000 --- a/packages/core/src.old/diff.rs +++ /dev/null @@ -1,1246 +0,0 @@ -#![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.old/events.rs b/packages/core/src.old/events.rs deleted file mode 100644 index 0668f7582..000000000 --- a/packages/core/src.old/events.rs +++ /dev/null @@ -1,182 +0,0 @@ -//! Internal and external event system -//! -//! -//! This is all kinda WIP, but the bones are there. - -use crate::{ElementId, ScopeId}; -use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc}; - -pub(crate) struct BubbleState { - pub canceled: Cell, -} - -impl BubbleState { - pub fn new() -> Self { - Self { - canceled: Cell::new(false), - } - } -} - -/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel. -/// -/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree -/// where each listener is checked and fired if the event name matches. -/// -/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in -/// attempting to downcast the event data. -/// -/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but -/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel. -/// -/// # Example -/// ```rust, ignore -/// fn App(cx: Scope) -> Element { -/// render!(div { -/// onclick: move |_| println!("Clicked!") -/// }) -/// } -/// -/// let mut dom = VirtualDom::new(App); -/// let mut scheduler = dom.get_scheduler_channel(); -/// scheduler.unbounded_send(SchedulerMsg::UiEvent( -/// UserEvent { -/// scope_id: None, -/// priority: EventPriority::Medium, -/// name: "click", -/// element: Some(ElementId(0)), -/// data: Arc::new(ClickEvent { .. }) -/// } -/// )).unwrap(); -/// ``` -#[derive(Debug, Clone)] -pub struct UserEvent { - /// The originator of the event trigger if available - pub scope_id: Option, - - /// The priority of the event to be scheduled around ongoing work - pub priority: EventPriority, - - /// The optional real node associated with the trigger - pub element: Option, - - /// The event type IE "onclick" or "onmouseover" - pub name: &'static str, - - /// If the event is bubbles up through the vdom - pub bubbles: bool, - - /// The event data to be passed onto the event handler - pub data: Arc, -} - -/// Priority of Event Triggers. -/// -/// Internally, Dioxus will abort work that's taking too long if new, more important work arrives. Unlike React, Dioxus -/// won't be afraid to pause work or flush changes to the Real Dom. This is called "cooperative scheduling". Some Renderers -/// implement this form of scheduling internally, however Dioxus will perform its own scheduling as well. -/// -/// The ultimate goal of the scheduler is to manage latency of changes, prioritizing "flashier" changes over "subtler" changes. -/// -/// React has a 5-tier priority system. However, they break things into "Continuous" and "Discrete" priority. For now, -/// we keep it simple, and just use a 3-tier priority system. -/// -/// - `NoPriority` = 0 -/// - `LowPriority` = 1 -/// - `NormalPriority` = 2 -/// - `UserBlocking` = 3 -/// - `HighPriority` = 4 -/// - `ImmediatePriority` = 5 -/// -/// We still have a concept of discrete vs continuous though - discrete events won't be batched, but continuous events will. -/// This means that multiple "scroll" events will be processed in a single frame, but multiple "click" events will be -/// flushed before proceeding. Multiple discrete events is highly unlikely, though. -#[derive(Debug, PartialEq, Eq, Clone, Copy, Hash, PartialOrd, Ord)] -pub enum EventPriority { - /// Work that must be completed during the EventHandler phase. - /// - /// Currently this is reserved for controlled inputs. - Immediate = 3, - - /// "High Priority" work will not interrupt other high priority work, but will interrupt medium and low priority work. - /// - /// This is typically reserved for things like user interaction. - /// - /// React calls these "discrete" events, but with an extra category of "user-blocking" (Immediate). - High = 2, - - /// "Medium priority" work is generated by page events not triggered by the user. These types of events are less important - /// than "High Priority" events and will take precedence over low priority events. - /// - /// This is typically reserved for VirtualEvents that are not related to keyboard or mouse input. - /// - /// React calls these "continuous" events (e.g. mouse move, mouse wheel, touch move, etc). - Medium = 1, - - /// "Low Priority" work will always be preempted unless the work is significantly delayed, in which case it will be - /// advanced to the front of the work queue until completed. - /// - /// The primary user of Low Priority work is the asynchronous work system (Suspense). - /// - /// This is considered "idle" work or "background" work. - Low = 0, -} - -/// The internal Dioxus type that carries any event data to the relevant handler. - -pub struct AnyEvent { - pub(crate) bubble_state: Rc, - pub(crate) data: Arc, -} - -impl AnyEvent { - /// Convert this [`AnyEvent`] into a specific [`UiEvent`] with [`EventData`]. - /// - /// ```rust, ignore - /// let evt: FormEvent = evvt.downcast().unwrap(); - /// ``` - #[must_use] - pub fn downcast(self) -> Option> { - let AnyEvent { data, bubble_state } = self; - - data.downcast::() - .ok() - .map(|data| UiEvent { data, bubble_state }) - } -} - -/// A [`UiEvent`] is a type that wraps various [`EventData`]. -/// -/// You should prefer to use the name of the event directly, rather than -/// the [`UiEvent`] generic type. -/// -/// For the HTML crate, this would include `MouseEvent`, `FormEvent` etc. -pub struct UiEvent { - /// The internal data of the event - /// This is wrapped in an Arc so that it can be sent across threads - pub data: Arc, - - #[allow(unused)] - bubble_state: Rc, -} - -impl std::fmt::Debug for UiEvent { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("UiEvent").field("data", &self.data).finish() - } -} - -impl std::ops::Deref for UiEvent { - type Target = T; - - fn deref(&self) -> &Self::Target { - self.data.as_ref() - } -} - -impl UiEvent { - /// Prevent this event from bubbling up the tree. - pub fn cancel_bubble(&self) { - self.bubble_state.canceled.set(true); - } -} diff --git a/packages/core/src.old/lazynodes.rs b/packages/core/src.old/lazynodes.rs deleted file mode 100644 index 528d8a5fb..000000000 --- a/packages/core/src.old/lazynodes.rs +++ /dev/null @@ -1,322 +0,0 @@ -//! Support for storing lazy-nodes on the stack -//! -//! This module provides support for a type called `LazyNodes` which is a micro-heap located on the stack to make calls -//! to `rsx!` more efficient. -//! -//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`NodeFactory`] closures. -//! -//! This can be done either through boxing directly, or by using dynamic-sized-types and a custom allocator. In our case, -//! we build a tiny alloactor in the stack and allocate the closure into that. -//! -//! The logic for this was borrowed from . Unfortunately, this crate does not -//! support non-static closures, so we've implemented the core logic of `ValueA` in this module. - -use crate::innerlude::{NodeFactory, VNode}; -use std::mem; - -/// A concrete type provider for closures that build [`VNode`] structures. -/// -/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over -/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`]. -/// -/// -/// ```rust, ignore -/// LazyNodes::new(|f| f.element("div", [], [], [] None)) -/// ``` -pub struct LazyNodes<'a, 'b> { - inner: StackNodeStorage<'a, 'b>, -} - -type StackHeapSize = [usize; 16]; - -enum StackNodeStorage<'a, 'b> { - Stack(LazyStack), - Heap(Box>) -> Option> + 'b>), -} - -impl<'a, 'b> LazyNodes<'a, 'b> { - /// Create a new [`LazyNodes`] closure, optimistically placing it onto the stack. - /// - /// If the closure cannot fit into the stack allocation (16 bytes), then it - /// is placed on the heap. Most closures will fit into the stack, and is - /// the most optimal way to use the creation function. - pub fn new(val: impl FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b) -> Self { - // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch - let mut slot = Some(val); - - let val = move |fac: Option>| { - fac.map( - slot.take() - .expect("LazyNodes closure to be called only once"), - ) - }; - - // miri does not know how to work with mucking directly into bytes - // just use a heap allocated type when miri is running - if cfg!(miri) { - Self { - inner: StackNodeStorage::Heap(Box::new(val)), - } - } else { - unsafe { LazyNodes::new_inner(val) } - } - } - - /// Create a new [`LazyNodes`] closure, but force it onto the heap. - pub fn new_boxed(inner: F) -> Self - where - F: FnOnce(NodeFactory<'a>) -> VNode<'a> + 'b, - { - // there's no way to call FnOnce without a box, so we need to store it in a slot and use static dispatch - let mut slot = Some(inner); - - Self { - inner: StackNodeStorage::Heap(Box::new(move |fac: Option>| { - fac.map( - slot.take() - .expect("LazyNodes closure to be called only once"), - ) - })), - } - } - - unsafe fn new_inner(val: F) -> Self - where - F: FnMut(Option>) -> Option> + 'b, - { - let mut ptr: *const _ = &val as &dyn FnMut(Option>) -> Option>; - - assert_eq!( - ptr as *const u8, &val as *const _ as *const u8, - "MISUSE: Closure returned different pointer" - ); - assert_eq!( - std::mem::size_of_val(&*ptr), - std::mem::size_of::(), - "MISUSE: Closure returned a subset pointer" - ); - - let words = ptr_as_slice(&mut ptr); - assert!( - words[0] == &val as *const _ as usize, - "BUG: Pointer layout is not (data_ptr, info...)" - ); - - // - Ensure that Self is aligned same as data requires - assert!( - std::mem::align_of::() <= std::mem::align_of::(), - "TODO: Enforce alignment >{} (requires {})", - std::mem::align_of::(), - std::mem::align_of::() - ); - - let info = &words[1..]; - let data = words[0] as *mut (); - let size = mem::size_of::(); - - let stored_size = info.len() * mem::size_of::() + size; - let max_size = mem::size_of::(); - - if stored_size > max_size { - Self { - inner: StackNodeStorage::Heap(Box::new(val)), - } - } else { - let mut buf: StackHeapSize = StackHeapSize::default(); - - assert!(info.len() + round_to_words(size) <= buf.as_ref().len()); - - // Place pointer information at the end of the region - // - Allows the data to be at the start for alignment purposes - { - let info_ofs = buf.as_ref().len() - info.len(); - let info_dst = &mut buf.as_mut()[info_ofs..]; - for (d, v) in Iterator::zip(info_dst.iter_mut(), info.iter()) { - *d = *v; - } - } - - let src_ptr = data as *const u8; - let dataptr = buf.as_mut_ptr().cast::(); - - for i in 0..size { - *dataptr.add(i) = *src_ptr.add(i); - } - - std::mem::forget(val); - - Self { - inner: StackNodeStorage::Stack(LazyStack { - _align: [], - buf, - dropped: false, - }), - } - } - } - - /// Call the closure with the given factory to produce real [`VNode`]. - /// - /// ```rust, ignore - /// let f = LazyNodes::new(move |f| f.element("div", [], [], [] None)); - /// - /// let fac = NodeFactory::new(&cx); - /// - /// let node = f.call(cac); - /// ``` - #[must_use] - pub fn call(self, f: NodeFactory<'a>) -> VNode<'a> { - match self.inner { - StackNodeStorage::Heap(mut lazy) => { - lazy(Some(f)).expect("Closure should not be called twice") - } - StackNodeStorage::Stack(mut stack) => stack.call(f), - } - } -} - -struct LazyStack { - _align: [u64; 0], - buf: StackHeapSize, - dropped: bool, -} - -impl LazyStack { - fn call<'a>(&mut self, f: NodeFactory<'a>) -> VNode<'a> { - let LazyStack { buf, .. } = self; - let data = buf.as_ref(); - - let info_size = - mem::size_of::<*mut dyn FnMut(Option>) -> Option>>() - / mem::size_of::() - - 1; - - let info_ofs = data.len() - info_size; - - let g: *mut dyn FnMut(Option>) -> Option> = - unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) }; - - self.dropped = true; - - let clos = unsafe { &mut *g }; - clos(Some(f)).unwrap() - } -} -impl Drop for LazyStack { - fn drop(&mut self) { - if !self.dropped { - let LazyStack { buf, .. } = self; - let data = buf.as_ref(); - - let info_size = mem::size_of::< - *mut dyn FnMut(Option>) -> Option>, - >() / mem::size_of::() - - 1; - - let info_ofs = data.len() - info_size; - - let g: *mut dyn FnMut(Option>) -> Option> = - unsafe { make_fat_ptr(data[..].as_ptr() as usize, &data[info_ofs..]) }; - - self.dropped = true; - - let clos = unsafe { &mut *g }; - clos(None); - } - } -} - -/// Obtain mutable access to a pointer's words -fn ptr_as_slice(ptr: &mut T) -> &mut [usize] { - assert!(mem::size_of::() % mem::size_of::() == 0); - let words = mem::size_of::() / mem::size_of::(); - // SAFE: Points to valid memory (a raw pointer) - unsafe { core::slice::from_raw_parts_mut(ptr as *mut _ as *mut usize, words) } -} - -/// Re-construct a fat pointer -unsafe fn make_fat_ptr(data_ptr: usize, meta_vals: &[usize]) -> *mut T { - let mut rv = mem::MaybeUninit::<*mut T>::uninit(); - { - let s = ptr_as_slice(&mut rv); - s[0] = data_ptr; - s[1..].copy_from_slice(meta_vals); - } - let rv = rv.assume_init(); - assert_eq!(rv as *const (), data_ptr as *const ()); - rv -} - -fn round_to_words(len: usize) -> usize { - (len + mem::size_of::() - 1) / mem::size_of::() -} - -// #[cfg(test)] -// mod tests { -// use super::*; -// use crate::innerlude::{Element, Scope, VirtualDom}; - -// #[test] -// fn it_works() { -// fn app(cx: Scope<()>) -> Element { -// cx.render(LazyNodes::new(|f| f.text(format_args!("hello world!")))) -// } - -// let mut dom = VirtualDom::new(app); -// dom.rebuild(); - -// let g = dom.base_scope().root_node(); -// dbg!(g); -// } - -// #[test] -// fn it_drops() { -// use std::rc::Rc; - -// struct AppProps { -// inner: Rc, -// } - -// fn app(cx: Scope) -> Element { -// struct DropInner { -// id: i32, -// } -// impl Drop for DropInner { -// fn drop(&mut self) { -// eprintln!("dropping inner"); -// } -// } - -// let caller = { -// let it = (0..10).map(|i| { -// let val = cx.props.inner.clone(); -// LazyNodes::new(move |f| { -// eprintln!("hell closure"); -// let inner = DropInner { id: i }; -// f.text(format_args!("hello world {:?}, {:?}", inner.id, val)) -// }) -// }); - -// LazyNodes::new(|f| { -// eprintln!("main closure"); -// f.fragment_from_iter(it) -// }) -// }; - -// cx.render(caller) -// } - -// let inner = Rc::new(0); -// let mut dom = VirtualDom::new_with_props( -// app, -// AppProps { -// inner: inner.clone(), -// }, -// ); -// dom.rebuild(); - -// drop(dom); - -// assert_eq!(Rc::strong_count(&inner), 1); -// } -// } diff --git a/packages/core/src.old/lib.rs b/packages/core/src.old/lib.rs deleted file mode 100644 index c909e11f7..000000000 --- a/packages/core/src.old/lib.rs +++ /dev/null @@ -1,131 +0,0 @@ -#![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 deleted file mode 100644 index 0ddbd398b..000000000 --- a/packages/core/src.old/mutations.rs +++ /dev/null @@ -1,507 +0,0 @@ -//! 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) -*/ - - -//! 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::*; -use std::{any::Any, fmt::Debug}; - -/// ## Mutations -/// -/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also -/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has -/// applied the edits. -/// -/// Mutations are the only link between the RealDOM and the VirtualDOM. -pub struct Mutations<'a> { - /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM. - pub edits: Vec>, - - /// The list of Scopes that were diffed, created, and removed during the Diff process. - pub dirty_scopes: FxHashSet, - - /// The list of nodes to connect to the RealDOM. - pub refs: Vec>, -} - -impl Debug for Mutations<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("Mutations") - .field("edits", &self.edits) - .field("noderefs", &self.refs) - .finish() - } -} - -/// A `DomEdit` represents a serialized form of the VirtualDom's trait-based API. This allows streaming edits across the -/// network or through FFI boundaries. -#[derive(Debug, PartialEq)] -#[cfg_attr( - feature = "serialize", - derive(serde::Serialize, serde::Deserialize), - serde(tag = "type") -)] -pub enum DomEdit<'bump> { - /// Pop the topmost node from our stack and append them to the node - /// at the top of the stack. - AppendChildren { - /// The parent to append nodes to. - root: Option, - - /// The ids of the children to append. - children: Vec, - }, - - /// Replace a given (single) node with a handful of nodes currently on the stack. - ReplaceWith { - /// The ID of the node to be replaced. - root: Option, - - /// The ids of the nodes to replace the root with. - nodes: Vec, - }, - - /// Insert a number of nodes after a given node. - InsertAfter { - /// The ID of the node to insert after. - root: Option, - - /// The ids of the nodes to insert after the target node. - nodes: Vec, - }, - - /// Insert a number of nodes before a given node. - InsertBefore { - /// The ID of the node to insert before. - root: Option, - - /// The ids of the nodes to insert before the target node. - nodes: Vec, - }, - - /// Remove a particular node from the DOM - Remove { - /// The ID of the node to remove. - root: Option, - }, - - /// Create a new purely-text node - CreateTextNode { - /// The ID the new node should have. - root: Option, - - /// The textcontent of the node - text: &'bump str, - }, - - /// Create a new purely-element node - CreateElement { - /// The ID the new node should have. - root: Option, - - /// The tagname of the node - tag: &'bump str, - - /// The number of children nodes that will follow this message. - children: u32, - }, - - /// Create a new purely-comment node with a given namespace - CreateElementNs { - /// The ID the new node should have. - root: Option, - - /// The namespace of the node - tag: &'bump str, - - /// The namespace of the node (like `SVG`) - ns: &'static str, - - /// The number of children nodes that will follow this message. - children: u32, - }, - - /// Create a new placeholder node. - /// In most implementations, this will either be a hidden div or a comment node. - CreatePlaceholder { - /// The ID the new node should have. - root: Option, - }, - - /// Create a new Event Listener. - NewEventListener { - /// The name of the event to listen for. - event_name: &'static str, - - /// The ID of the node to attach the listener to. - scope: ScopeId, - - /// The ID of the node to attach the listener to. - root: Option, - }, - - /// Remove an existing Event Listener. - RemoveEventListener { - /// The ID of the node to remove. - root: Option, - - /// The name of the event to remove. - event: &'static str, - }, - - /// Set the textcontent of a node. - SetText { - /// The ID of the node to set the textcontent of. - root: Option, - - /// The textcontent of the node - text: &'bump str, - }, - - /// Set the value of a node's attribute. - SetAttribute { - /// The ID of the node to set the attribute of. - root: Option, - - /// The name of the attribute to set. - field: &'static str, - - /// The value of the attribute. - value: AttributeValue<'bump>, - - // value: &'bump str, - /// The (optional) namespace of the attribute. - /// For instance, "style" is in the "style" namespace. - ns: Option<&'bump str>, - }, - - /// Remove an attribute from a node. - RemoveAttribute { - /// The ID of the node to remove. - root: Option, - - /// The name of the attribute to remove. - name: &'static str, - - /// The namespace of the attribute. - ns: Option<&'bump str>, - }, - - /// Clones a node. - CloneNode { - /// The ID of the node to clone. - id: Option, - - /// The ID of the new node. - new_id: u64, - }, - - /// Clones the children of a node. (allows cloning fragments) - CloneNodeChildren { - /// The ID of the node to clone. - id: Option, - - /// The ID of the new node. - new_ids: Vec, - }, - - /// Navigates to the last node to the first child of the current node. - FirstChild {}, - - /// Navigates to the last node to the last child of the current node. - NextSibling {}, - - /// Navigates to the last node to the parent of the current node. - ParentNode {}, - - /// Stores the last node with a new id. - StoreWithId { - /// The ID of the node to store. - id: u64, - }, - - /// Manually set the last node. - SetLastNode { - /// The ID to set the last node to. - id: u64, - }, -} - -use rustc_hash::FxHashSet; -use DomEdit::*; - -#[allow(unused)] -impl<'a> Mutations<'a> { - pub(crate) fn new() -> Self { - Self { - edits: Vec::new(), - refs: Vec::new(), - dirty_scopes: Default::default(), - } - } - - pub(crate) fn replace_with(&mut self, root: Option, nodes: Vec) { - self.edits.push(ReplaceWith { nodes, root }); - } - - pub(crate) fn insert_after(&mut self, root: Option, nodes: Vec) { - self.edits.push(InsertAfter { nodes, root }); - } - - pub(crate) fn insert_before(&mut self, root: Option, nodes: Vec) { - self.edits.push(InsertBefore { nodes, root }); - } - - pub(crate) fn append_children(&mut self, root: Option, children: Vec) { - self.edits.push(AppendChildren { root, children }); - } - - // Remove Nodes from the dom - pub(crate) fn remove(&mut self, id: Option) { - self.edits.push(Remove { root: id }); - } - - // Create - pub(crate) fn create_text_node(&mut self, text: &'a str, id: Option) { - self.edits.push(CreateTextNode { text, root: id }); - } - - pub(crate) fn create_element( - &mut self, - tag: &'static str, - ns: Option<&'static str>, - id: Option, - children: u32, - ) { - match ns { - Some(ns) => self.edits.push(CreateElementNs { - root: id, - ns, - tag, - children, - }), - None => self.edits.push(CreateElement { - root: id, - tag, - children, - }), - } - } - - // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom - pub(crate) fn create_placeholder(&mut self, id: Option) { - self.edits.push(CreatePlaceholder { root: id }); - } - - // events - pub(crate) fn new_event_listener(&mut self, listener: &Listener, scope: ScopeId) { - let Listener { - event, - mounted_node, - .. - } = listener; - - let element_id = Some(mounted_node.get().unwrap().into()); - - self.edits.push(NewEventListener { - scope, - event_name: event, - root: element_id, - }); - } - - pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: Option) { - self.edits.push(RemoveEventListener { event, root }); - } - - // modify - pub(crate) fn set_text(&mut self, text: &'a str, root: Option) { - self.edits.push(SetText { text, root }); - } - - pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: Option) { - let Attribute { - value, attribute, .. - } = attribute; - - self.edits.push(SetAttribute { - field: attribute.name, - value: value.clone(), - ns: attribute.namespace, - root, - }); - } - - pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: Option) { - let Attribute { attribute, .. } = attribute; - - self.edits.push(RemoveAttribute { - name: attribute.name, - ns: attribute.namespace, - root, - }); - } - - pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) { - self.dirty_scopes.insert(scope); - } - - pub(crate) fn clone_node(&mut self, id: Option, new_id: u64) { - self.edits.push(CloneNode { id, new_id }); - } - - pub(crate) fn clone_node_children(&mut self, id: Option, new_ids: Vec) { - self.edits.push(CloneNodeChildren { id, new_ids }); - } - - pub(crate) fn first_child(&mut self) { - self.edits.push(FirstChild {}); - } - - pub(crate) fn next_sibling(&mut self) { - self.edits.push(NextSibling {}); - } - - pub(crate) fn parent_node(&mut self) { - self.edits.push(ParentNode {}); - } - - pub(crate) fn store_with_id(&mut self, id: u64) { - self.edits.push(StoreWithId { id }); - } - - pub(crate) fn set_last_node(&mut self, id: u64) { - self.edits.push(SetLastNode { id }); - } -} - -// refs are only assigned once -pub struct NodeRefMutation<'a> { - pub element: &'a mut Option>>, - pub element_id: ElementId, -} - -impl<'a> std::fmt::Debug for NodeRefMutation<'a> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("NodeRefMutation") - .field("element_id", &self.element_id) - .finish() - } -} - -impl<'a> NodeRefMutation<'a> { - pub fn downcast_ref(&self) -> Option<&T> { - self.element - .as_ref() - .and_then(|f| f.get()) - .and_then(|f| f.downcast_ref::()) - } - pub fn downcast_mut(&mut self) -> Option<&mut T> { - self.element - .as_mut() - .and_then(|f| f.get_mut()) - .and_then(|f| f.downcast_mut::()) - } -} diff --git a/packages/core/src.old/nodes.old.rs b/packages/core/src.old/nodes.old.rs deleted file mode 100644 index 32c10f070..000000000 --- a/packages/core/src.old/nodes.old.rs +++ /dev/null @@ -1,866 +0,0 @@ -//! Virtual Node Support -//! -//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very* -//! cheap and *very* fast to construct - building a full tree should be quick. -use crate::{ - innerlude::{AttributeValue, ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState}, - lazynodes::LazyNodes, - AnyEvent, Component, -}; -use bumpalo::{boxed::Box as BumpBox, Bump}; -use std::{ - cell::{Cell, RefCell}, - fmt::{Arguments, Debug, Formatter}, -}; - -use crate::template::*; - -/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM. -/// -/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of: -/// -/// - the `rsx!` macro -/// - the [`NodeFactory`] API -pub enum VNode<'src> { - /// Text VNodes are simply bump-allocated (or static) string slices - /// - /// # Example - /// - /// ```rust, ignore - /// let mut vdom = VirtualDom::new(); - /// let node = vdom.render_vnode(rsx!( "hello" )); - /// - /// if let VNode::Text(vtext) = node { - /// assert_eq!(vtext.text, "hello"); - /// assert_eq!(vtext.dom_id.get(), None); - /// assert_eq!(vtext.is_static, true); - /// } - /// ``` - Text(&'src VText<'src>), - - /// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children. - /// - /// # Example - /// - /// ```rust, ignore - /// let mut vdom = VirtualDom::new(); - /// - /// let node = vdom.render_vnode(rsx!{ - /// div { - /// key: "a", - /// onclick: |e| log::info!("clicked"), - /// hidden: "true", - /// style: { background_color: "red" }, - /// "hello" - /// } - /// }); - /// - /// if let VNode::Element(velement) = node { - /// assert_eq!(velement.tag_name, "div"); - /// assert_eq!(velement.namespace, None); - /// assert_eq!(velement.key, Some("a")); - /// } - /// ``` - Element(&'src VElement<'src>), - - /// Fragment nodes may contain many VNodes without a single root. - /// - /// # Example - /// - /// ```rust, ignore - /// rsx!{ - /// a {} - /// link {} - /// style {} - /// "asd" - /// Example {} - /// } - /// ``` - Fragment(&'src VFragment<'src>), - - /// Component nodes represent a mounted component with props, children, and a key. - /// - /// # Example - /// - /// ```rust, ignore - /// fn Example(cx: Scope) -> Element { - /// ... - /// } - /// - /// let mut vdom = VirtualDom::new(); - /// - /// let node = vdom.render_vnode(rsx!( Example {} )); - /// - /// if let VNode::Component(vcomp) = node { - /// assert_eq!(vcomp.user_fc, Example as *const ()); - /// } - /// ``` - Component(&'src VComponent<'src>), - - /// Templates are containers for other nodes - /// - /// - Template(&'src VTemplate<'src>), -} - -impl<'src> VNode<'src> { - /// Get the VNode's "key" used in the keyed diffing algorithm. - pub fn key(&self) -> Option<&'src str> { - match &self { - VNode::Element(el) => el.key, - VNode::Component(c) => c.key, - VNode::Text(_t) => None, - VNode::Template(_t) => None, - VNode::Fragment(_f) => None, - } - } - - /// Get the ElementID of the mounted VNode. - /// - /// Panics if the mounted ID is None or if the VNode is not represented by a single Element. - pub fn mounted_id(&self) -> ElementId { - self.try_mounted_id().unwrap() - } - - /// Try to get the ElementID of the mounted VNode. - /// - /// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component) - pub fn try_mounted_id(&self) -> Option { - match &self { - VNode::Text(el) => el.id.get(), - VNode::Element(el) => el.id.get(), - VNode::Fragment(_) => None, - VNode::Component(_) => None, - VNode::Template(_) => todo!(), - } - } - - // Create an "owned" version of the vnode. - pub(crate) fn decouple(&self) -> VNode<'src> { - match *self { - VNode::Text(t) => VNode::Text(t), - VNode::Element(e) => VNode::Element(e), - VNode::Component(c) => VNode::Component(c), - VNode::Fragment(f) => VNode::Fragment(f), - VNode::Template(_) => todo!(), - } - } -} - -impl Debug for VNode<'_> { - fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - match &self { - VNode::Element(el) => s - .debug_struct("VNode::Element") - .field("name", &el.tag) - .field("key", &el.key) - .field("attrs", &el.attributes) - .field("children", &el.children) - .field("id", &el.id) - .finish(), - VNode::Text(t) => s - .debug_struct("VNode::Text") - .field("text", &t.text) - .field("id", &t.id) - .finish(), - VNode::Fragment(frag) => s - .debug_struct("VNode::Fragment") - .field("children", &frag.children) - .finish(), - VNode::Component(comp) => s - .debug_struct("VNode::Component") - .field("name", &comp.fn_name) - .field("fnptr", &comp.user_fc) - .field("key", &comp.key) - .field("scope", &comp.scope) - .finish(), - VNode::Template(_) => todo!(), - } - } -} - -/// 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 { - /// Convertt the ElementId to a `u64`. - 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 VPlaceholder { - /// The [`ElementId`] of the placeholder. - pub id: Cell>, -} - -/// A bump-allocated string slice and metadata. -pub struct VText<'src> { - /// The [`ElementId`] of the VText. - pub id: Cell>, - - /// The text of the VText. - pub text: &'src str, - - /// An indiciation if this VText can be ignored during diffing - /// Is usually only when there are no strings to be formatted (so the text is &'static str) - pub is_static: bool, -} - -/// A list of VNodes with no single root. -pub struct VFragment<'src> { - /// The key of the fragment to be used during keyed diffing. - pub key: Option<&'src str>, - - pub memo: Option>, - - /// Fragments can never have zero children. Enforced by NodeFactory. - /// - /// You *can* make a fragment with no children, but it's not a valid fragment and your VDom will panic. - pub children: &'src [VNode<'src>], -} - -pub struct FragmentMemo<'a> { - pub template: TemplateDef<'static>, - - pub dynamic_nodes: &'a Cell<[ElementId]>, -} - -/// An element like a "div" with children, listeners, and attributes. -pub struct VElement<'a> { - /// The [`ElementId`] of the VText. - pub id: Cell>, - - /// The key of the element to be used during keyed diffing. - pub key: Option<&'a str>, - - /// The tag name of the element. - /// - /// IE "div" - pub tag: &'static str, - - /// The namespace of the VElement - /// - /// IE "svg" - pub namespace: Option<&'static str>, - - /// The parent of the Element (if any). - /// - /// Used when bubbling events - pub parent: Cell>, - - /// The Listeners of the VElement. - pub listeners: &'a [Listener<'a>], - - /// The attributes of the VElement. - pub attributes: &'a [Attribute<'a>], - - /// The children of the VElement. - pub children: &'a [VNode<'a>], -} - -impl Debug for VElement<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("VElement") - .field("tag_name", &self.tag) - .field("namespace", &self.namespace) - .field("key", &self.key) - .field("id", &self.id) - .field("parent", &self.parent) - .field("listeners", &self.listeners.len()) - .field("attributes", &self.attributes) - .field("children", &self.children) - .finish() - } -} - -/// A trait for any generic Dioxus Element. -/// -/// This trait provides the ability to use custom elements in the `rsx!` macro. -/// -/// ```rust, ignore -/// struct my_element; -/// -/// impl DioxusElement for my_element { -/// const TAG_NAME: "my_element"; -/// const NAME_SPACE: None; -/// } -/// -/// let _ = rsx!{ -/// my_element {} -/// }; -/// ``` -pub trait DioxusElement { - /// The tag name of the element. - const TAG_NAME: &'static str; - - /// The namespace of the element. - const NAME_SPACE: Option<&'static str>; - - /// The tag name of the element. - #[inline] - fn tag_name(&self) -> &'static str { - Self::TAG_NAME - } - - /// The namespace of the element. - #[inline] - fn namespace(&self) -> Option<&'static str> { - Self::NAME_SPACE - } -} - -/// An attribute on a DOM node, such as `id="my-thing"` or -/// `href="https://example.com"`. -#[derive(Clone, Debug)] -pub struct Attribute<'a> { - /// The name of the attribute. - pub name: &'static str, - - /// The value of the attribute. - pub value: AttributeValue<'a>, - - /// An indication if this attribute can be ignored during diffing - /// - /// Usually only when there are no strings to be formatted (so the value is &'static str) - pub is_static: bool, - - /// An indication of we should always try and set the attribute. - /// Used in controlled components to ensure changes are propagated. - pub is_volatile: bool, - - /// The namespace of the attribute. - /// - /// Doesn't exist in the html spec. - /// Used in Dioxus to denote "style" tags and other attribute groups. - pub namespace: Option<&'static str>, -} - -/// An event listener. -/// IE onclick, onkeydown, etc -pub struct Listener<'bump> { - /// The ID of the node that this listener is mounted to - /// Used to generate the event listener's ID on the DOM - pub mounted_node: Cell>, - - /// The type of event to listen for. - /// - /// IE "click" - whatever the renderer needs to attach the listener by name. - pub event: &'static str, - - /// The actual callback that the user specified - pub(crate) callback: InternalHandler<'bump>, -} - -pub type InternalHandler<'bump> = &'bump RefCell>>; -type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>; -type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>; - -/// The callback type generated by the `rsx!` macro when an `on` field is specified for components. -/// -/// This makes it possible to pass `move |evt| {}` style closures into components as property fields. -/// -/// -/// # Example -/// -/// ```rust, ignore -/// -/// rsx!{ -/// MyComponent { onclick: move |evt| log::info!("clicked"), } -/// } -/// -/// #[derive(Props)] -/// struct MyProps<'a> { -/// onclick: EventHandler<'a, MouseEvent>, -/// } -/// -/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element { -/// cx.render(rsx!{ -/// button { -/// onclick: move |evt| cx.props.onclick.call(evt), -/// } -/// }) -/// } -/// -/// ``` -pub struct EventHandler<'bump, T = ()> { - /// The (optional) callback that the user specified - /// Uses a `RefCell` to allow for interior mutability, and FnMut closures. - pub callback: RefCell>>, -} - -impl<'a, T> Default for EventHandler<'a, T> { - fn default() -> Self { - Self { - callback: RefCell::new(None), - } - } -} - -impl EventHandler<'_, T> { - /// Call this event handler with the appropriate event type - pub fn call(&self, event: T) { - if let Some(callback) = self.callback.borrow_mut().as_mut() { - callback(event); - } - } - - /// Forcibly drop the internal handler callback, releasing memory - pub fn release(&self) { - self.callback.replace(None); - } -} - -/// Virtual Components for custom user-defined components -/// Only supports the functional syntax -pub struct VComponent<'src> { - /// The key of the component to be used during keyed diffing. - pub key: Option<&'src str>, - - /// The ID of the component. - /// Will not be assigned until after the component has been initialized. - pub scope: Cell>, - - /// An indication if the component is static (can be memozied) - pub can_memoize: bool, - - /// The function pointer to the component's render function. - pub user_fc: ComponentPtr, - - /// The actual name of the component. - pub fn_name: &'static str, - - /// The props of the component. - pub props: RefCell>>, -} - -pub(crate) struct VComponentProps

{ - pub render_fn: Component

, - pub memo: unsafe fn(&P, &P) -> bool, - pub props: P, -} - -pub trait AnyProps { - fn as_ptr(&self) -> *const (); - fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>; - unsafe fn memoize(&self, other: &dyn AnyProps) -> bool; -} - -impl

AnyProps for VComponentProps

{ - fn as_ptr(&self) -> *const () { - &self.props as *const _ as *const () - } - - // Safety: - // this will downcast the other ptr as our swallowed type! - // you *must* make this check *before* calling this method - // if your functions are not the same, then you will downcast a pointer into a different type (UB) - unsafe fn memoize(&self, other: &dyn AnyProps) -> bool { - let real_other: &P = &*(other.as_ptr() as *const _ as *const P); - let real_us: &P = &*(self.as_ptr() as *const _ as *const P); - (self.memo)(real_us, real_other) - } - - fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> { - let props = unsafe { std::mem::transmute::<&P, &P>(&self.props) }; - (self.render_fn)(Scope { scope, props }) - } -} - -/// This struct provides an ergonomic API to quickly build VNodes. -/// -/// NodeFactory is used to build VNodes in the component's memory space. -/// This struct adds metadata to the final VNode about listeners, attributes, and children -#[derive(Copy, Clone)] -pub struct NodeFactory<'a> { - pub(crate) scope: &'a ScopeState, - pub(crate) bump: &'a Bump, -} - -impl<'a> NodeFactory<'a> { - /// Create a new [`NodeFactory`] from a [`Scope`] or [`ScopeState`] - pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> { - NodeFactory { - scope, - bump: &scope.wip_frame().bump, - } - } - - /// Get the custom allocator for this component - #[inline] - pub fn bump(&self) -> &'a bumpalo::Bump { - self.bump - } - - /// Directly pass in text blocks without the need to use the format_args macro. - pub fn static_text(&self, text: &'static str) -> VNode<'a> { - VNode::Text(self.bump.alloc(VText { - id: empty_cell(), - text, - is_static: true, - })) - } - - /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static - /// - /// Text that's static may be pointer compared, making it cheaper to diff - pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) { - match args.as_str() { - Some(static_str) => (static_str, true), - None => { - use bumpalo::core_alloc::fmt::Write; - let mut str_buf = bumpalo::collections::String::new_in(self.bump); - str_buf.write_fmt(args).unwrap(); - (str_buf.into_bump_str(), false) - } - } - } - - /// Create some text that's allocated along with the other vnodes - /// - pub fn text(&self, args: Arguments) -> VNode<'a> { - let (text, is_static) = self.raw_text(args); - - VNode::Text(self.bump.alloc(VText { - text, - is_static, - id: empty_cell(), - })) - } - - /// Create a new [`VNode::Element`] - pub fn element( - &self, - el: impl DioxusElement, - listeners: &'a [Listener<'a>], - attributes: &'a [Attribute<'a>], - children: &'a [VNode<'a>], - key: Option, - ) -> VNode<'a> { - self.raw_element( - el.tag_name(), - el.namespace(), - listeners, - attributes, - children, - key, - ) - } - - /// Create a new [`VNode::Element`] without the trait bound - /// - /// IE pass in "div" instead of `div` - pub fn raw_element( - &self, - tag_name: &'static str, - namespace: Option<&'static str>, - listeners: &'a [Listener<'a>], - attributes: &'a [Attribute<'a>], - children: &'a [VNode<'a>], - key: Option, - ) -> VNode<'a> { - let key = key.map(|f| self.raw_text(f).0); - - let mut items = self.scope.items.borrow_mut(); - for listener in listeners { - let long_listener = unsafe { std::mem::transmute(listener) }; - items.listeners.push(long_listener); - } - - VNode::Element(self.bump.alloc(VElement { - tag: tag_name, - key, - namespace, - listeners, - attributes, - children, - id: empty_cell(), - parent: empty_cell(), - })) - } - - /// Create a new [`Attribute`] - pub fn attr( - &self, - name: &'static str, - val: Arguments, - namespace: Option<&'static str>, - is_volatile: bool, - ) -> Attribute<'a> { - let (value, is_static) = self.raw_text(val); - Attribute { - name, - value: AttributeValue::Text(value), - is_static, - namespace, - is_volatile, - } - } - - /// Create a new [`Attribute`] using non-arguments - pub fn custom_attr( - &self, - name: &'static str, - value: AttributeValue<'a>, - namespace: Option<&'static str>, - is_volatile: bool, - is_static: bool, - ) -> Attribute<'a> { - Attribute { - name, - value, - is_static, - namespace, - is_volatile, - } - } - - /// Create a new [`VNode::Component`] - pub fn component

( - &self, - component: fn(Scope<'a, P>) -> Element, - props: P, - key: Option, - fn_name: &'static str, - ) -> VNode<'a> - where - P: Properties + 'a, - { - let vcomp = self.bump.alloc(VComponent { - key: key.map(|f| self.raw_text(f).0), - scope: Default::default(), - can_memoize: P::IS_STATIC, - user_fc: component as ComponentPtr, - fn_name, - props: RefCell::new(Some(Box::new(VComponentProps { - props, - memo: P::memoize, // smuggle the memoization function across borders - - // i'm sorry but I just need to bludgeon the lifetimes into place here - // this is safe because we're managing all lifetimes to originate from previous calls - // the intricacies of Rust's lifetime system make it difficult to properly express - // the transformation from this specific lifetime to the for<'a> lifetime - render_fn: unsafe { std::mem::transmute(component) }, - }))), - }); - - if !P::IS_STATIC { - let vcomp = &*vcomp; - let vcomp = unsafe { std::mem::transmute(vcomp) }; - self.scope.items.borrow_mut().borrowed_props.push(vcomp); - } - - VNode::Component(vcomp) - } - - /// Create a new [`Listener`] - pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> { - Listener { - event, - mounted_node: Cell::new(None), - callback, - } - } - - pub fn template(&self, def: &'static TemplateDef, dynamic_nodes: &'a [VNode<'a>]) -> VNode<'a> { - VNode::Template(self.bump.alloc(VTemplate { - def, - dynamic_nodes, - rendered_nodes: &[], - })) - } - - // /// Create a new [`VNode::Fragment`] from a root of the rsx! call - // pub fn fragment_root<'b, 'c>( - // self, - // node_iter: impl IntoIterator + 'c> + 'b, - // ) -> VNode<'a> { - // let mut nodes = bumpalo::collections::Vec::new_in(self.bump); - - // for node in node_iter { - // nodes.push(node.into_vnode(self)); - // } - - // if nodes.is_empty() { - // VNode::Placeholder(self.bump.alloc(VPlaceholder { id: empty_cell() })) - // } else { - // VNode::Fragment(self.bump.alloc(VFragment { - // children: nodes.into_bump_slice(), - // key: None, - // })) - // } - // } - - // /// Create a new [`VNode::Fragment`] from any iterator - // pub fn fragment_from_iter<'c, I, J>( - // self, - // node_iter: impl IntoVNode<'a, I, J> + 'c, - // ) -> VNode<'a> { - // node_iter.into_vnode(self) - // } - - // /// Create a new [`VNode`] from any iterator of children - // pub fn create_children( - // self, - // node_iter: impl IntoIterator>, - // ) -> Element<'a> { - // let mut nodes = bumpalo::collections::Vec::new_in(self.bump); - - // for node in node_iter { - // nodes.push(node.into_vnode(self)); - // } - - // if nodes.is_empty() { - // Some(VNode::Placeholder( - // self.bump.alloc(VPlaceholder { id: empty_cell() }), - // )) - // } else { - // let children = nodes.into_bump_slice(); - - // Some(VNode::Fragment(self.bump.alloc(VFragment { - // children, - // key: None, - // }))) - // } - // } - - /// Create a new [`EventHandler`] from an [`FnMut`] - pub fn event_handler(self, f: impl FnMut(T) + 'a) -> EventHandler<'a, T> { - let handler: &mut dyn FnMut(T) = self.bump.alloc(f); - let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) }; - let callback = RefCell::new(Some(caller)); - EventHandler { callback } - } -} - -impl Debug for NodeFactory<'_> { - fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} - -/// Trait implementations for use in the rsx! and html! macros. -/// -/// ## Details -/// -/// This section provides convenience methods and trait implementations for converting common structs into a format accepted -/// by the macros. -/// -/// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out. -/// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type: -/// ```rust, ignore -/// impl IntoIterator> -/// ``` -/// -/// As such, all node creation must go through the factory, which is only available in the component context. -/// These strict requirements make it possible to manage lifetimes and state. -pub trait IntoVNode<'a, I = (), J = ()> { - /// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; -} - -// TODO: do we even need this? It almost seems better not to -// // For the case where a rendered VNode is passed into the rsx! macro through curly braces -impl<'a> IntoVNode<'a> for VNode<'a> { - fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { - self - } -} - -// Conveniently, we also support "null" (nothing) passed in -impl IntoVNode<'_> for () { - fn into_vnode(self, cx: NodeFactory) -> VNode { - todo!() - // VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() })) - } -} - -impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - self.call(cx) - } -} - -impl<'b> IntoVNode<'_> for &'b str { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.text(format_args!("{}", self)) - } -} - -impl IntoVNode<'_> for String { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.text(format_args!("{}", self)) - } -} - -impl IntoVNode<'_> for Arguments<'_> { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.text(self) - } -} - -impl<'a> IntoVNode<'a> for &VNode<'a> { - fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> { - // borrowed nodes are strange - self.decouple() - } -} - -// Note that we're using the E as a generic but this is never crafted anyways. -pub struct FromNodeIterator; -impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T -where - T: IntoIterator, - I: IntoVNode<'a, E>, -{ - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - let mut nodes = bumpalo::collections::Vec::new_in(cx.bump); - - for node in self { - nodes.push(node.into_vnode(cx)); - } - - todo!() - // if nodes.is_empty() { - // VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() })) - // } else { - // let children = nodes.into_bump_slice(); - - // if cfg!(debug_assertions) - // && children.len() > 1 - // && children.last().unwrap().key().is_none() - // { - // // todo: make the backtrace prettier or remove it altogether - // log::error!( - // r#" - // Warning: Each child in an array or iterator should have a unique "key" prop. - // Not providing a key will lead to poor performance with lists. - // See docs.rs/dioxus for more information. - // ------------- - // {:?} - // "#, - // backtrace::Backtrace::new() - // ); - // } - - // VNode::Fragment(cx.bump.alloc(VFragment { - // children, - // key: None, - // })) - // } - } -} diff --git a/packages/core/src.old/nodes/arbitrary_value.rs b/packages/core/src.old/nodes/arbitrary_value.rs deleted file mode 100644 index f890936ac..000000000 --- a/packages/core/src.old/nodes/arbitrary_value.rs +++ /dev/null @@ -1,163 +0,0 @@ -use std::{ - any::Any, - fmt::{Arguments, Formatter}, -}; - -use bumpalo::Bump; - -/// Possible values for an attribute -#[derive(Clone, Copy)] -pub enum AttributeValue<'a> { - /// Reference strs, most common - Text(&'a str), - /// Basic float values - Float(f32), - /// Basic Int values - Int(i32), - /// Basic bool values - Bool(bool), - /// Everything else - Any(&'a dyn AnyAttributeValue), -} - -impl<'a> PartialEq for AttributeValue<'a> { - fn eq(&self, other: &Self) -> bool { - match (self, other) { - (Self::Text(l0), Self::Text(r0)) => l0 == r0, - (Self::Float(l0), Self::Float(r0)) => l0 == r0, - (Self::Int(l0), Self::Int(r0)) => l0 == r0, - (Self::Bool(l0), Self::Bool(r0)) => l0 == r0, - (Self::Any(l0), Self::Any(r0)) => (*l0).cmp_any(*r0), - _ => false, - } - } -} - -impl std::fmt::Debug for AttributeValue<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - match self { - AttributeValue::Text(s) => write!(f, "AttributeValue::Text({:?})", s), - AttributeValue::Float(v) => write!(f, "AttributeValue::Float({:?})", v), - AttributeValue::Int(v) => write!(f, "AttributeValue::Int({:?})", v), - AttributeValue::Bool(b) => write!(f, "AttributeValue::Bool({:?})", b), - AttributeValue::Any(_) => write!(f, "AttributeValue::Any()"), - } - } -} - -/// A value that can be converted into an attribute value -pub trait IntoAttributeValue<'a> { - /// Convert into an attribute value - fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>; -} - -impl<'a> IntoAttributeValue<'a> for &'a str { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Text(self) - } -} -impl<'a> IntoAttributeValue<'a> for f32 { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Float(self) - } -} -impl<'a> IntoAttributeValue<'a> for i32 { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Int(self) - } -} -impl<'a> IntoAttributeValue<'a> for bool { - fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { - AttributeValue::Bool(self) - } -} -impl<'a> IntoAttributeValue<'a> for Arguments<'_> { - fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> { - use bumpalo::core_alloc::fmt::Write; - let mut str_buf = bumpalo::collections::String::new_in(bump); - str_buf.write_fmt(self).unwrap(); - AttributeValue::Text(str_buf.into_bump_str()) - } -} - -// todo -#[allow(missing_docs)] -impl<'a> AttributeValue<'a> { - pub fn is_truthy(&self) -> bool { - match self { - AttributeValue::Text(t) => *t == "true", - AttributeValue::Bool(t) => *t, - _ => false, - } - } - - pub fn is_falsy(&self) -> bool { - match self { - AttributeValue::Text(t) => *t == "false", - AttributeValue::Bool(t) => !(*t), - _ => false, - } - } -} - -/// A trait that allows for comparing two values of the same type through the Any trait -/// -/// Defaults to false if the types are not the same -/// -/// This is an implicit trait, so any value that is 'static and PartialEq can be used directly -/// -/// If you want to override the default behavior, you should implement PartialEq through a wrapper type -pub trait AnyAttributeValue: Any { - /// Perform a comparison between two values - fn cmp_any<'a>(&'a self, _other: &'a dyn AnyAttributeValue) -> bool { - false - } -} - -impl AnyAttributeValue for T { - fn cmp_any(&self, other: &dyn AnyAttributeValue) -> bool { - // we can't, for whatever reason use other as &dyn Any - let right: &dyn Any = unsafe { std::mem::transmute(other) }; - - match right.downcast_ref::() { - Some(right) => self == right, - None => false, - } - } -} - -#[test] -fn cmp_any_works_even_though_it_transmutes() { - // same type, same value - let a = 2; - let b = 2; - assert!(a.cmp_any(&b as &dyn AnyAttributeValue)); - - // same type, different value - let a = "asds"; - let b = "asdsasd"; - assert!(!a.cmp_any(&b as &dyn AnyAttributeValue)); - - // different type, different values - let a = 123; - let b = "asdsasd"; - assert!(!a.cmp_any(&b as &dyn AnyAttributeValue)); - - // Custom structs - #[derive(PartialEq)] - struct CustomStruct { - a: i32, - } - - let a = CustomStruct { a: 1 }; - let b = CustomStruct { a: 1 }; - assert!(a.cmp_any(&b as &dyn AnyAttributeValue)); - - let a = CustomStruct { a: 1 }; - let b = CustomStruct { a: 123 }; - assert!(!a.cmp_any(&b as &dyn AnyAttributeValue)); - - let a = CustomStruct { a: 1 }; - let b = "asdasd"; - assert!(!a.cmp_any(&b as &dyn AnyAttributeValue)); -} diff --git a/packages/core/src.old/nodes/component.rs b/packages/core/src.old/nodes/component.rs deleted file mode 100644 index f32a30ac7..000000000 --- a/packages/core/src.old/nodes/component.rs +++ /dev/null @@ -1,61 +0,0 @@ -use crate::{ - innerlude::{ComponentPtr, Element, Scope, ScopeId, ScopeState}, - Component, -}; -use std::cell::{Cell, RefCell}; - -/// Virtual Components for custom user-defined components -/// Only supports the functional syntax -pub struct VComponent<'src> { - /// The key of the component to be used during keyed diffing. - pub key: Option<&'src str>, - - /// The ID of the component. - /// Will not be assigned until after the component has been initialized. - pub scope: Cell>, - - /// An indication if the component is static (can be memozied) - pub can_memoize: bool, - - /// The function pointer to the component's render function. - pub user_fc: ComponentPtr, - - /// The actual name of the component. - pub fn_name: &'static str, - - /// The props of the component. - pub props: RefCell>>, -} - -pub(crate) struct VComponentProps

{ - pub render_fn: Component

, - pub memo: unsafe fn(&P, &P) -> bool, - pub props: P, -} - -pub trait AnyProps { - fn as_ptr(&self) -> *const (); - fn render<'a>(&'a self, bump: &'a ScopeState) -> Element<'a>; - unsafe fn memoize(&self, other: &dyn AnyProps) -> bool; -} - -impl

AnyProps for VComponentProps

{ - fn as_ptr(&self) -> *const () { - &self.props as *const _ as *const () - } - - // Safety: - // this will downcast the other ptr as our swallowed type! - // you *must* make this check *before* calling this method - // if your functions are not the same, then you will downcast a pointer into a different type (UB) - unsafe fn memoize(&self, other: &dyn AnyProps) -> bool { - let real_other: &P = &*(other.as_ptr() as *const _ as *const P); - let real_us: &P = &*(self.as_ptr() as *const _ as *const P); - (self.memo)(real_us, real_other) - } - - fn render<'a>(&'a self, scope: &'a ScopeState) -> Element<'a> { - let props = unsafe { std::mem::transmute::<&P, &P>(&self.props) }; - (self.render_fn)(Scope { scope, props }) - } -} diff --git a/packages/core/src.old/nodes/element.rs b/packages/core/src.old/nodes/element.rs deleted file mode 100644 index bbe42f91e..000000000 --- a/packages/core/src.old/nodes/element.rs +++ /dev/null @@ -1,150 +0,0 @@ -use crate::{innerlude::AttributeValue, AnyEvent, ElementId, VNode}; -use bumpalo::boxed::Box as BumpBox; -use std::{ - cell::{Cell, RefCell}, - fmt::{Debug, Formatter}, -}; - -/// An element like a "div" with children, listeners, and attributes. -pub struct VElement<'a> { - /// The [`ElementId`] of the VText. - pub id: Cell>, - - /// The key of the element to be used during keyed diffing. - pub key: Option<&'a str>, - - /// The tag name of the element. - /// - /// IE "div" - pub tag: &'static str, - - /// The namespace of the VElement - /// - /// IE "svg" - pub namespace: Option<&'static str>, - - /// The parent of the Element (if any). - /// - /// Used when bubbling events - pub parent: Cell>, - - /// The Listeners of the VElement. - pub listeners: &'a [Listener<'a>], - - /// The attributes of the VElement. - pub attributes: &'a [Attribute<'a>], - - /// The children of the VElement. - pub children: &'a [VNode<'a>], -} - -impl Debug for VElement<'_> { - fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - f.debug_struct("VElement") - .field("tag_name", &self.tag) - .field("namespace", &self.namespace) - .field("key", &self.key) - .field("id", &self.id) - .field("parent", &self.parent) - .field("listeners", &self.listeners.len()) - .field("attributes", &self.attributes) - .field("children", &self.children) - .finish() - } -} - -/// An attribute on a DOM node, such as `id="my-thing"` or -/// `href="https://example.com"`. -#[derive(Clone, Debug)] -pub struct Attribute<'a> { - /// The name of the attribute. - pub name: &'static str, - - /// The namespace of the attribute. - /// - /// Doesn't exist in the html spec. - /// Used in Dioxus to denote "style" tags and other attribute groups. - pub namespace: Option<&'static str>, - - /// An indication of we should always try and set the attribute. - /// Used in controlled components to ensure changes are propagated. - pub volatile: bool, - - /// The value of the attribute. - pub value: AttributeValue<'a>, -} - -/// An event listener. -/// IE onclick, onkeydown, etc -pub struct Listener<'bump> { - /// The ID of the node that this listener is mounted to - /// Used to generate the event listener's ID on the DOM - pub mounted_node: Cell, - - /// The type of event to listen for. - /// - /// IE "click" - whatever the renderer needs to attach the listener by name. - pub event: &'static str, - - /// The actual callback that the user specified - pub(crate) callback: InternalHandler<'bump>, -} - -pub type InternalHandler<'bump> = &'bump RefCell>>; -type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(AnyEvent) + 'bump>; -type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>; - -/// The callback type generated by the `rsx!` macro when an `on` field is specified for components. -/// -/// This makes it possible to pass `move |evt| {}` style closures into components as property fields. -/// -/// -/// # Example -/// -/// ```rust, ignore -/// -/// rsx!{ -/// MyComponent { onclick: move |evt| log::info!("clicked"), } -/// } -/// -/// #[derive(Props)] -/// struct MyProps<'a> { -/// onclick: EventHandler<'a, MouseEvent>, -/// } -/// -/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element { -/// cx.render(rsx!{ -/// button { -/// onclick: move |evt| cx.props.onclick.call(evt), -/// } -/// }) -/// } -/// -/// ``` -pub struct EventHandler<'bump, T = ()> { - /// The (optional) callback that the user specified - /// Uses a `RefCell` to allow for interior mutability, and FnMut closures. - pub callback: RefCell>>, -} - -impl<'a, T> Default for EventHandler<'a, T> { - fn default() -> Self { - Self { - callback: RefCell::new(None), - } - } -} - -impl EventHandler<'_, T> { - /// Call this event handler with the appropriate event type - pub fn call(&self, event: T) { - if let Some(callback) = self.callback.borrow_mut().as_mut() { - callback(event); - } - } - - /// Forcibly drop the internal handler callback, releasing memory - pub fn release(&self) { - self.callback.replace(None); - } -} diff --git a/packages/core/src.old/nodes/factory.rs b/packages/core/src.old/nodes/factory.rs deleted file mode 100644 index a336d7dca..000000000 --- a/packages/core/src.old/nodes/factory.rs +++ /dev/null @@ -1,362 +0,0 @@ -use crate::{innerlude::*, Attribute, Listener, VElement, VNode, VText}; -use bumpalo::{boxed::Box as BumpBox, Bump}; -use std::{ - cell::{Cell, RefCell}, - fmt::{Arguments, Debug}, -}; - -/// This struct provides an ergonomic API to quickly build VNodes. -/// -/// NodeFactory is used to build VNodes in the component's memory space. -/// This struct adds metadata to the final VNode about listeners, attributes, and children -#[derive(Copy, Clone)] -pub struct NodeFactory<'a> { - pub(crate) scope: &'a ScopeState, - pub(crate) bump: &'a Bump, -} - -impl<'a> NodeFactory<'a> { - /// Create a new [`NodeFactory`] from a [`Scope`] or [`ScopeState`] - pub fn new(scope: &'a ScopeState) -> NodeFactory<'a> { - NodeFactory { - scope, - bump: &scope.wip_frame().bump, - } - } - - /// Get the custom allocator for this component - #[inline] - pub fn bump(&self) -> &'a bumpalo::Bump { - self.bump - } - - /// Directly pass in text blocks without the need to use the format_args macro. - pub fn static_text(&self, text: &'static str) -> VNode<'a> { - VNode::Text(self.bump.alloc(VText { - id: Default::default(), - text, - })) - } - - /// Parses a lazy text Arguments and returns a string and a flag indicating if the text is 'static - /// - /// Text that's static may be pointer compared, making it cheaper to diff - pub fn raw_text(&self, args: Arguments) -> (&'a str, bool) { - match args.as_str() { - Some(static_str) => (static_str, true), - None => { - use bumpalo::core_alloc::fmt::Write; - let mut str_buf = bumpalo::collections::String::new_in(self.bump); - str_buf.write_fmt(args).unwrap(); - (str_buf.into_bump_str(), false) - } - } - } - - /// Create some text that's allocated along with the other vnodes - /// - pub fn text(&self, args: Arguments) -> VNode<'a> { - let (text, _is_static) = self.raw_text(args); - - VNode::Text(self.bump.alloc(VText { - text, - id: Default::default(), - })) - } - - /// Create a new [`VNode::Element`] without the trait bound - /// - /// IE pass in "div" instead of `div` - pub fn raw_element( - &self, - tag_name: &'static str, - namespace: Option<&'static str>, - listeners: &'a [Listener<'a>], - attributes: &'a [Attribute<'a>], - children: &'a [VNode<'a>], - key: Option, - ) -> VNode<'a> { - let key = key.map(|f| self.raw_text(f).0); - - let mut items = self.scope.items.borrow_mut(); - for listener in listeners { - let long_listener = unsafe { std::mem::transmute(listener) }; - items.listeners.push(long_listener); - } - - VNode::Element(self.bump.alloc(VElement { - tag: tag_name, - key, - namespace, - listeners, - attributes, - children, - id: Default::default(), - parent: Default::default(), - })) - } - - /// Create a new [`Attribute`] - pub fn attr( - &self, - name: &'static str, - val: impl IntoAttributeValue<'a>, - namespace: Option<&'static str>, - is_volatile: bool, - ) -> Attribute<'a> { - Attribute { - name, - namespace, - volatile: is_volatile, - value: val.into_value(self.bump), - } - } - - /// Create a new [`Attribute`] using non-arguments - pub fn custom_attr( - &self, - name: &'static str, - value: AttributeValue<'a>, - namespace: Option<&'static str>, - is_volatile: bool, - ) -> Attribute<'a> { - Attribute { - name, - namespace, - volatile: is_volatile, - value, - } - } - - /// Create a new [`VNode::Component`] - pub fn component

( - &self, - component: fn(Scope<'a, P>) -> Element, - props: P, - key: Option, - fn_name: &'static str, - ) -> VNode<'a> - where - P: Properties + 'a, - { - let vcomp = self.bump.alloc(VComponent { - key: key.map(|f| self.raw_text(f).0), - scope: Default::default(), - can_memoize: P::IS_STATIC, - user_fc: component as ComponentPtr, - fn_name, - props: RefCell::new(Some(Box::new(VComponentProps { - props, - memo: P::memoize, // smuggle the memoization function across borders - - // i'm sorry but I just need to bludgeon the lifetimes into place here - // this is safe because we're managing all lifetimes to originate from previous calls - // the intricacies of Rust's lifetime system make it difficult to properly express - // the transformation from this specific lifetime to the for<'a> lifetime - render_fn: unsafe { std::mem::transmute(component) }, - }))), - }); - - if !P::IS_STATIC { - let vcomp = &*vcomp; - let vcomp = unsafe { std::mem::transmute(vcomp) }; - self.scope.items.borrow_mut().borrowed_props.push(vcomp); - } - - VNode::Component(vcomp) - } - - /// Create a new [`Listener`] - pub fn listener(self, event: &'static str, callback: InternalHandler<'a>) -> Listener<'a> { - Listener { - event, - mounted_node: Cell::new(ElementId(0)), - callback, - } - } - - /// Create a new [`VNode::Fragment`] from a root of the rsx! call - pub fn fragment_root<'b, 'c>( - self, - node_iter: impl IntoIterator + 'c> + 'b, - ) -> VNode<'a> { - let mut nodes = bumpalo::collections::Vec::new_in(self.bump); - - for node in node_iter { - nodes.push(node.into_vnode(self)); - } - - VNode::Fragment(self.bump.alloc(VFragment { - children: nodes.into_bump_slice(), - placeholder: Default::default(), - key: None, - })) - } - - /// Create a new [`VNode::Fragment`] from any iterator - pub fn fragment_from_iter<'c, I, J>( - self, - node_iter: impl IntoVNode<'a, I, J> + 'c, - ) -> VNode<'a> { - node_iter.into_vnode(self) - } - - /// Create a new [`VNode`] from any iterator of children - pub fn create_children( - self, - node_iter: impl IntoIterator>, - ) -> Element<'a> { - let mut nodes = bumpalo::collections::Vec::new_in(self.bump); - - for node in node_iter { - nodes.push(node.into_vnode(self)); - } - - let children = nodes.into_bump_slice(); - - Some(VNode::Fragment(self.bump.alloc(VFragment { - children, - key: None, - placeholder: Default::default(), - }))) - } - - /// Create a new [`EventHandler`] from an [`FnMut`] - pub fn event_handler(self, f: impl FnMut(T) + 'a) -> EventHandler<'a, T> { - let handler: &mut dyn FnMut(T) = self.bump.alloc(f); - let caller = unsafe { BumpBox::from_raw(handler as *mut dyn FnMut(T)) }; - let callback = RefCell::new(Some(caller)); - EventHandler { callback } - } - - /// Create a refrence to a template - pub fn template_ref( - &self, - template: Template<'static>, - nodes: &'a [VNode<'a>], - attributes: &'a [Attribute<'a>], - listeners: &'a [Listener<'a>], - key: Option, - ) -> VNode<'a> { - VNode::Template( - self.bump.alloc(VTemplate { - key: None, - node_id: Cell::new(ElementId(0)), - template, - dynamic_nodes: self.bump.alloc([]), - dynamic_attrs: self.bump.alloc([]), - listeners, - root_ids: - bumpalo::vec![in self.bump; Cell::new(ElementId(0)); template.roots.len()] - .into_bump_slice(), - }), - ) - } -} - -impl Debug for NodeFactory<'_> { - fn fmt(&self, _: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - Ok(()) - } -} - -/// Trait implementations for use in the rsx! and html! macros. -/// -/// ## Details -/// -/// This section provides convenience methods and trait implementations for converting common structs into a format accepted -/// by the macros. -/// -/// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out. -/// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type: -/// ```rust, ignore -/// impl IntoIterator> -/// ``` -/// -/// As such, all node creation must go through the factory, which is only available in the component context. -/// These strict requirements make it possible to manage lifetimes and state. -pub trait IntoVNode<'a, I = (), J = ()> { - /// Convert this into a [`VNode`], using the [`NodeFactory`] as a source of allocation - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; -} - -// TODO: do we even need this? It almost seems better not to -// // For the case where a rendered VNode is passed into the rsx! macro through curly braces -impl<'a> IntoVNode<'a> for VNode<'a> { - fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { - self - } -} - -impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, '_> { - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - self.call(cx) - } -} - -impl<'b> IntoVNode<'_> for &'b str { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.text(format_args!("{}", self)) - } -} - -impl IntoVNode<'_> for String { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.text(format_args!("{}", self)) - } -} - -impl IntoVNode<'_> for Arguments<'_> { - fn into_vnode(self, cx: NodeFactory) -> VNode { - cx.text(self) - } -} - -impl<'a> IntoVNode<'a> for &VNode<'a> { - fn into_vnode(self, _cx: NodeFactory<'a>) -> VNode<'a> { - // borrowed nodes are strange - self.decouple() - } -} - -// Note that we're using the E as a generic but this is never crafted anyways. -pub struct FromNodeIterator; -impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T -where - T: IntoIterator, - I: IntoVNode<'a, E>, -{ - fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> { - let mut nodes = bumpalo::collections::Vec::new_in(cx.bump); - - for node in self { - nodes.push(node.into_vnode(cx)); - } - - let children = nodes.into_bump_slice(); - - if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none() - { - // let bt = backtrace::Backtrace::new(); - let bt = "no backtrace available"; - - // todo: make the backtrace prettier or remove it altogether - log::error!( - r#" - Warning: Each child in an array or iterator should have a unique "key" prop. - Not providing a key will lead to poor performance with lists. - See docs.rs/dioxus for more information. - ------------- - {:?} - "#, - bt - ); - } - - VNode::Fragment(cx.bump.alloc(VFragment { - children, - placeholder: Default::default(), - key: None, - })) - } -} diff --git a/packages/core/src.old/nodes/fragment.rs b/packages/core/src.old/nodes/fragment.rs deleted file mode 100644 index 15317fb42..000000000 --- a/packages/core/src.old/nodes/fragment.rs +++ /dev/null @@ -1,16 +0,0 @@ -use crate::{ElementId, VNode}; -use std::cell::Cell; - -/// A list of VNodes with no single root. -pub struct VFragment<'src> { - /// The key of the fragment to be used during keyed diffing. - pub key: Option<&'src str>, - - /// The [`ElementId`] of the placeholder if it exists - pub placeholder: Cell>, - - /// Fragments can never have zero children. Enforced by NodeFactory. - /// - /// You *can* make a fragment with no children, but it's not a valid fragment and your VDom will panic. - pub children: &'src [VNode<'src>], -} diff --git a/packages/core/src.old/nodes/mod.old.rs b/packages/core/src.old/nodes/mod.old.rs deleted file mode 100644 index 7ff6a9136..000000000 --- a/packages/core/src.old/nodes/mod.old.rs +++ /dev/null @@ -1,207 +0,0 @@ -//! Virtual Node Support -//! -//! VNodes represent lazily-constructed VDom trees that support diffing and event handlers. These VNodes should be *very* -//! cheap and *very* fast to construct - building a full tree should be quick. - -use std::fmt::{Debug, Formatter}; - -mod arbitrary_value; -mod component; -mod element; -mod factory; -mod fragment; -mod placeholder; -mod suspense; -mod template; -mod text; - -pub use arbitrary_value::*; -pub use component::*; -pub use element::*; -pub use factory::*; -pub use fragment::*; -pub use suspense::*; -pub use template::*; -pub use text::*; - -use self::placeholder::VPlaceholder; - -/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM. -/// -/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of: -/// -/// - the `rsx!` macro -/// - the [`NodeFactory`] API -pub enum VNode<'src> { - /// Text VNodes are simply bump-allocated (or static) string slices - /// - /// # Example - /// - /// ```rust, ignore - /// let mut vdom = VirtualDom::new(); - /// let node = vdom.render_vnode(rsx!( "hello" )); - /// - /// if let VNode::Text(vtext) = node { - /// assert_eq!(vtext.text, "hello"); - /// assert_eq!(vtext.dom_id.get(), None); - /// assert_eq!(vtext.is_static, true); - /// } - /// ``` - Text(&'src VText<'src>), - - /// Element VNodes are VNodes that may contain attributes, listeners, a key, a tag, and children. - /// - /// # Example - /// - /// ```rust, ignore - /// let mut vdom = VirtualDom::new(); - /// - /// let node = vdom.render_vnode(rsx!{ - /// div { - /// key: "a", - /// onclick: |e| log::info!("clicked"), - /// hidden: "true", - /// style: { background_color: "red" }, - /// "hello" - /// } - /// }); - /// - /// if let VNode::Element(velement) = node { - /// assert_eq!(velement.tag_name, "div"); - /// assert_eq!(velement.namespace, None); - /// assert_eq!(velement.key, Some("a")); - /// } - /// ``` - Element(&'src VElement<'src>), - - /// Fragment nodes may contain many VNodes without a single root. - /// - /// # Example - /// - /// ```rust, ignore - /// rsx!{ - /// a {} - /// link {} - /// style {} - /// "asd" - /// Example {} - /// } - /// ``` - Fragment(&'src VFragment<'src>), - - /// Component nodes represent a mounted component with props, children, and a key. - /// - /// # Example - /// - /// ```rust, ignore - /// fn Example(cx: Scope) -> Element { - /// ... - /// } - /// - /// let mut vdom = VirtualDom::new(); - /// - /// let node = vdom.render_vnode(rsx!( Example {} )); - /// - /// if let VNode::Component(vcomp) = node { - /// assert_eq!(vcomp.user_fc, Example as *const ()); - /// } - /// ``` - Component(&'src VComponent<'src>), - - /// Templetes ase generated by the rsx macro to eleminate diffing static nodes. - /// - /// - /// - /// - /// - /// - Template(&'src VTemplate<'src>), -} - -/// 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)] -#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] -#[cfg_attr(feature = "serialize", serde(transparent))] -pub struct ElementId(pub usize); - -impl<'src> VNode<'src> { - /// Get the VNode's "key" used in the keyed diffing algorithm. - pub fn key(&self) -> Option<&'src str> { - match &self { - VNode::Element(el) => el.key, - VNode::Component(c) => c.key, - VNode::Fragment(f) => f.key, - VNode::Template(t) => t.key, - VNode::Text(_t) => None, - } - } - - /// Get the ElementID of the mounted VNode. - /// - /// Panics if the mounted ID is None or if the VNode is not represented by a single Element. - pub fn mounted_id(&self) -> ElementId { - self.try_mounted_id().unwrap() - } - - /// Try to get the ElementID of the mounted VNode. - /// - /// Returns None if the VNode is not mounted, or if the VNode cannot be presented by a mounted ID (Fragment/Component) - pub fn try_mounted_id(&self) -> Option { - match &self { - VNode::Text(el) => el.id.get(), - VNode::Element(el) => el.id.get(), - VNode::Fragment(_) => None, - VNode::Component(_) => None, - VNode::Template(_) => None, - } - } - - // Create an "owned" version of the vnode. - pub(crate) fn decouple(&self) -> VNode<'src> { - match *self { - VNode::Text(t) => VNode::Text(t), - VNode::Element(e) => VNode::Element(e), - VNode::Component(c) => VNode::Component(c), - VNode::Fragment(f) => VNode::Fragment(f), - VNode::Template(t) => VNode::Template(t), - } - } -} - -impl Debug for VNode<'_> { - fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { - match &self { - VNode::Element(el) => s - .debug_struct("VNode::Element") - .field("name", &el.tag) - .field("key", &el.key) - .field("attrs", &el.attributes) - .field("children", &el.children) - .field("id", &el.id) - .finish(), - VNode::Text(t) => s - .debug_struct("VNode::Text") - .field("text", &t.text) - .field("id", &t.id) - .finish(), - VNode::Fragment(frag) => s - .debug_struct("VNode::Fragment") - .field("children", &frag.children) - .finish(), - VNode::Component(comp) => s - .debug_struct("VNode::Component") - .field("name", &comp.fn_name) - .field("fnptr", &comp.user_fc) - .field("key", &comp.key) - .field("scope", &comp.scope) - .finish(), - VNode::Template(temp) => s - .debug_struct("VNode::Templates") - .field("template_id", &temp.template.id) - .finish(), - } - } -} diff --git a/packages/core/src.old/nodes/mod.rs b/packages/core/src.old/nodes/mod.rs deleted file mode 100644 index 343f73325..000000000 --- a/packages/core/src.old/nodes/mod.rs +++ /dev/null @@ -1,88 +0,0 @@ -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::