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(|nodes| nodes.as_slice())
250 }
251
252 fn get_scopes(&self, slot: &SlotId) -> Option<&[RecomposeScope]> {
253 self.slot_to_scopes
254 .get(slot)
255 .map(|scopes| scopes.as_slice())
256 }
257
258 fn slot_has_invalid_scopes(&self, slot: SlotId) -> bool {
259 self.slot_to_scopes
260 .get(&slot)
261 .is_some_and(|scopes| scopes.iter().any(RecomposeScope::is_invalid))
262 }
263
264 fn slot_has_inactive_scopes(&self, slot: SlotId) -> bool {
265 self.slot_to_scopes
266 .get(&slot)
267 .is_some_and(|scopes| scopes.iter().any(|scope| !scope.is_active()))
268 }
269
270 fn deactivate_slot(&self, slot: SlotId) {
271 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
272 for scope in scopes {
273 scope.deactivate();
274 }
275 }
276 }
277
278 fn invalidate_scopes(&self) {
279 for scopes in self.slot_to_scopes.values() {
280 for scope in scopes {
281 scope.invalidate();
282 }
283 }
284 }
285}
286
287pub struct SubcomposeState {
290 mapping: NodeSlotMapping,
291 active_order: Vec<SlotId>,
292 live_slots: HashSet<SlotId>,
293 current_pass_active_slots: HashSet<SlotId>,
294 reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
295 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
296 reusable_node_counts: HashMap<SlotId, usize>,
297 exact_reactivation_slots: HashSet<SlotId>,
298 slot_content_types: HashMap<SlotId, u64>,
299 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
300 policy: Box<dyn SlotReusePolicy>,
301 pub(crate) current_index: usize,
302 pub(crate) reusable_count: usize,
303 pub(crate) precomposed_count: usize,
304 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
305 slot_callbacks: HashMap<SlotId, CallbackHolder>,
306 max_reusable_per_type: usize,
307 max_reusable_untyped: usize,
308 last_slot_reused: Option<bool>,
309 retained_capture_keys: HashMap<SlotId, RetainedCaptureKey>,
310 content_generation: std::cell::Cell<u64>,
311 slot_composed_generation: HashMap<SlotId, (u64, u64)>,
312}
313
314struct RetainedCaptureKey {
315 value: Box<dyn Any>,
316 eq: fn(&dyn Any, &dyn Any) -> bool,
317}
318
319fn retained_capture_key_eq<K: PartialEq + 'static>(stored: &dyn Any, candidate: &dyn Any) -> bool {
320 match (stored.downcast_ref::<K>(), candidate.downcast_ref::<K>()) {
321 (Some(stored), Some(candidate)) => stored == candidate,
322 _ => false,
323 }
324}
325
326impl fmt::Debug for SubcomposeState {
327 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
328 f.debug_struct("SubcomposeState")
329 .field("mapping", &self.mapping)
330 .field("active_order", &self.active_order)
331 .field("reusable_by_type_count", &self.reusable_by_type.len())
332 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
333 .field("precomposed_nodes", &self.precomposed_nodes)
334 .field("current_index", &self.current_index)
335 .field("reusable_count", &self.reusable_count)
336 .field("precomposed_count", &self.precomposed_count)
337 .field("slot_compositions_count", &self.slot_compositions.len())
338 .finish()
339 }
340}
341
342impl Default for SubcomposeState {
343 fn default() -> Self {
344 Self::new(Box::new(DefaultSlotReusePolicy))
345 }
346}
347
348const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
349
350const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
351
352impl SubcomposeState {
353 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
355 Self {
356 mapping: NodeSlotMapping::default(),
357 active_order: Vec::new(),
358 live_slots: HashSet::default(),
359 current_pass_active_slots: HashSet::default(),
360 reusable_by_type: HashMap::default(),
361 reusable_nodes_untyped: VecDeque::new(),
362 reusable_node_counts: HashMap::default(),
363 exact_reactivation_slots: HashSet::default(),
364 slot_content_types: HashMap::default(),
365 precomposed_nodes: HashMap::default(),
366 policy,
367 current_index: 0,
368 reusable_count: 0,
369 precomposed_count: 0,
370 slot_compositions: HashMap::default(),
371 slot_callbacks: HashMap::default(),
372 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
373 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
374 last_slot_reused: None,
375 retained_capture_keys: HashMap::default(),
376 content_generation: std::cell::Cell::new(0),
377 slot_composed_generation: HashMap::default(),
378 }
379 }
380
381 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
383 self.policy = policy;
384 }
385
386 pub fn set_reusable_pool_limits(&mut self, per_type: usize, untyped: usize) {
387 self.max_reusable_per_type = per_type;
388 self.max_reusable_untyped = untyped;
389 }
390
391 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
398 self.slot_content_types.insert(slot_id, content_type);
399 self.policy.register_content_type(slot_id, content_type);
400 }
401
402 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
408 match content_type {
409 Some(ct) => self.register_content_type(slot_id, ct),
410 None => {
411 self.slot_content_types.remove(&slot_id);
412 self.policy.remove_content_type(slot_id);
413 }
414 }
415 }
416
417 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
419 self.slot_content_types.get(&slot_id).copied()
420 }
421
422 pub fn active_slot_for_node(&self, node_id: NodeId) -> Option<SlotId> {
427 self.mapping
428 .node_to_slot
429 .get(&node_id)
430 .copied()
431 .filter(|slot| self.live_slots.contains(slot))
432 }
433
434 pub fn begin_pass(&mut self) {
439 self.current_index = 0;
440 self.current_pass_active_slots.clear();
441 }
442
443 pub fn active_slot_cursor(&self) -> usize {
445 self.current_index
446 }
447
448 pub fn restore_active_slot_cursor(&mut self, cursor: usize) {
451 self.current_index = cursor.min(self.active_order.len());
452 }
453
454 pub fn recycle_active_slot(&mut self, slot_id: SlotId) -> Vec<NodeId> {
456 self.recycle_active_slot_internal(slot_id, false)
457 }
458
459 pub fn recycle_prefetched_active_slot(&mut self, slot_id: SlotId) -> Vec<NodeId> {
460 self.recycle_active_slot_internal(slot_id, true)
461 }
462
463 pub fn recycle_active_slots_where(
464 &mut self,
465 mut predicate: impl FnMut(SlotId) -> bool,
466 ) -> Vec<NodeId> {
467 let slots: Vec<_> = self
468 .active_order
469 .iter()
470 .copied()
471 .filter(|slot| !self.current_pass_active_slots.contains(slot))
472 .filter(|slot| predicate(*slot))
473 .collect();
474 let mut disposed = Vec::new();
475 for slot in slots {
476 disposed.extend(self.recycle_active_slot(slot));
477 }
478 disposed
479 }
480
481 fn recycle_active_slot_internal(
482 &mut self,
483 slot_id: SlotId,
484 allow_exact_reactivation: bool,
485 ) -> Vec<NodeId> {
486 let Some(position) = self
487 .active_order
488 .iter()
489 .position(|candidate| *candidate == slot_id)
490 else {
491 return Vec::new();
492 };
493 self.active_order.remove(position);
494 if position < self.current_index {
495 self.current_index = self.current_index.saturating_sub(1);
496 }
497 self.move_slot_to_reusable(slot_id, allow_exact_reactivation);
498 self.enforce_reusable_pool_limits()
499 }
500
501 pub fn finish_pass(&mut self) -> Vec<NodeId> {
503 self.dispose_or_reuse_starting_from_index(self.current_index)
504 }
505
506 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
510 Rc::clone(
511 self.slot_compositions
512 .entry(slot_id)
513 .or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
514 )
515 }
516
517 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
519 self.slot_callbacks.entry(slot_id).or_default().clone()
520 }
521
522 #[doc(hidden)]
523 pub fn activate_current_active_slot(&mut self, slot_id: SlotId) -> Option<Vec<NodeId>> {
524 if self.current_pass_active_slots.contains(&slot_id)
525 || self.exact_reactivation_slots.contains(&slot_id)
526 || self.reusable_node_counts.contains_key(&slot_id)
527 || self
528 .active_order
529 .get(self.current_index)
530 .copied()
531 .is_none_or(|active_slot| active_slot != slot_id)
532 || self.mapping.slot_has_invalid_scopes(slot_id)
533 || self.mapping.slot_has_inactive_scopes(slot_id)
534 {
535 return None;
536 }
537
538 let nodes = self.mapping.get_nodes(&slot_id)?.to_vec();
539 self.last_slot_reused = Some(true);
540 self.live_slots.insert(slot_id);
541 self.current_pass_active_slots.insert(slot_id);
542 self.current_index += 1;
543 Some(nodes)
544 }
545
546 pub fn has_pending_precompositions(&self, slot_id: SlotId) -> bool {
550 self.precomposed_nodes.contains_key(&slot_id)
551 }
552
553 pub fn retained_capture_key_matches<K: PartialEq + 'static>(
557 &self,
558 slot_id: SlotId,
559 key: &K,
560 ) -> bool {
561 self.retained_capture_keys
562 .get(&slot_id)
563 .is_some_and(|retained| (retained.eq)(retained.value.as_ref(), key))
564 }
565
566 pub fn store_retained_capture_key<K: PartialEq + 'static>(&mut self, slot_id: SlotId, key: K) {
568 self.retained_capture_keys.insert(
569 slot_id,
570 RetainedCaptureKey {
571 value: Box::new(key),
572 eq: retained_capture_key_eq::<K>,
573 },
574 );
575 }
576
577 #[doc(hidden)]
578 pub fn take_exact_slot_activation(&mut self, slot_id: SlotId) -> Option<ExactSlotActivation> {
579 let active_position = self
580 .active_order
581 .iter()
582 .position(|candidate| *candidate == slot_id);
583 let is_exact_reactivation = self.exact_reactivation_slots.contains(&slot_id);
584 if active_position.is_none() && !is_exact_reactivation
585 || self.mapping.slot_has_invalid_scopes(slot_id)
586 {
587 return None;
588 }
589
590 let nodes = self.mapping.get_nodes(&slot_id)?.to_vec();
591 let scopes = self
592 .mapping
593 .get_scopes(&slot_id)
594 .unwrap_or_default()
595 .to_vec();
596 if active_position.is_none() {
597 for node in &nodes {
598 let _ = self.remove_from_reusable_pools(*node);
599 }
600 }
601 self.exact_reactivation_slots.remove(&slot_id);
602 Some(ExactSlotActivation {
603 nodes,
604 scopes,
605 reactivate_scopes: active_position.is_none(),
606 })
607 }
608
609 #[doc(hidden)]
610 pub fn take_exact_slot_for_activation(
611 &mut self,
612 slot_id: SlotId,
613 ) -> Option<(Vec<NodeId>, Vec<RecomposeScope>)> {
614 self.take_exact_slot_activation(slot_id)
615 .map(|activation| (activation.nodes, activation.scopes))
616 }
617
618 pub fn register_active(
621 &mut self,
622 slot_id: SlotId,
623 node_ids: &[NodeId],
624 scopes: &[RecomposeScope],
625 ) {
626 self.register_active_with_scope_reactivation(slot_id, node_ids, scopes, true);
627 }
628
629 #[doc(hidden)]
630 pub fn register_active_with_scope_reactivation(
631 &mut self,
632 slot_id: SlotId,
633 node_ids: &[NodeId],
634 scopes: &[RecomposeScope],
635 reactivate_scopes: bool,
636 ) {
637 let was_reused = self.mapping.get_nodes(&slot_id).is_some();
638 self.last_slot_reused = Some(was_reused);
639 self.live_slots.insert(slot_id);
640 self.current_pass_active_slots.insert(slot_id);
641 self.exact_reactivation_slots.remove(&slot_id);
642
643 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
644 if position < self.current_index {
645 if reactivate_scopes {
646 for scope in scopes {
647 scope.reactivate();
648 }
649 }
650 self.update_active_slot_mapping(slot_id, node_ids, scopes);
651 return;
652 }
653 self.active_order.remove(position);
654 }
655 if reactivate_scopes {
656 for scope in scopes {
657 scope.reactivate();
658 }
659 }
660 self.update_active_slot_mapping(slot_id, node_ids, scopes);
661 let insert_at = self.current_index.min(self.active_order.len());
662 self.active_order.insert(insert_at, slot_id);
663 self.current_index += 1;
664 }
665
666 fn update_active_slot_mapping(
667 &mut self,
668 slot_id: SlotId,
669 node_ids: &[NodeId],
670 scopes: &[RecomposeScope],
671 ) {
672 self.mapping.set_nodes(slot_id, node_ids);
673 self.mapping.set_scopes(slot_id, scopes);
674 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
675 let before_len = nodes.len();
676 nodes.retain(|node| !node_ids.contains(node));
677 let removed = before_len - nodes.len();
678 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
679 if nodes.is_empty() {
680 self.precomposed_nodes.remove(&slot_id);
681 }
682 }
683 }
684
685 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
688 self.precomposed_nodes
689 .entry(slot_id)
690 .or_default()
691 .push(node_id);
692 self.precomposed_count += 1;
693 }
694
695 fn increment_reusable_slot(&mut self, slot_id: SlotId) {
696 *self.reusable_node_counts.entry(slot_id).or_insert(0) += 1;
697 self.reusable_count += 1;
698 }
699
700 fn decrement_reusable_slot(&mut self, slot_id: SlotId) {
701 let Some(entry) = self.reusable_node_counts.get_mut(&slot_id) else {
702 self.reusable_count = self.reusable_count.saturating_sub(1);
703 return;
704 };
705 *entry = entry.saturating_sub(1);
706 if *entry == 0 {
707 self.reusable_node_counts.remove(&slot_id);
708 }
709 self.reusable_count = self.reusable_count.saturating_sub(1);
710 }
711
712 fn prune_slot_if_unused(&mut self, slot_id: SlotId) {
713 if self.live_slots.contains(&slot_id) || self.reusable_node_counts.contains_key(&slot_id) {
714 return;
715 }
716
717 debug_assert!(
718 self.mapping.get_nodes(&slot_id).is_none(),
719 "inactive slot {slot_id:?} still has mapped nodes",
720 );
721
722 self.slot_compositions.remove(&slot_id);
723 self.slot_callbacks.remove(&slot_id);
724 self.slot_content_types.remove(&slot_id);
725 self.retained_capture_keys.remove(&slot_id);
726 self.slot_composed_generation.remove(&slot_id);
727 self.policy.remove_content_type(slot_id);
728 if let Some(nodes) = self.precomposed_nodes.remove(&slot_id) {
729 self.precomposed_count = self.precomposed_count.saturating_sub(nodes.len());
730 }
731 }
732
733 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<(NodeId, bool)> {
744 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
745 let first_node = nodes.first().copied();
746 if let Some(node_id) = first_node {
747 let _ = self.remove_from_reusable_pools(node_id);
748 self.exact_reactivation_slots.remove(&slot_id);
749 return Some((node_id, false));
750 }
751 }
752
753 let content_type = self.slot_content_types.get(&slot_id).copied();
754
755 if let Some(ct) = content_type
756 && let Some((old_slot, node_id)) = self.take_compatible_typed_reusable(ct, slot_id)
757 {
758 self.decrement_reusable_slot(old_slot);
759 self.move_node_to_slot(node_id, old_slot, slot_id);
760 return Some((node_id, true));
761 }
762
763 let exact_reactivation_slots = &self.exact_reactivation_slots;
764 let policy = &self.policy;
765 let position = self
766 .reusable_nodes_untyped
767 .iter()
768 .position(|(existing_slot, _)| {
769 !exact_reactivation_slots.contains(existing_slot)
770 && policy.are_compatible(*existing_slot, slot_id)
771 });
772
773 if let Some(index) = position
774 && let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index)
775 {
776 self.decrement_reusable_slot(old_slot);
777 self.move_node_to_slot(node_id, old_slot, slot_id);
778 return Some((node_id, true));
779 }
780
781 None
782 }
783
784 fn take_compatible_typed_reusable(
785 &mut self,
786 content_type: u64,
787 requested_slot: SlotId,
788 ) -> Option<(SlotId, NodeId)> {
789 let reused = {
790 let policy = &self.policy;
791 let exact_reactivation_slots = &self.exact_reactivation_slots;
792 let pool = self.reusable_by_type.get_mut(&content_type)?;
793 let index = pool.iter().position(|(existing_slot, _)| {
794 !exact_reactivation_slots.contains(existing_slot)
795 && policy.are_compatible(*existing_slot, requested_slot)
796 })?;
797 pool.remove(index)
798 };
799
800 if self
801 .reusable_by_type
802 .get(&content_type)
803 .map(|pool| pool.is_empty())
804 .unwrap_or(false)
805 {
806 self.reusable_by_type.remove(&content_type);
807 }
808
809 reused
810 }
811
812 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> Option<SlotId> {
813 let mut typed_match = None;
814 for (&content_type, pool) in &self.reusable_by_type {
815 if let Some(position) = pool
816 .iter()
817 .position(|(_, pooled_node)| *pooled_node == node_id)
818 {
819 typed_match = Some((content_type, position));
820 break;
821 }
822 }
823 if let Some((content_type, position)) = typed_match {
824 let pool = self.reusable_by_type.get_mut(&content_type)?;
825 let (slot, _) = pool.remove(position)?;
826 if pool.is_empty() {
827 self.reusable_by_type.remove(&content_type);
828 }
829 self.decrement_reusable_slot(slot);
830 return Some(slot);
831 }
832 if let Some(position) = self
833 .reusable_nodes_untyped
834 .iter()
835 .position(|(_, n)| *n == node_id)
836 && let Some((slot, _)) = self.reusable_nodes_untyped.remove(position)
837 {
838 self.decrement_reusable_slot(slot);
839 return Some(slot);
840 }
841 None
842 }
843
844 fn move_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
845 if old_slot == new_slot {
846 return;
847 }
848
849 self.mapping.remove_by_node(&node_id);
850 self.exact_reactivation_slots.remove(&old_slot);
851 self.mapping.add_node(new_slot, node_id);
852 self.slot_content_types.remove(&old_slot);
853 self.retained_capture_keys.remove(&old_slot);
854 self.retained_capture_keys.remove(&new_slot);
855 self.slot_composed_generation.remove(&old_slot);
856 self.slot_composed_generation.remove(&new_slot);
857 self.policy.remove_content_type(old_slot);
858 if let Some(slots) = self.slot_compositions.remove(&old_slot) {
859 self.slot_compositions.insert(new_slot, slots);
860 }
861 if let Some(callback) = self.slot_callbacks.remove(&old_slot) {
862 self.slot_callbacks.insert(new_slot, callback);
863 }
864 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
865 let before_len = nodes.len();
866 nodes.retain(|candidate| *candidate != node_id);
867 let removed = before_len - nodes.len();
868 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
869 if nodes.is_empty() {
870 self.precomposed_nodes.remove(&old_slot);
871 }
872 }
873 }
874
875 pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
879 if start_index >= self.active_order.len() {
880 return Vec::new();
881 }
882
883 let retain = self
884 .policy
885 .get_slots_to_retain(&self.active_order[start_index..]);
886 let mut retained = Vec::new();
887 while self.active_order.len() > start_index {
888 let Some(slot) = self.active_order.pop() else {
889 break;
890 };
891 if retain.contains(&slot) {
892 retained.push(slot);
893 continue;
894 }
895 self.move_slot_to_reusable(slot, false);
896 }
897 retained.reverse();
898 self.active_order.extend(retained);
899
900 self.enforce_reusable_pool_limits()
901 }
902
903 fn move_slot_to_reusable(&mut self, slot: SlotId, allow_exact_reactivation: bool) {
904 self.live_slots.remove(&slot);
905 self.current_pass_active_slots.remove(&slot);
906 self.mapping.deactivate_slot(slot);
907 let forgot_effects = self
908 .slot_compositions
909 .get(&slot)
910 .is_some_and(|host| host.forget_effects());
911 if allow_exact_reactivation && !forgot_effects {
912 self.exact_reactivation_slots.insert(slot);
913 } else {
914 self.exact_reactivation_slots.remove(&slot);
915 }
916
917 let content_type = self.slot_content_types.get(&slot).copied();
918 let nodes: SmallVec<[NodeId; 4]> = self
919 .mapping
920 .get_nodes(&slot)
921 .into_iter()
922 .flatten()
923 .copied()
924 .collect();
925 for node in nodes {
926 if let Some(ct) = content_type {
927 self.reusable_by_type
928 .entry(ct)
929 .or_default()
930 .push_back((slot, node));
931 } else {
932 self.reusable_nodes_untyped.push_back((slot, node));
933 }
934 self.increment_reusable_slot(slot);
935 }
936 }
937
938 fn enforce_reusable_pool_limits(&mut self) -> Vec<NodeId> {
939 let mut disposed = Vec::new();
940 let mut typed_disposals = Vec::new();
941 for pool in self.reusable_by_type.values_mut() {
942 while pool.len() > self.max_reusable_per_type {
943 if let Some((slot, node_id)) = pool.pop_front() {
944 typed_disposals.push((slot, node_id));
945 }
946 }
947 }
948 for (slot, node_id) in typed_disposals {
949 self.decrement_reusable_slot(slot);
950 self.mapping.remove_by_node(&node_id);
951 self.exact_reactivation_slots.remove(&slot);
952 disposed.push(node_id);
953 }
954
955 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
956 if let Some((slot, node_id)) = self.reusable_nodes_untyped.pop_front() {
957 self.decrement_reusable_slot(slot);
958 self.mapping.remove_by_node(&node_id);
959 self.exact_reactivation_slots.remove(&slot);
960 disposed.push(node_id);
961 }
962 }
963
964 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
965 disposed
966 }
967
968 pub fn reusable(&self) -> Vec<NodeId> {
970 let mut nodes: Vec<NodeId> = self
971 .reusable_by_type
972 .values()
973 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
974 .collect();
975 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
976 nodes
977 }
978
979 pub fn active_slots_count(&self) -> usize {
984 self.active_order.len()
985 }
986
987 pub fn reusable_slots_count(&self) -> usize {
992 self.reusable_count
993 }
994
995 pub fn invalidate_scopes(&self) {
1008 self.mapping.invalidate_scopes();
1009 self.content_generation
1010 .set(self.content_generation.get() + 1);
1011 }
1012
1013 pub fn bump_content_generation(&self) {
1020 self.content_generation
1021 .set(self.content_generation.get() + 1);
1022 }
1023
1024 pub fn slot_content_generation_current(&self, slot_id: SlotId, owner_epoch: u64) -> bool {
1030 self.slot_composed_generation.get(&slot_id).copied()
1031 == Some((self.content_generation.get(), owner_epoch))
1032 }
1033
1034 pub fn mark_slot_composed_current(&mut self, slot_id: SlotId, owner_epoch: u64) {
1037 self.slot_composed_generation
1038 .insert(slot_id, (self.content_generation.get(), owner_epoch));
1039 }
1040
1041 pub fn was_last_slot_reused(&self) -> Option<bool> {
1049 self.last_slot_reused
1050 }
1051
1052 #[doc(hidden)]
1053 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
1054 self.mapping
1055 .slot_to_scopes
1056 .iter()
1057 .map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
1058 .collect()
1059 }
1060
1061 #[doc(hidden)]
1062 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<crate::SlotDebugEntry>> {
1063 let slots = self.slot_compositions.get(&slot_id)?;
1064 Some(slots.borrow().debug_dump_slot_entries())
1065 }
1066
1067 #[doc(hidden)]
1068 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
1069 let slots = self.slot_compositions.get(&slot_id)?;
1070 Some(slots.borrow().debug_dump_groups())
1071 }
1072
1073 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
1075 &self.precomposed_nodes
1076 }
1077
1078 pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
1081 let mut disposed = Vec::new();
1082 let mut empty_slots = Vec::new();
1083 for (slot, nodes) in self.precomposed_nodes.iter_mut() {
1084 if !self.current_pass_active_slots.contains(slot) {
1085 disposed.extend(nodes.iter().copied());
1086 empty_slots.push(*slot);
1087 }
1088 }
1089 for slot in empty_slots {
1090 self.precomposed_nodes.remove(&slot);
1091 self.prune_slot_if_unused(slot);
1092 }
1093 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
1094 disposed
1095 }
1096}
1097
1098#[cfg(test)]
1099#[path = "tests/subcompose_tests.rs"]
1100mod tests;