Merge branch 'upstream' into fix-non-str-attributes
This commit is contained in:
commit
044462876d
52 files changed
+3002
-2852
No files matched your search
@@ -1,4 +1,4 @@
|
||||
use std::marker::PhantomData;
|
||||
use std::{marker::PhantomData, panic::AssertUnwindSafe};
|
||||
|
||||
use crate::{
|
||||
innerlude::Scoped,
|
||||
@@ -62,12 +62,19 @@ where
|
||||
}
|
||||
|
||||
fn render(&'a self, cx: &'a ScopeState) -> RenderReturn<'a> {
|
||||
let scope: &mut Scoped<P> = cx.bump().alloc(Scoped {
|
||||
props: &self.props,
|
||||
scope: cx,
|
||||
});
|
||||
let res = std::panic::catch_unwind(AssertUnwindSafe(move || {
|
||||
// Call the render function directly
|
||||
let scope: &mut Scoped<P> = cx.bump().alloc(Scoped {
|
||||
props: &self.props,
|
||||
scope: cx,
|
||||
});
|
||||
|
||||
// Call the render function directly
|
||||
(self.render_fn)(scope).into_return(cx)
|
||||
(self.render_fn)(scope).into_return(cx)
|
||||
}));
|
||||
|
||||
match res {
|
||||
Ok(e) => e,
|
||||
Err(_) => RenderReturn::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -81,12 +81,12 @@ impl VirtualDom {
|
||||
self.ensure_drop_safety(id);
|
||||
|
||||
if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
|
||||
if let RenderReturn::Sync(Some(node)) = unsafe { root.extend_lifetime_ref() } {
|
||||
if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
|
||||
self.drop_scope_inner(node)
|
||||
}
|
||||
}
|
||||
if let Some(root) = unsafe { self.scopes[id.0].as_ref().previous_frame().try_load_node() } {
|
||||
if let RenderReturn::Sync(Some(node)) = unsafe { root.extend_lifetime_ref() } {
|
||||
if let RenderReturn::Ready(node) = unsafe { root.extend_lifetime_ref() } {
|
||||
self.drop_scope_inner(node)
|
||||
}
|
||||
}
|
||||
@@ -126,11 +126,9 @@ impl VirtualDom {
|
||||
}
|
||||
});
|
||||
|
||||
for root in node.root_ids {
|
||||
if let Some(id) = root.get() {
|
||||
if id.0 != 0 {
|
||||
self.try_reclaim(id);
|
||||
}
|
||||
for id in &node.root_ids {
|
||||
if id.0 != 0 {
|
||||
self.try_reclaim(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+236
-73
@@ -5,13 +5,50 @@ use crate::mutations::Mutation::*;
|
||||
use crate::nodes::VNode;
|
||||
use crate::nodes::{DynamicNode, TemplateNode};
|
||||
use crate::virtual_dom::VirtualDom;
|
||||
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
|
||||
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext, Template};
|
||||
use std::cell::Cell;
|
||||
use std::iter::{Enumerate, Peekable};
|
||||
use std::iter::Peekable;
|
||||
use std::rc::Rc;
|
||||
use std::slice;
|
||||
use TemplateNode::*;
|
||||
|
||||
fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
|
||||
let mut with_indecies = paths.iter().copied().enumerate().collect::<Vec<_>>();
|
||||
with_indecies.sort_unstable_by(|(_, a), (_, b)| {
|
||||
let mut a = a.iter();
|
||||
let mut b = b.iter();
|
||||
loop {
|
||||
match (a.next(), b.next()) {
|
||||
(Some(a), Some(b)) => {
|
||||
if a != b {
|
||||
return a.cmp(b);
|
||||
}
|
||||
}
|
||||
// The shorter path goes first
|
||||
(Some(_), None) => return std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => return std::cmp::Ordering::Greater,
|
||||
(None, None) => return std::cmp::Ordering::Equal,
|
||||
}
|
||||
}
|
||||
});
|
||||
with_indecies
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sorting() {
|
||||
let r: [(usize, &[u8]); 5] = [
|
||||
(0, &[0, 1]),
|
||||
(1, &[0, 2]),
|
||||
(2, &[1, 0]),
|
||||
(4, &[1, 1]),
|
||||
(3, &[1, 2]),
|
||||
];
|
||||
assert_eq!(
|
||||
sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 2,], &[1, 1,],]),
|
||||
r
|
||||
);
|
||||
assert!(matches!(&[0], &[_, ..]))
|
||||
}
|
||||
|
||||
impl<'b> VirtualDom {
|
||||
/// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
|
||||
///
|
||||
@@ -25,21 +62,79 @@ impl<'b> VirtualDom {
|
||||
|
||||
/// Create this template and write its mutations
|
||||
pub(crate) fn create(&mut self, node: &'b VNode<'b>) -> usize {
|
||||
// check for a overriden template
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let (path, byte_index) = node.template.get().name.rsplit_once(':').unwrap();
|
||||
if let Some(template) = self
|
||||
.templates
|
||||
.get(path)
|
||||
.and_then(|map| map.get(&byte_index.parse().unwrap()))
|
||||
{
|
||||
node.template.set(*template);
|
||||
}
|
||||
}
|
||||
|
||||
// Intialize the root nodes slice
|
||||
node.root_ids
|
||||
.intialize(vec![ElementId(0); node.template.get().roots.len()].into_boxed_slice());
|
||||
|
||||
// The best renderers will have templates prehydrated and registered
|
||||
// Just in case, let's create the template using instructions anyways
|
||||
if !self.templates.contains_key(&node.template.name) {
|
||||
self.register_template(node);
|
||||
if !self.templates.contains_key(&node.template.get().name) {
|
||||
self.register_template(node.template.get());
|
||||
}
|
||||
|
||||
// we know that this will generate at least one mutation per node
|
||||
self.mutations.edits.reserve(node.template.roots.len());
|
||||
self.mutations
|
||||
.edits
|
||||
.reserve(node.template.get().roots.len());
|
||||
|
||||
// Walk the roots, creating nodes and assigning IDs
|
||||
// nodes in an iterator of ((dynamic_node_index, sorted_index), path)
|
||||
// todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
|
||||
let mut attrs = node.template.attr_paths.iter().enumerate().peekable();
|
||||
let mut nodes = node.template.node_paths.iter().enumerate().peekable();
|
||||
#[cfg(not(debug_assertions))]
|
||||
let (mut attrs, mut nodes) = (
|
||||
node.template
|
||||
.get()
|
||||
.attr_paths
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.peekable(),
|
||||
node.template
|
||||
.get()
|
||||
.node_paths
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.map(|(i, path)| ((i, i), path))
|
||||
.peekable(),
|
||||
);
|
||||
// If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let (attrs_sorted, nodes_sorted) = {
|
||||
(
|
||||
sort_bfs(node.template.get().attr_paths),
|
||||
sort_bfs(node.template.get().node_paths),
|
||||
)
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
let (mut attrs, mut nodes) = {
|
||||
(
|
||||
attrs_sorted.into_iter().peekable(),
|
||||
nodes_sorted
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.map(|(i, (id, path))| ((id, i), path))
|
||||
.peekable(),
|
||||
)
|
||||
};
|
||||
|
||||
node.template
|
||||
.get()
|
||||
.roots
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -48,7 +143,14 @@ impl<'b> VirtualDom {
|
||||
nodes.next().unwrap();
|
||||
self.write_dynamic_root(node, *id)
|
||||
}
|
||||
Element { .. } => self.write_element_root(node, idx, &mut attrs, &mut nodes),
|
||||
Element { .. } => {
|
||||
#[cfg(not(debug_assertions))]
|
||||
let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[]);
|
||||
#[cfg(debug_assertions)]
|
||||
let id =
|
||||
self.write_element_root(node, idx, &mut attrs, &mut nodes, &nodes_sorted);
|
||||
id
|
||||
}
|
||||
Text { .. } => self.write_static_text_root(node, idx),
|
||||
})
|
||||
.sum()
|
||||
@@ -65,8 +167,9 @@ impl<'b> VirtualDom {
|
||||
fn write_dynamic_root(&mut self, template: &'b VNode<'b>, idx: usize) -> usize {
|
||||
use DynamicNode::*;
|
||||
match &template.dynamic_nodes[idx] {
|
||||
node @ Fragment(_) => self.create_dynamic_node(template, node, idx),
|
||||
node @ Component { .. } => self.create_dynamic_node(template, node, idx),
|
||||
node @ Component { .. } | node @ Fragment(_) => {
|
||||
self.create_dynamic_node(template, node, idx)
|
||||
}
|
||||
Placeholder(VPlaceholder { id }) => {
|
||||
let id = self.set_slot(template, id, idx);
|
||||
self.mutations.push(CreatePlaceholder { id });
|
||||
@@ -98,17 +201,18 @@ impl<'b> VirtualDom {
|
||||
&mut self,
|
||||
template: &'b VNode<'b>,
|
||||
root_idx: usize,
|
||||
dynamic_attrs: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
dynamic_attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
|
||||
dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
|
||||
dynamic_nodes: &[(usize, &'static [u8])],
|
||||
) -> usize {
|
||||
// Load the template root and get the ID for the node on the stack
|
||||
let root_on_stack = self.load_template_root(template, root_idx);
|
||||
|
||||
// Write all the attributes below this root
|
||||
self.write_attrs_on_root(dynamic_attrs, root_idx, root_on_stack, template);
|
||||
self.write_attrs_on_root(dynamic_attrs, root_idx as u8, root_on_stack, template);
|
||||
|
||||
// Load in all of the placeholder or dynamic content under this root too
|
||||
self.load_placeholders(dynamic_nodes, root_idx, template);
|
||||
self.load_placeholders(dynamic_nodes_iter, dynamic_nodes, root_idx as u8, template);
|
||||
|
||||
1
|
||||
}
|
||||
@@ -126,22 +230,32 @@ impl<'b> VirtualDom {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(unused)]
|
||||
fn load_placeholders(
|
||||
&mut self,
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
root_idx: usize,
|
||||
dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
|
||||
dynamic_nodes: &[(usize, &'static [u8])],
|
||||
root_idx: u8,
|
||||
template: &'b VNode<'b>,
|
||||
) {
|
||||
let (start, end) = match collect_dyn_node_range(dynamic_nodes, root_idx) {
|
||||
let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) {
|
||||
Some((a, b)) => (a, b),
|
||||
None => return,
|
||||
};
|
||||
|
||||
for idx in (start..=end).rev() {
|
||||
// If hot reloading is enabled, we need to map the sorted index to the original index of the dynamic node. If it is disabled, we can just use the sorted index
|
||||
#[cfg(not(debug_assertions))]
|
||||
let reversed_iter = (start..=end).rev();
|
||||
#[cfg(debug_assertions)]
|
||||
let reversed_iter = (start..=end)
|
||||
.rev()
|
||||
.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);
|
||||
if m > 0 {
|
||||
// The path is one shorter because the top node is the root
|
||||
let path = &template.template.node_paths[idx][1..];
|
||||
let path = &template.template.get().node_paths[idx][1..];
|
||||
self.mutations.push(ReplacePlaceholder { m, path });
|
||||
}
|
||||
}
|
||||
@@ -149,12 +263,14 @@ impl<'b> VirtualDom {
|
||||
|
||||
fn write_attrs_on_root(
|
||||
&mut self,
|
||||
attrs: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
root_idx: usize,
|
||||
attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
|
||||
root_idx: u8,
|
||||
root: ElementId,
|
||||
node: &VNode,
|
||||
) {
|
||||
while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p[0] == root_idx as u8) {
|
||||
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);
|
||||
|
||||
loop {
|
||||
@@ -236,10 +352,10 @@ 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);
|
||||
template.root_ids[root_idx].set(Some(this_id));
|
||||
template.root_ids.set(root_idx, this_id);
|
||||
|
||||
self.mutations.push(LoadTemplate {
|
||||
name: template.template.name,
|
||||
name: template.template.get().name,
|
||||
index: root_idx,
|
||||
id: this_id,
|
||||
});
|
||||
@@ -267,7 +383,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.attr_paths[attr_id]);
|
||||
let id = self.next_element(template, template.template.get().attr_paths[attr_id]);
|
||||
|
||||
self.mutations.push(Mutation::AssignId {
|
||||
path: &path[1..],
|
||||
@@ -278,14 +394,59 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
/// Insert a new template into the VirtualDom's template registry
|
||||
fn register_template(&mut self, template: &'b VNode<'b>) {
|
||||
pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template<'static>) {
|
||||
// First, make sure we mark the template as seen, regardless if we process it
|
||||
self.templates
|
||||
.insert(template.template.name, template.template);
|
||||
let (path, _) = template.name.rsplit_once(':').unwrap();
|
||||
if let Some((_, old_template)) = self
|
||||
.templates
|
||||
.entry(path)
|
||||
.or_default()
|
||||
.iter_mut()
|
||||
.min_by_key(|(byte_index, _)| **byte_index)
|
||||
{
|
||||
// the byte index of the hot reloaded template could be different
|
||||
template.name = old_template.name;
|
||||
*old_template = template;
|
||||
} else {
|
||||
// This is a template without any current instances
|
||||
self.templates
|
||||
.entry(path)
|
||||
.or_default()
|
||||
.insert(usize::MAX, template);
|
||||
}
|
||||
|
||||
// If it's all dynamic nodes, then we don't need to register it
|
||||
if !template.template.is_completely_dynamic() {
|
||||
self.mutations.templates.push(template.template);
|
||||
if !template.is_completely_dynamic() {
|
||||
self.mutations.templates.push(template);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a new template into the VirtualDom's template registry
|
||||
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
|
||||
// First, make sure we mark the template as seen, regardless if we process it
|
||||
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
|
||||
let byte_index = byte_index.parse::<usize>().unwrap();
|
||||
|
||||
// if hot reloading is enabled, then we need to check for a template that has overriten this one
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(mut new_template) = self
|
||||
.templates
|
||||
.get_mut(path)
|
||||
.and_then(|map| map.remove(&usize::MAX))
|
||||
{
|
||||
// the byte index of the hot reloaded template could be different
|
||||
new_template.name = template.name;
|
||||
template = new_template;
|
||||
}
|
||||
|
||||
self.templates
|
||||
.entry(path)
|
||||
.or_default()
|
||||
.insert(byte_index, template);
|
||||
|
||||
// If it's all dynamic nodes, then we don't need to register it
|
||||
if !template.is_completely_dynamic() {
|
||||
self.mutations.templates.push(template);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -298,9 +459,9 @@ impl<'b> VirtualDom {
|
||||
use DynamicNode::*;
|
||||
match node {
|
||||
Text(text) => self.create_dynamic_text(template, text, idx),
|
||||
Fragment(frag) => self.create_fragment(frag),
|
||||
Placeholder(frag) => self.create_placeholder(frag, template, idx),
|
||||
Placeholder(place) => self.create_placeholder(place, template, idx),
|
||||
Component(component) => self.create_component_node(template, component, idx),
|
||||
Fragment(frag) => frag.iter().map(|child| self.create(child)).sum(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -311,7 +472,7 @@ impl<'b> VirtualDom {
|
||||
idx: usize,
|
||||
) -> usize {
|
||||
// Allocate a dynamic element reference for this text node
|
||||
let new_id = self.next_element(template, template.template.node_paths[idx]);
|
||||
let new_id = self.next_element(template, template.template.get().node_paths[idx]);
|
||||
|
||||
// Make sure the text node is assigned to the correct element
|
||||
text.id.set(Some(new_id));
|
||||
@@ -322,7 +483,7 @@ impl<'b> VirtualDom {
|
||||
// Add the mutation to the list
|
||||
self.mutations.push(HydrateText {
|
||||
id: new_id,
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
path: &template.template.get().node_paths[idx][1..],
|
||||
value,
|
||||
});
|
||||
|
||||
@@ -337,14 +498,14 @@ impl<'b> VirtualDom {
|
||||
idx: usize,
|
||||
) -> usize {
|
||||
// Allocate a dynamic element reference for this text node
|
||||
let id = self.next_element(template, template.template.node_paths[idx]);
|
||||
let id = self.next_element(template, template.template.get().node_paths[idx]);
|
||||
|
||||
// Make sure the text node is assigned to the correct element
|
||||
placeholder.id.set(Some(id));
|
||||
|
||||
// Assign the ID to the existing node in the template
|
||||
self.mutations.push(AssignId {
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
path: &template.template.get().node_paths[idx][1..],
|
||||
id,
|
||||
});
|
||||
|
||||
@@ -352,40 +513,38 @@ impl<'b> VirtualDom {
|
||||
0
|
||||
}
|
||||
|
||||
pub(crate) fn create_fragment(&mut self, nodes: &'b [VNode<'b>]) -> usize {
|
||||
nodes.iter().map(|child| self.create(child)).sum()
|
||||
}
|
||||
|
||||
pub(super) fn create_component_node(
|
||||
&mut self,
|
||||
template: &'b VNode<'b>,
|
||||
component: &'b VComponent<'b>,
|
||||
idx: usize,
|
||||
) -> usize {
|
||||
let scope = match component.props.take() {
|
||||
Some(props) => {
|
||||
let unbounded_props: Box<dyn AnyProps> = unsafe { std::mem::transmute(props) };
|
||||
let scope = self.new_scope(unbounded_props, component.name);
|
||||
scope.id
|
||||
}
|
||||
use RenderReturn::*;
|
||||
|
||||
// Component is coming back, it probably still exists, right?
|
||||
None => component.scope.get().unwrap(),
|
||||
};
|
||||
// Load up a ScopeId for this vcomponent
|
||||
let scope = self.load_scope_from_vcomponent(component);
|
||||
|
||||
component.scope.set(Some(scope));
|
||||
|
||||
let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
|
||||
|
||||
use RenderReturn::*;
|
||||
|
||||
match return_nodes {
|
||||
Sync(Some(t)) => self.mount_component(scope, template, t, idx),
|
||||
Sync(None) => todo!("Propogate error upwards"),
|
||||
Async(_) => self.mount_component_placeholder(template, idx, scope),
|
||||
match unsafe { self.run_scope(scope).extend_lifetime_ref() } {
|
||||
Ready(t) => self.mount_component(scope, template, t, idx),
|
||||
Aborted(t) => self.mount_aborted(template, t),
|
||||
Pending(_) => self.mount_async(template, idx, scope),
|
||||
}
|
||||
}
|
||||
|
||||
/// Load a scope from a vcomponent. If the props don't exist, that means the component is currently "live"
|
||||
fn load_scope_from_vcomponent(&mut self, component: &VComponent) -> ScopeId {
|
||||
component
|
||||
.props
|
||||
.take()
|
||||
.map(|props| {
|
||||
let unbounded_props: Box<dyn AnyProps> = unsafe { std::mem::transmute(props) };
|
||||
self.new_scope(unbounded_props, component.name).id
|
||||
})
|
||||
.unwrap_or_else(|| component.scope.get().unwrap())
|
||||
}
|
||||
|
||||
fn mount_component(
|
||||
&mut self,
|
||||
scope: ScopeId,
|
||||
@@ -412,7 +571,7 @@ impl<'b> VirtualDom {
|
||||
};
|
||||
|
||||
// Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree
|
||||
let new_id = self.next_element(new, parent.template.node_paths[idx]);
|
||||
let new_id = self.next_element(new, parent.template.get().node_paths[idx]);
|
||||
|
||||
// Now connect everything to the boundary
|
||||
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||
@@ -434,22 +593,24 @@ impl<'b> VirtualDom {
|
||||
// Now assign the placeholder in the DOM
|
||||
self.mutations.push(AssignId {
|
||||
id: new_id,
|
||||
path: &parent.template.node_paths[idx][1..],
|
||||
path: &parent.template.get().node_paths[idx][1..],
|
||||
});
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
fn mount_aborted(&mut self, parent: &'b VNode<'b>, placeholder: &VPlaceholder) -> usize {
|
||||
let id = self.next_element(parent, &[]);
|
||||
self.mutations.push(Mutation::CreatePlaceholder { id });
|
||||
placeholder.id.set(Some(id));
|
||||
1
|
||||
}
|
||||
|
||||
/// Take the rendered nodes from a component and handle them if they were async
|
||||
///
|
||||
/// IE simply assign an ID to the placeholder
|
||||
fn mount_component_placeholder(
|
||||
&mut self,
|
||||
template: &VNode,
|
||||
idx: usize,
|
||||
scope: ScopeId,
|
||||
) -> usize {
|
||||
let new_id = self.next_element(template, template.template.node_paths[idx]);
|
||||
fn mount_async(&mut self, template: &VNode, idx: usize, scope: ScopeId) -> usize {
|
||||
let new_id = self.next_element(template, template.template.get().node_paths[idx]);
|
||||
|
||||
// Set the placeholder of the scope
|
||||
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||
@@ -457,7 +618,7 @@ impl<'b> VirtualDom {
|
||||
// Since the placeholder is already in the DOM, we don't create any new nodes
|
||||
self.mutations.push(AssignId {
|
||||
id: new_id,
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
path: &template.template.get().node_paths[idx][1..],
|
||||
});
|
||||
|
||||
0
|
||||
@@ -469,24 +630,26 @@ impl<'b> VirtualDom {
|
||||
slot: &'b Cell<Option<ElementId>>,
|
||||
id: usize,
|
||||
) -> ElementId {
|
||||
let id = self.next_element(template, template.template.node_paths[id]);
|
||||
let id = self.next_element(template, template.template.get().node_paths[id]);
|
||||
slot.set(Some(id));
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_dyn_node_range(
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&[u8]>>>,
|
||||
root_idx: usize,
|
||||
dynamic_nodes: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
|
||||
root_idx: u8,
|
||||
) -> Option<(usize, usize)> {
|
||||
let start = match dynamic_nodes.peek() {
|
||||
Some((idx, p)) if p[0] == root_idx as u8 => *idx,
|
||||
Some(((_, idx), [first, ..])) if *first == root_idx => *idx,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let mut end = start;
|
||||
|
||||
while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) {
|
||||
while let Some(((_, idx), p)) =
|
||||
dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx))
|
||||
{
|
||||
if p.len() == 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
+111
-50
@@ -30,31 +30,61 @@ impl<'b> VirtualDom {
|
||||
.try_load_node()
|
||||
.expect("Call rebuild before diffing");
|
||||
|
||||
use RenderReturn::{Async, Sync};
|
||||
use RenderReturn::{Aborted, Pending, Ready};
|
||||
|
||||
match (old, new) {
|
||||
(Sync(Some(l)), Sync(Some(r))) => self.diff_node(l, r),
|
||||
// Normal pathway
|
||||
(Ready(l), Ready(r)) => self.diff_node(l, r),
|
||||
|
||||
// Err cases
|
||||
(Sync(Some(l)), Sync(None)) => self.diff_ok_to_err(l),
|
||||
(Sync(None), Sync(Some(r))) => self.diff_err_to_ok(r),
|
||||
(Sync(None), Sync(None)) => { /* nothing */ }
|
||||
// Unwind the mutations if need be
|
||||
(Ready(l), Aborted(p)) => self.diff_ok_to_err(l, p),
|
||||
|
||||
// Async
|
||||
(Sync(Some(_l)), Async(_)) => todo!(),
|
||||
(Sync(None), Async(_)) => todo!(),
|
||||
(Async(_), Sync(Some(_r))) => todo!(),
|
||||
(Async(_), Sync(None)) => { /* nothing */ }
|
||||
(Async(_), Async(_)) => { /* nothing */ }
|
||||
// Just move over the placeholder
|
||||
(Aborted(l), Aborted(r)) => r.id.set(l.id.get()),
|
||||
|
||||
// Becomes async, do nothing while we ait
|
||||
(Ready(_nodes), Pending(_fut)) => self.diff_ok_to_async(_nodes, scope),
|
||||
|
||||
// Placeholder becomes something
|
||||
// We should also clear the error now
|
||||
(Aborted(l), Ready(r)) => self.replace_placeholder(l, [r]),
|
||||
|
||||
(Aborted(_), Pending(_)) => todo!("async should not resolve here"),
|
||||
(Pending(_), Ready(_)) => todo!("async should not resolve here"),
|
||||
(Pending(_), Aborted(_)) => todo!("async should not resolve here"),
|
||||
(Pending(_), Pending(_)) => {
|
||||
// All suspense should resolve before we diff it again
|
||||
panic!("Should not roll from suspense to suspense.");
|
||||
}
|
||||
};
|
||||
}
|
||||
self.scope_stack.pop();
|
||||
}
|
||||
|
||||
fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>) {}
|
||||
fn diff_err_to_ok(&mut self, _l: &'b VNode<'b>) {}
|
||||
fn diff_ok_to_async(&mut self, _new: &'b VNode<'b>, _scope: ScopeId) {
|
||||
//
|
||||
}
|
||||
|
||||
fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _p: &'b VPlaceholder) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
// If hot reloading is enabled, we need to make sure we're using the latest template
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let (path, byte_index) = right_template.template.get().name.rsplit_once(':').unwrap();
|
||||
if let Some(map) = self.templates.get(path) {
|
||||
let byte_index = byte_index.parse::<usize>().unwrap();
|
||||
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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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;
|
||||
@@ -99,11 +129,7 @@ impl<'b> VirtualDom {
|
||||
});
|
||||
|
||||
// Make sure the roots get transferred over while we're here
|
||||
left_template
|
||||
.root_ids
|
||||
.iter()
|
||||
.zip(right_template.root_ids.iter())
|
||||
.for_each(|(left, right)| right.set(left.get()));
|
||||
right_template.root_ids.transfer(&left_template.root_ids);
|
||||
}
|
||||
|
||||
fn diff_dynamic_node(
|
||||
@@ -118,7 +144,7 @@ impl<'b> VirtualDom {
|
||||
(Fragment(left), Fragment(right)) => self.diff_non_empty_fragment(left, right),
|
||||
(Placeholder(left), Placeholder(right)) => right.id.set(left.id.get()),
|
||||
(Component(left), Component(right)) => self.diff_vcomponent(left, right, node, idx),
|
||||
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, right),
|
||||
(Placeholder(left), Fragment(right)) => self.replace_placeholder(left, *right),
|
||||
(Fragment(left), Placeholder(right)) => self.node_to_placeholder(left, right),
|
||||
_ => todo!("This is an usual custom case for dynamic nodes. We don't know how to handle it yet."),
|
||||
};
|
||||
@@ -191,8 +217,12 @@ impl<'b> VirtualDom {
|
||||
) {
|
||||
let m = self.create_component_node(right_template, right, idx);
|
||||
|
||||
let pre_edits = self.mutations.edits.len();
|
||||
|
||||
self.remove_component_node(left, true);
|
||||
|
||||
assert!(self.mutations.edits.len() > pre_edits);
|
||||
|
||||
// We want to optimize the replace case to use one less mutation if possible
|
||||
// Since mutations are done in reverse, the last node removed will be the first in the stack
|
||||
// Instead of *just* removing it, we can use the replace mutation
|
||||
@@ -638,6 +668,7 @@ impl<'b> VirtualDom {
|
||||
/// Push all the real nodes on the stack
|
||||
fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
|
||||
node.template
|
||||
.get()
|
||||
.roots
|
||||
.iter()
|
||||
.enumerate()
|
||||
@@ -646,7 +677,7 @@ impl<'b> VirtualDom {
|
||||
Some(node) => node,
|
||||
None => {
|
||||
self.mutations.push(Mutation::PushRoot {
|
||||
id: node.root_ids[idx].get().unwrap(),
|
||||
id: node.root_ids.get(idx).unwrap(),
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
@@ -673,7 +704,8 @@ impl<'b> VirtualDom {
|
||||
Component(comp) => {
|
||||
let scope = comp.scope.get().unwrap();
|
||||
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
|
||||
RenderReturn::Sync(Some(node)) => self.push_all_real_nodes(node),
|
||||
RenderReturn::Ready(node) => self.push_all_real_nodes(node),
|
||||
RenderReturn::Aborted(_node) => todo!(),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
@@ -701,7 +733,11 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
/// Simply replace a placeholder with a list of nodes
|
||||
fn replace_placeholder(&mut self, l: &'b VPlaceholder, r: &'b [VNode<'b>]) {
|
||||
fn replace_placeholder(
|
||||
&mut self,
|
||||
l: &'b VPlaceholder,
|
||||
r: impl IntoIterator<Item = &'b VNode<'b>>,
|
||||
) {
|
||||
let m = self.create_children(r);
|
||||
let id = l.id.get().unwrap();
|
||||
self.mutations.push(Mutation::ReplaceWith { id, m });
|
||||
@@ -773,11 +809,11 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
|
||||
for (idx, _) in node.template.roots.iter().enumerate() {
|
||||
for (idx, _) in node.template.get().roots.iter().enumerate() {
|
||||
if let Some(dy) = node.dynamic_root(idx) {
|
||||
self.remove_dynamic_node(dy, gen_muts);
|
||||
} else {
|
||||
let id = node.root_ids[idx].get().unwrap();
|
||||
let id = node.root_ids.get(idx).unwrap();
|
||||
if gen_muts {
|
||||
self.mutations.push(Mutation::Remove { id });
|
||||
}
|
||||
@@ -790,8 +826,19 @@ impl<'b> VirtualDom {
|
||||
let mut id = None;
|
||||
for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
|
||||
// We'll clean up the root nodes either way, so don't worry
|
||||
if node.template.attr_paths[idx].len() == 1 {
|
||||
continue;
|
||||
let path_len = node
|
||||
.template
|
||||
.get()
|
||||
.attr_paths
|
||||
.get(idx)
|
||||
.map(|path| path.len());
|
||||
// if the path is 1 the attribute is in the root, so we don't need to clean it up
|
||||
// if the path is 0, the attribute is a not attached at all, so we don't need to clean it up
|
||||
|
||||
if let Some(len) = path_len {
|
||||
if (..=1).contains(&len) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
let next_id = attr.mounted_element.get();
|
||||
@@ -808,12 +855,16 @@ impl<'b> VirtualDom {
|
||||
|
||||
fn remove_nested_dyn_nodes(&mut self, node: &VNode) {
|
||||
for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
|
||||
// Roots are cleaned up automatically above
|
||||
if node.template.node_paths[idx].len() == 1 {
|
||||
continue;
|
||||
let path_len = node
|
||||
.template
|
||||
.get()
|
||||
.node_paths
|
||||
.get(idx)
|
||||
.map(|path| path.len());
|
||||
// Roots are cleaned up automatically above and nodes with a empty path are placeholders
|
||||
if let Some(2..) = path_len {
|
||||
self.remove_dynamic_node(dyn_node, false)
|
||||
}
|
||||
|
||||
self.remove_dynamic_node(dyn_node, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -847,14 +898,20 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
fn remove_component_node(&mut self, comp: &VComponent, gen_muts: bool) {
|
||||
let scope = comp.scope.take().unwrap();
|
||||
let scope = comp
|
||||
.scope
|
||||
.take()
|
||||
.expect("VComponents to always have a scope");
|
||||
|
||||
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
|
||||
RenderReturn::Sync(Some(t)) => {
|
||||
println!("Removing component node sync {:?}", gen_muts);
|
||||
self.remove_node(t, gen_muts)
|
||||
RenderReturn::Ready(t) => self.remove_node(t, gen_muts),
|
||||
RenderReturn::Aborted(t) => {
|
||||
if let Some(id) = t.id.get() {
|
||||
self.try_reclaim(id);
|
||||
}
|
||||
return;
|
||||
}
|
||||
_ => todo!("cannot handle nonstandard nodes"),
|
||||
_ => todo!(),
|
||||
};
|
||||
|
||||
let props = self.scopes[scope.0].props.take();
|
||||
@@ -873,14 +930,14 @@ impl<'b> VirtualDom {
|
||||
|
||||
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
|
||||
match node.dynamic_root(0) {
|
||||
None => node.root_ids[0].get().unwrap(),
|
||||
None => node.root_ids.get(0).unwrap(),
|
||||
Some(Text(t)) => t.id.get().unwrap(),
|
||||
Some(Fragment(t)) => self.find_first_element(&t[0]),
|
||||
Some(Placeholder(t)) => t.id.get().unwrap(),
|
||||
Some(Component(comp)) => {
|
||||
let scope = comp.scope.get().unwrap();
|
||||
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
|
||||
RenderReturn::Sync(Some(t)) => self.find_first_element(t),
|
||||
RenderReturn::Ready(t) => self.find_first_element(t),
|
||||
_ => todo!("cannot handle nonstandard nodes"),
|
||||
}
|
||||
}
|
||||
@@ -888,15 +945,15 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
|
||||
match node.dynamic_root(node.template.roots.len() - 1) {
|
||||
None => node.root_ids.last().unwrap().get().unwrap(),
|
||||
match node.dynamic_root(node.template.get().roots.len() - 1) {
|
||||
None => node.root_ids.last().unwrap(),
|
||||
Some(Text(t)) => t.id.get().unwrap(),
|
||||
Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
|
||||
Some(Placeholder(t)) => t.id.get().unwrap(),
|
||||
Some(Component(comp)) => {
|
||||
let scope = comp.scope.get().unwrap();
|
||||
match unsafe { self.scopes[scope.0].root_node().extend_lifetime_ref() } {
|
||||
RenderReturn::Sync(Some(t)) => self.find_last_element(t),
|
||||
RenderReturn::Ready(t) => self.find_last_element(t),
|
||||
_ => todo!("cannot handle nonstandard nodes"),
|
||||
}
|
||||
}
|
||||
@@ -911,27 +968,30 @@ impl<'b> VirtualDom {
|
||||
/// We use the pointer of the dynamic_node list in this case
|
||||
fn templates_are_the_same<'b>(left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) -> bool {
|
||||
std::ptr::eq(left_template, right_template)
|
||||
|| std::ptr::eq(left_template.dynamic_nodes, right_template.dynamic_nodes)
|
||||
}
|
||||
|
||||
fn templates_are_different(left_template: &VNode, right_template: &VNode) -> bool {
|
||||
!std::ptr::eq(left_template.template.name, right_template.template.name)
|
||||
&& left_template.template.name != right_template.template.name
|
||||
let left_template_name = left_template.template.get().name;
|
||||
let right_template_name = right_template.template.get().name;
|
||||
// we want to re-create the node if the template name is different by pointer even if the value is the same so that we can detect when hot reloading changes the template
|
||||
!std::ptr::eq(left_template_name, right_template_name)
|
||||
}
|
||||
|
||||
fn matching_components<'a>(
|
||||
left: &'a VNode<'a>,
|
||||
right: &'a VNode<'a>,
|
||||
) -> Option<Vec<(&'a VComponent<'a>, &'a VComponent<'a>)>> {
|
||||
if left.template.roots.len() != right.template.roots.len() {
|
||||
let left_template = left.template.get();
|
||||
let right_template = right.template.get();
|
||||
if left_template.roots.len() != right_template.roots.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// run through the components, ensuring they're the same
|
||||
left.template
|
||||
left_template
|
||||
.roots
|
||||
.iter()
|
||||
.zip(right.template.roots.iter())
|
||||
.zip(right_template.roots.iter())
|
||||
.map(|(l, r)| {
|
||||
let (l, r) = match (l, r) {
|
||||
(TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
|
||||
@@ -956,11 +1016,12 @@ fn matching_components<'a>(
|
||||
/// - for appending children we can use AppendChildren
|
||||
#[allow(dead_code)]
|
||||
fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
|
||||
let path = node.template.node_paths[idx];
|
||||
let template = node.template.get();
|
||||
let path = template.node_paths[idx];
|
||||
|
||||
// use a loop to index every static node's children until the path has run out
|
||||
// only break if the last path index is a dynamic node
|
||||
let mut static_node = &node.template.roots[path[0] as usize];
|
||||
let mut static_node = &template.roots[path[0] as usize];
|
||||
|
||||
for i in 1..path.len() - 1 {
|
||||
match static_node {
|
||||
|
||||
@@ -1,19 +1,203 @@
|
||||
use std::cell::RefCell;
|
||||
|
||||
use crate::ScopeId;
|
||||
use crate::{ScopeId, ScopeState};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::RefCell,
|
||||
fmt::Debug,
|
||||
};
|
||||
|
||||
/// A boundary that will capture any errors from child components
|
||||
#[allow(dead_code)]
|
||||
pub struct ErrorBoundary {
|
||||
error: RefCell<Option<ScopeId>>,
|
||||
id: ScopeId,
|
||||
error: RefCell<Option<CapturedError>>,
|
||||
_id: ScopeId,
|
||||
}
|
||||
|
||||
/// 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 scope that threw the error
|
||||
pub scope: ScopeId,
|
||||
}
|
||||
|
||||
impl CapturedError {
|
||||
/// Downcast the error type into a concrete error type
|
||||
pub fn downcast<T: 'static>(&self) -> Option<&T> {
|
||||
if TypeId::of::<T>() == self.error.type_id() {
|
||||
let raw = self.error.as_ref() as *const _ as *const T;
|
||||
Some(unsafe { &*raw })
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ErrorBoundary {
|
||||
pub fn new(id: ScopeId) -> Self {
|
||||
Self {
|
||||
error: RefCell::new(None),
|
||||
id,
|
||||
_id: id,
|
||||
}
|
||||
}
|
||||
|
||||
/// 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 }));
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait to allow results to be thrown upwards to the nearest Error Boundary
|
||||
///
|
||||
/// The canonical way of using this trait is to throw results from hooks, aborting rendering
|
||||
/// through question mark synax. The throw method returns an option that evalutes to None
|
||||
/// if there is an error, injecting the error to the nearest error boundary.
|
||||
///
|
||||
/// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
|
||||
///
|
||||
/// The call stack is saved for this component and provided to the error boundary
|
||||
///
|
||||
/// ```rust, ignore
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// fn app(cx: Scope, count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw(cx)?;
|
||||
///
|
||||
/// cx.render(rsx! {
|
||||
/// div { "Count {}" }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
pub trait Throw<S = ()>: Sized {
|
||||
/// The value that will be returned in if the given value is `Ok`.
|
||||
type Out;
|
||||
|
||||
/// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
|
||||
///
|
||||
/// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
|
||||
///
|
||||
/// The call stack is saved for this component and provided to the error boundary
|
||||
///
|
||||
///
|
||||
/// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
|
||||
/// which is what this trait shells out to.
|
||||
///
|
||||
///
|
||||
/// ```rust, ignore
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// fn app(cx: Scope, count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw(cx)?;
|
||||
///
|
||||
/// cx.render(rsx! {
|
||||
/// div { "Count {}" }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
fn throw(self, cx: &ScopeState) -> Option<Self::Out>;
|
||||
|
||||
/// Returns an option that evalutes to None if there is an error, injecting the error to the nearest error boundary.
|
||||
///
|
||||
/// If the value is `Ok`, then throw returns the value, not aborting the rendering preocess.
|
||||
///
|
||||
/// The call stack is saved for this component and provided to the error boundary
|
||||
///
|
||||
///
|
||||
/// Note that you can also manually throw errors using the throw method on `ScopeState` directly,
|
||||
/// which is what this trait shells out to.
|
||||
///
|
||||
///
|
||||
/// ```rust, ignore
|
||||
///
|
||||
/// #[inline_props]
|
||||
/// fn app(cx: Scope, count: String) -> Element {
|
||||
/// let id: i32 = count.parse().throw(cx)?;
|
||||
///
|
||||
/// cx.render(rsx! {
|
||||
/// div { "Count {}" }
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
fn throw_with<D: Debug + 'static>(
|
||||
self,
|
||||
cx: &ScopeState,
|
||||
e: impl FnOnce() -> D,
|
||||
) -> Option<Self::Out>;
|
||||
}
|
||||
|
||||
/// 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> {
|
||||
match self {
|
||||
Ok(t) => Some(t),
|
||||
Err(e) => {
|
||||
cx.throw(e.to_owned());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn throw_with<D: Debug + 'static>(
|
||||
self,
|
||||
cx: &ScopeState,
|
||||
err: impl FnOnce() -> D,
|
||||
) -> Option<Self::Out> {
|
||||
match self {
|
||||
Ok(t) => Some(t),
|
||||
Err(_e) => {
|
||||
cx.throw(err());
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Or just throw errors we know about
|
||||
impl<T, E: Debug + 'static> Throw for Result<T, E> {
|
||||
type Out = T;
|
||||
|
||||
fn throw(self, cx: &ScopeState) -> Option<T> {
|
||||
match self {
|
||||
Ok(t) => Some(t),
|
||||
Err(e) => {
|
||||
cx.throw(e);
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn throw_with<D: Debug + 'static>(
|
||||
self,
|
||||
cx: &ScopeState,
|
||||
error: impl FnOnce() -> D,
|
||||
) -> Option<Self::Out> {
|
||||
self.ok().or_else(|| {
|
||||
cx.throw(error());
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/// Or just throw errors we know about
|
||||
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_with<D: Debug + 'static>(
|
||||
self,
|
||||
cx: &ScopeState,
|
||||
error: impl FnOnce() -> D,
|
||||
) -> Option<Self::Out> {
|
||||
self.or_else(|| {
|
||||
cx.throw(error());
|
||||
None
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -31,8 +31,8 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
Some(VNode {
|
||||
key: children.key,
|
||||
parent: children.parent,
|
||||
template: children.template,
|
||||
root_ids: children.root_ids,
|
||||
template: children.template.clone(),
|
||||
root_ids: children.root_ids.clone(),
|
||||
dynamic_nodes: children.dynamic_nodes,
|
||||
dynamic_attrs: children.dynamic_attrs,
|
||||
})
|
||||
|
||||
@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, AnyValue, AnyValueContainer, Attribute, AttributeValue, Component, DynamicNode,
|
||||
Element, ElementId, Event, Fragment, IntoAttributeValue, IntoDynNode, LazyNodes, Mutation,
|
||||
Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
|
||||
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
|
||||
fc_to_builder, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element,
|
||||
ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties,
|
||||
RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template,
|
||||
TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
|
||||
};
|
||||
|
||||
/// The purpose of this module is to alleviate imports of many common types
|
||||
@@ -81,9 +81,9 @@ pub use crate::innerlude::{
|
||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||
pub mod prelude {
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, Component, Element, Event, EventHandler, Fragment, IntoAttributeValue,
|
||||
LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template,
|
||||
TemplateAttribute, TemplateNode, VNode, VirtualDom,
|
||||
fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
|
||||
Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
|
||||
Throw, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
+188
-15
@@ -5,7 +5,7 @@ use bumpalo::boxed::Box as BumpBox;
|
||||
use bumpalo::Bump;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
cell::{Cell, RefCell, UnsafeCell},
|
||||
fmt::Arguments,
|
||||
future::Future,
|
||||
ops::Deref,
|
||||
@@ -22,10 +22,22 @@ pub type TemplateId = &'static str;
|
||||
/// you might need to handle the case where there's no node immediately ready.
|
||||
pub enum RenderReturn<'a> {
|
||||
/// A currently-available element
|
||||
Sync(Element<'a>),
|
||||
Ready(VNode<'a>),
|
||||
|
||||
/// The component aborted rendering early. It might've thrown an error.
|
||||
///
|
||||
/// In its place we've produced a placeholder to locate its spot in the dom when
|
||||
/// it recovers.
|
||||
Aborted(VPlaceholder),
|
||||
|
||||
/// An ongoing future that will resolve to a [`Element`]
|
||||
Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
||||
Pending(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
||||
}
|
||||
|
||||
impl<'a> Default for RenderReturn<'a> {
|
||||
fn default() -> Self {
|
||||
RenderReturn::Aborted(VPlaceholder::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// A reference to a template along with any context needed to hydrate it
|
||||
@@ -43,11 +55,11 @@ pub struct VNode<'a> {
|
||||
pub parent: Option<ElementId>,
|
||||
|
||||
/// The static nodes and static descriptor of the template
|
||||
pub template: Template<'static>,
|
||||
pub template: Cell<Template<'static>>,
|
||||
|
||||
/// The IDs for the roots of this template - to be used when moving the template around and removing it from
|
||||
/// the actual Dom
|
||||
pub root_ids: &'a [Cell<Option<ElementId>>],
|
||||
pub root_ids: BoxedCellSlice,
|
||||
|
||||
/// The dynamic parts of the template
|
||||
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
||||
@@ -56,21 +68,120 @@ pub struct VNode<'a> {
|
||||
pub dynamic_attrs: &'a [Attribute<'a>],
|
||||
}
|
||||
|
||||
// Saftey: There is no way to get references to the internal data of this struct so no refrences will be invalidated by mutating the data with a immutable reference (The same principle behind Cell)
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BoxedCellSlice(UnsafeCell<Option<Box<[ElementId]>>>);
|
||||
|
||||
impl Clone for BoxedCellSlice {
|
||||
fn clone(&self) -> Self {
|
||||
Self(UnsafeCell::new(unsafe { (*self.0.get()).clone() }))
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxedCellSlice {
|
||||
pub fn last(&self) -> Option<ElementId> {
|
||||
unsafe {
|
||||
(*self.0.get())
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.as_ref().last().copied())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, idx: usize) -> Option<ElementId> {
|
||||
unsafe {
|
||||
(*self.0.get())
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.as_ref().get(idx).copied())
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_unchecked(&self, idx: usize) -> Option<ElementId> {
|
||||
(*self.0.get())
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.as_ref().get(idx).copied())
|
||||
}
|
||||
|
||||
pub fn set(&self, idx: usize, new: ElementId) {
|
||||
unsafe {
|
||||
if let Some(inner) = &mut *self.0.get() {
|
||||
inner[idx] = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intialize(&self, contents: Box<[ElementId]>) {
|
||||
unsafe {
|
||||
*self.0.get() = Some(contents);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transfer(&self, other: &Self) {
|
||||
unsafe {
|
||||
*self.0.get() = (*other.0.get()).clone();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_from(&self, other: &Self) {
|
||||
unsafe {
|
||||
*self.0.get() = (*other.0.get()).take();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe {
|
||||
(*self.0.get())
|
||||
.as_ref()
|
||||
.map(|inner| inner.len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a BoxedCellSlice {
|
||||
type Item = ElementId;
|
||||
|
||||
type IntoIter = BoxedCellSliceIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
BoxedCellSliceIter {
|
||||
index: 0,
|
||||
borrow: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoxedCellSliceIter<'a> {
|
||||
index: usize,
|
||||
borrow: &'a BoxedCellSlice,
|
||||
}
|
||||
|
||||
impl Iterator for BoxedCellSliceIter<'_> {
|
||||
type Item = ElementId;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let result = self.borrow.get(self.index);
|
||||
if result.is_some() {
|
||||
self.index += 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VNode<'a> {
|
||||
/// Create a template with no nodes that will be skipped over during diffing
|
||||
pub fn empty() -> Element<'a> {
|
||||
Some(VNode {
|
||||
key: None,
|
||||
parent: None,
|
||||
root_ids: &[],
|
||||
root_ids: BoxedCellSlice::default(),
|
||||
dynamic_nodes: &[],
|
||||
dynamic_attrs: &[],
|
||||
template: Template {
|
||||
template: Cell::new(Template {
|
||||
name: "dioxus-empty",
|
||||
roots: &[],
|
||||
node_paths: &[],
|
||||
attr_paths: &[],
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -78,7 +189,7 @@ impl<'a> VNode<'a> {
|
||||
///
|
||||
/// Returns [`None`] if the root is actually a static node (Element/Text)
|
||||
pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
|
||||
match &self.template.roots[idx] {
|
||||
match &self.template.get().roots[idx] {
|
||||
TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
|
||||
TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
|
||||
Some(&self.dynamic_nodes[*id])
|
||||
@@ -103,32 +214,85 @@ impl<'a> VNode<'a> {
|
||||
///
|
||||
/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
|
||||
/// ways, with the suggested approach being the unique code location (file, line, col, etc).
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
pub struct Template<'a> {
|
||||
/// The name of the template. This must be unique across your entire program for template diffing to work properly
|
||||
///
|
||||
/// If two templates have the same name, it's likely that Dioxus will panic when diffing.
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(deserialize_with = "deserialize_string_leaky")
|
||||
)]
|
||||
pub name: &'a str,
|
||||
|
||||
/// The list of template nodes that make up the template
|
||||
///
|
||||
/// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
|
||||
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
|
||||
pub roots: &'a [TemplateNode<'a>],
|
||||
|
||||
/// The paths of each node relative to the root of the template.
|
||||
///
|
||||
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
|
||||
/// topmost element, not the `roots` field.
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(deserialize_with = "deserialize_bytes_leaky")
|
||||
)]
|
||||
pub node_paths: &'a [&'a [u8]],
|
||||
|
||||
/// The paths of each dynamic attribute relative to the root of the template
|
||||
///
|
||||
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
|
||||
/// topmost element, not the `roots` field.
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(deserialize_with = "deserialize_bytes_leaky")
|
||||
)]
|
||||
pub attr_paths: &'a [&'a [u8]],
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
|
||||
let deserialized = String::deserialize(deserializer)?;
|
||||
Ok(&*Box::leak(deserialized.into_boxed_str()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_bytes_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a [&'a [u8]], D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
|
||||
let deserialized = Vec::<Vec<u8>>::deserialize(deserializer)?;
|
||||
let deserialized = deserialized
|
||||
.into_iter()
|
||||
.map(|v| &*Box::leak(v.into_boxed_slice()))
|
||||
.collect::<Vec<_>>();
|
||||
Ok(&*Box::leak(deserialized.into_boxed_slice()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_leaky<'a, 'de, T: serde::Deserialize<'de>, D>(
|
||||
deserializer: D,
|
||||
) -> Result<&'a [T], D::Error>
|
||||
where
|
||||
T: serde::Deserialize<'de>,
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
|
||||
let deserialized = Box::<[T]>::deserialize(deserializer)?;
|
||||
Ok(&*Box::leak(deserialized))
|
||||
}
|
||||
|
||||
impl<'a> Template<'a> {
|
||||
/// Is this template worth caching at all, since it's completely runtime?
|
||||
///
|
||||
@@ -145,7 +309,11 @@ impl<'a> Template<'a> {
|
||||
///
|
||||
/// This can be created at compile time, saving the VirtualDom time when diffing the tree
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(tag = "type")
|
||||
)]
|
||||
pub enum TemplateNode<'a> {
|
||||
/// An statically known element in the dom.
|
||||
///
|
||||
@@ -165,9 +333,11 @@ pub enum TemplateNode<'a> {
|
||||
/// A list of possibly dynamic attribues for this element
|
||||
///
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
|
||||
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
|
||||
attrs: &'a [TemplateAttribute<'a>],
|
||||
|
||||
/// A list of template nodes that define another set of template nodes
|
||||
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
|
||||
children: &'a [TemplateNode<'a>],
|
||||
},
|
||||
|
||||
@@ -544,7 +714,10 @@ pub trait ComponentReturn<'a, A = ()> {
|
||||
|
||||
impl<'a> ComponentReturn<'a> for Element<'a> {
|
||||
fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
|
||||
RenderReturn::Sync(self)
|
||||
match self {
|
||||
Some(node) => RenderReturn::Ready(node),
|
||||
None => RenderReturn::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -556,7 +729,7 @@ where
|
||||
{
|
||||
fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a> {
|
||||
let f: &mut dyn Future<Output = Element<'a>> = cx.bump().alloc(self);
|
||||
RenderReturn::Async(unsafe { BumpBox::from_raw(f) })
|
||||
RenderReturn::Pending(unsafe { BumpBox::from_raw(f) })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -640,8 +813,8 @@ impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(_cx.bump().alloc([VNode {
|
||||
parent: self.parent,
|
||||
template: self.template,
|
||||
root_ids: self.root_ids,
|
||||
template: self.template.clone(),
|
||||
root_ids: self.root_ids.clone(),
|
||||
key: self.key,
|
||||
dynamic_nodes: self.dynamic_nodes,
|
||||
dynamic_attrs: self.dynamic_attrs,
|
||||
|
||||
@@ -8,7 +8,7 @@ mod waker;
|
||||
|
||||
pub use suspense::*;
|
||||
pub use task::*;
|
||||
pub use waker::RcWake;
|
||||
pub use waker::ArcWake;
|
||||
|
||||
/// The type of message that can be sent to the scheduler.
|
||||
///
|
||||
@@ -25,16 +25,16 @@ pub(crate) enum SchedulerMsg {
|
||||
SuspenseNotified(SuspenseId),
|
||||
}
|
||||
|
||||
use std::{cell::RefCell, rc::Rc};
|
||||
use std::{cell::RefCell, rc::Rc, sync::Arc};
|
||||
|
||||
pub(crate) struct Scheduler {
|
||||
pub sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||
|
||||
/// Tasks created with cx.spawn
|
||||
pub tasks: RefCell<Slab<Rc<LocalTask>>>,
|
||||
pub tasks: RefCell<Slab<Arc<LocalTask>>>,
|
||||
|
||||
/// Async components
|
||||
pub leaves: RefCell<Slab<Rc<SuspenseLeaf>>>,
|
||||
pub leaves: RefCell<Slab<Arc<SuspenseLeaf>>>,
|
||||
}
|
||||
|
||||
impl Scheduler {
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
use super::{waker::RcWake, SchedulerMsg};
|
||||
use super::{waker::ArcWake, SchedulerMsg};
|
||||
use crate::ElementId;
|
||||
use crate::{innerlude::Mutations, Element, ScopeId};
|
||||
use std::future::Future;
|
||||
use std::sync::Arc;
|
||||
use std::{
|
||||
cell::{Cell, RefCell},
|
||||
collections::HashSet,
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// An ID representing an ongoing suspended component
|
||||
@@ -42,8 +42,8 @@ pub(crate) struct SuspenseLeaf {
|
||||
pub(crate) task: *mut dyn Future<Output = Element<'static>>,
|
||||
}
|
||||
|
||||
impl RcWake for SuspenseLeaf {
|
||||
fn wake_by_ref(arc_self: &Rc<Self>) {
|
||||
impl ArcWake for SuspenseLeaf {
|
||||
fn wake_by_ref(arc_self: &Arc<Self>) {
|
||||
arc_self.notified.set(true);
|
||||
_ = arc_self
|
||||
.tx
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
use super::{waker::RcWake, Scheduler, SchedulerMsg};
|
||||
use super::{waker::ArcWake, Scheduler, SchedulerMsg};
|
||||
use crate::ScopeId;
|
||||
use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::{pin::Pin, rc::Rc};
|
||||
use std::pin::Pin;
|
||||
use std::sync::Arc;
|
||||
|
||||
/// A task's unique identifier.
|
||||
///
|
||||
@@ -35,7 +36,7 @@ impl Scheduler {
|
||||
let entry = tasks.vacant_entry();
|
||||
let task_id = TaskId(entry.key());
|
||||
|
||||
entry.insert(Rc::new(LocalTask {
|
||||
entry.insert(Arc::new(LocalTask {
|
||||
id: task_id,
|
||||
tx: self.sender.clone(),
|
||||
task: RefCell::new(Box::pin(task)),
|
||||
@@ -57,8 +58,8 @@ impl Scheduler {
|
||||
}
|
||||
}
|
||||
|
||||
impl RcWake for LocalTask {
|
||||
fn wake_by_ref(arc_self: &Rc<Self>) {
|
||||
impl ArcWake for LocalTask {
|
||||
fn wake_by_ref(arc_self: &Arc<Self>) {
|
||||
_ = arc_self
|
||||
.tx
|
||||
.unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
|
||||
|
||||
@@ -10,7 +10,7 @@ use crate::{
|
||||
ScopeId, TaskId, VNode, VirtualDom,
|
||||
};
|
||||
|
||||
use super::{waker::RcWake, SuspenseId};
|
||||
use super::{waker::ArcWake, SuspenseId};
|
||||
|
||||
impl VirtualDom {
|
||||
/// Handle notifications by tasks inside the scheduler
|
||||
@@ -67,18 +67,21 @@ impl VirtualDom {
|
||||
// we should attach them to that component and then render its children
|
||||
// continue rendering the tree until we hit yet another suspended component
|
||||
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
||||
// safety: we're not going to modify the suspense context but we don't want to make a clone of it
|
||||
let fiber = self.acquire_suspense_boundary(leaf.scope_id);
|
||||
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
let arena = scope.current_frame();
|
||||
|
||||
let ret = arena.bump.alloc(RenderReturn::Sync(new_nodes));
|
||||
let ret = arena.bump.alloc(match new_nodes {
|
||||
Some(new) => RenderReturn::Ready(new),
|
||||
None => RenderReturn::default(),
|
||||
});
|
||||
|
||||
arena.node.set(ret);
|
||||
|
||||
fiber.waiting_on.borrow_mut().remove(&id);
|
||||
|
||||
if let RenderReturn::Sync(Some(template)) = ret {
|
||||
if let RenderReturn::Ready(template) = ret {
|
||||
let mutations_ref = &mut fiber.mutations.borrow_mut();
|
||||
let mutations = &mut **mutations_ref;
|
||||
let template: &VNode = unsafe { std::mem::transmute(template) };
|
||||
|
||||
@@ -1,36 +1,37 @@
|
||||
use std::mem;
|
||||
use std::sync::Arc;
|
||||
use std::task::{RawWaker, RawWakerVTable, Waker};
|
||||
use std::{mem, rc::Rc};
|
||||
|
||||
pub trait RcWake: Sized {
|
||||
pub trait ArcWake: Sized {
|
||||
/// Create a waker from this self-wakening object
|
||||
fn waker(self: &Rc<Self>) -> Waker {
|
||||
unsafe fn rc_vtable<T: RcWake>() -> &'static RawWakerVTable {
|
||||
fn waker(self: &Arc<Self>) -> Waker {
|
||||
unsafe fn rc_vtable<T: ArcWake>() -> &'static RawWakerVTable {
|
||||
&RawWakerVTable::new(
|
||||
|data| {
|
||||
let arc = mem::ManuallyDrop::new(Rc::<T>::from_raw(data.cast::<T>()));
|
||||
let arc = mem::ManuallyDrop::new(Arc::<T>::from_raw(data.cast::<T>()));
|
||||
let _rc_clone: mem::ManuallyDrop<_> = arc.clone();
|
||||
RawWaker::new(data, rc_vtable::<T>())
|
||||
},
|
||||
|data| Rc::from_raw(data.cast::<T>()).wake(),
|
||||
|data| Arc::from_raw(data.cast::<T>()).wake(),
|
||||
|data| {
|
||||
let arc = mem::ManuallyDrop::new(Rc::<T>::from_raw(data.cast::<T>()));
|
||||
RcWake::wake_by_ref(&arc);
|
||||
let arc = mem::ManuallyDrop::new(Arc::<T>::from_raw(data.cast::<T>()));
|
||||
ArcWake::wake_by_ref(&arc);
|
||||
},
|
||||
|data| drop(Rc::<T>::from_raw(data.cast::<T>())),
|
||||
|data| drop(Arc::<T>::from_raw(data.cast::<T>())),
|
||||
)
|
||||
}
|
||||
|
||||
unsafe {
|
||||
Waker::from_raw(RawWaker::new(
|
||||
Rc::into_raw(self.clone()).cast(),
|
||||
Arc::into_raw(self.clone()).cast(),
|
||||
rc_vtable::<Self>(),
|
||||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn wake_by_ref(arc_self: &Rc<Self>);
|
||||
fn wake_by_ref(arc_self: &Arc<Self>);
|
||||
|
||||
fn wake(self: Rc<Self>) {
|
||||
fn wake(self: Arc<Self>) {
|
||||
Self::wake_by_ref(&self)
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
innerlude::DirtyScope,
|
||||
innerlude::{SuspenseId, SuspenseLeaf},
|
||||
nodes::RenderReturn,
|
||||
scheduler::RcWake,
|
||||
scheduler::ArcWake,
|
||||
scopes::{ScopeId, ScopeState},
|
||||
virtual_dom::VirtualDom,
|
||||
};
|
||||
@@ -13,7 +13,7 @@ use futures_util::FutureExt;
|
||||
use std::{
|
||||
mem,
|
||||
pin::Pin,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
@@ -79,17 +79,18 @@ impl VirtualDom {
|
||||
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
||||
let props: &dyn AnyProps = scope.props.as_ref().unwrap().as_ref();
|
||||
let props: &dyn AnyProps = mem::transmute(props);
|
||||
|
||||
props.render(scope).extend_lifetime()
|
||||
};
|
||||
|
||||
// immediately resolve futures that can be resolved
|
||||
if let RenderReturn::Async(task) = &mut new_nodes {
|
||||
if let RenderReturn::Pending(task) = &mut new_nodes {
|
||||
let mut leaves = self.scheduler.leaves.borrow_mut();
|
||||
|
||||
let entry = leaves.vacant_entry();
|
||||
let suspense_id = SuspenseId(entry.key());
|
||||
|
||||
let leaf = Rc::new(SuspenseLeaf {
|
||||
let leaf = Arc::new(SuspenseLeaf {
|
||||
scope_id,
|
||||
task: task.as_mut(),
|
||||
id: suspense_id,
|
||||
@@ -108,7 +109,11 @@ impl VirtualDom {
|
||||
match pinned.poll_unpin(&mut cx) {
|
||||
// If nodes are produced, then set it and we can break
|
||||
Poll::Ready(nodes) => {
|
||||
new_nodes = RenderReturn::Sync(nodes);
|
||||
new_nodes = match nodes {
|
||||
Some(nodes) => RenderReturn::Ready(nodes),
|
||||
None => RenderReturn::default(),
|
||||
};
|
||||
|
||||
break;
|
||||
}
|
||||
|
||||
@@ -150,6 +155,6 @@ impl VirtualDom {
|
||||
});
|
||||
|
||||
// rebind the lifetime now that its stored internally
|
||||
unsafe { mem::transmute(allocated) }
|
||||
unsafe { allocated.extend_lifetime_ref() }
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,7 @@ use crate::{
|
||||
arena::ElementId,
|
||||
bump_frame::BumpFrame,
|
||||
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
||||
innerlude::{ListenerCb, Scheduler, SchedulerMsg},
|
||||
innerlude::{ErrorBoundary, ListenerCb, Scheduler, SchedulerMsg},
|
||||
lazynodes::LazyNodes,
|
||||
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
|
||||
Attribute, AttributeValue, Element, Event, Properties, TaskId,
|
||||
@@ -14,7 +14,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
fmt::Arguments,
|
||||
fmt::{Arguments, Debug},
|
||||
future::Future,
|
||||
rc::Rc,
|
||||
sync::Arc,
|
||||
@@ -513,6 +513,19 @@ impl<'src> ScopeState {
|
||||
AttributeValue::Listener(ListenerCb(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
|
||||
}
|
||||
|
||||
/// Store a value between renders. The foundational hook for all other hooks.
|
||||
///
|
||||
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). The return value of this closure is stored for the lifetime of the component, and a mutable reference to it is provided on every render as the return value of `use_hook`.
|
||||
|
||||
@@ -142,7 +142,8 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
|
||||
/// }
|
||||
/// ```
|
||||
pub struct VirtualDom {
|
||||
pub(crate) templates: FxHashMap<TemplateId, Template<'static>>,
|
||||
// Maps a template path to a map of byteindexes to templates
|
||||
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
|
||||
pub(crate) scopes: Slab<Box<ScopeState>>,
|
||||
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
|
||||
pub(crate) scheduler: Rc<Scheduler>,
|
||||
@@ -354,10 +355,11 @@ impl VirtualDom {
|
||||
while let Some(el_ref) = parent_path {
|
||||
// safety: we maintain references of all vnodes in the element slab
|
||||
let template = unsafe { &*el_ref.template };
|
||||
let node_template = template.template.get();
|
||||
let target_path = el_ref.path;
|
||||
|
||||
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
||||
let this_path = template.template.attr_paths[idx];
|
||||
let this_path = node_template.attr_paths[idx];
|
||||
|
||||
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
|
||||
// we should fix this so that we look for "onclick" instead of "click"
|
||||
@@ -454,6 +456,30 @@ impl VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
/// Replace a template at runtime. This will re-render all components that use this template.
|
||||
/// This is the primitive that enables hot-reloading.
|
||||
///
|
||||
/// The caller must ensure that the template refrences the same dynamic attributes and nodes as the original template.
|
||||
///
|
||||
/// This will only replace the the parent template, not any nested templates.
|
||||
pub fn replace_template(&mut self, template: Template<'static>) {
|
||||
self.register_template_first_byte_index(template);
|
||||
// iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine
|
||||
for (_, scope) in &self.scopes {
|
||||
if let Some(RenderReturn::Ready(sync)) = scope.try_root_node() {
|
||||
if sync.template.get().name.rsplit_once(':').unwrap().0
|
||||
== template.name.rsplit_once(':').unwrap().0
|
||||
{
|
||||
let height = scope.height;
|
||||
self.dirty_scopes.insert(DirtyScope {
|
||||
height,
|
||||
id: scope.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
|
||||
///
|
||||
/// The mutations item expects the RealDom's stack to be the root of the application.
|
||||
@@ -477,7 +503,7 @@ impl VirtualDom {
|
||||
pub fn rebuild(&mut self) -> Mutations {
|
||||
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
|
||||
// Rebuilding implies we append the created elements to the root
|
||||
RenderReturn::Sync(Some(node)) => {
|
||||
RenderReturn::Ready(node) => {
|
||||
let m = self.create_scope(ScopeId(0), node);
|
||||
self.mutations.edits.push(Mutation::AppendChildren {
|
||||
id: ElementId(0),
|
||||
@@ -485,8 +511,8 @@ impl VirtualDom {
|
||||
});
|
||||
}
|
||||
// If an error occurs, we should try to render the default error component and context where the error occured
|
||||
RenderReturn::Sync(None) => panic!("Cannot catch errors during rebuild"),
|
||||
RenderReturn::Async(_) => unreachable!("Root scope cannot be an async component"),
|
||||
RenderReturn::Aborted(_placeholder) => panic!("Cannot catch errors during rebuild"),
|
||||
RenderReturn::Pending(_) => unreachable!("Root scope cannot be an async component"),
|
||||
}
|
||||
|
||||
self.finalize()
|
||||
|
||||
Reference in new issue
Block a user