From e840f472faf25d8e1d65abeb610daed6a522771d Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 27 Feb 2021 11:43:28 -0500 Subject: [PATCH] WIP: moving to CbIdx as serializable event system --- README.md | 4 +- notes/CHANGELOG.md | 15 ++-- packages/core/src/changelist.rs | 71 ++++++++++++------ packages/core/src/context.rs | 9 ++- packages/core/src/dodriodiff.rs | 72 ++++++++++++------ packages/core/src/lib.rs | 117 +----------------------------- packages/web/Cargo.toml | 1 + packages/web/examples/jackjill.rs | 13 ---- packages/web/src/interpreter.rs | 58 ++++++++++++--- 9 files changed, 160 insertions(+), 200 deletions(-) diff --git a/README.md b/README.md index 091fcabae..4cc9fec32 100644 --- a/README.md +++ b/README.md @@ -16,11 +16,9 @@ static Example: FC<()> = |ctx| { ctx.view(html! {
+

"Hello, {value}"

-
-

"Hello, {value}"

-
}) }; diff --git a/notes/CHANGELOG.md b/notes/CHANGELOG.md index 1f232c486..eaa255047 100644 --- a/notes/CHANGELOG.md +++ b/notes/CHANGELOG.md @@ -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 🤲 🍨 @@ -8,6 +6,7 @@ # Project: Sanitization (TBD) +> Improve code health - [ ] (Macro) Clippy sanity for html macro - [ ] (Macro) Error sanitization @@ -22,13 +21,19 @@ # Project: Concurrency (TBD) > 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) -- [ ] (Web) Web-sys renderer and web tests +- [x] WebSys edit interpreter +- [ ] Event system using async channels # Project: String Render (TBD) > Implement a light-weight string renderer with basic caching - [ ] (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) > Implement the foundations for state management diff --git a/packages/core/src/changelist.rs b/packages/core/src/changelist.rs index e44fa4c57..9f4d374b6 100644 --- a/packages/core/src/changelist.rs +++ b/packages/core/src/changelist.rs @@ -22,7 +22,7 @@ use bumpalo::Bump; use crate::innerlude::Listener; - +use serde::{Deserialize, Serialize}; /// 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 -#[derive(Debug, serde::Serialize, serde::Deserialize)] +#[derive(Debug, Serialize, Deserialize)] #[serde(tag = "type")] pub enum Edit<'d> { SetText { text: &'d str }, @@ -47,8 +47,8 @@ pub enum Edit<'d> { AppendChild, CreateTextNode { text: &'d str }, CreateElement { tag_name: &'d str }, - NewEventListener { event_type: &'d str, a: u32, b: u32 }, - UpdateEventListener { event_type: &'d str, a: u32, b: u32 }, + NewEventListener { event_type: &'d str, idx: CbIdx }, + UpdateEventListener { event_type: &'d str, idx: CbIdx }, RemoveEventListener { event_type: &'d str }, CreateElementNs { tag_name: &'d str, ns: &'d str }, SaveChildrenToTemporaries { temp: u32, start: u32, end: u32 }, @@ -60,6 +60,26 @@ pub enum Edit<'d> { 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>; pub struct EditMachine<'src> { @@ -306,38 +326,41 @@ impl<'a> EditMachine<'a> { 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()); - // 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 { - event_type: listener.event.into(), - a, - b, + event_type: event, + idx, }); + // 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); } - 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()); - if self.forcing_new_listeners { - self.new_event_listener(listener); + self.new_event_listener(event, idx); return; } - 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.push(Edit::NewEventListener { + event_type: event, + idx, }); + + // 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); } diff --git a/packages/core/src/context.rs b/packages/core/src/context.rs index e3e77df80..1feeb2b61 100644 --- a/packages/core/src/context.rs +++ b/packages/core/src/context.rs @@ -44,9 +44,9 @@ pub struct Context<'src> { } impl<'a> Context<'a> { - pub fn props

() -> &'a P { - todo!() - } + // pub fn props

