1use std::{any::Any, fmt, rc::Rc};
10
11use smallvec::SmallVec;
12
13use crate::{
14 CallbackHolder, NodeId, RecomposeScope, SlotTable, SlotsHost, collections::map::HashMap,
15};
16
17mod reuse_pool;
18mod slot_reuse_policy;
19
20use reuse_pool::ReusePool;
21pub use slot_reuse_policy::{
22 ContentTypeReusePolicy, DefaultSlotReusePolicy, SlotReusePolicy, TypedSlot,
23};
24
25pub type DebugSlotGroup = (usize, crate::Key, Option<usize>, usize);
28
29#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
34pub struct SlotId(pub u64);
35
36impl SlotId {
37 #[inline]
39 pub fn new(raw: u64) -> Self {
40 Self(raw)
41 }
42
43 #[inline]
45 pub fn raw(self) -> u64 {
46 self.0
47 }
48}
49
50#[doc(hidden)]
51pub struct ExactSlotActivation<'a> {
52 pub nodes: &'a [NodeId],
53 pub was_recycled: bool,
54}
55
56#[derive(Default)]
59pub struct SubcomposeDisposal {
60 pub(crate) nodes: Vec<NodeId>,
61 pub(crate) slot_hosts: Vec<Rc<SlotsHost>>,
62}
63
64impl SubcomposeDisposal {
65 pub fn nodes(&self) -> &[NodeId] {
74 &self.nodes
75 }
76
77 fn append(&mut self, mut other: Self) {
78 self.nodes.append(&mut other.nodes);
79 self.slot_hosts.append(&mut other.slot_hosts);
80 }
81}
82
83#[derive(Default)]
86struct SlotRecord {
87 nodes: Option<Vec<NodeId>>,
89 scopes: Option<Vec<RecomposeScope>>,
91 live: bool,
93 active_pass: u64,
95 reusable_nodes: usize,
97 exact_reactivation: bool,
99 content_type: Option<u64>,
100 composition: Option<Rc<SlotsHost>>,
101 callback: Option<CallbackHolder>,
102 capture_key: Option<RetainedCaptureKey>,
103 composed_generation: Option<(u64, u64)>,
105}
106
107impl SlotRecord {
108 fn has_stale_scopes(&self) -> bool {
111 self.scopes
112 .iter()
113 .flatten()
114 .any(|scope| scope.is_invalid() || !scope.is_active())
115 }
116
117 fn has_invalid_scopes(&self) -> bool {
118 self.scopes.iter().flatten().any(RecomposeScope::is_invalid)
119 }
120
121 fn is_empty(&self) -> bool {
123 self.nodes.is_none()
124 && self.scopes.is_none()
125 && !self.live
126 && self.reusable_nodes == 0
127 && !self.exact_reactivation
128 && self.content_type.is_none()
129 && self.composition.is_none()
130 && self.callback.is_none()
131 && self.capture_key.is_none()
132 && self.composed_generation.is_none()
133 }
134}
135
136pub struct SubcomposeState {
139 slots: HashMap<SlotId, SlotRecord>,
140 node_to_slot: HashMap<NodeId, SlotId>,
141 active_order: Vec<SlotId>,
142 pass: u64,
145 pool: ReusePool,
146 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
147 policy: Box<dyn SlotReusePolicy>,
148 pub(crate) current_index: usize,
149 pub(crate) reusable_count: usize,
150 pub(crate) precomposed_count: usize,
151 last_slot_reused: Option<bool>,
152 content_generation: std::cell::Cell<u64>,
153 locals_generation: std::cell::Cell<u64>,
156}
157
158struct RetainedCaptureKey {
159 value: Box<dyn Any>,
160 eq: fn(&dyn Any, &dyn Any) -> bool,
161}
162
163fn retained_capture_key_eq<K: PartialEq + 'static>(stored: &dyn Any, candidate: &dyn Any) -> bool {
164 match (stored.downcast_ref::<K>(), candidate.downcast_ref::<K>()) {
165 (Some(stored), Some(candidate)) => stored == candidate,
166 _ => false,
167 }
168}
169
170impl fmt::Debug for SubcomposeState {
171 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
172 f.debug_struct("SubcomposeState")
173 .field("slots_count", &self.slots.len())
174 .field("node_to_slot", &self.node_to_slot)
175 .field("active_order", &self.active_order)
176 .field("reusable_by_type_count", &self.pool.type_count())
177 .field("reusable_untyped_count", &self.pool.untyped_len())
178 .field("precomposed_nodes", &self.precomposed_nodes)
179 .field("current_index", &self.current_index)
180 .field("reusable_count", &self.reusable_count)
181 .field("precomposed_count", &self.precomposed_count)
182 .finish()
183 }
184}
185
186impl Default for SubcomposeState {
187 fn default() -> Self {
188 Self::new(Box::new(DefaultSlotReusePolicy))
189 }
190}
191
192impl SubcomposeState {
193 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
195 Self {
196 slots: HashMap::default(),
197 node_to_slot: HashMap::default(),
198 active_order: Vec::new(),
199 pass: 1,
200 pool: ReusePool::new(),
201 precomposed_nodes: HashMap::default(),
202 policy,
203 current_index: 0,
204 reusable_count: 0,
205 precomposed_count: 0,
206 last_slot_reused: None,
207 content_generation: std::cell::Cell::new(0),
208 locals_generation: std::cell::Cell::new(0),
209 }
210 }
211
212 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
214 self.policy = policy;
215 }
216
217 pub fn set_reusable_pool_limits(&mut self, per_type: usize, untyped: usize) {
220 self.pool.set_limits(per_type, untyped);
221 }
222
223 fn record(&mut self, slot_id: SlotId) -> &mut SlotRecord {
224 self.slots.entry(slot_id).or_default()
225 }
226
227 fn slot_nodes(&self, slot_id: SlotId) -> Option<&[NodeId]> {
228 self.slots.get(&slot_id)?.nodes.as_deref()
229 }
230
231 fn typed_slot(&self, slot_id: SlotId) -> TypedSlot {
232 TypedSlot {
233 id: slot_id,
234 content_type: self.get_content_type(slot_id),
235 }
236 }
237
238 fn slot_active_in_pass(&self, slot_id: SlotId) -> bool {
239 self.slots
240 .get(&slot_id)
241 .is_some_and(|record| record.active_pass == self.pass)
242 }
243
244 fn clear_exact_reactivation(&mut self, slot_id: SlotId) {
245 if let Some(record) = self.slots.get_mut(&slot_id) {
246 record.exact_reactivation = false;
247 }
248 }
249
250 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
254 self.record(slot_id).content_type = Some(content_type);
255 }
256
257 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
263 match content_type {
264 Some(content_type) => self.register_content_type(slot_id, content_type),
265 None => {
266 if let Some(record) = self.slots.get_mut(&slot_id) {
267 record.content_type = None;
268 }
269 }
270 }
271 }
272
273 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
275 self.slots.get(&slot_id)?.content_type
276 }
277
278 pub fn active_slot_for_node(&self, node_id: NodeId) -> Option<SlotId> {
283 let slot = *self.node_to_slot.get(&node_id)?;
284 self.slot_is_active(slot).then_some(slot)
285 }
286
287 pub fn begin_pass(&mut self) {
292 self.current_index = 0;
293 self.pass += 1;
294 }
295
296 pub fn active_slot_cursor(&self) -> usize {
298 self.current_index
299 }
300
301 pub fn restore_active_slot_cursor(&mut self, cursor: usize) {
304 self.current_index = cursor.min(self.active_order.len());
305 }
306
307 pub fn recycle_active_slot(&mut self, slot_id: SlotId) -> SubcomposeDisposal {
309 self.recycle_active_slot_internal(slot_id, false)
310 }
311
312 pub fn recycle_prefetched_active_slot(&mut self, slot_id: SlotId) -> SubcomposeDisposal {
315 self.recycle_active_slot_internal(slot_id, true)
316 }
317
318 pub fn recycle_active_slots_where(
321 &mut self,
322 mut predicate: impl FnMut(SlotId) -> bool,
323 ) -> SubcomposeDisposal {
324 let slots: Vec<_> = self
325 .active_order
326 .iter()
327 .copied()
328 .filter(|slot| !self.slot_active_in_pass(*slot))
329 .filter(|slot| predicate(*slot))
330 .collect();
331 let mut disposed = SubcomposeDisposal::default();
332 for slot in slots {
333 disposed.append(self.recycle_active_slot(slot));
334 }
335 disposed
336 }
337
338 fn recycle_active_slot_internal(
339 &mut self,
340 slot_id: SlotId,
341 allow_exact_reactivation: bool,
342 ) -> SubcomposeDisposal {
343 let Some(position) = self
344 .active_order
345 .iter()
346 .position(|candidate| *candidate == slot_id)
347 else {
348 return SubcomposeDisposal::default();
349 };
350 self.active_order.remove(position);
351 if position < self.current_index {
352 self.current_index = self.current_index.saturating_sub(1);
353 }
354 self.move_slot_to_reusable(slot_id, allow_exact_reactivation);
355 self.enforce_reusable_pool_limits()
356 }
357
358 pub fn finish_pass(&mut self) -> SubcomposeDisposal {
360 let mut disposed = self.dispose_or_reuse_starting_from_index(self.current_index);
361 for (slot, node_id) in self.pool.end_pass() {
362 self.dispose_reusable_node(slot, node_id, &mut disposed);
363 }
364 disposed
365 }
366
367 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
371 Rc::clone(
372 self.record(slot_id)
373 .composition
374 .get_or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
375 )
376 }
377
378 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
380 self.record(slot_id)
381 .callback
382 .get_or_insert_with(CallbackHolder::default)
383 .clone()
384 }
385
386 pub fn kept_slot_nodes_at(&self, position: usize, slot_id: SlotId) -> Option<&[NodeId]> {
391 if self.active_order.get(position) != Some(&slot_id) {
392 return None;
393 }
394 let record = self.slots.get(&slot_id)?;
395 if record.exact_reactivation || record.reusable_nodes > 0 || record.has_stale_scopes() {
396 return None;
397 }
398 record.nodes.as_deref()
399 }
400
401 pub fn active_slots(&self) -> &[SlotId] {
403 &self.active_order
404 }
405
406 #[doc(hidden)]
407 pub fn activate_current_active_slot(&mut self, slot_id: SlotId) -> Option<&[NodeId]> {
408 if !self.activate_current_active_slot_at_cursor(slot_id) {
409 return None;
410 }
411 self.slot_nodes(slot_id)
412 }
413
414 fn activate_current_active_slot_at_cursor(&mut self, slot_id: SlotId) -> bool {
415 if self.active_order.get(self.current_index) != Some(&slot_id) {
416 return false;
417 }
418 let pass = self.pass;
419 let Some(record) = self.slots.get_mut(&slot_id) else {
420 return false;
421 };
422 if record.active_pass == pass
423 || record.exact_reactivation
424 || record.reusable_nodes > 0
425 || record.nodes.is_none()
426 || record.has_stale_scopes()
427 {
428 return false;
429 }
430 record.live = true;
431 record.active_pass = pass;
432 self.last_slot_reused = Some(true);
433 self.current_index += 1;
434 true
435 }
436
437 pub fn has_pending_precompositions(&self, slot_id: SlotId) -> bool {
441 self.precomposed_nodes.contains_key(&slot_id)
442 }
443
444 pub fn retained_capture_key_matches<K: PartialEq + 'static>(
448 &self,
449 slot_id: SlotId,
450 key: &K,
451 ) -> bool {
452 self.slots
453 .get(&slot_id)
454 .and_then(|record| record.capture_key.as_ref())
455 .is_some_and(|retained| (retained.eq)(retained.value.as_ref(), key))
456 }
457
458 pub fn store_retained_capture_key<K: PartialEq + 'static>(&mut self, slot_id: SlotId, key: K) {
460 self.record(slot_id).capture_key = Some(RetainedCaptureKey {
461 value: Box::new(key),
462 eq: retained_capture_key_eq::<K>,
463 });
464 }
465
466 #[doc(hidden)]
467 pub fn activate_exact_slot(&mut self, slot_id: SlotId) -> Option<ExactSlotActivation<'_>> {
468 let was_recycled = if self.activate_current_active_slot_at_cursor(slot_id) {
469 false
470 } else {
471 self.reactivate_exact_slot(slot_id)?
472 };
473 Some(ExactSlotActivation {
474 nodes: self.slot_nodes(slot_id)?,
475 was_recycled,
476 })
477 }
478
479 fn reactivate_exact_slot(&mut self, slot_id: SlotId) -> Option<bool> {
483 let active_position = self
484 .active_order
485 .iter()
486 .position(|candidate| *candidate == slot_id);
487 let record = self.slots.get(&slot_id)?;
488 if active_position.is_none() && !record.exact_reactivation || record.has_invalid_scopes() {
489 return None;
490 }
491 let nodes: SmallVec<[NodeId; 4]> = record.nodes.as_deref()?.iter().copied().collect();
492 if active_position.is_none() {
493 for scope in record.scopes.iter().flatten() {
494 scope.reactivate();
495 }
496 for &node in &nodes {
497 let _ = self.remove_from_reusable_pools(node);
498 }
499 }
500 self.mark_slot_active(slot_id, true);
501 Self::consume_precomposed_nodes(
502 &mut self.precomposed_nodes,
503 &mut self.precomposed_count,
504 slot_id,
505 &nodes,
506 );
507 Some(active_position.is_none())
508 }
509
510 pub fn register_active(
513 &mut self,
514 slot_id: SlotId,
515 node_ids: &[NodeId],
516 scopes: &[RecomposeScope],
517 ) {
518 for scope in scopes {
519 scope.reactivate();
520 }
521 let record = self.slots.entry(slot_id).or_default();
522 let was_reused = record.nodes.is_some();
523 let nodes = record.nodes.get_or_insert_default();
524 nodes.clear();
525 nodes.extend_from_slice(node_ids);
526 let held_scopes = record.scopes.get_or_insert_default();
527 held_scopes.clear();
528 held_scopes.extend_from_slice(scopes);
529 for &node in node_ids {
530 self.node_to_slot.insert(node, slot_id);
531 }
532 Self::consume_precomposed_nodes(
533 &mut self.precomposed_nodes,
534 &mut self.precomposed_count,
535 slot_id,
536 node_ids,
537 );
538 self.mark_slot_active(slot_id, was_reused);
539 }
540
541 fn mark_slot_active(&mut self, slot_id: SlotId, was_reused: bool) {
542 self.last_slot_reused = Some(was_reused);
543 let pass = self.pass;
544 let record = self.record(slot_id);
545 record.live = true;
546 record.active_pass = pass;
547 record.exact_reactivation = false;
548
549 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
550 if position < self.current_index {
551 return;
552 }
553 self.active_order.remove(position);
554 }
555 let insert_at = self.current_index.min(self.active_order.len());
556 self.active_order.insert(insert_at, slot_id);
557 self.current_index += 1;
558 }
559
560 fn consume_precomposed_nodes(
561 precomposed_nodes: &mut HashMap<SlotId, Vec<NodeId>>,
562 precomposed_count: &mut usize,
563 slot_id: SlotId,
564 node_ids: &[NodeId],
565 ) {
566 if let Some(nodes) = precomposed_nodes.get_mut(&slot_id) {
567 let before_len = nodes.len();
568 nodes.retain(|node| !node_ids.contains(node));
569 let removed = before_len - nodes.len();
570 *precomposed_count = precomposed_count.saturating_sub(removed);
571 if nodes.is_empty() {
572 precomposed_nodes.remove(&slot_id);
573 }
574 }
575 }
576
577 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
580 self.precomposed_nodes
581 .entry(slot_id)
582 .or_default()
583 .push(node_id);
584 self.precomposed_count += 1;
585 }
586
587 fn decrement_reusable_slot(&mut self, slot_id: SlotId) {
588 if let Some(record) = self.slots.get_mut(&slot_id) {
589 record.reusable_nodes = record.reusable_nodes.saturating_sub(1);
590 }
591 self.reusable_count = self.reusable_count.saturating_sub(1);
592 }
593
594 fn remove_node_from_slot(&mut self, node: NodeId) -> Option<SlotId> {
597 let slot = self.node_to_slot.remove(&node)?;
598 if let Some(record) = self.slots.get_mut(&slot)
599 && let Some(nodes) = record.nodes.as_mut()
600 {
601 if let Some(index) = nodes.iter().position(|candidate| *candidate == node) {
602 nodes.remove(index);
603 }
604 if nodes.is_empty() {
605 record.nodes = None;
606 record.scopes = None;
607 }
608 }
609 Some(slot)
610 }
611
612 pub fn slot_is_active(&self, slot_id: SlotId) -> bool {
615 self.slots.get(&slot_id).is_some_and(|record| record.live)
616 }
617
618 pub fn slot_is_retained(&self, slot_id: SlotId) -> bool {
622 self.slots
623 .get(&slot_id)
624 .is_some_and(|record| record.live || record.reusable_nodes > 0)
625 }
626
627 fn prune_slot_if_unused(&mut self, slot_id: SlotId) -> Option<Rc<SlotsHost>> {
628 if self.slot_is_retained(slot_id) {
629 return None;
630 }
631
632 debug_assert!(
633 self.slot_nodes(slot_id).is_none(),
634 "inactive slot {slot_id:?} still has mapped nodes",
635 );
636
637 let host = self
638 .slots
639 .remove(&slot_id)
640 .and_then(|record| record.composition);
641 if let Some(nodes) = self.precomposed_nodes.remove(&slot_id) {
642 self.precomposed_count = self.precomposed_count.saturating_sub(nodes.len());
643 }
644 host
645 }
646
647 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<(NodeId, bool)> {
658 if let Some(node_id) = self
659 .slot_nodes(slot_id)
660 .and_then(|nodes| nodes.first().copied())
661 {
662 let _ = self.remove_from_reusable_pools(node_id);
663 self.clear_exact_reactivation(slot_id);
664 return Some((node_id, false));
665 }
666
667 let requested = self.typed_slot(slot_id);
668 let (pool, slots, policy) = (&mut self.pool, &self.slots, &self.policy);
669 let accepts = |existing| reusable_for(slots, policy.as_ref(), existing, requested);
670 let (old_slot, node_id) = requested
672 .content_type
673 .and_then(|content_type| pool.take(Some(content_type), accepts))
674 .or_else(|| pool.take(None, accepts))?;
675 self.decrement_reusable_slot(old_slot);
676 self.move_node_to_slot(node_id, old_slot, slot_id);
677 Some((node_id, true))
678 }
679
680 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> Option<SlotId> {
681 let slot = self.pool.remove_node(node_id)?;
682 self.decrement_reusable_slot(slot);
683 Some(slot)
684 }
685
686 fn move_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
687 if old_slot == new_slot {
688 return;
689 }
690
691 self.remove_node_from_slot(node_id);
692 let (composition, callback) = match self.slots.get_mut(&old_slot) {
693 Some(old) => {
694 old.exact_reactivation = false;
695 old.content_type = None;
696 old.capture_key = None;
697 old.composed_generation = None;
698 (old.composition.take(), old.callback.take())
699 }
700 None => (None, None),
701 };
702 if self.slots.get(&old_slot).is_some_and(SlotRecord::is_empty) {
703 self.slots.remove(&old_slot);
704 }
705 self.node_to_slot.insert(node_id, new_slot);
706 let new = self.record(new_slot);
707 new.nodes.get_or_insert_default().push(node_id);
708 new.capture_key = None;
709 new.composed_generation = None;
710 if composition.is_some() {
711 new.composition = composition;
712 }
713 if callback.is_some() {
714 new.callback = callback;
715 }
716 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
717 let before_len = nodes.len();
718 nodes.retain(|candidate| *candidate != node_id);
719 let removed = before_len - nodes.len();
720 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
721 if nodes.is_empty() {
722 self.precomposed_nodes.remove(&old_slot);
723 }
724 }
725 }
726
727 pub fn dispose_or_reuse_starting_from_index(
731 &mut self,
732 start_index: usize,
733 ) -> SubcomposeDisposal {
734 if start_index >= self.active_order.len() {
735 return SubcomposeDisposal::default();
736 }
737
738 let retain = self
739 .policy
740 .get_slots_to_retain(&self.active_order[start_index..]);
741 let mut retained = Vec::new();
742 while self.active_order.len() > start_index {
743 let Some(slot) = self.active_order.pop() else {
744 break;
745 };
746 if retain.contains(&slot) {
747 retained.push(slot);
748 continue;
749 }
750 self.move_slot_to_reusable(slot, false);
751 }
752 retained.reverse();
753 self.active_order.extend(retained);
754
755 self.enforce_reusable_pool_limits()
756 }
757
758 fn move_slot_to_reusable(&mut self, slot: SlotId, allow_exact_reactivation: bool) {
759 let record = self.slots.entry(slot).or_default();
760 record.live = false;
761 record.active_pass = 0;
762 for scope in record.scopes.iter().flatten() {
763 scope.deactivate();
764 }
765 let forgot_effects = record
766 .composition
767 .as_ref()
768 .is_some_and(|host| host.forget_effects());
769 record.exact_reactivation = allow_exact_reactivation && !forgot_effects;
770
771 let content_type = record.content_type;
772 let nodes = record.nodes.as_deref().unwrap_or_default();
773 record.reusable_nodes += nodes.len();
774 self.reusable_count += nodes.len();
775 self.pool.push(content_type, slot, nodes.iter().copied());
776 }
777
778 fn enforce_reusable_pool_limits(&mut self) -> SubcomposeDisposal {
779 let mut disposed = SubcomposeDisposal::default();
780 for (slot, node_id) in self.pool.take_excess() {
781 self.dispose_reusable_node(slot, node_id, &mut disposed);
782 }
783 disposed
784 }
785
786 fn dispose_reusable_node(
788 &mut self,
789 slot: SlotId,
790 node_id: NodeId,
791 disposed: &mut SubcomposeDisposal,
792 ) {
793 self.decrement_reusable_slot(slot);
794 self.remove_node_from_slot(node_id);
795 self.clear_exact_reactivation(slot);
796 disposed.slot_hosts.extend(self.prune_slot_if_unused(slot));
797 disposed.nodes.push(node_id);
798 }
799
800 pub fn reusable(&self) -> Vec<NodeId> {
802 self.pool.nodes().collect()
803 }
804
805 pub fn active_slots_count(&self) -> usize {
810 self.active_order.len()
811 }
812
813 pub fn reusable_slots_count(&self) -> usize {
818 self.reusable_count
819 }
820
821 pub fn invalidate_scopes(&self) {
834 for scope in self
835 .slots
836 .values()
837 .flat_map(|record| record.scopes.iter().flatten())
838 {
839 scope.invalidate();
840 }
841 self.content_generation
842 .set(self.content_generation.get() + 1);
843 }
844
845 pub fn invalidate_locals(&self) {
849 self.bump_content_generation();
850 self.locals_generation.set(self.content_generation.get());
851 }
852
853 pub fn slot_locals_stale(&self, slot_id: SlotId) -> bool {
858 let changed_at = self.locals_generation.get();
859 self.slots
860 .get(&slot_id)
861 .and_then(|record| record.composed_generation)
862 .is_some_and(|(generation, _)| generation < changed_at)
863 }
864
865 pub fn bump_content_generation(&self) {
872 self.content_generation
873 .set(self.content_generation.get() + 1);
874 }
875
876 pub fn slot_content_generation_current(&self, slot_id: SlotId, owner_epoch: u64) -> bool {
882 self.slots
883 .get(&slot_id)
884 .and_then(|record| record.composed_generation)
885 == Some((self.content_generation.get(), owner_epoch))
886 }
887
888 pub fn mark_slot_composed_current(&mut self, slot_id: SlotId, owner_epoch: u64) {
891 let generation = self.content_generation.get();
892 self.record(slot_id).composed_generation = Some((generation, owner_epoch));
893 }
894
895 pub fn was_last_slot_reused(&self) -> Option<bool> {
903 self.last_slot_reused
904 }
905
906 #[doc(hidden)]
907 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
908 self.slots
909 .iter()
910 .filter_map(|(slot, record)| {
911 let scopes = record.scopes.as_ref()?;
912 Some((slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
913 })
914 .collect()
915 }
916
917 #[doc(hidden)]
918 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<crate::SlotDebugEntry>> {
919 let slots = self.slots.get(&slot_id)?.composition.as_ref()?;
920 Some(slots.borrow().debug_dump_slot_entries())
921 }
922
923 #[doc(hidden)]
924 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
925 let slots = self.slots.get(&slot_id)?.composition.as_ref()?;
926 Some(slots.borrow().debug_dump_groups())
927 }
928
929 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
931 &self.precomposed_nodes
932 }
933
934 pub fn drain_inactive_precomposed(&mut self) -> SubcomposeDisposal {
937 let mut disposed = SubcomposeDisposal::default();
938 let mut empty_slots = Vec::new();
939 for (slot, nodes) in &self.precomposed_nodes {
940 if !self.slot_active_in_pass(*slot) {
941 disposed.nodes.extend(nodes.iter().copied());
942 empty_slots.push(*slot);
943 }
944 }
945 for slot in empty_slots {
946 self.precomposed_nodes.remove(&slot);
947 disposed.slot_hosts.extend(self.prune_slot_if_unused(slot));
948 }
949 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.nodes.len());
950 disposed
951 }
952}
953
954fn reusable_for(
957 slots: &HashMap<SlotId, SlotRecord>,
958 policy: &dyn SlotReusePolicy,
959 existing: SlotId,
960 requested: TypedSlot,
961) -> bool {
962 let record = slots.get(&existing);
963 !record.is_some_and(|record| record.exact_reactivation)
964 && policy.are_compatible(
965 TypedSlot {
966 id: existing,
967 content_type: record.and_then(|record| record.content_type),
968 },
969 requested,
970 )
971}
972
973#[cfg(test)]
974#[path = "tests/subcompose_tests.rs"]
975mod tests;