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