feat: props memoization is more powerful
This commit solves the memoization , properly memoizing properties that don't have any generic parameters. This is a rough heuristic to prevent non-static lifetimes from creeping into props and breaking our minual lifetime management. Props that have a generic parameter are opted-out of the `partialeq` requirement and props *without* lifetimes must implement partialeq. We're going to leave manual disabling of memoization for future work.
This commit is contained in:
1 parent
7102fe5f98
commit
73047fe956
37 files changed
+877
-977
No files matched your search
@@ -7,21 +7,27 @@
|
||||
|
||||
use crate::innerlude::FC;
|
||||
|
||||
pub unsafe trait Properties: PartialEq + Sized {
|
||||
pub trait Properties: Sized {
|
||||
type Builder;
|
||||
const CAN_BE_MEMOIZED: bool;
|
||||
fn builder() -> Self::Builder;
|
||||
|
||||
/// Memoization can only happen if the props are 'static
|
||||
/// The user must know if their props are static, but if they make a mistake, UB happens
|
||||
/// Therefore it's unsafe to memeoize.
|
||||
unsafe fn memoize(&self, other: &Self) -> bool;
|
||||
}
|
||||
|
||||
unsafe impl Properties for () {
|
||||
const CAN_BE_MEMOIZED: bool = true;
|
||||
impl Properties for () {
|
||||
type Builder = EmptyBuilder;
|
||||
|
||||
fn builder() -> Self::Builder {
|
||||
EmptyBuilder {}
|
||||
}
|
||||
unsafe fn memoize(&self, _other: &Self) -> bool {
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
// We allow components to use the () generic parameter if they have no props. This impl enables the "build" method
|
||||
// that the macros use to anonymously complete prop construction.
|
||||
pub struct EmptyBuilder;
|
||||
impl EmptyBuilder {
|
||||
#[inline]
|
||||
@@ -30,6 +36,8 @@ impl EmptyBuilder {
|
||||
}
|
||||
}
|
||||
|
||||
/// This utility function launches the builder method so rsx! and html! macros can use the typed-builder pattern
|
||||
/// to initialize a component's props.
|
||||
pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
||||
T::builder()
|
||||
}
|
||||
@@ -39,9 +47,10 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
|
||||
///
|
||||
/// Fragments capture a series of children without rendering extra nodes.
|
||||
///
|
||||
///
|
||||
///
|
||||
pub static Fragment: FC<()> = |ctx| {
|
||||
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
|
||||
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
|
||||
#[allow(non_upper_case_globals)]
|
||||
pub const Fragment: FC<()> = |ctx| {
|
||||
use crate::prelude::*;
|
||||
ctx.render(LazyNodes::new(move |c| {
|
||||
crate::nodebuilder::vfragment(c, None, ctx.children())
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
//!
|
||||
//! Renderers don't actually need to own the virtual dom (it's up to the implementer).
|
||||
|
||||
use crate::innerlude::RealDom;
|
||||
use crate::{events::EventTrigger, virtual_dom::VirtualDom};
|
||||
use crate::{innerlude::Result, prelude::*};
|
||||
|
||||
@@ -40,7 +41,7 @@ impl DebugRenderer {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn step(&mut self, machine: &mut DiffMachine) -> Result<()> {
|
||||
pub fn step<Dom: RealDom>(&mut self, machine: &mut DiffMachine<Dom>) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -70,6 +71,27 @@ impl DebugRenderer {
|
||||
pub fn trigger_listener(&mut self, id: usize) -> Result<()> {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn render_nodes<'a, F>(&self, other: LazyNodes<'a, F>) -> Result<()>
|
||||
where
|
||||
F: for<'b> FnOnce(&'b NodeCtx<'a>) -> VNode<'a> + 'a,
|
||||
{
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub struct DebugVNodeSource {
|
||||
bump: Bump,
|
||||
}
|
||||
impl DebugVNodeSource {
|
||||
fn new() -> Self {
|
||||
Self { bump: Bump::new() }
|
||||
}
|
||||
|
||||
fn render_nodes(&self) -> VNode {
|
||||
// let ctx = NodeCtx
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
||||
+21
-17
@@ -44,31 +44,30 @@ use std::{
|
||||
/// single node
|
||||
pub trait RealDom {
|
||||
// Navigation
|
||||
fn push_root(&self, root: RealDomNode);
|
||||
fn pop(&self);
|
||||
fn push_root(&mut self, root: RealDomNode);
|
||||
|
||||
// Add Nodes to the dom
|
||||
fn append_child(&self);
|
||||
fn replace_with(&self);
|
||||
fn append_child(&mut self);
|
||||
fn replace_with(&mut self);
|
||||
|
||||
// Remove Nodesfrom the dom
|
||||
fn remove(&self);
|
||||
fn remove_all_children(&self);
|
||||
fn remove(&mut self);
|
||||
fn remove_all_children(&mut self);
|
||||
|
||||
// Create
|
||||
fn create_text_node(&self, text: &str) -> RealDomNode;
|
||||
fn create_element(&self, tag: &str) -> RealDomNode;
|
||||
fn create_element_ns(&self, tag: &str, namespace: &str) -> RealDomNode;
|
||||
fn create_text_node(&mut self, text: &str) -> RealDomNode;
|
||||
fn create_element(&mut self, tag: &str) -> RealDomNode;
|
||||
fn create_element_ns(&mut self, tag: &str, namespace: &str) -> RealDomNode;
|
||||
|
||||
// events
|
||||
fn new_event_listener(&self, event: &str, scope: ScopeIdx, id: usize);
|
||||
// fn new_event_listener(&self, event: &str);
|
||||
fn remove_event_listener(&self, event: &str);
|
||||
fn new_event_listener(&mut self, event: &str, scope: ScopeIdx, id: usize);
|
||||
// fn new_event_listener(&mut self, event: &str);
|
||||
fn remove_event_listener(&mut self, event: &str);
|
||||
|
||||
// modify
|
||||
fn set_text(&self, text: &str);
|
||||
fn set_attribute(&self, name: &str, value: &str, is_namespaced: bool);
|
||||
fn remove_attribute(&self, name: &str);
|
||||
fn set_text(&mut self, text: &str);
|
||||
fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool);
|
||||
fn remove_attribute(&mut self, name: &str);
|
||||
|
||||
// node ref
|
||||
fn raw_node_as_any_mut(&self) -> &mut dyn Any;
|
||||
@@ -470,7 +469,7 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
// [... node]
|
||||
//
|
||||
// The change list stack is left unchanged.
|
||||
fn diff_listeners(&self, old: &[Listener<'_>], new: &[Listener<'_>]) {
|
||||
fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) {
|
||||
if !old.is_empty() || !new.is_empty() {
|
||||
// self.dom.commit_traversal();
|
||||
}
|
||||
@@ -518,7 +517,12 @@ impl<'a, Dom: RealDom> DiffMachine<'a, Dom> {
|
||||
// [... node]
|
||||
//
|
||||
// The change list stack is left unchanged.
|
||||
fn diff_attr(&self, old: &'a [Attribute<'a>], new: &'a [Attribute<'a>], is_namespaced: bool) {
|
||||
fn diff_attr(
|
||||
&mut self,
|
||||
old: &'a [Attribute<'a>],
|
||||
new: &'a [Attribute<'a>],
|
||||
is_namespaced: bool,
|
||||
) {
|
||||
// Do O(n^2) passes to add/update and remove attributes, since
|
||||
// there are almost always very few attributes.
|
||||
//
|
||||
|
||||
@@ -11,10 +11,9 @@
|
||||
pub mod arena;
|
||||
pub mod component; // Logic for extending FC
|
||||
|
||||
// pub mod debug_renderer;
|
||||
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
|
||||
// the diffing algorithm that builds the ChangeList
|
||||
|
||||
pub mod error; // Error type we expose to the renderers
|
||||
pub mod events; // Manages the synthetic event API
|
||||
pub mod hooks; // Built-in hooks
|
||||
@@ -36,7 +35,6 @@ pub(crate) mod innerlude {
|
||||
pub use crate::hooks::*;
|
||||
pub use crate::nodebuilder::*;
|
||||
pub use crate::nodes::*;
|
||||
pub use crate::patch::*;
|
||||
pub use crate::virtual_dom::*;
|
||||
|
||||
pub type FC<P> = fn(Context<P>) -> VNode;
|
||||
|
||||
@@ -494,10 +494,22 @@ where
|
||||
/// .finish();
|
||||
/// ```
|
||||
pub fn iter_child(mut self, nodes: impl IntoIterator<Item = impl IntoVNode<'a>>) -> Self {
|
||||
let len_before = self.children.len();
|
||||
for item in nodes {
|
||||
let child = item.into_vnode(&self.ctx);
|
||||
self.children.push(child);
|
||||
}
|
||||
let len_after = self.children.len();
|
||||
if len_after > len_before {
|
||||
let last_child = self.children.last().unwrap();
|
||||
if last_child.key().is_none() {
|
||||
// TODO: Somehow get the name of the component when NodeCtx is being made
|
||||
const ERR_MSG: &str = r#"Warning: Each child in an array or iterator should have a unique "key" prop.
|
||||
Check the render method of XXXX.
|
||||
See fb.me/react-warning-keys for more information. "#;
|
||||
log::error!("{}", ERR_MSG);
|
||||
}
|
||||
}
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
+146
-97
@@ -111,9 +111,29 @@ impl<'a> VNode<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
fn get_child(&self, id: u32) -> Option<VNode<'a>> {
|
||||
fn get_child(&self, id: u32) -> Option<&'a VNode<'a>> {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn is_real(&self) -> bool {
|
||||
match self {
|
||||
VNode::Element(_) => true,
|
||||
VNode::Text(_) => true,
|
||||
VNode::Fragment(_) => false,
|
||||
VNode::Suspended => false,
|
||||
VNode::Component(_) => false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_mounted_id(&self) -> Option<RealDomNode> {
|
||||
match self {
|
||||
VNode::Element(_) => todo!(),
|
||||
VNode::Text(_) => todo!(),
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@@ -257,88 +277,68 @@ pub struct VComponent<'src> {
|
||||
}
|
||||
|
||||
impl<'a> VComponent<'a> {
|
||||
// use the type parameter on props creation and move it into a portable context
|
||||
// this lets us keep scope generic *and* downcast its props when we need to:
|
||||
// - perform comparisons when diffing (memoization)
|
||||
// TODO: lift the requirement that props need to be static
|
||||
// we want them to borrow references... maybe force implementing a "to_static_unsafe" trait
|
||||
|
||||
/// When the rsx! macro is called, it will check if the CanMemo flag is set to true (from the Props impl)
|
||||
/// If it is set to true, then this method will be called which implements automatic memoization.
|
||||
///
|
||||
/// If the CanMemo is `false`, then the macro will call the backup method which always defaults to "false"
|
||||
pub fn new<P: Properties + 'a>(
|
||||
// bump: &'a Bump,
|
||||
ctx: &NodeCtx<'a>,
|
||||
component: FC<P>,
|
||||
// props: bumpalo::boxed::Box<'a, P>,
|
||||
props: P,
|
||||
key: Option<&'a str>,
|
||||
children: &'a [VNode<'a>],
|
||||
) -> Self {
|
||||
// pub fn new<P: Properties + 'a>(component: FC<P>, props: P, key: Option<&'a str>) -> Self {
|
||||
// let bad_props = unsafe { transmogrify(props) };
|
||||
let bump = ctx.bump();
|
||||
let caller_ref = component as *const ();
|
||||
let props = bump.alloc(props);
|
||||
let user_fc = component as *const ();
|
||||
|
||||
let props = bump.alloc(props);
|
||||
let raw_props = props as *const P as *const ();
|
||||
|
||||
let comparator: Option<&dyn Fn(&VComponent) -> bool> = {
|
||||
if P::CAN_BE_MEMOIZED {
|
||||
Some(bump.alloc(move |other: &VComponent| {
|
||||
// Safety:
|
||||
// We are guaranteed that the props will be of the same type because
|
||||
// there is no way to create a VComponent other than this `new` method.
|
||||
//
|
||||
// Therefore, if the render functions are identical (by address), then so will be
|
||||
// props type paramter (because it is the same render function). Therefore, we can be
|
||||
// sure
|
||||
if caller_ref == other.user_fc {
|
||||
// let g = other.raw_ctx.downcast_ref::<P>().unwrap();
|
||||
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||
&props == &real_other
|
||||
} else {
|
||||
false
|
||||
Some(bump.alloc(move |other: &VComponent| {
|
||||
// Safety:
|
||||
// ------
|
||||
//
|
||||
// Invariants:
|
||||
// - Component function pointers are the same
|
||||
// - Generic properties on the same function pointer are the same
|
||||
// - Lifetime of P borrows from its parent
|
||||
// - The parent scope still exists when method is called
|
||||
// - Casting from T to *const () is portable
|
||||
//
|
||||
// Explanation:
|
||||
// We are guaranteed that the props will be of the same type because
|
||||
// there is no way to create a VComponent other than this `new` method.
|
||||
//
|
||||
// Therefore, if the render functions are identical (by address), then so will be
|
||||
// props type paramter (because it is the same render function). Therefore, we can be
|
||||
// sure that it is safe to interperet the previous props raw pointer as the same props
|
||||
// type. From there, we can call the props' "memoize" method to see if we can
|
||||
// avoid re-rendering the component.
|
||||
if user_fc == other.user_fc {
|
||||
let real_other = unsafe { &*(other.raw_props as *const _ as *const P) };
|
||||
let props_memoized = unsafe { props.memoize(&real_other) };
|
||||
match (props_memoized, children.len() == 0) {
|
||||
(true, true) => true,
|
||||
_ => false,
|
||||
}
|
||||
}))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}))
|
||||
};
|
||||
|
||||
// let prref: &'a P = props.as_ref();
|
||||
|
||||
// let r = create_closure(component, raw_props);
|
||||
// let caller: Rc<dyn for<'g> Fn(&'g Scope) -> VNode<'g>> = Rc::new(move |scope| {
|
||||
// // r(scope);
|
||||
// //
|
||||
// // let props2 = bad_props;
|
||||
// // props.as_ref();
|
||||
// // let ctx = Context {
|
||||
// // props: prref,
|
||||
// // scope,
|
||||
// // };
|
||||
// // let ctx: Context<'g, P> = todo!();
|
||||
// // todo!()
|
||||
// // let r = component(ctx);
|
||||
// todo!()
|
||||
// });
|
||||
let caller = create_closure(component, raw_props);
|
||||
|
||||
// let caller: Rc<dyn Fn(&Scope) -> VNode> = Rc::new(create_closure(component, raw_props));
|
||||
|
||||
let key = match key {
|
||||
Some(key) => NodeKey::new(key),
|
||||
None => NodeKey(None),
|
||||
};
|
||||
|
||||
// raw_props: Box::new(props),
|
||||
// comparator: Rc::new(props_comparator),
|
||||
Self {
|
||||
key,
|
||||
ass_scope: RefCell::new(None),
|
||||
user_fc: caller_ref,
|
||||
user_fc,
|
||||
comparator,
|
||||
raw_props,
|
||||
children,
|
||||
caller,
|
||||
ass_scope: RefCell::new(None),
|
||||
key: match key {
|
||||
Some(key) => NodeKey::new(key),
|
||||
None => NodeKey(None),
|
||||
},
|
||||
caller: create_closure(component, raw_props),
|
||||
mounted_root: Cell::new(RealDomNode::empty()),
|
||||
}
|
||||
}
|
||||
@@ -346,7 +346,7 @@ impl<'a> VComponent<'a> {
|
||||
|
||||
type Captured<'a> = Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r> + 'a>;
|
||||
|
||||
fn create_closure<'a, P: Properties + 'a>(
|
||||
fn create_closure<'a, P: 'a>(
|
||||
component: FC<P>,
|
||||
raw_props: *const (),
|
||||
) -> Rc<dyn for<'r> Fn(&'r Scope) -> VNode<'r>> {
|
||||
@@ -385,7 +385,9 @@ impl<'a> VFragment<'a> {
|
||||
}
|
||||
|
||||
/// This method converts a list of nested real/virtual nodes into a stream of nodes that are definitely associated
|
||||
/// with the real dom.
|
||||
/// with the real dom. The only types of nodes that may be returned are text, elemets, and components.
|
||||
///
|
||||
/// Components *are* considered virtual, but this iterator can't necessarily handle them without the scope arena.
|
||||
///
|
||||
/// Why?
|
||||
/// ---
|
||||
@@ -401,47 +403,80 @@ pub fn iterate_real_nodes<'a>(nodes: &'a [VNode<'a>]) -> RealNodeIterator<'a> {
|
||||
RealNodeIterator::new(nodes)
|
||||
}
|
||||
|
||||
struct RealNodeIterator<'a> {
|
||||
pub struct RealNodeIterator<'a> {
|
||||
nodes: &'a [VNode<'a>],
|
||||
|
||||
// an idx for each level of nesting
|
||||
// it's highly highly unlikely to hit 4 levels of nested fragments
|
||||
// so... we just don't support it
|
||||
nesting_idxs: [Option<u32>; 3],
|
||||
// this node is always a "real" node
|
||||
// the index is "what sibling # is it"
|
||||
// IE in a list of children on a fragment, the node will be a text node that's the 5th sibling
|
||||
node_stack: Vec<(&'a VNode<'a>, u32)>,
|
||||
}
|
||||
|
||||
impl<'a> RealNodeIterator<'a> {
|
||||
// We immediately descend to the first real node we can find
|
||||
fn new(nodes: &'a [VNode<'a>]) -> Self {
|
||||
Self {
|
||||
nodes,
|
||||
nesting_idxs: [None, None, None],
|
||||
let mut node_stack = Vec::new();
|
||||
if nodes.len() > 0 {
|
||||
let mut cur_node = nodes.get(0).unwrap();
|
||||
loop {
|
||||
node_stack.push((cur_node, 0_u32));
|
||||
if !cur_node.is_real() {
|
||||
cur_node = cur_node.get_child(0).unwrap();
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Self { nodes, node_stack }
|
||||
}
|
||||
|
||||
// advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
|
||||
fn advance_cursor(&mut self) {
|
||||
match self.nesting_idxs {
|
||||
[None, ..] => {}
|
||||
// // advances the cursor to the next element, panicing if we're on the 3rd level and still finding fragments
|
||||
// fn advance_cursor(&mut self) {
|
||||
// let (mut cur_node, mut cur_id) = self.node_stack.last().unwrap();
|
||||
|
||||
// while !cur_node.is_real() {
|
||||
// match cur_node {
|
||||
// VNode::Element(_) | VNode::Text(_) => todo!(),
|
||||
// VNode::Suspended => todo!(),
|
||||
// VNode::Component(_) => todo!(),
|
||||
// VNode::Fragment(frag) => {
|
||||
// let p = frag.children;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
fn next_node(&mut self) -> bool {
|
||||
let (mut cur_node, cur_id) = self.node_stack.last_mut().unwrap();
|
||||
|
||||
match cur_node {
|
||||
VNode::Fragment(frag) => {
|
||||
//
|
||||
if *cur_id + 1 > frag.children.len() as u32 {
|
||||
self.node_stack.pop();
|
||||
let next = self.node_stack.last_mut();
|
||||
return false;
|
||||
}
|
||||
*cur_id += 1;
|
||||
true
|
||||
}
|
||||
|
||||
VNode::Element(_) => todo!(),
|
||||
VNode::Text(_) => todo!(),
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get_current_node(&self) -> Option<&VNode<'a>> {
|
||||
match self.nesting_idxs {
|
||||
[None, None, None] => None,
|
||||
[Some(a), None, None] => Some(&self.nodes[a as usize]),
|
||||
[Some(a), Some(b), None] => {
|
||||
//
|
||||
*&self.nodes[a as usize].get_child(b).as_ref()
|
||||
}
|
||||
[Some(a), Some(b), Some(c)] => {
|
||||
//
|
||||
*&self.nodes[a as usize]
|
||||
.get_child(b)
|
||||
.unwrap()
|
||||
.get_child(c)
|
||||
.as_ref()
|
||||
}
|
||||
}
|
||||
self.node_stack.last().map(|(node, id)| match node {
|
||||
VNode::Element(_) => todo!(),
|
||||
VNode::Text(_) => todo!(),
|
||||
VNode::Fragment(_) => todo!(),
|
||||
VNode::Suspended => todo!(),
|
||||
VNode::Component(_) => todo!(),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -485,12 +520,26 @@ impl<'a> Iterator for RealNodeIterator<'a> {
|
||||
}
|
||||
|
||||
mod tests {
|
||||
use crate::debug_renderer::DebugRenderer;
|
||||
use crate::nodebuilder::LazyNodes;
|
||||
|
||||
use crate as dioxus;
|
||||
use dioxus::prelude::*;
|
||||
#[test]
|
||||
fn iterate_nodes() {
|
||||
// let t1 = LazyNodes::new(|b| {
|
||||
// //
|
||||
// });
|
||||
let rs = rsx! {
|
||||
Fragment {
|
||||
Fragment {
|
||||
Fragment {
|
||||
Fragment {
|
||||
h1 {"abc1"}
|
||||
}
|
||||
h2 {"abc2"}
|
||||
}
|
||||
h3 {"abc3"}
|
||||
}
|
||||
h4 {"abc4"}
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -1,695 +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 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.
|
||||
//!
|
||||
//! # Known Issues
|
||||
//! ----
|
||||
//! - stack machine approach does not work when 3rd party extensions inject elements (breaking our view of the dom) - solvable by the renderer
|
||||
|
||||
use crate::innerlude::ScopeIdx;
|
||||
|
||||
pub type EditList<'src> = Vec<Edit<'src>>;
|
||||
|
||||
/// The `Edit` represents a single modifcation of the renderer tree.
|
||||
/// todo @jon, go through and make certain fields static. tag names should be known at compile time
|
||||
#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serde", serde(tag = "type"))]
|
||||
#[derive(Debug)]
|
||||
pub enum Edit<'src_bump> {
|
||||
// ========================================================
|
||||
// Common Ops: The most common operation types
|
||||
// ========================================================
|
||||
SetText {
|
||||
text: &'src_bump str,
|
||||
},
|
||||
SetClass {
|
||||
class_name: &'src_bump str,
|
||||
},
|
||||
CreateTextNode {
|
||||
text: &'src_bump str,
|
||||
},
|
||||
CreateElement {
|
||||
// todo - make static?
|
||||
tag_name: &'src_bump str,
|
||||
},
|
||||
CreateElementNs {
|
||||
// todo - make static?
|
||||
tag_name: &'src_bump str,
|
||||
// todo - make static?
|
||||
ns: &'src_bump str,
|
||||
},
|
||||
|
||||
// ========================================================
|
||||
// Attributes
|
||||
// ========================================================
|
||||
SetAttribute {
|
||||
name: &'src_bump str,
|
||||
value: &'src_bump str,
|
||||
},
|
||||
RemoveAttribute {
|
||||
name: &'src_bump str,
|
||||
},
|
||||
RemoveChild {
|
||||
n: u32,
|
||||
},
|
||||
|
||||
// ============================================================
|
||||
// Event Listeners: Event types and IDs used to update the VDOM
|
||||
// ============================================================
|
||||
NewListener {
|
||||
// todo - make static?
|
||||
event: &'src_bump str,
|
||||
scope: ScopeIdx,
|
||||
id: usize,
|
||||
},
|
||||
UpdateListener {
|
||||
// todo - make static?
|
||||
event: &'src_bump str,
|
||||
scope: ScopeIdx,
|
||||
id: usize,
|
||||
},
|
||||
RemoveListener {
|
||||
// todo - make static?
|
||||
event: &'src_bump str,
|
||||
},
|
||||
|
||||
// ========================================================
|
||||
// Cached Roots: The mount point for individual components
|
||||
// Allows quick traversal to cached entrypoints
|
||||
// ========================================================
|
||||
// push a known node on to the stack
|
||||
TraverseToKnown {
|
||||
node: u32,
|
||||
// node: ScopeIdx,
|
||||
},
|
||||
// Add the current top of the stack to the known nodes
|
||||
MakeKnown {
|
||||
node: u32,
|
||||
// node: ScopeIdx,
|
||||
},
|
||||
// Remove the current top of the stack from the known nodes
|
||||
RemoveKnown,
|
||||
|
||||
// ========================================================
|
||||
// Stack OPs: Operations for manipulating the stack machine
|
||||
// ========================================================
|
||||
PushReverseChild {
|
||||
n: u32,
|
||||
},
|
||||
PopPushChild {
|
||||
n: u32,
|
||||
},
|
||||
Pop,
|
||||
AppendChild,
|
||||
RemoveSelfAndNextSiblings {},
|
||||
ReplaceWith,
|
||||
SaveChildrenToTemporaries {
|
||||
temp: u32,
|
||||
start: u32,
|
||||
end: u32,
|
||||
},
|
||||
PushChild {
|
||||
n: u32,
|
||||
},
|
||||
PushTemporary {
|
||||
temp: u32,
|
||||
},
|
||||
InsertBefore,
|
||||
PopPushReverseChild {
|
||||
n: u32,
|
||||
},
|
||||
}
|
||||
|
||||
/// The edit machine represents a stream of differences between two component trees.
|
||||
///
|
||||
/// This struct is interesting in that it keeps track of differences by borrowing
|
||||
/// from the source rather than writing to a new buffer. This means that the virtual dom
|
||||
/// *cannot* be updated while this machine is in existence without "unsafe".
|
||||
///
|
||||
/// This unsafety is handled by methods on the virtual dom and is not exposed via lib code.
|
||||
pub struct EditMachine<'lock> {
|
||||
pub traversal: Traversal,
|
||||
next_temporary: u32,
|
||||
forcing_new_listeners: bool,
|
||||
pub cur_height: u32,
|
||||
|
||||
// // if the current node is a "known" node
|
||||
// // any actions that modify this node should update the mapping
|
||||
// current_known: Option<u32>,
|
||||
pub emitter: EditList<'lock>,
|
||||
}
|
||||
|
||||
impl<'lock> EditMachine<'lock> {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
// current_known: None,
|
||||
traversal: Traversal::new(),
|
||||
cur_height: 0,
|
||||
next_temporary: 0,
|
||||
forcing_new_listeners: false,
|
||||
emitter: EditList::<'lock>::default(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// Traversal Methods
|
||||
// ===================================
|
||||
impl<'src> EditMachine<'src> {
|
||||
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() {
|
||||
return;
|
||||
}
|
||||
|
||||
for mv in self.traversal.commit() {
|
||||
match mv {
|
||||
MoveTo::Parent => self.emitter.push(Edit::Pop {}),
|
||||
MoveTo::Child(n) => self.emitter.push(Edit::PushChild { n }),
|
||||
MoveTo::ReverseChild(n) => self.emitter.push(Edit::PushReverseChild { n }),
|
||||
MoveTo::Sibling(n) => self.emitter.push(Edit::PopPushChild { n }),
|
||||
MoveTo::ReverseSibling(n) => self.emitter.push(Edit::PopPushReverseChild { n }),
|
||||
MoveTo::TempChild(temp) => self.emitter.push(Edit::PushTemporary { temp }),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn traversal_is_committed(&self) -> bool {
|
||||
self.traversal.is_committed()
|
||||
}
|
||||
}
|
||||
|
||||
// ===================================
|
||||
// Stack methods
|
||||
// ===================================
|
||||
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;
|
||||
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());
|
||||
|
||||
self.emitter.push(Edit::PushTemporary { temp });
|
||||
}
|
||||
|
||||
pub fn remove_child(&mut self, child: usize) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
|
||||
self.emitter.push(Edit::RemoveChild { n: child as u32 })
|
||||
}
|
||||
|
||||
pub fn insert_before(&mut self) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
|
||||
self.emitter.push(Edit::InsertBefore {})
|
||||
}
|
||||
|
||||
pub fn set_text(&mut self, text: &'a str) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::SetText { text });
|
||||
}
|
||||
|
||||
pub fn remove_self_and_next_siblings(&mut self) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
|
||||
}
|
||||
|
||||
pub fn replace_with(&mut self) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
// debug!("emit: replace_with()");
|
||||
// if let Some(id) = self.current_known {
|
||||
// // update mapping
|
||||
// self.emitter.push(Edit::MakeKnown{node: id});
|
||||
// self.current_known = None;
|
||||
// }
|
||||
// self.emitter.replace_with();
|
||||
self.emitter.push(Edit::ReplaceWith {});
|
||||
}
|
||||
|
||||
pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
|
||||
if name == "class" && !is_namespaced {
|
||||
self.emitter.push(Edit::SetClass { class_name: value });
|
||||
} else {
|
||||
self.emitter.push(Edit::SetAttribute { name, value });
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove_attribute(&mut self, name: &'a str) {
|
||||
self.emitter.push(Edit::RemoveAttribute { name });
|
||||
}
|
||||
|
||||
pub fn append_child(&mut self) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::AppendChild {});
|
||||
}
|
||||
|
||||
pub fn create_text_node(&mut self, text: &'a str) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::CreateTextNode { text });
|
||||
}
|
||||
|
||||
pub fn create_element(&mut self, tag_name: &'a str) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::CreateElement { tag_name });
|
||||
}
|
||||
|
||||
pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::CreateElementNs { tag_name, ns });
|
||||
}
|
||||
|
||||
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, scope: ScopeIdx, id: usize) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::NewListener { event, scope, id });
|
||||
}
|
||||
|
||||
pub fn update_event_listener(&mut self, event: &'a str, scope: ScopeIdx, id: usize) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
if self.forcing_new_listeners {
|
||||
self.new_event_listener(event, scope, id);
|
||||
return;
|
||||
}
|
||||
|
||||
self.emitter.push(Edit::NewListener { event, scope, id });
|
||||
}
|
||||
|
||||
pub fn remove_event_listener(&mut self, event: &'a str) {
|
||||
debug_assert!(self.traversal_is_committed());
|
||||
self.emitter.push(Edit::RemoveListener { event });
|
||||
}
|
||||
|
||||
pub fn save_known_root(&mut self, id: u32) {
|
||||
log::debug!("emit: save_known_root({:?})", id);
|
||||
self.emitter.push(Edit::MakeKnown { node: id })
|
||||
}
|
||||
|
||||
pub fn load_known_root(&mut self, id: u32) {
|
||||
log::debug!("emit: TraverseToKnown({:?})", id);
|
||||
self.emitter.push(Edit::TraverseToKnown { node: id })
|
||||
}
|
||||
}
|
||||
|
||||
// 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 {
|
||||
self.uncommitted.is_empty()
|
||||
}
|
||||
|
||||
/// Commit this traversals moves and return the optimized path from the last
|
||||
/// commit.
|
||||
#[inline]
|
||||
pub fn commit(&mut self) -> std::vec::Drain<'_, MoveTo> {
|
||||
self.uncommitted.drain(..)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
self.uncommitted.clear();
|
||||
}
|
||||
}
|
||||
|
||||
#[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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -64,8 +64,11 @@ pub struct VirtualDom {
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
pub struct RealDomNode(u32);
|
||||
pub struct RealDomNode(pub u32);
|
||||
impl RealDomNode {
|
||||
pub fn new(id: u32) -> Self {
|
||||
Self(id)
|
||||
}
|
||||
pub fn empty() -> Self {
|
||||
Self(u32::MIN)
|
||||
}
|
||||
@@ -318,7 +321,8 @@ impl VirtualDom {
|
||||
cur_component.run_scope()?;
|
||||
// diff_machine.change_list.load_known_root(1);
|
||||
|
||||
let (old, new) = cur_component.get_frames_mut();
|
||||
let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
|
||||
// let (old, new) = cur_component.get_frames_mut();
|
||||
diff_machine.diff_node(old, new);
|
||||
|
||||
// cur_height = cur_component.height;
|
||||
@@ -527,7 +531,8 @@ impl Scope {
|
||||
let EventTrigger {
|
||||
listener_id, event, ..
|
||||
} = trigger;
|
||||
//
|
||||
|
||||
// todo: implement scanning for outdated events
|
||||
unsafe {
|
||||
// Convert the raw ptr into an actual object
|
||||
// This operation is assumed to be safe
|
||||
@@ -547,12 +552,6 @@ impl Scope {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn get_frames_mut<'bump>(
|
||||
&'bump mut self,
|
||||
) -> (&'bump mut VNode<'bump>, &'bump mut VNode<'bump>) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
pub fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||
self.frames.current_head_node()
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user