feat: fix the scope rollover bug
This commit is contained in:
1 parent
e4d089fabc
commit
d4b6bb8a7b
17 files changed
+591
-360
No files matched your search
+251
-180
@@ -1,13 +1,15 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::innerlude::{VComponent, VText};
|
||||
use crate::innerlude::{VComponent, VPlaceholder, VText};
|
||||
use crate::mutations::Mutation;
|
||||
use crate::mutations::Mutation::*;
|
||||
use crate::nodes::VNode;
|
||||
use crate::nodes::{DynamicNode, TemplateNode};
|
||||
use crate::virtual_dom::VirtualDom;
|
||||
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
|
||||
use std::cell::Cell;
|
||||
use std::iter::{Enumerate, Peekable};
|
||||
use std::rc::Rc;
|
||||
use std::slice;
|
||||
use TemplateNode::*;
|
||||
|
||||
impl<'b> VirtualDom {
|
||||
/// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
|
||||
@@ -17,190 +19,232 @@ impl<'b> VirtualDom {
|
||||
self.scope_stack.push(scope);
|
||||
let out = self.create(template);
|
||||
self.scope_stack.pop();
|
||||
|
||||
out
|
||||
}
|
||||
|
||||
/// Create this template and write its mutations
|
||||
pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize {
|
||||
pub(crate) fn create(&mut self, node: &'b VNode<'b>) -> usize {
|
||||
// The best renderers will have templates prehydrated and registered
|
||||
// Just in case, let's create the template using instructions anyways
|
||||
if !self.templates.contains_key(&template.template.name) {
|
||||
self.register_template(template);
|
||||
if !self.templates.contains_key(&node.template.name) {
|
||||
self.register_template(node);
|
||||
}
|
||||
|
||||
// we know that this will generate at least one mutation per node
|
||||
self.mutations.edits.reserve(node.template.roots.len());
|
||||
|
||||
// Walk the roots, creating nodes and assigning IDs
|
||||
// todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
|
||||
let mut dynamic_attrs = template.template.attr_paths.iter().enumerate().peekable();
|
||||
let mut dynamic_nodes = template.template.node_paths.iter().enumerate().peekable();
|
||||
let mut attrs = node.template.attr_paths.iter().enumerate().peekable();
|
||||
let mut nodes = node.template.node_paths.iter().enumerate().peekable();
|
||||
|
||||
let cur_scope = self.scope_stack.last().copied().unwrap();
|
||||
node.template
|
||||
.roots
|
||||
.iter()
|
||||
.enumerate()
|
||||
.map(|(idx, root)| match root {
|
||||
DynamicText { id } | Dynamic { id } => self.write_dynamic_root(node, *id),
|
||||
Element { .. } => self.write_element_root(node, idx, &mut attrs, &mut nodes),
|
||||
Text { .. } => self.write_static_text_root(node, idx),
|
||||
})
|
||||
.sum()
|
||||
}
|
||||
|
||||
// we know that this will generate at least one mutation per node
|
||||
self.mutations.edits.reserve(template.template.roots.len());
|
||||
fn write_static_text_root(&mut self, node: &VNode, idx: usize) -> usize {
|
||||
// Simply just load the template root, no modifications needed
|
||||
self.load_template_root(node, idx);
|
||||
|
||||
let mut on_stack = 0;
|
||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||
// We might need to generate an ID for the root node
|
||||
on_stack += match root {
|
||||
TemplateNode::DynamicText { id } | TemplateNode::Dynamic { id } => {
|
||||
match &template.dynamic_nodes[*id] {
|
||||
// a dynamic text node doesn't replace a template node, instead we create it on the fly
|
||||
DynamicNode::Text(VText { id: slot, value }) => {
|
||||
let id = self.next_element(template, template.template.node_paths[*id]);
|
||||
slot.set(id);
|
||||
// Text producs just one node on the stack
|
||||
1
|
||||
}
|
||||
|
||||
// Safety: we promise not to re-alias this text later on after committing it to the mutation
|
||||
let unbounded_text = unsafe { std::mem::transmute(*value) };
|
||||
self.mutations.push(CreateTextNode {
|
||||
value: unbounded_text,
|
||||
id,
|
||||
});
|
||||
fn write_dynamic_root(&mut self, template: &'b VNode<'b>, idx: usize) -> usize {
|
||||
use DynamicNode::*;
|
||||
match &template.dynamic_nodes[idx] {
|
||||
node @ Fragment(_) => self.create_dynamic_node(template, node, idx),
|
||||
node @ Component { .. } => self.create_dynamic_node(template, node, idx),
|
||||
Placeholder(VPlaceholder { id }) => {
|
||||
let id = self.set_slot(template, id, idx);
|
||||
self.mutations.push(CreatePlaceholder { id });
|
||||
1
|
||||
}
|
||||
Text(VText { id, value }) => {
|
||||
let id = self.set_slot(template, id, idx);
|
||||
self.create_static_text(value, id);
|
||||
1
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
1
|
||||
}
|
||||
fn create_static_text(&mut self, value: &str, id: ElementId) {
|
||||
// Safety: we promise not to re-alias this text later on after committing it to the mutation
|
||||
let unbounded_text: &str = unsafe { std::mem::transmute(value) };
|
||||
self.mutations.push(CreateTextNode {
|
||||
value: unbounded_text,
|
||||
id,
|
||||
});
|
||||
}
|
||||
|
||||
DynamicNode::Placeholder(slot) => {
|
||||
let id = self.next_element(template, template.template.node_paths[*id]);
|
||||
slot.set(id);
|
||||
self.mutations.push(CreatePlaceholder { id });
|
||||
1
|
||||
}
|
||||
/// We write all the descndent data for this element
|
||||
///
|
||||
/// Elements can contain other nodes - and those nodes can be dynamic or static
|
||||
///
|
||||
/// We want to make sure we write these nodes while on top of the root
|
||||
fn write_element_root(
|
||||
&mut self,
|
||||
template: &'b VNode<'b>,
|
||||
root_idx: usize,
|
||||
dynamic_attrs: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&'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);
|
||||
|
||||
DynamicNode::Fragment(_) | DynamicNode::Component { .. } => {
|
||||
self.create_dynamic_node(template, &template.dynamic_nodes[*id], *id)
|
||||
}
|
||||
}
|
||||
// Write all the attributes below this root
|
||||
self.write_attrs_on_root(dynamic_attrs, root_idx, root_on_stack, template);
|
||||
|
||||
// Load in all of the placeholder or dynamic content under this root too
|
||||
self.load_placeholders(dynamic_nodes, root_idx, template);
|
||||
|
||||
1
|
||||
}
|
||||
|
||||
/// Load all of the placeholder nodes for descendents of this root node
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// rsx! {
|
||||
/// div {
|
||||
/// // This is a placeholder
|
||||
/// some_value,
|
||||
///
|
||||
/// // Load this too
|
||||
/// "{some_text}"
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
fn load_placeholders(
|
||||
&mut self,
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
root_idx: usize,
|
||||
template: &'b VNode<'b>,
|
||||
) {
|
||||
let (start, end) = match collect_dyn_node_range(dynamic_nodes, root_idx) {
|
||||
Some((a, b)) => (a, b),
|
||||
None => return,
|
||||
};
|
||||
|
||||
for idx in start..=end {
|
||||
let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx);
|
||||
if m > 0 {
|
||||
// The path is one shorter because the top node is the root
|
||||
let path = &template.template.node_paths[idx][1..];
|
||||
self.mutations.push(ReplacePlaceholder { m, path });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn write_attrs_on_root(
|
||||
&mut self,
|
||||
attrs: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
root_idx: usize,
|
||||
root: ElementId,
|
||||
node: &VNode,
|
||||
) {
|
||||
while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p[0] == root_idx as u8) {
|
||||
let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
|
||||
|
||||
loop {
|
||||
self.write_attribute(&node.dynamic_attrs[attr_id], id);
|
||||
|
||||
// Only push the dynamic attributes forward if they match the current path (same element)
|
||||
match attrs.next_if(|(_, p)| *p == path) {
|
||||
Some((next_attr_id, _)) => attr_id = next_attr_id,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TemplateNode::Element { .. } | TemplateNode::Text { .. } => {
|
||||
let this_id = self.next_root(template, root_idx);
|
||||
fn write_attribute(&mut self, attribute: &crate::Attribute, id: ElementId) {
|
||||
// Make sure we set the attribute's associated id
|
||||
attribute.mounted_element.set(id);
|
||||
|
||||
template.root_ids[root_idx].set(this_id);
|
||||
self.mutations.push(LoadTemplate {
|
||||
name: template.template.name,
|
||||
index: root_idx,
|
||||
id: this_id,
|
||||
});
|
||||
// Safety: we promise not to re-alias this text later on after committing it to the mutation
|
||||
let unbounded_name = unsafe { std::mem::transmute(attribute.name) };
|
||||
|
||||
// we're on top of a node that has a dynamic attribute for a descendant
|
||||
// Set that attribute now before the stack gets in a weird state
|
||||
while let Some((mut attr_id, path)) =
|
||||
dynamic_attrs.next_if(|(_, p)| p[0] == root_idx as u8)
|
||||
{
|
||||
// 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 = match path.len() {
|
||||
1 => this_id,
|
||||
_ => {
|
||||
let id = self
|
||||
.next_element(template, template.template.attr_paths[attr_id]);
|
||||
self.mutations.push(Mutation::AssignId {
|
||||
path: &path[1..],
|
||||
id,
|
||||
});
|
||||
id
|
||||
}
|
||||
};
|
||||
match &attribute.value {
|
||||
AttributeValue::Text(value) => {
|
||||
// Safety: we promise not to re-alias this text later on after committing it to the mutation
|
||||
let unbounded_value = unsafe { std::mem::transmute(*value) };
|
||||
|
||||
loop {
|
||||
let attribute = template.dynamic_attrs.get(attr_id).unwrap();
|
||||
attribute.mounted_element.set(id);
|
||||
self.mutations.push(SetAttribute {
|
||||
name: unbounded_name,
|
||||
value: unbounded_value,
|
||||
ns: attribute.namespace,
|
||||
id,
|
||||
})
|
||||
}
|
||||
AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute {
|
||||
name: unbounded_name,
|
||||
value: *value,
|
||||
id,
|
||||
}),
|
||||
AttributeValue::Listener(_) => {
|
||||
self.mutations.push(NewEventListener {
|
||||
// all listeners start with "on"
|
||||
name: &unbounded_name[2..],
|
||||
id,
|
||||
})
|
||||
}
|
||||
AttributeValue::Float(_) => todo!(),
|
||||
AttributeValue::Int(_) => todo!(),
|
||||
AttributeValue::Any(_) => todo!(),
|
||||
AttributeValue::None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
// Safety: we promise not to re-alias this text later on after committing it to the mutation
|
||||
let unbounded_name = unsafe { std::mem::transmute(attribute.name) };
|
||||
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));
|
||||
|
||||
match &attribute.value {
|
||||
AttributeValue::Text(value) => {
|
||||
// Safety: we promise not to re-alias this text later on after committing it to the mutation
|
||||
let unbounded_value = unsafe { std::mem::transmute(*value) };
|
||||
self.mutations.push(LoadTemplate {
|
||||
name: template.template.name,
|
||||
index: root_idx,
|
||||
id: this_id,
|
||||
});
|
||||
|
||||
self.mutations.push(SetAttribute {
|
||||
name: unbounded_name,
|
||||
value: unbounded_value,
|
||||
ns: attribute.namespace,
|
||||
id,
|
||||
})
|
||||
}
|
||||
AttributeValue::Bool(value) => {
|
||||
self.mutations.push(SetBoolAttribute {
|
||||
name: unbounded_name,
|
||||
value: *value,
|
||||
id,
|
||||
})
|
||||
}
|
||||
AttributeValue::Listener(_) => {
|
||||
self.mutations.push(NewEventListener {
|
||||
// all listeners start with "on"
|
||||
name: &unbounded_name[2..],
|
||||
scope: cur_scope,
|
||||
id,
|
||||
})
|
||||
}
|
||||
AttributeValue::Float(_) => todo!(),
|
||||
AttributeValue::Int(_) => todo!(),
|
||||
AttributeValue::Any(_) => todo!(),
|
||||
AttributeValue::None => todo!(),
|
||||
}
|
||||
this_id
|
||||
}
|
||||
|
||||
// Only push the dynamic attributes forward if they match the current path (same element)
|
||||
match dynamic_attrs.next_if(|(_, p)| *p == path) {
|
||||
Some((next_attr_id, _)) => attr_id = next_attr_id,
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// We're on top of a node that has a dynamic child for a descendant
|
||||
// Skip any node that's a root
|
||||
let mut start = None;
|
||||
let mut end = None;
|
||||
|
||||
// Collect all the dynamic nodes below this root
|
||||
// We assign the start and end of the range of dynamic nodes since they area ordered in terms of tree path
|
||||
//
|
||||
// [0]
|
||||
// [1, 1] <---|
|
||||
// [1, 1, 1] <---| these are the range of dynamic nodes below root 1
|
||||
// [1, 1, 2] <---|
|
||||
// [2]
|
||||
//
|
||||
// We collect each range and then create them and replace the placeholder in the template
|
||||
while let Some((idx, p)) =
|
||||
dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8)
|
||||
{
|
||||
if p.len() == 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
if start.is_none() {
|
||||
start = Some(idx);
|
||||
}
|
||||
|
||||
end = Some(idx);
|
||||
}
|
||||
|
||||
//
|
||||
if let (Some(start), Some(end)) = (start, end) {
|
||||
for idx in start..=end {
|
||||
let node = &template.dynamic_nodes[idx];
|
||||
let m = self.create_dynamic_node(template, node, idx);
|
||||
if m > 0 {
|
||||
self.mutations.push(ReplacePlaceholder {
|
||||
m,
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// elements create only one node :-)
|
||||
1
|
||||
}
|
||||
};
|
||||
/// We have some dynamic attributes attached to a some node
|
||||
///
|
||||
/// That node needs to be loaded at runtime, so we need to give it an ID
|
||||
///
|
||||
/// If the node in question is on the stack, we just return that ID
|
||||
///
|
||||
/// If the node is not on the stack, we create a new ID for it and assign it
|
||||
fn assign_static_node_as_dynamic(
|
||||
&mut self,
|
||||
path: &'static [u8],
|
||||
this_id: ElementId,
|
||||
template: &VNode,
|
||||
attr_id: usize,
|
||||
) -> ElementId {
|
||||
if path.len() == 1 {
|
||||
return this_id;
|
||||
}
|
||||
|
||||
on_stack
|
||||
// if attribute is on a root node, then we've already created the element
|
||||
// Else, it's deep in the template and we should create a new id for it
|
||||
let id = self.next_element(template, template.template.attr_paths[attr_id]);
|
||||
|
||||
self.mutations.push(Mutation::AssignId {
|
||||
path: &path[1..],
|
||||
id,
|
||||
});
|
||||
|
||||
id
|
||||
}
|
||||
|
||||
/// Insert a new template into the VirtualDom's template registry
|
||||
@@ -210,17 +254,9 @@ impl<'b> VirtualDom {
|
||||
.insert(template.template.name, template.template);
|
||||
|
||||
// If it's all dynamic nodes, then we don't need to register it
|
||||
// Quickly run through and see if it's all just dynamic nodes
|
||||
if template.template.roots.iter().all(|root| {
|
||||
matches!(
|
||||
root,
|
||||
TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
|
||||
)
|
||||
}) {
|
||||
return;
|
||||
if !template.template.is_completely_dynamic() {
|
||||
self.mutations.templates.push(template.template);
|
||||
}
|
||||
|
||||
self.mutations.templates.push(template.template);
|
||||
}
|
||||
|
||||
pub(crate) fn create_dynamic_node(
|
||||
@@ -248,7 +284,7 @@ impl<'b> VirtualDom {
|
||||
let new_id = self.next_element(template, template.template.node_paths[idx]);
|
||||
|
||||
// Make sure the text node is assigned to the correct element
|
||||
text.id.set(new_id);
|
||||
text.id.set(Some(new_id));
|
||||
|
||||
// Safety: we promise not to re-alias this text later on after committing it to the mutation
|
||||
let value = unsafe { std::mem::transmute(text.value) };
|
||||
@@ -266,7 +302,7 @@ impl<'b> VirtualDom {
|
||||
|
||||
pub(crate) fn create_placeholder(
|
||||
&mut self,
|
||||
slot: &Cell<ElementId>,
|
||||
placeholder: &VPlaceholder,
|
||||
template: &'b VNode<'b>,
|
||||
idx: usize,
|
||||
) -> usize {
|
||||
@@ -274,7 +310,7 @@ impl<'b> VirtualDom {
|
||||
let id = self.next_element(template, template.template.node_paths[idx]);
|
||||
|
||||
// Make sure the text node is assigned to the correct element
|
||||
slot.set(id);
|
||||
placeholder.id.set(Some(id));
|
||||
|
||||
// Assign the ID to the existing node in the template
|
||||
self.mutations.push(AssignId {
|
||||
@@ -296,15 +332,17 @@ impl<'b> VirtualDom {
|
||||
component: &'b VComponent<'b>,
|
||||
idx: usize,
|
||||
) -> usize {
|
||||
let props = component
|
||||
.props
|
||||
.take()
|
||||
.expect("Props to always exist when a component is being created");
|
||||
let scope = match component.props.take() {
|
||||
Some(props) => {
|
||||
let unbounded_props = unsafe { std::mem::transmute(props) };
|
||||
let scope = self.new_scope(unbounded_props, component.name);
|
||||
scope.id
|
||||
}
|
||||
|
||||
let unbounded_props = unsafe { std::mem::transmute(props) };
|
||||
// Component is coming back, it probably still exists, right?
|
||||
None => component.scope.get().unwrap(),
|
||||
};
|
||||
|
||||
let scope = self.new_scope(unbounded_props, component.name);
|
||||
let scope = scope.id;
|
||||
component.scope.set(Some(scope));
|
||||
|
||||
let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
|
||||
@@ -394,4 +432,37 @@ impl<'b> VirtualDom {
|
||||
|
||||
0
|
||||
}
|
||||
|
||||
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.node_paths[id]);
|
||||
slot.set(Some(id));
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_dyn_node_range(
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&[u8]>>>,
|
||||
root_idx: usize,
|
||||
) -> Option<(usize, usize)> {
|
||||
let start = match dynamic_nodes.peek() {
|
||||
Some((idx, p)) if p[0] == root_idx as u8 => *idx,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let mut end = start;
|
||||
|
||||
while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) {
|
||||
if p.len() == 1 {
|
||||
continue;
|
||||
}
|
||||
|
||||
end = idx;
|
||||
}
|
||||
|
||||
Some((start, end))
|
||||
}
|
||||
Reference in new issue
Block a user