feat: context api overhaul
This commit is contained in:
1 parent
11a45c8ef0
commit
a61daf220d
44 files changed
+812
-621
No files matched your search
@@ -1,8 +1,8 @@
|
||||
use std::marker::PhantomData;
|
||||
|
||||
use crate::{
|
||||
factory::{ComponentReturn, RenderReturn},
|
||||
innerlude::Scoped,
|
||||
nodes::{ComponentReturn, RenderReturn},
|
||||
scopes::{Scope, ScopeState},
|
||||
Element,
|
||||
};
|
||||
|
||||
@@ -1,9 +1,13 @@
|
||||
use crate::{
|
||||
factory::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
|
||||
nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
|
||||
ScopeId,
|
||||
};
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
|
||||
/// An Element's unique identifier.
|
||||
///
|
||||
/// `ElementId` is a `usize` that is unique across the entire VirtualDOM - but not unique across time. If a component is
|
||||
/// unmounted, then the `ElementId` will be reused for a new component.
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||
pub struct ElementId(pub usize);
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::factory::RenderReturn;
|
||||
use crate::nodes::RenderReturn;
|
||||
use bumpalo::Bump;
|
||||
use std::cell::Cell;
|
||||
|
||||
@@ -8,7 +8,7 @@ pub(crate) struct BumpFrame {
|
||||
}
|
||||
|
||||
impl BumpFrame {
|
||||
pub fn new(capacity: usize) -> Self {
|
||||
pub(crate) fn new(capacity: usize) -> Self {
|
||||
let bump = Bump::with_capacity(capacity);
|
||||
Self {
|
||||
bump,
|
||||
@@ -17,7 +17,7 @@ impl BumpFrame {
|
||||
}
|
||||
|
||||
/// Creates a new lifetime out of thin air
|
||||
pub unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> {
|
||||
pub(crate) unsafe fn try_load_node<'b>(&self) -> Option<&'b RenderReturn<'b>> {
|
||||
let node = self.node.get();
|
||||
|
||||
if node.is_null() {
|
||||
|
||||
@@ -1,13 +1,12 @@
|
||||
use std::cell::Cell;
|
||||
|
||||
use crate::factory::RenderReturn;
|
||||
use crate::innerlude::{VComponent, 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, ScopeId, SuspenseContext};
|
||||
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
|
||||
|
||||
impl<'b> VirtualDom {
|
||||
/// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
|
||||
@@ -25,7 +24,7 @@ impl<'b> VirtualDom {
|
||||
pub(crate) fn create(&mut self, template: &'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.id) {
|
||||
if !self.templates.contains_key(&template.template.name) {
|
||||
self.register_template(template);
|
||||
}
|
||||
|
||||
@@ -75,7 +74,7 @@ impl<'b> VirtualDom {
|
||||
|
||||
template.root_ids[root_idx].set(this_id);
|
||||
self.mutations.push(LoadTemplate {
|
||||
name: template.template.id,
|
||||
name: template.template.name,
|
||||
index: root_idx,
|
||||
id: this_id,
|
||||
});
|
||||
@@ -204,7 +203,7 @@ impl<'b> VirtualDom {
|
||||
fn register_template(&mut self, template: &'b VNode<'b>) {
|
||||
// First, make sure we mark the template as seen, regardless if we process it
|
||||
self.templates
|
||||
.insert(template.template.id, template.template);
|
||||
.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
|
||||
@@ -342,7 +341,7 @@ 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) => boundary,
|
||||
Some(boundary) => unsafe { &*(boundary as *const SuspenseContext) },
|
||||
_ => return created,
|
||||
};
|
||||
|
||||
|
||||
@@ -2,9 +2,9 @@ use std::cell::Cell;
|
||||
|
||||
use crate::{
|
||||
arena::ElementId,
|
||||
factory::RenderReturn,
|
||||
innerlude::{DirtyScope, VComponent, VText},
|
||||
mutations::Mutation,
|
||||
nodes::RenderReturn,
|
||||
nodes::{DynamicNode, VNode},
|
||||
scopes::ScopeId,
|
||||
virtual_dom::VirtualDom,
|
||||
@@ -63,10 +63,10 @@ impl<'b> VirtualDom {
|
||||
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
println!(
|
||||
"diffing node {:#?},\n\n{:#?}",
|
||||
left_template.template.id, right_template.template.id
|
||||
left_template.template.name, right_template.template.name
|
||||
);
|
||||
|
||||
if left_template.template.id != right_template.template.id {
|
||||
if left_template.template.name != right_template.template.name {
|
||||
return self.light_diff_templates(left_template, right_template);
|
||||
}
|
||||
|
||||
|
||||
@@ -1,197 +0,0 @@
|
||||
use crate::{innerlude::DynamicNode, AttributeValue, Element, LazyNodes, ScopeState, VNode};
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
use bumpalo::Bump;
|
||||
use std::fmt::Arguments;
|
||||
use std::future::Future;
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait ComponentReturn<'a, A = ()> {
|
||||
fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
|
||||
}
|
||||
|
||||
impl<'a> ComponentReturn<'a> for Element<'a> {
|
||||
fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
|
||||
RenderReturn::Sync(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncMarker;
|
||||
impl<'a, F> ComponentReturn<'a, AsyncMarker> for F
|
||||
where
|
||||
F: Future<Output = Element<'a>> + 'a,
|
||||
{
|
||||
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) })
|
||||
}
|
||||
}
|
||||
|
||||
pub enum RenderReturn<'a> {
|
||||
/// A currently-available element
|
||||
Sync(Element<'a>),
|
||||
|
||||
/// An ongoing future that will resolve to a [`Element`]
|
||||
Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
||||
}
|
||||
|
||||
impl<'a> RenderReturn<'a> {
|
||||
pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait IntoDynNode<'a, A = ()> {
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
|
||||
}
|
||||
|
||||
impl<'a> IntoDynNode<'a> for () {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::placeholder()
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDynNode<'a> for VNode<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(_cx.bump().alloc([self]))
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDynNode<'a> for Element<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
match self {
|
||||
Ok(val) => val.into_vnode(_cx),
|
||||
_ => DynamicNode::placeholder(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
match self {
|
||||
Some(val) => val.into_vnode(_cx),
|
||||
None => DynamicNode::placeholder(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDynNode<'a> for &Element<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
match self.as_ref() {
|
||||
Ok(val) => val.clone().into_vnode(_cx),
|
||||
_ => DynamicNode::placeholder(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDynNode<'_> for &'a str {
|
||||
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
||||
cx.text_node(format_args!("{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDynNode<'_> for String {
|
||||
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
||||
cx.text_node(format_args!("{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> IntoDynNode<'b> for Arguments<'_> {
|
||||
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
|
||||
cx.text_node(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(_cx.bump().alloc([VNode {
|
||||
parent: self.parent,
|
||||
template: self.template,
|
||||
root_ids: self.root_ids,
|
||||
key: self.key,
|
||||
dynamic_nodes: self.dynamic_nodes,
|
||||
dynamic_attrs: self.dynamic_attrs,
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoTemplate<'a> {
|
||||
fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
|
||||
}
|
||||
impl<'a> IntoTemplate<'a> for VNode<'a> {
|
||||
fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||
self.call(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we're using the E as a generic but this is never crafted anyways.
|
||||
#[doc(hidden)]
|
||||
pub struct FromNodeIterator;
|
||||
impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
|
||||
where
|
||||
T: Iterator<Item = I>,
|
||||
I: IntoTemplate<'a>,
|
||||
{
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
|
||||
|
||||
for node in self {
|
||||
nodes.push(node.into_template(cx));
|
||||
}
|
||||
|
||||
let children = nodes.into_bump_slice();
|
||||
|
||||
match children.len() {
|
||||
0 => DynamicNode::placeholder(),
|
||||
_ => DynamicNode::Fragment(children),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that can be converted into an attribute value
|
||||
pub trait IntoAttributeValue<'a> {
|
||||
/// Convert into an attribute value
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for &'a str {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Text(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for f32 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Float(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for i32 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Int(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for bool {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Bool(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut str_buf = bumpalo::collections::String::new_in(bump);
|
||||
str_buf.write_fmt(self).unwrap();
|
||||
AttributeValue::Text(str_buf.into_bump_str())
|
||||
}
|
||||
}
|
||||
@@ -26,17 +26,16 @@ use crate::innerlude::*;
|
||||
///
|
||||
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
|
||||
#[allow(non_upper_case_globals, non_snake_case)]
|
||||
pub fn Fragment<'a>(_cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||
// let children = cx.props.0.as_ref().unwrap();
|
||||
todo!()
|
||||
// Ok(VNode {
|
||||
// key: children.key.clone(),
|
||||
// parent: children.parent.clone(),
|
||||
// template: children.template,
|
||||
// root_ids: children.root_ids,
|
||||
// dynamic_nodes: children.dynamic_nodes,
|
||||
// dynamic_attrs: children.dynamic_attrs,
|
||||
// })
|
||||
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(),
|
||||
template: children.template,
|
||||
root_ids: children.root_ids,
|
||||
dynamic_nodes: children.dynamic_nodes,
|
||||
dynamic_attrs: children.dynamic_attrs,
|
||||
})
|
||||
}
|
||||
|
||||
pub struct FragmentProps<'a>(Element<'a>);
|
||||
@@ -96,7 +95,7 @@ impl<'a> Properties for FragmentProps<'a> {
|
||||
type Builder = FragmentBuilder<'a, false>;
|
||||
const IS_STATIC: bool = false;
|
||||
fn builder() -> Self::Builder {
|
||||
todo!()
|
||||
FragmentBuilder(VNode::empty())
|
||||
}
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
false
|
||||
|
||||
@@ -17,7 +17,7 @@ use std::mem;
|
||||
/// A concrete type provider for closures that build [`VNode`] structures.
|
||||
///
|
||||
/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
|
||||
/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of [`IntoVNode`].
|
||||
/// closures, but if we wrap the closure in a concrete type, we can use it for different branches in matching.
|
||||
///
|
||||
///
|
||||
/// ```rust, ignore
|
||||
|
||||
@@ -9,7 +9,6 @@ mod diff;
|
||||
mod dirty_scope;
|
||||
mod error_boundary;
|
||||
mod events;
|
||||
mod factory;
|
||||
mod fragment;
|
||||
mod lazynodes;
|
||||
mod mutations;
|
||||
@@ -25,10 +24,10 @@ pub(crate) mod innerlude {
|
||||
pub use crate::dirty_scope::*;
|
||||
pub use crate::error_boundary::*;
|
||||
pub use crate::events::*;
|
||||
pub use crate::factory::RenderReturn;
|
||||
pub use crate::fragment::*;
|
||||
pub use crate::lazynodes::*;
|
||||
pub use crate::mutations::*;
|
||||
pub use crate::nodes::RenderReturn;
|
||||
pub use crate::nodes::*;
|
||||
pub use crate::properties::*;
|
||||
pub use crate::scheduler::*;
|
||||
@@ -72,9 +71,9 @@ pub(crate) mod innerlude {
|
||||
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
|
||||
Fragment, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState,
|
||||
Scoped, SuspenseBoundary, SuspenseContext, TaskId, Template, TemplateAttribute, TemplateNode,
|
||||
VComponent, VNode, VText, VirtualDom,
|
||||
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
|
||||
|
||||
+116
-42
@@ -4,9 +4,13 @@ use crate::{arena::ElementId, ScopeId, Template};
|
||||
|
||||
/// A container for all the relevant steps to modify the Real DOM
|
||||
///
|
||||
/// This method returns "mutations" - IE the necessary changes to get the RealDOM to match the VirtualDOM. It also
|
||||
/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
|
||||
/// applied the edits.
|
||||
/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the
|
||||
/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes
|
||||
/// in the form of a [`Mutation`].
|
||||
///
|
||||
/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately.
|
||||
///
|
||||
/// Templates, however, apply to all subtrees, not just target subtree.
|
||||
///
|
||||
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
||||
#[derive(Debug, Default)]
|
||||
@@ -32,17 +36,13 @@ impl<'a> Mutations<'a> {
|
||||
///
|
||||
/// Used really only for testing
|
||||
pub fn santize(mut self) -> Self {
|
||||
todo!()
|
||||
// self.templates
|
||||
// .iter_mut()
|
||||
// .chain(self.dom_edits.iter_mut())
|
||||
// .for_each(|edit| match edit {
|
||||
// Mutation::LoadTemplate { name, .. } => *name = "template",
|
||||
// Mutation::SaveTemplate { name, .. } => *name = "template",
|
||||
// _ => {}
|
||||
// });
|
||||
for edit in self.edits.iter_mut() {
|
||||
if let Mutation::LoadTemplate { name, .. } = edit {
|
||||
*name = "template"
|
||||
}
|
||||
}
|
||||
|
||||
// self
|
||||
self
|
||||
}
|
||||
|
||||
/// Push a new mutation into the dom_edits list
|
||||
@@ -51,10 +51,10 @@ impl<'a> Mutations<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
each subtree has its own numbering scheme
|
||||
*/
|
||||
|
||||
/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
|
||||
/// of the Dioxus VirtualDom.
|
||||
///
|
||||
/// These edits can be serialized and sent over the network or through any interface
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
@@ -62,37 +62,111 @@ each subtree has its own numbering scheme
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
pub enum Mutation<'a> {
|
||||
AssignId {
|
||||
path: &'static [u8],
|
||||
/// Add these m children to the target element
|
||||
AppendChildren {
|
||||
/// The ID of the element being mounted to
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
CreatePlaceholder {
|
||||
id: ElementId,
|
||||
},
|
||||
CreateTextNode {
|
||||
value: &'a str,
|
||||
id: ElementId,
|
||||
},
|
||||
HydrateText {
|
||||
path: &'static [u8],
|
||||
value: &'a str,
|
||||
id: ElementId,
|
||||
},
|
||||
LoadTemplate {
|
||||
name: &'static str,
|
||||
index: usize,
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
// Take the current element and replace it with the element with the given id.
|
||||
ReplaceWith {
|
||||
id: ElementId,
|
||||
/// The number of nodes on the stack
|
||||
m: usize,
|
||||
},
|
||||
|
||||
ReplacePlaceholder {
|
||||
/// Assign the element at the given path the target ElementId.
|
||||
///
|
||||
/// The path is in the form of a list of indices based on children. Templates cannot have more than 255 children per
|
||||
/// element, hence the use of a single byte.
|
||||
///
|
||||
///
|
||||
AssignId {
|
||||
/// The path of the child of the topmost node on the stack
|
||||
///
|
||||
/// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
|
||||
/// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
|
||||
path: &'static [u8],
|
||||
|
||||
/// The ID we're assigning to this element/placeholder.
|
||||
///
|
||||
/// This will be used later to modify the element or replace it with another element.
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
/// Create an placeholder int he DOM that we will use later.
|
||||
///
|
||||
/// Dioxus currently requires the use of placeholders to maintain a re-entrance point for things like list diffing
|
||||
CreatePlaceholder {
|
||||
/// The ID we're assigning to this element/placeholder.
|
||||
///
|
||||
/// This will be used later to modify the element or replace it with another element.
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
/// Create a node specifically for text with the given value
|
||||
CreateTextNode {
|
||||
/// The text content of this text node
|
||||
value: &'a str,
|
||||
|
||||
/// The ID we're assigning to this specific text nodes
|
||||
///
|
||||
/// This will be used later to modify the element or replace it with another element.
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
/// Hydrate an existing text node at the given path with the given text.
|
||||
///
|
||||
/// Assign this text node the given ID since we will likely need to modify this text at a later point
|
||||
HydrateText {
|
||||
/// The path of the child of the topmost node on the stack
|
||||
///
|
||||
/// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
|
||||
/// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
|
||||
path: &'static [u8],
|
||||
|
||||
/// The value of the textnode that we want to set the placeholder with
|
||||
value: &'a str,
|
||||
|
||||
/// The ID we're assigning to this specific text nodes
|
||||
///
|
||||
/// This will be used later to modify the element or replace it with another element.
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
/// Load and clone an existing node from a template saved under that specific name
|
||||
///
|
||||
/// Dioxus guarantees that the renderer will have already been provided the template.
|
||||
/// When the template is picked up in the template list, it should be saved under its "name" - here, the name
|
||||
LoadTemplate {
|
||||
/// The "name" of the template. When paired with `rsx!`, this is autogenerated
|
||||
name: &'static str,
|
||||
|
||||
/// Which root are we loading from the template?
|
||||
///
|
||||
/// The template is stored as a list of nodes. This index represents the position of that root
|
||||
index: usize,
|
||||
|
||||
/// The ID we're assigning to this element being loaded from the template
|
||||
///
|
||||
/// This will be used later to move the element around in lists
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
/// Replace the target element (given by its ID) with the topmost m nodes on the stack
|
||||
ReplaceWith {
|
||||
/// The ID of the node we're going to replace with
|
||||
id: ElementId,
|
||||
|
||||
/// The number of nodes on the stack to use to replace
|
||||
m: usize,
|
||||
},
|
||||
|
||||
/// Replace an existing element in the template at the given path with the m nodes on the stack
|
||||
ReplacePlaceholder {
|
||||
/// The path of the child of the topmost node on the stack
|
||||
///
|
||||
/// A path of `[]` represents the topmost node. A path of `[0]` represents the first child.
|
||||
/// `[0,1,2]` represents 1st child's 2nd child's 3rd child.
|
||||
path: &'static [u8],
|
||||
|
||||
/// The number of nodes on the stack to use to replace
|
||||
m: usize,
|
||||
},
|
||||
|
||||
|
||||
+356
-55
@@ -1,12 +1,32 @@
|
||||
use crate::{any_props::AnyProps, arena::ElementId, Element, Event, ScopeId, ScopeState};
|
||||
use crate::{
|
||||
any_props::AnyProps, arena::ElementId, Element, Event, LazyNodes, ScopeId, ScopeState,
|
||||
};
|
||||
use bumpalo::boxed::Box as BumpBox;
|
||||
use bumpalo::Bump;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
fmt::Arguments,
|
||||
future::Future,
|
||||
};
|
||||
|
||||
pub type TemplateId = &'static str;
|
||||
|
||||
/// The actual state of the component's most recent computation
|
||||
///
|
||||
/// Because Dioxus accepts components in the form of `async fn(Scope) -> Result<VNode>`, we need to support both
|
||||
/// sync and async versions.
|
||||
///
|
||||
/// Dioxus will do its best to immediately resolve any async components into a regular Element, but as an implementor
|
||||
/// you might need to handle the case where there's no node immediately ready.
|
||||
pub enum RenderReturn<'a> {
|
||||
/// A currently-available element
|
||||
Sync(Element<'a>),
|
||||
|
||||
/// An ongoing future that will resolve to a [`Element`]
|
||||
Async(BumpBox<'a, dyn Future<Output = Element<'a>> + 'a>),
|
||||
}
|
||||
|
||||
/// A reference to a template along with any context needed to hydrate it
|
||||
///
|
||||
/// The dynamic parts of the template are stored separately from the static parts. This allows faster diffing by skipping
|
||||
@@ -36,6 +56,7 @@ pub struct VNode<'a> {
|
||||
}
|
||||
|
||||
impl<'a> VNode<'a> {
|
||||
/// Create a template with no nodes that will be skipped over during diffing
|
||||
pub fn empty() -> Element<'a> {
|
||||
Ok(VNode {
|
||||
key: None,
|
||||
@@ -44,7 +65,7 @@ impl<'a> VNode<'a> {
|
||||
dynamic_nodes: &[],
|
||||
dynamic_attrs: &[],
|
||||
template: Template {
|
||||
id: "dioxus-empty",
|
||||
name: "dioxus-empty",
|
||||
roots: &[],
|
||||
node_paths: &[],
|
||||
attr_paths: &[],
|
||||
@@ -52,6 +73,9 @@ impl<'a> VNode<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
/// Load a dynamic root at the given index
|
||||
///
|
||||
/// 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,
|
||||
@@ -62,52 +86,139 @@ impl<'a> VNode<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// A static layout of a UI tree that describes a set of dynamic and static nodes.
|
||||
///
|
||||
/// This is the core innovation in Dioxus. Most UIs are made of static nodes, yet participate in diffing like any
|
||||
/// dynamic node. This struct can be created at compile time. It promises that its name is unique, allow Dioxus to use
|
||||
/// its static description of the UI to skip immediately to the dynamic nodes during diffing.
|
||||
///
|
||||
/// 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))]
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
pub struct Template<'a> {
|
||||
pub id: &'a str,
|
||||
/// 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.
|
||||
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.
|
||||
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.
|
||||
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.
|
||||
pub attr_paths: &'a [&'a [u8]],
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy)]
|
||||
/// A statically known node in a layout.
|
||||
///
|
||||
/// 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))]
|
||||
pub enum TemplateNode<'a> {
|
||||
/// An statically known element in the dom.
|
||||
///
|
||||
/// In HTML this would be something like `<div id="123"> </div>`
|
||||
Element {
|
||||
/// The name of the element
|
||||
///
|
||||
/// IE for a div, it would be the string "div"
|
||||
tag: &'a str,
|
||||
|
||||
/// The namespace of the element
|
||||
///
|
||||
/// In HTML, this would be a valid URI that defines a namespace for all elements below it
|
||||
/// SVG is an example of this namespace
|
||||
namespace: Option<&'a str>,
|
||||
|
||||
/// 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"`.
|
||||
attrs: &'a [TemplateAttribute<'a>],
|
||||
|
||||
/// A list of template nodes that define another set of template nodes
|
||||
children: &'a [TemplateNode<'a>],
|
||||
inner_opt: bool,
|
||||
},
|
||||
|
||||
/// This template node is just a piece of static text
|
||||
Text(&'a str),
|
||||
|
||||
/// This template node is unknown, and needs to be created at runtime.
|
||||
Dynamic(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),
|
||||
}
|
||||
|
||||
/// A node created at runtime
|
||||
///
|
||||
/// This node's index in the DynamicNode list on VNode should match its repsective `Dynamic` index
|
||||
#[derive(Debug)]
|
||||
pub enum DynamicNode<'a> {
|
||||
/// A component node
|
||||
///
|
||||
/// Most of the time, Dioxus will actually know which component this is as compile time, but the props and
|
||||
/// assigned scope are dynamic.
|
||||
///
|
||||
/// The actual VComponent can be dynamic between two VNodes, though, allowing implementations to swap
|
||||
/// the render function at runtime
|
||||
Component(VComponent<'a>),
|
||||
|
||||
/// A text node
|
||||
Text(VText<'a>),
|
||||
|
||||
/// A placeholder
|
||||
///
|
||||
/// Used by suspense when a node isn't ready and by fragments that don't render anything
|
||||
///
|
||||
/// In code, this is just an ElementId whose initial value is set to 0 upon creation
|
||||
Placeholder(Cell<ElementId>),
|
||||
|
||||
/// A list of VNodes.
|
||||
///
|
||||
/// Note that this is not a list of dynamic nodes. These must be VNodes and created through conditional rendering
|
||||
/// or iterators.
|
||||
Fragment(&'a [VNode<'a>]),
|
||||
}
|
||||
|
||||
impl<'a> DynamicNode<'a> {
|
||||
pub fn is_component(&self) -> bool {
|
||||
matches!(self, DynamicNode::Component(_))
|
||||
}
|
||||
pub fn placeholder() -> Self {
|
||||
impl Default for DynamicNode<'_> {
|
||||
fn default() -> Self {
|
||||
Self::Placeholder(Default::default())
|
||||
}
|
||||
}
|
||||
|
||||
/// An instance of a child component
|
||||
pub struct VComponent<'a> {
|
||||
/// The name of this component
|
||||
pub name: &'static str,
|
||||
|
||||
/// Are the props valid for the 'static lifetime?
|
||||
///
|
||||
/// Internally, this is used as a guarantee. Externally, this might be incorrect, so don't count on it.
|
||||
///
|
||||
/// This flag is assumed by the [`crate::Properties`] trait which is unsafe to implement
|
||||
pub static_props: bool,
|
||||
|
||||
/// The assigned Scope for this component
|
||||
pub scope: Cell<Option<ScopeId>>,
|
||||
|
||||
/// The function pointer of the component, known at compile time
|
||||
///
|
||||
/// It is possible that components get folded at comppile time, so these shouldn't be really used as a key
|
||||
pub render_fn: *const (),
|
||||
|
||||
pub(crate) props: Cell<Option<Box<dyn AnyProps<'a> + 'a>>>,
|
||||
}
|
||||
|
||||
@@ -121,65 +232,92 @@ impl<'a> std::fmt::Debug for VComponent<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
/// An instance of some text, mounted to the DOM
|
||||
#[derive(Debug)]
|
||||
pub struct VText<'a> {
|
||||
pub id: Cell<ElementId>,
|
||||
/// The actual text itself
|
||||
pub value: &'a str,
|
||||
|
||||
/// The ID of this node in the real DOM
|
||||
pub id: Cell<ElementId>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
/// 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))]
|
||||
pub enum TemplateAttribute<'a> {
|
||||
/// This attribute is entirely known at compile time, enabling
|
||||
Static {
|
||||
/// The name of this attribute.
|
||||
///
|
||||
/// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
|
||||
name: &'a str,
|
||||
|
||||
/// The value of this attribute, known at compile time
|
||||
///
|
||||
/// Currently this only accepts &str, so values, even if they're known at compile time, are not known
|
||||
value: &'a str,
|
||||
|
||||
/// The namespace of this attribute. Does not exist in the HTML spec
|
||||
namespace: Option<&'a str>,
|
||||
volatile: bool,
|
||||
},
|
||||
|
||||
/// 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),
|
||||
}
|
||||
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
|
||||
#[derive(Debug)]
|
||||
pub struct Attribute<'a> {
|
||||
/// The name of the attribute.
|
||||
pub name: &'a str,
|
||||
|
||||
/// The value of the attribute
|
||||
pub value: AttributeValue<'a>,
|
||||
|
||||
/// The namespace of the attribute.
|
||||
///
|
||||
/// Doesn’t exist in the html spec. Used in Dioxus to denote “style” tags and other attribute groups.
|
||||
pub namespace: Option<&'static str>,
|
||||
|
||||
/// The element in the DOM that this attribute belongs to
|
||||
pub mounted_element: Cell<ElementId>,
|
||||
|
||||
/// An indication of we should always try and set the attribute. Used in controlled components to ensure changes are propagated
|
||||
pub volatile: bool,
|
||||
}
|
||||
|
||||
/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements
|
||||
///
|
||||
/// These are built-in to be faster during the diffing process. To use a custom value, use the [`AttributeValue::Any`]
|
||||
/// variant.
|
||||
pub enum AttributeValue<'a> {
|
||||
/// Text attribute
|
||||
Text(&'a str),
|
||||
Float(f32),
|
||||
Int(i32),
|
||||
|
||||
/// A float
|
||||
Float(f64),
|
||||
|
||||
/// Signed integer
|
||||
Int(i64),
|
||||
|
||||
/// Boolean
|
||||
Bool(bool),
|
||||
|
||||
/// A listener, like "onclick"
|
||||
Listener(RefCell<Option<ListenerCb<'a>>>),
|
||||
|
||||
/// An arbitrary value that implements PartialEq and is static
|
||||
Any(BumpBox<'a, dyn AnyValue>),
|
||||
|
||||
/// A "none" value, resulting in the removal of an attribute from the dom
|
||||
None,
|
||||
}
|
||||
|
||||
type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
|
||||
|
||||
impl<'a> AttributeValue<'a> {
|
||||
pub fn new_listener<T: 'static>(
|
||||
cx: &'a ScopeState,
|
||||
mut callback: impl FnMut(Event<T>) + 'a,
|
||||
) -> AttributeValue<'a> {
|
||||
let boxed: BumpBox<'a, dyn FnMut(_) + 'a> = unsafe {
|
||||
BumpBox::from_raw(cx.bump().alloc(move |event: Event<dyn Any>| {
|
||||
if let Ok(data) = event.data.downcast::<T>() {
|
||||
callback(Event {
|
||||
propogates: event.propogates,
|
||||
data,
|
||||
})
|
||||
}
|
||||
}))
|
||||
};
|
||||
|
||||
AttributeValue::Listener(RefCell::new(Some(boxed)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for AttributeValue<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
@@ -208,20 +346,7 @@ impl<'a> PartialEq for AttributeValue<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> AttributeValue<'a> {
|
||||
pub fn matches_type(&self, other: &'a AttributeValue<'a>) -> bool {
|
||||
matches!(
|
||||
(self, other),
|
||||
(Self::Text(_), Self::Text(_))
|
||||
| (Self::Float(_), Self::Float(_))
|
||||
| (Self::Int(_), Self::Int(_))
|
||||
| (Self::Bool(_), Self::Bool(_))
|
||||
| (Self::Listener(_), Self::Listener(_))
|
||||
| (Self::Any(_), Self::Any(_))
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait AnyValue {
|
||||
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
|
||||
fn our_typeid(&self) -> TypeId;
|
||||
@@ -241,9 +366,185 @@ impl<T: PartialEq + Any> AnyValue for T {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn what_are_the_sizes() {
|
||||
dbg!(std::mem::size_of::<VNode>());
|
||||
dbg!(std::mem::size_of::<Template>());
|
||||
dbg!(std::mem::size_of::<TemplateNode>());
|
||||
#[doc(hidden)]
|
||||
pub trait ComponentReturn<'a, A = ()> {
|
||||
fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
|
||||
}
|
||||
|
||||
impl<'a> ComponentReturn<'a> for Element<'a> {
|
||||
fn into_return(self, _cx: &ScopeState) -> RenderReturn<'a> {
|
||||
RenderReturn::Sync(self)
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub struct AsyncMarker;
|
||||
impl<'a, F> ComponentReturn<'a, AsyncMarker> for F
|
||||
where
|
||||
F: Future<Output = Element<'a>> + 'a,
|
||||
{
|
||||
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) })
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RenderReturn<'a> {
|
||||
pub(crate) unsafe fn extend_lifetime_ref<'c>(&self) -> &'c RenderReturn<'c> {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
pub(crate) unsafe fn extend_lifetime<'c>(self) -> RenderReturn<'c> {
|
||||
unsafe { std::mem::transmute(self) }
|
||||
}
|
||||
}
|
||||
|
||||
/// A trait that allows various items to be converted into a dynamic node for the rsx macro
|
||||
pub trait IntoDynNode<'a, A = ()> {
|
||||
/// Consume this item along with a scopestate and produce a DynamicNode
|
||||
///
|
||||
/// You can use the bump alloactor of the scopestate to creat the dynamic node
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
|
||||
}
|
||||
|
||||
impl<'a> IntoDynNode<'a> for () {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::default()
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDynNode<'a> for VNode<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(_cx.bump().alloc([self]))
|
||||
}
|
||||
}
|
||||
impl<'a> IntoDynNode<'a> for Element<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
match self {
|
||||
Ok(val) => val.into_vnode(_cx),
|
||||
_ => DynamicNode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: IntoDynNode<'a>> IntoDynNode<'a> for Option<T> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
match self {
|
||||
Some(val) => val.into_vnode(_cx),
|
||||
None => DynamicNode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDynNode<'a> for &Element<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
match self.as_ref() {
|
||||
Ok(val) => val.clone().into_vnode(_cx),
|
||||
_ => DynamicNode::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, 'b> IntoDynNode<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(cx.bump().alloc([self.call(cx)]))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDynNode<'_> for &'a str {
|
||||
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
||||
cx.text_node(format_args!("{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl IntoDynNode<'_> for String {
|
||||
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
|
||||
cx.text_node(format_args!("{}", self))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'b> IntoDynNode<'b> for Arguments<'_> {
|
||||
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
|
||||
cx.text_node(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
|
||||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(_cx.bump().alloc([VNode {
|
||||
parent: self.parent,
|
||||
template: self.template,
|
||||
root_ids: self.root_ids,
|
||||
key: self.key,
|
||||
dynamic_nodes: self.dynamic_nodes,
|
||||
dynamic_attrs: self.dynamic_attrs,
|
||||
}]))
|
||||
}
|
||||
}
|
||||
|
||||
pub trait IntoTemplate<'a> {
|
||||
fn into_template(self, _cx: &'a ScopeState) -> VNode<'a>;
|
||||
}
|
||||
impl<'a> IntoTemplate<'a> for VNode<'a> {
|
||||
fn into_template(self, _cx: &'a ScopeState) -> VNode<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
|
||||
fn into_template(self, cx: &'a ScopeState) -> VNode<'a> {
|
||||
self.call(cx)
|
||||
}
|
||||
}
|
||||
|
||||
// Note that we're using the E as a generic but this is never crafted anyways.
|
||||
#[doc(hidden)]
|
||||
pub struct FromNodeIterator;
|
||||
impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
|
||||
where
|
||||
T: Iterator<Item = I>,
|
||||
I: IntoTemplate<'a>,
|
||||
{
|
||||
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
|
||||
|
||||
nodes.extend(self.into_iter().map(|node| node.into_template(cx)));
|
||||
|
||||
match nodes.into_bump_slice() {
|
||||
children if children.is_empty() => DynamicNode::default(),
|
||||
children => DynamicNode::Fragment(children),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// A value that can be converted into an attribute value
|
||||
pub trait IntoAttributeValue<'a> {
|
||||
/// Convert into an attribute value
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for &'a str {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Text(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for f64 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Float(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for i64 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Int(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for bool {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Bool(self)
|
||||
}
|
||||
}
|
||||
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
let mut str_buf = bumpalo::collections::String::new_in(bump);
|
||||
str_buf.write_fmt(self).unwrap();
|
||||
AttributeValue::Text(str_buf.into_bump_str())
|
||||
}
|
||||
}
|
||||
@@ -8,13 +8,12 @@ use std::{
|
||||
rc::Rc,
|
||||
};
|
||||
|
||||
/// An ID representing an ongoing suspended component
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct SuspenseId(pub usize);
|
||||
pub(crate) struct SuspenseId(pub usize);
|
||||
|
||||
pub type SuspenseContext = Rc<SuspenseBoundary>;
|
||||
|
||||
/// Essentially a fiber in React
|
||||
pub struct SuspenseBoundary {
|
||||
/// A boundary in the VirtualDom that captures all suspended components below it
|
||||
pub struct SuspenseContext {
|
||||
pub(crate) id: ScopeId,
|
||||
pub(crate) waiting_on: RefCell<HashSet<SuspenseId>>,
|
||||
pub(crate) mutations: RefCell<Mutations<'static>>,
|
||||
@@ -22,7 +21,8 @@ pub struct SuspenseBoundary {
|
||||
pub(crate) created_on_stack: Cell<usize>,
|
||||
}
|
||||
|
||||
impl SuspenseBoundary {
|
||||
impl SuspenseContext {
|
||||
/// Create a new boundary for suspense
|
||||
pub fn new(id: ScopeId) -> Self {
|
||||
Self {
|
||||
id,
|
||||
@@ -35,11 +35,11 @@ impl SuspenseBoundary {
|
||||
}
|
||||
|
||||
pub(crate) struct SuspenseLeaf {
|
||||
pub id: SuspenseId,
|
||||
pub scope_id: ScopeId,
|
||||
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||
pub notified: Cell<bool>,
|
||||
pub task: *mut dyn Future<Output = Element<'static>>,
|
||||
pub(crate) id: SuspenseId,
|
||||
pub(crate) scope_id: ScopeId,
|
||||
pub(crate) tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||
pub(crate) notified: Cell<bool>,
|
||||
pub(crate) task: *mut dyn Future<Output = Element<'static>>,
|
||||
}
|
||||
|
||||
impl RcWake for SuspenseLeaf {
|
||||
|
||||
@@ -4,6 +4,10 @@ use std::cell::RefCell;
|
||||
use std::future::Future;
|
||||
use std::{pin::Pin, rc::Rc};
|
||||
|
||||
/// A task's unique identifier.
|
||||
///
|
||||
/// `TaskId` is a `usize` that is unique across the entire VirtualDOM and across time. TaskIDs will never be reused
|
||||
/// once a Task has been completed.
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||
pub struct TaskId(pub usize);
|
||||
|
||||
@@ -2,9 +2,9 @@ use futures_util::FutureExt;
|
||||
use std::task::{Context, Poll};
|
||||
|
||||
use crate::{
|
||||
factory::RenderReturn,
|
||||
innerlude::{Mutation, Mutations, SuspenseContext},
|
||||
TaskId, VNode, VirtualDom,
|
||||
nodes::RenderReturn,
|
||||
ScopeId, TaskId, VNode, VirtualDom,
|
||||
};
|
||||
|
||||
use super::{waker::RcWake, SuspenseId};
|
||||
@@ -14,7 +14,7 @@ impl VirtualDom {
|
||||
///
|
||||
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
|
||||
/// queue
|
||||
pub fn handle_task_wakeup(&mut self, id: TaskId) {
|
||||
pub(crate) fn handle_task_wakeup(&mut self, id: TaskId) {
|
||||
let mut tasks = self.scheduler.tasks.borrow_mut();
|
||||
let task = &tasks[id.0];
|
||||
|
||||
@@ -31,7 +31,15 @@ impl VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn handle_suspense_wakeup(&mut self, id: SuspenseId) {
|
||||
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 handle_suspense_wakeup(&mut self, id: SuspenseId) {
|
||||
println!("suspense notified");
|
||||
|
||||
let leaf = self
|
||||
@@ -56,9 +64,8 @@ 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) {
|
||||
let fiber = &self.scopes[leaf.scope_id.0]
|
||||
.consume_context::<SuspenseContext>()
|
||||
.unwrap();
|
||||
// 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);
|
||||
|
||||
println!("ready pool");
|
||||
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::{
|
||||
any_props::AnyProps,
|
||||
bump_frame::BumpFrame,
|
||||
factory::RenderReturn,
|
||||
innerlude::DirtyScope,
|
||||
innerlude::{SuspenseId, SuspenseLeaf},
|
||||
nodes::RenderReturn,
|
||||
scheduler::RcWake,
|
||||
scopes::{ScopeId, ScopeState},
|
||||
virtual_dom::VirtualDom,
|
||||
@@ -18,7 +18,7 @@ use std::{
|
||||
};
|
||||
|
||||
impl VirtualDom {
|
||||
pub(super) fn new_scope(&mut self, props: Box<dyn AnyProps<'static>>) -> &mut ScopeState {
|
||||
pub(super) fn new_scope(&mut self, props: Box<dyn AnyProps<'static>>) -> &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) };
|
||||
@@ -86,8 +86,6 @@ impl VirtualDom {
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||
println!("Running scope {:?}", scope_id);
|
||||
|
||||
// Cycle to the next frame and then reset it
|
||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||
// Remove all the outdated listeners
|
||||
|
||||
+66
-37
@@ -3,11 +3,11 @@ use crate::{
|
||||
any_props::VProps,
|
||||
arena::ElementId,
|
||||
bump_frame::BumpFrame,
|
||||
factory::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
|
||||
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
||||
innerlude::{Scheduler, SchedulerMsg},
|
||||
lazynodes::LazyNodes,
|
||||
Attribute, Element, Properties, TaskId,
|
||||
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
|
||||
Attribute, AttributeValue, Element, Event, Properties, TaskId,
|
||||
};
|
||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
use std::{
|
||||
@@ -23,7 +23,7 @@ use std::{
|
||||
/// A wrapper around the [`Scoped`] object that contains a reference to the [`ScopeState`] and properties for a given
|
||||
/// component.
|
||||
///
|
||||
/// The [`Scope`] is your handle to the [`VirtualDom`] and the component state. Every component is given its own
|
||||
/// The [`Scope`] is your handle to the [`crate::VirtualDom`] and the component state. Every component is given its own
|
||||
/// [`ScopeState`] and merged with its properties to create a [`Scoped`].
|
||||
///
|
||||
/// The [`Scope`] handle specifically exists to provide a stable reference to these items for the lifetime of the
|
||||
@@ -56,8 +56,9 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
||||
|
||||
/// A component's unique identifier.
|
||||
///
|
||||
/// `ScopeId` is a `usize` that is unique across the entire [`VirtualDom`] and across time. [`ScopeID`]s will never be reused
|
||||
/// once a component has been unmounted.
|
||||
/// `ScopeId` is a `usize` that acts a key for the internal slab of Scopes. This means that the key is not unqiue across
|
||||
/// time. We do try and guarantee that between calls to `wait_for_work`, no ScopeIds will be recycled in order to give
|
||||
/// time for any logic that relies on these IDs to properly update.
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||
pub struct ScopeId(pub usize);
|
||||
@@ -177,9 +178,9 @@ impl<'src> ScopeState {
|
||||
self.height
|
||||
}
|
||||
|
||||
/// Get the Parent of this [`Scope`] within this Dioxus [`VirtualDom`].
|
||||
/// Get the Parent of this [`Scope`] within this Dioxus [`crate::VirtualDom`].
|
||||
///
|
||||
/// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
||||
/// This ID is not unique across Dioxus [`crate::VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
||||
///
|
||||
/// The base component will not have a parent, and will return `None`.
|
||||
///
|
||||
@@ -198,9 +199,9 @@ impl<'src> ScopeState {
|
||||
self.parent.map(|p| unsafe { &*p }.id)
|
||||
}
|
||||
|
||||
/// Get the ID of this Scope within this Dioxus [`VirtualDom`].
|
||||
/// Get the ID of this Scope within this Dioxus [`crate::VirtualDom`].
|
||||
///
|
||||
/// This ID is not unique across Dioxus [`VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
||||
/// This ID is not unique across Dioxus [`crate::VirtualDom`]s or across time. IDs will be reused when components are unmounted.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -217,7 +218,7 @@ impl<'src> ScopeState {
|
||||
|
||||
/// Create a subscription that schedules a future render for the reference component
|
||||
///
|
||||
/// ## Notice: you should prefer using [`schedule_update_any`] and [`scope_id`]
|
||||
/// ## Notice: you should prefer using [`Self::schedule_update_any`] and [`Self::scope_id`]
|
||||
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
|
||||
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||
@@ -240,7 +241,7 @@ impl<'src> ScopeState {
|
||||
|
||||
/// Get the [`ScopeId`] of a mounted component.
|
||||
///
|
||||
/// `ScopeId` is not unique for the lifetime of the [`VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
|
||||
/// `ScopeId` is not unique for the lifetime of the [`crate::VirtualDom`] - a [`ScopeId`] will be reused if a component is unmounted.
|
||||
pub fn needs_update_any(&self, id: ScopeId) {
|
||||
self.tasks
|
||||
.sender
|
||||
@@ -249,20 +250,17 @@ impl<'src> ScopeState {
|
||||
}
|
||||
|
||||
/// Return any context of type T if it exists on this scope
|
||||
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
self.shared_contexts
|
||||
.borrow()
|
||||
.get(&TypeId::of::<T>())
|
||||
.and_then(|shared| shared.downcast_ref::<T>())
|
||||
.cloned()
|
||||
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 })
|
||||
}
|
||||
|
||||
/// Try to retrieve a shared state with type `T` from any parent scope.
|
||||
///
|
||||
/// The state will be cloned and returned, if it exists.
|
||||
///
|
||||
/// We recommend wrapping the state in an `Rc` or `Arc` to avoid deep cloning.
|
||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||
/// To release the borrow, use `cloned` if the context is clone.
|
||||
pub fn consume_context<T: 'static>(&self) -> Option<&T> {
|
||||
if let Some(this_ctx) = self.has_context() {
|
||||
return Some(this_ctx);
|
||||
}
|
||||
@@ -272,26 +270,24 @@ 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>()) {
|
||||
return Some(
|
||||
shared
|
||||
.downcast_ref::<T>()
|
||||
.expect("Context of type T should exist")
|
||||
.clone(),
|
||||
);
|
||||
let as_concrete = shared.downcast_ref::<T>()? as *const T;
|
||||
return Some(unsafe { &*as_concrete });
|
||||
}
|
||||
search_parent = parent.parent;
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
/// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
|
||||
/// Expose state to children further down the [`crate::VirtualDom`] Tree. Does not require `clone` on the context,
|
||||
/// though we do recommend it.
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
|
||||
/// the context via Rc/Weak.
|
||||
/// 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
|
||||
/// behavior might change in the future.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
@@ -308,12 +304,20 @@ impl<'src> ScopeState {
|
||||
/// render!(div { "hello {state.0}" })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
||||
self.shared_contexts
|
||||
.borrow_mut()
|
||||
.insert(TypeId::of::<T>(), Box::new(value.clone()))
|
||||
.and_then(|f| f.downcast::<T>().ok());
|
||||
value
|
||||
pub fn provide_context<T: 'static>(&self, value: T) -> &T {
|
||||
let mut contexts = self.shared_contexts.borrow_mut();
|
||||
|
||||
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
|
||||
}
|
||||
};
|
||||
|
||||
unsafe { &*any }
|
||||
}
|
||||
|
||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
||||
@@ -349,7 +353,7 @@ impl<'src> ScopeState {
|
||||
self.tasks.remove(id);
|
||||
}
|
||||
|
||||
/// Take a lazy [`VNode`] structure and actually build it with the context of the efficient [`Bump`] allocator.
|
||||
/// Take a lazy [`crate::VNode`] structure and actually build it with the context of the efficient [`bumpalo::Bump`] allocator.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
@@ -457,6 +461,31 @@ impl<'src> ScopeState {
|
||||
EventHandler { callback }
|
||||
}
|
||||
|
||||
/// Create a new [`AttributeValue`] with the listener variant from a callback
|
||||
///
|
||||
/// The callback must be confined to the lifetime of the ScopeState
|
||||
pub fn listener<T: 'static>(
|
||||
&'src self,
|
||||
mut callback: impl FnMut(Event<T>) + 'src,
|
||||
) -> AttributeValue<'src> {
|
||||
// safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw
|
||||
// This is the suggested way to build a bumpbox
|
||||
//
|
||||
// In theory, we could just use regular boxes
|
||||
let boxed: BumpBox<'src, dyn FnMut(_) + 'src> = unsafe {
|
||||
BumpBox::from_raw(self.bump().alloc(move |event: Event<dyn Any>| {
|
||||
if let Ok(data) = event.data.downcast::<T>() {
|
||||
callback(Event {
|
||||
propogates: event.propogates,
|
||||
data,
|
||||
})
|
||||
}
|
||||
}))
|
||||
};
|
||||
|
||||
AttributeValue::Listener(RefCell::new(Some(boxed)))
|
||||
}
|
||||
|
||||
/// 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`.
|
||||
|
||||
@@ -5,11 +5,11 @@
|
||||
use crate::{
|
||||
any_props::VProps,
|
||||
arena::{ElementId, ElementRef},
|
||||
factory::RenderReturn,
|
||||
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
|
||||
mutations::Mutation,
|
||||
nodes::RenderReturn,
|
||||
nodes::{Template, TemplateId},
|
||||
scheduler::{SuspenseBoundary, SuspenseId},
|
||||
scheduler::SuspenseId,
|
||||
scopes::{ScopeId, ScopeState},
|
||||
AttributeValue, Element, Event, Scope, SuspenseContext,
|
||||
};
|
||||
@@ -249,10 +249,10 @@ impl VirtualDom {
|
||||
// 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(Rc::new(SuspenseBoundary::new(ScopeId(0))));
|
||||
root.provide_context(SuspenseContext::new(ScopeId(0)));
|
||||
|
||||
// Unlike react, we provide a default error boundary that just renders the error as a string
|
||||
root.provide_context(Rc::new(ErrorBoundary::new(ScopeId(0))));
|
||||
root.provide_context(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());
|
||||
@@ -489,7 +489,11 @@ impl VirtualDom {
|
||||
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
|
||||
// Rebuilding implies we append the created elements to the root
|
||||
RenderReturn::Sync(Ok(node)) => {
|
||||
let _m = self.create_scope(ScopeId(0), node);
|
||||
let m = self.create_scope(ScopeId(0), node);
|
||||
self.mutations.edits.push(Mutation::AppendChildren {
|
||||
id: ElementId(0),
|
||||
m,
|
||||
});
|
||||
}
|
||||
// If an error occurs, we should try to render the default error component and context where the error occured
|
||||
RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e),
|
||||
|
||||
Reference in new issue
Block a user