Merge branch 'master' into pr/atty303/1349

This commit is contained in:
Evan Almloff committed 2024-01-06 08:52:37 -06:00
commit e5df83ceec
278 files changed
+11299 -3674

No files matched your search

+62 -58
View File
@@ -13,62 +13,50 @@ use crate::{
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize);
pub(crate) struct ElementRef {
/// An Element that can be bubbled to's unique identifier.
///
/// `BubbleId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
/// unmounted, then the `BubbleId` 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 VNodeId(pub usize);
#[derive(Debug, Clone, Copy)]
pub struct ElementRef {
// the pathway of the real element inside the template
pub path: ElementPath,
pub(crate) path: ElementPath,
// The actual template
pub template: Option<NonNull<VNode<'static>>>,
pub(crate) template: VNodeId,
// The scope the element belongs to
pub scope: ScopeId,
pub(crate) scope: ScopeId,
}
#[derive(Clone, Copy, Debug)]
pub enum ElementPath {
Deep(&'static [u8]),
Root(usize),
}
impl ElementRef {
pub(crate) fn none() -> Self {
Self {
template: None,
path: ElementPath::Root(0),
scope: ScopeId::ROOT,
}
}
pub struct ElementPath {
pub(crate) path: &'static [u8],
}
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_element(&mut self) -> ElementId {
ElementId(self.elements.insert(None))
}
pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
self.next_reference(template, ElementPath::Root(path))
}
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())
})));
pub(crate) fn next_null(&mut self) -> ElementId {
let entry = self.elements.vacant_entry();
let id = entry.key();
// Set this id to be dropped when the scope is rerun
if let Some(scope) = self.runtime.current_scope_id() {
self.scopes[scope.0]
.element_refs_to_drop
.borrow_mut()
.push(new_id);
}
entry.insert(ElementRef::none());
ElementId(id)
}
fn next_reference(&mut self, template: &VNode, path: ElementPath) -> ElementId {
let entry = self.elements.vacant_entry();
let id = entry.key();
let scope = self.runtime.current_scope_id().unwrap_or(ScopeId::ROOT);
entry.insert(ElementRef {
// We know this is non-null because it comes from a reference
template: Some(unsafe { NonNull::new_unchecked(template as *const _ as *mut _) }),
path,
scope,
});
ElementId(id)
new_id
}
pub(crate) fn reclaim(&mut self, el: ElementId) {
@@ -76,7 +64,7 @@ impl VirtualDom {
.unwrap_or_else(|| panic!("cannot reclaim {:?}", el));
}
pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<()> {
if el.0 == 0 {
panic!(
"Cannot reclaim the root element - {:#?}",
@@ -84,12 +72,12 @@ impl VirtualDom {
);
}
self.elements.try_remove(el.0)
self.elements.try_remove(el.0).map(|_| ())
}
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) };
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
@@ -101,6 +89,15 @@ impl VirtualDom {
id,
});
// Remove all VNode ids from the scope
for id in self.scopes[id.0]
.element_refs_to_drop
.borrow_mut()
.drain(..)
{
self.element_refs.try_remove(id.0);
}
self.ensure_drop_safety(id);
if recursive {
@@ -145,14 +142,25 @@ impl VirtualDom {
}
/// Descend through the tree, removing any borrowed props and listeners
pub(crate) fn ensure_drop_safety(&self, scope_id: ScopeId) {
pub(crate) fn ensure_drop_safety(&mut self, scope_id: ScopeId) {
let scope = &self.scopes[scope_id.0];
{
// Drop all element refs that could be invalidated when the component was rerun
let mut element_refs = self.scopes[scope_id.0].element_refs_to_drop.borrow_mut();
let element_refs_slab = &mut self.element_refs;
for element_ref in element_refs.drain(..) {
if let Some(element_ref) = element_refs_slab.get_mut(element_ref.0) {
*element_ref = None;
}
}
}
// 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 props = { scope.borrowed_props.borrow_mut().clone() };
for comp in props {
let comp = unsafe { &*comp };
match comp.scope.get() {
Some(child) if child != scope_id => self.ensure_drop_safety(child),
@@ -161,7 +169,9 @@ impl VirtualDom {
if let Ok(mut props) = comp.props.try_borrow_mut() {
*props = None;
}
});
}
let scope = &self.scopes[scope_id.0];
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();
@@ -176,18 +186,12 @@ impl VirtualDom {
impl ElementPath {
pub(crate) fn is_decendant(&self, small: &&[u8]) -> bool {
match *self {
ElementPath::Deep(big) => small.len() <= big.len() && *small == &big[..small.len()],
ElementPath::Root(r) => small.len() == 1 && small[0] == r as u8,
}
small.len() <= self.path.len() && *small == &self.path[..small.len()]
}
}
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,
}
self.path.eq(*other)
}
}
+20 -1
View File
@@ -1,5 +1,5 @@
use crate::nodes::RenderReturn;
use crate::{Attribute, AttributeValue};
use crate::{Attribute, AttributeValue, VComponent};
use bumpalo::Bump;
use std::cell::RefCell;
use std::cell::{Cell, UnsafeCell};
@@ -7,7 +7,10 @@ use std::cell::{Cell, UnsafeCell};
pub(crate) struct BumpFrame {
pub bump: UnsafeCell<Bump>,
pub node: Cell<*const RenderReturn<'static>>,
// The bump allocator will not call the destructor of the objects it allocated. Attributes and props need to have there destructor called, so we keep a list of them to drop before the bump allocator is reset.
pub(crate) attributes_to_drop_before_reset: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) props_to_drop_before_reset: RefCell<Vec<*const VComponent<'static>>>,
}
impl BumpFrame {
@@ -17,6 +20,7 @@ impl BumpFrame {
bump: UnsafeCell::new(bump),
node: Cell::new(std::ptr::null()),
attributes_to_drop_before_reset: Default::default(),
props_to_drop_before_reset: Default::default(),
}
}
@@ -41,6 +45,10 @@ impl BumpFrame {
.push(attribute);
}
/// Reset the bump allocator and drop all the attributes and props that were allocated in it.
///
/// # Safety
/// The caller must insure that no reference to anything allocated in the bump allocator is available after this function is called.
pub(crate) unsafe fn reset(&self) {
let mut attributes = self.attributes_to_drop_before_reset.borrow_mut();
attributes.drain(..).for_each(|attribute| {
@@ -49,9 +57,20 @@ impl BumpFrame {
_ = l.take();
}
});
let mut props = self.props_to_drop_before_reset.borrow_mut();
props.drain(..).for_each(|prop| {
let prop = unsafe { &*prop };
_ = prop.props.borrow_mut().take();
});
unsafe {
let bump = &mut *self.bump.get();
bump.reset();
}
}
}
impl Drop for BumpFrame {
fn drop(&mut self) {
unsafe { self.reset() }
}
}
+88 -48
View File
@@ -1,6 +1,7 @@
use crate::any_props::AnyProps;
use crate::innerlude::{
AttributeType, BorrowedAttributeValue, MountedAttribute, VComponent, VPlaceholder, VText,
AttributeType, BorrowedAttributeValue, ElementPath, ElementRef, MountedAttribute, VComponent,
VPlaceholder, VText,
};
use crate::mutations::Mutation;
use crate::mutations::Mutation::*;
@@ -96,6 +97,9 @@ impl<'b> 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());
@@ -183,15 +187,30 @@ impl<'b> VirtualDom {
use DynamicNode::*;
match &template.dynamic_nodes[idx] {
node @ Component { .. } | node @ Fragment(_) => {
self.create_dynamic_node(template, node, idx)
let template_ref = ElementRef {
path: ElementPath {
path: template.template.get().node_paths[idx],
},
template: template.stable_id().unwrap(),
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
};
self.create_dynamic_node(template_ref, node)
}
Placeholder(VPlaceholder { id }) => {
let id = self.set_slot(template, id, idx);
Placeholder(VPlaceholder { id, parent }) => {
let template_ref = ElementRef {
path: ElementPath {
path: template.template.get().node_paths[idx],
},
template: template.stable_id().unwrap(),
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
};
parent.set(Some(template_ref));
let id = self.set_slot(id);
self.mutations.push(CreatePlaceholder { id });
1
}
Text(VText { id, value }) => {
let id = self.set_slot(template, id, idx);
let id = self.set_slot(id);
self.create_static_text(value, id);
1
}
@@ -207,7 +226,7 @@ impl<'b> VirtualDom {
});
}
/// We write all the descndent data for this element
/// We write all the descendent data for this element
///
/// Elements can contain other nodes - and those nodes can be dynamic or static
///
@@ -267,7 +286,14 @@ impl<'b> VirtualDom {
.map(|sorted_index| dynamic_nodes[sorted_index].0);
for idx in reversed_iter {
let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx);
let boundary_ref = ElementRef {
path: ElementPath {
path: template.template.get().node_paths[idx],
},
template: template.stable_id().unwrap(),
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
};
let m = self.create_dynamic_node(boundary_ref, &template.dynamic_nodes[idx]);
if m > 0 {
// The path is one shorter because the top node is the root
let path = &template.template.get().node_paths[idx][1..];
@@ -281,15 +307,15 @@ impl<'b> VirtualDom {
attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
root_idx: u8,
root: ElementId,
node: &VNode,
node: &'b VNode<'b>,
) {
while let Some((mut attr_id, path)) =
attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
{
let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
let id = self.assign_static_node_as_dynamic(path, root);
loop {
self.write_attribute_type(&node.dynamic_attrs[attr_id], id);
self.write_attribute_type(node, &node.dynamic_attrs[attr_id], attr_id, id);
// Only push the dynamic attributes forward if they match the current path (same element)
match attrs.next_if(|(_, p)| *p == path) {
@@ -300,25 +326,44 @@ impl<'b> VirtualDom {
}
}
fn write_attribute_type(&mut self, attribute: &'b MountedAttribute<'b>, id: ElementId) {
fn write_attribute_type(
&mut self,
vnode: &'b VNode<'b>,
attribute: &'b MountedAttribute<'b>,
idx: usize,
id: ElementId,
) {
// Make sure we set the attribute's associated id
attribute.mounted_element.set(id);
match &attribute.ty {
AttributeType::Single(attribute) => self.write_attribute(attribute, id),
AttributeType::Single(attribute) => self.write_attribute(vnode, attribute, idx, id),
AttributeType::Many(attribute) => {
for attribute in *attribute {
self.write_attribute(attribute, id);
self.write_attribute(vnode, attribute, idx, id);
}
}
}
}
pub(crate) fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) {
pub(crate) fn write_attribute(
&mut self,
vnode: &'b VNode<'b>,
attribute: &'b crate::Attribute<'b>,
idx: usize,
id: ElementId,
) {
// 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::Listener(_) => {
let path = &vnode.template.get().attr_paths[idx];
let element_ref = ElementRef {
path: ElementPath { path },
template: vnode.stable_id().unwrap(),
scope: self.runtime.current_scope_id().unwrap_or(ScopeId(0)),
};
self.elements[id.0] = Some(element_ref);
self.mutations.push(NewEventListener {
// all listeners start with "on"
name: &unbounded_name[2..],
@@ -342,7 +387,7 @@ impl<'b> VirtualDom {
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);
let this_id = self.next_element();
template.root_ids.borrow_mut()[root_idx] = this_id;
self.mutations.push(LoadTemplate {
@@ -365,8 +410,6 @@ impl<'b> VirtualDom {
&mut self,
path: &'static [u8],
this_id: ElementId,
template: &VNode,
attr_id: usize,
) -> ElementId {
if path.len() == 1 {
return this_id;
@@ -374,7 +417,7 @@ impl<'b> VirtualDom {
// 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.get().attr_paths[attr_id]);
let id = self.next_element();
self.mutations.push(Mutation::AssignId {
path: &path[1..],
@@ -417,6 +460,7 @@ impl<'b> VirtualDom {
#[allow(unused_mut)]
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
let byte_index = byte_index.parse::<usize>().unwrap();
// First, check if we've already seen this template
if self
@@ -451,27 +495,21 @@ impl<'b> VirtualDom {
pub(crate) fn create_dynamic_node(
&mut self,
template: &'b VNode<'b>,
parent: ElementRef,
node: &'b DynamicNode<'b>,
idx: usize,
) -> usize {
use DynamicNode::*;
match node {
Text(text) => self.create_dynamic_text(template, text, idx),
Placeholder(place) => self.create_placeholder(place, template, idx),
Component(component) => self.create_component_node(template, component),
Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(),
Text(text) => self.create_dynamic_text(parent, text),
Placeholder(place) => self.create_placeholder(place, parent),
Component(component) => self.create_component_node(Some(parent), component),
Fragment(frag) => self.create_children(*frag, Some(parent)),
}
}
fn create_dynamic_text(
&mut self,
template: &'b VNode<'b>,
text: &'b VText<'b>,
idx: usize,
) -> usize {
fn create_dynamic_text(&mut self, parent: ElementRef, text: &'b VText<'b>) -> usize {
// Allocate a dynamic element reference for this text node
let new_id = self.next_element(template, template.template.get().node_paths[idx]);
let new_id = self.next_element();
// Make sure the text node is assigned to the correct element
text.id.set(Some(new_id));
@@ -482,7 +520,7 @@ impl<'b> VirtualDom {
// Add the mutation to the list
self.mutations.push(HydrateText {
id: new_id,
path: &template.template.get().node_paths[idx][1..],
path: &parent.path.path[1..],
value,
});
@@ -493,18 +531,20 @@ impl<'b> VirtualDom {
pub(crate) fn create_placeholder(
&mut self,
placeholder: &VPlaceholder,
template: &'b VNode<'b>,
idx: usize,
parent: ElementRef,
) -> usize {
// Allocate a dynamic element reference for this text node
let id = self.next_element(template, template.template.get().node_paths[idx]);
let id = self.next_element();
// Make sure the text node is assigned to the correct element
placeholder.id.set(Some(id));
// Assign the placeholder's parent
placeholder.parent.set(Some(parent));
// Assign the ID to the existing node in the template
self.mutations.push(AssignId {
path: &template.template.get().node_paths[idx][1..],
path: &parent.path.path[1..],
id,
});
@@ -514,7 +554,7 @@ impl<'b> VirtualDom {
pub(super) fn create_component_node(
&mut self,
template: &'b VNode<'b>,
parent: Option<ElementRef>,
component: &'b VComponent<'b>,
) -> usize {
use RenderReturn::*;
@@ -526,8 +566,11 @@ impl<'b> VirtualDom {
match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
// Create the component's root element
Ready(t) => self.create_scope(scope, t),
Aborted(t) => self.mount_aborted(template, t),
Ready(t) => {
self.assign_boundary_ref(parent, t);
self.create_scope(scope, t)
}
Aborted(t) => self.mount_aborted(t, parent),
}
}
@@ -543,20 +586,17 @@ impl<'b> VirtualDom {
.unwrap_or_else(|| component.scope.get().unwrap())
}
fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize {
let id = self.next_element(parent, &[]);
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);
1
}
fn set_slot(
&mut self,
template: &'b VNode<'b>,
slot: &'b Cell<Option<ElementId>>,
id: usize,
) -> ElementId {
let id = self.next_element(template, template.template.get().node_paths[id]);
fn set_slot(&mut self, slot: &'b Cell<Option<ElementId>>) -> ElementId {
let id = self.next_element();
slot.set(Some(id));
id
}
+147 -64
View File
@@ -2,7 +2,8 @@ use crate::{
any_props::AnyProps,
arena::ElementId,
innerlude::{
AttributeType, BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText,
AttributeType, BorrowedAttributeValue, DirtyScope, ElementPath, ElementRef, VComponent,
VPlaceholder, VText,
},
mutations::Mutation,
nodes::RenderReturn,
@@ -41,19 +42,27 @@ impl<'b> VirtualDom {
(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()),
(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]),
(Aborted(l), Ready(r)) => self.replace_placeholder(
l,
[r],
l.parent.get().expect("root node should not be none"),
),
};
}
self.runtime.scope_stack.borrow_mut().pop();
}
fn diff_ok_to_err(&mut self, l: &'b VNode<'b>, p: &'b VPlaceholder) {
let id = self.next_null();
let id = self.next_element();
p.id.set(Some(id));
p.parent.set(l.parent.get());
self.mutations.push(Mutation::CreatePlaceholder { id });
let pre_edits = self.mutations.edits.len();
@@ -83,12 +92,24 @@ impl<'b> VirtualDom {
if let Some(&template) = map.get(&byte_index) {
right_template.template.set(template);
if template != left_template.template.get() {
return self.replace(left_template, [right_template]);
let parent = left_template.parent.take();
return self.replace(left_template, [right_template], parent);
}
}
}
}
// 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);
}
// 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;
@@ -107,11 +128,8 @@ impl<'b> VirtualDom {
.zip(right_template.dynamic_attrs.iter())
.for_each(|(left_attr, right_attr)| {
// Move over the ID from the old to the new
let mounted_id = left_attr.mounted_element.get();
right_attr.mounted_element.set(mounted_id);
// We want to make sure anything that gets pulled is valid
self.update_template(left_attr.mounted_element.get(), right_template);
let mounted_element = left_attr.mounted_element.get();
right_attr.mounted_element.set(mounted_element);
match (&left_attr.ty, &right_attr.ty) {
(AttributeType::Single(left), AttributeType::Single(right)) => {
@@ -160,8 +178,16 @@ impl<'b> VirtualDom {
.dynamic_nodes
.iter()
.zip(right_template.dynamic_nodes.iter())
.for_each(|(left_node, right_node)| {
self.diff_dynamic_node(left_node, right_node, right_template);
.enumerate()
.for_each(|(dyn_node_idx, (left_node, right_node))| {
let current_ref = ElementRef {
template: right_template.stable_id().unwrap(),
path: ElementPath {
path: left_template.template.get().node_paths[dyn_node_idx],
},
scope: self.runtime.scope_stack.borrow().last().copied().unwrap(),
};
self.diff_dynamic_node(left_node, right_node, current_ref);
});
// Make sure the roots get transferred over while we're here
@@ -172,15 +198,6 @@ impl<'b> VirtualDom {
right.push(element);
}
}
let root_ids = right_template.root_ids.borrow();
// Update the node refs
for i in 0..root_ids.len() {
if let Some(root_id) = root_ids.get(i) {
self.update_template(*root_id, right_template);
}
}
}
fn diff_attribute(
@@ -199,15 +216,18 @@ impl<'b> VirtualDom {
&mut self,
left_node: &'b DynamicNode<'b>,
right_node: &'b DynamicNode<'b>,
node: &'b VNode<'b>,
parent: ElementRef,
) {
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),
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right),
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right),
(Text(left), Text(right)) => self.diff_vtext(left, right),
(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());
},
(Component(left), Component(right)) => self.diff_vcomponent(left, right, Some(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."),
};
}
@@ -245,7 +265,7 @@ impl<'b> VirtualDom {
&mut self,
left: &'b VComponent<'b>,
right: &'b VComponent<'b>,
right_template: &'b VNode<'b>,
parent: Option<ElementRef>,
) {
if std::ptr::eq(left, right) {
return;
@@ -253,7 +273,7 @@ impl<'b> VirtualDom {
// Replace components that have different render fns
if left.render_fn != right.render_fn {
return self.replace_vcomponent(right_template, right, left);
return self.replace_vcomponent(right, left, parent);
}
// Make sure the new vcomponent has the right scopeid associated to it
@@ -294,11 +314,11 @@ impl<'b> VirtualDom {
fn replace_vcomponent(
&mut self,
right_template: &'b VNode<'b>,
right: &'b VComponent<'b>,
left: &'b VComponent<'b>,
parent: Option<ElementRef>,
) {
let m = self.create_component_node(right_template, right);
let m = self.create_component_node(parent, right);
let pre_edits = self.mutations.edits.len();
@@ -353,11 +373,12 @@ impl<'b> VirtualDom {
/// }
/// ```
fn light_diff_templates(&mut self, left: &'b VNode<'b>, right: &'b VNode<'b>) {
let parent = left.parent.take();
match matching_components(left, right) {
None => self.replace(left, [right]),
None => self.replace(left, [right], parent),
Some(components) => components
.into_iter()
.for_each(|(l, r)| self.diff_vcomponent(l, r, right)),
.for_each(|(l, r)| self.diff_vcomponent(l, r, parent)),
}
}
@@ -365,11 +386,8 @@ impl<'b> VirtualDom {
///
/// 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]));
fn diff_vtext(&mut self, left: &'b VText<'b>, right: &'b VText<'b>) {
let id = left.id.get().unwrap_or_else(|| self.next_element());
right.id.set(Some(id));
if left.value != right.value {
@@ -378,7 +396,12 @@ impl<'b> VirtualDom {
}
}
fn diff_non_empty_fragment(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
fn diff_non_empty_fragment(
&mut self,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
parent: ElementRef,
) {
let new_is_keyed = new[0].key.is_some();
let old_is_keyed = old[0].key.is_some();
debug_assert!(
@@ -391,9 +414,9 @@ impl<'b> VirtualDom {
);
if new_is_keyed && old_is_keyed {
self.diff_keyed_children(old, new);
self.diff_keyed_children(old, new, parent);
} else {
self.diff_non_keyed_children(old, new);
self.diff_non_keyed_children(old, new, parent);
}
}
@@ -405,7 +428,12 @@ impl<'b> VirtualDom {
// [... parent]
//
// the change list stack is in the same state when this function returns.
fn diff_non_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
fn diff_non_keyed_children(
&mut self,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
parent: ElementRef,
) {
use std::cmp::Ordering;
// Handled these cases in `diff_children` before calling this function.
@@ -414,7 +442,9 @@ impl<'b> VirtualDom {
match old.len().cmp(&new.len()) {
Ordering::Greater => self.remove_nodes(&old[new.len()..]),
Ordering::Less => self.create_and_insert_after(&new[old.len()..], old.last().unwrap()),
Ordering::Less => {
self.create_and_insert_after(&new[old.len()..], old.last().unwrap(), parent)
}
Ordering::Equal => {}
}
@@ -439,7 +469,12 @@ impl<'b> VirtualDom {
// https://github.com/infernojs/inferno/blob/36fd96/packages/inferno/src/DOM/patching.ts#L530-L739
//
// The stack is empty upon entry.
fn diff_keyed_children(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
fn diff_keyed_children(
&mut self,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
parent: ElementRef,
) {
if cfg!(debug_assertions) {
let mut keys = rustc_hash::FxHashSet::default();
let mut assert_unique_keys = |children: &'b [VNode<'b>]| {
@@ -467,7 +502,7 @@ impl<'b> VirtualDom {
//
// `shared_prefix_count` is the count of how many nodes at the start of
// `new` and `old` share the same keys.
let (left_offset, right_offset) = match self.diff_keyed_ends(old, new) {
let (left_offset, right_offset) = match self.diff_keyed_ends(old, new, parent) {
Some(count) => count,
None => return,
};
@@ -493,18 +528,18 @@ impl<'b> VirtualDom {
if left_offset == 0 {
// insert at the beginning of the old list
let foothold = &old[old.len() - right_offset];
self.create_and_insert_before(new_middle, foothold);
self.create_and_insert_before(new_middle, foothold, parent);
} else if right_offset == 0 {
// insert at the end the old list
let foothold = old.last().unwrap();
self.create_and_insert_after(new_middle, foothold);
self.create_and_insert_after(new_middle, foothold, parent);
} else {
// inserting in the middle
let foothold = &old[left_offset - 1];
self.create_and_insert_after(new_middle, foothold);
self.create_and_insert_after(new_middle, foothold, parent);
}
} else {
self.diff_keyed_middle(old_middle, new_middle);
self.diff_keyed_middle(old_middle, new_middle, parent);
}
}
@@ -517,6 +552,7 @@ impl<'b> VirtualDom {
&mut self,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
parent: ElementRef,
) -> Option<(usize, usize)> {
let mut left_offset = 0;
@@ -532,7 +568,7 @@ impl<'b> VirtualDom {
// If that was all of the old children, then create and append the remaining
// new children and we're finished.
if left_offset == old.len() {
self.create_and_insert_after(&new[left_offset..], old.last().unwrap());
self.create_and_insert_after(&new[left_offset..], old.last().unwrap(), parent);
return None;
}
@@ -571,7 +607,12 @@ impl<'b> VirtualDom {
//
// Upon exit from this function, it will be restored to that same self.
#[allow(clippy::too_many_lines)]
fn diff_keyed_middle(&mut self, old: &'b [VNode<'b>], new: &'b [VNode<'b>]) {
fn diff_keyed_middle(
&mut self,
old: &'b [VNode<'b>],
new: &'b [VNode<'b>],
parent: ElementRef,
) {
/*
1. Map the old keys into a numerical ordering based on indices.
2. Create a map of old key to its index
@@ -628,7 +669,7 @@ impl<'b> VirtualDom {
if shared_keys.is_empty() {
if !old.is_empty() {
self.remove_nodes(&old[1..]);
self.replace(&old[0], new);
self.replace(&old[0], new, Some(parent));
} else {
// I think this is wrong - why are we appending?
// only valid of the if there are no trailing elements
@@ -805,20 +846,38 @@ impl<'b> VirtualDom {
.sum()
}
fn create_children(&mut self, nodes: impl IntoIterator<Item = &'b VNode<'b>>) -> usize {
pub(crate) fn create_children(
&mut self,
nodes: impl IntoIterator<Item = &'b VNode<'b>>,
parent: Option<ElementRef>,
) -> usize {
nodes
.into_iter()
.fold(0, |acc, child| acc + self.create(child))
.map(|child| {
self.assign_boundary_ref(parent, child);
self.create(child)
})
.sum()
}
fn create_and_insert_before(&mut self, new: &'b [VNode<'b>], before: &'b VNode<'b>) {
let m = self.create_children(new);
fn create_and_insert_before(
&mut self,
new: &'b [VNode<'b>],
before: &'b VNode<'b>,
parent: ElementRef,
) {
let m = self.create_children(new, Some(parent));
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);
fn create_and_insert_after(
&mut self,
new: &'b [VNode<'b>],
after: &'b VNode<'b>,
parent: ElementRef,
) {
let m = self.create_children(new, Some(parent));
let id = self.find_last_element(after);
self.mutations.push(Mutation::InsertAfter { id, m })
}
@@ -828,15 +887,21 @@ impl<'b> VirtualDom {
&mut self,
l: &'b VPlaceholder,
r: impl IntoIterator<Item = &'b VNode<'b>>,
parent: ElementRef,
) {
let m = self.create_children(r);
let m = self.create_children(r, Some(parent));
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<Item = &'b VNode<'b>>) {
let m = self.create_children(right);
fn replace(
&mut self,
left: &'b VNode<'b>,
right: impl IntoIterator<Item = &'b VNode<'b>>,
parent: Option<ElementRef>,
) {
let m = self.create_children(right, parent);
let pre_edits = self.mutations.edits.len();
@@ -855,11 +920,12 @@ impl<'b> VirtualDom {
};
}
fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder) {
fn node_to_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b VPlaceholder, parent: ElementRef) {
// Create the placeholder first, ensuring we get a dedicated ID for the placeholder
let placeholder = self.next_element(&l[0], &[]);
let placeholder = self.next_element();
r.id.set(Some(placeholder));
r.parent.set(Some(parent));
self.mutations
.push(Mutation::CreatePlaceholder { id: placeholder });
@@ -897,6 +963,16 @@ impl<'b> 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: &'b VNode<'b>) {
// 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) {
@@ -1055,6 +1131,13 @@ impl<'b> VirtualDom {
}
}
}
pub(crate) fn assign_boundary_ref(&mut self, parent: Option<ElementRef>, child: &'b VNode<'b>) {
if let Some(parent) = parent {
// assign the parent of the child
child.parent.set(Some(parent));
}
}
}
/// Are the templates the same?
+350 -62
View File
@@ -1,25 +1,68 @@
use crate::{ScopeId, ScopeState};
use crate::{
scope_context::{consume_context, current_scope_id, schedule_update_any},
Element, IntoDynNode, LazyNodes, Properties, Scope, ScopeId, ScopeState, Template,
TemplateAttribute, TemplateNode, VNode,
};
use std::{
any::{Any, TypeId},
backtrace::Backtrace,
cell::RefCell,
fmt::Debug,
error::Error,
fmt::{Debug, Display},
rc::Rc,
sync::Arc,
};
/// A boundary that will capture any errors from child components
pub struct ErrorBoundary {
error: RefCell<Option<CapturedError>>,
_id: ScopeId,
/// 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()))
}
/// A boundary that will capture any errors from child components
#[derive(Debug, Clone, Default)]
pub struct ErrorBoundary {
inner: Rc<ErrorBoundaryInner>,
}
/// A boundary that will capture any errors from child components
pub struct ErrorBoundaryInner {
error: RefCell<Option<CapturedError>>,
_id: ScopeId,
rerun_boundary: Arc<dyn Fn(ScopeId) + Send + Sync>,
}
impl Debug for ErrorBoundaryInner {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("ErrorBoundaryInner")
.field("error", &self.error)
.finish()
}
}
#[derive(Debug)]
/// An instance of an error captured by a descendant component.
pub struct CapturedError {
/// The error captured by the error boundary
pub error: Box<dyn Debug + 'static>,
/// The backtrace of the error
pub backtrace: Backtrace,
/// The scope that threw the error
pub scope: ScopeId,
}
impl Display for CapturedError {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.write_fmt(format_args!(
"Encountered error: {:?}\nIn scope: {:?}\nBacktrace: {}",
self.error, self.scope, self.backtrace
))
}
}
impl Error for CapturedError {}
impl CapturedError {
/// Downcast the error type into a concrete error type
pub fn downcast<T: 'static>(&self) -> Option<&T> {
@@ -32,17 +75,56 @@ impl CapturedError {
}
}
impl ErrorBoundary {
pub fn new(id: ScopeId) -> Self {
impl Default for ErrorBoundaryInner {
fn default() -> Self {
Self {
error: RefCell::new(None),
_id: id,
_id: current_scope_id()
.expect("Cannot create an error boundary outside of a component's scope."),
rerun_boundary: schedule_update_any().unwrap(),
}
}
}
impl ErrorBoundary {
/// Create a new error boundary
pub fn new() -> Self {
Self::default()
}
/// Create a new error boundary in the current scope
pub(crate) fn new_in_scope(
scope: ScopeId,
rerun_boundary: Arc<dyn Fn(ScopeId) + Send + Sync>,
) -> Self {
Self {
inner: Rc::new(ErrorBoundaryInner {
error: RefCell::new(None),
_id: scope,
rerun_boundary,
}),
}
}
/// Push an error into this Error Boundary
pub fn insert_error(&self, scope: ScopeId, error: Box<dyn Debug + 'static>) {
self.error.replace(Some(CapturedError { error, scope }));
pub fn insert_error(
&self,
scope: ScopeId,
error: Box<dyn Debug + 'static>,
backtrace: Backtrace,
) {
println!("{:?} {:?}", error, self.inner._id);
self.inner.error.replace(Some(CapturedError {
error,
scope,
backtrace,
}));
(self.inner.rerun_boundary)(self.inner._id);
}
/// Take any error that has been captured by this error boundary
pub fn take_error(&self) -> Option<CapturedError> {
self.inner.error.take()
}
}
@@ -59,7 +141,7 @@ impl ErrorBoundary {
/// ```rust, ignore
/// #[component]
/// fn App(cx: Scope, count: String) -> Element {
/// let id: i32 = count.parse().throw(cx)?;
/// let id: i32 = count.parse().throw()?;
///
/// cx.render(rsx! {
/// div { "Count {}" }
@@ -84,14 +166,14 @@ pub trait Throw<S = ()>: Sized {
/// ```rust, ignore
/// #[component]
/// fn App(cx: Scope, count: String) -> Element {
/// let id: i32 = count.parse().throw(cx)?;
/// let id: i32 = count.parse().throw()?;
///
/// cx.render(rsx! {
/// div { "Count {}" }
/// })
/// }
/// ```
fn throw(self, cx: &ScopeState) -> Option<Self::Out>;
fn throw(self) -> Option<Self::Out>;
/// Returns an option that evaluates to None if there is an error, injecting the error to the nearest error boundary.
///
@@ -107,45 +189,46 @@ pub trait Throw<S = ()>: Sized {
/// ```rust, ignore
/// #[component]
/// fn App(cx: Scope, count: String) -> Element {
/// let id: i32 = count.parse().throw(cx)?;
/// let id: i32 = count.parse().throw()?;
///
/// cx.render(rsx! {
/// div { "Count {}" }
/// })
/// }
/// ```
fn throw_with<D: Debug + 'static>(
self,
cx: &ScopeState,
e: impl FnOnce() -> D,
) -> Option<Self::Out>;
fn throw_with<D: Debug + 'static>(self, e: impl FnOnce() -> D) -> Option<Self::Out> {
self.throw().or_else(|| throw_error(e()))
}
}
fn throw_error<T>(e: impl Debug + 'static) -> Option<T> {
if let Some(cx) = consume_context::<ErrorBoundary>() {
match current_scope_id() {
Some(id) => cx.insert_error(id, Box::new(e), Backtrace::capture()),
None => {
tracing::error!("Cannot throw error outside of a component's scope.")
}
}
}
None
}
/// We call clone on any errors that can be owned out of a reference
impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E> {
type Out = &'a T;
fn throw(self, cx: &ScopeState) -> Option<Self::Out> {
fn throw(self) -> Option<Self::Out> {
match self {
Ok(t) => Some(t),
Err(e) => {
cx.throw(e.to_owned());
None
}
Err(e) => throw_error(e.to_owned()),
}
}
fn throw_with<D: Debug + 'static>(
self,
cx: &ScopeState,
err: impl FnOnce() -> D,
) -> Option<Self::Out> {
fn throw_with<D: Debug + 'static>(self, err: impl FnOnce() -> D) -> Option<Self::Out> {
match self {
Ok(t) => Some(t),
Err(_e) => {
cx.throw(err());
None
}
Err(_e) => throw_error(err()),
}
}
}
@@ -154,25 +237,15 @@ impl<'a, T, O: Debug + 'static, E: ToOwned<Owned = O>> Throw for &'a Result<T, E
impl<T, E: Debug + 'static> Throw for Result<T, E> {
type Out = T;
fn throw(self, cx: &ScopeState) -> Option<T> {
fn throw(self) -> Option<T> {
match self {
Ok(t) => Some(t),
Err(e) => {
cx.throw(e);
None
}
Err(e) => throw_error(e),
}
}
fn throw_with<D: Debug + 'static>(
self,
cx: &ScopeState,
error: impl FnOnce() -> D,
) -> Option<Self::Out> {
self.ok().or_else(|| {
cx.throw(error());
None
})
fn throw_with<D: Debug + 'static>(self, error: impl FnOnce() -> D) -> Option<Self::Out> {
self.ok().or_else(|| throw_error(error()))
}
}
@@ -180,21 +253,236 @@ impl<T, E: Debug + 'static> Throw for Result<T, E> {
impl<T> Throw for Option<T> {
type Out = T;
fn throw(self, cx: &ScopeState) -> Option<T> {
self.or_else(|| {
cx.throw("None error.");
None
})
fn throw(self) -> Option<T> {
self.or_else(|| throw_error("Attempted to unwrap a None value."))
}
fn throw_with<D: Debug + 'static>(
self,
cx: &ScopeState,
error: impl FnOnce() -> D,
) -> Option<Self::Out> {
self.or_else(|| {
cx.throw(error());
None
})
fn throw_with<D: Debug + 'static>(self, error: impl FnOnce() -> D) -> Option<Self::Out> {
self.or_else(|| throw_error(error()))
}
}
pub struct ErrorHandler<'a>(Box<dyn Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a>);
impl<'a, F: Fn(CapturedError) -> LazyNodes<'a, 'a> + 'a> From<F> for ErrorHandler<'a> {
fn from(value: F) -> Self {
Self(Box::new(value))
}
}
fn default_handler<'a>(error: CapturedError) -> LazyNodes<'a, 'a> {
LazyNodes::new(move |__cx: &ScopeState| -> VNode {
static TEMPLATE: Template = Template {
name: "error_handle.rs:42:5:884",
roots: &[TemplateNode::Element {
tag: "pre",
namespace: None,
attrs: &[TemplateAttribute::Static {
name: "color",
namespace: Some("style"),
value: "red",
}],
children: &[TemplateNode::DynamicText { id: 0usize }],
}],
node_paths: &[&[0u8, 0u8]],
attr_paths: &[],
};
VNode {
parent: Default::default(),
stable_id: Default::default(),
key: None,
template: std::cell::Cell::new(TEMPLATE),
root_ids: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
dynamic_nodes: __cx
.bump()
.alloc([__cx.text_node(format_args!("{0}", error))]),
dynamic_attrs: __cx.bump().alloc([]),
}
})
}
pub struct ErrorBoundaryProps<'a> {
children: Element<'a>,
handle_error: ErrorHandler<'a>,
}
impl<'a> ErrorBoundaryProps<'a> {
/**
Create a builder for building `ErrorBoundaryProps`.
On the builder, call `.children(...)`(optional), `.handle_error(...)`(optional) to set the values of the fields.
Finally, call `.build()` to create the instance of `ErrorBoundaryProps`.
*/
#[allow(dead_code)]
pub fn builder() -> ErrorBoundaryPropsBuilder<'a, ((), ())> {
ErrorBoundaryPropsBuilder {
fields: ((), ()),
_phantom: ::core::default::Default::default(),
}
}
}
#[must_use]
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub struct ErrorBoundaryPropsBuilder<'a, TypedBuilderFields> {
fields: TypedBuilderFields,
_phantom: ::core::marker::PhantomData<&'a ()>,
}
impl<'a, TypedBuilderFields> Clone for ErrorBoundaryPropsBuilder<'a, TypedBuilderFields>
where
TypedBuilderFields: Clone,
{
fn clone(&self) -> Self {
Self {
fields: self.fields.clone(),
_phantom: ::core::default::Default::default(),
}
}
}
impl<'a> Properties for ErrorBoundaryProps<'a> {
type Builder = ErrorBoundaryPropsBuilder<'a, ((), ())>;
const IS_STATIC: bool = false;
fn builder() -> Self::Builder {
ErrorBoundaryProps::builder()
}
unsafe fn memoize(&self, _: &Self) -> bool {
false
}
}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub trait ErrorBoundaryPropsBuilder_Optional<T> {
fn into_value<F: FnOnce() -> T>(self, default: F) -> T;
}
impl<T> ErrorBoundaryPropsBuilder_Optional<T> for () {
fn into_value<F: FnOnce() -> T>(self, default: F) -> T {
default()
}
}
impl<T> ErrorBoundaryPropsBuilder_Optional<T> for (T,) {
fn into_value<F: FnOnce() -> T>(self, _: F) -> T {
self.0
}
}
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((), __handle_error)> {
pub fn children(
self,
children: Element<'a>,
) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
let children = (children,);
let (_, handle_error) = self.fields;
ErrorBoundaryPropsBuilder {
fields: (children, handle_error),
_phantom: self._phantom,
}
}
}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_children {}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl<'a, __handle_error> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
#[deprecated(note = "Repeated field children")]
pub fn children(
self,
_: ErrorBoundaryPropsBuilder_Error_Repeated_field_children,
) -> ErrorBoundaryPropsBuilder<'a, ((Element<'a>,), __handle_error)> {
self
}
}
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, ())> {
pub fn handle_error(
self,
handle_error: impl ::core::convert::Into<ErrorHandler<'a>>,
) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
let handle_error = (handle_error.into(),);
let (children, _) = self.fields;
ErrorBoundaryPropsBuilder {
fields: (children, handle_error),
_phantom: self._phantom,
}
}
}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, non_snake_case)]
pub enum ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error {}
#[doc(hidden)]
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl<'a, __children> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
#[deprecated(note = "Repeated field handle_error")]
pub fn handle_error(
self,
_: ErrorBoundaryPropsBuilder_Error_Repeated_field_handle_error,
) -> ErrorBoundaryPropsBuilder<'a, (__children, (ErrorHandler<'a>,))> {
self
}
}
#[allow(dead_code, non_camel_case_types, missing_docs)]
impl<
'a,
__handle_error: ErrorBoundaryPropsBuilder_Optional<ErrorHandler<'a>>,
__children: ErrorBoundaryPropsBuilder_Optional<Element<'a>>,
> ErrorBoundaryPropsBuilder<'a, (__children, __handle_error)>
{
pub fn build(self) -> ErrorBoundaryProps<'a> {
let (children, handle_error) = self.fields;
let children = ErrorBoundaryPropsBuilder_Optional::into_value(children, || {
::core::default::Default::default()
});
let handle_error = ErrorBoundaryPropsBuilder_Optional::into_value(handle_error, || {
ErrorHandler(Box::new(default_handler))
});
ErrorBoundaryProps {
children,
handle_error,
}
}
}
/// Create a new error boundary component.
///
/// ## Details
///
/// Error boundaries handle errors within a specific part of your application. Any errors passed in a child with [`Throw`] will be caught by the nearest error boundary.
///
/// ## Example
///
/// ```rust, ignore
/// rsx!{
/// ErrorBoundary {
/// handle_error: |error| rsx! { "Oops, we encountered an error. Please report {error} to the developer of this application" }
/// ThrowsError {}
/// }
/// }
/// ```
///
/// ## Usage
///
/// Error boundaries are an easy way to handle errors in your application.
/// 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<'a>(cx: Scope<'a, ErrorBoundaryProps<'a>>) -> Element {
let error_boundary = use_error_boundary(cx);
match error_boundary.take_error() {
Some(error) => cx.render((cx.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: bumpalo::collections::Vec::with_capacity_in(1usize, __cx.bump()).into(),
dynamic_nodes: __cx.bump().alloc([{
let ___nodes = (&cx.props.children).into_dyn_node(__cx);
___nodes
}]),
dynamic_attrs: __cx.bump().alloc([]),
}
}),
}
}
+21 -2
View File
@@ -28,6 +28,27 @@ pub struct Event<T: 'static + ?Sized> {
}
impl<T> Event<T> {
/// Map the event data to a new type
///
/// # Example
///
/// ```rust, ignore
/// rsx! {
/// button {
/// onclick: move |evt: Event<FormData>| {
/// let data = evt.map(|data| data.value());
/// assert_eq!(data.inner(), "hello world");
/// }
/// }
/// }
/// ```
pub fn map<U: 'static, F: FnOnce(&T) -> U>(&self, f: F) -> Event<U> {
Event {
data: Rc::new(f(&self.data)),
propagates: self.propagates.clone(),
}
}
/// Prevent this event from continuing to bubble up the tree to parent elements.
///
/// # Example
@@ -107,8 +128,6 @@ impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
}
}
#[doc(hidden)]
/// 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.
+2 -1
View File
@@ -30,7 +30,8 @@ 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,
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,
+1 -1
View File
@@ -18,7 +18,7 @@ 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
/// 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 use it for different branches in matching.
///
///
+6 -5
View File
@@ -90,11 +90,12 @@ pub mod prelude {
pub use crate::innerlude::{
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, has_context,
provide_context, provide_context_to_scope, provide_root_context, push_future,
remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw, AnyValue,
Attribute, AttributeType, Component, Element, Event, EventHandler, Fragment, HasAttributes,
IntoAttributeValue, IntoDynNode, LazyNodes, MountedAttribute, Properties, Runtime,
RuntimeGuard, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
TemplateNode, Throw, VNode, VirtualDom,
remove_future, schedule_update_any, spawn, spawn_forever, suspend, throw,
use_error_boundary, AnyValue, AnyValue, Attribute, AttributeType, Component, Element,
ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
IntoDynNode, LazyNodes, MountedAttribute, Properties, Runtime, RuntimeGuard, Scope,
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, Throw,
VNode, VirtualDom,
};
}
+2 -2
View File
@@ -26,7 +26,7 @@ pub struct Mutations<'a> {
/// Any templates encountered while diffing the DOM.
///
/// These must be loaded into a cache before applying the edits
pub templates: Vec<Template<'a>>,
pub templates: Vec<Template<'static>>,
/// Any mutations required to patch the renderer to match the layout of the VirtualDom
pub edits: Vec<Mutation<'a>>,
@@ -91,7 +91,7 @@ pub enum Mutation<'a> {
id: ElementId,
},
/// Create an placeholder int he DOM that we will use later.
/// Create a placeholder in the 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 {
+64 -32
View File
@@ -1,3 +1,4 @@
use crate::innerlude::{ElementRef, VNodeId};
use crate::{
any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
};
@@ -47,7 +48,10 @@ pub struct VNode<'a> {
pub key: Option<&'a str>,
/// When rendered, this template will be linked to its parent manually
pub parent: Option<ElementId>,
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>>,
@@ -68,7 +72,8 @@ impl<'a> VNode<'a> {
pub fn empty(cx: &'a ScopeState) -> Element<'a> {
Some(VNode {
key: None,
parent: None,
parent: Default::default(),
stable_id: Default::default(),
root_ids: RefCell::new(bumpalo::collections::Vec::new_in(cx.bump())),
dynamic_nodes: &[],
dynamic_attrs: &[],
@@ -81,6 +86,30 @@ impl<'a> VNode<'a> {
})
}
/// Create a new VNode
pub fn new(
key: Option<&'a str>,
template: Template<'static>,
root_ids: bumpalo::collections::Vec<'a, ElementId>,
dynamic_nodes: &'a [DynamicNode<'a>],
dynamic_attrs: &'a [Attribute<'a>],
) -> Self {
Self {
key,
parent: Cell::new(None),
stable_id: Cell::new(None),
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
///
/// Returns [`None`] if the root is actually a static node (Element/Text)
@@ -319,7 +348,7 @@ pub struct VComponent<'a> {
/// 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
/// It is possible that components get folded at compile time, so these shouldn't be really used as a key
pub(crate) render_fn: *const (),
pub(crate) props: RefCell<Option<Box<dyn AnyProps<'a> + 'a>>>,
@@ -372,6 +401,8 @@ impl<'a> VText<'a> {
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>>,
}
impl VPlaceholder {
@@ -720,52 +751,52 @@ pub trait IntoDynNode<'a, A = ()> {
/// Consume this item along with a scopestate and produce a DynamicNode
///
/// You can use the bump alloactor of the scopestate to creat the dynamic node
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode<'a>;
}
impl<'a> IntoDynNode<'a> for () {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::default()
}
}
impl<'a> IntoDynNode<'a> for VNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::Fragment(_cx.bump().alloc([self]))
}
}
impl<'a> IntoDynNode<'a> for DynamicNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
self
}
}
impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
match self {
Some(val) => val.into_vnode(_cx),
Some(val) => val.into_dyn_node(_cx),
None => DynamicNode::default(),
}
}
}
impl<'a> IntoDynNode<'a> for &Element<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
match self.as_ref() {
Some(val) => val.clone().into_vnode(_cx),
Some(val) => val.clone().into_dyn_node(_cx),
_ => DynamicNode::default(),
}
}
}
impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::Fragment(cx.bump().alloc([cx.render(self).unwrap()]))
}
}
impl<'a, 'b> IntoDynNode<'b> for &'a str {
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
fn into_dyn_node(self, cx: &'b ScopeState) -> DynamicNode<'b> {
DynamicNode::Text(VText {
value: cx.bump().alloc_str(self),
id: Default::default(),
@@ -774,24 +805,25 @@ impl<'a, 'b> IntoDynNode<'b> for &'a str {
}
impl IntoDynNode<'_> for String {
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
fn into_dyn_node(self, cx: &ScopeState) -> DynamicNode {
DynamicNode::Text(VText {
value: cx.bump().alloc(self),
value: cx.bump().alloc_str(&self),
id: Default::default(),
})
}
}
impl<'b> IntoDynNode<'b> for Arguments<'_> {
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
fn into_dyn_node(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> {
fn into_dyn_node(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
DynamicNode::Fragment(_cx.bump().alloc([VNode {
parent: self.parent,
parent: self.parent.clone(),
stable_id: self.stable_id.clone(),
template: self.template.clone(),
root_ids: self.root_ids.clone(),
key: self.key,
@@ -801,24 +833,24 @@ impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
}
}
pub trait IntoTemplate<'a> {
fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
pub trait IntoVNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> VNode<'a>;
}
impl<'a> IntoTemplate<'a> for VNode<'a> {
fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
impl<'a> IntoVNode<'a> for VNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> VNode<'a> {
self
}
}
impl<'a> IntoTemplate<'a> for Element<'a> {
fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
impl<'a> IntoVNode<'a> for Element<'a> {
fn into_vnode(self, cx: &'a ScopeState) -> VNode<'a> {
match self {
Some(val) => val.into_template(cx),
Some(val) => val.into_vnode(cx),
_ => VNode::empty(cx).unwrap(),
}
}
}
impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
fn into_vnode(self, cx: &'a ScopeState) -> VNode<'a> {
cx.render(self).unwrap()
}
}
@@ -828,12 +860,12 @@ pub struct FromNodeIterator;
impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
where
T: Iterator<Item = I>,
I: IntoTemplate<'a>,
I: IntoVNode<'a>,
{
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
fn into_dyn_node(self, cx: &'a ScopeState) -> DynamicNode<'a> {
let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
nodes.extend(self.into_iter().map(|node| node.into_template(cx)));
nodes.extend(self.into_iter().map(|node| node.into_vnode(cx)));
match nodes.into_bump_slice() {
children if children.is_empty() => DynamicNode::default(),
@@ -861,8 +893,8 @@ impl<'a> IntoAttributeValue<'a> for &'a str {
}
impl<'a> IntoAttributeValue<'a> for String {
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Text(bump.alloc(self))
fn into_value(self, cx: &'a Bump) -> AttributeValue<'a> {
AttributeValue::Text(cx.alloc_str(&self))
}
}
+1 -1
View File
@@ -98,7 +98,7 @@ impl Runtime {
}
}
/// A gaurd for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
/// A guard for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
///
/// ```rust
/// use dioxus::prelude::*;
+11 -7
View File
@@ -19,7 +19,7 @@ pub struct TaskId(pub usize);
/// the task itself is the waker
pub(crate) struct LocalTask {
pub scope: ScopeId,
pub(super) task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
pub task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
pub waker: Waker,
}
@@ -48,11 +48,15 @@ impl Scheduler {
})),
};
entry.insert(task);
let mut cx = std::task::Context::from_waker(&task.waker);
self.sender
.unbounded_send(SchedulerMsg::TaskNotified(task_id))
.expect("Scheduler should exist");
if !task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
self.sender
.unbounded_send(SchedulerMsg::TaskNotified(task_id))
.expect("Scheduler should exist");
}
entry.insert(task);
task_id
}
@@ -60,8 +64,8 @@ impl Scheduler {
/// Drop the future with the given TaskId
///
/// This does not 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().try_remove(id.0);
pub fn remove(&self, id: TaskId) -> Option<LocalTask> {
self.tasks.borrow_mut().try_remove(id.0)
}
}
+1
View File
@@ -36,6 +36,7 @@ impl VirtualDom {
borrowed_props: Default::default(),
attributes_to_drop_before_render: Default::default(),
element_refs_to_drop: Default::default(),
}));
let context =
+2 -31
View File
@@ -1,5 +1,5 @@
use crate::{
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
innerlude::{Scheduler, SchedulerMsg},
runtime::{with_current_scope, with_runtime},
Element, ScopeId, TaskId,
};
@@ -7,7 +7,6 @@ use rustc_hash::FxHashSet;
use std::{
any::Any,
cell::{Cell, RefCell},
fmt::Debug,
future::Future,
rc::Rc,
sync::Arc,
@@ -230,17 +229,7 @@ impl ScopeContext {
/// This is good for tasks that need to be run after the component has been dropped.
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
// The root scope will never be unmounted so we can just add the task at the top of the app
let id = self.tasks.spawn(ScopeId::ROOT, fut);
// wake up the scheduler if it is sleeping
self.tasks
.sender
.unbounded_send(SchedulerMsg::TaskNotified(id))
.expect("Scheduler should exist");
self.spawned_tasks.borrow_mut().insert(id);
id
self.tasks.spawn(ScopeId::ROOT, fut)
}
/// Informs the scheduler that this task is no longer needed and should be removed.
@@ -250,19 +239,6 @@ impl ScopeContext {
self.tasks.remove(id);
}
/// Inject an error into the nearest error boundary and quit rendering
///
/// The error doesn't need to implement Error or any specific traits since the boundary
/// itself will downcast the error into a trait object.
pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
cx.insert_error(self.scope_id(), Box::new(error));
}
// Always return none during a throw
None
}
/// Mark this component as suspended and then return None
pub fn suspend(&self) -> Option<Element> {
self.suspended.set(true);
@@ -332,11 +308,6 @@ pub fn suspend() -> Option<Element<'static>> {
None
}
/// Throw an error into the nearest error boundary
pub fn throw(error: impl Debug + 'static) -> Option<()> {
with_current_scope(|cx| cx.throw(error)).flatten()
}
/// Pushes the future onto the poll queue to be polled after the component renders.
pub fn push_future(fut: impl Future<Output = ()> + 'static) -> Option<TaskId> {
with_current_scope(|cx| cx.push_future(fut))
+10 -18
View File
@@ -2,8 +2,7 @@ use crate::{
any_props::AnyProps,
any_props::VProps,
bump_frame::BumpFrame,
innerlude::ErrorBoundary,
innerlude::{DynamicNode, EventHandler, VComponent, VText},
innerlude::{DynamicNode, EventHandler, VComponent, VNodeId, VText},
lazynodes::LazyNodes,
nodes::{IntoAttributeValue, IntoDynNode, RenderReturn},
runtime::Runtime,
@@ -95,6 +94,7 @@ pub struct ScopeState {
pub(crate) hook_idx: Cell<usize>,
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
pub(crate) element_refs_to_drop: RefCell<Vec<VNodeId>>,
pub(crate) attributes_to_drop_before_render: RefCell<Vec<*const Attribute<'static>>>,
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
@@ -370,12 +370,17 @@ impl<'src> ScopeState {
}
let mut props = self.borrowed_props.borrow_mut();
let mut drop_props = self
.previous_frame()
.props_to_drop_before_reset
.borrow_mut();
for node in element.dynamic_nodes {
if let DynamicNode::Component(comp) = node {
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
if !comp.static_props {
let unbounded = unsafe { std::mem::transmute(comp as *const VComponent) };
props.push(unbounded);
}
drop_props.push(unbounded);
}
}
@@ -404,7 +409,7 @@ impl<'src> ScopeState {
/// Convert any item that implements [`IntoDynNode`] into a [`DynamicNode`] using the internal [`Bump`] allocator
pub fn make_node<'c, I>(&'src self, into: impl IntoDynNode<'src, I> + 'c) -> DynamicNode {
into.into_vnode(self)
into.into_dyn_node(self)
}
/// Create a new [`Attribute`] from a name, value, namespace, and volatile bool
@@ -466,7 +471,7 @@ impl<'src> ScopeState {
render_fn: component as *const (),
static_props: P::IS_STATIC,
props: RefCell::new(Some(extended)),
scope: Cell::new(None),
scope: Default::default(),
})
}
@@ -517,19 +522,6 @@ impl<'src> ScopeState {
AttributeValue::Any(RefCell::new(Some(boxed)))
}
/// Inject an error into the nearest error boundary and quit rendering
///
/// The error doesn't need to implement Error or any specific traits since the boundary
/// itself will downcast the error into a trait object.
pub fn throw(&self, error: impl Debug + 'static) -> Option<()> {
if let Some(cx) = self.consume_context::<Rc<ErrorBoundary>>() {
cx.insert_error(self.scope_id(), Box::new(error));
}
// Always return none during a throw
None
}
/// Mark this component as suspended and then return None
pub fn suspend(&self) -> Option<Element> {
let cx = self.context();
+91 -107
View File
@@ -11,12 +11,14 @@ use crate::{
nodes::{Template, TemplateId},
runtime::{Runtime, RuntimeGuard},
scopes::{ScopeId, ScopeState},
Attribute, AttributeValue, Element, Event, Scope,
Attribute, AttributeValue, Element, Event, Scope, VNode,
};
use futures_util::{pin_mut, StreamExt};
use rustc_hash::{FxHashMap, FxHashSet};
use slab::Slab;
use std::{any::Any, cell::Cell, collections::BTreeSet, future::Future, rc::Rc};
use std::{
any::Any, cell::Cell, collections::BTreeSet, future::Future, ptr::NonNull, rc::Rc, sync::Arc,
};
/// A virtual node system that progresses user events and diffs UI trees.
///
@@ -186,7 +188,10 @@ pub struct VirtualDom {
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) elements: Slab<ElementRef>,
pub(crate) element_refs: Slab<Option<NonNull<VNode<'static>>>>,
// The element ids that are used in the renderer
pub(crate) elements: Slab<Option<ElementRef>>,
pub(crate) mutations: Mutations<'static>,
@@ -263,6 +268,7 @@ impl VirtualDom {
dirty_scopes: Default::default(),
templates: Default::default(),
elements: Default::default(),
element_refs: Default::default(),
mutations: Mutations::default(),
suspended_scopes: Default::default(),
};
@@ -273,10 +279,13 @@ impl VirtualDom {
);
// Unlike react, we provide a default error boundary that just renders the error as a string
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId::ROOT)));
root.provide_context(Rc::new(ErrorBoundary::new_in_scope(
ScopeId::ROOT,
Arc::new(|_| {}),
)));
// the root element is always given element ID 0 since it's the container for the entire tree
dom.elements.insert(ElementRef::none());
dom.elements.insert(None);
dom
}
@@ -314,9 +323,9 @@ impl VirtualDom {
}
}
/// Call a listener inside the VirtualDom with data from outside the VirtualDom.
/// Call a listener inside the VirtualDom with data from outside the VirtualDom. **The ElementId passed in must be the id of an dynamic element, not a static node or a text node.**
///
/// This method will identify the appropriate element. The data must match up with the listener delcared. Note that
/// This method will identify the appropriate element. The data must match up with the listener declared. Note that
/// this method does not give any indication as to the success of the listener call. If the listener is not found,
/// nothing will happen.
///
@@ -353,7 +362,15 @@ impl VirtualDom {
| | | <-- no, broke early
| <-- no, broke early
*/
let mut parent_path = self.elements.get(element.0);
let parent_path = match self.elements.get(element.0) {
Some(Some(el)) => el,
_ => return,
};
let mut parent_node = self
.element_refs
.get(parent_path.template.0)
.cloned()
.map(|el| (*parent_path, el));
let mut listeners = vec![];
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
@@ -365,121 +382,91 @@ 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(el_ref) = parent_path {
while let Some((path, el_ref)) = parent_node {
// safety: we maintain references of all vnodes in the element slab
if let Some(template) = el_ref.template {
let template = unsafe { template.as_ref() };
let node_template = template.template.get();
let target_path = el_ref.path;
let template = unsafe { el_ref.unwrap().as_ref() };
let node_template = template.template.get();
let target_path = path.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx];
for (idx, attr) in template.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
if target_path.is_decendant(&this_path) {
attr.ty.for_each(|attribute| {
if attribute.name.trim_start_matches("on") == name {
listeners.push(&attribute.value);
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
if target_path.is_decendant(&this_path) {
attr.ty.for_each(|attribute| {
if attribute.name.trim_start_matches("on") == name {
if let AttributeValue::Listener(listener) = &attribute.value {
listeners.push(&listener);
}
});
// Break if this is the exact target element.
// This means we won't call two listeners with the same name on the same element. This should be
// documented, or be rejected from the rsx! macro outright
if target_path == this_path {
break;
}
});
// Break if this is the exact target element.
// This means we won't call two listeners with the same name on the same element. This should be
// documented, or be rejected from the rsx! macro outright
if target_path == this_path {
break;
}
}
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
// We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() {
if let AttributeValue::Listener(listener) = listener {
let origin = el_ref.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());
}
self.runtime.scope_stack.borrow_mut().pop();
self.runtime.rendering.set(true);
if !uievent.propagates.get() {
return;
}
}
}
parent_path = template.parent.and_then(|id| self.elements.get(id.0));
} else {
break;
}
// Now that we've accumulated all the parent attributes for the target element, call them in reverse order
// We check the bubble state between each call to see if the event has been stopped from bubbling
for listener in listeners.drain(..).rev() {
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());
}
self.runtime.scope_stack.borrow_mut().pop();
self.runtime.rendering.set(true);
if !uievent.propagates.get() {
return;
}
}
parent_node = template.parent.get().and_then(|element_ref| {
self.element_refs
.get(element_ref.template.0)
.cloned()
.map(|el| (element_ref, el))
});
}
} else {
// Otherwise, we just call the listener on the target element
if let Some(el_ref) = parent_path {
if let Some((path, el_ref)) = parent_node {
// safety: we maintain references of all vnodes in the element slab
if let Some(template) = el_ref.template {
let template = unsafe { template.as_ref() };
let node_template = template.template.get();
let target_path = el_ref.path;
let template = unsafe { el_ref.unwrap().as_ref() };
let node_template = template.template.get();
let target_path = path.path;
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx];
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
let this_path = node_template.attr_paths[idx];
fn call_listener(
attribute: &Attribute,
event_name: &str,
uievent: &Event<dyn Any>,
runtime: &Runtime,
origin: ScopeId,
) -> bool {
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
// Only call the listener if this is the exact target element.
if attribute.name.trim_start_matches("on") == event_name {
if let AttributeValue::Listener(listener) = &attribute.value {
runtime.scope_stack.borrow_mut().push(origin);
runtime.rendering.set(false);
// Remove the "on" prefix if it exists, TODO, we should remove this and settle on one
// Only call the listener if this is the exact target element.
if target_path == this_path {
let mut should_stop = false;
attr.ty.for_each(|attribute| {
if attribute.name.trim_start_matches("on") == name {
if let AttributeValue::Listener(listener) = &attr.value {
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());
}
runtime.scope_stack.borrow_mut().pop();
runtime.rendering.set(true);
self.runtime.scope_stack.borrow_mut().pop();
self.runtime.rendering.set(true);
return true;
}
}
false
}
if target_path == this_path {
match &attr.ty {
AttributeType::Single(attribute) => {
if call_listener(
attribute,
name,
&uievent,
&self.runtime,
el_ref.scope,
) {
return;
}
}
AttributeType::Many(attributes) => {
for attribute in *attributes {
if call_listener(
attribute,
name,
&uievent,
&self.runtime,
el_ref.scope,
) {
return;
}
}
should_stop = true;
}
}
});
if should_stop {
return;
}
}
}
@@ -602,7 +589,7 @@ impl VirtualDom {
// If an error occurs, we should try to render the default error component and context where the error occured
RenderReturn::Aborted(placeholder) => {
tracing::debug!("Ran into suspended or aborted scope during rebuild");
let id = self.next_null();
let id = self.next_element();
placeholder.id.set(Some(id));
self.mutations.push(Mutation::CreatePlaceholder { id });
}
@@ -634,15 +621,12 @@ impl VirtualDom {
/// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
pub async fn wait_for_suspense(&mut self) {
loop {
// println!("waiting for suspense {:?}", self.suspended_scopes);
if self.suspended_scopes.is_empty() {
return;
}
// println!("waiting for suspense");
self.wait_for_work().await;
// println!("Rendered immediately");
_ = self.render_immediate();
}
}