1use crate::collections::map::HashMap;
2use crate::collections::map::HashSet;
3use crate::state::{MutationPolicy, NeverEqual};
4use crate::MutableStateInner;
5use std::any::Any;
6use std::cell::{Cell, RefCell};
7use std::collections::VecDeque;
8use std::future::Future;
9use std::pin::Pin;
10use std::rc::{Rc, Weak};
11use std::sync::atomic::{AtomicUsize, Ordering};
12use std::sync::{mpsc, Arc};
13use std::task::{Context, Poll, Waker};
14use std::thread::ThreadId;
15use std::thread_local;
16
17#[cfg(any(feature = "internal", test))]
18use crate::frame_clock::FrameClock;
19use crate::platform::RuntimeScheduler;
20use crate::{Applier, Command, FrameCallbackId, NodeError, RecomposeScopeInner, ScopeId};
21
22enum UiMessage {
23 Task(Box<dyn FnOnce() + Send + 'static>),
24 Invoke { id: u64, value: Box<dyn Any + Send> },
25}
26
27type UiContinuation = Box<dyn Fn(Box<dyn Any>) -> bool + 'static>;
28type UiContinuationMap = HashMap<u64, UiContinuation>;
29
30struct TypedStateCell<T: Clone + 'static> {
31 inner: MutableStateInner<T>,
32}
33
34trait ScopeWatchCell {
35 fn unregister_scope(&self, scope_id: ScopeId);
36}
37
38impl<T: Clone + 'static> ScopeWatchCell for TypedStateCell<T> {
39 fn unregister_scope(&self, scope_id: ScopeId) {
40 self.inner.unregister_scope(scope_id);
41 }
42}
43
44struct StateArenaSlot {
45 generation: u32,
46 cell: Option<Rc<dyn Any>>,
47 watcher_cell: Option<Rc<dyn ScopeWatchCell>>,
48 lease: Option<Weak<StateHandleLease>>,
49}
50
51#[derive(Default)]
52struct StateArenaInner {
53 cells: Vec<StateArenaSlot>,
54 free: Vec<u32>,
55}
56
57#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
58pub struct StateArenaDebugStats {
59 pub cells_len: usize,
60 pub cells_cap: usize,
61 pub free_len: usize,
62 pub free_cap: usize,
63}
64
65#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
66pub struct RuntimeDebugStats {
67 pub node_updates_len: usize,
68 pub node_updates_cap: usize,
69 pub invalid_scopes_len: usize,
70 pub invalid_scopes_cap: usize,
71 pub scope_queue_len: usize,
72 pub scope_queue_cap: usize,
73 pub frame_callbacks_len: usize,
74 pub frame_callbacks_cap: usize,
75 pub local_tasks_len: usize,
76 pub local_tasks_cap: usize,
77 pub ui_conts_len: usize,
78 pub ui_conts_cap: usize,
79 pub tasks_len: usize,
80 pub tasks_cap: usize,
81 pub external_state_owners_len: usize,
82 pub external_state_owners_cap: usize,
83 pub ui_dispatcher_pending: usize,
84}
85
86#[derive(Default)]
87pub(crate) struct StateArena {
88 inner: RefCell<StateArenaInner>,
89}
90
91impl StateArena {
92 pub(crate) fn alloc<T: Clone + 'static>(&self, value: T, runtime: RuntimeHandle) -> StateId {
93 self.alloc_with_policy(value, runtime, Arc::new(NeverEqual))
94 }
95
96 pub(crate) fn alloc_with_policy<T: Clone + 'static>(
97 &self,
98 value: T,
99 runtime: RuntimeHandle,
100 policy: Arc<dyn MutationPolicy<T>>,
101 ) -> StateId {
102 let (slot, generation) = {
103 let mut inner = self.inner.borrow_mut();
104 loop {
105 let Some(slot) = inner.free.pop() else {
106 let slot = inner.cells.len() as u32;
107 inner.cells.push(StateArenaSlot {
108 generation: 0,
109 cell: None,
110 watcher_cell: None,
111 lease: None,
112 });
113 break (slot, 0);
114 };
115
116 let Some(entry) = inner.cells.get_mut(slot as usize) else {
117 continue;
118 };
119 if entry.cell.is_some() {
120 continue;
121 }
122
123 entry.watcher_cell = None;
124 entry.lease = None;
125 entry.generation = entry.generation.wrapping_add(1);
126 break (slot, entry.generation);
127 }
128 };
129 let id = StateId::new(slot, generation);
130 let inner = MutableStateInner::new_with_policy(value, runtime.clone(), policy);
131 inner.install_snapshot_observer(id);
132 let typed_cell = Rc::new(TypedStateCell { inner });
133 let cell: Rc<dyn Any> = typed_cell.clone();
134 let watcher_cell: Rc<dyn ScopeWatchCell> = typed_cell;
135 let mut arena = self.inner.borrow_mut();
136 let slot_entry = &mut arena.cells[slot as usize];
137 slot_entry.cell = Some(cell);
138 slot_entry.watcher_cell = Some(watcher_cell);
139 id
140 }
141
142 fn get_cell_opt(&self, id: StateId) -> Option<Rc<dyn Any>> {
143 self.inner
144 .borrow()
145 .cells
146 .get(id.slot_index())
147 .filter(|cell| cell.generation == id.generation())
148 .and_then(|cell| cell.cell.as_ref())
149 .cloned()
150 }
151
152 fn get_typed<T: Clone + 'static>(&self, id: StateId) -> Rc<TypedStateCell<T>> {
153 match self.get_cell_opt(id) {
154 None => panic!(
155 "state cell missing: slot={}, gen={}, expected={}",
156 id.slot(),
157 id.generation(),
158 std::any::type_name::<T>(),
159 ),
160 Some(cell) => Rc::downcast::<TypedStateCell<T>>(cell).unwrap_or_else(|_| {
161 panic!(
162 "state cell type mismatch: slot={}, gen={}, expected={}",
163 id.slot(),
164 id.generation(),
165 std::any::type_name::<T>(),
166 )
167 }),
168 }
169 }
170
171 fn get_typed_opt<T: Clone + 'static>(&self, id: StateId) -> Option<Rc<TypedStateCell<T>>> {
172 Rc::downcast::<TypedStateCell<T>>(self.get_cell_opt(id)?).ok()
173 }
174
175 pub(crate) fn with_typed<T: Clone + 'static, R>(
176 &self,
177 id: StateId,
178 f: impl FnOnce(&MutableStateInner<T>) -> R,
179 ) -> R {
180 let cell = self.get_typed::<T>(id);
181 f(&cell.inner)
182 }
183
184 pub(crate) fn with_typed_opt<T: Clone + 'static, R>(
185 &self,
186 id: StateId,
187 f: impl FnOnce(&MutableStateInner<T>) -> R,
188 ) -> Option<R> {
189 let cell = self.get_typed_opt::<T>(id)?;
190 Some(f(&cell.inner))
191 }
192
193 pub(crate) fn release(&self, id: StateId) {
194 let cell = {
195 let mut inner = self.inner.borrow_mut();
196 let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
197 return;
198 };
199 if slot.generation != id.generation() {
200 return;
201 }
202 slot.lease = None;
203 slot.watcher_cell = None;
204 let cell = slot.cell.take();
205 if cell.is_some() {
206 inner.free.push(id.slot());
207 }
208 cell
209 };
210 drop(cell);
211 }
212
213 pub(crate) fn stats(&self) -> (usize, usize) {
214 let inner = self.inner.borrow();
215 (inner.cells.len(), inner.free.len())
216 }
217
218 pub(crate) fn debug_stats(&self) -> StateArenaDebugStats {
219 let inner = self.inner.borrow();
220 StateArenaDebugStats {
221 cells_len: inner.cells.len(),
222 cells_cap: inner.cells.capacity(),
223 free_len: inner.free.len(),
224 free_cap: inner.free.capacity(),
225 }
226 }
227
228 pub(crate) fn unregister_scope(&self, id: StateId, scope_id: ScopeId) {
229 let watcher_cell = {
230 let inner = self.inner.borrow();
231 inner
232 .cells
233 .get(id.slot_index())
234 .filter(|slot| slot.generation == id.generation())
235 .and_then(|slot| slot.watcher_cell.as_ref())
236 .cloned()
237 };
238 if let Some(watcher_cell) = watcher_cell {
239 watcher_cell.unregister_scope(scope_id);
240 }
241 }
242
243 pub(crate) fn register_lease(&self, id: StateId, lease: &Rc<StateHandleLease>) {
244 let mut inner = self.inner.borrow_mut();
245 let Some(slot) = inner.cells.get_mut(id.slot_index()) else {
246 return;
247 };
248 if slot.generation != id.generation() {
249 return;
250 }
251 slot.lease = Some(Rc::downgrade(lease));
252 }
253
254 pub(crate) fn retain_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
255 let inner = self.inner.borrow();
256 let slot = inner.cells.get(id.slot_index())?;
257 if slot.generation != id.generation() {
258 return None;
259 }
260 slot.lease.as_ref()?.upgrade()
261 }
262}
263
264#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
265pub struct StateId {
266 slot: u32,
267 generation: u32,
268}
269
270impl StateId {
271 const fn new(slot: u32, generation: u32) -> Self {
272 Self { slot, generation }
273 }
274
275 pub(crate) const fn slot(self) -> u32 {
276 self.slot
277 }
278
279 pub(crate) const fn slot_index(self) -> usize {
280 self.slot as usize
281 }
282
283 pub(crate) const fn generation(self) -> u32 {
284 self.generation
285 }
286}
287
288#[derive(Copy, Clone, Debug, PartialEq, Eq, Hash)]
289pub struct RuntimeId(u32);
290
291impl RuntimeId {
292 fn next() -> Self {
293 NEXT_RUNTIME_ID.with(|next| {
294 let id = next.get();
295 next.set(id.wrapping_add(1));
296 Self(id)
297 })
298 }
299}
300
301struct UiDispatcherInner {
302 scheduler: Arc<dyn RuntimeScheduler>,
303 tx: mpsc::Sender<UiMessage>,
304 pending: AtomicUsize,
305}
306
307impl UiDispatcherInner {
308 fn new(scheduler: Arc<dyn RuntimeScheduler>, tx: mpsc::Sender<UiMessage>) -> Self {
309 Self {
310 scheduler,
311 tx,
312 pending: AtomicUsize::new(0),
313 }
314 }
315
316 fn post(&self, task: impl FnOnce() + Send + 'static) {
317 self.pending.fetch_add(1, Ordering::SeqCst);
318 if self.tx.send(UiMessage::Task(Box::new(task))).is_ok() {
319 self.scheduler.schedule_frame();
320 } else {
321 self.pending.fetch_sub(1, Ordering::SeqCst);
322 }
323 }
324
325 fn post_invoke(&self, id: u64, value: Box<dyn Any + Send>) {
326 self.pending.fetch_add(1, Ordering::SeqCst);
327 if self.tx.send(UiMessage::Invoke { id, value }).is_ok() {
328 self.scheduler.schedule_frame();
329 } else {
330 self.pending.fetch_sub(1, Ordering::SeqCst);
331 }
332 }
333
334 fn has_pending(&self) -> bool {
335 self.pending.load(Ordering::SeqCst) > 0
336 }
337}
338
339struct PendingGuard<'a> {
340 counter: &'a AtomicUsize,
341}
342
343impl<'a> PendingGuard<'a> {
344 fn new(counter: &'a AtomicUsize) -> Self {
345 Self { counter }
346 }
347}
348
349impl<'a> Drop for PendingGuard<'a> {
350 fn drop(&mut self) {
351 let mut current = self.counter.load(Ordering::SeqCst);
352 loop {
353 if current == 0 {
354 return;
355 }
356 match self.counter.compare_exchange(
357 current,
358 current - 1,
359 Ordering::SeqCst,
360 Ordering::SeqCst,
361 ) {
362 Ok(_) => return,
363 Err(next) => current = next,
364 }
365 }
366 }
367}
368
369#[derive(Clone)]
370pub struct UiDispatcher {
371 inner: Arc<UiDispatcherInner>,
372}
373
374impl UiDispatcher {
375 fn new(inner: Arc<UiDispatcherInner>) -> Self {
376 Self { inner }
377 }
378
379 pub fn post(&self, task: impl FnOnce() + Send + 'static) {
380 self.inner.post(task);
381 }
382
383 pub fn post_invoke<T>(&self, id: u64, value: T)
384 where
385 T: Send + 'static,
386 {
387 self.inner.post_invoke(id, Box::new(value));
388 }
389
390 pub fn has_pending(&self) -> bool {
391 self.inner.has_pending()
392 }
393}
394
395struct RuntimeInner {
396 scheduler: Arc<dyn RuntimeScheduler>,
397 needs_frame: RefCell<bool>,
398 node_updates: RefCell<Vec<Command>>,
399 invalid_scopes: RefCell<HashSet<ScopeId>>,
400 scope_queue: RefCell<Vec<(ScopeId, Weak<RecomposeScopeInner>)>>,
401 frame_callbacks: RefCell<VecDeque<FrameCallbackEntry>>,
402 next_frame_callback_id: Cell<u64>,
403 last_frame_time_nanos: Cell<Option<u64>>,
404 ui_dispatcher: Arc<UiDispatcherInner>,
405 ui_rx: RefCell<mpsc::Receiver<UiMessage>>,
406 local_tasks: RefCell<VecDeque<Box<dyn FnOnce() + 'static>>>,
407 ui_conts: RefCell<UiContinuationMap>,
408 next_cont_id: Cell<u64>,
409 ui_thread_id: ThreadId,
410 tasks: RefCell<Vec<TaskEntry>>,
411 next_task_id: Cell<u64>,
412 task_waker: RefCell<Option<Waker>>,
413 state_arena: StateArena,
414 external_state_owners: RefCell<HashMap<StateId, Rc<StateHandleLease>>>,
415 live_recompose_scope_count: Cell<usize>,
416 runtime_id: RuntimeId,
417}
418
419struct TaskEntry {
420 id: u64,
421 future: Pin<Box<dyn Future<Output = ()> + 'static>>,
422}
423
424impl RuntimeInner {
425 fn new(scheduler: Arc<dyn RuntimeScheduler>) -> Self {
426 let (tx, rx) = mpsc::channel();
427 let dispatcher = Arc::new(UiDispatcherInner::new(scheduler.clone(), tx));
428 Self {
429 scheduler,
430 needs_frame: RefCell::new(false),
431 node_updates: RefCell::new(Vec::new()),
432 invalid_scopes: RefCell::new(HashSet::default()),
433 scope_queue: RefCell::new(Vec::new()),
434 frame_callbacks: RefCell::new(VecDeque::new()),
435 next_frame_callback_id: Cell::new(1),
436 last_frame_time_nanos: Cell::new(None),
437 ui_dispatcher: dispatcher,
438 ui_rx: RefCell::new(rx),
439 local_tasks: RefCell::new(VecDeque::new()),
440 ui_conts: RefCell::new(UiContinuationMap::default()),
441 next_cont_id: Cell::new(1),
442 ui_thread_id: std::thread::current().id(),
443 tasks: RefCell::new(Vec::new()),
444 next_task_id: Cell::new(1),
445 task_waker: RefCell::new(None),
446 state_arena: StateArena::default(),
447 external_state_owners: RefCell::new(HashMap::default()),
448 live_recompose_scope_count: Cell::new(0),
449 runtime_id: RuntimeId::next(),
450 }
451 }
452
453 fn init_task_waker(this: &Rc<Self>) {
454 let waker = RuntimeTaskWaker::new(this).into_waker();
455 *this.task_waker.borrow_mut() = Some(waker);
456 }
457
458 fn schedule(&self) {
459 *self.needs_frame.borrow_mut() = true;
460 self.scheduler.schedule_frame();
461 }
462
463 fn enqueue_update(&self, command: Command) {
464 self.node_updates.borrow_mut().push(command);
465 self.schedule(); }
467
468 fn take_updates(&self) -> Vec<Command> {
469 let updates = self.node_updates.borrow_mut().drain(..).collect::<Vec<_>>();
470 updates
471 }
472
473 fn has_updates(&self) -> bool {
474 !self.node_updates.borrow().is_empty() || self.has_invalid_scopes()
475 }
476
477 fn register_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
478 let mut invalid = self.invalid_scopes.borrow_mut();
479 if invalid.insert(id) {
480 self.scope_queue.borrow_mut().push((id, scope));
481 self.schedule();
482 }
483 }
484
485 fn requeue_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
486 if self.invalid_scopes.borrow().contains(&id) {
487 self.scope_queue.borrow_mut().push((id, scope));
488 self.schedule();
489 }
490 }
491
492 fn mark_scope_recomposed(&self, id: ScopeId) {
493 self.invalid_scopes.borrow_mut().remove(&id);
494 }
495
496 fn take_invalidated_scopes(&self) -> Vec<(ScopeId, Weak<RecomposeScopeInner>)> {
497 let mut queue = self.scope_queue.borrow_mut();
498 if queue.is_empty() {
499 return Vec::new();
500 }
501 let pending: Vec<_> = queue.drain(..).collect();
502 drop(queue);
503 let invalid = self.invalid_scopes.borrow();
504 pending
505 .into_iter()
506 .filter(|(id, _)| invalid.contains(id))
507 .collect()
508 }
509
510 fn has_invalid_scopes(&self) -> bool {
511 !self.invalid_scopes.borrow().is_empty()
512 }
513
514 fn increment_live_recompose_scope_count(&self) {
515 self.live_recompose_scope_count
516 .set(self.live_recompose_scope_count.get().saturating_add(1));
517 }
518
519 fn decrement_live_recompose_scope_count(&self) {
520 self.live_recompose_scope_count
521 .set(self.live_recompose_scope_count.get().saturating_sub(1));
522 }
523
524 fn live_recompose_scope_count(&self) -> usize {
525 self.live_recompose_scope_count.get()
526 }
527
528 fn has_frame_callbacks(&self) -> bool {
529 !self.frame_callbacks.borrow().is_empty()
530 }
531
532 fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
537 self.local_tasks.borrow_mut().push_back(task);
538 self.schedule();
539 }
540
541 fn spawn_ui_task(&self, future: Pin<Box<dyn Future<Output = ()> + 'static>>) -> u64 {
542 let id = self.next_task_id.get();
543 self.next_task_id.set(id + 1);
544 self.tasks.borrow_mut().push(TaskEntry { id, future });
545 self.schedule();
546 id
547 }
548
549 fn cancel_task(&self, id: u64) {
550 let mut tasks = self.tasks.borrow_mut();
551 if tasks.iter().any(|entry| entry.id == id) {
552 tasks.retain(|entry| entry.id != id);
553 }
554 }
555
556 fn poll_async_tasks(&self) -> bool {
557 let waker = match self.task_waker.borrow().as_ref() {
558 Some(waker) => waker.clone(),
559 None => return false,
560 };
561 let mut cx = Context::from_waker(&waker);
562 let mut tasks_ref = self.tasks.borrow_mut();
563 let tasks = std::mem::take(&mut *tasks_ref);
564 drop(tasks_ref);
565 let mut pending = Vec::with_capacity(tasks.len());
566 let mut made_progress = false;
567 for mut entry in tasks.into_iter() {
568 match entry.future.as_mut().poll(&mut cx) {
569 Poll::Ready(()) => {
570 made_progress = true;
571 }
572 Poll::Pending => {
573 pending.push(entry);
574 }
575 }
576 }
577 if !pending.is_empty() {
578 self.tasks.borrow_mut().extend(pending);
579 }
580 made_progress
581 }
582
583 fn drain_ui(&self) {
584 loop {
585 let mut executed = false;
586
587 {
588 let rx = &mut *self.ui_rx.borrow_mut();
589 for message in rx.try_iter() {
590 executed = true;
591 let _guard = PendingGuard::new(&self.ui_dispatcher.pending);
592 match message {
593 UiMessage::Task(task) => {
594 task();
595 }
596 UiMessage::Invoke { id, value } => {
597 self.invoke_ui_cont(id, value);
598 }
599 }
600 }
601 }
602
603 loop {
604 let task = {
605 let mut local = self.local_tasks.borrow_mut();
606 local.pop_front()
607 };
608
609 match task {
610 Some(task) => {
611 executed = true;
612 task();
613 }
614 None => break,
615 }
616 }
617
618 if self.poll_async_tasks() {
619 executed = true;
620 }
621
622 if !executed {
623 break;
624 }
625 }
626 }
627
628 fn has_pending_ui(&self) -> bool {
629 let local_pending = self
630 .local_tasks
631 .try_borrow()
632 .map(|tasks| !tasks.is_empty())
633 .unwrap_or(true);
634
635 let async_pending = self
636 .tasks
637 .try_borrow()
638 .map(|tasks| !tasks.is_empty())
639 .unwrap_or(true);
640
641 local_pending || self.ui_dispatcher.has_pending() || async_pending
642 }
643
644 fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> u64 {
645 debug_assert_eq!(
646 std::thread::current().id(),
647 self.ui_thread_id,
648 "UI continuation registered off the runtime thread",
649 );
650 let id = self.next_cont_id.get();
651 self.next_cont_id.set(id + 1);
652 let callback = RefCell::new(Some(f));
653 self.ui_conts.borrow_mut().insert(
654 id,
655 Box::new(move |value: Box<dyn Any>| {
656 let Ok(value) = value.downcast::<T>() else {
657 return false;
658 };
659 let Some(slot) = callback.borrow_mut().take() else {
660 return true;
661 };
662 slot(*value);
663 true
664 }),
665 );
666 id
667 }
668
669 fn invoke_ui_cont(&self, id: u64, value: Box<dyn Any + Send>) {
670 debug_assert_eq!(
671 std::thread::current().id(),
672 self.ui_thread_id,
673 "UI continuation invoked off the runtime thread",
674 );
675 let callback = { self.ui_conts.borrow_mut().remove(&id) };
676 if let Some(callback) = callback {
677 let value: Box<dyn Any> = value;
678 if !callback(value) {
679 self.ui_conts.borrow_mut().insert(id, callback);
680 }
681 }
682 }
683
684 fn cancel_ui_cont(&self, id: u64) {
685 self.ui_conts.borrow_mut().remove(&id);
686 }
687
688 fn register_frame_callback(&self, callback: Box<dyn FnOnce(u64) + 'static>) -> FrameCallbackId {
689 let id = self.next_frame_callback_id.get();
690 self.next_frame_callback_id.set(id + 1);
691 self.frame_callbacks
692 .borrow_mut()
693 .push_back(FrameCallbackEntry {
694 id,
695 callback: Some(callback),
696 });
697 self.schedule();
698 id
699 }
700
701 fn cancel_frame_callback(&self, id: FrameCallbackId) {
702 let mut callbacks = self.frame_callbacks.borrow_mut();
703 if let Some(index) = callbacks.iter().position(|entry| entry.id == id) {
704 callbacks.remove(index);
705 }
706 let callbacks_empty = callbacks.is_empty();
707 drop(callbacks);
708 let local_pending = self
709 .local_tasks
710 .try_borrow()
711 .map(|tasks| !tasks.is_empty())
712 .unwrap_or(true);
713 let async_pending = self
714 .tasks
715 .try_borrow()
716 .map(|tasks| !tasks.is_empty())
717 .unwrap_or(true);
718 if !self.has_invalid_scopes()
719 && !self.has_updates()
720 && callbacks_empty
721 && !local_pending
722 && !self.ui_dispatcher.has_pending()
723 && !async_pending
724 {
725 *self.needs_frame.borrow_mut() = false;
726 }
727 }
728
729 fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
730 self.last_frame_time_nanos.set(Some(frame_time_nanos));
731 let mut callbacks = self.frame_callbacks.borrow_mut();
732 let mut pending: Vec<Box<dyn FnOnce(u64) + 'static>> = Vec::with_capacity(callbacks.len());
733 while let Some(mut entry) = callbacks.pop_front() {
734 if let Some(callback) = entry.callback.take() {
735 pending.push(callback);
736 }
737 }
738 drop(callbacks);
739
740 if !pending.is_empty() {
745 let _ = crate::run_in_mutable_snapshot(|| {
746 for callback in pending {
747 callback(frame_time_nanos);
748 }
749 });
750 }
751
752 if !self.has_invalid_scopes()
753 && !self.has_updates()
754 && !self.has_frame_callbacks()
755 && !self.has_pending_ui()
756 {
757 *self.needs_frame.borrow_mut() = false;
758 }
759 }
760
761 fn debug_stats(&self) -> RuntimeDebugStats {
762 let node_updates = self.node_updates.borrow();
763 let invalid_scopes = self.invalid_scopes.borrow();
764 let scope_queue = self.scope_queue.borrow();
765 let frame_callbacks = self.frame_callbacks.borrow();
766 let local_tasks = self.local_tasks.borrow();
767 let ui_conts = self.ui_conts.borrow();
768 let tasks = self.tasks.borrow();
769 let external_state_owners = self.external_state_owners.borrow();
770
771 RuntimeDebugStats {
772 node_updates_len: node_updates.len(),
773 node_updates_cap: node_updates.capacity(),
774 invalid_scopes_len: invalid_scopes.len(),
775 invalid_scopes_cap: invalid_scopes.capacity(),
776 scope_queue_len: scope_queue.len(),
777 scope_queue_cap: scope_queue.capacity(),
778 frame_callbacks_len: frame_callbacks.len(),
779 frame_callbacks_cap: frame_callbacks.capacity(),
780 local_tasks_len: local_tasks.len(),
781 local_tasks_cap: local_tasks.capacity(),
782 ui_conts_len: ui_conts.len(),
783 ui_conts_cap: ui_conts.capacity(),
784 tasks_len: tasks.len(),
785 tasks_cap: tasks.capacity(),
786 external_state_owners_len: external_state_owners.len(),
787 external_state_owners_cap: external_state_owners.capacity(),
788 ui_dispatcher_pending: self.ui_dispatcher.pending.load(Ordering::SeqCst),
789 }
790 }
791}
792
793#[derive(Clone)]
794pub struct Runtime {
795 inner: Rc<RuntimeInner>,
796}
797
798impl Runtime {
799 pub fn new(scheduler: Arc<dyn RuntimeScheduler>) -> Self {
800 let inner = Rc::new(RuntimeInner::new(scheduler));
801 RuntimeInner::init_task_waker(&inner);
802 let runtime = Self { inner };
803 let handle = runtime.handle();
804 register_runtime_handle(&handle);
805 LAST_RUNTIME.with(|slot| *slot.borrow_mut() = Some(handle));
806 runtime
807 }
808
809 pub fn handle(&self) -> RuntimeHandle {
810 RuntimeHandle {
811 inner: Rc::downgrade(&self.inner),
812 dispatcher: UiDispatcher::new(self.inner.ui_dispatcher.clone()),
813 ui_thread_id: self.inner.ui_thread_id,
814 id: self.inner.runtime_id,
815 }
816 }
817
818 pub fn has_updates(&self) -> bool {
819 self.inner.has_updates()
820 }
821
822 pub fn needs_frame(&self) -> bool {
823 *self.inner.needs_frame.borrow()
824 }
825
826 pub fn set_needs_frame(&self, value: bool) {
827 *self.inner.needs_frame.borrow_mut() = value;
828 }
829
830 pub fn last_frame_time_nanos(&self) -> Option<u64> {
835 self.inner.last_frame_time_nanos.get()
836 }
837
838 #[cfg(any(feature = "internal", test))]
839 pub fn frame_clock(&self) -> FrameClock {
840 FrameClock::new(self.handle())
841 }
842}
843
844impl Drop for Runtime {
845 fn drop(&mut self) {
846 if Rc::strong_count(&self.inner) != 1 {
847 return;
848 }
849 unregister_runtime_handle(self.inner.runtime_id);
850 LAST_RUNTIME.with(|slot| {
851 let should_clear = slot
852 .borrow()
853 .as_ref()
854 .is_some_and(|handle| handle.id() == self.inner.runtime_id);
855 if should_clear {
856 *slot.borrow_mut() = None;
857 }
858 });
859 }
860}
861
862#[derive(Default)]
863pub struct DefaultScheduler;
864
865impl RuntimeScheduler for DefaultScheduler {
866 fn schedule_frame(&self) {}
867}
868
869#[cfg(test)]
870#[derive(Default)]
871pub struct TestScheduler;
872
873#[cfg(test)]
874impl RuntimeScheduler for TestScheduler {
875 fn schedule_frame(&self) {}
876}
877
878#[cfg(test)]
879pub struct TestRuntime {
880 runtime: Runtime,
881}
882
883#[cfg(test)]
884impl Default for TestRuntime {
885 fn default() -> Self {
886 Self::new()
887 }
888}
889
890#[cfg(test)]
891impl TestRuntime {
892 pub fn new() -> Self {
893 Self {
894 runtime: Runtime::new(Arc::new(TestScheduler)),
895 }
896 }
897
898 pub fn handle(&self) -> RuntimeHandle {
899 self.runtime.handle()
900 }
901}
902
903#[derive(Clone)]
904pub struct RuntimeHandle {
905 inner: Weak<RuntimeInner>,
906 dispatcher: UiDispatcher,
907 ui_thread_id: ThreadId,
908 id: RuntimeId,
909}
910
911pub struct TaskHandle {
912 id: u64,
913 runtime: RuntimeHandle,
914}
915
916struct DeferredStateRelease {
917 runtime: RuntimeHandle,
918 id: StateId,
919}
920
921pub(crate) struct StateHandleLease {
922 id: StateId,
923 runtime: RuntimeHandle,
924}
925
926impl StateHandleLease {
927 pub(crate) fn id(&self) -> StateId {
928 self.id
929 }
930
931 pub(crate) fn runtime(&self) -> RuntimeHandle {
932 self.runtime.clone()
933 }
934}
935
936impl Drop for StateHandleLease {
937 fn drop(&mut self) {
938 defer_state_release(self.runtime.clone(), self.id);
939 }
940}
941
942impl RuntimeHandle {
943 pub fn id(&self) -> RuntimeId {
944 self.id
945 }
946
947 pub(crate) fn alloc_state<T: Clone + 'static>(&self, value: T) -> Rc<StateHandleLease> {
948 let id = self.with_state_arena(|arena| arena.alloc(value, self.clone()));
949 let lease = Rc::new(StateHandleLease {
950 id,
951 runtime: self.clone(),
952 });
953 self.with_state_arena(|arena| arena.register_lease(id, &lease));
954 lease
955 }
956
957 pub(crate) fn alloc_state_with_policy<T: Clone + 'static>(
958 &self,
959 value: T,
960 policy: Arc<dyn MutationPolicy<T>>,
961 ) -> Rc<StateHandleLease> {
962 let id =
963 self.with_state_arena(|arena| arena.alloc_with_policy(value, self.clone(), policy));
964 let lease = Rc::new(StateHandleLease {
965 id,
966 runtime: self.clone(),
967 });
968 self.with_state_arena(|arena| arena.register_lease(id, &lease));
969 lease
970 }
971
972 pub(crate) fn alloc_persistent_state<T: Clone + 'static>(
973 &self,
974 value: T,
975 ) -> crate::MutableState<T> {
976 let lease = self.alloc_state(value);
977 if let Some(inner) = self.inner.upgrade() {
978 inner
979 .external_state_owners
980 .borrow_mut()
981 .insert(lease.id(), Rc::clone(&lease));
982 }
983 crate::MutableState::from_lease(&lease)
984 }
985
986 pub(crate) fn retain_state_lease(&self, id: StateId) -> Option<Rc<StateHandleLease>> {
987 self.with_state_arena(|arena| arena.retain_lease(id))
988 }
989
990 pub(crate) fn with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> R {
991 self.try_with_state_arena(f)
992 .unwrap_or_else(|| panic!("runtime dropped"))
993 }
994
995 pub(crate) fn try_with_state_arena<R>(&self, f: impl FnOnce(&StateArena) -> R) -> Option<R> {
996 self.inner.upgrade().map(|inner| f(&inner.state_arena))
997 }
998
999 fn release_state_immediate(&self, id: StateId) {
1000 if let Some(inner) = self.inner.upgrade() {
1001 inner.state_arena.release(id);
1002 }
1003 }
1004
1005 pub fn state_arena_stats(&self) -> (usize, usize) {
1006 self.try_with_state_arena(StateArena::stats)
1007 .unwrap_or_default()
1008 }
1009
1010 pub fn state_arena_debug_stats(&self) -> StateArenaDebugStats {
1011 self.try_with_state_arena(StateArena::debug_stats)
1012 .unwrap_or_default()
1013 }
1014
1015 pub fn debug_stats(&self) -> RuntimeDebugStats {
1016 self.inner
1017 .upgrade()
1018 .map(|inner| inner.debug_stats())
1019 .unwrap_or_default()
1020 }
1021
1022 pub(crate) fn unregister_state_scope(&self, id: StateId, scope_id: ScopeId) {
1023 if let Some(inner) = self.inner.upgrade() {
1024 inner.state_arena.unregister_scope(id, scope_id);
1025 }
1026 }
1027
1028 pub fn schedule(&self) {
1029 if let Some(inner) = self.inner.upgrade() {
1030 inner.schedule();
1031 }
1032 }
1033
1034 pub(crate) fn enqueue_node_update(&self, command: Command) {
1035 if let Some(inner) = self.inner.upgrade() {
1036 inner.enqueue_update(command);
1037 }
1038 }
1039
1040 pub fn enqueue_ui_task(&self, task: Box<dyn FnOnce() + 'static>) {
1047 if let Some(inner) = self.inner.upgrade() {
1048 inner.enqueue_ui_task(task);
1049 } else {
1050 task();
1051 }
1052 }
1053
1054 pub fn spawn_ui<F>(&self, fut: F) -> Option<TaskHandle>
1055 where
1056 F: Future<Output = ()> + 'static,
1057 {
1058 self.inner.upgrade().map(|inner| {
1059 let id = inner.spawn_ui_task(Box::pin(fut));
1060 TaskHandle {
1061 id,
1062 runtime: self.clone(),
1063 }
1064 })
1065 }
1066
1067 pub fn cancel_task(&self, id: u64) {
1068 if let Some(inner) = self.inner.upgrade() {
1069 inner.cancel_task(id);
1070 }
1071 }
1072
1073 pub fn post_ui(&self, task: impl FnOnce() + Send + 'static) {
1078 self.dispatcher.post(task);
1079 }
1080
1081 pub fn register_ui_cont<T: 'static>(&self, f: impl FnOnce(T) + 'static) -> Option<u64> {
1082 self.inner.upgrade().map(|inner| inner.register_ui_cont(f))
1083 }
1084
1085 pub fn cancel_ui_cont(&self, id: u64) {
1086 if let Some(inner) = self.inner.upgrade() {
1087 inner.cancel_ui_cont(id);
1088 }
1089 }
1090
1091 pub fn drain_ui(&self) {
1092 if let Some(inner) = self.inner.upgrade() {
1093 inner.drain_ui();
1094 }
1095 }
1096
1097 pub fn has_pending_ui(&self) -> bool {
1098 self.inner
1099 .upgrade()
1100 .map(|inner| inner.has_pending_ui())
1101 .unwrap_or_else(|| self.dispatcher.has_pending())
1102 }
1103
1104 pub fn register_frame_callback(
1105 &self,
1106 callback: impl FnOnce(u64) + 'static,
1107 ) -> Option<FrameCallbackId> {
1108 self.inner
1109 .upgrade()
1110 .map(|inner| inner.register_frame_callback(Box::new(callback)))
1111 }
1112
1113 pub fn cancel_frame_callback(&self, id: FrameCallbackId) {
1114 if let Some(inner) = self.inner.upgrade() {
1115 inner.cancel_frame_callback(id);
1116 }
1117 }
1118
1119 pub fn drain_frame_callbacks(&self, frame_time_nanos: u64) {
1120 if let Some(inner) = self.inner.upgrade() {
1121 inner.drain_frame_callbacks(frame_time_nanos);
1122 }
1123 }
1124
1125 pub fn last_frame_time_nanos(&self) -> Option<u64> {
1128 self.inner
1129 .upgrade()
1130 .and_then(|inner| inner.last_frame_time_nanos.get())
1131 }
1132
1133 #[cfg(any(feature = "internal", test))]
1134 pub fn frame_clock(&self) -> FrameClock {
1135 FrameClock::new(self.clone())
1136 }
1137
1138 pub fn set_needs_frame(&self, value: bool) {
1139 if let Some(inner) = self.inner.upgrade() {
1140 *inner.needs_frame.borrow_mut() = value;
1141 }
1142 }
1143
1144 pub(crate) fn take_updates(&self) -> Vec<Command> {
1145 self.inner
1146 .upgrade()
1147 .map(|inner| inner.take_updates())
1148 .unwrap_or_default()
1149 }
1150
1151 pub fn has_updates(&self) -> bool {
1152 self.inner
1153 .upgrade()
1154 .map(|inner| inner.has_updates())
1155 .unwrap_or(false)
1156 }
1157
1158 pub(crate) fn mark_scope_recomposed(&self, id: ScopeId) {
1159 if let Some(inner) = self.inner.upgrade() {
1160 inner.mark_scope_recomposed(id);
1161 }
1162 }
1163
1164 pub(crate) fn register_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
1165 if let Some(inner) = self.inner.upgrade() {
1166 inner.register_invalid_scope(id, scope);
1167 }
1168 }
1169
1170 pub(crate) fn requeue_invalid_scope(&self, id: ScopeId, scope: Weak<RecomposeScopeInner>) {
1171 if let Some(inner) = self.inner.upgrade() {
1172 inner.requeue_invalid_scope(id, scope);
1173 }
1174 }
1175
1176 pub(crate) fn take_invalidated_scopes(&self) -> Vec<(ScopeId, Weak<RecomposeScopeInner>)> {
1177 self.inner
1178 .upgrade()
1179 .map(|inner| inner.take_invalidated_scopes())
1180 .unwrap_or_default()
1181 }
1182
1183 pub fn has_invalid_scopes(&self) -> bool {
1184 self.inner
1185 .upgrade()
1186 .map(|inner| inner.has_invalid_scopes())
1187 .unwrap_or(false)
1188 }
1189
1190 pub(crate) fn increment_live_recompose_scope_count(&self) {
1191 if let Some(inner) = self.inner.upgrade() {
1192 inner.increment_live_recompose_scope_count();
1193 }
1194 }
1195
1196 pub(crate) fn decrement_live_recompose_scope_count(&self) {
1197 if let Some(inner) = self.inner.upgrade() {
1198 inner.decrement_live_recompose_scope_count();
1199 }
1200 }
1201
1202 fn live_recompose_scope_count(&self) -> usize {
1203 self.inner
1204 .upgrade()
1205 .map(|inner| inner.live_recompose_scope_count())
1206 .unwrap_or_default()
1207 }
1208
1209 #[doc(hidden)]
1210 pub fn debug_invalid_scope_ids(&self) -> Vec<usize> {
1211 self.inner
1212 .upgrade()
1213 .map(|inner| inner.invalid_scopes.borrow().iter().copied().collect())
1214 .unwrap_or_default()
1215 }
1216
1217 pub fn has_frame_callbacks(&self) -> bool {
1218 self.inner
1219 .upgrade()
1220 .map(|inner| inner.has_frame_callbacks())
1221 .unwrap_or(false)
1222 }
1223
1224 pub fn assert_ui_thread(&self) {
1225 debug_assert_eq!(
1226 std::thread::current().id(),
1227 self.ui_thread_id,
1228 "state mutated off the runtime's UI thread"
1229 );
1230 }
1231
1232 pub fn dispatcher(&self) -> UiDispatcher {
1233 self.dispatcher.clone()
1234 }
1235
1236 #[doc(hidden)]
1237 pub fn with_deferred_state_releases<R>(&self, f: impl FnOnce() -> R) -> R {
1238 let _scope = enter_state_teardown_scope();
1239 f()
1240 }
1241}
1242
1243impl TaskHandle {
1244 pub fn cancel(self) {
1245 self.runtime.cancel_task(self.id);
1246 }
1247}
1248
1249pub(crate) struct FrameCallbackEntry {
1250 id: FrameCallbackId,
1251 callback: Option<Box<dyn FnOnce(u64) + 'static>>,
1252}
1253
1254#[cfg(not(target_arch = "wasm32"))]
1255struct RuntimeTaskWaker {
1256 scheduler: Arc<dyn RuntimeScheduler>,
1257}
1258
1259#[cfg(target_arch = "wasm32")]
1260struct RuntimeTaskWaker {
1261 runtime_id: RuntimeId,
1262}
1263
1264impl RuntimeTaskWaker {
1265 #[cfg(not(target_arch = "wasm32"))]
1266 fn new(inner: &RuntimeInner) -> Self {
1267 let scheduler = inner.scheduler.clone();
1268 Self { scheduler }
1269 }
1270
1271 #[cfg(target_arch = "wasm32")]
1272 fn new(inner: &RuntimeInner) -> Self {
1273 let runtime_id = inner.runtime_id;
1274 Self { runtime_id }
1275 }
1276
1277 fn into_waker(self) -> Waker {
1278 futures_task::waker(Arc::new(self))
1279 }
1280}
1281
1282impl futures_task::ArcWake for RuntimeTaskWaker {
1283 #[cfg(not(target_arch = "wasm32"))]
1284 fn wake_by_ref(arc_self: &Arc<Self>) {
1285 arc_self.scheduler.schedule_frame();
1286 }
1287
1288 #[cfg(target_arch = "wasm32")]
1289 fn wake_by_ref(arc_self: &Arc<Self>) {
1290 REGISTERED_RUNTIMES.with(|registry| {
1291 if let Some(handle) = registry.borrow().get(&arc_self.runtime_id).cloned() {
1292 handle.schedule();
1293 }
1294 });
1295 }
1296}
1297
1298thread_local! {
1299 static NEXT_RUNTIME_ID: Cell<u32> = const { Cell::new(1) };
1300 static ACTIVE_RUNTIMES: RefCell<Vec<RuntimeHandle>> = const { RefCell::new(Vec::new()) };
1301 static LAST_RUNTIME: RefCell<Option<RuntimeHandle>> = const { RefCell::new(None) };
1302 static REGISTERED_RUNTIMES: RefCell<HashMap<RuntimeId, RuntimeHandle>> = RefCell::new(HashMap::default());
1303 static STATE_TEARDOWN_DEPTH: Cell<usize> = const { Cell::new(0) };
1304 static DEFERRED_STATE_RELEASES: RefCell<Vec<DeferredStateRelease>> = const { RefCell::new(Vec::new()) };
1305}
1306
1307#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
1308pub struct RuntimeThreadLocalDebugStats {
1309 pub active_runtimes_len: usize,
1310 pub active_runtimes_cap: usize,
1311 pub registered_runtimes_len: usize,
1312 pub registered_runtimes_cap: usize,
1313 pub deferred_state_releases_len: usize,
1314 pub deferred_state_releases_cap: usize,
1315}
1316
1317pub fn current_runtime_handle() -> Option<RuntimeHandle> {
1322 if let Some(handle) = ACTIVE_RUNTIMES.with(|stack| stack.borrow().last().cloned()) {
1323 return Some(handle);
1324 }
1325 LAST_RUNTIME.with(|slot| slot.borrow().clone())
1326}
1327
1328pub(crate) fn runtime_handle_by_id(id: RuntimeId) -> Option<RuntimeHandle> {
1329 REGISTERED_RUNTIMES.with(|registry| registry.borrow().get(&id).cloned())
1330}
1331
1332pub(crate) fn live_recompose_scope_count() -> usize {
1333 REGISTERED_RUNTIMES.with(|registry| {
1334 registry
1335 .borrow()
1336 .values()
1337 .map(RuntimeHandle::live_recompose_scope_count)
1338 .sum()
1339 })
1340}
1341
1342pub fn debug_runtime_thread_local_stats() -> RuntimeThreadLocalDebugStats {
1343 let (active_runtimes_len, active_runtimes_cap) = ACTIVE_RUNTIMES.with(|stack| {
1344 let stack = stack.borrow();
1345 (stack.len(), stack.capacity())
1346 });
1347 let (registered_runtimes_len, registered_runtimes_cap) = REGISTERED_RUNTIMES.with(|registry| {
1348 let registry = registry.borrow();
1349 (registry.len(), registry.capacity())
1350 });
1351 let (deferred_state_releases_len, deferred_state_releases_cap) =
1352 DEFERRED_STATE_RELEASES.with(|releases| {
1353 let releases = releases.borrow();
1354 (releases.len(), releases.capacity())
1355 });
1356
1357 RuntimeThreadLocalDebugStats {
1358 active_runtimes_len,
1359 active_runtimes_cap,
1360 registered_runtimes_len,
1361 registered_runtimes_cap,
1362 deferred_state_releases_len,
1363 deferred_state_releases_cap,
1364 }
1365}
1366
1367fn register_runtime_handle(handle: &RuntimeHandle) {
1368 REGISTERED_RUNTIMES.with(|registry| {
1369 registry.borrow_mut().insert(handle.id(), handle.clone());
1370 });
1371}
1372
1373fn unregister_runtime_handle(id: RuntimeId) {
1374 REGISTERED_RUNTIMES.with(|registry| {
1375 registry.borrow_mut().remove(&id);
1376 });
1377}
1378
1379fn defer_state_release(runtime: RuntimeHandle, id: StateId) {
1380 let teardown_active = STATE_TEARDOWN_DEPTH.with(|depth| depth.get() > 0);
1381 if teardown_active {
1382 DEFERRED_STATE_RELEASES.with(|releases| {
1383 releases
1384 .borrow_mut()
1385 .push(DeferredStateRelease { runtime, id });
1386 });
1387 } else {
1388 runtime.release_state_immediate(id);
1389 }
1390}
1391
1392fn flush_deferred_state_releases() {
1393 DEFERRED_STATE_RELEASES.with(|releases| {
1394 let mut releases = releases.borrow_mut();
1395 while let Some(deferred) = releases.pop() {
1396 deferred.runtime.release_state_immediate(deferred.id);
1397 }
1398 });
1399}
1400
1401pub(crate) struct StateTeardownScope;
1402
1403pub(crate) fn enter_state_teardown_scope() -> StateTeardownScope {
1404 STATE_TEARDOWN_DEPTH.with(|depth| depth.set(depth.get() + 1));
1405 StateTeardownScope
1406}
1407
1408impl Drop for StateTeardownScope {
1409 fn drop(&mut self) {
1410 STATE_TEARDOWN_DEPTH.with(|depth| {
1411 let next = depth.get().saturating_sub(1);
1412 depth.set(next);
1413 if next == 0 {
1414 flush_deferred_state_releases();
1415 }
1416 });
1417 }
1418}
1419
1420pub(crate) fn push_active_runtime(handle: &RuntimeHandle) {
1421 register_runtime_handle(handle);
1422 ACTIVE_RUNTIMES.with(|stack| stack.borrow_mut().push(handle.clone()));
1423 LAST_RUNTIME.with(|slot| *slot.borrow_mut() = Some(handle.clone()));
1424}
1425
1426pub(crate) fn pop_active_runtime() {
1427 ACTIVE_RUNTIMES.with(|stack| {
1428 stack.borrow_mut().pop();
1429 });
1430}
1431
1432pub fn schedule_frame() {
1434 if let Some(handle) = current_runtime_handle() {
1435 handle.schedule();
1436 return;
1437 }
1438 log::debug!(
1439 target: "cranpose::runtime",
1440 "ignoring frame request without an active runtime",
1441 );
1442}
1443
1444pub fn schedule_node_update(
1446 update: impl FnOnce(&mut dyn Applier) -> Result<(), NodeError> + 'static,
1447) {
1448 if let Some(handle) = current_runtime_handle() {
1449 handle.enqueue_node_update(Command::callback(update));
1450 } else {
1451 drop(update);
1452 log::debug!(
1453 target: "cranpose::runtime",
1454 "ignoring node update request without an active runtime",
1455 );
1456 }
1457}
1458
1459#[cfg(test)]
1460mod tests {
1461 use super::*;
1462
1463 #[test]
1464 fn state_arena_alloc_skips_free_slot_outside_cell_storage() {
1465 let runtime = TestRuntime::new();
1466 let arena = StateArena::default();
1467 let first = arena.alloc(1_i32, runtime.handle());
1468 arena.inner.borrow_mut().free.push(u32::MAX);
1469
1470 let second = arena.alloc(2_i32, runtime.handle());
1471
1472 assert_eq!(first.slot(), 0);
1473 assert_eq!(second.slot(), 1);
1474 assert!(arena.get_typed_opt::<i32>(first).is_some());
1475 assert!(arena.get_typed_opt::<i32>(second).is_some());
1476 }
1477
1478 #[test]
1479 fn state_arena_alloc_skips_occupied_free_slot() {
1480 let runtime = TestRuntime::new();
1481 let arena = StateArena::default();
1482 let first = arena.alloc(1_i32, runtime.handle());
1483 arena.inner.borrow_mut().free.push(first.slot());
1484
1485 let second = arena.alloc(2_i32, runtime.handle());
1486
1487 assert_ne!(first.slot(), second.slot());
1488 assert_eq!(second.slot(), 1);
1489 assert!(arena.get_typed_opt::<i32>(first).is_some());
1490 assert!(arena.get_typed_opt::<i32>(second).is_some());
1491 }
1492
1493 #[test]
1494 fn state_arena_register_lease_ignores_stale_id() {
1495 let runtime = TestRuntime::new();
1496 let arena = StateArena::default();
1497 let stale_id = StateId::new(99, 0);
1498 let lease = Rc::new(StateHandleLease {
1499 id: stale_id,
1500 runtime: runtime.handle(),
1501 });
1502
1503 arena.register_lease(stale_id, &lease);
1504
1505 assert!(arena.retain_lease(stale_id).is_none());
1506 }
1507
1508 #[test]
1509 fn ui_continuation_type_mismatch_is_ignored_until_matching_payload() {
1510 let runtime = TestRuntime::new();
1511 let handle = runtime.handle();
1512 let received = Rc::new(Cell::new(None));
1513 let received_for_continuation = Rc::clone(&received);
1514 let cont_id = handle
1515 .register_ui_cont(move |value: u32| {
1516 received_for_continuation.set(Some(value));
1517 })
1518 .expect("test runtime is alive");
1519
1520 handle.dispatcher().post_invoke(cont_id, "wrong payload");
1521 handle.drain_ui();
1522
1523 assert_eq!(received.get(), None);
1524 assert_eq!(handle.debug_stats().ui_conts_len, 1);
1525
1526 handle.dispatcher().post_invoke(cont_id, 42_u32);
1527 handle.drain_ui();
1528
1529 assert_eq!(received.get(), Some(42));
1530 assert_eq!(handle.debug_stats().ui_conts_len, 0);
1531 }
1532
1533 #[test]
1534 fn ui_dispatcher_failed_send_does_not_leave_pending_work() {
1535 let runtime = Runtime::new(Arc::new(TestScheduler));
1536 let dispatcher = runtime.handle().dispatcher();
1537
1538 drop(runtime);
1539
1540 assert!(!dispatcher.has_pending());
1541 dispatcher.post(|| {});
1542 assert!(!dispatcher.has_pending());
1543 dispatcher.post_invoke(404, 12_u32);
1544 assert!(!dispatcher.has_pending());
1545 }
1546
1547 #[test]
1548 fn pending_guard_does_not_wrap_on_underflow() {
1549 let counter = AtomicUsize::new(0);
1550
1551 {
1552 let _guard = PendingGuard::new(&counter);
1553 }
1554
1555 assert_eq!(counter.load(Ordering::SeqCst), 0);
1556 }
1557
1558 #[test]
1559 fn pending_guard_decrements_pending_count() {
1560 let counter = AtomicUsize::new(2);
1561
1562 {
1563 let _guard = PendingGuard::new(&counter);
1564 }
1565
1566 assert_eq!(counter.load(Ordering::SeqCst), 1);
1567 }
1568
1569 #[test]
1570 fn schedule_frame_without_runtime_is_ignored() {
1571 let ok = std::thread::spawn(|| std::panic::catch_unwind(super::schedule_frame).is_ok())
1572 .join()
1573 .expect("test thread should join");
1574
1575 assert!(ok);
1576 }
1577
1578 #[test]
1579 fn schedule_node_update_without_runtime_is_ignored() {
1580 let ok = std::thread::spawn(|| {
1581 std::panic::catch_unwind(|| {
1582 super::schedule_node_update(|_| Ok(()));
1583 })
1584 .is_ok()
1585 })
1586 .join()
1587 .expect("test thread should join");
1588
1589 assert!(ok);
1590 }
1591}