WIP: moving to CbIdx as serializable event system

This commit is contained in:
Jonathan Kelley 2021-02-27 11:43:28 -05:00
parent e4b1f6ea0d
commit e840f472fa
9 changed files with 160 additions and 200 deletions

View file

@ -16,11 +16,9 @@ static Example: FC<()> = |ctx| {
ctx.view(html! { ctx.view(html! {
<div> <div>
<h1> "Hello, {value}" </h1>
<button onclick={move |_| set_value("world!")}> "?" </button> <button onclick={move |_| set_value("world!")}> "?" </button>
<button onclick={move |_| set_value("Dioxus 🎉")}> "?" </button> <button onclick={move |_| set_value("Dioxus 🎉")}> "?" </button>
<div>
<h1> "Hello, {value}" </h1>
</div>
</div> </div>
}) })
}; };

View file

@ -1,6 +1,4 @@
# Project: Web-View 🤲 🍨
> Proof of concept: stream render edits from server to client
- [x] Prove that the diffing and patching framework can support patch streaming
# Project: Live-View 🤲 🍨 # Project: Live-View 🤲 🍨
@ -8,6 +6,7 @@
# Project: Sanitization (TBD) # Project: Sanitization (TBD)
> Improve code health
- [ ] (Macro) Clippy sanity for html macro - [ ] (Macro) Clippy sanity for html macro
- [ ] (Macro) Error sanitization - [ ] (Macro) Error sanitization
@ -22,13 +21,19 @@
# Project: Concurrency (TBD) # Project: Concurrency (TBD)
> Ensure the concurrency model works well, play with lifetimes to check if it can be multithreaded + halted > Ensure the concurrency model works well, play with lifetimes to check if it can be multithreaded + halted
# Project: Web-View 🤲 🍨
> Proof of concept: stream render edits from server to client
- [x] Prove that the diffing and patching framework can support patch streaming
# Project: Web_sys renderer (TBD) # Project: Web_sys renderer (TBD)
- [ ] (Web) Web-sys renderer and web tests - [x] WebSys edit interpreter
- [ ] Event system using async channels
# Project: String Render (TBD) # Project: String Render (TBD)
> Implement a light-weight string renderer with basic caching > Implement a light-weight string renderer with basic caching
- [ ] (SSR) Implement stateful 3rd party string renderer - [ ] (SSR) Implement stateful 3rd party string renderer
- [ ] (Macro) Make VText nodes automatically capture and format IE allow "Text is {blah}" in place of {format!("Text is {}",blah)} - [x] (Macro) Make VText nodes automatically capture and format IE allow "Text is {blah}"
# Project: Hooks + Context + Subscriptions (TBD) # Project: Hooks + Context + Subscriptions (TBD)
> Implement the foundations for state management > Implement the foundations for state management

View file

