move ScopeId methods to the runtime (#4723)

* move `ScopeId` methods to runtime methods

* cargo check unused

* wip...

* wip...

* fix consume context

* fix test

* guard hoisted

* fix doc comment
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2025-10-04 01:55:52 -07:00
1 parent c56bac04f4
commit c29cef4ea3
40 files changed
+466 -697

No files matched your search

+3 -3
View File
@@ -55,7 +55,7 @@ impl VirtualDom {
self.scopes[scope.0].last_rendered_node = Some(LastRenderedNode::new(new_nodes)); self.scopes[scope.0].last_rendered_node = Some(LastRenderedNode::new(new_nodes));
if render_to.is_some() { if render_to.is_some() {
self.runtime.get_state(scope).unwrap().mount(&self.runtime); self.runtime.get_state(scope).mount(&self.runtime);
} }
}) })
} }
@@ -84,7 +84,7 @@ impl VirtualDom {
self.scopes[scope.0].last_rendered_node = Some(new_nodes); self.scopes[scope.0].last_rendered_node = Some(new_nodes);
if render_to.is_some() { if render_to.is_some() {
self.runtime.get_state(scope).unwrap().mount(&self.runtime); self.runtime.get_state(scope).mount(&self.runtime);
} }
nodes nodes
@@ -146,7 +146,7 @@ impl VNode {
// Now diff the scope // Now diff the scope
dom.run_and_diff_scope(to, scope_id); dom.run_and_diff_scope(to, scope_id);
let height = dom.runtime.get_state(scope_id).unwrap().height; let height = dom.runtime.get_state(scope_id).height;
dom.dirty_scopes.remove(&ScopeOrder::new(height, scope_id)); dom.dirty_scopes.remove(&ScopeOrder::new(height, scope_id));
} }
+1 -1
View File
@@ -67,7 +67,7 @@ pub fn provide_create_error_boundary(create_error_boundary: fn() -> ErrorContext
/// Create an error boundary with the current error boundary factory (either hydration compatible or default) /// Create an error boundary with the current error boundary factory (either hydration compatible or default)
fn create_error_boundary() -> ErrorContext { fn create_error_boundary() -> ErrorContext {
let create_error_boundary: CreateErrorBoundary = try_consume_context().unwrap_or_default(); let create_error_boundary = try_consume_context::<CreateErrorBoundary>().unwrap_or_default();
(create_error_boundary.0)() (create_error_boundary.0)()
} }
+7 -12
View File
@@ -487,10 +487,8 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
pub fn new<MaybeAsync: SpawnIfAsync<Marker, Ret>, Marker>( pub fn new<MaybeAsync: SpawnIfAsync<Marker, Ret>, Marker>(
mut f: impl FnMut(Args) -> MaybeAsync + 'static, mut f: impl FnMut(Args) -> MaybeAsync + 'static,
) -> Self { ) -> Self {
let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e)); let runtime = Runtime::current();
let origin = runtime let origin = runtime.current_scope_id();
.current_scope_id()
.unwrap_or_else(|e| panic!("{}", e));
let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>(); let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>();
let callback = owner.insert_rc(Some(ExternalListenerCallback { let callback = owner.insert_rc(Some(ExternalListenerCallback {
callback: Box::new(move |event: Args| f(event).spawn()), callback: Box::new(move |event: Args| f(event).spawn()),
@@ -502,10 +500,8 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
/// Leak a new [`Callback`] that will not be dropped unless it is manually dropped. /// Leak a new [`Callback`] that will not be dropped unless it is manually dropped.
#[track_caller] #[track_caller]
pub fn leak(mut f: impl FnMut(Args) -> Ret + 'static) -> Self { pub fn leak(mut f: impl FnMut(Args) -> Ret + 'static) -> Self {
let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e)); let runtime = Runtime::current();
let origin = runtime let origin = runtime.current_scope_id();
.current_scope_id()
.unwrap_or_else(|e| panic!("{}", e));
let callback = GenerationalBox::leak_rc( let callback = GenerationalBox::leak_rc(
Some(ExternalListenerCallback { Some(ExternalListenerCallback {
callback: Box::new(move |event: Args| f(event).spawn()), callback: Box::new(move |event: Args| f(event).spawn()),
@@ -547,7 +543,7 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
/// Replace the function in the callback with a new one /// Replace the function in the callback with a new one
pub fn replace(&mut self, callback: Box<dyn FnMut(Args) -> Ret>) { pub fn replace(&mut self, callback: Box<dyn FnMut(Args) -> Ret>) {
let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e)); let runtime = Runtime::current();
self.callback.set(Some(ExternalListenerCallback { self.callback.set(Some(ExternalListenerCallback {
callback, callback,
runtime: Rc::downgrade(&runtime), runtime: Rc::downgrade(&runtime),
@@ -647,7 +643,7 @@ impl<T> ListenerCallback<T> {
MaybeAsync: SpawnIfAsync<Marker>, MaybeAsync: SpawnIfAsync<Marker>,
{ {
Self { Self {
origin: current_scope_id().expect("ListenerCallback must be created within a scope"), origin: current_scope_id(),
callback: Rc::new(RefCell::new(move |event: Event<dyn Any>| { callback: Rc::new(RefCell::new(move |event: Event<dyn Any>| {
let data = event.data.downcast::<T>().unwrap(); let data = event.data.downcast::<T>().unwrap();
f(Event { f(Event {
@@ -665,8 +661,7 @@ impl<T> ListenerCallback<T> {
/// This is expected to be called within a runtime scope. Make sure a runtime is current before /// This is expected to be called within a runtime scope. Make sure a runtime is current before
/// calling this method. /// calling this method.
pub fn call(&self, event: Event<dyn Any>) { pub fn call(&self, event: Event<dyn Any>) {
let runtime = Runtime::current().expect("ListenerCallback must be called within a runtime"); Runtime::current().with_scope_on_stack(self.origin, || {
runtime.with_scope_on_stack(self.origin, || {
(self.callback.borrow_mut())(event); (self.callback.borrow_mut())(event);
}); });
} }
+2 -14
View File
@@ -2,15 +2,12 @@
//! //!
//! Each scope in dioxus has a single [Owner] //! Each scope in dioxus has a single [Owner]
use generational_box::{AnyStorage, Owner, SyncStorage, UnsyncStorage};
use std::{ use std::{
any::{Any, TypeId}, any::{Any, TypeId},
cell::RefCell, cell::RefCell,
}; };
use generational_box::{AnyStorage, Owner, SyncStorage, UnsyncStorage};
use crate::{innerlude::current_scope_id, Runtime, ScopeId};
/// Run a closure with the given owner. /// Run a closure with the given owner.
/// ///
/// This will override the default owner for the current component. /// This will override the default owner for the current component.
@@ -84,14 +81,5 @@ pub fn current_owner<S: AnyStorage>() -> Owner<S> {
return owner; return owner;
} }
// Otherwise get the owner from the current scope crate::Runtime::current().current_owner()
current_scope_id().expect("in a virtual dom").owner()
}
impl ScopeId {
/// Get the owner for the current scope.
#[track_caller]
pub fn owner<S: AnyStorage>(self) -> Owner<S> {
Runtime::with_scope(self, |cx| cx.owner::<S>()).unwrap_or_else(|e| panic!("{}", e))
}
} }
+24 -70
View File
@@ -1,23 +1,12 @@
use crate::{innerlude::CapturedError, runtime::RuntimeError}; use crate::innerlude::CapturedError;
use crate::{ use crate::{innerlude::SuspendedFuture, runtime::Runtime, Element, ScopeId, Task};
innerlude::SuspendedFuture, runtime::Runtime, Element, ScopeId, SuspenseContext, Task,
};
use std::future::Future; use std::future::Future;
use std::rc::Rc; use std::rc::Rc;
use std::sync::Arc; use std::sync::Arc;
/// Get the current scope id /// Get the current scope id
pub fn current_scope_id() -> Result<ScopeId, RuntimeError> { pub fn current_scope_id() -> ScopeId {
Runtime::with(|rt| rt.current_scope_id().ok()) Runtime::with(|rt| rt.current_scope_id())
.ok()
.flatten()
.ok_or(RuntimeError::new())
}
#[doc(hidden)]
/// Check if the virtual dom is currently inside of the body of a component
pub fn vdom_is_rendering() -> bool {
Runtime::with(|rt| rt.rendering.get()).unwrap_or_default()
} }
/// Throw a [`CapturedError`] into the current scope. The error will bubble up to the nearest [`crate::ErrorBoundary()`] or the root of the app. /// Throw a [`CapturedError`] into the current scope. The error will bubble up to the nearest [`crate::ErrorBoundary()`] or the root of the app.
@@ -30,7 +19,7 @@ pub fn vdom_is_rendering() -> bool {
/// match reqwest::get("https://api.example.com").await { /// match reqwest::get("https://api.example.com").await {
/// Ok(_) => unimplemented!(), /// Ok(_) => unimplemented!(),
/// // You can explicitly throw an error into a scope with throw_error /// // You can explicitly throw an error into a scope with throw_error
/// Err(err) => ScopeId::APP.throw_error(err) /// Err(err) => dioxus::core::throw_error(err),
/// } /// }
/// }); /// });
/// ///
@@ -38,58 +27,40 @@ pub fn vdom_is_rendering() -> bool {
/// } /// }
/// ``` /// ```
pub fn throw_error(error: impl Into<CapturedError> + 'static) { pub fn throw_error(error: impl Into<CapturedError> + 'static) {
current_scope_id() Runtime::with(|rt| rt.throw_error(rt.current_scope_id(), error))
.unwrap_or_else(|e| panic!("{}", e))
.throw_error(error)
}
/// Get the suspense context the current scope is in
pub fn suspense_context() -> Option<SuspenseContext> {
current_scope_id()
.unwrap_or_else(|e| panic!("{}", e))
.suspense_context()
} }
/// Consume context from the current scope /// Consume context from the current scope
pub fn try_consume_context<T: 'static + Clone>() -> Option<T> { pub fn try_consume_context<T: 'static + Clone>() -> Option<T> {
Runtime::with_current_scope(|cx| cx.consume_context::<T>()) Runtime::with_current_scope(|cx| cx.consume_context::<T>())
.ok()
.flatten()
} }
/// Consume context from the current scope /// Consume context from the current scope
pub fn consume_context<T: 'static + Clone>() -> T { pub fn consume_context<T: 'static + Clone>() -> T {
Runtime::with_current_scope(|cx| cx.consume_context::<T>()) Runtime::with_current_scope(|cx| cx.consume_context::<T>())
.ok()
.flatten()
.unwrap_or_else(|| panic!("Could not find context {}", std::any::type_name::<T>())) .unwrap_or_else(|| panic!("Could not find context {}", std::any::type_name::<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::current()
rt.get_state(scope_id) .try_get_state(scope_id)
.and_then(|cx| cx.consume_context::<T>()) .and_then(|cx| cx.consume_context::<T>())
})
.ok()
.flatten()
} }
/// Check if the current scope has a context /// Check if the current scope has a context
pub fn has_context<T: 'static + Clone>() -> Option<T> { pub fn has_context<T: 'static + Clone>() -> Option<T> {
Runtime::with_current_scope(|cx| cx.has_context::<T>()) Runtime::with_current_scope(|cx| cx.has_context::<T>())
.ok()
.flatten()
} }
/// Provide context to the current scope /// Provide context to the current scope
pub fn provide_context<T: 'static + Clone>(value: T) -> T { pub fn provide_context<T: 'static + Clone>(value: T) -> T {
Runtime::with_current_scope(|cx| cx.provide_context(value)).unwrap() Runtime::with_current_scope(|cx| cx.provide_context(value))
} }
/// Provide a context to the root scope /// Provide a context to the root scope
pub fn provide_root_context<T: 'static + Clone>(value: T) -> T { pub fn provide_root_context<T: 'static + Clone>(value: T) -> T {
Runtime::with_current_scope(|cx| cx.provide_root_context(value)).unwrap() Runtime::with_current_scope(|cx| cx.provide_root_context(value))
} }
/// Suspended the current component on a specific task and then return None /// Suspended the current component on a specific task and then return None
@@ -128,7 +99,7 @@ pub fn suspend(task: Task) -> Element {
/// ///
#[doc = include_str!("../docs/common_spawn_errors.md")] #[doc = include_str!("../docs/common_spawn_errors.md")]
pub fn spawn_isomorphic(fut: impl Future<Output = ()> + 'static) -> Task { pub fn spawn_isomorphic(fut: impl Future<Output = ()> + 'static) -> Task {
Runtime::with_current_scope(|cx| cx.spawn_isomorphic(fut)).unwrap() Runtime::with_current_scope(|cx| cx.spawn_isomorphic(fut))
} }
/// Spawns the future and returns the [`Task`]. This task will automatically be canceled when the component is dropped. /// Spawns the future and returns the [`Task`]. This task will automatically be canceled when the component is dropped.
@@ -154,12 +125,12 @@ pub fn spawn_isomorphic(fut: impl Future<Output = ()> + 'static) -> Task {
/// ///
#[doc = include_str!("../docs/common_spawn_errors.md")] #[doc = include_str!("../docs/common_spawn_errors.md")]
pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task { pub fn spawn(fut: impl Future<Output = ()> + 'static) -> Task {
Runtime::with_current_scope(|cx| cx.spawn(fut)).unwrap() Runtime::with_current_scope(|cx| cx.spawn(fut))
} }
/// Queue an effect to run after the next render. You generally shouldn't need to interact with this function directly. [use_effect](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) will call this function for you. /// Queue an effect to run after the next render. You generally shouldn't need to interact with this function directly. [use_effect](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_effect.html) will call this function for you.
pub fn queue_effect(f: impl FnOnce() + 'static) { pub fn queue_effect(f: impl FnOnce() + 'static) {
Runtime::with_current_scope(|cx| cx.queue_effect(f)).unwrap() Runtime::with_current_scope(|cx| cx.queue_effect(f))
} }
/// Spawn a future that Dioxus won't clean up when this component is unmounted /// Spawn a future that Dioxus won't clean up when this component is unmounted
@@ -215,8 +186,8 @@ pub fn queue_effect(f: impl FnOnce() + 'static) {
/// ``` /// ```
/// ///
#[doc = include_str!("../docs/common_spawn_errors.md")] #[doc = include_str!("../docs/common_spawn_errors.md")]
pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Option<Task> { pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Task {
Runtime::with_scope(ScopeId::ROOT, |cx| cx.spawn(fut)).ok() Runtime::with_scope(ScopeId::ROOT, |cx| cx.spawn(fut))
} }
/// 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.
@@ -311,31 +282,29 @@ pub fn remove_future(id: Task) {
/// ``` /// ```
#[track_caller] #[track_caller]
pub fn use_hook<State: Clone + 'static>(initializer: impl FnOnce() -> State) -> State { pub fn use_hook<State: Clone + 'static>(initializer: impl FnOnce() -> State) -> State {
Runtime::with_current_scope(|cx| cx.use_hook(initializer)).unwrap() Runtime::with_current_scope(|cx| cx.use_hook(initializer))
} }
/// 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.
pub fn generation() -> usize { pub fn generation() -> usize {
Runtime::with_current_scope(|cx| cx.generation()).unwrap() Runtime::with_current_scope(|cx| cx.generation())
} }
/// Get the parent of the current scope if it exists. /// Get the parent of the current scope if it exists.
pub fn parent_scope() -> Option<ScopeId> { pub fn parent_scope() -> Option<ScopeId> {
Runtime::with_current_scope(|cx| cx.parent_id()) Runtime::with_current_scope(|cx| cx.parent_id())
.ok()
.flatten()
} }
/// Mark the current scope as dirty, causing it to re-render. /// Mark the current scope as dirty, causing it to re-render.
pub fn needs_update() { pub fn needs_update() {
let _ = Runtime::with_current_scope(|cx| cx.needs_update()); Runtime::with_current_scope(|cx| cx.needs_update());
} }
/// Mark the current scope as dirty, causing it to re-render. /// Mark the current scope as dirty, causing it to re-render.
pub fn needs_update_any(id: ScopeId) { pub fn needs_update_any(id: ScopeId) {
let _ = Runtime::with_current_scope(|cx| cx.needs_update_any(id)); Runtime::with_current_scope(|cx| cx.needs_update_any(id));
} }
/// Schedule an update for the current component. /// Schedule an update for the current component.
@@ -349,7 +318,7 @@ pub fn needs_update_any(id: ScopeId) {
/// You should prefer [`schedule_update_any`] if you need to update multiple components. /// You should prefer [`schedule_update_any`] if you need to update multiple components.
#[track_caller] #[track_caller]
pub fn schedule_update() -> Arc<dyn Fn() + Send + Sync> { pub fn schedule_update() -> Arc<dyn Fn() + Send + Sync> {
Runtime::with_current_scope(|cx| cx.schedule_update()).unwrap_or_else(|e| panic!("{}", e)) Runtime::with_current_scope(|cx| cx.schedule_update())
} }
/// Schedule an update for any component given its [`ScopeId`]. /// Schedule an update for any component given its [`ScopeId`].
@@ -362,7 +331,7 @@ pub fn schedule_update() -> Arc<dyn Fn() + Send + Sync> {
/// If the desired behavior is to schedule invalidation of the current rendering of a component, use [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead. /// If the desired behavior is to schedule invalidation of the current rendering of a component, use [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.
#[track_caller] #[track_caller]
pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> { pub fn schedule_update_any() -> Arc<dyn Fn(ScopeId) + Send + Sync> {
Runtime::with_current_scope(|cx| cx.schedule_update_any()).unwrap_or_else(|e| panic!("{}", e)) Runtime::with_current_scope(|cx| cx.schedule_update_any())
} }
/// Creates a callback that will be run before the component is removed. /// Creates a callback that will be run before the component is removed.
@@ -468,12 +437,12 @@ pub fn use_after_render(f: impl FnMut() + 'static) {
/// This is a hook and will always run, so you can't unschedule it /// 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 /// Will run for every progression of suspense, though this might change in the future
pub fn before_render(f: impl FnMut() + 'static) { pub fn before_render(f: impl FnMut() + 'static) {
let _ = Runtime::with_current_scope(|cx| cx.push_before_render(f)); Runtime::with_current_scope(|cx| cx.push_before_render(f));
} }
/// Push a function to be run after the render is complete, even if it didn't complete successfully /// Push a function to be run after the render is complete, even if it didn't complete successfully
pub fn after_render(f: impl FnMut() + 'static) { pub fn after_render(f: impl FnMut() + 'static) {
let _ = Runtime::with_current_scope(|cx| cx.push_after_render(f)); Runtime::with_current_scope(|cx| cx.push_after_render(f));
} }
/// Use a hook with a cleanup function /// Use a hook with a cleanup function
@@ -486,18 +455,3 @@ pub fn use_hook_with_cleanup<T: Clone + 'static>(
use_drop(move || cleanup(_value)); use_drop(move || cleanup(_value));
value value
} }
/// Force every component to be dirty and require a re-render. Used by hot-reloading.
///
/// This might need to change to a different flag in the event hooks order changes within components.
/// What we really need is a way to mark components as needing a complete rebuild if they were hit by changes.
pub fn force_all_dirty() {
Runtime::with(|rt| {
rt.scope_states.borrow_mut().iter().for_each(|state| {
if let Some(scope) = state.as_ref() {
scope.needs_update();
}
});
})
.expect("Runtime to exist");
}
+13 -14
View File
@@ -91,20 +91,19 @@ pub(crate) mod innerlude {
pub use crate::innerlude::{ pub use crate::innerlude::{
anyhow, consume_context, consume_context_from_scope, current_owner, current_scope_id, anyhow, consume_context, consume_context_from_scope, current_owner, current_scope_id,
fc_to_builder, force_all_dirty, generation, has_context, needs_update, needs_update_any, fc_to_builder, generation, has_context, needs_update, needs_update_any, parent_scope,
parent_scope, provide_context, provide_create_error_boundary, provide_root_context, provide_context, provide_create_error_boundary, provide_root_context, queue_effect,
queue_effect, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, spawn_isomorphic,
spawn_isomorphic, suspend, suspense_context, throw_error, try_consume_context, suspend, throw_error, try_consume_context, use_after_render, use_before_render, use_drop,
use_after_render, use_before_render, use_drop, use_hook, use_hook_with_cleanup, use_hook, use_hook_with_cleanup, with_owner, AnyValue, AnyhowContext, Attribute,
vdom_is_rendering, with_owner, AnyValue, AnyhowContext, Attribute, AttributeValue, Callback, AttributeValue, Callback, CapturedError, Component, ComponentFunction, DynamicNode, Element,
CapturedError, Component, ComponentFunction, DynamicNode, Element, ElementId, Error, ElementId, Error, ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes,
ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue, IntoAttributeValue, IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation,
IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation, Mutations, NoOpMutations, Mutations, NoOpMutations, OptionStringFromMarker, Properties, ReactiveContext, RenderError,
OptionStringFromMarker, Properties, ReactiveContext, RenderError, Result, Runtime, Result, Runtime, RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers,
RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers, SuperFrom, SuperFrom, SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps,
SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps, SuspenseContext, Task, SuspenseContext, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode,
Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner, VPlaceholder, VText, VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
VirtualDom, WriteMutations,
}; };
pub use anyhow::Ok; pub use anyhow::Ok;
+2 -6
View File
@@ -62,11 +62,7 @@ impl ReactiveContext {
} }
let _ = tx.unbounded_send(()); let _ = tx.unbounded_send(());
}; };
let _self = Self::new_with_callback( let _self = Self::new_with_callback(callback, current_scope_id(), origin);
callback,
current_scope_id().unwrap_or_else(|e| panic!("{}", e)),
origin,
);
(_self, rx) (_self, rx)
} }
@@ -86,7 +82,7 @@ impl ReactiveContext {
scope: None, scope: None,
}; };
let owner = scope.owner(); let owner = Runtime::current().scope_owner(scope);
let self_ = Self { let self_ = Self {
scope, scope,
+196 -99
View File
@@ -1,8 +1,11 @@
use crate::arena::ElementRef;
use crate::innerlude::{DirtyTasks, Effect};
use crate::nodes::VNodeMount; use crate::nodes::VNodeMount;
use crate::scheduler::ScopeOrder; use crate::scheduler::ScopeOrder;
use crate::scope_context::SuspenseLocation; use crate::scope_context::SuspenseLocation;
use crate::{arena::ElementRef, CapturedError};
use crate::{
innerlude::{DirtyTasks, Effect},
SuspenseContext,
};
use crate::{ use crate::{
innerlude::{LocalTask, SchedulerMsg}, innerlude::{LocalTask, SchedulerMsg},
scope_context::Scope, scope_context::Scope,
@@ -10,11 +13,11 @@ use crate::{
Task, Task,
}; };
use crate::{AttributeValue, ElementId, Event}; use crate::{AttributeValue, ElementId, Event};
use generational_box::{AnyStorage, Owner};
use slab::Slab; use slab::Slab;
use slotmap::DefaultKey; use slotmap::DefaultKey;
use std::any::Any; use std::any::Any;
use std::collections::BTreeSet; use std::collections::BTreeSet;
use std::fmt;
use std::{ use std::{
cell::{Cell, Ref, RefCell}, cell::{Cell, Ref, RefCell},
rc::Rc, rc::Rc,
@@ -27,8 +30,6 @@ 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_states: RefCell<Vec<Option<Scope>>>,
// We use this to track the current scope // We use this to track the current scope
// This stack should only be modified through [`Runtime::with_scope_on_stack`] to ensure that the stack is correctly restored // This stack should only be modified through [`Runtime::with_scope_on_stack`] to ensure that the stack is correctly restored
scope_stack: RefCell<Vec<ScopeId>>, scope_stack: RefCell<Vec<ScopeId>>,
@@ -37,6 +38,9 @@ pub struct Runtime {
// This stack should only be modified through [`Runtime::with_suspense_location`] to ensure that the stack is correctly restored // This stack should only be modified through [`Runtime::with_suspense_location`] to ensure that the stack is correctly restored
suspense_stack: RefCell<Vec<SuspenseLocation>>, suspense_stack: RefCell<Vec<SuspenseLocation>>,
// A hand-rolled slab of scope states
pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,
// We use this to track the current task // We use this to track the current task
pub(crate) current_task: Cell<Option<Task>>, pub(crate) current_task: Cell<Option<Task>>,
@@ -89,18 +93,55 @@ impl Runtime {
} }
/// Get the current runtime /// Get the current runtime
pub fn current() -> Result<Rc<Self>, RuntimeError> { pub fn current() -> Rc<Self> {
RUNTIMES RUNTIMES
.with(|stack| stack.borrow().last().cloned()) .with(|stack| stack.borrow().last().cloned())
.ok_or(RuntimeError::new()) .unwrap_or_else(|| {
panic!(
"Must be called from inside a Dioxus runtime.
Help: Some APIs in dioxus require a global runtime to be present.
If you are calling one of these APIs from outside of a dioxus runtime
(typically in a web-sys closure or dynamic library), you will need to
grab the runtime from a scope that has it and then move it into your
new scope with a runtime guard.
For example, if you are trying to use dioxus apis from a web-sys
closure, you can grab the runtime from the scope it is created in:
```rust
use dioxus::prelude::*;
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
#[component]
fn MyComponent() -> Element {{
use_effect(|| {{
// Grab the runtime from the MyComponent scope
let runtime = Runtime::current().expect(\"Components run in the Dioxus runtime\");
// Move the runtime into the web-sys closure scope
let web_sys_closure = Closure::new(|| {{
// Then create a guard to provide the runtime to the closure
let _guard = RuntimeGuard::new(runtime);
// and run whatever code needs the runtime
tracing::info!(\"The count is: {{COUNT}}\");
}});
}})
}}
```"
)
})
}
/// Try to get the current runtime, returning None if it doesn't exist (outside the context of a dioxus app)
pub fn try_current() -> Option<Rc<Self>> {
RUNTIMES.with(|stack| stack.borrow().last().cloned())
} }
/// Wrap a closure so that it always runs in the runtime that is currently active /// Wrap a closure so that it always runs in the runtime that is currently active
pub fn wrap_closure<'a, I, O>(f: impl Fn(I) -> O + 'a) -> impl Fn(I) -> O + 'a { pub fn wrap_closure<'a, I, O>(f: impl Fn(I) -> O + 'a) -> impl Fn(I) -> O + 'a {
let current_runtime = Self::current().unwrap(); let current_runtime = Self::current();
let current_scope = current_runtime.current_scope_id().ok(); move |input| match current_runtime.try_current_scope_id() {
move |input| match current_scope { Some(scope) => current_runtime.in_scope(scope, || f(input)),
Some(scope) => current_runtime.on_scope(scope, || f(input)),
None => { None => {
let _runtime_guard = RuntimeGuard::new(current_runtime.clone()); let _runtime_guard = RuntimeGuard::new(current_runtime.clone());
f(input) f(input)
@@ -140,7 +181,7 @@ impl Runtime {
let borrow = self.scope_states.borrow(); let borrow = self.scope_states.borrow();
if let Some(scope) = &borrow[id.0] { if let Some(scope) = &borrow[id.0] {
// 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.in_scope(id, || {
// Drop all spawned tasks - order doesn't matter since tasks don't rely on eachother // Drop all spawned tasks - order doesn't matter since tasks don't rely on eachother
// In theory nested tasks might not like this // In theory nested tasks might not like this
for id in scope.spawned_tasks.take() { for id in scope.spawned_tasks.take() {
@@ -165,19 +206,31 @@ impl Runtime {
self.scope_states.borrow_mut()[id.0].take(); self.scope_states.borrow_mut()[id.0].take();
} }
/// Get the owner for the current scope.
#[track_caller]
pub fn current_owner<S: AnyStorage>(&self) -> Owner<S> {
self.get_state(self.current_scope_id()).owner()
}
/// Get the owner for the current scope.
#[track_caller]
pub fn scope_owner<S: AnyStorage>(&self, scope: ScopeId) -> Owner<S> {
self.get_state(scope).owner()
}
/// Get the current scope id /// Get the current scope id
pub(crate) fn current_scope_id(&self) -> Result<ScopeId, RuntimeError> { pub fn current_scope_id(&self) -> ScopeId {
self.scope_stack self.scope_stack.borrow().last().copied().unwrap()
.borrow() }
.last()
.copied() /// Try to get the current scope id, returning None if it we aren't actively inside a scope
.ok_or(RuntimeError { _priv: () }) pub fn try_current_scope_id(&self) -> Option<ScopeId> {
self.scope_stack.borrow().last().copied()
} }
/// Call this function with the current scope set to the given scope /// Call this function with the current scope set to the given scope
/// #[track_caller]
/// Useful in a limited number of scenarios pub fn in_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
pub fn on_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
let _runtime_guard = RuntimeGuard::new(self.clone()); let _runtime_guard = RuntimeGuard::new(self.clone());
{ {
self.push_scope(id); self.push_scope(id);
@@ -236,7 +289,18 @@ impl Runtime {
/// Get the state 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_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> { pub(crate) fn get_state(&self, id: ScopeId) -> Ref<'_, Scope> {
Ref::filter_map(self.scope_states.borrow(), |scopes| {
scopes.get(id.0).and_then(|f| f.as_ref())
})
.ok()
.unwrap()
}
/// 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
pub(crate) fn try_get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {
Ref::filter_map(self.scope_states.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())
}) })
@@ -254,30 +318,19 @@ impl Runtime {
} }
/// Runs a function with the current runtime /// Runs a function with the current runtime
pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> Result<R, RuntimeError> { pub(crate) fn with<R>(callback: impl FnOnce(&Runtime) -> R) -> R {
Self::current().map(|r| f(&r)) callback(&Self::current())
} }
/// Runs a function with the current scope /// Runs a function with the current scope
pub(crate) fn with_current_scope<R>(f: impl FnOnce(&Scope) -> R) -> Result<R, RuntimeError> { pub(crate) fn with_current_scope<R>(callback: impl FnOnce(&Scope) -> R) -> R {
Self::with(|rt| { Self::with(|rt| Self::with_scope(rt.current_scope_id(), callback))
rt.current_scope_id()
.ok()
.and_then(|scope| rt.get_state(scope).map(|sc| f(&sc)))
})
.ok()
.flatten()
.ok_or(RuntimeError::new())
} }
/// Runs a function with the current scope /// Runs a function with the current scope
pub(crate) fn with_scope<R>( pub(crate) fn with_scope<R>(scope: ScopeId, callback: impl FnOnce(&Scope) -> R) -> R {
scope: ScopeId, let rt = Runtime::current();
callback: impl FnOnce(&Scope) -> R, Self::in_scope(&rt, scope, || callback(&rt.get_state(scope)))
) -> Result<R, RuntimeError> {
Self::with(|rt| rt.get_state(scope).map(|scopestate| callback(&scopestate)))
.expect("Runtime should exist")
.ok_or(RuntimeError::new())
} }
/// Finish a render. This will mark all effects as ready to run and send the render signal. /// Finish a render. This will mark all effects as ready to run and send the render signal.
@@ -296,6 +349,7 @@ impl Runtime {
if self.suspended_tasks.get() == 0 { if self.suspended_tasks.get() == 0 {
return true; return true;
} }
// If this is not a suspended scope, and we are under a frozen context, then we should // If this is not a suspended scope, and we are under a frozen context, then we should
let scopes = self.scope_states.borrow(); let scopes = self.scope_states.borrow();
let scope = &scopes[scope_id.0].as_ref().unwrap(); let scope = &scopes[scope_id.0].as_ref().unwrap();
@@ -441,6 +495,107 @@ impl Runtime {
} }
} }
} }
/// Consume context from the current scope
pub fn consume_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {
self.get_state(id).consume_context::<T>()
}
/// Consume context from the current scope
pub fn consume_context_from_scope<T: 'static + Clone>(&self, scope_id: ScopeId) -> Option<T> {
self.get_state(scope_id).consume_context::<T>()
}
/// Check if the current scope has a context
pub fn has_context<T: 'static + Clone>(&self, id: ScopeId) -> Option<T> {
self.get_state(id).has_context::<T>()
}
/// Provide context to the current scope
pub fn provide_context<T: 'static + Clone>(&self, id: ScopeId, value: T) -> T {
self.get_state(id).provide_context(value)
}
/// Get the parent of the current scope if it exists
pub fn parent_scope(&self, scope: ScopeId) -> Option<ScopeId> {
self.get_state(scope).parent_id()
}
/// Check if the current scope is a descendant of the given scope
pub fn is_descendant_of(&self, us: ScopeId, other: ScopeId) -> bool {
let mut current = us;
while let Some(parent) = self.parent_scope(current) {
if parent == other {
return true;
}
current = parent;
}
false
}
/// Mark the current scope as dirty, causing it to re-render
pub fn needs_update(&self, scope: ScopeId) {
self.get_state(scope).needs_update();
}
/// Get the height of the current scope
pub fn height(&self, id: ScopeId) -> u32 {
self.get_state(id).height
}
/// Throw a [`CapturedError`] into a scope. The error will bubble up to the nearest [`ErrorBoundary`](crate::ErrorBoundary) or the root of the app.
///
/// # Examples
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// fn Component() -> Element {
/// let request = spawn(async move {
/// match reqwest::get("https://api.example.com").await {
/// Ok(_) => unimplemented!(),
/// // You can explicitly throw an error into a scope with throw_error
/// Err(err) => dioxus::core::Runtime::current().throw_error(ScopeId::APP, err),
/// }
/// });
///
/// unimplemented!()
/// }
/// ```
pub fn throw_error(&self, id: ScopeId, error: impl Into<CapturedError> + 'static) {
let error = error.into();
if let Some(cx) = self.consume_context::<crate::ErrorContext>(id) {
cx.insert_error(error)
} else {
tracing::error!(
"Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}",
error
)
}
}
/// Get the suspense context the current scope is in
pub fn suspense_context(&self) -> Option<SuspenseContext> {
self.get_state(self.current_scope_id())
.suspense_location()
.suspense_context()
.cloned()
}
/// Force every component to be dirty and require a re-render. Used by hot-reloading.
///
/// This might need to change to a different flag in the event hooks order changes within components.
/// What we really need is a way to mark components as needing a complete rebuild if they were hit by changes.
pub fn force_all_dirty(&self) {
self.scope_states.borrow_mut().iter().for_each(|state| {
if let Some(scope) = state.as_ref() {
scope.needs_update();
}
});
}
/// Check if the virtual dom is currently rendering
pub fn vdom_is_rendering(&self) -> bool {
self.rendering.get()
}
} }
/// A guard for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime. /// A guard for a new runtime. This must be used to override the current runtime when importing components from a dynamic library that has it's own runtime.
@@ -454,7 +609,7 @@ impl Runtime {
/// } /// }
/// ///
/// fn app() -> Element { /// fn app() -> Element {
/// rsx! { Component { runtime: Runtime::current().unwrap() } } /// rsx! { Component { runtime: Runtime::current() } }
/// } /// }
/// ///
/// // In a dynamic library /// // In a dynamic library
@@ -492,61 +647,3 @@ impl Drop for RuntimeGuard {
Runtime::pop(); Runtime::pop();
} }
} }
/// Missing Dioxus runtime error.
pub struct RuntimeError {
_priv: (),
}
impl RuntimeError {
#[inline(always)]
pub(crate) fn new() -> Self {
Self { _priv: () }
}
}
impl fmt::Debug for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RuntimeError").finish()
}
}
impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Must be called from inside a Dioxus runtime.
Help: Some APIs in dioxus require a global runtime to be present.
If you are calling one of these APIs from outside of a dioxus runtime
(typically in a web-sys closure or dynamic library), you will need to
grab the runtime from a scope that has it and then move it into your
new scope with a runtime guard.
For example, if you are trying to use dioxus apis from a web-sys
closure, you can grab the runtime from the scope it is created in:
```rust
use dioxus::prelude::*;
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
#[component]
fn MyComponent() -> Element {{
use_effect(|| {{
// Grab the runtime from the MyComponent scope
let runtime = Runtime::current().expect(\"Components run in the Dioxus runtime\");
// Move the runtime into the web-sys closure scope
let web_sys_closure = Closure::new(|| {{
// Then create a guard to provide the runtime to the closure
let _guard = RuntimeGuard::new(runtime);
// and run whatever code needs the runtime
tracing::info!(\"The count is: {{COUNT}}\");
}});
}})
}}
```"
)
}
}
impl std::error::Error for RuntimeError {}
+9 -18
View File
@@ -13,8 +13,8 @@ impl VirtualDom {
props: BoxedAnyProps, props: BoxedAnyProps,
name: &'static str, name: &'static str,
) -> &mut ScopeState { ) -> &mut ScopeState {
let parent_id = self.runtime.current_scope_id().ok(); let parent_id = self.runtime.try_current_scope_id();
let height = match parent_id.and_then(|id| self.runtime.get_state(id)) { let height = match parent_id.and_then(|id| self.runtime.try_get_state(id)) {
Some(parent) => parent.height() + 1, Some(parent) => parent.height() + 1,
None => 0, None => 0,
}; };
@@ -46,7 +46,7 @@ impl VirtualDom {
#[track_caller] #[track_caller]
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> Element { pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> Element {
// Ensure we are currently inside a `Runtime`. // Ensure we are currently inside a `Runtime`.
crate::Runtime::current().unwrap_or_else(|e| panic!("{}", e)); crate::Runtime::current();
self.runtime.clone().with_scope_on_stack(scope_id, || { self.runtime.clone().with_scope_on_stack(scope_id, || {
let scope = &self.scopes[scope_id.0]; let scope = &self.scopes[scope_id.0];
@@ -84,7 +84,7 @@ impl VirtualDom {
} }
}; };
self.handle_element_return(&mut render_return, scope_id, &scope.state()); self.handle_element_return(&mut render_return, &scope.state());
render_return render_return
}) })
}) })
@@ -105,23 +105,16 @@ impl VirtualDom {
} }
/// Insert any errors, or suspended tasks from an element return into the runtime /// Insert any errors, or suspended tasks from an element return into the runtime
fn handle_element_return(&self, node: &mut Element, scope_id: ScopeId, scope_state: &Scope) { fn handle_element_return(&self, node: &mut Element, scope: &Scope) {
match node { match node {
Err(RenderError::Error(e)) => { Err(RenderError::Error(e)) => {
tracing::error!( tracing::error!("Error while rendering component `{}`:\n{e}", scope.name);
"Error while rendering component `{}`:\n{e}",
scope_state.name
);
throw_error(e.clone()); throw_error(e.clone());
} }
Err(RenderError::Suspended(e)) => { Err(RenderError::Suspended(e)) => {
let task = e.task(); let task = e.task();
// Insert the task into the nearest suspense boundary if it exists // Insert the task into the nearest suspense boundary if it exists
let boundary = self let boundary = scope.suspense_location();
.runtime
.get_state(scope_id)
.unwrap()
.suspense_location();
let already_suspended = self let already_suspended = self
.runtime .runtime
.tasks .tasks
@@ -130,7 +123,7 @@ impl VirtualDom {
.expect("Suspended on a task that no longer exists") .expect("Suspended on a task that no longer exists")
.suspend(boundary.clone()); .suspend(boundary.clone());
if !already_suspended { if !already_suspended {
tracing::trace!("Suspending {:?} on {:?}", scope_id, task); tracing::trace!("Suspending {:?} on {:?}", scope.id, task);
// Add this task to the suspended tasks list of the boundary // Add this task to the suspended tasks list of the boundary
if let SuspenseLocation::UnderSuspense(boundary) = &boundary { if let SuspenseLocation::UnderSuspense(boundary) = &boundary {
boundary.add_suspended_task(e.clone()); boundary.add_suspended_task(e.clone());
@@ -142,9 +135,7 @@ impl VirtualDom {
} }
Ok(_) => { Ok(_) => {
// If the render was successful, we can move the render generation forward by one // If the render was successful, we can move the render generation forward by one
scope_state scope.render_count.set(scope.render_count.get() + 1);
.render_count
.set(scope_state.render_count.get() + 1);
} }
} }
} }
+33 -283
View File
@@ -1,6 +1,5 @@
use crate::{ use crate::{
innerlude::{CapturedError, SchedulerMsg, SuspenseContext}, innerlude::{SchedulerMsg, SuspenseContext},
runtime::RuntimeError,
Runtime, ScopeId, Task, Runtime, ScopeId, Task,
}; };
use generational_box::{AnyStorage, Owner}; use generational_box::{AnyStorage, Owner};
@@ -96,7 +95,7 @@ impl Scope {
} }
fn sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> { fn sender(&self) -> futures_channel::mpsc::UnboundedSender<SchedulerMsg> {
Runtime::with(|rt| rt.sender.clone()).unwrap_or_else(|e| panic!("{}", e)) Runtime::current().sender.clone()
} }
/// Mount the scope and queue any pending effects if it is not already mounted /// Mount the scope and queue any pending effects if it is not already mounted
@@ -133,12 +132,12 @@ impl Scope {
} }
/// Mark this scope as dirty, and schedule a render for it. /// Mark this scope as dirty, and schedule a render for it.
pub fn needs_update(&self) { pub(crate) fn needs_update(&self) {
self.needs_update_any(self.id) self.needs_update_any(self.id)
} }
/// Mark this scope as dirty, and schedule a render for it. /// Mark this scope as dirty, and schedule a render for it.
pub fn needs_update_any(&self, id: ScopeId) { pub(crate) fn needs_update_any(&self, id: ScopeId) {
self.sender() self.sender()
.unbounded_send(SchedulerMsg::Immediate(id)) .unbounded_send(SchedulerMsg::Immediate(id))
.expect("Scheduler to exist if scope exists"); .expect("Scheduler to exist if scope exists");
@@ -151,7 +150,7 @@ impl Scope {
/// Note: The function returned by this method will schedule an update for the current component even if it has already updated between when `schedule_update` was called and when the returned function is called. /// Note: The function returned by this method will schedule an update for the current component even if it has already updated between when `schedule_update` was called and when the returned function is called.
/// If the desired behavior is to invalidate the current rendering of the current component (and no-op if already invalidated) /// If the desired behavior is to invalidate the current rendering of the current component (and no-op if already invalidated)
/// [`subscribe`](crate::reactive_context::ReactiveContext::subscribe) to the [`current`](crate::reactive_context::ReactiveContext::current) [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead. /// [`subscribe`](crate::reactive_context::ReactiveContext::subscribe) to the [`current`](crate::reactive_context::ReactiveContext::current) [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> { pub(crate) fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
let (chan, id) = (self.sender(), self.id); let (chan, id) = (self.sender(), self.id);
Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id)))) Arc::new(move || drop(chan.unbounded_send(SchedulerMsg::Immediate(id))))
} }
@@ -164,7 +163,7 @@ impl Scope {
/// ///
/// Note: It does not matter when `schedule_update_any` is called: the returned function will invalidate what ever generation of the specified component is current when returned function is called. /// Note: It does not matter when `schedule_update_any` is called: the returned function will invalidate what ever generation of the specified component is current when returned function is called.
/// If the desired behavior is to schedule invalidation of the current rendering of a component, use [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead. /// If the desired behavior is to schedule invalidation of the current rendering of a component, use [`ReactiveContext`](crate::reactive_context::ReactiveContext) instead.
pub fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> { pub(crate) fn schedule_update_any(&self) -> Arc<dyn Fn(ScopeId) + Send + Sync> {
let chan = self.sender(); let chan = self.sender();
Arc::new(move |id| { Arc::new(move |id| {
_ = chan.unbounded_send(SchedulerMsg::Immediate(id)); _ = chan.unbounded_send(SchedulerMsg::Immediate(id));
@@ -172,7 +171,7 @@ impl Scope {
} }
/// Get the owner for the current scope. /// Get the owner for the current scope.
pub fn owner<S: AnyStorage>(&self) -> Owner<S> { pub(crate) fn owner<S: AnyStorage>(&self) -> Owner<S> {
match self.has_context() { match self.has_context() {
Some(rt) => rt, Some(rt) => rt,
None => { None => {
@@ -183,7 +182,7 @@ impl Scope {
} }
/// Return any context of type T if it exists on this scope /// Return any context of type T if it exists on this scope
pub fn has_context<T: 'static + Clone>(&self) -> Option<T> { pub(crate) fn has_context<T: 'static + Clone>(&self) -> Option<T> {
self.shared_contexts self.shared_contexts
.borrow() .borrow()
.iter() .iter()
@@ -194,49 +193,23 @@ impl Scope {
/// Try to retrieve a shared state with type `T` from any parent scope. /// Try to retrieve a shared state with type `T` from any parent scope.
/// ///
/// Clones the state if it exists. /// Clones the state if it exists.
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> { pub(crate) fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
tracing::trace!( if let Some(this_ctx) = self.has_context::<T>() {
"looking for context {} ({:?}) in {:?}",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>(),
self.id
);
if let Some(this_ctx) = self.has_context() {
return Some(this_ctx); return Some(this_ctx);
} }
let mut search_parent = self.parent_id; let mut search_parent = self.parent_id;
let cur_runtime = Runtime::with(|runtime| {
Runtime::with(|runtime| {
while let Some(parent_id) = search_parent { while let Some(parent_id) = search_parent {
let Some(parent) = runtime.get_state(parent_id) else { let parent = runtime.try_get_state(parent_id)?;
tracing::error!("Parent scope {:?} not found", parent_id); if let Some(shared) = parent.has_context::<T>() {
return None;
};
tracing::trace!(
"looking for context {} ({:?}) in {:?}",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>(),
parent.id
);
if let Some(shared) = parent.has_context() {
return Some(shared); return Some(shared);
} }
search_parent = parent.parent_id; search_parent = parent.parent_id;
} }
None None
}); })
match cur_runtime.ok().flatten() {
Some(ctx) => Some(ctx),
None => {
tracing::trace!(
"context {} ({:?}) not found",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>()
);
None
}
}
} }
/// Inject a `Box<dyn Any>` into the context of this scope /// Inject a `Box<dyn Any>` into the context of this scope
@@ -281,13 +254,7 @@ impl Scope {
/// rsx!(div { "hello {state.0}" }) /// rsx!(div { "hello {state.0}" })
/// } /// }
/// ``` /// ```
pub fn provide_context<T: 'static + Clone>(&self, value: T) -> T { pub(crate) fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
tracing::trace!(
"providing context {} ({:?}) in {:?}",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>(),
self.id
);
let mut contexts = self.shared_contexts.borrow_mut(); let mut contexts = self.shared_contexts.borrow_mut();
// If the context exists, swap it out for the new value // If the context exists, swap it out for the new value
@@ -312,14 +279,8 @@ impl Scope {
/// ///
/// Note that you should be checking if the context existed before trying to provide a new one. Providing a context /// Note that you should be checking if the context existed before trying to provide a new one. Providing a context
/// when a context already exists will swap the context out for the new one, which may not be what you want. /// when a context already exists will swap the context out for the new one, which may not be what you want.
pub fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T { pub(crate) fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
Runtime::with(|runtime| { Runtime::with(|runtime| runtime.get_state(ScopeId::ROOT).provide_context(context))
runtime
.get_state(ScopeId::ROOT)
.unwrap()
.provide_context(context)
})
.expect("Runtime to exist")
} }
/// Start a new future on the same thread as the rest of the VirtualDom. /// Start a new future on the same thread as the rest of the VirtualDom.
@@ -348,108 +309,29 @@ impl Scope {
/// } /// }
/// }); /// });
/// ``` /// ```
pub fn spawn_isomorphic(&self, fut: impl Future<Output = ()> + 'static) -> Task { pub(crate) fn spawn_isomorphic(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut)).expect("Runtime to exist"); let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut));
self.spawned_tasks.borrow_mut().insert(id); self.spawned_tasks.borrow_mut().insert(id);
id id
} }
/// Spawns the future and returns the [`Task`] /// Spawns the future and returns the [`Task`]
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task { pub(crate) fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist"); let id = Runtime::with(|rt| rt.spawn(self.id, fut));
self.spawned_tasks.borrow_mut().insert(id); self.spawned_tasks.borrow_mut().insert(id);
id id
} }
/// Queue an effect to run after the next render /// Queue an effect to run after the next render
pub fn queue_effect(&self, f: impl FnOnce() + 'static) { pub(crate) fn queue_effect(&self, f: impl FnOnce() + 'static) {
Runtime::with(|rt| rt.queue_effect(self.id, f)).expect("Runtime to exist"); Runtime::with(|rt| rt.queue_effect(self.id, f));
} }
/// Store a value between renders. The foundational hook for all other hooks. /// Store a value in the hook list, returning the value.
/// pub(crate) fn use_hook<State: Clone + 'static>(
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render). &self,
/// `use_hook` will return a clone of the value on every render. initializer: impl FnOnce() -> State,
/// ) -> State {
/// In order to clean up resources you would need to implement the [`Drop`] trait for an inner value stored in a RC or similar (Signals for instance),
/// as these only drop their inner value once all references have been dropped, which only happens when the component is dropped.
///
/// <div class="warning">
///
/// `use_hook` is not reactive. It just returns the value on every render. If you need state that will track changes, use [`use_signal`](https://docs.rs/dioxus-hooks/latest/dioxus_hooks/fn.use_signal.html) instead.
///
/// ❌ Don't use `use_hook` with `Rc<RefCell<T>>` for state. It will not update the UI and other hooks when the state changes.
/// ```rust
/// use dioxus::prelude::*;
/// use std::rc::Rc;
/// use std::cell::RefCell;
///
/// pub fn Comp() -> Element {
/// let count = use_hook(|| Rc::new(RefCell::new(0)));
///
/// rsx! {
/// button {
/// onclick: move |_| *count.borrow_mut() += 1,
/// "{count.borrow()}"
/// }
/// }
/// }
/// ```
///
/// ✅ Use `use_signal` instead.
/// ```rust
/// use dioxus::prelude::*;
///
/// pub fn Comp() -> Element {
/// let mut count = use_signal(|| 0);
///
/// rsx! {
/// button {
/// onclick: move |_| count += 1,
/// "{count}"
/// }
/// }
/// }
/// ```
///
/// </div>
///
/// # Example
///
/// ```rust
/// use dioxus::prelude::*;
///
/// // prints a greeting on the initial render
/// pub fn use_hello_world() {
/// use_hook(|| println!("Hello, world!"));
/// }
/// ```
///
/// # Custom Hook Example
///
/// ```rust
/// use dioxus::prelude::*;
///
/// pub struct InnerCustomState(usize);
///
/// impl Drop for InnerCustomState {
/// fn drop(&mut self){
/// println!("Component has been dropped.");
/// }
/// }
///
/// #[derive(Clone, Copy)]
/// pub struct CustomState {
/// inner: Signal<InnerCustomState>
/// }
///
/// pub fn use_custom_state() -> CustomState {
/// use_hook(|| CustomState {
/// inner: Signal::new(InnerCustomState(0))
/// })
/// }
/// ```
pub fn use_hook<State: Clone + 'static>(&self, initializer: impl FnOnce() -> State) -> State {
let cur_hook = self.hook_index.get(); let cur_hook = self.hook_index.get();
// The hook list works by keeping track of the current hook index and pushing the index forward // The hook list works by keeping track of the current hook index and pushing the index forward
@@ -513,155 +395,23 @@ impl Scope {
); );
} }
pub fn push_before_render(&self, f: impl FnMut() + 'static) { pub(crate) fn push_before_render(&self, f: impl FnMut() + 'static) {
self.before_render.borrow_mut().push(Box::new(f)); self.before_render.borrow_mut().push(Box::new(f));
} }
pub fn push_after_render(&self, f: impl FnMut() + 'static) { pub(crate) fn push_after_render(&self, f: impl FnMut() + 'static) {
self.after_render.borrow_mut().push(Box::new(f)); self.after_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
pub fn generation(&self) -> usize { pub(crate) fn generation(&self) -> usize {
self.render_count.get() self.render_count.get()
} }
/// Get the height of this scope /// Get the height of this scope
pub fn height(&self) -> u32 { pub(crate) fn height(&self) -> u32 {
self.height self.height
} }
} }
impl ScopeId {
/// Get the current scope id
pub fn current_scope_id(self) -> Result<ScopeId, RuntimeError> {
Runtime::with(|rt| rt.current_scope_id().ok())
.ok()
.flatten()
.ok_or(RuntimeError::new())
}
/// Consume context from the current scope
pub fn consume_context<T: 'static + Clone>(self) -> Option<T> {
Runtime::with_scope(self, |cx| cx.consume_context::<T>()).expect("Runtime to exist")
}
/// Consume context from the current scope
pub fn consume_context_from_scope<T: 'static + Clone>(self, scope_id: ScopeId) -> Option<T> {
Runtime::with(|rt| {
rt.get_state(scope_id)
.and_then(|cx| cx.consume_context::<T>())
})
.ok()
.flatten()
}
/// Check if the current scope has a context
pub fn has_context<T: 'static + Clone>(self) -> Option<T> {
Runtime::with_scope(self, |cx| cx.has_context::<T>())
.ok()
.flatten()
}
/// Provide context to the current scope
pub fn provide_context<T: 'static + Clone>(self, value: T) -> T {
Runtime::with_scope(self, |cx| cx.provide_context(value)).unwrap()
}
/// Pushes the future onto the poll queue to be polled after the component renders.
pub fn push_future(self, fut: impl Future<Output = ()> + 'static) -> Option<Task> {
Runtime::with_scope(self, |cx| cx.spawn(fut)).ok()
}
/// Spawns the future but does not return the [`Task`]
pub fn spawn(self, fut: impl Future<Output = ()> + 'static) {
Runtime::with_scope(self, |cx| cx.spawn(fut)).unwrap();
}
/// Get the current render since the inception of this component
///
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc
pub fn generation(self) -> Option<usize> {
Runtime::with_scope(self, |cx| Some(cx.generation())).unwrap()
}
/// Get the parent of the current scope if it exists
pub fn parent_scope(self) -> Option<ScopeId> {
Runtime::with_scope(self, |cx| cx.parent_id())
.ok()
.flatten()
}
/// Check if the current scope is a descendant of the given scope
pub fn is_descendant_of(self, other: ScopeId) -> bool {
let mut current = self;
while let Some(parent) = current.parent_scope() {
if parent == other {
return true;
}
current = parent;
}
false
}
/// Mark the current scope as dirty, causing it to re-render
pub fn needs_update(self) {
Runtime::with_scope(self, |cx| cx.needs_update()).unwrap();
}
/// Create a subscription that schedules a future render for the reference component. Unlike [`Self::needs_update`], this function will work outside of the dioxus runtime.
///
/// ## Notice: you should prefer using [`crate::schedule_update_any`]
pub fn schedule_update(&self) -> Arc<dyn Fn() + Send + Sync + 'static> {
Runtime::with_scope(*self, |cx| cx.schedule_update()).unwrap()
}
/// Get the height of the current scope
pub fn height(self) -> u32 {
Runtime::with_scope(self, |cx| cx.height()).unwrap()
}
/// Run a closure inside of scope's runtime
#[track_caller]
pub fn in_runtime<T>(self, f: impl FnOnce() -> T) -> T {
Runtime::current()
.unwrap_or_else(|e| panic!("{}", e))
.on_scope(self, f)
}
/// Throw a [`CapturedError`] into a scope. The error will bubble up to the nearest [`ErrorBoundary`](crate::ErrorBoundary) or the root of the app.
///
/// # Examples
/// ```rust, no_run
/// # use dioxus::prelude::*;
/// fn Component() -> Element {
/// let request = spawn(async move {
/// match reqwest::get("https://api.example.com").await {
/// Ok(_) => unimplemented!(),
/// // You can explicitly throw an error into a scope with throw_error
/// Err(err) => ScopeId::APP.throw_error(err)
/// }
/// });
///
/// unimplemented!()
/// }
/// ```
pub fn throw_error(self, error: impl Into<CapturedError> + 'static) {
let error = error.into();
if let Some(cx) = self.consume_context::<crate::ErrorContext>() {
cx.insert_error(error)
} else {
tracing::error!(
"Tried to throw an error into an error boundary, but failed to locate a boundary: {:?}",
error
)
}
}
/// Get the suspense context the current scope is in
pub fn suspense_context(&self) -> Option<SuspenseContext> {
Runtime::with_scope(*self, |cx| cx.suspense_boundary.suspense_context().cloned()).unwrap()
}
}
+42 -38
View File
@@ -20,12 +20,11 @@ impl std::fmt::Debug for ScopeId {
let mut builder = builder.field(&self.0); let mut builder = builder.field(&self.0);
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
if let Some(name) = Runtime::current() if let Some(scope) = Runtime::try_current()
.ok()
.as_ref() .as_ref()
.and_then(|rt| rt.get_state(*self)) .and_then(|r| r.try_get_state(*self))
{ {
builder = builder.field(&name.name); builder = builder.field(&scope.name);
} }
} }
builder.finish() builder.finish()
@@ -81,6 +80,45 @@ pub struct ScopeState {
pub(crate) reactive_context: ReactiveContext, pub(crate) reactive_context: ReactiveContext,
} }
impl ScopeState {
/// 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.
///
/// Panics if the tree has not been built yet.
pub fn root_node(&self) -> &VNode {
self.try_root_node()
.expect("The tree has not been built yet. Make sure to call rebuild on the tree before accessing its nodes.")
}
/// Try to 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.
///
/// Returns [`None`] if the tree has not been built yet.
pub fn try_root_node(&self) -> Option<&VNode> {
match &self.last_rendered_node {
Some(LastRenderedNode::Real(vnode)) => Some(vnode),
Some(LastRenderedNode::Placeholder(vnode, _)) => Some(vnode),
None => None,
}
}
/// Returns the scope id of this [`ScopeState`].
pub fn id(&self) -> ScopeId {
self.context_id
}
pub(crate) fn state(&self) -> Ref<'_, Scope> {
self.runtime.get_state(self.context_id)
}
/// Returns the height of this scope in the tree.
pub fn height(&self) -> u32 {
self.state().height()
}
}
#[derive(Clone, PartialEq, Debug)] #[derive(Clone, PartialEq, Debug)]
pub enum LastRenderedNode { pub enum LastRenderedNode {
Real(VNode), Real(VNode),
@@ -119,37 +157,3 @@ impl Drop for ScopeState {
self.runtime.remove_scope(self.context_id); self.runtime.remove_scope(self.context_id);
} }
} }
impl ScopeState {
/// 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.
///
/// Panics if the tree has not been built yet.
pub fn root_node(&self) -> &VNode {
self.try_root_node()
.expect("The tree has not been built yet. Make sure to call rebuild on the tree before accessing its nodes.")
}
/// Try to 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.
///
/// Returns [`None`] if the tree has not been built yet.
pub fn try_root_node(&self) -> Option<&VNode> {
match &self.last_rendered_node {
Some(LastRenderedNode::Real(vnode)) => Some(vnode),
Some(LastRenderedNode::Placeholder(vnode, _)) => Some(vnode),
None => None,
}
}
/// Returns the scope id of this [`ScopeState`].
pub fn id(&self) -> ScopeId {
self.context_id
}
pub(crate) fn state(&self) -> Ref<'_, Scope> {
self.runtime.get_state(self.context_id).unwrap()
}
}
+1 -1
View File
@@ -607,7 +607,7 @@ impl SuspenseContext {
scope_id: ScopeId, scope_id: ScopeId,
) -> Option<Self> { ) -> Option<Self> {
runtime runtime
.get_state(scope_id) .try_get_state(scope_id)
.and_then(|scope| scope.suspense_boundary()) .and_then(|scope| scope.suspense_boundary())
} }
+10 -3
View File
@@ -45,7 +45,7 @@ impl SuspendedFuture {
pub fn new(task: Task) -> Self { pub fn new(task: Task) -> Self {
Self { Self {
task: task.id, task: task.id,
origin: current_scope_id().unwrap_or_else(|e| panic!("{}", e)), origin: current_scope_id(),
} }
} }
@@ -86,6 +86,7 @@ impl SuspenseContext {
pub(crate) fn new() -> Self { pub(crate) fn new() -> Self {
Self { Self {
inner: Rc::new(SuspenseBoundaryInner { inner: Rc::new(SuspenseBoundaryInner {
rt: Runtime::current(),
suspended_tasks: RefCell::new(vec![]), suspended_tasks: RefCell::new(vec![]),
id: Cell::new(ScopeId::ROOT), id: Cell::new(ScopeId::ROOT),
suspended_nodes: Default::default(), suspended_nodes: Default::default(),
@@ -145,7 +146,7 @@ impl SuspenseContext {
/// Add a suspended task /// Add a suspended task
pub(crate) fn add_suspended_task(&self, task: SuspendedFuture) { pub(crate) fn add_suspended_task(&self, task: SuspendedFuture) {
self.inner.suspended_tasks.borrow_mut().push(task); self.inner.suspended_tasks.borrow_mut().push(task);
self.inner.id.get().needs_update(); self.inner.rt.needs_update(self.inner.id.get());
} }
/// Remove a suspended task /// Remove a suspended task
@@ -154,7 +155,7 @@ impl SuspenseContext {
.suspended_tasks .suspended_tasks
.borrow_mut() .borrow_mut()
.retain(|t| t.task != task.id); .retain(|t| t.task != task.id);
self.inner.id.get().needs_update(); self.inner.rt.needs_update(self.inner.id.get());
} }
/// Get all suspended tasks /// Get all suspended tasks
@@ -184,12 +185,18 @@ impl SuspenseContext {
/// A boundary that will capture any errors from child components /// A boundary that will capture any errors from child components
pub struct SuspenseBoundaryInner { pub struct SuspenseBoundaryInner {
rt: Rc<Runtime>,
suspended_tasks: RefCell<Vec<SuspendedFuture>>, suspended_tasks: RefCell<Vec<SuspendedFuture>>,
id: Cell<ScopeId>, id: Cell<ScopeId>,
/// The nodes that are suspended under this boundary /// The nodes that are suspended under this boundary
suspended_nodes: RefCell<Option<VNode>>, suspended_nodes: RefCell<Option<VNode>>,
/// On the server, you can only resolve a suspense boundary once. This is used to track if the suspense boundary has been resolved and if it should be frozen /// On the server, you can only resolve a suspense boundary once. This is used to track if the suspense boundary has been resolved and if it should be frozen
frozen: Cell<bool>, frozen: Cell<bool>,
/// Closures queued to run after the suspense boundary is resolved /// Closures queued to run after the suspense boundary is resolved
after_suspense_resolved: RefCell<Vec<Box<dyn FnOnce()>>>, after_suspense_resolved: RefCell<Vec<Box<dyn FnOnce()>>>,
} }
+6 -10
View File
@@ -7,7 +7,6 @@ use crate::ScopeId;
use futures_util::task::ArcWake; use futures_util::task::ArcWake;
use slotmap::DefaultKey; use slotmap::DefaultKey;
use std::marker::PhantomData; use std::marker::PhantomData;
use std::panic;
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::Cell, future::Future};
@@ -73,7 +72,6 @@ impl Task {
false false
} }
}) })
.unwrap_or_default()
} }
/// Wake the task. /// Wake the task.
@@ -84,13 +82,12 @@ impl Task {
.sender .sender
.unbounded_send(SchedulerMsg::TaskNotified(self.id)) .unbounded_send(SchedulerMsg::TaskNotified(self.id))
}) })
.unwrap_or_else(|e| panic!("{}", e))
} }
/// Poll the task immediately. /// Poll the task immediately.
#[track_caller] #[track_caller]
pub fn poll_now(&self) -> Poll<()> { pub fn poll_now(&self) -> Poll<()> {
Runtime::with(|rt| rt.handle_task_wakeup(*self)).unwrap_or_else(|e| panic!("{}", e)) Runtime::with(|rt| rt.handle_task_wakeup(*self))
} }
/// Set the task as active or paused. /// Set the task as active or paused.
@@ -106,7 +103,6 @@ impl Task {
} }
} }
}) })
.unwrap_or_else(|e| panic!("{}", e))
} }
} }
@@ -217,7 +213,7 @@ impl Runtime {
/// Queue an effect to run after the next render /// Queue an effect to run after the next render
pub(crate) fn queue_effect(&self, id: ScopeId, f: impl FnOnce() + 'static) { pub(crate) fn queue_effect(&self, id: ScopeId, f: impl FnOnce() + 'static) {
let effect = Box::new(f) as Box<dyn FnOnce() + 'static>; let effect = Box::new(f) as Box<dyn FnOnce() + 'static>;
let Some(scope) = self.get_state(id) else { let Some(scope) = self.try_get_state(id) else {
return; return;
}; };
let mut status = scope.status.borrow_mut(); let mut status = scope.status.borrow_mut();
@@ -239,7 +235,8 @@ impl Runtime {
) { ) {
// Add the effect to the queue of effects to run after the next render for the given scope // Add the effect to the queue of effects to run after the next render for the given scope
let mut effects = self.pending_effects.borrow_mut(); let mut effects = self.pending_effects.borrow_mut();
let scope_order = ScopeOrder::new(id.height(), id); let height = self.get_state(id).height();
let scope_order = ScopeOrder::new(height, id);
match effects.get(&scope_order) { match effects.get(&scope_order) {
Some(effects) => effects.push_back(f), Some(effects) => effects.push_back(f),
None => { None => {
@@ -267,7 +264,7 @@ impl Runtime {
#[cfg(debug_assertions)] #[cfg(debug_assertions)]
{ {
// Ensure we are currently inside a `Runtime`. // Ensure we are currently inside a `Runtime`.
Runtime::current().unwrap_or_else(|e| panic!("{}", e)); Runtime::current();
} }
let task = self.tasks.borrow().get(id.id).cloned(); let task = self.tasks.borrow().get(id.id).cloned();
@@ -293,7 +290,6 @@ impl Runtime {
if poll_result.is_ready() { if poll_result.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_state(task.scope) self.get_state(task.scope)
.unwrap()
.spawned_tasks .spawned_tasks
.borrow_mut() .borrow_mut()
.remove(&id); .remove(&id);
@@ -325,7 +321,7 @@ impl Runtime {
} }
// Remove the task from pending work. We could reuse the slot before the task is polled and discarded so we need to remove it from pending work instead of filtering out dead tasks when we try to poll them // Remove the task from pending work. We could reuse the slot before the task is polled and discarded so we need to remove it from pending work instead of filtering out dead tasks when we try to poll them
if let Some(scope) = self.get_state(task.scope) { if let Some(scope) = self.try_get_state(task.scope) {
let order = ScopeOrder::new(scope.height(), scope.id); let order = ScopeOrder::new(scope.height(), scope.id);
if let Some(dirty_tasks) = self.dirty_tasks.borrow_mut().get(&order) { if let Some(dirty_tasks) = self.dirty_tasks.borrow_mut().get(&order) {
dirty_tasks.remove(id); dirty_tasks.remove(id);
+5 -5
View File
@@ -353,7 +353,7 @@ impl VirtualDom {
/// Run a closure inside a specific scope /// Run a closure inside a specific scope
pub fn in_scope<T>(&self, scope: ScopeId, f: impl FnOnce() -> T) -> T { pub fn in_scope<T>(&self, scope: ScopeId, f: impl FnOnce() -> T) -> T {
self.in_runtime(|| scope.in_runtime(f)) self.runtime.in_scope(scope, f)
} }
/// Build the virtualdom with a global context inserted into the base scope /// Build the virtualdom with a global context inserted into the base scope
@@ -393,7 +393,7 @@ impl VirtualDom {
/// ///
/// 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) {
let Some(scope) = self.runtime.get_state(id) else { let Some(scope) = self.runtime.try_get_state(id) else {
return; return;
}; };
@@ -408,7 +408,7 @@ impl VirtualDom {
let Some(scope) = self.runtime.task_scope(task) else { let Some(scope) = self.runtime.task_scope(task) else {
return; return;
}; };
let Some(scope) = self.runtime.get_state(scope) else { let Some(scope) = self.runtime.try_get_state(scope) else {
return; return;
}; };
@@ -718,7 +718,7 @@ impl VirtualDom {
let scope_id: ScopeId = scope.id; let scope_id: ScopeId = scope.id;
let run_scope = self let run_scope = self
.runtime .runtime
.get_state(scope.id) .try_get_state(scope.id)
.filter(|scope| scope.should_run_during_suspense()) .filter(|scope| scope.should_run_during_suspense())
.is_some(); .is_some();
if run_scope { if run_scope {
@@ -747,7 +747,7 @@ impl VirtualDom {
} }
self.resolved_scopes self.resolved_scopes
.sort_by_key(|&id| self.runtime.get_state(id).unwrap().height); .sort_by_key(|&id| self.runtime.get_state(id).height);
std::mem::take(&mut self.resolved_scopes) std::mem::take(&mut self.resolved_scopes)
} }
+3 -3
View File
@@ -1,6 +1,6 @@
use dioxus::dioxus_core::{ElementId, Mutation::*}; use dioxus::dioxus_core::{ElementId, Mutation::*};
use dioxus::prelude::*; use dioxus::prelude::*;
use dioxus_core::generation; use dioxus_core::{consume_context_from_scope, generation};
#[test] #[test]
fn state_shares() { fn state_shares() {
@@ -31,13 +31,13 @@ fn state_shares() {
dom.mark_dirty(ScopeId::APP); dom.mark_dirty(ScopeId::APP);
_ = dom.render_immediate_to_vec(); _ = dom.render_immediate_to_vec();
dom.in_runtime(|| { dom.in_runtime(|| {
assert_eq!(ScopeId::APP.consume_context::<i32>().unwrap(), 1); assert_eq!(consume_context_from_scope::<i32>(ScopeId::APP).unwrap(), 1);
}); });
dom.mark_dirty(ScopeId::APP); dom.mark_dirty(ScopeId::APP);
_ = dom.render_immediate_to_vec(); _ = dom.render_immediate_to_vec();
dom.in_runtime(|| { dom.in_runtime(|| {
assert_eq!(ScopeId::APP.consume_context::<i32>().unwrap(), 2); assert_eq!(consume_context_from_scope::<i32>(ScopeId::APP).unwrap(), 2);
}); });
dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2)); dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2));
+1 -1
View File
@@ -14,6 +14,6 @@ fn root_node_isnt_null() {
dom.in_runtime(|| { dom.in_runtime(|| {
// The height should be 0 // The height should be 0
assert_eq!(ScopeId::ROOT.height(), 0); assert_eq!(scope.height(), 0);
}); });
} }
+1 -1
View File
@@ -272,7 +272,7 @@ fn resolved_to_suspended() {
assert_eq!(out, "rendered 1 times"); assert_eq!(out, "rendered 1 times");
dom.in_runtime(|| ScopeId::APP.in_runtime(|| *SUSPENDED.write() = true)); dom.in_scope(ScopeId::APP, || *SUSPENDED.write() = true);
dom.render_suspense_immediate().await; dom.render_suspense_immediate().await;
let out = dioxus_ssr::render(&dom); let out = dioxus_ssr::render(&dom);
+5 -11
View File
@@ -8,7 +8,7 @@ use crate::{
shortcut::ShortcutRegistry, shortcut::ShortcutRegistry,
webview::{PendingWebview, WebviewInstance}, webview::{PendingWebview, WebviewInstance},
}; };
use dioxus_core::{ElementId, ScopeId, VirtualDom}; use dioxus_core::{consume_context, ElementId, ScopeId, VirtualDom};
use dioxus_history::History; use dioxus_history::History;
use dioxus_html::PlatformEventData; use dioxus_html::PlatformEventData;
use std::{ use std::{
@@ -550,11 +550,8 @@ impl App {
return; return;
}; };
let url = webview.dom.in_runtime(|| { let url = webview.dom.in_scope(ScopeId::ROOT, || {
ScopeId::ROOT consume_context::<Rc<dyn History>>().current_route()
.consume_context::<Rc<dyn History>>()
.unwrap()
.current_route()
}); });
let state = PreservedWindowState { let state = PreservedWindowState {
@@ -619,12 +616,9 @@ impl App {
} }
// Set the url if it exists // Set the url if it exists
webview.dom.in_runtime(|| { webview.dom.in_scope(ScopeId::ROOT, || {
if let Some(url) = state.url { if let Some(url) = state.url {
ScopeId::ROOT consume_context::<Rc<dyn History>>().replace(url);
.consume_context::<Rc<dyn History>>()
.unwrap()
.replace(url);
} }
}) })
} }
+3 -3
View File
@@ -22,13 +22,13 @@ pub fn use_wry_event_handler(
use dioxus_core::current_scope_id; use dioxus_core::current_scope_id;
// Capture the current runtime and scope ID. // Capture the current runtime and scope ID.
let runtime = Runtime::current().unwrap(); let runtime = Runtime::current();
let scope_id = current_scope_id().unwrap(); let scope_id = current_scope_id();
use_hook_with_cleanup( use_hook_with_cleanup(
move || { move || {
window().create_wry_event_handler(move |event, target| { window().create_wry_event_handler(move |event, target| {
runtime.on_scope(scope_id, || handler(event, target)) runtime.in_scope(scope_id, || handler(event, target))
}) })
}, },
move |handler| handler.remove(), move |handler| handler.remove(),
+10 -16
View File
@@ -60,37 +60,31 @@ pub fn launch_virtual_dom_blocking(virtual_dom: VirtualDom, mut desktop_config:
// Windows-only drag-n-drop fix events. We need to call the interpreter drag-n-drop code. // Windows-only drag-n-drop fix events. We need to call the interpreter drag-n-drop code.
UserWindowEvent::WindowsDragDrop(id) => { UserWindowEvent::WindowsDragDrop(id) => {
if let Some(webview) = app.webviews.get(&id) { if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_runtime(|| { webview.dom.in_scope(ScopeId::ROOT, || {
ScopeId::ROOT.in_runtime(|| { eval("window.interpreter.handleWindowsDragDrop();");
eval("window.interpreter.handleWindowsDragDrop();");
});
}); });
} }
} }
UserWindowEvent::WindowsDragLeave(id) => { UserWindowEvent::WindowsDragLeave(id) => {
if let Some(webview) = app.webviews.get(&id) { if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_runtime(|| { webview.dom.in_scope(ScopeId::ROOT, || {
ScopeId::ROOT.in_runtime(|| { eval("window.interpreter.handleWindowsDragLeave();");
eval("window.interpreter.handleWindowsDragLeave();");
});
}); });
} }
} }
UserWindowEvent::WindowsDragOver(id, x_pos, y_pos) => { UserWindowEvent::WindowsDragOver(id, x_pos, y_pos) => {
if let Some(webview) = app.webviews.get(&id) { if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_runtime(|| { webview.dom.in_scope(ScopeId::ROOT, || {
ScopeId::ROOT.in_runtime(|| { let e = eval(
let e = eval( r#"
r#"
const xPos = await dioxus.recv(); const xPos = await dioxus.recv();
const yPos = await dioxus.recv(); const yPos = await dioxus.recv();
window.interpreter.handleWindowsDragOver(xPos, yPos) window.interpreter.handleWindowsDragOver(xPos, yPos)
"#, "#,
); );
_ = e.send(x_pos); _ = e.send(x_pos);
_ = e.send(y_pos); _ = e.send(y_pos);
});
}); });
} }
} }
+8 -10
View File
@@ -10,7 +10,7 @@ use crate::{
}; };
use crate::{document::DesktopDocument, WeakDesktopContext}; use crate::{document::DesktopDocument, WeakDesktopContext};
use base64::prelude::BASE64_STANDARD; use base64::prelude::BASE64_STANDARD;
use dioxus_core::{Runtime, ScopeId, VirtualDom}; use dioxus_core::{consume_context, provide_context, Runtime, ScopeId, VirtualDom};
use dioxus_document::Document; use dioxus_document::Document;
use dioxus_history::{History, MemoryHistory}; use dioxus_history::{History, MemoryHistory};
use dioxus_hooks::to_owned; use dioxus_hooks::to_owned;
@@ -427,10 +427,10 @@ impl WebviewInstance {
edits.set_desktop_context(Rc::downgrade(&desktop_context)); edits.set_desktop_context(Rc::downgrade(&desktop_context));
let provider: Rc<dyn Document> = Rc::new(DesktopDocument::new(desktop_context.clone())); let provider: Rc<dyn Document> = Rc::new(DesktopDocument::new(desktop_context.clone()));
let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default()); let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
dom.in_runtime(|| { dom.in_scope(ScopeId::ROOT, || {
ScopeId::ROOT.provide_context(desktop_context.clone()); provide_context(desktop_context.clone());
ScopeId::ROOT.provide_context(provider); provide_context(provider);
ScopeId::ROOT.provide_context(history_provider); provide_context(history_provider);
}); });
WebviewInstance { WebviewInstance {
@@ -566,11 +566,9 @@ impl PendingWebview {
pub(crate) fn create_window(self, shared: &Rc<SharedContext>) -> WebviewInstance { pub(crate) fn create_window(self, shared: &Rc<SharedContext>) -> WebviewInstance {
let window = WebviewInstance::new(self.cfg, self.dom, shared.clone()); let window = WebviewInstance::new(self.cfg, self.dom, shared.clone());
let cx = window.dom.in_runtime(|| { let cx = window
ScopeId::ROOT .dom
.consume_context::<Rc<DesktopService>>() .in_scope(ScopeId::ROOT, consume_context::<Rc<DesktopService>>);
.unwrap()
});
_ = self.sender.send(cx); _ = self.sender.send(cx);
window window
+2 -2
View File
@@ -17,7 +17,7 @@ pub fn apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) {
/// ///
/// Assets need to be handled by the renderer. /// Assets need to be handled by the renderer.
pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), PatchError> { pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), PatchError> {
dom.runtime().on_scope(ScopeId::ROOT, || { dom.runtime().in_scope(ScopeId::ROOT, || {
// 1. Update signals... // 1. Update signals...
let ctx = dioxus_signals::get_global_context(); let ctx = dioxus_signals::get_global_context();
for template in &msg.templates { for template in &msg.templates {
@@ -44,7 +44,7 @@ pub fn try_apply_changes(dom: &VirtualDom, msg: &HotReloadMsg) -> Result<(), Pat
if msg.for_pid == our_pid { if msg.for_pid == our_pid {
unsafe { subsecond::apply_patch(jump_table) }?; unsafe { subsecond::apply_patch(jump_table) }?;
dioxus_core::force_all_dirty(); dom.runtime().force_all_dirty();
ctx.clear::<Signal<Option<HotReloadedTemplate>>>(); ctx.clear::<Signal<Option<HotReloadedTemplate>>>();
} }
} }
+6 -3
View File
@@ -2,7 +2,9 @@
use std::{cell::RefCell, collections::HashSet, rc::Rc}; use std::{cell::RefCell, collections::HashSet, rc::Rc};
use dioxus_core::{Attribute, DynamicNode, Element, RenderError, ScopeId, Template, TemplateNode}; use dioxus_core::{
Attribute, DynamicNode, Element, RenderError, Runtime, ScopeId, Template, TemplateNode,
};
use dioxus_core_macro::*; use dioxus_core_macro::*;
mod link; mod link;
@@ -101,11 +103,12 @@ fn extract_single_text_node(children: &Element) -> Result<String, ExtractSingleT
} }
fn get_or_insert_root_context<T: Default + Clone + 'static>() -> T { fn get_or_insert_root_context<T: Default + Clone + 'static>() -> T {
match ScopeId::ROOT.has_context::<T>() { let rt = Runtime::current();
match rt.has_context::<T>(ScopeId::ROOT) {
Some(context) => context, Some(context) => context,
None => { None => {
let context = T::default(); let context = T::default();
ScopeId::ROOT.provide_context(context.clone()); rt.provide_context(ScopeId::ROOT, context.clone());
context context
} }
} }
+18 -23
View File
@@ -7,8 +7,8 @@ use crate::streaming::{Mount, StreamingRenderer};
use crate::{document::ServerDocument, ServeConfig}; use crate::{document::ServerDocument, ServeConfig};
use dioxus_cli_config::base_path; use dioxus_cli_config::base_path;
use dioxus_core::{ use dioxus_core::{
has_context, DynamicNode, ErrorContext, ScopeId, SuspenseContext, TemplateNode, VNode, consume_context, has_context, try_consume_context, DynamicNode, ErrorContext, Runtime, ScopeId,
VirtualDom, SuspenseContext, TemplateNode, VNode, VirtualDom,
}; };
use dioxus_fullstack_core::{history::provide_fullstack_history_context, HttpError, ServerFnError}; use dioxus_fullstack_core::{history::provide_fullstack_history_context, HttpError, ServerFnError};
use dioxus_fullstack_core::{HydrationContext, SerializedHydrationData}; use dioxus_fullstack_core::{HydrationContext, SerializedHydrationData};
@@ -219,11 +219,8 @@ impl SsrRendererPool {
} }
// check if there are any errors // check if there are any errors
let error = virtual_dom.in_runtime(|| { let error = virtual_dom.in_scope(ScopeId::ROOT_ERROR_BOUNDARY, || {
ScopeId::ROOT_ERROR_BOUNDARY consume_context::<ErrorContext>().error()
.consume_context::<ErrorContext>()
.expect("The root should be under an error boundary")
.error()
}); });
if let Some(error) = error { if let Some(error) = error {
@@ -497,7 +494,9 @@ impl SsrRendererPool {
fn start_capturing_errors(suspense_scope: ScopeId) { fn start_capturing_errors(suspense_scope: ScopeId) {
// Add an error boundary to the scope. We serialize the suspense error boundary separately so we can use // Add an error boundary to the scope. We serialize the suspense error boundary separately so we can use
// the normal in memory ErrorContext here // the normal in memory ErrorContext here
suspense_scope.in_runtime(|| dioxus_core::provide_context(ErrorContext::new(None))); Runtime::current().in_scope(suspense_scope, || {
dioxus_core::provide_context(ErrorContext::new(None))
});
} }
fn serialize_server_data(virtual_dom: &VirtualDom, scope: ScopeId) -> SerializedHydrationData { fn serialize_server_data(virtual_dom: &VirtualDom, scope: ScopeId) -> SerializedHydrationData {
@@ -525,10 +524,8 @@ impl SsrRendererPool {
fn serialize_errors(context: &HydrationContext, vdom: &VirtualDom, scope: ScopeId) { fn serialize_errors(context: &HydrationContext, vdom: &VirtualDom, scope: ScopeId) {
// If there is an error boundary on the suspense boundary, grab the error from the context API // If there is an error boundary on the suspense boundary, grab the error from the context API
// and throw it on the client so that it bubbles up to the nearest error boundary // and throw it on the client so that it bubbles up to the nearest error boundary
let error = vdom.in_runtime(|| { let error = vdom.in_scope(scope, || {
scope try_consume_context::<ErrorContext>().and_then(|error_context| error_context.error())
.consume_context::<ErrorContext>()
.and_then(|error_context| error_context.error())
}); });
context context
.error_entry() .error_entry()
@@ -536,14 +533,12 @@ impl SsrRendererPool {
} }
fn take_from_scope(context: &HydrationContext, vdom: &VirtualDom, scope: ScopeId) { fn take_from_scope(context: &HydrationContext, vdom: &VirtualDom, scope: ScopeId) {
vdom.in_runtime(|| { vdom.in_scope(scope, || {
scope.in_runtime(|| { // Grab any serializable server context from this scope
// Grab any serializable server context from this scope let other: Option<HydrationContext> = has_context();
let other: Option<HydrationContext> = has_context(); if let Some(other) = other {
if let Some(other) = other { context.extend(&other);
context.extend(&other); }
}
});
}); });
// then continue to any children // then continue to any children
@@ -659,7 +654,7 @@ impl SsrRendererPool {
let title = { let title = {
let document: Option<Rc<ServerDocument>> = let document: Option<Rc<ServerDocument>> =
virtual_dom.in_runtime(|| ScopeId::ROOT.consume_context()); virtual_dom.in_scope(ScopeId::ROOT, dioxus_core::try_consume_context);
// Collect any head content from the document provider and inject that into the head // Collect any head content from the document provider and inject that into the head
document.and_then(|document| document.title()) document.and_then(|document| document.title())
}; };
@@ -672,8 +667,8 @@ impl SsrRendererPool {
} }
to.write_str(&index.head_after_title)?; to.write_str(&index.head_after_title)?;
let document: Option<Rc<ServerDocument>> = let document =
virtual_dom.in_runtime(|| ScopeId::ROOT.consume_context()); virtual_dom.in_scope(ScopeId::ROOT, try_consume_context::<Rc<ServerDocument>>);
if let Some(document) = document { if let Some(document) = document {
// Collect any head content from the document provider and inject that into the head // Collect any head content from the document provider and inject that into the head
document.render(to)?; document.render(to)?;
@@ -1,4 +1,4 @@
use dioxus_core::{suspense_context, use_hook}; use dioxus_core::{use_hook, Runtime};
/// Run a closure after the suspense boundary this is under is resolved. The /// Run a closure after the suspense boundary this is under is resolved. The
/// closure will be run immediately if the suspense boundary is already resolved /// closure will be run immediately if the suspense boundary is already resolved
@@ -6,7 +6,7 @@ use dioxus_core::{suspense_context, use_hook};
pub fn use_after_suspense_resolved(suspense_resolved: impl FnOnce() + 'static) { pub fn use_after_suspense_resolved(suspense_resolved: impl FnOnce() + 'static) {
use_hook(|| { use_hook(|| {
// If this is under a suspense boundary, we need to check if it is resolved // If this is under a suspense boundary, we need to check if it is resolved
match suspense_context() { match Runtime::current().suspense_context() {
Some(context) => { Some(context) => {
// If it is suspended, run the closure after the suspense is resolved // If it is suspended, run the closure after the suspense is resolved
context.after_suspense_resolved(suspense_resolved) context.after_suspense_resolved(suspense_resolved)
+5 -4
View File
@@ -64,18 +64,19 @@ impl Evaluator for LiveviewEvaluator {
} }
} }
/// Provides the LiveviewDocument through [`ScopeId::provide_context`]. /// Provides the LiveviewDocument through [`dioxus_core::Runtime::provide_context`].
pub fn init_document() { pub fn init_document() {
let query = ScopeId::ROOT.consume_context::<QueryEngine>().unwrap(); let rt = dioxus_core::Runtime::current();
let query = rt.consume_context::<QueryEngine>(ScopeId::ROOT).unwrap();
let provider: Rc<dyn Document> = Rc::new(LiveviewDocument { let provider: Rc<dyn Document> = Rc::new(LiveviewDocument {
query: query.clone(), query: query.clone(),
}); });
ScopeId::ROOT.provide_context(provider); rt.provide_context(ScopeId::ROOT, provider);
let history = LiveviewHistory::new(Rc::new(move |script: &str| { let history = LiveviewHistory::new(Rc::new(move |script: &str| {
Eval::new(LiveviewEvaluator::create(query.clone(), script.to_string())) Eval::new(LiveviewEvaluator::create(query.clone(), script.to_string()))
})); }));
let history: Rc<dyn History> = Rc::new(history); let history: Rc<dyn History> = Rc::new(history);
ScopeId::ROOT.provide_context(history); rt.provide_context(ScopeId::ROOT, history);
} }
/// Reprints the liveview-target's provider of evaluators. /// Reprints the liveview-target's provider of evaluators.
+1 -1
View File
@@ -133,7 +133,7 @@ pub async fn run(mut vdom: VirtualDom, ws: impl LiveViewSocket) -> Result<(), Li
// Create the a proxy for query engine // Create the a proxy for query engine
let (query_tx, mut query_rx) = tokio::sync::mpsc::unbounded_channel(); let (query_tx, mut query_rx) = tokio::sync::mpsc::unbounded_channel();
let query_engine = QueryEngine::new(query_tx); let query_engine = QueryEngine::new(query_tx);
vdom.runtime().on_scope(ScopeId::ROOT, || { vdom.runtime().in_scope(ScopeId::ROOT, || {
provide_context(query_engine.clone()); provide_context(query_engine.clone());
init_document(); init_document();
}); });
+5 -5
View File
@@ -1,5 +1,5 @@
use blitz_shell::{BlitzApplication, View}; use blitz_shell::{BlitzApplication, View};
use dioxus_core::ScopeId; use dioxus_core::{provide_context, ScopeId};
use dioxus_history::{History, MemoryHistory}; use dioxus_history::{History, MemoryHistory};
use std::rc::Rc; use std::rc::Rc;
use winit::application::ApplicationHandler; use winit::application::ApplicationHandler;
@@ -120,20 +120,20 @@ impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
let window_id = window.window_id(); let window_id = window.window_id();
let doc = window.downcast_doc_mut::<DioxusDocument>(); let doc = window.downcast_doc_mut::<DioxusDocument>();
doc.vdom.in_runtime(|| { doc.vdom.in_scope(ScopeId::ROOT, || {
let shared: Rc<dyn dioxus_document::Document> = let shared: Rc<dyn dioxus_document::Document> =
Rc::new(DioxusNativeDocument::new(self.proxy.clone(), window_id)); Rc::new(DioxusNativeDocument::new(self.proxy.clone(), window_id));
ScopeId::ROOT.provide_context(shared); provide_context(shared);
}); });
// Add history // Add history
let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default()); let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
doc.vdom doc.vdom
.in_runtime(move || ScopeId::ROOT.provide_context(history_provider)); .in_scope(ScopeId::ROOT, move || provide_context(history_provider));
// Add renderer // Add renderer
doc.vdom doc.vdom
.in_runtime(move || ScopeId::ROOT.provide_context(renderer)); .in_scope(ScopeId::ROOT, move || provide_context(renderer));
// Queue rebuild // Queue rebuild
doc.initial_build(); doc.initial_build();
+1 -1
View File
@@ -115,7 +115,7 @@ fn WebSysClosure() -> Element {
// Make sure passing the runtime into the closure works // Make sure passing the runtime into the closure works
let callback = Callback::new(|_| { let callback = Callback::new(|_| {
assert!(!dioxus::dioxus_core::vdom_is_rendering()); assert!(!dioxus::dioxus_core::Runtime::current().vdom_is_rendering());
*TRIGGERED.write() = true; *TRIGGERED.write() = true;
}); });
let closure: Closure<dyn Fn()> = Closure::new({ let closure: Closure<dyn Fn()> = Closure::new({
+10 -5
View File
@@ -35,20 +35,25 @@ struct RootRouterContext(Signal<Option<RouterContext>>);
/// ///
/// This will return `None` if there is no router present or the router has not been created yet. /// This will return `None` if there is no router present or the router has not been created yet.
pub fn root_router() -> Option<RouterContext> { pub fn root_router() -> Option<RouterContext> {
if let Some(ctx) = ScopeId::ROOT.consume_context::<RootRouterContext>() { let rt = dioxus_core::Runtime::current();
if let Some(ctx) = rt.consume_context::<RootRouterContext>(ScopeId::ROOT) {
ctx.0.cloned() ctx.0.cloned()
} else { } else {
ScopeId::ROOT.provide_context(RootRouterContext(Signal::new_in_scope(None, ScopeId::ROOT))); rt.provide_context(
ScopeId::ROOT,
RootRouterContext(Signal::new_in_scope(None, ScopeId::ROOT)),
);
None None
} }
} }
pub(crate) fn provide_router_context(ctx: RouterContext) { pub(crate) fn provide_router_context(ctx: RouterContext) {
if root_router().is_none() { if root_router().is_none() {
ScopeId::ROOT.provide_context(RootRouterContext(Signal::new_in_scope( dioxus_core::Runtime::current().provide_context(
Some(ctx),
ScopeId::ROOT, ScopeId::ROOT,
))); RootRouterContext(Signal::new_in_scope(Some(ctx), ScopeId::ROOT)),
);
} }
provide_context(ctx); provide_context(ctx);
} }
+1 -1
View File
@@ -166,7 +166,7 @@ impl ToTokens for TemplateBody {
#index #index
); );
dioxus_core::Runtime::current().ok().map(|_| __TEMPLATE.read()) dioxus_core::Runtime::try_current().map(|_| __TEMPLATE.read())
}; };
// If the template has not been hot reloaded, we always use the original template // If the template has not been hot reloaded, we always use the original template
// Templates nested within macros may be merged because they have the same file-line-column-index // Templates nested within macros may be merged because they have the same file-line-column-index
+4 -5
View File
@@ -1,8 +1,8 @@
#![allow(clippy::unnecessary_operation)] #![allow(clippy::unnecessary_operation)]
#![allow(clippy::no_effect)] #![allow(clippy::no_effect)]
use dioxus_core::Subscribers;
use dioxus_core::{current_owner, current_scope_id, ScopeId}; use dioxus_core::{current_owner, current_scope_id, ScopeId};
use dioxus_core::{Runtime, Subscribers};
use generational_box::{ use generational_box::{
AnyStorage, BorrowResult, GenerationalBox, GenerationalBoxId, Storage, UnsyncStorage, AnyStorage, BorrowResult, GenerationalBox, GenerationalBoxId, Storage, UnsyncStorage,
}; };
@@ -82,7 +82,7 @@ impl<T, S: Storage<T>> CopyValue<T, S> {
{ {
Self { Self {
value: GenerationalBox::leak(value, caller), value: GenerationalBox::leak(value, caller),
origin_scope: current_scope_id().expect("in a virtual dom"), origin_scope: current_scope_id(),
} }
} }
@@ -99,7 +99,7 @@ impl<T, S: Storage<T>> CopyValue<T, S> {
Self { Self {
value: owner.insert_rc_with_caller(value, caller), value: owner.insert_rc_with_caller(value, caller),
origin_scope: current_scope_id().expect("in a virtual dom"), origin_scope: current_scope_id(),
} }
} }
@@ -116,8 +116,7 @@ impl<T, S: Storage<T>> CopyValue<T, S> {
scope: ScopeId, scope: ScopeId,
caller: &'static std::panic::Location<'static>, caller: &'static std::panic::Location<'static>,
) -> Self { ) -> Self {
let owner = scope.owner(); let owner = Runtime::current().scope_owner(scope);
Self { Self {
value: owner.insert_rc_with_caller(value, caller), value: owner.insert_rc_with_caller(value, caller),
origin_scope: scope, origin_scope: scope,
+7 -4
View File
@@ -1,4 +1,4 @@
use dioxus_core::{ScopeId, Subscribers}; use dioxus_core::{Runtime, ScopeId, Subscribers};
use generational_box::BorrowResult; use generational_box::BorrowResult;
use std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc}; use std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc};
@@ -190,7 +190,9 @@ where
} }
// Otherwise, create it // Otherwise, create it
// Constructors are always run in the root scope // Constructors are always run in the root scope
let signal = ScopeId::ROOT.in_runtime(|| T::initialize_from_function(self.constructor)); let signal = dioxus_core::Runtime::current().in_scope(ScopeId::ROOT, || {
T::initialize_from_function(self.constructor)
});
context context
.map .map
.borrow_mut() .borrow_mut()
@@ -274,9 +276,10 @@ impl GlobalLazyContext {
/// Get the global context for signals /// Get the global context for signals
pub fn get_global_context() -> GlobalLazyContext { pub fn get_global_context() -> GlobalLazyContext {
match ScopeId::ROOT.has_context() { let rt = Runtime::current();
match rt.has_context(ScopeId::ROOT) {
Some(context) => context, Some(context) => context,
None => ScopeId::ROOT.provide_context(Default::default()), None => rt.provide_context(ScopeId::ROOT, Default::default()),
} }
} }
+1 -2
View File
@@ -56,8 +56,7 @@ impl<T> Memo<T> {
let _ = tx.unbounded_send(()); let _ = tx.unbounded_send(());
} }
}; };
let rc = let rc = ReactiveContext::new_with_callback(callback, current_scope_id(), location);
ReactiveContext::new_with_callback(callback, current_scope_id().unwrap(), location);
// Create a new signal in that context, wiring up its dependencies and subscribers // Create a new signal in that context, wiring up its dependencies and subscribers
let mut recompute = move || rc.reset_and_run_in(&mut f); let mut recompute = move || rc.reset_and_run_in(&mut f);
+6 -2
View File
@@ -9,9 +9,13 @@ pub fn copy_value_hoisted<T: 'static, S: generational_box::Storage<T> + 'static>
caller: &'static std::panic::Location<'static>, caller: &'static std::panic::Location<'static>,
) { ) {
let origin_scope = value.origin_scope; let origin_scope = value.origin_scope;
if let Ok(current_scope) = dioxus_core::current_scope_id() { let Some(rt) = dioxus_core::Runtime::try_current() else {
return;
};
if let Some(current_scope) = rt.try_current_scope_id() {
// If the current scope is a descendant of the origin scope or is the same scope, we don't need to warn // If the current scope is a descendant of the origin scope or is the same scope, we don't need to warn
if origin_scope == current_scope || current_scope.is_descendant_of(origin_scope) { if origin_scope == current_scope || rt.is_descendant_of(current_scope, origin_scope) {
return; return;
} }
let create_location = value.value.created_at().unwrap(); let create_location = value.value.created_at().unwrap();
+1 -1
View File
@@ -61,7 +61,7 @@ fn reading_subscribes() {
.counter .counter
.borrow_mut() .borrow_mut()
.children .children
.entry(current_scope_id().unwrap()) .entry(current_scope_id())
.or_default() += 1; .or_default() += 1;
rsx! { rsx! {
+3 -4
View File
@@ -1,6 +1,6 @@
use dioxus_core::provide_context;
use dioxus_core::queue_effect; use dioxus_core::queue_effect;
use dioxus_core::ScopeId; use dioxus_core::ScopeId;
use dioxus_core::{provide_context, Runtime};
use dioxus_document::{ use dioxus_document::{
Document, Eval, EvalError, Evaluator, LinkProps, MetaProps, ScriptProps, StyleProps, Document, Eval, EvalError, Evaluator, LinkProps, MetaProps, ScriptProps, StyleProps,
}; };
@@ -69,8 +69,7 @@ extern "C" {
fn init_document_with(document: impl FnOnce(), history: impl FnOnce()) { fn init_document_with(document: impl FnOnce(), history: impl FnOnce()) {
use dioxus_core::has_context; use dioxus_core::has_context;
Runtime::current().in_scope(ScopeId::ROOT, || {
ScopeId::ROOT.in_runtime(|| {
if has_context::<Rc<dyn Document>>().is_none() { if has_context::<Rc<dyn Document>>().is_none() {
document(); document();
} }
@@ -80,7 +79,7 @@ fn init_document_with(document: impl FnOnce(), history: impl FnOnce()) {
}) })
} }
/// Provides the Document through [`ScopeId::provide_context`]. /// Provides the Document through [`dioxus_core::provide_context`].
pub fn init_document() { pub fn init_document() {
// If hydrate is enabled, we add the FullstackWebDocument with the initial hydration data // If hydrate is enabled, we add the FullstackWebDocument with the initial hydration data
#[cfg(not(feature = "hydrate"))] #[cfg(not(feature = "hydrate"))]
+1 -1
View File
@@ -149,7 +149,7 @@ impl WebsysDom {
let server_data = HydrationContext::from_serialized(&data, debug_types, debug_locations); let server_data = HydrationContext::from_serialized(&data, debug_types, debug_locations);
// If the server serialized an error into the suspense boundary, throw it on the client so that it bubbles up to the nearest error boundary // If the server serialized an error into the suspense boundary, throw it on the client so that it bubbles up to the nearest error boundary
if let Some(error) = server_data.error_entry().get().ok().flatten() { if let Some(error) = server_data.error_entry().get().ok().flatten() {
dom.in_runtime(|| id.throw_error(error)); dom.in_runtime(|| dom.runtime().throw_error(id, error));
} }
server_data.in_context(|| { server_data.in_context(|| {
// rerun the scope with the new data // rerun the scope with the new data
+7 -9
View File
@@ -6,7 +6,7 @@
pub use crate::cfg::Config; pub use crate::cfg::Config;
use crate::hydration::SuspenseMessage; use crate::hydration::SuspenseMessage;
use dioxus_core::VirtualDom; use dioxus_core::{ScopeId, VirtualDom};
use dom::WebsysDom; use dom::WebsysDom;
use futures_util::{pin_mut, select, FutureExt, StreamExt}; use futures_util::{pin_mut, select, FutureExt, StreamExt};
@@ -53,7 +53,7 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
#[cfg(feature = "document")] #[cfg(feature = "document")]
if let Some(history) = web_config.history.clone() { if let Some(history) = web_config.history.clone() {
virtual_dom.in_runtime(|| dioxus_core::ScopeId::ROOT.provide_context(history)); virtual_dom.in_scope(ScopeId::ROOT, || dioxus_core::provide_context(history));
} }
#[cfg(feature = "document")] #[cfg(feature = "document")]
@@ -129,16 +129,14 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
HydrationContext::from_serialized(&hydration_data, debug_types, debug_locations); HydrationContext::from_serialized(&hydration_data, debug_types, debug_locations);
// If the server serialized an error into the root suspense boundary, throw it into the root scope // If the server serialized an error into the root suspense boundary, throw it into the root scope
if let Some(error) = server_data.error_entry().get().ok().flatten() { if let Some(error) = server_data.error_entry().get().ok().flatten() {
virtual_dom.in_runtime(|| dioxus_core::ScopeId::APP.throw_error(error)); virtual_dom.in_runtime(|| virtual_dom.runtime().throw_error(ScopeId::APP, error));
} }
server_data.in_context(|| { server_data.in_context(|| {
virtual_dom.in_runtime(|| { virtual_dom.in_scope(ScopeId::ROOT, || {
// Provide a hydration compatible create error boundary method // Provide a hydration compatible create error boundary method
dioxus_core::ScopeId::ROOT.in_runtime(|| { dioxus_core::provide_create_error_boundary(
dioxus_core::provide_create_error_boundary( dioxus_fullstack_core::init_error_boundary,
dioxus_fullstack_core::init_error_boundary, );
);
});
#[cfg(feature = "document")] #[cfg(feature = "document")]
document::init_fullstack_document(); document::init_fullstack_document();
}); });