1use std::{any::Any, collections::VecDeque, fmt, rc::Rc};
10
11use smallvec::SmallVec;
12
13use crate::{
14 CallbackHolder, NodeId, RecomposeScope, SlotTable, SlotsHost,
15 collections::map::{HashMap, HashSet},
16};
17
18pub type DebugSlotGroup = (usize, crate::Key, Option<usize>, usize);
19
20#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
25pub struct SlotId(pub u64);
26
27impl SlotId {
28 #[inline]
29 pub fn new(raw: u64) -> Self {
30 Self(raw)
31 }
32
33 #[inline]
34 pub fn raw(self) -> u64 {
35 self.0
36 }
37}
38
39pub trait SlotReusePolicy: 'static {
45 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId>;
49
50 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool;
58
59 fn register_content_type(&self, _slot_id: SlotId, _content_type: u64) {}
66
67 fn remove_content_type(&self, _slot_id: SlotId) {}
72}
73
74#[derive(Debug, Default)]
78pub struct DefaultSlotReusePolicy;
79
80impl SlotReusePolicy for DefaultSlotReusePolicy {
81 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
82 let _ = active;
83 HashSet::default()
84 }
85
86 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
87 existing == requested
88 }
89}
90
91pub struct ContentTypeReusePolicy {
113 slot_types: std::cell::RefCell<HashMap<SlotId, u64>>,
114}
115
116impl std::fmt::Debug for ContentTypeReusePolicy {
117 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
118 let types = self.slot_types.borrow();
119 f.debug_struct("ContentTypeReusePolicy")
120 .field("slot_types", &*types)
121 .finish()
122 }
123}
124
125impl Default for ContentTypeReusePolicy {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131impl ContentTypeReusePolicy {
132 pub fn new() -> Self {
134 Self {
135 slot_types: std::cell::RefCell::new(HashMap::default()),
136 }
137 }
138
139 pub fn set_content_type(&self, slot: SlotId, content_type: u64) {
143 self.slot_types.borrow_mut().insert(slot, content_type);
144 }
145
146 pub fn remove_content_type(&self, slot: SlotId) {
148 self.slot_types.borrow_mut().remove(&slot);
149 }
150
151 pub fn clear(&self) {
153 self.slot_types.borrow_mut().clear();
154 }
155
156 pub fn get_content_type(&self, slot: SlotId) -> Option<u64> {
158 self.slot_types.borrow().get(&slot).copied()
159 }
160}
161
162impl SlotReusePolicy for ContentTypeReusePolicy {
163 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
164 let _ = active;
165 HashSet::default()
166 }
167
168 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
169 if existing == requested {
170 return true;
171 }
172
173 let types = self.slot_types.borrow();
174 match (types.get(&existing), types.get(&requested)) {
175 (Some(existing_type), Some(requested_type)) => existing_type == requested_type,
176 (None, None) => true,
177 _ => false,
178 }
179 }
180
181 fn register_content_type(&self, slot_id: SlotId, content_type: u64) {
182 self.set_content_type(slot_id, content_type);
183 }
184
185 fn remove_content_type(&self, slot_id: SlotId) {
186 ContentTypeReusePolicy::remove_content_type(self, slot_id);
187 }
188}
189
190#[doc(hidden)]
191pub struct ExactSlotActivation {
192 pub nodes: Vec<NodeId>,
193 pub scopes: Vec<RecomposeScope>,
194 pub reactivate_scopes: bool,
195}
196
197#[derive(Default, Clone)]
198
199struct NodeSlotMapping {
200 slot_to_nodes: HashMap<SlotId, Vec<NodeId>>,
201 node_to_slot: HashMap<NodeId, SlotId>,
202 slot_to_scopes: HashMap<SlotId, Vec<RecomposeScope>>,
203}
204
205impl fmt::Debug for NodeSlotMapping {
206 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
207 f.debug_struct("NodeSlotMapping")
208 .field("slot_to_nodes", &self.slot_to_nodes)
209 .field("node_to_slot", &self.node_to_slot)
210 .finish()
211 }
212}
213
214impl NodeSlotMapping {
215 fn set_nodes(&mut self, slot: SlotId, nodes: &[NodeId]) {
216 self.slot_to_nodes.insert(slot, nodes.to_vec());
217 for node in nodes {
218 self.node_to_slot.insert(*node, slot);
219 }
220 }
221
222 fn set_scopes(&mut self, slot: SlotId, scopes: &[RecomposeScope]) {
223 self.slot_to_scopes.insert(slot, scopes.to_vec());
224 }
225
226 fn add_node(&mut self, slot: SlotId, node: NodeId) {
227 self.slot_to_nodes.entry(slot).or_default().push(node);
228 self.node_to_slot.insert(node, slot);
229 }
230
231 fn remove_by_node(&mut self, node: &NodeId) -> Option<SlotId> {
232 if let Some(slot) = self.node_to_slot.remove(node) {
233 if let Some(nodes) = self.slot_to_nodes.get_mut(&slot) {
234 if let Some(index) = nodes.iter().position(|candidate| candidate == node) {
235 nodes.remove(index);
236 }
237 if nodes.is_empty() {
238 self.slot_to_nodes.remove(&slot);
239 self.slot_to_scopes.remove(&slot);
240 }
241 }
242 Some(slot)
243 } else {
244 None
245 }
246 }
247
248 fn get_nodes(&self, slot: &SlotId) -> Option<&[NodeId]> {
249 self.slot_to_nodes.get(slot).map(Vec::as_slice)
250 }
251
252 fn get_scopes(&self, slot: &SlotId) -> Option<&[RecomposeScope]> {
253 self.slot_to_scopes.get(slot).map(Vec::as_slice)
254 }
255
256 fn slot_has_invalid_scopes(&self, slot: SlotId) -> bool {
257 self.slot_to_scopes
258 .get(&slot)
259 .is_some_and(|scopes| scopes.iter().any(RecomposeScope::is_invalid))
260 }
261
262 fn slot_has_inactive_scopes(&self, slot: SlotId) -> bool {
263 self.slot_to_scopes
264 .get(&slot)
265 .is_some_and(|scopes| scopes.iter().any(|scope| !scope.is_active()))
266 }
267
268 fn deactivate_slot(&self, slot: SlotId) {
269 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
270 for scope in scopes {
271 scope.deactivate();
272 }
273 }
274 }
275
276 fn invalidate_scopes(&self) {
277 for scopes in self.slot_to_scopes.values() {
278 for scope in scopes {
279 scope.invalidate();
280 }
281 }
282 }
283}
284
285pub struct SubcomposeState {
288 mapping: NodeSlotMapping,
289 active_order: Vec<SlotId>,
290 live_slots: HashSet<SlotId>,
291 current_pass_active_slots: HashSet<SlotId>,
292 reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
293 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
294 reusable_node_counts: HashMap<SlotId, usize>,
295 exact_reactivation_slots: HashSet<SlotId>,
296 slot_content_types: HashMap<SlotId, u64>,
297 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
298 policy: Box<dyn SlotReusePolicy>,
299 pub(crate) current_index: usize,
300 pub(crate) reusable_count: usize,
301 pub(crate) precomposed_count: usize,
302 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
303 slot_callbacks: HashMap<SlotId, CallbackHolder>,
304 max_reusable_per_type: usize,
305 max_reusable_untyped: usize,
306 last_slot_reused: Option<bool>,
307 retained_capture_keys: HashMap<SlotId, RetainedCaptureKey>,
308 content_generation: std::cell::Cell<u64>,
309 slot_composed_generation: HashMap<SlotId, (u64, u64)>,
310}
311
312struct RetainedCaptureKey {
313 value: Box<dyn Any>,
314 eq: fn(&dyn Any, &dyn Any) -> bool,
315}
316
317fn retained_capture_key_eq<K: PartialEq + 'static>(stored: &dyn Any, candidate: &dyn Any) -> bool {
318 match (stored.downcast_ref::<K>(), candidate.downcast_ref::<K>()) {
319 (Some(stored), Some(candidate)) => stored == candidate,
320 _ => false,
321 }
322}
323
324impl fmt::Debug for SubcomposeState {
325 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
326 f.debug_struct("SubcomposeState")
327 .field("mapping", &self.mapping)
328 .field("active_order", &self.active_order)
329 .field("reusable_by_type_count", &self.reusable_by_type.len())
330 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
331 .field("precomposed_nodes", &self.precomposed_nodes)
332 .field("current_index", &self.current_index)
333 .field("reusable_count", &self.reusable_count)
334 .field("precomposed_count", &self.precomposed_count)
335 .field("slot_compositions_count", &self.slot_compositions.len())
336 .finish()
337 }
338}
339
340impl Default for SubcomposeState {
341 fn default() -> Self {
342 Self::new(Box::new(DefaultSlotReusePolicy))
343 }
344}
345
346const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
347
348const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
349
350impl SubcomposeState {
351 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
353 Self {
354 mapping: NodeSlotMapping::default(),
355 active_order: Vec::new(),
356 live_slots: HashSet::default(),
357 current_pass_active_slots: HashSet::default(),
358 reusable_by_type: HashMap::default(),
359 reusable_nodes_untyped: VecDeque::new(),
360 reusable_node_counts: HashMap::default(),
361 exact_reactivation_slots: HashSet::default(),
362 slot_content_types: HashMap::default(),
363 precomposed_nodes: HashMap::default(),
364 policy,
365 current_index: 0,
366 reusable_count: 0,
367 precomposed_count: 0,
368 slot_compositions: HashMap::default(),
369 slot_callbacks: HashMap::default(),
370 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
371 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
372 last_slot_reused: None,
373 retained_capture_keys: HashMap::default(),
374 content_generation: std::cell::Cell::new(0),
375 slot_composed_generation: HashMap::default(),
376 }
377 }
378
379 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
381 self.policy = policy;
382 }
383
384 pub fn set_reusable_pool_limits(&mut self, per_type: usize, untyped: usize) {
385 self.max_reusable_per_type = per_type;
386 self.max_reusable_untyped = untyped;
387 }
388
389 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
396 self.slot_content_types.insert(slot_id, content_type);
397 self.policy.register_content_type(slot_id, content_type);
398 }
399
400 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
406 match content_type {
407 Some(ct) => self.register_content_type(slot_id, ct),
408 None => {
409 self.slot_content_types.remove(&slot_id);
410 self.policy.remove_content_type(slot_id);
411 }
412 }
413 }
414
415 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
417 self.slot_content_types.get(&slot_id).copied()
418 }
419
420 pub fn active_slot_for_node(&self, node_id: NodeId) -> Option<SlotId> {
425 self.mapping
426 .node_to_slot
427 .get(&node_id)
428 .copied()
429 .filter(|slot| self.live_slots.contains(slot))
430 }
431
432 pub fn begin_pass(&mut self) {
437 self.current_index = 0;
438 self.current_pass_active_slots.clear();
439 }
440
441 pub fn active_slot_cursor(&self) -> usize {
443 self.current_index
444 }
445
446 pub fn restore_active_slot_cursor(&mut self, cursor: usize) {
449 self.current_index = cursor.min(self.active_order.len());
450 }
451
452 pub fn recycle_active_slot(&mut self, slot_id: SlotId) -> Vec<NodeId> {
454 self.recycle_active_slot_internal(slot_id, false)
455 }
456
457 pub fn recycle_prefetched_active_slot(&mut self, slot_id: SlotId) -> Vec<NodeId> {
458 self.recycle_active_slot_internal(slot_id, true)
459 }
460
461 pub fn recycle_active_slots_where(
462 &mut self,
463 mut predicate: impl FnMut(SlotId) -> bool,
464 ) -> Vec<NodeId> {
465 let slots: Vec<_> = self
466 .active_order
467 .iter()
468 .copied()
469 .filter(|slot| !self.current_pass_active_slots.contains(slot))
470 .filter(|slot| predicate(*slot))
471 .collect();
472 let mut disposed = Vec::new();
473 for slot in slots {
474 disposed.extend(self.recycle_active_slot(slot));
475 }
476 disposed
477 }
478
479 fn recycle_active_slot_internal(
480 &mut self,
481 slot_id: SlotId,
482 allow_exact_reactivation: bool,
483 ) -> Vec<NodeId> {
484 let Some(position) = self
485 .active_order
486 .iter()
487 .position(|candidate| *candidate == slot_id)
488 else {
489 return Vec::new();
490 };
491 self.active_order.remove(position);
492 if position < self.current_index {
493 self.current_index = self.current_index.saturating_sub(1);
494 }
495 self.move_slot_to_reusable(slot_id, allow_exact_reactivation);
496 self.enforce_reusable_pool_limits()
497 }
498
499 pub fn finish_pass(&mut self) -> Vec<NodeId> {
501 self.dispose_or_reuse_starting_from_index(self.current_index)
502 }
503
504 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
508 Rc::clone(
509 self.slot_compositions
510 .entry(slot_id)
511 .or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
512 )
513 }
514
515 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
517 self.slot_callbacks.entry(slot_id).or_default().clone()
518 }
519
520 #[doc(hidden)]
521 pub fn activate_current_active_slot(&mut self, slot_id: SlotId) -> Option<Vec<NodeId>> {
522 if self.current_pass_active_slots.contains(&slot_id)
523 || self.exact_reactivation_slots.contains(&slot_id)
524 || self.reusable_node_counts.contains_key(&slot_id)
525 || self
526 .active_order
527 .get(self.current_index)
528 .copied()
529 .is_none_or(|active_slot| active_slot != slot_id)
530 || self.mapping.slot_has_invalid_scopes(slot_id)
531 || self.mapping.slot_has_inactive_scopes(slot_id)
532 {
533 return None;
534 }
535
536 let nodes = self.mapping.get_nodes(&slot_id)?.to_vec();
537 self.last_slot_reused = Some(true);
538 self.live_slots.insert(slot_id);
539 self.current_pass_active_slots.insert(slot_id);
540 self.current_index += 1;
541 Some(nodes)
542 }
543
544 pub fn has_pending_precompositions(&self, slot_id: SlotId) -> bool {
548 self.precomposed_nodes.contains_key(&slot_id)
549 }
550
551 pub fn retained_capture_key_matches<K: PartialEq + 'static>(
555 &self,
556 slot_id: SlotId,
557 key: &K,
558 ) -> bool {
559 self.retained_capture_keys
560 .get(&slot_id)
561 .is_some_and(|retained| (retained.eq)(retained.value.as_ref(), key))
562 }
563
564 pub fn store_retained_capture_key<K: PartialEq + 'static>(&mut self, slot_id: SlotId, key: K) {
566 self.retained_capture_keys.insert(
567 slot_id,
568 RetainedCaptureKey {
569 value: Box::new(key),
570 eq: retained_capture_key_eq::<K>,
571 },
572 );
573 }
574
575 #[doc(hidden)]
576 pub fn take_exact_slot_activation(&mut self, slot_id: SlotId) -> Option<ExactSlotActivation> {
577 let active_position = self
578 .active_order
579 .iter()
580 .position(|candidate| *candidate == slot_id);
581 let is_exact_reactivation = self.exact_reactivation_slots.contains(&slot_id);
582 if active_position.is_none() && !is_exact_reactivation
583 || self.mapping.slot_has_invalid_scopes(slot_id)
584 {
585 return None;
586 }
587
588 let nodes = self.mapping.get_nodes(&slot_id)?.to_vec();
589 let scopes = self
590 .mapping
591 .get_scopes(&slot_id)
592 .unwrap_or_default()
593 .to_vec();
594 if active_position.is_none() {
595 for node in &nodes {
596 let _ = self.remove_from_reusable_pools(*node);
597 }
598 }
599 self.exact_reactivation_slots.remove(&slot_id);
600 Some(ExactSlotActivation {
601 nodes,
602 scopes,
603 reactivate_scopes: active_position.is_none(),
604 })
605 }
606
607 #[doc(hidden)]
608 pub fn take_exact_slot_for_activation(
609 &mut self,
610 slot_id: SlotId,
611 ) -> Option<(Vec<NodeId>, Vec<RecomposeScope>)> {
612 self.take_exact_slot_activation(slot_id)
613 .map(|activation| (activation.nodes, activation.scopes))
614 }
615
616 pub fn register_active(
619 &mut self,
620 slot_id: SlotId,
621 node_ids: &[NodeId],
622 scopes: &[RecomposeScope],
623 ) {
624 self.register_active_with_scope_reactivation(slot_id, node_ids, scopes, true);
625 }
626
627 #[doc(hidden)]
628 pub fn register_active_with_scope_reactivation(
629 &mut self,
630 slot_id: SlotId,
631 node_ids: &[NodeId],
632 scopes: &[RecomposeScope],
633 reactivate_scopes: bool,
634 ) {
635 let was_reused = self.mapping.get_nodes(&slot_id).is_some();
636 self.last_slot_reused = Some(was_reused);
637 self.live_slots.insert(slot_id);
638 self.current_pass_active_slots.insert(slot_id);
639 self.exact_reactivation_slots.remove(&slot_id);
640
641 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
642 if position < self.current_index {
643 if reactivate_scopes {
644 for scope in scopes {
645 scope.reactivate();
646 }
647 }
648 self.update_active_slot_mapping(slot_id, node_ids, scopes);
649 return;
650 }
651 self.active_order.remove(position);
652 }
653 if reactivate_scopes {
654 for scope in scopes {
655 scope.reactivate();
656 }
657 }
658 self.update_active_slot_mapping(slot_id, node_ids, scopes);
659 let insert_at = self.current_index.min(self.active_order.len());
660 self.active_order.insert(insert_at, slot_id);
661 self.current_index += 1;
662 }
663
664 fn update_active_slot_mapping(
665 &mut self,
666 slot_id: SlotId,
667 node_ids: &[NodeId],
668 scopes: &[RecomposeScope],
669 ) {
670 self.mapping.set_nodes(slot_id, node_ids);
671 self.mapping.set_scopes(slot_id, scopes);
672 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
673 let before_len = nodes.len();
674 nodes.retain(|node| !node_ids.contains(node));
675 let removed = before_len - nodes.len();
676 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
677 if nodes.is_empty() {
678 self.precomposed_nodes.remove(&slot_id);
679 }
680 }
681 }
682
683 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
686 self.precomposed_nodes
687 .entry(slot_id)
688 .or_default()
689 .push(node_id);
690 self.precomposed_count += 1;
691 }
692
693 fn increment_reusable_slot(&mut self, slot_id: SlotId) {
694 *self.reusable_node_counts.entry(slot_id).or_insert(0) += 1;
695 self.reusable_count += 1;
696 }
697
698 fn decrement_reusable_slot(&mut self, slot_id: SlotId) {
699 let Some(entry) = self.reusable_node_counts.get_mut(&slot_id) else {
700 self.reusable_count = self.reusable_count.saturating_sub(1);
701 return;
702 };
703 *entry = entry.saturating_sub(1);
704 if *entry == 0 {
705 self.reusable_node_counts.remove(&slot_id);
706 }
707 self.reusable_count = self.reusable_count.saturating_sub(1);
708 }
709
710 fn prune_slot_if_unused(&mut self, slot_id: SlotId) {
711 if self.live_slots.contains(&slot_id) || self.reusable_node_counts.contains_key(&slot_id) {
712 return;
713 }
714
715 debug_assert!(
716 self.mapping.get_nodes(&slot_id).is_none(),
717 "inactive slot {slot_id:?} still has mapped nodes",
718 );
719
720 self.slot_compositions.remove(&slot_id);
721 self.slot_callbacks.remove(&slot_id);
722 self.slot_content_types.remove(&slot_id);
723 self.retained_capture_keys.remove(&slot_id);
724 self.slot_composed_generation.remove(&slot_id);
725 self.policy.remove_content_type(slot_id);
726 if let Some(nodes) = self.precomposed_nodes.remove(&slot_id) {
727 self.precomposed_count = self.precomposed_count.saturating_sub(nodes.len());
728 }
729 }
730
731 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<(NodeId, bool)> {
742 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
743 let first_node = nodes.first().copied();
744 if let Some(node_id) = first_node {
745 let _ = self.remove_from_reusable_pools(node_id);
746 self.exact_reactivation_slots.remove(&slot_id);
747 return Some((node_id, false));
748 }
749 }
750
751 let content_type = self.slot_content_types.get(&slot_id).copied();
752
753 if let Some(ct) = content_type
754 && let Some((old_slot, node_id)) = self.take_compatible_typed_reusable(ct, slot_id)
755 {
756 self.decrement_reusable_slot(old_slot);
757 self.move_node_to_slot(node_id, old_slot, slot_id);
758 return Some((node_id, true));
759 }
760
761 let exact_reactivation_slots = &self.exact_reactivation_slots;
762 let policy = &self.policy;
763 let position = self
764 .reusable_nodes_untyped
765 .iter()
766 .position(|(existing_slot, _)| {
767 !exact_reactivation_slots.contains(existing_slot)
768 && policy.are_compatible(*existing_slot, slot_id)
769 });
770
771 if let Some(index) = position
772 && let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index)
773 {
774 self.decrement_reusable_slot(old_slot);
775 self.move_node_to_slot(node_id, old_slot, slot_id);
776 return Some((node_id, true));
777 }
778
779 None
780 }
781
782 fn take_compatible_typed_reusable(
783 &mut self,
784 content_type: u64,
785 requested_slot: SlotId,
786 ) -> Option<(SlotId, NodeId)> {
787 let reused = {
788 let policy = &self.policy;
789 let exact_reactivation_slots = &self.exact_reactivation_slots;
790 let pool = self.reusable_by_type.get_mut(&content_type)?;
791 let index = pool.iter().position(|(existing_slot, _)| {
792 !exact_reactivation_slots.contains(existing_slot)
793 && policy.are_compatible(*existing_slot, requested_slot)
794 })?;
795 pool.remove(index)
796 };
797
798 if self
799 .reusable_by_type
800 .get(&content_type)
801 .is_some_and(std::collections::VecDeque::is_empty)
802 {
803 self.reusable_by_type.remove(&content_type);
804 }
805
806 reused
807 }
808
809 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> Option<SlotId> {
810 let mut typed_match = None;
811 for (&content_type, pool) in &self.reusable_by_type {
812 if let Some(position) = pool
813 .iter()
814 .position(|(_, pooled_node)| *pooled_node == node_id)
815 {
816 typed_match = Some((content_type, position));
817 break;
818 }
819 }
820 if let Some((content_type, position)) = typed_match {
821 let pool = self.reusable_by_type.get_mut(&content_type)?;
822 let (slot, _) = pool.remove(position)?;
823 if pool.is_empty() {
824 self.reusable_by_type.remove(&content_type);
825 }
826 self.decrement_reusable_slot(slot);
827 return Some(slot);
828 }
829 if let Some(position) = self
830 .reusable_nodes_untyped
831 .iter()
832 .position(|(_, n)| *n == node_id)
833 && let Some((slot, _)) = self.reusable_nodes_untyped.remove(position)
834 {
835 self.decrement_reusable_slot(slot);
836 return Some(slot);
837 }
838 None
839 }
840
841 fn move_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
842 if old_slot == new_slot {
843 return;
844 }
845
846 self.mapping.remove_by_node(&node_id);
847 self.exact_reactivation_slots.remove(&old_slot);
848 self.mapping.add_node(new_slot, node_id);
849 self.slot_content_types.remove(&old_slot);
850 self.retained_capture_keys.remove(&old_slot);
851 self.retained_capture_keys.remove(&new_slot);
852 self.slot_composed_generation.remove(&old_slot);
853 self.slot_composed_generation.remove(&new_slot);
854 self.policy.remove_content_type(old_slot);
855 if let Some(slots) = self.slot_compositions.remove(&old_slot) {
856 self.slot_compositions.insert(new_slot, slots);
857 }
858 if let Some(callback) = self.slot_callbacks.remove(&old_slot) {
859 self.slot_callbacks.insert(new_slot, callback);
860 }
861 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
862 let before_len = nodes.len();
863 nodes.retain(|candidate| *candidate != node_id);
864 let removed = before_len - nodes.len();
865 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
866 if nodes.is_empty() {
867 self.precomposed_nodes.remove(&old_slot);
868 }
869 }
870 }
871
872 pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
876 if start_index >= self.active_order.len() {
877 return Vec::new();
878 }
879
880 let retain = self
881 .policy
882 .get_slots_to_retain(&self.active_order[start_index..]);
883 let mut retained = Vec::new();
884 while self.active_order.len() > start_index {
885 let Some(slot) = self.active_order.pop() else {
886 break;
887 };
888 if retain.contains(&slot) {
889 retained.push(slot);
890 continue;
891 }
892 self.move_slot_to_reusable(slot, false);
893 }
894 retained.reverse();
895 self.active_order.extend(retained);
896
897 self.enforce_reusable_pool_limits()
898 }
899
900 fn move_slot_to_reusable(&mut self, slot: SlotId, allow_exact_reactivation: bool) {
901 self.live_slots.remove(&slot);
902 self.current_pass_active_slots.remove(&slot);
903 self.mapping.deactivate_slot(slot);
904 let forgot_effects = self
905 .slot_compositions
906 .get(&slot)
907 .is_some_and(|host| host.forget_effects());
908 if allow_exact_reactivation && !forgot_effects {
909 self.exact_reactivation_slots.insert(slot);
910 } else {
911 self.exact_reactivation_slots.remove(&slot);
912 }
913
914 let content_type = self.slot_content_types.get(&slot).copied();
915 let nodes: SmallVec<[NodeId; 4]> = self
916 .mapping
917 .get_nodes(&slot)
918 .into_iter()
919 .flatten()
920 .copied()
921 .collect();
922 for node in nodes {
923 if let Some(ct) = content_type {
924 self.reusable_by_type
925 .entry(ct)
926 .or_default()
927 .push_back((slot, node));
928 } else {
929 self.reusable_nodes_untyped.push_back((slot, node));
930 }
931 self.increment_reusable_slot(slot);
932 }
933 }
934
935 fn enforce_reusable_pool_limits(&mut self) -> Vec<NodeId> {
936 let mut disposed = Vec::new();
937 let mut typed_disposals = Vec::new();
938 for pool in self.reusable_by_type.values_mut() {
939 while pool.len() > self.max_reusable_per_type {
940 if let Some((slot, node_id)) = pool.pop_front() {
941 typed_disposals.push((slot, node_id));
942 }
943 }
944 }
945 for (slot, node_id) in typed_disposals {
946 self.decrement_reusable_slot(slot);
947 self.mapping.remove_by_node(&node_id);
948 self.exact_reactivation_slots.remove(&slot);
949 disposed.push(node_id);
950 }
951
952 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
953 if let Some((slot, node_id)) = self.reusable_nodes_untyped.pop_front() {
954 self.decrement_reusable_slot(slot);
955 self.mapping.remove_by_node(&node_id);
956 self.exact_reactivation_slots.remove(&slot);
957 disposed.push(node_id);
958 }
959 }
960
961 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
962 disposed
963 }
964
965 pub fn reusable(&self) -> Vec<NodeId> {
967 let mut nodes: Vec<NodeId> = self
968 .reusable_by_type
969 .values()
970 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
971 .collect();
972 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
973 nodes
974 }
975
976 pub fn active_slots_count(&self) -> usize {
981 self.active_order.len()
982 }
983
984 pub fn reusable_slots_count(&self) -> usize {
989 self.reusable_count
990 }
991
992 pub fn invalidate_scopes(&self) {
1005 self.mapping.invalidate_scopes();
1006 self.content_generation
1007 .set(self.content_generation.get() + 1);
1008 }
1009
1010 pub fn bump_content_generation(&self) {
1017 self.content_generation
1018 .set(self.content_generation.get() + 1);
1019 }
1020
1021 pub fn slot_content_generation_current(&self, slot_id: SlotId, owner_epoch: u64) -> bool {
1027 self.slot_composed_generation.get(&slot_id).copied()
1028 == Some((self.content_generation.get(), owner_epoch))
1029 }
1030
1031 pub fn mark_slot_composed_current(&mut self, slot_id: SlotId, owner_epoch: u64) {
1034 self.slot_composed_generation
1035 .insert(slot_id, (self.content_generation.get(), owner_epoch));
1036 }
1037
1038 pub fn was_last_slot_reused(&self) -> Option<bool> {
1046 self.last_slot_reused
1047 }
1048
1049 #[doc(hidden)]
1050 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
1051 self.mapping
1052 .slot_to_scopes
1053 .iter()
1054 .map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
1055 .collect()
1056 }
1057
1058 #[doc(hidden)]
1059 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<crate::SlotDebugEntry>> {
1060 let slots = self.slot_compositions.get(&slot_id)?;
1061 Some(slots.borrow().debug_dump_slot_entries())
1062 }
1063
1064 #[doc(hidden)]
1065 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
1066 let slots = self.slot_compositions.get(&slot_id)?;
1067 Some(slots.borrow().debug_dump_groups())
1068 }
1069
1070 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
1072 &self.precomposed_nodes
1073 }
1074
1075 pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
1078 let mut disposed = Vec::new();
1079 let mut empty_slots = Vec::new();
1080 for (slot, nodes) in &mut self.precomposed_nodes {
1081 if !self.current_pass_active_slots.contains(slot) {
1082 disposed.extend(nodes.iter().copied());
1083 empty_slots.push(*slot);
1084 }
1085 }
1086 for slot in empty_slots {
1087 self.precomposed_nodes.remove(&slot);
1088 self.prune_slot_if_unused(slot);
1089 }
1090 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
1091 disposed
1092 }
1093}
1094
1095#[cfg(test)]
1096#[path = "tests/subcompose_tests.rs"]
1097mod tests;