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));
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);
if render_to.is_some() {
self.runtime.get_state(scope).unwrap().mount(&self.runtime);
self.runtime.get_state(scope).mount(&self.runtime);
}
nodes
@@ -146,7 +146,7 @@ impl VNode {
// Now diff the scope
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));
}
+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)
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)()
}
+7 -12
View File
@@ -487,10 +487,8 @@ impl<Args: 'static, Ret: 'static> Callback<Args, Ret> {
pub fn new<MaybeAsync: SpawnIfAsync<Marker, Ret>, Marker>(
mut f: impl FnMut(Args) -> MaybeAsync + 'static,
) -> Self {
let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e));
let origin = runtime
.current_scope_id()
.unwrap_or_else(|e| panic!("{}", e));
let runtime = Runtime::current();
let origin = runtime.current_scope_id();
let owner = crate::innerlude::current_owner::<generational_box::UnsyncStorage>();
let callback = owner.insert_rc(Some(ExternalListenerCallback {
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.
#[track_caller]
pub fn leak(mut f: impl FnMut(Args) -> Ret + 'static) -> Self {
let runtime = Runtime::current().unwrap_or_else(|e| panic!("{}", e));
let origin = runtime
.current_scope_id()
.unwrap_or_else(|e| panic!("{}", e));
let runtime = Runtime::current();
let origin = runtime.current_scope_id();
let callback = GenerationalBox::leak_rc(
Some(ExternalListenerCallback {
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
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 {
callback,
runtime: Rc::downgrade(&runtime),
@@ -647,7 +643,7 @@ impl<T> ListenerCallback<T> {
MaybeAsync: SpawnIfAsync<Marker>,
{
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>| {
let data = event.data.downcast::<T>().unwrap();
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
/// calling this method.
pub fn call(&self, event: Event<dyn Any>) {
let runtime = Runtime::current().expect("ListenerCallback must be called within a runtime");
runtime.with_scope_on_stack(self.origin, || {
Runtime::current().with_scope_on_stack(self.origin, || {
(self.callback.borrow_mut())(event);
});
}
+2 -14
View File
@@ -2,15 +2,12 @@
//!
//! Each scope in dioxus has a single [Owner]
use generational_box::{AnyStorage, Owner, SyncStorage, UnsyncStorage};
use std::{
any::{Any, TypeId},
cell::RefCell,
};
use generational_box::{AnyStorage, Owner, SyncStorage, UnsyncStorage};
use crate::{innerlude::current_scope_id, Runtime, ScopeId};
/// Run a closure with the given owner.
///
/// This will override the default owner for the current component.
@@ -84,14 +81,5 @@ pub fn current_owner<S: AnyStorage>() -> Owner<S> {
return owner;
}
// Otherwise get the owner from the current scope
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))
}
crate::Runtime::current().current_owner()
}
+24 -70
View File
@@ -1,23 +1,12 @@
use crate::{innerlude::CapturedError, runtime::RuntimeError};
use crate::{
innerlude::SuspendedFuture, runtime::Runtime, Element, ScopeId, SuspenseContext, Task,
};
use crate::innerlude::CapturedError;
use crate::{innerlude::SuspendedFuture, runtime::Runtime, Element, ScopeId, Task};
use std::future::Future;
use std::rc::Rc;
use std::sync::Arc;
/// Get the current scope id
pub fn current_scope_id() -> Result<ScopeId, RuntimeError> {
Runtime::with(|rt| rt.current_scope_id().ok())
.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()
pub fn current_scope_id() -> ScopeId {
Runtime::with(|rt| rt.current_scope_id())
}
/// 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 {
/// Ok(_) => unimplemented!(),
/// // 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) {
current_scope_id()
.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()
Runtime::with(|rt| rt.throw_error(rt.current_scope_id(), error))
}
/// Consume context from the current scope
pub fn try_consume_context<T: 'static + Clone>() -> Option<T> {
Runtime::with_current_scope(|cx| cx.consume_context::<T>())
.ok()
.flatten()
}
/// Consume context from the current scope
pub fn consume_context<T: 'static + Clone>() -> 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>()))
}
/// Consume context from the current scope
pub fn consume_context_from_scope<T: 'static + Clone>(scope_id: ScopeId) -> Option<T> {
Runtime::with(|rt| {
rt.get_state(scope_id)
.and_then(|cx| cx.consume_context::<T>())
})
.ok()
.flatten()
Runtime::current()
.try_get_state(scope_id)
.and_then(|cx| cx.consume_context::<T>())
}
/// Check if the current scope has a context
pub fn has_context<T: 'static + Clone>() -> Option<T> {
Runtime::with_current_scope(|cx| cx.has_context::<T>())
.ok()
.flatten()
}
/// Provide context to the current scope
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
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
@@ -128,7 +99,7 @@ pub fn suspend(task: Task) -> Element {
///
#[doc = include_str!("../docs/common_spawn_errors.md")]
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.
@@ -154,12 +125,12 @@ pub fn spawn_isomorphic(fut: impl Future<Output = ()> + 'static) -> Task {
///
#[doc = include_str!("../docs/common_spawn_errors.md")]
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.
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
@@ -215,8 +186,8 @@ pub fn queue_effect(f: impl FnOnce() + 'static) {
/// ```
///
#[doc = include_str!("../docs/common_spawn_errors.md")]
pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Option<Task> {
Runtime::with_scope(ScopeId::ROOT, |cx| cx.spawn(fut)).ok()
pub fn spawn_forever(fut: impl Future<Output = ()> + 'static) -> Task {
Runtime::with_scope(ScopeId::ROOT, |cx| cx.spawn(fut))
}
/// 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]
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.
///
/// This can be used as a helpful diagnostic when debugging hooks/renders, etc.
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.
pub fn parent_scope() -> Option<ScopeId> {
Runtime::with_current_scope(|cx| cx.parent_id())
.ok()
.flatten()
}
/// Mark the current scope as dirty, causing it to re-render.
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.
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.
@@ -349,7 +318,7 @@ pub fn needs_update_any(id: ScopeId) {
/// You should prefer [`schedule_update_any`] if you need to update multiple components.
#[track_caller]
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`].
@@ -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.
#[track_caller]
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.
@@ -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
/// Will run for every progression of suspense, though this might change in the future
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
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
@@ -486,18 +455,3 @@ pub fn use_hook_with_cleanup<T: Clone + 'static>(
use_drop(move || cleanup(_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::{
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,
parent_scope, provide_context, provide_create_error_boundary, provide_root_context,
queue_effect, remove_future, schedule_update, schedule_update_any, spawn, spawn_forever,
spawn_isomorphic, suspend, suspense_context, throw_error, try_consume_context,
use_after_render, use_before_render, use_drop, use_hook, use_hook_with_cleanup,
vdom_is_rendering, with_owner, AnyValue, AnyhowContext, Attribute, AttributeValue, Callback,
CapturedError, Component, ComponentFunction, DynamicNode, Element, ElementId, Error,
ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes, IntoAttributeValue,
IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation, Mutations, NoOpMutations,
OptionStringFromMarker, Properties, ReactiveContext, RenderError, Result, Runtime,
RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers, SuperFrom,
SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps, SuspenseContext, Task,
Template, TemplateAttribute, TemplateNode, VComponent, VNode, VNodeInner, VPlaceholder, VText,
VirtualDom, WriteMutations,
fc_to_builder, generation, has_context, needs_update, needs_update_any, parent_scope,
provide_context, provide_create_error_boundary, provide_root_context, queue_effect,
remove_future, schedule_update, schedule_update_any, spawn, spawn_forever, spawn_isomorphic,
suspend, throw_error, try_consume_context, use_after_render, use_before_render, use_drop,
use_hook, use_hook_with_cleanup, with_owner, AnyValue, AnyhowContext, Attribute,
AttributeValue, Callback, CapturedError, Component, ComponentFunction, DynamicNode, Element,
ElementId, Error, ErrorBoundary, ErrorContext, Event, EventHandler, Fragment, HasAttributes,
IntoAttributeValue, IntoDynNode, LaunchConfig, ListenerCallback, MarkerWrapper, Mutation,
Mutations, NoOpMutations, OptionStringFromMarker, Properties, ReactiveContext, RenderError,
Result, Runtime, RuntimeGuard, ScopeId, ScopeState, SpawnIfAsync, SubscriberList, Subscribers,
SuperFrom, SuperInto, SuspendedFuture, SuspenseBoundary, SuspenseBoundaryProps,
SuspenseContext, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode,
VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations,
};
pub use anyhow::Ok;
+2 -6
View File
@@ -62,11 +62,7 @@ impl ReactiveContext {
}
let _ = tx.unbounded_send(());
};
let _self = Self::new_with_callback(
callback,
current_scope_id().unwrap_or_else(|e| panic!("{}", e)),
origin,
);
let _self = Self::new_with_callback(callback, current_scope_id(), origin);
(_self, rx)
}
@@ -86,7 +82,7 @@ impl ReactiveContext {
scope: None,
};
let owner = scope.owner();
let owner = Runtime::current().scope_owner(scope);
let self_ = Self {
scope,
+196 -99
View File
@@ -1,8 +1,11 @@
use crate::arena::ElementRef;
use crate::innerlude::{DirtyTasks, Effect};
use crate::nodes::VNodeMount;
use crate::scheduler::ScopeOrder;
use crate::scope_context::SuspenseLocation;
use crate::{arena::ElementRef, CapturedError};
use crate::{
innerlude::{DirtyTasks, Effect},
SuspenseContext,
};
use crate::{
innerlude::{LocalTask, SchedulerMsg},
scope_context::Scope,
@@ -10,11 +13,11 @@ use crate::{
Task,
};
use crate::{AttributeValue, ElementId, Event};
use generational_box::{AnyStorage, Owner};
use slab::Slab;
use slotmap::DefaultKey;
use std::any::Any;
use std::collections::BTreeSet;
use std::fmt;
use std::{
cell::{Cell, Ref, RefCell},
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
pub struct Runtime {
pub(crate) scope_states: RefCell<Vec<Option<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
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
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
pub(crate) current_task: Cell<Option<Task>>,
@@ -89,18 +93,55 @@ impl Runtime {
}
/// Get the current runtime
pub fn current() -> Result<Rc<Self>, RuntimeError> {
pub fn current() -> Rc<Self> {
RUNTIMES
.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
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_scope = current_runtime.current_scope_id().ok();
move |input| match current_scope {
Some(scope) => current_runtime.on_scope(scope, || f(input)),
let current_runtime = Self::current();
move |input| match current_runtime.try_current_scope_id() {
Some(scope) => current_runtime.in_scope(scope, || f(input)),
None => {
let _runtime_guard = RuntimeGuard::new(current_runtime.clone());
f(input)
@@ -140,7 +181,7 @@ impl Runtime {
let borrow = self.scope_states.borrow();
if let Some(scope) = &borrow[id.0] {
// 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
// In theory nested tasks might not like this
for id in scope.spawned_tasks.take() {
@@ -165,19 +206,31 @@ impl Runtime {
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
pub(crate) fn current_scope_id(&self) -> Result<ScopeId, RuntimeError> {
self.scope_stack
.borrow()
.last()
.copied()
.ok_or(RuntimeError { _priv: () })
pub fn current_scope_id(&self) -> ScopeId {
self.scope_stack.borrow().last().copied().unwrap()
}
/// Try to get the current scope id, returning None if it we aren't actively inside a scope
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
///
/// Useful in a limited number of scenarios
pub fn on_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
#[track_caller]
pub fn in_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
let _runtime_guard = RuntimeGuard::new(self.clone());
{
self.push_scope(id);
@@ -236,7 +289,18 @@ impl Runtime {
/// 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 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| {
contexts.get(id.0).and_then(|f| f.as_ref())
})
@@ -254,30 +318,19 @@ impl Runtime {
}
/// Runs a function with the current runtime
pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> Result<R, RuntimeError> {
Self::current().map(|r| f(&r))
pub(crate) fn with<R>(callback: impl FnOnce(&Runtime) -> R) -> R {
callback(&Self::current())
}
/// Runs a function with the current scope
pub(crate) fn with_current_scope<R>(f: impl FnOnce(&Scope) -> R) -> Result<R, RuntimeError> {
Self::with(|rt| {
rt.current_scope_id()
.ok()
.and_then(|scope| rt.get_state(scope).map(|sc| f(&sc)))
})
.ok()
.flatten()
.ok_or(RuntimeError::new())
pub(crate) fn with_current_scope<R>(callback: impl FnOnce(&Scope) -> R) -> R {
Self::with(|rt| Self::with_scope(rt.current_scope_id(), callback))
}
/// Runs a function with the current scope
pub(crate) fn with_scope<R>(
scope: ScopeId,
callback: impl FnOnce(&Scope) -> R,
) -> Result<R, RuntimeError> {
Self::with(|rt| rt.get_state(scope).map(|scopestate| callback(&scopestate)))
.expect("Runtime should exist")
.ok_or(RuntimeError::new())
pub(crate) fn with_scope<R>(scope: ScopeId, callback: impl FnOnce(&Scope) -> R) -> R {
let rt = Runtime::current();
Self::in_scope(&rt, scope, || callback(&rt.get_state(scope)))
}
/// 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 {
return true;
}
// If this is not a suspended scope, and we are under a frozen context, then we should
let scopes = self.scope_states.borrow();
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.
@@ -454,7 +609,7 @@ impl Runtime {
/// }
///
/// fn app() -> Element {
/// rsx! { Component { runtime: Runtime::current().unwrap() } }
/// rsx! { Component { runtime: Runtime::current() } }
/// }
///
/// // In a dynamic library
@@ -492,61 +647,3 @@ impl Drop for RuntimeGuard {
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,
name: &'static str,
) -> &mut ScopeState {
let parent_id = self.runtime.current_scope_id().ok();
let height = match parent_id.and_then(|id| self.runtime.get_state(id)) {
let parent_id = self.runtime.try_current_scope_id();
let height = match parent_id.and_then(|id| self.runtime.try_get_state(id)) {
Some(parent) => parent.height() + 1,
None => 0,
};
@@ -46,7 +46,7 @@ impl VirtualDom {
#[track_caller]
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> Element {
// 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, || {
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
})
})
@@ -105,23 +105,16 @@ impl VirtualDom {
}
/// 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 {
Err(RenderError::Error(e)) => {
tracing::error!(
"Error while rendering component `{}`:\n{e}",
scope_state.name
);
tracing::error!("Error while rendering component `{}`:\n{e}", scope.name);
throw_error(e.clone());
}
Err(RenderError::Suspended(e)) => {
let task = e.task();
// Insert the task into the nearest suspense boundary if it exists
let boundary = self
.runtime
.get_state(scope_id)
.unwrap()
.suspense_location();
let boundary = scope.suspense_location();
let already_suspended = self
.runtime
.tasks
@@ -130,7 +123,7 @@ impl VirtualDom {
.expect("Suspended on a task that no longer exists")
.suspend(boundary.clone());
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
if let SuspenseLocation::UnderSuspense(boundary) = &boundary {
boundary.add_suspended_task(e.clone());
@@ -142,9 +135,7 @@ impl VirtualDom {
}
Ok(_) => {
// If the render was successful, we can move the render generation forward by one
scope_state
.render_count
.set(scope_state.render_count.get() + 1);
scope.render_count.set(scope.render_count.get() + 1);
}
}
}
+33 -283
View File
@@ -1,6 +1,5 @@
use crate::{
innerlude::{CapturedError, SchedulerMsg, SuspenseContext},
runtime::RuntimeError,
innerlude::{SchedulerMsg, SuspenseContext},
Runtime, ScopeId, Task,
};
use generational_box::{AnyStorage, Owner};
@@ -96,7 +95,7 @@ impl Scope {
}
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
@@ -133,12 +132,12 @@ impl Scope {
}
/// 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)
}
/// 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()
.unbounded_send(SchedulerMsg::Immediate(id))
.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.
/// 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.
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);
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.
/// 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();
Arc::new(move |id| {
_ = chan.unbounded_send(SchedulerMsg::Immediate(id));
@@ -172,7 +171,7 @@ impl 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() {
Some(rt) => rt,
None => {
@@ -183,7 +182,7 @@ impl 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
.borrow()
.iter()
@@ -194,49 +193,23 @@ impl Scope {
/// Try to retrieve a shared state with type `T` from any parent scope.
///
/// Clones the state if it exists.
pub fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
tracing::trace!(
"looking for context {} ({:?}) in {:?}",
std::any::type_name::<T>(),
std::any::TypeId::of::<T>(),
self.id
);
if let Some(this_ctx) = self.has_context() {
pub(crate) fn consume_context<T: 'static + Clone>(&self) -> Option<T> {
if let Some(this_ctx) = self.has_context::<T>() {
return Some(this_ctx);
}
let mut search_parent = self.parent_id;
let cur_runtime = Runtime::with(|runtime| {
Runtime::with(|runtime| {
while let Some(parent_id) = search_parent {
let Some(parent) = runtime.get_state(parent_id) else {
tracing::error!("Parent scope {:?} not found", parent_id);
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() {
let parent = runtime.try_get_state(parent_id)?;
if let Some(shared) = parent.has_context::<T>() {
return Some(shared);
}
search_parent = parent.parent_id;
}
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
@@ -281,13 +254,7 @@ impl Scope {
/// rsx!(div { "hello {state.0}" })
/// }
/// ```
pub 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
);
pub(crate) fn provide_context<T: 'static + Clone>(&self, value: T) -> T {
let mut contexts = self.shared_contexts.borrow_mut();
// 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
/// 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 {
Runtime::with(|runtime| {
runtime
.get_state(ScopeId::ROOT)
.unwrap()
.provide_context(context)
})
.expect("Runtime to exist")
pub(crate) fn provide_root_context<T: 'static + Clone>(&self, context: T) -> T {
Runtime::with(|runtime| runtime.get_state(ScopeId::ROOT).provide_context(context))
}
/// 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 {
let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut)).expect("Runtime to exist");
pub(crate) fn spawn_isomorphic(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn_isomorphic(self.id, fut));
self.spawned_tasks.borrow_mut().insert(id);
id
}
/// Spawns the future and returns the [`Task`]
pub fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist");
pub(crate) fn spawn(&self, fut: impl Future<Output = ()> + 'static) -> Task {
let id = Runtime::with(|rt| rt.spawn(self.id, fut));
self.spawned_tasks.borrow_mut().insert(id);
id
}
/// Queue an effect to run after the next render
pub fn queue_effect(&self, f: impl FnOnce() + 'static) {
Runtime::with(|rt| rt.queue_effect(self.id, f)).expect("Runtime to exist");
pub(crate) fn queue_effect(&self, f: impl FnOnce() + 'static) {
Runtime::with(|rt| rt.queue_effect(self.id, f));
}
/// Store a value between renders. The foundational hook for all other hooks.
///
/// Accepts an `initializer` closure, which is run on the first use of the hook (typically the initial render).
/// `use_hook` will return a clone of the value on every render.
///
/// 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 {
/// Store a value in the hook list, returning the value.
pub(crate) fn use_hook<State: Clone + 'static>(
&self,
initializer: impl FnOnce() -> State,
) -> State {
let cur_hook = self.hook_index.get();
// 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));
}
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));
}
/// 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) -> usize {
pub(crate) fn generation(&self) -> usize {
self.render_count.get()
}
/// Get the height of this scope
pub fn height(&self) -> u32 {
pub(crate) fn height(&self) -> u32 {
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);
#[cfg(debug_assertions)]
{
if let Some(name) = Runtime::current()
.ok()
if let Some(scope) = Runtime::try_current()
.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()
@@ -81,6 +80,45 @@ pub struct ScopeState {
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)]
pub enum LastRenderedNode {
Real(VNode),
@@ -119,37 +157,3 @@ impl Drop for ScopeState {
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,
) -> Option<Self> {
runtime
.get_state(scope_id)
.try_get_state(scope_id)
.and_then(|scope| scope.suspense_boundary())
}
+10 -3
View File
@@ -45,7 +45,7 @@ impl SuspendedFuture {
pub fn new(task: Task) -> Self {
Self {
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 {
Self {
inner: Rc::new(SuspenseBoundaryInner {
rt: Runtime::current(),
suspended_tasks: RefCell::new(vec![]),
id: Cell::new(ScopeId::ROOT),
suspended_nodes: Default::default(),
@@ -145,7 +146,7 @@ impl SuspenseContext {
/// Add a suspended task
pub(crate) fn add_suspended_task(&self, task: SuspendedFuture) {
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
@@ -154,7 +155,7 @@ impl SuspenseContext {
.suspended_tasks
.borrow_mut()
.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
@@ -184,12 +185,18 @@ impl SuspenseContext {
/// A boundary that will capture any errors from child components
pub struct SuspenseBoundaryInner {
rt: Rc<Runtime>,
suspended_tasks: RefCell<Vec<SuspendedFuture>>,
id: Cell<ScopeId>,
/// The nodes that are suspended under this boundary
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
frozen: Cell<bool>,
/// Closures queued to run after the suspense boundary is resolved
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 slotmap::DefaultKey;
use std::marker::PhantomData;
use std::panic;
use std::sync::Arc;
use std::task::Waker;
use std::{cell::Cell, future::Future};
@@ -73,7 +72,6 @@ impl Task {
false
}
})
.unwrap_or_default()
}
/// Wake the task.
@@ -84,13 +82,12 @@ impl Task {
.sender
.unbounded_send(SchedulerMsg::TaskNotified(self.id))
})
.unwrap_or_else(|e| panic!("{}", e))
}
/// Poll the task immediately.
#[track_caller]
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.
@@ -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
pub(crate) fn queue_effect(&self, id: ScopeId, f: impl 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;
};
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
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) {
Some(effects) => effects.push_back(f),
None => {
@@ -267,7 +264,7 @@ impl Runtime {
#[cfg(debug_assertions)]
{
// 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();
@@ -293,7 +290,6 @@ impl Runtime {
if poll_result.is_ready() {
// Remove it from the scope so we dont try to double drop it when the scope dropes
self.get_state(task.scope)
.unwrap()
.spawned_tasks
.borrow_mut()
.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
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);
if let Some(dirty_tasks) = self.dirty_tasks.borrow_mut().get(&order) {
dirty_tasks.remove(id);
+5 -5
View File
@@ -353,7 +353,7 @@ impl VirtualDom {
/// Run a closure inside a specific scope
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
@@ -393,7 +393,7 @@ impl VirtualDom {
///
/// Whenever the Runtime "works", it will re-render this scope
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;
};
@@ -408,7 +408,7 @@ impl VirtualDom {
let Some(scope) = self.runtime.task_scope(task) else {
return;
};
let Some(scope) = self.runtime.get_state(scope) else {
let Some(scope) = self.runtime.try_get_state(scope) else {
return;
};
@@ -718,7 +718,7 @@ impl VirtualDom {
let scope_id: ScopeId = scope.id;
let run_scope = self
.runtime
.get_state(scope.id)
.try_get_state(scope.id)
.filter(|scope| scope.should_run_during_suspense())
.is_some();
if run_scope {
@@ -747,7 +747,7 @@ impl VirtualDom {
}
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)
}
+3 -3
View File
@@ -1,6 +1,6 @@
use dioxus::dioxus_core::{ElementId, Mutation::*};
use dioxus::prelude::*;
use dioxus_core::generation;
use dioxus_core::{consume_context_from_scope, generation};
#[test]
fn state_shares() {
@@ -31,13 +31,13 @@ fn state_shares() {
dom.mark_dirty(ScopeId::APP);
_ = dom.render_immediate_to_vec();
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.render_immediate_to_vec();
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));
+1 -1
View File
@@ -14,6 +14,6 @@ fn root_node_isnt_null() {
dom.in_runtime(|| {
// 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");
dom.in_runtime(|| ScopeId::APP.in_runtime(|| *SUSPENDED.write() = true));
dom.in_scope(ScopeId::APP, || *SUSPENDED.write() = true);
dom.render_suspense_immediate().await;
let out = dioxus_ssr::render(&dom);
+5 -11
View File
@@ -8,7 +8,7 @@ use crate::{
shortcut::ShortcutRegistry,
webview::{PendingWebview, WebviewInstance},
};
use dioxus_core::{ElementId, ScopeId, VirtualDom};
use dioxus_core::{consume_context, ElementId, ScopeId, VirtualDom};
use dioxus_history::History;
use dioxus_html::PlatformEventData;
use std::{
@@ -550,11 +550,8 @@ impl App {
return;
};
let url = webview.dom.in_runtime(|| {
ScopeId::ROOT
.consume_context::<Rc<dyn History>>()
.unwrap()
.current_route()
let url = webview.dom.in_scope(ScopeId::ROOT, || {
consume_context::<Rc<dyn History>>().current_route()
});
let state = PreservedWindowState {
@@ -619,12 +616,9 @@ impl App {
}
// Set the url if it exists
webview.dom.in_runtime(|| {
webview.dom.in_scope(ScopeId::ROOT, || {
if let Some(url) = state.url {
ScopeId::ROOT
.consume_context::<Rc<dyn History>>()
.unwrap()
.replace(url);
consume_context::<Rc<dyn History>>().replace(url);
}
})
}
+3 -3
View File
@@ -22,13 +22,13 @@ pub fn use_wry_event_handler(
use dioxus_core::current_scope_id;
// Capture the current runtime and scope ID.
let runtime = Runtime::current().unwrap();
let scope_id = current_scope_id().unwrap();
let runtime = Runtime::current();
let scope_id = current_scope_id();
use_hook_with_cleanup(
move || {
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(),
+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.
UserWindowEvent::WindowsDragDrop(id) => {
if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_runtime(|| {
ScopeId::ROOT.in_runtime(|| {
eval("window.interpreter.handleWindowsDragDrop();");
});
webview.dom.in_scope(ScopeId::ROOT, || {
eval("window.interpreter.handleWindowsDragDrop();");
});
}
}
UserWindowEvent::WindowsDragLeave(id) => {
if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_runtime(|| {
ScopeId::ROOT.in_runtime(|| {
eval("window.interpreter.handleWindowsDragLeave();");
});
webview.dom.in_scope(ScopeId::ROOT, || {
eval("window.interpreter.handleWindowsDragLeave();");
});
}
}
UserWindowEvent::WindowsDragOver(id, x_pos, y_pos) => {
if let Some(webview) = app.webviews.get(&id) {
webview.dom.in_runtime(|| {
ScopeId::ROOT.in_runtime(|| {
let e = eval(
r#"
webview.dom.in_scope(ScopeId::ROOT, || {
let e = eval(
r#"
const xPos = await dioxus.recv();
const yPos = await dioxus.recv();
window.interpreter.handleWindowsDragOver(xPos, yPos)
"#,
);
);
_ = e.send(x_pos);
_ = e.send(y_pos);
});
_ = e.send(x_pos);
_ = e.send(y_pos);
});
}
}
+8 -10
View File
@@ -10,7 +10,7 @@ use crate::{
};
use crate::{document::DesktopDocument, WeakDesktopContext};
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_history::{History, MemoryHistory};
use dioxus_hooks::to_owned;
@@ -427,10 +427,10 @@ impl WebviewInstance {
edits.set_desktop_context(Rc::downgrade(&desktop_context));
let provider: Rc<dyn Document> = Rc::new(DesktopDocument::new(desktop_context.clone()));
let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
dom.in_runtime(|| {
ScopeId::ROOT.provide_context(desktop_context.clone());
ScopeId::ROOT.provide_context(provider);
ScopeId::ROOT.provide_context(history_provider);
dom.in_scope(ScopeId::ROOT, || {
provide_context(desktop_context.clone());
provide_context(provider);
provide_context(history_provider);
});
WebviewInstance {
@@ -566,11 +566,9 @@ impl PendingWebview {
pub(crate) fn create_window(self, shared: &Rc<SharedContext>) -> WebviewInstance {
let window = WebviewInstance::new(self.cfg, self.dom, shared.clone());
let cx = window.dom.in_runtime(|| {
ScopeId::ROOT
.consume_context::<Rc<DesktopService>>()
.unwrap()
});
let cx = window
.dom
.in_scope(ScopeId::ROOT, consume_context::<Rc<DesktopService>>);
_ = self.sender.send(cx);
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.
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...
let ctx = dioxus_signals::get_global_context();
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 {
unsafe { subsecond::apply_patch(jump_table) }?;
dioxus_core::force_all_dirty();
dom.runtime().force_all_dirty();
ctx.clear::<Signal<Option<HotReloadedTemplate>>>();
}
}
+6 -3
View File
@@ -2,7 +2,9 @@
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::*;
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 {
match ScopeId::ROOT.has_context::<T>() {
let rt = Runtime::current();
match rt.has_context::<T>(ScopeId::ROOT) {
Some(context) => context,
None => {
let context = T::default();
ScopeId::ROOT.provide_context(context.clone());
rt.provide_context(ScopeId::ROOT, context.clone());
context
}
}
+18 -23
View File
@@ -7,8 +7,8 @@ use crate::streaming::{Mount, StreamingRenderer};
use crate::{document::ServerDocument, ServeConfig};
use dioxus_cli_config::base_path;
use dioxus_core::{
has_context, DynamicNode, ErrorContext, ScopeId, SuspenseContext, TemplateNode, VNode,
VirtualDom,
consume_context, has_context, try_consume_context, DynamicNode, ErrorContext, Runtime, ScopeId,
SuspenseContext, TemplateNode, VNode, VirtualDom,
};
use dioxus_fullstack_core::{history::provide_fullstack_history_context, HttpError, ServerFnError};
use dioxus_fullstack_core::{HydrationContext, SerializedHydrationData};
@@ -219,11 +219,8 @@ impl SsrRendererPool {
}
// check if there are any errors
let error = virtual_dom.in_runtime(|| {
ScopeId::ROOT_ERROR_BOUNDARY
.consume_context::<ErrorContext>()
.expect("The root should be under an error boundary")
.error()
let error = virtual_dom.in_scope(ScopeId::ROOT_ERROR_BOUNDARY, || {
consume_context::<ErrorContext>().error()
});
if let Some(error) = error {
@@ -497,7 +494,9 @@ impl SsrRendererPool {
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
// 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 {
@@ -525,10 +524,8 @@ impl SsrRendererPool {
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
// and throw it on the client so that it bubbles up to the nearest error boundary
let error = vdom.in_runtime(|| {
scope
.consume_context::<ErrorContext>()
.and_then(|error_context| error_context.error())
let error = vdom.in_scope(scope, || {
try_consume_context::<ErrorContext>().and_then(|error_context| error_context.error())
});
context
.error_entry()
@@ -536,14 +533,12 @@ impl SsrRendererPool {
}
fn take_from_scope(context: &HydrationContext, vdom: &VirtualDom, scope: ScopeId) {
vdom.in_runtime(|| {
scope.in_runtime(|| {
// Grab any serializable server context from this scope
let other: Option<HydrationContext> = has_context();
if let Some(other) = other {
context.extend(&other);
}
});
vdom.in_scope(scope, || {
// Grab any serializable server context from this scope
let other: Option<HydrationContext> = has_context();
if let Some(other) = other {
context.extend(&other);
}
});
// then continue to any children
@@ -659,7 +654,7 @@ impl SsrRendererPool {
let title = {
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
document.and_then(|document| document.title())
};
@@ -672,8 +667,8 @@ impl SsrRendererPool {
}
to.write_str(&index.head_after_title)?;
let document: Option<Rc<ServerDocument>> =
virtual_dom.in_runtime(|| ScopeId::ROOT.consume_context());
let document =
virtual_dom.in_scope(ScopeId::ROOT, try_consume_context::<Rc<ServerDocument>>);
if let Some(document) = document {
// Collect any head content from the document provider and inject that into the head
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
/// 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) {
use_hook(|| {
// 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) => {
// If it is suspended, run the closure after the suspense is 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() {
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 {
query: query.clone(),
});
ScopeId::ROOT.provide_context(provider);
rt.provide_context(ScopeId::ROOT, provider);
let history = LiveviewHistory::new(Rc::new(move |script: &str| {
Eval::new(LiveviewEvaluator::create(query.clone(), script.to_string()))
}));
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.
+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
let (query_tx, mut query_rx) = tokio::sync::mpsc::unbounded_channel();
let query_engine = QueryEngine::new(query_tx);
vdom.runtime().on_scope(ScopeId::ROOT, || {
vdom.runtime().in_scope(ScopeId::ROOT, || {
provide_context(query_engine.clone());
init_document();
});
+5 -5
View File
@@ -1,5 +1,5 @@
use blitz_shell::{BlitzApplication, View};
use dioxus_core::ScopeId;
use dioxus_core::{provide_context, ScopeId};
use dioxus_history::{History, MemoryHistory};
use std::rc::Rc;
use winit::application::ApplicationHandler;
@@ -120,20 +120,20 @@ impl ApplicationHandler<BlitzShellEvent> for DioxusNativeApplication {
let window_id = window.window_id();
let doc = window.downcast_doc_mut::<DioxusDocument>();
doc.vdom.in_runtime(|| {
doc.vdom.in_scope(ScopeId::ROOT, || {
let shared: Rc<dyn dioxus_document::Document> =
Rc::new(DioxusNativeDocument::new(self.proxy.clone(), window_id));
ScopeId::ROOT.provide_context(shared);
provide_context(shared);
});
// Add history
let history_provider: Rc<dyn History> = Rc::new(MemoryHistory::default());
doc.vdom
.in_runtime(move || ScopeId::ROOT.provide_context(history_provider));
.in_scope(ScopeId::ROOT, move || provide_context(history_provider));
// Add renderer
doc.vdom
.in_runtime(move || ScopeId::ROOT.provide_context(renderer));
.in_scope(ScopeId::ROOT, move || provide_context(renderer));
// Queue rebuild
doc.initial_build();
+1 -1
View File
@@ -115,7 +115,7 @@ fn WebSysClosure() -> Element {
// Make sure passing the runtime into the closure works
let callback = Callback::new(|_| {
assert!(!dioxus::dioxus_core::vdom_is_rendering());
assert!(!dioxus::dioxus_core::Runtime::current().vdom_is_rendering());
*TRIGGERED.write() = true;
});
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.
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()
} 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
}
}
pub(crate) fn provide_router_context(ctx: RouterContext) {
if root_router().is_none() {
ScopeId::ROOT.provide_context(RootRouterContext(Signal::new_in_scope(
Some(ctx),
dioxus_core::Runtime::current().provide_context(
ScopeId::ROOT,
)));
RootRouterContext(Signal::new_in_scope(Some(ctx), ScopeId::ROOT)),
);
}
provide_context(ctx);
}
+1 -1
View File
@@ -166,7 +166,7 @@ impl ToTokens for TemplateBody {
#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
// 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::no_effect)]
use dioxus_core::Subscribers;
use dioxus_core::{current_owner, current_scope_id, ScopeId};
use dioxus_core::{Runtime, Subscribers};
use generational_box::{
AnyStorage, BorrowResult, GenerationalBox, GenerationalBoxId, Storage, UnsyncStorage,
};
@@ -82,7 +82,7 @@ impl<T, S: Storage<T>> CopyValue<T, S> {
{
Self {
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 {
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,
caller: &'static std::panic::Location<'static>,
) -> Self {
let owner = scope.owner();
let owner = Runtime::current().scope_owner(scope);
Self {
value: owner.insert_rc_with_caller(value, caller),
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 std::{any::Any, cell::RefCell, collections::HashMap, ops::Deref, panic::Location, rc::Rc};
@@ -190,7 +190,9 @@ where
}
// Otherwise, create it
// 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
.map
.borrow_mut()
@@ -274,9 +276,10 @@ impl GlobalLazyContext {
/// Get the global context for signals
pub fn get_global_context() -> GlobalLazyContext {
match ScopeId::ROOT.has_context() {
let rt = Runtime::current();
match rt.has_context(ScopeId::ROOT) {
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 rc =
ReactiveContext::new_with_callback(callback, current_scope_id().unwrap(), location);
let rc = ReactiveContext::new_with_callback(callback, current_scope_id(), location);
// Create a new signal in that context, wiring up its dependencies and subscribers
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>,
) {
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 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;
}
let create_location = value.value.created_at().unwrap();
+1 -1
View File
@@ -61,7 +61,7 @@ fn reading_subscribes() {
.counter
.borrow_mut()
.children
.entry(current_scope_id().unwrap())
.entry(current_scope_id())
.or_default() += 1;
rsx! {
+3 -4
View File
@@ -1,6 +1,6 @@
use dioxus_core::provide_context;
use dioxus_core::queue_effect;
use dioxus_core::ScopeId;
use dioxus_core::{provide_context, Runtime};
use dioxus_document::{
Document, Eval, EvalError, Evaluator, LinkProps, MetaProps, ScriptProps, StyleProps,
};
@@ -69,8 +69,7 @@ extern "C" {
fn init_document_with(document: impl FnOnce(), history: impl FnOnce()) {
use dioxus_core::has_context;
ScopeId::ROOT.in_runtime(|| {
Runtime::current().in_scope(ScopeId::ROOT, || {
if has_context::<Rc<dyn Document>>().is_none() {
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() {
// If hydrate is enabled, we add the FullstackWebDocument with the initial hydration data
#[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);
// 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() {
dom.in_runtime(|| id.throw_error(error));
dom.in_runtime(|| dom.runtime().throw_error(id, error));
}
server_data.in_context(|| {
// rerun the scope with the new data
+7 -9
View File
@@ -6,7 +6,7 @@
pub use crate::cfg::Config;
use crate::hydration::SuspenseMessage;
use dioxus_core::VirtualDom;
use dioxus_core::{ScopeId, VirtualDom};
use dom::WebsysDom;
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")]
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")]
@@ -129,16 +129,14 @@ pub async fn run(mut virtual_dom: VirtualDom, web_config: Config) -> ! {
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 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(|| {
virtual_dom.in_runtime(|| {
virtual_dom.in_scope(ScopeId::ROOT, || {
// Provide a hydration compatible create error boundary method
dioxus_core::ScopeId::ROOT.in_runtime(|| {
dioxus_core::provide_create_error_boundary(
dioxus_fullstack_core::init_error_boundary,
);
});
dioxus_core::provide_create_error_boundary(
dioxus_fullstack_core::init_error_boundary,
);
#[cfg(feature = "document")]
document::init_fullstack_document();
});