wip: move things into a "shared" object

This commit is contained in:
Jonathan Kelley committed 2021-07-23 10:27:43 -04:00
1 parent 22f894e6b9
commit f644d7c441
19 files changed
+542 -698

No files matched your search

+17 -15
View File
@@ -109,20 +109,29 @@ impl Parse for Element<AS_HTML> {
let name_str = name.to_string(); let name_str = name.to_string();
stream.parse::<Token![=]>()?; stream.parse::<Token![=]>()?;
if name_str.starts_with("on") { if name_str.starts_with("on") {
todo!() let inner;
syn::braced!(inner in stream);
let toks = inner.parse::<Expr>()?;
let ty = AttrType::EventTokens(toks);
listeners.push(ElementAttr {
element_name: el_name.clone(),
name,
value: ty,
namespace: None,
})
} else { } else {
match name_str.as_str() { match name_str.as_str() {
"style" => todo!(), "style" => {}
"key" => todo!(), "key" => {}
"classes" => todo!(), "classes" | "namespace" | "ref" | _ => {
"namespace" => todo!(),
"ref" => todo!(),
_ => {
let ty = if stream.peek(LitStr) { let ty = if stream.peek(LitStr) {
let rawtext = stream.parse::<LitStr>().unwrap(); let rawtext = stream.parse::<LitStr>().unwrap();
AttrType::BumpText(rawtext) AttrType::BumpText(rawtext)
} else { } else {
let toks = stream.parse::<Expr>()?; // like JSX, we expect raw expressions
let inner;
syn::braced!(inner in stream);
let toks = inner.parse::<Expr>()?;
AttrType::FieldTokens(toks) AttrType::FieldTokens(toks)
}; };
attributes.push(ElementAttr { attributes.push(ElementAttr {
@@ -134,13 +143,6 @@ impl Parse for Element<AS_HTML> {
} }
} }
}; };
// if stream.peek(LitStr) {
// } else {
// }
// if name_str.starts_with("on") {}
// attributes.push(stream.parse()?);
} }
stream.parse::<Token![>]>()?; stream.parse::<Token![>]>()?;
+1 -1
View File
@@ -1,3 +1,3 @@
{ {
"rust-analyzer.inlayHints.enable": false "rust-analyzer.inlayHints.enable": true
} }
+111 -37
View File
@@ -1,55 +1,99 @@
use std::cell::{RefCell, RefMut};
use std::{cell::UnsafeCell, rc::Rc}; use std::{cell::UnsafeCell, rc::Rc};
use crate::heuristics::*;
use crate::innerlude::*; use crate::innerlude::*;
use futures_util::stream::FuturesUnordered;
use slotmap::SlotMap; use slotmap::SlotMap;
slotmap::new_key_type! {
// A dedicated key type for the all the scopes
pub struct ScopeId;
}
slotmap::new_key_type! {
// A dedicated key type for every real element that the virtualdom creates.
//
// This is a slotmap key because we expect the "mirror" realdom to also maintain a slotmap mapping
// of virtual element to real element.
pub struct ElementId;
}
impl ElementId {
pub fn as_u64(self) -> u64 {
self.0.as_ffi()
}
}
type Shared<T> = Rc<RefCell<T>>;
/// These are resources shared among all the components and the virtualdom itself
#[derive(Clone)] #[derive(Clone)]
pub struct SharedArena { pub struct SharedResources {
pub components: Rc<UnsafeCell<ScopeMap>>, pub components: Rc<UnsafeCell<SlotMap<ScopeId, Scope>>>,
}
pub type ScopeMap = SlotMap<ScopeId, Scope>;
enum MutStatus { pub event_queue: Shared<Vec<HeightMarker>>,
Immut,
Mut, pub events: Shared<Vec<EventTrigger>>,
pub(crate) heuristics: Shared<HeuristicsEngine>,
pub(crate) tasks: Shared<FuturesUnordered<FiberTask>>,
/// We use a SlotSet to keep track of the keys that are currently being used.
/// However, we don't store any specific data since the "mirror"
pub raw_elements: Shared<SlotMap<ElementId, ()>>,
pub task_setter: Rc<dyn Fn(ScopeId)>,
} }
impl SharedArena { impl SharedResources {
pub fn new(arena: ScopeMap) -> Self { pub fn new() -> Self {
let components = Rc::new(UnsafeCell::new(arena)); // preallocate 1000 elements and 20 scopes to avoid dynamic allocation
SharedArena { components } let components = Rc::new(UnsafeCell::new(
SlotMap::<ScopeId, Scope>::with_capacity_and_key(20),
));
let raw_elements = SlotMap::<ElementId, ()>::with_capacity_and_key(1000);
let event_queue = Rc::new(RefCell::new(Vec::new()));
let tasks = Vec::new();
let heuristics = HeuristicsEngine::new();
let queue = event_queue.clone();
let _components = components.clone();
let task_setter = Rc::new(move |idx| {
let comps = unsafe { &*_components.get() };
if let Some(scope) = comps.get(idx) {
queue.borrow_mut().push(HeightMarker {
height: scope.height,
idx,
})
}
});
Self {
event_queue,
components,
tasks: Rc::new(RefCell::new(FuturesUnordered::new())),
events: Rc::new(RefCell::new(tasks)),
heuristics: Rc::new(RefCell::new(heuristics)),
raw_elements: Rc::new(RefCell::new(raw_elements)),
task_setter,
}
} }
/// THIS METHOD IS CURRENTLY UNSAFE /// this is unsafe because the caller needs to track which other scopes it's already using
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS pub unsafe fn get_scope(&self, idx: ScopeId) -> Option<&Scope> {
pub fn get(&self, idx: ScopeId) -> Option<&Scope> { let inner = &*self.components.get();
let inner = unsafe { &*self.components.get() };
inner.get(idx) inner.get(idx)
} }
/// THIS METHOD IS CURRENTLY UNSAFE /// this is unsafe because the caller needs to track which other scopes it's already using
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS pub unsafe fn get_sope_mut(&self, idx: ScopeId) -> Option<&mut Scope> {
pub fn get_mut(&self, idx: ScopeId) -> Option<&mut Scope> { let inner = &mut *self.components.get();
let inner = unsafe { &mut *self.components.get() };
inner.get_mut(idx) inner.get_mut(idx)
} }
fn inner(&self) -> &ScopeMap {
todo!()
}
fn inner_mut(&mut self) -> &mut ScopeMap {
todo!()
}
/// THIS METHOD IS CURRENTLY UNSAFE
/// THERE ARE NO CHECKS TO VERIFY THAT WE ARE ALLOWED TO DO THIS
pub fn with<T>(&self, f: impl FnOnce(&mut ScopeMap) -> T) -> Result<T> {
let inner = unsafe { &mut *self.components.get() };
Ok(f(inner))
// todo!()
}
pub fn with_scope<'b, O: 'static>( pub fn with_scope<'b, O: 'static>(
&'b self, &'b self,
_id: ScopeId, _id: ScopeId,
@@ -75,7 +119,37 @@ impl SharedArena {
.ok_or_else(|| Error::FatalInternal("Scope not found")) .ok_or_else(|| Error::FatalInternal("Scope not found"))
} }
unsafe fn inner_unchecked<'s>() -> &'s mut ScopeMap { pub fn reserve_node(&self) -> ElementId {
todo!() self.raw_elements.borrow_mut().insert(())
}
/// return the id, freeing the space of the original node
pub fn collect_garbage(&self, id: ElementId) {}
pub fn borrow_queue(&self) -> RefMut<Vec<HeightMarker>> {
self.event_queue.borrow_mut()
}
pub fn insert_scope_with_key(&self, f: impl FnOnce(ScopeId) -> Scope) -> ScopeId {
let g = unsafe { &mut *self.components.get() };
g.insert_with_key(f)
}
pub fn schedule_update(&self) -> Rc<dyn Fn(ScopeId)> {
self.task_setter.clone()
}
pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
self.tasks.borrow_mut().push(task);
TaskHandle {}
} }
} }
pub struct TaskHandle {}
impl TaskHandle {
pub fn toggle(&self) {}
pub fn start(&self) {}
pub fn stop(&self) {}
pub fn restart(&self) {}
}
+71 -49
View File
@@ -1,10 +1,10 @@
use crate::innerlude::*; use crate::innerlude::*;
use bumpalo::Bump; use bumpalo::Bump;
use std::cell::Cell;
use std::cell::RefCell;
pub struct ActiveFrame { pub struct ActiveFrame {
// We use a "generation" for users of contents in the bump frames to ensure their data isn't broken // We use a "generation" for users of contents in the bump frames to ensure their data isn't broken
pub generation: RefCell<usize>, pub generation: Cell<usize>,
// The double-buffering situation that we will use // The double-buffering situation that we will use
pub frames: [BumpFrame; 2], pub frames: [BumpFrame; 2],
@@ -13,78 +13,100 @@ pub struct ActiveFrame {
pub struct BumpFrame { pub struct BumpFrame {
pub bump: Bump, pub bump: Bump,
pub head_node: VNode<'static>, pub head_node: VNode<'static>,
#[cfg(test)]
name: &'static str,
} }
impl ActiveFrame { impl ActiveFrame {
pub fn new() -> Self { pub fn new() -> Self {
Self::from_frames( let frame_a = BumpFrame {
BumpFrame { bump: Bump::new(),
bump: Bump::new(), head_node: NodeFactory::unstable_place_holder(),
head_node: NodeFactory::static_text(""),
},
BumpFrame {
bump: Bump::new(),
head_node: NodeFactory::static_text(""),
},
)
}
pub fn from_frames(a: BumpFrame, b: BumpFrame) -> Self { #[cfg(test)]
name: "old",
};
let frame_b = BumpFrame {
bump: Bump::new(),
head_node: NodeFactory::unstable_place_holder(),
#[cfg(test)]
name: "new",
};
Self { Self {
generation: 0.into(), generation: 0.into(),
frames: [a, b], frames: [frame_a, frame_b],
} }
} }
pub fn cur_frame(&self) -> &BumpFrame { pub fn prev_frame(&self) -> &BumpFrame {
match *self.generation.borrow() & 1 == 0 { match self.generation.get() & 1 == 0 {
true => &self.frames[0], true => &self.frames[0],
false => &self.frames[1], false => &self.frames[1],
} }
} }
pub fn cur_frame_mut(&mut self) -> &mut BumpFrame {
match *self.generation.borrow() & 1 == 0 { pub fn prev_frame_mut(&mut self) -> &mut BumpFrame {
match self.generation.get() & 1 == 0 {
true => &mut self.frames[0], true => &mut self.frames[0],
false => &mut self.frames[1], false => &mut self.frames[1],
} }
} }
pub fn cur_frame(&self) -> &BumpFrame {
match self.generation.get() & 1 == 1 {
true => &self.frames[0],
false => &self.frames[1],
}
}
pub fn cur_frame_mut(&mut self) -> &mut BumpFrame {
match self.generation.get() & 1 == 1 {
true => &mut self.frames[0],
false => &mut self.frames[1],
}
}
/// Give out our self-referential item with our own borrowed lifetime
pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> { pub fn current_head_node<'b>(&'b self) -> &'b VNode<'b> {
let raw_node = match *self.generation.borrow() & 1 == 0 { let cur_head = &self.cur_frame().head_node;
true => &self.frames[0], unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
false => &self.frames[1],
};
// Give out our self-referential item with our own borrowed lifetime
unsafe {
let unsafe_head = &raw_node.head_node;
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
safe_node
}
} }
/// Give out our self-referential item with our own borrowed lifetime
pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> { pub fn prev_head_node<'b>(&'b self) -> &'b VNode<'b> {
let raw_node = match *self.generation.borrow() & 1 != 0 { let cur_head = &self.prev_frame().head_node;
true => &self.frames[0], unsafe { std::mem::transmute::<&VNode<'static>, &VNode<'b>>(cur_head) }
false => &self.frames[1],
};
// Give out our self-referential item with our own borrowed lifetime
unsafe {
let unsafe_head = &raw_node.head_node;
let safe_node = std::mem::transmute::<&VNode<'static>, &VNode<'b>>(unsafe_head);
safe_node
}
}
pub fn old_frame_mut(&mut self) -> &mut BumpFrame {
match *self.generation.borrow() & 1 == 0 {
true => &mut self.frames[1],
false => &mut self.frames[0],
}
} }
pub fn cycle_frame(&mut self) { pub fn cycle_frame(&mut self) {
*self.generation.borrow_mut() += 1; self.generation.set(self.generation.get() + 1);
}
}
#[cfg(test)]
mod tests {
//! These tests are bad. I don't have a good way of properly testing the ActiveFrame stuff
use super::*;
#[test]
fn test_bump_frame() {
let mut frames = ActiveFrame::new();
// just cycle a few times and make sure we get the right frames out
for _ in 0..5 {
let old = frames.prev_frame();
let new = frames.cur_frame();
assert_eq!(old.name, "old");
assert_eq!(new.name, "new");
frames.cycle_frame();
let old = frames.prev_frame();
let new = frames.cur_frame();
assert_eq!(old.name, "new");
assert_eq!(new.name, "old");
frames.cycle_frame();
}
assert_eq!(frames.generation.get(), 10);
} }
} }
-91
View File
@@ -1,91 +0,0 @@
/// This iterator iterates through a list of virtual children and only returns real children (Elements or Text).
///
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
/// "InsertBefore".
struct RealChildIterator<'a> {
scopes: &'a SharedArena,
// Heuristcally we should never bleed into 5 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
stack: smallvec::SmallVec<[(u16, &'a VNode<'a>); 5]>,
}
impl<'a> RealChildIterator<'a> {
fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self {
Self {
scopes,
stack: smallvec::smallvec![(0, starter)],
}
}
}
impl<'a> Iterator for RealChildIterator<'a> {
type Item = &'a VNode<'a>;
fn next(&mut self) -> Option<&'a VNode<'a>> {
let mut should_pop = false;
let mut returned_node = None;
let mut should_push = None;
while returned_node.is_none() {
if let Some((count, node)) = self.stack.last_mut() {
match node {
// We can only exit our looping when we get "real" nodes
// This includes fragments and components when they're empty (have a single root)
VNode::Element(_) | VNode::Text(_) => {
// We've recursed INTO an element/text
// We need to recurse *out* of it and move forward to the next
should_pop = true;
returned_node = Some(&**node);
}
// If we get a fragment we push the next child
VNode::Fragment(frag) => {
let subcount = *count as usize;
if frag.children.len() == 0 {
should_pop = true;
returned_node = Some(&**node);
}
if subcount >= frag.children.len() {
should_pop = true;
} else {
should_push = Some(&frag.children[subcount]);
}
}
// Immediately abort suspended nodes - can't do anything with them yet
// VNode::Suspended => should_pop = true,
VNode::Suspended { real } => todo!(),
// For components, we load their root and push them onto the stack
VNode::Component(sc) => {
let scope = self.scopes.try_get(sc.ass_scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component
*node = scope.root();
}
}
} else {
// If there's no more items on the stack, we're done!
return None;
}
if should_pop {
self.stack.pop();
if let Some((id, _)) = self.stack.last_mut() {
*id += 1;
}
should_pop = false;
}
if let Some(push) = should_push {
self.stack.push((0, push));
should_push = None;
}
}
returned_node
}
}
+43 -1
View File
@@ -5,8 +5,40 @@
//! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder //! if the type suppports PartialEq. The Properties trait is used by the rsx! and html! macros to generate the type-safe builder
//! that ensures compile-time required and optional fields on cx. //! that ensures compile-time required and optional fields on cx.
use crate::innerlude::{Context, DomTree, LazyNodes, VNode, FC}; use crate::innerlude::{Context, DomTree, LazyNodes, FC};
/// Every "Props" used for a component must implement the `Properties` trait. This trait gives some hints to Dioxus
/// on how to memoize the props and some additional optimizations that can be made. We strongly encourage using the
/// derive macro to implement the `Properties` trait automatically as guarantee that your memoization strategy is safe.
///
/// If your props are 'static, then Dioxus will require that they also be PartialEq for the derived memoize strategy. However,
/// if your props borrow data, then the memoization strategy will simply default to "false" and the PartialEq will be ignored.
/// This tends to be useful when props borrow something that simply cannot be compared (IE a reference to a closure);
///
/// By default, the memoization strategy is very conservative, but can be tuned to be more aggressive manually. However,
/// this is only safe if the props are 'static - otherwise you might borrow references after-free.
///
/// We strongly suggest that any changes to memoization be done at the "PartialEq" level for 'static props. Additionally,
/// we advise the use of smart pointers in cases where memoization is important.
///
/// ## Example
///
/// For props that are 'static:
/// ```rust ignore
/// #[derive(Props, PartialEq)]
/// struct MyProps {
/// data: String
/// }
/// ```
///
/// For props that borrow:
///
/// ```rust ignore
/// #[derive(Props)]
/// struct MyProps<'a >{
/// data: &'a str
/// }
/// ```
pub trait Properties: Sized { pub trait Properties: Sized {
type Builder; type Builder;
const IS_STATIC: bool = false; const IS_STATIC: bool = false;
@@ -50,6 +82,16 @@ pub fn fc_to_builder<T: Properties>(_: FC<T>) -> T::Builder {
/// ///
/// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase. /// Fragments are incredibly useful when necessary, but *do* add cost in the diffing phase.
/// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash. /// Try to avoid nesting fragments if you can. Infinitely nested Fragments *will* cause diffing to crash.
///
/// This function defines a dedicated `Fragment` component that can be used to create inline fragments in the RSX macro.
///
/// You want to use this free-function when your fragment needs a key and simply returning multiple nodes from rsx! won't cut it.
///
/// ```rust
/// rsx!{
/// Fragment { key: "abc" }
/// }
/// ```
#[allow(non_upper_case_globals, non_snake_case)] #[allow(non_upper_case_globals, non_snake_case)]
pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> { pub fn Fragment<'a>(cx: Context<'a, ()>) -> DomTree<'a> {
cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children()))) cx.render(LazyNodes::new(move |f| f.fragment_from_iter(cx.children())))
+28 -27
View File
@@ -99,12 +99,21 @@ impl<'src, P> Context<'src, P> {
/// } /// }
/// ``` /// ```
pub fn children(&self) -> &'src [VNode<'src>] { pub fn children(&self) -> &'src [VNode<'src>] {
self.scope.child_nodes self.scope.child_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
///
/// ## Notice: you should prefer using prepare_update and get_scope_id
///
pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> { pub fn schedule_update(&self) -> Rc<dyn Fn() + 'static> {
self.scope.event_channel.clone() let cb = self.scope.vdom.schedule_update();
let id = self.get_scope_id();
Rc::new(move || cb(id))
}
fn prepare_update(&self) -> Rc<dyn Fn(ScopeId)> {
self.scope.vdom.schedule_update()
} }
pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> { pub fn schedule_effect(&self) -> Rc<dyn Fn() + 'static> {
@@ -142,7 +151,7 @@ impl<'src, P> Context<'src, P> {
let scope_ref = self.scope; let scope_ref = self.scope;
let listener_id = &scope_ref.listener_idx; let listener_id = &scope_ref.listener_idx;
Some(lazy_nodes.into_vnode(NodeFactory { Some(lazy_nodes.into_vnode(NodeFactory {
scope_ref, scope: scope_ref,
listener_id, listener_id,
})) }))
} }
@@ -297,7 +306,7 @@ Any function prefixed with "use" should not be called conditionally.
} else { } else {
match inner.parent_idx { match inner.parent_idx {
Some(parent_id) => { Some(parent_id) => {
scope = inner.arena_link.get(parent_id); scope = unsafe { inner.vdom.get_scope(parent_id) };
} }
None => break, None => break,
} }
@@ -329,8 +338,7 @@ Any function prefixed with "use" should not be called conditionally.
/// ///
/// ///
pub fn submit_task(&self, task: FiberTask) -> TaskHandle { pub fn submit_task(&self, task: FiberTask) -> TaskHandle {
(self.scope.task_submitter)(task); self.scope.vdom.submit_task(task)
TaskHandle { _p: PhantomData {} }
} }
/// Awaits the given task, forcing the component to re-render when the value is ready. /// Awaits the given task, forcing the component to re-render when the value is ready.
@@ -341,13 +349,14 @@ Any function prefixed with "use" should not be called conditionally.
pub fn use_task<Out, Fut, Init>( pub fn use_task<Out, Fut, Init>(
self, self,
task_initializer: Init, task_initializer: Init,
) -> (TaskHandle<'src>, &'src Option<Out>) ) -> (&'src TaskHandle, &'src Option<Out>)
where where
Out: 'static, Out: 'static,
Fut: Future<Output = Out> + 'static, Fut: Future<Output = Out> + 'static,
Init: FnOnce() -> Fut + 'src, Init: FnOnce() -> Fut + 'src,
{ {
struct TaskHook<T> { struct TaskHook<T> {
handle: TaskHandle,
task_dump: Rc<RefCell<Option<T>>>, task_dump: Rc<RefCell<Option<T>>>,
value: Option<T>, value: Option<T>,
} }
@@ -360,12 +369,15 @@ Any function prefixed with "use" should not be called conditionally.
let task_dump = Rc::new(RefCell::new(None)); let task_dump = Rc::new(RefCell::new(None));
let slot = task_dump.clone(); let slot = task_dump.clone();
let update = self.schedule_update();
let updater = self.prepare_update();
let update_id = self.get_scope_id();
let originator = self.scope.our_arena_idx.clone(); let originator = self.scope.our_arena_idx.clone();
self.submit_task(Box::pin(task_fut.then(move |output| async move { let handle = 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);
update(); updater(update_id);
EventTrigger { EventTrigger {
event: VirtualEvent::AsyncEvent { hook_idx }, event: VirtualEvent::AsyncEvent { hook_idx },
originator, originator,
@@ -377,13 +389,14 @@ Any function prefixed with "use" should not be called conditionally.
TaskHook { TaskHook {
task_dump, task_dump,
value: None, value: None,
handle,
} }
}, },
|hook| { |hook| {
if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() { if let Some(val) = hook.task_dump.as_ref().borrow_mut().take() {
hook.value = Some(val); hook.value = Some(val);
} }
(TaskHandle { _p: PhantomData }, &hook.value) (&hook.handle, &hook.value)
}, },
|_| {}, |_| {},
) )
@@ -393,7 +406,7 @@ Any function prefixed with "use" should not be called conditionally.
pub(crate) struct SuspenseHook { pub(crate) struct SuspenseHook {
pub value: Rc<RefCell<Option<Box<dyn Any>>>>, pub value: Rc<RefCell<Option<Box<dyn Any>>>>,
pub callback: SuspendedCallback, pub callback: SuspendedCallback,
pub dom_node_id: Rc<Cell<RealDomNode>>, pub dom_node_id: Rc<Cell<Option<ElementId>>>,
} }
type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>; type SuspendedCallback = Box<dyn for<'a> Fn(SuspendedContext<'a>) -> DomTree<'a>>;
@@ -422,7 +435,7 @@ impl<'src, P> Context<'src, P> {
move |hook_idx| { move |hook_idx| {
let value = Rc::new(RefCell::new(None)); let value = Rc::new(RefCell::new(None));
let dom_node_id = Rc::new(RealDomNode::empty_cell()); let dom_node_id = Rc::new(empty_cell());
let domnode = dom_node_id.clone(); let domnode = dom_node_id.clone();
let slot = value.clone(); let slot = value.clone();
@@ -438,7 +451,7 @@ impl<'src, P> Context<'src, P> {
None => { None => {
// //
Some(VNode { Some(VNode {
dom_id: RealDomNode::empty_cell(), dom_id: empty_cell(),
key: None, key: None,
kind: VNodeKind::Suspended { kind: VNodeKind::Suspended {
node: domnode.clone(), node: domnode.clone(),
@@ -495,20 +508,8 @@ impl<'src> SuspendedContext<'src> {
let scope_ref = self.inner.scope; let scope_ref = self.inner.scope;
let listener_id = &scope_ref.listener_idx; let listener_id = &scope_ref.listener_idx;
Some(lazy_nodes.into_vnode(NodeFactory { Some(lazy_nodes.into_vnode(NodeFactory {
scope_ref, scope: scope_ref,
listener_id, listener_id,
})) }))
} }
} }
#[derive(Clone, Copy)]
pub struct TaskHandle<'src> {
_p: PhantomData<&'src ()>,
}
impl<'src> TaskHandle<'src> {
pub fn toggle(&self) {}
pub fn start(&self) {}
pub fn stop(&self) {}
pub fn restart(&self) {}
}
+91 -75
View File
@@ -15,7 +15,7 @@
//! whose keys can be converted to u64 on FFI boundaries. //! whose keys can be converted to u64 on FFI boundaries.
//! //!
//! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we //! When new nodes are created through `render`, they won't know which real node they correspond to. During diffing, we
//! always make sure to copy over the ID. If we don't do this properly, the realdomnode will be populated incorrectly and //! always make sure to copy over the ID. If we don't do this properly, the ElementId will be populated incorrectly and
//! brick the user's page. //! brick the user's page.
//! //!
//! ## Subtree Memoization //! ## Subtree Memoization
@@ -48,11 +48,11 @@
//! More info on how to improve this diffing algorithm: //! More info on how to improve this diffing algorithm:
//! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/ //! - https://hacks.mozilla.org/2019/03/fast-bump-allocated-virtual-doms-with-rust-and-wasm/
use crate::{arena::SharedArena, innerlude::*, tasks::TaskQueue}; use crate::{arena::SharedResources, innerlude::*};
use fxhash::FxHashSet; use fxhash::{FxHashMap, FxHashSet};
use smallvec::{smallvec, SmallVec}; use smallvec::{smallvec, SmallVec};
use std::any::Any; use std::{any::Any, borrow::Borrow};
/// The accompanying "real dom" exposes an imperative API for controlling the UI layout /// The accompanying "real dom" exposes an imperative API for controlling the UI layout
/// ///
@@ -66,21 +66,35 @@ use std::any::Any;
/// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any /// any modifications that follow. This technique enables the diffing algorithm to avoid directly handling or storing any
/// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection. /// target-specific Node type as well as easily serializing the edits to be sent over a network or IPC connection.
pub trait RealDom<'a> { pub trait RealDom<'a> {
fn request_available_node(&mut self) -> RealDomNode;
fn raw_node_as_any(&self) -> &mut dyn Any; fn raw_node_as_any(&self) -> &mut dyn Any;
} }
pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> { pub struct DiffMachine<'real, 'bump, Dom: RealDom<'bump>> {
pub dom: &'real mut Dom, pub real_dom: &'real mut Dom,
pub vdom: &'bump SharedResources,
pub edits: DomEditor<'real, 'bump>, pub edits: DomEditor<'real, 'bump>,
pub components: &'bump SharedArena,
pub task_queue: &'bump TaskQueue,
pub cur_idxs: SmallVec<[ScopeId; 5]>, pub cur_idxs: SmallVec<[ScopeId; 5]>,
pub diffed: FxHashSet<ScopeId>, pub diffed: FxHashSet<ScopeId>,
pub event_queue: EventQueue,
pub seen_nodes: FxHashSet<ScopeId>, pub seen_nodes: FxHashSet<ScopeId>,
} }
impl<'r, 'b, D: RealDom<'b>> DiffMachine<'r, 'b, D> {
pub fn get_scope_mut(&mut self, id: &ScopeId) -> Option<&'b mut Scope> {
// ensure we haven't seen this scope before
// if we have, then we're trying to alias it, which is not allowed
debug_assert!(!self.seen_nodes.contains(id));
let compon = unsafe { &mut *self.vdom.components.get() };
compon.get_mut(*id)
}
pub fn get_scope(&mut self, id: &ScopeId) -> Option<&'b Scope> {
// ensure we haven't seen this scope before
// if we have, then we're trying to alias it, which is not allowed
let compon = unsafe { &*self.vdom.components.get() };
compon.get(*id)
}
}
impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom> impl<'real, 'bump, Dom> DiffMachine<'real, 'bump, Dom>
where where
Dom: RealDom<'bump>, Dom: RealDom<'bump>,
@@ -88,18 +102,14 @@ where
pub fn new( pub fn new(
edits: &'real mut Vec<DomEdit<'bump>>, edits: &'real mut Vec<DomEdit<'bump>>,
dom: &'real mut Dom, dom: &'real mut Dom,
components: &'bump SharedArena,
cur_idx: ScopeId, cur_idx: ScopeId,
event_queue: EventQueue, shared: &'bump SharedResources,
task_queue: &'bump TaskQueue,
) -> Self { ) -> Self {
Self { Self {
real_dom: dom,
edits: DomEditor::new(edits), edits: DomEditor::new(edits),
components,
dom,
cur_idxs: smallvec![cur_idx], cur_idxs: smallvec![cur_idx],
event_queue, vdom: shared,
task_queue,
diffed: FxHashSet::default(), diffed: FxHashSet::default(),
seen_nodes: FxHashSet::default(), seen_nodes: FxHashSet::default(),
} }
@@ -112,20 +122,24 @@ where
// //
// each function call assumes the stack is fresh (empty). // each function call assumes the stack is fresh (empty).
pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) { pub fn diff_node(&mut self, old_node: &'bump VNode<'bump>, new_node: &'bump VNode<'bump>) {
let root = old_node
.dom_id
.get()
.expect("Should not be diffing old nodes that were never assigned");
match (&old_node.kind, &new_node.kind) { match (&old_node.kind, &new_node.kind) {
// Handle the "sane" cases first. // Handle the "sane" cases first.
// The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment". // The rsx and html macros strongly discourage dynamic lists not encapsulated by a "Fragment".
// So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable. // So the sane (and fast!) cases are where the virtual structure stays the same and is easily diffable.
(VNodeKind::Text(old), VNodeKind::Text(new)) => { (VNodeKind::Text(old), VNodeKind::Text(new)) => {
let root = old_node.dom_id.get();
if old.text != new.text { if old.text != new.text {
self.edits.push(root); self.edits.push_root(root);
log::debug!("Text has changed {}, {}", old.text, new.text); log::debug!("Text has changed {}, {}", old.text, new.text);
self.edits.set_text(new.text); self.edits.set_text(new.text);
self.edits.pop(); self.edits.pop();
} }
new_node.dom_id.set(root); new_node.dom_id.set(Some(root));
} }
(VNodeKind::Element(old), VNodeKind::Element(new)) => { (VNodeKind::Element(old), VNodeKind::Element(new)) => {
@@ -133,20 +147,19 @@ where
// This is an optimization React makes due to how users structure their code // This is an optimization React makes due to how users structure their code
// //
// In Dioxus, this is less likely to occur unless through a fragment // In Dioxus, this is less likely to occur unless through a fragment
let root = old_node.dom_id.get();
if new.tag_name != old.tag_name || new.namespace != old.namespace { if new.tag_name != old.tag_name || new.namespace != old.namespace {
self.edits.push(root); self.edits.push_root(root);
let meta = self.create(new_node); let meta = self.create(new_node);
self.edits.replace_with(meta.added_to_stack); self.edits.replace_with(meta.added_to_stack);
self.edits.pop(); self.edits.pop();
return; return;
} }
new_node.dom_id.set(root); new_node.dom_id.set(Some(root));
// push it just in case // push it just in case
// TODO: remove this - it clogs up things and is inefficient // TODO: remove this - it clogs up things and is inefficient
self.edits.push(root); self.edits.push_root(root);
self.diff_listeners(old.listeners, new.listeners); self.diff_listeners(old.listeners, new.listeners);
self.diff_attr(old.attributes, new.attributes, new.namespace); self.diff_attr(old.attributes, new.attributes, new.namespace);
self.diff_children(old.children, new.children); self.diff_children(old.children, new.children);
@@ -160,11 +173,11 @@ where
self.cur_idxs.push(old.ass_scope.get().unwrap()); self.cur_idxs.push(old.ass_scope.get().unwrap());
// Make sure the new component vnode is referencing the right scope id // Make sure the new component vnode is referencing the right scope id
let scope_id = old.ass_scope.get(); let scope_addr = old.ass_scope.get().unwrap();
new.ass_scope.set(scope_id); new.ass_scope.set(Some(scope_addr));
// make sure the component's caller function is up to date // make sure the component's caller function is up to date
let scope = self.components.get_mut(scope_id.unwrap()).unwrap(); let scope = self.get_scope_mut(&scope_addr).unwrap();
scope.caller = new.caller.clone(); scope.caller = new.caller.clone();
@@ -185,22 +198,22 @@ where
} }
self.cur_idxs.pop(); self.cur_idxs.pop();
self.seen_nodes.insert(scope_id.unwrap()); self.seen_nodes.insert(scope_addr);
} else { } else {
// this seems to be a fairy common code path that we could // this seems to be a fairy common code path that we could
let mut old_iter = RealChildIterator::new(old_node, &self.components); let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
let first = old_iter let first = old_iter
.next() .next()
.expect("Components should generate a placeholder root"); .expect("Components should generate a placeholder root");
// remove any leftovers // remove any leftovers
for to_remove in old_iter { for to_remove in old_iter {
self.edits.push(to_remove); self.edits.push_root(to_remove);
self.edits.remove(); self.edits.remove();
} }
// seems like we could combine this into a single instruction.... // seems like we could combine this into a single instruction....
self.edits.push(first); self.edits.push_root(first);
let meta = self.create(new_node); let meta = self.create(new_node);
self.edits.replace_with(meta.added_to_stack); self.edits.replace_with(meta.added_to_stack);
self.edits.pop(); self.edits.pop();
@@ -229,8 +242,12 @@ where
} }
// The strategy here is to pick the first possible node from the previous set and use that as our replace with root // The strategy here is to pick the first possible node from the previous set and use that as our replace with root
//
// We also walk the "real node" list to make sure all latent roots are claened up // We also walk the "real node" list to make sure all latent roots are claened up
// This covers the case any time a fragment or component shows up with pretty much anything else // This covers the case any time a fragment or component shows up with pretty much anything else
//
// This likely isn't the fastest way to go about replacing one node with a virtual node, but the "insane" cases
// are pretty rare. IE replacing a list (component or fragment) with a single node.
( (
VNodeKind::Component(_) VNodeKind::Component(_)
| VNodeKind::Fragment(_) | VNodeKind::Fragment(_)
@@ -242,12 +259,12 @@ where
| VNodeKind::Element(_), | VNodeKind::Element(_),
) => { ) => {
// Choose the node to use as the placeholder for replacewith // Choose the node to use as the placeholder for replacewith
let back_node = match old_node.kind { let back_node_id = match old_node.kind {
// We special case these two types to avoid allocating the small-vecs // We special case these two types to avoid allocating the small-vecs
VNodeKind::Element(_) | VNodeKind::Text(_) => old_node.dom_id.get(), VNodeKind::Element(_) | VNodeKind::Text(_) => root,
_ => { _ => {
let mut old_iter = RealChildIterator::new(old_node, &self.components); let mut old_iter = RealChildIterator::new(old_node, &self.vdom);
let back_node = old_iter let back_node = old_iter
.next() .next()
@@ -255,7 +272,7 @@ where
// remove any leftovers // remove any leftovers
for to_remove in old_iter { for to_remove in old_iter {
self.edits.push(to_remove); self.edits.push_root(to_remove);
self.edits.remove(); self.edits.remove();
} }
@@ -264,7 +281,7 @@ where
}; };
// replace the placeholder or first node with the nodes generated from the "new" // replace the placeholder or first node with the nodes generated from the "new"
self.edits.push(back_node); self.edits.push_root(back_node_id);
let meta = self.create(new_node); let meta = self.create(new_node);
self.edits.replace_with(meta.added_to_stack); self.edits.replace_with(meta.added_to_stack);
@@ -314,9 +331,10 @@ where
log::warn!("Creating node! ... {:#?}", node); log::warn!("Creating node! ... {:#?}", node);
match &node.kind { match &node.kind {
VNodeKind::Text(text) => { VNodeKind::Text(text) => {
let real_id = self.dom.request_available_node(); let real_id = self.vdom.reserve_node();
self.edits.create_text_node(text.text, real_id); self.edits.create_text_node(text.text, real_id);
node.dom_id.set(real_id); node.dom_id.set(Some(real_id));
CreateMeta::new(text.is_static, 1) CreateMeta::new(text.is_static, 1)
} }
VNodeKind::Element(el) => { VNodeKind::Element(el) => {
@@ -338,18 +356,18 @@ where
static_listeners: _, static_listeners: _,
} = el; } = el;
let real_id = self.dom.request_available_node(); let real_id = self.vdom.reserve_node();
if let Some(namespace) = namespace { if let Some(namespace) = namespace {
self.edits self.edits
.create_element(tag_name, Some(namespace), real_id) .create_element(tag_name, Some(namespace), real_id)
} else { } else {
self.edits.create_element(tag_name, None, real_id) self.edits.create_element(tag_name, None, real_id)
}; };
node.dom_id.set(real_id); node.dom_id.set(Some(real_id));
listeners.iter().enumerate().for_each(|(idx, listener)| { listeners.iter().enumerate().for_each(|(idx, listener)| {
log::info!("setting listener id to {:#?}", real_id); log::info!("setting listener id to {:#?}", real_id);
listener.mounted_node.set(real_id); listener.mounted_node.set(Some(real_id));
self.edits self.edits
.new_event_listener(listener.event, listener.scope, idx, real_id); .new_event_listener(listener.event, listener.scope, idx, real_id);
@@ -405,25 +423,18 @@ where
let parent_idx = self.cur_idxs.last().unwrap().clone(); let parent_idx = self.cur_idxs.last().unwrap().clone();
// Insert a new scope into our component list // Insert a new scope into our component list
let new_idx = self let new_idx = self.vdom.insert_scope_with_key(|new_idx| {
.components let parent_scope = self.get_scope(&parent_idx).unwrap();
.with(|components| { let height = parent_scope.height + 1;
components.insert_with_key(|new_idx| { Scope::new(
let parent_scope = self.components.get(parent_idx).unwrap(); caller,
let height = parent_scope.height + 1; new_idx,
Scope::new( Some(parent_idx),
caller, height,
new_idx, vcomponent.children,
Some(parent_idx), self.vdom.clone(),
height, )
self.event_queue.new_channel(height, new_idx), });
self.components.clone(),
vcomponent.children,
self.task_queue.new_submitter(),
)
})
})
.unwrap();
// This code is supposed to insert the new idx into the parent's descendent list, but it doesn't really work. // This code is supposed to insert the new idx into the parent's descendent list, but it doesn't really work.
// This is mostly used for cleanup - to remove old scopes when components are destroyed. // This is mostly used for cleanup - to remove old scopes when components are destroyed.
@@ -437,7 +448,7 @@ where
// .insert(idx); // .insert(idx);
// TODO: abstract this unsafe into the arena abstraction // TODO: abstract this unsafe into the arena abstraction
let inner: &'bump mut _ = unsafe { &mut *self.components.components.get() }; let inner: &'bump mut _ = unsafe { &mut *self.vdom.components.get() };
let new_component = inner.get_mut(new_idx).unwrap(); let new_component = inner.get_mut(new_idx).unwrap();
// Actually initialize the caller's slot with the right address // Actually initialize the caller's slot with the right address
@@ -477,10 +488,10 @@ where
} }
VNodeKind::Suspended { node: real_node } => { VNodeKind::Suspended { node: real_node } => {
let id = self.dom.request_available_node(); let id = self.vdom.reserve_node();
self.edits.create_placeholder(id); self.edits.create_placeholder(id);
node.dom_id.set(id); node.dom_id.set(Some(id));
real_node.set(id); real_node.set(Some(id));
CreateMeta::new(false, 1) CreateMeta::new(false, 1)
} }
} }
@@ -500,7 +511,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
while let Some(scope_id) = scopes_to_explore.pop() { while let Some(scope_id) = scopes_to_explore.pop() {
// If we're planning on deleting this node, then we don't need to both rendering it // If we're planning on deleting this node, then we don't need to both rendering it
self.seen_nodes.insert(scope_id); self.seen_nodes.insert(scope_id);
let scope = self.components.get(scope_id).unwrap(); let scope = self.get_scope(&scope_id).unwrap();
for child in scope.descendents.borrow().iter() { for child in scope.descendents.borrow().iter() {
// Add this node to be explored // Add this node to be explored
scopes_to_explore.push(child.clone()); scopes_to_explore.push(child.clone());
@@ -513,7 +524,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
// Delete all scopes that we found as part of this subtree // Delete all scopes that we found as part of this subtree
for node in nodes_to_delete { for node in nodes_to_delete {
log::debug!("Removing scope {:#?}", node); log::debug!("Removing scope {:#?}", node);
let _scope = self.components.try_remove(node).unwrap(); let _scope = self.vdom.try_remove(node).unwrap();
// do anything we need to do to delete the scope // do anything we need to do to delete the scope
// I think we need to run the destructors on the hooks // I think we need to run the destructors on the hooks
// TODO // TODO
@@ -527,7 +538,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
// [... node] // [... node]
// //
// The change list stack is left unchanged. // The change list stack is left unchanged.
fn diff_listeners(&mut self, old: &[Listener<'_>], new: &[Listener<'_>]) { fn diff_listeners(&mut self, old: &[&mut Listener<'_>], new: &[&mut Listener<'_>]) {
if !old.is_empty() || !new.is_empty() { if !old.is_empty() || !new.is_empty() {
// self.edits.commit_traversal(); // self.edits.commit_traversal();
} }
@@ -547,7 +558,7 @@ impl<'a, 'bump, Dom: RealDom<'bump>> DiffMachine<'a, 'bump, Dom> {
new_l.mounted_node.set(old_l.mounted_node.get()); new_l.mounted_node.set(old_l.mounted_node.get());
// if new_l.id != old_l.id { // if new_l.id != old_l.id {
// self.edits.remove_event_listener(event_type); // self.edits.remove_event_listener(event_type);
// // TODO! we need to mess with events and assign them by RealDomNode // // TODO! we need to mess with events and assign them by ElementId
// // self.edits // // self.edits
// // .update_event_listener(event_type, new_l.scope, new_l.id) // // .update_event_listener(event_type, new_l.scope, new_l.id)
// } // }
@@ -1270,7 +1281,7 @@ enum KeyedPrefixResult {
/// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like /// This iterator is useful when it's important to load the next real root onto the top of the stack for operations like
/// "InsertBefore". /// "InsertBefore".
struct RealChildIterator<'a> { struct RealChildIterator<'a> {
scopes: &'a SharedArena, scopes: &'a SharedResources,
// Heuristcally we should never bleed into 4 completely nested fragments/components // Heuristcally we should never bleed into 4 completely nested fragments/components
// Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane // Smallvec lets us stack allocate our little stack machine so the vast majority of cases are sane
@@ -1279,7 +1290,7 @@ struct RealChildIterator<'a> {
} }
impl<'a> RealChildIterator<'a> { impl<'a> RealChildIterator<'a> {
fn new(starter: &'a VNode<'a>, scopes: &'a SharedArena) -> Self { fn new(starter: &'a VNode<'a>, scopes: &'a SharedResources) -> Self {
Self { Self {
scopes, scopes,
stack: smallvec::smallvec![(0, starter)], stack: smallvec::smallvec![(0, starter)],
@@ -1288,9 +1299,9 @@ impl<'a> RealChildIterator<'a> {
} }
impl<'a> Iterator for RealChildIterator<'a> { impl<'a> Iterator for RealChildIterator<'a> {
type Item = RealDomNode; type Item = ElementId;
fn next(&mut self) -> Option<RealDomNode> { fn next(&mut self) -> Option<ElementId> {
let mut should_pop = false; let mut should_pop = false;
let mut returned_node = None; let mut returned_node = None;
let mut should_push = None; let mut should_push = None;
@@ -1304,7 +1315,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
// We've recursed INTO an element/text // We've recursed INTO an element/text
// We need to recurse *out* of it and move forward to the next // We need to recurse *out* of it and move forward to the next
should_pop = true; should_pop = true;
returned_node = Some(node.dom_id.get()); returned_node = node.dom_id.get();
} }
// If we get a fragment we push the next child // If we get a fragment we push the next child
@@ -1313,7 +1324,7 @@ impl<'a> Iterator for RealChildIterator<'a> {
if frag.children.len() == 0 { if frag.children.len() == 0 {
should_pop = true; should_pop = true;
returned_node = Some(node.dom_id.get()); returned_node = node.dom_id.get();
} }
if subcount >= frag.children.len() { if subcount >= frag.children.len() {
@@ -1325,11 +1336,16 @@ impl<'a> Iterator for RealChildIterator<'a> {
// Immediately abort suspended nodes - can't do anything with them yet // Immediately abort suspended nodes - can't do anything with them yet
// VNodeKind::Suspended => should_pop = true, // VNodeKind::Suspended => should_pop = true,
VNodeKind::Suspended { .. } => todo!(), VNodeKind::Suspended { node, .. } => {
todo!()
// *node = node.as_ref().borrow().get().expect("msg");
}
// For components, we load their root and push them onto the stack // For components, we load their root and push them onto the stack
VNodeKind::Component(sc) => { VNodeKind::Component(sc) => {
let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap(); let scope =
unsafe { self.scopes.get_scope(sc.ass_scope.get().unwrap()) }.unwrap();
// let scope = self.scopes.get(sc.ass_scope.get().unwrap()).unwrap();
// Simply swap the current node on the stack with the root of the component // Simply swap the current node on the stack with the root of the component
*node = scope.root(); *node = scope.root();
+6 -6
View File
@@ -5,7 +5,7 @@
//! //!
//! //!
use crate::{innerlude::ScopeId, RealDomNode}; use crate::{innerlude::ScopeId, ElementId};
/// 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.
/// ///
@@ -24,7 +24,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
} }
// Navigation // Navigation
pub(crate) fn push(&mut self, root: RealDomNode) { pub(crate) fn push_root(&mut self, root: ElementId) {
let id = root.as_u64(); let id = root.as_u64();
self.edits.push(PushRoot { id }); self.edits.push(PushRoot { id });
} }
@@ -61,7 +61,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
// Create // Create
#[inline] #[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: ElementId) {
let id = id.as_u64(); let id = id.as_u64();
self.edits.push(CreateTextNode { text, id }); self.edits.push(CreateTextNode { text, id });
} }
@@ -70,7 +70,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
&mut self, &mut self,
tag: &'static str, tag: &'static str,
ns: Option<&'static str>, ns: Option<&'static str>,
id: RealDomNode, id: ElementId,
) { ) {
let id = id.as_u64(); let id = id.as_u64();
match ns { match ns {
@@ -80,7 +80,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
} }
// placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom // placeholders are nodes that don't get rendered but still exist as an "anchor" in the real dom
pub(crate) fn create_placeholder(&mut self, id: RealDomNode) { pub(crate) fn create_placeholder(&mut self, id: ElementId) {
let id = id.as_u64(); let id = id.as_u64();
self.edits.push(CreatePlaceholder { id }); self.edits.push(CreatePlaceholder { id });
} }
@@ -91,7 +91,7 @@ impl<'real, 'bump> DomEditor<'real, 'bump> {
event: &'static str, event: &'static str,
scope: ScopeId, scope: ScopeId,
element_id: usize, element_id: usize,
realnode: RealDomNode, realnode: ElementId,
) { ) {
self.edits.push(NewEventListener { self.edits.push(NewEventListener {
scope, scope,
+10 -9
View File
@@ -6,7 +6,7 @@
use std::{cell::Cell, rc::Rc}; use std::{cell::Cell, rc::Rc};
use crate::innerlude::{RealDomNode, ScopeId}; use crate::innerlude::{ElementId, HeightMarker, ScopeId};
#[derive(Debug)] #[derive(Debug)]
pub struct EventTrigger { pub struct EventTrigger {
@@ -14,7 +14,7 @@ pub struct EventTrigger {
pub originator: ScopeId, 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<ElementId>,
/// The type of event /// The type of event
pub event: VirtualEvent, pub event: VirtualEvent,
@@ -72,7 +72,7 @@ impl EventTrigger {
pub fn new( pub fn new(
event: VirtualEvent, event: VirtualEvent,
scope: ScopeId, scope: ScopeId,
mounted_dom_id: Option<RealDomNode>, mounted_dom_id: Option<ElementId>,
priority: EventPriority, priority: EventPriority,
) -> Self { ) -> Self {
Self { Self {
@@ -105,6 +105,9 @@ pub enum VirtualEvent {
// image event has conflicting method types // image event has conflicting method types
// ImageEvent(event_data::ImageEvent), // ImageEvent(event_data::ImageEvent),
SetStateEvent {
height: HeightMarker,
},
// Whenever a task is ready (complete) Dioxus produces this "AsyncEvent" // Whenever a task is ready (complete) Dioxus produces this "AsyncEvent"
// //
@@ -117,10 +120,8 @@ pub enum VirtualEvent {
// These are more intrusive than the rest // These are more intrusive than the rest
SuspenseEvent { SuspenseEvent {
hook_idx: usize, hook_idx: usize,
domnode: Rc<Cell<RealDomNode>>, domnode: Rc<Cell<Option<ElementId>>>,
}, },
OtherEvent,
} }
pub mod on { pub mod on {
@@ -138,7 +139,7 @@ pub mod on {
use crate::{ use crate::{
innerlude::NodeFactory, innerlude::NodeFactory,
innerlude::{Attribute, Listener, RealDomNode, VNode}, innerlude::{Attribute, ElementId, Listener, VNode},
}; };
use std::cell::Cell; use std::cell::Cell;
@@ -180,8 +181,8 @@ pub mod on {
let bump = &c.bump(); let bump = &c.bump();
Listener { Listener {
event: stringify!($name), event: stringify!($name),
mounted_node: bump.alloc(Cell::new(RealDomNode::empty())), mounted_node: Cell::new(None),
scope: c.scope_ref.our_arena_idx, scope: c.scope.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!")
+14 -5
View File
@@ -1,14 +1,23 @@
use std::collections::HashMap; use std::collections::HashMap;
use fxhash::FxHashMap;
use crate::FC; use crate::FC;
pub(crate) struct HeuristicsEngine { /// Provides heuristics to the "SharedResources" object for improving allocation performance.
heuristics: HashMap<FcSlot, Heuristic>, ///
/// This heueristic engine records the memory footprint of bump arenas and hook lists for each component. These records are
/// then used later on to optimize the initial allocation for future components. This helps save large allocations later on
/// that would slow down the diffing and initializion process.
///
///
pub struct HeuristicsEngine {
heuristics: FxHashMap<FcSlot, Heuristic>,
} }
pub(crate) type FcSlot = *const (); pub type FcSlot = *const ();
pub(crate) struct Heuristic { pub struct Heuristic {
hooks: u32, hooks: u32,
bump_size: u64, bump_size: u64,
} }
@@ -16,7 +25,7 @@ pub(crate) struct Heuristic {
impl HeuristicsEngine { impl HeuristicsEngine {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
heuristics: HashMap::new(), heuristics: FxHashMap::default(),
} }
} }
+2 -4
View File
@@ -10,8 +10,8 @@
//! //!
pub use crate::innerlude::{ pub use crate::innerlude::{
format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, EventTrigger, LazyNodes, format_args_f, html, rsx, Context, DioxusElement, DomEdit, DomTree, ElementId, EventTrigger,
NodeFactory, Properties, RealDom, RealDomNode, ScopeId, VNode, VNodeKind, VirtualDom, LazyNodes, NodeFactory, Properties, RealDom, ScopeId, VNode, VNodeKind, VirtualDom,
VirtualEvent, FC, VirtualEvent, FC,
}; };
@@ -38,7 +38,6 @@ pub(crate) mod innerlude {
pub use crate::hooklist::*; pub use crate::hooklist::*;
pub use crate::nodes::*; pub use crate::nodes::*;
pub use crate::scope::*; pub use crate::scope::*;
pub use crate::tasks::*;
pub use crate::util::*; pub use crate::util::*;
pub use crate::virtual_dom::*; pub use crate::virtual_dom::*;
@@ -66,6 +65,5 @@ pub mod hooklist;
pub mod nodes; pub mod nodes;
pub mod scope; pub mod scope;
pub mod signals; pub mod signals;
pub mod tasks;
pub mod util; pub mod util;
pub mod virtual_dom; pub mod virtual_dom;
+41 -26
View File
@@ -5,7 +5,7 @@
//! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick. //! These VNodes should be *very* cheap and *very* fast to construct - building a full tree should be insanely quick.
use crate::{ use crate::{
events::VirtualEvent, events::VirtualEvent,
innerlude::{Context, DomTree, Properties, RealDomNode, Scope, ScopeId, FC}, innerlude::{empty_cell, Context, DomTree, ElementId, Properties, Scope, ScopeId, FC},
}; };
use std::{ use std::{
cell::Cell, cell::Cell,
@@ -16,7 +16,12 @@ use std::{
pub struct VNode<'src> { pub struct VNode<'src> {
pub kind: VNodeKind<'src>, pub kind: VNodeKind<'src>,
pub(crate) dom_id: Cell<RealDomNode>,
///
/// ElementId supports NonZero32 and Cell is zero cost, so the size of this field is unaffected
///
///
pub(crate) dom_id: Cell<Option<ElementId>>,
pub(crate) key: Option<&'src str>, pub(crate) key: Option<&'src str>,
} }
impl VNode<'_> { impl VNode<'_> {
@@ -39,7 +44,7 @@ pub enum VNodeKind<'src> {
Component(&'src VComponent<'src>), Component(&'src VComponent<'src>),
Suspended { node: Rc<Cell<RealDomNode>> }, Suspended { node: Rc<Cell<Option<ElementId>>> },
} }
pub struct VText<'src> { pub struct VText<'src> {
@@ -50,7 +55,6 @@ pub struct VText<'src> {
pub struct VFragment<'src> { pub struct VFragment<'src> {
pub children: &'src [VNode<'src>], pub children: &'src [VNode<'src>],
pub is_static: bool, pub is_static: bool,
pub is_error: bool,
} }
pub trait DioxusElement { pub trait DioxusElement {
@@ -72,7 +76,7 @@ pub struct VElement<'a> {
pub namespace: Option<&'static str>, pub namespace: Option<&'static str>,
pub static_listeners: bool, pub static_listeners: bool,
pub listeners: &'a [Listener<'a>], pub listeners: &'a [&'a mut Listener<'a>],
pub static_attrs: bool, pub static_attrs: bool,
pub attributes: &'a [Attribute<'a>], pub attributes: &'a [Attribute<'a>],
@@ -105,9 +109,9 @@ pub struct Listener<'bump> {
pub scope: ScopeId, pub scope: ScopeId,
pub mounted_node: &'bump mut Cell<RealDomNode>, pub mounted_node: Cell<Option<ElementId>>,
pub(crate) callback: &'bump dyn FnMut(VirtualEvent), pub(crate) callback: &'bump mut dyn FnMut(VirtualEvent),
} }
/// Virtual Components for custom user-defined components /// Virtual Components for custom user-defined components
@@ -136,20 +140,31 @@ pub struct VComponent<'src> {
/// This struct adds metadata to the final VNode about listeners, attributes, and children /// This struct adds metadata to the final VNode about listeners, attributes, and children
#[derive(Copy, Clone)] #[derive(Copy, Clone)]
pub struct NodeFactory<'a> { pub struct NodeFactory<'a> {
pub scope_ref: &'a Scope, pub scope: &'a Scope,
pub listener_id: &'a Cell<usize>, pub listener_id: &'a Cell<usize>,
} }
impl<'a> NodeFactory<'a> { impl<'a> NodeFactory<'a> {
#[inline] #[inline]
pub fn bump(&self) -> &'a bumpalo::Bump { pub fn bump(&self) -> &'a bumpalo::Bump {
&self.scope_ref.cur_frame().bump &self.scope.cur_frame().bump
}
pub fn unstable_place_holder() -> VNode<'static> {
VNode {
dom_id: empty_cell(),
key: None,
kind: VNodeKind::Text(VText {
text: "",
is_static: true,
}),
}
} }
/// Used in a place or two to make it easier to build vnodes from dummy text /// Used in a place or two to make it easier to build vnodes from dummy text
pub fn static_text(text: &'static str) -> VNode { pub fn static_text(&self, text: &'static str) -> VNode<'a> {
VNode { VNode {
dom_id: RealDomNode::empty_cell(), dom_id: empty_cell(),
key: None, key: None,
kind: VNodeKind::Text(VText { kind: VNodeKind::Text(VText {
text, text,
@@ -178,7 +193,7 @@ impl<'a> NodeFactory<'a> {
pub fn text(&self, args: Arguments) -> VNode<'a> { pub fn text(&self, args: Arguments) -> VNode<'a> {
let (text, is_static) = self.raw_text(args); let (text, is_static) = self.raw_text(args);
VNode { VNode {
dom_id: RealDomNode::empty_cell(), dom_id: empty_cell(),
key: None, key: None,
kind: VNodeKind::Text(VText { text, is_static }), kind: VNodeKind::Text(VText { text, is_static }),
} }
@@ -187,7 +202,7 @@ impl<'a> NodeFactory<'a> {
pub fn element( pub fn element(
&self, &self,
el: impl DioxusElement, el: impl DioxusElement,
listeners: &'a mut [Listener<'a>], listeners: &'a mut [&'a mut Listener<'a>],
attributes: &'a [Attribute<'a>], attributes: &'a [Attribute<'a>],
children: &'a [VNode<'a>], children: &'a [VNode<'a>],
key: Option<&'a str>, key: Option<&'a str>,
@@ -206,7 +221,7 @@ impl<'a> NodeFactory<'a> {
&self, &self,
tag: &'static str, tag: &'static str,
namespace: Option<&'static str>, namespace: Option<&'static str>,
listeners: &'a mut [Listener], listeners: &'a mut [&'a mut Listener<'a>],
attributes: &'a [Attribute], attributes: &'a [Attribute],
children: &'a [VNode<'a>], children: &'a [VNode<'a>],
key: Option<&'a str>, key: Option<&'a str>,
@@ -215,15 +230,17 @@ impl<'a> NodeFactory<'a> {
// TODO: this code shouldn't necessarily be here of all places // TODO: this code shouldn't necessarily be here of all places
// It would make more sense to do this in diffing // It would make more sense to do this in diffing
let mut queue = self.scope_ref.listeners.borrow_mut(); let mut queue = self.scope.listeners.borrow_mut();
for listener in listeners.iter_mut() { for listener in listeners.iter_mut() {
let mounted = listener.mounted_node as *mut _; let raw_listener = &mut **listener;
let callback = listener.callback as *const _ as *mut _;
queue.push((mounted, callback)) let long_listener: &mut Listener<'static> =
unsafe { std::mem::transmute(raw_listener) };
queue.push(long_listener as *mut _)
} }
VNode { VNode {
dom_id: RealDomNode::empty_cell(), dom_id: empty_cell(),
key, key,
kind: VNodeKind::Element(self.bump().alloc(VElement { kind: VNodeKind::Element(self.bump().alloc(VElement {
tag_name: tag, tag_name: tag,
@@ -242,10 +259,10 @@ impl<'a> NodeFactory<'a> {
pub fn suspended() -> VNode<'static> { pub fn suspended() -> VNode<'static> {
VNode { VNode {
dom_id: RealDomNode::empty_cell(), dom_id: empty_cell(),
key: None, key: None,
kind: VNodeKind::Suspended { kind: VNodeKind::Suspended {
node: Rc::new(RealDomNode::empty_cell()), node: Rc::new(empty_cell()),
}, },
} }
} }
@@ -322,7 +339,7 @@ impl<'a> NodeFactory<'a> {
VNode { VNode {
key, key,
dom_id: Cell::new(RealDomNode::empty()), dom_id: empty_cell(),
kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent { kind: VNodeKind::Component(self.bump().alloc_with(|| VComponent {
user_fc, user_fc,
comparator, comparator,
@@ -383,12 +400,11 @@ To help you identify where this error is coming from, we've generated a backtrac
} }
} }
VNode { VNode {
dom_id: RealDomNode::empty_cell(), dom_id: empty_cell(),
key: None, key: None,
kind: VNodeKind::Fragment(VFragment { kind: VNodeKind::Fragment(VFragment {
children: nodes.into_bump_slice(), children: nodes.into_bump_slice(),
is_static: false, is_static: false,
is_error: false,
}), }),
} }
} }
@@ -444,7 +460,6 @@ impl<'a> IntoVNode<'a> for &VNode<'a> {
VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment { VNodeKind::Fragment(fragment) => VNodeKind::Fragment(VFragment {
children: fragment.children, children: fragment.children,
is_static: fragment.is_static, is_static: fragment.is_static,
is_error: false,
}), }),
VNodeKind::Component(component) => VNodeKind::Component(component), VNodeKind::Component(component) => VNodeKind::Component(component),
@@ -535,7 +550,7 @@ impl<'a> IntoVNode<'a> for Option<VNode<'a>> {
impl IntoVNode<'_> for &'static str { impl IntoVNode<'_> for &'static str {
fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> { fn into_vnode<'a>(self, cx: NodeFactory<'a>) -> VNode<'a> {
NodeFactory::static_text(self) cx.static_text(self)
} }
} }
impl IntoVNode<'_> for Arguments<'_> { impl IntoVNode<'_> for Arguments<'_> {
+31 -54
View File
@@ -18,40 +18,30 @@ use std::{
/// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever /// We expose the `Scope` type so downstream users can traverse the Dioxus VirtualDOM for whatever
/// usecase they might have. /// usecase they might have.
pub struct Scope { pub struct Scope {
// Book-keeping about the arena // Book-keeping about our spot in the arena
pub(crate) parent_idx: Option<ScopeId>, pub(crate) parent_idx: Option<ScopeId>,
pub(crate) descendents: RefCell<HashSet<ScopeId>>,
pub(crate) our_arena_idx: ScopeId, pub(crate) our_arena_idx: ScopeId,
pub(crate) height: u32, pub(crate) height: u32,
pub(crate) descendents: RefCell<HashSet<ScopeId>>,
// Nodes // Nodes
// an internal, highly efficient storage of vnodes // an internal, highly efficient storage of vnodes
pub(crate) frames: ActiveFrame, pub(crate) frames: ActiveFrame,
pub(crate) child_nodes: &'static [VNode<'static>],
pub(crate) caller: Rc<WrappedCaller>, pub(crate) caller: Rc<WrappedCaller>,
pub(crate) child_nodes: &'static [VNode<'static>],
// Listeners // Listeners
pub(crate) listeners: RefCell<Vec<(*mut Cell<RealDomNode>, *mut dyn FnMut(VirtualEvent))>>, pub(crate) listeners: RefCell<Vec<*mut Listener<'static>>>,
pub(crate) listener_idx: Cell<usize>, pub(crate) listener_idx: Cell<usize>,
// State // State
pub(crate) hooks: HookList, pub(crate) hooks: HookList,
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>, pub(crate) shared_contexts: RefCell<HashMap<TypeId, Rc<dyn Any>>>,
// Events // A reference to the resources shared by all the comonents
pub(crate) event_channel: Rc<dyn Fn() + 'static>, pub(crate) vdom: SharedResources,
// Tasks
pub(crate) task_submitter: TaskSubmitter,
// A reference to the list of components.
// This lets us traverse the component list whenever we need to access our parent or children.
pub(crate) arena_link: SharedArena,
} }
// The type of the channel function
type EventChannel = Rc<dyn Fn()>;
// The type of closure that wraps calling components // The type of closure that wraps calling components
pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>; pub type WrappedCaller = dyn for<'b> Fn(&'b Scope) -> DomTree<'b>;
@@ -68,13 +58,16 @@ 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: ScopeId, arena_idx: ScopeId,
parent: Option<ScopeId>, parent: Option<ScopeId>,
height: u32, height: u32,
event_channel: EventChannel,
arena_link: SharedArena,
child_nodes: &'creator_node [VNode<'creator_node>], child_nodes: &'creator_node [VNode<'creator_node>],
task_submitter: TaskSubmitter,
vdom: SharedResources,
) -> Self { ) -> Self {
let child_nodes = unsafe { std::mem::transmute(child_nodes) }; let child_nodes = unsafe { std::mem::transmute(child_nodes) };
Self { Self {
@@ -83,9 +76,7 @@ impl Scope {
parent_idx: parent, parent_idx: parent,
our_arena_idx: arena_idx, our_arena_idx: arena_idx,
height, height,
event_channel, vdom,
arena_link,
task_submitter,
listener_idx: Default::default(), listener_idx: Default::default(),
frames: ActiveFrame::new(), frames: ActiveFrame::new(),
hooks: Default::default(), hooks: Default::default(),
@@ -113,7 +104,7 @@ impl Scope {
// Remove all the outdated listeners // Remove all the outdated listeners
// This is a very dangerous operation // This is a very dangerous operation
let next_frame = self.frames.old_frame_mut(); let next_frame = self.frames.prev_frame_mut();
next_frame.bump.reset(); next_frame.bump.reset();
self.listeners.borrow_mut().clear(); self.listeners.borrow_mut().clear();
@@ -132,7 +123,7 @@ impl Scope {
} }
Some(new_head) => { Some(new_head) => {
// the user's component succeeded. We can safely cycle to the next frame // the user's component succeeded. We can safely cycle to the next frame
self.frames.old_frame_mut().head_node = unsafe { std::mem::transmute(new_head) }; self.frames.prev_frame_mut().head_node = unsafe { std::mem::transmute(new_head) };
self.frames.cycle_frame(); self.frames.cycle_frame();
Ok(()) Ok(())
} }
@@ -162,33 +153,27 @@ impl Scope {
let listners = self.listeners.borrow_mut(); let listners = self.listeners.borrow_mut();
let raw_listener = listners.iter().find(|(domptr, _)| { let raw_listener = listners.iter().find(|lis| {
let search = unsafe { &**domptr }; let search = unsafe { &mut ***lis };
let search_id = search.get(); let search_id = search.mounted_node.get();
log::info!("searching listener {:#?}", search_id); log::info!("searching listener {:#?}", search_id);
match real_node_id {
Some(e) => search_id == e, match (real_node_id, search_id) {
None => false, (Some(e), Some(search_id)) => search_id == e,
_ => false,
} }
}); });
match raw_listener { if let Some(raw_listener) = raw_listener {
Some((_node, listener)) => unsafe { let listener = unsafe { &mut **raw_listener };
// TODO: Don'tdo a linear scan! Do a hashmap lookup! It'll be faster! (listener.callback)(event);
let listener_fn = &mut **listener; } else {
listener_fn(event); log::warn!("An event was triggered but there was no listener to handle it");
},
None => todo!(),
} }
Ok(()) Ok(())
} }
pub(crate) fn submit_task(&self, task: FiberTask) {
log::debug!("Task submitted into scope");
(self.task_submitter)(task);
}
#[inline] #[inline]
pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> { pub(crate) fn next_frame<'bump>(&'bump self) -> &'bump VNode<'bump> {
self.frames.current_head_node() self.frames.current_head_node()
@@ -207,18 +192,10 @@ impl Scope {
/// Get the root VNode of this component /// Get the root VNode of this component
#[inline] #[inline]
pub fn root<'a>(&'a self) -> &'a VNode<'a> { pub fn root<'a>(&'a self) -> &'a VNode<'a> {
&self.frames.cur_frame().head_node &self.frames.current_head_node()
} }
} #[inline]
pub fn child_nodes<'a>(&'a self) -> &'a [VNode<'a>] {
pub fn errored_fragment() -> VNode<'static> { unsafe { std::mem::transmute(self.child_nodes) }
VNode {
dom_id: RealDomNode::empty_cell(),
key: None,
kind: VNodeKind::Fragment(VFragment {
children: &[],
is_static: false,
is_error: true,
}),
} }
} }
-137
View File
@@ -1,137 +0,0 @@
//! The TaskQueue serves as a centralized async store for all tasks in Dioxus.
//! When a component renders, it may submit an async task to the queue.
//!
//! Then the task complete, it is emitted from the virtual dom in the event loop, which is then fed back into the virtualdom
//! as an event trigger.
//!
//! When a component is scheduled to re-render, the awaing task must be dumped from the queue.
//!
//! This is all pretty unsafe stuff.
//! The major invariant here is that tasks that enter the queue may be invalidated during transitions.
use std::{
cell::Cell,
sync::{Arc, RwLock},
};
use futures_util::{stream::FuturesUnordered, Future, Stream, StreamExt};
use slotmap::{DefaultKey, SlotMap};
use crate::innerlude::{EventTrigger, FiberTask, ScopeId};
pub type TaskSubmitter = Arc<dyn Fn(FiberTask)>;
pub struct TaskQueue {
slots: Arc<RwLock<FuturesUnordered<FiberTask>>>,
// slots: Arc<RwLock<SlotMap<DefaultKey, DTask>>>,
submitter: TaskSubmitter,
}
impl TaskQueue {
pub fn new() -> Self {
let slots = Arc::new(RwLock::new(FuturesUnordered::new()));
let slots2 = slots.clone();
let submitter = Arc::new(move |task| {
let mut slots = slots2.write().unwrap();
log::debug!("Task submitted into global task queue");
slots.push(task);
});
Self { slots, submitter }
}
pub fn new_submitter(&self) -> TaskSubmitter {
self.submitter.clone()
}
pub fn submit_task(&mut self, task: FiberTask) {
self.slots.write().unwrap().push(task);
// TaskHandle { key }
}
pub fn is_empty(&self) -> bool {
self.slots.read().unwrap().is_empty()
}
pub fn len(&self) -> usize {
self.slots.read().unwrap().len()
}
pub async fn next(&mut self) -> Option<EventTrigger> {
let mut slots = self.slots.write().unwrap();
slots.next().await
}
}
// impl Stream for TaskQueue {
// type Item = EventTrigger;
// /// We can never be finished polling
// fn poll_next(
// self: Pin<&mut Self>,
// cx: &mut std::task::Context<'_>,
// ) -> std::task::Poll<Option<Self::Item>> {
// // let yield_every = self.len();
// // let mut polled = 0;
// let mut slots = self.slots.write().unwrap();
// for (_key, slot) in slots.iter_mut() {
// if slot.dead.get() {
// continue;
// }
// let r = slot.fut;
// // let fut = unsafe { &mut *r };
// // use futures::{future::Future, poll, FutureExt};
// let f2 = fut.as_mut();
// let w = cx.waker();
// let mut cx = Context::from_waker(&w);
// // Pin::new_unchecked(pointer)
// // use std::future::Future;
// match f2.poll(&mut cx) {
// Poll::Ready(_) => {
// let trigger = EventTrigger::new_from_task(slot.originator);
// slot.dead.set(true);
// return Poll::Ready(Some(trigger));
// }
// Poll::Pending => continue,
// }
// }
// // we tried polling every active task.
// // give up and relinquish controlto the parent
// // We have polled a large number of futures in a row without yielding.
// // To ensure we do not starve other tasks waiting on the executor,
// // we yield here, but immediately wake ourselves up to continue.
// // cx.waker().wake_by_ref();
// return Poll::Pending;
// }
// }
pub struct TaskHandle {
key: DefaultKey,
}
pub struct DTask {
fut: FiberTask,
originator: ScopeId,
dead: Cell<bool>,
}
impl DTask {
pub fn new(fut: FiberTask, originator: ScopeId) -> Self {
Self {
fut,
originator,
dead: Cell::new(false),
}
}
pub fn debug_new(fut: FiberTask) -> Self {
let originator = ScopeId::default();
Self {
fut,
originator,
dead: Cell::new(false),
}
}
}
+7 -67
View File
@@ -1,35 +1,11 @@
use std::{ use std::cell::Cell;
cell::{Cell, RefCell, RefMut},
rc::Rc,
};
use futures_util::StreamExt;
use slotmap::{DefaultKey, Key, KeyData};
use crate::innerlude::*; use crate::innerlude::*;
#[derive(PartialEq, Debug, Clone, Default)] // create a cell with a "none" value
pub struct EventQueue { #[inline]
pub queue: Rc<RefCell<Vec<HeightMarker>>>, pub fn empty_cell() -> Cell<Option<ElementId>> {
} Cell::new(None as Option<ElementId>)
impl EventQueue {
pub fn new_channel(&self, height: u32, idx: ScopeId) -> Rc<dyn Fn()> {
let inner = self.clone();
let marker = HeightMarker { height, idx };
Rc::new(move || {
log::debug!("channel updated {:#?}", marker);
inner.queue.as_ref().borrow_mut().push(marker)
})
}
pub fn sort_unstable(&self) {
self.queue.borrow_mut().sort_unstable()
}
pub fn borrow_mut(&self) -> RefMut<Vec<HeightMarker>> {
self.queue.borrow_mut()
}
} }
/// A helper type that lets scopes be ordered by their height /// A helper type that lets scopes be ordered by their height
@@ -51,41 +27,10 @@ impl PartialOrd for HeightMarker {
} }
} }
/// The `RealDomNode` is an ID handle that corresponds to a foreign DOM node. pub struct DebugDom {}
///
/// "u64" was chosen for two reasons
/// - 0 cost hashing
/// - use with slotmap and other versioned slot arenas
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
#[derive(Clone, Copy, Debug, PartialEq)]
pub struct RealDomNode(pub u64);
impl RealDomNode {
#[inline]
pub fn empty() -> Self {
Self(u64::MIN)
}
#[inline]
pub fn empty_cell() -> Cell<Self> {
Cell::new(Self::empty())
}
#[inline]
pub fn from_u64(id: u64) -> Self {
Self(id)
}
#[inline]
pub fn as_u64(&self) -> u64 {
self.0
}
}
pub struct DebugDom {
counter: u64,
}
impl DebugDom { impl DebugDom {
pub fn new() -> Self { pub fn new() -> Self {
Self { counter: 0 } Self {}
} }
} }
@@ -93,9 +38,4 @@ impl<'a> RealDom<'a> for DebugDom {
fn raw_node_as_any(&self) -> &mut dyn std::any::Any { fn raw_node_as_any(&self) -> &mut dyn std::any::Any {
todo!() todo!()
} }
fn request_available_node(&mut self) -> RealDomNode {
self.counter += 1;
RealDomNode::from_u64(self.counter)
}
} }
+51 -81
View File
@@ -19,8 +19,7 @@
//! This module includes just the barebones for a complete VirtualDOM API. //! This module includes just the barebones for a complete VirtualDOM API.
//! Additional functionality is defined in the respective files. //! Additional functionality is defined in the respective files.
use crate::tasks::TaskQueue; use crate::{arena::SharedResources, innerlude::*};
use crate::{arena::SharedArena, innerlude::*};
use slotmap::DefaultKey; use slotmap::DefaultKey;
use slotmap::SlotMap; use slotmap::SlotMap;
@@ -30,10 +29,6 @@ use std::any::TypeId;
use std::cell::RefCell; use std::cell::RefCell;
use std::pin::Pin; use std::pin::Pin;
slotmap::new_key_type! {
pub struct ScopeId;
}
/// 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.
/// ///
@@ -49,7 +44,7 @@ pub struct VirtualDom {
/// ///
/// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas /// This is wrapped in an UnsafeCell because we will need to get mutable access to unique values in unique bump arenas
/// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually. /// and rusts's guartnees cannot prove that this is safe. We will need to maintain the safety guarantees manually.
pub components: SharedArena, pub shared: SharedResources,
/// 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)
@@ -57,21 +52,12 @@ pub struct VirtualDom {
pub triggers: RefCell<Vec<EventTrigger>>, pub triggers: RefCell<Vec<EventTrigger>>,
/// All components dump their updates into a queue to be processed // for managing the props that were used to create the dom
pub event_queue: EventQueue,
pub tasks: TaskQueue,
heuristics: HeuristicsEngine,
root_props: std::pin::Pin<Box<dyn std::any::Any>>,
/// 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
/// match the props used to create the VirtualDom.
#[doc(hidden)] #[doc(hidden)]
_root_prop_type: std::any::TypeId, _root_prop_type: std::any::TypeId,
#[doc(hidden)]
_root_props: std::pin::Pin<Box<dyn std::any::Any>>,
} }
// ====================================== // ======================================
@@ -142,37 +128,22 @@ impl VirtualDom {
/// let dom = VirtualDom::new(Example); /// let dom = VirtualDom::new(Example);
/// ``` /// ```
pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self { pub fn new_with_props<P: Properties + 'static>(root: FC<P>, root_props: P) -> Self {
let components = SharedArena::new(SlotMap::<ScopeId, Scope>::with_key()); let components = SharedResources::new();
let root_props: Pin<Box<dyn Any>> = Box::pin(root_props); let root_props: Pin<Box<dyn Any>> = Box::pin(root_props);
let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P; let props_ptr = root_props.as_ref().downcast_ref::<P>().unwrap() as *const P;
// Build a funnel for hooks to send their updates into. The `use_hook` method will call into the update funnel.
let event_queue = EventQueue::default();
let _event_queue = event_queue.clone();
let link = components.clone(); let link = components.clone();
let tasks = TaskQueue::new(); let base_scope = components.insert_scope_with_key(move |myidx| {
let submitter = tasks.new_submitter(); let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
Scope::new(caller, myidx, None, 0, &[], link)
let base_scope = components });
.with(|arena| {
arena.insert_with_key(move |myidx| {
let event_channel = _event_queue.new_channel(0, myidx);
let caller = NodeFactory::create_component_caller(root, props_ptr as *const _);
Scope::new(caller, myidx, None, 0, event_channel, link, &[], submitter)
})
})
.unwrap();
Self { Self {
base_scope, base_scope,
event_queue, _root_props: root_props,
components, shared: components,
root_props,
tasks,
heuristics: HeuristicsEngine::new(),
triggers: Default::default(), triggers: Default::default(),
_root_prop_type: TypeId::of::<P>(), _root_prop_type: TypeId::of::<P>(),
} }
@@ -209,29 +180,28 @@ impl VirtualDom {
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch /// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom rom scratch
/// ///
/// Currently this doesn't do what we want it to do
///
/// The diff machine expects the RealDom's stack to be the root of the application /// The diff machine expects the RealDom's stack to be the root of the application
pub fn rebuild<'s, Dom: RealDom<'s>>( pub fn rebuild<'s, Dom: RealDom<'s>>(
&'s mut self, &'s mut self,
realdom: &mut Dom, realdom: &mut Dom,
edits: &mut Vec<DomEdit<'s>>, edits: &mut Vec<DomEdit<'s>>,
) -> Result<()> { ) -> Result<()> {
let mut diff_machine = DiffMachine::new( let mut diff_machine = DiffMachine::new(edits, realdom, self.base_scope, &self.shared);
edits,
realdom,
&self.components,
self.base_scope,
self.event_queue.clone(),
&self.tasks,
);
let cur_component = self.components.get_mut(self.base_scope).unwrap(); let cur_component = diff_machine
.get_scope_mut(&self.base_scope)
.expect("The base scope should never be moved");
// We run the component. If it succeeds, then we can diff it and add the changes to the dom. // We run the component. If it succeeds, then we can diff it and add the changes to the dom.
if cur_component.run_scope().is_ok() { if cur_component.run_scope().is_ok() {
let meta = diff_machine.create(cur_component.next_frame()); let meta = diff_machine.create(cur_component.next_frame());
diff_machine.edits.append_children(meta.added_to_stack); diff_machine.edits.append_children(meta.added_to_stack);
} else {
// todo: should this be a hard error?
log::warn!(
"Component failed to run succesfully during rebuild.
This does not result in a failed rebuild, but indicates a logic failure within your app."
);
} }
Ok(()) Ok(())
@@ -242,10 +212,9 @@ impl VirtualDom {
/// ///
/// ///
/// ///
pub fn queue_event(&self, trigger: EventTrigger) -> Result<()> { pub fn queue_event(&self, trigger: EventTrigger) {
let mut triggers = self.triggers.borrow_mut(); let mut triggers = self.triggers.borrow_mut();
triggers.push(trigger); triggers.push(trigger);
Ok(())
} }
/// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered. /// This method is the most sophisticated way of updating the virtual dom after an external event has been triggered.
@@ -298,26 +267,18 @@ impl VirtualDom {
) -> Result<()> { ) -> Result<()> {
let trigger = self.triggers.borrow_mut().pop().expect("failed"); let trigger = self.triggers.borrow_mut().pop().expect("failed");
let mut diff_machine = DiffMachine::new( let mut diff_machine = DiffMachine::new(edits, realdom, trigger.originator, &self.shared);
edits,
realdom,
&self.components,
trigger.originator,
self.event_queue.clone(),
&self.tasks,
);
match &trigger.event { match &trigger.event {
VirtualEvent::OtherEvent => todo!(),
// Nothing yet // Nothing yet
VirtualEvent::AsyncEvent { .. } => {} VirtualEvent::AsyncEvent { .. } => {}
// Suspense Events! A component's suspended node is updated // Suspense Events! A component's suspended node is updated
VirtualEvent::SuspenseEvent { hook_idx, domnode } => { VirtualEvent::SuspenseEvent { hook_idx, domnode } => {
let scope = self.components.get_mut(trigger.originator).unwrap(); // Safety: this handler is the only thing that can mutate shared items at this moment in tim
let scope = diff_machine.get_scope_mut(&trigger.originator).unwrap();
// safety: we are sure that there are no other references to the inner content of this hook // safety: we are sure that there are no other references to the inner content of suspense hooks
let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap(); let hook = unsafe { scope.hooks.get_mut::<SuspenseHook>(*hook_idx) }.unwrap();
let cx = Context { scope, props: &() }; let cx = Context { scope, props: &() };
@@ -325,17 +286,24 @@ impl VirtualDom {
// generate the new node! // generate the new node!
let nodes: Option<VNode<'s>> = (&hook.callback)(scx); let nodes: Option<VNode<'s>> = (&hook.callback)(scx);
let nodes = nodes.unwrap_or_else(|| errored_fragment()); match nodes {
let nodes = scope.cur_frame().bump.alloc(nodes); None => {
log::warn!("Suspense event came through, but there was no mounted node to update >:(");
}
Some(nodes) => {
let nodes = scope.cur_frame().bump.alloc(nodes);
// push the old node's root onto the stack // push the old node's root onto the stack
diff_machine.edits.push(domnode.get()); let real_id = domnode.get().ok_or(Error::NotMounted)?;
diff_machine.edits.push_root(real_id);
// push these new nodes onto the diff machines stack // push these new nodes onto the diff machines stack
let meta = diff_machine.create(&*nodes); let meta = diff_machine.create(&*nodes);
// replace the placeholder with the new nodes we just pushed on the stack // replace the placeholder with the new nodes we just pushed on the stack
diff_machine.edits.replace_with(meta.added_to_stack); diff_machine.edits.replace_with(meta.added_to_stack);
}
}
} }
// This is the "meat" of our cooperative scheduler // This is the "meat" of our cooperative scheduler
@@ -343,12 +311,12 @@ impl VirtualDom {
// //
// We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished // We use the reconciler to request new IDs and then commit/uncommit the IDs when the scheduler is finished
_ => { _ => {
self.components diff_machine
.get_mut(trigger.originator) .get_scope_mut(&trigger.originator)
.map(|f| f.call_listener(trigger)); .map(|f| f.call_listener(trigger));
// Now, there are events in the queue // Now, there are events in the queue
let mut updates = self.event_queue.queue.as_ref().borrow_mut(); let mut updates = self.shared.borrow_queue();
// Order the nodes by their height, we want the nodes with the smallest depth on top // Order the nodes by their height, we want the nodes with the smallest depth on top
// This prevents us from running the same component multiple times // This prevents us from running the same component multiple times
@@ -370,8 +338,10 @@ impl VirtualDom {
diff_machine.seen_nodes.insert(update.idx.clone()); diff_machine.seen_nodes.insert(update.idx.clone());
// Start a new mutable borrow to components // Start a new mutable borrow to components
// We are guaranteeed that this scope is unique because we are tracking which nodes have modified // We are guaranteeed that this scope is unique because we are tracking which nodes have modified in the diff machine
let cur_component = self.components.get_mut(update.idx).unwrap(); let cur_component = diff_machine
.get_scope_mut(&update.idx)
.expect("Failed to find scope or borrow would be aliasing");
if cur_component.run_scope().is_ok() { if cur_component.run_scope().is_ok() {
let (old, new) = (cur_component.old_frame(), cur_component.next_frame()); let (old, new) = (cur_component.old_frame(), cur_component.next_frame());
@@ -385,7 +355,7 @@ impl VirtualDom {
} }
pub fn base_scope(&self) -> &Scope { pub fn base_scope(&self) -> &Scope {
self.components.get(self.base_scope).unwrap() unsafe { self.shared.get_scope(self.base_scope).unwrap() }
} }
} }
+2 -2
View File
@@ -21,7 +21,7 @@ pub fn render_vdom_scope(vdom: &VirtualDom, scope: ScopeId) -> Option<String> {
"{:}", "{:}",
TextRenderer { TextRenderer {
cfg: SsrConfig::default(), cfg: SsrConfig::default(),
root: vdom.components.get(scope).unwrap().root(), root: vdom.shared.get_scope(scope).unwrap().root(),
vdom: Some(vdom) vdom: Some(vdom)
} }
)) ))
@@ -163,7 +163,7 @@ impl<'a> TextRenderer<'a> {
VNodeKind::Component(vcomp) => { VNodeKind::Component(vcomp) => {
let idx = vcomp.ass_scope.get().unwrap(); let idx = vcomp.ass_scope.get().unwrap();
if let Some(vdom) = self.vdom { if let Some(vdom) = self.vdom {
let new_node = vdom.components.get(idx).unwrap().root(); let new_node = vdom.shared.get_scope(idx).unwrap().root();
self.html_render(new_node, f, il + 1)?; self.html_render(new_node, f, il + 1)?;
} }
} }
+16 -11
View File
@@ -13,7 +13,7 @@ use web_sys::{
pub struct WebsysDom { pub struct WebsysDom {
pub stack: Stack, pub stack: Stack,
nodes: slotmap::SlotMap<DefaultKey, Option<Node>>, nodes: slotmap::SlotMap<DefaultKey, Node>,
document: Document, document: Document,
root: Element, root: Element,
@@ -51,7 +51,7 @@ impl WebsysDom {
let mut nodes = slotmap::SlotMap::with_capacity(1000); let mut nodes = slotmap::SlotMap::with_capacity(1000);
let root_id = nodes.insert(Some(root.clone().dyn_into::<Node>().unwrap())); let root_id = nodes.insert(root.clone().dyn_into::<Node>().unwrap());
Self { Self {
stack: Stack::with_capacity(10), stack: Stack::with_capacity(10),
@@ -101,11 +101,16 @@ impl WebsysDom {
let key = DefaultKey::from(KeyData::from_ffi(root)); let key = DefaultKey::from(KeyData::from_ffi(root));
let domnode = self.nodes.get_mut(key); let domnode = self.nodes.get_mut(key);
let domnode = domnode.unwrap().as_mut().unwrap(); let real_node: Node = match domnode {
Some(n) => n.clone(),
None => todo!(),
};
// let domnode = domnode.unwrap().as_mut().unwrap();
// .expect(&format!("Failed to pop know root: {:#?}", key)) // .expect(&format!("Failed to pop know root: {:#?}", key))
// .unwrap(); // .unwrap();
self.stack.push(domnode.clone()); self.stack.push(real_node);
} }
// drop the node off the stack // drop the node off the stack
fn pop(&mut self) { fn pop(&mut self) {
@@ -202,7 +207,7 @@ impl WebsysDom {
*self *self
.nodes .nodes
.get_mut(DefaultKey::from(KeyData::from_ffi(id))) .get_mut(DefaultKey::from(KeyData::from_ffi(id)))
.unwrap() = Some(textnode); .unwrap() = textnode;
} }
fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) { fn create_element(&mut self, tag: &str, ns: Option<&'static str>, id: u64) {
@@ -224,7 +229,7 @@ impl WebsysDom {
let id = DefaultKey::from(KeyData::from_ffi(id)); let id = DefaultKey::from(KeyData::from_ffi(id));
self.stack.push(el.clone()); self.stack.push(el.clone());
*self.nodes.get_mut(id).unwrap() = Some(el); *self.nodes.get_mut(id).unwrap() = el;
// let nid = self.node_counter.?next(); // let nid = self.node_counter.?next();
// let nid = self.nodes.insert(el).data().as_ffi(); // let nid = self.nodes.insert(el).data().as_ffi();
// log::debug!("Called [`create_element`]: {}, {:?}", tag, nid); // log::debug!("Called [`create_element`]: {}, {:?}", tag, nid);
@@ -344,11 +349,11 @@ impl WebsysDom {
} }
impl<'a> dioxus_core::diff::RealDom<'a> for WebsysDom { 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.data().as_ffi()) // 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 {
todo!() todo!()