Feat: remove generic paramter on VDOM

This makes storage of VDoms easier, and removes static dispatch on any function that relies on VDOMs.
This commit is contained in:
Jonathan Kelley committed 2021-02-13 03:19:35 -05:00
1 parent 4bbc5af6e1
commit 4c291a0efd
8 files changed
+113 -75

No files matched your search

+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": true "rust-analyzer.inlayHints.enable": false
} }
+36 -28
View File
@@ -19,7 +19,7 @@
//! //!
//! //!
use crate::innerlude::Listener; use crate::innerlude::{Listener, VirtualDom};
/// Renderers need to implement the interpreter trait /// Renderers need to implement the interpreter trait
/// ///
@@ -60,6 +60,7 @@ pub struct ChangeList<'src> {
next_temporary: u32, next_temporary: u32,
forcing_new_listeners: bool, forcing_new_listeners: bool,
emitter: &'src mut dyn Inrerpreter, emitter: &'src mut dyn Inrerpreter,
domlock: &'src VirtualDom,
} }
/// Traversal methods. /// Traversal methods.
@@ -158,19 +159,19 @@ impl ChangeList<'_> {
temp_base temp_base
} }
pub fn push_temporary(&self, temp: u32) { pub fn push_temporary(&mut self, temp: u32) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: push_temporary({})", temp); // debug!("emit: push_temporary({})", temp);
self.emitter.push_temporary(temp); self.emitter.push_temporary(temp);
} }
pub fn remove_child(&self, child: usize) { pub fn remove_child(&mut self, child: usize) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_child({})", child); // debug!("emit: remove_child({})", child);
self.emitter.remove_child(child as u32); self.emitter.remove_child(child as u32);
} }
pub fn insert_before(&self) { pub fn insert_before(&mut self) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: insert_before()"); // debug!("emit: insert_before()");
self.emitter.insert_before(); self.emitter.insert_before();
@@ -181,20 +182,20 @@ impl ChangeList<'_> {
// self.strings.ensure_string(string, &self.emitter) // self.strings.ensure_string(string, &self.emitter)
} }
pub fn set_text(&self, text: &str) { pub fn set_text(&mut self, text: &str) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: set_text({:?})", text); // debug!("emit: set_text({:?})", text);
self.emitter.set_text(text); self.emitter.set_text(text);
// .set_text(text.as_ptr() as u32, text.len() as u32); // .set_text(text.as_ptr() as u32, text.len() as u32);
} }
pub fn remove_self_and_next_siblings(&self) { pub fn remove_self_and_next_siblings(&mut self) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_self_and_next_siblings()"); // debug!("emit: remove_self_and_next_siblings()");
self.emitter.remove_self_and_next_siblings(); self.emitter.remove_self_and_next_siblings();
} }
pub fn replace_with(&self) { pub fn replace_with(&mut self) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: replace_with()"); // debug!("emit: replace_with()");
self.emitter.replace_with(); self.emitter.replace_with();
@@ -218,38 +219,42 @@ impl ChangeList<'_> {
} }
pub fn remove_attribute(&mut self, name: &str) { pub fn remove_attribute(&mut self, name: &str) {
debug_assert!(self.traversal_is_committed()); // todo!("figure out how to get this working with ensure string");
// debug!("emit: remove_attribute({:?})", name); self.emitter.remove_attribute(name);
let name_id = self.ensure_string(name); // debug_assert!(self.traversal_is_committed());
self.emitter.remove_attribute(name_id.into()); // // debug!("emit: remove_attribute({:?})", name);
// let name_id = self.ensure_string(name);
// self.emitter.remove_attribute(name_id.into());
} }
pub fn append_child(&self) { pub fn append_child(&mut self) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: append_child()"); // debug!("emit: append_child()");
self.emitter.append_child(); self.emitter.append_child();
} }
pub fn create_text_node(&self, text: &str) { pub fn create_text_node(&mut self, text: &str) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: create_text_node({:?})", text); // debug!("emit: create_text_node({:?})", text);
self.emitter.create_text_node(text); self.emitter.create_text_node(text);
} }
pub fn create_element(&mut self, tag_name: &str) { pub fn create_element(&mut self, tag_name: &str) {
debug_assert!(self.traversal_is_committed()); // debug_assert!(self.traversal_is_committed());
// debug!("emit: create_element({:?})", tag_name); // debug!("emit: create_element({:?})", tag_name);
let tag_name_id = self.ensure_string(tag_name); // let tag_name_id = self.ensure_string(tag_name);
self.emitter.create_element(tag_name_id.into()); self.emitter.create_element(tag_name);
// self.emitter.create_element(tag_name_id.into());
} }
pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) { pub fn create_element_ns(&mut self, tag_name: &str, ns: &str) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns); // debug!("emit: create_element_ns({:?}, {:?})", tag_name, ns);
let tag_name_id = self.ensure_string(tag_name); // let tag_name_id = self.ensure_string(tag_name);
let ns_id = self.ensure_string(ns); // let ns_id = self.ensure_string(ns);
self.emitter self.emitter.create_element_ns(tag_name, ns);
.create_element_ns(tag_name_id.into(), ns_id.into()); // self.emitter
// .create_element_ns(tag_name_id.into(), ns_id.into());
} }
pub fn push_force_new_listeners(&mut self) -> bool { pub fn push_force_new_listeners(&mut self) -> bool {
@@ -265,11 +270,12 @@ impl ChangeList<'_> {
pub fn new_event_listener(&mut self, listener: &Listener) { pub fn new_event_listener(&mut self, listener: &Listener) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
todo!("Event listener not wired up yet");
// debug!("emit: new_event_listener({:?})", listener); // debug!("emit: new_event_listener({:?})", listener);
let (a, b) = listener.get_callback_parts(); // let (a, b) = listener.get_callback_parts();
debug_assert!(a != 0); // debug_assert!(a != 0);
let event_id = self.ensure_string(listener.event); // let event_id = self.ensure_string(listener.event);
self.emitter.new_event_listener(event_id.into(), a, b); // self.emitter.new_event_listener(event_id.into(), a, b);
} }
pub fn update_event_listener(&mut self, listener: &Listener) { pub fn update_event_listener(&mut self, listener: &Listener) {
@@ -281,17 +287,19 @@ impl ChangeList<'_> {
} }
// debug!("emit: update_event_listener({:?})", listener); // debug!("emit: update_event_listener({:?})", listener);
let (a, b) = listener.get_callback_parts(); todo!("Event listener not wired up yet");
debug_assert!(a != 0); // let (a, b) = listener.get_callback_parts();
// debug_assert!(a != 0);
let event_id = self.ensure_string(listener.event); let event_id = self.ensure_string(listener.event);
self.emitter.update_event_listener(event_id.into(), a, b); // self.emitter.update_event_listener(event_id.into(), a, b);
} }
pub fn remove_event_listener(&mut self, event: &str) { pub fn remove_event_listener(&mut self, event: &str) {
debug_assert!(self.traversal_is_committed()); debug_assert!(self.traversal_is_committed());
// debug!("emit: remove_event_listener({:?})", event); // debug!("emit: remove_event_listener({:?})", event);
let event_id = self.ensure_string(event); let event_id = self.ensure_string(event);
self.emitter.remove_event_listener(event_id.into()); todo!("Event listener not wired up yet");
// self.emitter.remove_event_listener(event_id.into());
} }
// #[inline] // #[inline]
+4 -4
View File
@@ -5,12 +5,12 @@
use crate::prelude::{Properties, VirtualDom}; use crate::prelude::{Properties, VirtualDom};
pub struct DebugRenderer<'a, P: Properties> { pub struct DebugRenderer {
vdom: &'a mut VirtualDom<P>, vdom: VirtualDom,
} }
impl<'a, P: Properties> DebugRenderer<'a, P> { impl DebugRenderer {
pub fn new(vdom: &'a mut VirtualDom<P>) -> Self { pub fn new(vdom: VirtualDom) -> Self {
Self { vdom } Self { vdom }
} }
+3
View File
@@ -7,6 +7,9 @@ pub enum Error {
#[error("No event to progress")] #[error("No event to progress")]
NoEvent, NoEvent,
#[error("Wrong Properties Type")]
WrongProps,
// #[error("Out of compute credits")] // #[error("Out of compute credits")]
// OutOfCredits, // OutOfCredits,
+32 -12
View File
@@ -1,4 +1,4 @@
use crate::nodes::VNode; use crate::{changelist::ChangeList, nodes::VNode};
use crate::{events::EventTrigger, innerlude::*}; use crate::{events::EventTrigger, innerlude::*};
use any::Any; use any::Any;
use bumpalo::Bump; use bumpalo::Bump;
@@ -14,7 +14,8 @@ use std::{
/// An integrated virtual node system that progresses events and diffs UI trees. /// An integrated virtual node system that progresses events and diffs UI trees.
/// Differences are converted into patches which a renderer can use to draw the UI. /// Differences are converted into patches which a renderer can use to draw the UI.
pub struct VirtualDom<P: Properties> { pub struct VirtualDom {
// pub struct VirtualDom<P: Properties> {
/// All mounted components are arena allocated to make additions, removals, and references easy to work with /// All mounted components are arena allocated to make additions, removals, and references easy to work with
/// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena. /// A generational arean is used to re-use slots of deleted scopes without having to resize the underlying arena.
pub(crate) components: Arena<Scope>, pub(crate) components: Arena<Scope>,
@@ -28,11 +29,15 @@ pub struct VirtualDom<P: Properties> {
/// ///
event_queue: Rc<RefCell<Vec<LifecycleEvent>>>, event_queue: Rc<RefCell<Vec<LifecycleEvent>>>,
#[doc(hidden)] // 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
_root_props: PhantomData<P>, // This is done so we don't have a "generic" vdom, making it easier to hold references to it, especially when the holders
// don't care about the generic props type
// Most implementations that use the VirtualDom won't care about the root props anyways.
#[doc(hidden)]
_root_prop_type: std::any::TypeId,
} }
impl VirtualDom<()> { impl VirtualDom {
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component. /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
/// ///
/// This means that the root component must either consumes its own context, or statics are used to generate the page. /// This means that the root component must either consumes its own context, or statics are used to generate the page.
@@ -42,13 +47,14 @@ impl VirtualDom<()> {
} }
} }
impl<P: Properties + 'static> VirtualDom<P> { impl VirtualDom {
// impl<P: Properties + 'static> VirtualDom<P> {
/// Start a new VirtualDom instance with a dependent props. /// Start a new VirtualDom instance with a dependent props.
/// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders. /// Later, the props can be updated by calling "update" with a new set of props, causing a set of re-renders.
/// ///
/// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive /// This is useful when a component tree can be driven by external state (IE SSR) but it would be too expensive
/// to toss out the entire tree. /// to toss out the entire tree.
pub fn new_with_props(root: FC<P>, root_props: P) -> Self { pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
// 1. Create the component arena // 1. Create the component arena
// 2. Create the base scope (can never be removed) // 2. Create the base scope (can never be removed)
// 3. Create the lifecycle queue // 3. Create the lifecycle queue
@@ -67,28 +73,41 @@ impl<P: Properties + 'static> VirtualDom<P> {
// 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]));
let _root_prop_type = TypeId::of::<P>();
Self { Self {
components, components,
base_scope, base_scope,
event_queue, event_queue,
_root_props: PhantomData {}, _root_prop_type,
} }
} }
/// With access to the virtual dom, schedule an update to the Root component's props /// With access to the virtual dom, schedule an update to the Root component's props
pub fn update_props(&mut self, new_props: P) { pub fn update_props<P: Properties + 'static>(&mut self, new_props: P) -> Result<()> {
// Ensure the props match
if TypeId::of::<P>() != self._root_prop_type {
return Err(Error::WrongProps);
}
self.event_queue.borrow_mut().push(LifecycleEvent { self.event_queue.borrow_mut().push(LifecycleEvent {
event_type: LifecycleType::PropsChanged { event_type: LifecycleType::PropsChanged {
props: Box::new(new_props), props: Box::new(new_props),
}, },
index: self.base_scope, index: self.base_scope,
}); });
Ok(())
} }
/// Schedule a future update for a component from outside the vdom!
///
/// This lets services external to the virtual dom interact directly with the component and event system.
pub fn queue_update() {}
/// Pop an event off the event queue and process it /// Pop an event off the event queue and process it
/// 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.event_queue.borrow_mut().pop().ok_or(Error::NoEvent)?;
process_event(&mut self.components, event) process_event(&mut self.components, event)
@@ -109,7 +128,7 @@ impl<P: Properties + 'static> VirtualDom<P> {
/// ///
/// ///
/// ``` /// ```
pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<()> { pub async fn progress_with_event(&mut self, evt: EventTrigger) -> Result<ChangeList<'_>> {
let EventTrigger { let EventTrigger {
component_id, component_id,
listener_id, listener_id,
@@ -144,7 +163,8 @@ impl<P: Properties + 'static> VirtualDom<P> {
process_event(&mut self.components, event)?; process_event(&mut self.components, event)?;
} }
Ok(()) todo!()
// Ok(())
} }
pub async fn progress_completely(&mut self) -> Result<()> { pub async fn progress_completely(&mut self) -> Result<()> {
+2
View File
@@ -15,6 +15,7 @@ wasm-bindgen-futures = "0.4.20"
futures = "0.3.12" 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"
# html-validation = { path = "../html-validation", version = "0.1.1" } # html-validation = { path = "../html-validation", version = "0.1.1" }
[dependencies.web-sys] [dependencies.web-sys]
@@ -33,4 +34,5 @@ features = [
"Event", "Event",
"MouseEvent", "MouseEvent",
"InputEvent", "InputEvent",
"DocumentType",
] ]
+27 -22
View File
@@ -1,9 +1,12 @@
use crate::cached_set::CacheId; // use crate::cached_set::CacheId;
use crate::{Element, EventsTrampoline}; // 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, Event, Node}; use web_sys::{window, Document, Element, Event, Node};
#[derive(Clone, Copy, PartialEq, Eq, Debug, Hash)]
pub(crate) struct CacheId(u32);
#[derive(Debug)] #[derive(Debug)]
pub(crate) struct ChangeListInterpreter { pub(crate) struct ChangeListInterpreter {
@@ -86,27 +89,29 @@ impl ChangeListInterpreter {
self.templates.get(&id) self.templates.get(&id)
} }
pub fn init_events_trampoline(&mut self, mut trampoline: EventsTrampoline) { pub fn init_events_trampoline(&mut self, mut trampoline: ()) {
self.callback = Some(Closure::wrap(Box::new(move |event: &web_sys::Event| { todo!("Event trampoline not a thing anymore")
let target = event // pub fn init_events_trampoline(&mut self, mut trampoline: EventsTrampoline) {
.target() // self.callback = Some(Closure::wrap(Box::new(move |event: &web_sys::Event| {
.expect("missing target") // let target = event
.dyn_into::<Element>() // .target()
.expect("not a valid element"); // .expect("missing target")
let typ = event.type_(); // .dyn_into::<Element>()
let a: u32 = target // .expect("not a valid element");
.get_attribute(&format!("dodrio-a-{}", typ)) // let typ = event.type_();
.and_then(|v| v.parse().ok()) // let a: u32 = target
.unwrap_or_default(); // .get_attribute(&format!("dodrio-a-{}", typ))
// .and_then(|v| v.parse().ok())
// .unwrap_or_default();
let b: u32 = target // let b: u32 = target
.get_attribute(&format!("dodrio-b-{}", typ)) // .get_attribute(&format!("dodrio-b-{}", typ))
.and_then(|v| v.parse().ok()) // .and_then(|v| v.parse().ok())
.unwrap_or_default(); // .unwrap_or_default();
// get a and b from the target // // get a and b from the target
trampoline(event.clone(), a, b); // trampoline(event.clone(), a, b);
}) as Box<dyn FnMut(&Event)>)); // }) as Box<dyn FnMut(&Event)>));
} }
// 0 // 0
+8 -8
View File
@@ -29,12 +29,12 @@ pub mod interpreter;
/// Under the hood, we leverage WebSys and interact directly with the DOM /// Under the hood, we leverage WebSys and interact directly with the DOM
/// ///
pub struct WebsysRenderer<T: Properties> { pub struct WebsysRenderer {
internal_dom: VirtualDom<T>, internal_dom: VirtualDom,
} }
/// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally. /// Implement VirtualDom with no props for components that initialize their state internal to the VDom rather than externally.
impl WebsysRenderer<()> { impl WebsysRenderer {
/// Create a new instance of the Dioxus Virtual Dom with no properties for the root component. /// Create a new instance of the Dioxus Virtual Dom with no properties for the root component.
/// ///
/// This means that the root component must either consumes its own context, or statics are used to generate the page. /// This means that the root component must either consumes its own context, or statics are used to generate the page.
@@ -44,18 +44,18 @@ impl WebsysRenderer<()> {
} }
} }
impl<T: Properties + 'static> WebsysRenderer<T> { impl WebsysRenderer {
/// Create a new text-renderer instance from a functional component root. /// Create a new text-renderer instance from a functional component root.
/// Automatically progresses the creation of the VNode tree to completion. /// Automatically progresses the creation of the VNode tree to completion.
/// ///
/// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom` /// A VDom is automatically created. If you want more granular control of the VDom, use `from_vdom`
pub fn new_with_props(root: FC<T>, root_props: T) -> Self { pub fn new_with_props<T: Properties + 'static>(root: FC<T>, root_props: T) -> Self {
Self::from_vdom(VirtualDom::new_with_props(root, root_props)) Self::from_vdom(VirtualDom::new_with_props(root, root_props))
} }
/// Create a new text renderer from an existing Virtual DOM. /// Create a new text renderer from an existing Virtual DOM.
/// This will progress the existing VDom's events to completion. /// This will progress the existing VDom's events to completion.
pub fn from_vdom(dom: VirtualDom<T>) -> Self { pub fn from_vdom(dom: VirtualDom) -> Self {
Self { internal_dom: dom } Self { internal_dom: dom }
} }
@@ -76,7 +76,7 @@ impl<T: Properties + 'static> WebsysRenderer<T> {
// 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 f = 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
@@ -108,6 +108,6 @@ impl<T: Properties + 'static> WebsysRenderer<T> {
/// For any listeners in the tree, attach the sender closure. /// 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 /// 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<P: Properties>(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom<P>) {} fn attach_listeners(sender: &UnboundedSender<EventTrigger>, dom: &VirtualDom) {}
fn render_diffs() {} fn render_diffs() {}