diff --git a/packages/core/src/arena.rs b/packages/core/src/arena.rs index ffdd4656b..d1b6f9290 100644 --- a/packages/core/src/arena.rs +++ b/packages/core/src/arena.rs @@ -1,3 +1,4 @@ +use crate::ScopeState; use std::ptr::NonNull; use crate::{ @@ -174,7 +175,14 @@ impl VirtualDom { scope.borrowed_props.borrow_mut().clear(); // Now that all the references are gone, we can safely drop our own references in our listeners. - let mut listeners = scope.attributes_to_drop_before_render.borrow_mut(); + scope.drop_listeners(); + } +} + +impl ScopeState { + /// Drop all listeners that were allocated in the bump allocator. + pub(crate) fn drop_listeners(&self) { + let mut listeners = self.attributes_to_drop_before_render.borrow_mut(); listeners.drain(..).for_each(|listener| { let listener = unsafe { &*listener }; if let AttributeValue::Listener(l) = &listener.value { diff --git a/packages/core/src/fragment.rs b/packages/core/src/fragment.rs index 4a775a57c..1403f41b9 100644 --- a/packages/core/src/fragment.rs +++ b/packages/core/src/fragment.rs @@ -28,15 +28,14 @@ use crate::innerlude::*; #[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()?; - Some(VNode { - key: children.key, - parent: children.parent.clone(), - stable_id: children.stable_id.clone(), - template: children.template.clone(), - root_ids: children.root_ids.clone(), - dynamic_nodes: children.dynamic_nodes, - dynamic_attrs: children.dynamic_attrs, - }) + Some(cx.vnode( + children.parent.clone(), + children.key, + children.template.clone(), + children.root_ids.clone(), + children.dynamic_nodes, + children.dynamic_attrs, + )) } pub struct FragmentProps<'a>(Element<'a>); diff --git a/packages/core/src/lazynodes.rs b/packages/core/src/lazynodes.rs index ae1bd2109..a7e6906dc 100644 --- a/packages/core/src/lazynodes.rs +++ b/packages/core/src/lazynodes.rs @@ -24,7 +24,7 @@ use crate::{innerlude::VNode, ScopeState}; /// /// ```rust, ignore /// LazyNodes::new(|f| { -/// static TEMPLATE: dioxus::core::Template = dioxus::core::Template { +/// static TEMPLATE: dioxus::core::Template = dioxus::core::Template { /// name: "main.rs:5:5:20", // Source location of the template for hot reloading /// roots: &[ /// dioxus::core::TemplateNode::Element { @@ -37,19 +37,19 @@ use crate::{innerlude::VNode, ScopeState}; /// node_paths: &[], /// attr_paths: &[], /// }; -/// dioxus::core::VNode { -/// parent: None, -/// key: None, -/// template: std::cell::Cell::new(TEMPLATE), -/// root_ids: dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in( +/// f.vnode( +/// None, +/// None, +/// std::cell::Cell::new(TEMPLATE), +/// dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in( /// 1usize, /// f.bump(), /// ) /// .into(), -/// dynamic_nodes: f.bump().alloc([]), -/// dynamic_attrs: f.bump().alloc([]), -/// }) -/// } +/// f.bump().alloc([]), +/// f.bump().alloc([]), +/// ) +/// }) /// ``` /// /// Find more information about how to construct [`VNode`] at diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 6396504a4..98964e948 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -40,6 +40,7 @@ impl<'a> Default for RenderReturn<'a> { /// /// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping /// static parts of the template. +#[non_exhaustive] #[derive(Debug, Clone)] pub struct VNode<'a> { /// The key given to the root of this template. @@ -70,20 +71,19 @@ pub struct VNode<'a> { impl<'a> VNode<'a> { /// Create a template with no nodes that will be skipped over during diffing pub fn empty(cx: &'a ScopeState) -> Element<'a> { - Some(VNode { - key: None, - parent: Default::default(), - stable_id: Default::default(), - root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())), - dynamic_nodes: &[], - dynamic_attrs: &[], - template: Cell::new(Template { + Some(cx.vnode( + Cell::new(None), + None, + Cell::new(Template { name: "dioxus-empty", roots: &[], node_paths: &[], attr_paths: &[], }), - }) + RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())), + &[], + &[], + )) } /// Create a new VNode @@ -820,16 +820,16 @@ impl<'b> IntoDynNode<'b> for Arguments<'_> { } impl<'a> IntoDynNode<'a> for &'a VNode<'a> { - fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> { - DynamicNode::Fragment(_cx.bump().alloc([VNode { - parent: self.parent.clone(), - stable_id: self.stable_id.clone(), - template: self.template.clone(), - root_ids: self.root_ids.clone(), - key: self.key, - dynamic_nodes: self.dynamic_nodes, - dynamic_attrs: self.dynamic_attrs, - }])) + fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode<'a> { + let vnode = cx.vnode( + self.parent.clone(), + self.key, + self.template.clone(), + self.root_ids.clone(), + self.dynamic_nodes, + self.dynamic_attrs, + ); + DynamicNode::Fragment(cx.bump().alloc([vnode])) } } diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index 64a60b112..63a9144f2 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -2,13 +2,13 @@ use crate::{ any_props::AnyProps, any_props::VProps, bump_frame::BumpFrame, - innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText}, + innerlude::{DynamicNode, ElementRef, EventHandler, VComponent, VNodeId, VText}, lazynodes::LazyNodes, nodes::{IntoAttributeValue, IntoDynNode, RenderReturn}, runtime::Runtime, scope_context::ScopeContext, - AnyValue, Attribute, AttributeType, AttributeValue, Element, Event, MountedAttribute, - Properties, TaskId, + AnyValue, Attribute, AttributeType, AttributeValue, Element, ElementId, Event, + MountedAttribute, Properties, TaskId, Template, VNode, }; use bumpalo::{boxed::Box as BumpBox, Bump}; use std::{ @@ -102,6 +102,7 @@ pub struct ScopeState { impl Drop for ScopeState { fn drop(&mut self) { + self.drop_listeners(); self.runtime.remove_context(self.context_id); } } @@ -345,19 +346,37 @@ impl<'src> ScopeState { /// // Actually build the tree and allocate it /// cx.render(lazy_tree) /// } - ///``` + /// ``` pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> { - let element = rsx.call(self); + // Note: We can't do anything in this function related to safety because the user could always circumvent it by creating a VNode manually and return it without calling this function + Some(rsx.call(self)) + } + /// Create a [`crate::VNode`] from the component parts + pub fn vnode( + &'src self, + parent: Cell>, + key: Option<&'src str>, + template: Cell>, + root_ids: RefCell>, + dynamic_nodes: &'src [DynamicNode<'src>], + dynamic_attrs: &'src [MountedAttribute<'src>], + ) -> VNode<'src> { let mut listeners = self.attributes_to_drop_before_render.borrow_mut(); - for attr in element.dynamic_attrs { - attr.ty.for_each(|attr| { + for attr in dynamic_attrs { + let attrs = match attr.ty { + AttributeType::Single(ref attr) => std::slice::from_ref(attr), + AttributeType::Many(attrs) => attrs, + }; + + for attr in attrs { match attr.value { // We need to drop listeners before the next render because they may borrow data from the borrowed props which will be dropped AttributeValue::Listener(_) => { let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; listeners.push(unbounded); } + // We need to drop any values manually to make sure that their drop implementation is called before the next render AttributeValue::Any(_) => { let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) }; @@ -366,7 +385,7 @@ impl<'src> ScopeState { _ => (), } - }) + } } let mut props = self.borrowed_props.borrow_mut(); @@ -374,7 +393,7 @@ impl<'src> ScopeState { .previous_frame() .props_to_drop_before_reset .borrow_mut(); - for node in element.dynamic_nodes { + for node in dynamic_nodes { if let DynamicNode::Component(comp) = node { let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) }; if !comp.static_props { @@ -384,7 +403,15 @@ impl<'src> ScopeState { } } - Some(element) + VNode { + stable_id: Default::default(), + key, + parent, + template, + root_ids, + dynamic_nodes, + dynamic_attrs, + } } /// Create a dynamic text node using [`Arguments`] and the [`ScopeState`]'s internal [`Bump`] allocator diff --git a/packages/core/tests/fuzzing.rs b/packages/core/tests/fuzzing.rs index af5e9d05c..76065ccc1 100644 --- a/packages/core/tests/fuzzing.rs +++ b/packages/core/tests/fuzzing.rs @@ -2,7 +2,7 @@ use dioxus::prelude::Props; use dioxus_core::{MountedAttribute, *}; -use std::{cfg, collections::HashSet}; +use std::{cell::Cell, cfg, collections::HashSet}; fn random_ns() -> Option<&'static str> { let namespace = rand::random::() % 2; @@ -171,15 +171,16 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { match rand::random::() % range { 0 => DynamicNode::Placeholder(Default::default()), 1 => cx.make_node((0..(rand::random::() % 5)).map(|_| { - VNode::new( - None, - Template { + cx.vnode( + None.into(), + Default::default(), + Cell::new(Template { name: concat!(file!(), ":", line!(), ":", column!(), ":0"), roots: &[TemplateNode::Dynamic { id: 0 }], node_paths: &[&[0]], attr_paths: &[], - }, - bumpalo::collections::Vec::new_in(cx.bump()), + }), + bumpalo::collections::Vec::new_in(cx.bump()).into(), cx.bump().alloc([cx.component( create_random_element, DepthProps { depth, root: false }, @@ -273,10 +274,12 @@ fn create_random_element(cx: Scope) -> Element { ) .into_boxed_str(), )); - let node = VNode::new( + println!("{template:#?}"); + let node = cx.vnode( + None.into(), None, - template, - bumpalo::collections::Vec::new_in(cx.bump()), + Cell::new(template), + bumpalo::collections::Vec::new_in(cx.bump()).into(), { let dynamic_nodes: Vec<_> = dynamic_node_types .iter() diff --git a/packages/dioxus-tui/examples/many_small_edit_stress.rs b/packages/dioxus-tui/examples/many_small_edit_stress.rs index 0668077cd..5f9d9d76f 100644 --- a/packages/dioxus-tui/examples/many_small_edit_stress.rs +++ b/packages/dioxus-tui/examples/many_small_edit_stress.rs @@ -83,7 +83,7 @@ fn Grid(cx: Scope) -> Element { let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32; let key = format!("{}-{}", x, y); rsx! { - Box{ + Box { x: x, y: y, alpha: 100.0, diff --git a/packages/native-core/tests/fuzzing.rs b/packages/native-core/tests/fuzzing.rs index 4abcc2351..2496b1f1c 100644 --- a/packages/native-core/tests/fuzzing.rs +++ b/packages/native-core/tests/fuzzing.rs @@ -1,3 +1,5 @@ +use std::cell::Cell; + use dioxus::prelude::Props; use dioxus_core::*; use dioxus_native_core::prelude::*; @@ -178,15 +180,16 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { match rand::random::() % range { 0 => DynamicNode::Placeholder(Default::default()), 1 => cx.make_node((0..(rand::random::() % 5)).map(|_| { - VNode::new( - None, - Template { + cx.vnode( + None.into(), + Default::default(), + Cell::new(Template { name: concat!(file!(), ":", line!(), ":", column!(), ":0"), roots: &[TemplateNode::Dynamic { id: 0 }], node_paths: &[&[0]], attr_paths: &[], - }, - dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()), + }), + dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(), cx.bump().alloc([cx.component( create_random_element, DepthProps { depth, root: false }, @@ -204,20 +207,6 @@ fn create_random_dynamic_node(cx: &ScopeState, depth: usize) -> DynamicNode { } } -fn create_random_dynamic_mounted_attr(cx: &ScopeState) -> MountedAttribute { - match rand::random::() % 2 { - 0 => MountedAttribute::from( - &*cx.bump().alloc( - (0..(rand::random::() % 3) as usize) - .map(|_| create_random_dynamic_attr(cx)) - .collect::>(), - ), - ), - 1 => MountedAttribute::from(create_random_dynamic_attr(cx)), - _ => unreachable!(), - } -} - fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { let value = match rand::random::() % 6 { 0 => AttributeValue::Text(Box::leak( @@ -230,6 +219,7 @@ fn create_random_dynamic_attr(cx: &ScopeState) -> Attribute { 5 => AttributeValue::None, _ => unreachable!(), }; + Attribute::new( Box::leak(format!("attr{}", rand::random::()).into_boxed_str()), value, @@ -266,10 +256,11 @@ fn create_random_element(cx: Scope) -> Element { .into_boxed_str(), )); println!("{template:#?}"); - let node = VNode::new( + let node = cx.vnode( + None.into(), None, - template, - dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()), + Cell::new(template), + dioxus::core::exports::bumpalo::collections::Vec::new_in(cx.bump()).into(), { let dynamic_nodes: Vec<_> = dynamic_node_types .iter() @@ -287,7 +278,7 @@ fn create_random_element(cx: Scope) -> Element { cx.bump() .alloc( (0..template.attr_paths.len()) - .map(|_| create_random_dynamic_mounted_attr(cx)) + .map(|_| create_random_dynamic_attr(cx).into()) .collect::>(), ) .as_slice(), diff --git a/packages/rsx/src/lib.rs b/packages/rsx/src/lib.rs index aee1bb431..70d03fc52 100644 --- a/packages/rsx/src/lib.rs +++ b/packages/rsx/src/lib.rs @@ -254,10 +254,12 @@ impl<'a> ToTokens for TemplateRenderer<'a> { node_paths: &[ #(#node_paths),* ], attr_paths: &[ #(#attr_paths),* ], }; - ::dioxus::core::VNode::new( + + __cx.vnode( + None.into(), #key_tokens, - TEMPLATE, - dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()), + std::cell::Cell::new(TEMPLATE), + dioxus::core::exports::bumpalo::collections::Vec::with_capacity_in(#root_count, __cx.bump()).into(), __cx.bump().alloc([ #( #node_printer ),* ]), __cx.bump().alloc([ #( #dyn_attr_printer ),* ]), )