wip: broken, but solved
This commit is contained in:
parent
879e107634
commit
cb74d70f83
|
@ -0,0 +1,70 @@
|
||||||
|
# March 3, 2021
|
||||||
|
|
||||||
|
Still TODO:
|
||||||
|
- Wire up Nodebuilder to track listeners as they are added. (easyish)
|
||||||
|
- Wire up attrs on nodes to track listeners properly
|
||||||
|
- Could be done in the nodebuilder where the attrs are added automatically (easyish)
|
||||||
|
- Could just inject context into the diffing algorithm (hardish)
|
||||||
|
- Wire up component syntax (easy)
|
||||||
|
- Wire up component calling approach (easyish)
|
||||||
|
- Wire up component diffing (hardish)
|
||||||
|
|
||||||
|
|
||||||
|
Approach:
|
||||||
|
- move listeners out of vnode diffing
|
||||||
|
- move listeners onto scope via nodebuilder
|
||||||
|
- instead of a listeners list, store a list of listeners and their IDs
|
||||||
|
- this way means the diffing algorithm doesn't need to know that context
|
||||||
|
- This should fix our listener approach
|
||||||
|
- The only thing from here is child component
|
||||||
|
|
||||||
|
|
||||||
|
Thoughts:
|
||||||
|
- the macros should generate a static set of attrs into a [attr] array (faster, more predictable, no allocs)
|
||||||
|
- children should be generated as a static set if no parans are detected
|
||||||
|
- More complex in the macro sized, unfortunately, not *too* hard
|
||||||
|
- Listeners should also be a static set (dynamic listeners don't make too much sense)
|
||||||
|
- use the builder syntax if you're doing something wild and need this granular control
|
||||||
|
- Tags should also be &'static str - no reason to generate them on the fly
|
||||||
|
|
||||||
|
Major milestones going forward:
|
||||||
|
- Scheduled updates
|
||||||
|
- String renderer (and methods for accessing vdom directly as a tree of nodes)
|
||||||
|
- good existing work on this in some places
|
||||||
|
- Suspense
|
||||||
|
- Child support, nested diffing
|
||||||
|
- State management
|
||||||
|
- Tests tests tests
|
||||||
|
|
||||||
|
Done so far:
|
||||||
|
- websys
|
||||||
|
- webview
|
||||||
|
- rsx! macro
|
||||||
|
- html! macro
|
||||||
|
- lifecycles
|
||||||
|
- scopes
|
||||||
|
- hooks
|
||||||
|
- context API
|
||||||
|
- bump
|
||||||
|
|
||||||
|
|
||||||
|
## Solutions from today's thinking session...
|
||||||
|
|
||||||
|
### To solve children:
|
||||||
|
|
||||||
|
- maintain a map of `ScopeIdx` to `Node` in the renderer
|
||||||
|
- Add new patch commands
|
||||||
|
- traverse_to_known (idx)
|
||||||
|
- Pop known component onto stack (super easy)
|
||||||
|
- add_known (idx)
|
||||||
|
- Save top of stack as root associated with idx
|
||||||
|
- remove_known (idx)
|
||||||
|
- Remove node on top of stack from known roots
|
||||||
|
- ... Something like this
|
||||||
|
- Continue with BFS exploration of child components, DFS of VNodes
|
||||||
|
- Easier to write, easier to reason about
|
||||||
|
|
||||||
|
### To solve listeners:
|
||||||
|
|
||||||
|
- Map listeners directly as attrs before diffing via a listenerhandle
|
||||||
|
- Evaluation of nodes is now stateful where we track listeners as they are added
|
|
@ -58,7 +58,11 @@ impl ToTokens for HtmlRender {
|
||||||
|
|
||||||
// create a lazy tree that accepts a bump allocator
|
// create a lazy tree that accepts a bump allocator
|
||||||
let final_tokens = quote! {
|
let final_tokens = quote! {
|
||||||
move |bump| { #new_toks }
|
move |ctx| {
|
||||||
|
let bump = ctx.bump();
|
||||||
|
|
||||||
|
#new_toks
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final_tokens.to_tokens(out_tokens);
|
final_tokens.to_tokens(out_tokens);
|
||||||
|
|
|
@ -72,7 +72,10 @@ impl ToTokens for RsxRender {
|
||||||
|
|
||||||
// create a lazy tree that accepts a bump allocator
|
// create a lazy tree that accepts a bump allocator
|
||||||
let final_tokens = quote! {
|
let final_tokens = quote! {
|
||||||
move |bump: &Bump| { #new_toks }
|
move |ctx| {
|
||||||
|
let bump = ctx.bump();
|
||||||
|
#new_toks
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
final_tokens.to_tokens(out_tokens);
|
final_tokens.to_tokens(out_tokens);
|
||||||
|
|
|
@ -18,7 +18,7 @@ dioxus-core-macro = { path = "../core-macro", version = "0.1.1" }
|
||||||
once_cell = "1.5.2"
|
once_cell = "1.5.2"
|
||||||
|
|
||||||
# Backs the scope creation and reutilization
|
# Backs the scope creation and reutilization
|
||||||
generational-arena = "0.2.8"
|
generational-arena = { version = "0.2.8", features = ["serde"] }
|
||||||
# Bumpalo backs the VNode creation
|
# Bumpalo backs the VNode creation
|
||||||
bumpalo = { version = "3.6.0", features = ["collections"] }
|
bumpalo = { version = "3.6.0", features = ["collections"] }
|
||||||
|
|
||||||
|
|
|
@ -1,3 +1,4 @@
|
||||||
|
use bumpalo::Bump;
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
fn main() {}
|
fn main() {}
|
||||||
|
@ -12,21 +13,21 @@ struct ButtonProps<'a> {
|
||||||
fn CustomButton(ctx: Context, props: ButtonProps) -> DomTree {
|
fn CustomButton(ctx: Context, props: ButtonProps) -> DomTree {
|
||||||
let onfocus = move |evt: ()| log::debug!("Focused");
|
let onfocus = move |evt: ()| log::debug!("Focused");
|
||||||
|
|
||||||
todo!()
|
// todo!()
|
||||||
// ctx.render(rsx! {
|
ctx.render(rsx! {
|
||||||
// // button {
|
button {
|
||||||
// // // ..props.attrs,
|
// ..props.attrs,
|
||||||
// // class: "abc123",
|
class: "abc123",
|
||||||
// // // style: { a: 2, b: 3, c: 4 },
|
// style: { a: 2, b: 3, c: 4 },
|
||||||
// // onclick: {move |evt| {
|
onclick: move |evt| {
|
||||||
// // log::info("hello world");
|
// log::info("hello world");
|
||||||
// // }},
|
},
|
||||||
// // // Custom1 { a: 123 }
|
// Custom1 { a: 123 }
|
||||||
// // // Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
|
// Custom2 { a: 456, "abc", h1 {"1"}, h2 {"2"} }
|
||||||
// // // Custom3 { a: "sometext goes here" }
|
// Custom3 { a: "sometext goes here" }
|
||||||
// // // Custom4 { onclick: |evt| log::info("click") }
|
// Custom4 { onclick: |evt| log::info("click") }
|
||||||
// // }
|
}
|
||||||
// })
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// h1 {
|
// h1 {
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
use fxhash::FxHashMap;
|
||||||
|
|
||||||
|
use crate::innerlude::{VNode, VText};
|
||||||
|
|
||||||
|
/// A Patch encodes an operation that modifies a real DOM element.
|
||||||
|
///
|
||||||
|
/// To update the real DOM that a user sees you'll want to first diff your
|
||||||
|
/// old virtual dom and new virtual dom.
|
||||||
|
///
|
||||||
|
/// This diff operation will generate `Vec<Patch>` with zero or more patches that, when
|
||||||
|
/// applied to your real DOM, will make your real DOM look like your new virtual dom.
|
||||||
|
///
|
||||||
|
/// Each Patch has a u32 node index that helps us identify the real DOM node that it applies to.
|
||||||
|
///
|
||||||
|
/// Our old virtual dom's nodes are indexed depth first, as shown in this illustration
|
||||||
|
/// (0 being the root node, 1 being it's first child, 2 being it's first child's first child).
|
||||||
|
///
|
||||||
|
/// ```text
|
||||||
|
/// .─.
|
||||||
|
/// ( 0 )
|
||||||
|
/// `┬'
|
||||||
|
/// ┌────┴──────┐
|
||||||
|
/// │ │
|
||||||
|
/// ▼ ▼
|
||||||
|
/// .─. .─.
|
||||||
|
/// ( 1 ) ( 4 )
|
||||||
|
/// `┬' `─'
|
||||||
|
/// ┌────┴───┐ ├─────┬─────┐
|
||||||
|
/// │ │ │ │ │
|
||||||
|
/// ▼ ▼ ▼ ▼ ▼
|
||||||
|
/// .─. .─. .─. .─. .─.
|
||||||
|
/// ( 2 ) ( 3 ) ( 5 ) ( 6 ) ( 7 )
|
||||||
|
/// `─' `─' `─' `─' `─'
|
||||||
|
/// ```
|
||||||
|
///
|
||||||
|
/// The patching process is tested in a real browser in crates/virtual-dom-rs/tests/diff_patch.rs
|
||||||
|
|
||||||
|
// #[derive(serde::Serialize, serde::Deserialize)]
|
||||||
|
pub enum Patch<'a> {
|
||||||
|
/// Append a vector of child nodes to a parent node id.
|
||||||
|
AppendChildren(NodeIdx, Vec<&'a VNode<'a>>),
|
||||||
|
|
||||||
|
/// For a `node_i32`, remove all children besides the first `len`
|
||||||
|
TruncateChildren(NodeIdx, usize),
|
||||||
|
|
||||||
|
/// Replace a node with another node. This typically happens when a node's tag changes.
|
||||||
|
/// ex: <div> becomes <span>
|
||||||
|
Replace(NodeIdx, &'a VNode<'a>),
|
||||||
|
|
||||||
|
/// Add attributes that the new node has that the old node does not
|
||||||
|
AddAttributes(NodeIdx, FxHashMap<&'a str, &'a str>),
|
||||||
|
|
||||||
|
/// Remove attributes that the old node had that the new node doesn't
|
||||||
|
RemoveAttributes(NodeIdx, Vec<&'a str>),
|
||||||
|
|
||||||
|
/// Change the text of a Text node.
|
||||||
|
ChangeText(NodeIdx, &'a VText<'a>),
|
||||||
|
}
|
||||||
|
|
||||||
|
type NodeIdx = usize;
|
||||||
|
|
||||||
|
impl<'a> Patch<'a> {
|
||||||
|
/// Every Patch is meant to be applied to a specific node within the DOM. Get the
|
||||||
|
/// index of the DOM node that this patch should apply to. DOM nodes are indexed
|
||||||
|
/// depth first with the root node in the tree having index 0.
|
||||||
|
pub fn node_idx(&self) -> usize {
|
||||||
|
match self {
|
||||||
|
Patch::AppendChildren(node_idx, _) => *node_idx,
|
||||||
|
Patch::TruncateChildren(node_idx, _) => *node_idx,
|
||||||
|
Patch::Replace(node_idx, _) => *node_idx,
|
||||||
|
Patch::AddAttributes(node_idx, _) => *node_idx,
|
||||||
|
Patch::RemoveAttributes(node_idx, _) => *node_idx,
|
||||||
|
Patch::ChangeText(node_idx, _) => *node_idx,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct PatchList<'a> {
|
||||||
|
patches: Vec<Patch<'a>>,
|
||||||
|
}
|
|
@ -0,0 +1,329 @@
|
||||||
|
//! A primitive diffing algorithm
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
|
use std::{collections::HashMap, mem};
|
||||||
|
|
||||||
|
use crate::innerlude::*;
|
||||||
|
use crate::patch::Patch;
|
||||||
|
use fxhash::{FxBuildHasher, FxHashMap, FxHashSet};
|
||||||
|
use generational_arena::Index;
|
||||||
|
|
||||||
|
pub struct DiffMachine {
|
||||||
|
immediate_queue: Vec<Index>,
|
||||||
|
diffed: FxHashSet<Index>,
|
||||||
|
need_to_diff: FxHashSet<Index>,
|
||||||
|
marked_for_removal: Vec<Index>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffMachine {
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
immediate_queue: vec![],
|
||||||
|
diffed: FxHashSet::default(),
|
||||||
|
need_to_diff: FxHashSet::default(),
|
||||||
|
marked_for_removal: vec![],
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Given two VirtualNode's generate Patch's that would turn the old virtual node's
|
||||||
|
/// real DOM node equivalent into the new VirtualNode's real DOM node equivalent.
|
||||||
|
pub fn diff<'a>(&mut self, old: &'a VNode, new: &'a VNode) -> Vec<Patch<'a>> {
|
||||||
|
self.diff_recursive(&old, &new, &mut 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn diff_recursive<'a, 'b>(
|
||||||
|
&mut self,
|
||||||
|
old: &'a VNode,
|
||||||
|
new: &'a VNode,
|
||||||
|
cur_node_idx: &'b mut usize,
|
||||||
|
) -> Vec<Patch<'a>> {
|
||||||
|
let mut patches = vec![];
|
||||||
|
let mut replace = false;
|
||||||
|
|
||||||
|
// Different enum variants, replace!
|
||||||
|
if mem::discriminant(old) != mem::discriminant(new) {
|
||||||
|
replace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let (VNode::Element(old_element), VNode::Element(new_element)) = (old, new) {
|
||||||
|
// Replace if there are different element tags
|
||||||
|
if old_element.tag_name != new_element.tag_name {
|
||||||
|
// if old_element.tag != new_element.tag {
|
||||||
|
replace = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Replace if two elements have different keys
|
||||||
|
// TODO: More robust key support. This is just an early stopgap to allow you to force replace
|
||||||
|
// an element... say if it's event changed. Just change the key name for now.
|
||||||
|
// In the future we want keys to be used to create a Patch::ReOrder to re-order siblings
|
||||||
|
// todo!
|
||||||
|
// if old_element.attributes.get("key").is_some()
|
||||||
|
// && old_element.attrs.get("key") != new_element.attrs.get("key")
|
||||||
|
// {
|
||||||
|
// replace = true;
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle replacing of a node
|
||||||
|
if replace {
|
||||||
|
patches.push(Patch::Replace(*cur_node_idx, &new));
|
||||||
|
if let VNode::Element(old_element_node) = old {
|
||||||
|
for child in old_element_node.children.iter() {
|
||||||
|
increment_node_idx_for_children(child, cur_node_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return patches;
|
||||||
|
}
|
||||||
|
|
||||||
|
// The following comparison can only contain identical variants, other
|
||||||
|
// cases have already been handled above by comparing variant
|
||||||
|
// discriminants.
|
||||||
|
match (old, new) {
|
||||||
|
// We're comparing two text nodes
|
||||||
|
(VNode::Text(old_text), VNode::Text(new_text)) => {
|
||||||
|
if old_text != new_text {
|
||||||
|
patches.push(Patch::ChangeText(*cur_node_idx, &new_text));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// We're comparing two element nodes
|
||||||
|
(VNode::Element(old_element), VNode::Element(new_element)) => {
|
||||||
|
// let b: HashMap<&str, &str, FxBuildHasher> = HashMap::new()
|
||||||
|
let old_attrs = old_element
|
||||||
|
.attributes
|
||||||
|
.iter()
|
||||||
|
.map(|f| (f.name, f.value))
|
||||||
|
.collect::<HashMap<&'static str, &str, FxBuildHasher>>();
|
||||||
|
|
||||||
|
let new_attrs = old_element
|
||||||
|
.attributes
|
||||||
|
.iter()
|
||||||
|
.map(|f| (f.name, f.value))
|
||||||
|
.collect::<HashMap<&'static str, &str, FxBuildHasher>>();
|
||||||
|
|
||||||
|
let mut add_attributes = FxHashMap::<&'static str, &str>::default();
|
||||||
|
// [("blah", "blah")]
|
||||||
|
// .into_iter()
|
||||||
|
// .map(|f| (f.0, f.1))
|
||||||
|
// .collect::<HashMap<&'static str, &str, FxBuildHasher>>();
|
||||||
|
|
||||||
|
// let mut add_attribute = HashMap::<&str, &str, FxBuildHasher>::new();
|
||||||
|
let mut remove_attributes: Vec<&str> = vec![];
|
||||||
|
|
||||||
|
// TODO: -> split out into func
|
||||||
|
for (new_attr_name, new_attr_val) in new_attrs.iter() {
|
||||||
|
// for (new_attr_name, new_attr_val) in new_element.attrs.iter() {
|
||||||
|
match old_attrs.get(new_attr_name) {
|
||||||
|
// match old_element.attrs.get(new_attr_name) {
|
||||||
|
Some(ref old_attr_val) => {
|
||||||
|
if old_attr_val != &new_attr_val {
|
||||||
|
add_attributes.insert(new_attr_name, new_attr_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
add_attributes.insert(new_attr_name, new_attr_val);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: -> split out into func
|
||||||
|
for (old_attr_name, old_attr_val) in old_attrs.iter() {
|
||||||
|
// for (old_attr_name, old_attr_val) in old_element.attrs.iter() {
|
||||||
|
if add_attributes.get(&old_attr_name[..]).is_some() {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
|
||||||
|
match new_attrs.get(old_attr_name) {
|
||||||
|
// match new_element.attrs.get(old_attr_name) {
|
||||||
|
Some(ref new_attr_val) => {
|
||||||
|
if new_attr_val != &old_attr_val {
|
||||||
|
remove_attributes.push(old_attr_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
None => {
|
||||||
|
remove_attributes.push(old_attr_name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
if add_attributes.len() > 0 {
|
||||||
|
patches.push(Patch::AddAttributes(*cur_node_idx, add_attributes));
|
||||||
|
}
|
||||||
|
if remove_attributes.len() > 0 {
|
||||||
|
patches.push(Patch::RemoveAttributes(*cur_node_idx, remove_attributes));
|
||||||
|
}
|
||||||
|
|
||||||
|
let old_child_count = old_element.children.len();
|
||||||
|
let new_child_count = new_element.children.len();
|
||||||
|
|
||||||
|
if new_child_count > old_child_count {
|
||||||
|
let append_patch: Vec<&'a VNode> =
|
||||||
|
new_element.children[old_child_count..].iter().collect();
|
||||||
|
patches.push(Patch::AppendChildren(*cur_node_idx, append_patch))
|
||||||
|
}
|
||||||
|
|
||||||
|
if new_child_count < old_child_count {
|
||||||
|
patches.push(Patch::TruncateChildren(*cur_node_idx, new_child_count))
|
||||||
|
}
|
||||||
|
|
||||||
|
let min_count = std::cmp::min(old_child_count, new_child_count);
|
||||||
|
for index in 0..min_count {
|
||||||
|
*cur_node_idx = *cur_node_idx + 1;
|
||||||
|
let old_child = &old_element.children[index];
|
||||||
|
let new_child = &new_element.children[index];
|
||||||
|
patches.append(&mut self.diff_recursive(&old_child, &new_child, cur_node_idx))
|
||||||
|
}
|
||||||
|
if new_child_count < old_child_count {
|
||||||
|
for child in old_element.children[min_count..].iter() {
|
||||||
|
increment_node_idx_for_children(child, cur_node_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
(VNode::Suspended, _)
|
||||||
|
| (_, VNode::Suspended)
|
||||||
|
| (VNode::Component(_), _)
|
||||||
|
| (_, VNode::Component(_)) => {
|
||||||
|
todo!("cant yet handle these two")
|
||||||
|
}
|
||||||
|
|
||||||
|
(VNode::Text(_), VNode::Element(_))
|
||||||
|
| (VirtualNode::Element(_), VirtualNode::Text(_)) => {
|
||||||
|
unreachable!("Unequal variant discriminants should already have been handled");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// new_root.create_element()
|
||||||
|
patches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increment_node_idx_for_children<'a, 'b>(old: &'a VirtualNode, cur_node_idx: &'b mut usize) {
|
||||||
|
*cur_node_idx += 1;
|
||||||
|
if let VirtualNode::Element(element_node) = old {
|
||||||
|
for child in element_node.children.iter() {
|
||||||
|
increment_node_idx_for_children(&child, cur_node_idx);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use bumpalo::Bump;
|
||||||
|
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn test_diff(
|
||||||
|
tree1: impl Fn(&Bump) -> VNode<'_>,
|
||||||
|
tree2: impl Fn(&Bump) -> VNode<'_>,
|
||||||
|
expected_patches: Vec<Patch>,
|
||||||
|
description: &'static str,
|
||||||
|
) {
|
||||||
|
let bump = Bump::new();
|
||||||
|
|
||||||
|
let nodes1 = tree1(&bump);
|
||||||
|
let nodes2 = tree1(&bump);
|
||||||
|
|
||||||
|
let mut machine = DiffMachine::new();
|
||||||
|
|
||||||
|
let patches = machine.diff(&nodes1, &nodes2);
|
||||||
|
|
||||||
|
patches
|
||||||
|
.iter()
|
||||||
|
.zip(expected_patches.iter())
|
||||||
|
.for_each(|f| assert_eq!(compare_patch(f.0, f.1), true, "{}", description));
|
||||||
|
}
|
||||||
|
|
||||||
|
// todo: make this actually perform real comparisons
|
||||||
|
// by default, nothing is derived for vnodes or patches
|
||||||
|
fn compare_patch(patch1: &Patch, patch2: &Patch) -> bool {
|
||||||
|
match (patch1, patch2) {
|
||||||
|
(Patch::AppendChildren(_, _), Patch::AppendChildren(_, _)) => true,
|
||||||
|
(Patch::AppendChildren(_, _), _) => false,
|
||||||
|
|
||||||
|
(Patch::TruncateChildren(_, _), Patch::TruncateChildren(_, _)) => true,
|
||||||
|
(Patch::TruncateChildren(_, _), _) => false,
|
||||||
|
|
||||||
|
(Patch::Replace(_, _), Patch::Replace(_, _)) => true,
|
||||||
|
(Patch::Replace(_, _), _) => false,
|
||||||
|
|
||||||
|
(Patch::AddAttributes(_, _), Patch::AddAttributes(_, _)) => true,
|
||||||
|
(Patch::AddAttributes(_, _), _) => false,
|
||||||
|
|
||||||
|
(Patch::RemoveAttributes(_, _), Patch::RemoveAttributes(_, _)) => true,
|
||||||
|
(Patch::RemoveAttributes(_, _), _) => false,
|
||||||
|
|
||||||
|
(Patch::ChangeText(_, _), Patch::ChangeText(_, _)) => true,
|
||||||
|
(Patch::ChangeText(_, _), _) => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn printdiff(
|
||||||
|
tree1: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
|
||||||
|
tree2: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
|
||||||
|
desc: &'static str,
|
||||||
|
) {
|
||||||
|
let bump = Bump::new();
|
||||||
|
|
||||||
|
let nodes1 = tree1(&bump);
|
||||||
|
let nodes2 = tree2(&bump);
|
||||||
|
|
||||||
|
let mut machine = DiffMachine::new();
|
||||||
|
|
||||||
|
let patches = machine.diff(&nodes1, &nodes2);
|
||||||
|
|
||||||
|
patches.iter().for_each(|f| match f {
|
||||||
|
Patch::AppendChildren(idx, a) => {
|
||||||
|
println!("AppendChildren");
|
||||||
|
}
|
||||||
|
Patch::TruncateChildren(idx, a) => {
|
||||||
|
println!("TruncateChildren");
|
||||||
|
}
|
||||||
|
Patch::Replace(idx, a) => {
|
||||||
|
println!("Replace");
|
||||||
|
}
|
||||||
|
Patch::AddAttributes(idx, a) => {
|
||||||
|
println!("AddAttributes");
|
||||||
|
}
|
||||||
|
Patch::RemoveAttributes(idx, a) => {
|
||||||
|
println!("RemoveAttributes");
|
||||||
|
}
|
||||||
|
Patch::ChangeText(idx, a) => {
|
||||||
|
println!("ChangeText");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn example_diff() {
|
||||||
|
printdiff(
|
||||||
|
html! { <div> </div> },
|
||||||
|
html! { <div>"Hello world!" </div> },
|
||||||
|
"demo the difference between two simple dom tree",
|
||||||
|
);
|
||||||
|
|
||||||
|
printdiff(
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
"Hello world!"
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
html! {
|
||||||
|
<div>
|
||||||
|
<div>
|
||||||
|
"Hello world!"
|
||||||
|
"Hello world!"
|
||||||
|
"Hello world!"
|
||||||
|
"Hello world!"
|
||||||
|
"Hello world!"
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
},
|
||||||
|
"demo the difference between two simple dom tree",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,753 +0,0 @@
|
||||||
//! Changelist
|
|
||||||
//! ----------
|
|
||||||
//!
|
|
||||||
//! This module exposes the "changelist" object which allows 3rd party implementors to handle diffs to the virtual dom.
|
|
||||||
//!
|
|
||||||
//! # Design
|
|
||||||
//! ---
|
|
||||||
//! In essence, the changelist object connects a diff of two vdoms to the actual edits required to update the output renderer.
|
|
||||||
//!
|
|
||||||
//! This abstraction relies on the assumption that the final renderer accepts a tree of elements. For most target platforms,
|
|
||||||
//! this is an appropriate abstraction .
|
|
||||||
//!
|
|
||||||
//! During the diff phase, the change list is built. Once the diff phase is over, the change list is finished and returned back
|
|
||||||
//! to the renderer. The renderer is responsible for propogating the updates (a stream of u32) to the final display.
|
|
||||||
//!
|
|
||||||
//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom
|
|
||||||
//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the
|
|
||||||
//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object.
|
|
||||||
//!
|
|
||||||
//!
|
|
||||||
|
|
||||||
use bumpalo::Bump;
|
|
||||||
|
|
||||||
use crate::innerlude::Listener;
|
|
||||||
use serde::{Deserialize, Serialize};
|
|
||||||
/// The `Edit` represents a single modifcation of the renderer tree.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// todo@ jon: allow serde to be optional
|
|
||||||
#[derive(Debug, Serialize, Deserialize)]
|
|
||||||
#[serde(tag = "type")]
|
|
||||||
pub enum Edit<'d> {
|
|
||||||
SetText { text: &'d str },
|
|
||||||
RemoveSelfAndNextSiblings {},
|
|
||||||
ReplaceWith,
|
|
||||||
SetAttribute { name: &'d str, value: &'d str },
|
|
||||||
RemoveAttribute { name: &'d str },
|
|
||||||
PushReverseChild { n: u32 },
|
|
||||||
PopPushChild { n: u32 },
|
|
||||||
Pop,
|
|
||||||
AppendChild,
|
|
||||||
CreateTextNode { text: &'d str },
|
|
||||||
CreateElement { tag_name: &'d str },
|
|
||||||
NewEventListener { event_type: &'d str, idx: CbIdx },
|
|
||||||
UpdateEventListener { event_type: &'d str, idx: CbIdx },
|
|
||||||
RemoveEventListener { event_type: &'d str },
|
|
||||||
CreateElementNs { tag_name: &'d str, ns: &'d str },
|
|
||||||
SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 },
|
|
||||||
PushChild { n: u32 },
|
|
||||||
PushTemporary { temp: u32 },
|
|
||||||
InsertBefore,
|
|
||||||
PopPushReverseChild { n: u32 },
|
|
||||||
RemoveChild { n: u32 },
|
|
||||||
SetClass { class_name: &'d str },
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Re-export a cover over generational ID for libraries that don't need it
|
|
||||||
/// We can go back and forth between the two via methods on GI
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
|
|
||||||
pub struct CbIdx {
|
|
||||||
pub gi_id: usize,
|
|
||||||
pub gi_gen: u64,
|
|
||||||
pub listener_idx: usize,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl CbIdx {
|
|
||||||
pub fn from_gi_index(index: generational_arena::Index, listener_idx: usize) -> Self {
|
|
||||||
let (gi_id, gi_gen) = index.into_raw_parts();
|
|
||||||
Self {
|
|
||||||
gi_id,
|
|
||||||
gi_gen,
|
|
||||||
listener_idx,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub type EditList<'src> = Vec<Edit<'src>>;
|
|
||||||
|
|
||||||
pub struct EditMachine<'src> {
|
|
||||||
pub traversal: Traversal,
|
|
||||||
next_temporary: u32,
|
|
||||||
forcing_new_listeners: bool,
|
|
||||||
|
|
||||||
pub emitter: EditList<'src>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'b> EditMachine<'b> {
|
|
||||||
pub fn new(_bump: &'b Bump) -> Self {
|
|
||||||
Self {
|
|
||||||
traversal: Traversal::new(),
|
|
||||||
next_temporary: 0,
|
|
||||||
forcing_new_listeners: false,
|
|
||||||
emitter: EditList::default(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Traversal methods.
|
|
||||||
pub fn go_down(&mut self) {
|
|
||||||
self.traversal.down();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_down_to_child(&mut self, index: usize) {
|
|
||||||
self.traversal.down();
|
|
||||||
self.traversal.sibling(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_down_to_reverse_child(&mut self, index: usize) {
|
|
||||||
self.traversal.down();
|
|
||||||
self.traversal.reverse_sibling(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_up(&mut self) {
|
|
||||||
self.traversal.up();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_to_sibling(&mut self, index: usize) {
|
|
||||||
self.traversal.sibling(index);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_to_temp_sibling(&mut self, temp: u32) {
|
|
||||||
self.traversal.up();
|
|
||||||
self.traversal.down_to_temp(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn go_down_to_temp_child(&mut self, temp: u32) {
|
|
||||||
self.traversal.down_to_temp(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn commit_traversal(&mut self) {
|
|
||||||
if self.traversal.is_committed() {
|
|
||||||
log::debug!("Traversal already committed");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
for mv in self.traversal.commit() {
|
|
||||||
match mv {
|
|
||||||
MoveTo::Parent => {
|
|
||||||
log::debug!("emit: pop");
|
|
||||||
self.emitter.push(Edit::Pop {});
|
|
||||||
// self.emitter.pop();
|
|
||||||
}
|
|
||||||
MoveTo::Child(n) => {
|
|
||||||
log::debug!("emit: push_child({})", n);
|
|
||||||
self.emitter.push(Edit::PushChild { n });
|
|
||||||
}
|
|
||||||
MoveTo::ReverseChild(n) => {
|
|
||||||
log::debug!("emit: push_reverse_child({})", n);
|
|
||||||
self.emitter.push(Edit::PushReverseChild { n });
|
|
||||||
// self.emitter.push_reverse_child(n);
|
|
||||||
}
|
|
||||||
MoveTo::Sibling(n) => {
|
|
||||||
log::debug!("emit: pop_push_child({})", n);
|
|
||||||
self.emitter.push(Edit::PopPushChild { n });
|
|
||||||
// self.emitter.pop_push_child(n);
|
|
||||||
}
|
|
||||||
MoveTo::ReverseSibling(n) => {
|
|
||||||
log::debug!("emit: pop_push_reverse_child({})", n);
|
|
||||||
self.emitter.push(Edit::PopPushReverseChild { n });
|
|
||||||
}
|
|
||||||
MoveTo::TempChild(temp) => {
|
|
||||||
log::debug!("emit: push_temporary({})", temp);
|
|
||||||
self.emitter.push(Edit::PushTemporary { temp });
|
|
||||||
// self.emitter.push_temporary(temp);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn traversal_is_committed(&self) -> bool {
|
|
||||||
self.traversal.is_committed()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<'a> EditMachine<'a> {
|
|
||||||
pub fn next_temporary(&self) -> u32 {
|
|
||||||
self.next_temporary
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_next_temporary(&mut self, next_temporary: u32) {
|
|
||||||
self.next_temporary = next_temporary;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
debug_assert!(start < end);
|
|
||||||
let temp_base = self.next_temporary;
|
|
||||||
// debug!(
|
|
||||||
// "emit: save_children_to_temporaries({}, {}, {})",
|
|
||||||
// temp_base, start, end
|
|
||||||
// );
|
|
||||||
self.next_temporary = temp_base + (end - start) as u32;
|
|
||||||
self.emitter.push(Edit::SaveChildrenToTemporaries {
|
|
||||||
temp: temp_base,
|
|
||||||
start: start as u32,
|
|
||||||
end: end as u32,
|
|
||||||
});
|
|
||||||
temp_base
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_temporary(&mut self, temp: u32) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: push_temporary({})", temp);
|
|
||||||
self.emitter.push(Edit::PushTemporary { temp });
|
|
||||||
// self.emitter.push_temporary(temp);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_child(&mut self, child: usize) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: remove_child({})", child);
|
|
||||||
// self.emitter.remove_child(child as u32);
|
|
||||||
self.emitter.push(Edit::RemoveChild { n: child as u32 })
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn insert_before(&mut self) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: insert_before()");
|
|
||||||
// self.emitter.insert_before();
|
|
||||||
self.emitter.push(Edit::InsertBefore {})
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn ensure_string(&mut self, _string: &str) -> StringKey {
|
|
||||||
todo!()
|
|
||||||
// self.strings.ensure_string(string, &self.emitter)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_text(&mut self, text: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: set_text({:?})", text);
|
|
||||||
// self.emitter.set_text(text);
|
|
||||||
self.emitter.push(Edit::SetText { text });
|
|
||||||
// .set_text(text.as_ptr() as u32, text.len() as u32);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_self_and_next_siblings(&mut self) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: remove_self_and_next_siblings()");
|
|
||||||
self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
|
|
||||||
// self.emitter.remove_self_and_next_siblings();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn replace_with(&mut self) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: replace_with()");
|
|
||||||
self.emitter.push(Edit::ReplaceWith {});
|
|
||||||
// self.emitter.replace_with();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// todo!()
|
|
||||||
if name == "class" && !is_namespaced {
|
|
||||||
// let class_id = self.ensure_string(value);
|
|
||||||
// let class_id = self.ensure_string(value);
|
|
||||||
// debug!("emit: set_class({:?})", value);
|
|
||||||
// self.emitter.set_class(class_id.into());
|
|
||||||
self.emitter.push(Edit::SetClass { class_name: value });
|
|
||||||
} else {
|
|
||||||
self.emitter.push(Edit::SetAttribute { name, value });
|
|
||||||
// let name_id = self.ensure_string(name);
|
|
||||||
// let value_id = self.ensure_string(value);
|
|
||||||
// debug!("emit: set_attribute({:?}, {:?})", name, value);
|
|
||||||
// self.state
|
|
||||||
// .emitter
|
|
||||||
// .set_attribute(name_id.into(), value_id.into());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_attribute(&mut self, name: &'a str) {
|
|
||||||
// todo!("figure out how to get this working with ensure string");
|
|
||||||
self.emitter.push(Edit::RemoveAttribute { name });
|
|
||||||
// self.emitter.remove_attribute(name);
|
|
||||||
// debug_assert!(self.traversal_is_committed());
|
|
||||||
// // debug!("emit: remove_attribute({:?})", name);
|
|
||||||
// let name_id = self.ensure_string(name);
|
|
||||||
// self.emitter.remove_attribute(name_id.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn append_child(&mut self) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: append_child()");
|
|
||||||
self.emitter.push(Edit::AppendChild {});
|
|
||||||
// self.emitter.append_child();
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_text_node(&mut self, text: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: create_text_node({:?})", text);
|
|
||||||
// self.emitter.create_text_node(text);
|
|
||||||
self.emitter.push(Edit::CreateTextNode { text });
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_element(&mut self, tag_name: &'a str) {
|
|
||||||
// debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: create_element({:?})", tag_name);
|
|
||||||
// let tag_name_id = self.ensure_string(tag_name);
|
|
||||||
self.emitter.push(Edit::CreateElement { tag_name });
|
|
||||||
// self.emitter.create_element(tag_name);
|
|
||||||
// self.emitter.create_element(tag_name_id.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
|
|
||||||
// let tag_name_id = self.ensure_string(tag_name);
|
|
||||||
// let ns_id = self.ensure_string(ns);
|
|
||||||
// self.emitter.create_element_ns(tag_name, ns);
|
|
||||||
self.emitter.push(Edit::CreateElementNs { tag_name, ns });
|
|
||||||
// self.emitter
|
|
||||||
// .create_element_ns(tag_name_id.into(), ns_id.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn push_force_new_listeners(&mut self) -> bool {
|
|
||||||
let old = self.forcing_new_listeners;
|
|
||||||
self.forcing_new_listeners = true;
|
|
||||||
old
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn pop_force_new_listeners(&mut self, previous: bool) {
|
|
||||||
debug_assert!(self.forcing_new_listeners);
|
|
||||||
self.forcing_new_listeners = previous;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn new_event_listener(&mut self, event: &'a str, idx: CbIdx) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter.push(Edit::NewEventListener {
|
|
||||||
event_type: event,
|
|
||||||
idx,
|
|
||||||
});
|
|
||||||
// todo!("Event listener not wired up yet");
|
|
||||||
// log::debug!("emit: new_event_listener({:?})", listener);
|
|
||||||
// let (a, b) = listener.get_callback_parts();
|
|
||||||
// debug_assert!(a != 0);
|
|
||||||
// // let event_id = self.ensure_string(listener.event);
|
|
||||||
// self.emitter.new_event_listener(listener.event.into(), a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_event_listener(&mut self, event: &'a str, idx: CbIdx) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
if self.forcing_new_listeners {
|
|
||||||
self.new_event_listener(event, idx);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
self.emitter.push(Edit::NewEventListener {
|
|
||||||
event_type: event,
|
|
||||||
idx,
|
|
||||||
});
|
|
||||||
|
|
||||||
// log::debug!("emit: update_event_listener({:?})", listener);
|
|
||||||
// // todo!("Event listener not wired up yet");
|
|
||||||
// let (a, b) = listener.get_callback_parts();
|
|
||||||
// debug_assert!(a != 0);
|
|
||||||
// self.emitter.push(Edit::UpdateEventListener {
|
|
||||||
// event_type: listener.event.into(),
|
|
||||||
// a,
|
|
||||||
// b,
|
|
||||||
// });
|
|
||||||
// self.emitter.update_event_listener(event_id.into(), a, b);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn remove_event_listener(&mut self, event: &'a str) {
|
|
||||||
debug_assert!(self.traversal_is_committed());
|
|
||||||
self.emitter
|
|
||||||
.push(Edit::RemoveEventListener { event_type: event });
|
|
||||||
// debug!("emit: remove_event_listener({:?})", event);
|
|
||||||
// let _event_id = self.ensure_string(event);
|
|
||||||
// todo!("Event listener not wired up yet");
|
|
||||||
// self.emitter.remove_event_listener(event_id.into());
|
|
||||||
}
|
|
||||||
|
|
||||||
// #[inline]
|
|
||||||
// pub fn has_template(&mut self, id: CacheId) -> bool {
|
|
||||||
// self.templates.contains(&id)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn save_template(&mut self, id: CacheId) {
|
|
||||||
// debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug_assert!(!self.has_template(id));
|
|
||||||
// // debug!("emit: save_template({:?})", id);
|
|
||||||
// self.templates.insert(id);
|
|
||||||
// self.emitter.save_template(id.into());
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn push_template(&mut self, id: CacheId) {
|
|
||||||
// debug_assert!(self.traversal_is_committed());
|
|
||||||
// debug_assert!(self.has_template(id));
|
|
||||||
// // debug!("emit: push_template({:?})", id);
|
|
||||||
// self.emitter.push_template(id.into());
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Keeps track of where we are moving in a DOM tree, and shortens traversal
|
|
||||||
// paths between mutations to their minimal number of operations.
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum MoveTo {
|
|
||||||
/// Move from the current node up to its parent.
|
|
||||||
Parent,
|
|
||||||
|
|
||||||
/// Move to the current node's n^th child.
|
|
||||||
Child(u32),
|
|
||||||
|
|
||||||
/// Move to the current node's n^th from last child.
|
|
||||||
ReverseChild(u32),
|
|
||||||
|
|
||||||
/// Move to the n^th sibling. Not relative from the current
|
|
||||||
/// location. Absolute indexed within all of the current siblings.
|
|
||||||
Sibling(u32),
|
|
||||||
|
|
||||||
/// Move to the n^th from last sibling. Not relative from the current
|
|
||||||
/// location. Absolute indexed within all of the current siblings.
|
|
||||||
ReverseSibling(u32),
|
|
||||||
|
|
||||||
/// Move down to the given saved temporary child.
|
|
||||||
TempChild(u32),
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug)]
|
|
||||||
pub struct Traversal {
|
|
||||||
uncommitted: Vec<MoveTo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Traversal {
|
|
||||||
/// Construct a new `Traversal` with its internal storage backed by the
|
|
||||||
/// given bump arena.
|
|
||||||
pub fn new() -> Traversal {
|
|
||||||
Traversal {
|
|
||||||
uncommitted: Vec::with_capacity(32),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the traversal up in the tree.
|
|
||||||
pub fn up(&mut self) {
|
|
||||||
match self.uncommitted.last() {
|
|
||||||
Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::Parent);
|
|
||||||
}
|
|
||||||
Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
// And we're back at the parent.
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.uncommitted.push(MoveTo::Parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the traversal down in the tree to the first child of the current
|
|
||||||
/// node.
|
|
||||||
pub fn down(&mut self) {
|
|
||||||
if let Some(&MoveTo::Parent) = self.uncommitted.last() {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.sibling(0);
|
|
||||||
} else {
|
|
||||||
self.uncommitted.push(MoveTo::Child(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the traversal to the n^th sibling.
|
|
||||||
pub fn sibling(&mut self, index: usize) {
|
|
||||||
let index = index as u32;
|
|
||||||
match self.uncommitted.last_mut() {
|
|
||||||
Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
|
|
||||||
*n = index;
|
|
||||||
}
|
|
||||||
Some(MoveTo::ReverseSibling(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::Sibling(index));
|
|
||||||
}
|
|
||||||
Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::Child(index))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.uncommitted.push(MoveTo::Sibling(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Move the the n^th from last sibling.
|
|
||||||
pub fn reverse_sibling(&mut self, index: usize) {
|
|
||||||
let index = index as u32;
|
|
||||||
match self.uncommitted.last_mut() {
|
|
||||||
Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
|
|
||||||
*n = index;
|
|
||||||
}
|
|
||||||
Some(MoveTo::Sibling(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::ReverseSibling(index));
|
|
||||||
}
|
|
||||||
Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
self.uncommitted.push(MoveTo::ReverseChild(index))
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
self.uncommitted.push(MoveTo::ReverseSibling(index));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Go to the given saved temporary.
|
|
||||||
pub fn down_to_temp(&mut self, temp: u32) {
|
|
||||||
match self.uncommitted.last() {
|
|
||||||
Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
|
|
||||||
self.uncommitted.pop();
|
|
||||||
}
|
|
||||||
Some(MoveTo::Parent)
|
|
||||||
| Some(MoveTo::TempChild(_))
|
|
||||||
| Some(MoveTo::Child(_))
|
|
||||||
| Some(MoveTo::ReverseChild(_))
|
|
||||||
| None => {
|
|
||||||
// Can't remove moves to parents since we rely on their stack
|
|
||||||
// pops.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
self.uncommitted.push(MoveTo::TempChild(temp));
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Are all the traversal's moves committed? That is, are there no moves
|
|
||||||
/// that have *not* been committed yet?
|
|
||||||
#[inline]
|
|
||||||
pub fn is_committed(&self) -> bool {
|
|
||||||
// is_empty is not inlined?
|
|
||||||
self.uncommitted.is_empty()
|
|
||||||
// self.uncommitted.len() == 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Commit this traversals moves and return the optimized path from the last
|
|
||||||
/// commit.
|
|
||||||
#[inline]
|
|
||||||
pub fn commit(&mut self) -> Moves {
|
|
||||||
Moves {
|
|
||||||
inner: self.uncommitted.drain(..),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
pub fn reset(&mut self) {
|
|
||||||
self.uncommitted.clear();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub struct Moves<'a> {
|
|
||||||
inner: std::vec::Drain<'a, MoveTo>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Iterator for Moves<'_> {
|
|
||||||
type Item = MoveTo;
|
|
||||||
|
|
||||||
#[inline]
|
|
||||||
fn next(&mut self) -> Option<MoveTo> {
|
|
||||||
self.inner.next()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_traversal() {
|
|
||||||
fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
|
|
||||||
where
|
|
||||||
F: 'static + FnMut(&mut Traversal),
|
|
||||||
{
|
|
||||||
Box::new(f) as _
|
|
||||||
}
|
|
||||||
|
|
||||||
for (mut traverse, expected_moves) in vec![
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(0)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.sibling(42);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Sibling(42)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.sibling(2);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.sibling(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.sibling(4);
|
|
||||||
t.sibling(8);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(8)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.sibling(1);
|
|
||||||
t.sibling(1);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Sibling(1)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::ReverseSibling(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::ReverseChild(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
t.reverse_sibling(6);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::ReverseChild(6)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.up();
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
t.reverse_sibling(6);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.up();
|
|
||||||
t.sibling(3);
|
|
||||||
t.sibling(6);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent, MoveTo::Sibling(6)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.sibling(3);
|
|
||||||
t.sibling(6);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.reverse_sibling(3);
|
|
||||||
t.reverse_sibling(6);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down();
|
|
||||||
t.down_to_temp(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(0), MoveTo::TempChild(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down_to_temp(3);
|
|
||||||
t.sibling(5);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Child(5)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down_to_temp(3);
|
|
||||||
t.reverse_sibling(5);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::ReverseChild(5)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.down_to_temp(3);
|
|
||||||
t.up();
|
|
||||||
}),
|
|
||||||
vec![],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.sibling(2);
|
|
||||||
t.up();
|
|
||||||
t.down_to_temp(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent, MoveTo::TempChild(3)],
|
|
||||||
),
|
|
||||||
(
|
|
||||||
t(|t| {
|
|
||||||
t.up();
|
|
||||||
t.down_to_temp(3);
|
|
||||||
}),
|
|
||||||
vec![MoveTo::Parent, MoveTo::TempChild(3)],
|
|
||||||
),
|
|
||||||
] {
|
|
||||||
let mut traversal = Traversal::new();
|
|
||||||
traverse(&mut traversal);
|
|
||||||
let actual_moves: Vec<_> = traversal.commit().collect();
|
|
||||||
assert_eq!(actual_moves, expected_moves);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub struct StringKey(u32);
|
|
||||||
|
|
||||||
impl From<StringKey> for u32 {
|
|
||||||
#[inline]
|
|
||||||
fn from(key: StringKey) -> u32 {
|
|
||||||
key.0
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,7 @@
|
||||||
//! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible
|
//! This file handles the supporting infrastructure for the `Component` trait and `Properties` which makes it possible
|
||||||
//! for components to be used within Nodes.
|
//! for components to be used within Nodes.
|
||||||
//!
|
|
||||||
|
pub type ScopeIdx = generational_arena::Index;
|
||||||
|
|
||||||
/// The `Component` trait refers to any struct or funciton that can be used as a component
|
/// The `Component` trait refers to any struct or funciton that can be used as a component
|
||||||
/// We automatically implement Component for FC<T>
|
/// We automatically implement Component for FC<T>
|
||||||
|
@ -44,6 +45,7 @@ mod tests {
|
||||||
|
|
||||||
static TestComponent: FC<()> = |ctx, props| {
|
static TestComponent: FC<()> = |ctx, props| {
|
||||||
//
|
//
|
||||||
|
|
||||||
ctx.render(html! {
|
ctx.render(html! {
|
||||||
<div>
|
<div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -52,7 +54,7 @@ mod tests {
|
||||||
|
|
||||||
static TestComponent2: FC<()> = |ctx, props| {
|
static TestComponent2: FC<()> = |ctx, props| {
|
||||||
//
|
//
|
||||||
ctx.render(|bump: &Bump| VNode::text("blah"))
|
ctx.render(|ctx| VNode::text("blah"))
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|
|
@ -76,8 +76,10 @@ impl<'a> Context<'a> {
|
||||||
/// ctx.render(lazy_tree)
|
/// ctx.render(lazy_tree)
|
||||||
/// }
|
/// }
|
||||||
///```
|
///```
|
||||||
pub fn render(self, lazy_nodes: impl FnOnce(&'a Bump) -> VNode<'a> + 'a) -> DomTree {
|
pub fn render(self, lazy_nodes: impl FnOnce(NodeCtx<'a>) -> VNode<'a> + 'a) -> DomTree {
|
||||||
let safe_nodes = lazy_nodes(self.bump);
|
let ctx = NodeCtx { bump: self.bump };
|
||||||
|
let safe_nodes = lazy_nodes(ctx);
|
||||||
|
|
||||||
let unsafe_nodes = unsafe { std::mem::transmute::<VNode<'a>, VNode<'static>>(safe_nodes) };
|
let unsafe_nodes = unsafe { std::mem::transmute::<VNode<'a>, VNode<'static>>(safe_nodes) };
|
||||||
self.final_nodes.deref().borrow_mut().replace(unsafe_nodes);
|
self.final_nodes.deref().borrow_mut().replace(unsafe_nodes);
|
||||||
DomTree {}
|
DomTree {}
|
||||||
|
@ -90,12 +92,25 @@ impl<'a> Context<'a> {
|
||||||
/// When the future completes, the component will be renderered
|
/// When the future completes, the component will be renderered
|
||||||
pub fn suspend(
|
pub fn suspend(
|
||||||
&self,
|
&self,
|
||||||
_fut: impl Future<Output = impl FnOnce(&'a Bump) -> VNode<'a>>,
|
_fut: impl Future<Output = impl FnOnce(&'a NodeCtx<'a>) -> VNode<'a>>,
|
||||||
) -> VNode<'a> {
|
) -> VNode<'a> {
|
||||||
todo!()
|
todo!()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NodeCtx is used to build VNodes in the component's memory space.
|
||||||
|
// This struct adds metadata to the final DomTree about listeners, attributes, and children
|
||||||
|
pub struct NodeCtx<'a> {
|
||||||
|
bump: &'a Bump,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl NodeCtx<'_> {
|
||||||
|
#[inline]
|
||||||
|
pub fn bump(&self) -> &Bump {
|
||||||
|
self.bump
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
pub mod hooks {
|
pub mod hooks {
|
||||||
//! This module provides internal state management functionality for Dioxus components
|
//! This module provides internal state management functionality for Dioxus components
|
||||||
//!
|
//!
|
||||||
|
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -4,29 +4,21 @@
|
||||||
//! 3rd party renderers are responsible for forming this virtual events from events
|
//! 3rd party renderers are responsible for forming this virtual events from events
|
||||||
//!
|
//!
|
||||||
//! The goal here is to provide a consistent event interface across all renderer types
|
//! The goal here is to provide a consistent event interface across all renderer types
|
||||||
use generational_arena::Index;
|
|
||||||
|
|
||||||
use crate::innerlude::CbIdx;
|
use crate::innerlude::ScopeIdx;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct EventTrigger {
|
pub struct EventTrigger {
|
||||||
pub component_id: Index,
|
pub component_id: ScopeIdx,
|
||||||
pub listener_id: usize,
|
pub listener_id: usize,
|
||||||
pub event: VirtualEvent,
|
pub event: VirtualEvent,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl EventTrigger {
|
impl EventTrigger {
|
||||||
pub fn new(event: VirtualEvent, cb: CbIdx) -> Self {
|
pub fn new(event: VirtualEvent, scope: ScopeIdx, id: usize) -> Self {
|
||||||
let CbIdx {
|
|
||||||
gi_id,
|
|
||||||
gi_gen,
|
|
||||||
listener_idx,
|
|
||||||
} = cb;
|
|
||||||
|
|
||||||
let component_id = Index::from_raw_parts(gi_id, gi_gen);
|
|
||||||
Self {
|
Self {
|
||||||
component_id,
|
component_id: scope,
|
||||||
listener_id: listener_idx,
|
listener_id: id,
|
||||||
event,
|
event,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -65,20 +65,21 @@
|
||||||
//! - dioxus-liveview (SSR + StringRenderer)
|
//! - dioxus-liveview (SSR + StringRenderer)
|
||||||
//!
|
//!
|
||||||
|
|
||||||
pub mod changelist; // An "edit phase" described by transitions and edit operations
|
|
||||||
pub mod component; // Logic for extending FC
|
pub mod component; // Logic for extending FC
|
||||||
pub mod context; // Logic for providing hook + context functionality to user components
|
pub mod context; // Logic for providing hook + context functionality to user components
|
||||||
pub mod debug_renderer; // Test harness for validating that lifecycles and diffs work appropriately
|
pub mod debug_renderer;
|
||||||
// pub mod diff;
|
pub mod patch; // An "edit phase" described by transitions and edit operations // Test harness for validating that lifecycles and diffs work appropriately
|
||||||
// pub mod patch; // The diffing algorithm that builds the ChangeList
|
// pub mod diff;
|
||||||
pub mod dodriodiff; // The diffing algorithm that builds the ChangeList
|
// pub mod patch; // The diffing algorithm that builds the ChangeList
|
||||||
|
pub mod diff;
|
||||||
|
// the diffing algorithm that builds the ChangeList
|
||||||
pub mod error; // Error type we expose to the renderers
|
pub mod error; // Error type we expose to the renderers
|
||||||
pub mod events; // Manages the synthetic event API
|
pub mod events; // Manages the synthetic event API
|
||||||
pub mod hooks; // Built-in hooks
|
pub mod hooks; // Built-in hooks
|
||||||
pub mod nodebuilder; // Logic for building VNodes with a direct syntax
|
pub mod nodebuilder; // Logic for building VNodes with a direct syntax
|
||||||
pub mod nodes; // Logic for the VNodes
|
pub mod nodes; // Logic for the VNodes
|
||||||
pub mod scope; // Logic for single components
|
pub mod scope; // Logic for single components
|
||||||
pub mod validation; // Logic for validating trees
|
// pub mod validation; // Logic for validating trees
|
||||||
pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
|
pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
|
||||||
|
|
||||||
pub mod builder {
|
pub mod builder {
|
||||||
|
@ -96,7 +97,12 @@ pub(crate) mod innerlude {
|
||||||
pub(crate) use crate::virtual_dom::VirtualDom;
|
pub(crate) use crate::virtual_dom::VirtualDom;
|
||||||
pub(crate) use nodes::*;
|
pub(crate) use nodes::*;
|
||||||
|
|
||||||
pub use crate::changelist::CbIdx;
|
pub use crate::component::ScopeIdx;
|
||||||
|
pub use crate::diff::DiffMachine;
|
||||||
|
pub use crate::events::EventTrigger;
|
||||||
|
pub use crate::patch::{EditList, EditMachine};
|
||||||
|
// pub use crate::patchdx;
|
||||||
|
// pub use crate::patchtList;
|
||||||
// pub use nodes::iterables::IterableNodes;
|
// pub use nodes::iterables::IterableNodes;
|
||||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
||||||
|
|
||||||
|
@ -138,6 +144,7 @@ pub mod prelude {
|
||||||
|
|
||||||
// expose our bumpalo type
|
// expose our bumpalo type
|
||||||
pub use bumpalo;
|
pub use bumpalo;
|
||||||
|
pub use bumpalo::Bump;
|
||||||
|
|
||||||
// Re-export the FC macro
|
// Re-export the FC macro
|
||||||
pub use crate as dioxus;
|
pub use crate as dioxus;
|
||||||
|
@ -146,8 +153,8 @@ pub mod prelude {
|
||||||
pub use dioxus_core_macro::format_args_f;
|
pub use dioxus_core_macro::format_args_f;
|
||||||
pub use dioxus_core_macro::{fc, html, rsx};
|
pub use dioxus_core_macro::{fc, html, rsx};
|
||||||
|
|
||||||
// pub use crate::diff::DiffMachine;
|
pub use crate::component::ScopeIdx;
|
||||||
pub use crate::dodriodiff::DiffMachine;
|
pub use crate::diff::DiffMachine;
|
||||||
|
|
||||||
pub use crate::hooks::*;
|
pub use crate::hooks::*;
|
||||||
}
|
}
|
||||||
|
|
|
@ -333,7 +333,7 @@ where
|
||||||
///
|
///
|
||||||
/// // A button that does something when clicked!
|
/// // A button that does something when clicked!
|
||||||
/// let my_button = button(&b)
|
/// let my_button = button(&b)
|
||||||
/// .on("click", |root, vdom, event| {
|
/// .on("click", |event| {
|
||||||
/// // ...
|
/// // ...
|
||||||
/// })
|
/// })
|
||||||
/// .finish();
|
/// .finish();
|
||||||
|
|
|
@ -94,7 +94,8 @@ mod vnode {
|
||||||
}
|
}
|
||||||
|
|
||||||
mod velement {
|
mod velement {
|
||||||
use crate::events::VirtualEvent;
|
|
||||||
|
// use crate::{events::VirtualEvent, innerlude::CbIdx};
|
||||||
|
|
||||||
use super::*;
|
use super::*;
|
||||||
use std::fmt::Debug;
|
use std::fmt::Debug;
|
||||||
|
@ -179,6 +180,11 @@ mod velement {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub struct ListenerHandle {
|
||||||
|
pub event: &'static str,
|
||||||
|
pub idx: CbIdx,
|
||||||
|
}
|
||||||
|
|
||||||
/// An event listener.
|
/// An event listener.
|
||||||
pub struct Listener<'bump> {
|
pub struct Listener<'bump> {
|
||||||
/// The type of event to listen for.
|
/// The type of event to listen for.
|
||||||
|
|
|
@ -1,80 +1,734 @@
|
||||||
use fxhash::FxHashMap;
|
//! Changelist
|
||||||
|
//! ----------
|
||||||
|
//!
|
||||||
|
//! This module exposes the "changelist" object which allows 3rd party implementors to handle diffs to the virtual dom.
|
||||||
|
//!
|
||||||
|
//! # Design
|
||||||
|
//! ---
|
||||||
|
//! In essence, the changelist object connects a diff of two vdoms to the actual edits required to update the output renderer.
|
||||||
|
//!
|
||||||
|
//! This abstraction relies on the assumption that the final renderer accepts a tree of elements. For most target platforms,
|
||||||
|
//! this is an appropriate abstraction .
|
||||||
|
//!
|
||||||
|
//! During the diff phase, the change list is built. Once the diff phase is over, the change list is finished and returned back
|
||||||
|
//! to the renderer. The renderer is responsible for propogating the updates to the final display.
|
||||||
|
//!
|
||||||
|
//! Because the change list references data internal to the vdom, it needs to be consumed by the renderer before the vdom
|
||||||
|
//! can continue to work. This means once a change list is generated, it should be consumed as fast as possible, otherwise the
|
||||||
|
//! dom will be blocked from progressing. This is enforced by lifetimes on the returend changelist object.
|
||||||
|
//!
|
||||||
|
//!
|
||||||
|
|
||||||
use crate::innerlude::{VNode, VText};
|
use bumpalo::Bump;
|
||||||
|
|
||||||
/// A Patch encodes an operation that modifies a real DOM element.
|
use crate::innerlude::{Listener, ScopeIdx};
|
||||||
|
use serde::{Deserialize, Serialize};
|
||||||
|
/// The `Edit` represents a single modifcation of the renderer tree.
|
||||||
///
|
///
|
||||||
/// To update the real DOM that a user sees you'll want to first diff your
|
|
||||||
/// old virtual dom and new virtual dom.
|
|
||||||
///
|
///
|
||||||
/// This diff operation will generate `Vec<Patch>` with zero or more patches that, when
|
|
||||||
/// applied to your real DOM, will make your real DOM look like your new virtual dom.
|
|
||||||
///
|
///
|
||||||
/// Each Patch has a u32 node index that helps us identify the real DOM node that it applies to.
|
|
||||||
///
|
///
|
||||||
/// Our old virtual dom's nodes are indexed depth first, as shown in this illustration
|
|
||||||
/// (0 being the root node, 1 being it's first child, 2 being it's first child's first child).
|
|
||||||
///
|
///
|
||||||
/// ```text
|
|
||||||
/// .─.
|
|
||||||
/// ( 0 )
|
|
||||||
/// `┬'
|
|
||||||
/// ┌────┴──────┐
|
|
||||||
/// │ │
|
|
||||||
/// ▼ ▼
|
|
||||||
/// .─. .─.
|
|
||||||
/// ( 1 ) ( 4 )
|
|
||||||
/// `┬' `─'
|
|
||||||
/// ┌────┴───┐ ├─────┬─────┐
|
|
||||||
/// │ │ │ │ │
|
|
||||||
/// ▼ ▼ ▼ ▼ ▼
|
|
||||||
/// .─. .─. .─. .─. .─.
|
|
||||||
/// ( 2 ) ( 3 ) ( 5 ) ( 6 ) ( 7 )
|
|
||||||
/// `─' `─' `─' `─' `─'
|
|
||||||
/// ```
|
|
||||||
///
|
///
|
||||||
/// The patching process is tested in a real browser in crates/virtual-dom-rs/tests/diff_patch.rs
|
///
|
||||||
|
///
|
||||||
// #[derive(serde::Serialize, serde::Deserialize)]
|
/// todo@ jon: allow serde to be optional
|
||||||
pub enum Patch<'a> {
|
#[derive(Debug, Serialize, Deserialize)]
|
||||||
/// Append a vector of child nodes to a parent node id.
|
#[serde(tag = "type")]
|
||||||
AppendChildren(NodeIdx, Vec<&'a VNode<'a>>),
|
pub enum Edit<'d> {
|
||||||
|
SetText { text: &'d str },
|
||||||
/// For a `node_i32`, remove all children besides the first `len`
|
RemoveSelfAndNextSiblings {},
|
||||||
TruncateChildren(NodeIdx, usize),
|
ReplaceWith,
|
||||||
|
SetAttribute { name: &'d str, value: &'d str },
|
||||||
/// Replace a node with another node. This typically happens when a node's tag changes.
|
RemoveAttribute { name: &'d str },
|
||||||
/// ex: <div> becomes <span>
|
PushReverseChild { n: u32 },
|
||||||
Replace(NodeIdx, &'a VNode<'a>),
|
PopPushChild { n: u32 },
|
||||||
|
Pop,
|
||||||
/// Add attributes that the new node has that the old node does not
|
AppendChild,
|
||||||
AddAttributes(NodeIdx, FxHashMap<&'a str, &'a str>),
|
CreateTextNode { text: &'d str },
|
||||||
|
CreateElement { tag_name: &'d str },
|
||||||
/// Remove attributes that the old node had that the new node doesn't
|
NewEventListener { event_type: &'d str, s: ScopeIdx },
|
||||||
RemoveAttributes(NodeIdx, Vec<&'a str>),
|
UpdateEventListener { event_type: &'d str, s: ScopeIdx },
|
||||||
|
RemoveEventListener { event_type: &'d str },
|
||||||
/// Change the text of a Text node.
|
CreateElementNs { tag_name: &'d str, ns: &'d str },
|
||||||
ChangeText(NodeIdx, &'a VText<'a>),
|
SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 },
|
||||||
|
PushChild { n: u32 },
|
||||||
|
PushTemporary { temp: u32 },
|
||||||
|
InsertBefore,
|
||||||
|
PopPushReverseChild { n: u32 },
|
||||||
|
RemoveChild { n: u32 },
|
||||||
|
SetClass { class_name: &'d str },
|
||||||
|
PushKnown { node: ScopeIdx },
|
||||||
}
|
}
|
||||||
|
|
||||||
type NodeIdx = usize;
|
pub type EditList<'src> = Vec<Edit<'src>>;
|
||||||
|
|
||||||
impl<'a> Patch<'a> {
|
pub struct EditMachine<'src> {
|
||||||
/// Every Patch is meant to be applied to a specific node within the DOM. Get the
|
pub traversal: Traversal,
|
||||||
/// index of the DOM node that this patch should apply to. DOM nodes are indexed
|
next_temporary: u32,
|
||||||
/// depth first with the root node in the tree having index 0.
|
forcing_new_listeners: bool,
|
||||||
pub fn node_idx(&self) -> usize {
|
|
||||||
match self {
|
pub emitter: EditList<'src>,
|
||||||
Patch::AppendChildren(node_idx, _) => *node_idx,
|
}
|
||||||
Patch::TruncateChildren(node_idx, _) => *node_idx,
|
|
||||||
Patch::Replace(node_idx, _) => *node_idx,
|
impl<'b> EditMachine<'b> {
|
||||||
Patch::AddAttributes(node_idx, _) => *node_idx,
|
pub fn new(_bump: &'b Bump) -> Self {
|
||||||
Patch::RemoveAttributes(node_idx, _) => *node_idx,
|
Self {
|
||||||
Patch::ChangeText(node_idx, _) => *node_idx,
|
traversal: Traversal::new(),
|
||||||
|
next_temporary: 0,
|
||||||
|
forcing_new_listeners: false,
|
||||||
|
emitter: EditList::default(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Traversal methods.
|
||||||
|
pub fn go_down(&mut self) {
|
||||||
|
self.traversal.down();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_down_to_child(&mut self, index: usize) {
|
||||||
|
self.traversal.down();
|
||||||
|
self.traversal.sibling(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_down_to_reverse_child(&mut self, index: usize) {
|
||||||
|
self.traversal.down();
|
||||||
|
self.traversal.reverse_sibling(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_up(&mut self) {
|
||||||
|
self.traversal.up();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to_sibling(&mut self, index: usize) {
|
||||||
|
self.traversal.sibling(index);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_to_temp_sibling(&mut self, temp: u32) {
|
||||||
|
self.traversal.up();
|
||||||
|
self.traversal.down_to_temp(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn go_down_to_temp_child(&mut self, temp: u32) {
|
||||||
|
self.traversal.down_to_temp(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn commit_traversal(&mut self) {
|
||||||
|
if self.traversal.is_committed() {
|
||||||
|
log::debug!("Traversal already committed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
for mv in self.traversal.commit() {
|
||||||
|
match mv {
|
||||||
|
MoveTo::Parent => {
|
||||||
|
log::debug!("emit: pop");
|
||||||
|
self.emitter.push(Edit::Pop {});
|
||||||
|
// self.emitter.pop();
|
||||||
|
}
|
||||||
|
MoveTo::Child(n) => {
|
||||||
|
log::debug!("emit: push_child({})", n);
|
||||||
|
self.emitter.push(Edit::PushChild { n });
|
||||||
|
}
|
||||||
|
MoveTo::ReverseChild(n) => {
|
||||||
|
log::debug!("emit: push_reverse_child({})", n);
|
||||||
|
self.emitter.push(Edit::PushReverseChild { n });
|
||||||
|
// self.emitter.push_reverse_child(n);
|
||||||
|
}
|
||||||
|
MoveTo::Sibling(n) => {
|
||||||
|
log::debug!("emit: pop_push_child({})", n);
|
||||||
|
self.emitter.push(Edit::PopPushChild { n });
|
||||||
|
// self.emitter.pop_push_child(n);
|
||||||
|
}
|
||||||
|
MoveTo::ReverseSibling(n) => {
|
||||||
|
log::debug!("emit: pop_push_reverse_child({})", n);
|
||||||
|
self.emitter.push(Edit::PopPushReverseChild { n });
|
||||||
|
}
|
||||||
|
MoveTo::TempChild(temp) => {
|
||||||
|
log::debug!("emit: push_temporary({})", temp);
|
||||||
|
self.emitter.push(Edit::PushTemporary { temp });
|
||||||
|
// self.emitter.push_temporary(temp);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn traversal_is_committed(&self) -> bool {
|
||||||
|
self.traversal.is_committed()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<'a> EditMachine<'a> {
|
||||||
|
pub fn next_temporary(&self) -> u32 {
|
||||||
|
self.next_temporary
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_next_temporary(&mut self, next_temporary: u32) {
|
||||||
|
self.next_temporary = next_temporary;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn save_children_to_temporaries(&mut self, start: usize, end: usize) -> u32 {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
debug_assert!(start < end);
|
||||||
|
let temp_base = self.next_temporary;
|
||||||
|
// debug!(
|
||||||
|
// "emit: save_children_to_temporaries({}, {}, {})",
|
||||||
|
// temp_base, start, end
|
||||||
|
// );
|
||||||
|
self.next_temporary = temp_base + (end - start) as u32;
|
||||||
|
self.emitter.push(Edit::SaveChildrenToTemporaries {
|
||||||
|
temp: temp_base,
|
||||||
|
start: start as u32,
|
||||||
|
end: end as u32,
|
||||||
|
});
|
||||||
|
temp_base
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_temporary(&mut self, temp: u32) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: push_temporary({})", temp);
|
||||||
|
self.emitter.push(Edit::PushTemporary { temp });
|
||||||
|
// self.emitter.push_temporary(temp);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_child(&mut self, child: usize) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: remove_child({})", child);
|
||||||
|
// self.emitter.remove_child(child as u32);
|
||||||
|
self.emitter.push(Edit::RemoveChild { n: child as u32 })
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn insert_before(&mut self) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: insert_before()");
|
||||||
|
// self.emitter.insert_before();
|
||||||
|
self.emitter.push(Edit::InsertBefore {})
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn ensure_string(&mut self, _string: &str) -> StringKey {
|
||||||
|
todo!()
|
||||||
|
// self.strings.ensure_string(string, &self.emitter)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_text(&mut self, text: &'a str) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: set_text({:?})", text);
|
||||||
|
// self.emitter.set_text(text);
|
||||||
|
self.emitter.push(Edit::SetText { text });
|
||||||
|
// .set_text(text.as_ptr() as u32, text.len() as u32);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_self_and_next_siblings(&mut self) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: remove_self_and_next_siblings()");
|
||||||
|
self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
|
||||||
|
// self.emitter.remove_self_and_next_siblings();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn replace_with(&mut self) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: replace_with()");
|
||||||
|
self.emitter.push(Edit::ReplaceWith {});
|
||||||
|
// self.emitter.replace_with();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// todo!()
|
||||||
|
if name == "class" && !is_namespaced {
|
||||||
|
// let class_id = self.ensure_string(value);
|
||||||
|
// let class_id = self.ensure_string(value);
|
||||||
|
// debug!("emit: set_class({:?})", value);
|
||||||
|
// self.emitter.set_class(class_id.into());
|
||||||
|
self.emitter.push(Edit::SetClass { class_name: value });
|
||||||
|
} else {
|
||||||
|
self.emitter.push(Edit::SetAttribute { name, value });
|
||||||
|
// let name_id = self.ensure_string(name);
|
||||||
|
// let value_id = self.ensure_string(value);
|
||||||
|
// debug!("emit: set_attribute({:?}, {:?})", name, value);
|
||||||
|
// self.state
|
||||||
|
// .emitter
|
||||||
|
// .set_attribute(name_id.into(), value_id.into());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_attribute(&mut self, name: &'a str) {
|
||||||
|
// todo!("figure out how to get this working with ensure string");
|
||||||
|
self.emitter.push(Edit::RemoveAttribute { name });
|
||||||
|
// self.emitter.remove_attribute(name);
|
||||||
|
// debug_assert!(self.traversal_is_committed());
|
||||||
|
// // debug!("emit: remove_attribute({:?})", name);
|
||||||
|
// let name_id = self.ensure_string(name);
|
||||||
|
// self.emitter.remove_attribute(name_id.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn append_child(&mut self) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: append_child()");
|
||||||
|
self.emitter.push(Edit::AppendChild {});
|
||||||
|
// self.emitter.append_child();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_text_node(&mut self, text: &'a str) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: create_text_node({:?})", text);
|
||||||
|
// self.emitter.create_text_node(text);
|
||||||
|
self.emitter.push(Edit::CreateTextNode { text });
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_element(&mut self, tag_name: &'a str) {
|
||||||
|
// debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: create_element({:?})", tag_name);
|
||||||
|
// let tag_name_id = self.ensure_string(tag_name);
|
||||||
|
self.emitter.push(Edit::CreateElement { tag_name });
|
||||||
|
// self.emitter.create_element(tag_name);
|
||||||
|
// self.emitter.create_element(tag_name_id.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
|
||||||
|
// let tag_name_id = self.ensure_string(tag_name);
|
||||||
|
// let ns_id = self.ensure_string(ns);
|
||||||
|
// self.emitter.create_element_ns(tag_name, ns);
|
||||||
|
self.emitter.push(Edit::CreateElementNs { tag_name, ns });
|
||||||
|
// self.emitter
|
||||||
|
// .create_element_ns(tag_name_id.into(), ns_id.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn push_force_new_listeners(&mut self) -> bool {
|
||||||
|
let old = self.forcing_new_listeners;
|
||||||
|
self.forcing_new_listeners = true;
|
||||||
|
old
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn pop_force_new_listeners(&mut self, previous: bool) {
|
||||||
|
debug_assert!(self.forcing_new_listeners);
|
||||||
|
self.forcing_new_listeners = previous;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn new_event_listener(&mut self, event: &'a str, idx: CbIdx) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
self.emitter.push(Edit::NewEventListener {
|
||||||
|
event_type: event,
|
||||||
|
s: idx,
|
||||||
|
});
|
||||||
|
// todo!("Event listener not wired up yet");
|
||||||
|
// log::debug!("emit: new_event_listener({:?})", listener);
|
||||||
|
// let (a, b) = listener.get_callback_parts();
|
||||||
|
// debug_assert!(a != 0);
|
||||||
|
// // let event_id = self.ensure_string(listener.event);
|
||||||
|
// self.emitter.new_event_listener(listener.event.into(), a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_event_listener(&mut self, event: &'a str, idx: CbIdx) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
if self.forcing_new_listeners {
|
||||||
|
self.new_event_listener(event, idx);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
self.emitter.push(Edit::NewEventListener {
|
||||||
|
event_type: event,
|
||||||
|
s: idx,
|
||||||
|
});
|
||||||
|
|
||||||
|
// log::debug!("emit: update_event_listener({:?})", listener);
|
||||||
|
// // todo!("Event listener not wired up yet");
|
||||||
|
// let (a, b) = listener.get_callback_parts();
|
||||||
|
// debug_assert!(a != 0);
|
||||||
|
// self.emitter.push(Edit::UpdateEventListener {
|
||||||
|
// event_type: listener.event.into(),
|
||||||
|
// a,
|
||||||
|
// b,
|
||||||
|
// });
|
||||||
|
// self.emitter.update_event_listener(event_id.into(), a, b);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_event_listener(&mut self, event: &'a str) {
|
||||||
|
debug_assert!(self.traversal_is_committed());
|
||||||
|
self.emitter
|
||||||
|
.push(Edit::RemoveEventListener { event_type: event });
|
||||||
|
// debug!("emit: remove_event_listener({:?})", event);
|
||||||
|
// let _event_id = self.ensure_string(event);
|
||||||
|
// todo!("Event listener not wired up yet");
|
||||||
|
// self.emitter.remove_event_listener(event_id.into());
|
||||||
|
}
|
||||||
|
|
||||||
|
// #[inline]
|
||||||
|
// pub fn has_template(&mut self, id: CacheId) -> bool {
|
||||||
|
// self.templates.contains(&id)
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn save_template(&mut self, id: CacheId) {
|
||||||
|
// debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug_assert!(!self.has_template(id));
|
||||||
|
// // debug!("emit: save_template({:?})", id);
|
||||||
|
// self.templates.insert(id);
|
||||||
|
// self.emitter.save_template(id.into());
|
||||||
|
// }
|
||||||
|
|
||||||
|
// pub fn push_template(&mut self, id: CacheId) {
|
||||||
|
// debug_assert!(self.traversal_is_committed());
|
||||||
|
// debug_assert!(self.has_template(id));
|
||||||
|
// // debug!("emit: push_template({:?})", id);
|
||||||
|
// self.emitter.push_template(id.into());
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Keeps track of where we are moving in a DOM tree, and shortens traversal
|
||||||
|
// paths between mutations to their minimal number of operations.
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
pub enum MoveTo {
|
||||||
|
/// Move from the current node up to its parent.
|
||||||
|
Parent,
|
||||||
|
|
||||||
|
/// Move to the current node's n^th child.
|
||||||
|
Child(u32),
|
||||||
|
|
||||||
|
/// Move to the current node's n^th from last child.
|
||||||
|
ReverseChild(u32),
|
||||||
|
|
||||||
|
/// Move to the n^th sibling. Not relative from the current
|
||||||
|
/// location. Absolute indexed within all of the current siblings.
|
||||||
|
Sibling(u32),
|
||||||
|
|
||||||
|
/// Move to the n^th from last sibling. Not relative from the current
|
||||||
|
/// location. Absolute indexed within all of the current siblings.
|
||||||
|
ReverseSibling(u32),
|
||||||
|
|
||||||
|
/// Move down to the given saved temporary child.
|
||||||
|
TempChild(u32),
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct Traversal {
|
||||||
|
uncommitted: Vec<MoveTo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Traversal {
|
||||||
|
/// Construct a new `Traversal` with its internal storage backed by the
|
||||||
|
/// given bump arena.
|
||||||
|
pub fn new() -> Traversal {
|
||||||
|
Traversal {
|
||||||
|
uncommitted: Vec::with_capacity(32),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the traversal up in the tree.
|
||||||
|
pub fn up(&mut self) {
|
||||||
|
match self.uncommitted.last() {
|
||||||
|
Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
|
||||||
|
self.uncommitted.pop();
|
||||||
|
self.uncommitted.push(MoveTo::Parent);
|
||||||
|
}
|
||||||
|
Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) | Some(MoveTo::ReverseChild(_)) => {
|
||||||
|
self.uncommitted.pop();
|
||||||
|
// And we're back at the parent.
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.uncommitted.push(MoveTo::Parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the traversal down in the tree to the first child of the current
|
||||||
|
/// node.
|
||||||
|
pub fn down(&mut self) {
|
||||||
|
if let Some(&MoveTo::Parent) = self.uncommitted.last() {
|
||||||
|
self.uncommitted.pop();
|
||||||
|
self.sibling(0);
|
||||||
|
} else {
|
||||||
|
self.uncommitted.push(MoveTo::Child(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the traversal to the n^th sibling.
|
||||||
|
pub fn sibling(&mut self, index: usize) {
|
||||||
|
let index = index as u32;
|
||||||
|
match self.uncommitted.last_mut() {
|
||||||
|
Some(MoveTo::Sibling(ref mut n)) | Some(MoveTo::Child(ref mut n)) => {
|
||||||
|
*n = index;
|
||||||
|
}
|
||||||
|
Some(MoveTo::ReverseSibling(_)) => {
|
||||||
|
self.uncommitted.pop();
|
||||||
|
self.uncommitted.push(MoveTo::Sibling(index));
|
||||||
|
}
|
||||||
|
Some(MoveTo::TempChild(_)) | Some(MoveTo::ReverseChild(_)) => {
|
||||||
|
self.uncommitted.pop();
|
||||||
|
self.uncommitted.push(MoveTo::Child(index))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.uncommitted.push(MoveTo::Sibling(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Move the the n^th from last sibling.
|
||||||
|
pub fn reverse_sibling(&mut self, index: usize) {
|
||||||
|
let index = index as u32;
|
||||||
|
match self.uncommitted.last_mut() {
|
||||||
|
Some(MoveTo::ReverseSibling(ref mut n)) | Some(MoveTo::ReverseChild(ref mut n)) => {
|
||||||
|
*n = index;
|
||||||
|
}
|
||||||
|
Some(MoveTo::Sibling(_)) => {
|
||||||
|
self.uncommitted.pop();
|
||||||
|
self.uncommitted.push(MoveTo::ReverseSibling(index));
|
||||||
|
}
|
||||||
|
Some(MoveTo::TempChild(_)) | Some(MoveTo::Child(_)) => {
|
||||||
|
self.uncommitted.pop();
|
||||||
|
self.uncommitted.push(MoveTo::ReverseChild(index))
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
self.uncommitted.push(MoveTo::ReverseSibling(index));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Go to the given saved temporary.
|
||||||
|
pub fn down_to_temp(&mut self, temp: u32) {
|
||||||
|
match self.uncommitted.last() {
|
||||||
|
Some(MoveTo::Sibling(_)) | Some(MoveTo::ReverseSibling(_)) => {
|
||||||
|
self.uncommitted.pop();
|
||||||
|
}
|
||||||
|
Some(MoveTo::Parent)
|
||||||
|
| Some(MoveTo::TempChild(_))
|
||||||
|
| Some(MoveTo::Child(_))
|
||||||
|
| Some(MoveTo::ReverseChild(_))
|
||||||
|
| None => {
|
||||||
|
// Can't remove moves to parents since we rely on their stack
|
||||||
|
// pops.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
self.uncommitted.push(MoveTo::TempChild(temp));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Are all the traversal's moves committed? That is, are there no moves
|
||||||
|
/// that have *not* been committed yet?
|
||||||
|
#[inline]
|
||||||
|
pub fn is_committed(&self) -> bool {
|
||||||
|
// is_empty is not inlined?
|
||||||
|
self.uncommitted.is_empty()
|
||||||
|
// self.uncommitted.len() == 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Commit this traversals moves and return the optimized path from the last
|
||||||
|
/// commit.
|
||||||
|
#[inline]
|
||||||
|
pub fn commit(&mut self) -> Moves {
|
||||||
|
Moves {
|
||||||
|
inner: self.uncommitted.drain(..),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
pub fn reset(&mut self) {
|
||||||
|
self.uncommitted.clear();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct Moves<'a> {
|
||||||
|
inner: std::vec::Drain<'a, MoveTo>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Iterator for Moves<'_> {
|
||||||
|
type Item = MoveTo;
|
||||||
|
|
||||||
|
#[inline]
|
||||||
|
fn next(&mut self) -> Option<MoveTo> {
|
||||||
|
self.inner.next()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_traversal() {
|
||||||
|
fn t<F>(f: F) -> Box<dyn FnMut(&mut Traversal)>
|
||||||
|
where
|
||||||
|
F: 'static + FnMut(&mut Traversal),
|
||||||
|
{
|
||||||
|
Box::new(f) as _
|
||||||
|
}
|
||||||
|
|
||||||
|
for (mut traverse, expected_moves) in vec![
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Child(0)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.up();
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Parent],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.sibling(42);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Sibling(42)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
t.up();
|
||||||
|
}),
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
t.sibling(2);
|
||||||
|
t.up();
|
||||||
|
}),
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
t.sibling(3);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Child(3)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
t.sibling(4);
|
||||||
|
t.sibling(8);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Child(8)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.sibling(1);
|
||||||
|
t.sibling(1);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Sibling(1)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.reverse_sibling(3);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::ReverseSibling(3)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
t.reverse_sibling(3);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::ReverseChild(3)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
t.reverse_sibling(3);
|
||||||
|
t.up();
|
||||||
|
}),
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
t.reverse_sibling(3);
|
||||||
|
t.reverse_sibling(6);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::ReverseChild(6)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.up();
|
||||||
|
t.reverse_sibling(3);
|
||||||
|
t.reverse_sibling(6);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Parent, MoveTo::ReverseSibling(6)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.up();
|
||||||
|
t.sibling(3);
|
||||||
|
t.sibling(6);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Parent, MoveTo::Sibling(6)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.sibling(3);
|
||||||
|
t.sibling(6);
|
||||||
|
t.up();
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Parent],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.reverse_sibling(3);
|
||||||
|
t.reverse_sibling(6);
|
||||||
|
t.up();
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Parent],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down();
|
||||||
|
t.down_to_temp(3);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Child(0), MoveTo::TempChild(3)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down_to_temp(3);
|
||||||
|
t.sibling(5);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Child(5)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down_to_temp(3);
|
||||||
|
t.reverse_sibling(5);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::ReverseChild(5)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.down_to_temp(3);
|
||||||
|
t.up();
|
||||||
|
}),
|
||||||
|
vec![],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.sibling(2);
|
||||||
|
t.up();
|
||||||
|
t.down_to_temp(3);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Parent, MoveTo::TempChild(3)],
|
||||||
|
),
|
||||||
|
(
|
||||||
|
t(|t| {
|
||||||
|
t.up();
|
||||||
|
t.down_to_temp(3);
|
||||||
|
}),
|
||||||
|
vec![MoveTo::Parent, MoveTo::TempChild(3)],
|
||||||
|
),
|
||||||
|
] {
|
||||||
|
let mut traversal = Traversal::new();
|
||||||
|
traverse(&mut traversal);
|
||||||
|
let actual_moves: Vec<_> = traversal.commit().collect();
|
||||||
|
assert_eq!(actual_moves, expected_moves);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct PatchList<'a> {
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
patches: Vec<Patch<'a>>,
|
pub struct StringKey(u32);
|
||||||
|
|
||||||
|
impl From<StringKey> for u32 {
|
||||||
|
#[inline]
|
||||||
|
fn from(key: StringKey) -> u32 {
|
||||||
|
key.0
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,9 @@
|
||||||
|
use crate::component::ScopeIdx;
|
||||||
use crate::context::hooks::Hook;
|
use crate::context::hooks::Hook;
|
||||||
use crate::innerlude::*;
|
use crate::innerlude::*;
|
||||||
use crate::nodes::VNode;
|
use crate::nodes::VNode;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use generational_arena::Index;
|
// use generational_arena::ScopeIdx;
|
||||||
|
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId,
|
any::TypeId,
|
||||||
|
@ -32,7 +33,7 @@ pub struct Scope {
|
||||||
pub hook_arena: typed_arena::Arena<Hook>,
|
pub hook_arena: typed_arena::Arena<Hook>,
|
||||||
|
|
||||||
// Map to the parent
|
// Map to the parent
|
||||||
pub parent: Option<Index>,
|
pub parent: Option<ScopeIdx>,
|
||||||
|
|
||||||
pub frames: ActiveFrame,
|
pub frames: ActiveFrame,
|
||||||
|
|
||||||
|
@ -50,7 +51,7 @@ pub struct Scope {
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
// create a new scope from a function
|
// create a new scope from a function
|
||||||
pub fn new<'a, P1, P2: 'static>(f: FC<P1>, props: P1, parent: Option<Index>) -> Self {
|
pub fn new<'a, P1, P2: 'static>(f: FC<P1>, props: P1, parent: Option<ScopeIdx>) -> Self {
|
||||||
let hook_arena = typed_arena::Arena::new();
|
let hook_arena = typed_arena::Arena::new();
|
||||||
let hooks = RefCell::new(Vec::new());
|
let hooks = RefCell::new(Vec::new());
|
||||||
|
|
||||||
|
@ -288,7 +289,7 @@ mod tests {
|
||||||
fn test_scope() {
|
fn test_scope() {
|
||||||
let example: FC<()> = |ctx, props| {
|
let example: FC<()> = |ctx, props| {
|
||||||
use crate::builder::*;
|
use crate::builder::*;
|
||||||
ctx.render(|b| div(b).child(text("a")).finish())
|
ctx.render(|ctx| div(ctx.bump()).child(text("a")).finish())
|
||||||
};
|
};
|
||||||
|
|
||||||
let props = ();
|
let props = ();
|
||||||
|
@ -318,7 +319,8 @@ mod tests {
|
||||||
|
|
||||||
let childprops: ExampleProps<'a> = ExampleProps { name: content };
|
let childprops: ExampleProps<'a> = ExampleProps { name: content };
|
||||||
// let childprops: ExampleProps<'a> = ExampleProps { name: content };
|
// let childprops: ExampleProps<'a> = ExampleProps { name: content };
|
||||||
ctx.render(move |b: &'a Bump| {
|
ctx.render(move |ctx| {
|
||||||
|
let b = ctx.bump();
|
||||||
div(b)
|
div(b)
|
||||||
.child(text(props.name))
|
.child(text(props.name))
|
||||||
// .child(text(props.name))
|
// .child(text(props.name))
|
||||||
|
@ -336,8 +338,8 @@ mod tests {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree {
|
fn child_example<'b>(ctx: Context<'b>, props: &'b ExampleProps) -> DomTree {
|
||||||
ctx.render(move |b| {
|
ctx.render(move |ctx| {
|
||||||
div(b)
|
div(ctx.bump())
|
||||||
.child(text(props.name))
|
.child(text(props.name))
|
||||||
//
|
//
|
||||||
.finish()
|
.finish()
|
||||||
|
@ -346,8 +348,8 @@ mod tests {
|
||||||
|
|
||||||
static CHILD: FC<ExampleProps> = |ctx, props: &'_ ExampleProps| {
|
static CHILD: FC<ExampleProps> = |ctx, props: &'_ ExampleProps| {
|
||||||
// todo!()
|
// todo!()
|
||||||
ctx.render(move |b| {
|
ctx.render(move |ctx| {
|
||||||
div(b)
|
div(ctx.bump())
|
||||||
.child(text(props.name))
|
.child(text(props.name))
|
||||||
//
|
//
|
||||||
.finish()
|
.finish()
|
||||||
|
|
|
@ -1,15 +1,10 @@
|
||||||
// use crate::{changelist::EditList, nodes::VNode};
|
// use crate::{changelist::EditList, nodes::VNode};
|
||||||
use crate::{
|
|
||||||
changelist::{self, EditList},
|
|
||||||
dodriodiff::DiffMachine,
|
|
||||||
};
|
|
||||||
use crate::{events::EventTrigger, innerlude::*};
|
|
||||||
|
|
||||||
|
use crate::innerlude::*;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
|
use generational_arena::Arena;
|
||||||
use generational_arena::{Arena, Index};
|
|
||||||
use std::{
|
use std::{
|
||||||
any::{self, TypeId},
|
any::TypeId,
|
||||||
borrow::BorrowMut,
|
borrow::BorrowMut,
|
||||||
cell::{RefCell, UnsafeCell},
|
cell::{RefCell, UnsafeCell},
|
||||||
collections::{vec_deque, VecDeque},
|
collections::{vec_deque, VecDeque},
|
||||||
|
@ -28,7 +23,7 @@ pub struct VirtualDom {
|
||||||
|
|
||||||
/// The index of the root component.
|
/// The index of the root component.
|
||||||
/// Will not be ready if the dom is fresh
|
/// Will not be ready if the dom is fresh
|
||||||
base_scope: Index,
|
base_scope: ScopeIdx,
|
||||||
|
|
||||||
event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
|
event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
|
||||||
|
|
||||||
|
@ -98,11 +93,7 @@ impl VirtualDom {
|
||||||
|
|
||||||
component.run::<()>();
|
component.run::<()>();
|
||||||
|
|
||||||
diff_machine.diff_node(
|
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
||||||
component.old_frame(),
|
|
||||||
component.new_frame(),
|
|
||||||
Some(self.base_scope),
|
|
||||||
);
|
|
||||||
|
|
||||||
Ok(diff_machine.consume())
|
Ok(diff_machine.consume())
|
||||||
}
|
}
|
||||||
|
@ -173,11 +164,7 @@ impl VirtualDom {
|
||||||
|
|
||||||
component.run::<()>();
|
component.run::<()>();
|
||||||
|
|
||||||
diff_machine.diff_node(
|
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
||||||
component.old_frame(),
|
|
||||||
component.new_frame(),
|
|
||||||
Some(self.base_scope),
|
|
||||||
);
|
|
||||||
// diff_machine.diff_node(
|
// diff_machine.diff_node(
|
||||||
// component.old_frame(),
|
// component.old_frame(),
|
||||||
// component.new_frame(),
|
// component.new_frame(),
|
||||||
|
@ -252,7 +239,7 @@ pub struct LifecycleEvent {
|
||||||
pub enum LifecycleType {
|
pub enum LifecycleType {
|
||||||
// Component needs to be mounted, but its scope doesn't exist yet
|
// Component needs to be mounted, but its scope doesn't exist yet
|
||||||
Mount {
|
Mount {
|
||||||
to: Index,
|
to: ScopeIdx,
|
||||||
under: usize,
|
under: usize,
|
||||||
props: Box<dyn std::any::Any>,
|
props: Box<dyn std::any::Any>,
|
||||||
},
|
},
|
||||||
|
@ -260,17 +247,17 @@ pub enum LifecycleType {
|
||||||
// Parent was evalauted causing new props to generate
|
// Parent was evalauted causing new props to generate
|
||||||
PropsChanged {
|
PropsChanged {
|
||||||
props: Box<dyn std::any::Any>,
|
props: Box<dyn std::any::Any>,
|
||||||
component: Index,
|
component: ScopeIdx,
|
||||||
},
|
},
|
||||||
|
|
||||||
// Hook for the subscription API
|
// Hook for the subscription API
|
||||||
Callback {
|
Callback {
|
||||||
component: Index,
|
component: ScopeIdx,
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl LifecycleEvent {
|
impl LifecycleEvent {
|
||||||
fn index(&self) -> Option<Index> {
|
fn index(&self) -> Option<ScopeIdx> {
|
||||||
match &self.event_type {
|
match &self.event_type {
|
||||||
LifecycleType::Mount {
|
LifecycleType::Mount {
|
||||||
to: _,
|
to: _,
|
||||||
|
@ -290,8 +277,9 @@ mod tests {
|
||||||
#[test]
|
#[test]
|
||||||
fn start_dom() {
|
fn start_dom() {
|
||||||
let mut dom = VirtualDom::new(|ctx, props| {
|
let mut dom = VirtualDom::new(|ctx, props| {
|
||||||
ctx.render(|bump| {
|
ctx.render(|ctx| {
|
||||||
use crate::builder::*;
|
use crate::builder::*;
|
||||||
|
let bump = ctx.bump();
|
||||||
div(bump).child(text("hello, world")).finish()
|
div(bump).child(text("hello, world")).finish()
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
|
|
@ -1,9 +1,6 @@
|
||||||
use std::{borrow::Borrow, fmt::Debug, sync::Arc};
|
use std::{borrow::Borrow, fmt::Debug, sync::Arc};
|
||||||
|
|
||||||
use dioxus_core::{
|
use dioxus_core::events::{EventTrigger, MouseEvent, VirtualEvent};
|
||||||
changelist::{CbIdx, Edit},
|
|
||||||
events::{EventTrigger, MouseEvent, VirtualEvent},
|
|
||||||
};
|
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use log::debug;
|
use log::debug;
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
|
@ -91,7 +88,6 @@ impl EventDelegater {
|
||||||
.and_then(|v| v.parse().ok());
|
.and_then(|v| v.parse().ok());
|
||||||
|
|
||||||
if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
|
if let (Some(gi_id), Some(gi_gen), Some(li_idx)) = (gi_id, gi_gen, li_idx) {
|
||||||
|
|
||||||
// Call the trigger
|
// Call the trigger
|
||||||
trigger.0.as_ref()(EventTrigger::new(
|
trigger.0.as_ref()(EventTrigger::new(
|
||||||
virtual_event_from_websys_event(event),
|
virtual_event_from_websys_event(event),
|
||||||
|
|
Loading…
Reference in New Issue