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! {
<div>
<h1> "Hello, {value}" </h1>
<button onclick={move |_| set_value("world!")}> "?" </button>
<button onclick={move |_| set_value("Dioxus 🎉")}> "?" </button>
<div>
<h1> "Hello, {value}" </h1>
</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 🤲 🍨
@ -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

View file

@ -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<Edit<'src>>;
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);
}

View file

@ -44,9 +44,9 @@ pub struct Context<'src> {
}
impl<'a> Context<'a> {
pub fn props<P>() -> &'a P {
todo!()
}
// pub fn props<P>() -> &'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");
|| {}
}

View file

@ -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<Index>,
diffed: FxHashSet<Index>,
need_to_diff: FxHashSet<Index>,
// Current scopes we're comparing
current_idx: Option<generational_arena::Index>,
}
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<generational_arena::Index>,
) {
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

View file

@ -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);
}

View file

@ -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]

View file

@ -23,16 +23,3 @@ static Example: FC<()> = |ctx, props| {
</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) 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,
temporaries: FxHashMap<u32, Node>,
// callback: RootCallback,
// callback: Option<Closure<dyn Fn(EventTrigger)>>,
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<dyn Fn()>)>,
// Map of callback_id to component index and listener id
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)]
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::<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!("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