1use crate::collections::map::HashMap;
10use crate::collections::map::HashSet;
11use smallvec::SmallVec;
12use std::collections::VecDeque;
13use std::fmt;
14use std::rc::Rc;
15
16use crate::{CallbackHolder, NodeId, RecomposeScope, SlotTable, SlotsHost};
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 deactivate_slot(&self, slot: SlotId) {
272 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
273 for scope in scopes {
274 scope.deactivate();
275 }
276 }
277 }
278
279 fn invalidate_scopes(&self) {
280 for scopes in self.slot_to_scopes.values() {
281 for scope in scopes {
282 scope.invalidate();
283 }
284 }
285 }
286}
287
288pub struct SubcomposeState {
291 mapping: NodeSlotMapping,
292 active_order: Vec<SlotId>,
293 live_slots: HashSet<SlotId>,
294 current_pass_active_slots: HashSet<SlotId>,
295 reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
299 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
301 reusable_node_counts: HashMap<SlotId, usize>,
302 exact_reactivation_slots: HashSet<SlotId>,
303 slot_content_types: HashMap<SlotId, u64>,
305 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
306 policy: Box<dyn SlotReusePolicy>,
307 pub(crate) current_index: usize,
308 pub(crate) reusable_count: usize,
309 pub(crate) precomposed_count: usize,
310 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
314 slot_callbacks: HashMap<SlotId, CallbackHolder>,
316 max_reusable_per_type: usize,
318 max_reusable_untyped: usize,
320 last_slot_reused: Option<bool>,
323}
324
325impl fmt::Debug for SubcomposeState {
326 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
327 f.debug_struct("SubcomposeState")
328 .field("mapping", &self.mapping)
329 .field("active_order", &self.active_order)
330 .field("reusable_by_type_count", &self.reusable_by_type.len())
331 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
332 .field("precomposed_nodes", &self.precomposed_nodes)
333 .field("current_index", &self.current_index)
334 .field("reusable_count", &self.reusable_count)
335 .field("precomposed_count", &self.precomposed_count)
336 .field("slot_compositions_count", &self.slot_compositions.len())
337 .finish()
338 }
339}
340
341impl Default for SubcomposeState {
342 fn default() -> Self {
343 Self::new(Box::new(DefaultSlotReusePolicy))
344 }
345}
346
347const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
350
351const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
355
356impl SubcomposeState {
357 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
359 Self {
360 mapping: NodeSlotMapping::default(),
361 active_order: Vec::new(),
362 live_slots: HashSet::default(),
363 current_pass_active_slots: HashSet::default(),
364 reusable_by_type: HashMap::default(),
365 reusable_nodes_untyped: VecDeque::new(),
366 reusable_node_counts: HashMap::default(),
367 exact_reactivation_slots: HashSet::default(),
368 slot_content_types: HashMap::default(),
369 precomposed_nodes: HashMap::default(),
370 policy,
371 current_index: 0,
372 reusable_count: 0,
373 precomposed_count: 0,
374 slot_compositions: HashMap::default(),
375 slot_callbacks: HashMap::default(),
376 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
377 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
378 last_slot_reused: None,
379 }
380 }
381
382 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
384 self.policy = policy;
385 }
386
387 pub fn set_reusable_pool_limits(&mut self, per_type: usize, untyped: usize) {
388 self.max_reusable_per_type = per_type;
389 self.max_reusable_untyped = untyped;
390 }
391
392 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
399 self.slot_content_types.insert(slot_id, content_type);
400 self.policy.register_content_type(slot_id, content_type);
401 }
402
403 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
409 match content_type {
410 Some(ct) => self.register_content_type(slot_id, ct),
411 None => {
412 self.slot_content_types.remove(&slot_id);
413 self.policy.remove_content_type(slot_id);
414 }
415 }
416 }
417
418 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
420 self.slot_content_types.get(&slot_id).copied()
421 }
422
423 pub fn begin_pass(&mut self) {
428 self.current_index = 0;
429 self.current_pass_active_slots.clear();
430 }
431
432 pub fn active_slot_cursor(&self) -> usize {
434 self.current_index
435 }
436
437 pub fn restore_active_slot_cursor(&mut self, cursor: usize) {
440 self.current_index = cursor.min(self.active_order.len());
441 }
442
443 pub fn recycle_active_slot(&mut self, slot_id: SlotId) -> Vec<NodeId> {
445 self.recycle_active_slot_internal(slot_id, false)
446 }
447
448 pub fn recycle_prefetched_active_slot(&mut self, slot_id: SlotId) -> Vec<NodeId> {
449 self.recycle_active_slot_internal(slot_id, true)
450 }
451
452 pub fn recycle_active_slots_where(
453 &mut self,
454 mut predicate: impl FnMut(SlotId) -> bool,
455 ) -> Vec<NodeId> {
456 let slots: Vec<_> = self
457 .active_order
458 .iter()
459 .copied()
460 .filter(|slot| !self.current_pass_active_slots.contains(slot))
461 .filter(|slot| predicate(*slot))
462 .collect();
463 let mut disposed = Vec::new();
464 for slot in slots {
465 disposed.extend(self.recycle_active_slot(slot));
466 }
467 disposed
468 }
469
470 fn recycle_active_slot_internal(
471 &mut self,
472 slot_id: SlotId,
473 allow_exact_reactivation: bool,
474 ) -> Vec<NodeId> {
475 let Some(position) = self
476 .active_order
477 .iter()
478 .position(|candidate| *candidate == slot_id)
479 else {
480 return Vec::new();
481 };
482 self.active_order.remove(position);
483 if position < self.current_index {
484 self.current_index = self.current_index.saturating_sub(1);
485 }
486 self.move_slot_to_reusable(slot_id, allow_exact_reactivation);
487 self.enforce_reusable_pool_limits()
488 }
489
490 pub fn finish_pass(&mut self) -> Vec<NodeId> {
492 self.dispose_or_reuse_starting_from_index(self.current_index)
493 }
494
495 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
499 Rc::clone(
500 self.slot_compositions
501 .entry(slot_id)
502 .or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
503 )
504 }
505
506 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
508 self.slot_callbacks.entry(slot_id).or_default().clone()
509 }
510
511 #[doc(hidden)]
512 pub fn activate_current_active_slot(&mut self, slot_id: SlotId) -> Option<Vec<NodeId>> {
513 if self.current_pass_active_slots.contains(&slot_id)
514 || self.exact_reactivation_slots.contains(&slot_id)
515 || self
516 .active_order
517 .get(self.current_index)
518 .copied()
519 .is_none_or(|active_slot| active_slot != slot_id)
520 || self.mapping.slot_has_invalid_scopes(slot_id)
521 {
522 return None;
523 }
524
525 let nodes = self.mapping.get_nodes(&slot_id)?.to_vec();
526 self.last_slot_reused = Some(true);
527 self.live_slots.insert(slot_id);
528 self.current_pass_active_slots.insert(slot_id);
529 self.current_index += 1;
530 Some(nodes)
531 }
532
533 #[doc(hidden)]
534 pub fn take_exact_slot_activation(&mut self, slot_id: SlotId) -> Option<ExactSlotActivation> {
535 let active_position = self
536 .active_order
537 .iter()
538 .position(|candidate| *candidate == slot_id);
539 let is_exact_reactivation = self.exact_reactivation_slots.contains(&slot_id);
540 if active_position.is_none() && !is_exact_reactivation
541 || self.mapping.slot_has_invalid_scopes(slot_id)
542 {
543 return None;
544 }
545
546 let nodes = self.mapping.get_nodes(&slot_id)?.to_vec();
547 let scopes = self
548 .mapping
549 .get_scopes(&slot_id)
550 .unwrap_or_default()
551 .to_vec();
552 if active_position.is_none() {
553 for node in &nodes {
554 let _ = self.remove_from_reusable_pools(*node);
555 }
556 }
557 self.exact_reactivation_slots.remove(&slot_id);
558 Some(ExactSlotActivation {
559 nodes,
560 scopes,
561 reactivate_scopes: active_position.is_none(),
562 })
563 }
564
565 #[doc(hidden)]
566 pub fn take_exact_slot_for_activation(
567 &mut self,
568 slot_id: SlotId,
569 ) -> Option<(Vec<NodeId>, Vec<RecomposeScope>)> {
570 self.take_exact_slot_activation(slot_id)
571 .map(|activation| (activation.nodes, activation.scopes))
572 }
573
574 pub fn register_active(
577 &mut self,
578 slot_id: SlotId,
579 node_ids: &[NodeId],
580 scopes: &[RecomposeScope],
581 ) {
582 self.register_active_with_scope_reactivation(slot_id, node_ids, scopes, true);
583 }
584
585 #[doc(hidden)]
586 pub fn register_active_with_scope_reactivation(
587 &mut self,
588 slot_id: SlotId,
589 node_ids: &[NodeId],
590 scopes: &[RecomposeScope],
591 reactivate_scopes: bool,
592 ) {
593 let was_reused = self.mapping.get_nodes(&slot_id).is_some();
594 self.last_slot_reused = Some(was_reused);
595 self.live_slots.insert(slot_id);
596 self.current_pass_active_slots.insert(slot_id);
597 self.exact_reactivation_slots.remove(&slot_id);
598
599 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
600 if position < self.current_index {
601 if reactivate_scopes {
602 for scope in scopes {
603 scope.reactivate();
604 }
605 }
606 self.update_active_slot_mapping(slot_id, node_ids, scopes);
607 return;
608 }
609 self.active_order.remove(position);
610 }
611 if reactivate_scopes {
612 for scope in scopes {
613 scope.reactivate();
614 }
615 }
616 self.update_active_slot_mapping(slot_id, node_ids, scopes);
617 let insert_at = self.current_index.min(self.active_order.len());
618 self.active_order.insert(insert_at, slot_id);
619 self.current_index += 1;
620 }
621
622 fn update_active_slot_mapping(
623 &mut self,
624 slot_id: SlotId,
625 node_ids: &[NodeId],
626 scopes: &[RecomposeScope],
627 ) {
628 self.mapping.set_nodes(slot_id, node_ids);
629 self.mapping.set_scopes(slot_id, scopes);
630 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
631 let before_len = nodes.len();
632 nodes.retain(|node| !node_ids.contains(node));
633 let removed = before_len - nodes.len();
634 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
635 if nodes.is_empty() {
636 self.precomposed_nodes.remove(&slot_id);
637 }
638 }
639 }
640
641 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
644 self.precomposed_nodes
645 .entry(slot_id)
646 .or_default()
647 .push(node_id);
648 self.precomposed_count += 1;
649 }
650
651 fn increment_reusable_slot(&mut self, slot_id: SlotId) {
652 *self.reusable_node_counts.entry(slot_id).or_insert(0) += 1;
653 self.reusable_count += 1;
654 }
655
656 fn decrement_reusable_slot(&mut self, slot_id: SlotId) {
657 let Some(entry) = self.reusable_node_counts.get_mut(&slot_id) else {
658 self.reusable_count = self.reusable_count.saturating_sub(1);
659 return;
660 };
661 *entry = entry.saturating_sub(1);
662 if *entry == 0 {
663 self.reusable_node_counts.remove(&slot_id);
664 }
665 self.reusable_count = self.reusable_count.saturating_sub(1);
666 }
667
668 fn prune_slot_if_unused(&mut self, slot_id: SlotId) {
669 if self.live_slots.contains(&slot_id) || self.reusable_node_counts.contains_key(&slot_id) {
670 return;
671 }
672
673 debug_assert!(
674 self.mapping.get_nodes(&slot_id).is_none(),
675 "inactive slot {slot_id:?} still has mapped nodes",
676 );
677
678 self.slot_compositions.remove(&slot_id);
679 self.slot_callbacks.remove(&slot_id);
680 self.slot_content_types.remove(&slot_id);
681 self.policy.remove_content_type(slot_id);
682 if let Some(nodes) = self.precomposed_nodes.remove(&slot_id) {
683 self.precomposed_count = self.precomposed_count.saturating_sub(nodes.len());
684 }
685 }
686
687 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<NodeId> {
696 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
697 let first_node = nodes.first().copied();
698 if let Some(node_id) = first_node {
699 let _ = self.remove_from_reusable_pools(node_id);
700 self.exact_reactivation_slots.remove(&slot_id);
701 return Some(node_id);
702 }
703 }
704
705 let content_type = self.slot_content_types.get(&slot_id).copied();
706
707 if let Some(ct) = content_type {
708 if let Some((old_slot, node_id)) = self.take_compatible_typed_reusable(ct, slot_id) {
709 self.decrement_reusable_slot(old_slot);
710 self.move_node_to_slot(node_id, old_slot, slot_id);
711 return Some(node_id);
712 }
713 }
714
715 let exact_reactivation_slots = &self.exact_reactivation_slots;
716 let policy = &self.policy;
717 let position = self
718 .reusable_nodes_untyped
719 .iter()
720 .position(|(existing_slot, _)| {
721 !exact_reactivation_slots.contains(existing_slot)
722 && policy.are_compatible(*existing_slot, slot_id)
723 });
724
725 if let Some(index) = position {
726 if let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index) {
727 self.decrement_reusable_slot(old_slot);
728 self.move_node_to_slot(node_id, old_slot, slot_id);
729 return Some(node_id);
730 }
731 }
732
733 None
734 }
735
736 fn take_compatible_typed_reusable(
737 &mut self,
738 content_type: u64,
739 requested_slot: SlotId,
740 ) -> Option<(SlotId, NodeId)> {
741 let reused = {
742 let policy = &self.policy;
743 let exact_reactivation_slots = &self.exact_reactivation_slots;
744 let pool = self.reusable_by_type.get_mut(&content_type)?;
745 let index = pool.iter().position(|(existing_slot, _)| {
746 !exact_reactivation_slots.contains(existing_slot)
747 && policy.are_compatible(*existing_slot, requested_slot)
748 })?;
749 pool.remove(index)
750 };
751
752 if self
753 .reusable_by_type
754 .get(&content_type)
755 .map(|pool| pool.is_empty())
756 .unwrap_or(false)
757 {
758 self.reusable_by_type.remove(&content_type);
759 }
760
761 reused
762 }
763
764 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> Option<SlotId> {
766 let mut typed_match = None;
768 for (&content_type, pool) in &self.reusable_by_type {
769 if let Some(position) = pool
770 .iter()
771 .position(|(_, pooled_node)| *pooled_node == node_id)
772 {
773 typed_match = Some((content_type, position));
774 break;
775 }
776 }
777 if let Some((content_type, position)) = typed_match {
778 let pool = self.reusable_by_type.get_mut(&content_type)?;
779 let (slot, _) = pool.remove(position)?;
780 if pool.is_empty() {
781 self.reusable_by_type.remove(&content_type);
782 }
783 self.decrement_reusable_slot(slot);
784 return Some(slot);
785 }
786 if let Some(position) = self
788 .reusable_nodes_untyped
789 .iter()
790 .position(|(_, n)| *n == node_id)
791 {
792 if let Some((slot, _)) = self.reusable_nodes_untyped.remove(position) {
793 self.decrement_reusable_slot(slot);
794 return Some(slot);
795 }
796 }
797 None
798 }
799
800 fn move_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
802 if old_slot == new_slot {
803 return;
804 }
805
806 self.mapping.remove_by_node(&node_id);
807 self.exact_reactivation_slots.remove(&old_slot);
808 self.mapping.add_node(new_slot, node_id);
809 self.slot_content_types.remove(&old_slot);
810 self.policy.remove_content_type(old_slot);
811 if let Some(slots) = self.slot_compositions.remove(&old_slot) {
812 self.slot_compositions.insert(new_slot, slots);
813 }
814 if let Some(callback) = self.slot_callbacks.remove(&old_slot) {
815 self.slot_callbacks.insert(new_slot, callback);
816 }
817 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
818 let before_len = nodes.len();
819 nodes.retain(|candidate| *candidate != node_id);
820 let removed = before_len - nodes.len();
821 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
822 if nodes.is_empty() {
823 self.precomposed_nodes.remove(&old_slot);
824 }
825 }
826 }
827
828 pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
832 if start_index >= self.active_order.len() {
833 return Vec::new();
834 }
835
836 let retain = self
837 .policy
838 .get_slots_to_retain(&self.active_order[start_index..]);
839 let mut retained = Vec::new();
840 while self.active_order.len() > start_index {
841 let Some(slot) = self.active_order.pop() else {
842 break;
843 };
844 if retain.contains(&slot) {
845 retained.push(slot);
846 continue;
847 }
848 self.move_slot_to_reusable(slot, false);
849 }
850 retained.reverse();
851 self.active_order.extend(retained);
852
853 self.enforce_reusable_pool_limits()
854 }
855
856 fn move_slot_to_reusable(&mut self, slot: SlotId, allow_exact_reactivation: bool) {
857 self.live_slots.remove(&slot);
858 self.current_pass_active_slots.remove(&slot);
859 self.mapping.deactivate_slot(slot);
860 if allow_exact_reactivation {
861 self.exact_reactivation_slots.insert(slot);
862 } else {
863 self.exact_reactivation_slots.remove(&slot);
864 }
865
866 let content_type = self.slot_content_types.get(&slot).copied();
867 let nodes: SmallVec<[NodeId; 4]> = self
868 .mapping
869 .get_nodes(&slot)
870 .into_iter()
871 .flatten()
872 .copied()
873 .collect();
874 for node in nodes {
875 if let Some(ct) = content_type {
876 self.reusable_by_type
877 .entry(ct)
878 .or_default()
879 .push_back((slot, node));
880 } else {
881 self.reusable_nodes_untyped.push_back((slot, node));
882 }
883 self.increment_reusable_slot(slot);
884 }
885 }
886
887 fn enforce_reusable_pool_limits(&mut self) -> Vec<NodeId> {
888 let mut disposed = Vec::new();
889 let mut typed_disposals = Vec::new();
890 for pool in self.reusable_by_type.values_mut() {
891 while pool.len() > self.max_reusable_per_type {
892 if let Some((slot, node_id)) = pool.pop_front() {
893 typed_disposals.push((slot, node_id));
894 }
895 }
896 }
897 for (slot, node_id) in typed_disposals {
898 self.decrement_reusable_slot(slot);
899 self.mapping.remove_by_node(&node_id);
900 self.exact_reactivation_slots.remove(&slot);
901 disposed.push(node_id);
902 }
903
904 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
906 if let Some((slot, node_id)) = self.reusable_nodes_untyped.pop_front() {
907 self.decrement_reusable_slot(slot);
908 self.mapping.remove_by_node(&node_id);
909 self.exact_reactivation_slots.remove(&slot);
910 disposed.push(node_id);
911 }
912 }
913
914 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
915 disposed
916 }
917
918 pub fn reusable(&self) -> Vec<NodeId> {
920 let mut nodes: Vec<NodeId> = self
921 .reusable_by_type
922 .values()
923 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
924 .collect();
925 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
926 nodes
927 }
928
929 pub fn active_slots_count(&self) -> usize {
934 self.active_order.len()
935 }
936
937 pub fn reusable_slots_count(&self) -> usize {
942 self.reusable_count
943 }
944
945 pub fn invalidate_scopes(&self) {
951 self.mapping.invalidate_scopes();
952 }
953
954 pub fn was_last_slot_reused(&self) -> Option<bool> {
962 self.last_slot_reused
963 }
964
965 #[doc(hidden)]
966 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
967 self.mapping
968 .slot_to_scopes
969 .iter()
970 .map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
971 .collect()
972 }
973
974 #[doc(hidden)]
975 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<crate::SlotDebugEntry>> {
976 let slots = self.slot_compositions.get(&slot_id)?;
977 Some(slots.borrow().debug_dump_slot_entries())
978 }
979
980 #[doc(hidden)]
981 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
982 let slots = self.slot_compositions.get(&slot_id)?;
983 Some(slots.borrow().debug_dump_groups())
984 }
985
986 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
988 &self.precomposed_nodes
989 }
990
991 pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
994 let mut disposed = Vec::new();
995 let mut empty_slots = Vec::new();
996 for (slot, nodes) in self.precomposed_nodes.iter_mut() {
997 if !self.current_pass_active_slots.contains(slot) {
998 disposed.extend(nodes.iter().copied());
999 empty_slots.push(*slot);
1000 }
1001 }
1002 for slot in empty_slots {
1003 self.precomposed_nodes.remove(&slot);
1004 self.prune_slot_if_unused(slot);
1005 }
1006 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
1008 disposed
1009 }
1010}
1011
1012#[cfg(test)]
1013#[path = "tests/subcompose_tests.rs"]
1014mod tests;