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