Add flush_sync and pre_render methods to core
This commit is contained in:
17 files changed
+378
-282
No files matched your search
@@ -14,12 +14,14 @@ rustc-hash = { workspace = true }
|
|||||||
longest-increasing-subsequence = "0.1.0"
|
longest-increasing-subsequence = "0.1.0"
|
||||||
futures-util = { workspace = true, default-features = false, features = [
|
futures-util = { workspace = true, default-features = false, features = [
|
||||||
"alloc",
|
"alloc",
|
||||||
|
"std",
|
||||||
] }
|
] }
|
||||||
slab = { workspace = true }
|
slab = { workspace = true }
|
||||||
futures-channel = { workspace = true }
|
futures-channel = { workspace = true }
|
||||||
tracing = { workspace = true }
|
tracing = { workspace = true }
|
||||||
serde = { version = "1", features = ["derive"], optional = true }
|
serde = { version = "1", features = ["derive"], optional = true }
|
||||||
|
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
tokio = { workspace = true, features = ["full"] }
|
tokio = { workspace = true, features = ["full"] }
|
||||||
dioxus = { workspace = true }
|
dioxus = { workspace = true }
|
||||||
|
|||||||
+2
-10
@@ -41,7 +41,7 @@ use dioxus::prelude::*;
|
|||||||
fn app() -> Element {
|
fn app() -> Element {
|
||||||
rsx!{
|
rsx!{
|
||||||
div { "hello world" }
|
div { "hello world" }
|
||||||
})
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
@@ -49,13 +49,8 @@ fn main() {
|
|||||||
let mut dom = VirtualDom::new(app);
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|
||||||
// The initial render of the dom will generate a stream of edits for the real dom to apply
|
// The initial render of the dom will generate a stream of edits for the real dom to apply
|
||||||
let mutations = dom.rebuild();
|
let mutations = dom.rebuild_to_vec();
|
||||||
|
|
||||||
// Somehow, you can apply these edits to the real dom
|
|
||||||
apply_edits_to_real_dom(mutations);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
# fn apply_edits_to_real_dom(mutations: Mutations) {}
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
@@ -68,9 +63,6 @@ We can then wait for any asynchronous components or pending futures using the `w
|
|||||||
# async fn wait(mut dom: VirtualDom) {
|
# async fn wait(mut dom: VirtualDom) {
|
||||||
// Wait for the dom to be marked dirty internally
|
// Wait for the dom to be marked dirty internally
|
||||||
dom.wait_for_work().await;
|
dom.wait_for_work().await;
|
||||||
|
|
||||||
// Or wait for a deadline and then collect edits
|
|
||||||
let mutations = dom.render_with_deadline(tokio::time::sleep(Duration::from_millis(16)));
|
|
||||||
# }
|
# }
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ impl VirtualDom {
|
|||||||
pub(crate) fn drop_scope(&mut self, id: ScopeId) {
|
pub(crate) fn drop_scope(&mut self, id: ScopeId) {
|
||||||
let height = {
|
let height = {
|
||||||
let scope = self.scopes.remove(id.0);
|
let scope = self.scopes.remove(id.0);
|
||||||
let context = scope.context();
|
let context = scope.state();
|
||||||
context.height
|
context.height
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -82,7 +82,7 @@ impl VNode {
|
|||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"Memoized props for component {:#?} ({})",
|
"Memoized props for component {:#?} ({})",
|
||||||
scope_id,
|
scope_id,
|
||||||
old_scope.context().name
|
old_scope.state().name
|
||||||
);
|
);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -94,7 +94,7 @@ impl VNode {
|
|||||||
let new = dom.run_scope(scope_id);
|
let new = dom.run_scope(scope_id);
|
||||||
dom.diff_scope(to, scope_id, new);
|
dom.diff_scope(to, scope_id, new);
|
||||||
|
|
||||||
let height = dom.runtime.get_context(scope_id).unwrap().height;
|
let height = dom.runtime.get_state(scope_id).unwrap().height;
|
||||||
dom.dirty_scopes.remove(&DirtyScope {
|
dom.dirty_scopes.remove(&DirtyScope {
|
||||||
height,
|
height,
|
||||||
id: scope_id,
|
id: scope_id,
|
||||||
@@ -130,7 +130,7 @@ impl VNode {
|
|||||||
// Load up a ScopeId for this vcomponent. If it's already mounted, then we can just use that
|
// Load up a ScopeId for this vcomponent. If it's already mounted, then we can just use that
|
||||||
let scope = dom
|
let scope = dom
|
||||||
.new_scope(component.props.duplicate(), component.name)
|
.new_scope(component.props.duplicate(), component.name)
|
||||||
.context()
|
.state()
|
||||||
.id;
|
.id;
|
||||||
|
|
||||||
// Store the scope id for the next render
|
// Store the scope id for the next render
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
use std::sync::Arc;
|
|
||||||
|
|
||||||
use futures_util::Future;
|
|
||||||
|
|
||||||
use crate::{runtime::Runtime, Element, ScopeId, Task};
|
use crate::{runtime::Runtime, Element, ScopeId, Task};
|
||||||
|
use futures_util::{future::poll_fn, Future};
|
||||||
|
use std::sync::Arc;
|
||||||
|
|
||||||
/// Get the current scope id
|
/// Get the current scope id
|
||||||
pub fn current_scope_id() -> Option<ScopeId> {
|
pub fn current_scope_id() -> Option<ScopeId> {
|
||||||
@@ -30,7 +28,7 @@ pub fn consume_context<T: 'static + Clone>() -> T {
|
|||||||
/// Consume context from the current scope
|
/// Consume context from the current scope
|
||||||
pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
|
pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
|
||||||
Runtime::with(|rt| {
|
Runtime::with(|rt| {
|
||||||
rt.get_context(scope_id)
|
rt.get_state(scope_id)
|
||||||
.and_then(|cx| cx.consume_context::<T>())
|
.and_then(|cx| cx.consume_context::<T>())
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
@@ -53,9 +51,7 @@ pub fn provide_root_context<T: 'static + Clone>(value: T) -> Option<T> {
|
|||||||
|
|
||||||
/// Suspends the current component
|
/// Suspends the current component
|
||||||
pub fn suspend() -> Option<Element> {
|
pub fn suspend() -> Option<Element> {
|
||||||
Runtime::with_current_scope(|cx| {
|
Runtime::with_current_scope(|cx| cx.suspend());
|
||||||
cx.suspend();
|
|
||||||
});
|
|
||||||
None
|
None
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -87,7 +83,7 @@ pub fn remove_future(id: Task) {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
/// use dioxus_core::ScopeState;
|
/// use dioxus_core::use_hook;
|
||||||
///
|
///
|
||||||
/// // prints a greeting on the initial render
|
/// // prints a greeting on the initial render
|
||||||
/// pub fn use_hello_world() {
|
/// pub fn use_hello_world() {
|
||||||
@@ -98,6 +94,47 @@ pub fn use_hook<State: Clone + 'static>(initializer: impl FnOnce() -> State) ->
|
|||||||
Runtime::with_current_scope(|cx| cx.use_hook(initializer)).expect("to be in a dioxus runtime")
|
Runtime::with_current_scope(|cx| cx.use_hook(initializer)).expect("to be in a dioxus runtime")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Push a function to be run before the next render
|
||||||
|
/// This is a hook and will always run, so you can't unschedule it
|
||||||
|
/// Will run for every progression of suspense, though this might change in the future
|
||||||
|
pub fn use_before_render(f: impl FnMut() + 'static) {
|
||||||
|
Runtime::with_current_scope(|cx| cx.push_before_render(f));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Wait for the virtualdom to finish its sync work before proceeding
|
||||||
|
///
|
||||||
|
/// This is useful if you've just triggered an update and want to wait for it to finish before proceeding with valid
|
||||||
|
/// DOM nodes.
|
||||||
|
pub async fn flush_sync() {
|
||||||
|
let mut polled = false;
|
||||||
|
|
||||||
|
let _task =
|
||||||
|
FlushKey(Runtime::with(|rt| rt.add_to_flush_table()).expect("to be in a dioxus runtime"));
|
||||||
|
|
||||||
|
// Poll without giving the waker to anyone
|
||||||
|
// The runtime will manually wake this task up when it's ready
|
||||||
|
poll_fn(|_| {
|
||||||
|
if !polled {
|
||||||
|
polled = true;
|
||||||
|
futures_util::task::Poll::Pending
|
||||||
|
} else {
|
||||||
|
futures_util::task::Poll::Ready(())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.await;
|
||||||
|
|
||||||
|
// If the the future got polled, then we don't need to prevent it from being dropped
|
||||||
|
// This would all be solved with generational indicies on tasks
|
||||||
|
std::mem::forget(_task);
|
||||||
|
|
||||||
|
struct FlushKey(Task);
|
||||||
|
impl Drop for FlushKey {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
Runtime::with(|rt| rt.flush_table.borrow_mut().remove(&self.0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the current render since the inception of this component
|
/// Get the current render since the inception of this component
|
||||||
///
|
///
|
||||||
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
|
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
|
||||||
@@ -138,7 +175,7 @@ pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
|||||||
/// (created with [`use_effect`](crate::use_effect)).
|
/// (created with [`use_effect`](crate::use_effect)).
|
||||||
///
|
///
|
||||||
/// Example:
|
/// Example:
|
||||||
/// ```rust
|
/// ```rust, ignore
|
||||||
/// use dioxus::prelude::*;
|
/// use dioxus::prelude::*;
|
||||||
///
|
///
|
||||||
/// fn app() -> Element {
|
/// fn app() -> Element {
|
||||||
@@ -176,7 +213,7 @@ pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
|
|||||||
/// }
|
/// }
|
||||||
/// });
|
/// });
|
||||||
///
|
///
|
||||||
/// use_on_destroy({
|
/// use_drop({
|
||||||
/// to_owned![original_scroll_position];
|
/// to_owned![original_scroll_position];
|
||||||
/// /// restore scroll to the top of the page
|
/// /// restore scroll to the top of the page
|
||||||
/// move || {
|
/// move || {
|
||||||
|
|||||||
@@ -59,6 +59,7 @@ pub(crate) mod innerlude {
|
|||||||
/// example()
|
/// example()
|
||||||
/// )
|
/// )
|
||||||
/// ```
|
/// ```
|
||||||
|
///
|
||||||
/// ## React-Style
|
/// ## React-Style
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// fn Example(cx: Props) -> Element {
|
/// fn Example(cx: Props) -> Element {
|
||||||
@@ -86,14 +87,14 @@ pub use crate::innerlude::{
|
|||||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||||
pub mod prelude {
|
pub mod prelude {
|
||||||
pub use crate::innerlude::{
|
pub use crate::innerlude::{
|
||||||
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, generation,
|
consume_context, consume_context_from_scope, current_scope_id, fc_to_builder, flush_sync,
|
||||||
has_context, needs_update, parent_scope, provide_context, provide_root_context,
|
generation, has_context, needs_update, parent_scope, provide_context, provide_root_context,
|
||||||
remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
|
remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, suspend,
|
||||||
try_consume_context, use_drop, use_error_boundary, use_hook, use_hook_with_cleanup,
|
try_consume_context, use_before_render, use_drop, use_error_boundary, use_hook,
|
||||||
AnyValue, Attribute, Component, ComponentFunction, Element, ErrorBoundary, Event,
|
use_hook_with_cleanup, AnyValue, Attribute, Component, ComponentFunction, Element,
|
||||||
EventHandler, Fragment, HasAttributes, IntoAttributeValue, IntoDynNode,
|
ErrorBoundary, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
|
||||||
OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId, ScopeState, SuperFrom,
|
IntoDynNode, OptionStringFromMarker, Properties, Runtime, RuntimeGuard, ScopeId,
|
||||||
SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw, VNode, VNodeInner,
|
ScopeState, SuperFrom, SuperInto, Task, Template, TemplateAttribute, TemplateNode, Throw,
|
||||||
VirtualDom,
|
VNode, VNodeInner, VirtualDom,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -105,7 +105,7 @@ pub struct VNodeInner {
|
|||||||
/// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
|
/// The inner list *must* be in the format [static named attributes, remaining dynamically named attributes].
|
||||||
///
|
///
|
||||||
/// For example:
|
/// For example:
|
||||||
/// ```rust
|
/// ```rust, ignore
|
||||||
/// div {
|
/// div {
|
||||||
/// class: "{class}",
|
/// class: "{class}",
|
||||||
/// ..attrs,
|
/// ..attrs,
|
||||||
@@ -116,7 +116,7 @@ pub struct VNodeInner {
|
|||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// Would be represented as:
|
/// Would be represented as:
|
||||||
/// ```rust
|
/// ```rust, ignore
|
||||||
/// [
|
/// [
|
||||||
/// [class, every attribute in attrs sorted by name], // Slot 0 in the template
|
/// [class, every attribute in attrs sorted by name], // Slot 0 in the template
|
||||||
/// [color], // Slot 1 in the template
|
/// [color], // Slot 1 in the template
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
|
use rustc_hash::FxHashSet;
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
innerlude::{LocalTask, SchedulerMsg},
|
innerlude::{LocalTask, SchedulerMsg},
|
||||||
scope_context::ScopeContext,
|
scope_context::Scope,
|
||||||
scopes::ScopeId,
|
scopes::ScopeId,
|
||||||
Task,
|
Task,
|
||||||
};
|
};
|
||||||
use std::{
|
use std::{
|
||||||
cell::{Cell, Ref, RefCell},
|
cell::{Cell, Ref, RefCell},
|
||||||
collections::VecDeque,
|
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -16,7 +17,7 @@ thread_local! {
|
|||||||
|
|
||||||
/// A global runtime that is shared across all scopes that provides the async runtime and context API
|
/// A global runtime that is shared across all scopes that provides the async runtime and context API
|
||||||
pub struct Runtime {
|
pub struct Runtime {
|
||||||
pub(crate) scope_contexts: RefCell<Vec<Option<ScopeContext>>>,
|
pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,
|
||||||
|
|
||||||
// We use this to track the current scope
|
// We use this to track the current scope
|
||||||
pub(crate) scope_stack: RefCell<Vec<ScopeId>>,
|
pub(crate) scope_stack: RefCell<Vec<ScopeId>>,
|
||||||
@@ -29,10 +30,10 @@ pub struct Runtime {
|
|||||||
/// Tasks created with cx.spawn
|
/// Tasks created with cx.spawn
|
||||||
pub(crate) tasks: RefCell<slab::Slab<Rc<LocalTask>>>,
|
pub(crate) tasks: RefCell<slab::Slab<Rc<LocalTask>>>,
|
||||||
|
|
||||||
/// Queued tasks that are waiting to be polled
|
|
||||||
pub(crate) queued_tasks: Rc<RefCell<VecDeque<Task>>>,
|
|
||||||
|
|
||||||
pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
|
||||||
|
|
||||||
|
// Tasks waiting to be manually resumed when we call wait_for_work
|
||||||
|
pub(crate) flush_table: RefCell<FxHashSet<Task>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
@@ -40,11 +41,11 @@ impl Runtime {
|
|||||||
Rc::new(Self {
|
Rc::new(Self {
|
||||||
sender,
|
sender,
|
||||||
rendering: Cell::new(true),
|
rendering: Cell::new(true),
|
||||||
scope_contexts: Default::default(),
|
scope_states: Default::default(),
|
||||||
scope_stack: Default::default(),
|
scope_stack: Default::default(),
|
||||||
current_task: Default::default(),
|
current_task: Default::default(),
|
||||||
tasks: Default::default(),
|
tasks: Default::default(),
|
||||||
queued_tasks: Rc::new(RefCell::new(VecDeque::new())),
|
flush_table: Default::default(),
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -54,35 +55,39 @@ impl Runtime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Create a scope context. This slab is synchronized with the scope slab.
|
/// Create a scope context. This slab is synchronized with the scope slab.
|
||||||
pub(crate) fn create_context_at(&self, id: ScopeId, context: ScopeContext) {
|
pub(crate) fn create_scope(&self, context: Scope) {
|
||||||
let mut contexts = self.scope_contexts.borrow_mut();
|
let id = context.id;
|
||||||
if contexts.len() <= id.0 {
|
let mut scopes = self.scope_states.borrow_mut();
|
||||||
contexts.resize_with(id.0 + 1, Default::default);
|
if scopes.len() <= id.0 {
|
||||||
|
scopes.resize_with(id.0 + 1, Default::default);
|
||||||
}
|
}
|
||||||
contexts[id.0] = Some(context);
|
scopes[id.0] = Some(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn remove_context(self: &Rc<Self>, id: ScopeId) {
|
pub(crate) fn remove_scope(self: &Rc<Self>, id: ScopeId) {
|
||||||
{
|
{
|
||||||
let borrow = self.scope_contexts.borrow();
|
let borrow = self.scope_states.borrow();
|
||||||
if let Some(scope) = &borrow[id.0] {
|
if let Some(scope) = &borrow[id.0] {
|
||||||
let _runtime_guard = RuntimeGuard::new(self.clone());
|
let _runtime_guard = RuntimeGuard::new(self.clone());
|
||||||
// Manually drop tasks, hooks, and contexts inside of the runtime
|
// Manually drop tasks, hooks, and contexts inside of the runtime
|
||||||
self.on_scope(id, || {
|
self.on_scope(id, || {
|
||||||
// Drop all spawned tasks
|
// Drop all spawned tasks - order doesn't matter since tasks don't rely on eachother
|
||||||
|
// In theory nested tasks might not like this
|
||||||
for id in scope.spawned_tasks.take() {
|
for id in scope.spawned_tasks.take() {
|
||||||
self.remove_task(id);
|
self.remove_task(id);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Drop all hooks
|
// Drop all hooks in reverse order in case a hook depends on another hook.
|
||||||
scope.hooks.take();
|
for hook in scope.hooks.take().drain(..).rev() {
|
||||||
|
drop(hook);
|
||||||
|
}
|
||||||
|
|
||||||
// Drop all contexts
|
// Drop all contexts
|
||||||
scope.shared_contexts.take();
|
scope.shared_contexts.take();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.scope_contexts.borrow_mut()[id.0].take();
|
self.scope_states.borrow_mut()[id.0].take();
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the current scope id
|
/// Get the current scope id
|
||||||
@@ -104,11 +109,11 @@ impl Runtime {
|
|||||||
o
|
o
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Get the context for any scope given its ID
|
/// Get the state for any scope given its ID
|
||||||
///
|
///
|
||||||
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
|
/// This is useful for inserting or removing contexts from a scope, or rendering out its root node
|
||||||
pub(crate) fn get_context(&self, id: ScopeId) -> Option<Ref<'_, ScopeContext>> {
|
pub(crate) fn get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {
|
||||||
Ref::filter_map(self.scope_contexts.borrow(), |contexts| {
|
Ref::filter_map(self.scope_states.borrow(), |contexts| {
|
||||||
contexts.get(id.0).and_then(|f| f.as_ref())
|
contexts.get(id.0).and_then(|f| f.as_ref())
|
||||||
})
|
})
|
||||||
.ok()
|
.ok()
|
||||||
@@ -125,34 +130,22 @@ impl Runtime {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a function with the current runtime
|
/// Runs a function with the current runtime
|
||||||
pub(crate) fn with<F, R>(f: F) -> Option<R>
|
pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> Option<R> {
|
||||||
where
|
RUNTIMES.with(|stack| stack.borrow().last().map(|r| f(r)))
|
||||||
F: FnOnce(&Runtime) -> R,
|
|
||||||
{
|
|
||||||
RUNTIMES.with(|stack| {
|
|
||||||
let stack = stack.borrow();
|
|
||||||
stack.last().map(|r| f(r))
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a function with the current scope
|
/// Runs a function with the current scope
|
||||||
pub(crate) fn with_current_scope<F, R>(f: F) -> Option<R>
|
pub(crate) fn with_current_scope<R>(f: impl FnOnce(&Scope) -> R) -> Option<R> {
|
||||||
where
|
|
||||||
F: FnOnce(&ScopeContext) -> R,
|
|
||||||
{
|
|
||||||
Self::with(|rt| {
|
Self::with(|rt| {
|
||||||
rt.current_scope_id()
|
rt.current_scope_id()
|
||||||
.and_then(|scope| rt.get_context(scope).map(|sc| f(&sc)))
|
.and_then(|scope| rt.get_state(scope).map(|sc| f(&sc)))
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Runs a function with the current scope
|
/// Runs a function with the current scope
|
||||||
pub(crate) fn with_scope<F, R>(scope: ScopeId, f: F) -> Option<R>
|
pub(crate) fn with_scope<R>(scope: ScopeId, f: impl FnOnce(&Scope) -> R) -> Option<R> {
|
||||||
where
|
Self::with(|rt| rt.get_state(scope).map(|sc| f(&sc))).flatten()
|
||||||
F: FnOnce(&ScopeContext) -> R,
|
|
||||||
{
|
|
||||||
Self::with(|rt| rt.get_context(scope).map(|sc| f(&sc))).flatten()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -182,7 +175,9 @@ impl Runtime {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn Component(cx: ComponentProps) -> Element {
|
/// fn Component(cx: ComponentProps) -> Element {
|
||||||
/// cx.use_hook(|| RuntimeGuard::new(cx.runtime.clone()));
|
/// use_hook(|| {
|
||||||
|
/// let _guard = RuntimeGuard::new(cx.runtime.clone());
|
||||||
|
/// });
|
||||||
///
|
///
|
||||||
/// rsx! { div {} }
|
/// rsx! { div {} }
|
||||||
/// }
|
/// }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
any_props::{AnyProps, BoxedAnyProps},
|
any_props::{AnyProps, BoxedAnyProps},
|
||||||
innerlude::{DirtyScope, ScopeState},
|
innerlude::{DirtyScope, ScopeState},
|
||||||
nodes::RenderReturn,
|
nodes::RenderReturn,
|
||||||
scope_context::ScopeContext,
|
scope_context::Scope,
|
||||||
scopes::ScopeId,
|
scopes::ScopeId,
|
||||||
virtual_dom::VirtualDom,
|
virtual_dom::VirtualDom,
|
||||||
};
|
};
|
||||||
@@ -11,7 +11,7 @@ impl VirtualDom {
|
|||||||
pub(super) fn new_scope(&mut self, props: BoxedAnyProps, name: &'static str) -> &ScopeState {
|
pub(super) fn new_scope(&mut self, props: BoxedAnyProps, name: &'static str) -> &ScopeState {
|
||||||
let parent_id = self.runtime.current_scope_id();
|
let parent_id = self.runtime.current_scope_id();
|
||||||
let height = parent_id
|
let height = parent_id
|
||||||
.and_then(|parent_id| self.runtime.get_context(parent_id).map(|f| f.height + 1))
|
.and_then(|parent_id| self.runtime.get_state(parent_id).map(|f| f.height + 1))
|
||||||
.unwrap_or(0);
|
.unwrap_or(0);
|
||||||
let entry = self.scopes.vacant_entry();
|
let entry = self.scopes.vacant_entry();
|
||||||
let id = ScopeId(entry.key());
|
let id = ScopeId(entry.key());
|
||||||
@@ -19,34 +19,43 @@ impl VirtualDom {
|
|||||||
let scope = entry.insert(ScopeState {
|
let scope = entry.insert(ScopeState {
|
||||||
runtime: self.runtime.clone(),
|
runtime: self.runtime.clone(),
|
||||||
context_id: id,
|
context_id: id,
|
||||||
|
|
||||||
props,
|
props,
|
||||||
last_rendered_node: Default::default(),
|
last_rendered_node: Default::default(),
|
||||||
});
|
});
|
||||||
|
|
||||||
let context = ScopeContext::new(name, id, parent_id, height);
|
self.runtime
|
||||||
self.runtime.create_context_at(id, context);
|
.create_scope(Scope::new(name, id, parent_id, height));
|
||||||
|
|
||||||
scope
|
scope
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn {
|
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> RenderReturn {
|
||||||
self.runtime.scope_stack.borrow_mut().push(scope_id);
|
debug_assert!(
|
||||||
|
crate::Runtime::current().is_some(),
|
||||||
|
"Must be in a dioxus runtime"
|
||||||
|
);
|
||||||
|
|
||||||
|
self.runtime.scope_stack.borrow_mut().push(scope_id);
|
||||||
let scope = &self.scopes[scope_id.0];
|
let scope = &self.scopes[scope_id.0];
|
||||||
let new_nodes = {
|
let new_nodes = {
|
||||||
let context = scope.context();
|
let context = scope.state();
|
||||||
|
|
||||||
context.suspended.set(false);
|
context.suspended.set(false);
|
||||||
context.hook_index.set(0);
|
context.hook_index.set(0);
|
||||||
|
|
||||||
|
// Run all pre-render hooks
|
||||||
|
for pre_run in context.before_render.borrow_mut().iter_mut() {
|
||||||
|
pre_run();
|
||||||
|
}
|
||||||
|
|
||||||
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
||||||
let props: &dyn AnyProps = &*scope.props;
|
let props: &dyn AnyProps = &*scope.props;
|
||||||
|
|
||||||
let span = tracing::trace_span!("render", scope = %scope.context().name);
|
let span = tracing::trace_span!("render", scope = %scope.state().name);
|
||||||
span.in_scope(|| props.render())
|
span.in_scope(|| props.render())
|
||||||
};
|
};
|
||||||
|
|
||||||
let context = scope.context();
|
let context = scope.state();
|
||||||
|
|
||||||
// And move the render generation forward by one
|
// And move the render generation forward by one
|
||||||
context.render_count.set(context.render_count.get() + 1);
|
context.render_count.set(context.render_count.get() + 1);
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ use std::{
|
|||||||
/// A component's state separate from its props.
|
/// A component's state separate from its props.
|
||||||
///
|
///
|
||||||
/// This struct exists to provide a common interface for all scopes without relying on generics.
|
/// This struct exists to provide a common interface for all scopes without relying on generics.
|
||||||
pub(crate) struct ScopeContext {
|
pub(crate) struct Scope {
|
||||||
pub(crate) name: &'static str,
|
pub(crate) name: &'static str,
|
||||||
pub(crate) id: ScopeId,
|
pub(crate) id: ScopeId,
|
||||||
pub(crate) parent_id: Option<ScopeId>,
|
pub(crate) parent_id: Option<ScopeId>,
|
||||||
@@ -21,13 +21,12 @@ pub(crate) struct ScopeContext {
|
|||||||
// Note: the order of the hook and context fields is important. The hooks field must be dropped before the contexts field in case a hook drop implementation tries to access a context.
|
// Note: the order of the hook and context fields is important. The hooks field must be dropped before the contexts field in case a hook drop implementation tries to access a context.
|
||||||
pub(crate) hooks: RefCell<Vec<Box<dyn Any>>>,
|
pub(crate) hooks: RefCell<Vec<Box<dyn Any>>>,
|
||||||
pub(crate) hook_index: Cell<usize>,
|
pub(crate) hook_index: Cell<usize>,
|
||||||
|
|
||||||
pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,
|
pub(crate) shared_contexts: RefCell<Vec<Box<dyn Any>>>,
|
||||||
|
|
||||||
pub(crate) spawned_tasks: RefCell<FxHashSet<Task>>,
|
pub(crate) spawned_tasks: RefCell<FxHashSet<Task>>,
|
||||||
|
pub(crate) before_render: RefCell<Vec<Box<dyn FnMut()>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl ScopeContext {
|
impl Scope {
|
||||||
pub(crate) fn new(
|
pub(crate) fn new(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
id: ScopeId,
|
id: ScopeId,
|
||||||
@@ -45,6 +44,7 @@ impl ScopeContext {
|
|||||||
spawned_tasks: RefCell::new(FxHashSet::default()),
|
spawned_tasks: RefCell::new(FxHashSet::default()),
|
||||||
hooks: RefCell::new(vec![]),
|
hooks: RefCell::new(vec![]),
|
||||||
hook_index: Cell::new(0),
|
hook_index: Cell::new(0),
|
||||||
|
before_render: RefCell::new(vec![]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -109,7 +109,7 @@ impl ScopeContext {
|
|||||||
let mut search_parent = self.parent_id;
|
let mut search_parent = self.parent_id;
|
||||||
let cur_runtime = Runtime::with(|runtime| {
|
let cur_runtime = Runtime::with(|runtime| {
|
||||||
while let Some(parent_id) = search_parent {
|
while let Some(parent_id) = search_parent {
|
||||||
let parent = runtime.get_context(parent_id).unwrap();
|
let parent = runtime.get_state(parent_id).unwrap();
|
||||||
tracing::trace!(
|
tracing::trace!(
|
||||||
"looking for context {} ({:?}) in {}",
|
"looking for context {} ({:?}) in {}",
|
||||||
std::any::type_name::<T>(),
|
std::any::type_name::<T>(),
|
||||||
@@ -212,7 +212,7 @@ impl ScopeContext {
|
|||||||
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
|
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
|
||||||
Runtime::with(|runtime| {
|
Runtime::with(|runtime| {
|
||||||
runtime
|
runtime
|
||||||
.get_context(ScopeId::ROOT)
|
.get_state(ScopeId::ROOT)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.provide_context(context)
|
.provide_context(context)
|
||||||
})
|
})
|
||||||
@@ -256,9 +256,10 @@ impl ScopeContext {
|
|||||||
/// # Example
|
/// # Example
|
||||||
///
|
///
|
||||||
/// ```
|
/// ```
|
||||||
|
/// # use dioxus::prelude::*;
|
||||||
/// // prints a greeting on the initial render
|
/// // prints a greeting on the initial render
|
||||||
/// pub fn use_hello_world() {
|
/// pub fn use_hello_world() {
|
||||||
/// cx.use_hook(|| println!("Hello, world!"));
|
/// use_hook(|| println!("Hello, world!"));
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
pub fn use_hook<State: Clone + 'static>(&self, initializer: impl FnOnce() -> State) -> State {
|
pub fn use_hook<State: Clone + 'static>(&self, initializer: impl FnOnce() -> State) -> State {
|
||||||
@@ -287,6 +288,10 @@ impl ScopeContext {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn push_before_render(&self, f: impl FnMut() + 'static) {
|
||||||
|
self.before_render.borrow_mut().push(Box::new(f));
|
||||||
|
}
|
||||||
|
|
||||||
/// Get the current render since the inception of this component
|
/// Get the current render since the inception of this component
|
||||||
///
|
///
|
||||||
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
|
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
|
||||||
@@ -320,7 +325,7 @@ impl ScopeId {
|
|||||||
/// Consume context from the current scope
|
/// Consume context from the current scope
|
||||||
pub fn consume_context_from_scope<T: 'static + Clone>(self, scope_id: ScopeId) -> Option<T> {
|
pub fn consume_context_from_scope<T: 'static + Clone>(self, scope_id: ScopeId) -> Option<T> {
|
||||||
Runtime::with(|rt| {
|
Runtime::with(|rt| {
|
||||||
rt.get_context(scope_id)
|
rt.get_state(scope_id)
|
||||||
.and_then(|cx| cx.consume_context::<T>())
|
.and_then(|cx| cx.consume_context::<T>())
|
||||||
})
|
})
|
||||||
.flatten()
|
.flatten()
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
any_props::BoxedAnyProps, nodes::RenderReturn, runtime::Runtime, scope_context::ScopeContext,
|
any_props::BoxedAnyProps, nodes::RenderReturn, runtime::Runtime, scope_context::Scope,
|
||||||
};
|
};
|
||||||
use std::{cell::Ref, fmt::Debug, rc::Rc};
|
use std::{cell::Ref, fmt::Debug, rc::Rc};
|
||||||
|
|
||||||
@@ -39,7 +39,7 @@ pub struct ScopeState {
|
|||||||
|
|
||||||
impl Drop for ScopeState {
|
impl Drop for ScopeState {
|
||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
self.runtime.remove_context(self.context_id);
|
self.runtime.remove_scope(self.context_id);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -63,7 +63,7 @@ impl ScopeState {
|
|||||||
self.last_rendered_node.as_ref()
|
self.last_rendered_node.as_ref()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(crate) fn context(&self) -> Ref<'_, ScopeContext> {
|
pub(crate) fn state(&self) -> Ref<'_, Scope> {
|
||||||
self.runtime.get_context(self.context_id).unwrap()
|
self.runtime.get_state(self.context_id).unwrap()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+53
-34
@@ -1,10 +1,10 @@
|
|||||||
use crate::innerlude::{remove_future, spawn, Runtime};
|
use crate::innerlude::{remove_future, spawn, Runtime};
|
||||||
use crate::ScopeId;
|
use crate::ScopeId;
|
||||||
use futures_util::task::ArcWake;
|
use futures_util::task::ArcWake;
|
||||||
use std::future::Future;
|
|
||||||
use std::pin::Pin;
|
use std::pin::Pin;
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
use std::task::Waker;
|
use std::task::Waker;
|
||||||
|
use std::{cell::Cell, future::Future};
|
||||||
use std::{cell::RefCell, rc::Rc};
|
use std::{cell::RefCell, rc::Rc};
|
||||||
|
|
||||||
/// A task's unique identifier.
|
/// A task's unique identifier.
|
||||||
@@ -34,6 +34,25 @@ impl Task {
|
|||||||
pub fn stop(self) {
|
pub fn stop(self) {
|
||||||
remove_future(self);
|
remove_future(self);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Pause the task.
|
||||||
|
pub fn pause(&self) {
|
||||||
|
Runtime::with(|rt| rt.tasks.borrow()[self.0].active.set(false));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the task is paused.
|
||||||
|
pub fn paused(&self) -> bool {
|
||||||
|
Runtime::with(|rt| !rt.tasks.borrow()[self.0].active.get()).unwrap_or_default()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Resume the task.
|
||||||
|
pub fn resume(&self) {
|
||||||
|
Runtime::with(|rt| {
|
||||||
|
// set the active flag, and then ping the scheduler to ensure the task gets queued
|
||||||
|
rt.tasks.borrow()[self.0].active.set(true);
|
||||||
|
_ = rt.sender.unbounded_send(SchedulerMsg::TaskNotified(*self));
|
||||||
|
});
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Runtime {
|
impl Runtime {
|
||||||
@@ -55,9 +74,10 @@ impl Runtime {
|
|||||||
let task_id = Task(entry.key());
|
let task_id = Task(entry.key());
|
||||||
|
|
||||||
let task = Rc::new(LocalTask {
|
let task = Rc::new(LocalTask {
|
||||||
|
scope,
|
||||||
|
active: Cell::new(true),
|
||||||
parent: self.current_task(),
|
parent: self.current_task(),
|
||||||
task: RefCell::new(Box::pin(task)),
|
task: RefCell::new(Box::pin(task)),
|
||||||
scope,
|
|
||||||
waker: futures_util::task::waker(Arc::new(LocalTaskHandle {
|
waker: futures_util::task::waker(Arc::new(LocalTaskHandle {
|
||||||
id: task_id,
|
id: task_id,
|
||||||
tx: self.sender.clone(),
|
tx: self.sender.clone(),
|
||||||
@@ -73,18 +93,33 @@ impl Runtime {
|
|||||||
debug_assert!(self.tasks.try_borrow_mut().is_ok());
|
debug_assert!(self.tasks.try_borrow_mut().is_ok());
|
||||||
debug_assert!(task.task.try_borrow_mut().is_ok());
|
debug_assert!(task.task.try_borrow_mut().is_ok());
|
||||||
|
|
||||||
let mut cx = std::task::Context::from_waker(&task.waker);
|
self.sender
|
||||||
|
.unbounded_send(SchedulerMsg::TaskNotified(task_id))
|
||||||
if !task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
|
.expect("Scheduler should exist");
|
||||||
self.sender
|
|
||||||
.unbounded_send(SchedulerMsg::TaskNotified(task_id))
|
|
||||||
.expect("Scheduler should exist");
|
|
||||||
}
|
|
||||||
|
|
||||||
task_id
|
task_id
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Get the currently running task
|
||||||
|
pub fn current_task(&self) -> Option<Task> {
|
||||||
|
self.current_task.get()
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get the parent task of the given task, if it exists
|
||||||
|
pub fn parent_task(&self, task: Task) -> Option<Task> {
|
||||||
|
self.tasks.borrow().get(task.0)?.parent
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Add this task to the queue of tasks that will manually get poked when the scheduler is flushed
|
||||||
|
pub(crate) fn add_to_flush_table(&self) -> Task {
|
||||||
|
let value = self.current_task().unwrap();
|
||||||
|
self.flush_table.borrow_mut().insert(value);
|
||||||
|
value
|
||||||
|
}
|
||||||
|
|
||||||
pub(crate) fn handle_task_wakeup(&self, id: Task) {
|
pub(crate) fn handle_task_wakeup(&self, id: Task) {
|
||||||
|
debug_assert!(Runtime::current().is_some(), "Must be in a dioxus runtime");
|
||||||
|
|
||||||
let task = self.tasks.borrow().get(id.0).cloned();
|
let task = self.tasks.borrow().get(id.0).cloned();
|
||||||
|
|
||||||
// The task was removed from the scheduler, so we can just ignore it
|
// The task was removed from the scheduler, so we can just ignore it
|
||||||
@@ -92,6 +127,11 @@ impl Runtime {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// If a task woke up but is paused, we can just ignore it
|
||||||
|
if !task.active.get() {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
let mut cx = std::task::Context::from_waker(&task.waker);
|
let mut cx = std::task::Context::from_waker(&task.waker);
|
||||||
|
|
||||||
// update the scope stack
|
// update the scope stack
|
||||||
@@ -99,10 +139,9 @@ impl Runtime {
|
|||||||
self.rendering.set(false);
|
self.rendering.set(false);
|
||||||
self.current_task.set(Some(id));
|
self.current_task.set(Some(id));
|
||||||
|
|
||||||
// If the task completes...
|
|
||||||
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
|
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
|
||||||
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||||
self.get_context(task.scope)
|
self.get_state(task.scope)
|
||||||
.unwrap()
|
.unwrap()
|
||||||
.spawned_tasks
|
.spawned_tasks
|
||||||
.borrow_mut()
|
.borrow_mut()
|
||||||
@@ -118,31 +157,11 @@ impl Runtime {
|
|||||||
self.current_task.set(None);
|
self.current_task.set(None);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Take a queued task from the scheduler
|
|
||||||
pub(crate) fn take_queued_task(&self) -> Option<Task> {
|
|
||||||
self.queued_tasks.borrow_mut().pop_front()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Drop the future with the given TaskId
|
/// Drop the future with the given TaskId
|
||||||
///
|
///
|
||||||
/// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
|
/// This does not abort the task, so you'll want to wrap it in an abort handle if that's important to you
|
||||||
pub(crate) fn remove_task(&self, id: Task) -> Option<Rc<LocalTask>> {
|
pub(crate) fn remove_task(&self, id: Task) -> Option<Rc<LocalTask>> {
|
||||||
let task = self.tasks.borrow_mut().try_remove(id.0);
|
self.tasks.borrow_mut().try_remove(id.0)
|
||||||
|
|
||||||
// Remove the task from the queued tasks so we don't poll a different task with the same id
|
|
||||||
self.queued_tasks.borrow_mut().retain(|t| *t != id);
|
|
||||||
|
|
||||||
task
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the currently running task
|
|
||||||
pub fn current_task(&self) -> Option<Task> {
|
|
||||||
self.current_task.get()
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the parent task of the given task, if it exists
|
|
||||||
pub fn parent_task(&self, task: Task) -> Option<Task> {
|
|
||||||
self.tasks.borrow().get(task.0)?.parent
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -152,6 +171,7 @@ pub(crate) struct LocalTask {
|
|||||||
parent: Option<Task>,
|
parent: Option<Task>,
|
||||||
task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
|
task: RefCell<Pin<Box<dyn Future<Output = ()> + 'static>>>,
|
||||||
waker: Waker,
|
waker: Waker,
|
||||||
|
active: Cell<bool>,
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The type of message that can be sent to the scheduler.
|
/// The type of message that can be sent to the scheduler.
|
||||||
@@ -173,8 +193,7 @@ struct LocalTaskHandle {
|
|||||||
|
|
||||||
impl ArcWake for LocalTaskHandle {
|
impl ArcWake for LocalTaskHandle {
|
||||||
fn wake_by_ref(arc_self: &Arc<Self>) {
|
fn wake_by_ref(arc_self: &Arc<Self>) {
|
||||||
// This can fail if the scheduler has been dropped while the application is shutting down
|
_ = arc_self
|
||||||
let _ = arc_self
|
|
||||||
.tx
|
.tx
|
||||||
.unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
|
.unbounded_send(SchedulerMsg::TaskNotified(arc_self.id));
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,12 +13,12 @@ use crate::{
|
|||||||
nodes::{Template, TemplateId},
|
nodes::{Template, TemplateId},
|
||||||
runtime::{Runtime, RuntimeGuard},
|
runtime::{Runtime, RuntimeGuard},
|
||||||
scopes::ScopeId,
|
scopes::ScopeId,
|
||||||
AttributeValue, ComponentFunction, Element, Event, Mutations, Task,
|
AttributeValue, ComponentFunction, Element, Event, Mutations,
|
||||||
};
|
};
|
||||||
use futures_util::{pin_mut, StreamExt};
|
use futures_util::StreamExt;
|
||||||
use rustc_hash::{FxHashMap, FxHashSet};
|
use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use slab::Slab;
|
use slab::Slab;
|
||||||
use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
|
use std::{any::Any, collections::BTreeSet, rc::Rc};
|
||||||
|
|
||||||
/// A virtual node system that progresses user events and diffs UI trees.
|
/// A virtual node system that progresses user events and diffs UI trees.
|
||||||
///
|
///
|
||||||
@@ -29,7 +29,7 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
|
|||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
///
|
///
|
||||||
/// #[derive(Props, PartialEq)]
|
/// #[derive(Props, PartialEq, Clone)]
|
||||||
/// struct AppProps {
|
/// struct AppProps {
|
||||||
/// title: String
|
/// title: String
|
||||||
/// }
|
/// }
|
||||||
@@ -37,7 +37,7 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
|
|||||||
/// fn app(cx: AppProps) -> Element {
|
/// fn app(cx: AppProps) -> Element {
|
||||||
/// rsx!(
|
/// rsx!(
|
||||||
/// div {"hello, {cx.title}"}
|
/// div {"hello, {cx.title}"}
|
||||||
/// ))
|
/// )
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@@ -47,7 +47,7 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
|
|||||||
/// # #![allow(unused)]
|
/// # #![allow(unused)]
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
///
|
///
|
||||||
/// # #[derive(Props, PartialEq)]
|
/// # #[derive(Props, PartialEq, Clone)]
|
||||||
/// # struct AppProps {
|
/// # struct AppProps {
|
||||||
/// # title: String
|
/// # title: String
|
||||||
/// # }
|
/// # }
|
||||||
@@ -60,26 +60,26 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
|
|||||||
/// NavBar { routes: ROUTES }
|
/// NavBar { routes: ROUTES }
|
||||||
/// Title { "{cx.title}" }
|
/// Title { "{cx.title}" }
|
||||||
/// Footer {}
|
/// Footer {}
|
||||||
/// ))
|
/// )
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[component]
|
/// #[component]
|
||||||
/// fn NavBar( routes: &'static str) -> Element {
|
/// fn NavBar( routes: &'static str) -> Element {
|
||||||
/// rsx! {
|
/// rsx! {
|
||||||
/// div { "Routes: {routes}" }
|
/// div { "Routes: {routes}" }
|
||||||
/// })
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[component]
|
/// #[component]
|
||||||
/// fn Footer() -> Element {
|
/// fn Footer() -> Element {
|
||||||
/// rsx! { div { "Footer" } })
|
/// rsx! { div { "Footer" } }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// #[component]
|
/// #[component]
|
||||||
/// fn Title<'a>( children: Element) -> Element {
|
/// fn Title( children: Element) -> Element {
|
||||||
/// rsx! {
|
/// rsx! {
|
||||||
/// div { id: "title", {children} }
|
/// div { id: "title", {children} }
|
||||||
/// })
|
/// }
|
||||||
/// }
|
/// }
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
@@ -88,10 +88,10 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
|
|||||||
///
|
///
|
||||||
/// ```rust
|
/// ```rust
|
||||||
/// # use dioxus::prelude::*;
|
/// # use dioxus::prelude::*;
|
||||||
/// # fn app() -> Element { rsx! { div {} }) }
|
/// # fn app() -> Element { rsx! { div {} } }
|
||||||
///
|
///
|
||||||
/// let mut vdom = VirtualDom::new(app);
|
/// let mut vdom = VirtualDom::new(app);
|
||||||
/// let edits = vdom.rebuild();
|
/// let edits = vdom.rebuild_to_vec();
|
||||||
/// ```
|
/// ```
|
||||||
///
|
///
|
||||||
/// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
|
/// To call listeners inside the VirtualDom, call [`VirtualDom::handle_event`] with the appropriate event data.
|
||||||
@@ -130,7 +130,7 @@ use std::{any::Any, collections::BTreeSet, future::Future, rc::Rc};
|
|||||||
/// fn app() -> Element {
|
/// fn app() -> Element {
|
||||||
/// rsx! {
|
/// rsx! {
|
||||||
/// div { "Hello World" }
|
/// div { "Hello World" }
|
||||||
/// })
|
/// }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let dom = VirtualDom::new(app);
|
/// let dom = VirtualDom::new(app);
|
||||||
@@ -247,7 +247,7 @@ impl VirtualDom {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn Example(cx: SomeProps) -> Element {
|
/// fn Example(cx: SomeProps) -> Element {
|
||||||
/// rsx!{ div{ "hello {cx.name}" } })
|
/// rsx!{ div { "hello {cx.name}" } }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let dom = VirtualDom::new(Example);
|
/// let dom = VirtualDom::new(Example);
|
||||||
@@ -291,7 +291,7 @@ impl VirtualDom {
|
|||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// fn Example(cx: SomeProps) -> Element {
|
/// fn Example(cx: SomeProps) -> Element {
|
||||||
/// rsx!{ div{ "hello {cx.name}" } })
|
/// rsx!{ div{ "hello {cx.name}" } }
|
||||||
/// }
|
/// }
|
||||||
///
|
///
|
||||||
/// let dom = VirtualDom::new(Example);
|
/// let dom = VirtualDom::new(Example);
|
||||||
@@ -321,7 +321,7 @@ impl VirtualDom {
|
|||||||
let root = dom.new_scope(Box::new(root), "app");
|
let root = dom.new_scope(Box::new(root), "app");
|
||||||
|
|
||||||
// Unlike react, we provide a default error boundary that just renders the error as a string
|
// Unlike react, we provide a default error boundary that just renders the error as a string
|
||||||
root.context()
|
root.state()
|
||||||
.provide_context(Rc::new(ErrorBoundary::new_in_scope(ScopeId::ROOT)));
|
.provide_context(Rc::new(ErrorBoundary::new_in_scope(ScopeId::ROOT)));
|
||||||
|
|
||||||
// the root element is always given element ID 0 since it's the container for the entire tree
|
// the root element is always given element ID 0 since it's the container for the entire tree
|
||||||
@@ -354,7 +354,7 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// 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().context().provide_context(context);
|
self.base_scope().state().provide_context(context);
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -362,14 +362,14 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// This method is useful for when you want to provide a context in your app without knowing its type
|
/// This method is useful for when you want to provide a context in your app without knowing its type
|
||||||
pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {
|
pub fn insert_any_root_context(&mut self, context: Box<dyn Any>) {
|
||||||
self.base_scope().context().provide_any_context(context);
|
self.base_scope().state().provide_any_context(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Manually mark a scope as requiring a re-render
|
/// Manually mark a scope as requiring a re-render
|
||||||
///
|
///
|
||||||
/// Whenever the Runtime "works", it will re-render this scope
|
/// Whenever the Runtime "works", it will re-render this scope
|
||||||
pub fn mark_dirty(&mut self, id: ScopeId) {
|
pub fn mark_dirty(&mut self, id: ScopeId) {
|
||||||
if let Some(context) = self.runtime.get_context(id) {
|
if let Some(context) = self.runtime.get_state(id) {
|
||||||
let height = context.height();
|
let height = context.height();
|
||||||
tracing::trace!("Marking scope {:?} ({}) as dirty", id, context.name);
|
tracing::trace!("Marking scope {:?} ({}) as dirty", id, context.name);
|
||||||
self.dirty_scopes.insert(DirtyScope { height, id });
|
self.dirty_scopes.insert(DirtyScope { height, id });
|
||||||
@@ -417,52 +417,50 @@ impl VirtualDom {
|
|||||||
///
|
///
|
||||||
/// ```rust, ignore
|
/// ```rust, ignore
|
||||||
/// let dom = VirtualDom::new(app);
|
/// let dom = VirtualDom::new(app);
|
||||||
/// let sender = dom.get_scheduler_channel();
|
|
||||||
/// ```
|
/// ```
|
||||||
pub async fn wait_for_work(&mut self) {
|
pub async fn wait_for_work(&mut self) {
|
||||||
let mut some_msg = None;
|
// Ping tasks waiting on the flush table - they're waiting for sync stuff to be done before progressing
|
||||||
|
self.clear_flush_table();
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
// If a bunch of messages are ready in a sequence, try to pop them off synchronously
|
// Process all events - Scopes are marked dirty, etc
|
||||||
if let Some(msg) = some_msg.take() {
|
self.process_events();
|
||||||
match msg {
|
|
||||||
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
|
// Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures
|
||||||
SchedulerMsg::TaskNotified(task) => self.queue_task_wakeup(task),
|
if !self.dirty_scopes.is_empty() || !self.suspended_scopes.is_empty() {
|
||||||
}
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If they're not ready, then we should wait for them to be ready
|
// Make sure we set the runtime since we're running user code
|
||||||
match self.rx.try_next() {
|
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
||||||
Ok(Some(val)) => some_msg = Some(val),
|
|
||||||
Ok(None) => return,
|
|
||||||
Err(_) => {
|
|
||||||
// Now that we have collected all queued work, we should check if we have any dirty scopes. If there are not, then we can poll any queued futures
|
|
||||||
let has_dirty_scopes = !self.dirty_scopes.is_empty();
|
|
||||||
|
|
||||||
if !has_dirty_scopes {
|
match self.rx.next().await.expect("channel should never close") {
|
||||||
// If we have no dirty scopes, then we should poll any tasks that have been notified
|
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
|
||||||
while let Some(task) = self.runtime.take_queued_task() {
|
SchedulerMsg::TaskNotified(id) => self.runtime.handle_task_wakeup(id),
|
||||||
self.handle_task_wakeup(task);
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we have any dirty scopes, or finished fiber trees then we should exit
|
fn clear_flush_table(&mut self) {
|
||||||
if has_dirty_scopes || !self.suspended_scopes.is_empty() {
|
// Make sure we set the runtime since we're running user code
|
||||||
return;
|
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
||||||
}
|
|
||||||
|
|
||||||
some_msg = self.rx.next().await
|
// Manually flush tasks that called `flush().await`
|
||||||
}
|
// Tasks that might've been waiting for `flush` finally have a chance to run to their next await point
|
||||||
}
|
for task in self.runtime.flush_table.take() {
|
||||||
|
self.runtime.handle_task_wakeup(task);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Process all events in the queue until there are no more left
|
/// Process all events in the queue until there are no more left
|
||||||
pub fn process_events(&mut self) {
|
pub fn process_events(&mut self) {
|
||||||
|
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
||||||
|
|
||||||
|
// Prevent a task from deadlocking the runtime by repeatedly queueing itself
|
||||||
while let Ok(Some(msg)) = self.rx.try_next() {
|
while let Ok(Some(msg)) = self.rx.try_next() {
|
||||||
match msg {
|
match msg {
|
||||||
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
|
SchedulerMsg::Immediate(id) => self.mark_dirty(id),
|
||||||
SchedulerMsg::TaskNotified(task) => self.queue_task_wakeup(task),
|
SchedulerMsg::TaskNotified(task) => self.runtime.handle_task_wakeup(task),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -481,7 +479,7 @@ impl VirtualDom {
|
|||||||
if sync.template.get().name.rsplit_once(':').unwrap().0
|
if sync.template.get().name.rsplit_once(':').unwrap().0
|
||||||
== template.name.rsplit_once(':').unwrap().0
|
== template.name.rsplit_once(':').unwrap().0
|
||||||
{
|
{
|
||||||
let context = scope.context();
|
let context = scope.state();
|
||||||
let height = context.height;
|
let height = context.height;
|
||||||
self.dirty_scopes.insert(DirtyScope {
|
self.dirty_scopes.insert(DirtyScope {
|
||||||
height,
|
height,
|
||||||
@@ -542,18 +540,25 @@ impl VirtualDom {
|
|||||||
/// suspended subtrees.
|
/// suspended subtrees.
|
||||||
pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
|
pub fn render_immediate(&mut self, to: &mut impl WriteMutations) {
|
||||||
self.flush_templates(to);
|
self.flush_templates(to);
|
||||||
// Build a waker that won't wake up since our deadline is already expired when it's polled
|
|
||||||
let waker = futures_util::task::noop_waker();
|
|
||||||
let mut cx = std::task::Context::from_waker(&waker);
|
|
||||||
|
|
||||||
// Now run render with deadline but dont even try to poll any async tasks
|
// Process any events that might be pending in the queue
|
||||||
let fut = self.render_with_deadline(std::future::ready(()), to);
|
self.process_events();
|
||||||
pin_mut!(fut);
|
|
||||||
|
|
||||||
// The root component is not allowed to be async
|
// Next, diff any dirty scopes
|
||||||
match fut.poll(&mut cx) {
|
// We choose not to poll the deadline since we complete pretty quickly anyways
|
||||||
std::task::Poll::Ready(mutations) => mutations,
|
while let Some(dirty) = self.dirty_scopes.pop_first() {
|
||||||
std::task::Poll::Pending => panic!("render_immediate should never return pending"),
|
// If the scope doesn't exist for whatever reason, then we should skip it
|
||||||
|
if !self.scopes.contains(dirty.id.0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
||||||
|
// Run the scope and get the mutations
|
||||||
|
let new_nodes = self.run_scope(dirty.id);
|
||||||
|
|
||||||
|
self.diff_scope(to, dirty.id, new_nodes);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -567,72 +572,24 @@ impl VirtualDom {
|
|||||||
/// Render the virtual dom, waiting for all suspense to be finished
|
/// Render the virtual dom, waiting for all suspense to be finished
|
||||||
///
|
///
|
||||||
/// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
|
/// The mutations will be thrown out, so it's best to use this method for things like SSR that have async content
|
||||||
|
///
|
||||||
|
/// Tasks waiting to be flushed are *cleared* here *without running them*
|
||||||
|
/// This behavior is subject to change, but configured this way so use_future/use_memo/use_future won't react on the server
|
||||||
pub async fn wait_for_suspense(&mut self) {
|
pub async fn wait_for_suspense(&mut self) {
|
||||||
loop {
|
loop {
|
||||||
if self.suspended_scopes.is_empty() {
|
if self.suspended_scopes.is_empty() {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// not sure if we should be doing this?
|
||||||
|
self.runtime.flush_table.borrow_mut().clear();
|
||||||
|
|
||||||
self.wait_for_work().await;
|
self.wait_for_work().await;
|
||||||
|
|
||||||
self.render_immediate(&mut NoOpMutations);
|
self.render_immediate(&mut NoOpMutations);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Render what you can given the timeline and then move on
|
|
||||||
///
|
|
||||||
/// It's generally a good idea to put some sort of limit on the suspense process in case a future is having issues.
|
|
||||||
///
|
|
||||||
/// If no suspense trees are present
|
|
||||||
pub async fn render_with_deadline(
|
|
||||||
&mut self,
|
|
||||||
deadline: impl Future<Output = ()>,
|
|
||||||
to: &mut impl WriteMutations,
|
|
||||||
) {
|
|
||||||
self.flush_templates(to);
|
|
||||||
pin_mut!(deadline);
|
|
||||||
|
|
||||||
self.process_events();
|
|
||||||
|
|
||||||
loop {
|
|
||||||
// Next, diff any dirty scopes
|
|
||||||
// We choose not to poll the deadline since we complete pretty quickly anyways
|
|
||||||
while let Some(dirty) = self.dirty_scopes.pop_first() {
|
|
||||||
// If the scope doesn't exist for whatever reason, then we should skip it
|
|
||||||
if !self.scopes.contains(dirty.id.0) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
{
|
|
||||||
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
|
||||||
// Run the scope and get the mutations
|
|
||||||
let new_nodes = self.run_scope(dirty.id);
|
|
||||||
|
|
||||||
self.diff_scope(to, dirty.id, new_nodes);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Wait until the deadline is ready or we have work if there's no work ready
|
|
||||||
let work = self.wait_for_work();
|
|
||||||
pin_mut!(work);
|
|
||||||
|
|
||||||
use futures_util::future::{select, Either};
|
|
||||||
if let Either::Left((_, _)) = select(&mut deadline, &mut work).await {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// [`Self::render_with_deadline`] to a vector of mutations for testing purposes
|
|
||||||
pub async fn render_with_deadline_to_vec(
|
|
||||||
&mut self,
|
|
||||||
deadline: impl Future<Output = ()>,
|
|
||||||
) -> Mutations {
|
|
||||||
let mut mutations = Mutations::default();
|
|
||||||
self.render_with_deadline(deadline, &mut mutations).await;
|
|
||||||
mutations
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Get the current runtime
|
/// Get the current runtime
|
||||||
pub fn runtime(&self) -> Rc<Runtime> {
|
pub fn runtime(&self) -> Rc<Runtime> {
|
||||||
self.runtime.clone()
|
self.runtime.clone()
|
||||||
@@ -645,20 +602,6 @@ impl VirtualDom {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Queue a task to be polled after all dirty scopes have been rendered
|
|
||||||
fn queue_task_wakeup(&mut self, id: Task) {
|
|
||||||
self.runtime.queued_tasks.borrow_mut().push_back(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Handle notifications by tasks inside the scheduler
|
|
||||||
///
|
|
||||||
/// This is precise, meaning we won't poll every task, just tasks that have woken up as notified to use by the
|
|
||||||
/// queue
|
|
||||||
fn handle_task_wakeup(&mut self, id: Task) {
|
|
||||||
let _runtime = RuntimeGuard::new(self.runtime.clone());
|
|
||||||
self.runtime.handle_task_wakeup(id);
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
------------------------
|
------------------------
|
||||||
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
|
The algorithm works by walking through the list of dynamic attributes, checking their paths, and breaking when
|
||||||
@@ -764,7 +707,7 @@ impl Drop for VirtualDom {
|
|||||||
fn drop(&mut self) {
|
fn drop(&mut self) {
|
||||||
// Drop all scopes in order of height
|
// Drop all scopes in order of height
|
||||||
let mut scopes = self.scopes.drain().collect::<Vec<_>>();
|
let mut scopes = self.scopes.drain().collect::<Vec<_>>();
|
||||||
scopes.sort_by_key(|scope| scope.context().height);
|
scopes.sort_by_key(|scope| scope.state().height);
|
||||||
for scope in scopes.into_iter().rev() {
|
for scope in scopes.into_iter().rev() {
|
||||||
drop(scope);
|
drop(scope);
|
||||||
}
|
}
|
||||||
|
|||||||
+102
-12
@@ -1,11 +1,22 @@
|
|||||||
//! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
|
//! Verify that tasks get polled by the virtualdom properly, and that we escape wait_for_work safely
|
||||||
|
|
||||||
#[cfg(not(miri))]
|
use std::{sync::atomic::AtomicUsize, time::Duration};
|
||||||
|
|
||||||
|
use dioxus::prelude::*;
|
||||||
|
|
||||||
|
async fn run_vdom(app: fn() -> Element) {
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|
||||||
|
dom.rebuild(&mut dioxus_core::NoOpMutations);
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = dom.wait_for_work() => {}
|
||||||
|
_ = tokio::time::sleep(Duration::from_millis(500)) => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn it_works() {
|
async fn it_works() {
|
||||||
use dioxus::prelude::*;
|
|
||||||
use std::{sync::atomic::AtomicUsize, time::Duration};
|
|
||||||
|
|
||||||
static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);
|
static POLL_COUNT: AtomicUsize = AtomicUsize::new(0);
|
||||||
|
|
||||||
fn app() -> Element {
|
fn app() -> Element {
|
||||||
@@ -28,14 +39,7 @@ async fn it_works() {
|
|||||||
rsx!({ () })
|
rsx!({ () })
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut dom = VirtualDom::new(app);
|
run_vdom(app).await;
|
||||||
|
|
||||||
dom.rebuild(&mut dioxus_core::NoOpMutations);
|
|
||||||
|
|
||||||
tokio::select! {
|
|
||||||
_ = dom.wait_for_work() => {}
|
|
||||||
_ = tokio::time::sleep(Duration::from_millis(500)) => {}
|
|
||||||
};
|
|
||||||
|
|
||||||
// By the time the tasks are finished, we should've accumulated ticks from two tasks
|
// By the time the tasks are finished, we should've accumulated ticks from two tasks
|
||||||
// Be warned that by setting the delay to too short, tokio might not schedule in the tasks
|
// Be warned that by setting the delay to too short, tokio might not schedule in the tasks
|
||||||
@@ -44,3 +48,89 @@ async fn it_works() {
|
|||||||
135
|
135
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prove that yield_now doesn't cause a deadlock
|
||||||
|
#[tokio::test]
|
||||||
|
async fn yield_now_works() {
|
||||||
|
thread_local! {
|
||||||
|
static SEQUENCE: std::cell::RefCell<Vec<usize>> = std::cell::RefCell::new(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
// these two tasks should yield to eachother
|
||||||
|
use_hook(|| {
|
||||||
|
spawn(async move {
|
||||||
|
for x in 0..10 {
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
SEQUENCE.with(|s| s.borrow_mut().push(1));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
use_hook(|| {
|
||||||
|
spawn(async move {
|
||||||
|
for x in 0..10 {
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
SEQUENCE.with(|s| s.borrow_mut().push(2));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!({ () })
|
||||||
|
}
|
||||||
|
|
||||||
|
run_vdom(app).await;
|
||||||
|
|
||||||
|
SEQUENCE.with(|s| assert_eq!(s.borrow().len(), 20));
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Ensure that calling wait_for_flush waits for dioxus to finish its syncrhonous work
|
||||||
|
#[tokio::test]
|
||||||
|
async fn flushing() {
|
||||||
|
thread_local! {
|
||||||
|
static SEQUENCE: std::cell::RefCell<Vec<usize>> = std::cell::RefCell::new(Vec::new());
|
||||||
|
}
|
||||||
|
|
||||||
|
fn app() -> Element {
|
||||||
|
use_hook(|| {
|
||||||
|
spawn(async move {
|
||||||
|
for x in 0..10 {
|
||||||
|
flush_sync().await;
|
||||||
|
SEQUENCE.with(|s| s.borrow_mut().push(1));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
use_hook(|| {
|
||||||
|
spawn(async move {
|
||||||
|
for x in 0..10 {
|
||||||
|
flush_sync().await;
|
||||||
|
SEQUENCE.with(|s| s.borrow_mut().push(2));
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
rsx!({ () })
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut dom = VirtualDom::new(app);
|
||||||
|
|
||||||
|
dom.rebuild(&mut dioxus_core::NoOpMutations);
|
||||||
|
|
||||||
|
let fut = async {
|
||||||
|
// Trigger the flush by waiting for work
|
||||||
|
for _ in 0..40 {
|
||||||
|
tokio::select! {
|
||||||
|
_ = dom.wait_for_work() => {}
|
||||||
|
_ = tokio::time::sleep(Duration::from_millis(1)) => {}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
tokio::select! {
|
||||||
|
_ = fut => {}
|
||||||
|
_ = tokio::time::sleep(Duration::from_millis(500)) => {}
|
||||||
|
};
|
||||||
|
|
||||||
|
SEQUENCE.with(|s| assert_eq!(s.borrow().len(), 20));
|
||||||
|
}
|
||||||
@@ -65,7 +65,7 @@ struct BoxProps {
|
|||||||
alpha: f32,
|
alpha: f32,
|
||||||
}
|
}
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn Box(cx: Scope<BoxProps>) -> Element {
|
fn Box(cx: ScopeState<BoxProps>) -> Element {
|
||||||
let count = use_signal(|| 0);
|
let count = use_signal(|| 0);
|
||||||
|
|
||||||
let x = cx.props.x * 2;
|
let x = cx.props.x * 2;
|
||||||
@@ -94,7 +94,7 @@ struct GridProps {
|
|||||||
update_count: usize,
|
update_count: usize,
|
||||||
}
|
}
|
||||||
#[allow(non_snake_case)]
|
#[allow(non_snake_case)]
|
||||||
fn Grid(cx: Scope<GridProps>) -> Element {
|
fn Grid(cx: ScopeState<GridProps>) -> Element {
|
||||||
let size = cx.props.size;
|
let size = cx.props.size;
|
||||||
let count = use_signal(|| 0);
|
let count = use_signal(|| 0);
|
||||||
let counts = use_signal(|| vec![0; size * size]);
|
let counts = use_signal(|| vec![0; size * size]);
|
||||||
@@ -151,7 +151,7 @@ fn Grid(cx: Scope<GridProps>) -> Element {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope<GridProps>) -> Element {
|
fn app(cx: ScopeState<GridProps>) -> Element {
|
||||||
rsx! {
|
rsx! {
|
||||||
div{
|
div{
|
||||||
width: "100%",
|
width: "100%",
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ use crate::{
|
|||||||
LiveViewError,
|
LiveViewError,
|
||||||
};
|
};
|
||||||
use dioxus_core::prelude::*;
|
use dioxus_core::prelude::*;
|
||||||
use dioxus_html::{EventData, HtmlEvent, PlatformEventData};
|
use dioxus_html::{select, EventData, HtmlEvent, PlatformEventData};
|
||||||
use dioxus_interpreter_js::MutationState;
|
use dioxus_interpreter_js::MutationState;
|
||||||
use futures_util::{pin_mut, SinkExt, StreamExt};
|
use futures_util::{pin_mut, SinkExt, StreamExt};
|
||||||
use serde::Serialize;
|
use serde::Serialize;
|
||||||
@@ -227,11 +227,14 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
vdom.render_with_deadline(
|
// wait for suspense to resolve in a 10ms window
|
||||||
tokio::time::sleep(Duration::from_millis(10)),
|
tokio::select! {
|
||||||
&mut mutations,
|
_ = tokio::time::sleep(Duration::from_millis(10)) => {}
|
||||||
)
|
_ = vdom.wait_for_suspense() => {}
|
||||||
.await;
|
}
|
||||||
|
|
||||||
|
// render the vdom
|
||||||
|
vdom.render_immediate(&mut mutations);
|
||||||
|
|
||||||
if let Some(edits) = take_edits(&mut mutations) {
|
if let Some(edits) = take_edits(&mut mutations) {
|
||||||
ws.send(edits).await?;
|
ws.send(edits).await?;
|
||||||
|
|||||||
@@ -151,7 +151,7 @@ where
|
|||||||
|
|
||||||
#[cfg(feature = "serde")]
|
#[cfg(feature = "serde")]
|
||||||
/// A component that renders the current route.
|
/// A component that renders the current route.
|
||||||
pub fn Router<R: Routable + Clone>(cx: Scope<RouterProps<R>>) -> Element
|
pub fn Router<R: Routable + Clone>(cx: ScopeState<RouterProps<R>>) -> Element
|
||||||
where
|
where
|
||||||
<R as FromStr>::Err: std::fmt::Display,
|
<R as FromStr>::Err: std::fmt::Display,
|
||||||
R: serde::Serialize + serde::de::DeserializeOwned,
|
R: serde::Serialize + serde::de::DeserializeOwned,
|
||||||
|
|||||||
Reference in new issue
Block a user