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