() -> &'a P { + // todo!() + // } // impl<'a, PropType> Context<'a, PropType> { /// 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 pub fn schedule_update(&self) -> impl Fn() -> () { - todo!("Subscription API is not ready yet"); + // log::debug!() + // todo!("Subscription API is not ready yet"); || {} } diff --git a/packages/core/src/dodriodiff.rs b/packages/core/src/dodriodiff.rs index 269996248..62816d77c 100644 --- a/packages/core/src/dodriodiff.rs +++ b/packages/core/src/dodriodiff.rs @@ -37,7 +37,7 @@ use fxhash::{FxHashMap, FxHashSet}; use generational_arena::Index; use crate::{ - changelist::{Edit, EditList, EditMachine}, + changelist::{CbIdx, Edit, EditList, EditMachine}, innerlude::{Attribute, Listener, Scope, VElement, VNode, VText}, virtual_dom::LifecycleEvent, }; @@ -61,16 +61,20 @@ pub struct DiffMachine<'a> { immediate_queue: Vec, diffed: FxHashSet, need_to_diff: FxHashSet, + + // Current scopes we're comparing + current_idx: Option, } impl<'a> DiffMachine<'a> { pub fn new(bump: &'a Bump) -> Self { - log::debug!("starsting diff machine"); + log::debug!("starting diff machine"); Self { change_list: EditMachine::new(bump), immediate_queue: Vec::new(), diffed: FxHashSet::default(), need_to_diff: FxHashSet::default(), + current_idx: None, } } @@ -78,8 +82,17 @@ impl<'a> DiffMachine<'a> { 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, + ) { 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. 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") } } + self.current_idx = None; } // Diff event listeners between `old` and `new`. @@ -237,16 +251,20 @@ impl<'a> DiffMachine<'a> { } 'outer1: for new_l in new { - unsafe { - // Safety relies on removing `new_l` from the registry manually at - // the end of its lifetime. This happens below in the `'outer2` - // loop, and elsewhere in diffing when removing old dom trees. - // registry.add(new_l); - } + // unsafe { + // Safety relies on removing `new_l` from the registry manually at + // the end of its lifetime. This happens below in the `'outer2` + // loop, and elsewhere in diffing when removing old dom trees. + // 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 { - 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; } } @@ -507,7 +525,7 @@ impl<'a> DiffMachine<'a> { self.change_list.go_to_sibling(i); - self.diff_node(old, new); + self.diff_node(old, new, self.current_idx); shared_prefix_count += 1; } @@ -705,7 +723,7 @@ impl<'a> DiffMachine<'a> { // [... parent] self.change_list.go_down_to_temp_child(temp); // [... 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) { // Don't move it, since it is already where it needs to be. @@ -758,7 +776,7 @@ impl<'a> DiffMachine<'a> { // [... 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() { self.change_list.go_to_sibling(new_shared_suffix_start + i); - - self.diff_node(old_child, new_child); + self.diff_node(old_child, new_child, self.current_idx); } // [... parent] @@ -818,7 +835,7 @@ impl<'a> DiffMachine<'a> { // [... parent prev_child] self.change_list.go_to_sibling(i); // [... 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()) { @@ -880,12 +897,21 @@ impl<'a> DiffMachine<'a> { self.change_list.create_element(tag_name); } - for l in listeners { - // unsafe { - // registry.add(l); - // } - self.change_list.new_event_listener(l); - } + listeners.iter().enumerate().for_each(|(id, listener)| { + if let Some(index) = self.current_idx { + self.change_list + .new_event_listener(listener.event, CbIdx::from_gi_index(index, id)); + } 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 { self.change_list diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 83e87f656..479ceb0d4 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -96,6 +96,7 @@ pub(crate) mod innerlude { pub(crate) use crate::virtual_dom::VirtualDom; pub(crate) use nodes::*; + pub use crate::changelist::CbIdx; // pub use nodes::iterables::IterableNodes; /// 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::*; } - -// #[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); -} diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index fac29f672..0321fddd9 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -19,6 +19,7 @@ log = "0.4.14" fxhash = "0.2.1" pretty_env_logger = "0.4.0" console_error_panic_hook = "0.1.6" +generational-arena = "0.2.8" # html-validation = { path = "../html-validation", version = "0.1.1" } [dependencies.web-sys] diff --git a/packages/web/examples/jackjill.rs b/packages/web/examples/jackjill.rs index d14680c91..5933c4cf8 100644 --- a/packages/web/examples/jackjill.rs +++ b/packages/web/examples/jackjill.rs @@ -23,16 +23,3 @@ static Example: FC<()> = |ctx, props| { }) }; - -struct ItemProps { - name: String, - birthdate: String, -} -static Item: FC = |ctx, ItemProps { name, birthdate }| { - ctx.view(html! { -

-

"{name}"

-

"{birthdate}"

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