@ -22,7 +22,7 @@
use bumpalo::Bump; use bumpalo::Bump;
use crate::innerlude::Listener; use crate::innerlude::Listener;
use serde::{Deserialize, Serialize};
/// The `Edit` represents a single modifcation of the renderer tree. /// The `Edit` represents a single modifcation of the renderer tree.
/// ///
/// ///
@ -33,7 +33,7 @@ use crate::innerlude::Listener;
/// ///
/// ///
/// todo@ jon: allow serde to be optional /// todo@ jon: allow serde to be optional
#[derive(Debug, serde::Serialize, serde::Deserialize)] #[derive(Debug, Serialize, Deserialize)]
#[serde(tag = "type")] #[serde(tag = "type")]
pub enum Edit<'d> { pub enum Edit<'d> {
SetText { text: &'d str }, SetText { text: &'d str },
@ -47,8 +47,8 @@ pub enum Edit<'d> {
AppendChild, AppendChild,
CreateTextNode { text: &'d str }, CreateTextNode { text: &'d str },
CreateElement { tag_name: &'d str }, CreateElement { tag_name: &'d str },
NewEventListener { event_type: &'d str, a: u32, b: u32 }, NewEventListener { event_type: &'d str, idx: CbIdx },
UpdateEventListener { event_type: &'d str, a: u32, b: u32 }, UpdateEventListener { event_type: &'d str, idx: CbIdx },
RemoveEventListener { event_type: &'d str }, RemoveEventListener { event_type: &'d str },
CreateElementNs { tag_name: &'d str, ns: &'d str }, CreateElementNs { tag_name: &'d str, ns: &'d str },
SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 }, SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 },
@ -60,6 +60,26 @@ pub enum Edit<'d> {
SetClass { class_name: &'d str }, SetClass { class_name: &'d str },
} }
/// Re-export a cover over generational ID for libraries that don't need it
/// We can go back and forth between the two via methods on GI
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
pub struct CbIdx {
gi_id: usize,
gi_gen: u64,
listener_idx: usize,
}
impl CbIdx {
pub fn from_gi_index(index: generational_arena::Index, listener_idx: usize) -> Self {
let (gi_id, gi_gen) = index.into_raw_parts();
Self {
gi_id,
gi_gen,
listener_idx,
}
}
}
pub type EditList<'src> = Vec<Edit<'src>>; pub type EditList<'src> = Vec<Edit<'src>>;
pub struct EditMachine<'src> { pub struct EditMachine<'src> {
@ -306,38 +326,41 @@ impl<'a> EditMachine<'a> {
self.forcing_new_listeners = previous; self.forcing_new_listeners = previous;
} }
pub fn new_event_listener(&mut self, listener: &Listener) { pub fn new_event_listener(&mut self, event: &'a str, idx: CbIdx) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// todo!("Event listener not wired up yet");
log::debug!("emit: new_event_listener({:?})", listener);
let (a, b) = listener.get_callback_parts();
debug_assert!(a != 0);
// let event_id = self.ensure_string(listener.event);
self.emitter.push(Edit::NewEventListener { self.emitter.push(Edit::NewEventListener {
event_type: listener.event.into(), event_type: event,
a, idx,
b,
}); });
// todo!("Event listener not wired up yet");
// log::debug!("emit: new_event_listener({:?})", listener);
// let (a, b) = listener.get_callback_parts();
// debug_assert!(a != 0);
// // let event_id = self.ensure_string(listener.event);
// self.emitter.new_event_listener(listener.event.into(), a, b); // self.emitter.new_event_listener(listener.event.into(), a, b);
} }
pub fn update_event_listener(&mut self, listener: &Listener) { pub fn update_event_listener(&mut self, event: &'a str, idx: CbIdx) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
if self.forcing_new_listeners { if self.forcing_new_listeners {
self.new_event_listener(listener); self.new_event_listener(event, idx);
return; return;
} }
log::debug!("emit: update_event_listener({:?})", listener); self.emitter.push(Edit::NewEventListener {
// todo!("Event listener not wired up yet"); event_type: event,
let (a, b) = listener.get_callback_parts(); idx,
debug_assert!(a != 0);
self.emitter.push(Edit::UpdateEventListener {
event_type: listener.event.into(),
a,
b,
}); });
// log::debug!("emit: update_event_listener({:?})", listener);
// // todo!("Event listener not wired up yet");
// let (a, b) = listener.get_callback_parts();
// debug_assert!(a != 0);
// self.emitter.push(Edit::UpdateEventListener {
// event_type: listener.event.into(),
// a,
// b,
// });
// self.emitter.update_event_listener(event_id.into(), a, b); // self.emitter.update_event_listener(event_id.into(), a, b);
} }

View file

