wip: overhaul event system

This commit is contained in:
Jonathan Kelley committed 2022-11-15 16:05:22 -08:00
1 parent c44bd11fe5
commit 47d0f51e00
48 files changed
+3175 -2597

No files matched your search

+1
View File
@@ -1,5 +1,6 @@
use crate::{nodes::VNode, virtual_dom::VirtualDom};
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
pub struct ElementId(pub usize);
+20 -4
View File
@@ -87,10 +87,14 @@ impl VirtualDom {
value,
id,
}),
AttributeValue::Bool(value) => mutations.push(SetBoolAttribute {
name: attribute.name,
value,
id,
}),
AttributeValue::Listener(_) => todo!("create listener attributes"),
AttributeValue::Float(_) => todo!(),
AttributeValue::Int(_) => todo!(),
AttributeValue::Bool(_) => todo!(),
AttributeValue::Any(_) => todo!(),
AttributeValue::None => todo!(),
}
@@ -138,11 +142,13 @@ impl VirtualDom {
TemplateNode::DynamicText { .. } => mutations.push(CreateText {
value: "placeholder",
}),
TemplateNode::Element {
attrs,
children,
namespace,
tag,
inner_opt,
} => {
let id = self.next_element(template);
@@ -159,6 +165,10 @@ impl VirtualDom {
_ => None,
}));
if children.is_empty() && inner_opt {
return;
}
children
.into_iter()
.for_each(|child| self.create_static_node(mutations, template, child));
@@ -176,7 +186,7 @@ impl VirtualDom {
idx: usize,
) -> usize {
match &node {
DynamicNode::Text { id, value } => {
DynamicNode::Text { id, value, inner } => {
let new_id = self.next_element(template);
id.set(new_id);
mutations.push(HydrateText {
@@ -188,11 +198,17 @@ impl VirtualDom {
}
DynamicNode::Component {
props, placeholder, ..
props,
placeholder,
scope: scope_slot,
..
} => {
let scope = self
.new_scope(unsafe { std::mem::transmute(props.get()) })
.id;
scope_slot.set(Some(scope));
let return_nodes = unsafe { self.run_scope(scope).extend_lifetime_ref() };
match return_nodes {
@@ -247,7 +263,7 @@ impl VirtualDom {
}
}
DynamicNode::Fragment(children) => children
DynamicNode::Fragment { nodes, .. } => nodes
.iter()
.fold(0, |acc, child| acc + self.create(mutations, child)),
+2 -2
View File
@@ -177,7 +177,7 @@ impl<'b> VirtualDom {
todo!()
},
(DynamicNode::Text { id: lid, value: lvalue }, DynamicNode::Text { id: rid, value: rvalue }) => {
(DynamicNode::Text { id: lid, value: lvalue, .. }, DynamicNode::Text { id: rid, value: rvalue, .. }) => {
rid.set(lid.get());
if lvalue != rvalue {
muts.push(Mutation::SetText {
@@ -196,7 +196,7 @@ impl<'b> VirtualDom {
(DynamicNode::Placeholder(_), _) => todo!(),
(DynamicNode::Fragment (l), DynamicNode::Fragment (r)) => {
(DynamicNode::Fragment { nodes: lnodes, ..}, DynamicNode::Fragment { nodes: rnodes, ..}) => {
// match (old, new) {
+61 -47
View File
@@ -1,50 +1,5 @@
use crate::{arena::ElementId, virtual_dom::VirtualDom, Attribute, AttributeValue};
use std::cell::Cell;
/// User Events are events that are shuttled from the renderer into the [`VirtualDom`] through the scheduler channel.
///
/// These events will be passed to the appropriate Element given by `mounted_dom_id` and then bubbled up through the tree
/// where each listener is checked and fired if the event name matches.
///
/// It is the expectation that the event name matches the corresponding event listener, otherwise Dioxus will panic in
/// attempting to downcast the event data.
///
/// Because Event Data is sent across threads, it must be `Send + Sync`. We are hoping to lift the `Sync` restriction but
/// `Send` will not be lifted. The entire `UserEvent` must also be `Send + Sync` due to its use in the scheduler channel.
///
/// # Example
/// ```rust, ignore
/// fn App(cx: Scope) -> Element {
/// render!(div {
/// onclick: move |_| println!("Clicked!")
/// })
/// }
///
/// let mut dom = VirtualDom::new(App);
/// let mut scheduler = dom.get_scheduler_channel();
/// scheduler.unbounded_send(SchedulerMsg::UiEvent(
/// UserEvent {
/// scope_id: None,
/// priority: EventPriority::Medium,
/// name: "click",
/// element: Some(ElementId(0)),
/// data: Arc::new(ClickEvent { .. })
/// }
/// )).unwrap();
/// ```
#[derive(Debug)]
pub struct UiEvent<T> {
/// The priority of the event to be scheduled around ongoing work
pub priority: EventPriority,
/// The optional real node associated with the trigger
pub element: ElementId,
/// The event type IE "onclick" or "onmouseover"
pub name: &'static str,
pub bubble: Cell<bool>,
pub event: T,
}
use bumpalo::boxed::Box as BumpBox;
use std::{any::Any, cell::RefCell};
/// Priority of Event Triggers.
///
@@ -108,6 +63,65 @@ pub fn is_path_ascendant(small: &[u8], big: &[u8]) -> bool {
small == &big[..small.len()]
}
pub type InternalHandler<'bump> = &'bump RefCell<Option<InternalListenerCallback<'bump>>>;
type InternalListenerCallback<'bump> = BumpBox<'bump, dyn FnMut(&'bump dyn Any) + 'bump>;
type ExternalListenerCallback<'bump, T> = BumpBox<'bump, dyn FnMut(T) + 'bump>;
/// The callback type generated by the `rsx!` macro when an `on` field is specified for components.
///
/// This makes it possible to pass `move |evt| {}` style closures into components as property fields.
///
///
/// # Example
///
/// ```rust, ignore
///
/// rsx!{
/// MyComponent { onclick: move |evt| log::info!("clicked"), }
/// }
///
/// #[derive(Props)]
/// struct MyProps<'a> {
/// onclick: EventHandler<'a, MouseEvent>,
/// }
///
/// fn MyComponent(cx: Scope<'a, MyProps<'a>>) -> Element {
/// cx.render(rsx!{
/// button {
/// onclick: move |evt| cx.props.onclick.call(evt),
/// }
/// })
/// }
///
/// ```
pub struct EventHandler<'bump, T = ()> {
/// The (optional) callback that the user specified
/// Uses a `RefCell` to allow for interior mutability, and FnMut closures.
pub callback: RefCell<Option<ExternalListenerCallback<'bump, T>>>,
}
impl<'a, T> Default for EventHandler<'a, T> {
fn default() -> Self {
Self {
callback: RefCell::new(None),
}
}
}
impl<T> EventHandler<'_, T> {
/// Call this event handler with the appropriate event type
pub fn call(&self, event: T) {
if let Some(callback) = self.callback.borrow_mut().as_mut() {
callback(event);
}
}
/// Forcibly drop the internal handler callback, releasing memory
pub fn release(&self) {
self.callback.replace(None);
}
}
#[test]
fn matches_slice() {
let left = &[1, 2, 3];
+101 -33
View File
@@ -18,6 +18,7 @@ impl ScopeState {
DynamicNode::Text {
id: Cell::new(ElementId(0)),
value: text,
inner: false,
}
}
@@ -37,27 +38,32 @@ impl ScopeState {
}
}
pub fn fragment_from_iter<'a, I, F: IntoVnode<'a, I>>(
pub fn fragment_from_iter<'a, 'c, I, J>(
&'a self,
it: impl IntoIterator<Item = F>,
node_iter: impl IntoVNode<'a, I, J> + 'c,
) -> DynamicNode {
let mut bump_vec = bumpalo::vec![in self.bump();];
node_iter.into_vnode(self)
for item in it {
bump_vec.push(item.into_dynamic_node(self));
}
// let mut bump_vec = bumpalo::vec![in self.bump();];
match bump_vec.len() {
0 => DynamicNode::Placeholder(Cell::new(ElementId(0))),
_ => DynamicNode::Fragment(bump_vec.into_bump_slice()),
}
// for item in it {
// bump_vec.push(item.into_vnode(self));
// }
// match bump_vec.len() {
// 0 => DynamicNode::Placeholder(Cell::new(ElementId(0))),
// _ => DynamicNode::Fragment {
// inner: false,
// nodes: bump_vec.into_bump_slice(),
// },
// }
}
/// Create a new [`Attribute`]
pub fn attr<'a>(
&'a self,
name: &'static str,
val: impl IntoAttributeValue<'a>,
value: impl IntoAttributeValue<'a>,
namespace: Option<&'static str>,
volatile: bool,
) -> Attribute<'a> {
@@ -65,7 +71,7 @@ impl ScopeState {
name,
namespace,
volatile,
value: val.into_value(self.bump()),
value: value.into_value(self.bump()),
mounted_element: Cell::new(ElementId(0)),
}
}
@@ -97,6 +103,7 @@ impl ScopeState {
static_props: P::IS_STATIC,
props: Cell::new(detached_dyn),
placeholder: Cell::new(None),
scope: Cell::new(None),
}
}
}
@@ -137,39 +144,100 @@ impl<'a> RenderReturn<'a> {
}
}
pub trait IntoVnode<'a, A = ()> {
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a>;
pub trait IntoVNode<'a, A = (), J = ()> {
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a>;
}
impl<'a, 'b> IntoVnode<'a> for LazyNodes<'a, 'b> {
fn into_dynamic_node(self, cx: &'a ScopeState) -> VNode<'a> {
self.call(cx)
impl<'a, 'b> IntoVNode<'a> for VNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
todo!()
// self
}
}
impl<'a, 'b> IntoVnode<'a> for VNode<'a> {
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
self
impl<'a, 'b> IntoVNode<'a> for LazyNodes<'a, 'b> {
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
todo!()
// self.call(cx)
}
}
impl<'a, 'b> IntoVnode<'a> for Option<VNode<'a>> {
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
self.expect("please allow optional nodes in")
impl<'b> IntoVNode<'_> for &'b str {
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
// cx.text(format_args!("{}", self))
todo!()
}
}
impl<'a, 'b> IntoVnode<'a> for &'a VNode<'a> {
fn into_dynamic_node(self, _cx: &'a ScopeState) -> VNode<'a> {
VNode {
node_id: self.node_id.clone(),
parent: self.parent,
template: self.template,
root_ids: self.root_ids,
key: self.key,
dynamic_nodes: self.dynamic_nodes,
dynamic_attrs: self.dynamic_attrs,
impl IntoVNode<'_> for String {
fn into_vnode(self, cx: &ScopeState) -> DynamicNode {
// cx.text(format_args!("{}", self))
todo!()
}
}
impl<'b> IntoVNode<'b> for Arguments<'_> {
fn into_vnode(self, cx: &'b ScopeState) -> DynamicNode<'b> {
// cx.text(self)
todo!()
}
}
impl<'a, 'b> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
todo!()
// VNode {
// node_id: self.node_id.clone(),
// parent: self.parent,
// template: self.template,
// root_ids: self.root_ids,
// key: self.key,
// dynamic_nodes: self.dynamic_nodes,
// dynamic_attrs: self.dynamic_attrs,
// }
}
}
// Note that we're using the E as a generic but this is never crafted anyways.
pub struct FromNodeIterator;
impl<'a, T, I, E> IntoVNode<'a, FromNodeIterator, E> for T
where
T: IntoIterator<Item = I>,
I: IntoVNode<'a, E>,
{
fn into_vnode(self, cx: &'a ScopeState) -> DynamicNode<'a> {
let mut nodes = bumpalo::collections::Vec::new_in(cx.bump());
for node in self {
nodes.push(node.into_vnode(cx));
}
let children = nodes.into_bump_slice();
// if cfg!(debug_assertions) && children.len() > 1 && children.last().unwrap().key().is_none()
// {
// let bt = backtrace::Backtrace::new();
// let bt = "no backtrace available";
// // todo: make the backtrace prettier or remove it altogether
// log::error!(
// r#"
// Warning: Each child in an array or iterator should have a unique "key" prop.
// Not providing a key will lead to poor performance with lists.
// See docs.rs/dioxus for more information.
// -------------
// {:?}
// "#,
// bt
// );
// }
todo!()
// VNode::Fragment(cx.bump.alloc(VFragment {
// children,
// placeholder: Default::default(),
// key: None,
// }))
}
}
+2 -1
View File
@@ -25,8 +25,9 @@ impl<'b> VirtualDom {
todo!()
}
DynamicNode::Fragment(children) => {}
DynamicNode::Fragment { inner, nodes } => {}
DynamicNode::Placeholder(_) => todo!(),
_ => todo!(),
}
}
}
+3 -4
View File
@@ -92,7 +92,6 @@ pub use crate::innerlude::{
Template,
TemplateAttribute,
TemplateNode,
UiEvent,
VNode,
VirtualDom,
};
@@ -102,9 +101,9 @@ pub use crate::innerlude::{
/// This includes types like [`Scope`], [`Element`], and [`Component`].
pub mod prelude {
pub use crate::innerlude::{
fc_to_builder, Element, EventPriority, Fragment, LazyNodes, NodeFactory, Properties, Scope,
ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode, UiEvent,
VNode, VirtualDom,
fc_to_builder, Element, EventHandler, EventPriority, Fragment, LazyNodes, NodeFactory,
Properties, Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute,
TemplateNode, VNode, VirtualDom,
};
}
+11
View File
@@ -35,6 +35,7 @@ impl std::ops::DerefMut for Mutations<'_> {
each subtree has its own numbering scheme
*/
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Debug, PartialEq, Eq)]
pub enum Mutation<'a> {
SetAttribute {
@@ -43,6 +44,12 @@ pub enum Mutation<'a> {
id: ElementId,
},
SetBoolAttribute {
name: &'a str,
value: bool,
id: ElementId,
},
LoadTemplate {
name: &'static str,
index: usize,
@@ -86,6 +93,10 @@ pub enum Mutation<'a> {
id: ElementId,
},
SetInnerText {
value: &'a str,
},
CreateText {
value: &'a str,
},
+60 -3
View File
@@ -1,4 +1,4 @@
use crate::{any_props::AnyProps, arena::ElementId, ScopeState};
use crate::{any_props::AnyProps, arena::ElementId, ScopeId, ScopeState};
use std::{
any::{Any, TypeId},
cell::{Cell, RefCell},
@@ -15,7 +15,7 @@ pub struct VNode<'a> {
pub key: Option<&'a str>,
// When rendered, this template will be linked to its parent manually
pub parent: Option<(*mut VNode<'static>, usize)>,
pub parent: Option<*const DynamicNode<'a>>,
pub template: Template<'static>,
@@ -98,6 +98,7 @@ pub enum TemplateNode<'a> {
namespace: Option<&'a str>,
attrs: &'a [TemplateAttribute<'a>],
children: &'a [TemplateNode<'a>],
inner_opt: bool,
},
Text(&'a str),
Dynamic(usize),
@@ -110,12 +111,17 @@ pub enum DynamicNode<'a> {
static_props: bool,
props: Cell<*mut dyn AnyProps<'a>>,
placeholder: Cell<Option<ElementId>>,
scope: Cell<Option<ScopeId>>,
},
Text {
id: Cell<ElementId>,
value: &'a str,
inner: bool,
},
Fragment {
nodes: &'a [VNode<'a>],
inner: bool,
},
Fragment(&'a [VNode<'a>]),
Placeholder(Cell<ElementId>),
}
@@ -215,3 +221,54 @@ fn what_are_the_sizes() {
dbg!(std::mem::size_of::<Template>());
dbg!(std::mem::size_of::<TemplateNode>());
}
/*
SSR includes data-id which allows O(1) hydration
we read the edit stream dn then we can just rehydare
ideas:
- IDs for lookup
- use edit stream to hydrate
- write comments to dom that specify size of children
IDs for lookups
- adds noise to generated html
- doesnt work for text nodes
- suspense could cause ordering to be weird
Names for lookups:
- label each root or something with the template name
- label each dynamic node with a path
- noisy too
- allows reverse lookups
Ideal:
- no noise in the dom
- fast, ideally O(1)
- able to pick apart text nodes that get merged during SSR
--> render vdom
--> traverse vdom and real dom simultaneously
IE
div {
div {
div {
"thing"
}
}
}
*/
+1 -1
View File
@@ -17,7 +17,7 @@ use std::{
};
impl VirtualDom {
pub fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> &mut ScopeState {
pub(super) fn new_scope(&mut self, props: *mut dyn AnyProps<'static>) -> &mut ScopeState {
let parent = self.acquire_current_scope_raw();
let container = self.acquire_current_container();
let entry = self.scopes.vacant_entry();
+56 -27
View File
@@ -4,18 +4,21 @@ use crate::{
arena::ElementPath,
diff::DirtyScope,
factory::RenderReturn,
innerlude::{is_path_ascendant, Mutations, Scheduler, SchedulerMsg},
innerlude::{Mutations, Scheduler, SchedulerMsg},
mutations::Mutation,
nodes::{Template, TemplateId},
scheduler::{SuspenseBoundary, SuspenseId},
scopes::{ScopeId, ScopeState},
Attribute, AttributeValue, Element, Scope, SuspenseContext, UiEvent,
Attribute, AttributeValue, Element, EventPriority, Scope, SuspenseContext,
};
use futures_util::{pin_mut, FutureExt, StreamExt};
use slab::Slab;
use std::collections::{BTreeSet, HashMap};
use std::future::Future;
use std::rc::Rc;
use std::{
any::Any,
collections::{BTreeSet, HashMap},
};
/// A virtual node system that progresses user events and diffs UI trees.
///
@@ -275,42 +278,68 @@ impl VirtualDom {
!self.scheduler.leaves.borrow().is_empty()
}
/// Returns None if no element could be found
pub fn handle_event<T: 'static>(&mut self, event: &UiEvent<T>) -> Option<()> {
let path = self.elements.get(event.element.0)?;
/// Call a listener inside the VirtualDom with data from outside the VirtualDom.
///
/// This method will identify the appropriate element
///
///
///
///
///
///
///
pub fn handle_event(
&mut self,
name: &str,
event: &dyn Any,
element: ElementId,
bubbles: bool,
priority: EventPriority,
) -> Option<()> {
/*
- click registers
- walk upwards until first element with onclick listener
- get template from ElementID
- break out of wait loop
- send event to virtualdom
*/
let location = unsafe { &mut *path.template }
let path = &self.elements[element.0];
let template = unsafe { &*path.template };
let dynamic = &template.dynamic_nodes[path.element];
let location = template
.dynamic_attrs
.iter()
.position(|attr| attr.mounted_element.get() == event.element)?;
.position(|attr| attr.mounted_element.get() == element)?;
let mut index = Some((path.template, location));
// let mut index = Some((path.template, location));
let mut listeners = Vec::<&Attribute>::new();
while let Some((raw_parent, dyn_index)) = index {
let parent = unsafe { &mut *raw_parent };
let path = parent.template.node_paths[dyn_index];
// while let Some((raw_parent, dyn_index)) = index {
// let parent = unsafe { &mut *raw_parent };
// let path = parent.template.node_paths[dyn_index];
listeners.extend(
parent
.dynamic_attrs
.iter()
.enumerate()
.filter_map(|(idx, attr)| {
match is_path_ascendant(parent.template.node_paths[idx], path) {
true if attr.name == event.name => Some(attr),
_ => None,
}
}),
);
// listeners.extend(
// parent
// .dynamic_attrs
// .iter()
// .enumerate()
// .filter_map(|(idx, attr)| {
// match is_path_ascendant(parent.template.node_paths[idx], path) {
// true if attr.name == event.name => Some(attr),
// _ => None,
// }
// }),
// );
index = parent.parent;
}
// index = parent.parent;
// }
for listener in listeners {
if let AttributeValue::Listener(listener) = &listener.value {
(listener.borrow_mut())(&event.event)
(listener.borrow_mut())(event)
}
}