use crate::{ innerlude::{LocalTask, SchedulerMsg}, render_signal::RenderSignal, scope_context::Scope, scopes::ScopeId, Task, }; use std::{ cell::{Cell, Ref, RefCell}, rc::Rc, }; thread_local! { static RUNTIMES: RefCell>> = const { RefCell::new(vec![]) }; } /// 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 pub(crate) scope_stack: RefCell>, // We use this to track the current task pub(crate) current_task: Cell>, /// Tasks created with cx.spawn pub(crate) tasks: RefCell>>, // Currently suspended tasks pub(crate) suspended_tasks: Cell, pub(crate) rendering: Cell, pub(crate) sender: futures_channel::mpsc::UnboundedSender, // Synchronous tasks need to be run after the next render. The virtual dom stores a list of those tasks to send a signal to them when the next render is done. pub(crate) render_signal: RenderSignal, } impl Runtime { pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender) -> Rc { Rc::new(Self { sender, render_signal: RenderSignal::default(), rendering: Cell::new(true), scope_states: Default::default(), scope_stack: Default::default(), current_task: Default::default(), tasks: Default::default(), suspended_tasks: Default::default(), }) } /// Get the current runtime pub fn current() -> Option> { RUNTIMES.with(|stack| stack.borrow().last().cloned()) } /// Create a scope context. This slab is synchronized with the scope slab. pub(crate) fn create_scope(&self, context: Scope) { let id = context.id; let mut scopes = self.scope_states.borrow_mut(); if scopes.len() <= id.0 { scopes.resize_with(id.0 + 1, Default::default); } scopes[id.0] = Some(context); } pub(crate) fn remove_scope(self: &Rc, id: ScopeId) { { let borrow = self.scope_states.borrow(); if let Some(scope) = &borrow[id.0] { let _runtime_guard = RuntimeGuard::new(self.clone()); // Manually drop tasks, hooks, and contexts inside of the runtime self.on_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() { self.remove_task(id); } // Drop all hooks in reverse order in case a hook depends on another hook. for hook in scope.hooks.take().drain(..).rev() { drop(hook); } // Drop all contexts scope.shared_contexts.take(); }); } } self.scope_states.borrow_mut()[id.0].take(); } /// Get the current scope id pub(crate) fn 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, id: ScopeId, f: impl FnOnce() -> O) -> O { { self.scope_stack.borrow_mut().push(id); } let o = f(); { self.scope_stack.borrow_mut().pop(); } o } /// Get the state for any scope given its ID /// /// This is useful for inserting or removing contexts from a scope, or rendering out its root node pub(crate) fn get_state(&self, id: ScopeId) -> Option> { Ref::filter_map(self.scope_states.borrow(), |contexts| { contexts.get(id.0).and_then(|f| f.as_ref()) }) .ok() } /// Pushes a new scope onto the stack pub(crate) fn push(runtime: Rc) { RUNTIMES.with(|stack| stack.borrow_mut().push(runtime)); } /// Pops a scope off the stack pub(crate) fn pop() { RUNTIMES.with(|stack| stack.borrow_mut().pop()); } /// Runs a function with the current runtime pub(crate) fn with(f: impl FnOnce(&Runtime) -> R) -> Option { RUNTIMES.with(|stack| stack.borrow().last().map(|r| f(r))) } /// Runs a function with the current scope pub(crate) fn with_current_scope(f: impl FnOnce(&Scope) -> R) -> Option { Self::with(|rt| { rt.current_scope_id() .and_then(|scope| rt.get_state(scope).map(|sc| f(&sc))) }) .flatten() } /// Runs a function with the current scope pub(crate) fn with_scope(scope: ScopeId, f: impl FnOnce(&Scope) -> R) -> Option { Self::with(|rt| rt.get_state(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. /// /// ```rust /// use dioxus::prelude::*; /// /// fn main() { /// let virtual_dom = VirtualDom::new(app); /// } /// /// fn app() -> Element { /// rsx!{ Component { runtime: Runtime::current().unwrap() } } /// } /// /// // In a dynamic library /// #[derive(Props, Clone)] /// struct ComponentProps { /// runtime: std::rc::Rc, /// } /// /// impl PartialEq for ComponentProps { /// fn eq(&self, _other: &Self) -> bool { /// true /// } /// } /// /// fn Component(cx: ComponentProps) -> Element { /// use_hook(|| { /// let _guard = RuntimeGuard::new(cx.runtime.clone()); /// }); /// /// rsx! { div {} } /// } /// ``` 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 { Runtime::push(runtime); Self(()) } } impl Drop for RuntimeGuard { fn drop(&mut self) { Runtime::pop(); } }