1use std::{
2 cell::{Cell, RefCell},
3 hash::{Hash, Hasher},
4 rc::{Rc, Weak},
5};
6
7use smallvec::SmallVec;
8
9#[cfg(feature = "inspection")]
10use crate::source_trace;
11use crate::{
12 AnchorId, Composer, LocalStackSnapshot, NodeId, NodeIdCell, PackedU64, RetentionMode,
13 RuntimeHandle, ScopeId, SlotsHost, StateId, collections::map::HashSet, composer, runtime,
14 thread_state,
15};
16
17#[derive(Clone, Copy, PartialEq, Eq)]
19pub(crate) enum BodyRun {
20 Ran,
21 Skipped,
22}
23
24impl From<bool> for BodyRun {
25 fn from(ran: bool) -> Self {
26 if ran { Self::Ran } else { Self::Skipped }
27 }
28}
29
30fn scope_activity_epoch() -> u64 {
31 thread_state::cells(|cells| cells.scope_activity_epoch.get())
32}
33
34fn note_scope_activity_change() {
35 thread_state::cells(|cells| {
36 let epoch = &cells.scope_activity_epoch;
37 epoch.set(epoch.get() + 1);
38 });
39}
40
41fn read_cell<T: Default, R>(cell: &Cell<T>, read: impl FnOnce(&T) -> R) -> R {
46 let value = cell.take();
47 let result = read(&value);
48 cell.set(value);
49 result
50}
51
52fn update_cell<T: Default, R>(cell: &Cell<T>, update: impl FnOnce(&mut T) -> R) -> R {
54 let mut value = cell.take();
55 let result = update(&mut value);
56 cell.set(value);
57 result
58}
59
60#[derive(Clone, Copy, PartialEq, Eq)]
62enum OwnerKind {
63 Parent,
65 Lifetime,
68}
69
70type OwnerLink = Cell<Option<(OwnerKind, Weak<RecomposeScopeInner>)>>;
72
73type RecomposeClosure = Box<dyn FnMut(&Composer)>;
75
76#[derive(Clone, Copy)]
78enum ScopeFlag {
79 Invalid,
80 Enqueued,
81 Active,
82 ComposedOnce,
83 PendingRecompose,
84 Recomposing,
88 ForceReuse,
89 ForceRecompose,
90 Derivation,
91 UnknownInvalidationSource,
93 Paused,
95 Pending,
98}
99
100impl ScopeFlag {
101 const fn bit(self) -> u16 {
102 match self {
103 Self::Invalid => 1,
104 Self::Enqueued => 1 << 1,
105 Self::Active => 1 << 2,
106 Self::ComposedOnce => 1 << 3,
107 Self::PendingRecompose => 1 << 4,
108 Self::Recomposing => 1 << 5,
109 Self::ForceReuse => 1 << 6,
110 Self::ForceRecompose => 1 << 7,
111 Self::Derivation => 1 << 8,
112 Self::UnknownInvalidationSource => 1 << 9,
113 Self::Paused => 1 << 10,
114 Self::Pending => 1 << 11,
115 }
116 }
117}
118
119struct ScopeFlags(Cell<u16>);
122
123impl ScopeFlags {
124 fn get(&self, flag: ScopeFlag) -> bool {
125 self.0.get() & flag.bit() != 0
126 }
127
128 fn set(&self, flag: ScopeFlag, on: bool) {
129 let bits = self.0.get();
130 self.0.set(if on {
131 bits | flag.bit()
132 } else {
133 bits & !flag.bit()
134 });
135 }
136
137 fn either(&self, a: ScopeFlag, b: ScopeFlag) -> bool {
139 self.0.get() & (a.bit() | b.bit()) != 0
140 }
141
142 fn replace(&self, flag: ScopeFlag, on: bool) -> bool {
144 let was = self.get(flag);
145 self.set(flag, on);
146 was
147 }
148}
149
150pub(crate) struct RecomposeScopeInner {
151 runtime: runtime::ScopeRuntime,
152 flags: ScopeFlags,
153 active_epoch: Cell<PackedU64>,
154 deactivations: Cell<PackedU64>,
155 retention_mode: Cell<RetentionMode>,
156 parent_hint: NodeIdCell,
157 group_anchor: Cell<AnchorId>,
158 recompose: Cell<Option<fn(&Composer)>>,
162 recompose_closure: Cell<Option<RecomposeClosure>>,
163 owner: OwnerLink,
164 local_stack: Cell<LocalStackSnapshot>,
165 #[cfg(feature = "inspection")]
166 source_trace: RefCell<Rc<[source_trace::SourceLocation]>>,
167 #[cfg(all(feature = "inspection", debug_assertions))]
168 pub(crate) recompositions: std::cell::OnceCell<source_trace::RecompositionCounter>,
169 slots_storage_key: Cell<usize>,
170 slots_runtime_state: Cell<Option<Weak<composer::ComposerRuntimeState>>>,
171 state_subscriptions: RefCell<StateIds>,
174 run_reads: Cell<Option<Box<StateIds>>>,
177 read_cursor: Cell<u32>,
180 invalidation_sources: Cell<StateIds>,
181}
182
183type StateIds = SmallVec<[StateId; 2]>;
184
185pub(crate) enum ScopeRead {
187 Watched,
189 Collected,
192 NotReading,
194}
195
196const NOT_READING: u32 = u32::MAX;
198const DIVERGED: u32 = u32::MAX - 1;
201
202enum ScopeOwner {
203 Root,
204 Live(Rc<RecomposeScopeInner>),
205 Dropped,
206}
207
208impl RecomposeScopeInner {
209 fn owner(&self) -> ScopeOwner {
210 read_cell(&self.owner, |owner| {
211 owner.as_ref().map_or(ScopeOwner::Root, |(_, owner)| {
212 owner
213 .upgrade()
214 .map_or(ScopeOwner::Dropped, ScopeOwner::Live)
215 })
216 })
217 }
218
219 fn is_effectively_active(&self, epoch: u64) -> bool {
220 if self.active_epoch.get().get() == epoch {
221 return true;
222 }
223 if !self.flags.get(ScopeFlag::Active) {
224 return false;
225 }
226 let active = match self.owner() {
227 ScopeOwner::Root => true,
228 ScopeOwner::Dropped => false,
229 ScopeOwner::Live(owner) => owner.is_effectively_active(epoch),
230 };
231 if active {
232 self.active_epoch.set(PackedU64::new(epoch));
233 }
234 active
235 }
236
237 fn new(runtime: runtime::ScopeRuntime) -> Self {
238 runtime.increment_live_recompose_scope_count();
239 Self {
240 runtime,
241 flags: ScopeFlags(Cell::new(ScopeFlag::Active.bit())),
242 active_epoch: Cell::default(),
243 deactivations: Cell::default(),
244 retention_mode: Cell::new(RetentionMode::DisposeWhenInactive),
245 parent_hint: NodeIdCell::new(None),
246 group_anchor: Cell::new(AnchorId::INVALID),
247 recompose: Cell::new(None),
248 recompose_closure: Cell::new(None),
249 owner: Cell::new(None),
250 local_stack: Cell::new(None),
251 #[cfg(feature = "inspection")]
252 source_trace: RefCell::new(Rc::from([])),
253 #[cfg(all(feature = "inspection", debug_assertions))]
254 recompositions: std::cell::OnceCell::new(),
255 slots_storage_key: Cell::new(0),
256 slots_runtime_state: Cell::new(None),
257 state_subscriptions: RefCell::new(StateIds::new()),
258 run_reads: Cell::new(None),
259 read_cursor: Cell::new(NOT_READING),
260 invalidation_sources: Cell::new(StateIds::new()),
261 }
262 }
263
264 fn id(&self) -> ScopeId {
265 std::ptr::from_ref(self).addr()
266 }
267}
268
269fn push_unique_state_id(ids: &mut StateIds, state_id: StateId) {
270 if !ids.contains(&state_id) {
271 ids.push(state_id);
272 }
273}
274
275impl Drop for RecomposeScopeInner {
276 fn drop(&mut self) {
277 note_scope_activity_change();
278 let id = self.id();
279 self.runtime.decrement_live_recompose_scope_count();
280 let subscriptions = std::mem::take(self.state_subscriptions.get_mut());
281 let run_reads = self.run_reads.take().map(|reads| *reads);
282 for state_id in subscriptions
283 .into_iter()
284 .chain(run_reads.into_iter().flatten())
285 {
286 self.runtime.unregister_state_scope(state_id, id);
287 }
288 #[cfg(debug_assertions)]
289 {
290 thread_state::try_runtimes(|state| {
291 state.scope_labels.borrow_mut().remove(&id);
292 state.scope_invalidation_sources.borrow_mut().remove(&id);
293 });
294 }
295 if self.flags.replace(ScopeFlag::Enqueued, false) {
296 self.runtime.mark_scope_recomposed();
297 }
298 }
299}
300
301#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
303pub struct RecomposeScopeRegistryDebugStats {
304 pub len: usize,
306 pub capacity: usize,
308}
309
310#[derive(Clone)]
313pub struct RecomposeScope {
314 pub(crate) inner: Rc<RecomposeScopeInner>,
315}
316
317impl PartialEq for RecomposeScope {
318 fn eq(&self, other: &Self) -> bool {
319 Rc::ptr_eq(&self.inner, &other.inner)
320 }
321}
322
323impl Eq for RecomposeScope {}
324
325impl Hash for RecomposeScope {
326 fn hash<H: Hasher>(&self, state: &mut H) {
327 self.id().hash(state);
328 }
329}
330
331impl RecomposeScope {
332 pub(crate) fn mark_derivation(&self) {
336 self.inner.flags.set(ScopeFlag::Derivation, true);
337 }
338
339 pub(crate) fn is_derivation(&self) -> bool {
340 self.inner.flags.get(ScopeFlag::Derivation)
341 }
342
343 pub(crate) fn new(runtime: RuntimeHandle) -> Self {
344 Self {
345 inner: Rc::new(RecomposeScopeInner::new(runtime.scope_runtime())),
346 }
347 }
348
349 pub(crate) fn downgrade(&self) -> Weak<RecomposeScopeInner> {
350 Rc::downgrade(&self.inner)
351 }
352
353 pub(crate) fn upgrade(weak: &Weak<RecomposeScopeInner>) -> Option<Self> {
354 weak.upgrade().map(|inner| Self { inner })
355 }
356
357 pub fn id(&self) -> ScopeId {
359 self.inner.id()
360 }
361
362 pub fn is_invalid(&self) -> bool {
364 self.inner.flags.get(ScopeFlag::Invalid)
365 }
366
367 pub(crate) fn is_enqueued(&self) -> bool {
368 self.inner.flags.get(ScopeFlag::Enqueued)
369 }
370
371 pub fn is_active(&self) -> bool {
373 self.inner.flags.get(ScopeFlag::Active)
374 }
375
376 pub fn owner_chain_deactivation_epoch(&self) -> u64 {
383 let mut total = self.inner.deactivations.get().get();
384 let mut owner = self.inner.owner();
385 while let ScopeOwner::Live(scope) = owner {
386 total = total.wrapping_add(scope.deactivations.get().get());
387 owner = scope.owner();
388 }
389 total
390 }
391
392 pub(crate) fn is_effectively_active(&self) -> bool {
393 self.inner.is_effectively_active(scope_activity_epoch())
394 }
395
396 pub(crate) fn record_state_subscription(&self, state_id: StateId) {
397 push_unique_state_id(&mut self.inner.state_subscriptions.borrow_mut(), state_id);
398 }
399
400 pub(crate) fn begin_reads(&self) {
402 self.inner.read_cursor.set(0);
403 }
404
405 #[inline]
411 pub(crate) fn note_read(&self, state_id: StateId) -> ScopeRead {
412 let cursor = self.inner.read_cursor.get();
413 if cursor == NOT_READING {
414 return ScopeRead::NotReading;
415 }
416 if cursor == DIVERGED {
417 update_cell(&self.inner.run_reads, |reads| {
418 if let Some(reads) = reads
419 && reads.last() != Some(&state_id)
420 {
421 reads.push(state_id);
422 }
423 });
424 return ScopeRead::Collected;
425 }
426 let at = cursor as usize;
427 let subscribed = self.inner.state_subscriptions.borrow();
428 if subscribed.get(at) == Some(&state_id) {
429 self.inner.read_cursor.set(cursor + 1);
430 return ScopeRead::Watched;
431 }
432 if at > 0 && subscribed[at - 1] == state_id {
433 return ScopeRead::Watched;
434 }
435 let mut reads = StateIds::from_slice(&subscribed[..at]);
436 reads.push(state_id);
437 self.inner.run_reads.set(Some(Box::new(reads)));
438 self.inner.read_cursor.set(DIVERGED);
439 ScopeRead::Collected
440 }
441
442 pub(crate) fn keep_reads(&self) {
445 self.inner.read_cursor.set(NOT_READING);
446 }
447
448 #[inline]
452 pub(crate) fn end_reads(&self, run: BodyRun) {
453 let ran = run == BodyRun::Ran;
454 let cursor = self.inner.read_cursor.replace(NOT_READING);
455 if cursor == NOT_READING || (!ran && cursor != DIVERGED) {
458 return;
459 }
460 self.settle_reads(cursor, ran);
461 }
462
463 #[inline(never)]
466 fn settle_reads(&self, cursor: u32, ran: bool) {
467 let id = self.id();
468 let mut subscribed = self.inner.state_subscriptions.borrow_mut();
469 if cursor != DIVERGED {
470 let read = cursor as usize;
472 if ran && read < subscribed.len() {
473 for &state_id in &subscribed[read..] {
474 if !subscribed[..read].contains(&state_id) {
475 self.inner.runtime.forget_state_scope(state_id, id);
476 }
477 }
478 subscribed.truncate(read);
479 }
480 return;
481 }
482 let Some(reads) = self.inner.run_reads.take() else {
483 return;
484 };
485 if ran {
486 for &state_id in subscribed.iter() {
487 if !reads.contains(&state_id) {
488 self.inner.runtime.forget_state_scope(state_id, id);
489 }
490 }
491 *subscribed = *reads;
492 } else {
493 for state_id in *reads {
494 push_unique_state_id(&mut subscribed, state_id);
495 }
496 }
497 }
498
499 fn record_unknown_invalidation_source(&self) {
500 self.inner
501 .flags
502 .set(ScopeFlag::UnknownInvalidationSource, true);
503 update_cell(&self.inner.invalidation_sources, StateIds::clear);
504 }
505
506 fn record_state_invalidation_source(&self, state_id: StateId) {
507 if !self.inner.flags.get(ScopeFlag::UnknownInvalidationSource) {
508 update_cell(&self.inner.invalidation_sources, |ids| {
509 push_unique_state_id(ids, state_id);
510 });
511 }
512 }
513
514 fn enqueue_invalidation(&self) {
515 self.inner.flags.set(ScopeFlag::Invalid, true);
516 if self.is_effectively_active() && self.may_queue() {
517 self.queue_recompose();
518 }
519 }
520
521 fn may_queue(&self) -> bool {
524 !self
525 .inner
526 .flags
527 .either(ScopeFlag::Enqueued, ScopeFlag::Pending)
528 }
529
530 fn queue_recompose(&self) {
531 self.inner.flags.set(ScopeFlag::Enqueued, true);
532 self.inner.runtime.register_invalid_scope(self.downgrade());
533 }
534
535 pub(crate) fn invalidate(&self) {
536 self.record_unknown_invalidation_source();
537 self.enqueue_invalidation();
538 }
539
540 pub(crate) fn invalidate_from_state(&self, state_id: StateId) {
541 self.record_state_invalidation_source(state_id);
542 if self.inner.read_cursor.get() != NOT_READING {
543 self.note_running_write(state_id);
544 }
545 self.enqueue_invalidation();
546 }
547
548 #[inline(never)]
553 fn note_running_write(&self, state_id: StateId) {
554 let cursor = self.inner.read_cursor.get();
555 let read = if cursor == DIVERGED {
556 read_cell(&self.inner.run_reads, |reads| {
557 reads
558 .as_ref()
559 .is_some_and(|reads| reads.contains(&state_id))
560 })
561 } else {
562 self.inner.state_subscriptions.borrow()[..cursor as usize].contains(&state_id)
563 };
564 if read {
565 self.request_pending_recompose();
566 }
567 }
568
569 pub(crate) fn mark_recomposed(&self) {
570 if self.inner.flags.replace(ScopeFlag::Invalid, false) {
573 self.inner
574 .flags
575 .set(ScopeFlag::UnknownInvalidationSource, false);
576 update_cell(&self.inner.invalidation_sources, StateIds::clear);
577 }
578 self.inner.flags.set(ScopeFlag::ForceReuse, false);
579 self.inner.flags.set(ScopeFlag::ForceRecompose, false);
580 if self.inner.flags.replace(ScopeFlag::Enqueued, false) {
581 self.inner.runtime.mark_scope_recomposed();
582 }
583 let pending = self.inner.flags.replace(ScopeFlag::PendingRecompose, false);
584 if pending {
585 if self.inner.flags.get(ScopeFlag::Active) {
586 self.invalidate();
587 } else {
588 self.inner.flags.set(ScopeFlag::Invalid, true);
589 }
590 }
591 }
592
593 pub(crate) fn set_recompose_callback<F>(&self, callback: F)
596 where
597 F: FnMut(&Composer) + 'static,
598 {
599 self.note_recompose_source();
600 self.inner.recompose.set(None);
601 self.inner.recompose_closure.set(Some(Box::new(callback)));
602 }
603
604 #[inline]
607 pub(crate) fn set_recompose_fn(&self, body: fn(&Composer)) {
608 let same = self
609 .inner
610 .recompose
611 .get()
612 .is_some_and(|current| std::ptr::fn_addr_eq(current, body));
613 if !same {
614 self.set_recompose(body);
615 }
616 }
617
618 #[inline(never)]
619 fn set_recompose(&self, body: fn(&Composer)) {
620 self.note_recompose_source();
621 self.inner.recompose.set(Some(body));
622 self.inner.recompose_closure.set(None);
623 }
624
625 fn note_recompose_source(&self) {
626 #[cfg(feature = "inspection")]
627 self.inner
628 .source_trace
629 .replace(source_trace::current_source_trace());
630 }
631
632 #[inline(never)]
634 pub(crate) fn run_recompose(&self, composer: &Composer) -> bool {
635 #[cfg(feature = "inspection")]
636 let _source_context = source_trace::restore_source_trace(&self.inner.source_trace.borrow());
637 let Some(body) = self.inner.recompose.get() else {
638 return self.run_recompose_closure(composer);
639 };
640 self.begin_reads();
641 body(composer);
642 self.end_reads(BodyRun::Ran);
643 true
644 }
645
646 #[inline(never)]
647 fn run_recompose_closure(&self, composer: &Composer) -> bool {
648 let Some(mut closure) = self.inner.recompose_closure.take() else {
649 return false;
650 };
651 self.begin_reads();
652 self.inner.flags.set(ScopeFlag::Recomposing, true);
653 closure(composer);
654 self.inner.flags.set(ScopeFlag::Recomposing, false);
655 self.end_reads(BodyRun::Ran);
656 update_cell(&self.inner.recompose_closure, |slot| {
658 if slot.is_none() {
659 *slot = Some(closure);
660 }
661 });
662 true
663 }
664
665 pub(crate) fn has_recompose_callback(&self) -> bool {
666 self.inner.recompose.get().is_some()
667 || self.inner.flags.get(ScopeFlag::Recomposing)
668 || read_cell(&self.inner.recompose_closure, Option::is_some)
669 }
670
671 pub(crate) fn depth(&self) -> usize {
673 let mut depth = 0;
674 let mut current = self.parent_scope();
675 while let Some(scope) = current {
676 depth += 1;
677 current = scope.parent_scope();
678 }
679 depth
680 }
681
682 pub(crate) fn snapshot_locals(&self, stack: &LocalStackSnapshot) {
683 update_cell(&self.inner.local_stack, |locals| {
684 let unchanged = match (&*locals, stack) {
685 (Some(current), Some(stack)) => Rc::ptr_eq(current, stack),
686 (current, stack) => current.is_none() && stack.is_none(),
687 };
688 if !unchanged {
689 locals.clone_from(stack);
690 }
691 });
692 }
693
694 pub(crate) fn local_stack(&self) -> LocalStackSnapshot {
695 read_cell(&self.inner.local_stack, Clone::clone)
696 }
697
698 pub(crate) fn set_parent_hint(&self, parent: Option<NodeId>) {
699 self.inner.parent_hint.set(parent);
700 }
701
702 fn set_owner(&self, kind: OwnerKind, owner: Option<&Self>) {
704 update_cell(&self.inner.owner, |link| {
705 let current = link
706 .as_ref()
707 .map(|(kind, owner)| (*kind, Weak::as_ptr(owner)));
708 if current != owner.map(|owner| (kind, Rc::as_ptr(&owner.inner))) {
709 *link = owner.map(|owner| (kind, owner.downgrade()));
710 note_scope_activity_change();
711 }
712 });
713 }
714
715 pub(crate) fn set_parent_scope(&self, parent: Option<&Self>) {
717 self.set_owner(OwnerKind::Parent, parent);
718 }
719
720 fn owner_of_kind(&self, kind: OwnerKind) -> Option<Self> {
721 read_cell(&self.inner.owner, |owner| {
722 owner
723 .as_ref()
724 .filter(|(owner_kind, _)| *owner_kind == kind)
725 .and_then(|(_, owner)| Self::upgrade(owner))
726 })
727 }
728
729 pub(crate) fn parent_scope(&self) -> Option<Self> {
730 self.owner_of_kind(OwnerKind::Parent)
731 }
732
733 pub(crate) fn set_lifetime_owner_scope(&self, owner: Option<&Self>) {
736 self.set_owner(OwnerKind::Lifetime, owner);
737 }
738
739 #[cfg(test)]
740 pub(crate) fn lifetime_owner_scope(&self) -> Option<Self> {
741 self.owner_of_kind(OwnerKind::Lifetime)
742 }
743
744 pub(crate) fn callback_promotion_target(&self) -> Option<Self> {
745 let mut current = self.parent_scope();
746 while let Some(scope) = current {
747 if scope.has_recompose_callback() {
748 return Some(scope);
749 }
750 current = scope.parent_scope();
751 }
752 None
753 }
754
755 pub(crate) fn parent_hint(&self) -> Option<NodeId> {
756 self.inner.parent_hint.get()
757 }
758
759 pub(crate) fn group_anchor(&self) -> AnchorId {
760 self.inner.group_anchor.get()
761 }
762
763 pub(crate) fn set_group_anchor(&self, anchor: AnchorId) {
764 self.inner.group_anchor.set(anchor);
765 }
766
767 pub(crate) fn set_slots_host(&self, host: &SlotsHost) {
768 let storage_key = host.storage_key();
769 update_cell(&self.inner.slots_runtime_state, |runtime_state| {
770 if self.inner.slots_storage_key.get() == storage_key
771 && runtime_state.as_ref().map(Weak::as_ptr) == host.runtime_state_ptr()
772 {
773 return;
774 }
775 self.inner.slots_storage_key.set(storage_key);
776 *runtime_state = host.runtime_state().map(|state| Rc::downgrade(&state));
777 });
778 }
779
780 pub(crate) fn slots_storage_key(&self) -> Option<usize> {
781 let key = self.inner.slots_storage_key.get();
782 (key != 0).then_some(key)
783 }
784
785 pub(crate) fn slots_host_identity(&self) -> (usize, *const composer::ComposerRuntimeState) {
788 let state = read_cell(&self.inner.slots_runtime_state, |state| {
789 state.as_ref().map_or(std::ptr::null(), Weak::as_ptr)
790 });
791 (self.inner.slots_storage_key.get(), state)
792 }
793
794 pub(crate) fn slots_runtime_state(&self) -> Option<Rc<composer::ComposerRuntimeState>> {
795 read_cell(&self.inner.slots_runtime_state, |state| {
796 state.as_ref().and_then(Weak::upgrade)
797 })
798 }
799
800 pub fn deactivate(&self) {
803 if !self.inner.flags.replace(ScopeFlag::Active, false) {
804 return;
805 }
806 note_scope_activity_change();
807 self.inner
808 .deactivations
809 .set(PackedU64::new(self.inner.deactivations.get().get() + 1));
810 if self.inner.flags.replace(ScopeFlag::Enqueued, false) {
811 self.inner.runtime.mark_scope_recomposed();
812 }
813 }
814
815 pub(crate) fn defer_until_reactivated(&self) {
816 if self.inner.flags.replace(ScopeFlag::Enqueued, false) {
817 self.inner.runtime.mark_scope_recomposed();
818 }
819 }
820
821 pub fn reactivate(&self) {
824 self.inner.flags.set(ScopeFlag::Active, true);
825 if self.inner.flags.get(ScopeFlag::Invalid)
826 && self.is_effectively_active()
827 && self.may_queue()
828 {
829 self.queue_recompose();
830 }
831 }
832
833 pub fn force_reuse(&self) {
836 self.inner.flags.set(ScopeFlag::ForceReuse, true);
837 self.inner.flags.set(ScopeFlag::ForceRecompose, false);
838 self.inner.flags.set(ScopeFlag::PendingRecompose, true);
839 }
840
841 pub(crate) fn request_pending_recompose(&self) {
842 self.inner.flags.set(ScopeFlag::PendingRecompose, true);
843 }
844
845 pub fn force_recompose(&self) {
848 self.inner.flags.set(ScopeFlag::ForceRecompose, true);
849 self.inner.flags.set(ScopeFlag::ForceReuse, false);
850 self.inner.flags.set(ScopeFlag::PendingRecompose, false);
851 }
852
853 pub(crate) fn set_retention_mode(&self, mode: RetentionMode) {
854 self.inner.retention_mode.set(mode);
855 }
856
857 pub(crate) fn retention_mode(&self) -> RetentionMode {
858 self.inner.retention_mode.get()
859 }
860
861 pub fn should_recompose(&self) -> bool {
865 if self.inner.flags.replace(ScopeFlag::ForceRecompose, false) {
866 self.inner.flags.set(ScopeFlag::ForceReuse, false);
867 return true;
868 }
869 if self.inner.flags.replace(ScopeFlag::ForceReuse, false) {
870 return false;
871 }
872 self.is_invalid()
873 }
874
875 pub fn has_composed_once(&self) -> bool {
877 self.inner.flags.get(ScopeFlag::ComposedOnce)
878 }
879
880 pub(crate) fn mark_composed_once(&self) {
881 self.inner.flags.set(ScopeFlag::ComposedOnce, true);
882 }
883
884 pub(crate) fn is_paused(&self) -> bool {
886 self.inner.flags.get(ScopeFlag::Paused)
887 }
888
889 pub(crate) fn replace_paused(&self, paused: bool) -> bool {
892 self.inner.flags.replace(ScopeFlag::Paused, paused)
893 }
894
895 pub(crate) fn is_pending(&self) -> bool {
897 self.inner.flags.get(ScopeFlag::Pending)
898 }
899
900 pub(crate) fn set_pending(&self, pending: bool) {
903 self.inner.flags.set(ScopeFlag::Pending, pending);
904 if !pending && self.is_invalid() {
905 self.enqueue_invalidation();
906 }
907 }
908
909 pub(crate) fn invalidated_only_by_state(&self, state_id: StateId) -> bool {
911 !self.inner.flags.get(ScopeFlag::UnknownInvalidationSource)
912 && read_cell(&self.inner.invalidation_sources, |sources| {
913 !sources.is_empty() && sources.iter().all(|source| *source == state_id)
914 })
915 }
916
917 pub(crate) fn invalidated_only_by(&self, allowed_sources: &HashSet<StateId>) -> Option<bool> {
918 if self.inner.flags.get(ScopeFlag::UnknownInvalidationSource) {
919 return None;
920 }
921 read_cell(&self.inner.invalidation_sources, |sources| {
922 (!sources.is_empty()).then(|| {
923 sources
924 .iter()
925 .all(|source| allowed_sources.contains(source))
926 })
927 })
928 }
929}
930
931#[cfg(test)]
932impl RecomposeScope {
933 pub(crate) fn new_for_test(runtime: RuntimeHandle) -> Self {
934 Self::new(runtime)
935 }
936}
937
938#[derive(Debug, Clone, Copy, Default)]
940pub struct RecomposeOptions {
941 pub force_reuse: bool,
943 pub force_recompose: bool,
945 pub retention: RetentionMode,
947}