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 std::marker::PhantomData;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
factory::{ComponentReturn, RenderReturn},
|
|
||||||
innerlude::Scoped,
|
innerlude::Scoped,
|
||||||
|
nodes::{ComponentReturn, RenderReturn},
|
||||||
scopes::{Scope, ScopeState},
|
scopes::{Scope, ScopeState},
|
||||||
Element,
|
Element,
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,9 +1,13 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
factory::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
|
nodes::RenderReturn, nodes::VNode, virtual_dom::VirtualDom, AttributeValue, DynamicNode,
|
||||||
ScopeId,
|
ScopeId,
|
||||||
};
|
};
|
||||||
use bumpalo::boxed::Box as BumpBox;
|
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))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct ElementId(pub usize);
|
pub struct ElementId(pub usize);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::factory::RenderReturn;
|
use crate::nodes::RenderReturn;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
@@ -8,7 +8,7 @@ pub(crate) struct BumpFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
impl BumpFrame {
|
impl BumpFrame {
|
||||||
pub fn new(capacity: usize) -> Self {
|
pub(crate) fn new(capacity: usize) -> Self {
|
||||||
let bump = Bump::with_capacity(capacity);
|
let bump = Bump::with_capacity(capacity);
|
||||||
Self {
|
Self {
|
||||||
bump,
|
bump,
|
||||||
@@ -17,7 +17,7 @@ impl BumpFrame {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Creates a new lifetime out of thin air
|
/// 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();
|
let node = self.node.get();
|
||||||
|
|
||||||
if node.is_null() {
|
if node.is_null() {
|
||||||
|
|||||||
@@ -1,13 +1,12 @@
|
|||||||
use std::cell::Cell;
|
use std::cell::Cell;
|
||||||
|
|
||||||
use crate::factory::RenderReturn;
|
|
||||||
use crate::innerlude::{VComponent, VText};
|
use crate::innerlude::{VComponent, VText};
|
||||||
use crate::mutations::Mutation;
|
use crate::mutations::Mutation;
|
||||||
use crate::mutations::Mutation::*;
|
use crate::mutations::Mutation::*;
|
||||||
use crate::nodes::VNode;
|
use crate::nodes::VNode;
|
||||||
use crate::nodes::{DynamicNode, TemplateNode};
|
use crate::nodes::{DynamicNode, TemplateNode};
|
||||||
use crate::virtual_dom::VirtualDom;
|
use crate::virtual_dom::VirtualDom;
|
||||||
use crate::{AttributeValue, ElementId, ScopeId, SuspenseContext};
|
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
|
||||||
|
|
||||||
impl<'b> VirtualDom {
|
impl<'b> VirtualDom {
|
||||||
/// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
|
/// 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 {
|
pub(crate) fn create(&mut self, template: &'b VNode<'b>) -> usize {
|
||||||
// The best renderers will have templates prehydrated and registered
|
// The best renderers will have templates prehydrated and registered
|
||||||
// Just in case, let's create the template using instructions anyways
|
// 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);
|
self.register_template(template);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -75,7 +74,7 @@ impl<'b> VirtualDom {
|
|||||||
|
|
||||||
template.root_ids[root_idx].set(this_id);
|
template.root_ids[root_idx].set(this_id);
|
||||||
self.mutations.push(LoadTemplate {
|
self.mutations.push(LoadTemplate {
|
||||||
name: template.template.id,
|
name: template.template.name,
|
||||||
index: root_idx,
|
index: root_idx,
|
||||||
id: this_id,
|
id: this_id,
|
||||||
});
|
});
|
||||||
@@ -204,7 +203,7 @@ impl<'b> VirtualDom {
|
|||||||
fn register_template(&mut self, template: &'b VNode<'b>) {
|
fn register_template(&mut self, template: &'b VNode<'b>) {
|
||||||
// First, make sure we mark the template as seen, regardless if we process it
|
// First, make sure we mark the template as seen, regardless if we process it
|
||||||
self.templates
|
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
|
// 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
|
// 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
|
// 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>() {
|
let boundary = match self.scopes[scope.0].has_context::<SuspenseContext>() {
|
||||||
Some(boundary) => boundary,
|
Some(boundary) => unsafe { &*(boundary as *const SuspenseContext) },
|
||||||
_ => return created,
|
_ => return created,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use std::cell::Cell;
|
|||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
factory::RenderReturn,
|
|
||||||
innerlude::{DirtyScope, VComponent, VText},
|
innerlude::{DirtyScope, VComponent, VText},
|
||||||
mutations::Mutation,
|
mutations::Mutation,
|
||||||
|
nodes::RenderReturn,
|
||||||
nodes::{DynamicNode, VNode},
|
nodes::{DynamicNode, VNode},
|
||||||
scopes::ScopeId,
|
scopes::ScopeId,
|
||||||
virtual_dom::VirtualDom,
|
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>) {
|
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||||
println!(
|
println!(
|
||||||
"diffing node {:#?},\n\n{:#?}",
|
"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);
|
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.
|
/// 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)]
|
#[allow(non_upper_case_globals, non_snake_case)]
|
||||||
pub fn Fragment<'a>(_cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
||||||
// let children = cx.props.0.as_ref().unwrap();
|
let children = cx.props.0.as_ref().map_err(|e| anyhow::anyhow!("{}", e))?;
|
||||||
todo!()
|
Ok(VNode {
|
||||||
// Ok(VNode {
|
key: children.key.clone(),
|
||||||
// key: children.key.clone(),
|
parent: children.parent.clone(),
|
||||||
// parent: children.parent.clone(),
|
template: children.template,
|
||||||
// template: children.template,
|
root_ids: children.root_ids,
|
||||||
// root_ids: children.root_ids,
|
dynamic_nodes: children.dynamic_nodes,
|
||||||
// dynamic_nodes: children.dynamic_nodes,
|
dynamic_attrs: children.dynamic_attrs,
|
||||||
// dynamic_attrs: children.dynamic_attrs,
|
})
|
||||||
// })
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct FragmentProps<'a>(Element<'a>);
|
pub struct FragmentProps<'a>(Element<'a>);
|
||||||
@@ -96,7 +95,7 @@ impl<'a> Properties for FragmentProps<'a> {
|
|||||||
type Builder = FragmentBuilder<'a, false>;
|
type Builder = FragmentBuilder<'a, false>;
|
||||||
const IS_STATIC: bool = false;
|
const IS_STATIC: bool = false;
|
||||||
fn builder() -> Self::Builder {
|
fn builder() -> Self::Builder {
|
||||||
todo!()
|
FragmentBuilder(VNode::empty())
|
||||||
}
|
}
|
||||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||||
false
|
false
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ use std::mem;
|
|||||||
/// A concrete type provider for closures that build [`VNode`] structures.
|
/// A concrete type provider for closures that build [`VNode`] structures.
|
||||||
///
|
///
|
||||||
/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
|
/// This struct wraps lazy structs that build [`VNode`] trees Normally, we cannot perform a blanket implementation over
|
||||||
/// closures, but if we wrap the closure in a concrete type, we can 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
|
/// ```rust, ignore
|
||||||
|
|||||||
@@ -9,7 +9,6 @@ mod diff;
|
|||||||
mod dirty_scope;
|
mod dirty_scope;
|
||||||
mod error_boundary;
|
mod error_boundary;
|
||||||
mod events;
|
mod events;
|
||||||
mod factory;
|
|
||||||
mod fragment;
|
mod fragment;
|
||||||
mod lazynodes;
|
mod lazynodes;
|
||||||
mod mutations;
|
mod mutations;
|
||||||
@@ -25,10 +24,10 @@ pub(crate) mod innerlude {
|
|||||||
pub use crate::dirty_scope::*;
|
pub use crate::dirty_scope::*;
|
||||||
pub use crate::error_boundary::*;
|
pub use crate::error_boundary::*;
|
||||||
pub use crate::events::*;
|
pub use crate::events::*;
|
||||||
pub use crate::factory::RenderReturn;
|
|
||||||
pub use crate::fragment::*;
|
pub use crate::fragment::*;
|
||||||
pub use crate::lazynodes::*;
|
pub use crate::lazynodes::*;
|
||||||
pub use crate::mutations::*;
|
pub use crate::mutations::*;
|
||||||
|
pub use crate::nodes::RenderReturn;
|
||||||
pub use crate::nodes::*;
|
pub use crate::nodes::*;
|
||||||
pub use crate::properties::*;
|
pub use crate::properties::*;
|
||||||
pub use crate::scheduler::*;
|
pub use crate::scheduler::*;
|
||||||
@@ -72,9 +71,9 @@ pub(crate) mod innerlude {
|
|||||||
|
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
|
fc_to_builder, Attribute, AttributeValue, Component, DynamicNode, Element, ElementId, Event,
|
||||||
Fragment, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState,
|
Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties, RenderReturn, Scope,
|
||||||
Scoped, SuspenseBoundary, SuspenseContext, TaskId, Template, TemplateAttribute, TemplateNode,
|
ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template, TemplateAttribute,
|
||||||
VComponent, VNode, VText, VirtualDom,
|
TemplateNode, VComponent, VNode, VText, VirtualDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// The purpose of this module is to alleviate imports of many common types
|
/// 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
|
/// 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
|
/// This object provides a bunch of important information for a renderer to use patch the Real Dom with the state of the
|
||||||
/// includes a list of NodeRefs that need to be applied and effects that need to be triggered after the RealDOM has
|
/// VirtualDom. This includes the scopes that were modified, the templates that were discovered, and a list of changes
|
||||||
/// applied the edits.
|
/// 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.
|
/// Mutations are the only link between the RealDOM and the VirtualDOM.
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
@@ -32,17 +36,13 @@ impl<'a> Mutations<'a> {
|
|||||||
///
|
///
|
||||||
/// Used really only for testing
|
/// Used really only for testing
|
||||||
pub fn santize(mut self) -> Self {
|
pub fn santize(mut self) -> Self {
|
||||||
todo!()
|
for edit in self.edits.iter_mut() {
|
||||||
// self.templates
|
if let Mutation::LoadTemplate { name, .. } = edit {
|
||||||
// .iter_mut()
|
*name = "template"
|
||||||
// .chain(self.dom_edits.iter_mut())
|
}
|
||||||
// .for_each(|edit| match edit {
|
}
|
||||||
// Mutation::LoadTemplate { name, .. } => *name = "template",
|
|
||||||
// Mutation::SaveTemplate { name, .. } => *name = "template",
|
|
||||||
// _ => {}
|
|
||||||
// });
|
|
||||||
|
|
||||||
// self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Push a new mutation into the dom_edits list
|
/// Push a new mutation into the dom_edits list
|
||||||
@@ -51,10 +51,10 @@ impl<'a> Mutations<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
|
||||||
each subtree has its own numbering scheme
|
/// of the Dioxus VirtualDom.
|
||||||
*/
|
///
|
||||||
|
/// These edits can be serialized and sent over the network or through any interface
|
||||||
#[cfg_attr(
|
#[cfg_attr(
|
||||||
feature = "serialize",
|
feature = "serialize",
|
||||||
derive(serde::Serialize, serde::Deserialize),
|
derive(serde::Serialize, serde::Deserialize),
|
||||||
@@ -62,37 +62,111 @@ each subtree has its own numbering scheme
|
|||||||
)]
|
)]
|
||||||
#[derive(Debug, PartialEq, Eq)]
|
#[derive(Debug, PartialEq, Eq)]
|
||||||
pub enum Mutation<'a> {
|
pub enum Mutation<'a> {
|
||||||
AssignId {
|
/// Add these m children to the target element
|
||||||
path: &'static [u8],
|
AppendChildren {
|
||||||
|
/// The ID of the element being mounted to
|
||||||
id: ElementId,
|
id: ElementId,
|
||||||
},
|
|
||||||
|
|
||||||
CreatePlaceholder {
|
/// The number of nodes on the stack
|
||||||
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,
|
|
||||||
m: usize,
|
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],
|
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,
|
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::boxed::Box as BumpBox;
|
||||||
|
use bumpalo::Bump;
|
||||||
use std::{
|
use std::{
|
||||||
any::{Any, TypeId},
|
any::{Any, TypeId},
|
||||||
cell::{Cell, RefCell},
|
cell::{Cell, RefCell},
|
||||||
|
fmt::Arguments,
|
||||||
|
future::Future,
|
||||||
};
|
};
|
||||||
|
|
||||||
pub type TemplateId = &'static str;
|
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
|
/// 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
|
/// 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> {
|
impl<'a> VNode<'a> {
|
||||||
|
/// Create a template with no nodes that will be skipped over during diffing
|
||||||
pub fn empty() -> Element<'a> {
|
pub fn empty() -> Element<'a> {
|
||||||
Ok(VNode {
|
Ok(VNode {
|
||||||
key: None,
|
key: None,
|
||||||
@@ -44,7 +65,7 @@ impl<'a> VNode<'a> {
|
|||||||
dynamic_nodes: &[],
|
dynamic_nodes: &[],
|
||||||
dynamic_attrs: &[],
|
dynamic_attrs: &[],
|
||||||
template: Template {
|
template: Template {
|
||||||
id: "dioxus-empty",
|
name: "dioxus-empty",
|
||||||
roots: &[],
|
roots: &[],
|
||||||
node_paths: &[],
|
node_paths: &[],
|
||||||
attr_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>> {
|
pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
|
||||||
match &self.template.roots[idx] {
|
match &self.template.roots[idx] {
|
||||||
TemplateNode::Element { .. } | TemplateNode::Text(_) => None,
|
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))]
|
#[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 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>],
|
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]],
|
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]],
|
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))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
|
||||||
pub enum TemplateNode<'a> {
|
pub enum TemplateNode<'a> {
|
||||||
|
/// An statically known element in the dom.
|
||||||
|
///
|
||||||
|
/// In HTML this would be something like `<div id="123"> </div>`
|
||||||
Element {
|
Element {
|
||||||
|
/// The name of the element
|
||||||
|
///
|
||||||
|
/// IE for a div, it would be the string "div"
|
||||||
tag: &'a str,
|
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>,
|
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>],
|
attrs: &'a [TemplateAttribute<'a>],
|
||||||
|
|
||||||
|
/// A list of template nodes that define another set of template nodes
|
||||||
children: &'a [TemplateNode<'a>],
|
children: &'a [TemplateNode<'a>],
|
||||||
inner_opt: bool,
|
|
||||||
},
|
},
|
||||||
|
|
||||||
|
/// This template node is just a piece of static text
|
||||||
Text(&'a str),
|
Text(&'a str),
|
||||||
|
|
||||||
|
/// This template node is unknown, and needs to be created at runtime.
|
||||||
Dynamic(usize),
|
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),
|
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)]
|
#[derive(Debug)]
|
||||||
pub enum DynamicNode<'a> {
|
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>),
|
Component(VComponent<'a>),
|
||||||
|
|
||||||
|
/// A text node
|
||||||
Text(VText<'a>),
|
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>),
|
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>]),
|
Fragment(&'a [VNode<'a>]),
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DynamicNode<'a> {
|
impl Default for DynamicNode<'_> {
|
||||||
pub fn is_component(&self) -> bool {
|
fn default() -> Self {
|
||||||
matches!(self, DynamicNode::Component(_))
|
|
||||||
}
|
|
||||||
pub fn placeholder() -> Self {
|
|
||||||
Self::Placeholder(Default::default())
|
Self::Placeholder(Default::default())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An instance of a child component
|
||||||
pub struct VComponent<'a> {
|
pub struct VComponent<'a> {
|
||||||
|
/// The name of this component
|
||||||
pub name: &'static str,
|
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,
|
pub static_props: bool,
|
||||||
|
|
||||||
|
/// The assigned Scope for this component
|
||||||
pub scope: Cell<Option<ScopeId>>,
|
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 render_fn: *const (),
|
||||||
|
|
||||||
pub(crate) props: Cell<Option<Box<dyn AnyProps<'a> + 'a>>>,
|
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)]
|
#[derive(Debug)]
|
||||||
pub struct VText<'a> {
|
pub struct VText<'a> {
|
||||||
pub id: Cell<ElementId>,
|
/// The actual text itself
|
||||||
pub value: &'a str,
|
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))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
pub enum TemplateAttribute<'a> {
|
pub enum TemplateAttribute<'a> {
|
||||||
|
/// This attribute is entirely known at compile time, enabling
|
||||||
Static {
|
Static {
|
||||||
|
/// The name of this attribute.
|
||||||
|
///
|
||||||
|
/// For example, the `href` attribute in `href="https://example.com"`, would have the name "href"
|
||||||
name: &'a str,
|
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,
|
value: &'a str,
|
||||||
|
|
||||||
|
/// The namespace of this attribute. Does not exist in the HTML spec
|
||||||
namespace: Option<&'a str>,
|
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),
|
Dynamic(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct Attribute<'a> {
|
pub struct Attribute<'a> {
|
||||||
|
/// The name of the attribute.
|
||||||
pub name: &'a str,
|
pub name: &'a str,
|
||||||
|
|
||||||
|
/// The value of the attribute
|
||||||
pub value: AttributeValue<'a>,
|
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>,
|
pub namespace: Option<&'static str>,
|
||||||
|
|
||||||
|
/// The element in the DOM that this attribute belongs to
|
||||||
pub mounted_element: Cell<ElementId>,
|
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,
|
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> {
|
pub enum AttributeValue<'a> {
|
||||||
|
/// Text attribute
|
||||||
Text(&'a str),
|
Text(&'a str),
|
||||||
Float(f32),
|
|
||||||
Int(i32),
|
/// A float
|
||||||
|
Float(f64),
|
||||||
|
|
||||||
|
/// Signed integer
|
||||||
|
Int(i64),
|
||||||
|
|
||||||
|
/// Boolean
|
||||||
Bool(bool),
|
Bool(bool),
|
||||||
|
|
||||||
|
/// A listener, like "onclick"
|
||||||
Listener(RefCell<Option<ListenerCb<'a>>>),
|
Listener(RefCell<Option<ListenerCb<'a>>>),
|
||||||
|
|
||||||
|
/// An arbitrary value that implements PartialEq and is static
|
||||||
Any(BumpBox<'a, dyn AnyValue>),
|
Any(BumpBox<'a, dyn AnyValue>),
|
||||||
|
|
||||||
|
/// A "none" value, resulting in the removal of an attribute from the dom
|
||||||
None,
|
None,
|
||||||
}
|
}
|
||||||
|
|
||||||
type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
|
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> {
|
impl<'a> std::fmt::Debug for AttributeValue<'a> {
|
||||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
match self {
|
match self {
|
||||||
@@ -208,20 +346,7 @@ impl<'a> PartialEq for AttributeValue<'a> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> AttributeValue<'a> {
|
#[doc(hidden)]
|
||||||
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(_))
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait AnyValue {
|
pub trait AnyValue {
|
||||||
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
|
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
|
||||||
fn our_typeid(&self) -> TypeId;
|
fn our_typeid(&self) -> TypeId;
|
||||||
@@ -241,9 +366,185 @@ impl<T: PartialEq + Any> AnyValue for T {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[doc(hidden)]
|
||||||
fn what_are_the_sizes() {
|
pub trait ComponentReturn<'a, A = ()> {
|
||||||
dbg!(std::mem::size_of::<VNode>());
|
fn into_return(self, cx: &'a ScopeState) -> RenderReturn<'a>;
|
||||||
dbg!(std::mem::size_of::<Template>());
|
}
|
||||||
dbg!(std::mem::size_of::<TemplateNode>());
|
|
||||||
|
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,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// An ID representing an ongoing suspended component
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct SuspenseId(pub usize);
|
pub(crate) struct SuspenseId(pub usize);
|
||||||
|
|
||||||
pub type SuspenseContext = Rc<SuspenseBoundary>;
|
/// A boundary in the VirtualDom that captures all suspended components below it
|
||||||
|
pub struct SuspenseContext {
|
||||||
/// Essentially a fiber in React
|
|
||||||
pub struct SuspenseBoundary {
|
|
||||||
pub(crate) id: ScopeId,
|
pub(crate) id: ScopeId,
|
||||||
pub(crate) waiting_on: RefCell<HashSet<SuspenseId>>,
|
pub(crate) waiting_on: RefCell<HashSet<SuspenseId>>,
|
||||||
pub(crate) mutations: RefCell<Mutations<'static>>,
|
pub(crate) mutations: RefCell<Mutations<'static>>,
|
||||||
@@ -22,7 +21,8 @@ pub struct SuspenseBoundary {
|
|||||||
pub(crate) created_on_stack: Cell<usize>,
|
pub(crate) created_on_stack: Cell<usize>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl SuspenseBoundary {
|
impl SuspenseContext {
|
||||||
|
/// Create a new boundary for suspense
|
||||||
pub fn new(id: ScopeId) -> Self {
|
pub fn new(id: ScopeId) -> Self {
|
||||||
Self {
|
Self {
|
||||||
id,
|
id,
|
||||||
@@ -35,11 +35,11 @@ impl SuspenseBoundary {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) struct SuspenseLeaf {
|
pub(crate) struct SuspenseLeaf {
|
||||||
pub id: SuspenseId,
|
pub(crate) id: SuspenseId,
|
||||||
pub scope_id: ScopeId,
|
pub(crate) scope_id: ScopeId,
|
||||||
pub tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
pub(crate) tx: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||||
pub notified: Cell<bool>,
|
pub(crate) notified: Cell<bool>,
|
||||||
pub task: *mut dyn Future<Output = Element<'static>>,
|
pub(crate) task: *mut dyn Future<Output = Element<'static>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl RcWake for SuspenseLeaf {
|
impl RcWake for SuspenseLeaf {
|
||||||
|
|||||||
@@ -4,6 +4,10 @@ use std::cell::RefCell;
|
|||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
use std::{pin::Pin, rc::Rc};
|
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))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
|
||||||
pub struct TaskId(pub usize);
|
pub struct TaskId(pub usize);
|
||||||
|
|||||||
@@ -2,9 +2,9 @@ use futures_util::FutureExt;
|
|||||||
use std::task::{Context, Poll};
|
use std::task::{Context, Poll};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
factory::RenderReturn,
|
|
||||||
innerlude::{Mutation, Mutations, SuspenseContext},
|
innerlude::{Mutation, Mutations, SuspenseContext},
|
||||||
TaskId, VNode, VirtualDom,
|
nodes::RenderReturn,
|
||||||
|
ScopeId, TaskId, VNode, VirtualDom,
|
||||||
};
|
};
|
||||||
|
|
||||||
use super::{waker::RcWake, SuspenseId};
|
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
|
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
|
||||||
/// queue
|
/// 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 mut tasks = self.scheduler.tasks.borrow_mut();
|
||||||
let task = &tasks[id.0];
|
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");
|
println!("suspense notified");
|
||||||
|
|
||||||
let leaf = self
|
let leaf = self
|
||||||
@@ -56,9 +64,8 @@ impl VirtualDom {
|
|||||||
// we should attach them to that component and then render its children
|
// we should attach them to that component and then render its children
|
||||||
// continue rendering the tree until we hit yet another suspended component
|
// continue rendering the tree until we hit yet another suspended component
|
||||||
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
||||||
let fiber = &self.scopes[leaf.scope_id.0]
|
// safety: we're not going to modify the suspense context but we don't want to make a clone of it
|
||||||
.consume_context::<SuspenseContext>()
|
let fiber = self.acquire_suspense_boundary(leaf.scope_id);
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
println!("ready pool");
|
println!("ready pool");
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
bump_frame::BumpFrame,
|
bump_frame::BumpFrame,
|
||||||
factory::RenderReturn,
|
|
||||||
innerlude::DirtyScope,
|
innerlude::DirtyScope,
|
||||||
innerlude::{SuspenseId, SuspenseLeaf},
|
innerlude::{SuspenseId, SuspenseLeaf},
|
||||||
|
nodes::RenderReturn,
|
||||||
scheduler::RcWake,
|
scheduler::RcWake,
|
||||||
scopes::{ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
virtual_dom::VirtualDom,
|
virtual_dom::VirtualDom,
|
||||||
@@ -18,7 +18,7 @@ use std::{
|
|||||||
};
|
};
|
||||||
|
|
||||||
impl VirtualDom {
|
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 parent = self.acquire_current_scope_raw();
|
||||||
let entry = self.scopes.vacant_entry();
|
let entry = self.scopes.vacant_entry();
|
||||||
let height = unsafe { parent.map(|f| (*f).height + 1).unwrap_or(0) };
|
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 {
|
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
|
// Cycle to the next frame and then reset it
|
||||||
// This breaks any latent references, invalidating every pointer referencing into it.
|
// This breaks any latent references, invalidating every pointer referencing into it.
|
||||||
// Remove all the outdated listeners
|
// Remove all the outdated listeners
|
||||||
|
|||||||
+66
-37
@@ -3,11 +3,11 @@ use crate::{
|
|||||||
any_props::VProps,
|
any_props::VProps,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
bump_frame::BumpFrame,
|
bump_frame::BumpFrame,
|
||||||
factory::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
|
|
||||||
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
innerlude::{DynamicNode, EventHandler, VComponent, VText},
|
||||||
innerlude::{Scheduler, SchedulerMsg},
|
innerlude::{Scheduler, SchedulerMsg},
|
||||||
lazynodes::LazyNodes,
|
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 bumpalo::{boxed::Box as BumpBox, Bump};
|
||||||
use std::{
|
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
|
/// A wrapper around the [`Scoped`] object that contains a reference to the [`ScopeState`] and properties for a given
|
||||||
/// component.
|
/// 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`].
|
/// [`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
|
/// 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.
|
/// 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
|
/// `ScopeId` is a `usize` that acts a key for the internal slab of Scopes. This means that the key is not unqiue across
|
||||||
/// once a component has been unmounted.
|
/// 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))]
|
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||||
pub struct ScopeId(pub usize);
|
pub struct ScopeId(pub usize);
|
||||||
@@ -177,9 +178,9 @@ impl<'src> ScopeState {
|
|||||||
self.height
|
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`.
|
/// 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)
|
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
|
/// # Example
|
||||||
///
|
///
|
||||||
@@ -217,7 +218,7 @@ impl<'src> ScopeState {
|
|||||||
|
|
||||||
/// Create a subscription that schedules a future render for the reference component
|
/// 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> {
|
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
|
||||||
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
|
let (chan, id) = (self.tasks.sender.clone(), self.scope_id());
|
||||||
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(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.
|
/// 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) {
|
pub fn needs_update_any(&self, id: ScopeId) {
|
||||||
self.tasks
|
self.tasks
|
||||||
.sender
|
.sender
|
||||||
@@ -249,20 +250,17 @@ impl<'src> ScopeState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Return any context of type T if it exists on this scope
|
/// Return any context of type T if it exists on this scope
|
||||||
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
pub fn has_context<T: 'static>(&self) -> Option<&T> {
|
||||||
self.shared_contexts
|
let contextex = self.shared_contexts.borrow();
|
||||||
.borrow()
|
let val = contextex.get(&TypeId::of::<T>())?;
|
||||||
.get(&TypeId::of::<T>())
|
let as_concrete = val.downcast_ref::<T>()? as *const T;
|
||||||
.and_then(|shared| shared.downcast_ref::<T>())
|
Some(unsafe { &*as_concrete })
|
||||||
.cloned()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Try to retrieve a shared state with type `T` from any parent scope.
|
/// Try to retrieve a shared state with type `T` from any parent scope.
|
||||||
///
|
///
|
||||||
/// The state will be cloned and returned, if it exists.
|
/// To release the borrow, use `cloned` if the context is clone.
|
||||||
///
|
pub fn consume_context<T: 'static>(&self) -> Option<&T> {
|
||||||
/// We recommend wrapping the state in an `Rc` or `Arc` to avoid deep cloning.
|
|
||||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
||||||
if let Some(this_ctx) = self.has_context() {
|
if let Some(this_ctx) = self.has_context() {
|
||||||
return Some(this_ctx);
|
return Some(this_ctx);
|
||||||
}
|
}
|
||||||
@@ -272,26 +270,24 @@ impl<'src> ScopeState {
|
|||||||
// safety: all parent pointers are valid thanks to the bump arena
|
// safety: all parent pointers are valid thanks to the bump arena
|
||||||
let parent = unsafe { &*parent_ptr };
|
let parent = unsafe { &*parent_ptr };
|
||||||
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
||||||
return Some(
|
let as_concrete = shared.downcast_ref::<T>()? as *const T;
|
||||||
shared
|
return Some(unsafe { &*as_concrete });
|
||||||
.downcast_ref::<T>()
|
|
||||||
.expect("Context of type T should exist")
|
|
||||||
.clone(),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
search_parent = parent.parent;
|
search_parent = parent.parent;
|
||||||
}
|
}
|
||||||
None
|
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.
|
/// 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_consume_context` instead.
|
||||||
///
|
///
|
||||||
/// When the component is dropped, so is the context. Be aware of this behavior when consuming
|
/// If a state is provided that already exists, the new value will not be inserted. Instead, this method will
|
||||||
/// the context via Rc/Weak.
|
/// 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
|
/// # Example
|
||||||
///
|
///
|
||||||
@@ -308,12 +304,20 @@ impl<'src> ScopeState {
|
|||||||
/// render!(div { "hello {state.0}" })
|
/// render!(div { "hello {state.0}" })
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
|
pub fn provide_context<T: 'static>(&self, value: T) -> &T {
|
||||||
self.shared_contexts
|
let mut contexts = self.shared_contexts.borrow_mut();
|
||||||
.borrow_mut()
|
|
||||||
.insert(TypeId::of::<T>(), Box::new(value.clone()))
|
let any = match contexts.get(&TypeId::of::<T>()) {
|
||||||
.and_then(|f| f.downcast::<T>().ok());
|
Some(item) => item.downcast_ref::<T>().unwrap() as *const T,
|
||||||
value
|
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.
|
/// 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);
|
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
|
/// ## Example
|
||||||
///
|
///
|
||||||
@@ -457,6 +461,31 @@ impl<'src> ScopeState {
|
|||||||
EventHandler { callback }
|
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.
|
/// 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`.
|
/// 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::{
|
use crate::{
|
||||||
any_props::VProps,
|
any_props::VProps,
|
||||||
arena::{ElementId, ElementRef},
|
arena::{ElementId, ElementRef},
|
||||||
factory::RenderReturn,
|
|
||||||
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
|
innerlude::{DirtyScope, ErrorBoundary, Mutations, Scheduler, SchedulerMsg},
|
||||||
mutations::Mutation,
|
mutations::Mutation,
|
||||||
|
nodes::RenderReturn,
|
||||||
nodes::{Template, TemplateId},
|
nodes::{Template, TemplateId},
|
||||||
scheduler::{SuspenseBoundary, SuspenseId},
|
scheduler::SuspenseId,
|
||||||
scopes::{ScopeId, ScopeState},
|
scopes::{ScopeId, ScopeState},
|
||||||
AttributeValue, Element, Event, Scope, SuspenseContext,
|
AttributeValue, Element, Event, Scope, SuspenseContext,
|
||||||
};
|
};
|
||||||
@@ -249,10 +249,10 @@ impl VirtualDom {
|
|||||||
// This could be unexpected, so we might rethink this behavior later
|
// This could be unexpected, so we might rethink this behavior later
|
||||||
//
|
//
|
||||||
// We *could* just panic if the suspense boundary is not found
|
// 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
|
// 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
|
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||||
dom.elements.insert(ElementRef::null());
|
dom.elements.insert(ElementRef::null());
|
||||||
@@ -489,7 +489,11 @@ impl VirtualDom {
|
|||||||
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
|
match unsafe { self.run_scope(ScopeId(0)).extend_lifetime_ref() } {
|
||||||
// Rebuilding implies we append the created elements to the root
|
// Rebuilding implies we append the created elements to the root
|
||||||
RenderReturn::Sync(Ok(node)) => {
|
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
|
// 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),
|
RenderReturn::Sync(Err(e)) => panic!("Cannot catch errors during rebuild {:?}", e),
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ fn attrs_cycle() {
|
|||||||
dom.rebuild().santize().edits,
|
dom.rebuild().santize().edits,
|
||||||
[
|
[
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { m: 1, id: ElementId(0) },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ fn bool_test() {
|
|||||||
[
|
[
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||||
SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) },
|
SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { m: 1, id: ElementId(0) },
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -16,7 +16,7 @@ fn test_borrowed_state() {
|
|||||||
HydrateText { path: &[0,], value: "Hello w1!", id: ElementId(4,) },
|
HydrateText { path: &[0,], value: "Hello w1!", id: ElementId(4,) },
|
||||||
ReplacePlaceholder { path: &[1,], m: 1 },
|
ReplacePlaceholder { path: &[1,], m: 1 },
|
||||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { m: 1, id: ElementId(0) },
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ fn state_shares() {
|
|||||||
dom.rebuild().santize().edits,
|
dom.rebuild().santize().edits,
|
||||||
[
|
[
|
||||||
CreateTextNode { value: "Value is 0", id: ElementId(1,) },
|
CreateTextNode { value: "Value is 0", id: ElementId(1,) },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { m: 1, id: ElementId(0) },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -22,25 +22,13 @@ fn test_original_diff() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let edits = dom.rebuild().santize();
|
let edits = dom.rebuild().santize();
|
||||||
assert_eq!(
|
|
||||||
edits.templates,
|
|
||||||
[
|
|
||||||
// create template
|
|
||||||
CreateElement { name: "div" },
|
|
||||||
CreateElement { name: "div" },
|
|
||||||
CreateStaticText { value: "Hello, world!" },
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
SaveTemplate { name: "template", m: 1 },
|
|
||||||
]
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
// add to root
|
// add to root
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||||
AppendChildren { m: 1 }
|
AppendChildren { m: 1, id: ElementId(0) }
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -65,28 +53,31 @@ fn create() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let edits = dom.rebuild().santize();
|
let _edits = dom.rebuild().santize();
|
||||||
assert_eq!(
|
|
||||||
edits.templates,
|
// todo: we don't test template mutations anymore since the templates are passed along
|
||||||
[
|
|
||||||
// create template
|
// assert_eq!(
|
||||||
CreateElement { name: "div" },
|
// edits.templates,
|
||||||
CreateElement { name: "div" },
|
// [
|
||||||
CreateStaticText { value: "Hello, world!" },
|
// // create template
|
||||||
CreateElement { name: "div" },
|
// CreateElement { name: "div" },
|
||||||
CreateElement { name: "div" },
|
// CreateElement { name: "div" },
|
||||||
CreateStaticPlaceholder {},
|
// CreateStaticText { value: "Hello, world!" },
|
||||||
AppendChildren { m: 1 },
|
// CreateElement { name: "div" },
|
||||||
AppendChildren { m: 1 },
|
// CreateElement { name: "div" },
|
||||||
AppendChildren { m: 2 },
|
// CreateStaticPlaceholder {},
|
||||||
AppendChildren { m: 1 },
|
// AppendChildren { m: 1 },
|
||||||
SaveTemplate { name: "template", m: 1 },
|
// AppendChildren { m: 1 },
|
||||||
// The fragment child template
|
// AppendChildren { m: 2 },
|
||||||
CreateStaticText { value: "hello" },
|
// AppendChildren { m: 1 },
|
||||||
CreateStaticText { value: "world" },
|
// SaveTemplate { name: "template", m: 1 },
|
||||||
SaveTemplate { name: "template", m: 2 },
|
// // The fragment child template
|
||||||
]
|
// CreateStaticText { value: "hello" },
|
||||||
);
|
// CreateStaticText { value: "world" },
|
||||||
|
// SaveTemplate { name: "template", m: 2 },
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -97,17 +88,19 @@ fn create_list() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
let edits = dom.rebuild().santize();
|
let _edits = dom.rebuild().santize();
|
||||||
assert_eq!(
|
|
||||||
edits.templates,
|
// note: we dont test template edits anymore
|
||||||
[
|
// assert_eq!(
|
||||||
// create template
|
// edits.templates,
|
||||||
CreateElement { name: "div" },
|
// [
|
||||||
CreateStaticText { value: "hello" },
|
// // create template
|
||||||
AppendChildren { m: 1 },
|
// CreateElement { name: "div" },
|
||||||
SaveTemplate { name: "template", m: 1 }
|
// CreateStaticText { value: "hello" },
|
||||||
]
|
// AppendChildren { m: 1 },
|
||||||
);
|
// SaveTemplate { name: "template", m: 1 }
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -122,18 +115,20 @@ fn create_simple() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
let edits = dom.rebuild().santize();
|
let edits = dom.rebuild().santize();
|
||||||
assert_eq!(
|
|
||||||
edits.templates,
|
// note: we dont test template edits anymore
|
||||||
[
|
// assert_eq!(
|
||||||
// create template
|
// edits.templates,
|
||||||
CreateElement { name: "div" },
|
// [
|
||||||
CreateElement { name: "div" },
|
// // create template
|
||||||
CreateElement { name: "div" },
|
// CreateElement { name: "div" },
|
||||||
CreateElement { name: "div" },
|
// CreateElement { name: "div" },
|
||||||
// add to root
|
// CreateElement { name: "div" },
|
||||||
SaveTemplate { name: "template", m: 4 }
|
// CreateElement { name: "div" },
|
||||||
]
|
// // add to root
|
||||||
);
|
// SaveTemplate { name: "template", m: 4 }
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
}
|
}
|
||||||
#[test]
|
#[test]
|
||||||
fn create_components() {
|
fn create_components() {
|
||||||
@@ -158,26 +153,9 @@ fn create_components() {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
let edits = dom.rebuild().santize();
|
let _edits = dom.rebuild().santize();
|
||||||
assert_eq!(
|
|
||||||
edits.templates,
|
// todo: test this
|
||||||
[
|
|
||||||
// The "child" template
|
|
||||||
CreateElement { name: "h1" },
|
|
||||||
CreateElement { name: "div" },
|
|
||||||
CreateStaticPlaceholder {},
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
CreateElement { name: "p" },
|
|
||||||
SaveTemplate { name: "template", m: 3 },
|
|
||||||
// Sub template for component children
|
|
||||||
CreateStaticText { value: "abc1" },
|
|
||||||
SaveTemplate { name: "template", m: 1 },
|
|
||||||
CreateStaticText { value: "abc2" },
|
|
||||||
SaveTemplate { name: "template", m: 1 },
|
|
||||||
CreateStaticText { value: "abc3" },
|
|
||||||
SaveTemplate { name: "template", m: 1 }
|
|
||||||
]
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -195,23 +173,25 @@ fn anchors() {
|
|||||||
|
|
||||||
// note that the template under "false" doesn't show up since it's not loaded
|
// note that the template under "false" doesn't show up since it's not loaded
|
||||||
let edits = dom.rebuild().santize();
|
let edits = dom.rebuild().santize();
|
||||||
assert_eq!(
|
|
||||||
edits.templates,
|
// note: we dont test template edits anymore
|
||||||
[
|
// assert_eq!(
|
||||||
// create each template
|
// edits.templates,
|
||||||
CreateElement { name: "div" },
|
// [
|
||||||
CreateStaticText { value: "hello" },
|
// // create each template
|
||||||
AppendChildren { m: 1 },
|
// CreateElement { name: "div" },
|
||||||
SaveTemplate { m: 1, name: "template" },
|
// CreateStaticText { value: "hello" },
|
||||||
]
|
// AppendChildren { m: 1 },
|
||||||
);
|
// SaveTemplate { m: 1, name: "template" },
|
||||||
|
// ]
|
||||||
|
// );
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||||
CreatePlaceholder { id: ElementId(2) },
|
CreatePlaceholder { id: ElementId(2) },
|
||||||
AppendChildren { m: 2 }
|
AppendChildren { m: 2, id: ElementId(0) }
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
use dioxus::core::Mutation::*;
|
// use dioxus::core::Mutation::*;
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@@ -11,19 +11,22 @@ fn multiroot() {
|
|||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
||||||
assert_eq!(
|
// note: we dont test template edits anymore
|
||||||
dom.rebuild().santize().templates,
|
let _templates = dom.rebuild().santize().templates;
|
||||||
[
|
|
||||||
CreateElement { name: "div" },
|
// assert_eq!(
|
||||||
CreateStaticText { value: "Hello a" },
|
// dom.rebuild().santize().templates,
|
||||||
AppendChildren { m: 1 },
|
// [
|
||||||
CreateElement { name: "div" },
|
// CreateElement { name: "div" },
|
||||||
CreateStaticText { value: "Hello b" },
|
// CreateStaticText { value: "Hello a" },
|
||||||
AppendChildren { m: 1 },
|
// AppendChildren { m: 1 },
|
||||||
CreateElement { name: "div" },
|
// CreateElement { name: "div" },
|
||||||
CreateStaticText { value: "Hello c" },
|
// CreateStaticText { value: "Hello b" },
|
||||||
AppendChildren { m: 1 },
|
// AppendChildren { m: 1 },
|
||||||
SaveTemplate { name: "template", m: 3 }
|
// CreateElement { name: "div" },
|
||||||
]
|
// CreateStaticText { value: "Hello c" },
|
||||||
)
|
// AppendChildren { m: 1 },
|
||||||
|
// SaveTemplate { name: "template", m: 3 }
|
||||||
|
// ]
|
||||||
|
// )
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ fn empty_fragment_creates_nothing() {
|
|||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
CreatePlaceholder { id: ElementId(1) },
|
CreatePlaceholder { id: ElementId(1) },
|
||||||
AppendChildren { m: 1 }
|
AppendChildren { id: ElementId(0), m: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -33,7 +33,7 @@ fn root_fragments_work() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vdom.rebuild().edits.last().unwrap(),
|
vdom.rebuild().edits.last().unwrap(),
|
||||||
&AppendChildren { m: 2 }
|
&AppendChildren { id: ElementId(0), m: 2 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@ fn fragments_nested() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
vdom.rebuild().edits.last().unwrap(),
|
vdom.rebuild().edits.last().unwrap(),
|
||||||
&AppendChildren { m: 8 }
|
&AppendChildren { id: ElementId(0), m: 8 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -85,7 +85,7 @@ fn fragments_across_components() {
|
|||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
VirtualDom::new(app).rebuild().edits.last().unwrap(),
|
VirtualDom::new(app).rebuild().edits.last().unwrap(),
|
||||||
&AppendChildren { m: 8 }
|
&AppendChildren { id: ElementId(0), m: 8 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,6 +99,6 @@ fn list_fragments() {
|
|||||||
}
|
}
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
VirtualDom::new(app).rebuild().edits.last().unwrap(),
|
VirtualDom::new(app).rebuild().edits.last().unwrap(),
|
||||||
&AppendChildren { m: 7 }
|
&AppendChildren { id: ElementId(0), m: 7 }
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -27,28 +27,29 @@ fn list_renders() {
|
|||||||
|
|
||||||
let edits = dom.rebuild().santize();
|
let edits = dom.rebuild().santize();
|
||||||
|
|
||||||
assert_eq!(
|
// note: we dont test template edits anymore
|
||||||
edits.templates,
|
// assert_eq!(
|
||||||
[
|
// edits.templates,
|
||||||
// Create the outer div
|
// [
|
||||||
CreateElement { name: "div" },
|
// // Create the outer div
|
||||||
// todo: since this is the only child, we should just use
|
// CreateElement { name: "div" },
|
||||||
// append when modify the values (IE no need for a placeholder)
|
// // todo: since this is the only child, we should just use
|
||||||
CreateStaticPlaceholder,
|
// // append when modify the values (IE no need for a placeholder)
|
||||||
AppendChildren { m: 1 },
|
// CreateStaticPlaceholder,
|
||||||
SaveTemplate { name: "template", m: 1 },
|
// AppendChildren { m: 1 },
|
||||||
// Create the inner template div
|
// SaveTemplate { name: "template", m: 1 },
|
||||||
CreateElement { name: "div" },
|
// // Create the inner template div
|
||||||
CreateElement { name: "h1" },
|
// CreateElement { name: "div" },
|
||||||
CreateStaticText { value: "hello world! " },
|
// CreateElement { name: "h1" },
|
||||||
AppendChildren { m: 1 },
|
// CreateStaticText { value: "hello world! " },
|
||||||
CreateElement { name: "p" },
|
// AppendChildren { m: 1 },
|
||||||
CreateTextPlaceholder,
|
// CreateElement { name: "p" },
|
||||||
AppendChildren { m: 1 },
|
// CreateTextPlaceholder,
|
||||||
AppendChildren { m: 2 },
|
// AppendChildren { m: 1 },
|
||||||
SaveTemplate { name: "template", m: 1 }
|
// AppendChildren { m: 2 },
|
||||||
],
|
// SaveTemplate { name: "template", m: 1 }
|
||||||
);
|
// ],
|
||||||
|
// );
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
@@ -65,7 +66,7 @@ fn list_renders() {
|
|||||||
// Replace the 0th childn on the div with the 3 templates on the stack
|
// Replace the 0th childn on the div with the 3 templates on the stack
|
||||||
ReplacePlaceholder { m: 3, path: &[0] },
|
ReplacePlaceholder { m: 3, path: &[0] },
|
||||||
// Append the container div to the dom
|
// Append the container div to the dom
|
||||||
AppendChildren { m: 1 }
|
AppendChildren { m: 1, id: ElementId(0) }
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -29,7 +29,7 @@ fn nested_passthru_creates() {
|
|||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { m: 1, id: ElementId(0) },
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -74,7 +74,7 @@ fn nested_passthru_creates_add() {
|
|||||||
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
||||||
// load div that contains 4
|
// load div that contains 4
|
||||||
LoadTemplate { name: "template", index: 1, id: ElementId(4) },
|
LoadTemplate { name: "template", index: 1, id: ElementId(4) },
|
||||||
AppendChildren { m: 4 },
|
AppendChildren { id: ElementId(0), m: 4 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -92,7 +92,7 @@ fn dynamic_node_as_root() {
|
|||||||
let edits = dom.rebuild().santize();
|
let edits = dom.rebuild().santize();
|
||||||
|
|
||||||
// Since the roots were all dynamic, they should not cause any template muations
|
// Since the roots were all dynamic, they should not cause any template muations
|
||||||
assert_eq!(edits.templates, []);
|
assert!(edits.templates.is_empty());
|
||||||
|
|
||||||
// The root node is text, so we just create it on the spot
|
// The root node is text, so we just create it on the spot
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -100,7 +100,7 @@ fn dynamic_node_as_root() {
|
|||||||
[
|
[
|
||||||
CreateTextNode { value: "123", id: ElementId(1) },
|
CreateTextNode { value: "123", id: ElementId(1) },
|
||||||
CreateTextNode { value: "456", id: ElementId(2) },
|
CreateTextNode { value: "456", id: ElementId(2) },
|
||||||
AppendChildren { m: 2 }
|
AppendChildren { id: ElementId(0), m: 2 }
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -17,7 +17,7 @@ fn cycling_elements() {
|
|||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { m: 1, id: ElementId(0) },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ fn component_swap() {
|
|||||||
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
||||||
ReplacePlaceholder { path: &[1], m: 3 },
|
ReplacePlaceholder { path: &[1], m: 3 },
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||||
AppendChildren { m: 2 }
|
AppendChildren { m: 2, id: ElementId(0) }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ fn keyed_diffing_out_of_order() {
|
|||||||
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
|
||||||
AppendChildren { m: 10 },
|
AppendChildren { m: 10, id: ElementId(0) },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -21,7 +21,7 @@ fn list_creates_one_by_one() {
|
|||||||
[
|
[
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||||
AssignId { path: &[0], id: ElementId(2,) },
|
AssignId { path: &[0], id: ElementId(2,) },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { id: ElementId(0), m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -100,7 +100,7 @@ fn removes_one_by_one() {
|
|||||||
// replace the placeholder in the template with the 3 templates on the stack
|
// replace the placeholder in the template with the 3 templates on the stack
|
||||||
ReplacePlaceholder { m: 3, path: &[0] },
|
ReplacePlaceholder { m: 3, path: &[0] },
|
||||||
// Mount the div
|
// Mount the div
|
||||||
AppendChildren { m: 1 }
|
AppendChildren { id: ElementId(0), m: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -165,7 +165,7 @@ fn list_shrink_multiroot() {
|
|||||||
[
|
[
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||||
AssignId { path: &[0,], id: ElementId(2,) },
|
AssignId { path: &[0,], id: ElementId(2,) },
|
||||||
AppendChildren { m: 1 }
|
AppendChildren { id: ElementId(0), m: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -244,7 +244,7 @@ fn removes_one_by_one_multiroot() {
|
|||||||
//
|
//
|
||||||
ReplacePlaceholder { path: &[0], m: 6 },
|
ReplacePlaceholder { path: &[0], m: 6 },
|
||||||
//
|
//
|
||||||
AppendChildren { m: 1 }
|
AppendChildren { id: ElementId(0), m: 1 }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -320,7 +320,7 @@ fn remove_many() {
|
|||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
CreatePlaceholder { id: ElementId(1,) },
|
CreatePlaceholder { id: ElementId(1,) },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { id: ElementId(0), m: 1 },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
|||||||
@@ -29,42 +29,6 @@ fn dual_stream() {
|
|||||||
let edits = dom.rebuild().santize();
|
let edits = dom.rebuild().santize();
|
||||||
|
|
||||||
use Mutation::*;
|
use Mutation::*;
|
||||||
assert_eq!(
|
|
||||||
edits.templates,
|
|
||||||
[
|
|
||||||
CreateElement { name: "div" },
|
|
||||||
SetStaticAttribute { name: "class", value: "asd", ns: None },
|
|
||||||
CreateElement { name: "div" },
|
|
||||||
CreateTextPlaceholder,
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
CreateElement { name: "div" },
|
|
||||||
CreateElement { name: "h1" },
|
|
||||||
CreateStaticText { value: "var" },
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
CreateElement { name: "p" },
|
|
||||||
CreateStaticText { value: "you're great!" },
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
CreateElement { name: "div" },
|
|
||||||
SetStaticAttribute { name: "background-color", value: "red", ns: Some("style") },
|
|
||||||
CreateElement { name: "h1" },
|
|
||||||
CreateStaticText { value: "var" },
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
CreateElement { name: "div" },
|
|
||||||
CreateElement { name: "b" },
|
|
||||||
CreateStaticText { value: "asd" },
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
CreateStaticText { value: "not great" },
|
|
||||||
AppendChildren { m: 2 },
|
|
||||||
AppendChildren { m: 2 },
|
|
||||||
CreateElement { name: "p" },
|
|
||||||
CreateStaticText { value: "you're great!" },
|
|
||||||
AppendChildren { m: 1 },
|
|
||||||
AppendChildren { m: 4 },
|
|
||||||
AppendChildren { m: 2 },
|
|
||||||
SaveTemplate { name: "template", m: 1 }
|
|
||||||
],
|
|
||||||
);
|
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
edits.edits,
|
edits.edits,
|
||||||
[
|
[
|
||||||
@@ -72,7 +36,7 @@ fn dual_stream() {
|
|||||||
SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
|
SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
|
||||||
NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) },
|
NewEventListener { name: "click", scope: ScopeId(0), id: ElementId(1) },
|
||||||
HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
|
HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
|
||||||
AppendChildren { m: 1 }
|
AppendChildren { id: ElementId(0), m: 1 }
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@@ -32,7 +32,7 @@ fn manual_diffing() {
|
|||||||
[
|
[
|
||||||
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
||||||
HydrateText { path: &[0], value: "goodbye", id: ElementId(4) },
|
HydrateText { path: &[0], value: "goodbye", id: ElementId(4) },
|
||||||
AppendChildren { m: 1 }
|
AppendChildren { m: 1, id: ElementId(0) }
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use dioxus::core::ElementId;
|
use dioxus::core::ElementId;
|
||||||
use dioxus::core::{Mutation::*, SuspenseBoundary};
|
use dioxus::core::{Mutation::*, SuspenseContext};
|
||||||
use dioxus::prelude::*;
|
use dioxus::prelude::*;
|
||||||
use dioxus_core::SuspenseContext;
|
use dioxus_core::SuspenseContext;
|
||||||
use std::future::IntoFuture;
|
use std::future::IntoFuture;
|
||||||
@@ -12,16 +12,17 @@ async fn it_works() {
|
|||||||
let mutations = dom.rebuild().santize();
|
let mutations = dom.rebuild().santize();
|
||||||
|
|
||||||
// We should at least get the top-level template in before pausing for the children
|
// We should at least get the top-level template in before pausing for the children
|
||||||
assert_eq!(
|
// note: we dont test template edits anymore
|
||||||
mutations.templates,
|
// assert_eq!(
|
||||||
[
|
// mutations.templates,
|
||||||
CreateElement { name: "div" },
|
// [
|
||||||
CreateStaticText { value: "Waiting for child..." },
|
// CreateElement { name: "div" },
|
||||||
CreateStaticPlaceholder,
|
// CreateStaticText { value: "Waiting for child..." },
|
||||||
AppendChildren { m: 2 },
|
// CreateStaticPlaceholder,
|
||||||
SaveTemplate { name: "template", m: 1 }
|
// AppendChildren { m: 2 },
|
||||||
]
|
// SaveTemplate { name: "template", m: 1 }
|
||||||
);
|
// ]
|
||||||
|
// );
|
||||||
|
|
||||||
// And we should load it in and assign the placeholder properly
|
// And we should load it in and assign the placeholder properly
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
@@ -31,7 +32,7 @@ async fn it_works() {
|
|||||||
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
|
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
|
||||||
// can we even?
|
// can we even?
|
||||||
AssignId { path: &[1], id: ElementId(3) },
|
AssignId { path: &[1], id: ElementId(3) },
|
||||||
AppendChildren { m: 1 },
|
AppendChildren { m: 1, id: ElementId(0) },
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -50,7 +51,7 @@ fn app(cx: Scope) -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn suspense_boundary(cx: Scope) -> Element {
|
fn suspense_boundary(cx: Scope) -> Element {
|
||||||
cx.use_hook(|| cx.provide_context(Rc::new(SuspenseBoundary::new(cx.scope_id()))));
|
cx.use_hook(|| cx.provide_context(Rc::new(SuspenseContext::new(cx.scope_id()))));
|
||||||
|
|
||||||
// Ensure the right types are found
|
// Ensure the right types are found
|
||||||
cx.has_context::<SuspenseContext>().unwrap();
|
cx.has_context::<SuspenseContext>().unwrap();
|
||||||
|
|||||||
@@ -21,14 +21,17 @@ macro_rules! to_owned {
|
|||||||
)*}
|
)*}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
mod usecontext;
|
||||||
|
pub use usecontext::*;
|
||||||
|
|
||||||
mod usestate;
|
mod usestate;
|
||||||
pub use usestate::{use_state, UseState};
|
pub use usestate::{use_state, UseState};
|
||||||
|
|
||||||
mod useref;
|
mod useref;
|
||||||
pub use useref::*;
|
pub use useref::*;
|
||||||
|
|
||||||
mod use_shared_state;
|
// mod use_shared_state;
|
||||||
pub use use_shared_state::*;
|
// pub use use_shared_state::*;
|
||||||
|
|
||||||
mod usecoroutine;
|
mod usecoroutine;
|
||||||
pub use usecoroutine::*;
|
pub use usecoroutine::*;
|
||||||
|
|||||||
@@ -63,7 +63,7 @@ impl<T> ProvidedStateInner<T> {
|
|||||||
pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<UseSharedState<T>> {
|
pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<UseSharedState<T>> {
|
||||||
let state = cx.use_hook(|| {
|
let state = cx.use_hook(|| {
|
||||||
let scope_id = cx.scope_id();
|
let scope_id = cx.scope_id();
|
||||||
let root = cx.consume_context::<ProvidedState<T>>();
|
let root = cx.consume_context::<ProvidedState<T>>().cloned();
|
||||||
|
|
||||||
if let Some(root) = root.as_ref() {
|
if let Some(root) = root.as_ref() {
|
||||||
root.borrow_mut().consumers.insert(scope_id);
|
root.borrow_mut().consumers.insert(scope_id);
|
||||||
@@ -179,6 +179,7 @@ pub fn use_context_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T)
|
|||||||
notify_any: cx.schedule_update_any(),
|
notify_any: cx.schedule_update_any(),
|
||||||
consumers: HashSet::new(),
|
consumers: HashSet::new(),
|
||||||
}));
|
}));
|
||||||
cx.provide_context(state)
|
|
||||||
|
cx.provide_context(state);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,17 @@
|
|||||||
|
use dioxus_core::ScopeState;
|
||||||
|
|
||||||
|
/// Consume some context in the tree
|
||||||
|
pub fn use_context<T: 'static>(cx: &ScopeState) -> Option<&T> {
|
||||||
|
match *cx.use_hook(|| cx.consume_context::<T>().map(|t| t as *const T)) {
|
||||||
|
Some(res) => Some(unsafe { &*res }),
|
||||||
|
None => None,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Provide some context via the tree and return a reference to it
|
||||||
|
///
|
||||||
|
/// Once the context has been provided, it is immutable. Mutations should be done via interior mutability.
|
||||||
|
pub fn use_context_provider<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> &T {
|
||||||
|
let ptr = *cx.use_hook(|| cx.provide_context(f()) as *const T);
|
||||||
|
unsafe { &*ptr }
|
||||||
|
}
|
||||||
@@ -2,6 +2,8 @@ use dioxus_core::{ScopeState, TaskId};
|
|||||||
pub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
pub use futures_channel::mpsc::{UnboundedReceiver, UnboundedSender};
|
||||||
use std::future::Future;
|
use std::future::Future;
|
||||||
|
|
||||||
|
use crate::{use_context, use_context_provider};
|
||||||
|
|
||||||
/// Maintain a handle over a future that can be paused, resumed, and canceled.
|
/// Maintain a handle over a future that can be paused, resumed, and canceled.
|
||||||
///
|
///
|
||||||
/// This is an upgraded form of [`use_future`] with an integrated channel system.
|
/// This is an upgraded form of [`use_future`] with an integrated channel system.
|
||||||
@@ -59,33 +61,33 @@ use std::future::Future;
|
|||||||
/// }
|
/// }
|
||||||
/// })
|
/// })
|
||||||
/// ```
|
/// ```
|
||||||
pub fn use_coroutine<M, G, F>(cx: &ScopeState, init: G) -> &CoroutineHandle<M>
|
pub fn use_coroutine<M, G, F>(cx: &ScopeState, init: G) -> &Coroutine<M>
|
||||||
where
|
where
|
||||||
M: 'static,
|
M: 'static,
|
||||||
G: FnOnce(UnboundedReceiver<M>) -> F,
|
G: FnOnce(UnboundedReceiver<M>) -> F,
|
||||||
F: Future<Output = ()> + 'static,
|
F: Future<Output = ()> + 'static,
|
||||||
{
|
{
|
||||||
cx.use_hook(|| {
|
use_context_provider(cx, || {
|
||||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||||
let task = cx.push_future(init(rx));
|
let task = cx.push_future(init(rx));
|
||||||
cx.provide_context(CoroutineHandle { tx, task })
|
Coroutine { tx, task }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get a handle to a coroutine higher in the tree
|
/// Get a handle to a coroutine higher in the tree
|
||||||
///
|
///
|
||||||
/// See the docs for [`use_coroutine`] for more details.
|
/// See the docs for [`use_coroutine`] for more details.
|
||||||
pub fn use_coroutine_handle<M: 'static>(cx: &ScopeState) -> Option<&CoroutineHandle<M>> {
|
pub fn use_coroutine_handle<M: 'static>(cx: &ScopeState) -> Option<&Coroutine<M>> {
|
||||||
cx.use_hook(|| cx.consume_context::<CoroutineHandle<M>>())
|
use_context::<Coroutine<M>>(cx)
|
||||||
.as_ref()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct CoroutineHandle<T> {
|
pub struct Coroutine<T> {
|
||||||
tx: UnboundedSender<T>,
|
tx: UnboundedSender<T>,
|
||||||
task: TaskId,
|
task: TaskId,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> Clone for CoroutineHandle<T> {
|
// for use in futures
|
||||||
|
impl<T> Clone for Coroutine<T> {
|
||||||
fn clone(&self) -> Self {
|
fn clone(&self) -> Self {
|
||||||
Self {
|
Self {
|
||||||
tx: self.tx.clone(),
|
tx: self.tx.clone(),
|
||||||
@@ -94,7 +96,7 @@ impl<T> Clone for CoroutineHandle<T> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<T> CoroutineHandle<T> {
|
impl<T> Coroutine<T> {
|
||||||
/// Get the ID of this coroutine
|
/// Get the ID of this coroutine
|
||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn task_id(&self) -> TaskId {
|
pub fn task_id(&self) -> TaskId {
|
||||||
@@ -112,8 +114,8 @@ mod tests {
|
|||||||
#![allow(unused)]
|
#![allow(unused)]
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
use futures_channel::mpsc::unbounded;
|
||||||
use futures_util::StreamExt;
|
use futures_util::StreamExt;
|
||||||
|
|
||||||
fn app(cx: Scope, name: String) -> Element {
|
fn app(cx: Scope, name: String) -> Element {
|
||||||
@@ -127,7 +129,7 @@ mod tests {
|
|||||||
|
|
||||||
let task3 = use_coroutine(&cx, |rx| complex_task(rx, 10));
|
let task3 = use_coroutine(&cx, |rx| complex_task(rx, 10));
|
||||||
|
|
||||||
None
|
todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn view_task(mut rx: UnboundedReceiver<i32>) {
|
async fn view_task(mut rx: UnboundedReceiver<i32>) {
|
||||||
|
|||||||
@@ -86,7 +86,7 @@ mod tests {
|
|||||||
//
|
//
|
||||||
});
|
});
|
||||||
|
|
||||||
None
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -343,7 +343,7 @@ mod tests {
|
|||||||
|
|
||||||
let g = fut.await;
|
let g = fut.await;
|
||||||
|
|
||||||
None
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -466,7 +466,7 @@ fn api_makes_sense() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
cx.spawn({
|
cx.spawn({
|
||||||
dioxus_core::to_owned![val];
|
to_owned![val];
|
||||||
async move {
|
async move {
|
||||||
val.modify(|f| f + 1);
|
val.modify(|f| f + 1);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ macro_rules! impl_event {
|
|||||||
pub fn $name<'a>(_cx: &'a ::dioxus_core::ScopeState, _f: impl FnMut(::dioxus_core::Event<$data>) + 'a) -> ::dioxus_core::Attribute<'a> {
|
pub fn $name<'a>(_cx: &'a ::dioxus_core::ScopeState, _f: impl FnMut(::dioxus_core::Event<$data>) + 'a) -> ::dioxus_core::Attribute<'a> {
|
||||||
::dioxus_core::Attribute {
|
::dioxus_core::Attribute {
|
||||||
name: stringify!($name),
|
name: stringify!($name),
|
||||||
value: ::dioxus_core::AttributeValue::new_listener(_cx, _f),
|
value: _cx.listener(_f),
|
||||||
namespace: None,
|
namespace: None,
|
||||||
mounted_element: Default::default(),
|
mounted_element: Default::default(),
|
||||||
volatile: false,
|
volatile: false,
|
||||||
|
|||||||
@@ -130,7 +130,7 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
|||||||
|
|
||||||
out_tokens.append_all(quote! {
|
out_tokens.append_all(quote! {
|
||||||
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
static TEMPLATE: ::dioxus::core::Template = ::dioxus::core::Template {
|
||||||
id: concat!(
|
name: concat!(
|
||||||
file!(),
|
file!(),
|
||||||
":",
|
":",
|
||||||
line!(),
|
line!(),
|
||||||
@@ -187,8 +187,10 @@ impl<'a> DynamicContext<'a> {
|
|||||||
::dioxus::core::TemplateAttribute::Static {
|
::dioxus::core::TemplateAttribute::Static {
|
||||||
name: dioxus_elements::#el_name::#name.0,
|
name: dioxus_elements::#el_name::#name.0,
|
||||||
namespace: dioxus_elements::#el_name::#name.1,
|
namespace: dioxus_elements::#el_name::#name.1,
|
||||||
volatile: dioxus_elements::#el_name::#name.2,
|
|
||||||
value: #value,
|
value: #value,
|
||||||
|
|
||||||
|
// todo: we don't diff these so we never apply the volatile flag
|
||||||
|
// volatile: dioxus_elements::#el_name::#name.2,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -199,8 +201,10 @@ impl<'a> DynamicContext<'a> {
|
|||||||
::dioxus::core::TemplateAttribute::Static {
|
::dioxus::core::TemplateAttribute::Static {
|
||||||
name: dioxus_elements::#el_name::#name.0,
|
name: dioxus_elements::#el_name::#name.0,
|
||||||
namespace: dioxus_elements::#el_name::#name.1,
|
namespace: dioxus_elements::#el_name::#name.1,
|
||||||
volatile: dioxus_elements::#el_name::#name.2,
|
|
||||||
value: #value,
|
value: #value,
|
||||||
|
|
||||||
|
// todo: we don't diff these so we never apply the volatile flag
|
||||||
|
// volatile: dioxus_elements::#el_name::#name.2,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -226,7 +230,7 @@ impl<'a> DynamicContext<'a> {
|
|||||||
out
|
out
|
||||||
});
|
});
|
||||||
|
|
||||||
let opt = el.children.len() == 1;
|
let _opt = el.children.len() == 1;
|
||||||
let children = quote! { #(#children),* };
|
let children = quote! { #(#children),* };
|
||||||
|
|
||||||
quote! {
|
quote! {
|
||||||
@@ -235,7 +239,6 @@ impl<'a> DynamicContext<'a> {
|
|||||||
namespace: dioxus_elements::#el_name::NAME_SPACE,
|
namespace: dioxus_elements::#el_name::NAME_SPACE,
|
||||||
attrs: &[ #attrs ],
|
attrs: &[ #attrs ],
|
||||||
children: &[ #children ],
|
children: &[ #children ],
|
||||||
inner_opt: #opt,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,7 +35,7 @@ impl SsrRender {
|
|||||||
) -> std::fmt::Result {
|
) -> std::fmt::Result {
|
||||||
let entry = self
|
let entry = self
|
||||||
.template_cache
|
.template_cache
|
||||||
.entry(template.template.id)
|
.entry(template.template.name)
|
||||||
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
|
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
|
||||||
.clone();
|
.clone();
|
||||||
|
|
||||||
|
|||||||
@@ -59,7 +59,7 @@ impl WebsysDom {
|
|||||||
roots.push(self.create_template_node(root))
|
roots.push(self.create_template_node(root))
|
||||||
}
|
}
|
||||||
|
|
||||||
self.interpreter.SaveTemplate(roots, template.id);
|
self.interpreter.SaveTemplate(roots, template.name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -77,13 +77,11 @@ impl WebsysDom {
|
|||||||
Some(ns) => self.document.create_element_ns(Some(ns), tag).unwrap(),
|
Some(ns) => self.document.create_element_ns(Some(ns), tag).unwrap(),
|
||||||
None => self.document.create_element(tag).unwrap(),
|
None => self.document.create_element(tag).unwrap(),
|
||||||
};
|
};
|
||||||
|
|
||||||
for attr in *attrs {
|
for attr in *attrs {
|
||||||
if let TemplateAttribute::Static {
|
if let TemplateAttribute::Static {
|
||||||
name,
|
name,
|
||||||
value,
|
value,
|
||||||
namespace,
|
namespace,
|
||||||
volatile,
|
|
||||||
} = attr
|
} = attr
|
||||||
{
|
{
|
||||||
match namespace {
|
match namespace {
|
||||||
@@ -98,14 +96,11 @@ impl WebsysDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
for child in *children {
|
for child in *children {
|
||||||
el.append_child(&self.create_template_node(child));
|
el.append_child(&self.create_template_node(child));
|
||||||
}
|
}
|
||||||
|
|
||||||
el.dyn_into().unwrap()
|
el.dyn_into().unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
Text(t) => self.document.create_text_node(t).dyn_into().unwrap(),
|
Text(t) => self.document.create_text_node(t).dyn_into().unwrap(),
|
||||||
DynamicText(_) => self.document.create_text_node("p").dyn_into().unwrap(),
|
DynamicText(_) => self.document.create_text_node("p").dyn_into().unwrap(),
|
||||||
Dynamic(_) => {
|
Dynamic(_) => {
|
||||||
|
|||||||
Reference in new issue
Block a user