,
+}
+
+impl<'a, P, A, F> VProps<'a, P, A, F>
+where
+ F: ComponentReturn<'a, A>,
+{
+ pub(crate) fn new(
+ render_fn: fn(Scope<'a, P>) -> F,
+ memo: unsafe fn(&P, &P) -> bool,
+ props: P,
+ ) -> Self {
+ Self {
+ render_fn,
+ memo,
+ props,
+ _marker: PhantomData,
+ }
+ }
+}
+
+unsafe impl<'a, P, A, F> AnyProps<'a> for VProps<'a, P, A, F>
+where
+ F: ComponentReturn<'a, A>,
+{
+ fn props_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.props_ptr() as *const _ as *const P);
+ let real_us: &P = &*(self.props_ptr() as *const _ as *const P);
+ (self.memo)(real_us, real_other)
+ }
+
+ fn render(&'a self, cx: &'a ScopeState) -> RenderReturn<'a> {
+ let scope: &mut Scoped = cx.bump().alloc(Scoped {
+ props: &self.props,
+ scope: cx,
+ });
+
+ // Call the render function directly
+ (self.render_fn)(scope).into_return(cx)
+ }
+}
diff --git a/packages/core/src/arbitrary_value.rs b/packages/core/src/arbitrary_value.rs
deleted file mode 100644
index cc6b27838..000000000
--- a/packages/core/src/arbitrary_value.rs
+++ /dev/null
@@ -1,234 +0,0 @@
-use std::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(_) => todo!(),
- }
- }
-}
-
-#[derive(Clone, Copy)]
-pub struct ArbitraryAttributeValue<'a> {
- pub value: &'a dyn std::any::Any,
- pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool,
-}
-
-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/arena.rs b/packages/core/src/arena.rs
new file mode 100644
index 000000000..5702a9c9b
--- /dev/null
+++ b/packages/core/src/arena.rs
@@ -0,0 +1,181 @@
+use crate::{
+ nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
+ ScopeId,
+};
+use bumpalo::boxed::Box as BumpBox;
+
+/// 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.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
+pub struct ElementId(pub usize);
+
+pub(crate) struct ElementRef {
+ // the pathway of the real element inside the template
+ pub path: ElementPath,
+
+ // The actual template
+ pub template: *const VNode<'static>,
+}
+
+#[derive(Clone, Copy)]
+pub enum ElementPath {
+ Deep(&'static [u8]),
+ Root(usize),
+}
+
+impl ElementRef {
+ pub(crate) fn null() -> Self {
+ Self {
+ template: std::ptr::null_mut(),
+ path: ElementPath::Root(0),
+ }
+ }
+}
+
+impl VirtualDom {
+ pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
+ self.next_reference(template, ElementPath::Deep(path))
+ }
+
+ pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
+ self.next_reference(template, ElementPath::Root(path))
+ }
+
+ fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
+ let entry = self.elements.vacant_entry();
+ let id = entry.key();
+
+ entry.insert(ElementRef {
+ template: template as *const _ as *mut _,
+ path,
+ });
+ ElementId(id)
+ }
+
+ pub(crate) fn reclaim(&mut self, el: ElementId) {
+ self.try_reclaim(el)
+ .unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
+ }
+
+ pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option {
+ if el.0 == 0 {
+ panic!(
+ "Cannot reclaim the root element - {:#?}",
+ std::backtrace::Backtrace::force_capture()
+ );
+ }
+
+ self.elements.try_remove(el.0)
+ }
+
+ pub(crate) fn update_template(&mut self, el: ElementId, node: &VNode) {
+ let node: *const VNode = node as *const _;
+ self.elements[el.0].template = unsafe { std::mem::transmute(node) };
+ }
+
+ // Drop a scope and all its children
+ pub(crate) fn drop_scope(&mut self, id: ScopeId) {
+ self.ensure_drop_safety(id);
+
+ if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
+ if let RenderReturn::Sync(Ok(node)) = unsafe { root.extend_lifetime_ref() } {
+ self.drop_scope_inner(node)
+ }
+ }
+ if let Some(root) = unsafe { self.scopes[id.0].as_ref().previous_frame().try_load_node() } {
+ if let RenderReturn::Sync(Ok(node)) = unsafe { root.extend_lifetime_ref() } {
+ self.drop_scope_inner(node)
+ }
+ }
+
+ self.scopes[id.0].props.take();
+
+ let scope = &mut self.scopes[id.0];
+
+ // Drop all the hooks once the children are dropped
+ // this means we'll drop hooks bottom-up
+ for hook in scope.hook_list.get_mut().drain(..) {
+ drop(unsafe { BumpBox::from_raw(hook) });
+ }
+ }
+
+ fn drop_scope_inner(&mut self, node: &VNode) {
+ node.clear_listeners();
+ node.dynamic_nodes.iter().for_each(|node| match node {
+ DynamicNode::Component(c) => {
+ if let Some(f) = c.scope.get() {
+ self.drop_scope(f);
+ }
+ c.props.take();
+ }
+ DynamicNode::Fragment(nodes) => {
+ nodes.iter().for_each(|node| self.drop_scope_inner(node))
+ }
+ DynamicNode::Placeholder(t) => {
+ if let Some(id) = t.id.get() {
+ self.try_reclaim(id);
+ }
+ }
+ DynamicNode::Text(t) => {
+ if let Some(id) = t.id.get() {
+ self.try_reclaim(id);
+ }
+ }
+ });
+
+ for root in node.root_ids {
+ if let Some(id) = root.get() {
+ if id.0 != 0 {
+ self.try_reclaim(id);
+ }
+ }
+ }
+ }
+
+ /// Descend through the tree, removing any borrowed props and listeners
+ pub(crate) fn ensure_drop_safety(&self, scope: ScopeId) {
+ let scope = &self.scopes[scope.0];
+
+ // make sure we drop all borrowed props manually to guarantee that their drop implementation is called before we
+ // run the hooks (which hold an &mut Reference)
+ // recursively call ensure_drop_safety on all children
+ let mut props = scope.borrowed_props.borrow_mut();
+ props.drain(..).for_each(|comp| {
+ let comp = unsafe { &*comp };
+ if let Some(scope_id) = comp.scope.get() {
+ self.ensure_drop_safety(scope_id);
+ }
+ drop(comp.props.take());
+ });
+
+ // Now that all the references are gone, we can safely drop our own references in our listeners.
+ let mut listeners = scope.listeners.borrow_mut();
+ listeners.drain(..).for_each(|listener| {
+ let listener = unsafe { &*listener };
+ if let AttributeValue::Listener(l) = &listener.value {
+ _ = l.take();
+ }
+ });
+ }
+}
+
+impl ElementPath {
+ pub(crate) fn is_ascendant(&self, big: &&[u8]) -> bool {
+ match *self {
+ ElementPath::Deep(small) => small.len() <= big.len() && small == &big[..small.len()],
+ ElementPath::Root(r) => big.len() == 1 && big[0] == r as u8,
+ }
+ }
+}
+
+impl PartialEq<&[u8]> for ElementPath {
+ fn eq(&self, other: &&[u8]) -> bool {
+ match *self {
+ ElementPath::Deep(deep) => deep.eq(*other),
+ ElementPath::Root(r) => other.len() == 1 && other[0] == r as u8,
+ }
+ }
+}
diff --git a/packages/core/src/bump_frame.rs b/packages/core/src/bump_frame.rs
new file mode 100644
index 000000000..44ea4f2d6
--- /dev/null
+++ b/packages/core/src/bump_frame.rs
@@ -0,0 +1,29 @@
+use crate::nodes::RenderReturn;
+use bumpalo::Bump;
+use std::cell::Cell;
+
+pub(crate) struct BumpFrame {
+ pub bump: Bump,
+ pub node: Cell<*const RenderReturn<'static>>,
+}
+
+impl BumpFrame {
+ pub(crate) fn new(capacity: usize) -> Self {
+ let bump = Bump::with_capacity(capacity);
+ Self {
+ bump,
+ node: Cell::new(std::ptr::null()),
+ }
+ }
+
+ /// Creates a new lifetime out of thin air
+ pub(crate) unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> {
+ let node = self.node.get();
+
+ if node.is_null() {
+ return None;
+ }
+
+ unsafe { std::mem::transmute(&*node) }
+ }
+}
diff --git a/packages/core/src/create.rs b/packages/core/src/create.rs
new file mode 100644
index 000000000..1e0889208
--- /dev/null
+++ b/packages/core/src/create.rs
@@ -0,0 +1,472 @@
+use crate::any_props::AnyProps;
+use crate::innerlude::{VComponent, VPlaceholder, VText};
+use crate::mutations::Mutation;
+use crate::mutations::Mutation::*;
+use crate::nodes::VNode;
+use crate::nodes::{DynamicNode, TemplateNode};
+use crate::virtual_dom::VirtualDom;
+use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
+use std::cell::Cell;
+use std::iter::{Enumerate, Peekable};
+use std::rc::Rc;
+use std::slice;
+use TemplateNode::*;
+
+impl<'b> VirtualDom {
+ /// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
+ ///
+ /// This method pushes the ScopeID to the internal scopestack and returns the number of nodes created.
+ pub(crate) fn create_scope(&mut self, scope: ScopeId, template: &'b VNode<'b>) -> usize {
+ self.scope_stack.push(scope);
+ let out = self.create(template);
+ self.scope_stack.pop();
+ out
+ }
+
+ /// Create this template and write its mutations
+ pub(crate) fn create(&mut self, node: &'b VNode<'b>) -> usize {
+ // The best renderers will have templates prehydrated and registered
+ // Just in case, let's create the template using instructions anyways
+ if !self.templates.contains_key(&node.template.name) {
+ self.register_template(node);
+ }
+
+ // we know that this will generate at least one mutation per node
+ self.mutations.edits.reserve(node.template.roots.len());
+
+ // Walk the roots, creating nodes and assigning IDs
+ // todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
+ let mut attrs = node.template.attr_paths.iter().enumerate().peekable();
+ let mut nodes = node.template.node_paths.iter().enumerate().peekable();
+
+ node.template
+ .roots
+ .iter()
+ .enumerate()
+ .map(|(idx, root)| match root {
+ DynamicText { id } | Dynamic { id } => {
+ nodes.next().unwrap();
+ self.write_dynamic_root(node, *id)
+ }
+ Element { .. } => self.write_element_root(node, idx, &mut attrs, &mut nodes),
+ Text { .. } => self.write_static_text_root(node, idx),
+ })
+ .sum()
+ }
+
+ fn write_static_text_root(&mut self, node: &VNode, idx: usize) -> usize {
+ // Simply just load the template root, no modifications needed
+ self.load_template_root(node, idx);
+
+ // Text producs just one node on the stack
+ 1
+ }
+
+ fn write_dynamic_root(&mut self, template: &'b VNode<'b>, idx: usize) -> usize {
+ use DynamicNode::*;
+ match &template.dynamic_nodes[idx] {
+ node @ Fragment(_) => self.create_dynamic_node(template, node, idx),
+ node @ Component { .. } => self.create_dynamic_node(template, node, idx),
+ Placeholder(VPlaceholder { id }) => {
+ let id = self.set_slot(template, id, idx);
+ self.mutations.push(CreatePlaceholder { id });
+ 1
+ }
+ Text(VText { id, value }) => {
+ let id = self.set_slot(template, id, idx);
+ self.create_static_text(value, id);
+ 1
+ }
+ }
+ }
+
+ fn create_static_text(&mut self, value: &str, id: ElementId) {
+ // Safety: we promise not to re-alias this text later on after committing it to the mutation
+ let unbounded_text: &str = unsafe { std::mem::transmute(value) };
+ self.mutations.push(CreateTextNode {
+ value: unbounded_text,
+ id,
+ });
+ }
+
+ /// We write all the descndent data for this element
+ ///
+ /// Elements can contain other nodes - and those nodes can be dynamic or static
+ ///
+ /// We want to make sure we write these nodes while on top of the root
+ fn write_element_root(
+ &mut self,
+ template: &'b VNode<'b>,
+ root_idx: usize,
+ dynamic_attrs: &mut Peekable>>,
+ dynamic_nodes: &mut Peekable>>,
+ ) -> usize {
+ // Load the template root and get the ID for the node on the stack
+ let root_on_stack = self.load_template_root(template, root_idx);
+
+ // Write all the attributes below this root
+ self.write_attrs_on_root(dynamic_attrs, root_idx, root_on_stack, template);
+
+ // Load in all of the placeholder or dynamic content under this root too
+ self.load_placeholders(dynamic_nodes, root_idx, template);
+
+ 1
+ }
+
+ /// Load all of the placeholder nodes for descendents of this root node
+ ///
+ /// ```rust, ignore
+ /// rsx! {
+ /// div {
+ /// // This is a placeholder
+ /// some_value,
+ ///
+ /// // Load this too
+ /// "{some_text}"
+ /// }
+ /// }
+ /// ```
+ fn load_placeholders(
+ &mut self,
+ dynamic_nodes: &mut Peekable>>,
+ root_idx: usize,
+ template: &'b VNode<'b>,
+ ) {
+ let (start, end) = match collect_dyn_node_range(dynamic_nodes, root_idx) {
+ Some((a, b)) => (a, b),
+ None => return,
+ };
+
+ for idx in (start..=end).rev() {
+ let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx);
+ if m > 0 {
+ // The path is one shorter because the top node is the root
+ let path = &template.template.node_paths[idx][1..];
+ self.mutations.push(ReplacePlaceholder { m, path });
+ }
+ }
+ }
+
+ fn write_attrs_on_root(
+ &mut self,
+ attrs: &mut Peekable>>,
+ root_idx: usize,
+ root: ElementId,
+ node: &VNode,
+ ) {
+ while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p[0] == root_idx as u8) {
+ let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
+
+ loop {
+ self.write_attribute(&node.dynamic_attrs[attr_id], id);
+
+ // Only push the dynamic attributes forward if they match the current path (same element)
+ match attrs.next_if(|(_, p)| *p == path) {
+ Some((next_attr_id, _)) => attr_id = next_attr_id,
+ None => break,
+ }
+ }
+ }
+ }
+
+ fn write_attribute(&mut self, attribute: &crate::Attribute, id: ElementId) {
+ // Make sure we set the attribute's associated id
+ attribute.mounted_element.set(id);
+
+ // Safety: we promise not to re-alias this text later on after committing it to the mutation
+ let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) };
+
+ match &attribute.value {
+ AttributeValue::Text(value) => {
+ // Safety: we promise not to re-alias this text later on after committing it to the mutation
+ let unbounded_value: &str = unsafe { std::mem::transmute(*value) };
+
+ self.mutations.push(SetAttribute {
+ name: unbounded_name,
+ value: unbounded_value,
+ ns: attribute.namespace,
+ id,
+ })
+ }
+ AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute {
+ name: unbounded_name,
+ value: *value,
+ id,
+ }),
+ AttributeValue::Listener(_) => {
+ self.mutations.push(NewEventListener {
+ // all listeners start with "on"
+ name: &unbounded_name[2..],
+ id,
+ })
+ }
+ AttributeValue::Float(_) => todo!(),
+ AttributeValue::Int(_) => todo!(),
+ AttributeValue::Any(_) => todo!(),
+ AttributeValue::None => todo!(),
+ }
+ }
+
+ fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
+ // Get an ID for this root since it's a real root
+ let this_id = self.next_root(template, root_idx);
+ template.root_ids[root_idx].set(Some(this_id));
+
+ self.mutations.push(LoadTemplate {
+ name: template.template.name,
+ index: root_idx,
+ id: this_id,
+ });
+
+ this_id
+ }
+
+ /// We have some dynamic attributes attached to a some node
+ ///
+ /// That node needs to be loaded at runtime, so we need to give it an ID
+ ///
+ /// If the node in question is on the stack, we just return that ID
+ ///
+ /// If the node is not on the stack, we create a new ID for it and assign it
+ fn assign_static_node_as_dynamic(
+ &mut self,
+ path: &'static [u8],
+ this_id: ElementId,
+ template: &VNode,
+ attr_id: usize,
+ ) -> ElementId {
+ if path.len() == 1 {
+ return this_id;
+ }
+
+ // if attribute is on a root node, then we've already created the element
+ // Else, it's deep in the template and we should create a new id for it
+ let id = self.next_element(template, template.template.attr_paths[attr_id]);
+
+ self.mutations.push(Mutation::AssignId {
+ path: &path[1..],
+ id,
+ });
+
+ id
+ }
+
+ /// Insert a new template into the VirtualDom's template registry
+ fn register_template(&mut self, template: &'b VNode<'b>) {
+ // First, make sure we mark the template as seen, regardless if we process it
+ self.templates
+ .insert(template.template.name, template.template);
+
+ // If it's all dynamic nodes, then we don't need to register it
+ if !template.template.is_completely_dynamic() {
+ self.mutations.templates.push(template.template);
+ }
+ }
+
+ pub(crate) fn create_dynamic_node(
+ &mut self,
+ template: &'b VNode<'b>,
+ node: &'b DynamicNode<'b>,
+ idx: usize,
+ ) -> usize {
+ use DynamicNode::*;
+ match node {
+ Text(text) => self.create_dynamic_text(template, text, idx),
+ Fragment(frag) => self.create_fragment(frag),
+ Placeholder(frag) => self.create_placeholder(frag, template, idx),
+ Component(component) => self.create_component_node(template, component, idx),
+ }
+ }
+
+ fn create_dynamic_text(
+ &mut self,
+ template: &'b VNode<'b>,
+ text: &'b VText<'b>,
+ idx: usize,
+ ) -> usize {
+ // Allocate a dynamic element reference for this text node
+ let new_id = self.next_element(template, template.template.node_paths[idx]);
+
+ // Make sure the text node is assigned to the correct element
+ text.id.set(Some(new_id));
+
+ // Safety: we promise not to re-alias this text later on after committing it to the mutation
+ let value = unsafe { std::mem::transmute(text.value) };
+
+ // Add the mutation to the list
+ self.mutations.push(HydrateText {
+ id: new_id,
+ path: &template.template.node_paths[idx][1..],
+ value,
+ });
+
+ // Since we're hydrating an existing node, we don't create any new nodes
+ 0
+ }
+
+ pub(crate) fn create_placeholder(
+ &mut self,
+ placeholder: &VPlaceholder,
+ template: &'b VNode<'b>,
+ idx: usize,
+ ) -> usize {
+ // Allocate a dynamic element reference for this text node
+ let id = self.next_element(template, template.template.node_paths[idx]);
+
+ // Make sure the text node is assigned to the correct element
+ placeholder.id.set(Some(id));
+
+ // Assign the ID to the existing node in the template
+ self.mutations.push(AssignId {
+ path: &template.template.node_paths[idx][1..],
+ id,
+ });
+
+ // Since the placeholder is already in the DOM, we don't create any new nodes
+ 0
+ }
+
+ pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
+ nodes.iter().map(|child| self.create(child)).sum()
+ }
+
+ pub(super) fn create_component_node(
+ &mut self,
+ template: &'b VNode<'b>,
+ component: &'b VComponent<'b>,
+ idx: usize,
+ ) -> usize {
+ let scope = match component.props.take() {
+ Some(props) => {
+ let unbounded_props: Box = unsafe { std::mem::transmute(props) };
+ let scope = self.new_scope(unbounded_props, component.name);
+ scope.id
+ }
+
+ // Component is coming back, it probably still exists, right?
+ None => component.scope.get().unwrap(),
+ };
+
+ component.scope.set(Some(scope));
+
+ let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
+
+ use RenderReturn::*;
+
+ match return_nodes {
+ Sync(Ok(t)) => self.mount_component(scope, template, t, idx),
+ Sync(Err(_e)) => todo!("Propogate error upwards"),
+ Async(_) => self.mount_component_placeholder(template, idx, scope),
+ }
+ }
+
+ fn mount_component(
+ &mut self,
+ scope: ScopeId,
+ parent: &'b VNode<'b>,
+ new: &'b VNode<'b>,
+ idx: usize,
+ ) -> usize {
+ // Keep track of how many mutations are in the buffer in case we need to split them out if a suspense boundary
+ // is encountered
+ let mutations_to_this_point = self.mutations.edits.len();
+
+ // Create the component's root element
+ let created = self.create_scope(scope, new);
+
+ // If there are no suspense leaves below us, then just don't bother checking anything suspense related
+ if self.collected_leaves.is_empty() {
+ return created;
+ }
+
+ // If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense
+ let boundary = match self.scopes[scope.0].has_context::>() {
+ Some(boundary) => boundary,
+ _ => return created,
+ };
+
+ // Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree
+ let new_id = self.next_element(new, parent.template.node_paths[idx]);
+
+ // Now connect everything to the boundary
+ self.scopes[scope.0].placeholder.set(Some(new_id));
+
+ // This involves breaking off the mutations to this point, and then creating a new placeholder for the boundary
+ // Note that we break off dynamic mutations only - since static mutations aren't rendered immediately
+ let split_off = unsafe {
+ std::mem::transmute::, Vec>(
+ self.mutations.edits.split_off(mutations_to_this_point),
+ )
+ };
+ boundary.mutations.borrow_mut().edits.extend(split_off);
+ boundary.created_on_stack.set(created);
+ boundary
+ .waiting_on
+ .borrow_mut()
+ .extend(self.collected_leaves.drain(..));
+
+ // Now assign the placeholder in the DOM
+ self.mutations.push(AssignId {
+ id: new_id,
+ path: &parent.template.node_paths[idx][1..],
+ });
+
+ 0
+ }
+
+ /// Take the rendered nodes from a component and handle them if they were async
+ ///
+ /// IE simply assign an ID to the placeholder
+ fn mount_component_placeholder(
+ &mut self,
+ template: &VNode,
+ idx: usize,
+ scope: ScopeId,
+ ) -> usize {
+ let new_id = self.next_element(template, template.template.node_paths[idx]);
+
+ // Set the placeholder of the scope
+ self.scopes[scope.0].placeholder.set(Some(new_id));
+
+ // Since the placeholder is already in the DOM, we don't create any new nodes
+ self.mutations.push(AssignId {
+ id: new_id,
+ path: &template.template.node_paths[idx][1..],
+ });
+
+ 0
+ }
+
+ fn set_slot(
+ &mut self,
+ template: &'b VNode<'b>,
+ slot: &'b Cell>,
+ id: usize,
+ ) -> ElementId {
+ let id = self.next_element(template, template.template.node_paths[id]);
+ slot.set(Some(id));
+ id
+ }
+}
+
+fn collect_dyn_node_range(
+ dynamic_nodes: &mut Peekable>>,
+ root_idx: usize,
+) -> Option<(usize, usize)> {
+ let start = match dynamic_nodes.peek() {
+ Some((idx, p)) if p[0] == root_idx as u8 => *idx,
+ _ => return None,
+ };
+
+ let mut end = start;
+
+ while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) {
+ if p.len() == 1 {
+ continue;
+ }
+
+ end = idx;
+ }
+
+ Some((start, end))
+}
diff --git a/packages/core/src/diff.rs b/packages/core/src/diff.rs
index e2ef92b93..7b8e33083 100644
--- a/packages/core/src/diff.rs
+++ b/packages/core/src/diff.rs
@@ -1,575 +1,293 @@
-#![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.
-//!
-//! ### Suspense
-//! ------------
-//! Dioxus implements Suspense slightly differently than React. In React, each fiber is manually progressed until it runs
-//! into a promise-like value. React will then work on the next "ready" fiber, checking back on the previous fiber once
-//! it has finished its new work. In Dioxus, we use a similar approach, but try to completely render the tree before
-//! switching sub-fibers. Instead, each future is submitted into a futures-queue and the node is manually loaded later on.
-//! Due to the frequent calls to [`crate::virtual_dom::VirtualDom::work_with_deadline`] we can get the pure "fetch-as-you-render" behavior of React Fiber.
-//!
-//! We're able to use this approach because we use placeholder nodes - futures that aren't ready still get submitted to
-//! DOM, but as a placeholder.
-//!
-//! Right now, the "suspense" queue is intertwined with hooks. In the future, we should allow any future to drive attributes
-//! and contents, without the need for a `use_suspense` hook. In the interim, this is the quickest way to get Suspense working.
-//!
-//! ## 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, Mutations, ScopeArena, ScopeId, VComponent, VElement, VFragment, VNode,
- VPlaceholder, VText,
+use crate::{
+ any_props::AnyProps,
+ arena::ElementId,
+ innerlude::{DirtyScope, VComponent, VPlaceholder, VText},
+ mutations::Mutation,
+ nodes::RenderReturn,
+ nodes::{DynamicNode, VNode},
+ scopes::ScopeId,
+ virtual_dom::VirtualDom,
+ Attribute, AttributeValue, TemplateNode,
};
-use fxhash::{FxHashMap, FxHashSet};
-use smallvec::{smallvec, SmallVec};
-pub(crate) struct DiffState<'bump> {
- pub(crate) scopes: &'bump ScopeArena,
- pub(crate) mutations: Mutations<'bump>,
- pub(crate) force_diff: bool,
- pub(crate) element_stack: SmallVec<[ElementId; 10]>,
- pub(crate) scope_stack: SmallVec<[ScopeId; 5]>,
-}
+use rustc_hash::{FxHashMap, FxHashSet};
+use DynamicNode::*;
-impl<'b> DiffState<'b> {
- pub fn new(scopes: &'b ScopeArena) -> Self {
- Self {
- scopes,
- mutations: Mutations::new(),
- force_diff: false,
- element_stack: smallvec![],
- scope_stack: smallvec![],
+impl<'b> VirtualDom {
+ pub(super) fn diff_scope(&mut self, scope: ScopeId) {
+ let scope_state = &mut self.scopes[scope.0];
+
+ self.scope_stack.push(scope);
+ unsafe {
+ // Load the old and new bump arenas
+ let old = scope_state
+ .previous_frame()
+ .try_load_node()
+ .expect("Call rebuild before diffing");
+
+ let new = scope_state
+ .current_frame()
+ .try_load_node()
+ .expect("Call rebuild before diffing");
+
+ use RenderReturn::{Async, Sync};
+
+ match (old, new) {
+ (Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
+
+ // Err cases
+ (Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e),
+ (Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r),
+ (Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ }
+
+ // Async
+ (Sync(Ok(_l)), Async(_)) => todo!(),
+ (Sync(Err(_e)), Async(_)) => todo!(),
+ (Async(_), Sync(Ok(_r))) => todo!(),
+ (Async(_), Sync(Err(_e))) => { /* nothing */ }
+ (Async(_), Async(_)) => { /* nothing */ }
+ };
}
- }
-
- 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, Placeholder, Text};
- match (old_node, new_node) {
- (Text(old), Text(new)) => {
- self.diff_text_nodes(old, new, old_node, new_node);
- }
+ fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) {}
+ fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {}
- (Placeholder(old), Placeholder(new)) => {
- self.diff_placeholder_nodes(old, new, old_node, new_node);
- }
-
- (Element(old), Element(new)) => {
- self.diff_element_nodes(old, new, old_node, new_node);
- }
-
- (Component(old), Component(new)) => {
- self.diff_component_nodes(old_node, new_node, *old, *new);
- }
-
- (Fragment(old), Fragment(new)) => {
- self.diff_fragment_nodes(old, new);
- }
-
- (
- Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
- Component(_) | Fragment(_) | Text(_) | Element(_) | Placeholder(_),
- ) => self.replace_node(old_node, new_node),
+ fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
+ // If the templates are the same, we don't need to do anything, nor do we want to
+ if templates_are_the_same(left_template, right_template) {
+ return;
}
- }
- pub fn create_node(&mut self, node: &'b VNode<'b>) -> usize {
- match node {
- VNode::Text(vtext) => self.create_text_node(vtext, node),
- VNode::Placeholder(anchor) => self.create_anchor_node(anchor, node),
- VNode::Element(element) => self.create_element_node(element, node),
- VNode::Fragment(frag) => self.create_fragment_node(frag),
- VNode::Component(component) => self.create_component_node(*component),
+ // If the templates are different by name, we need to replace the entire template
+ if templates_are_different(left_template, right_template) {
+ return self.light_diff_templates(left_template, right_template);
}
+
+ // If the templates are the same, we can diff the attributes and children
+ // Start with the attributes
+ left_template
+ .dynamic_attrs
+ .iter()
+ .zip(right_template.dynamic_attrs.iter())
+ .for_each(|(left_attr, right_attr)| {
+ // Move over the ID from the old to the new
+ right_attr
+ .mounted_element
+ .set(left_attr.mounted_element.get());
+
+ // We want to make sure anything listener that gets pulled is valid
+ if let AttributeValue::Listener(_) = right_attr.value {
+ self.update_template(left_attr.mounted_element.get(), right_template);
+ }
+
+ // If the attributes are different (or volatile), we need to update them
+ if left_attr.value != right_attr.value || left_attr.volatile {
+ self.update_attribute(right_attr, left_attr);
+ }
+ });
+
+ // Now diff the dynamic nodes
+ left_template
+ .dynamic_nodes
+ .iter()
+ .zip(right_template.dynamic_nodes.iter())
+ .enumerate()
+ .for_each(|(idx, (left_node, right_node))| {
+ self.diff_dynamic_node(left_node, right_node, right_template, idx);
+ });
+
+ // Make sure the roots get transferred over while we're here
+ left_template
+ .root_ids
+ .iter()
+ .zip(right_template.root_ids.iter())
+ .for_each(|(left, right)| right.set(left.get()));
}
- fn create_text_node(&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_anchor_node(&mut self, anchor: &'b VPlaceholder, node: &'b VNode<'b>) -> usize {
- let real_id = self.scopes.reserve_node(node);
- anchor.id.set(Some(real_id));
- self.mutations.create_placeholder(real_id);
- 1
- }
-
- fn create_element_node(&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(Some(real_id));
- self.mutations.new_event_listener(listener, cur_scope_id);
- }
-
- for attr in attributes.iter() {
- self.mutations.set_attribute(attr, real_id.as_u64());
- }
-
- if !children.is_empty() {
- self.create_and_append_children(children);
- }
- }
- self.element_stack.pop();
-
- 1
- }
-
- fn create_fragment_node(&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,
- )
+ fn diff_dynamic_node(
+ &mut self,
+ left_node: &'b DynamicNode<'b>,
+ right_node: &'b DynamicNode<'b>,
+ node: &'b VNode<'b>,
+ idx: usize,
+ ) {
+ match (left_node, right_node) {
+ (Text(left), Text(right)) => self.diff_vtext(left, right, node),
+ (Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
+ (Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()),
+ (Component(left), Component(right)) => self.diff_vcomponent(left, right, node, idx),
+ (Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right),
+ (Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right),
+ _ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
};
+ }
- // Actually initialize the caller's slot with the right address
- vcomponent.scope.set(Some(new_idx));
+ fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) {
+ // todo: add more types of attribute values
+ match right_attr.value {
+ AttributeValue::Text(text) => {
+ let name = unsafe { std::mem::transmute(left_attr.name) };
+ let value = unsafe { std::mem::transmute(text) };
+ self.mutations.push(Mutation::SetAttribute {
+ id: left_attr.mounted_element.get(),
+ ns: right_attr.namespace,
+ name,
+ value,
+ });
+ }
+ // todo: more types of attribute values
+ _ => todo!("other attribute types"),
+ }
+ }
- log::trace!(
- "created component \"{}\", id: {:?} parent {:?}",
- vcomponent.fn_name,
- new_idx,
- parent_idx,
+ fn diff_vcomponent(
+ &mut self,
+ left: &'b VComponent<'b>,
+ right: &'b VComponent<'b>,
+ right_template: &'b VNode<'b>,
+ idx: usize,
+ ) {
+ if std::ptr::eq(left, right) {
+ return;
+ }
+
+ // Replace components that have different render fns
+ if left.render_fn != right.render_fn {
+ return self.replace_vcomponent(right_template, right, idx, left);
+ }
+
+ // Make sure the new vcomponent has the right scopeid associated to it
+ let scope_id = left.scope.get().unwrap();
+
+ right.scope.set(Some(scope_id));
+
+ // copy out the box for both
+ let old = self.scopes[scope_id.0].props.as_ref();
+ let new: Box = right.props.take().unwrap();
+ let new: Box = unsafe { std::mem::transmute(new) };
+
+ // If the props are static, then we try to memoize by setting the new with the old
+ // The target scopestate still has the reference to the old props, so there's no need to update anything
+ // This also implicitly drops the new props since they're not used
+ if left.static_props && unsafe { old.as_ref().unwrap().memoize(new.as_ref()) } {
+ return;
+ }
+
+ // First, move over the props from the old to the new, dropping old props in the process
+ self.scopes[scope_id.0].props = Some(new);
+
+ // Now run the component and diff it
+ self.run_scope(scope_id);
+ self.diff_scope(scope_id);
+
+ self.dirty_scopes.remove(&DirtyScope {
+ height: self.scopes[scope_id.0].height,
+ id: scope_id,
+ });
+ }
+
+ fn replace_vcomponent(
+ &mut self,
+ right_template: &'b VNode<'b>,
+ right: &'b VComponent<'b>,
+ idx: usize,
+ left: &'b VComponent<'b>,
+ ) {
+ let m = self.create_component_node(right_template, right, idx);
+
+ self.remove_component_node(left, true);
+
+ // We want to optimize the replace case to use one less mutation if possible
+ // Since mutations are done in reverse, the last node removed will be the first in the stack
+ // Instead of *just* removing it, we can use the replace mutation
+ match self.mutations.edits.pop().unwrap() {
+ Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m }),
+ at => panic!("Expected remove mutation from remove_node {:#?}", at),
+ };
+ }
+
+ /// Lightly diff the two templates, checking only their roots.
+ ///
+ /// The goal here is to preserve any existing component state that might exist. This is to preserve some React-like
+ /// behavior where the component state is preserved when the component is re-rendered.
+ ///
+ /// This is implemented by iterating each root, checking if the component is the same, if it is, then diff it.
+ ///
+ /// We then pass the new template through "create" which should be smart enough to skip roots.
+ ///
+ /// Currently, we only handle the case where the roots are the same component list. If there's any sort of deviation,
+ /// IE more nodes, less nodes, different nodes, or expressions, then we just replace the whole thing.
+ ///
+ /// This is mostly implemented to help solve the issue where the same component is rendered under two different
+ /// conditions:
+ ///
+ /// ```rust, ignore
+ /// if enabled {
+ /// rsx!{ Component { enabled_sign: "abc" } }
+ /// } else {
+ /// rsx!{ Component { enabled_sign: "xyz" } }
+ /// }
+ /// ```
+ ///
+ /// However, we should not that it's explicit in the docs that this is not a guarantee. If you need to preserve state,
+ /// then you should be passing in separate props instead.
+ ///
+ /// ```rust, ignore
+ /// let props = if enabled {
+ /// ComponentProps { enabled_sign: "abc" }
+ /// } else {
+ /// ComponentProps { enabled_sign: "xyz" }
+ /// };
+ ///
+ /// rsx! {
+ /// Component { ..props }
+ /// }
+ /// ```
+ fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
+ match matching_components(left, right) {
+ None => self.replace(left, [right]),
+ Some(components) => components
+ .into_iter()
+ .enumerate()
+ .for_each(|(idx, (l, r))| self.diff_vcomponent(l, r, right, idx)),
+ }
+ }
+
+ /// Diff the two text nodes
+ ///
+ /// This just moves the ID of the old node over to the new node, and then sets the text of the new node if it's
+ /// different.
+ fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>, node: &'b VNode<'b>) {
+ let id = left
+ .id
+ .get()
+ .unwrap_or_else(|| self.next_element(node, &[0]));
+
+ right.id.set(Some(id));
+ if left.value != right.value {
+ let value = unsafe { std::mem::transmute(right.value) };
+ self.mutations.push(Mutation::SetText { id, value });
+ }
+ }
+
+ fn diff_non_empty_fragment(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
+ 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 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_nodes(
- &mut self,
- old: &'b VText<'b>,
- new: &'b VText<'b>,
- _old_node: &'b VNode<'b>,
- new_node: &'b VNode<'b>,
- ) {
- if std::ptr::eq(old, new) {
- return;
- }
-
- // 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.as_u64());
- }
-
- self.scopes.update_node(new_node, root);
-
- new.id.set(Some(root));
- }
-
- pub(crate) fn diff_placeholder_nodes(
- &mut self,
- old: &'b VPlaceholder,
- new: &'b VPlaceholder,
- _old_node: &'b VNode<'b>,
- new_node: &'b VNode<'b>,
- ) {
- if std::ptr::eq(old, new) {
- return;
- }
-
- // 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),
- };
-
- self.scopes.update_node(new_node, root);
- new.id.set(Some(root));
- }
-
- fn diff_element_nodes(
- &mut self,
- old: &'b VElement<'b>,
- new: &'b VElement<'b>,
- old_node: &'b VNode<'b>,
- new_node: &'b VNode<'b>,
- ) {
- if std::ptr::eq(old, new) {
- return;
- }
-
- // 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.is_volatile {
- self.mutations.set_attribute(new_attr, root.as_u64());
- }
- }
+ if new_is_keyed && old_is_keyed {
+ self.diff_keyed_children(old, new);
} else {
- for attribute in old.attributes {
- self.mutations.remove_attribute(attribute, root.as_u64());
- }
- for attribute in new.attributes {
- self.mutations.set_attribute(attribute, root.as_u64());
- }
- }
-
- // 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.as_u64());
- self.mutations.new_event_listener(new_l, cur_scope_id);
- }
- }
- } else {
- for listener in old.listeners {
- self.mutations
- .remove_event_listener(listener.event, root.as_u64());
- }
- for listener in new.listeners {
- listener.mounted_node.set(Some(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_nodes(
- &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");
-
- if std::ptr::eq(old, new) {
- return;
- }
-
- // 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_nodes(&mut self, old: &'b VFragment<'b>, new: &'b VFragment<'b>) {
- if std::ptr::eq(old, new) {
- return;
- }
-
- // 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);
- }
- }
+ self.diff_non_keyed_children(old, new);
}
}
@@ -589,7 +307,7 @@ impl<'b> DiffState<'b> {
debug_assert!(!old.is_empty());
match old.len().cmp(&new.len()) {
- Ordering::Greater => self.remove_nodes(&old[new.len()..], true),
+ Ordering::Greater => self.remove_nodes(&old[new.len()..]),
Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
Ordering::Equal => {}
}
@@ -617,11 +335,11 @@ impl<'b> DiffState<'b> {
// 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 keys = rustc_hash::FxHashSet::default();
let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
keys.clear();
for child in children {
- let key = child.key();
+ let key = child.key;
debug_assert!(
key.is_some(),
"if any sibling is keyed, all siblings must be keyed"
@@ -662,7 +380,7 @@ impl<'b> DiffState<'b> {
if new_middle.is_empty() {
// remove the old elements
- self.remove_nodes(old_middle, true);
+ self.remove_nodes(old_middle);
} 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
@@ -698,7 +416,7 @@ impl<'b> DiffState<'b> {
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() {
+ if old.key != new.key {
break;
}
self.diff_node(old, new);
@@ -715,7 +433,7 @@ impl<'b> DiffState<'b> {
// 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);
+ self.remove_nodes(&old[left_offset..]);
return None;
}
@@ -723,7 +441,7 @@ impl<'b> DiffState<'b> {
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() {
+ if old.key != new.key {
break;
}
self.diff_node(old, new);
@@ -769,11 +487,10 @@ impl<'b> DiffState<'b> {
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));
+ debug_assert_ne!(new.first().map(|i| i.key), old.first().map(|i| i.key));
+ debug_assert_ne!(new.last().map(|i| i.key), old.last().map(|i| i.key));
// 1. Map the old keys into a numerical ordering based on indices.
// 2. Create a map of old key to its index
@@ -781,7 +498,7 @@ impl<'b> DiffState<'b> {
let old_key_to_old_index = old
.iter()
.enumerate()
- .map(|(i, o)| (o.key().unwrap(), i))
+ .map(|(i, o)| (o.key.unwrap(), i))
.collect::>();
let mut shared_keys = FxHashSet::default();
@@ -790,7 +507,7 @@ impl<'b> DiffState<'b> {
let new_index_to_old_index = new
.iter()
.map(|node| {
- let key = node.key().unwrap();
+ let key = node.key.unwrap();
if let Some(&index) = old_key_to_old_index.get(&key) {
shared_keys.insert(key);
index
@@ -803,14 +520,15 @@ impl<'b> DiffState<'b> {
// 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);
+ if old.get(0).is_some() {
+ self.remove_nodes(&old[1..]);
+ self.replace(&old[0], new);
} 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);
+ // self.create_and_append_children(new);
+
+ todo!("we should never be appending - just creating N");
}
return;
}
@@ -818,9 +536,9 @@ impl<'b> DiffState<'b> {
// remove any old children that are not shared
// todo: make this an iterator
for child in old {
- let key = child.key().unwrap();
+ let key = child.key.unwrap();
if !shared_keys.contains(&key) {
- self.remove_nodes([child], true);
+ self.remove_node(child, true);
}
}
@@ -860,17 +578,18 @@ impl<'b> DiffState<'b> {
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);
+ nodes_created += self.create(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,
- );
+ let id = self.find_last_element(&new[last]);
+ self.mutations.push(Mutation::InsertAfter {
+ id,
+ m: nodes_created,
+ });
nodes_created = 0;
}
@@ -883,17 +602,18 @@ impl<'b> DiffState<'b> {
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);
+ nodes_created += self.create(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,
- );
+ let id = self.find_first_element(&new[last]);
+ self.mutations.push(Mutation::InsertBefore {
+ id,
+ m: nodes_created,
+ });
nodes_created = 0;
}
@@ -906,223 +626,357 @@ impl<'b> DiffState<'b> {
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);
+ nodes_created += self.create(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,
- );
+ let id = self.find_first_element(&new[first_lis]);
+ self.mutations.push(Mutation::InsertBefore {
+ id,
+ m: nodes_created,
+ });
}
}
- 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);
- }
+ /// Push all the real nodes on the stack
+ fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
+ node.template
+ .roots
+ .iter()
+ .enumerate()
+ .map(|(idx, _)| {
+ let node = match node.dynamic_root(idx) {
+ Some(node) => node,
+ None => {
+ self.mutations.push(Mutation::PushRoot {
+ id: node.root_ids[idx].get().unwrap(),
+ });
+ return 1;
+ }
+ };
- 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));
+ match node {
+ Text(t) => {
+ self.mutations.push(Mutation::PushRoot {
+ id: t.id.get().unwrap(),
+ });
+ 1
+ }
+ Placeholder(t) => {
+ self.mutations.push(Mutation::PushRoot {
+ id: t.id.get().unwrap(),
+ });
+ 1
+ }
+ Fragment(nodes) => nodes
+ .iter()
+ .map(|node| self.push_all_real_nodes(node))
+ .count(),
- self.mutations.replace_with(id, nodes_created as u32);
- self.remove_nodes(el.children, false);
- self.scopes.collect_garbage(id);
- }
-
- VNode::Text(_) | VNode::Placeholder(_) => {
- 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) => {
- log::trace!("Replacing component {:?}", old);
- 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);
-
- log::trace!("Replacing component x2 {:?}", old);
-
- 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();
- }
- }
- }
-
- 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.as_u64());
+ Component(comp) => {
+ let scope = comp.scope.get().unwrap();
+ match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+ RenderReturn::Sync(Ok(node)) => self.push_all_real_nodes(node),
+ _ => todo!(),
}
}
}
- VNode::Placeholder(a) => {
- let id = a.id.get().unwrap();
- self.scopes.collect_garbage(id);
- a.id.set(None);
+ })
+ .count()
+ }
- if gen_muts {
- self.mutations.remove(id.as_u64());
- }
- }
- VNode::Element(e) => {
- let id = e.id.get().unwrap();
-
- if gen_muts {
- self.mutations.remove(id.as_u64());
- }
-
- 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();
+ fn create_children(&mut self, nodes: impl IntoIterator
- >) -> usize {
+ nodes
+ .into_iter()
+ .fold(0, |acc, child| acc + self.create(child))
+ }
+
+ fn create_and_insert_before(&mut self, new: &'b [VNode<'b>], before: &'b VNode<'b>) {
+ let m = self.create_children(new);
+ let id = self.find_first_element(before);
+ self.mutations.push(Mutation::InsertBefore { id, m })
+ }
+
+ fn create_and_insert_after(&mut self, new: &'b [VNode<'b>], after: &'b VNode<'b>) {
+ let m = self.create_children(new);
+ let id = self.find_last_element(after);
+ self.mutations.push(Mutation::InsertAfter { id, m })
+ }
+
+ /// Simply replace a placeholder with a list of nodes
+ fn replace_placeholder(&mut self, l: &'b VPlaceholder, r: &'b [VNode<'b>]) {
+ let m = self.create_children(r);
+ let id = l.id.get().unwrap();
+ self.mutations.push(Mutation::ReplaceWith { id, m });
+ self.reclaim(id);
+ }
+
+ fn replace(&mut self, left: &'b VNode<'b>, right: impl IntoIterator
- >) {
+ let m = self.create_children(right);
+
+ let pre_edits = self.mutations.edits.len();
+
+ self.remove_node(left, true);
+
+ // We should always have a remove mutation
+ // Eventually we don't want to generate placeholders, so this might not be true. But it's true today
+ assert!(self.mutations.edits.len() > pre_edits);
+
+ // We want to optimize the replace case to use one less mutation if possible
+ // Since mutations are done in reverse, the last node removed will be the first in the stack
+ // Instead of *just* removing it, we can use the replace mutation
+ match self.mutations.edits.pop().unwrap() {
+ Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m }),
+ _ => panic!("Expected remove mutation from remove_node"),
+ };
+ }
+
+ fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder) {
+ // Create the placeholder first, ensuring we get a dedicated ID for the placeholder
+ let placeholder = self.next_element(&l[0], &[]);
+
+ r.id.set(Some(placeholder));
+
+ self.mutations
+ .push(Mutation::CreatePlaceholder { id: placeholder });
+
+ self.remove_nodes(l);
+
+ // We want to optimize the replace case to use one less mutation if possible
+ // Since mutations are done in reverse, the last node removed will be the first in the stack
+ // Instead of *just* removing it, we can use the replace mutation
+ match self.mutations.edits.pop().unwrap() {
+ Mutation::Remove { id } => self.mutations.push(Mutation::ReplaceWith { id, m: 1 }),
+ _ => panic!("Expected remove mutation from remove_node"),
+ };
+ }
+
+ /// Remove these nodes from the dom
+ /// Wont generate mutations for the inner nodes
+ fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) {
+ nodes
+ .iter()
+ .rev()
+ .for_each(|node| self.remove_node(node, true));
+ }
+
+ fn remove_node(&mut self, node: &'b VNode<'b>, gen_muts: bool) {
+ // Clean up any attributes that have claimed a static node as dynamic for mount/unmounta
+ // Will not generate mutations!
+ self.reclaim_attributes(node);
+
+ // Remove the nested dynamic nodes
+ // We don't generate mutations for these, as they will be removed by the parent (in the next line)
+ // But we still need to make sure to reclaim them from the arena and drop their hooks, etc
+ self.remove_nested_dyn_nodes(node);
+
+ // Clean up the roots, assuming we need to generate mutations for these
+ // This is done last in order to preserve Node ID reclaim order (reclaim in reverse order of claim)
+ self.reclaim_roots(node, gen_muts);
+ }
+
+ fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
+ for (idx, _) in node.template.roots.iter().enumerate() {
+ if let Some(dy) = node.dynamic_root(idx) {
+ self.remove_dynamic_node(dy, gen_muts);
+ } else {
+ let id = node.root_ids[idx].get().unwrap();
+ if gen_muts {
+ self.mutations.push(Mutation::Remove { id });
}
+ self.reclaim(id);
}
}
}
- 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::Placeholder(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));
- }
+ fn reclaim_attributes(&mut self, node: &VNode) {
+ let mut id = None;
+ for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
+ // We'll clean up the root nodes either way, so don't worry
+ if node.template.attr_paths[idx].len() == 1 {
+ continue;
}
- }
- }
- 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::Placeholder(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));
- }
+ let next_id = attr.mounted_element.get();
+
+ if id == Some(next_id) {
+ continue;
}
+
+ id = Some(next_id);
+
+ self.reclaim(next_id);
}
}
- // 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 {
+ fn remove_nested_dyn_nodes(&mut self, node: &VNode) {
+ for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
+ // Roots are cleaned up automatically above
+ if node.template.node_paths[idx].len() == 1 {
+ continue;
+ }
+
+ self.remove_dynamic_node(dyn_node, false);
+ }
+ }
+
+ fn remove_dynamic_node(&mut self, node: &DynamicNode, gen_muts: bool) {
match node {
- VNode::Text(_) | VNode::Placeholder(_) | VNode::Element(_) => {
- self.mutations.push_root(node.mounted_id());
- 1
- }
+ Component(comp) => self.remove_component_node(comp, gen_muts),
+ Text(t) => self.remove_text_node(t, gen_muts),
+ Placeholder(t) => self.remove_placeholder(t, gen_muts),
+ Fragment(nodes) => nodes
+ .iter()
+ .for_each(|node| self.remove_node(node, gen_muts)),
+ };
+ }
- VNode::Fragment(frag) => {
- let mut added = 0;
- for child in frag.children {
- added += self.push_all_real_nodes(child);
+ fn remove_placeholder(&mut self, t: &VPlaceholder, gen_muts: bool) {
+ if let Some(id) = t.id.take() {
+ if gen_muts {
+ self.mutations.push(Mutation::Remove { id });
+ }
+ self.reclaim(id)
+ }
+ }
+
+ fn remove_text_node(&mut self, t: &VText, gen_muts: bool) {
+ if let Some(id) = t.id.take() {
+ if gen_muts {
+ self.mutations.push(Mutation::Remove { id });
+ }
+ self.reclaim(id)
+ }
+ }
+
+ fn remove_component_node(&mut self, comp: &VComponent, gen_muts: bool) {
+ let scope = comp.scope.take().unwrap();
+
+ match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+ RenderReturn::Sync(Ok(t)) => {
+ println!("Removing component node sync {:?}", gen_muts);
+ self.remove_node(t, gen_muts)
+ }
+ _ => todo!("cannot handle nonstandard nodes"),
+ };
+
+ let props = self.scopes[scope.0].props.take();
+
+ self.dirty_scopes.remove(&DirtyScope {
+ height: self.scopes[scope.0].height,
+ id: scope,
+ });
+
+ *comp.props.borrow_mut() = unsafe { std::mem::transmute(props) };
+
+ // make sure to wipe any of its props and listeners
+ self.ensure_drop_safety(scope);
+ self.scopes.remove(scope.0);
+ }
+
+ fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
+ match node.dynamic_root(0) {
+ None => node.root_ids[0].get().unwrap(),
+ Some(Text(t)) => t.id.get().unwrap(),
+ Some(Fragment(t)) => self.find_first_element(&t[0]),
+ Some(Placeholder(t)) => t.id.get().unwrap(),
+ Some(Component(comp)) => {
+ let scope = comp.scope.get().unwrap();
+ match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+ RenderReturn::Sync(Ok(t)) => self.find_first_element(t),
+ _ => todo!("cannot handle nonstandard nodes"),
}
- 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)
+ fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
+ match node.dynamic_root(node.template.roots.len() - 1) {
+ None => node.root_ids.last().unwrap().get().unwrap(),
+ Some(Text(t)) => t.id.get().unwrap(),
+ Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
+ Some(Placeholder(t)) => t.id.get().unwrap(),
+ Some(Component(comp)) => {
+ let scope = comp.scope.get().unwrap();
+ match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
+ RenderReturn::Sync(Ok(t)) => self.find_last_element(t),
+ _ => todo!("cannot handle nonstandard nodes"),
+ }
}
}
}
}
+
+/// Are the templates the same?
+///
+/// We need to check for the obvious case, and the non-obvious case where the template as cloned
+///
+/// We use the pointer of the dynamic_node list in this case
+fn templates_are_the_same<'b>(left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) -> bool {
+ std::ptr::eq(left_template, right_template)
+ || std::ptr::eq(left_template.dynamic_nodes, right_template.dynamic_nodes)
+}
+
+fn templates_are_different(left_template: &VNode, right_template: &VNode) -> bool {
+ !std::ptr::eq(left_template.template.name, right_template.template.name)
+ && left_template.template.name != right_template.template.name
+}
+
+fn matching_components<'a>(
+ left: &'a VNode<'a>,
+ right: &'a VNode<'a>,
+) -> Option, &'a VComponent<'a>)>> {
+ if left.template.roots.len() != right.template.roots.len() {
+ return None;
+ }
+
+ // run through the components, ensuring they're the same
+ left.template
+ .roots
+ .iter()
+ .zip(right.template.roots.iter())
+ .map(|(l, r)| {
+ let (l, r) = match (l, r) {
+ (TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
+ _ => return None,
+ };
+
+ let (l, r) = match (&left.dynamic_nodes[*l], &right.dynamic_nodes[*r]) {
+ (Component(l), Component(r)) => (l, r),
+ _ => return None,
+ };
+
+ Some((l, r))
+ })
+ .collect()
+}
+
+/// We can apply various optimizations to dynamic nodes that are the single child of their parent.
+///
+/// IE
+/// - for text - we can use SetTextContent
+/// - for clearning children we can use RemoveChildren
+/// - for appending children we can use AppendChildren
+#[allow(dead_code)]
+fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
+ let path = node.template.node_paths[idx];
+
+ // use a loop to index every static node's children until the path has run out
+ // only break if the last path index is a dynamic node
+ let mut static_node = &node.template.roots[path[0] as usize];
+
+ for i in 1..path.len() - 1 {
+ match static_node {
+ TemplateNode::Element { children, .. } => static_node = &children[path[i] as usize],
+ _ => return false,
+ }
+ }
+
+ match static_node {
+ TemplateNode::Element { children, .. } => children.len() == 1,
+ _ => false,
+ }
+}
diff --git a/packages/core/src/dirty_scope.rs b/packages/core/src/dirty_scope.rs
new file mode 100644
index 000000000..87fff3f45
--- /dev/null
+++ b/packages/core/src/dirty_scope.rs
@@ -0,0 +1,21 @@
+use std::hash::Hash;
+
+use crate::ScopeId;
+
+#[derive(Debug, Clone, Eq, PartialOrd, Ord)]
+pub struct DirtyScope {
+ pub height: u32,
+ pub id: ScopeId,
+}
+
+impl PartialEq for DirtyScope {
+ fn eq(&self, other: &Self) -> bool {
+ self.id == other.id
+ }
+}
+
+impl Hash for DirtyScope {
+ fn hash(&self, state: &mut H) {
+ self.id.hash(state);
+ }
+}
diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs
new file mode 100644
index 000000000..6f40c2484
--- /dev/null
+++ b/packages/core/src/error_boundary.rs
@@ -0,0 +1,19 @@
+use std::cell::RefCell;
+
+use crate::ScopeId;
+
+/// A boundary that will capture any errors from child components
+#[allow(dead_code)]
+pub struct ErrorBoundary {
+ error: RefCell>,
+ id: ScopeId,
+}
+
+impl ErrorBoundary {
+ pub fn new(id: ScopeId) -> Self {
+ Self {
+ error: RefCell::new(None),
+ id,
+ }
+ }
+}
diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs
index 0668f7582..9ab01e4d9 100644
--- a/packages/core/src/events.rs
+++ b/packages/core/src/events.rs
@@ -1,182 +1,165 @@
-//! Internal and external event system
-//!
-//!
-//! This is all kinda WIP, but the bones are there.
+use std::{
+ cell::{Cell, RefCell},
+ rc::Rc,
+};
-use crate::{ElementId, ScopeId};
-use std::{any::Any, cell::Cell, fmt::Debug, rc::Rc, sync::Arc};
-
-pub(crate) struct BubbleState {
- pub canceled: Cell,
+/// A wrapper around some generic data that handles the event's state
+///
+///
+/// Prevent this event from continuing to bubble up the tree to parent elements.
+///
+/// # Example
+///
+/// ```rust, ignore
+/// rsx! {
+/// button {
+/// onclick: move |evt: Event| {
+/// evt.cancel_bubble();
+///
+/// }
+/// }
+/// }
+/// ```
+pub struct Event {
+ /// The data associated with this event
+ pub data: Rc,
+ pub(crate) propogates: Rc>,
}
-impl BubbleState {
- pub fn new() -> Self {
+impl Event {
+ /// Prevent this event from continuing to bubble up the tree to parent elements.
+ ///
+ /// # Example
+ ///
+ /// ```rust, ignore
+ /// rsx! {
+ /// button {
+ /// onclick: move |evt: Event| {
+ /// evt.cancel_bubble();
+ /// }
+ /// }
+ /// }
+ /// ```
+ #[deprecated = "use stop_propogation instead"]
+ pub fn cancel_bubble(&self) {
+ self.propogates.set(false);
+ }
+
+ /// Prevent this event from continuing to bubble up the tree to parent elements.
+ ///
+ /// # Example
+ ///
+ /// ```rust, ignore
+ /// rsx! {
+ /// button {
+ /// onclick: move |evt: Event| {
+ /// evt.cancel_bubble();
+ /// }
+ /// }
+ /// }
+ /// ```
+ pub fn stop_propogation(&self) {
+ self.propogates.set(false);
+ }
+
+ /// Get a reference to the inner data from this event
+ ///
+ /// ```rust, ignore
+ /// rsx! {
+ /// button {
+ /// onclick: move |evt: Event| {
+ /// let data = evt.inner.clone();
+ /// cx.spawn(async move {
+ /// println!("{:?}", data);
+ /// });
+ /// }
+ /// }
+ /// }
+ /// ```
+ pub fn inner(&self) -> &Rc {
+ &self.data
+ }
+}
+
+impl Clone for Event {
+ fn clone(&self) -> Self {
Self {
- canceled: Cell::new(false),
+ propogates: self.propogates.clone(),
+ data: self.data.clone(),
}
}
}
-/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
+impl std::ops::Deref for Event {
+ type Target = Rc;
+ fn deref(&self) -> &Self::Target {
+ &self.data
+ }
+}
+
+impl std::fmt::Debug for Event {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("UiEvent")
+ .field("bubble_state", &self.propogates)
+ .field("data", &self.data)
+ .finish()
+ }
+}
+
+/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
///
-/// 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.
+/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
///
-/// 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!")
+/// 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),
+/// }
/// })
/// }
///
-/// 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,
+pub struct EventHandler<'bump, T = ()> {
+ pub(super) callback: RefCell>>,
}
-/// 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 })
+impl Default for EventHandler<'_, T> {
+ fn default() -> Self {
+ Self {
+ callback: Default::default(),
+ }
}
}
-/// 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,
+type ExternalListenerCallback<'bump, T> = bumpalo::boxed::Box<'bump, dyn FnMut(T) + 'bump>;
- #[allow(unused)]
- bubble_state: Rc,
-}
+impl EventHandler<'_, T> {
+ /// Call this event handler with the appropriate event type
+ ///
+ /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic.
+ pub fn call(&self, event: T) {
+ if let Some(callback) = self.callback.borrow_mut().as_mut() {
+ callback(event);
+ }
+ }
-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);
+ /// Forcibly drop the internal handler callback, releasing memory
+ ///
+ /// This will force any future calls to "call" to not doing anything
+ pub fn release(&self) {
+ self.callback.replace(None);
}
}
diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs
new file mode 100644
index 000000000..776d3e37a
--- /dev/null
+++ b/packages/core/src/fragment.rs
@@ -0,0 +1,103 @@
+use crate::innerlude::*;
+
+/// Create inline fragments using Component syntax.
+///
+/// ## Details
+///
+/// Fragments capture a series of children without rendering extra nodes.
+///
+/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
+/// a key is needed to identify each item.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// rsx!{
+/// Fragment { key: "abc" }
+/// }
+/// ```
+///
+/// ## Usage
+///
+/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
+/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
+///
+/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
+///
+/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
+#[allow(non_upper_case_globals, non_snake_case)]
+pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
+ let children = cx.props.0.as_ref().map_err(|e| anyhow::anyhow!("{}", e))?;
+ Ok(VNode {
+ key: children.key,
+ parent: children.parent,
+ template: children.template,
+ root_ids: children.root_ids,
+ dynamic_nodes: children.dynamic_nodes,
+ dynamic_attrs: children.dynamic_attrs,
+ })
+}
+
+pub struct FragmentProps<'a>(Element<'a>);
+pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
+impl<'a> FragmentBuilder<'a, false> {
+ pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
+ FragmentBuilder(children)
+ }
+}
+impl<'a, const A: bool> FragmentBuilder<'a, A> {
+ pub fn build(self) -> FragmentProps<'a> {
+ FragmentProps(self.0)
+ }
+}
+
+/// Access the children elements passed into the component
+///
+/// This enables patterns where a component is passed children from its parent.
+///
+/// ## Details
+///
+/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
+/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
+/// on the props that takes Context.
+///
+/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
+/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
+/// props are valid for the static lifetime.
+///
+/// ## Example
+///
+/// ```rust, ignore
+/// fn App(cx: Scope) -> Element {
+/// cx.render(rsx!{
+/// CustomCard {
+/// h1 {}
+/// p {}
+/// }
+/// })
+/// }
+///
+/// #[derive(PartialEq, Props)]
+/// struct CardProps {
+/// children: Element
+/// }
+///
+/// fn CustomCard(cx: Scope) -> Element {
+/// cx.render(rsx!{
+/// div {
+/// h1 {"Title card"}
+/// {cx.props.children}
+/// }
+/// })
+/// }
+/// ```
+impl<'a> Properties for FragmentProps<'a> {
+ type Builder = FragmentBuilder<'a, false>;
+ const IS_STATIC: bool = false;
+ fn builder() -> Self::Builder {
+ FragmentBuilder(VNode::empty())
+ }
+ unsafe fn memoize(&self, _other: &Self) -> bool {
+ false
+ }
+}
diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs
index 23c74eca0..2b7c4feb3 100644
--- a/packages/core/src/lazynodes.rs
+++ b/packages/core/src/lazynodes.rs
@@ -3,7 +3,7 @@
//! 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.
+//! To support returning rsx! from branches in match statements, we need to use dynamic dispatch on [`ScopeState`] 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.
@@ -11,27 +11,25 @@
//! 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;
+use smallbox::{smallbox, space::S16, SmallBox};
+
+use crate::{innerlude::VNode, ScopeState};
/// 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`].
+/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
///
///
/// ```rust, ignore
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
/// ```
pub struct LazyNodes<'a, 'b> {
- inner: StackNodeStorage<'a, 'b>,
-}
+ #[cfg(not(miri))]
+ inner: SmallBox VNode<'a> + 'b, S16>,
-type StackHeapSize = [usize; 16];
-
-enum StackNodeStorage<'a, 'b> {
- Stack(LazyStack),
- Heap(Box>) -> Option> + 'b>),
+ #[cfg(miri)]
+ inner: Box VNode<'a> + 'b>,
}
impl<'a, 'b> LazyNodes<'a, 'b> {
@@ -40,118 +38,22 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
/// 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 {
+ pub fn new(val: impl FnOnce(&'a ScopeState) -> 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"),
- )
- })),
- }
- }
+ #[cfg(not(miri))]
+ inner: smallbox!(move |f| {
+ let val = slot.take().expect("cannot call LazyNodes twice");
+ val(f)
+ }),
- 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,
- }),
- }
+ #[cfg(miri)]
+ inner: Box::new(move |f| {
+ let val = slot.take().expect("cannot call LazyNodes twice");
+ val(f)
+ }),
}
}
@@ -160,163 +62,10 @@ impl<'a, 'b> LazyNodes<'a, 'b> {
/// ```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);
+ pub fn call(mut self, f: &'a ScopeState) -> VNode<'a> {
+ (self.inner)(f)
}
}
diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs
index 4e497d690..7ce2b723f 100644
--- a/packages/core/src/lib.rs
+++ b/packages/core/src/lib.rs
@@ -1,33 +1,43 @@
-#![allow(non_snake_case)]
#![doc = include_str!("../README.md")]
-#![deny(missing_docs)]
+#![warn(missing_docs)]
-pub(crate) mod arbitrary_value;
-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 util;
-pub(crate) mod virtual_dom;
+mod any_props;
+mod arena;
+mod bump_frame;
+mod create;
+mod diff;
+mod dirty_scope;
+mod error_boundary;
+mod events;
+mod fragment;
+mod lazynodes;
+mod mutations;
+mod nodes;
+mod properties;
+mod scheduler;
+mod scope_arena;
+mod scopes;
+mod virtual_dom;
pub(crate) mod innerlude {
- pub use crate::arbitrary_value::*;
+ pub use crate::arena::*;
+ pub use crate::dirty_scope::*;
+ pub use crate::error_boundary::*;
pub use crate::events::*;
+ pub use crate::fragment::*;
pub use crate::lazynodes::*;
pub use crate::mutations::*;
+ pub use crate::nodes::RenderReturn;
pub use crate::nodes::*;
pub use crate::properties::*;
+ pub use crate::scheduler::*;
pub use crate::scopes::*;
- pub use crate::util::*;
pub use crate::virtual_dom::*;
- /// An [`Element`] is a possibly-none [`VNode`] created by calling `render` on [`Scope`] or [`ScopeState`].
+ /// An [`Element`] is a possibly-errored [`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>;
+ /// An Errored [`Element`] will propagate the error to the nearest error boundary.
+ pub type Element<'a> = Result, anyhow::Error>;
/// A [`Component`] is a function that takes a [`Scope`] and returns an [`Element`].
///
@@ -57,16 +67,13 @@ pub(crate) mod innerlude {
/// )
/// ```
pub type Component = fn(Scope ) -> Element;
-
- /// A list of attributes
- pub type Attributes<'a> = Option<&'a [Attribute<'a>]>;
}
pub use crate::innerlude::{
- AnyEvent, Attribute, AttributeValue, Component, DioxusElement, DomEdit, Element, ElementId,
- ElementIdIterator, EventHandler, EventPriority, IntoVNode, LazyNodes, Listener, Mutations,
- NodeFactory, Properties, SchedulerMsg, Scope, ScopeId, ScopeState, TaskId, UiEvent, UserEvent,
- VComponent, VElement, VFragment, VNode, VPlaceholder, VText, VirtualDom,
+ fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
+ Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope,
+ ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute,
+ TemplateNode, VComponent, VNode, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types
@@ -74,8 +81,9 @@ pub use crate::innerlude::{
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
- fc_to_builder, Attributes, Component, DioxusElement, Element, EventHandler, Fragment,
- LazyNodes, NodeFactory, Properties, Scope, ScopeId, ScopeState, VNode, VirtualDom,
+ fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
+ Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
+ VNode, VirtualDom,
};
}
@@ -83,37 +91,4 @@ 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();
- )*}
}
diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs
index 4218e613f..f8553cd46 100644
--- a/packages/core/src/mutations.rs
+++ b/packages/core/src/mutations.rs
@@ -1,350 +1,257 @@
-//! 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 rustc_hash::FxHashSet;
-use crate::innerlude::*;
-use std::{any::Any, fmt::Debug};
+use crate::{arena::ElementId, ScopeId, Template};
-/// ## Mutations
+/// A container for all the relevant steps to modify the Real DOM
///
-/// 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.
+/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the
+/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes
+/// in the form of a [`Mutation`].
+///
+/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately.
+///
+/// Templates, however, apply to all subtrees, not just target subtree.
///
/// Mutations are the only link between the RealDOM and the VirtualDOM.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[derive(Debug, Default)]
+#[must_use = "not handling edits can lead to visual inconsistencies in UI"]
pub struct Mutations<'a> {
- /// The list of edits that need to be applied for the RealDOM to match the VirtualDOM.
- pub edits: Vec>,
+ /// The ID of the subtree that these edits are targetting
+ pub subtree: usize,
/// 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>,
+ /// Any templates encountered while diffing the DOM.
+ ///
+ /// These must be loaded into a cache before applying the edits
+ pub templates: Vec>,
+
+ /// Any mutations required to patch the renderer to match the layout of the VirtualDom
+ pub edits: 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()
+impl<'a> Mutations<'a> {
+ /// Rewrites IDs to just be "template", so you can compare the mutations
+ ///
+ /// Used really only for testing
+ pub fn santize(mut self) -> Self {
+ for edit in self.edits.iter_mut() {
+ if let Mutation::LoadTemplate { name, .. } = edit {
+ *name = "template"
+ }
+ }
+
+ self
+ }
+
+ /// Push a new mutation into the dom_edits list
+ pub(crate) fn push(&mut self, mutation: Mutation<'static>) {
+ self.edits.push(mutation)
}
}
-/// 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)]
+/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
+/// of the Dioxus VirtualDom.
+///
+/// These edits can be serialized and sent over the network or through any interface
#[cfg_attr(
feature = "serialize",
derive(serde::Serialize, serde::Deserialize),
serde(tag = "type")
)]
-pub enum DomEdit<'bump> {
- /// Push the given root node onto our stack.
- PushRoot {
- /// The ID of the root node to push.
- root: u64,
- },
-
- /// Pop the topmost node from our stack and append them to the node
- /// at the top of the stack.
+#[derive(Debug, PartialEq, Eq)]
+pub enum Mutation<'a> {
+ /// Add these m children to the target element
AppendChildren {
- /// How many nodes should be popped from the stack.
- /// The node remaining on the stack will be the target for the append.
- many: u32,
+ /// The ID of the element being mounted to
+ id: ElementId,
+
+ /// The number of nodes on the stack
+ m: usize,
},
- /// Replace a given (single) node with a handful of nodes currently on the stack.
- ReplaceWith {
- /// The ID of the node to be replaced.
- root: u64,
+ /// Assign the element at the given path the target ElementId.
+ ///
+ /// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
+ /// element, hence the use of a single byte.
+ ///
+ ///
+ AssignId {
+ /// The path of the child of the topmost node on the stack
+ ///
+ /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+ /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+ path: &'static [u8],
- /// How many nodes should be popped from the stack to replace the target node.
- m: u32,
+ /// The ID we're assigning to this element/placeholder.
+ ///
+ /// This will be used later to modify the element or replace it with another element.
+ id: ElementId,
+ },
+
+ /// Create an placeholder int he DOM that we will use later.
+ ///
+ /// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
+ CreatePlaceholder {
+ /// The ID we're assigning to this element/placeholder.
+ ///
+ /// This will be used later to modify the element or replace it with another element.
+ id: ElementId,
+ },
+
+ /// Create a node specifically for text with the given value
+ CreateTextNode {
+ /// The text content of this text node
+ value: &'a str,
+
+ /// The ID we're assigning to this specific text nodes
+ ///
+ /// This will be used later to modify the element or replace it with another element.
+ id: ElementId,
+ },
+
+ /// Hydrate an existing text node at the given path with the given text.
+ ///
+ /// Assign this text node the given ID since we will likely need to modify this text at a later point
+ HydrateText {
+ /// The path of the child of the topmost node on the stack
+ ///
+ /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+ /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+ path: &'static [u8],
+
+ /// The value of the textnode that we want to set the placeholder with
+ value: &'a str,
+
+ /// The ID we're assigning to this specific text nodes
+ ///
+ /// This will be used later to modify the element or replace it with another element.
+ id: ElementId,
+ },
+
+ /// Load and clone an existing node from a template saved under that specific name
+ ///
+ /// Dioxus guarantees that the renderer will have already been provided the template.
+ /// When the template is picked up in the template list, it should be saved under its "name" - here, the name
+ LoadTemplate {
+ /// The "name" of the template. When paired with `rsx!`, this is autogenerated
+ name: &'static str,
+
+ /// Which root are we loading from the template?
+ ///
+ /// The template is stored as a list of nodes. This index represents the position of that root
+ index: usize,
+
+ /// The ID we're assigning to this element being loaded from the template
+ ///
+ /// This will be used later to move the element around in lists
+ id: ElementId,
+ },
+
+ /// Replace the target element (given by its ID) with the topmost m nodes on the stack
+ ReplaceWith {
+ /// The ID of the node we're going to replace with
+ id: ElementId,
+
+ /// The number of nodes on the stack to use to replace
+ m: usize,
+ },
+
+ /// Replace an existing element in the template at the given path with the m nodes on the stack
+ ReplacePlaceholder {
+ /// The path of the child of the topmost node on the stack
+ ///
+ /// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
+ /// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
+ path: &'static [u8],
+
+ /// The number of nodes on the stack to use to replace
+ m: usize,
},
/// Insert a number of nodes after a given node.
InsertAfter {
/// The ID of the node to insert after.
- root: u64,
+ id: ElementId,
- /// How many nodes should be popped from the stack to insert after the target node.
- n: u32,
+ /// The ids of the nodes to insert after the target node.
+ m: usize,
},
/// Insert a number of nodes before a given node.
InsertBefore {
/// The ID of the node to insert before.
- root: u64,
+ id: ElementId,
- /// How many nodes should be popped from the stack to insert before the target node.
- n: u32,
+ /// The ids of the nodes to insert before the target node.
+ m: usize,
},
- /// Remove a particular node from the DOM
- Remove {
- /// The ID of the node to remove.
- root: u64,
+ /// Set the value of a node's attribute.
+ SetAttribute {
+ /// The name of the attribute to set.
+ name: &'a str,
+ /// The value of the attribute.
+ value: &'a str,
+
+ /// The ID of the node to set the attribute of.
+ id: ElementId,
+
+ /// The (optional) namespace of the attribute.
+ /// For instance, "style" is in the "style" namespace.
+ ns: Option<&'a str>,
},
- /// Create a new purely-text node
- CreateTextNode {
- /// The ID the new node should have.
- root: u64,
+ /// Set the value of a node's attribute.
+ SetBoolAttribute {
+ /// The name of the attribute to set.
+ name: &'a str,
+ /// The value of the attribute.
+ value: bool,
+
+ /// The ID of the node to set the attribute of.
+ id: ElementId,
+ },
+
+ /// Set the textcontent of a node.
+ SetText {
/// The textcontent of the node
- text: &'bump str,
- },
+ value: &'a str,
- /// Create a new purely-element node
- CreateElement {
- /// The ID the new node should have.
- root: u64,
-
- /// The tagname of the node
- tag: &'bump str,
- },
-
- /// Create a new purely-comment node with a given namespace
- CreateElementNs {
- /// The ID the new node should have.
- root: u64,
-
- /// The namespace of the node
- tag: &'bump str,
-
- /// The namespace of the node (like `SVG`)
- ns: &'static str,
- },
-
- /// 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: u64,
+ /// The ID of the node to set the textcontent of.
+ id: ElementId,
},
/// Create a new Event Listener.
NewEventListener {
/// The name of the event to listen for.
- event_name: &'static str,
+ name: &'a str,
/// The ID of the node to attach the listener to.
- scope: ScopeId,
-
- /// The ID of the node to attach the listener to.
- root: u64,
+ id: ElementId,
},
/// Remove an existing Event Listener.
RemoveEventListener {
- /// The ID of the node to remove.
- root: u64,
-
/// The name of the event to remove.
- event: &'static str,
- },
+ name: &'a str,
- /// Set the textcontent of a node.
- SetText {
- /// The ID of the node to set the textcontent of.
- root: u64,
-
- /// 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: u64,
-
- /// 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: u64,
-
- /// The name of the attribute to remove.
- name: &'static str,
-
- /// The namespace of the attribute.
- ns: Option<&'bump str>,
+ id: ElementId,
},
- /// Manually pop a root node from the stack.
- PopRoot {},
-}
-
-use fxhash::FxHashSet;
-use DomEdit::*;
-
-impl<'a> Mutations<'a> {
- pub(crate) fn new() -> Self {
- Self {
- edits: Vec::new(),
- refs: Vec::new(),
- dirty_scopes: Default::default(),
- }
- }
-
- // Navigation
- pub(crate) fn push_root(&mut self, root: ElementId) {
- let id = root.as_u64();
- self.edits.push(PushRoot { root: id });
- }
-
- // Navigation
- pub(crate) fn pop_root(&mut self) {
- self.edits.push(PopRoot {});
- }
-
- pub(crate) fn replace_with(&mut self, root: ElementId, m: u32) {
- let root = root.as_u64();
- self.edits.push(ReplaceWith { m, root });
- }
-
- pub(crate) fn insert_after(&mut self, root: ElementId, n: u32) {
- let root = root.as_u64();
- self.edits.push(InsertAfter { n, root });
- }
-
- pub(crate) fn insert_before(&mut self, root: ElementId, n: u32) {
- let root = root.as_u64();
- self.edits.push(InsertBefore { n, root });
- }
-
- pub(crate) fn append_children(&mut self, n: u32) {
- self.edits.push(AppendChildren { many: n });
- }
-
- // Remove Nodes from the dom
- pub(crate) fn remove(&mut self, id: u64) {
- self.edits.push(Remove { root: id });
- }
-
- // Create
- pub(crate) fn create_text_node(&mut self, text: &'a str, id: ElementId) {
- let id = id.as_u64();
- self.edits.push(CreateTextNode { text, root: id });
- }
-
- pub(crate) fn create_element(
- &mut self,
- tag: &'static str,
- ns: Option<&'static str>,
+ /// Remove a particular node from the DOM
+ Remove {
+ /// The ID of the node to remove.
id: ElementId,
- ) {
- let id = id.as_u64();
- match ns {
- Some(ns) => self.edits.push(CreateElementNs { root: id, ns, tag }),
- None => self.edits.push(CreateElement { root: id, tag }),
- }
- }
- // 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: ElementId) {
- let id = id.as_u64();
- 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 = mounted_node.get().unwrap().as_u64();
-
- self.edits.push(NewEventListener {
- scope,
- event_name: event,
- root: element_id,
- });
- }
- pub(crate) fn remove_event_listener(&mut self, event: &'static str, root: u64) {
- self.edits.push(RemoveEventListener { event, root });
- }
-
- // modify
- pub(crate) fn set_text(&mut self, text: &'a str, root: u64) {
- self.edits.push(SetText { text, root });
- }
-
- pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) {
- let Attribute {
- name,
- value,
- namespace,
- ..
- } = attribute;
-
- self.edits.push(SetAttribute {
- field: name,
- value: value.clone(),
- ns: *namespace,
- root,
- });
- }
-
- pub(crate) fn remove_attribute(&mut self, attribute: &Attribute, root: u64) {
- let Attribute {
- name, namespace, ..
- } = attribute;
-
- self.edits.push(RemoveAttribute {
- name,
- ns: *namespace,
- root,
- });
- }
-
- pub(crate) fn mark_dirty_scope(&mut self, scope: ScopeId) {
- self.dirty_scopes.insert(scope);
- }
-}
-
-// 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::())
- }
+ /// Push the given root node onto our stack.
+ PushRoot {
+ /// The ID of the root node to push.
+ id: ElementId,
+ },
}
diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs
index 8bd91fdb1..288b15271 100644
--- a/packages/core/src/nodes.rs
+++ b/packages/core/src/nodes.rs
@@ -1,862 +1,609 @@
-//! 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,
+ any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
};
-use bumpalo::{boxed::Box as BumpBox, Bump};
+use bumpalo::boxed::Box as BumpBox;
+use bumpalo::Bump;
use std::{
+ any::{Any, TypeId},
cell::{Cell, RefCell},
- fmt::{Arguments, Debug, Formatter},
+ fmt::Arguments,
+ future::Future,
};
-/// A composable "VirtualNode" to declare a User Interface in the Dioxus VirtualDOM.
+pub type TemplateId = &'static str;
+
+/// The actual state of the component's most recent computation
///
-/// VNodes are designed to be lightweight and used with with a bump allocator. To create a VNode, you can use either of:
+/// Because Dioxus accepts components in the form of `async fn(Scope) -> Result`, we need to support both
+/// sync and async versions.
///
-/// - 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>),
+/// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor
+/// you might need to handle the case where there's no node immediately ready.
+pub enum RenderReturn<'a> {
+ /// A currently-available element
+ Sync(Element<'a>),
- /// 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>),
-
- /// Placeholders are a type of placeholder VNode used when fragments don't contain any children.
- ///
- /// Placeholders cannot be directly constructed via public APIs.
- ///
- /// # Example
- ///
- /// ```rust, ignore
- /// let mut vdom = VirtualDom::new();
- ///
- /// let node = vdom.render_vnode(rsx!( Fragment {} ));
- ///
- /// if let VNode::Fragment(frag) = node {
- /// let root = &frag.children[0];
- /// assert_eq!(root, VNode::Anchor);
- /// }
- /// ```
- Placeholder(&'src VPlaceholder),
+ /// An ongoing future that will resolve to a [`Element`]
+ Async(BumpBox<'a, dyn Future> + 'a>),
}
-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::Text(_t) => None,
- VNode::Placeholder(_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::Placeholder(el) => el.id.get(),
- VNode::Fragment(_) => None,
- VNode::Component(_) => 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::Placeholder(a) => VNode::Placeholder(a),
- VNode::Fragment(f) => VNode::Fragment(f),
- }
- }
-}
-
-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::Placeholder(t) => s
- .debug_struct("VNode::Placholder")
- .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(),
- }
- }
-}
-
-/// An Element's unique identifier.
+/// A reference to a template along with any context needed to hydrate it
///
-/// `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>,
-
- /// Fragments can never have zero children. Enforced by NodeFactory.
+/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
+/// static parts of the template.
+#[derive(Debug, Clone)]
+pub struct VNode<'a> {
+ /// The key given to the root of this template.
///
- /// 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>],
-}
-
-/// 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.
+ /// In fragments, this is the key of the first child. In other cases, it is the key of the root.
pub key: Option<&'a str>,
- /// The tag name of the element.
- ///
- /// IE "div"
- pub tag: &'static str,
+ /// When rendered, this template will be linked to its parent manually
+ pub parent: Option,
- /// The namespace of the VElement
- ///
- /// IE "svg"
- pub namespace: Option<&'static str>,
+ /// The static nodes and static descriptor of the template
+ pub template: Template<'static>,
- /// The parent of the Element (if any).
- ///
- /// Used when bubbling events
- pub parent: Cell>,
+ /// The IDs for the roots of this template - to be used when moving the template around and removing it from
+ /// the actual Dom
+ pub root_ids: &'a [Cell >],
- /// The Listeners of the VElement.
- pub listeners: &'a [Listener<'a>],
+ /// The dynamic parts of the template
+ pub dynamic_nodes: &'a [DynamicNode<'a>],
- /// The attributes of the VElement.
- pub attributes: &'a [Attribute<'a>],
-
- /// The children of the VElement.
- pub children: &'a [VNode<'a>],
+ /// The dynamic parts of the template
+ pub dynamic_attrs: &'a [Attribute<'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)
+impl<'a> VNode<'a> {
+ /// Create a template with no nodes that will be skipped over during diffing
+ pub fn empty() -> Element<'a> {
+ Ok(VNode {
+ key: None,
+ parent: None,
+ root_ids: &[],
+ dynamic_nodes: &[],
+ dynamic_attrs: &[],
+ template: Template {
+ name: "dioxus-empty",
+ roots: &[],
+ node_paths: &[],
+ attr_paths: &[],
+ },
+ })
+ }
+
+ /// Load a dynamic root at the given index
+ ///
+ /// Returns [`None`] if the root is actually a static node (Element/Text)
+ pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
+ match &self.template.roots[idx] {
+ TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
+ TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
+ Some(&self.dynamic_nodes[*id])
+ }
+ }
+ }
+
+ pub(crate) fn clear_listeners(&self) {
+ for attr in self.dynamic_attrs {
+ if let AttributeValue::Listener(l) = &attr.value {
+ l.borrow_mut().take();
+ }
+ }
+ }
+}
+
+/// A static layout of a UI tree that describes a set of dynamic and static nodes.
+///
+/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
+/// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use
+/// its static description of the UI to skip immediately to the dynamic nodes during diffing.
+///
+/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
+/// ways, with the suggested approach being the unique code location (file, line, col, etc).
+#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
+#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
+pub struct Template<'a> {
+ /// The name of the template. This must be unique across your entire program for template diffing to work properly
+ ///
+ /// If two templates have the same name, it's likely that Dioxus will panic when diffing.
+ pub name: &'a str,
+
+ /// The list of template nodes that make up the template
+ ///
+ /// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
+ pub roots: &'a [TemplateNode<'a>],
+
+ /// The paths of each node relative to the root of the template.
+ ///
+ /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
+ /// topmost element, not the `roots` field.
+ pub node_paths: &'a [&'a [u8]],
+
+ /// The paths of each dynamic attribute relative to the root of the template
+ ///
+ /// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
+ /// topmost element, not the `roots` field.
+ pub attr_paths: &'a [&'a [u8]],
+}
+
+impl<'a> Template<'a> {
+ /// Is this template worth caching at all, since it's completely runtime?
+ ///
+ /// There's no point in saving templates that are completely dynamic, since they'll be recreated every time anyway.
+ pub fn is_completely_dynamic(&self) -> bool {
+ use TemplateNode::*;
+ self.roots
+ .iter()
+ .all(|root| matches!(root, Dynamic { .. } | DynamicText { .. }))
+ }
+}
+
+/// A statically known node in a layout.
+///
+/// This can be created at compile time, saving the VirtualDom time when diffing the tree
+#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
+#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
+pub enum TemplateNode<'a> {
+ /// An statically known element in the dom.
+ ///
+ /// In HTML this would be something like ` `
+ Element {
+ /// The name of the element
+ ///
+ /// IE for a div, it would be the string "div"
+ tag: &'a str,
+
+ /// The namespace of the element
+ ///
+ /// In HTML, this would be a valid URI that defines a namespace for all elements below it
+ /// SVG is an example of this namespace
+ namespace: Option<&'a str>,
+
+ /// A list of possibly dynamic attribues for this element
+ ///
+ /// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
+ attrs: &'a [TemplateAttribute<'a>],
+
+ /// A list of template nodes that define another set of template nodes
+ children: &'a [TemplateNode<'a>],
+ },
+
+ /// This template node is just a piece of static text
+ Text {
+ /// The actual text
+ text: &'a str,
+ },
+
+ /// This template node is unknown, and needs to be created at runtime.
+ Dynamic {
+ /// The index of the dynamic node in the VNode's dynamic_nodes list
+ id: usize,
+ },
+
+ /// This template node is known to be some text, but needs to be created at runtime
+ ///
+ /// This is separate from the pure Dynamic variant for various optimizations
+ DynamicText {
+ /// The index of the dynamic node in the VNode's dynamic_nodes list
+ id: usize,
+ },
+}
+
+/// A node created at runtime
+///
+/// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
+#[derive(Debug)]
+pub enum DynamicNode<'a> {
+ /// A component node
+ ///
+ /// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
+ /// assigned scope are dynamic.
+ ///
+ /// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
+ /// the render function at runtime
+ Component(VComponent<'a>),
+
+ /// A text node
+ Text(VText<'a>),
+
+ /// A placeholder
+ ///
+ /// Used by suspense when a node isn't ready and by fragments that don't render anything
+ ///
+ /// In code, this is just an ElementId whose initial value is set to 0 upon creation
+ Placeholder(VPlaceholder),
+
+ /// A list of VNodes.
+ ///
+ /// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
+ /// or iterators.
+ Fragment(&'a [VNode<'a>]),
+}
+
+impl Default for DynamicNode<'_> {
+ fn default() -> Self {
+ Self::Placeholder(Default::default())
+ }
+}
+
+/// An instance of a child component
+pub struct VComponent<'a> {
+ /// The name of this component
+ pub name: &'static str,
+
+ /// Are the props valid for the 'static lifetime?
+ ///
+ /// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it.
+ ///
+ /// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement
+ pub static_props: bool,
+
+ /// The assigned Scope for this component
+ pub scope: Cell >,
+
+ /// The function pointer of the component, known at compile time
+ ///
+ /// It is possible that components get folded at comppile time, so these shouldn't be really used as a key
+ pub render_fn: *const (),
+
+ pub(crate) props: RefCell + 'a>>>,
+}
+
+impl<'a> std::fmt::Debug for VComponent<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ f.debug_struct("VComponent")
+ .field("name", &self.name)
+ .field("static_props", &self.static_props)
+ .field("scope", &self.scope)
.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;
+/// An instance of some text, mounted to the DOM
+#[derive(Debug)]
+pub struct VText<'a> {
+ /// The actual text itself
+ pub value: &'a 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
- }
+ /// The ID of this node in the real DOM
+ pub id: Cell >,
}
-/// An attribute on a DOM node, such as `id="my-thing"` or
-/// `href="https://example.com"`.
-#[derive(Clone, Debug)]
+/// A placeholder node, used by suspense and fragments
+#[derive(Debug, Default)]
+pub struct VPlaceholder {
+ /// The ID of this node in the real DOM
+ pub id: Cell >,
+}
+
+/// An attribute of the TemplateNode, created at compile time
+#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
+#[cfg_attr(
+ feature = "serialize",
+ derive(serde::Serialize, serde::Deserialize),
+ serde(tag = "type")
+)]
+pub enum TemplateAttribute<'a> {
+ /// This attribute is entirely known at compile time, enabling
+ Static {
+ /// The name of this attribute.
+ ///
+ /// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
+ name: &'a str,
+
+ /// The value of this attribute, known at compile time
+ ///
+ /// Currently this only accepts &str, so values, even if they're known at compile time, are not known
+ value: &'a str,
+
+ /// The namespace of this attribute. Does not exist in the HTML spec
+ namespace: Option<&'a str>,
+ },
+
+ /// The attribute in this position is actually determined dynamically at runtime
+ ///
+ /// This is the index into the dynamic_attributes field on the container VNode
+ Dynamic {
+ /// The index
+ id: usize,
+ },
+}
+
+/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
+#[derive(Debug)]
pub struct Attribute<'a> {
/// The name of the attribute.
- pub name: &'static str,
+ pub name: &'a str,
- /// The value of the attribute.
+ /// 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.
+ /// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
pub namespace: Option<&'static str>,
+
+ /// The element in the DOM that this attribute belongs to
+ pub mounted_element: Cell,
+
+ /// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
+ pub volatile: bool,
}
-/// 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>,
+/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
+///
+/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
+/// variant.
+pub enum AttributeValue<'a> {
+ /// Text attribute
+ Text(&'a str),
- /// The type of event to listen for.
+ /// A float
+ Float(f64),
+
+ /// Signed integer
+ Int(i64),
+
+ /// Boolean
+ Bool(bool),
+
+ /// A listener, like "onclick"
+ Listener(RefCell >>),
+
+ /// An arbitrary value that implements PartialEq and is static
+ Any(BumpBox<'a, dyn AnyValue>),
+
+ /// A "none" value, resulting in the removal of an attribute from the dom
+ None,
+}
+
+type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event) + 'a>;
+
+impl<'a> std::fmt::Debug for AttributeValue<'a> {
+ fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
+ match self {
+ Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
+ Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
+ Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
+ Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
+ Self::Listener(_) => f.debug_tuple("Listener").finish(),
+ Self::Any(_) => f.debug_tuple("Any").finish(),
+ Self::None => write!(f, "None"),
+ }
+ }
+}
+
+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::Listener(_), Self::Listener(_)) => true,
+ (Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
+ _ => core::mem::discriminant(self) == core::mem::discriminant(other),
+ }
+ }
+}
+
+#[doc(hidden)]
+pub trait AnyValue {
+ fn any_cmp(&self, other: &dyn AnyValue) -> bool;
+ fn our_typeid(&self) -> TypeId;
+}
+
+impl AnyValue for T {
+ fn any_cmp(&self, other: &dyn AnyValue) -> bool {
+ if self.type_id() != other.our_typeid() {
+ return false;
+ }
+
+ self == unsafe { &*(other as *const _ as *const T) }
+ }
+
+ fn our_typeid(&self) -> TypeId {
+ self.type_id()
+ }
+}
+
+#[doc(hidden)]
+pub trait ComponentReturn<'a, A = ()> {
+ fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
+}
+
+impl<'a> ComponentReturn<'a> for Element<'a> {
+ fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
+ RenderReturn::Sync(self)
+ }
+}
+
+#[doc(hidden)]
+pub struct AsyncMarker;
+impl<'a, F> ComponentReturn<'a, AsyncMarker> for F
+where
+ F: Future> + 'a,
+{
+ fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
+ let f: &mut dyn Future> = cx.bump().alloc(self);
+ RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
+ }
+}
+
+impl<'a> RenderReturn<'a> {
+ pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
+ unsafe { std::mem::transmute(self) }
+ }
+ pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
+ unsafe { std::mem::transmute(self) }
+ }
+}
+
+/// A trait that allows various items to be converted into a dynamic node for the rsx macro
+pub trait IntoDynNode<'a, A = ()> {
+ /// Consume this item along with a scopestate and produce a DynamicNode
///
- /// 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>,
+ /// You can use the bump alloactor of the scopestate to creat the dynamic node
+ fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
}
-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> IntoDynNode<'a> for () {
+ fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+ DynamicNode::default()
+ }
}
-
-impl<'a, T> Default for EventHandler<'a, T> {
- fn default() -> Self {
- Self {
- callback: RefCell::new(None),
- }
+impl<'a> IntoDynNode<'a> for VNode<'a> {
+ fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+ DynamicNode::Fragment(_cx.bump().alloc([self]))
}
}
-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,
- }
- }
-
- /// 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> {
+impl<'a> IntoDynNode<'a> for DynamicNode<'a> {
+ fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
self
}
}
-// Conveniently, we also support "null" (nothing) passed in
-impl IntoVNode<'_> for () {
- fn into_vnode(self, cx: NodeFactory) -> VNode {
- VNode::Placeholder(cx.bump.alloc(VPlaceholder { id: empty_cell() }))
+// An element that's an error is currently lost into the ether
+impl<'a> IntoDynNode<'a> for Element<'a> {
+ fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+ match self {
+ Ok(val) => val.into_vnode(_cx),
+ _ => DynamicNode::default(),
+ }
}
}
-impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
- fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
+impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option
{
+ fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+ match self {
+ Some(val) => val.into_vnode(_cx),
+ None => DynamicNode::default(),
+ }
+ }
+}
+
+impl<'a> IntoDynNode<'a> for &Element<'a> {
+ fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+ match self.as_ref() {
+ Ok(val) => val.clone().into_vnode(_cx),
+ _ => DynamicNode::default(),
+ }
+ }
+}
+
+impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
+ fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
+ DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
+ }
+}
+
+impl<'a> IntoDynNode<'_> for &'a str {
+ fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
+ cx.text_node(format_args!("{}", self))
+ }
+}
+
+impl IntoDynNode<'_> for String {
+ fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
+ cx.text_node(format_args!("{}", self))
+ }
+}
+
+impl<'b> IntoDynNode<'b> for Arguments<'_> {
+ fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
+ cx.text_node(self)
+ }
+}
+
+impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
+ fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
+ DynamicNode::Fragment(_cx.bump().alloc([VNode {
+ parent: self.parent,
+ template: self.template,
+ root_ids: self.root_ids,
+ key: self.key,
+ dynamic_nodes: self.dynamic_nodes,
+ dynamic_attrs: self.dynamic_attrs,
+ }]))
+ }
+}
+
+pub trait IntoTemplate<'a> {
+ fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
+}
+impl<'a> IntoTemplate<'a> for VNode<'a> {
+ fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
+ self
+ }
+}
+impl<'a> IntoTemplate<'a> for Element<'a> {
+ fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
+ match self {
+ Ok(val) => val.into_template(_cx),
+ _ => VNode::empty().unwrap(),
+ }
+ }
+}
+impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
+ fn into_template(self, cx: &'a ScopeState) -> 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.
+#[doc(hidden)]
pub struct FromNodeIterator;
-impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T
+impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
where
- T: IntoIterator- ,
- I: IntoVNode<'a, E>,
+ T: Iterator
- ,
+ I: IntoTemplate<'a>,
{
- fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a> {
- let mut nodes = bumpalo::collections::Vec::new_in(cx.bump);
+ fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
+ let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
- for node in self {
- nodes.push(node.into_vnode(cx));
- }
+ nodes.extend(self.into_iter().map(|node| node.into_template(cx)));
- 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,
- }))
+ match nodes.into_bump_slice() {
+ children if children.is_empty() => DynamicNode::default(),
+ children => DynamicNode::Fragment(children),
}
}
}
+
+/// 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 f64 {
+ fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
+ AttributeValue::Float(self)
+ }
+}
+impl<'a> IntoAttributeValue<'a> for i64 {
+ 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())
+ }
+}
diff --git a/packages/core/src/properties.rs b/packages/core/src/properties.rs
index e138acaa6..f8be8a0dd 100644
--- a/packages/core/src/properties.rs
+++ b/packages/core/src/properties.rs
@@ -1,100 +1,5 @@
use crate::innerlude::*;
-pub struct FragmentProps<'a>(Element<'a>);
-pub struct FragmentBuilder<'a, const BUILT: bool>(Element<'a>);
-impl<'a> FragmentBuilder<'a, false> {
- pub fn children(self, children: Element<'a>) -> FragmentBuilder<'a, true> {
- FragmentBuilder(children)
- }
-}
-impl<'a, const A: bool> FragmentBuilder<'a, A> {
- pub fn build(self) -> FragmentProps<'a> {
- FragmentProps(self.0)
- }
-}
-
-/// Access the children elements passed into the component
-///
-/// This enables patterns where a component is passed children from its parent.
-///
-/// ## Details
-///
-/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
-/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
-/// on the props that takes Context.
-///
-/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
-/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
-/// props are valid for the static lifetime.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// fn App(cx: Scope) -> Element {
-/// cx.render(rsx!{
-/// CustomCard {
-/// h1 {}
-/// p {}
-/// }
-/// })
-/// }
-///
-/// #[derive(PartialEq, Props)]
-/// struct CardProps {
-/// children: Element
-/// }
-///
-/// fn CustomCard(cx: Scope
) -> Element {
-/// cx.render(rsx!{
-/// div {
-/// h1 {"Title card"}
-/// {cx.props.children}
-/// }
-/// })
-/// }
-/// ```
-impl<'a> Properties for FragmentProps<'a> {
- type Builder = FragmentBuilder<'a, false>;
- const IS_STATIC: bool = false;
- fn builder() -> Self::Builder {
- FragmentBuilder(None)
- }
- unsafe fn memoize(&self, _other: &Self) -> bool {
- false
- }
-}
-
-/// Create inline fragments using Component syntax.
-///
-/// ## Details
-///
-/// Fragments capture a series of children without rendering extra nodes.
-///
-/// Creating fragments explicitly with the Fragment component is particularly useful when rendering lists or tables and
-/// a key is needed to identify each item.
-///
-/// ## Example
-///
-/// ```rust, ignore
-/// rsx!{
-/// Fragment { key: "abc" }
-/// }
-/// ```
-///
-/// ## Usage
-///
-/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
-/// Try to avoid highly nested fragments if you can. Unlike React, there is no protection against infinitely nested fragments.
-///
-/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
-///
-/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
-#[allow(non_upper_case_globals, non_snake_case)]
-pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
- let i = cx.props.0.as_ref().map(|f| f.decouple());
- cx.render(LazyNodes::new(|f| f.fragment_from_iter(i)))
-}
-
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
@@ -165,6 +70,6 @@ impl EmptyBuilder {
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
/// to initialize a component's props.
-pub fn fc_to_builder<'a, T: Properties + 'a>(_: fn(Scope<'a, T>) -> Element) -> T::Builder {
+pub fn fc_to_builder<'a, A, T: Properties + 'a>(_: fn(Scope<'a, T>) -> A) -> T::Builder {
T::builder()
}
diff --git a/packages/core/src/scheduler/mod.rs b/packages/core/src/scheduler/mod.rs
new file mode 100644
index 000000000..a6442184d
--- /dev/null
+++ b/packages/core/src/scheduler/mod.rs
@@ -0,0 +1,48 @@
+use crate::ScopeId;
+use slab::Slab;
+
+mod suspense;
+mod task;
+mod wait;
+mod waker;
+
+pub use suspense::*;
+pub use task::*;
+pub use waker::RcWake;
+
+/// The type of message that can be sent to the scheduler.
+///
+/// These messages control how the scheduler will process updates to the UI.
+#[derive(Debug)]
+pub(crate) enum SchedulerMsg {
+ /// Immediate updates from Components that mark them as dirty
+ Immediate(ScopeId),
+
+ /// A task has woken and needs to be progressed
+ TaskNotified(TaskId),
+
+ /// A task has woken and needs to be progressed
+ SuspenseNotified(SuspenseId),
+}
+
+use std::{cell::RefCell, rc::Rc};
+
+pub(crate) struct Scheduler {
+ pub sender: futures_channel::mpsc::UnboundedSender,
+
+ /// Tasks created with cx.spawn
+ pub tasks: RefCell>>,
+
+ /// Async components
+ pub leaves: RefCell>>,
+}
+
+impl Scheduler {
+ pub fn new(sender: futures_channel::mpsc::UnboundedSender) -> Rc {
+ Rc::new(Scheduler {
+ sender,
+ tasks: RefCell::new(Slab::new()),
+ leaves: RefCell::new(Slab::new()),
+ })
+ }
+}
diff --git a/packages/core/src/scheduler/suspense.rs b/packages/core/src/scheduler/suspense.rs
new file mode 100644
index 000000000..8d7d7337e
--- /dev/null
+++ b/packages/core/src/scheduler/suspense.rs
@@ -0,0 +1,52 @@
+use super::{waker::RcWake, SchedulerMsg};
+use crate::ElementId;
+use crate::{innerlude::Mutations, Element, ScopeId};
+use std::future::Future;
+use std::{
+ cell::{Cell, RefCell},
+ collections::HashSet,
+ rc::Rc,
+};
+
+/// An ID representing an ongoing suspended component
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub(crate) struct SuspenseId(pub usize);
+
+/// A boundary in the VirtualDom that captures all suspended components below it
+pub struct SuspenseContext {
+ pub(crate) id: ScopeId,
+ pub(crate) waiting_on: RefCell>,
+ pub(crate) mutations: RefCell>,
+ pub(crate) placeholder: Cell>,
+ pub(crate) created_on_stack: Cell,
+}
+
+impl SuspenseContext {
+ /// Create a new boundary for suspense
+ pub fn new(id: ScopeId) -> Self {
+ Self {
+ id,
+ waiting_on: Default::default(),
+ mutations: RefCell::new(Mutations::default()),
+ placeholder: Cell::new(None),
+ created_on_stack: Cell::new(0),
+ }
+ }
+}
+
+pub(crate) struct SuspenseLeaf {
+ pub(crate) id: SuspenseId,
+ pub(crate) scope_id: ScopeId,
+ pub(crate) tx: futures_channel::mpsc::UnboundedSender,
+ pub(crate) notified: Cell,
+ pub(crate) task: *mut dyn Future>,
+}
+
+impl RcWake for SuspenseLeaf {
+ fn wake_by_ref(arc_self: &Rc) {
+ arc_self.notified.set(true);
+ _ = arc_self
+ .tx
+ .unbounded_send(SchedulerMsg::SuspenseNotified(arc_self.id));
+ }
+}
diff --git a/packages/core/src/scheduler/task.rs b/packages/core/src/scheduler/task.rs
new file mode 100644
index 000000000..fb4378104
--- /dev/null
+++ b/packages/core/src/scheduler/task.rs
@@ -0,0 +1,66 @@
+use super::{waker::RcWake, Scheduler, SchedulerMsg};
+use crate::ScopeId;
+use std::cell::RefCell;
+use std::future::Future;
+use std::{pin::Pin, rc::Rc};
+
+/// A task's unique identifier.
+///
+/// `TaskId` is a `usize` that is unique across the entire VirtualDOM and across time. TaskIDs will never be reused
+/// once a Task has been completed.
+#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
+#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
+pub struct TaskId(pub usize);
+
+/// the task itself is the waker
+pub(crate) struct LocalTask {
+ pub scope: ScopeId,
+ pub(super) task: RefCell + 'static>>>,
+ id: TaskId,
+ tx: futures_channel::mpsc::UnboundedSender,
+}
+
+impl Scheduler {
+ /// Start a new future on the same thread as the rest of the VirtualDom.
+ ///
+ /// This future will not contribute to suspense resolving, so you should primarily use this for reacting to changes
+ /// and long running tasks.
+ ///
+ /// Whenever the component that owns this future is dropped, the future will be dropped as well.
+ ///
+ /// Spawning a future onto the root scope will cause it to be dropped when the root component is dropped - which
+ /// will only occur when the VirtuaalDom itself has been dropped.
+ pub fn spawn(&self, scope: ScopeId, task: impl Future + 'static) -> TaskId {
+ let mut tasks = self.tasks.borrow_mut();
+ let entry = tasks.vacant_entry();
+ let task_id = TaskId(entry.key());
+
+ entry.insert(Rc::new(LocalTask {
+ id: task_id,
+ tx: self.sender.clone(),
+ task: RefCell::new(Box::pin(task)),
+ scope,
+ }));
+
+ self.sender
+ .unbounded_send(SchedulerMsg::TaskNotified(task_id))
+ .expect("Scheduler should exist");
+
+ task_id
+ }
+
+ /// Drop the future with the given TaskId
+ ///
+ /// This does nto abort the task, so you'll want to wrap it in an aborthandle if that's important to you
+ pub fn remove(&self, id: TaskId) {
+ self.tasks.borrow_mut().remove(id.0);
+ }
+}
+
+impl RcWake for LocalTask {
+ fn wake_by_ref(arc_self: &Rc) {
+ _ = arc_self
+ .tx
+ .unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
+ }
+}
diff --git a/packages/core/src/scheduler/wait.rs b/packages/core/src/scheduler/wait.rs
new file mode 100644
index 000000000..8ae320e70
--- /dev/null
+++ b/packages/core/src/scheduler/wait.rs
@@ -0,0 +1,110 @@
+use futures_util::FutureExt;
+use std::{
+ rc::Rc,
+ task::{Context, Poll},
+};
+
+use crate::{
+ innerlude::{Mutation, Mutations, SuspenseContext},
+ nodes::RenderReturn,
+ ScopeId, TaskId, VNode, VirtualDom,
+};
+
+use super::{waker::RcWake, SuspenseId};
+
+impl VirtualDom {
+ /// Handle notifications by tasks inside the scheduler
+ ///
+ /// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
+ /// queue
+ pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
+ let mut tasks = self.scheduler.tasks.borrow_mut();
+
+ let task = match tasks.get(id.0) {
+ Some(task) => task,
+ None => return,
+ };
+
+ let waker = task.waker();
+ let mut cx = Context::from_waker(&waker);
+
+ // If the task completes...
+ if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
+ // Remove it from the scope so we dont try to double drop it when the scope dropes
+ self.scopes[task.scope.0].spawned_tasks.remove(&id);
+
+ // Remove it from the scheduler
+ tasks.remove(id.0);
+ }
+ }
+
+ pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc {
+ self.scopes[id.0]
+ .consume_context::>()
+ .unwrap()
+ }
+
+ pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
+ let leaf = self
+ .scheduler
+ .leaves
+ .borrow_mut()
+ .get(id.0)
+ .unwrap()
+ .clone();
+
+ let scope_id = leaf.scope_id;
+
+ // todo: cache the waker
+ let waker = leaf.waker();
+ let mut cx = Context::from_waker(&waker);
+
+ // Safety: the future is always pinned to the bump arena
+ let mut pinned = unsafe { std::pin::Pin::new_unchecked(&mut *leaf.task) };
+ let as_pinned_mut = &mut pinned;
+
+ // the component finished rendering and gave us nodes
+ // we should attach them to that component and then render its children
+ // continue rendering the tree until we hit yet another suspended component
+ if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
+ // safety: we're not going to modify the suspense context but we don't want to make a clone of it
+ let fiber = self.acquire_suspense_boundary(leaf.scope_id);
+
+ let scope = &mut self.scopes[scope_id.0];
+ let arena = scope.current_frame();
+
+ let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
+ arena.node.set(ret);
+
+ fiber.waiting_on.borrow_mut().remove(&id);
+
+ if let RenderReturn::Sync(Ok(template)) = ret {
+ let mutations_ref = &mut fiber.mutations.borrow_mut();
+ let mutations = &mut **mutations_ref;
+ let template: &VNode = unsafe { std::mem::transmute(template) };
+ let mutations: &mut Mutations = unsafe { std::mem::transmute(mutations) };
+
+ std::mem::swap(&mut self.mutations, mutations);
+
+ let place_holder_id = scope.placeholder.get().unwrap();
+ self.scope_stack.push(scope_id);
+ let created = self.create(template);
+ self.scope_stack.pop();
+ mutations.push(Mutation::ReplaceWith {
+ id: place_holder_id,
+ m: created,
+ });
+
+ for leaf in self.collected_leaves.drain(..) {
+ fiber.waiting_on.borrow_mut().insert(leaf);
+ }
+
+ std::mem::swap(&mut self.mutations, mutations);
+
+ if fiber.waiting_on.borrow().is_empty() {
+ self.finished_fibers.push(fiber.id);
+ }
+ }
+ }
+ }
+}
diff --git a/packages/core/src/scheduler/waker.rs b/packages/core/src/scheduler/waker.rs
new file mode 100644
index 000000000..762020167
--- /dev/null
+++ b/packages/core/src/scheduler/waker.rs
@@ -0,0 +1,36 @@
+use std::task::{RawWaker, RawWakerVTable, Waker};
+use std::{mem, rc::Rc};
+
+pub trait RcWake: Sized {
+ /// Create a waker from this self-wakening object
+ fn waker(self: &Rc) -> Waker {
+ unsafe fn rc_vtable() -> &'static RawWakerVTable {
+ &RawWakerVTable::new(
+ |data| {
+ let arc = mem::ManuallyDrop::new(Rc::::from_raw(data.cast::()));
+ let _rc_clone: mem::ManuallyDrop<_> = arc.clone();
+ RawWaker::new(data, rc_vtable::())
+ },
+ |data| Rc::from_raw(data.cast::()).wake(),
+ |data| {
+ let arc = mem::ManuallyDrop::new(Rc::::from_raw(data.cast::()));
+ RcWake::wake_by_ref(&arc);
+ },
+ |data| drop(Rc::::from_raw(data.cast::())),
+ )
+ }
+
+ unsafe {
+ Waker::from_raw(RawWaker::new(
+ Rc::into_raw(self.clone()).cast(),
+ rc_vtable::(),
+ ))
+ }
+ }
+
+ fn wake_by_ref(arc_self: &Rc |