use crate::innerlude::{DirtyTasks, Effect};
use crate::scope_context::SuspenseLocation;
use crate::{
innerlude::{LocalTask, SchedulerMsg},
scope_context::Scope,
scopes::ScopeId,
Task,
};
use slotmap::DefaultKey;
use std::collections::BTreeSet;
use std::fmt;
use std::{
cell::{Cell, Ref, RefCell},
rc::Rc,
};
thread_local! {
static RUNTIMES: RefCell<Vec<Rc<Runtime>>> = const { RefCell::new(vec![]) };
}
pub struct Runtime {
pub(crate) scope_states: RefCell<Vec<Option<Scope>>>,
scope_stack: RefCell<Vec<ScopeId>>,
suspense_stack: RefCell<Vec<SuspenseLocation>>,
pub(crate) current_task: Cell<Option<Task>>,
pub(crate) tasks: RefCell<slotmap::SlotMap<DefaultKey, Rc<LocalTask>>>,
pub(crate) suspended_tasks: Cell<usize>,
pub(crate) rendering: Cell<bool>,
pub(crate) sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>,
pub(crate) pending_effects: RefCell<BTreeSet<Effect>>,
pub(crate) dirty_tasks: RefCell<BTreeSet<DirtyTasks>>,
}
impl Runtime {
pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
Rc::new(Self {
sender,
rendering: Cell::new(true),
scope_states: Default::default(),
scope_stack: Default::default(),
suspense_stack: Default::default(),
current_task: Default::default(),
tasks: Default::default(),
suspended_tasks: Default::default(),
pending_effects: Default::default(),
dirty_tasks: Default::default(),
})
}
pub fn current() -> Result<Rc<Self>, RuntimeError> {
RUNTIMES
.with(|stack| stack.borrow().last().cloned())
.ok_or(RuntimeError::new())
}
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<Self>, id: ScopeId) {
{
let borrow = self.scope_states.borrow();
if let Some(scope) = &borrow[id.0] {
self.on_scope(id, || {
for id in scope.spawned_tasks.take() {
self.remove_task(id);
}
for hook in scope.hooks.take().drain(..).rev() {
drop(hook);
}
scope.shared_contexts.take();
});
}
}
self.scope_states.borrow_mut()[id.0].take();
}
pub(crate) fn current_scope_id(&self) -> Result<ScopeId, RuntimeError> {
self.scope_stack
.borrow()
.last()
.copied()
.ok_or(RuntimeError { _priv: () })
}
pub fn on_scope<O>(self: &Rc<Self>, id: ScopeId, f: impl FnOnce() -> O) -> O {
let _runtime_guard = RuntimeGuard::new(self.clone());
{
self.push_scope(id);
}
let o = f();
{
self.pop_scope();
}
o
}
pub(crate) fn current_suspense_location(&self) -> Option<SuspenseLocation> {
self.suspense_stack.borrow().last().cloned()
}
pub(crate) fn with_suspense_location<O>(
&self,
suspense_location: SuspenseLocation,
f: impl FnOnce() -> O,
) -> O {
self.suspense_stack.borrow_mut().push(suspense_location);
let o = f();
self.suspense_stack.borrow_mut().pop();
o
}
pub(crate) fn with_scope_on_stack<O>(&self, scope: ScopeId, f: impl FnOnce() -> O) -> O {
self.push_scope(scope);
let o = f();
self.pop_scope();
o
}
fn push_scope(&self, scope: ScopeId) {
let suspense_location = self
.scope_states
.borrow()
.get(scope.0)
.and_then(|s| s.as_ref())
.map(|s| s.suspense_location())
.unwrap_or_default();
self.suspense_stack.borrow_mut().push(suspense_location);
self.scope_stack.borrow_mut().push(scope);
}
fn pop_scope(&self) {
self.scope_stack.borrow_mut().pop();
self.suspense_stack.borrow_mut().pop();
}
pub(crate) fn get_state(&self, id: ScopeId) -> Option<Ref<'_, Scope>> {
Ref::filter_map(self.scope_states.borrow(), |contexts| {
contexts.get(id.0).and_then(|f| f.as_ref())
})
.ok()
}
pub(crate) fn push(runtime: Rc<Runtime>) {
RUNTIMES.with(|stack| stack.borrow_mut().push(runtime));
}
pub(crate) fn pop() {
RUNTIMES.with(|stack| stack.borrow_mut().pop());
}
pub(crate) fn with<R>(f: impl FnOnce(&Runtime) -> R) -> Result<R, RuntimeError> {
Self::current().map(|r| f(&r))
}
pub(crate) fn with_current_scope<R>(f: impl FnOnce(&Scope) -> R) -> Result<R, RuntimeError> {
Self::with(|rt| {
rt.current_scope_id()
.ok()
.and_then(|scope| rt.get_state(scope).map(|sc| f(&sc)))
})
.ok()
.flatten()
.ok_or(RuntimeError::new())
}
pub(crate) fn with_scope<R>(
scope: ScopeId,
f: impl FnOnce(&Scope) -> R,
) -> Result<R, RuntimeError> {
Self::with(|rt| rt.get_state(scope).map(|sc| f(&sc)))
.ok()
.flatten()
.ok_or(RuntimeError::new())
}
pub(crate) fn finish_render(&self) {
if !self.pending_effects.borrow().is_empty() {
self.sender
.unbounded_send(SchedulerMsg::EffectQueued)
.expect("Scheduler should exist");
}
}
pub(crate) fn scope_should_render(&self, scope_id: ScopeId) -> bool {
if self.suspended_tasks.get() == 0 {
return true;
}
let scopes = self.scope_states.borrow();
let scope = &scopes[scope_id.0].as_ref().unwrap();
!matches!(scope.suspense_location(), SuspenseLocation::UnderSuspense(suspense) if suspense.has_suspended_tasks())
}
}
pub struct RuntimeGuard(());
impl RuntimeGuard {
pub fn new(runtime: Rc<Runtime>) -> Self {
Runtime::push(runtime);
Self(())
}
}
impl Drop for RuntimeGuard {
fn drop(&mut self) {
Runtime::pop();
}
}
pub struct RuntimeError {
_priv: (),
}
impl RuntimeError {
#[inline(always)]
pub(crate) fn new() -> Self {
Self { _priv: () }
}
}
impl fmt::Debug for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.debug_struct("RuntimeError").finish()
}
}
impl fmt::Display for RuntimeError {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(
f,
"Must be called from inside a Dioxus runtime.
Help: Some APIs in dioxus require a global runtime to be present.
If you are calling one of these APIs from outside of a dioxus runtime
(typically in a web-sys closure or dynamic library), you will need to
grab the runtime from a scope that has it and then move it into your
new scope with a runtime guard.
For example, if you are trying to use dioxus apis from a web-sys
closure, you can grab the runtime from the scope it is created in:
```rust
use dioxus::prelude::*;
static COUNT: GlobalSignal<i32> = Signal::global(|| 0);
#[component]
fn MyComponent() -> Element {{
use_effect(|| {{
// Grab the runtime from the MyComponent scope
let runtime = Runtime::current().expect(\"Components run in the Dioxus runtime\");
// Move the runtime into the web-sys closure scope
let web_sys_closure = Closure::new(|| {{
// Then create a guard to provide the runtime to the closure
let _guard = RuntimeGuard::new(runtime);
// and run whatever code needs the runtime
tracing::info!(\"The count is: {{COUNT}}\");
}});
}})
}}
```"
)
}
}
impl std::error::Error for RuntimeError {}