use slotmap::DefaultKey;
use crate::innerlude::Effect;
use crate::{
innerlude::{LocalTask, SchedulerMsg},
render_signal::RenderSignal,
scope_context::Scope,
scopes::ScopeId,
Task,
};
use std::collections::BTreeSet;
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>>>,
pub(crate) scope_stack: RefCell<Vec<ScopeId>>,
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) render_signal: RenderSignal,
pub(crate) pending_effects: RefCell<BTreeSet<Effect>>,
}
impl Runtime {
pub(crate) fn new(sender: futures_channel::mpsc::UnboundedSender<SchedulerMsg>) -> Rc<Self> {
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(),
pending_effects: Default::default(),
})
}
pub fn current() -> Option<Rc<Self>> {
RUNTIMES.with(|stack| stack.borrow().last().cloned())
}
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] {
let _runtime_guard = RuntimeGuard::new(self.clone());
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) -> Option<ScopeId> {
self.scope_stack.borrow().last().copied()
}
pub fn on_scope<O>(&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
}
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) -> Option<R> {
RUNTIMES.with(|stack| stack.borrow().last().map(|r| f(r)))
}
pub(crate) fn with_current_scope<R>(f: impl FnOnce(&Scope) -> R) -> Option<R> {
Self::with(|rt| {
rt.current_scope_id()
.and_then(|scope| rt.get_state(scope).map(|sc| f(&sc)))
})
.flatten()
}
pub(crate) fn with_scope<R>(scope: ScopeId, f: impl FnOnce(&Scope) -> R) -> Option<R> {
Self::with(|rt| rt.get_state(scope).map(|sc| f(&sc))).flatten()
}
pub(crate) fn finish_render(&self) {
if !self.pending_effects.borrow().is_empty() {
self.sender
.unbounded_send(SchedulerMsg::EffectQueued)
.expect("Scheduler should exist");
}
self.render_signal.send();
}
}
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();
}
}