Skip to main content

cranpose_core/
runtime.rs

1use std::{
2    any::Any,
3    cell::{Cell, RefCell},
4    collections::VecDeque,
5    future::Future,
6    pin::Pin,
7    rc::{Rc, Weak},
8    sync::{
9        Arc,
10        atomic::{AtomicBool, AtomicUsize, Ordering},
11        mpsc,
12    },
13    task::{Context, Poll, Waker},
14    thread::ThreadId,
15};
16
17#[cfg(any(feature = "internal", test))]
18use crate::frame_clock::FrameClock;
19use crate::{
20    Applier, Command, FrameCallbackId, Key, MutableStateInner, NodeError, RecomposeScope,
21    RecomposeScopeInner, ScopeId,
22    collections::map::HashMap,
23    platform::{RuntimeScheduler, SchedulerRef},
24    state::{MutationPolicy, NeverEqual},
25    thread_state,
26};
27
28mod task_slots;
29
30#[derive(Clone, Copy, PartialEq, Eq)]
31pub(crate) enum FrameCallbackKind {
32    Transient,
33    Perpetual,
34}
35
36enum UiMessage {
37    Task(Box<dyn FnOnce() + Send + 'static>),
38    Invoke { id: u64, value: Box<dyn Any + Send> },
39}
40
41type UiContinuation = Box<dyn Fn(Box<dyn Any>) -> bool + 'static>;
42type UiContinuationMap = HashMap<u64, UiContinuation>;
43
44struct TypedStateCell<T: Clone + 'static> {
45    inner: MutableStateInner<T>,
46}
47
48trait ScopeWatchCell {
49    fn unregister_scope(&self, scope_id: ScopeId);
50    fn forget_scope(&self, scope_id: ScopeId);
51}
52
53impl<T: Clone + 'static> ScopeWatchCell for TypedStateCell<T> {
54    fn unregister_scope(&self, scope_id: ScopeId) {
55        self.inner.unregister_scope(scope_id);
56    }
57
58    fn forget_scope(&self, scope_id: ScopeId) {
59        self.inner.forget_scope(scope_id);
60    }
61}
62
63struct StateArenaSlot {
64    generation: u32,
65    cell: Option<Rc<dyn Any>>,
66    watcher_cell: Option<Rc<dyn ScopeWatchCell>>,
67    lease: Option<Weak<StateHandleLease>>,
68}
69
70#[derive(Default)]
71struct StateArenaInner {
72    cells: Vec<StateArenaSlot>,
73    free: Vec<u32>,
74    /// The derived states among the cells, by slot.
75    derived: HashMap<u32, Weak<dyn DerivedRefresh>>,
76}
77
78/// A derived state that brings its value up to date before a read.
79pub(crate) trait DerivedRefresh {
80    /// Derives the value again when a state it read changed since the
81    /// recomposition that last derived it.
82    fn refresh(&self);
83}
84
85/// Sizes of a runtime's state arena, for leak and memory checks. A `_len` field counts entries and
86/// a `_cap` field the capacity reserved.
87#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
88pub struct StateArenaDebugStats {
89    /// State cells, both in use and free.
90    pub cells_len: usize,
91    /// Capacity reserved for the state cells.
92    pub cells_cap: usize,
93    /// State cells free for reuse.
94    pub free_len: usize,
95    /// Capacity reserved for the free list.
96    pub free_cap: usize,
97}
98
99/// Sizes of a runtime's queues and tables, for leak and memory checks. A `_len` field counts
100/// entries and a `_cap` field the capacity reserved.
101#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
102pub struct RuntimeDebugStats {
103    /// Node updates queued and not yet applied.
104    pub node_updates_len: usize,
105    /// Capacity reserved for the node updates.
106    pub node_updates_cap: usize,
107    /// Recompose scopes that were invalidated and not yet recomposed.
108    pub invalid_scopes_len: usize,
109    /// Entries in the queue of invalidated scopes.
110    pub scope_queue_len: usize,
111    /// Capacity reserved for the invalid scope queue.
112    pub scope_queue_cap: usize,
113    /// Frame callbacks waiting for the next frame.
114    pub frame_callbacks_len: usize,
115    /// Capacity reserved for the frame callbacks.
116    pub frame_callbacks_cap: usize,
117    /// UI-thread closures queued by `enqueue_ui_task` and not yet run.
118    pub local_tasks_len: usize,
119    /// Capacity reserved for the local tasks.
120    pub local_tasks_cap: usize,
121    /// UI continuations registered and waiting for a value.
122    pub ui_conts_len: usize,
123    /// Capacity reserved for the UI continuations.
124    pub ui_conts_cap: usize,
125    /// Futures from `spawn_ui` that have not finished.
126    pub tasks_len: usize,
127    /// Capacity reserved for the spawned tasks.
128    pub tasks_cap: usize,
129    /// States kept alive on behalf of the runtime itself, not of a Rust owner.
130    pub external_state_owners_len: usize,
131    /// Capacity reserved for the external state owners.
132    pub external_state_owners_cap: usize,
133    /// Messages posted from other threads that the UI thread has not run yet.
134    pub ui_dispatcher_pending: usize,
135}
136
137#[derive(Default)]
138pub(crate) struct StateArena {
139    inner: RefCell<StateArenaInner>,
140}
141
142impl StateArena {
143    pub(crate) fn alloc<T: Clone + 'static>(&self, value: T, runtime: RuntimeHandle) -> StateId {
144        self.alloc_with_policy(value, runtime, Rc::new(NeverEqual))
145    }
146
147    /// A free cell's id, for a new value to take.
148    fn claim_slot(&self) -> StateId {
149        let mut inner = self.inner.borrow_mut();
150        loop {
151            let Some(slot) = inner.free.pop() else {
152                let slot = inner.cells.len() as u32;
153                inner.cells.push(StateArenaSlot {
154                    generation: 0,
155                    cell: None,
156                    watcher_cell: None,
157                    lease: None,
158                });
159                return StateId::new(slot, 0);
160            };
161
162            let Some(entry) = inner.cells.get_mut(slot as usize) else {
163                continue;
164            };
165            if entry.cell.is_some() {
166                continue;
167            }
168
169            entry.watcher_cell = None;
170            entry.lease = None;
171            entry.generation = entry.generation.wrapping_add(1);
172            return StateId::new(slot, entry.generation);
173        }
174    }
175
176    pub(crate) fn alloc_with_policy<T: Clone + 'static>(
177        &self,
178        value: T,
179        runtime: RuntimeHandle,
180        policy: Rc<dyn MutationPolicy<T>>,
181    ) -> StateId {
182        let id = self.claim_slot();
183        let slot = id.slot();
184        let inner = MutableStateInner::new_with_policy(value, runtime, policy);
185        inner.install_snapshot_observer(id);
186        let typed_cell = Rc::new(TypedStateCell { inner });
187        let cell: Rc<dyn Any> = typed_cell.clone();
188        let watcher_cell: Rc<dyn ScopeWatchCell> = typed_cell;
189        let mut arena = self.inner.borrow_mut();
190        let slot_entry = &mut arena.cells[slot as usize];
191        slot_entry.cell = Some(cell);
192        slot_entry.watcher_cell = Some(watcher_cell);
193        id
194    }
195
196    /// Stores `value` in a cell of its own, which keeps no snapshot records
197    /// and no readers: see [`crate::RuntimeCell`].
198    pub(crate) fn alloc_plain<T: 'static>(&self, value: T) -> StateId {
199        let id = self.claim_slot();
200        let cell: Rc<dyn Any> = Rc::new(value);
201        self.inner.borrow_mut().cells[id.slot_index()].cell = Some(cell);
202        id
203    }
204
205    /// The value [`Self::alloc_plain`] stored for `id`, while it lives.
206    pub(crate) fn plain<T: 'static>(&self, id: StateId) -> Option<Rc<T>> {
207        Rc::downcast::<T>(self.get_cell_opt(id)?).ok()
208    }
209
210    fn get_cell_opt(&self, id: StateId) -> Option<Rc<dyn Any>> {
211        self.inner
212            .borrow()
213            .cells
214            .get(id.slot_index())
215            .filter(|cell| cell.generation == id.generation())
216            .and_then(|cell| cell.cell.as_ref())
217            .cloned()
218    }
219
220    fn get_typed<T: Clone + 'static>(&self, id: StateId) -> Rc<TypedStateCell<T>> {
221        match self.get_cell_opt(id) {
222            None => panic!(
223                "state cell missing: slot={}, gen={}, expected={}",
224                id.slot(),
225                id.generation(),
226                std::any::type_name::<T>(),
227            ),
228            Some(cell) => Rc::downcast::<TypedStateCell<T>>(cell).unwrap_or_else(|_| {
229                panic!(
230                    "state cell type mismatch: slot={}, gen={}, expected={}",
231                    id.slot(),
232                    id.generation(),
233                    std::any::type_name::<T>(),
234                )
235            }),
236        }
237    }
238
239    fn get_typed_opt<T: Clone + 'static>(&self, id: StateId) -> Option<Rc<TypedStateCell<T>>> {
240        Rc::downcast::<TypedStateCell<T>>(self.get_cell_opt(id)?).ok()
241    }
242
243    pub(crate) fn with_typed<T: Clone + 'static, R>(
244        &self,
245        id: StateId,
246        f: impl FnOnce(&MutableStateInner<T>) -> R,
247    ) -> R {
248        let cell = self.get_typed::<T>(id);
249        f(&cell.inner)
250    }
251
252    pub(crate) fn with_typed_opt<T: Clone + 'static, R>(
253        &self,
254        id: StateId,
255        f: impl FnOnce(&MutableStateInner<T>) -> R,
256    ) -> Option<R> {
257        let cell = self.get_typed_opt::<T>(id)?;
258        Some(f(&cell.inner))
259    }
260
261    pub(crate) fn release(&self, id: StateId) {
262        let cell = {
263            let mut inner = self.inner.borrow_mut();
264            let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
265                return;
266            };
267            if slot.generation != id.generation() {
268                return;
269            }
270            slot.lease = None;
271            slot.watcher_cell = None;
272            let cell = slot.cell.take();
273            if cell.is_some() {
274                inner.free.push(id.slot());
275                if !inner.derived.is_empty() {
276                    inner.derived.remove(&id.slot());
277                }
278            }
279            cell
280        };
281        drop(cell);
282    }
283
284    /// Makes `refresh` what brings the derived state `id` up to date before
285    /// each read, until the state is released.
286    pub(crate) fn register_derived(&self, id: StateId, refresh: Weak<dyn DerivedRefresh>) {
287        self.inner.borrow_mut().derived.insert(id.slot(), refresh);
288    }
289
290    /// Brings the derived state `id` up to date before a read; see
291    /// [`DerivedRefresh`].
292    #[cold]
293    pub(crate) fn refresh_derived(&self, id: StateId) {
294        let refresh = {
295            let inner = self.inner.borrow();
296            inner
297                .cells
298                .get(id.slot_index())
299                .filter(|cell| cell.generation == id.generation())
300                .and_then(|_| inner.derived.get(&id.slot()))
301                .and_then(Weak::upgrade)
302        };
303        if let Some(refresh) = refresh {
304            refresh.refresh();
305        }
306    }
307
308    pub(crate) fn stats(&self) -> (usize, usize) {
309        let inner = self.inner.borrow();
310        (inner.cells.len(), inner.free.len())
311    }
312
313    pub(crate) fn debug_stats(&self) -> StateArenaDebugStats {
314        let inner = self.inner.borrow();
315        StateArenaDebugStats {
316            cells_len: inner.cells.len(),
317            cells_cap: inner.cells.capacity(),
318            free_len: inner.free.len(),
319            free_cap: inner.free.capacity(),
320        }
321    }
322
323    pub(crate) fn unregister_scope(&self, id: StateId, scope_id: ScopeId) {
324        if let Some(watcher_cell) = self.watcher_cell(id) {
325            watcher_cell.unregister_scope(scope_id);
326        }
327    }
328
329    /// Stops the state `id` from recomposing the live scope `scope_id`.
330    pub(crate) fn forget_scope(&self, id: StateId, scope_id: ScopeId) {
331        if let Some(watcher_cell) = self.watcher_cell(id) {
332            watcher_cell.forget_scope(scope_id);
333        }
334    }
335
336    fn watcher_cell(&self, id: StateId) -> Option<Rc<dyn ScopeWatchCell>> {
337        let inner = self.inner.borrow();
338        inner
339            .cells
340            .get(id.slot_index())
341            .filter(|slot| slot.generation == id.generation())
342            .and_then(|slot| slot.watcher_cell.as_ref())
343            .cloned()
344    }
345
346    pub(crate) fn register_lease(&self, id: StateId, lease: &Rc<StateHandleLease>) {
347        let mut inner = self.inner.borrow_mut();
348        let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
349            return;
350        };
351        if slot.generation != id.generation() {
352            return;
353        }
354        slot.lease = Some(Rc::downgrade(lease));
355    }
356
357    pub(crate) fn retain_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
358        let inner = self.inner.borrow();
359        let slot = inner.cells.get(id.slot_index())?;
360        if slot.generation != id.generation() {
361            return None;
362        }
363        slot.lease.as_ref()?.upgrade()
364    }
365}
366
367/// Names one state cell of a runtime. A generation count makes the id of a freed cell stale.
368#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
369pub struct StateId {
370    slot: u32,
371    generation: u32,
372}
373
374impl StateId {
375    const fn new(slot: u32, generation: u32) -> Self {
376        Self { slot, generation }
377    }
378
379    pub(crate) const fn slot(self) -> u32 {
380        self.slot
381    }
382
383    pub(crate) const fn slot_index(self) -> usize {
384        self.slot as usize
385    }
386
387    pub(crate) const fn generation(self) -> u32 {
388        self.generation
389    }
390}
391
392/// Identifies a runtime within its thread.
393#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
394pub struct RuntimeId(u32);
395
396impl RuntimeId {
397    fn next() -> Self {
398        thread_state::runtimes(|state| {
399            let id = state.next_runtime_id.get();
400            state.next_runtime_id.set(id.wrapping_add(1));
401            Self(id)
402        })
403    }
404}
405
406struct UiDispatcherInner {
407    scheduler: SchedulerRef,
408    tx: mpsc::Sender<UiMessage>,
409    pending: AtomicUsize,
410}
411
412#[cfg(not(target_arch = "wasm32"))]
413type UiDispatcherRef = Arc<UiDispatcherInner>;
414
415#[cfg(target_arch = "wasm32")]
416type UiDispatcherRef = Rc<UiDispatcherInner>;
417
418impl UiDispatcherInner {
419    fn new(scheduler: SchedulerRef, tx: mpsc::Sender<UiMessage>) -> Self {
420        Self {
421            scheduler,
422            tx,
423            pending: AtomicUsize::new(0),
424        }
425    }
426
427    fn post(&self, task: impl FnOnce() + Send + 'static) {
428        self.pending.fetch_add(1, Ordering::SeqCst);
429        if self.tx.send(UiMessage::Task(Box::new(task))).is_ok() {
430            self.scheduler.schedule_frame();
431        } else {
432            self.pending.fetch_sub(1, Ordering::SeqCst);
433        }
434    }
435
436    fn post_invoke(&self, id: u64, value: Box<dyn Any + Send>) {
437        self.pending.fetch_add(1, Ordering::SeqCst);
438        if self.tx.send(UiMessage::Invoke { id, value }).is_ok() {
439            self.scheduler.schedule_frame();
440        } else {
441            self.pending.fetch_sub(1, Ordering::SeqCst);
442        }
443    }
444
445    fn has_pending(&self) -> bool {
446        self.pending.load(Ordering::SeqCst) > 0
447    }
448}
449
450struct PendingGuard<'a> {
451    counter: &'a AtomicUsize,
452}
453
454impl<'a> PendingGuard<'a> {
455    fn new(counter: &'a AtomicUsize) -> Self {
456        Self { counter }
457    }
458}
459
460impl Drop for PendingGuard<'_> {
461    fn drop(&mut self) {
462        let mut current = self.counter.load(Ordering::SeqCst);
463        loop {
464            if current == 0 {
465                return;
466            }
467            match self.counter.compare_exchange(
468                current,
469                current - 1,
470                Ordering::SeqCst,
471                Ordering::SeqCst,
472            ) {
473                Ok(_) => return,
474                Err(next) => current = next,
475            }
476        }
477    }
478}
479
480/// Posts work from any thread to the UI thread of a runtime.
481#[derive(Clone)]
482pub struct UiDispatcher {
483    inner: UiDispatcherRef,
484}
485
486impl UiDispatcher {
487    fn new(inner: UiDispatcherRef) -> Self {
488        Self { inner }
489    }
490
491    /// Queues `task` to run on the UI thread the next time the runtime drains UI work. May be
492    /// called from any thread.
493    pub fn post(&self, task: impl FnOnce() + Send + 'static) {
494        self.inner.post(task);
495    }
496
497    /// Queues a call of the continuation registered under `id` with `value`, to run on the UI
498    /// thread. May be called from any thread.
499    pub fn post_invoke<T>(&self, id: u64, value: T)
500    where
501        T: Send + 'static,
502    {
503        self.inner.post_invoke(id, Box::new(value));
504    }
505
506    /// Returns `true` if a posted message has not run yet.
507    pub fn has_pending(&self) -> bool {
508        self.inner.has_pending()
509    }
510}
511
512struct RuntimeInner {
513    scheduler: SchedulerRef,
514    needs_frame: RefCell<bool>,
515    node_updates: RefCell<Vec<Command>>,
516    invalid_scope_count: Cell<usize>,
517    scope_queue: RefCell<Vec<Weak<RecomposeScopeInner>>>,
518    /// The storage of the last pass's scope list, for the next pass.
519    spare_scopes: RefCell<Vec<RecomposeScope>>,
520    /// The invalid scopes with their depths, while a recomposition sorts them.
521    spare_scopes_by_depth: RefCell<Vec<(usize, RecomposeScope)>>,
522    frame_callbacks: RefCell<VecDeque<FrameCallbackEntry>>,
523    next_frame_callback_id: Cell<u64>,
524    last_frame_time_nanos: Cell<Option<u64>>,
525    ui_dispatcher: UiDispatcherRef,
526    ui_rx: RefCell<mpsc::Receiver<UiMessage>>,
527    local_tasks: RefCell<VecDeque<Box<dyn FnOnce() + 'static>>>,
528    ui_conts: RefCell<UiContinuationMap>,
529    next_cont_id: Cell<u64>,
530    ui_thread_id: ThreadId,
531    tasks: RefCell<task_slots::Slots<TaskEntry>>,
532    task_order: RefCell<Vec<u64>>,
533    /// Set by every task wake after the task's own flag, and cleared by the
534    /// poll that then looks at the tasks: a poll after no wake skips them.
535    task_woken: Arc<AtomicBool>,
536    state_arena: StateArena,
537    external_state_owners: RefCell<HashMap<StateId, Rc<StateHandleLease>>>,
538    live_recompose_scope_count: Cell<usize>,
539    forgotten_movables: RefCell<Vec<Key>>,
540    next_movable_content_id: Cell<u64>,
541    runtime_id: RuntimeId,
542    #[cfg(target_os = "android")]
543    rescued: RefCell<Option<Rc<thread_state::RescuedThreadState>>>,
544}
545
546struct TaskEntry {
547    label: String,
548    future: Option<Pin<Box<dyn Future<Output = ()> + 'static>>>,
549    runnable: Arc<AtomicBool>,
550    waker: Waker,
551}
552
553/// Names the next task that `spawn_ui` creates on this thread, for `live_ui_task_labels` and leak
554/// reports. A task with no label is called `unnamed`.
555pub fn label_next_ui_task(label: impl Into<String>) {
556    thread_state::watch_thread_exit();
557    let label = label.into();
558    thread_state::runtimes(|state| *state.next_task_label.borrow_mut() = Some(label));
559}
560
561impl RuntimeInner {
562    fn new(scheduler: SchedulerRef) -> Self {
563        let (tx, rx) = mpsc::channel();
564        let dispatcher = UiDispatcherRef::new(UiDispatcherInner::new(scheduler.clone(), tx));
565        Self {
566            scheduler,
567            needs_frame: RefCell::new(false),
568            node_updates: RefCell::new(Vec::new()),
569            invalid_scope_count: Cell::new(0),
570            scope_queue: RefCell::new(Vec::new()),
571            spare_scopes: RefCell::new(Vec::new()),
572            spare_scopes_by_depth: RefCell::new(Vec::new()),
573            frame_callbacks: RefCell::new(VecDeque::new()),
574            next_frame_callback_id: Cell::new(1),
575            last_frame_time_nanos: Cell::new(None),
576            ui_dispatcher: dispatcher,
577            ui_rx: RefCell::new(rx),
578            local_tasks: RefCell::new(VecDeque::new()),
579            ui_conts: RefCell::new(UiContinuationMap::default()),
580            next_cont_id: Cell::new(1),
581            ui_thread_id: std::thread::current().id(),
582            tasks: RefCell::new(task_slots::Slots::new()),
583            task_order: RefCell::new(Vec::new()),
584            task_woken: Arc::new(AtomicBool::new(false)),
585            state_arena: StateArena::default(),
586            external_state_owners: RefCell::new(HashMap::default()),
587            live_recompose_scope_count: Cell::new(0),
588            forgotten_movables: RefCell::new(Vec::new()),
589            next_movable_content_id: Cell::new(1),
590            runtime_id: RuntimeId::next(),
591            #[cfg(target_os = "android")]
592            rescued: RefCell::new(None),
593        }
594    }
595
596    fn schedule(&self) {
597        *self.needs_frame.borrow_mut() = true;
598        self.scheduler.schedule_frame();
599    }
600
601    fn enqueue_update(&self, command: Command) {
602        self.node_updates.borrow_mut().push(command);
603        self.schedule();
604    }
605
606    fn take_updates(&self) -> Vec<Command> {
607        self.node_updates.borrow_mut().drain(..).collect::<Vec<_>>()
608    }
609
610    fn has_updates(&self) -> bool {
611        !self.node_updates.borrow().is_empty() || self.has_invalid_scopes()
612    }
613
614    fn register_invalid_scope(&self, scope: Weak<RecomposeScopeInner>) {
615        self.invalid_scope_count
616            .set(self.invalid_scope_count.get() + 1);
617        self.scope_queue.borrow_mut().push(scope);
618        self.schedule();
619    }
620
621    fn requeue_invalid_scope(&self, scope: &RecomposeScope) {
622        if scope.is_enqueued() {
623            self.scope_queue.borrow_mut().push(scope.downgrade());
624            self.schedule();
625        }
626    }
627
628    fn mark_scope_recomposed(&self) {
629        self.invalid_scope_count
630            .set(self.invalid_scope_count.get().saturating_sub(1));
631    }
632
633    fn take_invalidated_scopes(&self) -> Option<Vec<RecomposeScope>> {
634        // An empty queue keeps its storage for the next invalidation.
635        let mut pending = {
636            let mut queue = self.scope_queue.borrow_mut();
637            if queue.is_empty() {
638                return None;
639            }
640            std::mem::take(&mut *queue)
641        };
642        let mut scopes = std::mem::take(&mut *self.spare_scopes.borrow_mut());
643        scopes.extend(
644            pending
645                .iter()
646                .filter_map(RecomposeScope::upgrade)
647                .filter(RecomposeScope::is_enqueued),
648        );
649        pending.clear();
650        let mut queue = self.scope_queue.borrow_mut();
651        if queue.is_empty() {
652            std::mem::swap(&mut *queue, &mut pending);
653        }
654        drop(queue);
655        if scopes.is_empty() {
656            self.recycle_scopes(scopes);
657            return None;
658        }
659        // Ancestors first, as Compose recomposes in composition order: a
660        // scope that a recomposing parent removes or composes again does not
661        // run first with the arguments it had.
662        if scopes.len() > 1 {
663            // A scope's depth walks its ancestors, so each one is found once
664            // rather than at every comparison of the sort.
665            let mut by_depth = std::mem::take(&mut *self.spare_scopes_by_depth.borrow_mut());
666            by_depth.extend(scopes.drain(..).map(|scope| (scope.depth(), scope)));
667            by_depth.sort_by_key(|(depth, _)| *depth);
668            scopes.extend(by_depth.drain(..).map(|(_, scope)| scope));
669            *self.spare_scopes_by_depth.borrow_mut() = by_depth;
670        }
671        Some(scopes)
672    }
673
674    fn recycle_scopes(&self, mut scopes: Vec<RecomposeScope>) {
675        // Cleared before the borrow: dropping a scope may run any code.
676        scopes.clear();
677        let mut spare = self.spare_scopes.borrow_mut();
678        if spare.capacity() < scopes.capacity() {
679            *spare = scopes;
680        }
681    }
682
683    fn has_invalid_scopes(&self) -> bool {
684        self.invalid_scope_count.get() != 0
685    }
686
687    fn queued_invalid_scope_ids(&self) -> Vec<ScopeId> {
688        let mut ids: Vec<ScopeId> = self
689            .scope_queue
690            .borrow()
691            .iter()
692            .filter_map(RecomposeScope::upgrade)
693            .filter(RecomposeScope::is_enqueued)
694            .map(|scope| scope.id())
695            .collect();
696        ids.sort_unstable();
697        ids.dedup();
698        ids
699    }
700
701    fn increment_live_recompose_scope_count(&self) {
702        self.live_recompose_scope_count
703            .set(self.live_recompose_scope_count.get().saturating_add(1));
704    }
705
706    fn decrement_live_recompose_scope_count(&self) {
707        self.live_recompose_scope_count
708            .set(self.live_recompose_scope_count.get().saturating_sub(1));
709    }
710
711    fn live_recompose_scope_count(&self) -> usize {
712        self.live_recompose_scope_count.get()
713    }
714
715    fn has_frame_callbacks(&self) -> bool {
716        !self.frame_callbacks.borrow().is_empty()
717    }
718
719    fn has_transient_frame_callbacks(&self) -> bool {
720        self.frame_callbacks
721            .borrow()
722            .iter()
723            .any(|entry| entry.kind == FrameCallbackKind::Transient)
724    }
725
726    fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
727        self.local_tasks.borrow_mut().push_back(task);
728        self.schedule();
729    }
730
731    fn spawn_ui_task(&self, future: Pin<Box<dyn Future<Output = ()> + 'static>>) -> u64 {
732        let label = thread_state::runtimes(|state| state.next_task_label.borrow_mut().take())
733            .unwrap_or_else(|| "unnamed".to_string());
734        let runnable = Arc::new(AtomicBool::new(true));
735        self.task_woken.store(true, Ordering::Release);
736        let waker = RuntimeTaskWaker::new(self, Arc::clone(&runnable)).into_waker();
737        let id = self.tasks.borrow_mut().insert(TaskEntry {
738            label,
739            future: Some(future),
740            runnable,
741            waker,
742        });
743        self.task_order.borrow_mut().push(id);
744        self.schedule();
745        id
746    }
747
748    fn cancel_task(&self, id: u64) {
749        let task = self.tasks.borrow_mut().remove(id);
750        self.task_order.borrow_mut().retain(|queued| *queued != id);
751        drop(task);
752    }
753
754    fn has_task(&self, id: u64) -> bool {
755        self.tasks
756            .try_borrow()
757            .map_or(true, |tasks| tasks.contains(id))
758    }
759
760    fn poll_async_tasks(&self) -> bool {
761        // Without a wake no task can make progress, and the order stays.
762        if !self.task_woken.load(Ordering::Acquire) {
763            return false;
764        }
765        // The tasks still pending move to the front of `order`, which goes
766        // back as the task order, after any task spawned while polling.
767        let mut order = std::mem::take(&mut *self.task_order.borrow_mut());
768        // An empty order is a poll nested in another, which holds the order:
769        // the wake flag stays for the polls after it.
770        if order.is_empty() || !self.task_woken.swap(false, Ordering::AcqRel) {
771            let mut task_order = self.task_order.borrow_mut();
772            if task_order.is_empty() {
773                *task_order = order;
774            } else {
775                task_order.extend(order);
776            }
777            return false;
778        }
779        let mut pending = 0;
780        let mut made_progress = false;
781        for index in 0..order.len() {
782            let id = order[index];
783            let task = {
784                let mut tasks = self.tasks.borrow_mut();
785                let Some(entry) = tasks.get_mut(id) else {
786                    continue;
787                };
788                if entry.runnable.swap(false, Ordering::AcqRel) {
789                    entry
790                        .future
791                        .take()
792                        .map(|future| (future, entry.waker.clone()))
793                } else {
794                    None
795                }
796            };
797            let Some((mut future, waker)) = task else {
798                order[pending] = id;
799                pending += 1;
800                continue;
801            };
802            let mut cx = Context::from_waker(&waker);
803            match future.as_mut().poll(&mut cx) {
804                Poll::Ready(()) => {
805                    self.cancel_task(id);
806                    made_progress = true;
807                }
808                Poll::Pending => {
809                    let mut tasks = self.tasks.borrow_mut();
810                    if let Some(entry) = tasks.get_mut(id) {
811                        entry.future = Some(future);
812                        order[pending] = id;
813                        pending += 1;
814                    } else {
815                        drop(tasks);
816                        drop(future);
817                    }
818                }
819            }
820        }
821        order.truncate(pending);
822        order.retain(|id| self.has_task(*id));
823        let mut task_order = self.task_order.borrow_mut();
824        if task_order.is_empty() {
825            *task_order = order;
826        } else {
827            task_order.extend(order);
828        }
829        made_progress
830    }
831
832    fn drain_ui(&self) {
833        #[cfg(target_os = "emscripten")]
834        crate::concurrency::fire_due_alarms();
835        loop {
836            let mut executed = false;
837
838            // A message counts as pending from before it is sent until it
839            // ran, so none waits while none is pending.
840            if self.ui_dispatcher.has_pending() {
841                let rx = &mut *self.ui_rx.borrow_mut();
842                for message in rx.try_iter() {
843                    executed = true;
844                    let _guard = PendingGuard::new(&self.ui_dispatcher.pending);
845                    match message {
846                        UiMessage::Task(task) => {
847                            task();
848                        }
849                        UiMessage::Invoke { id, value } => {
850                            self.invoke_ui_cont(id, value);
851                        }
852                    }
853                }
854            }
855
856            if self.run_local_tasks() {
857                executed = true;
858            }
859
860            if self.poll_async_tasks() {
861                executed = true;
862            }
863            // The tasks just polled may have queued work of their own, such
864            // as a state observer's change notice for a state they wrote. It
865            // belongs to this drain, or a frame that resumed an animation
866            // would draw the value it wrote only on the next frame. It does
867            // not count as progress: a task writing state on every poll must
868            // not keep the drain polling it.
869            self.run_local_tasks();
870
871            if !executed {
872                break;
873            }
874        }
875
876        self.clear_needs_frame_if_idle();
877    }
878
879    fn run_local_tasks(&self) -> bool {
880        let mut ran = false;
881        loop {
882            let task = self.local_tasks.borrow_mut().pop_front();
883            let Some(task) = task else {
884                return ran;
885            };
886            ran = true;
887            task();
888        }
889    }
890
891    fn has_pending_ui(&self) -> bool {
892        let local_pending = self
893            .local_tasks
894            .try_borrow()
895            .map_or(true, |tasks| !tasks.is_empty());
896
897        local_pending || self.ui_dispatcher.has_pending() || self.has_runnable_tasks()
898    }
899
900    /// A finished task's waker may still wake after the task is gone; with
901    /// no task left that wake is stale.
902    fn has_runnable_tasks(&self) -> bool {
903        self.task_woken.load(Ordering::Acquire)
904            && self
905                .tasks
906                .try_borrow()
907                .map_or(true, |tasks| !tasks.is_empty())
908    }
909
910    fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> u64 {
911        debug_assert_eq!(
912            std::thread::current().id(),
913            self.ui_thread_id,
914            "UI continuation registered off the runtime thread",
915        );
916        let id = self.next_cont_id.get();
917        self.next_cont_id.set(id + 1);
918        let callback = RefCell::new(Some(f));
919        self.ui_conts.borrow_mut().insert(
920            id,
921            Box::new(move |value: Box<dyn Any>| {
922                let Ok(value) = value.downcast::<T>() else {
923                    return false;
924                };
925                let Some(slot) = callback.borrow_mut().take() else {
926                    return true;
927                };
928                slot(*value);
929                true
930            }),
931        );
932        id
933    }
934
935    fn invoke_ui_cont(&self, id: u64, value: Box<dyn Any + Send>) {
936        debug_assert_eq!(
937            std::thread::current().id(),
938            self.ui_thread_id,
939            "UI continuation invoked off the runtime thread",
940        );
941        let callback = { self.ui_conts.borrow_mut().remove(&id) };
942        if let Some(callback) = callback {
943            let value: Box<dyn Any> = value;
944            if !callback(value) {
945                self.ui_conts.borrow_mut().insert(id, callback);
946            }
947        }
948    }
949
950    fn cancel_ui_cont(&self, id: u64) {
951        let continuation = self.ui_conts.borrow_mut().remove(&id);
952        drop(continuation);
953    }
954
955    fn register_frame_callback(
956        &self,
957        kind: FrameCallbackKind,
958        callback: FrameCallback,
959    ) -> FrameCallbackId {
960        let id = self.next_frame_callback_id.get();
961        self.next_frame_callback_id.set(id + 1);
962        self.frame_callbacks
963            .borrow_mut()
964            .push_back(FrameCallbackEntry { id, kind, callback });
965        self.schedule();
966        id
967    }
968
969    /// The frame time once the frame callback `id` has run. A waiting
970    /// task's entry takes `waker` when the one it holds would wake another
971    /// task.
972    #[cfg(any(feature = "internal", test))]
973    fn frame_waker_time(&self, id: FrameCallbackId, waker: &Waker) -> Option<u64> {
974        let mut callbacks = self.frame_callbacks.borrow_mut();
975        let Some(entry) = callbacks.iter_mut().find(|entry| entry.id == id) else {
976            return self.last_frame_time_nanos.get();
977        };
978        if let FrameCallback::Wake(held) = &mut entry.callback
979            && !held.will_wake(waker)
980        {
981            held.clone_from(waker);
982        }
983        None
984    }
985
986    fn cancel_frame_callback(&self, id: FrameCallbackId) {
987        let removed = {
988            let mut callbacks = self.frame_callbacks.borrow_mut();
989            callbacks
990                .iter()
991                .position(|entry| entry.id == id)
992                .and_then(|index| callbacks.remove(index))
993        };
994        drop(removed);
995        self.clear_needs_frame_if_idle();
996    }
997
998    fn clear_needs_frame_if_idle(&self) {
999        if !self.has_invalid_scopes()
1000            && !self.has_updates()
1001            && !self.has_frame_callbacks()
1002            && !self.has_pending_ui()
1003        {
1004            *self.needs_frame.borrow_mut() = false;
1005        }
1006    }
1007
1008    fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
1009        if self
1010            .last_frame_time_nanos
1011            .get()
1012            .is_some_and(|previous| frame_time_nanos <= previous)
1013        {
1014            return;
1015        }
1016        self.last_frame_time_nanos.set(Some(frame_time_nanos));
1017        let next_frame_id = self.next_frame_callback_id.get();
1018        let drain = || {
1019            loop {
1020                let entry = {
1021                    let mut callbacks = self.frame_callbacks.borrow_mut();
1022                    if callbacks
1023                        .front()
1024                        .is_some_and(|entry| entry.id < next_frame_id)
1025                    {
1026                        callbacks.pop_front()
1027                    } else {
1028                        None
1029                    }
1030                };
1031                let Some(entry) = entry else {
1032                    break;
1033                };
1034                match entry.callback {
1035                    FrameCallback::Run(callback) => callback(frame_time_nanos),
1036                    #[cfg(any(feature = "internal", test))]
1037                    FrameCallback::Wake(waker) => waker.wake(),
1038                }
1039            }
1040        };
1041        // Callbacks write state in one snapshot. Waking a task writes none:
1042        // a frame of only waiting tasks opens no snapshot.
1043        let runs_callbacks = self
1044            .frame_callbacks
1045            .borrow()
1046            .iter()
1047            .any(|entry| matches!(entry.callback, FrameCallback::Run(_)));
1048        if runs_callbacks {
1049            let _ = crate::run_in_mutable_snapshot(drain);
1050        } else {
1051            drain();
1052        }
1053
1054        self.clear_needs_frame_if_idle();
1055    }
1056
1057    fn debug_stats(&self) -> RuntimeDebugStats {
1058        let node_updates = self.node_updates.borrow();
1059        let scope_queue = self.scope_queue.borrow();
1060        let frame_callbacks = self.frame_callbacks.borrow();
1061        let local_tasks = self.local_tasks.borrow();
1062        let ui_conts = self.ui_conts.borrow();
1063        let tasks = self.tasks.borrow();
1064        let external_state_owners = self.external_state_owners.borrow();
1065
1066        RuntimeDebugStats {
1067            node_updates_len: node_updates.len(),
1068            node_updates_cap: node_updates.capacity(),
1069            invalid_scopes_len: self.invalid_scope_count.get(),
1070            scope_queue_len: scope_queue.len(),
1071            scope_queue_cap: scope_queue.capacity(),
1072            frame_callbacks_len: frame_callbacks.len(),
1073            frame_callbacks_cap: frame_callbacks.capacity(),
1074            local_tasks_len: local_tasks.len(),
1075            local_tasks_cap: local_tasks.capacity(),
1076            ui_conts_len: ui_conts.len(),
1077            ui_conts_cap: ui_conts.capacity(),
1078            tasks_len: tasks.len(),
1079            tasks_cap: tasks.capacity(),
1080            external_state_owners_len: external_state_owners.len(),
1081            external_state_owners_cap: external_state_owners.capacity(),
1082            ui_dispatcher_pending: self.ui_dispatcher.pending.load(Ordering::SeqCst),
1083        }
1084    }
1085}
1086
1087/// Owns the state cells, UI tasks, frame callbacks and invalidated scopes of one composition.
1088/// Clones share the same runtime.
1089#[derive(Clone)]
1090pub struct Runtime {
1091    inner: Rc<RuntimeInner>,
1092}
1093
1094impl Runtime {
1095    /// Creates a runtime whose UI thread is the current thread. `scheduler` is asked for a frame
1096    /// whenever the runtime has work.
1097    pub fn new(scheduler: SchedulerRef) -> Self {
1098        thread_state::watch_thread_exit();
1099        let inner = Rc::new(RuntimeInner::new(scheduler));
1100        let runtime = Self { inner };
1101        let handle = runtime.handle();
1102        thread_state::runtimes(|state| {
1103            state
1104                .registered_runtimes
1105                .borrow_mut()
1106                .insert(handle.id(), handle.clone());
1107            *state.last_runtime.borrow_mut() = Some(handle);
1108        });
1109        runtime
1110    }
1111
1112    #[cfg(target_os = "android")]
1113    pub(crate) fn restore_thread_state(&self) {
1114        if let Some(rescued) = self.inner.rescued.take() {
1115            thread_state::restore(&rescued);
1116        }
1117    }
1118
1119    /// Returns a handle that reaches this runtime without keeping it alive.
1120    pub fn handle(&self) -> RuntimeHandle {
1121        RuntimeHandle {
1122            inner: Rc::downgrade(&self.inner),
1123            dispatcher: UiDispatcher::new(self.inner.ui_dispatcher.clone()),
1124            ui_thread_id: self.inner.ui_thread_id,
1125            id: self.inner.runtime_id,
1126        }
1127    }
1128
1129    /// Returns `true` if node updates are queued or any recompose scope is invalid.
1130    pub fn has_updates(&self) -> bool {
1131        self.inner.has_updates()
1132    }
1133
1134    /// Returns `true` if the runtime has asked for a frame or has a task ready to run.
1135    pub fn needs_frame(&self) -> bool {
1136        *self.inner.needs_frame.borrow() || self.inner.has_runnable_tasks()
1137    }
1138
1139    /// Sets or clears the request for a frame.
1140    pub fn set_needs_frame(&self, value: bool) {
1141        *self.inner.needs_frame.borrow_mut() = value;
1142    }
1143
1144    /// Returns `true` if posted UI work has not run yet. The owner of a
1145    /// runtime asks it here; a [`RuntimeHandle`] costs a clone of its parts.
1146    pub fn has_pending_ui(&self) -> bool {
1147        self.inner.has_pending_ui()
1148    }
1149
1150    /// Returns `true` if any recompose scope is invalid.
1151    pub fn has_invalid_scopes(&self) -> bool {
1152        self.inner.has_invalid_scopes()
1153    }
1154
1155    /// Returns `true` if any frame callback is registered.
1156    pub fn has_frame_callbacks(&self) -> bool {
1157        self.inner.has_frame_callbacks()
1158    }
1159
1160    /// Returns `true` while a one-shot frame callback waits for the next
1161    /// frame. Perpetual frame callbacks do not count.
1162    pub fn has_transient_frame_callbacks(&self) -> bool {
1163        self.inner.has_transient_frame_callbacks()
1164    }
1165
1166    /// Runs the frame callbacks due at `frame_time_nanos`.
1167    pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
1168        self.inner.drain_frame_callbacks(frame_time_nanos);
1169    }
1170
1171    pub(crate) fn take_forgotten_movables(&self) -> Vec<Key> {
1172        std::mem::take(&mut *self.inner.forgotten_movables.borrow_mut())
1173    }
1174
1175    pub(crate) fn take_updates(&self) -> Vec<Command> {
1176        self.inner.take_updates()
1177    }
1178
1179    /// Animation-clock time of the most recent frame-callback drain. This is
1180    /// the same clock `Animatable`s advance on (virtual under robot exact
1181    /// captures), so per-frame integrators derive dt from it instead of wall
1182    /// time.
1183    pub fn last_frame_time_nanos(&self) -> Option<u64> {
1184        self.inner.last_frame_time_nanos.get()
1185    }
1186
1187    /// Returns a clock for the frames of this runtime.
1188    #[cfg(any(feature = "internal", test))]
1189    pub fn frame_clock(&self) -> FrameClock {
1190        FrameClock::new(self.handle())
1191    }
1192}
1193
1194impl Drop for Runtime {
1195    fn drop(&mut self) {
1196        if Rc::strong_count(&self.inner) != 1 {
1197            return;
1198        }
1199        let id = self.inner.runtime_id;
1200        thread_state::try_runtimes(|state| {
1201            state.registered_runtimes.borrow_mut().remove(&id);
1202            let mut last = state.last_runtime.borrow_mut();
1203            if last.as_ref().is_some_and(|handle| handle.id() == id) {
1204                *last = None;
1205            }
1206        });
1207        thread_state::release_when_idle();
1208    }
1209}
1210
1211/// A scheduler that ignores frame requests, for compositions that the caller drives itself.
1212#[derive(Default)]
1213pub struct DefaultScheduler;
1214
1215impl RuntimeScheduler for DefaultScheduler {
1216    fn schedule_frame(&self) {}
1217}
1218
1219#[cfg(test)]
1220#[derive(Default)]
1221pub(crate) struct TestScheduler;
1222
1223#[cfg(test)]
1224impl RuntimeScheduler for TestScheduler {
1225    fn schedule_frame(&self) {}
1226}
1227
1228#[cfg(test)]
1229pub(crate) struct TestRuntime {
1230    runtime: Runtime,
1231}
1232
1233#[cfg(test)]
1234impl Default for TestRuntime {
1235    fn default() -> Self {
1236        Self::new()
1237    }
1238}
1239
1240#[cfg(test)]
1241impl TestRuntime {
1242    pub(crate) fn new() -> Self {
1243        Self {
1244            runtime: Runtime::new(Arc::new(TestScheduler)),
1245        }
1246    }
1247
1248    pub(crate) fn handle(&self) -> RuntimeHandle {
1249        self.runtime.handle()
1250    }
1251}
1252
1253/// A cheap handle to a [`Runtime`]. It does not keep the runtime alive, and its calls do nothing
1254/// once the runtime is dropped.
1255#[derive(Clone)]
1256pub struct RuntimeHandle {
1257    inner: Weak<RuntimeInner>,
1258    dispatcher: UiDispatcher,
1259    ui_thread_id: ThreadId,
1260    id: RuntimeId,
1261}
1262
1263/// What a recompose scope keeps of its runtime: one word, where a
1264/// [`RuntimeHandle`] takes four, on every scope of a composition. Each call
1265/// does nothing once the runtime is gone, as the handle's would.
1266pub(crate) struct ScopeRuntime(Weak<RuntimeInner>);
1267
1268impl ScopeRuntime {
1269    pub(crate) fn mark_scope_recomposed(&self) {
1270        if let Some(inner) = self.0.upgrade() {
1271            inner.mark_scope_recomposed();
1272        }
1273    }
1274
1275    pub(crate) fn register_invalid_scope(&self, scope: Weak<RecomposeScopeInner>) {
1276        if let Some(inner) = self.0.upgrade() {
1277            inner.register_invalid_scope(scope);
1278        }
1279    }
1280
1281    pub(crate) fn unregister_state_scope(&self, id: StateId, scope_id: ScopeId) {
1282        if let Some(inner) = self.0.upgrade() {
1283            inner.state_arena.unregister_scope(id, scope_id);
1284        }
1285    }
1286
1287    pub(crate) fn forget_state_scope(&self, id: StateId, scope_id: ScopeId) {
1288        if let Some(inner) = self.0.upgrade() {
1289            inner.state_arena.forget_scope(id, scope_id);
1290        }
1291    }
1292
1293    pub(crate) fn increment_live_recompose_scope_count(&self) {
1294        if let Some(inner) = self.0.upgrade() {
1295            inner.increment_live_recompose_scope_count();
1296        }
1297    }
1298
1299    pub(crate) fn decrement_live_recompose_scope_count(&self) {
1300        if let Some(inner) = self.0.upgrade() {
1301            inner.decrement_live_recompose_scope_count();
1302        }
1303    }
1304}
1305
1306/// Controls a future spawned with `RuntimeHandle::spawn_ui`.
1307pub struct TaskHandle {
1308    id: u64,
1309    runtime: RuntimeHandle,
1310}
1311
1312pub(crate) struct DeferredStateRelease {
1313    runtime: Weak<RuntimeInner>,
1314    id: StateId,
1315}
1316
1317pub(crate) struct StateHandleLease {
1318    id: StateId,
1319    runtime: RuntimeHandle,
1320}
1321
1322impl StateHandleLease {
1323    pub(crate) fn id(&self) -> StateId {
1324        self.id
1325    }
1326
1327    pub(crate) fn runtime_id(&self) -> RuntimeId {
1328        self.runtime.id()
1329    }
1330}
1331
1332impl Drop for StateHandleLease {
1333    fn drop(&mut self) {
1334        defer_state_release(&self.runtime, self.id);
1335    }
1336}
1337
1338struct StateOwnerFrame;
1339
1340impl Drop for StateOwnerFrame {
1341    fn drop(&mut self) {
1342        thread_state::runtimes(|state| state.state_owners.borrow_mut().pop());
1343    }
1344}
1345
1346/// Runs `build` with every state it creates owned by the caller, and returns
1347/// those states beside its value.
1348///
1349/// This is what makes the Jetpack Compose shape safe here:
1350///
1351/// ```rust,ignore
1352/// remember(|| Holder {
1353///     count: mutableStateOf(0),
1354/// })
1355/// ```
1356///
1357/// Kotlin leaves the state to the garbage collector, which frees it with the
1358/// object that holds it. Nothing collects here, so a state with no owner has
1359/// to be kept by the runtime for as long as the runtime lives, and a holder
1360/// built once per screen would pile up cells nobody can reach. Handing the
1361/// states to whoever is building the value puts them back on the object's
1362/// lifetime: the slot drops the value, the value drops the states.
1363pub(crate) fn collecting_states<T>(build: impl FnOnce() -> T) -> (T, Vec<Rc<StateHandleLease>>) {
1364    thread_state::runtimes(|state| state.state_owners.borrow_mut().push(Vec::new()));
1365    let frame = StateOwnerFrame;
1366    let value = build();
1367    let states = thread_state::runtimes(|state| {
1368        state
1369            .state_owners
1370            .borrow_mut()
1371            .last_mut()
1372            .map(std::mem::take)
1373            .unwrap_or_default()
1374    });
1375    drop(frame);
1376    (value, states)
1377}
1378
1379fn hand_to_current_owner(lease: &Rc<StateHandleLease>) -> bool {
1380    thread_state::runtimes(|state| match state.state_owners.borrow_mut().last_mut() {
1381        Some(owner) => {
1382            owner.push(Rc::clone(lease));
1383            true
1384        }
1385        None => false,
1386    })
1387}
1388
1389impl RuntimeHandle {
1390    /// Returns the id of the runtime.
1391    pub fn id(&self) -> RuntimeId {
1392        self.id
1393    }
1394
1395    pub(crate) fn alloc_state<T: Clone + 'static>(&self, value: T) -> Rc<StateHandleLease> {
1396        let id = self.with_state_arena(|arena| arena.alloc(value, self.clone()));
1397        let lease = Rc::new(StateHandleLease {
1398            id,
1399            runtime: self.clone(),
1400        });
1401        self.with_state_arena(|arena| arena.register_lease(id, &lease));
1402        lease
1403    }
1404
1405    pub(crate) fn alloc_state_with_policy<T: Clone + 'static>(
1406        &self,
1407        value: T,
1408        policy: Rc<dyn MutationPolicy<T>>,
1409    ) -> Rc<StateHandleLease> {
1410        let id =
1411            self.with_state_arena(|arena| arena.alloc_with_policy(value, self.clone(), policy));
1412        let lease = Rc::new(StateHandleLease {
1413            id,
1414            runtime: self.clone(),
1415        });
1416        self.with_state_arena(|arena| arena.register_lease(id, &lease));
1417        lease
1418    }
1419
1420    /// Stores `value` in a new plain cell, whose lease goes to the value
1421    /// being remembered, or else to the runtime; returns the cell's id.
1422    pub(crate) fn alloc_owned_plain<T: 'static>(&self, value: T) -> StateId {
1423        let id = self.with_state_arena(|arena| arena.alloc_plain(value));
1424        let lease = Rc::new(StateHandleLease {
1425            id,
1426            runtime: self.clone(),
1427        });
1428        self.with_state_arena(|arena| arena.register_lease(id, &lease));
1429        self.hand_to_owner(&lease);
1430        id
1431    }
1432
1433    pub(crate) fn alloc_persistent_state<T: Clone + 'static>(
1434        &self,
1435        value: T,
1436    ) -> crate::MutableState<T> {
1437        self.hand_out(self.alloc_state(value))
1438    }
1439
1440    pub(crate) fn alloc_persistent_state_with_policy<T: Clone + 'static>(
1441        &self,
1442        value: T,
1443        policy: Rc<dyn MutationPolicy<T>>,
1444    ) -> crate::MutableState<T> {
1445        self.hand_out(self.alloc_state_with_policy(value, policy))
1446    }
1447
1448    fn hand_out<T: Clone + 'static>(&self, lease: Rc<StateHandleLease>) -> crate::MutableState<T> {
1449        self.hand_to_owner(&lease);
1450        crate::MutableState::from_lease(&lease)
1451    }
1452
1453    /// Hands `lease` to the value being remembered, or else to the runtime,
1454    /// which keeps it as long as it lives.
1455    fn hand_to_owner(&self, lease: &Rc<StateHandleLease>) {
1456        if !hand_to_current_owner(lease)
1457            && let Some(inner) = self.inner.upgrade()
1458        {
1459            inner
1460                .external_state_owners
1461                .borrow_mut()
1462                .insert(lease.id(), Rc::clone(lease));
1463        }
1464    }
1465
1466    pub(crate) fn retain_state_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
1467        self.with_state_arena(|arena| arena.retain_lease(id))
1468    }
1469
1470    pub(crate) fn with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> R {
1471        self.try_with_state_arena(f)
1472            .unwrap_or_else(|| panic!("runtime dropped"))
1473    }
1474
1475    pub(crate) fn try_with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> Option<R> {
1476        self.inner.upgrade().map(|inner| f(&inner.state_arena))
1477    }
1478
1479    /// Returns the number of state cells and, of those, the number free for reuse.
1480    pub fn state_arena_stats(&self) -> (usize, usize) {
1481        self.try_with_state_arena(StateArena::stats)
1482            .unwrap_or_default()
1483    }
1484
1485    /// Returns the sizes of the runtime's state arena, for leak and memory checks.
1486    pub fn state_arena_debug_stats(&self) -> StateArenaDebugStats {
1487        self.try_with_state_arena(StateArena::debug_stats)
1488            .unwrap_or_default()
1489    }
1490
1491    /// Returns the sizes of the runtime's queues and tables, for leak and memory checks.
1492    pub fn debug_stats(&self) -> RuntimeDebugStats {
1493        self.inner
1494            .upgrade()
1495            .map(|inner| inner.debug_stats())
1496            .unwrap_or_default()
1497    }
1498
1499    /// Returns the id and label of every spawned UI task that has not finished.
1500    pub fn live_ui_task_labels(&self) -> Vec<(u64, String)> {
1501        self.inner
1502            .upgrade()
1503            .map(|inner| {
1504                inner
1505                    .tasks
1506                    .borrow()
1507                    .iter()
1508                    .map(|(id, entry)| (id, entry.label.clone()))
1509                    .collect()
1510            })
1511            .unwrap_or_default()
1512    }
1513
1514    /// Marks the runtime as needing a frame and asks its scheduler for one.
1515    pub fn schedule(&self) {
1516        if let Some(inner) = self.inner.upgrade() {
1517            inner.schedule();
1518        }
1519    }
1520
1521    pub(crate) fn enqueue_node_update(&self, command: Command) {
1522        if let Some(inner) = self.inner.upgrade() {
1523            inner.enqueue_update(command);
1524        }
1525    }
1526
1527    /// Schedules work that must run on the runtime thread.
1528    ///
1529    /// The closure executes on the UI thread immediately when the runtime
1530    /// drains its local queue, so it may capture `Rc`/`RefCell` values. Calling
1531    /// this from any other thread is a logic error and will panic in debug
1532    /// builds via the inner assertion.
1533    pub fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
1534        if let Some(inner) = self.inner.upgrade() {
1535            inner.enqueue_ui_task(task);
1536        } else {
1537            task();
1538        }
1539    }
1540
1541    /// Spawns `fut` on the UI thread, where `drain_ui` polls it whenever it is woken. Returns
1542    /// `None` if the runtime is gone.
1543    pub fn spawn_ui<F>(&self, fut: F) -> Option<TaskHandle>
1544    where
1545        F: Future<Output = ()> + 'static,
1546    {
1547        self.inner.upgrade().map(|inner| {
1548            let id = inner.spawn_ui_task(Box::pin(fut));
1549            TaskHandle {
1550                id,
1551                runtime: self.clone(),
1552            }
1553        })
1554    }
1555
1556    /// Cancels the spawned UI task `id` and drops its future.
1557    pub fn cancel_task(&self, id: u64) {
1558        if let Some(inner) = self.inner.upgrade() {
1559            inner.cancel_task(id);
1560        }
1561    }
1562
1563    /// Whether the runtime still holds the spawned task `id`.
1564    pub fn has_task(&self, id: u64) -> bool {
1565        self.inner.upgrade().is_some_and(|inner| inner.has_task(id))
1566    }
1567
1568    /// Enqueues work from any thread to run on the UI thread.
1569    ///
1570    /// The closure must be `Send` because it may cross threads before executing
1571    /// on the runtime thread. Use this when posting from background work.
1572    pub fn post_ui(&self, task: impl FnOnce() + Send + 'static) {
1573        self.dispatcher.post(task);
1574    }
1575
1576    /// Registers `f` to run once on the UI thread when `UiDispatcher::post_invoke` sends a `T` for
1577    /// the returned id. Returns `None` if the runtime is gone.
1578    pub fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> Option<u64> {
1579        self.inner.upgrade().map(|inner| inner.register_ui_cont(f))
1580    }
1581
1582    /// Removes the continuation `id` without running it.
1583    pub fn cancel_ui_cont(&self, id: u64) {
1584        if let Some(inner) = self.inner.upgrade() {
1585            inner.cancel_ui_cont(id);
1586        }
1587    }
1588
1589    /// Runs the work posted to the UI thread, including woken tasks, until none is left.
1590    pub fn drain_ui(&self) {
1591        if let Some(inner) = self.inner.upgrade() {
1592            inner.drain_ui();
1593        }
1594    }
1595
1596    /// Returns `true` if posted UI work has not run yet.
1597    pub fn has_pending_ui(&self) -> bool {
1598        self.inner.upgrade().map_or_else(
1599            || self.dispatcher.has_pending(),
1600            |inner| inner.has_pending_ui(),
1601        )
1602    }
1603
1604    /// Registers `callback` to run once on the next frame with the frame time in nanoseconds.
1605    /// Returns `None` if the runtime is gone.
1606    pub fn register_frame_callback(
1607        &self,
1608        callback: impl FnOnce(u64) + 'static,
1609    ) -> Option<FrameCallbackId> {
1610        self.inner.upgrade().map(|inner| {
1611            inner.register_frame_callback(
1612                FrameCallbackKind::Transient,
1613                FrameCallback::Run(Box::new(callback)),
1614            )
1615        })
1616    }
1617
1618    /// Registers `callback` to run once on the next frame, for a long-lived loop that registers
1619    /// again after every frame. It does not count in `has_transient_frame_callbacks`.
1620    pub fn register_perpetual_frame_callback(
1621        &self,
1622        callback: impl FnOnce(u64) + 'static,
1623    ) -> Option<FrameCallbackId> {
1624        self.inner.upgrade().map(|inner| {
1625            inner.register_frame_callback(
1626                FrameCallbackKind::Perpetual,
1627                FrameCallback::Run(Box::new(callback)),
1628            )
1629        })
1630    }
1631
1632    /// Wakes the task of `waker` at the next frame, whose time it then reads
1633    /// with [`Self::frame_waker_time`].
1634    #[cfg(any(feature = "internal", test))]
1635    pub(crate) fn register_frame_waker(
1636        &self,
1637        kind: FrameCallbackKind,
1638        waker: Waker,
1639    ) -> Option<FrameCallbackId> {
1640        self.inner
1641            .upgrade()
1642            .map(|inner| inner.register_frame_callback(kind, FrameCallback::Wake(waker)))
1643    }
1644
1645    /// The frame time once the frame waker `id` has run; until then its
1646    /// entry wakes `waker`.
1647    #[cfg(any(feature = "internal", test))]
1648    pub(crate) fn frame_waker_time(&self, id: FrameCallbackId, waker: &Waker) -> Option<u64> {
1649        self.inner
1650            .upgrade()
1651            .and_then(|inner| inner.frame_waker_time(id, waker))
1652    }
1653
1654    /// Cancels the frame callback `id` if it has not run.
1655    pub fn cancel_frame_callback(&self, id: FrameCallbackId) {
1656        if let Some(inner) = self.inner.upgrade() {
1657            inner.cancel_frame_callback(id);
1658        }
1659    }
1660
1661    /// Delivers callbacks registered before this display frame. Timestamps use
1662    /// one monotonic nanosecond epoch; duplicate or older timestamps are ignored.
1663    /// A callback registered during delivery waits for a newer frame timestamp.
1664    pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
1665        if let Some(inner) = self.inner.upgrade() {
1666            inner.drain_frame_callbacks(frame_time_nanos);
1667        }
1668    }
1669
1670    /// Animation-clock time of the most recent frame-callback drain (see
1671    /// [`Runtime::last_frame_time_nanos`]).
1672    pub fn last_frame_time_nanos(&self) -> Option<u64> {
1673        self.inner
1674            .upgrade()
1675            .and_then(|inner| inner.last_frame_time_nanos.get())
1676    }
1677
1678    /// Returns a clock for the frames of this runtime.
1679    #[cfg(any(feature = "internal", test))]
1680    pub fn frame_clock(&self) -> FrameClock {
1681        FrameClock::new(self.clone())
1682    }
1683
1684    /// Sets or clears the request for a frame.
1685    pub fn set_needs_frame(&self, value: bool) {
1686        if let Some(inner) = self.inner.upgrade() {
1687            *inner.needs_frame.borrow_mut() = value;
1688        }
1689    }
1690
1691    pub(crate) fn take_updates(&self) -> Vec<Command> {
1692        self.inner
1693            .upgrade()
1694            .map(|inner| inner.take_updates())
1695            .unwrap_or_default()
1696    }
1697
1698    /// Returns `true` if node updates are queued or any recompose scope is invalid.
1699    pub fn has_updates(&self) -> bool {
1700        self.inner
1701            .upgrade()
1702            .is_some_and(|inner| inner.has_updates())
1703    }
1704
1705    /// The link a recompose scope keeps to this runtime.
1706    pub(crate) fn scope_runtime(&self) -> ScopeRuntime {
1707        ScopeRuntime(Weak::clone(&self.inner))
1708    }
1709
1710    pub(crate) fn requeue_invalid_scope(&self, scope: &RecomposeScope) {
1711        if let Some(inner) = self.inner.upgrade() {
1712            inner.requeue_invalid_scope(scope);
1713        }
1714    }
1715
1716    pub(crate) fn take_invalidated_scopes(&self) -> Option<Vec<RecomposeScope>> {
1717        self.inner
1718            .upgrade()
1719            .and_then(|inner| inner.take_invalidated_scopes())
1720    }
1721
1722    /// Gives back a scope list from [`Self::take_invalidated_scopes`], for
1723    /// the next pass to reuse its storage.
1724    pub(crate) fn recycle_scopes(&self, scopes: Vec<RecomposeScope>) {
1725        if let Some(inner) = self.inner.upgrade() {
1726            inner.recycle_scopes(scopes);
1727        }
1728    }
1729
1730    /// Releases the retained state of the movable content with identity
1731    /// `id` at the composition's next opportunity. See
1732    /// [`crate::forget_movable`].
1733    pub fn forget_movable(&self, id: Key) {
1734        if let Some(inner) = self.inner.upgrade() {
1735            inner.forgotten_movables.borrow_mut().push(id);
1736            inner.schedule();
1737        }
1738    }
1739
1740    /// An identity for a piece of movable content, unique within this
1741    /// runtime. Owned by the runtime instance rather than a process-wide
1742    /// counter, so two compositions in one process cannot collide and a
1743    /// test cannot be made to pass by the order it happens to run in.
1744    pub(crate) fn next_movable_content_id(&self) -> Key {
1745        let Some(inner) = self.inner.upgrade() else {
1746            log::error!("movable content asked a runtime that is gone for an identity");
1747            return 0;
1748        };
1749        let id = inner.next_movable_content_id.get();
1750        inner.next_movable_content_id.set(id.wrapping_add(1).max(1));
1751        id
1752    }
1753
1754    /// Returns `true` if a recompose scope has been invalidated and not yet recomposed.
1755    pub fn has_invalid_scopes(&self) -> bool {
1756        self.inner
1757            .upgrade()
1758            .is_some_and(|inner| inner.has_invalid_scopes())
1759    }
1760
1761    fn live_recompose_scope_count(&self) -> usize {
1762        self.inner
1763            .upgrade()
1764            .map(|inner| inner.live_recompose_scope_count())
1765            .unwrap_or_default()
1766    }
1767
1768    #[doc(hidden)]
1769    pub fn debug_invalid_scope_ids(&self) -> Vec<usize> {
1770        self.inner
1771            .upgrade()
1772            .map(|inner| inner.queued_invalid_scope_ids())
1773            .unwrap_or_default()
1774    }
1775
1776    /// Returns `true` if any frame callback, transient or perpetual, is waiting.
1777    pub fn has_frame_callbacks(&self) -> bool {
1778        self.inner
1779            .upgrade()
1780            .is_some_and(|inner| inner.has_frame_callbacks())
1781    }
1782
1783    /// Returns `true` if a callback from `register_frame_callback` is waiting. Perpetual callbacks
1784    /// do not count.
1785    pub fn has_transient_frame_callbacks(&self) -> bool {
1786        self.inner
1787            .upgrade()
1788            .is_some_and(|inner| inner.has_transient_frame_callbacks())
1789    }
1790
1791    /// Panics in debug builds when called off the runtime's UI thread.
1792    pub fn assert_ui_thread(&self) {
1793        debug_assert_eq!(
1794            std::thread::current().id(),
1795            self.ui_thread_id,
1796            "state mutated off the runtime's UI thread"
1797        );
1798    }
1799
1800    /// Returns a dispatcher that posts work to the UI thread from any thread.
1801    pub fn dispatcher(&self) -> UiDispatcher {
1802        self.dispatcher.clone()
1803    }
1804
1805    #[doc(hidden)]
1806    pub fn with_deferred_state_releases<R>(&self, f: impl FnOnce() -> R) -> R {
1807        let _scope = enter_state_teardown_scope();
1808        f()
1809    }
1810}
1811
1812impl TaskHandle {
1813    /// Cancels the task if it has not finished.
1814    pub fn cancel(&self) {
1815        self.runtime.cancel_task(self.id);
1816    }
1817
1818    /// Whether the spawned future has finished or been cancelled.
1819    pub fn is_finished(&self) -> bool {
1820        !self.runtime.has_task(self.id)
1821    }
1822}
1823
1824pub(crate) struct FrameCallbackEntry {
1825    id: FrameCallbackId,
1826    kind: FrameCallbackKind,
1827    callback: FrameCallback,
1828}
1829
1830enum FrameCallback {
1831    /// Runs with the frame time.
1832    Run(Box<dyn FnOnce(u64) + 'static>),
1833    /// Wakes a task awaiting the frame.
1834    #[cfg(any(feature = "internal", test))]
1835    Wake(Waker),
1836}
1837
1838#[cfg(not(target_arch = "wasm32"))]
1839struct RuntimeTaskWaker {
1840    scheduler: SchedulerRef,
1841    runnable: Arc<AtomicBool>,
1842    woken: Arc<AtomicBool>,
1843}
1844
1845#[cfg(target_arch = "wasm32")]
1846struct RuntimeTaskWaker {
1847    runtime_id: RuntimeId,
1848    runnable: Arc<AtomicBool>,
1849    woken: Arc<AtomicBool>,
1850}
1851
1852impl RuntimeTaskWaker {
1853    #[cfg(not(target_arch = "wasm32"))]
1854    fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1855        let scheduler = inner.scheduler.clone();
1856        Self {
1857            scheduler,
1858            runnable,
1859            woken: Arc::clone(&inner.task_woken),
1860        }
1861    }
1862
1863    #[cfg(target_arch = "wasm32")]
1864    fn new(inner: &RuntimeInner, runnable: Arc<AtomicBool>) -> Self {
1865        let runtime_id = inner.runtime_id;
1866        Self {
1867            runtime_id,
1868            runnable,
1869            woken: Arc::clone(&inner.task_woken),
1870        }
1871    }
1872
1873    fn into_waker(self) -> Waker {
1874        futures_task::waker(Arc::new(self))
1875    }
1876}
1877
1878impl futures_task::ArcWake for RuntimeTaskWaker {
1879    #[cfg(not(target_arch = "wasm32"))]
1880    fn wake_by_ref(arc_self: &Arc<Self>) {
1881        arc_self.runnable.store(true, Ordering::Release);
1882        arc_self.woken.store(true, Ordering::Release);
1883        arc_self.scheduler.schedule_frame();
1884    }
1885
1886    #[cfg(target_arch = "wasm32")]
1887    fn wake_by_ref(arc_self: &Arc<Self>) {
1888        arc_self.runnable.store(true, Ordering::Release);
1889        arc_self.woken.store(true, Ordering::Release);
1890        if let Some(handle) = runtime_handle_by_id(arc_self.runtime_id) {
1891            handle.schedule();
1892        }
1893    }
1894}
1895
1896/// Sizes of the runtime registries of one thread, for leak and memory checks. A `_len` field counts
1897/// entries and a `_cap` field the capacity reserved.
1898#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1899pub struct RuntimeThreadLocalDebugStats {
1900    /// Runtimes currently installed as active on this thread.
1901    pub active_runtimes_len: usize,
1902    /// Capacity reserved for the active runtimes.
1903    pub active_runtimes_cap: usize,
1904    /// Runtimes registered on this thread.
1905    pub registered_runtimes_len: usize,
1906    /// Capacity reserved for the registered runtimes.
1907    pub registered_runtimes_cap: usize,
1908    /// State releases waiting for the end of a teardown scope.
1909    pub deferred_state_releases_len: usize,
1910    /// Capacity reserved for the deferred state releases.
1911    pub deferred_state_releases_cap: usize,
1912}
1913
1914/// Gets the current runtime handle from thread-local storage.
1915///
1916/// Returns the most recently pushed active runtime, or the last known runtime.
1917/// Used by fling animation and other components that need access to the runtime.
1918pub fn current_runtime_handle() -> Option<RuntimeHandle> {
1919    thread_state::try_runtimes(|state| {
1920        if let Some(handle) = state.active_runtimes.borrow().last() {
1921            return Some(handle.clone());
1922        }
1923        state.last_runtime.borrow().clone()
1924    })
1925    .flatten()
1926}
1927
1928pub(crate) fn runtime_handle_by_id(id: RuntimeId) -> Option<RuntimeHandle> {
1929    with_registered_runtime(id, |handle| Some(handle.clone()))
1930}
1931
1932/// Runs `f` on this thread's handle of the runtime `id`: the active
1933/// runtime's, which most lookups name, without hashing the id, or else the
1934/// registered one.
1935fn with_registered_runtime<R>(
1936    id: RuntimeId,
1937    f: impl FnOnce(&RuntimeHandle) -> Option<R>,
1938) -> Option<R> {
1939    thread_state::try_runtimes(|state| {
1940        if let Some(handle) = state.active_runtimes.borrow().last()
1941            && handle.id == id
1942        {
1943            return f(handle);
1944        }
1945        state.registered_runtimes.borrow().get(&id).and_then(f)
1946    })
1947    .flatten()
1948}
1949
1950#[cfg(target_os = "android")]
1951pub(crate) fn share_rescue<'a>(
1952    handles: impl Iterator<Item = &'a RuntimeHandle>,
1953) -> Option<Rc<thread_state::RescuedThreadState>> {
1954    let live: Vec<Rc<RuntimeInner>> = handles
1955        .filter_map(|handle| handle.inner.upgrade())
1956        .collect();
1957    let shared = live
1958        .iter()
1959        .find_map(|inner| inner.rescued.borrow().clone())
1960        .or_else(|| (!live.is_empty()).then(Rc::default))?;
1961    for inner in &live {
1962        *inner.rescued.borrow_mut() = Some(Rc::clone(&shared));
1963    }
1964    Some(shared)
1965}
1966
1967pub(crate) fn with_state_arena_by_id<R>(
1968    id: RuntimeId,
1969    f: impl FnOnce(&StateArena) -> R,
1970) -> Option<R> {
1971    let inner = with_registered_runtime(id, |handle| handle.inner.upgrade())?;
1972    Some(f(&inner.state_arena))
1973}
1974
1975pub(crate) fn live_recompose_scope_count() -> usize {
1976    thread_state::runtimes(|state| {
1977        state
1978            .registered_runtimes
1979            .borrow()
1980            .values()
1981            .map(RuntimeHandle::live_recompose_scope_count)
1982            .sum()
1983    })
1984}
1985
1986/// Returns the sizes of the runtime registries of the current thread, for leak and memory checks.
1987pub fn debug_runtime_thread_local_stats() -> RuntimeThreadLocalDebugStats {
1988    thread_state::runtimes(|state| {
1989        let active = state.active_runtimes.borrow();
1990        let registered = state.registered_runtimes.borrow();
1991        let deferred = state.deferred_state_releases.borrow();
1992        RuntimeThreadLocalDebugStats {
1993            active_runtimes_len: active.len(),
1994            active_runtimes_cap: active.capacity(),
1995            registered_runtimes_len: registered.len(),
1996            registered_runtimes_cap: registered.capacity(),
1997            deferred_state_releases_len: deferred.len(),
1998            deferred_state_releases_cap: deferred.capacity(),
1999        }
2000    })
2001}
2002
2003fn defer_state_release(runtime: &RuntimeHandle, id: StateId) {
2004    let deferred = thread_state::try_runtimes(|state| {
2005        if state.state_teardown_depth.get() == 0 {
2006            return false;
2007        }
2008        state
2009            .deferred_state_releases
2010            .borrow_mut()
2011            .push(DeferredStateRelease {
2012                runtime: Weak::clone(&runtime.inner),
2013                id,
2014            });
2015        true
2016    });
2017    if deferred != Some(true) {
2018        release_state(&runtime.inner, id);
2019    }
2020}
2021
2022fn release_state(runtime: &Weak<RuntimeInner>, id: StateId) {
2023    if let Some(inner) = runtime.upgrade() {
2024        inner.state_arena.release(id);
2025    }
2026}
2027
2028pub(crate) struct StateTeardownScope;
2029
2030pub(crate) fn enter_state_teardown_scope() -> StateTeardownScope {
2031    thread_state::try_runtimes(|state| {
2032        let depth = &state.state_teardown_depth;
2033        depth.set(depth.get() + 1);
2034    });
2035    StateTeardownScope
2036}
2037
2038impl Drop for StateTeardownScope {
2039    fn drop(&mut self) {
2040        let finished = thread_state::try_runtimes(|state| {
2041            let depth = &state.state_teardown_depth;
2042            let next = depth.get().saturating_sub(1);
2043            depth.set(next);
2044            if next == 0 {
2045                let mut releases = state.deferred_state_releases.borrow_mut();
2046                while let Some(deferred) = releases.pop() {
2047                    release_state(&deferred.runtime, deferred.id);
2048                }
2049            }
2050            next == 0
2051        });
2052        if finished == Some(true) {
2053            thread_state::release_when_idle();
2054        }
2055    }
2056}
2057
2058pub(crate) fn push_active_runtime(handle: &RuntimeHandle) {
2059    thread_state::runtimes(|state| {
2060        // A runtime's handles are alike: the ones the thread holds stay.
2061        state
2062            .registered_runtimes
2063            .borrow_mut()
2064            .entry(handle.id())
2065            .or_insert_with(|| handle.clone());
2066        state.active_runtimes.borrow_mut().push(handle.clone());
2067        let mut last = state.last_runtime.borrow_mut();
2068        if last.as_ref().is_none_or(|last| last.id != handle.id) {
2069            *last = Some(handle.clone());
2070        }
2071    });
2072}
2073
2074pub(crate) fn pop_active_runtime() {
2075    thread_state::runtimes(|state| {
2076        state.active_runtimes.borrow_mut().pop();
2077    });
2078}
2079
2080/// Schedule a new frame render using the most recently active runtime handle.
2081pub fn schedule_frame() {
2082    if let Some(handle) = current_runtime_handle() {
2083        handle.schedule();
2084        return;
2085    }
2086    log::debug!(
2087        target: "cranpose::runtime",
2088        "ignoring frame request without an active runtime",
2089    );
2090}
2091
2092/// Schedule an in-place node update using the most recently active runtime.
2093pub fn schedule_node_update(
2094    update: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
2095) {
2096    if let Some(handle) = current_runtime_handle() {
2097        handle.enqueue_node_update(Command::callback(update));
2098    } else {
2099        drop(update);
2100        log::debug!(
2101            target: "cranpose::runtime",
2102            "ignoring node update request without an active runtime",
2103        );
2104    }
2105}
2106
2107#[cfg(test)]
2108#[path = "tests/runtime_tests.rs"]
2109mod tests;