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 }
68
69 fn remove_content_type(&self, _slot_id: SlotId) {
74 }
76}
77
78#[derive(Debug, Default)]
82pub struct DefaultSlotReusePolicy;
83
84impl SlotReusePolicy for DefaultSlotReusePolicy {
85 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
86 let _ = active;
87 HashSet::default()
88 }
89
90 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
91 existing == requested
92 }
93}
94
95pub struct ContentTypeReusePolicy {
117 slot_types: std::cell::RefCell<HashMap<SlotId, u64>>,
119}
120
121impl std::fmt::Debug for ContentTypeReusePolicy {
122 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
123 let types = self.slot_types.borrow();
124 f.debug_struct("ContentTypeReusePolicy")
125 .field("slot_types", &*types)
126 .finish()
127 }
128}
129
130impl Default for ContentTypeReusePolicy {
131 fn default() -> Self {
132 Self::new()
133 }
134}
135
136impl ContentTypeReusePolicy {
137 pub fn new() -> Self {
139 Self {
140 slot_types: std::cell::RefCell::new(HashMap::default()),
141 }
142 }
143
144 pub fn set_content_type(&self, slot: SlotId, content_type: u64) {
148 self.slot_types.borrow_mut().insert(slot, content_type);
149 }
150
151 pub fn remove_content_type(&self, slot: SlotId) {
153 self.slot_types.borrow_mut().remove(&slot);
154 }
155
156 pub fn clear(&self) {
158 self.slot_types.borrow_mut().clear();
159 }
160
161 pub fn get_content_type(&self, slot: SlotId) -> Option<u64> {
163 self.slot_types.borrow().get(&slot).copied()
164 }
165}
166
167impl SlotReusePolicy for ContentTypeReusePolicy {
168 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
169 let _ = active;
170 HashSet::default()
172 }
173
174 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
175 if existing == requested {
176 return true;
177 }
178
179 let types = self.slot_types.borrow();
180 match (types.get(&existing), types.get(&requested)) {
181 (Some(existing_type), Some(requested_type)) => existing_type == requested_type,
182 (None, None) => true,
183 _ => false,
184 }
185 }
186
187 fn register_content_type(&self, slot_id: SlotId, content_type: u64) {
188 self.set_content_type(slot_id, content_type);
189 }
190
191 fn remove_content_type(&self, slot_id: SlotId) {
192 ContentTypeReusePolicy::remove_content_type(self, slot_id);
193 }
194}
195
196#[doc(hidden)]
197pub struct ExactSlotActivation {
198 pub nodes: Vec<NodeId>,
199 pub scopes: Vec<RecomposeScope>,
200 pub reactivate_scopes: bool,
201}
202
203#[derive(Default, Clone)]
204
205struct NodeSlotMapping {
206 slot_to_nodes: HashMap<SlotId, Vec<NodeId>>,
207 node_to_slot: HashMap<NodeId, SlotId>,
208 slot_to_scopes: HashMap<SlotId, Vec<RecomposeScope>>,
209}
210
211impl fmt::Debug for NodeSlotMapping {
212 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
213 f.debug_struct("NodeSlotMapping")
214 .field("slot_to_nodes", &self.slot_to_nodes)
215 .field("node_to_slot", &self.node_to_slot)
216 .finish()
217 }
218}
219
220impl NodeSlotMapping {
221 fn set_nodes(&mut self, slot: SlotId, nodes: &[NodeId]) {
222 self.slot_to_nodes.insert(slot, nodes.to_vec());
223 for node in nodes {
224 self.node_to_slot.insert(*node, slot);
225 }
226 }
227
228 fn set_scopes(&mut self, slot: SlotId, scopes: &[RecomposeScope]) {
229 self.slot_to_scopes.insert(slot, scopes.to_vec());
230 }
231
232 fn add_node(&mut self, slot: SlotId, node: NodeId) {
233 self.slot_to_nodes.entry(slot).or_default().push(node);
234 self.node_to_slot.insert(node, slot);
235 }
236
237 fn remove_by_node(&mut self, node: &NodeId) -> Option<SlotId> {
238 if let Some(slot) = self.node_to_slot.remove(node) {
239 if let Some(nodes) = self.slot_to_nodes.get_mut(&slot) {
240 if let Some(index) = nodes.iter().position(|candidate| candidate == node) {
241 nodes.remove(index);
242 }
243 if nodes.is_empty() {
244 self.slot_to_nodes.remove(&slot);
245 self.slot_to_scopes.remove(&slot);
247 }
248 }
249 Some(slot)
250 } else {
251 None
252 }
253 }
254
255 fn get_nodes(&self, slot: &SlotId) -> Option<&[NodeId]> {
256 self.slot_to_nodes.get(slot).map(|nodes| nodes.as_slice())
257 }
258
259 fn get_scopes(&self, slot: &SlotId) -> Option<&[RecomposeScope]> {
260 self.slot_to_scopes
261 .get(slot)
262 .map(|scopes| scopes.as_slice())
263 }
264
265 fn slot_has_invalid_scopes(&self, slot: SlotId) -> bool {
266 self.slot_to_scopes
267 .get(&slot)
268 .is_some_and(|scopes| scopes.iter().any(RecomposeScope::is_invalid))
269 }
270
271 fn slot_has_inactive_scopes(&self, slot: SlotId) -> bool {
272 self.slot_to_scopes
273 .get(&slot)
274 .is_some_and(|scopes| scopes.iter().any(|scope| !scope.is_active()))
275 }
276
277 fn deactivate_slot(&self, slot: SlotId) {
278 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
279 for scope in scopes {
280 scope.deactivate();
281 }
282 }
283 }
284
285 fn invalidate_scopes(&self) {
286 for scopes in self.slot_to_scopes.values() {
287 for scope in scopes {
288 scope.invalidate();
289 }
290 }
291 }
292}
293
294pub struct SubcomposeState {
297 mapping: NodeSlotMapping,
298 active_order: Vec<SlotId>,
299 live_slots: HashSet<SlotId>,
300 current_pass_active_slots: HashSet<SlotId>,
301 reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
305 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
307 reusable_node_counts: HashMap<SlotId, usize>,
308 exact_reactivation_slots: HashSet<SlotId>,
309 slot_content_types: HashMap<SlotId, u64>,
311 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
312 policy: Box<dyn SlotReusePolicy>,
313 pub(crate) current_index: usize,
314 pub(crate) reusable_count: usize,
315 pub(crate) precomposed_count: usize,
316 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
320 slot_callbacks: HashMap<SlotId, CallbackHolder>,
322 max_reusable_per_type: usize,
324 max_reusable_untyped: usize,
326 last_slot_reused: Option<bool>,
329 retained_capture_keys: HashMap<SlotId, RetainedCaptureKey>,
333 content_generation: std::cell::Cell<u64>,
336 slot_composed_generation: HashMap<SlotId, (u64, u64)>,
339}
340
341struct RetainedCaptureKey {
346 value: Box<dyn Any>,
347 eq: fn(&dyn Any, &dyn Any) -> bool,
348}
349
350fn retained_capture_key_eq<K: PartialEq + 'static>(stored: &dyn Any, candidate: &dyn Any) -> bool {
351 match (stored.downcast_ref::<K>(), candidate.downcast_ref::<K>()) {
352 (Some(stored), Some(candidate)) => stored == candidate,
353 _ => false,
354 }
355}
356
357impl fmt::Debug for SubcomposeState {
358 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
359 f.debug_struct("SubcomposeState")
360 .field("mapping", &self.mapping)
361 .field("active_order", &self.active_order)
362 .field("reusable_by_type_count", &self.reusable_by_type.len())
363 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
364 .field("precomposed_nodes", &self.precomposed_nodes)
365 .field("current_index", &self.current_index)
366 .field("reusable_count", &self.reusable_count)
367 .field("precomposed_count", &self.precomposed_count)
368 .field("slot_compositions_count", &self.slot_compositions.len())
369 .finish()
370 }
371}
372
373impl Default for SubcomposeState {
374 fn default() -> Self {
375 Self::new(Box::new(DefaultSlotReusePolicy))
376 }
377}
378
379const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
382
383const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
387
388impl SubcomposeState {
389 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
391 Self {
392 mapping: NodeSlotMapping::default(),
393 active_order: Vec::new(),
394 live_slots: HashSet::default(),
395 current_pass_active_slots: HashSet::default(),
396 reusable_by_type: HashMap::default(),
397 reusable_nodes_untyped: VecDeque::new(),
398 reusable_node_counts: HashMap::default(),
399 exact_reactivation_slots: HashSet::default(),
400 slot_content_types: HashMap::default(),
401 precomposed_nodes: HashMap::default(),
402 policy,
403 current_index: 0,
404 reusable_count: 0,
405 precomposed_count: 0,
406 slot_compositions: HashMap::default(),
407 slot_callbacks: HashMap::default(),
408 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
409 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
410 last_slot_reused: None,
411 retained_capture_keys: HashMap::default(),
412 content_generation: std::cell::Cell::new(0),
413 slot_composed_generation: HashMap::default(),
414 }
415 }
416
417 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
419 self.policy = policy;
420 }
421
422 pub fn set_reusable_pool_limits(&mut self, per_type: usize, untyped: usize) {
423 self.max_reusable_per_type = per_type;
424 self.max_reusable_untyped = untyped;
425 }
426
427 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
434 self.slot_content_types.insert(slot_id, content_type);
435 self.policy.register_content_type(slot_id, content_type);
436 }
437
438 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
444 match content_type {
445 Some(ct) => self.register_content_type(slot_id, ct),
446 None => {
447 self.slot_content_types.remove(&slot_id);
448 self.policy.remove_content_type(slot_id);
449 }
450 }
451 }
452
453 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
455 self.slot_content_types.get(&slot_id).copied()
456 }
457
458 pub fn begin_pass(&mut self) {
463 self.current_index = 0;
464 self.current_pass_active_slots.clear();
465 }
466
467 pub fn active_slot_cursor(&self) -> usize {
469 self.current_index
470 }
471
472 pub fn restore_active_slot_cursor(&mut self, cursor: usize) {
475 self.current_index = cursor.min(self.active_order.len());
476 }
477
478 pub fn recycle_active_slot(&mut self, slot_id: SlotId) -> Vec<NodeId> {
480 self.recycle_active_slot_internal(slot_id, false)
481 }
482
483 pub fn recycle_prefetched_active_slot(&mut self, slot_id: SlotId) -> Vec<NodeId> {
484 self.recycle_active_slot_internal(slot_id, true)
485 }
486
487 pub fn recycle_active_slots_where(
488 &mut self,
489 mut predicate: impl FnMut(SlotId) -> bool,
490 ) -> Vec<NodeId> {
491 let slots: Vec<_> = self
492 .active_order
493 .iter()
494 .copied()
495 .filter(|slot| !self.current_pass_active_slots.contains(slot))
496 .filter(|slot| predicate(*slot))
497 .collect();
498 let mut disposed = Vec::new();
499 for slot in slots {
500 disposed.extend(self.recycle_active_slot(slot));
501 }
502 disposed
503 }
504
505 fn recycle_active_slot_internal(
506 &mut self,
507 slot_id: SlotId,
508 allow_exact_reactivation: bool,
509 ) -> Vec<NodeId> {
510 let Some(position) = self
511 .active_order
512 .iter()
513 .position(|candidate| *candidate == slot_id)
514 else {
515 return Vec::new();
516 };
517 self.active_order.remove(position);
518 if position < self.current_index {
519 self.current_index = self.current_index.saturating_sub(1);
520 }
521 self.move_slot_to_reusable(slot_id, allow_exact_reactivation);
522 self.enforce_reusable_pool_limits()
523 }
524
525 pub fn finish_pass(&mut self) -> Vec<NodeId> {
527 self.dispose_or_reuse_starting_from_index(self.current_index)
528 }
529
530 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
534 Rc::clone(
535 self.slot_compositions
536 .entry(slot_id)
537 .or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
538 )
539 }
540
541 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
543 self.slot_callbacks.entry(slot_id).or_default().clone()
544 }
545
546 #[doc(hidden)]
547 pub fn activate_current_active_slot(&mut self, slot_id: SlotId) -> Option<Vec<NodeId>> {
548 if self.current_pass_active_slots.contains(&slot_id)
549 || self.exact_reactivation_slots.contains(&slot_id)
550 || self.reusable_node_counts.contains_key(&slot_id)
551 || self
552 .active_order
553 .get(self.current_index)
554 .copied()
555 .is_none_or(|active_slot| active_slot != slot_id)
556 || self.mapping.slot_has_invalid_scopes(slot_id)
557 || self.mapping.slot_has_inactive_scopes(slot_id)
558 {
559 return None;
560 }
561
562 let nodes = self.mapping.get_nodes(&slot_id)?.to_vec();
563 self.last_slot_reused = Some(true);
564 self.live_slots.insert(slot_id);
565 self.current_pass_active_slots.insert(slot_id);
566 self.current_index += 1;
567 Some(nodes)
568 }
569
570 pub fn has_pending_precompositions(&self, slot_id: SlotId) -> bool {
574 self.precomposed_nodes.contains_key(&slot_id)
575 }
576
577 pub fn retained_capture_key_matches<K: PartialEq + 'static>(
581 &self,
582 slot_id: SlotId,
583 key: &K,
584 ) -> bool {
585 self.retained_capture_keys
586 .get(&slot_id)
587 .is_some_and(|retained| (retained.eq)(retained.value.as_ref(), key))
588 }
589
590 pub fn store_retained_capture_key<K: PartialEq + 'static>(&mut self, slot_id: SlotId, key: K) {
592 self.retained_capture_keys.insert(
593 slot_id,
594 RetainedCaptureKey {
595 value: Box::new(key),
596 eq: retained_capture_key_eq::<K>,
597 },
598 );
599 }
600
601 #[doc(hidden)]
602 pub fn take_exact_slot_activation(&mut self, slot_id: SlotId) -> Option<ExactSlotActivation> {
603 let active_position = self
604 .active_order
605 .iter()
606 .position(|candidate| *candidate == slot_id);
607 let is_exact_reactivation = self.exact_reactivation_slots.contains(&slot_id);
608 if active_position.is_none() && !is_exact_reactivation
609 || self.mapping.slot_has_invalid_scopes(slot_id)
610 {
611 return None;
612 }
613
614 let nodes = self.mapping.get_nodes(&slot_id)?.to_vec();
615 let scopes = self
616 .mapping
617 .get_scopes(&slot_id)
618 .unwrap_or_default()
619 .to_vec();
620 if active_position.is_none() {
621 for node in &nodes {
622 let _ = self.remove_from_reusable_pools(*node);
623 }
624 }
625 self.exact_reactivation_slots.remove(&slot_id);
626 Some(ExactSlotActivation {
627 nodes,
628 scopes,
629 reactivate_scopes: active_position.is_none(),
630 })
631 }
632
633 #[doc(hidden)]
634 pub fn take_exact_slot_for_activation(
635 &mut self,
636 slot_id: SlotId,
637 ) -> Option<(Vec<NodeId>, Vec<RecomposeScope>)> {
638 self.take_exact_slot_activation(slot_id)
639 .map(|activation| (activation.nodes, activation.scopes))
640 }
641
642 pub fn register_active(
645 &mut self,
646 slot_id: SlotId,
647 node_ids: &[NodeId],
648 scopes: &[RecomposeScope],
649 ) {
650 self.register_active_with_scope_reactivation(slot_id, node_ids, scopes, true);
651 }
652
653 #[doc(hidden)]
654 pub fn register_active_with_scope_reactivation(
655 &mut self,
656 slot_id: SlotId,
657 node_ids: &[NodeId],
658 scopes: &[RecomposeScope],
659 reactivate_scopes: bool,
660 ) {
661 let was_reused = self.mapping.get_nodes(&slot_id).is_some();
662 self.last_slot_reused = Some(was_reused);
663 self.live_slots.insert(slot_id);
664 self.current_pass_active_slots.insert(slot_id);
665 self.exact_reactivation_slots.remove(&slot_id);
666
667 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
668 if position < self.current_index {
669 if reactivate_scopes {
670 for scope in scopes {
671 scope.reactivate();
672 }
673 }
674 self.update_active_slot_mapping(slot_id, node_ids, scopes);
675 return;
676 }
677 self.active_order.remove(position);
678 }
679 if reactivate_scopes {
680 for scope in scopes {
681 scope.reactivate();
682 }
683 }
684 self.update_active_slot_mapping(slot_id, node_ids, scopes);
685 let insert_at = self.current_index.min(self.active_order.len());
686 self.active_order.insert(insert_at, slot_id);
687 self.current_index += 1;
688 }
689
690 fn update_active_slot_mapping(
691 &mut self,
692 slot_id: SlotId,
693 node_ids: &[NodeId],
694 scopes: &[RecomposeScope],
695 ) {
696 self.mapping.set_nodes(slot_id, node_ids);
697 self.mapping.set_scopes(slot_id, scopes);
698 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
699 let before_len = nodes.len();
700 nodes.retain(|node| !node_ids.contains(node));
701 let removed = before_len - nodes.len();
702 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
703 if nodes.is_empty() {
704 self.precomposed_nodes.remove(&slot_id);
705 }
706 }
707 }
708
709 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
712 self.precomposed_nodes
713 .entry(slot_id)
714 .or_default()
715 .push(node_id);
716 self.precomposed_count += 1;
717 }
718
719 fn increment_reusable_slot(&mut self, slot_id: SlotId) {
720 *self.reusable_node_counts.entry(slot_id).or_insert(0) += 1;
721 self.reusable_count += 1;
722 }
723
724 fn decrement_reusable_slot(&mut self, slot_id: SlotId) {
725 let Some(entry) = self.reusable_node_counts.get_mut(&slot_id) else {
726 self.reusable_count = self.reusable_count.saturating_sub(1);
727 return;
728 };
729 *entry = entry.saturating_sub(1);
730 if *entry == 0 {
731 self.reusable_node_counts.remove(&slot_id);
732 }
733 self.reusable_count = self.reusable_count.saturating_sub(1);
734 }
735
736 fn prune_slot_if_unused(&mut self, slot_id: SlotId) {
737 if self.live_slots.contains(&slot_id) || self.reusable_node_counts.contains_key(&slot_id) {
738 return;
739 }
740
741 debug_assert!(
742 self.mapping.get_nodes(&slot_id).is_none(),
743 "inactive slot {slot_id:?} still has mapped nodes",
744 );
745
746 self.slot_compositions.remove(&slot_id);
747 self.slot_callbacks.remove(&slot_id);
748 self.slot_content_types.remove(&slot_id);
749 self.retained_capture_keys.remove(&slot_id);
750 self.slot_composed_generation.remove(&slot_id);
751 self.policy.remove_content_type(slot_id);
752 if let Some(nodes) = self.precomposed_nodes.remove(&slot_id) {
753 self.precomposed_count = self.precomposed_count.saturating_sub(nodes.len());
754 }
755 }
756
757 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<(NodeId, bool)> {
768 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
769 let first_node = nodes.first().copied();
770 if let Some(node_id) = first_node {
771 let _ = self.remove_from_reusable_pools(node_id);
772 self.exact_reactivation_slots.remove(&slot_id);
773 return Some((node_id, false));
774 }
775 }
776
777 let content_type = self.slot_content_types.get(&slot_id).copied();
778
779 if let Some(ct) = content_type
780 && let Some((old_slot, node_id)) = self.take_compatible_typed_reusable(ct, slot_id)
781 {
782 self.decrement_reusable_slot(old_slot);
783 self.move_node_to_slot(node_id, old_slot, slot_id);
784 return Some((node_id, true));
785 }
786
787 let exact_reactivation_slots = &self.exact_reactivation_slots;
788 let policy = &self.policy;
789 let position = self
790 .reusable_nodes_untyped
791 .iter()
792 .position(|(existing_slot, _)| {
793 !exact_reactivation_slots.contains(existing_slot)
794 && policy.are_compatible(*existing_slot, slot_id)
795 });
796
797 if let Some(index) = position
798 && let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index)
799 {
800 self.decrement_reusable_slot(old_slot);
801 self.move_node_to_slot(node_id, old_slot, slot_id);
802 return Some((node_id, true));
803 }
804
805 None
806 }
807
808 fn take_compatible_typed_reusable(
809 &mut self,
810 content_type: u64,
811 requested_slot: SlotId,
812 ) -> Option<(SlotId, NodeId)> {
813 let reused = {
814 let policy = &self.policy;
815 let exact_reactivation_slots = &self.exact_reactivation_slots;
816 let pool = self.reusable_by_type.get_mut(&content_type)?;
817 let index = pool.iter().position(|(existing_slot, _)| {
818 !exact_reactivation_slots.contains(existing_slot)
819 && policy.are_compatible(*existing_slot, requested_slot)
820 })?;
821 pool.remove(index)
822 };
823
824 if self
825 .reusable_by_type
826 .get(&content_type)
827 .map(|pool| pool.is_empty())
828 .unwrap_or(false)
829 {
830 self.reusable_by_type.remove(&content_type);
831 }
832
833 reused
834 }
835
836 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> Option<SlotId> {
838 let mut typed_match = None;
840 for (&content_type, pool) in &self.reusable_by_type {
841 if let Some(position) = pool
842 .iter()
843 .position(|(_, pooled_node)| *pooled_node == node_id)
844 {
845 typed_match = Some((content_type, position));
846 break;
847 }
848 }
849 if let Some((content_type, position)) = typed_match {
850 let pool = self.reusable_by_type.get_mut(&content_type)?;
851 let (slot, _) = pool.remove(position)?;
852 if pool.is_empty() {
853 self.reusable_by_type.remove(&content_type);
854 }
855 self.decrement_reusable_slot(slot);
856 return Some(slot);
857 }
858 if let Some(position) = self
860 .reusable_nodes_untyped
861 .iter()
862 .position(|(_, n)| *n == node_id)
863 && let Some((slot, _)) = self.reusable_nodes_untyped.remove(position)
864 {
865 self.decrement_reusable_slot(slot);
866 return Some(slot);
867 }
868 None
869 }
870
871 fn move_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
873 if old_slot == new_slot {
874 return;
875 }
876
877 self.mapping.remove_by_node(&node_id);
878 self.exact_reactivation_slots.remove(&old_slot);
879 self.mapping.add_node(new_slot, node_id);
880 self.slot_content_types.remove(&old_slot);
881 self.retained_capture_keys.remove(&old_slot);
882 self.retained_capture_keys.remove(&new_slot);
883 self.slot_composed_generation.remove(&old_slot);
884 self.slot_composed_generation.remove(&new_slot);
885 self.policy.remove_content_type(old_slot);
886 if let Some(slots) = self.slot_compositions.remove(&old_slot) {
887 self.slot_compositions.insert(new_slot, slots);
888 }
889 if let Some(callback) = self.slot_callbacks.remove(&old_slot) {
890 self.slot_callbacks.insert(new_slot, callback);
891 }
892 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
893 let before_len = nodes.len();
894 nodes.retain(|candidate| *candidate != node_id);
895 let removed = before_len - nodes.len();
896 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
897 if nodes.is_empty() {
898 self.precomposed_nodes.remove(&old_slot);
899 }
900 }
901 }
902
903 pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
907 if start_index >= self.active_order.len() {
908 return Vec::new();
909 }
910
911 let retain = self
912 .policy
913 .get_slots_to_retain(&self.active_order[start_index..]);
914 let mut retained = Vec::new();
915 while self.active_order.len() > start_index {
916 let Some(slot) = self.active_order.pop() else {
917 break;
918 };
919 if retain.contains(&slot) {
920 retained.push(slot);
921 continue;
922 }
923 self.move_slot_to_reusable(slot, false);
924 }
925 retained.reverse();
926 self.active_order.extend(retained);
927
928 self.enforce_reusable_pool_limits()
929 }
930
931 fn move_slot_to_reusable(&mut self, slot: SlotId, allow_exact_reactivation: bool) {
932 self.live_slots.remove(&slot);
933 self.current_pass_active_slots.remove(&slot);
934 self.mapping.deactivate_slot(slot);
935 let forgot_effects = self
936 .slot_compositions
937 .get(&slot)
938 .is_some_and(|host| host.forget_effects());
939 if allow_exact_reactivation && !forgot_effects {
940 self.exact_reactivation_slots.insert(slot);
941 } else {
942 self.exact_reactivation_slots.remove(&slot);
943 }
944
945 let content_type = self.slot_content_types.get(&slot).copied();
946 let nodes: SmallVec<[NodeId; 4]> = self
947 .mapping
948 .get_nodes(&slot)
949 .into_iter()
950 .flatten()
951 .copied()
952 .collect();
953 for node in nodes {
954 if let Some(ct) = content_type {
955 self.reusable_by_type
956 .entry(ct)
957 .or_default()
958 .push_back((slot, node));
959 } else {
960 self.reusable_nodes_untyped.push_back((slot, node));
961 }
962 self.increment_reusable_slot(slot);
963 }
964 }
965
966 fn enforce_reusable_pool_limits(&mut self) -> Vec<NodeId> {
967 let mut disposed = Vec::new();
968 let mut typed_disposals = Vec::new();
969 for pool in self.reusable_by_type.values_mut() {
970 while pool.len() > self.max_reusable_per_type {
971 if let Some((slot, node_id)) = pool.pop_front() {
972 typed_disposals.push((slot, node_id));
973 }
974 }
975 }
976 for (slot, node_id) in typed_disposals {
977 self.decrement_reusable_slot(slot);
978 self.mapping.remove_by_node(&node_id);
979 self.exact_reactivation_slots.remove(&slot);
980 disposed.push(node_id);
981 }
982
983 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
985 if let Some((slot, node_id)) = self.reusable_nodes_untyped.pop_front() {
986 self.decrement_reusable_slot(slot);
987 self.mapping.remove_by_node(&node_id);
988 self.exact_reactivation_slots.remove(&slot);
989 disposed.push(node_id);
990 }
991 }
992
993 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
994 disposed
995 }
996
997 pub fn reusable(&self) -> Vec<NodeId> {
999 let mut nodes: Vec<NodeId> = self
1000 .reusable_by_type
1001 .values()
1002 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
1003 .collect();
1004 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
1005 nodes
1006 }
1007
1008 pub fn active_slots_count(&self) -> usize {
1013 self.active_order.len()
1014 }
1015
1016 pub fn reusable_slots_count(&self) -> usize {
1021 self.reusable_count
1022 }
1023
1024 pub fn invalidate_scopes(&self) {
1037 self.mapping.invalidate_scopes();
1038 self.content_generation
1039 .set(self.content_generation.get() + 1);
1040 }
1041
1042 pub fn bump_content_generation(&self) {
1049 self.content_generation
1050 .set(self.content_generation.get() + 1);
1051 }
1052
1053 pub fn slot_content_generation_current(&self, slot_id: SlotId, owner_epoch: u64) -> bool {
1059 self.slot_composed_generation.get(&slot_id).copied()
1060 == Some((self.content_generation.get(), owner_epoch))
1061 }
1062
1063 pub fn mark_slot_composed_current(&mut self, slot_id: SlotId, owner_epoch: u64) {
1066 self.slot_composed_generation
1067 .insert(slot_id, (self.content_generation.get(), owner_epoch));
1068 }
1069
1070 pub fn was_last_slot_reused(&self) -> Option<bool> {
1078 self.last_slot_reused
1079 }
1080
1081 #[doc(hidden)]
1082 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
1083 self.mapping
1084 .slot_to_scopes
1085 .iter()
1086 .map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
1087 .collect()
1088 }
1089
1090 #[doc(hidden)]
1091 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<crate::SlotDebugEntry>> {
1092 let slots = self.slot_compositions.get(&slot_id)?;
1093 Some(slots.borrow().debug_dump_slot_entries())
1094 }
1095
1096 #[doc(hidden)]
1097 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
1098 let slots = self.slot_compositions.get(&slot_id)?;
1099 Some(slots.borrow().debug_dump_groups())
1100 }
1101
1102 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
1104 &self.precomposed_nodes
1105 }
1106
1107 pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
1110 let mut disposed = Vec::new();
1111 let mut empty_slots = Vec::new();
1112 for (slot, nodes) in self.precomposed_nodes.iter_mut() {
1113 if !self.current_pass_active_slots.contains(slot) {
1114 disposed.extend(nodes.iter().copied());
1115 empty_slots.push(*slot);
1116 }
1117 }
1118 for slot in empty_slots {
1119 self.precomposed_nodes.remove(&slot);
1120 self.prune_slot_if_unused(slot);
1121 }
1122 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
1124 disposed
1125 }
1126}
1127
1128#[cfg(test)]
1129#[path = "tests/subcompose_tests.rs"]
1130mod tests;