Feat: re-enable stack machine approach

This commit is contained in:
Jonathan Kelley committed 2021-02-17 10:53:55 -05:00
1 parent 1781ebba86
commit e3ede7fcbf
15 files changed
+1124 -614

No files matched your search

+25 -12
View File
@@ -19,6 +19,8 @@
//!
//!
use bumpalo::Bump;
use crate::innerlude::{Listener, VirtualDom};
/// The `Edit` represents a single modifcation of the renderer tree.
@@ -31,6 +33,7 @@ use crate::innerlude::{Listener, VirtualDom};
///
///
///
#[derive(Debug)]
pub enum Edit<'d> {
SetText { text: &'d str },
RemoveSelfAndNextSiblings {},
@@ -57,14 +60,23 @@ pub enum Edit<'d> {
}
pub struct EditList<'src> {
traversal: Traversal,
pub traversal: Traversal,
next_temporary: u32,
forcing_new_listeners: bool,
emitter: Vec<Edit<'src>>,
pub emitter: Vec<Edit<'src>>,
}
/// Traversal methods.
impl EditList<'_> {
impl<'b> EditList<'b> {
pub fn new(bump: &'b Bump) -> Self {
Self {
traversal: Traversal::new(),
next_temporary: 0,
forcing_new_listeners: false,
emitter: Vec::new(),
}
}
/// Traversal methods.
pub fn go_down(&mut self) {
self.traversal.down();
}
@@ -98,36 +110,37 @@ impl EditList<'_> {
pub fn commit_traversal(&mut self) {
if self.traversal.is_committed() {
log::debug!("Traversal already committed");
return;
}
for mv in self.traversal.commit() {
match mv {
MoveTo::Parent => {
// debug!("emit: pop");
log::debug!("emit: pop");
self.emitter.push(Edit::Pop {});
// self.emitter.pop();
}
MoveTo::Child(n) => {
// debug!("emit: push_child({})", n);
log::debug!("emit: push_child({})", n);
self.emitter.push(Edit::PushChild { n });
}
MoveTo::ReverseChild(n) => {
// debug!("emit: push_reverse_child({})", n);
log::debug!("emit: push_reverse_child({})", n);
self.emitter.push(Edit::PushReverseChild { n });
// self.emitter.push_reverse_child(n);
}
MoveTo::Sibling(n) => {
// debug!("emit: pop_push_child({})", n);
log::debug!("emit: pop_push_child({})", n);
self.emitter.push(Edit::PopPushChild { n });
// self.emitter.pop_push_child(n);
}
MoveTo::ReverseSibling(n) => {
// debug!("emit: pop_push_reverse_child({})", n);
log::debug!("emit: pop_push_reverse_child({})", n);
self.emitter.push(Edit::PopPushReverseChild { n });
}
MoveTo::TempChild(temp) => {
// debug!("emit: push_temporary({})", temp);
log::debug!("emit: push_temporary({})", temp);
self.emitter.push(Edit::PushTemporary { temp });
// self.emitter.push_temporary(temp);
}
@@ -477,8 +490,8 @@ impl Traversal {
#[inline]
pub fn is_committed(&self) -> bool {
// is_empty is not inlined?
// self.uncommitted.is_empty()
self.uncommitted.len() == 0
self.uncommitted.is_empty()
// self.uncommitted.len() == 0
}
/// Commit this traversals moves and return the optimized path from the last
+2
View File
@@ -238,6 +238,8 @@ mod tests {
.for_each(|f| assert_eq!(compare_patch(f.0, f.1), true, "{}", description));
}
// todo: make this actually perform real comparisons
// by default, nothing is derived for vnodes or patches
fn compare_patch(patch1: &Patch, patch2: &Patch) -> bool {
match (patch1, patch2) {
(Patch::AppendChildren(_, _), Patch::AppendChildren(_, _)) => true,
+20 -5
View File
@@ -1,3 +1,4 @@
use bumpalo::Bump;
/// Diff the `old` node with the `new` node. Emits instructions to modify a
/// physical DOM node that reflects `old` into something that reflects `new`.
///
@@ -36,7 +37,7 @@ use fxhash::{FxHashMap, FxHashSet};
use generational_arena::Index;
use crate::{
changelist::EditList,
changelist::{Edit, EditList},
innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
virtual_dom::LifecycleEvent,
};
@@ -55,8 +56,8 @@ 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
/// that were modified by the eventtrigger. This prevents doubly evaluating components if they wereboth updated via
/// subscriptions and props changes.
struct DiffingMachine<'a> {
change_list: &'a mut EditList<'a>,
pub struct DiffMachine<'a> {
pub change_list: EditList<'a>,
immediate_queue: Vec<Index>,
diffed: FxHashSet<Index>,
need_to_diff: FxHashSet<Index>,
@@ -67,8 +68,21 @@ enum NeedToDiff {
Subscription,
}
impl<'a> DiffingMachine<'a> {
fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) {
impl<'a> DiffMachine<'a> {
pub fn new(bump: &'a Bump) -> Self {
Self {
change_list: EditList::new(bump),
immediate_queue: Vec::new(),
diffed: FxHashSet::default(),
need_to_diff: FxHashSet::default(),
}
}
pub fn consume(self) -> Vec<Edit<'a>> {
self.change_list.emitter
}
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.
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.
@@ -107,6 +121,7 @@ impl<'a> DiffingMachine<'a> {
// compare elements
// if different, schedule different types of update
(VNode::Element(eold), VNode::Element(enew)) => {
log::debug!("elements are different");
// If the element type is completely different, the element needs to be re-rendered completely
if enew.tag_name != eold.tag_name || enew.namespace != eold.namespace {
self.change_list.commit_traversal();
+7 -6
View File
@@ -65,13 +65,13 @@
//! - dioxus-liveview (SSR + StringRenderer)
//!
// pub mod changelist; // An "edit phase" described by transitions and edit operations
pub mod changelist; // An "edit phase" described by transitions and edit operations
pub mod component; // Logic for extending FC
pub mod context; // Logic for providing hook + context functionality to user components
pub mod debug_renderer; // Test harness for validating that lifecycles and diffs work appropriately
pub mod diff;
pub mod patch; // The diffing algorithm that builds the ChangeList
// pub mod dodriodiff; // The diffing algorithm that builds the ChangeList
// pub mod diff;
// pub mod patch; // The diffing algorithm that builds the ChangeList
pub mod dodriodiff; // 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
@@ -99,7 +99,7 @@ pub(crate) mod innerlude {
// pub use nodes::iterables::IterableNodes;
/// This type alias is an internal way of abstracting over the static functions that represent components.
pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> VNode<'a>;
pub type FC<P> = for<'a> fn(Context<'a>, &'a P) -> &'a VNode<'a>;
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
// TODO @Jon, fix this
@@ -139,7 +139,8 @@ pub mod prelude {
pub use dioxus_core_macro::fc;
pub use dioxus_html_2::html;
pub use crate::diff::DiffMachine;
// pub use crate::diff::DiffMachine;
pub use crate::dodriodiff::DiffMachine;
pub use crate::hooks::*;
}
+1 -1
View File
@@ -192,7 +192,7 @@ mod velement {
/// Keys must be unique among siblings.
///
/// If any sibling is keyed, then they all must be keyed.
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub struct NodeKey(pub(crate) u32);
impl Default for NodeKey {
+138 -53
View File
@@ -3,11 +3,82 @@ use crate::innerlude::*;
use crate::nodes::VNode;
use bumpalo::Bump;
use generational_arena::Index;
use owning_ref::StableAddress;
use std::{
any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
sync::atomic::AtomicUsize, todo,
any::TypeId,
borrow::{Borrow, BorrowMut},
cell::{RefCell, UnsafeCell},
future::Future,
marker::PhantomData,
ops::{Deref, DerefMut},
sync::atomic::AtomicUsize,
todo,
};
pub struct BumpContainer(pub UnsafeCell<Bump>);
impl BumpContainer {
fn new() -> Self {
Self(UnsafeCell::new(Bump::new()))
}
}
impl Deref for BumpContainer {
type Target = Bump;
fn deref(&self) -> &Self::Target {
todo!()
// self.0.borrow()
}
}
impl DerefMut for BumpContainer {
fn deref_mut(&mut self) -> &mut Self::Target {
todo!()
// self.0.borrow_mut()
}
}
unsafe impl StableAddress for BumpContainer {}
#[ouroboros::self_referencing]
pub struct BumpFrame {
pub bump: BumpContainer,
#[covariant]
#[borrows(bump)]
pub head_node: &'this VNode<'this>,
}
pub struct ActiveFrame {
pub idx: AtomicUsize,
pub frames: [BumpFrame; 2],
}
impl ActiveFrame {
fn from_frames(a: BumpFrame, b: BumpFrame) -> Self {
Self {
idx: 0.into(),
frames: [a, b],
}
}
fn next(&self) -> &BumpFrame {
self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
match cur % 1 {
1 => &self.frames[1],
0 => &self.frames[0],
_ => unreachable!("mod cannot by non-zero"),
}
}
// fn next(&self) -> &BumpFrame {
// self.idx.fetch_add(1, std::sync::atomic::Ordering::Relaxed);
// let cur = self.idx.borrow().load(std::sync::atomic::Ordering::Relaxed);
// match cur % 2_usize {
// 1 => &self.frames[1],
// 0 => &self.frames[0],
// }
// }
}
/// Every component in Dioxus is represented by a `Scope`.
///
/// Scopes contain the state for hooks, the component's props, and other lifecycle information.
@@ -26,16 +97,12 @@ pub struct Scope {
pub parent: Option<Index>,
// todo, do better with the active frame stuff
pub frames: [Bump; 2],
// somehow build this vnode with a lifetime tied to self
// This root node has "static" lifetime, but it's really not static.
// It's goverened by the oldest of the two frames and is switched every time a new render occurs
// Use this node as if it were static is unsafe, and needs to be fixed with ourborous or owning ref
// ! do not copy this reference are things WILL break !
pub root_node: *mut VNode<'static>,
pub active_frame: ActiveFrame,
pub frames: ActiveFrame,
// IE Which listeners need to be woken up?
pub listeners: Vec<Box<dyn Fn()>>,
@@ -45,19 +112,19 @@ pub struct Scope {
pub caller: *const i32,
}
pub enum ActiveFrame {
First,
Second,
}
// pub enum ActiveFrame {
// First,
// Second,
// }
impl ActiveFrame {
fn next(&mut self) {
match self {
ActiveFrame::First => *self = ActiveFrame::Second,
ActiveFrame::Second => *self = ActiveFrame::First,
}
}
}
// impl ActiveFrame {
// fn next(&mut self) {
// match self {
// ActiveFrame::First => *self = ActiveFrame::Second,
// ActiveFrame::Second => *self = ActiveFrame::First,
// }
// }
// }
impl Scope {
// create a new scope from a function
@@ -72,26 +139,31 @@ impl Scope {
// Create the two buffers the componetn will render into
// There will always be an "old" and "new"
let frames = [Bump::new(), Bump::new()];
let listeners = Vec::new();
let active_frame = ActiveFrame::First;
let new_frame = BumpFrameBuilder {
bump: BumpContainer::new(),
head_node_builder: |bump| bump.alloc(VNode::text("")),
}
.build();
let new = frames[0].alloc(VNode::Text(VText::new("")));
let old_frame = BumpFrameBuilder {
bump: BumpContainer::new(),
head_node_builder: |bump| bump.alloc(VNode::text("")),
}
.build();
let cur_node = new as *mut _;
let frames = ActiveFrame::from_frames(old_frame, new_frame);
Self {
hook_arena,
hooks,
props_type,
caller,
active_frame,
frames,
listeners,
parent,
frames,
root_node: cur_node,
}
}
@@ -99,37 +171,50 @@ impl Scope {
/// 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(crate) fn run<'a, P: Properties + ?Sized>(&self, props: &'a P) {
let bump = match self.active_frame {
// If the active frame is the first, then we need to bump into the second
ActiveFrame::First => &self.frames[1],
// If the active frame is the second, then we need to bump into the first
ActiveFrame::Second => &self.frames[0],
}; // n.b, there might be a better way of doing this active frame stuff - perhaps swapping
pub(crate) fn run<'a, 'bump, P: Properties + ?Sized>(&'bump mut self, props: &'a P) {
// I really wanted to do this safely, but I don't think we can.
// We want to reset the bump before writing into it. This requires &mut to the bump
// Ouroborous lets us borrow with self, but the heads (IE the source) cannot be changed while the ref is live
let ctx = Context {
scope: &*self,
_p: PhantomData {},
arena: &self.hook_arena,
hooks: &self.hooks,
idx: 0.into(),
bump,
};
// n.b, there might be a better way of doing this active frame stuff - perhaps swapping
let frame = self.frames.next();
/*
SAFETY ALERT
frame.with_bump(|bump_container| {
let bump: &mut Bump = unsafe { &mut *bump_container.0.get() };
bump.reset();
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.
let bump = &*bump;
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.
*/
let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
let new_nodes = caller(ctx, props);
let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node };
let ctx: Context<'bump> = Context {
scope: &*self,
_p: PhantomData {},
arena: &self.hook_arena,
hooks: &self.hooks,
idx: 0.into(),
bump,
};
/*
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.
*/
let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
let nodes: &'bump VNode = caller(ctx, props);
});
// let new_nodes = caller(ctx, props);
// let r = new_nodes as *const _;
// self.old_root = self.new_root;
// self.new_root = new_nodes as *const _;
// let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node };
// TODO: Iterate through the new nodes
// move any listeners into ourself
+88 -70
View File
@@ -1,12 +1,14 @@
// use crate::{changelist::EditList, nodes::VNode};
use crate::nodes::VNode;
use crate::{dodriodiff::DiffMachine, nodes::VNode};
use crate::{events::EventTrigger, innerlude::*};
use any::Any;
use bumpalo::Bump;
use generational_arena::{Arena, Index};
use std::{
any::{self, TypeId},
borrow::BorrowMut,
cell::{RefCell, UnsafeCell},
collections::{vec_deque, VecDeque},
future::Future,
marker::PhantomData,
rc::Rc,
@@ -24,7 +26,7 @@ pub struct VirtualDom {
/// The index of the root component.
base_scope: Index,
event_queue: Rc<RefCell<Vec<LifecycleEvent>>>,
event_queue: Rc<RefCell<VecDeque<LifecycleEvent>>>,
// Mark the root props with P, even though they're held by the root component
// This is done so we don't have a "generic" vdom, making it easier to hold references to it, especially when the holders
@@ -65,7 +67,7 @@ impl VirtualDom {
let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props);
// Create an event queue with a mount for the base scope
let event_queue = Rc::new(RefCell::new(vec![first_event]));
let event_queue = Rc::new(RefCell::new(vec![first_event].into_iter().collect()));
let _root_prop_type = TypeId::of::<P>();
@@ -84,12 +86,15 @@ impl VirtualDom {
return Err(Error::WrongProps);
}
self.event_queue.borrow_mut().push(LifecycleEvent {
event_type: LifecycleType::PropsChanged {
props: Box::new(new_props),
},
index: self.base_scope,
});
self.event_queue
.as_ref()
.borrow_mut()
.push_back(LifecycleEvent {
event_type: LifecycleType::PropsChanged {
props: Box::new(new_props),
},
component_index: self.base_scope,
});
Ok(())
}
@@ -103,8 +108,13 @@ impl VirtualDom {
/// Update the root props, and progress
/// Takes a bump arena to allocate into, making the diff phase as fast as possible
pub fn progress(&mut self) -> Result<()> {
let event = self.event_queue.borrow_mut().pop().ok_or(Error::NoEvent)?;
process_event(&mut self.components, event)
let event = self
.event_queue
.as_ref()
.borrow_mut()
.pop_front()
.ok_or(Error::NoEvent)?;
self.process_event(event)
}
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
@@ -151,85 +161,93 @@ impl VirtualDom {
// Prop updates take prescedence over subscription updates
// Run all prop updates *first* as they will cascade into children.
// *then* run the non-prop updates that were not already covered by props
let mut events = self.event_queue.borrow_mut();
// for now, just naively process each event in the queue
for event in events.drain(..) {
process_event(&mut self.components, event)?;
let mut affected_components = Vec::new();
// It's essentially draining the vec, but with some dancing to release the RefMut
// We also want to be able to push events into the queue from processing the event
while let Some(event) = {
let new_evt = self.event_queue.as_ref().borrow_mut().pop_front();
new_evt
} {
affected_components.push(event.component_index);
self.process_event(event)?;
}
// todo!()
let diff_bump = Bump::new();
let diff_machine = DiffMachine::new(&diff_bump);
Ok(())
}
pub async fn progress_completely(&mut self) -> Result<()> {
Ok(())
}
}
/// Using mutable access to the Virtual Dom, progress a given lifecycle event
///
///
///
///
///
///
fn process_event(
&mut self,
LifecycleEvent {
component_index: index,
event_type,
}: LifecycleEvent,
) -> Result<()> {
let scope = self.components.get_mut(index).ok_or(Error::NoEvent)?;
/// Using mutable access to the Virtual Dom, progress a given lifecycle event
///
///
///
///
///
///
fn process_event(
// dom: &mut VirtualDom<P>,
components: &mut Arena<Scope>,
LifecycleEvent { index, event_type }: LifecycleEvent,
) -> Result<()> {
let scope = components.get(index).ok_or(Error::NoEvent)?;
match event_type {
// Component needs to be mounted to the virtual dom
LifecycleType::Mount { to, under, props } => {
if let Some(other) = to {
// mount to another component
} else {
// mount to the root
}
match event_type {
// Component needs to be mounted to the virtual dom
LifecycleType::Mount { to, under, props } => {
if let Some(other) = to {
// mount to another component
} else {
// mount to the root
let g = props.as_ref();
scope.run(g);
// scope.run(runner, props, dom);
}
let g = props.as_ref();
scope.run(g);
// scope.run(runner, props, dom);
}
// The parent for this component generated new props and the component needs update
LifecycleType::PropsChanged { props } => {
//
}
// The parent for this component generated new props and the component needs update
LifecycleType::PropsChanged { props } => {
// Component was successfully mounted to the dom
LifecycleType::Mounted {} => {
//
}
// Component was removed from the DOM
// Run any destructors and cleanup for the hooks and the dump the component
LifecycleType::Removed {} => {
let f = self.components.remove(index);
// let f = dom.components.remove(index);
}
// Component was messaged via the internal subscription service
LifecycleType::Messaged => {
//
}
// Event from renderer was fired with a given listener ID
//
LifecycleType::Callback { listener_id } => {}
// Run any post-render callbacks on a component
LifecycleType::Rendered => {}
}
// Component was successfully mounted to the dom
LifecycleType::Mounted {} => {
//
}
// Component was removed from the DOM
// Run any destructors and cleanup for the hooks and the dump the component
LifecycleType::Removed {} => {
let f = components.remove(index);
// let f = dom.components.remove(index);
}
// Component was messaged via the internal subscription service
LifecycleType::Messaged => {
//
}
// Event from renderer was fired with a given listener ID
//
LifecycleType::Callback { listener_id } => {}
// Run any post-render callbacks on a component
LifecycleType::Rendered => {}
Ok(())
}
Ok(())
}
pub struct LifecycleEvent {
pub index: Index,
pub component_index: Index,
pub event_type: LifecycleType,
}
@@ -265,7 +283,7 @@ impl LifecycleEvent {
props: P,
) -> Self {
Self {
index: which,
component_index: which,
event_type: LifecycleType::Mount {
to,
under,