remove a lot of unsafe

This commit is contained in:
Evan Almloff 2024-01-05 10:57:05 -06:00
parent c70e2bfcb6
commit 0c76770da0
12 changed files with 201 additions and 363 deletions

View file

@ -1,5 +1,5 @@
use crate::{nodes::RenderReturn, Element};
use std::{ops::Deref, panic::AssertUnwindSafe};
use std::{any::Any, ops::Deref, panic::AssertUnwindSafe};
/// A boxed version of AnyProps that can be cloned
pub(crate) struct BoxedAnyProps {
@ -7,7 +7,7 @@ pub(crate) struct BoxedAnyProps {
}
impl BoxedAnyProps {
fn new(inner: impl AnyProps + 'static) -> Self {
pub fn new(inner: impl AnyProps + 'static) -> Self {
Self {
inner: Box::new(inner),
}
@ -33,7 +33,8 @@ impl Clone for BoxedAnyProps {
/// A trait that essentially allows VComponentProps to be used generically
pub(crate) trait AnyProps {
fn render<'a>(&'a self) -> RenderReturn;
fn memoize(&self, other: &dyn AnyProps) -> bool;
fn memoize(&self, other: &dyn Any) -> bool;
fn props(&self) -> &dyn Any;
fn duplicate(&self) -> Box<dyn AnyProps>;
}
@ -41,25 +42,35 @@ pub(crate) struct VProps<P> {
pub render_fn: fn(P) -> Element,
pub memo: fn(&P, &P) -> bool,
pub props: P,
pub name: &'static str,
}
impl<P> VProps<P> {
pub(crate) fn new(render_fn: fn(P) -> Element, memo: fn(&P, &P) -> bool, props: P) -> Self {
pub(crate) fn new(
render_fn: fn(P) -> Element,
memo: fn(&P, &P) -> bool,
props: P,
name: &'static str,
) -> Self {
Self {
render_fn,
memo,
props,
name,
}
}
}
impl<P: Clone> AnyProps for VProps<P> {
// 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)
fn memoize(&self, other: &dyn AnyProps) -> bool {
(self.memo)(self, other)
fn memoize(&self, other: &dyn Any) -> bool {
match other.downcast_ref::<Self>() {
Some(other) => (self.memo)(&self.props, &other.props),
None => false,
}
}
fn props(&self) -> &dyn Any {
&self.props
}
fn render(&self) -> RenderReturn {
@ -72,7 +83,7 @@ impl<P: Clone> AnyProps for VProps<P> {
Ok(Some(e)) => RenderReturn::Ready(e),
Ok(None) => RenderReturn::default(),
Err(err) => {
let component_name = cx.name();
let component_name = self.name;
tracing::error!("Error while rendering component `{component_name}`: {err:?}");
RenderReturn::default()
}
@ -84,6 +95,7 @@ impl<P: Clone> AnyProps for VProps<P> {
render_fn: self.render_fn,
memo: self.memo,
props: self.props.clone(),
name: self.name,
})
}
}

View file

@ -1,5 +1,3 @@
use std::ptr::NonNull;
use crate::{
innerlude::DirtyScope, nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode,
ScopeId,
@ -21,16 +19,16 @@ pub struct ElementId(pub usize);
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct VNodeId(pub usize);
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone)]
pub struct ElementRef {
// the pathway of the real element inside the template
pub(crate) path: ElementPath,
// The actual template
pub(crate) template: VNodeId,
// The scope the element belongs to
// the scope that this element belongs to
pub(crate) scope: ScopeId,
// The actual element
pub(crate) element: VNode,
}
#[derive(Clone, Copy, Debug)]
@ -43,14 +41,6 @@ impl VirtualDom {
ElementId(self.elements.insert(None))
}
pub(crate) fn next_vnode_ref(&mut self, vnode: &VNode) -> VNodeId {
let new_id = VNodeId(self.element_refs.insert(Some(unsafe {
std::mem::transmute::<NonNull<VNode>, _>(vnode.into())
})));
new_id
}
pub(crate) fn reclaim(&mut self, el: ElementId) {
self.try_reclaim(el)
.unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
@ -67,11 +57,6 @@ impl VirtualDom {
self.elements.try_remove(el.0).map(|_| ())
}
pub(crate) fn set_template(&mut self, id: VNodeId, vnode: &VNode) {
self.element_refs[id.0] =
Some(unsafe { std::mem::transmute::<NonNull<VNode>, _>(vnode.into()) });
}
// Drop a scope and all its children
//
// Note: This will not remove any ids from the arena

View file

@ -1,7 +1,4 @@
use crate::any_props::AnyProps;
use crate::innerlude::{
BorrowedAttributeValue, ElementPath, ElementRef, VComponent, VPlaceholder, VText,
};
use crate::innerlude::{ElementPath, ElementRef, VComponent, VPlaceholder, VText};
use crate::mutations::Mutation;
use crate::mutations::Mutation::*;
use crate::nodes::VNode;
@ -96,9 +93,6 @@ impl VirtualDom {
nodes_mut.resize(len, ElementId::default());
};
// Set this node id
node.stable_id.set(Some(self.next_vnode_ref(node)));
// The best renderers will have templates prehydrated and registered
// Just in case, let's create the template using instructions anyways
self.register_template(node.template.get());
@ -190,7 +184,7 @@ impl VirtualDom {
path: ElementPath {
path: template.template.get().node_paths[idx],
},
template: template.stable_id().unwrap(),
element: template.clone(),
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
};
self.create_dynamic_node(template_ref, node)
@ -200,10 +194,10 @@ impl VirtualDom {
path: ElementPath {
path: template.template.get().node_paths[idx],
},
template: template.stable_id().unwrap(),
element: template.clone(),
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
};
parent.set(Some(template_ref));
*parent.borrow_mut() = Some(template_ref);
let id = self.set_slot(id);
self.mutations.push(CreatePlaceholder { id });
1
@ -217,12 +211,7 @@ impl VirtualDom {
}
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,
});
self.mutations.push(CreateTextNode { value, id });
}
/// We write all the descendent data for this element
@ -289,7 +278,7 @@ impl VirtualDom {
path: ElementPath {
path: template.template.get().node_paths[idx],
},
template: template.stable_id().unwrap(),
element: template.clone(),
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
};
let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[idx]);
@ -336,14 +325,14 @@ impl VirtualDom {
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) };
let unbounded_name: &str = &attribute.name;
match &attribute.value {
AttributeValue::Listener(_) => {
let path = &template.template.get().attr_paths[idx];
let element_ref = ElementRef {
path: ElementPath { path },
template: template.stable_id().unwrap(),
element: template.clone(),
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
};
self.elements[id.0] = Some(element_ref);
@ -354,9 +343,7 @@ impl VirtualDom {
})
}
_ => {
// Safety: we promise not to re-alias this text later on after committing it to the mutation
let value: BorrowedAttributeValue = (&attribute.value).into();
let unbounded_value = unsafe { std::mem::transmute(value) };
let unbounded_value = &attribute.value;
self.mutations.push(SetAttribute {
name: unbounded_name,
@ -516,7 +503,7 @@ impl VirtualDom {
placeholder.id.set(Some(id));
// Assign the placeholder's parent
placeholder.parent.set(Some(parent));
*placeholder.parent.borrow_mut() = Some(parent);
// Assign the ID to the existing node in the template
self.mutations.push(AssignId {
@ -540,7 +527,11 @@ impl VirtualDom {
component.scope.set(Some(scope));
match self.run_scope(scope) {
let new = self.run_scope(scope);
self.scopes[scope.0].last_rendered_node = Some(new.clone());
match &new {
// Create the component's root element
Ready(t) => {
self.assign_boundary_ref(parent, t);
@ -550,22 +541,20 @@ impl VirtualDom {
}
}
/// Load a scope from a vcomponent. If the props don't exist, that means the component is currently "live"
/// Load a scope from a vcomponent. If the scope id doesn't exist, that means the component is currently "live"
fn load_scope_from_vcomponent(&mut self, component: &VComponent) -> ScopeId {
component
.props
.map(|props| {
let unbounded_props: Box<dyn AnyProps> = unsafe { std::mem::transmute(props) };
self.new_scope(unbounded_props, component.name).context().id
})
.unwrap_or_else(|| component.scope.get().unwrap())
component.scope.get().unwrap_or_else(|| {
self.new_scope(component.props.clone(), component.name)
.context()
.id
})
}
fn mount_aborted(&mut self, placeholder: &VPlaceholder, parent: Option<ElementRef>) -> usize {
let id = self.next_element();
self.mutations.push(Mutation::CreatePlaceholder { id });
placeholder.id.set(Some(id));
placeholder.parent.set(parent);
*placeholder.parent.borrow_mut() = parent;
1
}

View file

@ -1,10 +1,9 @@
use std::ops::Deref;
use crate::{
any_props::AnyProps,
arena::ElementId,
innerlude::{
BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder,
VText,
},
innerlude::{DirtyScope, ElementPath, ElementRef, VComponent, VPlaceholder, VText},
mutations::Mutation,
nodes::RenderReturn,
nodes::{DynamicNode, VNode},
@ -17,52 +16,43 @@ use rustc_hash::{FxHashMap, FxHashSet};
use DynamicNode::*;
impl VirtualDom {
pub(super) fn diff_scope(&mut self, scope: ScopeId) {
pub(super) fn diff_scope(&mut self, scope: ScopeId, new_nodes: RenderReturn) {
self.runtime.scope_stack.borrow_mut().push(scope);
let scope_state = &mut self.get_scope(scope).unwrap();
unsafe {
// Load the old and new bump arenas
let old = scope_state
.previous_frame()
.try_load_node()
.expect("Call rebuild before diffing");
let scope_state = &mut self.scopes[scope.0];
// Load the old and new bump arenas
let old = std::mem::replace(&mut scope_state.last_rendered_node, Some(new_nodes)).unwrap();
let new = scope_state.last_rendered_node.as_ref().unwrap();
let new = scope_state
.current_frame()
.try_load_node()
.expect("Call rebuild before diffing");
use RenderReturn::{Aborted, Ready};
use RenderReturn::{Aborted, Ready};
match (&old, new) {
// Normal pathway
(Ready(l), Ready(r)) => self.diff_node(l, r),
match (old, new) {
// Normal pathway
(Ready(l), Ready(r)) => self.diff_node(l, r),
// Unwind the mutations if need be
(Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p),
// Unwind the mutations if need be
(Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p),
// Just move over the placeholder
(Aborted(l), Aborted(r)) => {
r.id.set(l.id.get());
*r.parent.borrow_mut() = l.parent.borrow().clone();
}
// Just move over the placeholder
(Aborted(l), Aborted(r)) => {
r.id.set(l.id.get());
r.parent.set(l.parent.get())
}
// Placeholder becomes something
// We should also clear the error now
(Aborted(l), Ready(r)) => self.replace_placeholder(
l,
[r],
l.parent.get().expect("root node should not be none"),
),
};
}
// Placeholder becomes something
// We should also clear the error now
(Aborted(l), Ready(r)) => self.replace_placeholder(
l,
[r],
l.parent.borrow().expect("root node should not be none"),
),
};
self.runtime.scope_stack.borrow_mut().pop();
}
fn diff_ok_to_err(&mut self, l: &VNode, p: &VPlaceholder) {
let id = self.next_element();
p.id.set(Some(id));
p.parent.set(l.parent.get());
*p.parent.borrow_mut() = l.parent.borrow().clone();
self.mutations.push(Mutation::CreatePlaceholder { id });
let pre_edits = self.mutations.edits.len();
@ -101,13 +91,7 @@ impl VirtualDom {
// Copy over the parent
{
right_template.parent.set(left_template.parent.get());
}
// Update the bubble id pointer
right_template.stable_id.set(left_template.stable_id.get());
if let Some(bubble_id) = right_template.stable_id.get() {
self.set_template(bubble_id, right_template);
*right_template.parent.borrow_mut() = left_template.parent.borrow().clone();
}
// If the templates are the same, we don't need to do anything, nor do we want to
@ -145,7 +129,7 @@ impl VirtualDom {
.enumerate()
.for_each(|(dyn_node_idx, (left_node, right_node))| {
let current_ref = ElementRef {
template: right_template.stable_id().unwrap(),
element: right_template.clone(),
path: ElementPath {
path: left_template.template.get().node_paths[dyn_node_idx],
},
@ -175,19 +159,18 @@ impl VirtualDom {
(Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right, parent),
(Placeholder(left), Placeholder(right)) => {
right.id.set(left.id.get());
right.parent.set(left.parent.get());
*right.parent.borrow_mut() = left.parent.borrow().clone();
},
(Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(parent)),
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right, parent),
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right, parent),
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right, parent),
_ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
};
}
fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) {
let name = unsafe { std::mem::transmute(left_attr.name) };
let value: BorrowedAttributeValue = (&right_attr.value).into();
let value = unsafe { std::mem::transmute(value) };
let name = &left_attr.name;
let value = &right_attr.value;
self.mutations.push(Mutation::SetAttribute {
id: left_attr.mounted_element.get(),
ns: right_attr.namespace,
@ -218,13 +201,13 @@ impl VirtualDom {
// copy out the box for both
let old_scope = &self.scopes[scope_id.0];
let old = old_scope.props.as_ref();
let new: &dyn AnyProps = right.props.as_ref();
let old = old_scope.props.deref();
let new: &dyn AnyProps = right.props.deref();
// 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 old.memoize(new) {
if old.memoize(new.props()) {
tracing::trace!(
"Memoized props for component {:#?} ({})",
scope_id,
@ -234,11 +217,11 @@ impl VirtualDom {
}
// First, move over the props from the old to the new, dropping old props in the process
self.scopes[scope_id.0].props = new;
self.scopes[scope_id.0].props = right.props.clone();
// Now run the component and diff it
self.run_scope(scope_id);
self.diff_scope(scope_id);
let new = self.run_scope(scope_id);
self.diff_scope(scope_id, new);
self.dirty_scopes.remove(&DirtyScope {
height: self.runtime.get_context(scope_id).unwrap().height,
@ -325,8 +308,10 @@ impl VirtualDom {
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 });
self.mutations.push(Mutation::SetText {
id,
value: &right.value,
});
}
}
@ -824,7 +809,7 @@ impl VirtualDom {
let placeholder = self.next_element();
r.id.set(Some(placeholder));
r.parent.set(Some(parent));
r.parent.borrow_mut().replace(parent);
self.mutations
.push(Mutation::CreatePlaceholder { id: placeholder });
@ -862,16 +847,6 @@ impl VirtualDom {
// 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);
// Clean up the vnode id
self.reclaim_vnode_id(node);
}
fn reclaim_vnode_id(&mut self, node: &VNode) {
// Clean up the vnode id
if let Some(id) = node.stable_id() {
self.element_refs.remove(id.0);
}
}
fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
@ -976,10 +951,6 @@ impl VirtualDom {
RenderReturn::Aborted(placeholder) => self.remove_placeholder(placeholder, gen_muts),
};
// Restore the props back to the vcomponent in case it gets rendered again
let props = self.scopes[scope.0].props.take();
*comp.props.borrow_mut() = unsafe { std::mem::transmute(props) };
// Now drop all the resouces
self.drop_scope(scope, false);
}
@ -1019,7 +990,7 @@ impl VirtualDom {
pub(crate) fn assign_boundary_ref(&mut self, parent: Option<ElementRef>, child: &VNode) {
if let Some(parent) = parent {
// assign the parent of the child
child.parent.set(Some(parent));
child.parent.borrow_mut().replace(parent);
}
}
}

View file

@ -1,7 +1,6 @@
use crate::{
scope_context::{consume_context, current_scope_id, schedule_update_any},
Element, IntoDynNode, Properties, ScopeId, ScopeState, Template, TemplateAttribute,
TemplateNode, VNode,
Element, IntoDynNode, Properties, ScopeId, Template, TemplateAttribute, TemplateNode, VNode,
};
use std::{
any::{Any, TypeId},
@ -14,8 +13,9 @@ use std::{
};
/// Provide an error boundary to catch errors from child components
pub fn use_error_boundary(cx: &ScopeState) -> &ErrorBoundary {
cx.use_hook(|| cx.provide_context(ErrorBoundary::new()))
pub fn use_error_boundary() -> ErrorBoundary {
// use_hook(|| cx.provide_context(ErrorBoundary::new()))
todo!()
}
/// A boundary that will capture any errors from child components
@ -262,10 +262,11 @@ impl<T> Throw for Option<T> {
}
}
pub struct ErrorHandler(Box<dyn Fn(CapturedError) -> Element>);
#[derive(Clone)]
pub struct ErrorHandler(Rc<dyn Fn(CapturedError) -> Element>);
impl<F: Fn(CapturedError) -> Element> From<F> for ErrorHandler {
fn from(value: F) -> Self {
Self(Box::new(value))
Self(Rc::new(value))
}
}
fn default_handler(error: CapturedError) -> Element {
@ -284,15 +285,13 @@ fn default_handler(error: CapturedError) -> Element {
node_paths: &[&[0u8, 0u8]],
attr_paths: &[],
};
Some(VNode {
parent: Default::default(),
stable_id: Default::default(),
key: None,
template: std::cell::Cell::new(TEMPLATE),
root_ids: Vec::with_capacity(1usize).into(),
dynamic_nodes: vec![error.to_string().into_dyn_node()],
dynamic_attrs: Default::default(),
})
Some(VNode::new(
None,
TEMPLATE,
Vec::with_capacity(1usize),
vec![error.to_string().into_dyn_node()],
Default::default(),
))
}
#[derive(Clone)]
@ -417,7 +416,7 @@ impl<
::core::default::Default::default()
});
let handle_error = ErrorBoundaryPropsBuilder_Optional::into_value(handle_error, || {
ErrorHandler(Box::new(default_handler))
ErrorHandler(Rc::new(default_handler))
});
ErrorBoundaryProps {
children,
@ -448,27 +447,24 @@ impl<
/// They are similar to `try/catch` in JavaScript, but they only catch errors in the tree below them.
/// Error boundaries are quick to implement, but it can be useful to individually handle errors in your components to provide a better user experience when you know that an error is likely to occur.
#[allow(non_upper_case_globals, non_snake_case)]
pub fn ErrorBoundary(cx: ErrorBoundaryProps) -> Element {
let error_boundary = use_error_boundary(cx);
pub fn ErrorBoundary(props: ErrorBoundaryProps) -> Element {
let error_boundary = use_error_boundary();
match error_boundary.take_error() {
Some(error) => cx.render((cx.props.handle_error.0)(error)),
Some(error) => (props.handle_error.0)(error),
None => Some({
let __cx = cx;
static TEMPLATE: Template = Template {
name: "examples/error_handle.rs:81:17:2342",
roots: &[TemplateNode::Dynamic { id: 0usize }],
node_paths: &[&[0u8]],
attr_paths: &[],
};
VNode {
parent: Default::default(),
stable_id: Default::default(),
key: None,
template: std::cell::Cell::new(TEMPLATE),
root_ids: Vec::with_capacity(1usize).into(),
dynamic_nodes: vec![(&cx.props.children).into_dyn_node()],
dynamic_attrs: __cx.bump().alloc([]),
}
VNode::new(
None,
TEMPLATE,
Vec::with_capacity(1usize),
vec![(props.children).into_dyn_node()],
Default::default(),
)
}),
}
}

View file

@ -27,16 +27,7 @@ use crate::innerlude::*;
/// 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(cx: FragmentProps) -> Element {
let children = cx.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,
})
cx.0.clone()
}
#[derive(Clone, PartialEq)]

View file

@ -73,10 +73,10 @@ pub(crate) mod innerlude {
}
pub use crate::innerlude::{
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue,
CapturedError, Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode,
Mutation, Mutations, Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template,
TemplateAttribute, TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom,
fc_to_builder, vdom_is_rendering, AnyValue, Attribute, AttributeValue, CapturedError,
Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, Mutation, Mutations,
Properties, RenderReturn, ScopeId, ScopeState, TaskId, Template, TemplateAttribute,
TemplateNode, VComponent, VNode, VPlaceholder, VText, VirtualDom,
};
/// The purpose of this module is to alleviate imports of many common types

View file

@ -1,6 +1,6 @@
use rustc_hash::FxHashSet;
use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Template};
use crate::{arena::ElementId, AttributeValue, ScopeId, Template};
/// A container for all the relevant steps to modify the Real DOM
///
@ -13,7 +13,6 @@ use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Templa
/// 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> {
@ -56,11 +55,6 @@ impl<'a> Mutations<'a> {
/// 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")
)]
#[derive(Debug, PartialEq)]
pub enum Mutation<'a> {
/// Add these m children to the target element
@ -195,7 +189,7 @@ pub enum Mutation<'a> {
name: &'a str,
/// The value of the attribute.
value: BorrowedAttributeValue<'a>,
value: &'a AttributeValue,
/// The ID of the node to set the attribute of.
id: ElementId,

View file

@ -1,5 +1,8 @@
use crate::innerlude::{ElementRef, VNodeId};
use crate::{any_props::AnyProps, arena::ElementId, Element, Event, ScopeId, ScopeState};
use crate::any_props::BoxedAnyProps;
use crate::innerlude::ElementRef;
use crate::{arena::ElementId, Element, Event, ScopeId};
use std::ops::Deref;
use std::rc::Rc;
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
@ -15,6 +18,7 @@ pub type TemplateId = &'static str;
///
/// 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.
#[derive(Clone)]
pub enum RenderReturn {
/// A currently-available element
Ready(VNode),
@ -36,26 +40,23 @@ impl Default for RenderReturn {
///
/// 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 {
#[derive(Debug)]
pub struct VNodeInner {
/// The key given to the root of this template.
///
/// In fragments, this is the key of the first child. In other cases, it is the key of the root.
pub key: Option<String>,
/// When rendered, this template will be linked to its parent manually
pub(crate) parent: Cell<Option<ElementRef>>,
/// The bubble id assigned to the child that we need to update and drop when diffing happens
pub(crate) stable_id: Cell<Option<VNodeId>>,
/// The static nodes and static descriptor of the template
pub template: Cell<Template<'static>>,
pub(crate) parent: RefCell<Option<ElementRef>>,
/// 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: RefCell<Vec<ElementId>>,
/// The static nodes and static descriptor of the template
pub template: Cell<Template<'static>>,
/// The dynamic parts of the template
pub dynamic_nodes: Vec<DynamicNode>,
@ -63,13 +64,33 @@ pub struct VNode {
pub dynamic_attrs: Vec<Attribute>,
}
/// A reference to a template along with any context needed to hydrate it
///
/// 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(Clone, Debug)]
pub struct VNode(Rc<VNodeInner>);
impl PartialEq for VNode {
fn eq(&self, other: &Self) -> bool {
Rc::ptr_eq(&self.0, &other.0)
}
}
impl Deref for VNode {
type Target = VNodeInner;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl VNode {
/// Create a template with no nodes that will be skipped over during diffing
pub fn empty() -> Element {
Some(VNode {
Some(Self(Rc::new(VNodeInner {
key: None,
parent: Default::default(),
stable_id: Default::default(),
root_ids: Default::default(),
dynamic_nodes: Vec::new(),
dynamic_attrs: Vec::new(),
@ -79,7 +100,7 @@ impl VNode {
node_paths: &[],
attr_paths: &[],
}),
})
})))
}
/// Create a new VNode
@ -90,20 +111,14 @@ impl VNode {
dynamic_nodes: Vec<DynamicNode>,
dynamic_attrs: Vec<Attribute>,
) -> Self {
Self {
Self(Rc::new(VNodeInner {
key,
parent: Cell::new(None),
stable_id: Cell::new(None),
parent: Default::default(),
template: Cell::new(template),
root_ids: RefCell::new(root_ids),
dynamic_nodes,
dynamic_attrs,
}
}
/// Get the stable id of this node used for bubbling events
pub(crate) fn stable_id(&self) -> Option<VNodeId> {
self.stable_id.get()
}))
}
/// Load a dynamic root at the given index
@ -293,7 +308,7 @@ pub enum TemplateNode {
/// A node created at runtime
///
/// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
#[derive(Debug, Clone)]
#[derive(Debug)]
pub enum DynamicNode {
/// A component node
///
@ -355,7 +370,6 @@ impl<'a> std::fmt::Debug for VComponent {
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()
}
@ -387,12 +401,12 @@ impl<'a> VText {
}
/// A placeholder node, used by suspense and fragments
#[derive(Debug, Default)]
#[derive(Clone, Debug, Default)]
pub struct VPlaceholder {
/// The ID of this node in the real DOM
pub(crate) id: Cell<Option<ElementId>>,
/// The parent of this node
pub(crate) parent: Cell<Option<ElementRef>>,
pub(crate) parent: RefCell<Option<ElementRef>>,
}
impl VPlaceholder {
@ -436,7 +450,7 @@ pub enum TemplateAttribute {
}
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
#[derive(Clone, Debug)]
#[derive(Debug)]
pub struct Attribute {
/// The name of the attribute.
pub name: &'static str,
@ -483,7 +497,6 @@ impl Attribute {
///
/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
/// variant.
#[derive(Clone)]
pub enum AttributeValue {
/// Text attribute
Text(String),
@ -498,7 +511,7 @@ pub enum AttributeValue {
Bool(bool),
/// A listener, like "onclick"
Listener(ListenerCb),
Listener(RefCell<ListenerCb>),
/// An arbitrary value that implements PartialEq and is static
Any(Box<dyn AnyValue>),
@ -509,85 +522,6 @@ pub enum AttributeValue {
pub type ListenerCb = Box<dyn FnMut(Event<dyn Any>)>;
/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed
///
/// These varients are used to communicate what the value of an attribute is that needs to be updated
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[cfg_attr(feature = "serialize", serde(untagged))]
pub enum BorrowedAttributeValue<'a> {
/// Text attribute
Text(&'a str),
/// A float
Float(f64),
/// Signed integer
Int(i64),
/// Boolean
Bool(bool),
/// An arbitrary value that implements PartialEq and is static
#[cfg_attr(
feature = "serialize",
serde(
deserialize_with = "deserialize_any_value",
serialize_with = "serialize_any_value"
)
)]
Any(std::cell::Ref<'a, dyn AnyValue>),
/// A "none" value, resulting in the removal of an attribute from the dom
None,
}
impl<'a> From<&'a AttributeValue> for BorrowedAttributeValue<'a> {
fn from(value: &'a AttributeValue) -> Self {
match value {
AttributeValue::Text(value) => BorrowedAttributeValue::Text(value),
AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value),
AttributeValue::Int(value) => BorrowedAttributeValue::Int(*value),
AttributeValue::Bool(value) => BorrowedAttributeValue::Bool(*value),
AttributeValue::Listener(_) => {
panic!("A listener cannot be turned into a borrowed value")
}
AttributeValue::Any(value) => {
let value = value.borrow();
BorrowedAttributeValue::Any(std::cell::Ref::map(value, |value| {
&**value.as_ref().unwrap()
}))
}
AttributeValue::None => BorrowedAttributeValue::None,
}
}
}
impl Debug for BorrowedAttributeValue<'_> {
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::Any(_) => f.debug_tuple("Any").field(&"...").finish(),
Self::None => write!(f, "None"),
}
}
}
impl PartialEq for BorrowedAttributeValue<'_> {
fn eq(&self, other: &Self) -> bool {
match (self, other) {
(Self::Text(l0), Self::Text(r0)) => l0 == r0,
(Self::Float(l0), Self::Float(r0)) => l0 == r0,
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(&**r0),
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
}
}
}
#[cfg(feature = "serialize")]
fn serialize_any_value<S>(_: &std::cell::Ref<'_, dyn AnyValue>, _: S) -> Result<S::Ok, S::Error>
where
@ -626,11 +560,7 @@ impl PartialEq for AttributeValue {
(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)) => {
let l0 = l0.borrow();
let r0 = r0.borrow();
l0.as_ref().unwrap().any_cmp(&**r0.as_ref().unwrap())
}
(Self::Any(l0), Self::Any(r0)) => l0.as_ref().any_cmp(r0.as_ref()),
_ => false,
}
}
@ -820,7 +750,7 @@ impl<'a> IntoAttributeValue for Arguments<'_> {
impl IntoAttributeValue for Box<dyn AnyValue> {
fn into_value(self) -> AttributeValue {
AttributeValue::Any(RefCell::new(Some(self)))
AttributeValue::Any(self)
}
}

View file

@ -1,5 +1,5 @@
use crate::{
any_props::AnyProps,
any_props::{AnyProps, BoxedAnyProps},
innerlude::DirtyScope,
nodes::RenderReturn,
scope_context::ScopeContext,
@ -8,11 +8,7 @@ use crate::{
};
impl VirtualDom {
pub(super) fn new_scope(
&mut self,
props: Box<dyn AnyProps>,
name: &'static str,
) -> &ScopeState {
pub(super) fn new_scope(&mut self, props: BoxedAnyProps, name: &'static str) -> &ScopeState {
let parent_id = self.runtime.current_scope_id();
let height = parent_id
.and_then(|parent_id| self.get_scope(parent_id).map(|f| f.context().height + 1))

View file

@ -1,6 +1,5 @@
use crate::{
any_props::AnyProps,
any_props::VProps,
any_props::{BoxedAnyProps, VProps},
innerlude::{DynamicNode, EventHandler, VComponent, VText},
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
runtime::Runtime,
@ -49,7 +48,7 @@ pub struct ScopeState {
pub(crate) last_rendered_node: Option<RenderReturn>,
pub(crate) props: Box<dyn AnyProps>,
pub(crate) props: BoxedAnyProps,
}
impl Drop for ScopeState {
@ -300,16 +299,12 @@ impl<'src> ScopeState {
// The properties must be valid until the next bump frame
P: Properties,
{
let vcomp = VProps::new(component, P::memoize, props);
// cast off the lifetime of the render return
let as_dyn: Box<dyn AnyProps> = Box::new(vcomp);
let extended: Box<dyn AnyProps> = unsafe { std::mem::transmute(as_dyn) };
let vcomp = VProps::new(component, P::memoize, props, fn_name);
DynamicNode::Component(VComponent {
name: fn_name,
render_fn: component as *const (),
props: extended,
props: BoxedAnyProps::new(vcomp),
scope: Default::default(),
})
}
@ -332,14 +327,14 @@ impl<'src> ScopeState {
&'src self,
mut callback: impl FnMut(Event<T>) + 'src,
) -> AttributeValue {
AttributeValue::Listener(Box::new(move |event: Event<dyn Any>| {
AttributeValue::Listener(RefCell::new(Box::new(move |event: Event<dyn Any>| {
if let Ok(data) = event.data.downcast::<T>() {
callback(Event {
propagates: event.propagates,
data,
});
}
}))
})))
}
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]

View file

@ -3,7 +3,7 @@
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
use crate::{
any_props::VProps,
any_props::{BoxedAnyProps, VProps},
arena::ElementId,
innerlude::{DirtyScope, ElementRef, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
mutations::Mutation,
@ -11,14 +11,12 @@ use crate::{
nodes::{Template, TemplateId},
runtime::{Runtime, RuntimeGuard},
scopes::{ScopeId, ScopeState},
AttributeValue, Element, Event, VNode,
AttributeValue, Element, Event,
};
use futures_util::{pin_mut, StreamExt};
use rustc_hash::{FxHashMap, FxHashSet};
use slab::Slab;
use std::{
any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc, sync::Arc,
};
use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc, sync::Arc};
/// A virtual node system that progresses user events and diffs UI trees.
///
@ -187,9 +185,6 @@ pub struct VirtualDom {
// Maps a template path to a map of byteindexes to templates
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
// Every element is actually a dual reference - one to the template and the other to the dynamic node in that template
pub(crate) element_refs: Slab<Option<NonNull<VNode>>>,
// The element ids that are used in the renderer
pub(crate) elements: Slab<Option<ElementRef>>,
@ -268,13 +263,12 @@ impl VirtualDom {
dirty_scopes: Default::default(),
templates: Default::default(),
elements: Default::default(),
element_refs: Default::default(),
mutations: Mutations::default(),
suspended_scopes: Default::default(),
};
let root = dom.new_scope(
Box::new(VProps::new(root, |_, _| unreachable!(), root_props)),
BoxedAnyProps::new(VProps::new(root, |_, _| unreachable!(), root_props, "root")),
"app",
);
@ -363,14 +357,10 @@ impl VirtualDom {
| <-- no, broke early
*/
let parent_path = match self.elements.get(element.0) {
Some(Some(el)) => el,
Some(Some(el)) => el.clone(),
_ => return,
};
let mut parent_node = self
.element_refs
.get(parent_path.template.0)
.cloned()
.map(|el| (*parent_path, el));
let mut parent_node = Some(parent_path);
let mut listeners = vec![];
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
@ -382,13 +372,12 @@ impl VirtualDom {
// If the event bubbles, we traverse through the tree until we find the target element.
if bubbles {
// Loop through each dynamic attribute (in a depth first order) in this template before moving up to the template's parent.
while let Some((path, el_ref)) = parent_node {
// safety: we maintain references of all vnodes in the element slab
let template = unsafe { el_ref.unwrap().as_ref() };
let node_template = template.template.get();
while let Some(path) = parent_node {
let el_ref = &path.element;
let node_template = el_ref.template.get();
let target_path = path.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx];
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
@ -413,9 +402,7 @@ impl VirtualDom {
let origin = path.scope;
self.runtime.scope_stack.borrow_mut().push(origin);
self.runtime.rendering.set(false);
if let Some(cb) = listener.borrow_mut().as_deref_mut() {
cb(uievent.clone());
}
(listener.borrow_mut())(uievent.clone());
self.runtime.scope_stack.borrow_mut().pop();
self.runtime.rendering.set(true);
@ -425,22 +412,16 @@ impl VirtualDom {
}
}
parent_node = template.parent.get().and_then(|element_ref| {
self.element_refs
.get(element_ref.template.0)
.cloned()
.map(|el| (element_ref, el))
});
parent_node = el_ref.parent.borrow().clone();
}
} else {
// Otherwise, we just call the listener on the target element
if let Some((path, el_ref)) = parent_node {
// safety: we maintain references of all vnodes in the element slab
let template = unsafe { el_ref.unwrap().as_ref() };
let node_template = template.template.get();
if let Some(path) = parent_node {
let el_ref = &path.element;
let node_template = el_ref.template.get();
let target_path = path.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
for (idx, attr) in el_ref.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx];
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
@ -450,9 +431,7 @@ impl VirtualDom {
let origin = path.scope;
self.runtime.scope_stack.borrow_mut().push(origin);
self.runtime.rendering.set(false);
if let Some(cb) = listener.as_deref_mut() {
cb(uievent.clone());
}
(listener.borrow_mut())(uievent.clone());
self.runtime.scope_stack.borrow_mut().pop();
self.runtime.rendering.set(true);
@ -570,7 +549,7 @@ impl VirtualDom {
match unsafe { self.run_scope(ScopeId::ROOT) } {
// Rebuilding implies we append the created elements to the root
RenderReturn::Ready(node) => {
let m = self.create_scope(ScopeId::ROOT, node);
let m = self.create_scope(ScopeId::ROOT, &node);
self.mutations.edits.push(Mutation::AppendChildren {
id: ElementId(0),
m,
@ -645,8 +624,8 @@ impl VirtualDom {
{
let _runtime = RuntimeGuard::new(self.runtime.clone());
// Run the scope and get the mutations
self.run_scope(dirty.id);
self.diff_scope(dirty.id);
let new_nodes = self.run_scope(dirty.id);
self.diff_scope(dirty.id, new_nodes);
}
}