feat: context api overhaul

This commit is contained in:
Jonathan Kelley committed 2022-12-02 16:24:49 -08:00
1 parent 11a45c8ef0
commit a61daf220d
44 files changed
+812 -621

No files matched your search

+1 -1
View File
@@ -1,8 +1,8 @@
use std::marker::PhantomData;
use crate::{
factory::{ComponentReturn, RenderReturn},
innerlude::Scoped,
nodes::{ComponentReturn, RenderReturn},
scopes::{Scope, ScopeState},
Element,
};
+5 -1
View File
@@ -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);
+3 -3
View File
@@ -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() {
+5 -6
View File
@@ -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,
};
+3 -3
View File
@@ -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);
}
-197
View File
@@ -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())
}
}
+11 -12
View File
@@ -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
+1 -1
View File
@@ -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
+4 -5
View File
@@ -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
View File
@@ -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
View File
@@ -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())
}
}
+11 -11
View File
@@ -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
View File
@@ -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);
+14 -7
View File
@@ -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");
+2 -4
View File
@@ -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
View File
@@ -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`.
+9 -5
View File
@@ -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),
+1 -1
View File
@@ -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) },
]
);
+1 -1
View File
@@ -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) },
]
)
}
+1 -1
View File
@@ -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) },
]
)
}
+1 -1
View File
@@ -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) },
]
);
+69 -89
View File
@@ -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) }
]
)
}
+19 -16
View File
@@ -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 }
// ]
// )
}
+5 -5
View File
@@ -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 }
);
}
+24 -23
View File
@@ -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) }
],
)
}
+4 -4
View File
@@ -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 }
]
)
}
+1 -1
View File
@@ -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) },
]
);
+1 -1
View File
@@ -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) }
]
);
+1 -1
View File
@@ -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) },
]
);
+5 -5
View File
@@ -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 },
]
);
+1 -37
View File
@@ -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 }
],
);
}
+1 -1
View File
@@ -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) }
]
);
}
+14 -13
View File
@@ -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();
+5 -2
View File
@@ -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::*;
+3 -2
View File
@@ -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);
});
}
+17
View File
@@ -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 }
}
+13 -11
View File
@@ -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>) {
+1 -1
View File
@@ -86,7 +86,7 @@ mod tests {
//
});
None
todo!()
}
}
}
+1 -1
View File
@@ -343,7 +343,7 @@ mod tests {
let g = fut.await;
None
todo!()
}
}
}
+1 -1
View File
@@ -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);
}
+1 -1
View File
@@ -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,
+8 -5
View File
@@ -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,
}
}
}
+1 -1
View File
@@ -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();
+1 -6
View File
@@ -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(_) => {