From 3008870818b737913e81109b6a5a0738207bb108 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 16 Jan 2024 17:38:39 -0800 Subject: [PATCH] Cleanup runtime code --- packages/core/src/events.rs | 10 +-- packages/core/src/global_context.rs | 41 ++++++------ packages/core/src/lib.rs | 4 +- packages/core/src/runtime.rs | 100 ++++++++++++---------------- packages/core/src/scope_context.rs | 49 +++++++------- packages/desktop/src/assets.rs | 11 +-- 6 files changed, 97 insertions(+), 118 deletions(-) diff --git a/packages/core/src/events.rs b/packages/core/src/events.rs index 333f4c5e6..879f36118 100644 --- a/packages/core/src/events.rs +++ b/packages/core/src/events.rs @@ -1,4 +1,4 @@ -use crate::{global_context::current_scope_id, runtime::with_runtime, ScopeId}; +use crate::{global_context::current_scope_id, Runtime, ScopeId}; use std::{ cell::{Cell, RefCell}, rc::Rc, @@ -211,13 +211,9 @@ impl EventHandler { /// This borrows the event using a RefCell. Recursively calling a listener will cause a panic. pub fn call(&self, event: T) { if let Some(callback) = self.callback.borrow_mut().as_mut() { - with_runtime(|rt| { - rt.scope_stack.borrow_mut().push(self.origin); - }); + Runtime::with(|rt| rt.scope_stack.borrow_mut().push(self.origin)); callback(event); - with_runtime(|rt| { - rt.scope_stack.borrow_mut().pop(); - }); + Runtime::with(|rt| rt.scope_stack.borrow_mut().pop()); } } diff --git a/packages/core/src/global_context.rs b/packages/core/src/global_context.rs index b26214469..c18b47429 100644 --- a/packages/core/src/global_context.rs +++ b/packages/core/src/global_context.rs @@ -2,37 +2,34 @@ use std::sync::Arc; use futures_util::Future; -use crate::{ - runtime::{with_current_scope, with_runtime}, - Element, ScopeId, Task, -}; +use crate::{runtime::Runtime, Element, ScopeId, Task}; /// Get the current scope id pub fn current_scope_id() -> Option { - with_runtime(|rt| rt.current_scope_id()).flatten() + Runtime::with(|rt| rt.current_scope_id()).flatten() } #[doc(hidden)] /// Check if the virtual dom is currently inside of the body of a component pub fn vdom_is_rendering() -> bool { - with_runtime(|rt| rt.rendering.get()).unwrap_or_default() + Runtime::with(|rt| rt.rendering.get()).unwrap_or_default() } /// Consume context from the current scope pub fn try_consume_context() -> Option { - with_current_scope(|cx| cx.consume_context::()).flatten() + Runtime::with_current_scope(|cx| cx.consume_context::()).flatten() } /// Consume context from the current scope pub fn consume_context() -> T { - with_current_scope(|cx| cx.consume_context::()) + Runtime::with_current_scope(|cx| cx.consume_context::()) .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 { - with_runtime(|rt| { + Runtime::with(|rt| { rt.get_context(scope_id) .and_then(|cx| cx.consume_context::()) }) @@ -41,22 +38,22 @@ pub fn consume_context_from_scope(scope_id: ScopeId) -> Opti /// Check if the current scope has a context pub fn has_context() -> Option { - with_current_scope(|cx| cx.has_context::()).flatten() + Runtime::with_current_scope(|cx| cx.has_context::()).flatten() } /// Provide context to the current scope pub fn provide_context(value: T) -> T { - with_current_scope(|cx| cx.provide_context(value)).expect("to be in a dioxus runtime") + Runtime::with_current_scope(|cx| cx.provide_context(value)).expect("to be in a dioxus runtime") } /// Provide a context to the root scope pub fn provide_root_context(value: T) -> Option { - with_current_scope(|cx| cx.provide_root_context(value)) + Runtime::with_current_scope(|cx| cx.provide_root_context(value)) } /// Suspends the current component pub fn suspend() -> Option { - with_current_scope(|cx| { + Runtime::with_current_scope(|cx| { cx.suspend(); }); None @@ -64,21 +61,21 @@ pub fn suspend() -> Option { /// Spawns the future but does not return the [`TaskId`] pub fn spawn(fut: impl Future + 'static) -> Task { - with_current_scope(|cx| cx.spawn(fut)).expect("to be in a dioxus runtime") + Runtime::with_current_scope(|cx| cx.spawn(fut)).expect("to be in a dioxus runtime") } /// Spawn a future that Dioxus won't clean up when this component is unmounted /// /// This is good for tasks that need to be run after the component has been dropped. pub fn spawn_forever(fut: impl Future + 'static) -> Option { - with_current_scope(|cx| cx.spawn_forever(fut)) + Runtime::with_current_scope(|cx| cx.spawn_forever(fut)) } /// Informs the scheduler that this task is no longer needed and should be removed. /// /// This drops the task immediately. pub fn remove_future(id: Task) { - with_current_scope(|cx| cx.remove_future(id)); + Runtime::with_current_scope(|cx| cx.remove_future(id)); } /// Store a value between renders. The foundational hook for all other hooks. @@ -98,24 +95,24 @@ pub fn remove_future(id: Task) { /// } /// ``` pub fn use_hook(initializer: impl FnOnce() -> State) -> State { - with_current_scope(|cx| cx.use_hook(initializer)).expect("to be in a dioxus runtime") + Runtime::with_current_scope(|cx| cx.use_hook(initializer)).expect("to be in a dioxus runtime") } /// 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 { - with_current_scope(|cx| cx.generation()).expect("to be in a dioxus runtime") + Runtime::with_current_scope(|cx| cx.generation()).expect("to be in a dioxus runtime") } /// Get the parent of the current scope if it exists pub fn parent_scope() -> Option { - with_current_scope(|cx| cx.parent_id()).flatten() + Runtime::with_current_scope(|cx| cx.parent_id()).flatten() } /// Mark the current scope as dirty, causing it to re-render pub fn needs_update() { - with_current_scope(|cx| cx.needs_update()); + Runtime::with_current_scope(|cx| cx.needs_update()); } /// Schedule an update for the current component @@ -124,7 +121,7 @@ pub fn needs_update() { /// /// You should prefer [`schedule_update_any`] if you need to update multiple components. pub fn schedule_update() -> Arc { - with_current_scope(|cx| cx.schedule_update()).expect("to be in a dioxus runtime") + Runtime::with_current_scope(|cx| cx.schedule_update()).expect("to be in a dioxus runtime") } /// Schedule an update for any component given its [`ScopeId`]. @@ -133,5 +130,5 @@ pub fn schedule_update() -> Arc { /// /// Note: Unlike [`needs_update`], the function returned by this method will work outside of the dioxus runtime. pub fn schedule_update_any() -> Arc { - with_current_scope(|cx| cx.schedule_update_any()).expect("to be in a dioxus runtime") + Runtime::with_current_scope(|cx| cx.schedule_update_any()).expect("to be in a dioxus runtime") } diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index d358edb47..244545387 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -78,8 +78,8 @@ pub use crate::innerlude::{ AnyValue, Attribute, AttributeValue, BoxedContext, CapturedError, Component, ComponentFunction, CrossPlatformConfig, DynamicNode, Element, ElementId, Event, Fragment, HasAttributes, IntoDynNode, Mutation, Mutations, NoOpMutations, PlatformBuilder, Properties, RenderReturn, - ScopeId, ScopeState, Task, Template, TemplateAttribute, TemplateNode, VComponent, VNode, - VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations, + Runtime, ScopeId, ScopeState, Task, Template, TemplateAttribute, TemplateNode, VComponent, + VNode, VNodeInner, VPlaceholder, VText, VirtualDom, WriteMutations, }; /// The purpose of this module is to alleviate imports of many common types diff --git a/packages/core/src/runtime.rs b/packages/core/src/runtime.rs index 240e95300..7488f7fe0 100644 --- a/packages/core/src/runtime.rs +++ b/packages/core/src/runtime.rs @@ -14,48 +14,6 @@ thread_local! { static RUNTIMES: RefCell>> = RefCell::new(vec![]); } -/// Pushes a new scope onto the stack -pub(crate) fn push_runtime(runtime: Rc) { - RUNTIMES.with(|stack| stack.borrow_mut().push(runtime)); -} - -/// Pops a scope off the stack -pub(crate) fn pop_runtime() { - RUNTIMES.with(|stack| stack.borrow_mut().pop()); -} - -/// Runs a function with the current runtime -pub(crate) fn with_runtime(f: F) -> Option -where - F: FnOnce(&Runtime) -> R, -{ - RUNTIMES.with(|stack| { - let stack = stack.borrow(); - stack.last().map(|r| f(r)) - }) -} - -/// Runs a function with the current scope -pub(crate) fn with_current_scope(f: F) -> Option -where - F: FnOnce(&ScopeContext) -> R, -{ - with_runtime(|runtime| { - runtime - .current_scope_id() - .and_then(|scope| runtime.get_context(scope).map(|sc| f(&sc))) - }) - .flatten() -} - -/// Runs a function with the current scope -pub(crate) fn with_scope(scope: ScopeId, f: F) -> Option -where - F: FnOnce(&ScopeContext) -> R, -{ - with_runtime(|runtime| runtime.get_context(scope).map(|sc| f(&sc))).flatten() -} - /// A global runtime that is shared across all scopes that provides the async runtime and context API pub struct Runtime { pub(crate) scope_contexts: RefCell>>, @@ -112,7 +70,7 @@ impl Runtime { /// Call this function with the current scope set to the given scope /// /// Useful in a limited number of scenarios, not public. - pub(crate) fn with_scope(&self, id: ScopeId, f: impl FnOnce() -> O) -> O { + pub fn on_scope(&self, id: ScopeId, f: impl FnOnce() -> O) -> O { self.scope_stack.borrow_mut().push(id); let o = f(); self.scope_stack.borrow_mut().pop(); @@ -128,6 +86,47 @@ impl Runtime { }) .ok() } + + /// Pushes a new scope onto the stack + pub(crate) fn push_runtime(runtime: Rc) { + RUNTIMES.with(|stack| stack.borrow_mut().push(runtime)); + } + + /// Pops a scope off the stack + pub(crate) fn pop_runtime() { + RUNTIMES.with(|stack| stack.borrow_mut().pop()); + } + + /// Runs a function with the current runtime + pub(crate) fn with(f: F) -> Option + where + F: FnOnce(&Runtime) -> R, + { + RUNTIMES.with(|stack| { + let stack = stack.borrow(); + stack.last().map(|r| f(r)) + }) + } + + /// Runs a function with the current scope + pub(crate) fn with_current_scope(f: F) -> Option + where + F: FnOnce(&ScopeContext) -> R, + { + Self::with(|rt| { + rt.current_scope_id() + .and_then(|scope| rt.get_context(scope).map(|sc| f(&sc))) + }) + .flatten() + } + + /// Runs a function with the current scope + pub(crate) fn with_scope(scope: ScopeId, f: F) -> Option + where + F: FnOnce(&ScopeContext) -> R, + { + Self::with(|rt| rt.get_context(scope).map(|sc| f(&sc))).flatten() + } } /// 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. @@ -166,24 +165,13 @@ pub struct RuntimeGuard(()); impl RuntimeGuard { /// Create a new runtime guard that sets the current Dioxus runtime. The runtime will be reset when the guard is dropped pub fn new(runtime: Rc) -> Self { - push_runtime(runtime); + Runtime::push_runtime(runtime); Self(()) } - - /// Run a function with a given runtime and scope in context - pub fn with(runtime: Rc, scope: Option, f: impl FnOnce() -> O) -> O { - let guard = Self::new(runtime.clone()); - let o = match scope { - Some(scope) => Runtime::with_scope(&runtime, scope, f), - None => f(), - }; - drop(guard); - o - } } impl Drop for RuntimeGuard { fn drop(&mut self) { - pop_runtime(); + Runtime::pop_runtime(); } } diff --git a/packages/core/src/scope_context.rs b/packages/core/src/scope_context.rs index 1e79b1d4d..955258b6b 100644 --- a/packages/core/src/scope_context.rs +++ b/packages/core/src/scope_context.rs @@ -1,8 +1,4 @@ -use crate::{ - innerlude::SchedulerMsg, - runtime::{with_runtime, with_scope}, - Element, ScopeId, Task, -}; +use crate::{innerlude::SchedulerMsg, Element, Runtime, ScopeId, Task}; use rustc_hash::FxHashSet; use std::{ any::Any, @@ -53,7 +49,7 @@ impl ScopeContext { } fn sender(&self) -> futures_channel::mpsc::UnboundedSender { - with_runtime(|rt| rt.sender.clone()).unwrap() + Runtime::with(|rt| rt.sender.clone()).unwrap() } /// Mark this scope as dirty, and schedule a render for it. @@ -107,7 +103,7 @@ impl ScopeContext { } let mut search_parent = self.parent_id; - let cur_runtime = with_runtime(|runtime: &crate::runtime::Runtime| { + let cur_runtime = Runtime::with(|runtime| { while let Some(parent_id) = search_parent { let parent = runtime.get_context(parent_id).unwrap(); tracing::trace!( @@ -210,7 +206,7 @@ impl ScopeContext { /// 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 { - with_runtime(|runtime| { + Runtime::with(|runtime| { runtime .get_context(ScopeId::ROOT) .unwrap() @@ -221,7 +217,7 @@ impl ScopeContext { /// Spawns the future but does not return the [`TaskId`] pub fn spawn(&self, fut: impl Future + 'static) -> Task { - let id = with_runtime(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist"); + let id = Runtime::with(|rt| rt.spawn(self.id, fut)).expect("Runtime to exist"); self.spawned_tasks.borrow_mut().insert(id); id } @@ -231,14 +227,14 @@ impl ScopeContext { /// This is good for tasks that need to be run after the component has been dropped. pub fn spawn_forever(&self, fut: impl Future + 'static) -> Task { // The root scope will never be unmounted so we can just add the task at the top of the app - with_runtime(|rt| rt.spawn(ScopeId::ROOT, fut)).expect("Runtime to exist") + Runtime::with(|rt| rt.spawn(ScopeId::ROOT, fut)).expect("Runtime to exist") } /// Informs the scheduler that this task is no longer needed and should be removed. /// /// This drops the task immediately. pub fn remove_future(&self, id: Task) { - with_runtime(|rt| rt.remove_task(id)).expect("Runtime to exist"); + Runtime::with(|rt| rt.remove_task(id)).expect("Runtime to exist"); } /// Mark this component as suspended and then return None @@ -303,7 +299,7 @@ impl ScopeContext { impl Drop for ScopeContext { fn drop(&mut self) { // Drop all spawned tasks - _ = with_runtime(|rt| { + _ = Runtime::with(|rt| { for id in self.spawned_tasks.borrow().iter() { rt.remove_task(*id); } @@ -314,23 +310,23 @@ impl Drop for ScopeContext { impl ScopeId { /// Get the current scope id pub fn current_scope_id(self) -> Option { - with_runtime(|rt| rt.current_scope_id()).flatten() + Runtime::with(|rt| rt.current_scope_id()).flatten() } #[doc(hidden)] /// Check if the virtual dom is currently inside of the body of a component pub fn vdom_is_rendering(self) -> bool { - with_runtime(|rt| rt.rendering.get()).unwrap_or_default() + Runtime::with(|rt| rt.rendering.get()).unwrap_or_default() } /// Consume context from the current scope pub fn consume_context(self) -> Option { - with_scope(self, |cx| cx.consume_context::()).flatten() + Runtime::with_scope(self, |cx| cx.consume_context::()).flatten() } /// Consume context from the current scope pub fn consume_context_from_scope(self, scope_id: ScopeId) -> Option { - with_runtime(|rt| { + Runtime::with(|rt| { rt.get_context(scope_id) .and_then(|cx| cx.consume_context::()) }) @@ -339,17 +335,18 @@ impl ScopeId { /// Check if the current scope has a context pub fn has_context(self) -> Option { - with_scope(self, |cx| cx.has_context::()).flatten() + Runtime::with_scope(self, |cx| cx.has_context::()).flatten() } /// Provide context to the current scope pub fn provide_context(self, value: T) -> T { - with_scope(self, |cx| cx.provide_context(value)).expect("to be in a dioxus runtime") + Runtime::with_scope(self, |cx| cx.provide_context(value)) + .expect("to be in a dioxus runtime") } /// Suspends the current component pub fn suspend(self) -> Option { - with_scope(self, |cx| { + Runtime::with_scope(self, |cx| { cx.suspend(); }); None @@ -357,40 +354,40 @@ impl ScopeId { /// Pushes the future onto the poll queue to be polled after the component renders. pub fn push_future(self, fut: impl Future + 'static) -> Option { - with_scope(self, |cx| cx.spawn(fut)) + Runtime::with_scope(self, |cx| cx.spawn(fut)) } /// Spawns the future but does not return the [`TaskId`] pub fn spawn(self, fut: impl Future + 'static) { - with_scope(self, |cx| cx.spawn(fut)); + Runtime::with_scope(self, |cx| cx.spawn(fut)); } /// 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 { - with_scope(self, |cx| Some(cx.generation())).expect("to be in a dioxus runtime") + Runtime::with_scope(self, |cx| Some(cx.generation())).expect("to be in a dioxus runtime") } /// Get the parent of the current scope if it exists pub fn parent_scope(self) -> Option { - with_scope(self, |cx| cx.parent_id()).flatten() + Runtime::with_scope(self, |cx| cx.parent_id()).flatten() } /// Mark the current scope as dirty, causing it to re-render pub fn needs_update(self) { - with_scope(self, |cx| cx.needs_update()); + Runtime::with_scope(self, |cx| cx.needs_update()); } /// 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 [`dioxus_core::schedule_update_any`] and [`Self::scope_id`] pub fn schedule_update(&self) -> Arc { - with_scope(*self, |cx| cx.schedule_update()).expect("to be in a dioxus runtime") + Runtime::with_scope(*self, |cx| cx.schedule_update()).expect("to be in a dioxus runtime") } /// Get the height of the current scope pub fn height(self) -> u32 { - with_scope(self, |cx| cx.height()).expect("to be in a dioxus runtime") + Runtime::with_scope(self, |cx| cx.height()).expect("to be in a dioxus runtime") } } diff --git a/packages/desktop/src/assets.rs b/packages/desktop/src/assets.rs index f0f5a03fc..fa26c06ef 100644 --- a/packages/desktop/src/assets.rs +++ b/packages/desktop/src/assets.rs @@ -36,11 +36,12 @@ impl AssetHandlerRegistry { responder: RequestAsyncResponder, ) { if let Some(handler) = self.handlers.borrow().get(name) { - // make sure the runtime is alive for the duration of the handler - // We should do this for all the things - not just asset handlers - RuntimeGuard::with(self.dom_rt.clone(), Some(handler.scope), || { - (handler.f)(request, responder) - }); + // Push the runtime onto the stack + let _guard = RuntimeGuard::new(self.dom_rt.clone()); + + // And run the handler in the scope of the component that created it + self.dom_rt + .on_scope(handler.scope, || (handler.f)(request, responder)); } }