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<'a> {
192 pub nodes: &'a [NodeId],
193 pub was_recycled: bool,
194}
195
196#[derive(Default)]
199pub struct SubcomposeDisposal {
200 pub(crate) nodes: Vec<NodeId>,
201 pub(crate) slot_hosts: Vec<Rc<SlotsHost>>,
202}
203
204impl SubcomposeDisposal {
205 pub fn nodes(&self) -> &[NodeId] {
214 &self.nodes
215 }
216
217 fn append(&mut self, mut other: Self) {
218 self.nodes.append(&mut other.nodes);
219 self.slot_hosts.append(&mut other.slot_hosts);
220 }
221}
222
223#[derive(Default, Clone)]
224
225struct NodeSlotMapping {
226 slot_to_nodes: HashMap<SlotId, Vec<NodeId>>,
227 node_to_slot: HashMap<NodeId, SlotId>,
228 slot_to_scopes: HashMap<SlotId, Vec<RecomposeScope>>,
229}
230
231impl fmt::Debug for NodeSlotMapping {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 f.debug_struct("NodeSlotMapping")
234 .field("slot_to_nodes", &self.slot_to_nodes)
235 .field("node_to_slot", &self.node_to_slot)
236 .finish()
237 }
238}
239
240impl NodeSlotMapping {
241 fn set_nodes(&mut self, slot: SlotId, nodes: &[NodeId]) {
242 self.slot_to_nodes.insert(slot, nodes.to_vec());
243 for node in nodes {
244 self.node_to_slot.insert(*node, slot);
245 }
246 }
247
248 fn set_scopes(&mut self, slot: SlotId, scopes: &[RecomposeScope]) {
249 self.slot_to_scopes.insert(slot, scopes.to_vec());
250 }
251
252 fn add_node(&mut self, slot: SlotId, node: NodeId) {
253 self.slot_to_nodes.entry(slot).or_default().push(node);
254 self.node_to_slot.insert(node, slot);
255 }
256
257 fn remove_by_node(&mut self, node: &NodeId) -> Option<SlotId> {
258 if let Some(slot) = self.node_to_slot.remove(node) {
259 if let Some(nodes) = self.slot_to_nodes.get_mut(&slot) {
260 if let Some(index) = nodes.iter().position(|candidate| candidate == node) {
261 nodes.remove(index);
262 }
263 if nodes.is_empty() {
264 self.slot_to_nodes.remove(&slot);
265 self.slot_to_scopes.remove(&slot);
266 }
267 }
268 Some(slot)
269 } else {
270 None
271 }
272 }
273
274 fn get_nodes(&self, slot: &SlotId) -> Option<&[NodeId]> {
275 self.slot_to_nodes.get(slot).map(Vec::as_slice)
276 }
277
278 fn get_scopes(&self, slot: &SlotId) -> Option<&[RecomposeScope]> {
279 self.slot_to_scopes.get(slot).map(Vec::as_slice)
280 }
281
282 fn slot_has_invalid_scopes(&self, slot: SlotId) -> bool {
283 self.slot_to_scopes
284 .get(&slot)
285 .is_some_and(|scopes| scopes.iter().any(RecomposeScope::is_invalid))
286 }
287
288 fn slot_has_inactive_scopes(&self, slot: SlotId) -> bool {
289 self.slot_to_scopes
290 .get(&slot)
291 .is_some_and(|scopes| scopes.iter().any(|scope| !scope.is_active()))
292 }
293
294 fn deactivate_slot(&self, slot: SlotId) {
295 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
296 for scope in scopes {
297 scope.deactivate();
298 }
299 }
300 }
301
302 fn invalidate_scopes(&self) {
303 for scopes in self.slot_to_scopes.values() {
304 for scope in scopes {
305 scope.invalidate();
306 }
307 }
308 }
309}
310
311pub struct SubcomposeState {
314 mapping: NodeSlotMapping,
315 active_order: Vec<SlotId>,
316 live_slots: HashSet<SlotId>,
317 current_pass_active_slots: HashSet<SlotId>,
318 reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
319 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
320 reusable_node_counts: HashMap<SlotId, usize>,
321 exact_reactivation_slots: HashSet<SlotId>,
322 slot_content_types: HashMap<SlotId, u64>,
323 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
324 policy: Box<dyn SlotReusePolicy>,
325 pub(crate) current_index: usize,
326 pub(crate) reusable_count: usize,
327 pub(crate) precomposed_count: usize,
328 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
329 slot_callbacks: HashMap<SlotId, CallbackHolder>,
330 max_reusable_per_type: usize,
331 max_reusable_untyped: usize,
332 last_slot_reused: Option<bool>,
333 retained_capture_keys: HashMap<SlotId, RetainedCaptureKey>,
334 content_generation: std::cell::Cell<u64>,
335 slot_composed_generation: HashMap<SlotId, (u64, u64)>,
336 locals_generation: std::cell::Cell<u64>,
339}
340
341struct RetainedCaptureKey {
342 value: Box<dyn Any>,
343 eq: fn(&dyn Any, &dyn Any) -> bool,
344}
345
346fn retained_capture_key_eq<K: PartialEq + 'static>(stored: &dyn Any, candidate: &dyn Any) -> bool {
347 match (stored.downcast_ref::<K>(), candidate.downcast_ref::<K>()) {
348 (Some(stored), Some(candidate)) => stored == candidate,
349 _ => false,
350 }
351}
352
353impl fmt::Debug for SubcomposeState {
354 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
355 f.debug_struct("SubcomposeState")
356 .field("mapping", &self.mapping)
357 .field("active_order", &self.active_order)
358 .field("reusable_by_type_count", &self.reusable_by_type.len())
359 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
360 .field("precomposed_nodes", &self.precomposed_nodes)
361 .field("current_index", &self.current_index)
362 .field("reusable_count", &self.reusable_count)
363 .field("precomposed_count", &self.precomposed_count)
364 .field("slot_compositions_count", &self.slot_compositions.len())
365 .finish()
366 }
367}
368
369impl Default for SubcomposeState {
370 fn default() -> Self {
371 Self::new(Box::new(DefaultSlotReusePolicy))
372 }
373}
374
375const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
376
377const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
378
379impl SubcomposeState {
380 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
382 Self {
383 mapping: NodeSlotMapping::default(),
384 active_order: Vec::new(),
385 live_slots: HashSet::default(),
386 current_pass_active_slots: HashSet::default(),
387 reusable_by_type: HashMap::default(),
388 reusable_nodes_untyped: VecDeque::new(),
389 reusable_node_counts: HashMap::default(),
390 exact_reactivation_slots: HashSet::default(),
391 slot_content_types: HashMap::default(),
392 precomposed_nodes: HashMap::default(),
393 policy,
394 current_index: 0,
395 reusable_count: 0,
396 precomposed_count: 0,
397 slot_compositions: HashMap::default(),
398 slot_callbacks: HashMap::default(),
399 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
400 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
401 last_slot_reused: None,
402 retained_capture_keys: HashMap::default(),
403 content_generation: std::cell::Cell::new(0),
404 slot_composed_generation: HashMap::default(),
405 locals_generation: std::cell::Cell::new(0),
406 }
407 }
408
409 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
411 self.policy = policy;
412 }
413
414 pub fn set_reusable_pool_limits(&mut self, per_type: usize, untyped: usize) {
415 self.max_reusable_per_type = per_type;
416 self.max_reusable_untyped = untyped;
417 }
418
419 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
426 self.slot_content_types.insert(slot_id, content_type);
427 self.policy.register_content_type(slot_id, content_type);
428 }
429
430 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
436 match content_type {
437 Some(ct) => self.register_content_type(slot_id, ct),
438 None => {
439 self.slot_content_types.remove(&slot_id);
440 self.policy.remove_content_type(slot_id);
441 }
442 }
443 }
444
445 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
447 self.slot_content_types.get(&slot_id).copied()
448 }
449
450 pub fn active_slot_for_node(&self, node_id: NodeId) -> Option<SlotId> {
455 self.mapping
456 .node_to_slot
457 .get(&node_id)
458 .copied()
459 .filter(|slot| self.live_slots.contains(slot))
460 }
461
462 pub fn begin_pass(&mut self) {
467 self.current_index = 0;
468 self.current_pass_active_slots.clear();
469 }
470
471 pub fn active_slot_cursor(&self) -> usize {
473 self.current_index
474 }
475
476 pub fn restore_active_slot_cursor(&mut self, cursor: usize) {
479 self.current_index = cursor.min(self.active_order.len());
480 }
481
482 pub fn recycle_active_slot(&mut self, slot_id: SlotId) -> SubcomposeDisposal {
484 self.recycle_active_slot_internal(slot_id, false)
485 }
486
487 pub fn recycle_prefetched_active_slot(&mut self, slot_id: SlotId) -> SubcomposeDisposal {
488 self.recycle_active_slot_internal(slot_id, true)
489 }
490
491 pub fn recycle_active_slots_where(
492 &mut self,
493 mut predicate: impl FnMut(SlotId) -> bool,
494 ) -> SubcomposeDisposal {
495 let slots: Vec<_> = self
496 .active_order
497 .iter()
498 .copied()
499 .filter(|slot| !self.current_pass_active_slots.contains(slot))
500 .filter(|slot| predicate(*slot))
501 .collect();
502 let mut disposed = SubcomposeDisposal::default();
503 for slot in slots {
504 disposed.append(self.recycle_active_slot(slot));
505 }
506 disposed
507 }
508
509 fn recycle_active_slot_internal(
510 &mut self,
511 slot_id: SlotId,
512 allow_exact_reactivation: bool,
513 ) -> SubcomposeDisposal {
514 let Some(position) = self
515 .active_order
516 .iter()
517 .position(|candidate| *candidate == slot_id)
518 else {
519 return SubcomposeDisposal::default();
520 };
521 self.active_order.remove(position);
522 if position < self.current_index {
523 self.current_index = self.current_index.saturating_sub(1);
524 }
525 self.move_slot_to_reusable(slot_id, allow_exact_reactivation);
526 self.enforce_reusable_pool_limits()
527 }
528
529 pub fn finish_pass(&mut self) -> SubcomposeDisposal {
531 self.dispose_or_reuse_starting_from_index(self.current_index)
532 }
533
534 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
538 Rc::clone(
539 self.slot_compositions
540 .entry(slot_id)
541 .or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
542 )
543 }
544
545 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
547 self.slot_callbacks.entry(slot_id).or_default().clone()
548 }
549
550 #[doc(hidden)]
551 pub fn activate_current_active_slot(&mut self, slot_id: SlotId) -> Option<&[NodeId]> {
552 self.activate_current_active_slot_at_cursor(slot_id)
553 .then(|| self.mapping.get_nodes(&slot_id))
554 .flatten()
555 }
556
557 fn activate_current_active_slot_at_cursor(&mut self, slot_id: SlotId) -> bool {
558 if self.current_pass_active_slots.contains(&slot_id)
559 || self.exact_reactivation_slots.contains(&slot_id)
560 || self.reusable_node_counts.contains_key(&slot_id)
561 || self
562 .active_order
563 .get(self.current_index)
564 .copied()
565 .is_none_or(|active_slot| active_slot != slot_id)
566 || self.mapping.slot_has_invalid_scopes(slot_id)
567 || self.mapping.slot_has_inactive_scopes(slot_id)
568 {
569 return false;
570 }
571
572 if self.mapping.get_nodes(&slot_id).is_none() {
573 return false;
574 }
575 self.last_slot_reused = Some(true);
576 self.live_slots.insert(slot_id);
577 self.current_pass_active_slots.insert(slot_id);
578 self.current_index += 1;
579 true
580 }
581
582 pub fn has_pending_precompositions(&self, slot_id: SlotId) -> bool {
586 self.precomposed_nodes.contains_key(&slot_id)
587 }
588
589 pub fn retained_capture_key_matches<K: PartialEq + 'static>(
593 &self,
594 slot_id: SlotId,
595 key: &K,
596 ) -> bool {
597 self.retained_capture_keys
598 .get(&slot_id)
599 .is_some_and(|retained| (retained.eq)(retained.value.as_ref(), key))
600 }
601
602 pub fn store_retained_capture_key<K: PartialEq + 'static>(&mut self, slot_id: SlotId, key: K) {
604 self.retained_capture_keys.insert(
605 slot_id,
606 RetainedCaptureKey {
607 value: Box::new(key),
608 eq: retained_capture_key_eq::<K>,
609 },
610 );
611 }
612
613 #[doc(hidden)]
614 pub fn activate_exact_slot(&mut self, slot_id: SlotId) -> Option<ExactSlotActivation<'_>> {
615 if self.activate_current_active_slot_at_cursor(slot_id) {
616 return Some(ExactSlotActivation {
617 nodes: self.mapping.get_nodes(&slot_id)?,
618 was_recycled: false,
619 });
620 }
621 let active_position = self
622 .active_order
623 .iter()
624 .position(|candidate| *candidate == slot_id);
625 let is_exact_reactivation = self.exact_reactivation_slots.contains(&slot_id);
626 if active_position.is_none() && !is_exact_reactivation
627 || self.mapping.slot_has_invalid_scopes(slot_id)
628 {
629 return None;
630 }
631
632 let node_count = self.mapping.get_nodes(&slot_id)?.len();
633 if active_position.is_none() {
634 for index in 0..node_count {
635 let node = self.mapping.get_nodes(&slot_id)?[index];
636 let _ = self.remove_from_reusable_pools(node);
637 }
638 for scope in self.mapping.get_scopes(&slot_id).unwrap_or_default() {
639 scope.reactivate();
640 }
641 }
642 self.mark_slot_active(slot_id, true);
643 Self::consume_precomposed_nodes(
644 &mut self.precomposed_nodes,
645 &mut self.precomposed_count,
646 slot_id,
647 self.mapping.get_nodes(&slot_id)?,
648 );
649 Some(ExactSlotActivation {
650 nodes: self.mapping.get_nodes(&slot_id)?,
651 was_recycled: active_position.is_none(),
652 })
653 }
654
655 pub fn register_active(
658 &mut self,
659 slot_id: SlotId,
660 node_ids: &[NodeId],
661 scopes: &[RecomposeScope],
662 ) {
663 let was_reused = self.mapping.get_nodes(&slot_id).is_some();
664 for scope in scopes {
665 scope.reactivate();
666 }
667 self.update_active_slot_mapping(slot_id, node_ids, scopes);
668 self.mark_slot_active(slot_id, was_reused);
669 }
670
671 fn mark_slot_active(&mut self, slot_id: SlotId, was_reused: bool) {
672 self.last_slot_reused = Some(was_reused);
673 self.live_slots.insert(slot_id);
674 self.current_pass_active_slots.insert(slot_id);
675 self.exact_reactivation_slots.remove(&slot_id);
676
677 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
678 if position < self.current_index {
679 return;
680 }
681 self.active_order.remove(position);
682 }
683 let insert_at = self.current_index.min(self.active_order.len());
684 self.active_order.insert(insert_at, slot_id);
685 self.current_index += 1;
686 }
687
688 fn update_active_slot_mapping(
689 &mut self,
690 slot_id: SlotId,
691 node_ids: &[NodeId],
692 scopes: &[RecomposeScope],
693 ) {
694 self.mapping.set_nodes(slot_id, node_ids);
695 self.mapping.set_scopes(slot_id, scopes);
696 Self::consume_precomposed_nodes(
697 &mut self.precomposed_nodes,
698 &mut self.precomposed_count,
699 slot_id,
700 node_ids,
701 );
702 }
703
704 fn consume_precomposed_nodes(
705 precomposed_nodes: &mut HashMap<SlotId, Vec<NodeId>>,
706 precomposed_count: &mut usize,
707 slot_id: SlotId,
708 node_ids: &[NodeId],
709 ) {
710 if let Some(nodes) = precomposed_nodes.get_mut(&slot_id) {
711 let before_len = nodes.len();
712 nodes.retain(|node| !node_ids.contains(node));
713 let removed = before_len - nodes.len();
714 *precomposed_count = precomposed_count.saturating_sub(removed);
715 if nodes.is_empty() {
716 precomposed_nodes.remove(&slot_id);
717 }
718 }
719 }
720
721 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
724 self.precomposed_nodes
725 .entry(slot_id)
726 .or_default()
727 .push(node_id);
728 self.precomposed_count += 1;
729 }
730
731 fn increment_reusable_slot(&mut self, slot_id: SlotId) {
732 *self.reusable_node_counts.entry(slot_id).or_insert(0) += 1;
733 self.reusable_count += 1;
734 }
735
736 fn decrement_reusable_slot(&mut self, slot_id: SlotId) {
737 let Some(entry) = self.reusable_node_counts.get_mut(&slot_id) else {
738 self.reusable_count = self.reusable_count.saturating_sub(1);
739 return;
740 };
741 *entry = entry.saturating_sub(1);
742 if *entry == 0 {
743 self.reusable_node_counts.remove(&slot_id);
744 }
745 self.reusable_count = self.reusable_count.saturating_sub(1);
746 }
747
748 pub fn slot_is_retained(&self, slot_id: SlotId) -> bool {
752 self.live_slots.contains(&slot_id) || self.reusable_node_counts.contains_key(&slot_id)
753 }
754
755 fn prune_slot_if_unused(&mut self, slot_id: SlotId) -> Option<Rc<SlotsHost>> {
756 if self.slot_is_retained(slot_id) {
757 return None;
758 }
759
760 debug_assert!(
761 self.mapping.get_nodes(&slot_id).is_none(),
762 "inactive slot {slot_id:?} still has mapped nodes",
763 );
764
765 let host = self.slot_compositions.remove(&slot_id);
766 self.slot_callbacks.remove(&slot_id);
767 self.slot_content_types.remove(&slot_id);
768 self.retained_capture_keys.remove(&slot_id);
769 self.slot_composed_generation.remove(&slot_id);
770 self.policy.remove_content_type(slot_id);
771 if let Some(nodes) = self.precomposed_nodes.remove(&slot_id) {
772 self.precomposed_count = self.precomposed_count.saturating_sub(nodes.len());
773 }
774 host
775 }
776
777 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<(NodeId, bool)> {
788 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
789 let first_node = nodes.first().copied();
790 if let Some(node_id) = first_node {
791 let _ = self.remove_from_reusable_pools(node_id);
792 self.exact_reactivation_slots.remove(&slot_id);
793 return Some((node_id, false));
794 }
795 }
796
797 let content_type = self.slot_content_types.get(&slot_id).copied();
798
799 if let Some(ct) = content_type
800 && let Some((old_slot, node_id)) = self.take_compatible_typed_reusable(ct, slot_id)
801 {
802 self.decrement_reusable_slot(old_slot);
803 self.move_node_to_slot(node_id, old_slot, slot_id);
804 return Some((node_id, true));
805 }
806
807 let exact_reactivation_slots = &self.exact_reactivation_slots;
808 let policy = &self.policy;
809 let position = self
810 .reusable_nodes_untyped
811 .iter()
812 .position(|(existing_slot, _)| {
813 !exact_reactivation_slots.contains(existing_slot)
814 && policy.are_compatible(*existing_slot, slot_id)
815 });
816
817 if let Some(index) = position
818 && let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index)
819 {
820 self.decrement_reusable_slot(old_slot);
821 self.move_node_to_slot(node_id, old_slot, slot_id);
822 return Some((node_id, true));
823 }
824
825 None
826 }
827
828 fn take_compatible_typed_reusable(
829 &mut self,
830 content_type: u64,
831 requested_slot: SlotId,
832 ) -> Option<(SlotId, NodeId)> {
833 let reused = {
834 let policy = &self.policy;
835 let exact_reactivation_slots = &self.exact_reactivation_slots;
836 let pool = self.reusable_by_type.get_mut(&content_type)?;
837 let index = pool.iter().position(|(existing_slot, _)| {
838 !exact_reactivation_slots.contains(existing_slot)
839 && policy.are_compatible(*existing_slot, requested_slot)
840 })?;
841 pool.remove(index)
842 };
843
844 if self
845 .reusable_by_type
846 .get(&content_type)
847 .is_some_and(std::collections::VecDeque::is_empty)
848 {
849 self.reusable_by_type.remove(&content_type);
850 }
851
852 reused
853 }
854
855 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> Option<SlotId> {
856 let mut typed_match = None;
857 for (&content_type, pool) in &self.reusable_by_type {
858 if let Some(position) = pool
859 .iter()
860 .position(|(_, pooled_node)| *pooled_node == node_id)
861 {
862 typed_match = Some((content_type, position));
863 break;
864 }
865 }
866 if let Some((content_type, position)) = typed_match {
867 let pool = self.reusable_by_type.get_mut(&content_type)?;
868 let (slot, _) = pool.remove(position)?;
869 if pool.is_empty() {
870 self.reusable_by_type.remove(&content_type);
871 }
872 self.decrement_reusable_slot(slot);
873 return Some(slot);
874 }
875 if let Some(position) = self
876 .reusable_nodes_untyped
877 .iter()
878 .position(|(_, n)| *n == node_id)
879 && let Some((slot, _)) = self.reusable_nodes_untyped.remove(position)
880 {
881 self.decrement_reusable_slot(slot);
882 return Some(slot);
883 }
884 None
885 }
886
887 fn move_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
888 if old_slot == new_slot {
889 return;
890 }
891
892 self.mapping.remove_by_node(&node_id);
893 self.exact_reactivation_slots.remove(&old_slot);
894 self.mapping.add_node(new_slot, node_id);
895 self.slot_content_types.remove(&old_slot);
896 self.retained_capture_keys.remove(&old_slot);
897 self.retained_capture_keys.remove(&new_slot);
898 self.slot_composed_generation.remove(&old_slot);
899 self.slot_composed_generation.remove(&new_slot);
900 self.policy.remove_content_type(old_slot);
901 if let Some(slots) = self.slot_compositions.remove(&old_slot) {
902 self.slot_compositions.insert(new_slot, slots);
903 }
904 if let Some(callback) = self.slot_callbacks.remove(&old_slot) {
905 self.slot_callbacks.insert(new_slot, callback);
906 }
907 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
908 let before_len = nodes.len();
909 nodes.retain(|candidate| *candidate != node_id);
910 let removed = before_len - nodes.len();
911 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
912 if nodes.is_empty() {
913 self.precomposed_nodes.remove(&old_slot);
914 }
915 }
916 }
917
918 pub fn dispose_or_reuse_starting_from_index(
922 &mut self,
923 start_index: usize,
924 ) -> SubcomposeDisposal {
925 if start_index >= self.active_order.len() {
926 return SubcomposeDisposal::default();
927 }
928
929 let retain = self
930 .policy
931 .get_slots_to_retain(&self.active_order[start_index..]);
932 let mut retained = Vec::new();
933 while self.active_order.len() > start_index {
934 let Some(slot) = self.active_order.pop() else {
935 break;
936 };
937 if retain.contains(&slot) {
938 retained.push(slot);
939 continue;
940 }
941 self.move_slot_to_reusable(slot, false);
942 }
943 retained.reverse();
944 self.active_order.extend(retained);
945
946 self.enforce_reusable_pool_limits()
947 }
948
949 fn move_slot_to_reusable(&mut self, slot: SlotId, allow_exact_reactivation: bool) {
950 self.live_slots.remove(&slot);
951 self.current_pass_active_slots.remove(&slot);
952 self.mapping.deactivate_slot(slot);
953 let forgot_effects = self
954 .slot_compositions
955 .get(&slot)
956 .is_some_and(|host| host.forget_effects());
957 if allow_exact_reactivation && !forgot_effects {
958 self.exact_reactivation_slots.insert(slot);
959 } else {
960 self.exact_reactivation_slots.remove(&slot);
961 }
962
963 let content_type = self.slot_content_types.get(&slot).copied();
964 let nodes: SmallVec<[NodeId; 4]> = self
965 .mapping
966 .get_nodes(&slot)
967 .into_iter()
968 .flatten()
969 .copied()
970 .collect();
971 for node in nodes {
972 if let Some(ct) = content_type {
973 self.reusable_by_type
974 .entry(ct)
975 .or_default()
976 .push_back((slot, node));
977 } else {
978 self.reusable_nodes_untyped.push_back((slot, node));
979 }
980 self.increment_reusable_slot(slot);
981 }
982 }
983
984 fn enforce_reusable_pool_limits(&mut self) -> SubcomposeDisposal {
985 let mut disposed = SubcomposeDisposal::default();
986 let mut typed_disposals = Vec::new();
987 for pool in self.reusable_by_type.values_mut() {
988 while pool.len() > self.max_reusable_per_type {
989 if let Some((slot, node_id)) = pool.pop_front() {
990 typed_disposals.push((slot, node_id));
991 }
992 }
993 }
994 for (slot, node_id) in typed_disposals {
995 self.decrement_reusable_slot(slot);
996 self.mapping.remove_by_node(&node_id);
997 self.exact_reactivation_slots.remove(&slot);
998 disposed.slot_hosts.extend(self.prune_slot_if_unused(slot));
999 disposed.nodes.push(node_id);
1000 }
1001
1002 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
1003 if let Some((slot, node_id)) = self.reusable_nodes_untyped.pop_front() {
1004 self.decrement_reusable_slot(slot);
1005 self.mapping.remove_by_node(&node_id);
1006 self.exact_reactivation_slots.remove(&slot);
1007 disposed.slot_hosts.extend(self.prune_slot_if_unused(slot));
1008 disposed.nodes.push(node_id);
1009 }
1010 }
1011
1012 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
1013 disposed
1014 }
1015
1016 pub fn reusable(&self) -> Vec<NodeId> {
1018 let mut nodes: Vec<NodeId> = self
1019 .reusable_by_type
1020 .values()
1021 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
1022 .collect();
1023 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
1024 nodes
1025 }
1026
1027 pub fn active_slots_count(&self) -> usize {
1032 self.active_order.len()
1033 }
1034
1035 pub fn reusable_slots_count(&self) -> usize {
1040 self.reusable_count
1041 }
1042
1043 pub fn invalidate_scopes(&self) {
1056 self.mapping.invalidate_scopes();
1057 self.content_generation
1058 .set(self.content_generation.get() + 1);
1059 }
1060
1061 pub fn invalidate_locals(&self) {
1065 self.bump_content_generation();
1066 self.locals_generation.set(self.content_generation.get());
1067 }
1068
1069 pub fn slot_locals_stale(&self, slot_id: SlotId) -> bool {
1074 let changed_at = self.locals_generation.get();
1075 self.slot_composed_generation
1076 .get(&slot_id)
1077 .is_some_and(|(generation, _)| *generation < changed_at)
1078 }
1079
1080 pub fn bump_content_generation(&self) {
1087 self.content_generation
1088 .set(self.content_generation.get() + 1);
1089 }
1090
1091 pub fn slot_content_generation_current(&self, slot_id: SlotId, owner_epoch: u64) -> bool {
1097 self.slot_composed_generation.get(&slot_id).copied()
1098 == Some((self.content_generation.get(), owner_epoch))
1099 }
1100
1101 pub fn mark_slot_composed_current(&mut self, slot_id: SlotId, owner_epoch: u64) {
1104 self.slot_composed_generation
1105 .insert(slot_id, (self.content_generation.get(), owner_epoch));
1106 }
1107
1108 pub fn was_last_slot_reused(&self) -> Option<bool> {
1116 self.last_slot_reused
1117 }
1118
1119 #[doc(hidden)]
1120 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
1121 self.mapping
1122 .slot_to_scopes
1123 .iter()
1124 .map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
1125 .collect()
1126 }
1127
1128 #[doc(hidden)]
1129 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<crate::SlotDebugEntry>> {
1130 let slots = self.slot_compositions.get(&slot_id)?;
1131 Some(slots.borrow().debug_dump_slot_entries())
1132 }
1133
1134 #[doc(hidden)]
1135 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
1136 let slots = self.slot_compositions.get(&slot_id)?;
1137 Some(slots.borrow().debug_dump_groups())
1138 }
1139
1140 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
1142 &self.precomposed_nodes
1143 }
1144
1145 pub fn drain_inactive_precomposed(&mut self) -> SubcomposeDisposal {
1148 let mut disposed = SubcomposeDisposal::default();
1149 let mut empty_slots = Vec::new();
1150 for (slot, nodes) in &mut self.precomposed_nodes {
1151 if !self.current_pass_active_slots.contains(slot) {
1152 disposed.nodes.extend(nodes.iter().copied());
1153 empty_slots.push(*slot);
1154 }
1155 }
1156 for slot in empty_slots {
1157 self.precomposed_nodes.remove(&slot);
1158 disposed.slot_hosts.extend(self.prune_slot_if_unused(slot));
1159 }
1160 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.nodes.len());
1161 disposed
1162 }
1163}
1164
1165#[cfg(test)]
1166#[path = "tests/subcompose_tests.rs"]
1167mod tests;