mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-23 04:33:06 +00:00
WIP: still a bit stumped on DFS vs BFS
This commit is contained in:
parent
89f2290692
commit
3740f81383
6 changed files with 205 additions and 291 deletions
|
@ -1,34 +1,19 @@
|
||||||
//! An example that shows how to:
|
|
||||||
//! create a scope,
|
|
||||||
//! render a component,
|
|
||||||
//! change some data
|
|
||||||
//! render it again
|
|
||||||
//! consume the diffs and write that to a renderer
|
|
||||||
|
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
|
|
||||||
fn main() -> Result<(), ()> {
|
fn main() -> Result<(), ()> {
|
||||||
let p1 = Props { name: "bob".into() };
|
let p1 = Props { name: "bob".into() };
|
||||||
|
|
||||||
let _vdom = VirtualDom::new_with_props(Example, p1);
|
let mut vdom = VirtualDom::new_with_props(Example, p1);
|
||||||
// vdom.progress()?;
|
vdom.update_props(|p: &mut Props| {});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug, PartialEq)]
|
||||||
struct Props {
|
struct Props {
|
||||||
name: String,
|
name: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
// impl Properties for Props {
|
|
||||||
// fn call(&self, ptr: *const ()) {}
|
|
||||||
|
|
||||||
// // fn new() -> Self {
|
|
||||||
// // todo!()
|
|
||||||
// // }
|
|
||||||
// }
|
|
||||||
|
|
||||||
static Example: FC<Props> = |ctx, _props| {
|
static Example: FC<Props> = |ctx, _props| {
|
||||||
ctx.render(html! {
|
ctx.render(html! {
|
||||||
<div>
|
<div>
|
||||||
|
|
|
@ -32,10 +32,14 @@
|
||||||
//!
|
//!
|
||||||
//! More info on how to improve this diffing algorithm:
|
//! More info on how to improve this diffing algorithm:
|
||||||
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
|
||||||
use crate::innerlude::*;
|
use crate::{
|
||||||
|
innerlude::*,
|
||||||
|
scope::{create_scoped, Scoped},
|
||||||
|
};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use fxhash::{FxHashMap, FxHashSet};
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
use std::cmp::Ordering;
|
use generational_arena::Arena;
|
||||||
|
use std::{cell::RefCell, cmp::Ordering, collections::VecDeque, rc::Rc};
|
||||||
|
|
||||||
/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
|
/// The DiffState is a cursor internal to the VirtualDOM's diffing algorithm that allows persistence of state while
|
||||||
/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
|
/// diffing trees of components. This means we can "re-enter" a subtree of a component by queuing a "NeedToDiff" event.
|
||||||
|
@ -49,20 +53,23 @@ use std::cmp::Ordering;
|
||||||
/// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
|
/// The order of these re-entrances is stored in the DiffState itself. The DiffState comes pre-loaded with a set of components
|
||||||
/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
|
/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
|
||||||
/// subscriptions and props changes.
|
/// subscriptions and props changes.
|
||||||
pub struct DiffMachine<'a> {
|
pub struct DiffMachine<'a, 'b> {
|
||||||
pub change_list: EditMachine<'a>,
|
pub change_list: EditMachine<'a>,
|
||||||
immediate_queue: Vec<ScopeIdx>,
|
|
||||||
diffed: FxHashSet<ScopeIdx>,
|
pub vdom: &'b VirtualDom,
|
||||||
need_to_diff: FxHashSet<ScopeIdx>,
|
pub cur_idx: ScopeIdx,
|
||||||
|
pub diffed: FxHashSet<ScopeIdx>,
|
||||||
|
pub need_to_diff: FxHashSet<ScopeIdx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DiffMachine<'a> {
|
impl<'a, 'b> DiffMachine<'a, 'b> {
|
||||||
pub fn new(bump: &'a Bump) -> Self {
|
pub fn new(vdom: &'b VirtualDom, bump: &'a Bump, idx: ScopeIdx) -> Self {
|
||||||
Self {
|
Self {
|
||||||
|
cur_idx: idx,
|
||||||
change_list: EditMachine::new(bump),
|
change_list: EditMachine::new(bump),
|
||||||
immediate_queue: Vec::new(),
|
|
||||||
diffed: FxHashSet::default(),
|
diffed: FxHashSet::default(),
|
||||||
need_to_diff: FxHashSet::default(),
|
need_to_diff: FxHashSet::default(),
|
||||||
|
vdom,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -71,6 +78,7 @@ impl<'a> DiffMachine<'a> {
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
|
pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
|
||||||
|
// pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
|
||||||
/*
|
/*
|
||||||
For each valid case, we "commit traversal", meaning we save this current position in the tree.
|
For each valid case, we "commit traversal", meaning we save this current position in the tree.
|
||||||
Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
|
Then, we diff and queue an edit event (via chagelist). s single trees - when components show up, we save that traversal and then re-enter later.
|
||||||
|
@ -120,7 +128,45 @@ impl<'a> DiffMachine<'a> {
|
||||||
todo!("Usage of component VNode not currently supported");
|
todo!("Usage of component VNode not currently supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
(_, VNode::Component(_)) | (VNode::Component(_), _) => {
|
(_, VNode::Component(new)) => {
|
||||||
|
// let VComponent {
|
||||||
|
// props,
|
||||||
|
// props_type,
|
||||||
|
// comp,
|
||||||
|
// caller,
|
||||||
|
// assigned_scope,
|
||||||
|
// ..
|
||||||
|
// } = *new;
|
||||||
|
|
||||||
|
// make the component
|
||||||
|
// let idx = unsafe {
|
||||||
|
// // let vdom = &mut *self.vdom;
|
||||||
|
// vdom.insert_with(|f| {
|
||||||
|
// todo!()
|
||||||
|
// //
|
||||||
|
// // create_scoped(caller, props, myidx, parent)
|
||||||
|
// })
|
||||||
|
// };
|
||||||
|
|
||||||
|
// we have no stable reference to work from
|
||||||
|
// push the lifecycle event onto the queue
|
||||||
|
// self.lifecycle_events
|
||||||
|
// .borrow_mut()
|
||||||
|
// .push_back(LifecycleEvent {
|
||||||
|
// event_type: LifecycleType::Mount {
|
||||||
|
// props: new.props,
|
||||||
|
// to: self.cur_idx,
|
||||||
|
// },
|
||||||
|
// });
|
||||||
|
// we need to associaote this new component with a scope...
|
||||||
|
|
||||||
|
// self.change_list.save_known_root(id)
|
||||||
|
self.change_list.commit_traversal();
|
||||||
|
|
||||||
|
// push the current
|
||||||
|
}
|
||||||
|
|
||||||
|
(VNode::Component(old), _) => {
|
||||||
todo!("Usage of component VNode not currently supported");
|
todo!("Usage of component VNode not currently supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -137,7 +183,8 @@ impl<'a> DiffMachine<'a> {
|
||||||
// [... node]
|
// [... node]
|
||||||
//
|
//
|
||||||
// The change list stack is left unchanged.
|
// The change list stack is left unchanged.
|
||||||
fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) {
|
fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) {
|
||||||
|
// fn diff_listeners(&mut self, old: &[Listener<'a>], new: &[Listener<'a>]) {
|
||||||
if !old.is_empty() || !new.is_empty() {
|
if !old.is_empty() || !new.is_empty() {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
}
|
}
|
||||||
|
|
|
@ -296,11 +296,13 @@ mod vtext {
|
||||||
/// Virtual Components for custom user-defined components
|
/// Virtual Components for custom user-defined components
|
||||||
/// Only supports the functional syntax
|
/// Only supports the functional syntax
|
||||||
mod vcomponent {
|
mod vcomponent {
|
||||||
use crate::innerlude::{Context, FC};
|
use crate::innerlude::{Context, ScopeIdx, FC};
|
||||||
use std::{any::TypeId, marker::PhantomData};
|
use std::{any::TypeId, cell::RefCell, marker::PhantomData, rc::Rc};
|
||||||
|
|
||||||
use super::DomTree;
|
use super::DomTree;
|
||||||
|
|
||||||
|
pub type StableScopeAddres = Rc<RefCell<Option<ScopeIdx>>>;
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub struct VComponent<'src> {
|
pub struct VComponent<'src> {
|
||||||
_p: PhantomData<&'src ()>,
|
_p: PhantomData<&'src ()>,
|
||||||
|
@ -308,6 +310,11 @@ mod vcomponent {
|
||||||
pub(crate) props_type: TypeId,
|
pub(crate) props_type: TypeId,
|
||||||
pub(crate) comp: *const (),
|
pub(crate) comp: *const (),
|
||||||
pub(crate) caller: Caller,
|
pub(crate) caller: Caller,
|
||||||
|
|
||||||
|
// once a component gets mounted, its parent gets a stable address.
|
||||||
|
// this way we can carry the scope index from between renders
|
||||||
|
// genius, really!
|
||||||
|
pub assigned_scope: StableScopeAddres,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct Caller(Box<dyn Fn(Context) -> DomTree>);
|
pub struct Caller(Box<dyn Fn(Context) -> DomTree>);
|
||||||
|
@ -323,10 +330,10 @@ mod vcomponent {
|
||||||
// - perform comparisons when diffing (memoization)
|
// - perform comparisons when diffing (memoization)
|
||||||
// -
|
// -
|
||||||
pub fn new<P>(comp: FC<P>, props: P) -> Self {
|
pub fn new<P>(comp: FC<P>, props: P) -> Self {
|
||||||
let caller = move |ctx: Context| {
|
// let caller = move |ctx: Context| {
|
||||||
let t = comp(ctx, &props);
|
// let t = comp(ctx, &props);
|
||||||
t
|
// t
|
||||||
};
|
// };
|
||||||
// let _caller = comp as *const ();
|
// let _caller = comp as *const ();
|
||||||
// let _props = Box::new(props);
|
// let _props = Box::new(props);
|
||||||
|
|
||||||
|
|
|
@ -126,6 +126,7 @@ pub struct EditMachine<'src> {
|
||||||
pub traversal: Traversal,
|
pub traversal: Traversal,
|
||||||
next_temporary: u32,
|
next_temporary: u32,
|
||||||
forcing_new_listeners: bool,
|
forcing_new_listeners: bool,
|
||||||
|
|
||||||
pub emitter: EditList<'src>,
|
pub emitter: EditList<'src>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -214,10 +215,6 @@ impl<'a> EditMachine<'a> {
|
||||||
debug_assert!(self.traversal_is_committed());
|
debug_assert!(self.traversal_is_committed());
|
||||||
debug_assert!(start < end);
|
debug_assert!(start < end);
|
||||||
let temp_base = self.next_temporary;
|
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.next_temporary = temp_base + (end - start) as u32;
|
||||||
self.emitter.push(Edit::SaveChildrenToTemporaries {
|
self.emitter.push(Edit::SaveChildrenToTemporaries {
|
||||||
temp: temp_base,
|
temp: temp_base,
|
||||||
|
@ -367,6 +364,11 @@ impl<'a> EditMachine<'a> {
|
||||||
// debug!("emit: remove_event_listener({:?})", event);
|
// debug!("emit: remove_event_listener({:?})", event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn save_known_root(&mut self, id: ScopeIdx) {
|
||||||
|
log::debug!("emit: save_known_root({:?})", id);
|
||||||
|
self.emitter.push(Edit::MakeKnown { node: id })
|
||||||
|
}
|
||||||
|
|
||||||
// pub fn save_template(&mut self, id: CacheId) {
|
// pub fn save_template(&mut self, id: CacheId) {
|
||||||
// debug_assert!(self.traversal_is_committed());
|
// debug_assert!(self.traversal_is_committed());
|
||||||
// debug_assert!(!self.has_template(id));
|
// debug_assert!(!self.has_template(id));
|
||||||
|
|
|
@ -12,11 +12,14 @@ use std::{
|
||||||
};
|
};
|
||||||
|
|
||||||
pub trait Properties: PartialEq {}
|
pub trait Properties: PartialEq {}
|
||||||
impl Properties for () {}
|
// just for now
|
||||||
|
impl<T: PartialEq> Properties for T {}
|
||||||
|
|
||||||
pub trait Scoped {
|
pub trait Scoped {
|
||||||
fn run(&mut self);
|
fn run(&mut self);
|
||||||
fn compare_props(&self, new: &dyn std::any::Any) -> bool;
|
fn compare_props(&self, new: &dyn std::any::Any) -> bool;
|
||||||
fn call_listener(&mut self, trigger: EventTrigger);
|
fn call_listener(&mut self, trigger: EventTrigger);
|
||||||
|
|
||||||
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
|
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
|
||||||
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
|
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
|
||||||
}
|
}
|
||||||
|
@ -46,6 +49,7 @@ pub struct Scope<P: Properties> {
|
||||||
|
|
||||||
// These hooks are actually references into the hook arena
|
// These hooks are actually references into the hook arena
|
||||||
// These two could be combined with "OwningRef" to remove unsafe usage
|
// These two could be combined with "OwningRef" to remove unsafe usage
|
||||||
|
// or we could dedicate a tiny bump arena just for them
|
||||||
// could also use ourborous
|
// could also use ourborous
|
||||||
pub hooks: RefCell<Vec<*mut Hook>>,
|
pub hooks: RefCell<Vec<*mut Hook>>,
|
||||||
pub hook_arena: typed_arena::Arena<Hook>,
|
pub hook_arena: typed_arena::Arena<Hook>,
|
||||||
|
@ -95,8 +99,51 @@ pub fn create_scoped<P: Properties + 'static>(
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Properties + 'static> Scoped for Scope<P> {
|
impl<P: Properties + 'static> Scoped for Scope<P> {
|
||||||
fn run(&mut self) {
|
/// Create a new context and run the component with references from the Virtual Dom
|
||||||
self.run()
|
/// This function downcasts the function pointer based on the stored props_type
|
||||||
|
///
|
||||||
|
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
|
||||||
|
fn run<'bump>(&'bump mut self) {
|
||||||
|
let frame = {
|
||||||
|
let frame = self.frames.next();
|
||||||
|
frame.bump.reset();
|
||||||
|
frame
|
||||||
|
};
|
||||||
|
|
||||||
|
let node_slot = std::rc::Rc::new(RefCell::new(None));
|
||||||
|
|
||||||
|
let ctx: Context<'bump> = Context {
|
||||||
|
arena: &self.hook_arena,
|
||||||
|
hooks: &self.hooks,
|
||||||
|
bump: &frame.bump,
|
||||||
|
idx: 0.into(),
|
||||||
|
_p: PhantomData {},
|
||||||
|
final_nodes: node_slot.clone(),
|
||||||
|
scope: self.myidx,
|
||||||
|
listeners: &self.listeners,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Note that the actual modification of the vnode head element occurs during this call
|
||||||
|
// let _: DomTree = caller(ctx, props);
|
||||||
|
let _: DomTree = (self.caller)(ctx, &self.props);
|
||||||
|
|
||||||
|
/*
|
||||||
|
SAFETY ALERT
|
||||||
|
|
||||||
|
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||||
|
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||||
|
|
||||||
|
Some things to note:
|
||||||
|
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||||
|
- The VNode has a private API and can only be used from accessors.
|
||||||
|
- Public API cannot drop or destructure VNode
|
||||||
|
*/
|
||||||
|
|
||||||
|
frame.head_node = node_slot
|
||||||
|
.deref()
|
||||||
|
.borrow_mut()
|
||||||
|
.take()
|
||||||
|
.expect("Viewing did not happen");
|
||||||
}
|
}
|
||||||
|
|
||||||
fn compare_props(&self, new: &Any) -> bool {
|
fn compare_props(&self, new: &Any) -> bool {
|
||||||
|
@ -145,103 +192,10 @@ impl<P: Properties + 'static> Scoped for Scope<P> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<P: Properties> Scope<P> {
|
|
||||||
/// Create a new context and run the component with references from the Virtual Dom
|
|
||||||
/// This function downcasts the function pointer based on the stored props_type
|
|
||||||
///
|
|
||||||
/// Props is ?Sized because we borrow the props and don't need to know the size. P (sized) is used as a marker (unsized)
|
|
||||||
pub fn run<'bump>(&'bump mut self) {
|
|
||||||
// pub fn run<'bump, PLocked: Sized + 'static>(&'bump mut self) {
|
|
||||||
let frame = {
|
|
||||||
let frame = self.frames.next();
|
|
||||||
frame.bump.reset();
|
|
||||||
frame
|
|
||||||
};
|
|
||||||
|
|
||||||
let node_slot = std::rc::Rc::new(RefCell::new(None));
|
|
||||||
|
|
||||||
let ctx: Context<'bump> = Context {
|
|
||||||
arena: &self.hook_arena,
|
|
||||||
hooks: &self.hooks,
|
|
||||||
bump: &frame.bump,
|
|
||||||
idx: 0.into(),
|
|
||||||
_p: PhantomData {},
|
|
||||||
final_nodes: node_slot.clone(),
|
|
||||||
scope: self.myidx,
|
|
||||||
listeners: &self.listeners,
|
|
||||||
};
|
|
||||||
|
|
||||||
// unsafe {
|
|
||||||
/*
|
|
||||||
SAFETY ALERT
|
|
||||||
|
|
||||||
This particular usage of transmute is outlined in its docs https://doc.rust-lang.org/std/mem/fn.transmute.html
|
|
||||||
We hide the generic bound on the function item by casting it to raw pointer. When the function is actually called,
|
|
||||||
we transmute the function back using the props as reference.
|
|
||||||
|
|
||||||
we could do a better check to make sure that the TypeID is correct before casting
|
|
||||||
--
|
|
||||||
This is safe because we check that the generic type matches before casting.
|
|
||||||
*/
|
|
||||||
// we use plocked to be able to remove the borrowed lifetime
|
|
||||||
// these lifetimes could be very broken, so we need to dynamically manage them
|
|
||||||
// let caller = std::mem::transmute::<*const (), FC<PLocked>>(self.caller);
|
|
||||||
// let props = self.props.downcast_ref::<PLocked>().unwrap();
|
|
||||||
let caller = self.caller;
|
|
||||||
let props = &self.props;
|
|
||||||
|
|
||||||
// Note that the actual modification of the vnode head element occurs during this call
|
|
||||||
let _: DomTree = caller(ctx, props);
|
|
||||||
|
|
||||||
/*
|
|
||||||
SAFETY ALERT
|
|
||||||
|
|
||||||
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
|
||||||
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
|
||||||
|
|
||||||
Some things to note:
|
|
||||||
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
|
||||||
- The VNode has a private API and can only be used from accessors.
|
|
||||||
- Public API cannot drop or destructure VNode
|
|
||||||
*/
|
|
||||||
// the nodes we care about have been unsafely extended to a static lifetime in context
|
|
||||||
frame.head_node = node_slot
|
|
||||||
.deref()
|
|
||||||
.borrow_mut()
|
|
||||||
.take()
|
|
||||||
.expect("Viewing did not happen");
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn retrieve_listeners(node: &VNode<'static>, listeners: &mut Vec<&Listener>) {
|
|
||||||
if let VNode::Element(el) = *node {
|
|
||||||
for listener in el.listeners {
|
|
||||||
// let g = listener as *const Listener;
|
|
||||||
listeners.push(listener);
|
|
||||||
}
|
|
||||||
for child in el.children {
|
|
||||||
retrieve_listeners(child, listeners);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// Active-frame related code
|
// Active-frame related code
|
||||||
// ==========================
|
// ==========================
|
||||||
|
|
||||||
// impl<P: Properties> Scope<P> {
|
|
||||||
// /// Accessor to get the root node and its children (safely)\
|
|
||||||
// /// Scope is self-referntial, so we are forced to use the 'static lifetime to cheat
|
|
||||||
// pub fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
|
||||||
// self.frames.current_head_node()
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
|
||||||
// self.frames.prev_head_node()
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// todo, do better with the active frame stuff
|
// todo, do better with the active frame stuff
|
||||||
// somehow build this vnode with a lifetime tied to self
|
// somehow build this vnode with a lifetime tied to self
|
||||||
// This root node has "static" lifetime, but it's really not static.
|
// This root node has "static" lifetime, but it's really not static.
|
||||||
|
|
|
@ -1,7 +1,10 @@
|
||||||
// use crate::{changelist::EditList, nodes::VNode};
|
// use crate::{changelist::EditList, nodes::VNode};
|
||||||
|
|
||||||
use crate::scope::{create_scoped, Scoped};
|
|
||||||
use crate::{innerlude::*, scope::Properties};
|
use crate::{innerlude::*, scope::Properties};
|
||||||
|
use crate::{
|
||||||
|
patch::Edit,
|
||||||
|
scope::{create_scoped, Scoped},
|
||||||
|
};
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use generational_arena::Arena;
|
use generational_arena::Arena;
|
||||||
use std::{
|
use std::{
|
||||||
|
@ -25,19 +28,31 @@ pub struct VirtualDom {
|
||||||
/// like a generational typemap bump arena
|
/// like a generational typemap bump arena
|
||||||
/// -> IE a cache line for each P type with soem heuristics on optimizing layout
|
/// -> IE a cache line for each P type with soem heuristics on optimizing layout
|
||||||
pub(crate) components: Arena<Box<dyn Scoped>>,
|
pub(crate) components: Arena<Box<dyn Scoped>>,
|
||||||
|
// pub(crate) components: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
|
||||||
/// 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: ScopeIdx,
|
pub(crate) base_scope: ScopeIdx,
|
||||||
|
|
||||||
event_queue: RefCell<VecDeque<LifecycleEvent>>,
|
|
||||||
|
|
||||||
// todo: encapsulate more state into this so we can better reuse it
|
|
||||||
diff_bump: Bump,
|
|
||||||
|
|
||||||
// Type of the original props. This is done so VirtualDom does not need to be generic.
|
// Type of the original props. This is done so VirtualDom does not need to be generic.
|
||||||
#[doc(hidden)]
|
#[doc(hidden)]
|
||||||
_root_prop_type: std::any::TypeId,
|
_root_prop_type: std::any::TypeId,
|
||||||
|
// ======================
|
||||||
|
// DIFF RELATED ITEMs
|
||||||
|
// ======================
|
||||||
|
// // todo: encapsulate more state into this so we can better reuse it
|
||||||
|
pub(crate) diff_bump: Bump,
|
||||||
|
// // be very very very very very careful
|
||||||
|
// pub change_list: EditMachine<'static>,
|
||||||
|
|
||||||
|
// // vdom: &'a VirtualDom,
|
||||||
|
// vdom: *mut Arena<Box<dyn Scoped>>,
|
||||||
|
|
||||||
|
// // vdom: Rc<RefCell<Arena<Box<dyn Scoped>>>>,
|
||||||
|
// pub cur_idx: ScopeIdx,
|
||||||
|
|
||||||
|
// // todo
|
||||||
|
// // do an indexmap sorted by height
|
||||||
|
// dirty_nodes: fxhash::FxHashSet<ScopeIdx>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl VirtualDom {
|
impl VirtualDom {
|
||||||
|
@ -57,51 +72,53 @@ impl VirtualDom {
|
||||||
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
|
||||||
let mut components = Arena::new();
|
let mut components = Arena::new();
|
||||||
|
|
||||||
let event_queue = RefCell::new(VecDeque::new());
|
|
||||||
|
|
||||||
// Create a reference to the component in the arena
|
// Create a reference to the component in the arena
|
||||||
// Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
|
// Note: we are essentially running the "Mount" lifecycle event manually while the vdom doesnt yet exist
|
||||||
// This puts the dom in a usable state on creation, rather than being potentially invalid
|
// This puts the dom in a usable state on creation, rather than being potentially invalid
|
||||||
let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
|
let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
|
||||||
|
|
||||||
// evaluate the component, pushing any updates its generates into the lifecycle queue
|
todo!()
|
||||||
// todo!
|
// Self {
|
||||||
|
// // components: RefCell::new(components),
|
||||||
let _root_prop_type = TypeId::of::<P>();
|
// components: components,
|
||||||
let diff_bump = Bump::new();
|
// // components: Rc::new(RefCell::new(components)),
|
||||||
|
// base_scope,
|
||||||
Self {
|
// // event_queue: RefCell::new(VecDeque::new()),
|
||||||
components,
|
// diff_bump: Bump::new(),
|
||||||
base_scope,
|
// _root_prop_type: TypeId::of::<P>(),
|
||||||
event_queue,
|
// }
|
||||||
diff_bump,
|
|
||||||
_root_prop_type,
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
|
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom.
|
||||||
///
|
|
||||||
///
|
// pub fn rebuild<'s>(&'s mut self) -> Result<> {
|
||||||
pub fn rebuild(&mut self) -> Result<EditList<'_>> {
|
// pub fn rebuild<'s>(&'s mut self) -> Result<std::cell::Ref<'_, Arena<Box<dyn Scoped>>>> {
|
||||||
|
pub fn rebuild<'s>(&'s mut self) -> Result<EditList<'s>> {
|
||||||
// Reset and then build a new diff machine
|
// Reset and then build a new diff machine
|
||||||
// The previous edit list cannot be around while &mut is held
|
// The previous edit list cannot be around while &mut is held
|
||||||
// Make sure variance doesnt break this
|
// Make sure variance doesnt break this
|
||||||
self.diff_bump.reset();
|
self.diff_bump.reset();
|
||||||
let mut diff_machine = DiffMachine::new(&self.diff_bump);
|
|
||||||
|
|
||||||
// this is still a WIP
|
self.components
|
||||||
// we'll need to re-fecth all the scopes that were changed and build the diff machine
|
|
||||||
// fetch the component again
|
|
||||||
let component = self
|
|
||||||
.components
|
|
||||||
.get_mut(self.base_scope)
|
.get_mut(self.base_scope)
|
||||||
.expect("Root should always exist");
|
.expect("Root should always exist")
|
||||||
|
.run();
|
||||||
|
|
||||||
component.run();
|
let b = Bump::new();
|
||||||
|
|
||||||
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
let mut diff_machine = DiffMachine::new(self, &b, self.base_scope);
|
||||||
|
// let mut diff_machine = DiffMachine::new(self, &self.diff_bump, self.base_scope);
|
||||||
|
|
||||||
Ok(diff_machine.consume())
|
todo!()
|
||||||
|
// let component = self.components.get(self.base_scope).unwrap();
|
||||||
|
|
||||||
|
// diff_machine.diff_node(component.old_frame(), component.new_frame());
|
||||||
|
|
||||||
|
// let edits = diff_machine.consume();
|
||||||
|
|
||||||
|
// self.diff_bump = b;
|
||||||
|
|
||||||
|
// Ok(edits)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
|
||||||
|
@ -129,23 +146,32 @@ impl VirtualDom {
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
|
pub fn progress_with_event(&mut self, event: EventTrigger) -> Result<EditList<'_>> {
|
||||||
let component = self
|
// self.components
|
||||||
.components
|
// .borrow_mut()
|
||||||
.get_mut(event.component_id)
|
// .get_mut(event.component_id)
|
||||||
.expect("Component should exist if an event was triggered");
|
// .map(|f| {
|
||||||
|
// f.call_listener(event);
|
||||||
|
// f
|
||||||
|
// })
|
||||||
|
// .map(|f| f.run())
|
||||||
|
// .expect("Borrowing should not fail");
|
||||||
|
|
||||||
component.call_listener(event);
|
// component.call_listener(event);
|
||||||
|
|
||||||
|
// .expect("Component should exist if an event was triggered");
|
||||||
// Reset and then build a new diff machine
|
// Reset and then build a new diff machine
|
||||||
// The previous edit list cannot be around while &mut is held
|
// The previous edit list cannot be around while &mut is held
|
||||||
// Make sure variance doesnt break this
|
// Make sure variance doesnt break this
|
||||||
self.diff_bump.reset();
|
// self.diff_bump.reset();
|
||||||
let mut diff_machine = DiffMachine::new(&self.diff_bump);
|
// let mut diff_machine = DiffMachine::new(&mut self, event.component_id);
|
||||||
|
// let mut diff_machine =
|
||||||
|
// DiffMachine::new(&self.diff_bump, &mut self.components, event.component_id);
|
||||||
|
|
||||||
component.run();
|
// component.run();
|
||||||
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
// diff_machine.diff_node(component.old_frame(), component.new_frame());
|
||||||
|
|
||||||
Ok(diff_machine.consume())
|
todo!()
|
||||||
|
// Ok(diff_machine.consume())
|
||||||
// Err(crate::error::Error::NoEvent)
|
// Err(crate::error::Error::NoEvent)
|
||||||
// Mark dirty components. Descend from the highest node until all dirty nodes are updated.
|
// Mark dirty components. Descend from the highest node until all dirty nodes are updated.
|
||||||
// let mut affected_components = Vec::new();
|
// let mut affected_components = Vec::new();
|
||||||
|
@ -159,115 +185,8 @@ impl VirtualDom {
|
||||||
|
|
||||||
// todo!()
|
// todo!()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Using mutable access to the Virtual Dom, progress a given lifecycle event
|
|
||||||
fn process_lifecycle(&mut self, LifecycleEvent { event_type }: LifecycleEvent) -> Result<()> {
|
|
||||||
match event_type {
|
|
||||||
// Component needs to be mounted to the virtual dom
|
|
||||||
LifecycleType::Mount {
|
|
||||||
to: _,
|
|
||||||
under: _,
|
|
||||||
props: _,
|
|
||||||
} => {}
|
|
||||||
|
|
||||||
// The parent for this component generated new props and the component needs update
|
|
||||||
LifecycleType::PropsChanged {
|
|
||||||
props: _,
|
|
||||||
component: _,
|
|
||||||
} => {}
|
|
||||||
|
|
||||||
// Component was messaged via the internal subscription service
|
|
||||||
LifecycleType::Callback { component: _ } => {}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pop the top event of the internal lifecycle event queu
|
|
||||||
pub fn pop_event(&self) -> Option<LifecycleEvent> {
|
|
||||||
self.event_queue.borrow_mut().pop_front()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// With access to the virtual dom, schedule an update to the Root component's props.
|
|
||||||
/// This generates the appropriate Lifecycle even. It's up to the renderer to actually feed this lifecycle event
|
|
||||||
/// back into the event system to get an edit list.
|
|
||||||
/// todo
|
|
||||||
/// change this to accept a modification closure, so the user gets 0-cost access to the props item.
|
|
||||||
/// Good for cases where the props is changed remotely (or something similar) and building a whole new props item would
|
|
||||||
/// be wasteful
|
|
||||||
pub fn update_props<P: 'static>(
|
|
||||||
&mut self,
|
|
||||||
updater: impl FnOnce(&mut P),
|
|
||||||
) -> Result<LifecycleEvent> {
|
|
||||||
todo!()
|
|
||||||
// // pub fn update_props<P: 'static>(&mut self, new_props: P) -> Result<LifecycleEvent> {
|
|
||||||
// // Ensure the props match
|
|
||||||
// if TypeId::of::<P>() != self._root_prop_type {
|
|
||||||
// return Err(Error::WrongProps);
|
|
||||||
// }
|
|
||||||
|
|
||||||
// Ok(LifecycleEvent {
|
|
||||||
// event_type: LifecycleType::PropsChanged {
|
|
||||||
// props: Box::new(new_props),
|
|
||||||
// component: self.base_scope,
|
|
||||||
// },
|
|
||||||
// })
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LifecycleEvent {
|
// struct LockedEdits<'src> {
|
||||||
pub event_type: LifecycleType,
|
// edits:
|
||||||
}
|
// }
|
||||||
|
|
||||||
pub enum LifecycleType {
|
|
||||||
// Component needs to be mounted, but its scope doesn't exist yet
|
|
||||||
Mount {
|
|
||||||
to: ScopeIdx,
|
|
||||||
under: usize,
|
|
||||||
props: Box<dyn std::any::Any>,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Parent was evalauted causing new props to generate
|
|
||||||
PropsChanged {
|
|
||||||
props: Box<dyn std::any::Any>,
|
|
||||||
component: ScopeIdx,
|
|
||||||
},
|
|
||||||
|
|
||||||
// Hook for the subscription API
|
|
||||||
Callback {
|
|
||||||
component: ScopeIdx,
|
|
||||||
},
|
|
||||||
}
|
|
||||||
|
|
||||||
impl LifecycleEvent {
|
|
||||||
fn index(&self) -> Option<ScopeIdx> {
|
|
||||||
match &self.event_type {
|
|
||||||
LifecycleType::Mount {
|
|
||||||
to: _,
|
|
||||||
under: _,
|
|
||||||
props: _,
|
|
||||||
} => None,
|
|
||||||
|
|
||||||
LifecycleType::PropsChanged { component, .. }
|
|
||||||
| LifecycleType::Callback { component } => Some(component.clone()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
mod tests {
|
|
||||||
use super::*;
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn start_dom() {
|
|
||||||
let mut dom = VirtualDom::new(|ctx, props| {
|
|
||||||
todo!()
|
|
||||||
// ctx.render(|ctx| {
|
|
||||||
// use crate::builder::*;
|
|
||||||
// let bump = ctx.bump();
|
|
||||||
// div(bump).child(text("hello, world")).finish()
|
|
||||||
// })
|
|
||||||
});
|
|
||||||
let edits = dom.rebuild().unwrap();
|
|
||||||
println!("{:#?}", edits);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
Loading…
Reference in a new issue