Merge remote-tracking branch 'upstream/master' into optimize-templates-v3
This commit is contained in:
commit
b79ad4f50e
68 files changed
+1030
-1281
No files matched your search
+59
-34
@@ -1,7 +1,4 @@
|
||||
use crate::{
|
||||
nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
|
||||
ScopeId,
|
||||
};
|
||||
use crate::{nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, DynamicNode, ScopeId};
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
|
||||
/// An Element's unique identifier.
|
||||
@@ -37,21 +34,20 @@ impl ElementRef {
|
||||
|
||||
impl VirtualDom {
|
||||
pub(crate) fn next_element(&mut self, template: &VNode, path: &'static [u8]) -> ElementId {
|
||||
let entry = self.elements.vacant_entry();
|
||||
let id = entry.key();
|
||||
entry.insert(ElementRef {
|
||||
template: template as *const _ as *mut _,
|
||||
path: ElementPath::Deep(path),
|
||||
});
|
||||
ElementId(id)
|
||||
self.next(template, ElementPath::Deep(path))
|
||||
}
|
||||
|
||||
pub(crate) fn next_root(&mut self, template: &VNode, path: usize) -> ElementId {
|
||||
self.next(template, ElementPath::Root(path))
|
||||
}
|
||||
|
||||
fn next(&mut self, template: &VNode, path: ElementPath) -> ElementId {
|
||||
let entry = self.elements.vacant_entry();
|
||||
let id = entry.key();
|
||||
|
||||
entry.insert(ElementRef {
|
||||
template: template as *const _ as *mut _,
|
||||
path: ElementPath::Root(path),
|
||||
path,
|
||||
});
|
||||
ElementId(id)
|
||||
}
|
||||
@@ -64,9 +60,9 @@ impl VirtualDom {
|
||||
pub(crate) fn try_reclaim(&mut self, el: ElementId) -> Option<ElementRef> {
|
||||
if el.0 == 0 {
|
||||
panic!(
|
||||
"Invalid element set to 0 - {:#?}",
|
||||
"Cannot reclaim the root element - {:#?}",
|
||||
std::backtrace::Backtrace::force_capture()
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
self.elements.try_remove(el.0)
|
||||
@@ -79,17 +75,15 @@ impl VirtualDom {
|
||||
|
||||
// Drop a scope and all its children
|
||||
pub(crate) fn drop_scope(&mut self, id: ScopeId) {
|
||||
let scope = self.scopes.get(id.0).unwrap();
|
||||
|
||||
if let Some(root) = scope.as_ref().try_root_node() {
|
||||
let root = unsafe { root.extend_lifetime_ref() };
|
||||
if let RenderReturn::Sync(Ok(node)) = root {
|
||||
if let Some(root) = self.scopes[id.0].as_ref().try_root_node() {
|
||||
if let RenderReturn::Sync(Ok(node)) = unsafe { root.extend_lifetime_ref() } {
|
||||
self.drop_scope_inner(node)
|
||||
}
|
||||
}
|
||||
|
||||
let scope = self.scopes.get_mut(id.0).unwrap();
|
||||
scope.props.take();
|
||||
self.scopes[id.0].props.take();
|
||||
|
||||
let scope = &mut self.scopes[id.0];
|
||||
|
||||
// Drop all the hooks once the children are dropped
|
||||
// this means we'll drop hooks bottom-up
|
||||
@@ -99,24 +93,55 @@ impl VirtualDom {
|
||||
}
|
||||
|
||||
fn drop_scope_inner(&mut self, node: &VNode) {
|
||||
for attr in node.dynamic_attrs {
|
||||
if let AttributeValue::Listener(l) = &attr.value {
|
||||
l.borrow_mut().take();
|
||||
node.clear_listeners();
|
||||
node.dynamic_nodes.iter().for_each(|node| match node {
|
||||
DynamicNode::Component(c) => self.drop_scope(c.scope.get().unwrap()),
|
||||
DynamicNode::Fragment(nodes) => {
|
||||
nodes.iter().for_each(|node| self.drop_scope_inner(node))
|
||||
}
|
||||
}
|
||||
DynamicNode::Placeholder(t) => {
|
||||
self.try_reclaim(t.get());
|
||||
}
|
||||
DynamicNode::Text(t) => {
|
||||
self.try_reclaim(t.id.get());
|
||||
}
|
||||
});
|
||||
|
||||
for (idx, _) in node.template.roots.iter().enumerate() {
|
||||
match node.dynamic_root(idx) {
|
||||
Some(DynamicNode::Component(c)) => self.drop_scope(c.scope.get().unwrap()),
|
||||
Some(DynamicNode::Fragment(nodes)) => {
|
||||
for node in *nodes {
|
||||
self.drop_scope_inner(node);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
for root in node.root_ids {
|
||||
let id = root.get();
|
||||
if id.0 != 0 {
|
||||
self.try_reclaim(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Descend through the tree, removing any borrowed props and listeners
|
||||
pub(crate) fn ensure_drop_safety(&self, scope: ScopeId) {
|
||||
let node = unsafe { self.scopes[scope.0].previous_frame().try_load_node() };
|
||||
|
||||
// And now we want to make sure the previous frame has dropped anything that borrows self
|
||||
if let Some(RenderReturn::Sync(Ok(node))) = node {
|
||||
self.ensure_drop_safety_inner(node);
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_drop_safety_inner(&self, node: &VNode) {
|
||||
node.clear_listeners();
|
||||
|
||||
node.dynamic_nodes.iter().for_each(|child| match child {
|
||||
// Only descend if the props are borrowed
|
||||
DynamicNode::Component(c) if !c.static_props => {
|
||||
self.ensure_drop_safety(c.scope.get().unwrap());
|
||||
c.props.set(None);
|
||||
}
|
||||
|
||||
DynamicNode::Fragment(f) => f
|
||||
.iter()
|
||||
.for_each(|node| self.ensure_drop_safety_inner(node)),
|
||||
|
||||
_ => {}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl ElementPath {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
use std::cell::Cell;
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::innerlude::{VComponent, VText};
|
||||
use crate::mutations::Mutation;
|
||||
@@ -42,7 +43,7 @@ impl<'b> VirtualDom {
|
||||
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) => {
|
||||
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 }) => {
|
||||
@@ -72,7 +73,7 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
TemplateNode::Element { .. } | TemplateNode::Text(_) => {
|
||||
TemplateNode::Element { .. } | TemplateNode::Text { .. } => {
|
||||
let this_id = self.next_root(template, root_idx);
|
||||
|
||||
template.root_ids[root_idx].set(this_id);
|
||||
@@ -213,7 +214,7 @@ impl<'b> VirtualDom {
|
||||
if template.template.roots.iter().all(|root| {
|
||||
matches!(
|
||||
root,
|
||||
TemplateNode::Dynamic(_) | TemplateNode::DynamicText(_)
|
||||
TemplateNode::Dynamic { .. } | TemplateNode::DynamicText { .. }
|
||||
)
|
||||
}) {
|
||||
return;
|
||||
@@ -302,7 +303,8 @@ impl<'b> VirtualDom {
|
||||
|
||||
let unbounded_props = unsafe { std::mem::transmute(props) };
|
||||
|
||||
let scope = self.new_scope(unbounded_props).id;
|
||||
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() };
|
||||
@@ -336,8 +338,8 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
// If running the scope has collected some leaves and *this* component is a boundary, then handle the suspense
|
||||
let boundary = match self.scopes[scope.0].has_context::<SuspenseContext>() {
|
||||
Some(boundary) => unsafe { &*(boundary as *const SuspenseContext) },
|
||||
let boundary = match self.scopes[scope.0].has_context::<Rc<SuspenseContext>>() {
|
||||
Some(boundary) => boundary,
|
||||
_ => return created,
|
||||
};
|
||||
|
||||
|
||||
+30
-32
@@ -25,40 +25,35 @@ impl<'b> VirtualDom {
|
||||
.previous_frame()
|
||||
.try_load_node()
|
||||
.expect("Call rebuild before diffing");
|
||||
|
||||
let new = scope_state
|
||||
.current_frame()
|
||||
.try_load_node()
|
||||
.expect("Call rebuild before diffing");
|
||||
self.diff_maybe_node(old, new);
|
||||
|
||||
use RenderReturn::{Async, Sync};
|
||||
|
||||
match (old, new) {
|
||||
(Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
|
||||
|
||||
// Err cases
|
||||
(Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e),
|
||||
(Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r),
|
||||
(Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ }
|
||||
|
||||
// Async
|
||||
(Sync(Ok(_l)), Async(_)) => todo!(),
|
||||
(Sync(Err(_e)), Async(_)) => todo!(),
|
||||
(Async(_), Sync(Ok(_r))) => todo!(),
|
||||
(Async(_), Sync(Err(_e))) => { /* nothing */ }
|
||||
(Async(_), Async(_)) => { /* nothing */ }
|
||||
};
|
||||
}
|
||||
self.scope_stack.pop();
|
||||
}
|
||||
|
||||
fn diff_maybe_node(&mut self, old: &'b RenderReturn<'b>, new: &'b RenderReturn<'b>) {
|
||||
use RenderReturn::{Async, Sync};
|
||||
match (old, new) {
|
||||
(Sync(Ok(l)), Sync(Ok(r))) => self.diff_node(l, r),
|
||||
|
||||
// Err cases
|
||||
(Sync(Ok(l)), Sync(Err(e))) => self.diff_ok_to_err(l, e),
|
||||
(Sync(Err(e)), Sync(Ok(r))) => self.diff_err_to_ok(e, r),
|
||||
(Sync(Err(_eo)), Sync(Err(_en))) => { /* nothing */ }
|
||||
|
||||
// Async
|
||||
(Sync(Ok(_l)), Async(_)) => todo!(),
|
||||
(Sync(Err(_e)), Async(_)) => todo!(),
|
||||
(Async(_), Sync(Ok(_r))) => todo!(),
|
||||
(Async(_), Sync(Err(_e))) => { /* nothing */ }
|
||||
(Async(_), Async(_)) => { /* nothing */ }
|
||||
}
|
||||
}
|
||||
|
||||
fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) {
|
||||
todo!("Not yet handling error rollover")
|
||||
}
|
||||
fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {
|
||||
todo!("Not yet handling error rollover")
|
||||
}
|
||||
fn diff_ok_to_err(&mut self, _l: &'b VNode<'b>, _e: &anyhow::Error) {}
|
||||
fn diff_err_to_ok(&mut self, _e: &anyhow::Error, _l: &'b VNode<'b>) {}
|
||||
|
||||
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
if !std::ptr::eq(left_template.template.name, right_template.template.name)
|
||||
@@ -144,16 +139,18 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
|
||||
fn replace_nodes_with_placeholder(&mut self, l: &'b [VNode<'b>], r: &'b Cell<ElementId>) {
|
||||
// Remove the old nodes, except for one
|
||||
self.remove_nodes(&l[1..]);
|
||||
|
||||
// Now create the new one
|
||||
let first = self.replace_inner(&l[0]);
|
||||
|
||||
// Create the placeholder first, ensuring we get a dedicated ID for the placeholder
|
||||
let placeholder = self.next_element(&l[0], &[]);
|
||||
r.set(placeholder);
|
||||
self.mutations
|
||||
.push(Mutation::CreatePlaceholder { id: placeholder });
|
||||
|
||||
// Remove the old nodes, except for onea
|
||||
let first = self.replace_inner(&l[0]);
|
||||
self.remove_nodes(&l[1..]);
|
||||
|
||||
self.mutations
|
||||
.push(Mutation::ReplaceWith { id: first, m: 1 });
|
||||
|
||||
@@ -746,7 +743,8 @@ impl<'b> VirtualDom {
|
||||
/// Remove these nodes from the dom
|
||||
/// Wont generate mutations for the inner nodes
|
||||
fn remove_nodes(&mut self, nodes: &'b [VNode<'b>]) {
|
||||
nodes.iter().for_each(|node| self.remove_node(node));
|
||||
// note that we iterate in reverse to unlink lists of nodes in their rough index order
|
||||
nodes.iter().rev().for_each(|node| self.remove_node(node));
|
||||
}
|
||||
|
||||
fn remove_node(&mut self, node: &'b VNode<'b>) {
|
||||
@@ -911,7 +909,7 @@ fn matching_components<'a>(
|
||||
.zip(right.template.roots.iter())
|
||||
.map(|(l, r)| {
|
||||
let (l, r) = match (l, r) {
|
||||
(TemplateNode::Dynamic(l), TemplateNode::Dynamic(r)) => (l, r),
|
||||
(TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
|
||||
@@ -29,8 +29,8 @@ use crate::innerlude::*;
|
||||
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
let children = cx.props.0.as_ref().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||
Ok(VNode {
|
||||
key: children.key.clone(),
|
||||
parent: children.parent.clone(),
|
||||
key: children.key,
|
||||
parent: children.parent,
|
||||
template: children.template,
|
||||
root_ids: children.root_ids,
|
||||
dynamic_nodes: children.dynamic_nodes,
|
||||
|
||||
@@ -13,6 +13,7 @@ use crate::{arena::ElementId, ScopeId, Template};
|
||||
/// Templates, however, apply to all subtrees, not just target subtree.
|
||||
///
|
||||
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
|
||||
#[derive(Debug, Default)]
|
||||
#[must_use = "not handling edits can lead to visual inconsistencies in UI"]
|
||||
pub struct Mutations<'a> {
|
||||
|
||||
@@ -78,12 +78,20 @@ 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] {
|
||||
TemplateNode::Element { .. } | TemplateNode::Text(_) => None,
|
||||
TemplateNode::Dynamic(id) | TemplateNode::DynamicText(id) => {
|
||||
TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
|
||||
TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
|
||||
Some(&self.dynamic_nodes[*id])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn clear_listeners(&self) {
|
||||
for attr in self.dynamic_attrs {
|
||||
if let AttributeValue::Listener(l) = &attr.value {
|
||||
l.borrow_mut().take();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A static layout of a UI tree that describes a set of dynamic and static nodes.
|
||||
@@ -124,7 +132,7 @@ pub struct 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))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
|
||||
pub enum TemplateNode<'a> {
|
||||
/// An statically known element in the dom.
|
||||
///
|
||||
@@ -151,15 +159,24 @@ pub enum TemplateNode<'a> {
|
||||
},
|
||||
|
||||
/// This template node is just a piece of static text
|
||||
Text(&'a str),
|
||||
Text {
|
||||
/// The actual text
|
||||
text: &'a str,
|
||||
},
|
||||
|
||||
/// This template node is unknown, and needs to be created at runtime.
|
||||
Dynamic(usize),
|
||||
Dynamic {
|
||||
/// The index of the dynamic node in the VNode's dynamic_nodes list
|
||||
id: usize,
|
||||
},
|
||||
|
||||
/// This template node is known to be some text, but needs to be created at runtime
|
||||
///
|
||||
/// This is separate from the pure Dynamic variant for various optimizations
|
||||
DynamicText(usize),
|
||||
DynamicText {
|
||||
/// The index of the dynamic node in the VNode's dynamic_nodes list
|
||||
id: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// A node created at runtime
|
||||
@@ -244,7 +261,11 @@ pub struct VText<'a> {
|
||||
|
||||
/// An attribute of the TemplateNode, created at compile time
|
||||
#[derive(Debug, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(tag = "type")
|
||||
)]
|
||||
pub enum TemplateAttribute<'a> {
|
||||
/// This attribute is entirely known at compile time, enabling
|
||||
Static {
|
||||
@@ -265,7 +286,10 @@ pub enum TemplateAttribute<'a> {
|
||||
/// The attribute in this position is actually determined dynamically at runtime
|
||||
///
|
||||
/// This is the index into the dynamic_attributes field on the container VNode
|
||||
Dynamic(usize),
|
||||
Dynamic {
|
||||
/// The index
|
||||
id: usize,
|
||||
},
|
||||
}
|
||||
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
|
||||
@@ -416,6 +440,8 @@ impl<'a> IntoDynNode<'a> for VNode<'a> {
|
||||
DynamicNode::Fragment(_cx.bump().alloc([self]))
|
||||
}
|
||||
}
|
||||
|
||||
// An element that's an error is currently lost into the ether
|
||||
impl<'a> IntoDynNode<'a> for Element<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
match self {
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
use futures_util::FutureExt;
|
||||
use std::task::{Context, Poll};
|
||||
use std::{
|
||||
rc::Rc,
|
||||
task::{Context, Poll},
|
||||
};
|
||||
|
||||
use crate::{
|
||||
innerlude::{Mutation, Mutations, SuspenseContext},
|
||||
@@ -31,12 +34,10 @@ impl VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn acquire_suspense_boundary<'a>(&self, id: ScopeId) -> &'a SuspenseContext {
|
||||
let ct = self.scopes[id.0]
|
||||
.consume_context::<SuspenseContext>()
|
||||
.unwrap();
|
||||
|
||||
unsafe { &*(ct as *const SuspenseContext) }
|
||||
pub(crate) fn acquire_suspense_boundary(&self, id: ScopeId) -> Rc<SuspenseContext> {
|
||||
self.scopes[id.0]
|
||||
.consume_context::<Rc<SuspenseContext>>()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::{
|
||||
scheduler::RcWake,
|
||||
scopes::{ScopeId, ScopeState},
|
||||
virtual_dom::VirtualDom,
|
||||
AttributeValue, DynamicNode, VNode,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use futures_util::FutureExt;
|
||||
@@ -19,7 +18,11 @@ use std::{
|
||||
};
|
||||
|
||||
impl VirtualDom {
|
||||
pub(super) fn new_scope(&mut self, props: Box<dyn AnyProps<'static>>) -> &ScopeState {
|
||||
pub(super) fn new_scope(
|
||||
&mut self,
|
||||
props: Box<dyn AnyProps<'static>>,
|
||||
name: &'static str,
|
||||
) -> &ScopeState {
|
||||
let parent = self.acquire_current_scope_raw();
|
||||
let entry = self.scopes.vacant_entry();
|
||||
let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
|
||||
@@ -30,6 +33,7 @@ impl VirtualDom {
|
||||
id,
|
||||
height,
|
||||
props: Some(props),
|
||||
name,
|
||||
placeholder: Default::default(),
|
||||
node_arena_1: BumpFrame::new(0),
|
||||
node_arena_2: BumpFrame::new(0),
|
||||
@@ -50,42 +54,6 @@ impl VirtualDom {
|
||||
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _))
|
||||
}
|
||||
|
||||
fn ensure_drop_safety(&self, scope: ScopeId) {
|
||||
let scope = &self.scopes[scope.0];
|
||||
let node = unsafe { scope.previous_frame().try_load_node() };
|
||||
|
||||
// And now we want to make sure the previous frame has dropped anything that borrows self
|
||||
if let Some(RenderReturn::Sync(Ok(node))) = node {
|
||||
self.ensure_drop_safety_inner(node);
|
||||
}
|
||||
}
|
||||
|
||||
fn ensure_drop_safety_inner(&self, node: &VNode) {
|
||||
for attr in node.dynamic_attrs {
|
||||
if let AttributeValue::Listener(l) = &attr.value {
|
||||
l.borrow_mut().take();
|
||||
}
|
||||
}
|
||||
|
||||
for child in node.dynamic_nodes {
|
||||
match child {
|
||||
DynamicNode::Component(c) => {
|
||||
// Only descend if the props are borrowed
|
||||
if !c.static_props {
|
||||
self.ensure_drop_safety(c.scope.get().unwrap());
|
||||
c.props.set(None);
|
||||
}
|
||||
}
|
||||
DynamicNode::Fragment(f) => {
|
||||
for node in *f {
|
||||
self.ensure_drop_safety_inner(node);
|
||||
}
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||
// Cycle to the next frame and then reset it
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
|
||||
+22
-22
@@ -69,6 +69,7 @@ pub struct ScopeId(pub usize);
|
||||
/// This struct exists to provide a common interface for all scopes without relying on generics.
|
||||
pub struct ScopeState {
|
||||
pub(crate) render_cnt: Cell<usize>,
|
||||
pub(crate) name: &'static str,
|
||||
|
||||
pub(crate) node_arena_1: BumpFrame,
|
||||
pub(crate) node_arena_2: BumpFrame,
|
||||
@@ -116,6 +117,11 @@ impl<'src> ScopeState {
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name of this component
|
||||
pub fn name(&self) -> &str {
|
||||
self.name
|
||||
}
|
||||
|
||||
/// Get the current render since the inception of this component
|
||||
///
|
||||
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
|
||||
@@ -251,17 +257,18 @@ impl<'src> ScopeState {
|
||||
}
|
||||
|
||||
/// Return any context of type T if it exists on this scope
|
||||
pub fn has_context<T: 'static>(&self) -> Option<&T> {
|
||||
let contextex = self.shared_contexts.borrow();
|
||||
let val = contextex.get(&TypeId::of::<T>())?;
|
||||
let as_concrete = val.downcast_ref::<T>()? as *const T;
|
||||
Some(unsafe { &*as_concrete })
|
||||
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
self.shared_contexts
|
||||
.borrow()
|
||||
.get(&TypeId::of::<T>())?
|
||||
.downcast_ref::<T>()
|
||||
.cloned()
|
||||
}
|
||||
|
||||
/// Try to retrieve a shared state with type `T` from any parent scope.
|
||||
///
|
||||
/// To release the borrow, use `cloned` if the context is clone.
|
||||
pub fn consume_context<T: 'static>(&self) -> Option<&T> {
|
||||
/// Clones the state if it exists.
|
||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
if let Some(this_ctx) = self.has_context() {
|
||||
return Some(this_ctx);
|
||||
}
|
||||
@@ -271,8 +278,7 @@ impl<'src> ScopeState {
|
||||
// safety: all parent pointers are valid thanks to the bump arena
|
||||
let parent = unsafe { &*parent_ptr };
|
||||
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
||||
let as_concrete = shared.downcast_ref::<T>()? as *const T;
|
||||
return Some(unsafe { &*as_concrete });
|
||||
return shared.downcast_ref::<T>().cloned();
|
||||
}
|
||||
search_parent = parent.parent;
|
||||
}
|
||||
@@ -284,7 +290,7 @@ impl<'src> ScopeState {
|
||||
///
|
||||
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
||||
///
|
||||
/// For a hook that provides the same functionality, use `use_provide_context` and `use_consume_context` instead.
|
||||
/// For a hook that provides the same functionality, use `use_provide_context` and `use_context` instead.
|
||||
///
|
||||
/// If a state is provided that already exists, the new value will not be inserted. Instead, this method will
|
||||
/// return the existing value. This behavior is chosen so shared values do not need to be `Clone`. This particular
|
||||
@@ -305,20 +311,14 @@ impl<'src> ScopeState {
|
||||
/// render!(div { "hello {state.0}" })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn provide_context<T: 'static>(&self, value: T) -> &T {
|
||||
let mut contexts = self.shared_contexts.borrow_mut();
|
||||
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||
let value2 = value.clone();
|
||||
|
||||
let any = match contexts.get(&TypeId::of::<T>()) {
|
||||
Some(item) => item.downcast_ref::<T>().unwrap() as *const T,
|
||||
None => {
|
||||
let boxed = Box::new(value);
|
||||
let boxed_ptr = boxed.as_ref() as *const T;
|
||||
contexts.insert(TypeId::of::<T>(), boxed);
|
||||
boxed_ptr
|
||||
}
|
||||
};
|
||||
self.shared_contexts
|
||||
.borrow_mut()
|
||||
.insert(TypeId::of::<T>(), Box::new(value));
|
||||
|
||||
unsafe { &*any }
|
||||
value2
|
||||
}
|
||||
|
||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
||||
|
||||
@@ -240,20 +240,19 @@ impl VirtualDom {
|
||||
mutations: Mutations::default(),
|
||||
};
|
||||
|
||||
let root = dom.new_scope(Box::new(VProps::new(
|
||||
root,
|
||||
|_, _| unreachable!(),
|
||||
root_props,
|
||||
)));
|
||||
let root = dom.new_scope(
|
||||
Box::new(VProps::new(root, |_, _| unreachable!(), root_props)),
|
||||
"app",
|
||||
);
|
||||
|
||||
// The root component is always a suspense boundary for any async children
|
||||
// This could be unexpected, so we might rethink this behavior later
|
||||
//
|
||||
// We *could* just panic if the suspense boundary is not found
|
||||
root.provide_context(SuspenseContext::new(ScopeId(0)));
|
||||
root.provide_context(Rc::new(SuspenseContext::new(ScopeId(0))));
|
||||
|
||||
// Unlike react, we provide a default error boundary that just renders the error as a string
|
||||
root.provide_context(ErrorBoundary::new(ScopeId(0)));
|
||||
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
|
||||
|
||||
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||
dom.elements.insert(ElementRef::null());
|
||||
@@ -297,7 +296,7 @@ impl VirtualDom {
|
||||
/// currently suspended.
|
||||
pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
|
||||
!self.scopes[id.0]
|
||||
.consume_context::<SuspenseContext>()
|
||||
.consume_context::<Rc<SuspenseContext>>()
|
||||
.unwrap()
|
||||
.waiting_on
|
||||
.borrow()
|
||||
@@ -528,7 +527,7 @@ impl VirtualDom {
|
||||
// first, unload any complete suspense trees
|
||||
for finished_fiber in self.finished_fibers.drain(..) {
|
||||
let scope = &mut self.scopes[finished_fiber.0];
|
||||
let context = scope.has_context::<SuspenseContext>().unwrap();
|
||||
let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
|
||||
|
||||
self.mutations
|
||||
.templates
|
||||
@@ -566,7 +565,7 @@ impl VirtualDom {
|
||||
// No placeholder necessary since this is a diff
|
||||
if !self.collected_leaves.is_empty() {
|
||||
let mut boundary = self.scopes[dirty.id.0]
|
||||
.consume_context::<SuspenseContext>()
|
||||
.consume_context::<Rc<SuspenseContext>>()
|
||||
.unwrap();
|
||||
|
||||
let boundary_mut = boundary.borrow_mut();
|
||||
|
||||
Reference in new issue
Block a user