From c29cef4ea31ff1b7e4d22f228a324a974b51a8b4 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Sat, 4 Oct 2025 01:55:52 -0700 Subject: [PATCH] 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 --- packages/core/src/diff/component.rs | 6 +- packages/core/src/error_boundary.rs | 2 +- packages/core/src/events.rs | 19 +- packages/core/src/generational_box.rs | 16 +- packages/core/src/global_context.rs | 94 ++---- packages/core/src/lib.rs | 27 +- packages/core/src/reactive_context.rs | 8 +- packages/core/src/runtime.rs | 295 ++++++++++------ packages/core/src/scope_arena.rs | 27 +- packages/core/src/scope_context.rs | 316 ++---------------- packages/core/src/scopes.rs | 80 ++--- packages/core/src/suspense/component.rs | 2 +- packages/core/src/suspense/mod.rs | 13 +- packages/core/src/tasks.rs | 16 +- packages/core/src/virtual_dom.rs | 10 +- packages/core/tests/context_api.rs | 6 +- packages/core/tests/safety.rs | 2 +- packages/core/tests/suspense.rs | 2 +- packages/desktop/src/app.rs | 16 +- packages/desktop/src/hooks.rs | 6 +- packages/desktop/src/launch.rs | 26 +- packages/desktop/src/webview.rs | 18 +- packages/devtools/src/lib.rs | 4 +- packages/document/src/elements/mod.rs | 9 +- packages/fullstack-server/src/ssr.rs | 41 +-- .../hooks/src/use_after_suspense_resolved.rs | 4 +- packages/liveview/src/document.rs | 9 +- packages/liveview/src/pool.rs | 2 +- packages/native/src/dioxus_application.rs | 10 +- packages/playwright-tests/web/src/main.rs | 2 +- packages/router/src/contexts/router.rs | 15 +- packages/rsx/src/template_body.rs | 2 +- packages/signals/src/copy_value.rs | 9 +- packages/signals/src/global/mod.rs | 11 +- packages/signals/src/memo.rs | 3 +- packages/signals/src/warnings.rs | 8 +- packages/signals/tests/subscribe.rs | 2 +- packages/web/src/document.rs | 7 +- packages/web/src/hydration/hydrate.rs | 2 +- packages/web/src/lib.rs | 16 +- 40 files changed, 466 insertions(+), 697 deletions(-) diff --git a/packages/core/src/diff/component.rs b/packages/core/src/diff/component.rs index 77b494084..883d77ec7 100644 --- a/packages/core/src/diff/component.rs +++ b/packages/core/src/diff/component.rs @@ -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)); } diff --git a/packages/core/src/error_boundary.rs b/packages/core/src/error_boundary.rs index fddc59eaf..ea1203ac8 100644 --- a/packages/core/src/error_boundary.rs +++ b/packages/core/src/error_boundary.rs @@ -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::().unwrap_or_default(); (create_error_boundary.0)() } diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 93f64481f..f49cb8f93 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -487,10 +487,8 @@ impl Callback { pub fn new, 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::(); let callback = owner.insert_rc(Some(ExternalListenerCallback { callback: Box::new(move |event: Args| f(event).spawn()), @@ -502,10 +500,8 @@ impl Callback { /// 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 Callback { /// Replace the function in the callback with a new one pub fn replace(&mut self, callback: Box 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 ListenerCallback { MaybeAsync: SpawnIfAsync, { 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| { let data = event.data.downcast::().unwrap(); f(Event { @@ -665,8 +661,7 @@ impl ListenerCallback { /// 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) { - 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); }); } diff --git a/packages/core/src/generational_box.rs b/packages/core/src/generational_box.rs index e9a5c5fb2..b9bd54002 100644 --- a/packages/core/src/generational_box.rs +++ b/packages/core/src/generational_box.rs @@ -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() -> Owner { 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(self) -> Owner { - Runtime::with_scope(self, |cx| cx.owner::()).unwrap_or_else(|e| panic!("{}", e)) - } + crate::Runtime::current().current_owner() } diff --git a/packages/core/src/global_context.rs b/packages/core/src/global_context.rs index eb6335c72..9922fa8ed 100644 --- a/packages/core/src/global_context.rs +++ b/packages/core/src/global_context.rs @@ -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 { - 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 + '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 { - 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() -> Option { Runtime::with_current_scope(|cx| cx.consume_context::()) - .ok() - .flatten() } /// Consume context from the current scope pub fn consume_context() -> T { Runtime::with_current_scope(|cx| cx.consume_context::()) - .ok() - .flatten() .unwrap_or_else(|| panic!("Could not find context {}", std::any::type_name::())) } /// Consume context from the current scope pub fn consume_context_from_scope(scope_id: ScopeId) -> Option { - Runtime::with(|rt| { - rt.get_state(scope_id) - .and_then(|cx| cx.consume_context::()) - }) - .ok() - .flatten() + Runtime::current() + .try_get_state(scope_id) + .and_then(|cx| cx.consume_context::()) } /// Check if the current scope has a context pub fn has_context() -> Option { Runtime::with_current_scope(|cx| cx.has_context::()) - .ok() - .flatten() } /// Provide context to the current scope pub fn provide_context(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(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 + '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 + 'static) -> Task { /// #[doc = include_str!("../docs/common_spawn_errors.md")] pub fn spawn(fut: impl Future + '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 + 'static) -> Option { - Runtime::with_scope(ScopeId::ROOT, |cx| cx.spawn(fut)).ok() +pub fn spawn_forever(fut: impl Future + '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(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 { 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 { - 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 { /// 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 { - 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( 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"); -} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index 8d3bd0176..862d48854 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -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; diff --git a/packages/core/src/reactive_context.rs b/packages/core/src/reactive_context.rs index db4a57504..6774ff757 100644 --- a/packages/core/src/reactive_context.rs +++ b/packages/core/src/reactive_context.rs @@ -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, diff --git a/packages/core/src/runtime.rs b/packages/core/src/runtime.rs index da613ccb9..42fdc54d4 100644 --- a/packages/core/src/runtime.rs +++ b/packages/core/src/runtime.rs @@ -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>>, - // 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>, @@ -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>, + // A hand-rolled slab of scope states + pub(crate) scope_states: RefCell>>, + // We use this to track the current task pub(crate) current_task: Cell>, @@ -89,18 +93,55 @@ impl Runtime { } /// Get the current runtime - pub fn current() -> Result, RuntimeError> { + pub fn current() -> Rc { 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 = 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> { + 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(&self) -> Owner { + self.get_state(self.current_scope_id()).owner() + } + + /// Get the owner for the current scope. + #[track_caller] + pub fn scope_owner(&self, scope: ScopeId) -> Owner { + self.get_state(scope).owner() + } + /// Get the current scope id - pub(crate) fn current_scope_id(&self) -> Result { - 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 { + 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(self: &Rc, id: ScopeId, f: impl FnOnce() -> O) -> O { + #[track_caller] + pub fn in_scope(self: &Rc, 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> { + 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::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(f: impl FnOnce(&Runtime) -> R) -> Result { - Self::current().map(|r| f(&r)) + pub(crate) fn with(callback: impl FnOnce(&Runtime) -> R) -> R { + callback(&Self::current()) } /// Runs a function with the current scope - pub(crate) fn with_current_scope(f: impl FnOnce(&Scope) -> R) -> Result { - 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(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( - scope: ScopeId, - callback: impl FnOnce(&Scope) -> R, - ) -> Result { - Self::with(|rt| rt.get_state(scope).map(|scopestate| callback(&scopestate))) - .expect("Runtime should exist") - .ok_or(RuntimeError::new()) + pub(crate) fn with_scope(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(&self, id: ScopeId) -> Option { + self.get_state(id).consume_context::() + } + + /// Consume context from the current scope + pub fn consume_context_from_scope(&self, scope_id: ScopeId) -> Option { + self.get_state(scope_id).consume_context::() + } + + /// Check if the current scope has a context + pub fn has_context(&self, id: ScopeId) -> Option { + self.get_state(id).has_context::() + } + + /// Provide context to the current scope + pub fn provide_context(&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 { + 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 + 'static) { + let error = error.into(); + if let Some(cx) = self.consume_context::(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 { + 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 = 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 {} diff --git a/packages/core/src/scope_arena.rs b/packages/core/src/scope_arena.rs index 02daa6b6b..981817f0f 100644 --- a/packages/core/src/scope_arena.rs +++ b/packages/core/src/scope_arena.rs @@ -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); } } } diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index a41eee54a..6c90e955e 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -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 { - 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 { + pub(crate) fn schedule_update(&self) -> Arc { 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 { + pub(crate) fn schedule_update_any(&self) -> Arc { 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(&self) -> Owner { + pub(crate) fn owner(&self) -> Owner { 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(&self) -> Option { + pub(crate) fn has_context(&self) -> Option { 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(&self) -> Option { - tracing::trace!( - "looking for context {} ({:?}) in {:?}", - std::any::type_name::(), - std::any::TypeId::of::(), - self.id - ); - if let Some(this_ctx) = self.has_context() { + pub(crate) fn consume_context(&self) -> Option { + if let Some(this_ctx) = self.has_context::() { 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::(), - std::any::TypeId::of::(), - parent.id - ); - if let Some(shared) = parent.has_context() { + let parent = runtime.try_get_state(parent_id)?; + if let Some(shared) = parent.has_context::() { 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::(), - std::any::TypeId::of::() - ); - None - } - } + }) } /// Inject a `Box` into the context of this scope @@ -281,13 +254,7 @@ impl Scope { /// rsx!(div { "hello {state.0}" }) /// } /// ``` - pub fn provide_context(&self, value: T) -> T { - tracing::trace!( - "providing context {} ({:?}) in {:?}", - std::any::type_name::(), - std::any::TypeId::of::(), - self.id - ); + pub(crate) fn provide_context(&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(&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(&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 + '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 + '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 + 'static) -> Task { - let id = Runtime::with(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist"); + pub(crate) fn spawn(&self, fut: impl Future + '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. - /// - ///
- /// - /// `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>` 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}" - /// } - /// } - /// } - /// ``` - /// - ///
- /// - /// # 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 - /// } - /// - /// pub fn use_custom_state() -> CustomState { - /// use_hook(|| CustomState { - /// inner: Signal::new(InnerCustomState(0)) - /// }) - /// } - /// ``` - pub fn use_hook(&self, initializer: impl FnOnce() -> State) -> State { + /// Store a value in the hook list, returning the value. + pub(crate) fn use_hook( + &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 { - Runtime::with(|rt| rt.current_scope_id().ok()) - .ok() - .flatten() - .ok_or(RuntimeError::new()) - } - - /// Consume context from the current scope - pub fn consume_context(self) -> Option { - Runtime::with_scope(self, |cx| cx.consume_context::()).expect("Runtime to exist") - } - - /// Consume context from the current scope - pub fn consume_context_from_scope(self, scope_id: ScopeId) -> Option { - Runtime::with(|rt| { - rt.get_state(scope_id) - .and_then(|cx| cx.consume_context::()) - }) - .ok() - .flatten() - } - - /// Check if the current scope has a context - pub fn has_context(self) -> Option { - Runtime::with_scope(self, |cx| cx.has_context::()) - .ok() - .flatten() - } - - /// Provide context to the current scope - pub fn provide_context(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 + 'static) -> Option { - 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 + '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 { - 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 { - 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 { - 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(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 + 'static) { - let error = error.into(); - if let Some(cx) = self.consume_context::() { - 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 { - Runtime::with_scope(*self, |cx| cx.suspense_boundary.suspense_context().cloned()).unwrap() - } -} diff --git a/packages/core/src/scopes.rs b/packages/core/src/scopes.rs index ea45252d0..45b331c36 100644 --- a/packages/core/src/scopes.rs +++ b/packages/core/src/scopes.rs @@ -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() - } -} diff --git a/packages/core/src/suspense/component.rs b/packages/core/src/suspense/component.rs index 5fe3ba1af..8baa3b1b0 100644 --- a/packages/core/src/suspense/component.rs +++ b/packages/core/src/suspense/component.rs @@ -607,7 +607,7 @@ impl SuspenseContext { scope_id: ScopeId, ) -> Option { runtime - .get_state(scope_id) + .try_get_state(scope_id) .and_then(|scope| scope.suspense_boundary()) } diff --git a/packages/core/src/suspense/mod.rs b/packages/core/src/suspense/mod.rs index da2d48174..45b02ce0c 100644 --- a/packages/core/src/suspense/mod.rs +++ b/packages/core/src/suspense/mod.rs @@ -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, + suspended_tasks: RefCell>, + id: Cell, + /// The nodes that are suspended under this boundary suspended_nodes: RefCell>, + /// 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, + /// Closures queued to run after the suspense boundary is resolved after_suspense_resolved: RefCell>>, } diff --git a/packages/core/src/tasks.rs b/packages/core/src/tasks.rs index 70ce6c3e6..ef8685bab 100644 --- a/packages/core/src/tasks.rs +++ b/packages/core/src/tasks.rs @@ -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; - 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); diff --git a/packages/core/src/virtual_dom.rs b/packages/core/src/virtual_dom.rs index 7a64e1265..f1f47d7fb 100644 --- a/packages/core/src/virtual_dom.rs +++ b/packages/core/src/virtual_dom.rs @@ -353,7 +353,7 @@ impl VirtualDom { /// Run a closure inside a specific scope pub fn in_scope(&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) } diff --git a/packages/core/tests/context_api.rs b/packages/core/tests/context_api.rs index e00dfe2e5..f35d474db 100644 --- a/packages/core/tests/context_api.rs +++ b/packages/core/tests/context_api.rs @@ -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::().unwrap(), 1); + assert_eq!(consume_context_from_scope::(ScopeId::APP).unwrap(), 1); }); dom.mark_dirty(ScopeId::APP); _ = dom.render_immediate_to_vec(); dom.in_runtime(|| { - assert_eq!(ScopeId::APP.consume_context::().unwrap(), 2); + assert_eq!(consume_context_from_scope::(ScopeId::APP).unwrap(), 2); }); dom.mark_dirty(ScopeId(ScopeId::APP.0 + 2)); diff --git a/packages/core/tests/safety.rs b/packages/core/tests/safety.rs index d6598cad5..33f357f16 100644 --- a/packages/core/tests/safety.rs +++ b/packages/core/tests/safety.rs @@ -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); }); } diff --git a/packages/core/tests/suspense.rs b/packages/core/tests/suspense.rs index ecf3035df..1e2dce058 100644 --- a/packages/core/tests/suspense.rs +++ b/packages/core/tests/suspense.rs @@ -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); diff --git a/packages/desktop/src/app.rs b/packages/desktop/src/app.rs index 95848853f..dbe772f72 100644 --- a/packages/desktop/src/app.rs +++ b/packages/desktop/src/app.rs @@ -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::>() - .unwrap() - .current_route() + let url = webview.dom.in_scope(ScopeId::ROOT, || { + consume_context::>().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::>() - .unwrap() - .replace(url); + consume_context::>().replace(url); } }) } diff --git a/packages/desktop/src/hooks.rs b/packages/desktop/src/hooks.rs index c6fd33ad6..c641b0131 100644 --- a/packages/desktop/src/hooks.rs +++ b/packages/desktop/src/hooks.rs @@ -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(), diff --git a/packages/desktop/src/launch.rs b/packages/desktop/src/launch.rs index 550b0af25..59dbda9a7 100644 --- a/packages/desktop/src/launch.rs +++ b/packages/desktop/src/launch.rs @@ -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); }); } } diff --git a/packages/desktop/src/webview.rs b/packages/desktop/src/webview.rs index fdd0c33c3..456e217d5 100644 --- a/packages/desktop/src/webview.rs +++ b/packages/desktop/src/webview.rs @@ -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 = Rc::new(DesktopDocument::new(desktop_context.clone())); let history_provider: Rc = 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) -> WebviewInstance { let window = WebviewInstance::new(self.cfg, self.dom, shared.clone()); - let cx = window.dom.in_runtime(|| { - ScopeId::ROOT - .consume_context::>() - .unwrap() - }); + let cx = window + .dom + .in_scope(ScopeId::ROOT, consume_context::>); _ = self.sender.send(cx); window diff --git a/packages/devtools/src/lib.rs b/packages/devtools/src/lib.rs index 7e2e106c6..aae88fc4b 100644 --- a/packages/devtools/src/lib.rs +++ b/packages/devtools/src/lib.rs @@ -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::>>(); } } diff --git a/packages/document/src/elements/mod.rs b/packages/document/src/elements/mod.rs index a78c011dc..da3990686 100644 --- a/packages/document/src/elements/mod.rs +++ b/packages/document/src/elements/mod.rs @@ -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() -> T { - match ScopeId::ROOT.has_context::() { + let rt = Runtime::current(); + match rt.has_context::(ScopeId::ROOT) { Some(context) => context, None => { let context = T::default(); - ScopeId::ROOT.provide_context(context.clone()); + rt.provide_context(ScopeId::ROOT, context.clone()); context } } diff --git a/packages/fullstack-server/src/ssr.rs b/packages/fullstack-server/src/ssr.rs index 663d34bd3..3aabb791b 100644 --- a/packages/fullstack-server/src/ssr.rs +++ b/packages/fullstack-server/src/ssr.rs @@ -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::() - .expect("The root should be under an error boundary") - .error() + let error = virtual_dom.in_scope(ScopeId::ROOT_ERROR_BOUNDARY, || { + consume_context::().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::() - .and_then(|error_context| error_context.error()) + let error = vdom.in_scope(scope, || { + try_consume_context::().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 = 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 = 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> = - 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> = - virtual_dom.in_runtime(|| ScopeId::ROOT.consume_context()); + let document = + virtual_dom.in_scope(ScopeId::ROOT, try_consume_context::>); if let Some(document) = document { // Collect any head content from the document provider and inject that into the head document.render(to)?; diff --git a/packages/hooks/src/use_after_suspense_resolved.rs b/packages/hooks/src/use_after_suspense_resolved.rs index bc1551b77..df23e575e 100644 --- a/packages/hooks/src/use_after_suspense_resolved.rs +++ b/packages/hooks/src/use_after_suspense_resolved.rs @@ -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) diff --git a/packages/liveview/src/document.rs b/packages/liveview/src/document.rs index 1d4a84d3b..7b4ac7ccf 100644 --- a/packages/liveview/src/document.rs +++ b/packages/liveview/src/document.rs @@ -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::().unwrap(); + let rt = dioxus_core::Runtime::current(); + let query = rt.consume_context::(ScopeId::ROOT).unwrap(); let provider: Rc = 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 = Rc::new(history); - ScopeId::ROOT.provide_context(history); + rt.provide_context(ScopeId::ROOT, history); } /// Reprints the liveview-target's provider of evaluators. diff --git a/packages/liveview/src/pool.rs b/packages/liveview/src/pool.rs index 72a840a95..afedced14 100644 --- a/packages/liveview/src/pool.rs +++ b/packages/liveview/src/pool.rs @@ -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(); }); diff --git a/packages/native/src/dioxus_application.rs b/packages/native/src/dioxus_application.rs index 1b713d81a..bbb8739d0 100644 --- a/packages/native/src/dioxus_application.rs +++ b/packages/native/src/dioxus_application.rs @@ -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 for DioxusNativeApplication { let window_id = window.window_id(); let doc = window.downcast_doc_mut::(); - doc.vdom.in_runtime(|| { + doc.vdom.in_scope(ScopeId::ROOT, || { let shared: Rc = Rc::new(DioxusNativeDocument::new(self.proxy.clone(), window_id)); - ScopeId::ROOT.provide_context(shared); + provide_context(shared); }); // Add history let history_provider: Rc = 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(); diff --git a/packages/playwright-tests/web/src/main.rs b/packages/playwright-tests/web/src/main.rs index 292c06d3c..600c6f755 100644 --- a/packages/playwright-tests/web/src/main.rs +++ b/packages/playwright-tests/web/src/main.rs @@ -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 = Closure::new({ diff --git a/packages/router/src/contexts/router.rs b/packages/router/src/contexts/router.rs index 87451041f..81584fa96 100644 --- a/packages/router/src/contexts/router.rs +++ b/packages/router/src/contexts/router.rs @@ -35,20 +35,25 @@ struct RootRouterContext(Signal>); /// /// This will return `None` if there is no router present or the router has not been created yet. pub fn root_router() -> Option { - if let Some(ctx) = ScopeId::ROOT.consume_context::() { + let rt = dioxus_core::Runtime::current(); + + if let Some(ctx) = rt.consume_context::(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); } diff --git a/packages/rsx/src/template_body.rs b/packages/rsx/src/template_body.rs index 645cd2e57..f17dff906 100644 --- a/packages/rsx/src/template_body.rs +++ b/packages/rsx/src/template_body.rs @@ -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 diff --git a/packages/signals/src/copy_value.rs b/packages/signals/src/copy_value.rs index 4abca6d07..cac7d439e 100644 --- a/packages/signals/src/copy_value.rs +++ b/packages/signals/src/copy_value.rs @@ -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> CopyValue { { 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> CopyValue { 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> CopyValue { 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, diff --git a/packages/signals/src/global/mod.rs b/packages/signals/src/global/mod.rs index 8b66a90a1..b6fdb4a87 100644 --- a/packages/signals/src/global/mod.rs +++ b/packages/signals/src/global/mod.rs @@ -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()), } } diff --git a/packages/signals/src/memo.rs b/packages/signals/src/memo.rs index 9cdf71ec9..8f2c588e8 100644 --- a/packages/signals/src/memo.rs +++ b/packages/signals/src/memo.rs @@ -56,8 +56,7 @@ impl Memo { 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); diff --git a/packages/signals/src/warnings.rs b/packages/signals/src/warnings.rs index f6bbe6b0a..08b83205d 100644 --- a/packages/signals/src/warnings.rs +++ b/packages/signals/src/warnings.rs @@ -9,9 +9,13 @@ pub fn copy_value_hoisted + '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(); diff --git a/packages/signals/tests/subscribe.rs b/packages/signals/tests/subscribe.rs index 48f5b3828..ec06c142a 100644 --- a/packages/signals/tests/subscribe.rs +++ b/packages/signals/tests/subscribe.rs @@ -61,7 +61,7 @@ fn reading_subscribes() { .counter .borrow_mut() .children - .entry(current_scope_id().unwrap()) + .entry(current_scope_id()) .or_default() += 1; rsx! { diff --git a/packages/web/src/document.rs b/packages/web/src/document.rs index b46a62093..15bec8914 100644 --- a/packages/web/src/document.rs +++ b/packages/web/src/document.rs @@ -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::>().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"))] diff --git a/packages/web/src/hydration/hydrate.rs b/packages/web/src/hydration/hydrate.rs index f06ac1e87..35b4ad1a6 100644 --- a/packages/web/src/hydration/hydrate.rs +++ b/packages/web/src/hydration/hydrate.rs @@ -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 diff --git a/packages/web/src/lib.rs b/packages/web/src/lib.rs index 79ec91e72..2a4146455 100644 --- a/packages/web/src/lib.rs +++ b/packages/web/src/lib.rs @@ -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(); });