wip: more work on suspense and documentation

This commit is contained in:
Jonathan Kelley committed 2021-07-15 03:38:09 -04:00
1 parent 4837d8e741
commit 37ed4bed8c
14 files changed
+240 -192

No files matched your search

+5 -5
View File
@@ -27,7 +27,7 @@ impl SharedArena {
/// THIS METHOD IS CURRENTLY UNSAFE /// THIS METHOD IS CURRENTLY UNSAFE
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
pub fn try_get(&self, idx: ScopeIdx) -> Result<&Scope> { pub fn try_get(&self, idx: ScopeId) -> Result<&Scope> {
let inner = unsafe { &*self.components.get() }; let inner = unsafe { &*self.components.get() };
let scope = inner.get(idx); let scope = inner.get(idx);
scope.ok_or_else(|| Error::FatalInternal("Scope not found")) scope.ok_or_else(|| Error::FatalInternal("Scope not found"))
@@ -35,7 +35,7 @@ impl SharedArena {
/// THIS METHOD IS CURRENTLY UNSAFE /// THIS METHOD IS CURRENTLY UNSAFE
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS /// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
pub fn try_get_mut(&self, idx: ScopeIdx) -> Result<&mut Scope> { pub fn try_get_mut(&self, idx: ScopeId) -> Result<&mut Scope> {
let inner = unsafe { &mut *self.components.get() }; let inner = unsafe { &mut *self.components.get() };
let scope = inner.get_mut(idx); let scope = inner.get_mut(idx);
scope.ok_or_else(|| Error::FatalInternal("Scope not found")) scope.ok_or_else(|| Error::FatalInternal("Scope not found"))
@@ -59,7 +59,7 @@ impl SharedArena {
pub fn with_scope<'b, O: 'static>( pub fn with_scope<'b, O: 'static>(
&'b self, &'b self,
_id: ScopeIdx, _id: ScopeId,
_f: impl FnOnce(&'b mut Scope) -> O, _f: impl FnOnce(&'b mut Scope) -> O,
) -> Result<O> { ) -> Result<O> {
todo!() todo!()
@@ -69,13 +69,13 @@ impl SharedArena {
// this is useful for merging lifetimes // this is useful for merging lifetimes
pub fn with_scope_vnode<'b>( pub fn with_scope_vnode<'b>(
&self, &self,
_id: ScopeIdx, _id: ScopeId,
_f: impl FnOnce(&mut Scope) -> &VNode<'b>, _f: impl FnOnce(&mut Scope) -> &VNode<'b>,
) -> Result<&VNode<'b>> { ) -> Result<&VNode<'b>> {
todo!() todo!()
} }
pub fn try_remove(&self, id: ScopeIdx) -> Result<Scope> { pub fn try_remove(&self, id: ScopeId) -> Result<Scope> {
let inner = unsafe { &mut *self.components.get() }; let inner = unsafe { &mut *self.components.get() };
inner inner
.remove(id) .remove(id)
+54 -17
View File
@@ -1,6 +1,5 @@
use crate::innerlude::*; use crate::innerlude::*;
use bumpalo::Bump;
use futures_util::FutureExt; use futures_util::FutureExt;
use std::any::Any; use std::any::Any;
@@ -12,7 +11,6 @@ use std::{
cell::RefCell, cell::RefCell,
future::Future, future::Future,
ops::Deref, ops::Deref,
pin::Pin,
rc::{Rc, Weak}, rc::{Rc, Weak},
}; };
@@ -34,13 +32,20 @@ use std::{
/// } /// }
/// } /// }
/// ``` /// ```
// todo: force lifetime of source into T as a valid lifetime too ///
// it's definitely possible, just needs some more messing around /// ## Available Methods:
/// - render
/// - use_hook
/// - use_task
/// - use_suspense
/// - submit_task
/// - children
/// - use_effect
///
pub struct Context<'src, T> { pub struct Context<'src, T> {
pub props: &'src T, pub props: &'src T,
pub scope: &'src Scope, pub scope: &'src Scope,
} }
// pub type PinnedTask = Pin<Box<dyn Future<Output = ()>>>;
impl<'src, T> Copy for Context<'src, T> {} impl<'src, T> Copy for Context<'src, T> {}
impl<'src, T> Clone for Context<'src, T> { impl<'src, T> Clone for Context<'src, T> {
@@ -52,9 +57,10 @@ impl<'src, T> Clone for Context<'src, T> {
} }
} }
// We currently deref to props, but it might make more sense to deref to Scope?
// This allows for code that takes cx.xyz instead of cx.props.xyz
impl<'a, T> Deref for Context<'a, T> { impl<'a, T> Deref for Context<'a, T> {
type Target = &'a T; type Target = &'a T;
fn deref(&self) -> &Self::Target { fn deref(&self) -> &Self::Target {
&self.props &self.props
} }
@@ -62,14 +68,42 @@ impl<'a, T> Deref for Context<'a, T> {
impl<'src, P> Context<'src, P> { impl<'src, P> Context<'src, P> {
/// Access the children elements passed into the component /// Access the children elements passed into the component
///
/// This enables patterns where a component is passed children from its parent.
///
/// ## Details
///
/// Unlike React, Dioxus allows *only* lists of children to be passed from parent to child - not arbitrary functions
/// or classes. If you want to generate nodes instead of accepting them as a list, consider declaring a closure
/// on the props that takes Context.
///
/// If a parent passes children into a component, the child will always re-render when the parent re-renders. In other
/// words, a component cannot be automatically memoized if it borrows nodes from its parent, even if the component's
/// props are valid for the static lifetime.
///
/// ## Example
///
/// ```rust
/// const App: FC<()> = |cx| {
/// cx.render(rsx!{
/// CustomCard {
/// h1 {}
/// p {}
/// }
/// })
/// }
///
/// const CustomCard: FC<()> = |cx| {
/// cx.render(rsx!{
/// div {
/// h1 {"Title card"}
/// {cx.children()}
/// }
/// })
/// }
/// ```
pub fn children(&self) -> &'src [VNode<'src>] { pub fn children(&self) -> &'src [VNode<'src>] {
// We're re-casting the nodes back out self.scope.child_nodes
// They don't really have a static lifetime
unsafe {
let scope = self.scope;
let nodes = scope.child_nodes;
nodes
}
} }
/// Create a subscription that schedules a future render for the reference component /// Create a subscription that schedules a future render for the reference component
@@ -227,7 +261,10 @@ Any function prefixed with "use" should not be called conditionally.
let ty = TypeId::of::<T>(); let ty = TypeId::of::<T>();
while let Some(inner) = scope { while let Some(inner) = scope {
log::debug!("Searching {:#?} for valid shared_context", inner.arena_idx); log::debug!(
"Searching {:#?} for valid shared_context",
inner.our_arena_idx
);
let shared_contexts = inner.shared_contexts.borrow(); let shared_contexts = inner.shared_contexts.borrow();
if let Some(shared_cx) = shared_contexts.get(&ty) { if let Some(shared_cx) = shared_contexts.get(&ty) {
@@ -241,7 +278,7 @@ Any function prefixed with "use" should not be called conditionally.
hook.par = Some(rc); hook.par = Some(rc);
return Ok(hook.par.as_ref().unwrap()); return Ok(hook.par.as_ref().unwrap());
} else { } else {
match inner.parent { match inner.parent_idx {
Some(parent_id) => { Some(parent_id) => {
let parent = inner let parent = inner
.arena_link .arena_link
@@ -324,7 +361,7 @@ Any function prefixed with "use" should not be called conditionally.
let slot = task_dump.clone(); let slot = task_dump.clone();
let update = self.schedule_update(); let update = self.schedule_update();
let originator = self.scope.arena_idx.clone(); let originator = self.scope.our_arena_idx.clone();
self.submit_task(Box::pin(task_fut.then(move |output| async move { self.submit_task(Box::pin(task_fut.then(move |output| async move {
*slot.as_ref().borrow_mut() = Some(output); *slot.as_ref().borrow_mut() = Some(output);
@@ -411,7 +448,7 @@ impl<'src, P> Context<'src, P> {
} }
}); });
let originator = self.scope.arena_idx.clone(); let originator = self.scope.our_arena_idx.clone();
let task_fut = task_initializer(); let task_fut = task_initializer();
let domnode = dom_node_id.clone(); let domnode = dom_node_id.clone();
+5 -5
View File
@@ -75,10 +75,10 @@ pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
pub edits: DomEditor<'real, 'bump>, pub edits: DomEditor<'real, 'bump>,
pub components: &'bump SharedArena, pub components: &'bump SharedArena,
pub task_queue: &'bump TaskQueue, pub task_queue: &'bump TaskQueue,
pub cur_idx: ScopeIdx, pub cur_idx: ScopeId,
pub diffed: FxHashSet<ScopeIdx>, pub diffed: FxHashSet<ScopeId>,
pub event_queue: EventQueue, pub event_queue: EventQueue,
pub seen_nodes: FxHashSet<ScopeIdx>, pub seen_nodes: FxHashSet<ScopeId>,
} }
impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom> impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
@@ -89,7 +89,7 @@ where
edits: &'real mut Vec<DomEdit<'bump>>, edits: &'real mut Vec<DomEdit<'bump>>,
dom: &'real mut Dom, dom: &'real mut Dom,
components: &'bump SharedArena, components: &'bump SharedArena,
cur_idx: ScopeIdx, cur_idx: ScopeId,
event_queue: EventQueue, event_queue: EventQueue,
task_queue: &'bump TaskQueue, task_queue: &'bump TaskQueue,
) -> Self { ) -> Self {
@@ -488,7 +488,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
/// ///
/// Calling this will run the destuctors on all hooks in the tree. /// Calling this will run the destuctors on all hooks in the tree.
/// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered. /// It will also add the destroyed nodes to the `seen_nodes` cache to prevent them from being renderered.
fn destroy_scopes(&mut self, old_scope: ScopeIdx) { fn destroy_scopes(&mut self, old_scope: ScopeId) {
let mut nodes_to_delete = vec![old_scope]; let mut nodes_to_delete = vec![old_scope];
let mut scopes_to_explore = vec![old_scope]; let mut scopes_to_explore = vec![old_scope];
+26 -11
View File
@@ -1,5 +1,3 @@
//! Serialization
//! -------------
//! //!
//! //!
//! //!
@@ -7,7 +5,7 @@
//! //!
//! //!
use crate::{innerlude::ScopeIdx, RealDomNode}; use crate::{innerlude::ScopeId, RealDomNode};
/// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes. /// The `DomEditor` provides an imperative interface for the Diffing algorithm to plan out its changes.
/// ///
@@ -18,6 +16,7 @@ use crate::{innerlude::ScopeIdx, RealDomNode};
pub struct DomEditor<'real, 'bump> { pub struct DomEditor<'real, 'bump> {
pub edits: &'real mut Vec<DomEdit<'bump>>, pub edits: &'real mut Vec<DomEdit<'bump>>,
} }
use DomEdit::*; use DomEdit::*;
impl<'real, 'bump> DomEditor<'real, 'bump> { impl<'real, 'bump> DomEditor<'real, 'bump> {
pub fn new(edits: &'real mut Vec<DomEdit<'bump>>) -> Self { pub fn new(edits: &'real mut Vec<DomEdit<'bump>>) -> Self {
@@ -29,35 +28,44 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
let id = root.as_u64(); let id = root.as_u64();
self.edits.push(PushRoot { id }); self.edits.push(PushRoot { id });
} }
#[inline]
pub(crate) fn pop(&mut self) { pub(crate) fn pop(&mut self) {
self.edits.push(PopRoot {}); self.edits.push(PopRoot {});
} }
// Add Nodes to the dom // Add Nodes to the dom
// add m nodes from the stack // add m nodes from the stack
#[inline]
pub(crate) fn append_children(&mut self, many: u32) { pub(crate) fn append_children(&mut self, many: u32) {
self.edits.push(AppendChildren { many }); self.edits.push(AppendChildren { many });
} }
// replace the n-m node on the stack with the m nodes // replace the n-m node on the stack with the m nodes
// ends with the last element of the chain on the top of the stack // ends with the last element of the chain on the top of the stack
#[inline]
pub(crate) fn replace_with(&mut self, many: u32) { pub(crate) fn replace_with(&mut self, many: u32) {
self.edits.push(ReplaceWith { many }); self.edits.push(ReplaceWith { many });
} }
// Remove Nodesfrom the dom // Remove Nodesfrom the dom
#[inline]
pub(crate) fn remove(&mut self) { pub(crate) fn remove(&mut self) {
self.edits.push(Remove); self.edits.push(Remove);
} }
#[inline]
pub(crate) fn remove_all_children(&mut self) { pub(crate) fn remove_all_children(&mut self) {
self.edits.push(RemoveAllChildren); self.edits.push(RemoveAllChildren);
} }
// Create // Create
#[inline]
pub(crate) fn create_text_node(&mut self, text: &'bump str, id: RealDomNode) { pub(crate) fn create_text_node(&mut self, text: &'bump str, id: RealDomNode) {
let id = id.as_u64(); let id = id.as_u64();
self.edits.push(CreateTextNode { text, id }); self.edits.push(CreateTextNode { text, id });
} }
pub(crate) fn create_element( pub(crate) fn create_element(
&mut self, &mut self,
tag: &'static str, tag: &'static str,
@@ -81,25 +89,30 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
pub(crate) fn new_event_listener( pub(crate) fn new_event_listener(
&mut self, &mut self,
event: &'static str, event: &'static str,
scope: ScopeIdx, scope: ScopeId,
element_id: usize, element_id: usize,
realnode: RealDomNode, realnode: RealDomNode,
) { ) {
self.edits.push(NewEventListener { self.edits.push(NewEventListener {
scope, scope,
event, event_name: event,
idx: element_id, element_id,
node: realnode.as_u64(), mounted_node_id: realnode.as_u64(),
}); });
} }
#[inline]
pub(crate) fn remove_event_listener(&mut self, event: &'static str) { pub(crate) fn remove_event_listener(&mut self, event: &'static str) {
self.edits.push(RemoveEventListener { event }); self.edits.push(RemoveEventListener { event });
} }
// modify // modify
#[inline]
pub(crate) fn set_text(&mut self, text: &'bump str) { pub(crate) fn set_text(&mut self, text: &'bump str) {
self.edits.push(SetText { text }); self.edits.push(SetText { text });
} }
#[inline]
pub(crate) fn set_attribute( pub(crate) fn set_attribute(
&mut self, &mut self,
field: &'static str, field: &'static str,
@@ -108,6 +121,8 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
) { ) {
self.edits.push(SetAttribute { field, value, ns }); self.edits.push(SetAttribute { field, value, ns });
} }
#[inline]
pub(crate) fn remove_attribute(&mut self, name: &'static str) { pub(crate) fn remove_attribute(&mut self, name: &'static str) {
self.edits.push(RemoveAttribute { name }); self.edits.push(RemoveAttribute { name });
} }
@@ -151,10 +166,10 @@ pub enum DomEdit<'bump> {
id: u64, id: u64,
}, },
NewEventListener { NewEventListener {
event: &'static str, event_name: &'static str,
scope: ScopeIdx, scope: ScopeId,
node: u64, mounted_node_id: u64,
idx: usize, element_id: usize,
}, },
RemoveEventListener { RemoveEventListener {
event: &'static str, event: &'static str,
+6 -9
View File
@@ -4,17 +4,14 @@
//! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might //! 3rd party renderers are responsible for converting their native events into these virtual event types. Events might
//! be heavy or need to interact through FFI, so the events themselves are designed to be lazy. //! be heavy or need to interact through FFI, so the events themselves are designed to be lazy.
use std::{ use std::{cell::Cell, rc::Rc};
cell::{Cell, RefCell},
rc::Rc,
};
use crate::innerlude::{RealDomNode, ScopeIdx}; use crate::innerlude::{RealDomNode, ScopeId};
#[derive(Debug)] #[derive(Debug)]
pub struct EventTrigger { pub struct EventTrigger {
/// The originator of the event trigger /// The originator of the event trigger
pub originator: ScopeIdx, pub originator: ScopeId,
/// The optional real node associated with the trigger /// The optional real node associated with the trigger
pub real_node_id: Option<RealDomNode>, pub real_node_id: Option<RealDomNode>,
@@ -27,7 +24,7 @@ pub struct EventTrigger {
} }
impl EventTrigger { impl EventTrigger {
pub fn new_from_task(originator: ScopeIdx, hook_idx: usize) -> Self { pub fn new_from_task(originator: ScopeId, hook_idx: usize) -> Self {
Self { Self {
originator, originator,
event: VirtualEvent::AsyncEvent { hook_idx }, event: VirtualEvent::AsyncEvent { hook_idx },
@@ -74,7 +71,7 @@ pub enum EventPriority {
impl EventTrigger { impl EventTrigger {
pub fn new( pub fn new(
event: VirtualEvent, event: VirtualEvent,
scope: ScopeIdx, scope: ScopeId,
mounted_dom_id: Option<RealDomNode>, mounted_dom_id: Option<RealDomNode>,
priority: EventPriority, priority: EventPriority,
) -> Self { ) -> Self {
@@ -184,7 +181,7 @@ pub mod on {
Listener { Listener {
event: stringify!($name), event: stringify!($name),
mounted_node: bump.alloc(Cell::new(RealDomNode::empty())), mounted_node: bump.alloc(Cell::new(RealDomNode::empty())),
scope: c.scope_ref.arena_idx, scope: c.scope_ref.our_arena_idx,
callback: bump.alloc(move |evt: VirtualEvent| match evt { callback: bump.alloc(move |evt: VirtualEvent| match evt {
VirtualEvent::$wrapper(event) => callback(event), VirtualEvent::$wrapper(event) => callback(event),
_ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!") _ => unreachable!("Downcasted VirtualEvent to wrong event type - this is an internal bug!")
+2 -2
View File
@@ -1,4 +1,4 @@
#![allow(non_snake_case, dead_code, unused_must_use, unreachable_code)] #![allow(non_snake_case)]
//! Dioxus Core //! Dioxus Core
//! ---------- //! ----------
//! //!
@@ -11,7 +11,7 @@
pub use crate::innerlude::{ pub use crate::innerlude::{
format_args_f, html, rsx, DioxusElement, DomEdit, EventTrigger, LazyNodes, NodeFactory, format_args_f, html, rsx, DioxusElement, DomEdit, EventTrigger, LazyNodes, NodeFactory,
Properties, RealDom, RealDomNode, ScopeIdx, VNode, VNodeKind, VirtualDom, VirtualEvent, FC, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, VirtualEvent, FC,
}; };
pub mod prelude { pub mod prelude {
+64 -42
View File
@@ -5,10 +5,10 @@
use crate::{ use crate::{
events::VirtualEvent, events::VirtualEvent,
innerlude::{Context, Properties, RealDomNode, Scope, ScopeIdx, FC}, innerlude::{Context, Properties, RealDomNode, Scope, ScopeId, FC},
}; };
use std::{ use std::{
cell::{Cell, RefCell}, cell::Cell,
fmt::{Arguments, Debug, Formatter}, fmt::{Arguments, Debug, Formatter},
marker::PhantomData, marker::PhantomData,
rc::Rc, rc::Rc,
@@ -87,7 +87,7 @@ pub struct Attribute<'a> {
pub struct Listener<'bump> { pub struct Listener<'bump> {
/// The type of event to listen for. /// The type of event to listen for.
pub(crate) event: &'static str, pub(crate) event: &'static str,
pub scope: ScopeIdx, pub scope: ScopeId,
pub mounted_node: &'bump Cell<RealDomNode>, pub mounted_node: &'bump Cell<RealDomNode>,
pub(crate) callback: &'bump dyn FnMut(VirtualEvent), pub(crate) callback: &'bump dyn FnMut(VirtualEvent),
} }
@@ -95,7 +95,7 @@ pub struct Listener<'bump> {
/// Virtual Components for custom user-defined components /// Virtual Components for custom user-defined components
/// Only supports the functional syntax /// Only supports the functional syntax
pub struct VComponent<'src> { pub struct VComponent<'src> {
pub ass_scope: Cell<Option<ScopeIdx>>, pub ass_scope: Cell<Option<ScopeId>>,
pub(crate) caller: Rc<dyn Fn(&Scope) -> VNode>, pub(crate) caller: Rc<dyn Fn(&Scope) -> VNode>,
pub(crate) children: &'src [VNode<'src>], pub(crate) children: &'src [VNode<'src>],
pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>, pub(crate) comparator: Option<&'src dyn Fn(&VComponent) -> bool>,
@@ -328,6 +328,26 @@ impl<'a> NodeFactory<'a> {
} }
} }
/// Trait implementations for use in the rsx! and html! macros.
///
/// ## Details
///
/// This section provides convenience methods and trait implementations for converting common structs into a format accepted
/// by the macros.
///
/// All dynamic content in the macros must flow in through `fragment_from_iter`. Everything else must be statically layed out.
/// We pipe basically everything through `fragment_from_iter`, so we expect a very specific type:
/// ```
/// impl IntoIterator<Item = impl IntoVNode<'a>>
/// ```
///
/// As such, all node creation must go through the factory, which is only availble in the component context.
/// These strict requirements make it possible to manage lifetimes and state.
pub trait IntoVNode<'a> {
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>;
}
// For the case where a rendered VNode is passed into the rsx! macro through curly braces
impl<'a> IntoIterator for VNode<'a> { impl<'a> IntoIterator for VNode<'a> {
type Item = VNode<'a>; type Item = VNode<'a>;
type IntoIter = std::iter::Once<Self::Item>; type IntoIter = std::iter::Once<Self::Item>;
@@ -335,25 +355,53 @@ impl<'a> IntoIterator for VNode<'a> {
std::iter::once(self) std::iter::once(self)
} }
} }
// For the case where a rendered VNode is passed into the rsx! macro through curly braces
impl<'a> IntoVNode<'a> for VNode<'a> { impl<'a> IntoVNode<'a> for VNode<'a> {
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
self self
} }
} }
// For the case where a rendered VNode is by reference passed into the rsx! macro through curly braces
// This behavior is designed for the cx.children method where child nodes are passed by reference.
//
// Designed to support indexing
impl<'a> IntoVNode<'a> for &VNode<'a> { impl<'a> IntoVNode<'a> for &VNode<'a> {
fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> { fn into_vnode(self, _: NodeFactory<'a>) -> VNode<'a> {
self.clone() let kind = match &self.kind {
VNodeKind::Element(element) => VNodeKind::Element(element),
VNodeKind::Text(old) => VNodeKind::Text(VText {
text: old.text,
is_static: old.is_static,
}),
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
children: fragment.children,
is_static: fragment.is_static,
}),
VNodeKind::Component(component) => VNodeKind::Component(component),
// todo: it doesn't make much sense to pass in suspended nodes
// I think this is right but I'm not too sure.
VNodeKind::Suspended { node } => VNodeKind::Suspended { node: node.clone() },
};
VNode {
kind,
dom_id: self.dom_id.clone(),
key: self.key.clone(),
}
} }
} }
pub trait IntoVNode<'a> { /// A concrete type provider for closures that build VNode structures.
fn into_vnode(self, cx: NodeFactory<'a>) -> VNode<'a>; ///
} /// This struct wraps lazy structs that build VNode trees Normally, we cannot perform a blanket implementation over
/// closures, but if we wrap the closure in a concrete type, we can maintain separate implementations of IntoVNode.
// Wrap the the node-builder closure in a concrete type. ///
// --- ///
// This is a bit of a hack to implement the IntoVNode trait for closure types. /// ```rust
/// LazyNodes::new(|f| f.element("div", [], [], [] None))
/// ```
pub struct LazyNodes<'a, G> pub struct LazyNodes<'a, G>
where where
G: FnOnce(NodeFactory<'a>) -> VNode<'a>, G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
@@ -374,11 +422,7 @@ where
} }
} }
// Cover the cases where nodes are used by macro. // Our blanket impl
// Likely used directly.
// ---
// let nodes = rsx!{ ... };
// rsx! { {nodes } }
impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G> impl<'a, G> IntoVNode<'a> for LazyNodes<'a, G>
where where
G: FnOnce(NodeFactory<'a>) -> VNode<'a>, G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
@@ -388,7 +432,7 @@ where
} }
} }
// Required because anything that enters brackets in the rsx! macro needs to implement IntoIterator // Our blanket impl
impl<'a, G> IntoIterator for LazyNodes<'a, G> impl<'a, G> IntoIterator for LazyNodes<'a, G>
where where
G: FnOnce(NodeFactory<'a>) -> VNode<'a>, G: FnOnce(NodeFactory<'a>) -> VNode<'a>,
@@ -400,12 +444,14 @@ where
} }
} }
// Conveniently, we also support "null" (nothing) passed in
impl IntoVNode<'_> for () { impl IntoVNode<'_> for () {
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
cx.fragment_from_iter(None as Option<VNode>) cx.fragment_from_iter(None as Option<VNode>)
} }
} }
// Conveniently, we also support "None"
impl IntoVNode<'_> for Option<()> { impl IntoVNode<'_> for Option<()> {
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
cx.fragment_from_iter(None as Option<VNode>) cx.fragment_from_iter(None as Option<VNode>)
@@ -418,30 +464,6 @@ impl Debug for NodeFactory<'_> {
} }
} }
// it's okay to clone because vnodes are just references to places into the bump
impl<'a> Clone for VNode<'a> {
fn clone(&self) -> Self {
let kind = match &self.kind {
VNodeKind::Element(element) => VNodeKind::Element(element),
VNodeKind::Text(old) => VNodeKind::Text(VText {
text: old.text,
is_static: old.is_static,
}),
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
children: fragment.children,
is_static: fragment.is_static,
}),
VNodeKind::Component(component) => VNodeKind::Component(component),
VNodeKind::Suspended { node } => VNodeKind::Suspended { node: node.clone() },
};
VNode {
kind,
dom_id: self.dom_id.clone(),
key: self.key.clone(),
}
}
}
impl Debug for VNode<'_> { impl Debug for VNode<'_> {
fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> { fn fmt(&self, s: &mut Formatter<'_>) -> std::result::Result<(), std::fmt::Error> {
match &self.kind { match &self.kind {
+30 -47
View File
@@ -22,53 +22,36 @@ pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> VNode<'b>;
/// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components. /// Scopes are allocated in a generational arena. As components are mounted/unmounted, they will replace slots of dead components.
/// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently. /// The actual contents of the hooks, though, will be allocated with the standard allocator. These should not allocate as frequently.
pub struct Scope { pub struct Scope {
// The parent's scope ID // Book-keeping about the arena
pub parent: Option<ScopeIdx>, pub(crate) parent_idx: Option<ScopeId>,
pub(crate) descendents: RefCell<HashSet<ScopeId>>,
pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32,
// IDs of children that this scope has created // Nodes
// This enables us to drop the children and their children when this scope is destroyed // an internal, highly efficient storage of vnodes
pub(crate) descendents: RefCell<HashSet<ScopeIdx>>, pub(crate) frames: ActiveFrame,
pub(crate) child_nodes: &'static [VNode<'static>],
pub(crate) caller: Rc<WrappedCaller>,
pub child_nodes: &'static [VNode<'static>], // Listeners
pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
pub(crate) listener_idx: Cell<usize>,
// State
pub(crate) hooks: HookList,
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
// Events
pub(crate) event_channel: Rc<dyn Fn() + 'static>,
// Tasks
pub(crate) task_submitter: TaskSubmitter,
pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
// A reference to the list of components. // A reference to the list of components.
// This lets us traverse the component list whenever we need to access our parent or children. // This lets us traverse the component list whenever we need to access our parent or children.
pub arena_link: SharedArena, pub(crate) arena_link: SharedArena,
pub shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
// Our own ID accessible from the component map
pub arena_idx: ScopeIdx,
pub height: u32,
pub event_channel: Rc<dyn Fn() + 'static>,
pub caller: Rc<WrappedCaller>,
// ==========================
// slightly unsafe stuff
// ==========================
// an internal, highly efficient storage of vnodes
pub frames: ActiveFrame,
// These hooks are actually references into the hook arena
// These two could be combined with "OwningRef" to remove unsafe usage
// or we could dedicate a tiny bump arena just for them
// could also use ourborous
pub hooks: HookList,
pub(crate) listener_idx: Cell<usize>,
// Unsafety:
// - is self-refenrential and therefore needs to point into the bump
// Stores references into the listeners attached to the vnodes
// NEEDS TO BE PRIVATE
pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>,
pub task_submitter: TaskSubmitter,
pub(crate) suspended_tasks: Vec<*mut Pin<Box<dyn Future<Output = VNode<'static>>>>>,
} }
pub type FiberTask = Pin<Box<dyn Future<Output = EventTrigger>>>; pub type FiberTask = Pin<Box<dyn Future<Output = EventTrigger>>>;
@@ -83,8 +66,8 @@ impl Scope {
// Therefore, their lifetimes are connected exclusively to the virtual dom // Therefore, their lifetimes are connected exclusively to the virtual dom
pub fn new<'creator_node>( pub fn new<'creator_node>(
caller: Rc<WrappedCaller>, caller: Rc<WrappedCaller>,
arena_idx: ScopeIdx, arena_idx: ScopeId,
parent: Option<ScopeIdx>, parent: Option<ScopeId>,
height: u32, height: u32,
event_channel: EventChannel, event_channel: EventChannel,
arena_link: SharedArena, arena_link: SharedArena,
@@ -102,8 +85,8 @@ impl Scope {
Self { Self {
child_nodes, child_nodes,
caller, caller,
parent, parent_idx: parent,
arena_idx, our_arena_idx: arena_idx,
height, height,
event_channel, event_channel,
arena_link, arena_link,
@@ -190,7 +173,7 @@ impl Scope {
log::debug!( log::debug!(
"There are {:?} listeners associated with this scope {:#?}", "There are {:?} listeners associated with this scope {:#?}",
self.listeners.borrow().len(), self.listeners.borrow().len(),
self.arena_idx self.our_arena_idx
); );
let listners = self.listeners.borrow_mut(); let listners = self.listeners.borrow_mut();
+4 -6
View File
@@ -11,15 +11,13 @@
use std::{ use std::{
cell::Cell, cell::Cell,
pin::Pin,
sync::{Arc, RwLock}, sync::{Arc, RwLock},
task::{Context, Poll},
}; };
use futures_util::{stream::FuturesUnordered, Future, Stream, StreamExt}; use futures_util::{stream::FuturesUnordered, Future, Stream, StreamExt};
use slotmap::{DefaultKey, SlotMap}; use slotmap::{DefaultKey, SlotMap};
use crate::innerlude::{EventTrigger, FiberTask, ScopeIdx}; use crate::innerlude::{EventTrigger, FiberTask, ScopeId};
pub type TaskSubmitter = Arc<dyn Fn(FiberTask)>; pub type TaskSubmitter = Arc<dyn Fn(FiberTask)>;
@@ -117,11 +115,11 @@ pub struct TaskHandle {
pub struct DTask { pub struct DTask {
fut: FiberTask, fut: FiberTask,
originator: ScopeIdx, originator: ScopeId,
dead: Cell<bool>, dead: Cell<bool>,
} }
impl DTask { impl DTask {
pub fn new(fut: FiberTask, originator: ScopeIdx) -> Self { pub fn new(fut: FiberTask, originator: ScopeId) -> Self {
Self { Self {
fut, fut,
originator, originator,
@@ -129,7 +127,7 @@ impl DTask {
} }
} }
pub fn debug_new(fut: FiberTask) -> Self { pub fn debug_new(fut: FiberTask) -> Self {
let originator = ScopeIdx::default(); let originator = ScopeId::default();
Self { Self {
fut, fut,
originator, originator,
+16 -10
View File
@@ -14,7 +14,7 @@ pub struct EventQueue {
} }
impl EventQueue { impl EventQueue {
pub fn new_channel(&self, height: u32, idx: ScopeIdx) -> Rc<dyn Fn()> { pub fn new_channel(&self, height: u32, idx: ScopeId) -> Rc<dyn Fn()> {
let inner = self.clone(); let inner = self.clone();
let marker = HeightMarker { height, idx }; let marker = HeightMarker { height, idx };
Rc::new(move || { Rc::new(move || {
@@ -35,7 +35,7 @@ impl EventQueue {
/// A helper type that lets scopes be ordered by their height /// A helper type that lets scopes be ordered by their height
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct HeightMarker { pub struct HeightMarker {
pub idx: ScopeIdx, pub idx: ScopeId,
pub height: u32, pub height: u32,
} }
@@ -59,23 +59,29 @@ impl PartialOrd for HeightMarker {
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] #[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq)] #[derive(Clone, Copy, Debug, PartialEq)]
pub struct RealDomNode(pub DefaultKey); pub struct RealDomNode(pub u64);
impl RealDomNode { impl RealDomNode {
#[inline]
pub fn empty() -> Self { pub fn empty() -> Self {
let data = KeyData::from_ffi(u64::MIN); // let data = KeyData::from_ffi(u64::MIN);
let key: DefaultKey = data.into(); // let key: DefaultKey = data.into();
Self(key) Self(u64::MIN)
} }
#[inline]
pub fn empty_cell() -> Cell<Self> { pub fn empty_cell() -> Cell<Self> {
Cell::new(Self::empty()) Cell::new(Self::empty())
} }
#[inline]
pub fn from_u64(id: u64) -> Self { pub fn from_u64(id: u64) -> Self {
let data = KeyData::from_ffi(id); // let data = KeyData::from_ffi(id);
let key: DefaultKey = data.into(); // let key: DefaultKey = data.into();
Self(key) Self(id)
} }
#[inline]
pub fn as_u64(&self) -> u64 { pub fn as_u64(&self) -> u64 {
self.0.data().as_ffi() // self.0.data().as_ffi()
self.0
} }
} }
+10 -3
View File
@@ -30,10 +30,17 @@ use std::any::TypeId;
use std::cell::RefCell; use std::cell::RefCell;
use std::pin::Pin; use std::pin::Pin;
pub type ScopeIdx = DefaultKey; pub type ScopeId = DefaultKey;
/// 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 { pub struct VirtualDom {
/// 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 arena is used to re-use slots of deleted scopes without having to resize the underlying arena. /// A generational arena is used to re-use slots of deleted scopes without having to resize the underlying arena.
@@ -44,7 +51,7 @@ pub struct VirtualDom {
/// The index of the root component /// The index of the root component
/// Should always be the first (gen=0, id=0) /// Should always be the first (gen=0, id=0)
pub base_scope: ScopeIdx, pub base_scope: ScopeId,
pub triggers: RefCell<Vec<EventTrigger>>, pub triggers: RefCell<Vec<EventTrigger>>,
@@ -55,7 +62,7 @@ pub struct VirtualDom {
root_props: std::pin::Pin<Box<dyn std::any::Any>>, root_props: std::pin::Pin<Box<dyn std::any::Any>>,
/// Type of the original cx. This is stored as TypeId so VirtualDom does not need to be generic. /// Type of the original props. This is stored as TypeId so VirtualDom does not need to be generic.
/// ///
/// Whenver props need to be updated, an Error will be thrown if the new props do not /// Whenver props need to be updated, an Error will be thrown if the new props do not
/// match the props used to create the VirtualDom. /// match the props used to create the VirtualDom.
+3 -1
View File
@@ -130,7 +130,9 @@ impl<'a> TextRenderer<'a> {
self.html_render(new_node, f, il + 1)?; self.html_render(new_node, f, il + 1)?;
} }
VNodeKind::Suspended => todo!(), VNodeKind::Suspended { .. } => {
// we can't do anything with suspended nodes
}
} }
Ok(()) Ok(())
} }
+14 -33
View File
@@ -2,7 +2,7 @@ use std::{collections::HashMap, rc::Rc, sync::Arc};
use dioxus_core::{ use dioxus_core::{
events::{EventTrigger, VirtualEvent}, events::{EventTrigger, VirtualEvent},
DomEdit, RealDomNode, ScopeIdx, DomEdit, RealDomNode, ScopeId,
}; };
use fxhash::FxHashMap; use fxhash::FxHashMap;
use slotmap::{DefaultKey, Key, KeyData}; use slotmap::{DefaultKey, Key, KeyData};
@@ -85,10 +85,10 @@ impl WebsysDom {
DomEdit::CreateElementNs { tag, id, ns } => self.create_element(tag, Some(ns), id), DomEdit::CreateElementNs { tag, id, ns } => self.create_element(tag, Some(ns), id),
DomEdit::CreatePlaceholder { id } => self.create_placeholder(id), DomEdit::CreatePlaceholder { id } => self.create_placeholder(id),
DomEdit::NewEventListener { DomEdit::NewEventListener {
event, event_name: event,
scope, scope,
node, mounted_node_id: node,
idx, element_id: idx,
} => self.new_event_listener(event, scope, idx, node), } => self.new_event_listener(event, scope, idx, node),
DomEdit::RemoveEventListener { event } => todo!(), DomEdit::RemoveEventListener { event } => todo!(),
DomEdit::SetText { text } => self.set_text(text), DomEdit::SetText { text } => self.set_text(text),
@@ -234,7 +234,7 @@ impl WebsysDom {
fn new_event_listener( fn new_event_listener(
&mut self, &mut self,
event: &'static str, event: &'static str,
scope: ScopeIdx, scope: ScopeId,
_element_id: usize, _element_id: usize,
real_id: u64, real_id: u64,
) { ) {
@@ -335,7 +335,7 @@ impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom {
fn request_available_node(&mut self) -> RealDomNode { fn request_available_node(&mut self) -> RealDomNode {
let key = self.nodes.insert(None); let key = self.nodes.insert(None);
log::debug!("making new key: {:#?}", key); log::debug!("making new key: {:#?}", key);
RealDomNode(key) RealDomNode(key.data().as_ffi())
} }
fn raw_node_as_any(&self) -> &mut dyn std::any::Any { fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
@@ -349,22 +349,24 @@ pub struct Stack {
} }
impl Stack { impl Stack {
#[inline]
pub fn with_capacity(cap: usize) -> Self { pub fn with_capacity(cap: usize) -> Self {
Stack { Stack {
list: Vec::with_capacity(cap), list: Vec::with_capacity(cap),
} }
} }
#[inline]
pub fn push(&mut self, node: Node) { pub fn push(&mut self, node: Node) {
// debug!("stack-push: {:?}", node);
self.list.push(node); self.list.push(node);
} }
#[inline]
pub fn pop(&mut self) -> Node { pub fn pop(&mut self) -> Node {
let res = self.list.pop().unwrap(); self.list.pop().unwrap()
res
} }
#[inline]
pub fn clear(&mut self) { pub fn clear(&mut self) {
self.list.clear(); self.list.clear();
} }
@@ -588,16 +590,11 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
use anyhow::Context; use anyhow::Context;
// for attr in {
let attrs = target.attributes(); let attrs = target.attributes();
for x in 0..attrs.length() { for x in 0..attrs.length() {
let attr = attrs.item(x).unwrap(); let attr = attrs.item(x).unwrap();
log::debug!("attrs include: {:#?}", attr); log::debug!("attrs include: {:#?}", attr);
} }
// }
// for attr in target.attributes() {
// log::debug!("attrs include: {:#?}", attr);
// }
// The error handling here is not very descriptive and needs to be replaced with a zero-cost error system // The error handling here is not very descriptive and needs to be replaced with a zero-cost error system
let val: String = target let val: String = target
@@ -613,34 +610,18 @@ fn decode_trigger(event: &web_sys::Event) -> anyhow::Result<EventTrigger> {
let real_id = fields let real_id = fields
.next() .next()
.and_then(|raw_id| { .and_then(|raw_id| raw_id.parse::<u64>().ok())
raw_id
.parse::<u64>()
.ok()
.map(|value| KeyData::from_ffi(value))
.map(|f| RealDomNode(f.into()))
})
.context("failed to parse real id")?; .context("failed to parse real id")?;
// Call the trigger // Call the trigger
log::debug!("decoded scope_id: {}, node_id: {:#?}", gi_id, real_id); log::debug!("decoded scope_id: {}, node_id: {:#?}", gi_id, real_id);
let triggered_scope: ScopeIdx = KeyData::from_ffi(gi_id).into(); let triggered_scope: ScopeId = KeyData::from_ffi(gi_id).into();
log::debug!("Triggered scope is {:#?}", triggered_scope); log::debug!("Triggered scope is {:#?}", triggered_scope);
Ok(EventTrigger::new( Ok(EventTrigger::new(
virtual_event_from_websys_event(event), virtual_event_from_websys_event(event),
triggered_scope, triggered_scope,
Some(real_id), Some(RealDomNode::from_u64(real_id)),
dioxus_core::events::EventPriority::High, dioxus_core::events::EventPriority::High,
)) ))
} }
struct ListenerMap {}
impl ListenerMap {
fn get(&self, event: &'static str) -> bool {
false
}
}
struct JsStringCache {}
impl JsStringCache {}
+1 -1
View File
@@ -1,6 +1,6 @@
//! webview dom //! webview dom
use dioxus_core::{DomEdit, RealDom, RealDomNode, ScopeIdx}; use dioxus_core::{DomEdit, RealDom, RealDomNode, ScopeId};
use DomEdit::*; use DomEdit::*;
pub struct WebviewRegistry {} pub struct WebviewRegistry {}