@ -44,9 +44,9 @@ pub struct Context<'src> {
} }
impl<'a> Context<'a> { impl<'a> Context<'a> {
pub fn props<P>() -> &'a P { // pub fn props<P>() -> &'a P {
todo!() // todo!()
} // }
// impl<'a, PropType> Context<'a, PropType> { // impl<'a, PropType> Context<'a, PropType> {
/// Access the children elements passed into the component /// Access the children elements passed into the component
@ -56,7 +56,8 @@ impl<'a> Context<'a> {
/// Create a subscription that schedules a future render for the reference component /// Create a subscription that schedules a future render for the reference component
pub fn schedule_update(&self) -> impl Fn() -> () { pub fn schedule_update(&self) -> impl Fn() -> () {
todo!("Subscription API is not ready yet"); // log::debug!()
// todo!("Subscription API is not ready yet");
|| {} || {}
} }

View file

@ -37,7 +37,7 @@ use fxhash::{FxHashMap, FxHashSet};
use generational_arena::Index; use generational_arena::Index;
use crate::{ use crate::{
changelist::{Edit, EditList, EditMachine}, changelist::{CbIdx, Edit, EditList, EditMachine},
innerlude::{Attribute, Listener, Scope, VElement, VNode, VText}, innerlude::{Attribute, Listener, Scope, VElement, VNode, VText},
virtual_dom::LifecycleEvent, virtual_dom::LifecycleEvent,
}; };
@ -61,16 +61,20 @@ pub struct DiffMachine<'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>,
// Current scopes we're comparing
current_idx: Option<generational_arena::Index>,
} }
impl<'a> DiffMachine<'a> { impl<'a> DiffMachine<'a> {
pub fn new(bump: &'a Bump) -> Self { pub fn new(bump: &'a Bump) -> Self {
log::debug!("starsting diff machine"); log::debug!("starting diff machine");
Self { Self {
change_list: EditMachine::new(bump), change_list: EditMachine::new(bump),
immediate_queue: Vec::new(), immediate_queue: Vec::new(),
diffed: FxHashSet::default(), diffed: FxHashSet::default(),
need_to_diff: FxHashSet::default(), need_to_diff: FxHashSet::default(),
current_idx: None,
} }
} }
@ -78,8 +82,17 @@ impl<'a> DiffMachine<'a> {
self.change_list.emitter self.change_list.emitter
} }
pub fn diff_node(&mut self, old: &VNode<'a>, new: &VNode<'a>) { pub fn diff_node(
&mut self,
old: &VNode<'a>,
new: &VNode<'a>,
scope: Option<generational_arena::Index>,
) {
log::debug!("Diffing nodes"); log::debug!("Diffing nodes");
// Set it while diffing
// Reset it when finished diffing
self.current_idx = scope;
/* /*
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.
@ -222,6 +235,7 @@ impl<'a> DiffMachine<'a> {
todo!("Suspended components not currently available") todo!("Suspended components not currently available")
} }
} }
self.current_idx = None;
} }
// Diff event listeners between `old` and `new`. // Diff event listeners between `old` and `new`.
@ -237,16 +251,20 @@ impl<'a> DiffMachine<'a> {
} }
'outer1: for new_l in new { 'outer1: for new_l in new {
unsafe { // unsafe {
// Safety relies on removing `new_l` from the registry manually at // Safety relies on removing `new_l` from the registry manually at
// the end of its lifetime. This happens below in the `'outer2` // the end of its lifetime. This happens below in the `'outer2`
// loop, and elsewhere in diffing when removing old dom trees. // loop, and elsewhere in diffing when removing old dom trees.
// registry.add(new_l); // registry.add(new_l);
} // }
for old_l in old { for (l_idx, old_l) in old.iter().enumerate() {
if new_l.event == old_l.event { if new_l.event == old_l.event {
self.change_list.update_event_listener(new_l); let event_type = new_l.event;
if let Some(scope) = self.current_idx {
let cb = CbIdx::from_gi_index(scope, l_idx);
self.change_list.update_event_listener(event_type, cb);
}
continue 'outer1; continue 'outer1;
} }
} }
@ -507,7 +525,7 @@ impl<'a> DiffMachine<'a> {
self.change_list.go_to_sibling(i); self.change_list.go_to_sibling(i);
self.diff_node(old, new); self.diff_node(old, new, self.current_idx);
shared_prefix_count += 1; shared_prefix_count += 1;
} }
@ -705,7 +723,7 @@ impl<'a> DiffMachine<'a> {
// [... parent] // [... parent]
self.change_list.go_down_to_temp_child(temp); self.change_list.go_down_to_temp_child(temp);
// [... parent last] // [... parent last]
self.diff_node(&old[old_index], last); self.diff_node(&old[old_index], last, self.current_idx);
if new_index_is_in_lis.contains(&last_index) { if new_index_is_in_lis.contains(&last_index) {
// Don't move it, since it is already where it needs to be. // Don't move it, since it is already where it needs to be.
@ -758,7 +776,7 @@ impl<'a> DiffMachine<'a> {
// [... parent new_child] // [... parent new_child]
} }
self.diff_node(&old[old_index], new_child); self.diff_node(&old[old_index], new_child, self.current_idx);
} }
} }
@ -789,8 +807,7 @@ impl<'a> DiffMachine<'a> {
for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() { for (i, (old_child, new_child)) in old.iter().zip(new.iter()).enumerate() {
self.change_list.go_to_sibling(new_shared_suffix_start + i); self.change_list.go_to_sibling(new_shared_suffix_start + i);
self.diff_node(old_child, new_child, self.current_idx);
self.diff_node(old_child, new_child);
} }
// [... parent] // [... parent]
@ -818,7 +835,7 @@ impl<'a> DiffMachine<'a> {
// [... parent prev_child] // [... parent prev_child]
self.change_list.go_to_sibling(i); self.change_list.go_to_sibling(i);
// [... parent this_child] // [... parent this_child]
self.diff_node(old_child, new_child); self.diff_node(old_child, new_child, self.current_idx);
} }
match old.len().cmp(&new.len()) { match old.len().cmp(&new.len()) {
@ -880,12 +897,21 @@ impl<'a> DiffMachine<'a> {
self.change_list.create_element(tag_name); self.change_list.create_element(tag_name);
} }
for l in listeners { listeners.iter().enumerate().for_each(|(id, listener)| {
// unsafe { if let Some(index) = self.current_idx {
// registry.add(l); self.change_list
// } .new_event_listener(listener.event, CbIdx::from_gi_index(index, id));
self.change_list.new_event_listener(l); } else {
} // Don't panic
// Used for testing
log::trace!("Failed to set listener, create was not called in the context of the virtual dom");
}
});
// for l in listeners {
// unsafe {
// registry.add(l);
// }
// }
for attr in attributes { for attr in attributes {
self.change_list self.change_list

View file

@ -96,6 +96,7 @@ pub(crate) mod innerlude {
pub(crate) use crate::virtual_dom::VirtualDom; pub(crate) use crate::virtual_dom::VirtualDom;
pub(crate) use nodes::*; pub(crate) use nodes::*;
pub use crate::changelist::CbIdx;
// 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.
@ -151,119 +152,3 @@ pub mod prelude {
pub use crate::hooks::*; pub use crate::hooks::*;
} }
// #[macro_use]
// extern crate dioxus_core_macro;
// #[macro_use]
// extern crate fstrings;
// pub use dioxus_core_macro::format_args_f;
// macro_rules! mk_macros {( @with_dollar![$dol:tt]=>
// $(
// #[doc = $doc_string:literal]
// $printlnf:ident
// => $println:ident!($($stream:ident,)? ...)
// ,
// )*
// ) => (
// $(
// #[doc = $doc_string]
// #[macro_export]
// macro_rules! $printlnf {(
// $($dol $stream : expr,)? $dol($dol args:tt)*
// ) => (
// $println!($($dol $stream,)? "{}", format_args_f!($dol($dol args)*))
// )}
// )*
// )}
// mk_macros! { @with_dollar![$]=>
// #[doc = "Like [`print!`](https://doc.rust-lang.org/std/macro.print.html), but with basic f-string interpolation."]
// print_f
// => print!(...)
// ,
// #[doc = "Like [`println!`](https://doc.rust-lang.org/std/macro.println.html), but with basic f-string interpolation."]
// println_f
// => println!(...)
// ,
// #[doc = "Like [`eprint!`](https://doc.rust-lang.org/std/macro.eprint.html), but with basic f-string interpolation."]
// eprint_f
// => eprint!(...)
// ,
// #[doc = "Like [`eprintln!`](https://doc.rust-lang.org/std/macro.eprintln.html), but with basic f-string interpolation."]
// eprintln_f
// => eprintln!(...)
// ,
// #[doc = "Like [`format!`](https://doc.rust-lang.org/std/macro.format.html), but with basic f-string interpolation."]
// format_f
// => format!(...)
// ,
// #[doc = "Shorthand for [`format_f`]."]
// f
// => format!(...)
// ,
// #[doc = "Like [`panic!`](https://doc.rust-lang.org/std/macro.panic.html), but with basic f-string interpolation."]
// panic_f
// => panic!(...)
// ,
// #[doc = "Like [`unreachable!`](https://doc.rust-lang.org/std/macro.unreachable.html), but with basic f-string interpolation."]
// unreachable_f
// => unreachable!(...)
// ,
// #[doc = "Like [`unimplemented!`](https://doc.rust-lang.org/std/macro.unimplemented.html), but with basic f-string interpolation."]
// unimplemented_f
// => unimplemented!(...)
// ,
// #[doc = "Like [`write!`](https://doc.rust-lang.org/std/macro.write.html), but with basic f-string interpolation."]
// write_f
// => write!(stream, ...)
// ,
// #[doc = "Like [`writeln!`](https://doc.rust-lang.org/std/macro.writeln.html), but with basic f-string interpolation."]
// writeln_f
// => writeln!(stream, ...)
// ,
// }
/// Like the `format!` macro for creating `std::string::String`s but for
/// `bumpalo::collections::String`.
///
/// # Examples
///
/// ```
/// use bumpalo::Bump;
///
/// let b = Bump::new();
///
/// let who = "World";
/// let s = bumpalo::format!(in &b, "Hello, {}!", who);
/// assert_eq!(s, "Hello, World!")
/// ```
#[macro_export]
macro_rules! ifmt {
( in $bump:expr; $fmt:literal;) => {{
use bumpalo::core_alloc::fmt::Write;
use $crate::prelude::bumpalo;
let bump = $bump;
let mut s = bumpalo::collections::String::new_in(bump);
let args = $crate::prelude::format_args_f!($fmt);
s.write_fmt(args);
s
}};
}
// ( in $bump:expr; $fmt:expr; ) => {
// $println!("{}", format_args_f!($dol($dol args)*))
// write!(&mut s, println!("{}", args));
// let _ = $crate::write_f!(&mut s, $fmt);
// s
// use fstrings::*;
// $crate::ifmt!(in $bump, $fmt)
// };
#[test]
fn macro_test() {
let w = 123;
let world = &w;
// let g = format_args_f!("Hello {world}");
// dbg!(g);
}

View file

@ -19,6 +19,7 @@ log = "0.4.14"
fxhash = "0.2.1" fxhash = "0.2.1"
pretty_env_logger = "0.4.0" pretty_env_logger = "0.4.0"
console_error_panic_hook = "0.1.6" console_error_panic_hook = "0.1.6"
generational-arena = "0.2.8"
# html-validation = { path = "../html-validation", version = "0.1.1" } # html-validation = { path = "../html-validation", version = "0.1.1" }
[dependencies.web-sys] [dependencies.web-sys]

View file

@ -23,16 +23,3 @@ static Example: FC<()> = |ctx, props| {
</div> </div>
}) })
}; };
struct ItemProps {
name: String,
birthdate: String,
}
static Item: FC<ItemProps> = |ctx, ItemProps { name, birthdate }| {
ctx.view(html! {
<div>
<p>"{name}"</p>
<p>"{birthdate}"</p>
</div>
})
};

View file

@ -20,22 +20,50 @@ impl std::fmt::Debug for RootCallback {
pub(crate) struct PatchMachine { pub(crate) struct PatchMachine {
pub(crate) stack: Stack, pub(crate) stack: Stack,
pub(crate) root: Element,
pub(crate) temporaries: FxHashMap<u32, Node>,
pub(crate) document: Document,
pub(crate) events: EventDelegater,
}
#[derive(Debug)]
pub(crate) struct EventDelegater {
root: Element, root: Element,
temporaries: FxHashMap<u32, Node>,
// callback: RootCallback,
// callback: Option<Closure<dyn Fn(EventTrigger)>>,
document: Document,
// every callback gets a monotomically increasing callback ID // every callback gets a monotomically increasing callback ID
callback_id: usize, callback_id: usize,
// map of listener types to number of those listeners
listeners: FxHashMap<&'static str, (usize, Closure<dyn Fn()>)>,
// Map of callback_id to component index and listener id // Map of callback_id to component index and listener id
callback_map: FxHashMap<usize, (usize, usize)>, callback_map: FxHashMap<usize, (usize, usize)>,
} }
// templates: FxHashMap<CacheId, Node>, impl EventDelegater {
pub fn new(root: Element) -> Self {
Self {
root,
callback_id: 0,
listeners: FxHashMap::default(),
callback_map: FxHashMap::default(),
}
}
pub fn add_listener(
&mut self,
event: &'static str,
gi: generational_arena::Index,
listener_id: usize,
) {
}
}
// callback: RootCallback,
// callback: Option<Closure<dyn Fn(EventTrigger)>>,
#[derive(Debug, Default)] #[derive(Debug, Default)]
pub struct Stack { pub struct Stack {
@ -85,16 +113,14 @@ impl PatchMachine {
.expect("must have access to the Document"); .expect("must have access to the Document");
// attach all listeners to the container element // attach all listeners to the container element
let events = EventDelegater::new(root.clone());
// templates: Default::default(),
Self { Self {
root, root,
events,
stack: Stack::with_capacity(20), stack: Stack::with_capacity(20),
temporaries: Default::default(), temporaries: Default::default(),
// callback: None,
document, document,
callback_id: 0,
callback_map: FxHashMap::default(),
} }
} }
@ -293,7 +319,11 @@ impl PatchMachine {
} }
// 11 // 11
Edit::NewEventListener { event_type, a, b } => { Edit::NewEventListener {
event_type,
idx: a,
b,
} => {
// attach the correct attributes to the element // attach the correct attributes to the element
// these will be used by accessing the event's target // these will be used by accessing the event's target
// This ensures we only ever have one handler attached to the root, but decide // This ensures we only ever have one handler attached to the root, but decide
@ -304,16 +334,20 @@ impl PatchMachine {
let el = el let el = el
.dyn_ref::<Element>() .dyn_ref::<Element>()
.expect(&format!("not an element: {:?}", el)); .expect(&format!("not an element: {:?}", el));
// el.add_event_listener_with_callback( // el.add_event_listener_with_callback(
// event_type, // event_type,
// self.callback.as_ref().unwrap().as_ref().unchecked_ref(), // self.callback.as_ref().unwrap().as_ref().unchecked_ref(),
// ) // )
// .unwrap(); // .unwrap();
debug!("adding attributes: {}, {}", a, b); debug!("adding attributes: {}, {}", a, b);
el.set_attribute(&format!("dioxus-a-{}", event_type), &a.to_string()) el.set_attribute(&format!("dioxus-a-{}", event_type), &a.to_string())
.unwrap(); .unwrap();
el.set_attribute(&format!("dioxus-b-{}", event_type), &b.to_string()) el.set_attribute(&format!("dioxus-b-{}", event_type), &b.to_string())
.unwrap(); .unwrap();
self.events.add_listener(event_type, gi, listener_id)
} }
// 12 // 12