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