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);
21
22#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
27pub struct SlotId(pub u64);
28
29impl SlotId {
30 #[inline]
32 pub fn new(raw: u64) -> Self {
33 Self(raw)
34 }
35
36 #[inline]
38 pub fn raw(self) -> u64 {
39 self.0
40 }
41}
42
43pub trait SlotReusePolicy: 'static {
49 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId>;
53
54 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool;
62
63 fn register_content_type(&self, _slot_id: SlotId, _content_type: u64) {}
70
71 fn remove_content_type(&self, _slot_id: SlotId) {}
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>>,
118}
119
120impl std::fmt::Debug for ContentTypeReusePolicy {
121 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
122 let types = self.slot_types.borrow();
123 f.debug_struct("ContentTypeReusePolicy")
124 .field("slot_types", &*types)
125 .finish()
126 }
127}
128
129impl Default for ContentTypeReusePolicy {
130 fn default() -> Self {
131 Self::new()
132 }
133}
134
135impl ContentTypeReusePolicy {
136 pub fn new() -> Self {
138 Self {
139 slot_types: std::cell::RefCell::new(HashMap::default()),
140 }
141 }
142
143 pub fn set_content_type(&self, slot: SlotId, content_type: u64) {
147 self.slot_types.borrow_mut().insert(slot, content_type);
148 }
149
150 pub fn remove_content_type(&self, slot: SlotId) {
152 self.slot_types.borrow_mut().remove(&slot);
153 }
154
155 pub fn clear(&self) {
157 self.slot_types.borrow_mut().clear();
158 }
159
160 pub fn get_content_type(&self, slot: SlotId) -> Option<u64> {
162 self.slot_types.borrow().get(&slot).copied()
163 }
164}
165
166impl SlotReusePolicy for ContentTypeReusePolicy {
167 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
168 let _ = active;
169 HashSet::default()
170 }
171
172 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
173 if existing == requested {
174 return true;
175 }
176
177 let types = self.slot_types.borrow();
178 match (types.get(&existing), types.get(&requested)) {
179 (Some(existing_type), Some(requested_type)) => existing_type == requested_type,
180 (None, None) => true,
181 _ => false,
182 }
183 }
184
185 fn register_content_type(&self, slot_id: SlotId, content_type: u64) {
186 self.set_content_type(slot_id, content_type);
187 }
188
189 fn remove_content_type(&self, slot_id: SlotId) {
190 ContentTypeReusePolicy::remove_content_type(self, slot_id);
191 }
192}
193
194#[doc(hidden)]
195pub struct ExactSlotActivation<'a> {
196 pub nodes: &'a [NodeId],
197 pub was_recycled: bool,
198}
199
200#[derive(Default)]
203pub struct SubcomposeDisposal {
204 pub(crate) nodes: Vec<NodeId>,
205 pub(crate) slot_hosts: Vec<Rc<SlotsHost>>,
206}
207
208impl SubcomposeDisposal {
209 pub fn nodes(&self) -> &[NodeId] {
218 &self.nodes
219 }
220
221 fn append(&mut self, mut other: Self) {
222 self.nodes.append(&mut other.nodes);
223 self.slot_hosts.append(&mut other.slot_hosts);
224 }
225}
226
227#[derive(Default, Clone)]
228
229struct NodeSlotMapping {
230 slot_to_nodes: HashMap<SlotId, Vec<NodeId>>,
231 node_to_slot: HashMap<NodeId, SlotId>,
232 slot_to_scopes: HashMap<SlotId, Vec<RecomposeScope>>,
233}
234
235impl fmt::Debug for NodeSlotMapping {
236 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
237 f.debug_struct("NodeSlotMapping")
238 .field("slot_to_nodes", &self.slot_to_nodes)
239 .field("node_to_slot", &self.node_to_slot)
240 .finish()
241 }
242}
243
244impl NodeSlotMapping {
245 fn set_nodes(&mut self, slot: SlotId, nodes: &[NodeId]) {
246 self.slot_to_nodes.insert(slot, nodes.to_vec());
247 for node in nodes {
248 self.node_to_slot.insert(*node, slot);
249 }
250 }
251
252 fn set_scopes(&mut self, slot: SlotId, scopes: &[RecomposeScope]) {
253 self.slot_to_scopes.insert(slot, scopes.to_vec());
254 }
255
256 fn add_node(&mut self, slot: SlotId, node: NodeId) {
257 self.slot_to_nodes.entry(slot).or_default().push(node);
258 self.node_to_slot.insert(node, slot);
259 }
260
261 fn remove_by_node(&mut self, node: &NodeId) -> Option<SlotId> {
262 if let Some(slot) = self.node_to_slot.remove(node) {
263 if let Some(nodes) = self.slot_to_nodes.get_mut(&slot) {
264 if let Some(index) = nodes.iter().position(|candidate| candidate == node) {
265 nodes.remove(index);
266 }
267 if nodes.is_empty() {
268 self.slot_to_nodes.remove(&slot);
269 self.slot_to_scopes.remove(&slot);
270 }
271 }
272 Some(slot)
273 } else {
274 None
275 }
276 }
277
278 fn get_nodes(&self, slot: &SlotId) -> Option<&[NodeId]> {
279 self.slot_to_nodes.get(slot).map(Vec::as_slice)
280 }
281
282 fn get_scopes(&self, slot: &SlotId) -> Option<&[RecomposeScope]> {
283 self.slot_to_scopes.get(slot).map(Vec::as_slice)
284 }
285
286 fn slot_has_invalid_scopes(&self, slot: SlotId) -> bool {
287 self.slot_to_scopes
288 .get(&slot)
289 .is_some_and(|scopes| scopes.iter().any(RecomposeScope::is_invalid))
290 }
291
292 fn slot_has_inactive_scopes(&self, slot: SlotId) -> bool {
293 self.slot_to_scopes
294 .get(&slot)
295 .is_some_and(|scopes| scopes.iter().any(|scope| !scope.is_active()))
296 }
297
298 fn deactivate_slot(&self, slot: SlotId) {
299 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
300 for scope in scopes {
301 scope.deactivate();
302 }
303 }
304 }
305
306 fn invalidate_scopes(&self) {
307 for scopes in self.slot_to_scopes.values() {
308 for scope in scopes {
309 scope.invalidate();
310 }
311 }
312 }
313}
314
315pub struct SubcomposeState {
318 mapping: NodeSlotMapping,
319 active_order: Vec<SlotId>,
320 live_slots: HashSet<SlotId>,
321 current_pass_active_slots: HashSet<SlotId>,
322 reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
323 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
324 reusable_node_counts: HashMap<SlotId, usize>,
325 exact_reactivation_slots: HashSet<SlotId>,
326 slot_content_types: HashMap<SlotId, u64>,
327 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
328 policy: Box<dyn SlotReusePolicy>,
329 pub(crate) current_index: usize,
330 pub(crate) reusable_count: usize,
331 pub(crate) precomposed_count: usize,
332 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
333 slot_callbacks: HashMap<SlotId, CallbackHolder>,
334 max_reusable_per_type: usize,
335 max_reusable_untyped: usize,
336 last_slot_reused: Option<bool>,
337 retained_capture_keys: HashMap<SlotId, RetainedCaptureKey>,
338 content_generation: std::cell::Cell<u64>,
339 slot_composed_generation: HashMap<SlotId, (u64, u64)>,
340 locals_generation: std::cell::Cell<u64>,
343}
344
345struct 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;
380
381const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
382
383impl SubcomposeState {
384 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
386 Self {
387 mapping: NodeSlotMapping::default(),
388 active_order: Vec::new(),
389 live_slots: HashSet::default(),
390 current_pass_active_slots: HashSet::default(),
391 reusable_by_type: HashMap::default(),
392 reusable_nodes_untyped: VecDeque::new(),
393 reusable_node_counts: HashMap::default(),
394 exact_reactivation_slots: HashSet::default(),
395 slot_content_types: HashMap::default(),
396 precomposed_nodes: HashMap::default(),
397 policy,
398 current_index: 0,
399 reusable_count: 0,
400 precomposed_count: 0,
401 slot_compositions: HashMap::default(),
402 slot_callbacks: HashMap::default(),
403 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
404 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
405 last_slot_reused: None,
406 retained_capture_keys: HashMap::default(),
407 content_generation: std::cell::Cell::new(0),
408 slot_composed_generation: HashMap::default(),
409 locals_generation: std::cell::Cell::new(0),
410 }
411 }
412
413 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
415 self.policy = policy;
416 }
417
418 pub fn set_reusable_pool_limits(&mut self, per_type: usize, untyped: usize) {
421 self.max_reusable_per_type = per_type;
422 self.max_reusable_untyped = untyped;
423 }
424
425 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
432 self.slot_content_types.insert(slot_id, content_type);
433 self.policy.register_content_type(slot_id, content_type);
434 }
435
436 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
442 match content_type {
443 Some(ct) => self.register_content_type(slot_id, ct),
444 None => {
445 self.slot_content_types.remove(&slot_id);
446 self.policy.remove_content_type(slot_id);
447 }
448 }
449 }
450
451 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
453 self.slot_content_types.get(&slot_id).copied()
454 }
455
456 pub fn active_slot_for_node(&self, node_id: NodeId) -> Option<SlotId> {
461 self.mapping
462 .node_to_slot
463 .get(&node_id)
464 .copied()
465 .filter(|slot| self.live_slots.contains(slot))
466 }
467
468 pub fn begin_pass(&mut self) {
473 self.current_index = 0;
474 self.current_pass_active_slots.clear();
475 }
476
477 pub fn active_slot_cursor(&self) -> usize {
479 self.current_index
480 }
481
482 pub fn restore_active_slot_cursor(&mut self, cursor: usize) {
485 self.current_index = cursor.min(self.active_order.len());
486 }
487
488 pub fn recycle_active_slot(&mut self, slot_id: SlotId) -> SubcomposeDisposal {
490 self.recycle_active_slot_internal(slot_id, false)
491 }
492
493 pub fn recycle_prefetched_active_slot(&mut self, slot_id: SlotId) -> SubcomposeDisposal {
496 self.recycle_active_slot_internal(slot_id, true)
497 }
498
499 pub fn recycle_active_slots_where(
502 &mut self,
503 mut predicate: impl FnMut(SlotId) -> bool,
504 ) -> SubcomposeDisposal {
505 let slots: Vec<_> = self
506 .active_order
507 .iter()
508 .copied()
509 .filter(|slot| !self.current_pass_active_slots.contains(slot))
510 .filter(|slot| predicate(*slot))
511 .collect();
512 let mut disposed = SubcomposeDisposal::default();
513 for slot in slots {
514 disposed.append(self.recycle_active_slot(slot));
515 }
516 disposed
517 }
518
519 fn recycle_active_slot_internal(
520 &mut self,
521 slot_id: SlotId,
522 allow_exact_reactivation: bool,
523 ) -> SubcomposeDisposal {
524 let Some(position) = self
525 .active_order
526 .iter()
527 .position(|candidate| *candidate == slot_id)
528 else {
529 return SubcomposeDisposal::default();
530 };
531 self.active_order.remove(position);
532 if position < self.current_index {
533 self.current_index = self.current_index.saturating_sub(1);
534 }
535 self.move_slot_to_reusable(slot_id, allow_exact_reactivation);
536 self.enforce_reusable_pool_limits()
537 }
538
539 pub fn finish_pass(&mut self) -> SubcomposeDisposal {
541 self.dispose_or_reuse_starting_from_index(self.current_index)
542 }
543
544 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
548 Rc::clone(
549 self.slot_compositions
550 .entry(slot_id)
551 .or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
552 )
553 }
554
555 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
557 self.slot_callbacks.entry(slot_id).or_default().clone()
558 }
559
560 #[doc(hidden)]
561 pub fn activate_current_active_slot(&mut self, slot_id: SlotId) -> Option<&[NodeId]> {
562 self.activate_current_active_slot_at_cursor(slot_id)
563 .then(|| self.mapping.get_nodes(&slot_id))
564 .flatten()
565 }
566
567 fn activate_current_active_slot_at_cursor(&mut self, slot_id: SlotId) -> bool {
568 if self.current_pass_active_slots.contains(&slot_id)
569 || self.exact_reactivation_slots.contains(&slot_id)
570 || self.reusable_node_counts.contains_key(&slot_id)
571 || self
572 .active_order
573 .get(self.current_index)
574 .copied()
575 .is_none_or(|active_slot| active_slot != slot_id)
576 || self.mapping.slot_has_invalid_scopes(slot_id)
577 || self.mapping.slot_has_inactive_scopes(slot_id)
578 {
579 return false;
580 }
581
582 if self.mapping.get_nodes(&slot_id).is_none() {
583 return false;
584 }
585 self.last_slot_reused = Some(true);
586 self.live_slots.insert(slot_id);
587 self.current_pass_active_slots.insert(slot_id);
588 self.current_index += 1;
589 true
590 }
591
592 pub fn has_pending_precompositions(&self, slot_id: SlotId) -> bool {
596 self.precomposed_nodes.contains_key(&slot_id)
597 }
598
599 pub fn retained_capture_key_matches<K: PartialEq + 'static>(
603 &self,
604 slot_id: SlotId,
605 key: &K,
606 ) -> bool {
607 self.retained_capture_keys
608 .get(&slot_id)
609 .is_some_and(|retained| (retained.eq)(retained.value.as_ref(), key))
610 }
611
612 pub fn store_retained_capture_key<K: PartialEq + 'static>(&mut self, slot_id: SlotId, key: K) {
614 self.retained_capture_keys.insert(
615 slot_id,
616 RetainedCaptureKey {
617 value: Box::new(key),
618 eq: retained_capture_key_eq::<K>,
619 },
620 );
621 }
622
623 #[doc(hidden)]
624 pub fn activate_exact_slot(&mut self, slot_id: SlotId) -> Option<ExactSlotActivation<'_>> {
625 if self.activate_current_active_slot_at_cursor(slot_id) {
626 return Some(ExactSlotActivation {
627 nodes: self.mapping.get_nodes(&slot_id)?,
628 was_recycled: false,
629 });
630 }
631 let active_position = self
632 .active_order
633 .iter()
634 .position(|candidate| *candidate == slot_id);
635 let is_exact_reactivation = self.exact_reactivation_slots.contains(&slot_id);
636 if active_position.is_none() && !is_exact_reactivation
637 || self.mapping.slot_has_invalid_scopes(slot_id)
638 {
639 return None;
640 }
641
642 let node_count = self.mapping.get_nodes(&slot_id)?.len();
643 if active_position.is_none() {
644 for index in 0..node_count {
645 let node = self.mapping.get_nodes(&slot_id)?[index];
646 let _ = self.remove_from_reusable_pools(node);
647 }
648 for scope in self.mapping.get_scopes(&slot_id).unwrap_or_default() {
649 scope.reactivate();
650 }
651 }
652 self.mark_slot_active(slot_id, true);
653 Self::consume_precomposed_nodes(
654 &mut self.precomposed_nodes,
655 &mut self.precomposed_count,
656 slot_id,
657 self.mapping.get_nodes(&slot_id)?,
658 );
659 Some(ExactSlotActivation {
660 nodes: self.mapping.get_nodes(&slot_id)?,
661 was_recycled: active_position.is_none(),
662 })
663 }
664
665 pub fn register_active(
668 &mut self,
669 slot_id: SlotId,
670 node_ids: &[NodeId],
671 scopes: &[RecomposeScope],
672 ) {
673 let was_reused = self.mapping.get_nodes(&slot_id).is_some();
674 for scope in scopes {
675 scope.reactivate();
676 }
677 self.update_active_slot_mapping(slot_id, node_ids, scopes);
678 self.mark_slot_active(slot_id, was_reused);
679 }
680
681 fn mark_slot_active(&mut self, slot_id: SlotId, was_reused: bool) {
682 self.last_slot_reused = Some(was_reused);
683 self.live_slots.insert(slot_id);
684 self.current_pass_active_slots.insert(slot_id);
685 self.exact_reactivation_slots.remove(&slot_id);
686
687 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
688 if position < self.current_index {
689 return;
690 }
691 self.active_order.remove(position);
692 }
693 let insert_at = self.current_index.min(self.active_order.len());
694 self.active_order.insert(insert_at, slot_id);
695 self.current_index += 1;
696 }
697
698 fn update_active_slot_mapping(
699 &mut self,
700 slot_id: SlotId,
701 node_ids: &[NodeId],
702 scopes: &[RecomposeScope],
703 ) {
704 self.mapping.set_nodes(slot_id, node_ids);
705 self.mapping.set_scopes(slot_id, scopes);
706 Self::consume_precomposed_nodes(
707 &mut self.precomposed_nodes,
708 &mut self.precomposed_count,
709 slot_id,
710 node_ids,
711 );
712 }
713
714 fn consume_precomposed_nodes(
715 precomposed_nodes: &mut HashMap<SlotId, Vec<NodeId>>,
716 precomposed_count: &mut usize,
717 slot_id: SlotId,
718 node_ids: &[NodeId],
719 ) {
720 if let Some(nodes) = precomposed_nodes.get_mut(&slot_id) {
721 let before_len = nodes.len();
722 nodes.retain(|node| !node_ids.contains(node));
723 let removed = before_len - nodes.len();
724 *precomposed_count = precomposed_count.saturating_sub(removed);
725 if nodes.is_empty() {
726 precomposed_nodes.remove(&slot_id);
727 }
728 }
729 }
730
731 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
734 self.precomposed_nodes
735 .entry(slot_id)
736 .or_default()
737 .push(node_id);
738 self.precomposed_count += 1;
739 }
740
741 fn increment_reusable_slot(&mut self, slot_id: SlotId) {
742 *self.reusable_node_counts.entry(slot_id).or_insert(0) += 1;
743 self.reusable_count += 1;
744 }
745
746 fn decrement_reusable_slot(&mut self, slot_id: SlotId) {
747 let Some(entry) = self.reusable_node_counts.get_mut(&slot_id) else {
748 self.reusable_count = self.reusable_count.saturating_sub(1);
749 return;
750 };
751 *entry = entry.saturating_sub(1);
752 if *entry == 0 {
753 self.reusable_node_counts.remove(&slot_id);
754 }
755 self.reusable_count = self.reusable_count.saturating_sub(1);
756 }
757
758 pub fn slot_is_active(&self, slot_id: SlotId) -> bool {
761 self.live_slots.contains(&slot_id)
762 }
763
764 pub fn slot_is_retained(&self, slot_id: SlotId) -> bool {
768 self.live_slots.contains(&slot_id) || self.reusable_node_counts.contains_key(&slot_id)
769 }
770
771 fn prune_slot_if_unused(&mut self, slot_id: SlotId) -> Option<Rc<SlotsHost>> {
772 if self.slot_is_retained(slot_id) {
773 return None;
774 }
775
776 debug_assert!(
777 self.mapping.get_nodes(&slot_id).is_none(),
778 "inactive slot {slot_id:?} still has mapped nodes",
779 );
780
781 let host = self.slot_compositions.remove(&slot_id);
782 self.slot_callbacks.remove(&slot_id);
783 self.slot_content_types.remove(&slot_id);
784 self.retained_capture_keys.remove(&slot_id);
785 self.slot_composed_generation.remove(&slot_id);
786 self.policy.remove_content_type(slot_id);
787 if let Some(nodes) = self.precomposed_nodes.remove(&slot_id) {
788 self.precomposed_count = self.precomposed_count.saturating_sub(nodes.len());
789 }
790 host
791 }
792
793 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<(NodeId, bool)> {
804 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
805 let first_node = nodes.first().copied();
806 if let Some(node_id) = first_node {
807 let _ = self.remove_from_reusable_pools(node_id);
808 self.exact_reactivation_slots.remove(&slot_id);
809 return Some((node_id, false));
810 }
811 }
812
813 let content_type = self.slot_content_types.get(&slot_id).copied();
814
815 if let Some(ct) = content_type
816 && let Some((old_slot, node_id)) = self.take_compatible_typed_reusable(ct, slot_id)
817 {
818 self.decrement_reusable_slot(old_slot);
819 self.move_node_to_slot(node_id, old_slot, slot_id);
820 return Some((node_id, true));
821 }
822
823 let exact_reactivation_slots = &self.exact_reactivation_slots;
824 let policy = &self.policy;
825 let position = self
826 .reusable_nodes_untyped
827 .iter()
828 .position(|(existing_slot, _)| {
829 !exact_reactivation_slots.contains(existing_slot)
830 && policy.are_compatible(*existing_slot, slot_id)
831 });
832
833 if let Some(index) = position
834 && let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index)
835 {
836 self.decrement_reusable_slot(old_slot);
837 self.move_node_to_slot(node_id, old_slot, slot_id);
838 return Some((node_id, true));
839 }
840
841 None
842 }
843
844 fn take_compatible_typed_reusable(
845 &mut self,
846 content_type: u64,
847 requested_slot: SlotId,
848 ) -> Option<(SlotId, NodeId)> {
849 let reused = {
850 let policy = &self.policy;
851 let exact_reactivation_slots = &self.exact_reactivation_slots;
852 let pool = self.reusable_by_type.get_mut(&content_type)?;
853 let index = pool.iter().position(|(existing_slot, _)| {
854 !exact_reactivation_slots.contains(existing_slot)
855 && policy.are_compatible(*existing_slot, requested_slot)
856 })?;
857 pool.remove(index)
858 };
859
860 if self
861 .reusable_by_type
862 .get(&content_type)
863 .is_some_and(std::collections::VecDeque::is_empty)
864 {
865 self.reusable_by_type.remove(&content_type);
866 }
867
868 reused
869 }
870
871 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> Option<SlotId> {
872 let mut typed_match = None;
873 for (&content_type, pool) in &self.reusable_by_type {
874 if let Some(position) = pool
875 .iter()
876 .position(|(_, pooled_node)| *pooled_node == node_id)
877 {
878 typed_match = Some((content_type, position));
879 break;
880 }
881 }
882 if let Some((content_type, position)) = typed_match {
883 let pool = self.reusable_by_type.get_mut(&content_type)?;
884 let (slot, _) = pool.remove(position)?;
885 if pool.is_empty() {
886 self.reusable_by_type.remove(&content_type);
887 }
888 self.decrement_reusable_slot(slot);
889 return Some(slot);
890 }
891 if let Some(position) = self
892 .reusable_nodes_untyped
893 .iter()
894 .position(|(_, n)| *n == node_id)
895 && let Some((slot, _)) = self.reusable_nodes_untyped.remove(position)
896 {
897 self.decrement_reusable_slot(slot);
898 return Some(slot);
899 }
900 None
901 }
902
903 fn move_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
904 if old_slot == new_slot {
905 return;
906 }
907
908 self.mapping.remove_by_node(&node_id);
909 self.exact_reactivation_slots.remove(&old_slot);
910 self.mapping.add_node(new_slot, node_id);
911 self.slot_content_types.remove(&old_slot);
912 self.retained_capture_keys.remove(&old_slot);
913 self.retained_capture_keys.remove(&new_slot);
914 self.slot_composed_generation.remove(&old_slot);
915 self.slot_composed_generation.remove(&new_slot);
916 self.policy.remove_content_type(old_slot);
917 if let Some(slots) = self.slot_compositions.remove(&old_slot) {
918 self.slot_compositions.insert(new_slot, slots);
919 }
920 if let Some(callback) = self.slot_callbacks.remove(&old_slot) {
921 self.slot_callbacks.insert(new_slot, callback);
922 }
923 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
924 let before_len = nodes.len();
925 nodes.retain(|candidate| *candidate != node_id);
926 let removed = before_len - nodes.len();
927 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
928 if nodes.is_empty() {
929 self.precomposed_nodes.remove(&old_slot);
930 }
931 }
932 }
933
934 pub fn dispose_or_reuse_starting_from_index(
938 &mut self,
939 start_index: usize,
940 ) -> SubcomposeDisposal {
941 if start_index >= self.active_order.len() {
942 return SubcomposeDisposal::default();
943 }
944
945 let retain = self
946 .policy
947 .get_slots_to_retain(&self.active_order[start_index..]);
948 let mut retained = Vec::new();
949 while self.active_order.len() > start_index {
950 let Some(slot) = self.active_order.pop() else {
951 break;
952 };
953 if retain.contains(&slot) {
954 retained.push(slot);
955 continue;
956 }
957 self.move_slot_to_reusable(slot, false);
958 }
959 retained.reverse();
960 self.active_order.extend(retained);
961
962 self.enforce_reusable_pool_limits()
963 }
964
965 fn move_slot_to_reusable(&mut self, slot: SlotId, allow_exact_reactivation: bool) {
966 self.live_slots.remove(&slot);
967 self.current_pass_active_slots.remove(&slot);
968 self.mapping.deactivate_slot(slot);
969 let forgot_effects = self
970 .slot_compositions
971 .get(&slot)
972 .is_some_and(|host| host.forget_effects());
973 if allow_exact_reactivation && !forgot_effects {
974 self.exact_reactivation_slots.insert(slot);
975 } else {
976 self.exact_reactivation_slots.remove(&slot);
977 }
978
979 let content_type = self.slot_content_types.get(&slot).copied();
980 let nodes: SmallVec<[NodeId; 4]> = self
981 .mapping
982 .get_nodes(&slot)
983 .into_iter()
984 .flatten()
985 .copied()
986 .collect();
987 for node in nodes {
988 if let Some(ct) = content_type {
989 self.reusable_by_type
990 .entry(ct)
991 .or_default()
992 .push_back((slot, node));
993 } else {
994 self.reusable_nodes_untyped.push_back((slot, node));
995 }
996 self.increment_reusable_slot(slot);
997 }
998 }
999
1000 fn enforce_reusable_pool_limits(&mut self) -> SubcomposeDisposal {
1001 let mut disposed = SubcomposeDisposal::default();
1002 let mut typed_disposals = Vec::new();
1003 for pool in self.reusable_by_type.values_mut() {
1004 while pool.len() > self.max_reusable_per_type {
1005 if let Some((slot, node_id)) = pool.pop_front() {
1006 typed_disposals.push((slot, node_id));
1007 }
1008 }
1009 }
1010 for (slot, node_id) in typed_disposals {
1011 self.decrement_reusable_slot(slot);
1012 self.mapping.remove_by_node(&node_id);
1013 self.exact_reactivation_slots.remove(&slot);
1014 disposed.slot_hosts.extend(self.prune_slot_if_unused(slot));
1015 disposed.nodes.push(node_id);
1016 }
1017
1018 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
1019 if let Some((slot, node_id)) = self.reusable_nodes_untyped.pop_front() {
1020 self.decrement_reusable_slot(slot);
1021 self.mapping.remove_by_node(&node_id);
1022 self.exact_reactivation_slots.remove(&slot);
1023 disposed.slot_hosts.extend(self.prune_slot_if_unused(slot));
1024 disposed.nodes.push(node_id);
1025 }
1026 }
1027
1028 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
1029 disposed
1030 }
1031
1032 pub fn reusable(&self) -> Vec<NodeId> {
1034 let mut nodes: Vec<NodeId> = self
1035 .reusable_by_type
1036 .values()
1037 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
1038 .collect();
1039 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
1040 nodes
1041 }
1042
1043 pub fn active_slots_count(&self) -> usize {
1048 self.active_order.len()
1049 }
1050
1051 pub fn reusable_slots_count(&self) -> usize {
1056 self.reusable_count
1057 }
1058
1059 pub fn invalidate_scopes(&self) {
1072 self.mapping.invalidate_scopes();
1073 self.content_generation
1074 .set(self.content_generation.get() + 1);
1075 }
1076
1077 pub fn invalidate_locals(&self) {
1081 self.bump_content_generation();
1082 self.locals_generation.set(self.content_generation.get());
1083 }
1084
1085 pub fn slot_locals_stale(&self, slot_id: SlotId) -> bool {
1090 let changed_at = self.locals_generation.get();
1091 self.slot_composed_generation
1092 .get(&slot_id)
1093 .is_some_and(|(generation, _)| *generation < changed_at)
1094 }
1095
1096 pub fn bump_content_generation(&self) {
1103 self.content_generation
1104 .set(self.content_generation.get() + 1);
1105 }
1106
1107 pub fn slot_content_generation_current(&self, slot_id: SlotId, owner_epoch: u64) -> bool {
1113 self.slot_composed_generation.get(&slot_id).copied()
1114 == Some((self.content_generation.get(), owner_epoch))
1115 }
1116
1117 pub fn mark_slot_composed_current(&mut self, slot_id: SlotId, owner_epoch: u64) {
1120 self.slot_composed_generation
1121 .insert(slot_id, (self.content_generation.get(), owner_epoch));
1122 }
1123
1124 pub fn was_last_slot_reused(&self) -> Option<bool> {
1132 self.last_slot_reused
1133 }
1134
1135 #[doc(hidden)]
1136 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
1137 self.mapping
1138 .slot_to_scopes
1139 .iter()
1140 .map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
1141 .collect()
1142 }
1143
1144 #[doc(hidden)]
1145 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<crate::SlotDebugEntry>> {
1146 let slots = self.slot_compositions.get(&slot_id)?;
1147 Some(slots.borrow().debug_dump_slot_entries())
1148 }
1149
1150 #[doc(hidden)]
1151 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
1152 let slots = self.slot_compositions.get(&slot_id)?;
1153 Some(slots.borrow().debug_dump_groups())
1154 }
1155
1156 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
1158 &self.precomposed_nodes
1159 }
1160
1161 pub fn drain_inactive_precomposed(&mut self) -> SubcomposeDisposal {
1164 let mut disposed = SubcomposeDisposal::default();
1165 let mut empty_slots = Vec::new();
1166 for (slot, nodes) in &mut self.precomposed_nodes {
1167 if !self.current_pass_active_slots.contains(slot) {
1168 disposed.nodes.extend(nodes.iter().copied());
1169 empty_slots.push(*slot);
1170 }
1171 }
1172 for slot in empty_slots {
1173 self.precomposed_nodes.remove(&slot);
1174 disposed.slot_hosts.extend(self.prune_slot_if_unused(slot));
1175 }
1176 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.nodes.len());
1177 disposed
1178 }
1179}
1180
1181#[cfg(test)]
1182#[path = "tests/subcompose_tests.rs"]
1183mod tests;