Feat: re-enable stack machine approach
This commit is contained in:
1 parent
1781ebba86
commit
e3ede7fcbf
15 files changed
+1124
-614
No files matched your search
Vendored
+1
@@ -25,3 +25,4 @@ Derefing
|
|||||||
rei
|
rei
|
||||||
RefMut
|
RefMut
|
||||||
diffed
|
diffed
|
||||||
|
datafetching
|
||||||
@@ -31,12 +31,14 @@ pub fn set_up_logging() {
|
|||||||
})
|
})
|
||||||
// set the default log level. to filter out verbose log messages from dependencies, set
|
// set the default log level. to filter out verbose log messages from dependencies, set
|
||||||
// this to Warn and overwrite the log level for your crate.
|
// this to Warn and overwrite the log level for your crate.
|
||||||
.level(log::LevelFilter::Warn)
|
.level(log::LevelFilter::Info)
|
||||||
|
// .level(log::LevelFilter::Warn)
|
||||||
// change log levels for individual modules. Note: This looks for the record's target
|
// change log levels for individual modules. Note: This looks for the record's target
|
||||||
// field which defaults to the module path but can be overwritten with the `target`
|
// field which defaults to the module path but can be overwritten with the `target`
|
||||||
// parameter:
|
// parameter:
|
||||||
// `info!(target="special_target", "This log message is about special_target");`
|
// `info!(target="special_target", "This log message is about special_target");`
|
||||||
.level_for("dioxus", log::LevelFilter::Info)
|
// .level_for("dioxus", log::LevelFilter::Debug)
|
||||||
|
// .level_for("dioxus", log::LevelFilter::Info)
|
||||||
// .level_for("pretty_colored", log::LevelFilter::Trace)
|
// .level_for("pretty_colored", log::LevelFilter::Trace)
|
||||||
// output to stdout
|
// output to stdout
|
||||||
.chain(std::io::stdout())
|
.chain(std::io::stdout())
|
||||||
|
|||||||
@@ -31,5 +31,8 @@ id-arena = "2.2.1"
|
|||||||
thiserror = "1.0.23"
|
thiserror = "1.0.23"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
longest-increasing-subsequence = "0.1.0"
|
longest-increasing-subsequence = "0.1.0"
|
||||||
serde = "1.0.123"
|
serde = { version = "1.0.123", features = ["derive"] }
|
||||||
|
log = "0.4.14"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
ouroboros = "0.8.0"
|
||||||
# hashbrown = { version = "0.9.1", features = ["bumpalo"] }
|
# hashbrown = { version = "0.9.1", features = ["bumpalo"] }
|
||||||
@@ -19,6 +19,8 @@
|
|||||||
//!
|
//!
|
||||||
//!
|
//!
|
||||||
|
|
||||||
|
use bumpalo::Bump;
|
||||||
|
|
||||||
use crate::innerlude::{Listener, VirtualDom};
|
use crate::innerlude::{Listener, VirtualDom};
|
||||||
|
|
||||||
/// The `Edit` represents a single modifcation of the renderer tree.
|
/// 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> {
|
pub enum Edit<'d> {
|
||||||
SetText { text: &'d str },
|
SetText { text: &'d str },
|
||||||
RemoveSelfAndNextSiblings {},
|
RemoveSelfAndNextSiblings {},
|
||||||
@@ -57,14 +60,23 @@ pub enum Edit<'d> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct EditList<'src> {
|
pub struct EditList<'src> {
|
||||||
traversal: Traversal,
|
pub traversal: Traversal,
|
||||||
next_temporary: u32,
|
next_temporary: u32,
|
||||||
forcing_new_listeners: bool,
|
forcing_new_listeners: bool,
|
||||||
emitter: Vec<Edit<'src>>,
|
pub emitter: Vec<Edit<'src>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Traversal methods.
|
impl<'b> EditList<'b> {
|
||||||
impl EditList<'_> {
|
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) {
|
pub fn go_down(&mut self) {
|
||||||
self.traversal.down();
|
self.traversal.down();
|
||||||
}
|
}
|
||||||
@@ -98,36 +110,37 @@ impl EditList<'_> {
|
|||||||
|
|
||||||
pub fn commit_traversal(&mut self) {
|
pub fn commit_traversal(&mut self) {
|
||||||
if self.traversal.is_committed() {
|
if self.traversal.is_committed() {
|
||||||
|
log::debug!("Traversal already committed");
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
for mv in self.traversal.commit() {
|
for mv in self.traversal.commit() {
|
||||||
match mv {
|
match mv {
|
||||||
MoveTo::Parent => {
|
MoveTo::Parent => {
|
||||||
// debug!("emit: pop");
|
log::debug!("emit: pop");
|
||||||
self.emitter.push(Edit::Pop {});
|
self.emitter.push(Edit::Pop {});
|
||||||
// self.emitter.pop();
|
// self.emitter.pop();
|
||||||
}
|
}
|
||||||
MoveTo::Child(n) => {
|
MoveTo::Child(n) => {
|
||||||
// debug!("emit: push_child({})", n);
|
log::debug!("emit: push_child({})", n);
|
||||||
self.emitter.push(Edit::PushChild { n });
|
self.emitter.push(Edit::PushChild { n });
|
||||||
}
|
}
|
||||||
MoveTo::ReverseChild(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(Edit::PushReverseChild { n });
|
||||||
// self.emitter.push_reverse_child(n);
|
// self.emitter.push_reverse_child(n);
|
||||||
}
|
}
|
||||||
MoveTo::Sibling(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.push(Edit::PopPushChild { n });
|
||||||
// self.emitter.pop_push_child(n);
|
// self.emitter.pop_push_child(n);
|
||||||
}
|
}
|
||||||
MoveTo::ReverseSibling(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 });
|
self.emitter.push(Edit::PopPushReverseChild { n });
|
||||||
}
|
}
|
||||||
MoveTo::TempChild(temp) => {
|
MoveTo::TempChild(temp) => {
|
||||||
// debug!("emit: push_temporary({})", temp);
|
log::debug!("emit: push_temporary({})", temp);
|
||||||
self.emitter.push(Edit::PushTemporary { temp });
|
self.emitter.push(Edit::PushTemporary { temp });
|
||||||
// self.emitter.push_temporary(temp);
|
// self.emitter.push_temporary(temp);
|
||||||
}
|
}
|
||||||
@@ -477,8 +490,8 @@ impl Traversal {
|
|||||||
#[inline]
|
#[inline]
|
||||||
pub fn is_committed(&self) -> bool {
|
pub fn is_committed(&self) -> bool {
|
||||||
// is_empty is not inlined?
|
// is_empty is not inlined?
|
||||||
// self.uncommitted.is_empty()
|
self.uncommitted.is_empty()
|
||||||
self.uncommitted.len() == 0
|
// self.uncommitted.len() == 0
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Commit this traversals moves and return the optimized path from the last
|
/// Commit this traversals moves and return the optimized path from the last
|
||||||
|
|||||||
@@ -238,6 +238,8 @@ mod tests {
|
|||||||
.for_each(|f| assert_eq!(compare_patch(f.0, f.1), true, "{}", description));
|
.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 {
|
fn compare_patch(patch1: &Patch, patch2: &Patch) -> bool {
|
||||||
match (patch1, patch2) {
|
match (patch1, patch2) {
|
||||||
(Patch::AppendChildren(_, _), Patch::AppendChildren(_, _)) => true,
|
(Patch::AppendChildren(_, _), Patch::AppendChildren(_, _)) => true,
|
||||||
|
|||||||
@@ -1,3 +1,4 @@
|
|||||||
|
use bumpalo::Bump;
|
||||||
/// Diff the `old` node with the `new` node. Emits instructions to modify a
|
/// Diff the `old` node with the `new` node. Emits instructions to modify a
|
||||||
/// physical DOM node that reflects `old` into something that reflects `new`.
|
/// physical DOM node that reflects `old` into something that reflects `new`.
|
||||||
///
|
///
|
||||||
@@ -36,7 +37,7 @@ use fxhash::{FxHashMap, FxHashSet};
|
|||||||
use generational_arena::Index;
|
use generational_arena::Index;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
changelist::EditList,
|
changelist::{Edit, EditList},
|
||||||
innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
|
innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
|
||||||
virtual_dom::LifecycleEvent,
|
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
|
/// 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.
|
||||||
struct DiffingMachine<'a> {
|
pub struct DiffMachine<'a> {
|
||||||
change_list: &'a mut EditList<'a>,
|
pub change_list: EditList<'a>,
|
||||||
immediate_queue: Vec<Index>,
|
immediate_queue: Vec<Index>,
|
||||||
diffed: FxHashSet<Index>,
|
diffed: FxHashSet<Index>,
|
||||||
need_to_diff: FxHashSet<Index>,
|
need_to_diff: FxHashSet<Index>,
|
||||||
@@ -67,8 +68,21 @@ enum NeedToDiff {
|
|||||||
Subscription,
|
Subscription,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> DiffingMachine<'a> {
|
impl<'a> DiffMachine<'a> {
|
||||||
fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'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.
|
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.
|
||||||
@@ -107,6 +121,7 @@ impl<'a> DiffingMachine<'a> {
|
|||||||
// compare elements
|
// compare elements
|
||||||
// if different, schedule different types of update
|
// if different, schedule different types of update
|
||||||
(VNode::Element(eold), VNode::Element(enew)) => {
|
(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 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 {
|
if enew.tag_name != eold.tag_name || enew.namespace != eold.namespace {
|
||||||
self.change_list.commit_traversal();
|
self.change_list.commit_traversal();
|
||||||
|
|||||||
@@ -65,13 +65,13 @@
|
|||||||
//! - dioxus-liveview (SSR + StringRenderer)
|
//! - 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 component; // Logic for extending FC
|
||||||
pub mod context; // Logic for providing hook + context functionality to user components
|
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 debug_renderer; // Test harness for validating that lifecycles and diffs work appropriately
|
||||||
pub mod diff;
|
// pub mod diff;
|
||||||
pub mod patch; // The diffing algorithm that builds the ChangeList
|
// pub mod patch; // The diffing algorithm that builds the ChangeList
|
||||||
// pub mod dodriodiff; // 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 error; // Error type we expose to the renderers
|
||||||
pub mod events; // Manages the synthetic event API
|
pub mod events; // Manages the synthetic event API
|
||||||
pub mod hooks; // Built-in hooks
|
pub mod hooks; // Built-in hooks
|
||||||
@@ -99,7 +99,7 @@ pub(crate) mod innerlude {
|
|||||||
// pub use nodes::iterables::IterableNodes;
|
// pub use nodes::iterables::IterableNodes;
|
||||||
/// This type alias is an internal way of abstracting over the static functions that represent components.
|
/// 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>;
|
// pub type FC<P> = for<'a> fn(Context<'a, P>) -> VNode<'a>;
|
||||||
|
|
||||||
// TODO @Jon, fix this
|
// TODO @Jon, fix this
|
||||||
@@ -139,7 +139,8 @@ pub mod prelude {
|
|||||||
pub use dioxus_core_macro::fc;
|
pub use dioxus_core_macro::fc;
|
||||||
pub use dioxus_html_2::html;
|
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::*;
|
pub use crate::hooks::*;
|
||||||
}
|
}
|
||||||
@@ -192,7 +192,7 @@ mod velement {
|
|||||||
/// Keys must be unique among siblings.
|
/// Keys must be unique among siblings.
|
||||||
///
|
///
|
||||||
/// If any sibling is keyed, then they all must be keyed.
|
/// 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);
|
pub struct NodeKey(pub(crate) u32);
|
||||||
|
|
||||||
impl Default for NodeKey {
|
impl Default for NodeKey {
|
||||||
|
|||||||
+138
-53
@@ -3,11 +3,82 @@ use crate::innerlude::*;
|
|||||||
use crate::nodes::VNode;
|
use crate::nodes::VNode;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use generational_arena::Index;
|
use generational_arena::Index;
|
||||||
|
use owning_ref::StableAddress;
|
||||||
use std::{
|
use std::{
|
||||||
any::TypeId, borrow::Borrow, cell::RefCell, future::Future, marker::PhantomData,
|
any::TypeId,
|
||||||
sync::atomic::AtomicUsize, todo,
|
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`.
|
/// 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.
|
||||||
@@ -26,16 +97,12 @@ pub struct Scope {
|
|||||||
pub parent: Option<Index>,
|
pub parent: Option<Index>,
|
||||||
|
|
||||||
// todo, do better with the active frame stuff
|
// todo, do better with the active frame stuff
|
||||||
pub frames: [Bump; 2],
|
|
||||||
|
|
||||||
// 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.
|
||||||
// It's goverened by the oldest of the two frames and is switched every time a new render occurs
|
// 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
|
// 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 !
|
// ! do not copy this reference are things WILL break !
|
||||||
pub root_node: *mut VNode<'static>,
|
pub frames: ActiveFrame,
|
||||||
|
|
||||||
pub active_frame: ActiveFrame,
|
|
||||||
|
|
||||||
// IE Which listeners need to be woken up?
|
// IE Which listeners need to be woken up?
|
||||||
pub listeners: Vec<Box<dyn Fn()>>,
|
pub listeners: Vec<Box<dyn Fn()>>,
|
||||||
@@ -45,19 +112,19 @@ pub struct Scope {
|
|||||||
pub caller: *const i32,
|
pub caller: *const i32,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub enum ActiveFrame {
|
// pub enum ActiveFrame {
|
||||||
First,
|
// First,
|
||||||
Second,
|
// Second,
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl ActiveFrame {
|
// impl ActiveFrame {
|
||||||
fn next(&mut self) {
|
// fn next(&mut self) {
|
||||||
match self {
|
// match self {
|
||||||
ActiveFrame::First => *self = ActiveFrame::Second,
|
// ActiveFrame::First => *self = ActiveFrame::Second,
|
||||||
ActiveFrame::Second => *self = ActiveFrame::First,
|
// ActiveFrame::Second => *self = ActiveFrame::First,
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
|
|
||||||
impl Scope {
|
impl Scope {
|
||||||
// create a new scope from a function
|
// create a new scope from a function
|
||||||
@@ -72,26 +139,31 @@ impl Scope {
|
|||||||
|
|
||||||
// Create the two buffers the componetn will render into
|
// Create the two buffers the componetn will render into
|
||||||
// There will always be an "old" and "new"
|
// There will always be an "old" and "new"
|
||||||
let frames = [Bump::new(), Bump::new()];
|
|
||||||
|
|
||||||
let listeners = Vec::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 {
|
Self {
|
||||||
hook_arena,
|
hook_arena,
|
||||||
hooks,
|
hooks,
|
||||||
props_type,
|
props_type,
|
||||||
caller,
|
caller,
|
||||||
active_frame,
|
frames,
|
||||||
listeners,
|
listeners,
|
||||||
parent,
|
parent,
|
||||||
frames,
|
|
||||||
root_node: cur_node,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -99,37 +171,50 @@ impl Scope {
|
|||||||
/// This function downcasts the function pointer based on the stored props_type
|
/// 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)
|
/// 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) {
|
pub(crate) fn run<'a, 'bump, P: Properties + ?Sized>(&'bump mut self, props: &'a P) {
|
||||||
let bump = match self.active_frame {
|
// I really wanted to do this safely, but I don't think we can.
|
||||||
// If the active frame is the first, then we need to bump into the second
|
// We want to reset the bump before writing into it. This requires &mut to the bump
|
||||||
ActiveFrame::First => &self.frames[1],
|
// Ouroborous lets us borrow with self, but the heads (IE the source) cannot be changed while the ref is live
|
||||||
// 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 {
|
// n.b, there might be a better way of doing this active frame stuff - perhaps swapping
|
||||||
scope: &*self,
|
let frame = self.frames.next();
|
||||||
_p: PhantomData {},
|
|
||||||
arena: &self.hook_arena,
|
|
||||||
hooks: &self.hooks,
|
|
||||||
idx: 0.into(),
|
|
||||||
bump,
|
|
||||||
};
|
|
||||||
|
|
||||||
/*
|
frame.with_bump(|bump_container| {
|
||||||
SAFETY ALERT
|
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
|
let bump = &*bump;
|
||||||
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
|
let ctx: Context<'bump> = Context {
|
||||||
--
|
scope: &*self,
|
||||||
This is safe because we check that the generic type matches before casting.
|
_p: PhantomData {},
|
||||||
*/
|
arena: &self.hook_arena,
|
||||||
let caller = unsafe { std::mem::transmute::<*const i32, FC<P>>(self.caller) };
|
hooks: &self.hooks,
|
||||||
let new_nodes = caller(ctx, props);
|
idx: 0.into(),
|
||||||
let old_nodes: &mut VNode<'static> = unsafe { &mut *self.root_node };
|
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
|
// TODO: Iterate through the new nodes
|
||||||
// move any listeners into ourself
|
// move any listeners into ourself
|
||||||
|
|||||||
@@ -1,12 +1,14 @@
|
|||||||
// use crate::{changelist::EditList, nodes::VNode};
|
// use crate::{changelist::EditList, nodes::VNode};
|
||||||
use crate::nodes::VNode;
|
use crate::{dodriodiff::DiffMachine, nodes::VNode};
|
||||||
use crate::{events::EventTrigger, innerlude::*};
|
use crate::{events::EventTrigger, innerlude::*};
|
||||||
use any::Any;
|
use any::Any;
|
||||||
use bumpalo::Bump;
|
use bumpalo::Bump;
|
||||||
use generational_arena::{Arena, Index};
|
use generational_arena::{Arena, Index};
|
||||||
use std::{
|
use std::{
|
||||||
any::{self, TypeId},
|
any::{self, TypeId},
|
||||||
|
borrow::BorrowMut,
|
||||||
cell::{RefCell, UnsafeCell},
|
cell::{RefCell, UnsafeCell},
|
||||||
|
collections::{vec_deque, VecDeque},
|
||||||
future::Future,
|
future::Future,
|
||||||
marker::PhantomData,
|
marker::PhantomData,
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
@@ -24,7 +26,7 @@ pub struct VirtualDom {
|
|||||||
/// The index of the root component.
|
/// The index of the root component.
|
||||||
base_scope: Index,
|
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
|
// 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
|
// 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);
|
let first_event = LifecycleEvent::mount(base_scope, None, 0, root_props);
|
||||||
|
|
||||||
// Create an event queue with a mount for the base scope
|
// 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>();
|
let _root_prop_type = TypeId::of::<P>();
|
||||||
|
|
||||||
@@ -84,12 +86,15 @@ impl VirtualDom {
|
|||||||
return Err(Error::WrongProps);
|
return Err(Error::WrongProps);
|
||||||
}
|
}
|
||||||
|
|
||||||
self.event_queue.borrow_mut().push(LifecycleEvent {
|
self.event_queue
|
||||||
event_type: LifecycleType::PropsChanged {
|
.as_ref()
|
||||||
props: Box::new(new_props),
|
.borrow_mut()
|
||||||
},
|
.push_back(LifecycleEvent {
|
||||||
index: self.base_scope,
|
event_type: LifecycleType::PropsChanged {
|
||||||
});
|
props: Box::new(new_props),
|
||||||
|
},
|
||||||
|
component_index: self.base_scope,
|
||||||
|
});
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@@ -103,8 +108,13 @@ impl VirtualDom {
|
|||||||
/// Update the root props, and progress
|
/// Update the root props, and progress
|
||||||
/// Takes a bump arena to allocate into, making the diff phase as fast as possible
|
/// Takes a bump arena to allocate into, making the diff phase as fast as possible
|
||||||
pub fn progress(&mut self) -> Result<()> {
|
pub fn progress(&mut self) -> Result<()> {
|
||||||
let event = self.event_queue.borrow_mut().pop().ok_or(Error::NoEvent)?;
|
let event = self
|
||||||
process_event(&mut self.components, event)
|
.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.
|
/// 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
|
// Prop updates take prescedence over subscription updates
|
||||||
// Run all prop updates *first* as they will cascade into children.
|
// Run all prop updates *first* as they will cascade into children.
|
||||||
// *then* run the non-prop updates that were not already covered by props
|
// *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
|
let mut affected_components = Vec::new();
|
||||||
for event in events.drain(..) {
|
// It's essentially draining the vec, but with some dancing to release the RefMut
|
||||||
process_event(&mut self.components, event)?;
|
// 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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn progress_completely(&mut self) -> Result<()> {
|
pub async fn progress_completely(&mut self) -> Result<()> {
|
||||||
Ok(())
|
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
|
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
|
||||||
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 {
|
let g = props.as_ref();
|
||||||
// Component needs to be mounted to the virtual dom
|
scope.run(g);
|
||||||
LifecycleType::Mount { to, under, props } => {
|
// scope.run(runner, props, dom);
|
||||||
if let Some(other) = to {
|
|
||||||
// mount to another component
|
|
||||||
} else {
|
|
||||||
// mount to the root
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let g = props.as_ref();
|
// The parent for this component generated new props and the component needs update
|
||||||
scope.run(g);
|
LifecycleType::PropsChanged { props } => {
|
||||||
// scope.run(runner, props, dom);
|
//
|
||||||
}
|
}
|
||||||
|
|
||||||
// The parent for this component generated new props and the component needs update
|
// Component was successfully mounted to the dom
|
||||||
LifecycleType::PropsChanged { props } => {
|
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
|
Ok(())
|
||||||
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(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub struct LifecycleEvent {
|
pub struct LifecycleEvent {
|
||||||
pub index: Index,
|
pub component_index: Index,
|
||||||
pub event_type: LifecycleType,
|
pub event_type: LifecycleType,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -265,7 +283,7 @@ impl LifecycleEvent {
|
|||||||
props: P,
|
props: P,
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
index: which,
|
component_index: which,
|
||||||
event_type: LifecycleType::Mount {
|
event_type: LifecycleType::Mount {
|
||||||
to,
|
to,
|
||||||
under,
|
under,
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ futures = "0.3.12"
|
|||||||
wasm-logger = "0.2.0"
|
wasm-logger = "0.2.0"
|
||||||
log = "0.4.14"
|
log = "0.4.14"
|
||||||
fxhash = "0.2.1"
|
fxhash = "0.2.1"
|
||||||
|
pretty_env_logger = "0.4.0"
|
||||||
|
console_error_panic_hook = "0.1.6"
|
||||||
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
# html-validation = { path = "../html-validation", version = "0.1.1" }
|
||||||
|
|
||||||
[dependencies.web-sys]
|
[dependencies.web-sys]
|
||||||
@@ -36,6 +38,7 @@ features = [
|
|||||||
"MouseEvent",
|
"MouseEvent",
|
||||||
"InputEvent",
|
"InputEvent",
|
||||||
"DocumentType",
|
"DocumentType",
|
||||||
|
"CharacterData",
|
||||||
]
|
]
|
||||||
|
|
||||||
[profile.release]
|
[profile.release]
|
||||||
|
|||||||
@@ -8,42 +8,47 @@ use dioxus_web::*;
|
|||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
wasm_logger::init(wasm_logger::Config::new(log::Level::Debug));
|
||||||
// log::debug!("Hello world, from the app");
|
console_error_panic_hook::set_once();
|
||||||
WebsysRenderer::simple_render(html! {
|
WebsysRenderer::simple_render(html! {
|
||||||
|
<div>
|
||||||
// Body
|
<div class="flex items-center justify-center flex-col">
|
||||||
<div class="flex items-center justify-center flex-col">
|
<div class="font-bold text-xl"> "Count is {}" </div>
|
||||||
<div class="flex items-center justify-center">
|
<button onclick={move |_| log::info!("button1 clicked!")}> "increment" </button>
|
||||||
<div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
|
<button onclick={move |_| log::info!("button2 clicked!")}> "decrement" </button>
|
||||||
// Title
|
|
||||||
<div class="font-bold text-xl">
|
|
||||||
// {format!("Fibonacci Calculator: n = {}",n)}
|
|
||||||
"Jon's awesome site!!11"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// Subtext / description
|
|
||||||
<div class="text-sm text-gray-500">
|
|
||||||
// {format!("Calculated in {} nanoseconds",duration)}
|
|
||||||
// {format!("Calculated in {} nanoseconds",duration)}
|
|
||||||
"He worked so hard on it :)"
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="flex flex-row items-center justify-center mt-6">
|
|
||||||
// Main number
|
|
||||||
<div class="font-medium text-6xl">
|
|
||||||
"1337"
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
// Try another
|
|
||||||
<div class="flex flex-row justify-between mt-6">
|
|
||||||
// <a href=format!("http://localhost:8080/fib/{}", other_fib_to_try) class="underline">
|
|
||||||
"Legit made my own React"
|
|
||||||
// </a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
});
|
});
|
||||||
|
// WebsysRenderer::simple_render(html! {
|
||||||
|
// <div>
|
||||||
|
// <div class="flex items-center justify-center flex-col">
|
||||||
|
// <div class="flex items-center justify-center">
|
||||||
|
// <div class="flex flex-col bg-white rounded p-4 w-full max-w-xs">
|
||||||
|
// // Title
|
||||||
|
// <div class="font-bold text-xl">
|
||||||
|
// "Jon's awesome site!!11"
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// // Subtext / description
|
||||||
|
// <div class="text-sm text-gray-500">
|
||||||
|
// "He worked so hard on it :)"
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// <div class="flex flex-row items-center justify-center mt-6">
|
||||||
|
// // Main number
|
||||||
|
// <div class="font-medium text-6xl">
|
||||||
|
// "1337"
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
|
||||||
|
// // Try another
|
||||||
|
// <div class="flex flex-row justify-between mt-6">
|
||||||
|
// // <a href=format!("http://localhost:8080/fib/{}", other_fib_to_try) class="underline">
|
||||||
|
// "Legit made my own React"
|
||||||
|
// // </a>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// </div>
|
||||||
|
// });
|
||||||
}
|
}
|
||||||
@@ -1,17 +1,16 @@
|
|||||||
// use crate::cached_set::CacheId;
|
use dioxus_core::changelist::Edit;
|
||||||
// use crate::{Element, EventsTrampoline};
|
|
||||||
use fxhash::FxHashMap;
|
use fxhash::FxHashMap;
|
||||||
use log::{debug, info, log};
|
use log::{debug, info, log};
|
||||||
use wasm_bindgen::{closure::Closure, JsCast};
|
use wasm_bindgen::{closure::Closure, JsCast};
|
||||||
use web_sys::{window, Document, Element, Event, Node};
|
use web_sys::{window, Document, Element, Event, Node};
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
|
||||||
pub(crate) struct CacheId(u32);
|
pub struct CacheId(u32);
|
||||||
|
|
||||||
#[derive(Debug)]
|
#[derive(Debug)]
|
||||||
pub(crate) struct ChangeListInterpreter {
|
pub struct PatchMachine {
|
||||||
container: Element,
|
container: Element,
|
||||||
stack: Stack,
|
pub stack: Stack,
|
||||||
temporaries: FxHashMap<u32, Node>,
|
temporaries: FxHashMap<u32, Node>,
|
||||||
templates: FxHashMap<CacheId, Node>,
|
templates: FxHashMap<CacheId, Node>,
|
||||||
callback: Option<Closure<dyn FnMut(&Event)>>,
|
callback: Option<Closure<dyn FnMut(&Event)>>,
|
||||||
@@ -19,7 +18,7 @@ pub(crate) struct ChangeListInterpreter {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Default)]
|
#[derive(Debug, Default)]
|
||||||
struct Stack {
|
pub struct Stack {
|
||||||
list: Vec<Node>,
|
list: Vec<Node>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -47,11 +46,19 @@ impl Stack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn top(&self) -> &Node {
|
pub fn top(&self) -> &Node {
|
||||||
&self.list[self.list.len() - 1]
|
log::info!(
|
||||||
|
"Called top of stack with {} items remaining",
|
||||||
|
self.list.len()
|
||||||
|
);
|
||||||
|
match self.list.last() {
|
||||||
|
Some(a) => a,
|
||||||
|
None => panic!("should not happen"),
|
||||||
|
}
|
||||||
|
// &self.list[self.list.len() - 1]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ChangeListInterpreter {
|
impl PatchMachine {
|
||||||
pub fn new(container: Element) -> Self {
|
pub fn new(container: Element) -> Self {
|
||||||
let document = window()
|
let document = window()
|
||||||
.expect("must have access to the window")
|
.expect("must have access to the window")
|
||||||
@@ -114,6 +121,265 @@ impl ChangeListInterpreter {
|
|||||||
// }) as Box<dyn FnMut(&Event)>));
|
// }) as Box<dyn FnMut(&Event)>));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn handle_edit(&mut self, edit: &Edit) {
|
||||||
|
match *edit {
|
||||||
|
// 0
|
||||||
|
Edit::SetText { text } => {
|
||||||
|
//
|
||||||
|
self.stack.top().set_text_content(Some(text))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 1
|
||||||
|
Edit::RemoveSelfAndNextSiblings {} => {
|
||||||
|
let node = self.stack.pop();
|
||||||
|
let mut sibling = node.next_sibling();
|
||||||
|
|
||||||
|
while let Some(inner) = sibling {
|
||||||
|
let temp = inner.next_sibling();
|
||||||
|
if let Some(sibling) = inner.dyn_ref::<Element>() {
|
||||||
|
sibling.remove();
|
||||||
|
}
|
||||||
|
sibling = temp;
|
||||||
|
}
|
||||||
|
if let Some(node) = node.dyn_ref::<Element>() {
|
||||||
|
node.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 2
|
||||||
|
Edit::ReplaceWith => {
|
||||||
|
let new_node = self.stack.pop();
|
||||||
|
let old_node = self.stack.pop();
|
||||||
|
|
||||||
|
if old_node.has_type::<Element>() {
|
||||||
|
old_node
|
||||||
|
.dyn_ref::<Element>()
|
||||||
|
.unwrap()
|
||||||
|
.replace_with_with_node_1(&new_node)
|
||||||
|
.unwrap();
|
||||||
|
} else if old_node.has_type::<web_sys::CharacterData>() {
|
||||||
|
old_node
|
||||||
|
.dyn_ref::<web_sys::CharacterData>()
|
||||||
|
.unwrap()
|
||||||
|
.replace_with_with_node_1(&new_node)
|
||||||
|
.unwrap();
|
||||||
|
} else if old_node.has_type::<web_sys::DocumentType>() {
|
||||||
|
old_node
|
||||||
|
.dyn_ref::<web_sys::DocumentType>()
|
||||||
|
.unwrap()
|
||||||
|
.replace_with_with_node_1(&new_node)
|
||||||
|
.unwrap();
|
||||||
|
} else {
|
||||||
|
panic!("Cannot replace node: {:?}", old_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
self.stack.push(new_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3
|
||||||
|
Edit::SetAttribute { name, value } => {
|
||||||
|
let node = self.stack.top();
|
||||||
|
|
||||||
|
if let Some(node) = node.dyn_ref::<Element>() {
|
||||||
|
node.set_attribute(name, value).unwrap();
|
||||||
|
|
||||||
|
// Some attributes are "volatile" and don't work through `setAttribute`.
|
||||||
|
// TODO:
|
||||||
|
// if name == "value" {
|
||||||
|
// node.set_value(value);
|
||||||
|
// }
|
||||||
|
// if name == "checked" {
|
||||||
|
// node.set_checked(true);
|
||||||
|
// }
|
||||||
|
// if name == "selected" {
|
||||||
|
// node.set_selected(true);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4
|
||||||
|
Edit::RemoveAttribute { name } => {
|
||||||
|
let node = self.stack.top();
|
||||||
|
if let Some(node) = node.dyn_ref::<Element>() {
|
||||||
|
node.remove_attribute(name).unwrap();
|
||||||
|
|
||||||
|
// Some attributes are "volatile" and don't work through `removeAttribute`.
|
||||||
|
// TODO:
|
||||||
|
// if name == "value" {
|
||||||
|
// node.set_value("");
|
||||||
|
// }
|
||||||
|
// if name == "checked" {
|
||||||
|
// node.set_checked(false);
|
||||||
|
// }
|
||||||
|
// if name == "selected" {
|
||||||
|
// node.set_selected(false);
|
||||||
|
// }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 5
|
||||||
|
Edit::PushReverseChild { n } => {
|
||||||
|
let parent = self.stack.top();
|
||||||
|
let children = parent.child_nodes();
|
||||||
|
let child = children.get(children.length() - n - 1).unwrap();
|
||||||
|
self.stack.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 6
|
||||||
|
Edit::PopPushChild { n } => {
|
||||||
|
self.stack.pop();
|
||||||
|
let parent = self.stack.top();
|
||||||
|
let children = parent.child_nodes();
|
||||||
|
let child = children.get(n).unwrap();
|
||||||
|
self.stack.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 7
|
||||||
|
Edit::Pop => {
|
||||||
|
self.stack.pop();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 8
|
||||||
|
Edit::AppendChild => {
|
||||||
|
let child = self.stack.pop();
|
||||||
|
self.stack.top().append_child(&child).unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 9
|
||||||
|
Edit::CreateTextNode { text } => self.stack.push(
|
||||||
|
self.document
|
||||||
|
.create_text_node(text)
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap(),
|
||||||
|
),
|
||||||
|
|
||||||
|
// 10
|
||||||
|
Edit::CreateElement { tag_name } => {
|
||||||
|
let el = self
|
||||||
|
.document
|
||||||
|
.create_element(tag_name)
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap();
|
||||||
|
self.stack.push(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 11
|
||||||
|
Edit::NewEventListener { event_type, a, b } => {
|
||||||
|
let el = self.stack.top();
|
||||||
|
|
||||||
|
let el = el
|
||||||
|
.dyn_ref::<Element>()
|
||||||
|
.expect(&format!("not an element: {:?}", el));
|
||||||
|
el.add_event_listener_with_callback(
|
||||||
|
event_type,
|
||||||
|
self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
debug!("adding attributes: {}, {}", a, b);
|
||||||
|
el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string())
|
||||||
|
.unwrap();
|
||||||
|
el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 12
|
||||||
|
Edit::UpdateEventListener { event_type, a, b } => {
|
||||||
|
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
||||||
|
el.set_attribute(&format!("dodrio-a-{}", event_type), &a.to_string())
|
||||||
|
.unwrap();
|
||||||
|
el.set_attribute(&format!("dodrio-b-{}", event_type), &b.to_string())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 13
|
||||||
|
Edit::RemoveEventListener { event_type } => {
|
||||||
|
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
||||||
|
el.remove_event_listener_with_callback(
|
||||||
|
event_type,
|
||||||
|
self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
|
||||||
|
)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 14
|
||||||
|
Edit::CreateElementNs { tag_name, ns } => {
|
||||||
|
let el = self
|
||||||
|
.document
|
||||||
|
.create_element_ns(Some(ns), tag_name)
|
||||||
|
.unwrap()
|
||||||
|
.dyn_into::<Node>()
|
||||||
|
.unwrap();
|
||||||
|
self.stack.push(el);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 15
|
||||||
|
Edit::SaveChildrenToTemporaries {
|
||||||
|
mut temp,
|
||||||
|
start,
|
||||||
|
end,
|
||||||
|
} => {
|
||||||
|
let parent = self.stack.top();
|
||||||
|
let children = parent.child_nodes();
|
||||||
|
for i in start..end {
|
||||||
|
self.temporaries.insert(temp, children.get(i).unwrap());
|
||||||
|
temp += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 16
|
||||||
|
Edit::PushChild { n } => {
|
||||||
|
let parent = self.stack.top();
|
||||||
|
let child = parent.child_nodes().get(n).unwrap();
|
||||||
|
self.stack.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 17
|
||||||
|
Edit::PushTemporary { temp } => {
|
||||||
|
let t = self.temporaries.get(&temp).unwrap().clone();
|
||||||
|
self.stack.push(t);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 18
|
||||||
|
Edit::InsertBefore => {
|
||||||
|
let before = self.stack.pop();
|
||||||
|
let after = self.stack.pop();
|
||||||
|
after
|
||||||
|
.parent_node()
|
||||||
|
.unwrap()
|
||||||
|
.insert_before(&before, Some(&after))
|
||||||
|
.unwrap();
|
||||||
|
self.stack.push(before);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 19
|
||||||
|
Edit::PopPushReverseChild { n } => {
|
||||||
|
self.stack.pop();
|
||||||
|
let parent = self.stack.top();
|
||||||
|
let children = parent.child_nodes();
|
||||||
|
let child = children.get(children.length() - n - 1).unwrap();
|
||||||
|
self.stack.push(child);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 20
|
||||||
|
Edit::RemoveChild { n } => {
|
||||||
|
let parent = self.stack.top();
|
||||||
|
if let Some(child) = parent.child_nodes().get(n).unwrap().dyn_ref::<Element>() {
|
||||||
|
child.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 21
|
||||||
|
Edit::SetClass { class_name } => {
|
||||||
|
if let Some(el) = self.stack.top().dyn_ref::<Element>() {
|
||||||
|
el.set_class_name(class_name);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 0
|
// 0
|
||||||
pub fn set_text(&mut self, text: &str) {
|
pub fn set_text(&mut self, text: &str) {
|
||||||
self.stack.top().set_text_content(Some(text));
|
self.stack.top().set_text_content(Some(text));
|
||||||
|
|||||||
+109
-422
@@ -1,5 +1,5 @@
|
|||||||
//! Dioxus WebSys
|
//! Dioxus WebSys
|
||||||
//!
|
//! --------------
|
||||||
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser.
|
//! This crate implements a renderer of the Dioxus Virtual DOM for the web browser.
|
||||||
//!
|
//!
|
||||||
//! While it is possible to render a single component directly, it is not possible to render component trees. For these,
|
//! While it is possible to render a single component directly, it is not possible to render component trees. For these,
|
||||||
@@ -16,8 +16,11 @@
|
|||||||
//! ```
|
//! ```
|
||||||
//!
|
//!
|
||||||
//! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
|
//! The `WebsysRenderer` is particularly useful when needing to cache a Virtual DOM in between requests
|
||||||
|
use web_sys::{window, Document, Element, Event, Node};
|
||||||
|
|
||||||
use dioxus::{patch::Patch, prelude::VText};
|
use dioxus::prelude::VElement;
|
||||||
|
// use dioxus::{patch::Patch, prelude::VText};
|
||||||
|
// use dioxus::{patch::Patch, prelude::VText};
|
||||||
pub use dioxus_core as dioxus;
|
pub use dioxus_core as dioxus;
|
||||||
use dioxus_core::{
|
use dioxus_core::{
|
||||||
events::EventTrigger,
|
events::EventTrigger,
|
||||||
@@ -26,6 +29,7 @@ use dioxus_core::{
|
|||||||
use futures::{channel::mpsc, future, SinkExt, StreamExt};
|
use futures::{channel::mpsc, future, SinkExt, StreamExt};
|
||||||
use mpsc::UnboundedSender;
|
use mpsc::UnboundedSender;
|
||||||
pub mod interpreter;
|
pub mod interpreter;
|
||||||
|
use interpreter::PatchMachine;
|
||||||
/// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
|
/// The `WebsysRenderer` provides a way of rendering a Dioxus Virtual DOM to the browser's DOM.
|
||||||
/// Under the hood, we leverage WebSys and interact directly with the DOM
|
/// Under the hood, we leverage WebSys and interact directly with the DOM
|
||||||
|
|
||||||
@@ -71,18 +75,18 @@ impl WebsysRenderer {
|
|||||||
let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
|
let (sender, mut receiver) = mpsc::unbounded::<EventTrigger>();
|
||||||
|
|
||||||
// Iterate through the nodes, attaching the closure and sender to the listener
|
// Iterate through the nodes, attaching the closure and sender to the listener
|
||||||
{
|
// {
|
||||||
let mut remote_sender = sender.clone();
|
// let mut remote_sender = sender.clone();
|
||||||
let listener = move || {
|
// let listener = move || {
|
||||||
let event = EventTrigger::new();
|
// let event = EventTrigger::new();
|
||||||
wasm_bindgen_futures::spawn_local(async move {
|
// wasm_bindgen_futures::spawn_local(async move {
|
||||||
remote_sender
|
// remote_sender
|
||||||
.send(event)
|
// .send(event)
|
||||||
.await
|
// .await
|
||||||
.expect("Updating receiver failed");
|
// .expect("Updating receiver failed");
|
||||||
})
|
// })
|
||||||
};
|
// };
|
||||||
}
|
// }
|
||||||
|
|
||||||
// Event loop waits for the receiver to finish up
|
// Event loop waits for the receiver to finish up
|
||||||
// TODO! Connect the sender to the virtual dom's suspense system
|
// TODO! Connect the sender to the virtual dom's suspense system
|
||||||
@@ -105,433 +109,116 @@ impl WebsysRenderer {
|
|||||||
pub fn simple_render(tree: impl for<'a> Fn(&'a Bump) -> VNode<'a>) {
|
pub fn simple_render(tree: impl for<'a> Fn(&'a Bump) -> VNode<'a>) {
|
||||||
let bump = Bump::new();
|
let bump = Bump::new();
|
||||||
|
|
||||||
|
// Choose the body to render the app into
|
||||||
|
let window = web_sys::window().expect("should have access to the Window");
|
||||||
|
let document = window
|
||||||
|
.document()
|
||||||
|
.expect("should have access to the Document");
|
||||||
|
let body = document.body().unwrap();
|
||||||
|
|
||||||
|
// Build a dummy div
|
||||||
|
let container: &Element = body.as_ref();
|
||||||
|
container.set_inner_html("");
|
||||||
|
container
|
||||||
|
.append_child(
|
||||||
|
document
|
||||||
|
.create_element("div")
|
||||||
|
.expect("should create element OK")
|
||||||
|
.as_ref(),
|
||||||
|
)
|
||||||
|
.expect("should append child OK");
|
||||||
|
|
||||||
|
// Create the old dom and the new dom
|
||||||
|
// The old is just an empty div, like the one we made above
|
||||||
let old = html! { <div> </div> }(&bump);
|
let old = html! { <div> </div> }(&bump);
|
||||||
|
|
||||||
let created = create_dom_node(&old);
|
|
||||||
let root_node = created.node;
|
|
||||||
|
|
||||||
let new = tree(&bump);
|
let new = tree(&bump);
|
||||||
|
|
||||||
let mut machine = DiffMachine::new();
|
// Build a machine that diffs doms
|
||||||
|
let mut diff_machine = DiffMachine::new(&bump);
|
||||||
|
diff_machine.diff_node(&old, &new);
|
||||||
|
|
||||||
let patches = machine.diff(&old, &new);
|
// Build a machine that patches doms
|
||||||
// log::info!("There are {:?} patches", patches.len());
|
// In practice, the diff machine might be on a different computer, sending us patches
|
||||||
|
let mut patch_machine = PatchMachine::new(body.clone().into());
|
||||||
|
|
||||||
let root2 = root_node.clone();
|
// need to make sure we push the root node onto the stack before trying to run anything
|
||||||
patch(root_node, &patches).expect("Failed to simple render");
|
// this provides an entrance for the diffing machine to do its work
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
// Here, we grab the div out of the container (the body) to connect with the dummy div we made above
|
||||||
|
// This is because we don't support fragments (yet)
|
||||||
|
let root_node = container.first_child().unwrap();
|
||||||
|
patch_machine.stack.push(root_node);
|
||||||
|
|
||||||
document.body().unwrap().append_child(&root2);
|
// Consume the diff machine, generating the patch list
|
||||||
// log::info!("Succesfully patched the dom");
|
for patch in diff_machine.consume() {
|
||||||
}
|
patch_machine.handle_edit(&patch);
|
||||||
}
|
log::info!("Patch is {:?}", patch);
|
||||||
|
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::{cmp::min, rc::Rc};
|
|
||||||
use wasm_bindgen::JsCast;
|
|
||||||
use wasm_bindgen::JsValue;
|
|
||||||
use web_sys::{Element, Node, Text};
|
|
||||||
|
|
||||||
/// Apply all of the patches to our old root node in order to create the new root node
|
|
||||||
/// that we desire.
|
|
||||||
/// This is usually used after diffing two virtual nodes.
|
|
||||||
pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<(), JsValue> {
|
|
||||||
// pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
|
|
||||||
let root_node: Node = root_node.into();
|
|
||||||
|
|
||||||
let mut cur_node_idx = 0;
|
|
||||||
|
|
||||||
let mut nodes_to_find = HashSet::new();
|
|
||||||
|
|
||||||
for patch in patches {
|
|
||||||
nodes_to_find.insert(patch.node_idx());
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut element_nodes_to_patch = HashMap::new();
|
|
||||||
let mut text_nodes_to_patch = HashMap::new();
|
|
||||||
|
|
||||||
// Closures that were added to the DOM during this patch operation.
|
|
||||||
// let mut active_closures = HashMap::new();
|
|
||||||
|
|
||||||
find_nodes(
|
|
||||||
root_node,
|
|
||||||
&mut cur_node_idx,
|
|
||||||
&mut nodes_to_find,
|
|
||||||
&mut element_nodes_to_patch,
|
|
||||||
&mut text_nodes_to_patch,
|
|
||||||
);
|
|
||||||
|
|
||||||
for patch in patches {
|
|
||||||
let patch_node_idx = patch.node_idx();
|
|
||||||
|
|
||||||
if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
|
|
||||||
let new_closures = apply_element_patch(&element, &patch)?;
|
|
||||||
// active_closures.extend(new_closures);
|
|
||||||
continue;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
|
|
||||||
apply_text_patch(&text_node, &patch)?;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ok(active_closures)
|
pub fn complex_render(
|
||||||
Ok(())
|
tree1: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
|
||||||
}
|
tree2: impl for<'a> Fn(&'a Bump) -> VNode<'a>,
|
||||||
|
) {
|
||||||
|
let bump = Bump::new();
|
||||||
|
|
||||||
fn find_nodes(
|
let old = tree1(&bump);
|
||||||
root_node: Node,
|
let new = tree2(&bump);
|
||||||
cur_node_idx: &mut usize,
|
|
||||||
nodes_to_find: &mut HashSet<usize>,
|
|
||||||
element_nodes_to_patch: &mut HashMap<usize, Element>,
|
|
||||||
text_nodes_to_patch: &mut HashMap<usize, Text>,
|
|
||||||
) {
|
|
||||||
if nodes_to_find.len() == 0 {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// We use child_nodes() instead of children() because children() ignores text nodes
|
let mut machine = DiffMachine::new(&bump);
|
||||||
let children = root_node.child_nodes();
|
machine.diff_node(&old, &new);
|
||||||
let child_node_count = children.length();
|
|
||||||
|
|
||||||
// If the root node matches, mark it for patching
|
for patch in machine.consume() {
|
||||||
if nodes_to_find.get(&cur_node_idx).is_some() {
|
println!("Patch is {:?}", patch);
|
||||||
match root_node.node_type() {
|
|
||||||
Node::ELEMENT_NODE => {
|
|
||||||
element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
|
||||||
}
|
|
||||||
Node::TEXT_NODE => {
|
|
||||||
text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
|
||||||
}
|
|
||||||
other => unimplemented!("Unsupported root node type: {}", other),
|
|
||||||
}
|
|
||||||
nodes_to_find.remove(&cur_node_idx);
|
|
||||||
}
|
|
||||||
|
|
||||||
*cur_node_idx += 1;
|
|
||||||
|
|
||||||
for i in 0..child_node_count {
|
|
||||||
let node = children.item(i).unwrap();
|
|
||||||
|
|
||||||
match node.node_type() {
|
|
||||||
Node::ELEMENT_NODE => {
|
|
||||||
find_nodes(
|
|
||||||
node,
|
|
||||||
cur_node_idx,
|
|
||||||
nodes_to_find,
|
|
||||||
element_nodes_to_patch,
|
|
||||||
text_nodes_to_patch,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
Node::TEXT_NODE => {
|
|
||||||
if nodes_to_find.get(&cur_node_idx).is_some() {
|
|
||||||
text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
|
|
||||||
}
|
|
||||||
|
|
||||||
*cur_node_idx += 1;
|
|
||||||
}
|
|
||||||
Node::COMMENT_NODE => {
|
|
||||||
// At this time we do not support user entered comment nodes, so if we see a comment
|
|
||||||
// then it was a delimiter created by virtual-dom-rs in order to ensure that two
|
|
||||||
// neighboring text nodes did not get merged into one by the browser. So we skip
|
|
||||||
// over this virtual-dom-rs generated comment node.
|
|
||||||
}
|
|
||||||
_other => {
|
|
||||||
// Ignoring unsupported child node type
|
|
||||||
// TODO: What do we do with this situation? Log a warning?
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use std::env;
|
||||||
|
|
||||||
// fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
|
use super::*;
|
||||||
fn apply_element_patch(node: &Element, patch: &Patch) -> Result<(), JsValue> {
|
use dioxus_core as dioxus;
|
||||||
// let active_closures = HashMap::new();
|
use dioxus_core::prelude::html;
|
||||||
|
|
||||||
match patch {
|
#[test]
|
||||||
Patch::AddAttributes(_node_idx, attributes) => {
|
fn simple_patch() {
|
||||||
for (attrib_name, attrib_val) in attributes.iter() {
|
env::set_var("RUST_LOG", "trace");
|
||||||
node.set_attribute(attrib_name, attrib_val)?;
|
pretty_env_logger::init();
|
||||||
}
|
log::info!("Hello!");
|
||||||
|
let renderer = WebsysRenderer::simple_render(html! {
|
||||||
// Ok(active_closures)
|
<div>
|
||||||
Ok(())
|
"Hello world"
|
||||||
}
|
<button onclick={move |_| log::info!("button1 clicked!")}> "click me" </button>
|
||||||
Patch::RemoveAttributes(_node_idx, attributes) => {
|
<button onclick={move |_| log::info!("button2 clicked!")}> "click me" </button>
|
||||||
for attrib_name in attributes.iter() {
|
</div>
|
||||||
node.remove_attribute(attrib_name)?;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Ok(active_closures)
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
Patch::Replace(_node_idx, new_node) => {
|
|
||||||
let created_node = create_dom_node(&new_node);
|
|
||||||
|
|
||||||
node.replace_with_with_node_1(&created_node.node)?;
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
// Ok(created_node.closures)
|
|
||||||
}
|
|
||||||
Patch::TruncateChildren(_node_idx, num_children_remaining) => {
|
|
||||||
let children = node.child_nodes();
|
|
||||||
let mut child_count = children.length();
|
|
||||||
|
|
||||||
// We skip over any separators that we placed between two text nodes
|
|
||||||
// -> `<!--ptns-->`
|
|
||||||
// and trim all children that come after our new desired `num_children_remaining`
|
|
||||||
let mut non_separator_children_found = 0;
|
|
||||||
|
|
||||||
for index in 0 as u32..child_count {
|
|
||||||
let child = children
|
|
||||||
.get(min(index, child_count - 1))
|
|
||||||
.expect("Potential child to truncate");
|
|
||||||
|
|
||||||
// If this is a comment node then we know that it is a `<!--ptns-->`
|
|
||||||
// text node separator that was created in virtual_node/mod.rs.
|
|
||||||
if child.node_type() == Node::COMMENT_NODE {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
non_separator_children_found += 1;
|
|
||||||
|
|
||||||
if non_separator_children_found <= *num_children_remaining as u32 {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
node.remove_child(&child).expect("Truncated children");
|
|
||||||
child_count -= 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
// Ok(active_closures)
|
|
||||||
}
|
|
||||||
Patch::AppendChildren(_node_idx, new_nodes) => {
|
|
||||||
let parent = &node;
|
|
||||||
|
|
||||||
let mut active_closures = HashMap::new();
|
|
||||||
|
|
||||||
for new_node in new_nodes {
|
|
||||||
let created_node = create_dom_node(&new_node);
|
|
||||||
// let created_node = new_node.create_dom_node();
|
|
||||||
|
|
||||||
parent.append_child(&created_node.node)?;
|
|
||||||
|
|
||||||
active_closures.extend(created_node.closures);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
// Ok(active_closures)
|
|
||||||
}
|
|
||||||
Patch::ChangeText(_node_idx, _new_node) => {
|
|
||||||
unreachable!("Elements should not receive ChangeText patches.")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
|
|
||||||
match patch {
|
|
||||||
Patch::ChangeText(_node_idx, new_node) => {
|
|
||||||
node.set_node_value(Some(&new_node.text));
|
|
||||||
}
|
|
||||||
Patch::Replace(_node_idx, new_node) => {
|
|
||||||
node.replace_with_with_node_1(&create_dom_node(&new_node).node)?;
|
|
||||||
// node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
|
|
||||||
}
|
|
||||||
other => unreachable!(
|
|
||||||
"Text nodes should only receive ChangeText or Replace patches, not ",
|
|
||||||
// other,
|
|
||||||
// "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
|
|
||||||
// other,
|
|
||||||
),
|
|
||||||
};
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A node along with all of the closures that were created for that
|
|
||||||
/// node's events and all of it's child node's events.
|
|
||||||
pub struct CreatedNode<T> {
|
|
||||||
/// A `Node` or `Element` that was created from a `VirtualNode`
|
|
||||||
pub node: T,
|
|
||||||
/// A map of a node's unique identifier along with all of the Closures for that node.
|
|
||||||
///
|
|
||||||
/// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
|
|
||||||
/// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
|
|
||||||
/// memory.
|
|
||||||
pub closures: HashMap<u32, Vec<DynClosure>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
|
|
||||||
/// any Closure regardless of the arguments.
|
|
||||||
pub type DynClosure = Rc<dyn AsRef<JsValue>>;
|
|
||||||
|
|
||||||
impl<T> CreatedNode<T> {
|
|
||||||
pub fn without_closures<N: Into<T>>(node: N) -> Self {
|
|
||||||
CreatedNode {
|
|
||||||
node: node.into(),
|
|
||||||
closures: HashMap::with_capacity(0),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<T> std::ops::Deref for CreatedNode<T> {
|
|
||||||
type Target = T;
|
|
||||||
fn deref(&self) -> &Self::Target {
|
|
||||||
&self.node
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<CreatedNode<Element>> for CreatedNode<Node> {
|
|
||||||
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
|
|
||||||
CreatedNode {
|
|
||||||
node: other.node.into(),
|
|
||||||
closures: other.closures,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
fn create_dom_node(node: &VNode<'_>) -> CreatedNode<Node> {
|
|
||||||
match node {
|
|
||||||
VNode::Text(text_node) => CreatedNode::without_closures(create_text_node(text_node)),
|
|
||||||
VNode::Element(element_node) => create_element_node(element_node).into(),
|
|
||||||
// VNode::Element(element_node) => element_node.create_element_node().into(),
|
|
||||||
VNode::Suspended => todo!(" not iimplemented yet"),
|
|
||||||
VNode::Component(_) => todo!(" not iimplemented yet"),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Build a DOM element by recursively creating DOM nodes for this element and it's
|
|
||||||
/// children, it's children's children, etc.
|
|
||||||
pub fn create_element_node(node: &dioxus_core::nodes::VElement) -> CreatedNode<Element> {
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
|
|
||||||
// TODO: enable svg again
|
|
||||||
// let element = if html_validation::is_svg_namespace(&node.tag_name) {
|
|
||||||
// document
|
|
||||||
// .create_element_ns(Some("http://www.w3.org/2000/svg"), &node.tag_name)
|
|
||||||
// .unwrap()
|
|
||||||
// } else {
|
|
||||||
let element = document.create_element(&node.tag_name).unwrap();
|
|
||||||
// };
|
|
||||||
|
|
||||||
let mut closures = HashMap::new();
|
|
||||||
|
|
||||||
node.attributes
|
|
||||||
.iter()
|
|
||||||
.map(|f| (f.name, f.value))
|
|
||||||
.for_each(|(name, value)| {
|
|
||||||
if name == "unsafe_inner_html" {
|
|
||||||
element.set_inner_html(value);
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
element
|
|
||||||
.set_attribute(name, value)
|
|
||||||
.expect("Set element attribute in create element");
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// if node.events.0.len() > 0 {
|
#[test]
|
||||||
// let unique_id = create_unique_identifier();
|
fn complex_patch() {
|
||||||
|
env::set_var("RUST_LOG", "trace");
|
||||||
// element
|
pretty_env_logger::init();
|
||||||
// .set_attribute("data-vdom-id".into(), &unique_id.to_string())
|
log::info!("Hello!");
|
||||||
// .expect("Could not set attribute on element");
|
let renderer = WebsysRenderer::complex_render(
|
||||||
|
html! {
|
||||||
// closures.insert(unique_id, vec![]);
|
<div>
|
||||||
|
"Hello world"
|
||||||
// node.events.0.iter().for_each(|(onevent, callback)| {
|
<div>
|
||||||
// // onclick -> click
|
<h1> "Heading" </h1>
|
||||||
// let event = &onevent[2..];
|
</div>
|
||||||
|
</div>
|
||||||
// let current_elem: &EventTarget = element.dyn_ref().unwrap();
|
},
|
||||||
|
html! {
|
||||||
// current_elem
|
<div>
|
||||||
// .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
|
"Hello world"
|
||||||
// .unwrap();
|
"Hello world"
|
||||||
|
"Hello world"
|
||||||
// closures
|
<div>
|
||||||
// .get_mut(&unique_id)
|
<h1> "Heading" </h1>
|
||||||
// .unwrap()
|
</div>
|
||||||
// .push(Rc::clone(callback));
|
</div>
|
||||||
// });
|
},
|
||||||
// }
|
);
|
||||||
|
|
||||||
let mut previous_node_was_text = false;
|
|
||||||
|
|
||||||
node.children.iter().for_each(|child| {
|
|
||||||
// log::info!("Patching child");
|
|
||||||
match child {
|
|
||||||
VNode::Text(text_node) => {
|
|
||||||
let current_node = element.as_ref() as &web_sys::Node;
|
|
||||||
|
|
||||||
// We ensure that the text siblings are patched by preventing the browser from merging
|
|
||||||
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
|
||||||
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
|
||||||
// -> https://github.com/facebook/react/pull/5753
|
|
||||||
//
|
|
||||||
// `ptns` = Percy text node separator
|
|
||||||
if previous_node_was_text {
|
|
||||||
let separator = document.create_comment("ptns");
|
|
||||||
current_node
|
|
||||||
.append_child(separator.as_ref() as &web_sys::Node)
|
|
||||||
.unwrap();
|
|
||||||
}
|
|
||||||
|
|
||||||
current_node
|
|
||||||
.append_child(&create_text_node(&text_node))
|
|
||||||
.unwrap();
|
|
||||||
|
|
||||||
previous_node_was_text = true;
|
|
||||||
}
|
|
||||||
VNode::Element(element_node) => {
|
|
||||||
previous_node_was_text = false;
|
|
||||||
|
|
||||||
let child = create_element_node(element_node);
|
|
||||||
// let child = element_node.create_element_node();
|
|
||||||
let child_elem: Element = child.node;
|
|
||||||
|
|
||||||
closures.extend(child.closures);
|
|
||||||
|
|
||||||
element.append_child(&child_elem).unwrap();
|
|
||||||
}
|
|
||||||
VNode::Suspended => {
|
|
||||||
todo!("Not yet supported")
|
|
||||||
}
|
|
||||||
VNode::Component(_) => {
|
|
||||||
todo!("Not yet supported")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// TODO: connect on mount to the event system somehow
|
|
||||||
// if let Some(on_create_elem) = node.events.0.get("on_create_elem") {
|
|
||||||
// let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
|
|
||||||
// on_create_elem
|
|
||||||
// .call1(&wasm_bindgen::JsValue::NULL, &element)
|
|
||||||
// .unwrap();
|
|
||||||
// }
|
|
||||||
|
|
||||||
CreatedNode {
|
|
||||||
node: element,
|
|
||||||
closures,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Return a `Text` element from a `VirtualNode`, typically right before adding it
|
|
||||||
/// into the DOM.
|
|
||||||
pub fn create_text_node(node: &VText) -> Text {
|
|
||||||
let document = web_sys::window().unwrap().document().unwrap();
|
|
||||||
document.create_text_node(&node.text)
|
|
||||||
}
|
|
||||||
|
|
||||||
// /// For any listeners in the tree, attach the sender closure.
|
|
||||||
// /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
|
|
||||||
// fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
|
|
||||||
// fn render_diffs() {}
|
|
||||||
@@ -0,0 +1,409 @@
|
|||||||
|
use std::collections::HashMap;
|
||||||
|
use std::collections::HashSet;
|
||||||
|
use std::{cmp::min, rc::Rc};
|
||||||
|
use wasm_bindgen::JsCast;
|
||||||
|
use wasm_bindgen::JsValue;
|
||||||
|
use web_sys::{Element, Node, Text};
|
||||||
|
|
||||||
|
/// Apply all of the patches to our old root node in order to create the new root node
|
||||||
|
/// that we desire.
|
||||||
|
/// This is usually used after diffing two virtual nodes.
|
||||||
|
pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<(), JsValue> {
|
||||||
|
// pub fn patch<N: Into<Node>>(root_node: N, patches: &Vec<Patch>) -> Result<ActiveClosures, JsValue> {
|
||||||
|
let root_node: Node = root_node.into();
|
||||||
|
|
||||||
|
let mut cur_node_idx = 0;
|
||||||
|
|
||||||
|
let mut nodes_to_find = HashSet::new();
|
||||||
|
|
||||||
|
for patch in patches {
|
||||||
|
nodes_to_find.insert(patch.node_idx());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut element_nodes_to_patch = HashMap::new();
|
||||||
|
let mut text_nodes_to_patch = HashMap::new();
|
||||||
|
|
||||||
|
// Closures that were added to the DOM during this patch operation.
|
||||||
|
// let mut active_closures = HashMap::new();
|
||||||
|
|
||||||
|
find_nodes(
|
||||||
|
root_node,
|
||||||
|
&mut cur_node_idx,
|
||||||
|
&mut nodes_to_find,
|
||||||
|
&mut element_nodes_to_patch,
|
||||||
|
&mut text_nodes_to_patch,
|
||||||
|
);
|
||||||
|
|
||||||
|
for patch in patches {
|
||||||
|
let patch_node_idx = patch.node_idx();
|
||||||
|
|
||||||
|
if let Some(element) = element_nodes_to_patch.get(&patch_node_idx) {
|
||||||
|
let new_closures = apply_element_patch(&element, &patch)?;
|
||||||
|
// active_closures.extend(new_closures);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if let Some(text_node) = text_nodes_to_patch.get(&patch_node_idx) {
|
||||||
|
apply_text_patch(&text_node, &patch)?;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
unreachable!("Getting here means we didn't find the element or next node that we were supposed to patch.")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok(active_closures)
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn find_nodes(
|
||||||
|
root_node: Node,
|
||||||
|
cur_node_idx: &mut usize,
|
||||||
|
nodes_to_find: &mut HashSet<usize>,
|
||||||
|
element_nodes_to_patch: &mut HashMap<usize, Element>,
|
||||||
|
text_nodes_to_patch: &mut HashMap<usize, Text>,
|
||||||
|
) {
|
||||||
|
if nodes_to_find.len() == 0 {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// We use child_nodes() instead of children() because children() ignores text nodes
|
||||||
|
let children = root_node.child_nodes();
|
||||||
|
let child_node_count = children.length();
|
||||||
|
|
||||||
|
// If the root node matches, mark it for patching
|
||||||
|
if nodes_to_find.get(&cur_node_idx).is_some() {
|
||||||
|
match root_node.node_type() {
|
||||||
|
Node::ELEMENT_NODE => {
|
||||||
|
element_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
||||||
|
}
|
||||||
|
Node::TEXT_NODE => {
|
||||||
|
text_nodes_to_patch.insert(*cur_node_idx, root_node.unchecked_into());
|
||||||
|
}
|
||||||
|
other => unimplemented!("Unsupported root node type: {}", other),
|
||||||
|
}
|
||||||
|
nodes_to_find.remove(&cur_node_idx);
|
||||||
|
}
|
||||||
|
|
||||||
|
*cur_node_idx += 1;
|
||||||
|
|
||||||
|
for i in 0..child_node_count {
|
||||||
|
let node = children.item(i).unwrap();
|
||||||
|
|
||||||
|
match node.node_type() {
|
||||||
|
Node::ELEMENT_NODE => {
|
||||||
|
find_nodes(
|
||||||
|
node,
|
||||||
|
cur_node_idx,
|
||||||
|
nodes_to_find,
|
||||||
|
element_nodes_to_patch,
|
||||||
|
text_nodes_to_patch,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
Node::TEXT_NODE => {
|
||||||
|
if nodes_to_find.get(&cur_node_idx).is_some() {
|
||||||
|
text_nodes_to_patch.insert(*cur_node_idx, node.unchecked_into());
|
||||||
|
}
|
||||||
|
|
||||||
|
*cur_node_idx += 1;
|
||||||
|
}
|
||||||
|
Node::COMMENT_NODE => {
|
||||||
|
// At this time we do not support user entered comment nodes, so if we see a comment
|
||||||
|
// then it was a delimiter created by virtual-dom-rs in order to ensure that two
|
||||||
|
// neighboring text nodes did not get merged into one by the browser. So we skip
|
||||||
|
// over this virtual-dom-rs generated comment node.
|
||||||
|
}
|
||||||
|
_other => {
|
||||||
|
// Ignoring unsupported child node type
|
||||||
|
// TODO: What do we do with this situation? Log a warning?
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// pub type ActiveClosures = HashMap<u32, Vec<DynClosure>>;
|
||||||
|
|
||||||
|
// fn apply_element_patch(node: &Element, patch: &Patch) -> Result<ActiveClosures, JsValue> {
|
||||||
|
fn apply_element_patch(node: &Element, patch: &Patch) -> Result<(), JsValue> {
|
||||||
|
// let active_closures = HashMap::new();
|
||||||
|
|
||||||
|
match patch {
|
||||||
|
Patch::AddAttributes(_node_idx, attributes) => {
|
||||||
|
for (attrib_name, attrib_val) in attributes.iter() {
|
||||||
|
node.set_attribute(attrib_name, attrib_val)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok(active_closures)
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Patch::RemoveAttributes(_node_idx, attributes) => {
|
||||||
|
for attrib_name in attributes.iter() {
|
||||||
|
node.remove_attribute(attrib_name)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ok(active_closures)
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
Patch::Replace(_node_idx, new_node) => {
|
||||||
|
let created_node = create_dom_node(&new_node);
|
||||||
|
|
||||||
|
node.replace_with_with_node_1(&created_node.node)?;
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
// Ok(created_node.closures)
|
||||||
|
}
|
||||||
|
Patch::TruncateChildren(_node_idx, num_children_remaining) => {
|
||||||
|
let children = node.child_nodes();
|
||||||
|
let mut child_count = children.length();
|
||||||
|
|
||||||
|
// We skip over any separators that we placed between two text nodes
|
||||||
|
// -> `<!--ptns-->`
|
||||||
|
// and trim all children that come after our new desired `num_children_remaining`
|
||||||
|
let mut non_separator_children_found = 0;
|
||||||
|
|
||||||
|
for index in 0 as u32..child_count {
|
||||||
|
let child = children
|
||||||
|
.get(min(index, child_count - 1))
|
||||||
|
.expect("Potential child to truncate");
|
||||||
|
|
||||||
|
// If this is a comment node then we know that it is a `<!--ptns-->`
|
||||||
|
// text node separator that was created in virtual_node/mod.rs.
|
||||||
|
if child.node_type() == Node::COMMENT_NODE {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
non_separator_children_found += 1;
|
||||||
|
|
||||||
|
if non_separator_children_found <= *num_children_remaining as u32 {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
node.remove_child(&child).expect("Truncated children");
|
||||||
|
child_count -= 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
// Ok(active_closures)
|
||||||
|
}
|
||||||
|
Patch::AppendChildren(_node_idx, new_nodes) => {
|
||||||
|
let parent = &node;
|
||||||
|
|
||||||
|
let mut active_closures = HashMap::new();
|
||||||
|
|
||||||
|
for new_node in new_nodes {
|
||||||
|
let created_node = create_dom_node(&new_node);
|
||||||
|
// let created_node = new_node.create_dom_node();
|
||||||
|
|
||||||
|
parent.append_child(&created_node.node)?;
|
||||||
|
|
||||||
|
active_closures.extend(created_node.closures);
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
// Ok(active_closures)
|
||||||
|
}
|
||||||
|
Patch::ChangeText(_node_idx, _new_node) => {
|
||||||
|
unreachable!("Elements should not receive ChangeText patches.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn apply_text_patch(node: &Text, patch: &Patch) -> Result<(), JsValue> {
|
||||||
|
match patch {
|
||||||
|
Patch::ChangeText(_node_idx, new_node) => {
|
||||||
|
node.set_node_value(Some(&new_node.text));
|
||||||
|
}
|
||||||
|
Patch::Replace(_node_idx, new_node) => {
|
||||||
|
node.replace_with_with_node_1(&create_dom_node(&new_node).node)?;
|
||||||
|
// node.replace_with_with_node_1(&new_node.create_dom_node().node)?;
|
||||||
|
}
|
||||||
|
other => unreachable!(
|
||||||
|
"Text nodes should only receive ChangeText or Replace patches, not ",
|
||||||
|
// other,
|
||||||
|
// "Text nodes should only receive ChangeText or Replace patches, not {:?}.",
|
||||||
|
// other,
|
||||||
|
),
|
||||||
|
};
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// A node along with all of the closures that were created for that
|
||||||
|
/// node's events and all of it's child node's events.
|
||||||
|
pub struct CreatedNode<T> {
|
||||||
|
/// A `Node` or `Element` that was created from a `VirtualNode`
|
||||||
|
pub node: T,
|
||||||
|
/// A map of a node's unique identifier along with all of the Closures for that node.
|
||||||
|
///
|
||||||
|
/// The DomUpdater uses this to look up nodes and see if they're still in the page. If not
|
||||||
|
/// the reference that we maintain to their closure will be dropped, thus freeing the Closure's
|
||||||
|
/// memory.
|
||||||
|
pub closures: HashMap<u32, Vec<DynClosure>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Box<dyn AsRef<JsValue>>> is our js_sys::Closure. Stored this way to allow us to store
|
||||||
|
/// any Closure regardless of the arguments.
|
||||||
|
pub type DynClosure = Rc<dyn AsRef<JsValue>>;
|
||||||
|
|
||||||
|
impl<T> CreatedNode<T> {
|
||||||
|
pub fn without_closures<N: Into<T>>(node: N) -> Self {
|
||||||
|
CreatedNode {
|
||||||
|
node: node.into(),
|
||||||
|
closures: HashMap::with_capacity(0),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T> std::ops::Deref for CreatedNode<T> {
|
||||||
|
type Target = T;
|
||||||
|
fn deref(&self) -> &Self::Target {
|
||||||
|
&self.node
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<CreatedNode<Element>> for CreatedNode<Node> {
|
||||||
|
fn from(other: CreatedNode<Element>) -> CreatedNode<Node> {
|
||||||
|
CreatedNode {
|
||||||
|
node: other.node.into(),
|
||||||
|
closures: other.closures,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn create_dom_node(node: &VNode<'_>) -> CreatedNode<Node> {
|
||||||
|
match node {
|
||||||
|
VNode::Text(text_node) => CreatedNode::without_closures(create_text_node(text_node)),
|
||||||
|
VNode::Element(element_node) => create_element_node(element_node).into(),
|
||||||
|
// VNode::Element(element_node) => element_node.create_element_node().into(),
|
||||||
|
VNode::Suspended => todo!(" not iimplemented yet"),
|
||||||
|
VNode::Component(_) => todo!(" not iimplemented yet"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Build a DOM element by recursively creating DOM nodes for this element and it's
|
||||||
|
/// children, it's children's children, etc.
|
||||||
|
pub fn create_element_node(node: &dioxus_core::nodes::VElement) -> CreatedNode<Element> {
|
||||||
|
let document = web_sys::window().unwrap().document().unwrap();
|
||||||
|
|
||||||
|
// TODO: enable svg again
|
||||||
|
// let element = if html_validation::is_svg_namespace(&node.tag_name) {
|
||||||
|
// document
|
||||||
|
// .create_element_ns(Some("http://www.w3.org/2000/svg"), &node.tag_name)
|
||||||
|
// .unwrap()
|
||||||
|
// } else {
|
||||||
|
let element = document.create_element(&node.tag_name).unwrap();
|
||||||
|
// };
|
||||||
|
|
||||||
|
let mut closures = HashMap::new();
|
||||||
|
|
||||||
|
node.attributes
|
||||||
|
.iter()
|
||||||
|
.map(|f| (f.name, f.value))
|
||||||
|
.for_each(|(name, value)| {
|
||||||
|
if name == "unsafe_inner_html" {
|
||||||
|
element.set_inner_html(value);
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
element
|
||||||
|
.set_attribute(name, value)
|
||||||
|
.expect("Set element attribute in create element");
|
||||||
|
});
|
||||||
|
|
||||||
|
// if node.events.0.len() > 0 {
|
||||||
|
// let unique_id = create_unique_identifier();
|
||||||
|
|
||||||
|
// element
|
||||||
|
// .set_attribute("data-vdom-id".into(), &unique_id.to_string())
|
||||||
|
// .expect("Could not set attribute on element");
|
||||||
|
|
||||||
|
// closures.insert(unique_id, vec![]);
|
||||||
|
|
||||||
|
// node.events.0.iter().for_each(|(onevent, callback)| {
|
||||||
|
// // onclick -> click
|
||||||
|
// let event = &onevent[2..];
|
||||||
|
|
||||||
|
// let current_elem: &EventTarget = element.dyn_ref().unwrap();
|
||||||
|
|
||||||
|
// current_elem
|
||||||
|
// .add_event_listener_with_callback(event, callback.as_ref().as_ref().unchecked_ref())
|
||||||
|
// .unwrap();
|
||||||
|
|
||||||
|
// closures
|
||||||
|
// .get_mut(&unique_id)
|
||||||
|
// .unwrap()
|
||||||
|
// .push(Rc::clone(callback));
|
||||||
|
// });
|
||||||
|
// }
|
||||||
|
|
||||||
|
let mut previous_node_was_text = false;
|
||||||
|
|
||||||
|
node.children.iter().for_each(|child| {
|
||||||
|
// log::info!("Patching child");
|
||||||
|
match child {
|
||||||
|
VNode::Text(text_node) => {
|
||||||
|
let current_node = element.as_ref() as &web_sys::Node;
|
||||||
|
|
||||||
|
// We ensure that the text siblings are patched by preventing the browser from merging
|
||||||
|
// neighboring text nodes. Originally inspired by some of React's work from 2016.
|
||||||
|
// -> https://reactjs.org/blog/2016/04/07/react-v15.html#major-changes
|
||||||
|
// -> https://github.com/facebook/react/pull/5753
|
||||||
|
//
|
||||||
|
// `ptns` = Percy text node separator
|
||||||
|
if previous_node_was_text {
|
||||||
|
let separator = document.create_comment("ptns");
|
||||||
|
current_node
|
||||||
|
.append_child(separator.as_ref() as &web_sys::Node)
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
current_node
|
||||||
|
.append_child(&create_text_node(&text_node))
|
||||||
|
.unwrap();
|
||||||
|
|
||||||
|
previous_node_was_text = true;
|
||||||
|
}
|
||||||
|
VNode::Element(element_node) => {
|
||||||
|
previous_node_was_text = false;
|
||||||
|
|
||||||
|
let child = create_element_node(element_node);
|
||||||
|
// let child = element_node.create_element_node();
|
||||||
|
let child_elem: Element = child.node;
|
||||||
|
|
||||||
|
closures.extend(child.closures);
|
||||||
|
|
||||||
|
element.append_child(&child_elem).unwrap();
|
||||||
|
}
|
||||||
|
VNode::Suspended => {
|
||||||
|
todo!("Not yet supported")
|
||||||
|
}
|
||||||
|
VNode::Component(_) => {
|
||||||
|
todo!("Not yet supported")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO: connect on mount to the event system somehow
|
||||||
|
// if let Some(on_create_elem) = node.events.0.get("on_create_elem") {
|
||||||
|
// let on_create_elem: &js_sys::Function = on_create_elem.as_ref().as_ref().unchecked_ref();
|
||||||
|
// on_create_elem
|
||||||
|
// .call1(&wasm_bindgen::JsValue::NULL, &element)
|
||||||
|
// .unwrap();
|
||||||
|
// }
|
||||||
|
|
||||||
|
CreatedNode {
|
||||||
|
node: element,
|
||||||
|
closures,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return a `Text` element from a `VirtualNode`, typically right before adding it
|
||||||
|
/// into the DOM.
|
||||||
|
pub fn create_text_node(node: &VText) -> Text {
|
||||||
|
let document = web_sys::window().unwrap().document().unwrap();
|
||||||
|
document.create_text_node(&node.text)
|
||||||
|
}
|
||||||
|
|
||||||
|
// /// For any listeners in the tree, attach the sender closure.
|
||||||
|
// /// When a event is triggered, we convert it into the synthetic event type and dump it back in the Virtual Dom's queu
|
||||||
|
// fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
|
||||||
|
// fn render_diffs() {}
|
||||||
Reference in new issue
Block a user