Skip to main content

cranpose_core/
runtime.rs

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