wip: think about dyn any for ScopeState
This commit is contained in:
1 parent
5253ce6b65
commit
12808ec0aa
5 files changed
+119
-139
No files matched your search
@@ -158,14 +158,3 @@ impl<T> EventHandler<'_, T> {
|
|||||||
self.callback.replace(None);
|
self.callback.replace(None);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn matches_slice() {
|
|
||||||
let left = &[1, 2, 3];
|
|
||||||
let right = &[1, 2, 3, 4, 5];
|
|
||||||
assert!(is_path_ascendant(left, right));
|
|
||||||
assert!(!is_path_ascendant(right, left));
|
|
||||||
assert!(!is_path_ascendant(left, left));
|
|
||||||
|
|
||||||
assert!(is_path_ascendant(&[1, 2], &[1, 2, 3, 4, 5]));
|
|
||||||
}
|
|
||||||
@@ -36,7 +36,7 @@ impl VirtualDom {
|
|||||||
spawned_tasks: Default::default(),
|
spawned_tasks: Default::default(),
|
||||||
render_cnt: Default::default(),
|
render_cnt: Default::default(),
|
||||||
hook_arena: Default::default(),
|
hook_arena: Default::default(),
|
||||||
hook_vals: Default::default(),
|
hook_list: Default::default(),
|
||||||
hook_idx: Default::default(),
|
hook_idx: Default::default(),
|
||||||
shared_contexts: Default::default(),
|
shared_contexts: Default::default(),
|
||||||
tasks: self.scheduler.clone(),
|
tasks: self.scheduler.clone(),
|
||||||
|
|||||||
+87
-116
@@ -1,14 +1,3 @@
|
|||||||
use std::{
|
|
||||||
any::{Any, TypeId},
|
|
||||||
cell::{Cell, RefCell},
|
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
rc::Rc,
|
|
||||||
sync::Arc,
|
|
||||||
};
|
|
||||||
|
|
||||||
use bumpalo::Bump;
|
|
||||||
use std::future::Future;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any_props::AnyProps,
|
any_props::AnyProps,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
@@ -18,17 +7,40 @@ use crate::{
|
|||||||
nodes::VNode,
|
nodes::VNode,
|
||||||
TaskId,
|
TaskId,
|
||||||
};
|
};
|
||||||
|
use bumpalo::Bump;
|
||||||
|
use std::future::Future;
|
||||||
|
use std::{
|
||||||
|
any::{Any, TypeId},
|
||||||
|
cell::{Cell, RefCell},
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
rc::Rc,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
|
/// A wrapper around the [`Scoped`] object that contains a reference to the [`ScopeState`] and properties for a given
|
||||||
|
/// component.
|
||||||
|
///
|
||||||
|
/// The [`Scope`] is your handle to the [`VirtualDom`] and the component state. Every component is given its own
|
||||||
|
/// [`ScopeState`] and merged with its properties to create a [`Scoped`].
|
||||||
|
///
|
||||||
|
/// The [`Scope`] handle specifically exists to provide a stable reference to these items for the lifetime of the
|
||||||
|
/// component render.
|
||||||
pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
|
pub type Scope<'a, T = ()> = &'a Scoped<'a, T>;
|
||||||
|
|
||||||
|
/// A wrapper around a component's [`ScopeState`] and properties. The [`ScopeState`] provides the majority of methods
|
||||||
|
/// for the VirtualDom and component state.
|
||||||
pub struct Scoped<'a, T = ()> {
|
pub struct Scoped<'a, T = ()> {
|
||||||
|
/// The component's state and handle to the scheduler.
|
||||||
|
///
|
||||||
|
/// Stores things like the custom bump arena, spawn functions, hooks, and the scheduler.
|
||||||
pub scope: &'a ScopeState,
|
pub scope: &'a ScopeState,
|
||||||
|
|
||||||
|
/// The component's properties.
|
||||||
pub props: &'a T,
|
pub props: &'a T,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
||||||
type Target = &'a ScopeState;
|
type Target = &'a ScopeState;
|
||||||
|
|
||||||
fn deref(&self) -> &Self::Target {
|
fn deref(&self) -> &Self::Target {
|
||||||
&self.scope
|
&self.scope
|
||||||
}
|
}
|
||||||
@@ -42,6 +54,9 @@ impl<'a, T> std::ops::Deref for Scoped<'a, T> {
|
|||||||
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug, PartialOrd, Ord)]
|
||||||
pub struct ScopeId(pub usize);
|
pub struct ScopeId(pub usize);
|
||||||
|
|
||||||
|
/// A component's state.
|
||||||
|
///
|
||||||
|
/// This struct stores all the important information about a component's state without the props.
|
||||||
pub struct ScopeState {
|
pub struct ScopeState {
|
||||||
pub(crate) render_cnt: Cell<usize>,
|
pub(crate) render_cnt: Cell<usize>,
|
||||||
|
|
||||||
@@ -55,7 +70,7 @@ pub struct ScopeState {
|
|||||||
pub(crate) height: u32,
|
pub(crate) height: u32,
|
||||||
|
|
||||||
pub(crate) hook_arena: Bump,
|
pub(crate) hook_arena: Bump,
|
||||||
pub(crate) hook_vals: RefCell<Vec<*mut dyn Any>>,
|
pub(crate) hook_list: RefCell<Vec<*mut dyn Any>>,
|
||||||
pub(crate) hook_idx: Cell<usize>,
|
pub(crate) hook_idx: Cell<usize>,
|
||||||
|
|
||||||
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
pub(crate) shared_contexts: RefCell<HashMap<TypeId, Box<dyn Any>>>,
|
||||||
@@ -75,6 +90,7 @@ impl ScopeState {
|
|||||||
_ => unreachable!(),
|
_ => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn previous_frame(&self) -> &BumpFrame {
|
pub fn previous_frame(&self) -> &BumpFrame {
|
||||||
match self.render_cnt.get() % 2 {
|
match self.render_cnt.get() % 2 {
|
||||||
1 => &self.node_arena_1,
|
1 => &self.node_arena_1,
|
||||||
@@ -83,10 +99,19 @@ impl ScopeState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the currently active bump arena for this Scope
|
||||||
|
///
|
||||||
|
/// This is a bump memory allocator. Be careful using this directly since the contents will be wiped on the next render.
|
||||||
|
/// It's easy to leak memory here since the drop implementation will not be called for any objects allocated in this arena.
|
||||||
|
///
|
||||||
|
/// If you need to allocate items that need to be dropped, use bumpalo's box.
|
||||||
pub fn bump(&self) -> &Bump {
|
pub fn bump(&self) -> &Bump {
|
||||||
&self.current_frame().bump
|
&self.current_frame().bump
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get a handle to the currently active head node arena for this Scope
|
||||||
|
///
|
||||||
|
/// This is useful for traversing the tree outside of the VirtualDom, such as in a custom renderer or in SSR.
|
||||||
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
pub fn root_node<'a>(&'a self) -> &'a VNode<'a> {
|
||||||
let r = unsafe { &*self.current_frame().node.get() };
|
let r = unsafe { &*self.current_frame().node.get() };
|
||||||
unsafe { std::mem::transmute(r) }
|
unsafe { std::mem::transmute(r) }
|
||||||
@@ -166,6 +191,7 @@ impl ScopeState {
|
|||||||
Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
Arc::new(move |id| drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Mark this scope as dirty, and schedule a render for it.
|
||||||
pub fn needs_update(&self) {
|
pub fn needs_update(&self) {
|
||||||
self.needs_update_any(self.scope_id());
|
self.needs_update_any(self.scope_id());
|
||||||
}
|
}
|
||||||
@@ -180,6 +206,42 @@ impl ScopeState {
|
|||||||
.expect("Scheduler to exist if scope exists");
|
.expect("Scheduler to exist if scope exists");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Return any context of type T if it exists on this scope
|
||||||
|
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||||
|
self.shared_contexts
|
||||||
|
.borrow()
|
||||||
|
.get(&TypeId::of::<T>())
|
||||||
|
.and_then(|shared| shared.downcast_ref::<T>())
|
||||||
|
.cloned()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Try to retrieve a shared state with type `T` from any parent scope.
|
||||||
|
///
|
||||||
|
/// The state will be cloned and returned, if it exists.
|
||||||
|
///
|
||||||
|
/// We recommend wrapping the state in an `Rc` or `Arc` to avoid deep cloning.
|
||||||
|
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
||||||
|
if let Some(this_ctx) = self.has_context() {
|
||||||
|
return Some(this_ctx);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut search_parent = self.parent;
|
||||||
|
while let Some(parent_ptr) = search_parent {
|
||||||
|
// safety: all parent pointers are valid thanks to the bump arena
|
||||||
|
let parent = unsafe { &*parent_ptr };
|
||||||
|
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
||||||
|
return Some(
|
||||||
|
shared
|
||||||
|
.downcast_ref::<T>()
|
||||||
|
.expect("Context of type T should exist")
|
||||||
|
.clone(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
search_parent = parent.parent;
|
||||||
|
}
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
/// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
|
/// This method enables the ability to expose state to children further down the [`VirtualDom`] Tree.
|
||||||
///
|
///
|
||||||
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
/// This is a "fundamental" operation and should only be called during initialization of a hook.
|
||||||
@@ -212,96 +274,6 @@ impl ScopeState {
|
|||||||
value
|
value
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Provide a context for the root component from anywhere in your app.
|
|
||||||
///
|
|
||||||
///
|
|
||||||
/// # Example
|
|
||||||
///
|
|
||||||
/// ```rust, ignore
|
|
||||||
/// struct SharedState(&'static str);
|
|
||||||
///
|
|
||||||
/// static App: Component = |cx| {
|
|
||||||
/// cx.use_hook(|| cx.provide_root_context(SharedState("world")));
|
|
||||||
/// render!(Child {})
|
|
||||||
/// }
|
|
||||||
///
|
|
||||||
/// static Child: Component = |cx| {
|
|
||||||
/// let state = cx.consume_state::<SharedState>();
|
|
||||||
/// render!(div { "hello {state.0}" })
|
|
||||||
/// }
|
|
||||||
/// ```
|
|
||||||
pub fn provide_root_context<T: 'static + Clone>(&self, value: T) -> T {
|
|
||||||
// if we *are* the root component, then we can just provide the context directly
|
|
||||||
if self.scope_id() == ScopeId(0) {
|
|
||||||
self.shared_contexts
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(TypeId::of::<T>(), Box::new(value.clone()))
|
|
||||||
.and_then(|f| f.downcast::<T>().ok());
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
let mut search_parent = self.parent;
|
|
||||||
|
|
||||||
while let Some(parent) = search_parent.take() {
|
|
||||||
let parent = unsafe { &*parent };
|
|
||||||
|
|
||||||
if parent.scope_id() == ScopeId(0) {
|
|
||||||
let _ = parent
|
|
||||||
.shared_contexts
|
|
||||||
.borrow_mut()
|
|
||||||
.insert(TypeId::of::<T>(), Box::new(value.clone()));
|
|
||||||
|
|
||||||
return value;
|
|
||||||
}
|
|
||||||
|
|
||||||
search_parent = parent.parent;
|
|
||||||
}
|
|
||||||
|
|
||||||
unreachable!("all apps have a root scope")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Try to retrieve a shared state with type T from the any parent Scope.
|
|
||||||
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
||||||
if let Some(shared) = self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
|
||||||
Some(
|
|
||||||
(*shared
|
|
||||||
.downcast_ref::<T>()
|
|
||||||
.expect("Context of type T should exist"))
|
|
||||||
.clone(),
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
let mut search_parent = self.parent;
|
|
||||||
|
|
||||||
while let Some(parent_ptr) = search_parent {
|
|
||||||
// safety: all parent pointers are valid thanks to the bump arena
|
|
||||||
let parent = unsafe { &*parent_ptr };
|
|
||||||
if let Some(shared) = parent.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
|
||||||
return Some(
|
|
||||||
shared
|
|
||||||
.downcast_ref::<T>()
|
|
||||||
.expect("Context of type T should exist")
|
|
||||||
.clone(),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
search_parent = parent.parent;
|
|
||||||
}
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Return any context of type T if it exists on this scope
|
|
||||||
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> {
|
|
||||||
match self.shared_contexts.borrow().get(&TypeId::of::<T>()) {
|
|
||||||
Some(shared) => Some(
|
|
||||||
(*shared
|
|
||||||
.downcast_ref::<T>()
|
|
||||||
.expect("Context of type T should exist"))
|
|
||||||
.clone(),
|
|
||||||
),
|
|
||||||
None => None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Pushes the future onto the poll queue to be polled after the component renders.
|
/// Pushes the future onto the poll queue to be polled after the component renders.
|
||||||
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
pub fn push_future(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
self.tasks.spawn(self.id, fut)
|
self.tasks.spawn(self.id, fut)
|
||||||
@@ -312,7 +284,7 @@ impl ScopeState {
|
|||||||
self.push_future(fut);
|
self.push_future(fut);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Spawn a future that Dioxus will never clean up
|
/// Spawn a future that Dioxus won't clean up when this component is unmounted
|
||||||
///
|
///
|
||||||
/// This is good for tasks that need to be run after the component has been dropped.
|
/// This is good for tasks that need to be run after the component has been dropped.
|
||||||
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
pub fn spawn_forever(&self, fut: impl Future<Output = ()> + 'static) -> TaskId {
|
||||||
@@ -328,13 +300,14 @@ impl ScopeState {
|
|||||||
id
|
id
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Informs the scheduler that this task is no longer needed and should be removed
|
/// Informs the scheduler that this task is no longer needed and should be removed.
|
||||||
/// on next poll.
|
///
|
||||||
|
/// This drops the task immediately.
|
||||||
pub fn remove_future(&self, id: TaskId) {
|
pub fn remove_future(&self, id: TaskId) {
|
||||||
self.tasks.remove(id);
|
self.tasks.remove(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take a lazy [`VNode`] structure and actually build it with the context of the Vdoms efficient [`VNode`] allocator.
|
/// Take a lazy [`VNode`] structure and actually build it with the context of the efficient [`Bump`] allocator.
|
||||||
///
|
///
|
||||||
/// ## Example
|
/// ## Example
|
||||||
///
|
///
|
||||||
@@ -369,19 +342,17 @@ impl ScopeState {
|
|||||||
/// ```
|
/// ```
|
||||||
#[allow(clippy::mut_from_ref)]
|
#[allow(clippy::mut_from_ref)]
|
||||||
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
|
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
|
||||||
let mut vals = self.hook_vals.borrow_mut();
|
let cur_hook = self.hook_idx.get();
|
||||||
|
let mut hook_list = self.hook_list.borrow_mut();
|
||||||
|
|
||||||
let hook_len = vals.len();
|
if cur_hook >= hook_list.len() {
|
||||||
let cur_idx = self.hook_idx.get();
|
hook_list.push(self.hook_arena.alloc(initializer()));
|
||||||
|
|
||||||
if cur_idx >= hook_len {
|
|
||||||
vals.push(self.hook_arena.alloc(initializer()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
vals
|
hook_list
|
||||||
.get(cur_idx)
|
.get(cur_hook)
|
||||||
.and_then(|inn| {
|
.and_then(|inn| {
|
||||||
self.hook_idx.set(cur_idx + 1);
|
self.hook_idx.set(cur_hook + 1);
|
||||||
let raw_box = unsafe { &mut **inn };
|
let raw_box = unsafe { &mut **inn };
|
||||||
raw_box.downcast_mut::<State>()
|
raw_box.downcast_mut::<State>()
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -1,3 +1,7 @@
|
|||||||
|
//! # Virtual DOM Implementation for Rust
|
||||||
|
//!
|
||||||
|
//! This module provides the primary mechanics to create a hook-based, concurrent VDOM for Rust.
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
any_props::VComponentProps,
|
any_props::VComponentProps,
|
||||||
arena::ElementId,
|
arena::ElementId,
|
||||||
@@ -13,12 +17,13 @@ use crate::{
|
|||||||
};
|
};
|
||||||
use futures_util::{pin_mut, StreamExt};
|
use futures_util::{pin_mut, StreamExt};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::rc::Rc;
|
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
|
cell::Cell,
|
||||||
collections::{BTreeSet, HashMap},
|
collections::{BTreeSet, HashMap},
|
||||||
|
future::Future,
|
||||||
|
rc::Rc,
|
||||||
};
|
};
|
||||||
use std::{cell::Cell, future::Future};
|
|
||||||
|
|
||||||
/// A virtual node system that progresses user events and diffs UI trees.
|
/// A virtual node system that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
@@ -245,9 +250,11 @@ impl VirtualDom {
|
|||||||
|
|
||||||
// The root component is always a suspense boundary for any async children
|
// The root component is always a suspense boundary for any async children
|
||||||
// This could be unexpected, so we might rethink this behavior later
|
// This could be unexpected, so we might rethink this behavior later
|
||||||
|
//
|
||||||
|
// We *could* just panic if the suspense boundary is not found
|
||||||
root.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
root.provide_context(SuspenseBoundary::new(ScopeId(0)));
|
||||||
|
|
||||||
// the root element is always given element 0
|
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||||
dom.elements.insert(ElementRef::null());
|
dom.elements.insert(ElementRef::null());
|
||||||
|
|
||||||
dom
|
dom
|
||||||
@@ -269,19 +276,24 @@ impl VirtualDom {
|
|||||||
|
|
||||||
/// Build the virtualdom with a global context inserted into the base scope
|
/// Build the virtualdom with a global context inserted into the base scope
|
||||||
///
|
///
|
||||||
/// This is useful for what is essentially dependency injection, when building the app
|
/// This is useful for what is essentially dependency injection when building the app
|
||||||
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
|
pub fn with_root_context<T: Clone + 'static>(self, context: T) -> Self {
|
||||||
self.base_scope().provide_context(context);
|
self.base_scope().provide_context(context);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually mark a scope as requiring a re-render
|
/// Manually mark a scope as requiring a re-render
|
||||||
|
///
|
||||||
|
/// Whenever the VirtualDom "works", it will re-render this scope
|
||||||
pub fn mark_dirty_scope(&mut self, id: ScopeId) {
|
pub fn mark_dirty_scope(&mut self, id: ScopeId) {
|
||||||
let height = self.scopes[id.0].height;
|
let height = self.scopes[id.0].height;
|
||||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine whether or not a scope is currently in a suspended state
|
/// Determine whether or not a scope is currently in a suspended state
|
||||||
|
///
|
||||||
|
/// This does not mean the scope is waiting on its own futures, just that the tree that the scope exists in is
|
||||||
|
/// currently suspended.
|
||||||
pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
|
pub fn is_scope_suspended(&self, id: ScopeId) -> bool {
|
||||||
!self.scopes[id.0]
|
!self.scopes[id.0]
|
||||||
.consume_context::<SuspenseContext>()
|
.consume_context::<SuspenseContext>()
|
||||||
@@ -291,7 +303,7 @@ impl VirtualDom {
|
|||||||
.is_empty()
|
.is_empty()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Determine is the tree is at all suspended. Used by SSR and other outside mechanisms to determine if the tree is
|
/// Determine if the tree is at all suspended. Used by SSR and other outside mechanisms to determine if the tree is
|
||||||
/// ready to be rendered.
|
/// ready to be rendered.
|
||||||
pub fn has_suspended_work(&self) -> bool {
|
pub fn has_suspended_work(&self) -> bool {
|
||||||
!self.scheduler.leaves.borrow().is_empty()
|
!self.scheduler.leaves.borrow().is_empty()
|
||||||
@@ -324,6 +336,10 @@ impl VirtualDom {
|
|||||||
|
|
||||||
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
|
If we wanted to do capturing, then we would accumulate all the listeners and call them in reverse order.
|
||||||
----------------------
|
----------------------
|
||||||
|
|
||||||
|
For a visual demonstration, here we present a tree on the left and whether or not a listener is collected on the
|
||||||
|
right.
|
||||||
|
|
||||||
| <-- yes (is ascendant)
|
| <-- yes (is ascendant)
|
||||||
| | | <-- no (is not direct ascendant)
|
| | | <-- no (is not direct ascendant)
|
||||||
| | <-- yes (is ascendant)
|
| | <-- yes (is ascendant)
|
||||||
@@ -354,6 +370,7 @@ impl VirtualDom {
|
|||||||
let this_path = template.template.attr_paths[idx];
|
let this_path = template.template.attr_paths[idx];
|
||||||
|
|
||||||
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
|
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
|
||||||
|
// we should fix this so that we look for "onclick" instead of "click"
|
||||||
if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) {
|
if &attr.name[2..] == name && is_path_ascendant(&target_path, &this_path) {
|
||||||
listeners.push(&attr.value);
|
listeners.push(&attr.value);
|
||||||
|
|
||||||
@@ -362,7 +379,9 @@ impl VirtualDom {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Break if this is the exact target element
|
// Break if this is the exact target element.
|
||||||
|
// This means we won't call two listeners with the same name on the same element. This should be
|
||||||
|
// documented, or be rejected from the rsx! macro outright
|
||||||
if this_path == target_path {
|
if this_path == target_path {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -392,7 +411,7 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This method is cancel-safe, so you're fine to discard the future in a select block.
|
/// This method is cancel-safe, so you're fine to discard the future in a select block.
|
||||||
///
|
///
|
||||||
/// This lets us poll async tasks during idle periods without blocking the main thread.
|
/// This lets us poll async tasks and suspended trees during idle periods without blocking the main thread.
|
||||||
///
|
///
|
||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
@@ -433,7 +452,7 @@ impl VirtualDom {
|
|||||||
|
|
||||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
|
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
|
||||||
///
|
///
|
||||||
/// The diff machine expects the RealDom's stack to be the root of the application.
|
/// The mutations item expects the RealDom's stack to be the root of the application.
|
||||||
///
|
///
|
||||||
/// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
|
/// Tasks will not be polled with this method, nor will any events be processed from the event queue. Instead, the
|
||||||
/// root component will be ran once and then diffed. All updates will flow out as mutations.
|
/// root component will be ran once and then diffed. All updates will flow out as mutations.
|
||||||
@@ -498,7 +517,6 @@ impl VirtualDom {
|
|||||||
&'a mut self,
|
&'a mut self,
|
||||||
deadline: impl Future<Output = ()>,
|
deadline: impl Future<Output = ()>,
|
||||||
) -> Mutations<'a> {
|
) -> Mutations<'a> {
|
||||||
use futures_util::future::{select, Either};
|
|
||||||
pin_mut!(deadline);
|
pin_mut!(deadline);
|
||||||
|
|
||||||
let mut mutations = Mutations::new(0);
|
let mut mutations = Mutations::new(0);
|
||||||
@@ -533,7 +551,7 @@ impl VirtualDom {
|
|||||||
|
|
||||||
// Wait for suspense, or a deadline
|
// Wait for suspense, or a deadline
|
||||||
if self.dirty_scopes.is_empty() {
|
if self.dirty_scopes.is_empty() {
|
||||||
// If there's no suspense, then we have no reason to wait
|
// If there's no pending suspense, then we have no reason to wait
|
||||||
if self.scheduler.leaves.borrow().is_empty() {
|
if self.scheduler.leaves.borrow().is_empty() {
|
||||||
return mutations;
|
return mutations;
|
||||||
}
|
}
|
||||||
@@ -543,6 +561,7 @@ impl VirtualDom {
|
|||||||
pin_mut!(work);
|
pin_mut!(work);
|
||||||
|
|
||||||
// If the deadline is exceded (left) then we should return the mutations we have
|
// If the deadline is exceded (left) then we should return the mutations we have
|
||||||
|
use futures_util::future::{select, Either};
|
||||||
if let Either::Left((_, _)) = select(&mut deadline, work).await {
|
if let Either::Left((_, _)) = select(&mut deadline, work).await {
|
||||||
return mutations;
|
return mutations;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use std::time::Duration;
|
//! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
|
||||||
|
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn it_works() {
|
async fn it_works() {
|
||||||
|
|||||||
Reference in new issue
Block a user