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