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