Merge pull request #1 from jkelleyrtp/jk/dyn_scope
Feat: move to dyn Scope for arena
This commit is contained in:
6 files changed
+301
-347
No files matched your search
@@ -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>
|
||||||
|
|||||||
+58
-11
@@ -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));
|
||||||
|
|||||||
+137
-142
@@ -11,22 +11,36 @@ use std::{
|
|||||||
ops::Deref,
|
ops::Deref,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub trait Properties: PartialEq {}
|
||||||
|
// just for now
|
||||||
|
impl<T: PartialEq> Properties for T {}
|
||||||
|
|
||||||
|
pub trait Scoped {
|
||||||
|
fn run(&mut self);
|
||||||
|
fn compare_props(&self, new: &dyn std::any::Any) -> bool;
|
||||||
|
fn call_listener(&mut self, trigger: EventTrigger);
|
||||||
|
|
||||||
|
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
|
||||||
|
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump>;
|
||||||
|
}
|
||||||
|
|
||||||
/// Every component in Dioxus is represented by a `Scope`.
|
/// Every component in Dioxus is represented by a `Scope`.
|
||||||
///
|
///
|
||||||
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
|
||||||
///
|
///
|
||||||
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
|
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
|
||||||
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
|
||||||
pub struct Scope {
|
pub struct Scope<P: Properties> {
|
||||||
// Map to the parent
|
// Map to the parent
|
||||||
pub parent: Option<ScopeIdx>,
|
pub parent: Option<ScopeIdx>,
|
||||||
|
|
||||||
// lying, cheating reference >:(
|
|
||||||
pub props: Box<dyn std::any::Any>,
|
|
||||||
|
|
||||||
// our own index
|
// our own index
|
||||||
pub myidx: ScopeIdx,
|
pub myidx: ScopeIdx,
|
||||||
|
|
||||||
|
pub caller: FC<P>,
|
||||||
|
|
||||||
|
pub props: P,
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// slightly unsafe stuff
|
// slightly unsafe stuff
|
||||||
// ==========================
|
// ==========================
|
||||||
@@ -35,71 +49,113 @@ pub struct Scope {
|
|||||||
|
|
||||||
// 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>,
|
||||||
|
|
||||||
// Unsafety:
|
// Unsafety:
|
||||||
// - is self-refenrential and therefore needs to point into the bump
|
// - is self-refenrential and therefore needs to point into the bump
|
||||||
//
|
|
||||||
// Stores references into the listeners attached to the vnodes
|
// Stores references into the listeners attached to the vnodes
|
||||||
// NEEDS TO BE PRIVATE
|
// NEEDS TO BE PRIVATE
|
||||||
listeners: RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
|
listeners: RefCell<Vec<*const dyn Fn(crate::events::VirtualEvent)>>,
|
||||||
|
|
||||||
// Unsafety
|
|
||||||
// - is a raw ptr because we need to compare
|
|
||||||
pub caller: *const (),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Scope {
|
// instead of having it as a trait method, we use a single function
|
||||||
// create a new scope from a function
|
// todo: do the unsafety magic stuff to erase the type of p
|
||||||
pub fn new<'a, P1, P2: 'static>(
|
pub fn create_scoped<P: Properties + 'static>(
|
||||||
f: FC<P1>,
|
caller: FC<P>,
|
||||||
props: P1,
|
props: P,
|
||||||
myidx: ScopeIdx,
|
myidx: ScopeIdx,
|
||||||
parent: Option<ScopeIdx>,
|
parent: Option<ScopeIdx>,
|
||||||
) -> Self {
|
) -> Box<dyn Scoped> {
|
||||||
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());
|
||||||
|
|
||||||
// Capture the caller
|
let listeners = Default::default();
|
||||||
let caller = f as *const ();
|
|
||||||
|
|
||||||
let listeners = Default::default();
|
let old_frame = BumpFrame {
|
||||||
|
bump: Bump::new(),
|
||||||
|
head_node: VNode::text(""),
|
||||||
|
};
|
||||||
|
|
||||||
let old_frame = BumpFrame {
|
let new_frame = BumpFrame {
|
||||||
bump: Bump::new(),
|
bump: Bump::new(),
|
||||||
head_node: VNode::text(""),
|
head_node: VNode::text(""),
|
||||||
|
};
|
||||||
|
|
||||||
|
let frames = ActiveFrame::from_frames(old_frame, new_frame);
|
||||||
|
|
||||||
|
Box::new(Scope {
|
||||||
|
myidx,
|
||||||
|
hook_arena,
|
||||||
|
hooks,
|
||||||
|
caller,
|
||||||
|
frames,
|
||||||
|
listeners,
|
||||||
|
parent,
|
||||||
|
props,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<P: Properties + 'static> Scoped for 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)
|
||||||
|
fn run<'bump>(&'bump mut self) {
|
||||||
|
let frame = {
|
||||||
|
let frame = self.frames.next();
|
||||||
|
frame.bump.reset();
|
||||||
|
frame
|
||||||
};
|
};
|
||||||
|
|
||||||
let new_frame = BumpFrame {
|
let node_slot = std::rc::Rc::new(RefCell::new(None));
|
||||||
bump: Bump::new(),
|
|
||||||
head_node: VNode::text(""),
|
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,
|
||||||
};
|
};
|
||||||
|
|
||||||
let frames = ActiveFrame::from_frames(old_frame, new_frame);
|
// 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);
|
||||||
|
|
||||||
// box the props
|
/*
|
||||||
let props = Box::new(props);
|
SAFETY ALERT
|
||||||
|
|
||||||
let props = unsafe { std::mem::transmute::<_, Box<P2>>(props) };
|
DO NOT USE THIS VNODE WITHOUT THE APPOPRIATE ACCESSORS.
|
||||||
|
KEEPING THIS STATIC REFERENCE CAN LEAD TO UB.
|
||||||
|
|
||||||
Self {
|
Some things to note:
|
||||||
myidx,
|
- The VNode itself is bound to the lifetime, but it itself is owned by scope.
|
||||||
hook_arena,
|
- The VNode has a private API and can only be used from accessors.
|
||||||
hooks,
|
- Public API cannot drop or destructure VNode
|
||||||
caller,
|
*/
|
||||||
frames,
|
|
||||||
listeners,
|
frame.head_node = node_slot
|
||||||
parent,
|
.deref()
|
||||||
props,
|
.borrow_mut()
|
||||||
}
|
.take()
|
||||||
|
.expect("Viewing did not happen");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compare_props(&self, new: &Any) -> bool {
|
||||||
|
new.downcast_ref::<P>()
|
||||||
|
.map(|f| &self.props == f)
|
||||||
|
.expect("Props should not be of a different type")
|
||||||
}
|
}
|
||||||
|
|
||||||
// A safe wrapper around calling listeners
|
// A safe wrapper around calling listeners
|
||||||
// calling listeners will invalidate the list of listeners
|
// calling listeners will invalidate the list of listeners
|
||||||
// The listener list will be completely drained because the next frame will write over previous listeners
|
// The listener list will be completely drained because the next frame will write over previous listeners
|
||||||
pub fn call_listener(&mut self, trigger: EventTrigger) {
|
fn call_listener(&mut self, trigger: EventTrigger) {
|
||||||
let EventTrigger {
|
let EventTrigger {
|
||||||
listener_id,
|
listener_id,
|
||||||
event: source,
|
event: source,
|
||||||
@@ -126,112 +182,19 @@ impl Scope {
|
|||||||
self.listeners.borrow_mut().drain(..);
|
self.listeners.borrow_mut().drain(..);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
pub struct RawComponent {
|
fn new_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
// used as a memoization strategy
|
self.frames.current_head_node()
|
||||||
comparator: *const Box<dyn Fn(&Box<dyn Any>) -> bool>,
|
|
||||||
|
|
||||||
// used to actually run the component
|
|
||||||
// encapsulates props
|
|
||||||
runner: *const Box<dyn Fn(Context) -> DomTree>,
|
|
||||||
|
|
||||||
// the actual FC<T>
|
|
||||||
raw: *const (),
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Scope {
|
|
||||||
/// 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, 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();
|
|
||||||
|
|
||||||
// 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>) {
|
fn old_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
|
||||||
if let VNode::Element(el) = *node {
|
self.frames.prev_head_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 Scope {
|
|
||||||
/// 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
|
||||||
@@ -323,8 +286,8 @@ mod tests {
|
|||||||
let props = ();
|
let props = ();
|
||||||
let parent = None;
|
let parent = None;
|
||||||
let mut nodes = generational_arena::Arena::new();
|
let mut nodes = generational_arena::Arena::new();
|
||||||
nodes.insert_with(|f| {
|
nodes.insert_with(|myidx| {
|
||||||
let scope = Scope::new::<(), ()>(example, props, f, parent);
|
let scope = create_scoped(example, props, myidx, parent);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -390,3 +353,35 @@ mod tests {
|
|||||||
let props = ExampleProps { name: &source_text };
|
let props = ExampleProps { name: &source_text };
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(asd)]
|
||||||
|
mod old {
|
||||||
|
|
||||||
|
/// The ComponentCaller struct is an opaque object that encapsultes the memoization and running functionality for FC
|
||||||
|
///
|
||||||
|
/// It's opaque because during the diffing mechanism, the type of props is sealed away in a closure. This makes it so
|
||||||
|
/// scope doesn't need to be generic
|
||||||
|
pub struct ComponentCaller {
|
||||||
|
// used as a memoization strategy
|
||||||
|
comparator: Box<dyn Fn(&Box<dyn Any>) -> bool>,
|
||||||
|
|
||||||
|
// used to actually run the component
|
||||||
|
// encapsulates props
|
||||||
|
runner: Box<dyn Fn(Context) -> DomTree>,
|
||||||
|
|
||||||
|
props_type: TypeId,
|
||||||
|
|
||||||
|
// the actual FC<T>
|
||||||
|
raw: *const (),
|
||||||
|
}
|
||||||
|
|
||||||
|
impl ComponentCaller {
|
||||||
|
fn new<P>(props: P) -> Self {
|
||||||
|
let comparator = Box::new(|f| false);
|
||||||
|
todo!();
|
||||||
|
// Self { comparator }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update_props<P>(props: P) {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,10 @@
|
|||||||
// use crate::{changelist::EditList, nodes::VNode};
|
// use crate::{changelist::EditList, nodes::VNode};
|
||||||
|
|
||||||
use crate::innerlude::*;
|
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::{
|
||||||
@@ -18,21 +22,37 @@ use std::{
|
|||||||
/// Differences are converted into patches which a renderer can use to draw the UI.
|
/// Differences are converted into patches which a renderer can use to draw the UI.
|
||||||
pub struct VirtualDom {
|
pub struct VirtualDom {
|
||||||
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
/// All mounted components are arena allocated to make additions, removals, and references easy to work with
|
||||||
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
/// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
|
||||||
pub(crate) components: Arena<Scope>,
|
///
|
||||||
|
/// eventually, come up with a better datastructure that reuses boxes for known P types
|
||||||
|
/// like a generational typemap bump arena
|
||||||
|
/// -> IE a cache line for each P type with soem heuristics on optimizing layout
|
||||||
|
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 {
|
||||||
@@ -49,55 +69,56 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
|
||||||
/// to toss out the entire tree.
|
/// to toss out the entire tree.
|
||||||
pub fn new_with_props<P: '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 =
|
let base_scope = components.insert_with(|id| create_scoped(root, root_props, id, None));
|
||||||
components.insert_with(|id| Scope::new::<_, P>(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.
|
||||||
@@ -125,37 +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);
|
||||||
|
|
||||||
// this is still a WIP
|
// component.run();
|
||||||
// we'll need to re-fecth all the scopes that were changed and build the diff machine
|
// diff_machine.diff_node(component.old_frame(), component.new_frame());
|
||||||
// fetch the component again
|
|
||||||
// let component = self
|
|
||||||
// .components
|
|
||||||
// .get_mut(self.base_scope)
|
|
||||||
// .expect("Root should always exist");
|
|
||||||
|
|
||||||
component.run::<()>();
|
todo!()
|
||||||
|
// Ok(diff_machine.consume())
|
||||||
diff_machine.diff_node(component.old_frame(), component.new_frame());
|
|
||||||
// diff_machine.diff_node(
|
|
||||||
// component.old_frame(),
|
|
||||||
// component.new_frame(),
|
|
||||||
// Some(self.base_scope),
|
|
||||||
// );
|
|
||||||
|
|
||||||
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();
|
||||||
@@ -169,106 +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.
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in new issue
Block a user