Update custom renderer guide

This commit is contained in:
Evan Almloff committed 2023-03-14 16:06:32 -05:00
1 parent ae1300b015
commit 4b81cdbf1b
8 files changed
+870 -212

No files matched your search

+25
View File
@@ -11,6 +11,11 @@ use syn::{parse_macro_input, ItemImpl, Type, TypePath, TypeTuple};
pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream {
let impl_block: syn::ItemImpl = parse_macro_input!(input as syn::ItemImpl);
let has_create_fn = impl_block
.items
.iter()
.any(|item| matches!(item, syn::ImplItem::Method(method) if method.sig.ident == "create"));
let parent_dependencies = impl_block
.items
.iter()
@@ -295,9 +300,29 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream {
.arguments
.clone();
// if a create function is defined, we don't generate one
// otherwise we generate a default one that uses the update function and the default constructor
let create_fn = (!has_create_fn).then(||{
quote!{
fn create<'a>(
node_view: dioxus_native_core::prelude::NodeView<()>,
node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
context: &dioxus_native_core::prelude::SendAnyMap,
) -> Self {
let mut myself = Self::default();
myself.update(node_view, node, parent, children, context);
myself
}
}
});
quote!(
#(#attrs)*
#defaultness #unsafety #impl_token #generics #trait_ #for_ #self_ty {
#create_fn
#(#items)*
fn workload_system(type_id: std::any::TypeId, dependants: dioxus_native_core::exports::FxHashSet<std::any::TypeId>, pass_direction: dioxus_native_core::prelude::PassDirection) -> dioxus_native_core::exports::shipyard::WorkloadSystem {
+222
View File
@@ -0,0 +1,222 @@
use dioxus_native_core::exports::shipyard::Component;
use dioxus_native_core::node_ref::*;
use dioxus_native_core::prelude::*;
use dioxus_native_core::real_dom::NodeTypeMut;
use dioxus_native_core_macro::partial_derive_state;
struct FontSize(f64);
// All states need to derive Component
#[derive(Default, Debug, Copy, Clone, Component)]
struct Size(f64, f64);
/// Derive some of the boilerplate for the State implementation
#[partial_derive_state]
impl State for Size {
type ParentDependencies = ();
// The size of the current node depends on the size of its children
type ChildDependencies = (Self,);
type NodeDependencies = ();
// Size only cares about the width, height, and text parts of the current node
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
// Get access to the width and height attributes
.with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
// Get access to the text of the node
.with_text();
fn update<'a>(
&mut self,
node_view: NodeView<()>,
_node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
_parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
context: &SendAnyMap,
) -> bool {
let font_size = context.get::<FontSize>().unwrap().0;
let mut width;
let mut height;
if let Some(text) = node_view.text() {
// if the node has text, use the text to size our object
width = text.len() as f64 * font_size;
height = font_size;
} else {
// otherwise, the size is the maximum size of the children
width = children
.iter()
.map(|(item,)| item.0)
.reduce(|accum, item| if accum >= item { accum } else { item })
.unwrap_or(0.0);
height = children
.iter()
.map(|(item,)| item.1)
.reduce(|accum, item| if accum >= item { accum } else { item })
.unwrap_or(0.0);
}
// if the node contains a width or height attribute it overrides the other size
for a in node_view.attributes().into_iter().flatten() {
match &*a.attribute.name {
"width" => width = a.value.as_float().unwrap(),
"height" => height = a.value.as_float().unwrap(),
// because Size only depends on the width and height, no other attributes will be passed to the member
_ => panic!(),
}
}
// to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
let changed = (width != self.0) || (height != self.1);
*self = Self(width, height);
changed
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
struct TextColor {
r: u8,
g: u8,
b: u8,
}
#[partial_derive_state]
impl State for TextColor {
// TextColor depends on the TextColor part of the parent
type ParentDependencies = (Self,);
type ChildDependencies = ();
type NodeDependencies = ();
// TextColor only cares about the color attribute of the current node
const NODE_MASK: NodeMaskBuilder<'static> =
// Get access to the color attribute
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"]));
fn update<'a>(
&mut self,
node_view: NodeView<()>,
_node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
_children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
_context: &SendAnyMap,
) -> bool {
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
let new = match node_view
.attributes()
.and_then(|mut attrs| attrs.next())
.and_then(|attr| attr.value.as_text())
{
// if there is a color tag, translate it
Some("red") => TextColor { r: 255, g: 0, b: 0 },
Some("green") => TextColor { r: 0, g: 255, b: 0 },
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
Some(color) => panic!("unknown color {color}"),
// otherwise check if the node has a parent and inherit that color
None => match parent {
Some((parent,)) => *parent,
None => Self::default(),
},
};
// check if the member has changed
let changed = new != *self;
*self = new;
changed
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
struct Border(bool);
#[partial_derive_state]
impl State for Border {
// TextColor depends on the TextColor part of the parent
type ParentDependencies = (Self,);
type ChildDependencies = ();
type NodeDependencies = ();
// Border does not depended on any other member in the current node
const NODE_MASK: NodeMaskBuilder<'static> =
// Get access to the border attribute
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
fn update<'a>(
&mut self,
node_view: NodeView<()>,
_node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
_parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
_children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
_context: &SendAnyMap,
) -> bool {
// check if the node contians a border attribute
let new = Self(
node_view
.attributes()
.and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
.is_some(),
);
// check if the member has changed
let changed = new != *self;
*self = new;
changed
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let mut rdom: RealDom = RealDom::new([
Border::to_type_erased(),
TextColor::to_type_erased(),
Size::to_type_erased(),
]);
let mut count = 0;
// intial render
let text_id = rdom.create_node(format!("Count: {count}")).id();
let mut root = rdom.get_mut(rdom.root_id()).unwrap();
// set the color to red
if let NodeTypeMut::Element(mut element) = root.node_type_mut() {
element.set_attribute(("color", "style"), "red".to_string());
}
root.add_child(text_id);
let mut ctx = SendAnyMap::new();
// set the font size to 3.3
ctx.insert(FontSize(3.3));
// update the State for nodes in the real_dom tree
let _to_rerender = rdom.update_state(ctx);
// we need to run the vdom in a async runtime
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(async {
loop {
// update the count
count += 1;
let mut text = rdom.get_mut(text_id).unwrap();
if let NodeTypeMut::Text(mut text) = text.node_type_mut() {
*text = format!("Count: {count}");
}
let mut ctx = SendAnyMap::new();
ctx.insert(FontSize(3.3));
let _to_rerender = rdom.update_state(ctx);
// render...
rdom.traverse_depth_first(|node| {
let indent = " ".repeat(node.height() as usize);
let color = *node.get::<TextColor>().unwrap();
let size = *node.get::<Size>().unwrap();
let border = *node.get::<Border>().unwrap();
let id = node.id();
println!("{indent}{id:?} {color:?} {size:?} {border:?}");
});
// wait 1 second
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
}
})
}
@@ -0,0 +1,248 @@
use dioxus::prelude::*;
use dioxus_native_core::exports::shipyard::Component;
use dioxus_native_core::node_ref::*;
use dioxus_native_core::prelude::*;
use dioxus_native_core_macro::partial_derive_state;
struct FontSize(f64);
// All states need to derive Component
#[derive(Default, Debug, Copy, Clone, Component)]
struct Size(f64, f64);
/// Derive some of the boilerplate for the State implementation
#[partial_derive_state]
impl State for Size {
type ParentDependencies = ();
// The size of the current node depends on the size of its children
type ChildDependencies = (Self,);
type NodeDependencies = ();
// Size only cares about the width, height, and text parts of the current node
const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new()
// Get access to the width and height attributes
.with_attrs(AttributeMaskBuilder::Some(&["width", "height"]))
// Get access to the text of the node
.with_text();
fn update<'a>(
&mut self,
node_view: NodeView<()>,
_node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
_parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
context: &SendAnyMap,
) -> bool {
let font_size = context.get::<FontSize>().unwrap().0;
let mut width;
let mut height;
if let Some(text) = node_view.text() {
// if the node has text, use the text to size our object
width = text.len() as f64 * font_size;
height = font_size;
} else {
// otherwise, the size is the maximum size of the children
width = children
.iter()
.map(|(item,)| item.0)
.reduce(|accum, item| if accum >= item { accum } else { item })
.unwrap_or(0.0);
height = children
.iter()
.map(|(item,)| item.1)
.reduce(|accum, item| if accum >= item { accum } else { item })
.unwrap_or(0.0);
}
// if the node contains a width or height attribute it overrides the other size
for a in node_view.attributes().into_iter().flatten() {
match &*a.attribute.name {
"width" => width = a.value.as_float().unwrap(),
"height" => height = a.value.as_float().unwrap(),
// because Size only depends on the width and height, no other attributes will be passed to the member
_ => panic!(),
}
}
// to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed
let changed = (width != self.0) || (height != self.1);
*self = Self(width, height);
changed
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
struct TextColor {
r: u8,
g: u8,
b: u8,
}
#[partial_derive_state]
impl State for TextColor {
// TextColor depends on the TextColor part of the parent
type ParentDependencies = (Self,);
type ChildDependencies = ();
type NodeDependencies = ();
// TextColor only cares about the color attribute of the current node
const NODE_MASK: NodeMaskBuilder<'static> =
// Get access to the color attribute
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"]));
fn update<'a>(
&mut self,
node_view: NodeView<()>,
_node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
_children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
_context: &SendAnyMap,
) -> bool {
// TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags
let new = match node_view
.attributes()
.and_then(|mut attrs| attrs.next())
.and_then(|attr| attr.value.as_text())
{
// if there is a color tag, translate it
Some("red") => TextColor { r: 255, g: 0, b: 0 },
Some("green") => TextColor { r: 0, g: 255, b: 0 },
Some("blue") => TextColor { r: 0, g: 0, b: 255 },
Some(color) => panic!("unknown color {color}"),
// otherwise check if the node has a parent and inherit that color
None => match parent {
Some((parent,)) => *parent,
None => Self::default(),
},
};
// check if the member has changed
let changed = new != *self;
*self = new;
changed
}
}
#[derive(Debug, Clone, Copy, PartialEq, Default, Component)]
struct Border(bool);
#[partial_derive_state]
impl State for Border {
// TextColor depends on the TextColor part of the parent
type ParentDependencies = (Self,);
type ChildDependencies = ();
type NodeDependencies = ();
// Border does not depended on any other member in the current node
const NODE_MASK: NodeMaskBuilder<'static> =
// Get access to the border attribute
NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"]));
fn update<'a>(
&mut self,
node_view: NodeView<()>,
_node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
_parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
_children: Vec<<Self::ChildDependencies as Dependancy>::ElementBorrowed<'a>>,
_context: &SendAnyMap,
) -> bool {
// check if the node contians a border attribute
let new = Self(
node_view
.attributes()
.and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border"))
.is_some(),
);
// check if the member has changed
let changed = new != *self;
*self = new;
changed
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
fn app(cx: Scope) -> Element {
let count = use_state(cx, || 0);
use_future(cx, (count,), |(count,)| async move {
loop {
tokio::time::sleep(std::time::Duration::from_secs(1)).await;
count.set(*count + 1);
}
});
cx.render(rsx! {
div{
color: "red",
"{count}",
Comp {}
}
})
}
fn Comp(cx: Scope) -> Element {
cx.render(rsx! {
div{
border: "",
"hello world"
}
})
}
// create the vdom, the real_dom, and the binding layer between them
let mut vdom = VirtualDom::new(app);
let mut rdom: RealDom = RealDom::new([
Border::to_type_erased(),
TextColor::to_type_erased(),
Size::to_type_erased(),
]);
let mut dioxus_intigration_state = DioxusState::create(&mut rdom);
let mutations = vdom.rebuild();
// update the structure of the real_dom tree
dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
let mut ctx = SendAnyMap::new();
// set the font size to 3.3
ctx.insert(FontSize(3.3));
// update the State for nodes in the real_dom tree
let _to_rerender = rdom.update_state(ctx);
// we need to run the vdom in a async runtime
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(async {
loop {
// wait for the vdom to update
vdom.wait_for_work().await;
// get the mutations from the vdom
let mutations = vdom.render_immediate();
// update the structure of the real_dom tree
dioxus_intigration_state.apply_mutations(&mut rdom, mutations);
// update the state of the real_dom tree
let mut ctx = SendAnyMap::new();
// set the font size to 3.3
ctx.insert(FontSize(3.3));
let _to_rerender = rdom.update_state(ctx);
// render...
rdom.traverse_depth_first(|node| {
let indent = " ".repeat(node.height() as usize);
let color = *node.get::<TextColor>().unwrap();
let size = *node.get::<Size>().unwrap();
let border = *node.get::<Border>().unwrap();
let id = node.id();
let node = node.node_type();
let node_type = &*node;
println!("{indent}{id:?} {color:?} {size:?} {border:?} {node_type:?}");
});
}
})
}
+32 -2
View File
@@ -20,6 +20,18 @@ pub struct ElementNode<V: FromAnyValue = ()> {
pub listeners: FxHashSet<String>,
}
impl ElementNode {
/// Create a new element node
pub fn new(tag: impl Into<String>, namespace: impl Into<Option<String>>) -> Self {
Self {
tag: tag.into(),
namespace: namespace.into(),
attributes: Default::default(),
listeners: Default::default(),
}
}
}
/// A text node in the RealDom
#[derive(Debug, Clone, Default)]
pub struct TextNode {
@@ -50,6 +62,24 @@ pub enum NodeType<V: FromAnyValue = ()> {
Placeholder,
}
impl<S: Into<String>> From<S> for NodeType {
fn from(text: S) -> Self {
Self::Text(TextNode::new(text.into()))
}
}
impl From<TextNode> for NodeType {
fn from(text: TextNode) -> Self {
Self::Text(text)
}
}
impl<V: FromAnyValue> From<ElementNode<V>> for NodeType<V> {
fn from(element: ElementNode<V>) -> Self {
Self::Element(element)
}
}
/// A discription of an attribute on a DOM node, such as `id` or `href`.
#[derive(Debug, Clone, Hash, PartialEq, Eq)]
pub struct OwnedAttributeDiscription {
@@ -158,14 +188,14 @@ impl<V: FromAnyValue> Debug for OwnedAttributeValue<V> {
}
}
impl<V: FromAnyValue + Display> Display for OwnedAttributeValue<V> {
impl<V: FromAnyValue> Display for OwnedAttributeValue<V> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Text(arg0) => f.write_str(arg0),
Self::Float(arg0) => f.write_str(&arg0.to_string()),
Self::Int(arg0) => f.write_str(&arg0.to_string()),
Self::Bool(arg0) => f.write_str(&arg0.to_string()),
Self::Custom(arg0) => f.write_str(&arg0.to_string()),
Self::Custom(_) => f.write_str("custom"),
}
}
}
+4 -4
View File
@@ -105,13 +105,13 @@ impl DirtyNodeStates {
/// A state that is automatically inserted in a node with dependencies.
pub trait State<V: FromAnyValue + Send + Sync = ()>: Any + Send + Sync {
/// This is a tuple of (T: State, ..) of states read from the parent required to run this pass
/// This is a tuple of (T: State, ..) of states read from the parent required to update this state
type ParentDependencies: Dependancy;
/// This is a tuple of (T: State, ..) of states read from the children required to run this pass
/// This is a tuple of (T: State, ..) of states read from the children required to update this state
type ChildDependencies: Dependancy;
/// This is a tuple of (T: State, ..) of states read from the node required to run this pass
/// This is a tuple of (T: State, ..) of states read from the node required to update this state
type NodeDependencies: Dependancy;
/// This is a mask of what aspects of the node are required to run this pass
/// This is a mask of what aspects of the node are required to update this state
const NODE_MASK: NodeMaskBuilder<'static>;
/// Update this state in a node, returns if the state was updated
+4 -3
View File
@@ -113,7 +113,8 @@ pub struct RealDom<V: FromAnyValue + Send + Sync = ()> {
impl<V: FromAnyValue + Send + Sync> RealDom<V> {
/// Create a new RealDom with the given states that will be inserted and updated when needed
pub fn new(mut tracked_states: Box<[TypeErasedState<V>]>) -> RealDom<V> {
pub fn new(tracked_states: impl Into<Box<[TypeErasedState<V>]>>) -> RealDom<V> {
let mut tracked_states = tracked_states.into();
// resolve dependants for each pass
for i in 1..tracked_states.len() {
let (before, after) = tracked_states.split_at_mut(i);
@@ -177,8 +178,8 @@ impl<V: FromAnyValue + Send + Sync> RealDom<V> {
}
/// Create a new node of the given type in the dom and return a mutable reference to it.
pub fn create_node(&mut self, node: NodeType<V>) -> NodeMut<'_, V> {
let id = self.world.add_entity(node);
pub fn create_node(&mut self, node: impl Into<NodeType<V>>) -> NodeMut<'_, V> {
let id = self.world.add_entity(node.into());
self.tree_mut().create_node(id);
self.dirty_nodes
.passes_updated