Feat: major overhaul to diffing, using a "diffing machine" now

This commit is contained in:
Jonathan Kelley 2021-02-14 18:03:16 -05:00
parent 4c291a0efd
commit 4dfdf91236
10 changed files with 822 additions and 780 deletions

View file

@ -15,19 +15,19 @@ static Example: FC<()> = |ctx| {
ctx.view(html! {
<div>
<button onclick={move |_| set_value("world!")}> "world" </button>
<button onclick={move |_| set_value("dioxus 🎉")}> "dioxus" </button>
<button onclick={move |_| set_value("world!")}> "?" </button>
<button onclick={move |_| set_value("Dioxus 🎉")}> "?" </button>
<div>
<p> "Hello, {val1}" </p>
<h1> "Hello, {val1}" </h1>
</div>
</div>
})
};
```
Dioxus can be used to serve webapps, desktop apps, static pages, LiveView apps, Android apps, iOS Apps, and more. At its core,
Dioxus is entirely renderer agnostic and has great documentation for creating new renderers for any platform.
Dioxus is supported by Dioxus Labs, a company providing end-to-end services for building, testing, deploying, and managing Dioxus apps on all supported platforms, designed especially for your next startup.
Dioxus can be used to deliver webapps, desktop apps, static pages, liveview apps, Android apps, iOS Apps, and more. At its core, Dioxus is entirely renderer agnostic and has great documentation for creating new renderers for any platform.
Dioxus is supported by Dioxus Labs, a company providing end-to-end services for building, testing, deploying, and managing Dioxus apps on all supported platforms, designed especially for your next startup.
### Get Started with...
<table style="width:100%" align="center">

View file

@ -45,6 +45,10 @@ path = "common.rs"
path = "example_app.rs"
name = "example_app"
[[example]]
path = "website.rs"
name = "website"
# [[example]]
# path = "hello_web.rs"
# name = "hello_web"

45
examples/website.rs Normal file
View file

@ -0,0 +1,45 @@
//! DioxusLabs Webiste
//! ------------------
//!
//! This is the example powering the DioxusLabs website :)
//! It's always good to dogfood your code, right?
use dioxus::prelude::*;
fn main() {}
mod state {
pub struct AppState {
cur_page: Route,
}
pub enum Route {
Homepage,
Docs,
}
}
static APP: FC<()> = |ctx, props| {
ctx.view(html! {
<div>
<div>
})
};
/// Draw the navbar on top of the screen
static Navbar: FC<state::Route> = |ctx, props| {
ctx.view(html! {
<div>
<div>
})
};
static Homepage: FC<()> = |ctx, props| {
ctx.view(html! {
<div>
<div>
})
};

View file

@ -29,3 +29,5 @@ typed-arena = "2.0.1"
toolshed = "0.8.1"
id-arena = "2.2.1"
thiserror = "1.0.23"
fxhash = "0.2.1"
longest-increasing-subsequence = "0.1.0"

View file

@ -21,50 +21,50 @@
use crate::innerlude::{Listener, VirtualDom};
/// Renderers need to implement the interpreter trait
/// The `Edit` represents a single modifcation of the renderer tree.
///
///
///
///
///
trait Inrerpreter {
fn set_text(&mut self, text: &str);
fn remove_self_and_next_siblings(&mut self);
fn replace_with(&mut self);
fn set_attribute(&mut self, name: &str, value: &str);
fn remove_attribute(&mut self, name: &str);
fn push_reverse_child(&mut self, n: u32);
fn pop_push_child(&mut self, n: u32);
fn pop(&mut self);
fn append_child(&mut self);
fn create_text_node(&mut self, text: &str);
fn create_element(&mut self, tag_name: &str);
fn new_event_listener(&mut self, event_type: &str, a: u32, b: u32);
fn update_event_listener(&mut self, event_type: &str, a: u32, b: u32);
fn remove_event_listener(&mut self, event_type: &str);
fn create_element_ns(&mut self, tag_name: &str, ns: &str);
fn save_children_to_temporaries(&mut self, temp: u32, start: u32, end: u32);
// fn save_children_to_temporaries(&mut self, mut temp: u32, start: u32, end: u32);
fn push_child(&mut self, n: u32);
fn push_temporary(&mut self, temp: u32);
fn insert_before(&mut self);
fn pop_push_reverse_child(&mut self, n: u32);
fn remove_child(&mut self, n: u32);
fn set_class(&mut self, class_name: &str);
// fn save_template(&mut self, id: CacheId);
// fn push_template(&mut self, id: CacheId);
///
///
///
///
pub enum Edit<'d> {
SetText { text: &'d str },
RemoveSelfAndNextSiblings {},
ReplaceWith,
SetAttribute { name: &'d str, value: &'d str },
RemoveAttribute { name: &'d str },
PushReverseChild { n: u32 },
PopPushChild { n: u32 },
Pop,
AppendChild,
CreateTextNode { text: &'d str },
CreateElement { tag_name: &'d str },
NewEventListener { event_type: &'d str, a: u32, b: u32 },
UpdateEventListener { event_type: &'d str, a: u32, b: u32 },
RemoveEventListener { event_type: &'d str },
CreateElementNs { tag_name: &'d str, ns: &'d str },
SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 },
PushChild { n: u32 },
PushTemporary { temp: u32 },
InsertBefore,
PopPushReverseChild { n: u32 },
RemoveChild { n: u32 },
SetClass { class_name: &'d str },
}
pub struct ChangeList<'src> {
pub struct EditList<'src> {
traversal: Traversal,
next_temporary: u32,
forcing_new_listeners: bool,
emitter: &'src mut dyn Inrerpreter,
domlock: &'src VirtualDom,
emitter: Vec<Edit<'src>>,
}
/// Traversal methods.
impl ChangeList<'_> {
impl EditList<'_> {
pub fn go_down(&mut self) {
self.traversal.down();
}
@ -102,32 +102,36 @@ impl ChangeList<'_> {
}
for mv in self.traversal.commit() {
// match mv {
// MoveTo::Parent => {
// // debug!("emit: pop");
// self.emitter.pop();
// }
// MoveTo::Child(n) => {
// // debug!("emit: push_child({})", n);
// self.emitter.push_child(n);
// }
// MoveTo::ReverseChild(n) => {
// // debug!("emit: push_reverse_child({})", n);
// self.emitter.push_reverse_child(n);
// }
// MoveTo::Sibling(n) => {
// // debug!("emit: pop_push_child({})", n);
// self.emitter.pop_push_child(n);
// }
// MoveTo::ReverseSibling(n) => {
// // debug!("emit: pop_push_reverse_child({})", n);
// self.emitter.pop_push_reverse_child(n);
// }
// MoveTo::TempChild(temp) => {
// // debug!("emit: push_temporary({})", temp);
// self.emitter.push_temporary(temp);
// }
// }
match mv {
MoveTo::Parent => {
// debug!("emit: pop");
self.emitter.push(Edit::Pop {});
// self.emitter.pop();
}
MoveTo::Child(n) => {
// debug!("emit: push_child({})", n);
self.emitter.push(Edit::PushChild { n });
}
MoveTo::ReverseChild(n) => {
// 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);
self.emitter.push(Edit::PopPushChild { n });
// self.emitter.pop_push_child(n);
}
MoveTo::ReverseSibling(n) => {
// debug!("emit: pop_push_reverse_child({})", n);
self.emitter.push(Edit::PopPushReverseChild { n });
}
MoveTo::TempChild(temp) => {
// debug!("emit: push_temporary({})", temp);
self.emitter.push(Edit::PushTemporary { temp });
// self.emitter.push_temporary(temp);
}
}
}
}
@ -136,7 +140,7 @@ impl ChangeList<'_> {
}
}
impl ChangeList<'_> {
impl<'a> EditList<'a> {
pub fn next_temporary(&self) -> u32 {
self.next_temporary
}
@ -154,27 +158,33 @@ impl ChangeList<'_> {
// temp_base, start, end
// );
self.next_temporary = temp_base + (end - start) as u32;
self.emitter
.save_children_to_temporaries(temp_base, start as u32, end as u32);
self.emitter.push(Edit::SaveChildrenToTemporaries {
temp: temp_base,
start: start as u32,
end: end as u32,
});
temp_base
}
pub fn push_temporary(&mut self, temp: u32) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: push_temporary({})", temp);
self.emitter.push_temporary(temp);
self.emitter.push(Edit::PushTemporary { temp });
// self.emitter.push_temporary(temp);
}
pub fn remove_child(&mut self, child: usize) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_child({})", child);
self.emitter.remove_child(child as u32);
// self.emitter.remove_child(child as u32);
self.emitter.push(Edit::RemoveChild { n: child as u32 })
}
pub fn insert_before(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: insert_before()");
self.emitter.insert_before();
// self.emitter.insert_before();
self.emitter.push(Edit::InsertBefore {})
}
pub fn ensure_string(&mut self, string: &str) -> StringKey {
@ -182,45 +192,52 @@ impl ChangeList<'_> {
// self.strings.ensure_string(string, &self.emitter)
}
pub fn set_text(&mut self, text: &str) {
pub fn set_text(&mut self, text: &'a str) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: set_text({:?})", text);
self.emitter.set_text(text);
// self.emitter.set_text(text);
self.emitter.push(Edit::SetText { text });
// .set_text(text.as_ptr() as u32, text.len() as u32);
}
pub fn remove_self_and_next_siblings(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_self_and_next_siblings()");
self.emitter.remove_self_and_next_siblings();
self.emitter.push(Edit::RemoveSelfAndNextSiblings {});
// self.emitter.remove_self_and_next_siblings();
}
pub fn replace_with(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: replace_with()");
self.emitter.replace_with();
self.emitter.push(Edit::ReplaceWith {});
// self.emitter.replace_with();
}
pub fn set_attribute(&mut self, name: &str, value: &str, is_namespaced: bool) {
pub fn set_attribute(&mut self, name: &'a str, value: &'a str, is_namespaced: bool) {
debug_assert!(self.traversal_is_committed());
todo!()
// if name == "class" && !is_namespaced {
// let class_id = self.ensure_string(value);
// // debug!("emit: set_class({:?})", value);
// self.emitter.set_class(class_id.into());
// } else {
// let name_id = self.ensure_string(name);
// let value_id = self.ensure_string(value);
// // debug!("emit: set_attribute({:?}, {:?})", name, value);
// self.state
// .emitter
// .set_attribute(name_id.into(), value_id.into());
// }
// todo!()
if name == "class" && !is_namespaced {
// let class_id = self.ensure_string(value);
// let class_id = self.ensure_string(value);
// debug!("emit: set_class({:?})", value);
// self.emitter.set_class(class_id.into());
self.emitter.push(Edit::SetClass { class_name: value });
} else {
self.emitter.push(Edit::SetAttribute { name, value });
// let name_id = self.ensure_string(name);
// let value_id = self.ensure_string(value);
// debug!("emit: set_attribute({:?}, {:?})", name, value);
// self.state
// .emitter
// .set_attribute(name_id.into(), value_id.into());
}
}
pub fn remove_attribute(&mut self, name: &str) {
pub fn remove_attribute(&mut self, name: &'a str) {
// todo!("figure out how to get this working with ensure string");
self.emitter.remove_attribute(name);
self.emitter.push(Edit::RemoveAttribute { name });
// self.emitter.remove_attribute(name);
// debug_assert!(self.traversal_is_committed());
// // debug!("emit: remove_attribute({:?})", name);
// let name_id = self.ensure_string(name);
@ -230,29 +247,33 @@ impl ChangeList<'_> {
pub fn append_child(&mut self) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: append_child()");
self.emitter.append_child();
self.emitter.push(Edit::AppendChild {});
// self.emitter.append_child();
}
pub fn create_text_node(&mut self, text: &str) {
pub fn create_text_node(&mut self, text: &'a str) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: create_text_node({:?})", text);
self.emitter.create_text_node(text);
// self.emitter.create_text_node(text);
self.emitter.push(Edit::CreateTextNode { text });
}
pub fn create_element(&mut self, tag_name: &str) {
pub fn create_element(&mut self, tag_name: &'a str) {
// debug_assert!(self.traversal_is_committed());
// debug!("emit: create_element({:?})", tag_name);
// let tag_name_id = self.ensure_string(tag_name);
self.emitter.create_element(tag_name);
self.emitter.push(Edit::CreateElement { tag_name });
// self.emitter.create_element(tag_name);
// self.emitter.create_element(tag_name_id.into());
}
pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
pub fn create_element_ns(&mut self, tag_name: &'a str, ns: &'a str) {
debug_assert!(self.traversal_is_committed());
// debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
// let tag_name_id = self.ensure_string(tag_name);
// let ns_id = self.ensure_string(ns);
self.emitter.create_element_ns(tag_name, ns);
// self.emitter.create_element_ns(tag_name, ns);
self.emitter.push(Edit::CreateElementNs { tag_name, ns });
// self.emitter
// .create_element_ns(tag_name_id.into(), ns_id.into());
}
@ -455,7 +476,9 @@ impl Traversal {
/// that have *not* been committed yet?
#[inline]
pub fn is_committed(&self) -> bool {
self.uncommitted.is_empty()
// is_empty is not inlined?
// self.uncommitted.is_empty()
self.uncommitted.len() == 0
}
/// Commit this traversals moves and return the optimized path from the last

File diff suppressed because it is too large Load diff

View file

@ -65,19 +65,19 @@
//! - dioxus-liveview (SSR + StringRenderer)
//!
pub mod changelist;
pub mod component;
pub mod context;
pub mod debug_renderer;
pub mod diff;
pub mod error;
pub mod events;
pub mod hooks;
pub mod nodebuilder;
pub mod nodes;
pub mod scope;
pub mod validation;
pub mod virtual_dom;
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; // 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
pub mod nodebuilder; // Logic for building VNodes with a direct syntax
pub mod nodes; // Logic for the VNodes
pub mod scope; // Logic for single components
pub mod validation; // Logic for validating trees
pub mod virtual_dom; // Most fun logic starts here, manages the lifecycle and suspense
pub mod builder {
pub use super::nodebuilder::*;

View file

@ -1,11 +1,11 @@
use crate::context::hooks::Hook;
use crate::innerlude::*;
use crate::nodes::VNode;
use crate::{context::hooks::Hook, diff::diff};
use bumpalo::Bump;
use generational_arena::Index;
use std::{
any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
sync::atomic::AtomicUsize,
sync::atomic::AtomicUsize, todo,
};
/// Every component in Dioxus is represented by a `Scope`.
@ -29,9 +29,13 @@ pub struct Scope {
pub frames: [Bump; 2],
// somehow build this vnode with a lifetime tied to self
pub cur_node: *mut VNode<'static>,
// 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: u8,
pub active_frame: ActiveFrame,
// IE Which listeners need to be woken up?
pub listeners: Vec<Box<dyn Fn()>>,
@ -41,6 +45,20 @@ pub struct Scope {
pub caller: *const i32,
}
pub enum ActiveFrame {
First,
Second,
}
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
pub(crate) fn new<T: 'static>(f: FC<T>, parent: Option<Index>) -> Self {
@ -49,15 +67,19 @@ impl Scope {
let hook_arena = typed_arena::Arena::new();
let hooks = RefCell::new(Vec::new());
// Capture the caller
let caller = f as *const i32;
// 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 = 1;
let active_frame = ActiveFrame::First;
let new = frames[0].alloc(VNode::Text(VText::new("")));
let cur_node = new as *mut _;
Self {
@ -69,7 +91,7 @@ impl Scope {
listeners,
parent,
frames,
cur_node,
root_node: cur_node,
}
}
@ -78,7 +100,12 @@ impl Scope {
///
/// 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 = &self.frames[0];
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
let ctx = Context {
scope: &*self,
@ -102,13 +129,14 @@ impl Scope {
*/
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.cur_node };
let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node };
// TODO: Iterate through the new nodes
// move any listeners into ourself
// perform the diff, dumping into the mutable change list
// this doesnt perform any "diff compression" where an event and a re-render
crate::diff::diff(old_nodes, &new_nodes);
// crate::diff::diff(old_nodes, &new_nodes);
todo!()
}
}

View file

@ -1,4 +1,4 @@
use crate::{changelist::ChangeList, nodes::VNode};
use crate::{changelist::EditList, nodes::VNode};
use crate::{events::EventTrigger, innerlude::*};
use any::Any;
use bumpalo::Bump;
@ -23,10 +23,6 @@ pub struct VirtualDom {
/// The index of the root component.
base_scope: Index,
///
///
///
///
event_queue: Rc<RefCell<Vec<LifecycleEvent>>>,
// Mark the root props with P, even though they're held by the root component
@ -45,10 +41,7 @@ impl VirtualDom {
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
}
}
impl VirtualDom {
// impl<P: Properties + 'static> VirtualDom<P> {
/// Start a new VirtualDom instance with a dependent props.
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
///
@ -128,7 +121,7 @@ impl VirtualDom {
///
///
/// ```
pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<ChangeList<'_>> {
pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<EditList<'_>> {
let EventTrigger {
component_id,
listener_id,

View file

@ -2,7 +2,7 @@
//!
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser.
//!
//! While `VNode` supports "to_string" directly, it renders child components as the RSX! macro tokens. For custom components,
//! While it is possible to render a single component directly, it is not possible to render component trees. For these,
//! an external renderer is needed to progress the component lifecycles. The `WebsysRenderer` shows how to use the Virtual DOM
//! API to progress these lifecycle events to generate a fully-mounted Virtual DOM instance which can be renderer in the
//! `render` method.
@ -16,7 +16,6 @@
//! ```
//!
//! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
//!
use dioxus_core::{
events::EventTrigger,
@ -33,7 +32,6 @@ pub struct WebsysRenderer {
internal_dom: VirtualDom,
}
/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
impl WebsysRenderer {
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
///
@ -42,9 +40,6 @@ impl WebsysRenderer {
pub fn new(root: FC<()>) -> Self {
Self::new_with_props(root, ())
}
}
impl WebsysRenderer {
/// Create a new text-renderer instance from a functional component root.
/// Automatically progresses the creation of the VNode tree to completion.
///