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 {
177 return true;
178 }
179
180 let types = self.slot_types.borrow();
182 match (types.get(&existing), types.get(&requested)) {
183 (Some(existing_type), Some(requested_type)) => existing_type == requested_type,
184 _ => false,
186 }
187 }
188
189 fn register_content_type(&self, slot_id: SlotId, content_type: u64) {
190 self.set_content_type(slot_id, content_type);
191 }
192
193 fn remove_content_type(&self, slot_id: SlotId) {
194 ContentTypeReusePolicy::remove_content_type(self, slot_id);
195 }
196}
197
198#[derive(Default, Clone)]
199
200struct NodeSlotMapping {
201 slot_to_nodes: HashMap<SlotId, Vec<NodeId>>,
202 node_to_slot: HashMap<NodeId, SlotId>,
203 slot_to_scopes: HashMap<SlotId, Vec<RecomposeScope>>,
204}
205
206impl fmt::Debug for NodeSlotMapping {
207 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
208 f.debug_struct("NodeSlotMapping")
209 .field("slot_to_nodes", &self.slot_to_nodes)
210 .field("node_to_slot", &self.node_to_slot)
211 .finish()
212 }
213}
214
215impl NodeSlotMapping {
216 fn set_nodes(&mut self, slot: SlotId, nodes: &[NodeId]) {
217 self.slot_to_nodes.insert(slot, nodes.to_vec());
218 for node in nodes {
219 self.node_to_slot.insert(*node, slot);
220 }
221 }
222
223 fn set_scopes(&mut self, slot: SlotId, scopes: &[RecomposeScope]) {
224 self.slot_to_scopes.insert(slot, scopes.to_vec());
225 }
226
227 fn add_node(&mut self, slot: SlotId, node: NodeId) {
228 self.slot_to_nodes.entry(slot).or_default().push(node);
229 self.node_to_slot.insert(node, slot);
230 }
231
232 fn remove_by_node(&mut self, node: &NodeId) -> Option<SlotId> {
233 if let Some(slot) = self.node_to_slot.remove(node) {
234 if let Some(nodes) = self.slot_to_nodes.get_mut(&slot) {
235 if let Some(index) = nodes.iter().position(|candidate| candidate == node) {
236 nodes.remove(index);
237 }
238 if nodes.is_empty() {
239 self.slot_to_nodes.remove(&slot);
240 self.slot_to_scopes.remove(&slot);
242 }
243 }
244 Some(slot)
245 } else {
246 None
247 }
248 }
249
250 fn get_nodes(&self, slot: &SlotId) -> Option<&[NodeId]> {
251 self.slot_to_nodes.get(slot).map(|nodes| nodes.as_slice())
252 }
253
254 fn deactivate_slot(&self, slot: SlotId) {
255 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
256 for scope in scopes {
257 scope.deactivate();
258 }
259 }
260 }
261
262 fn invalidate_scopes(&self) {
263 for scopes in self.slot_to_scopes.values() {
264 for scope in scopes {
265 scope.invalidate();
266 }
267 }
268 }
269}
270
271pub struct SubcomposeState {
274 mapping: NodeSlotMapping,
275 active_order: Vec<SlotId>,
276 live_slots: HashSet<SlotId>,
277 current_pass_active_slots: HashSet<SlotId>,
278 reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
282 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
284 reusable_node_counts: HashMap<SlotId, usize>,
285 slot_content_types: HashMap<SlotId, u64>,
287 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
288 policy: Box<dyn SlotReusePolicy>,
289 pub(crate) current_index: usize,
290 pub(crate) reusable_count: usize,
291 pub(crate) precomposed_count: usize,
292 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
296 slot_callbacks: HashMap<SlotId, CallbackHolder>,
298 max_reusable_per_type: usize,
300 max_reusable_untyped: usize,
302 last_slot_reused: Option<bool>,
305}
306
307impl fmt::Debug for SubcomposeState {
308 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
309 f.debug_struct("SubcomposeState")
310 .field("mapping", &self.mapping)
311 .field("active_order", &self.active_order)
312 .field("reusable_by_type_count", &self.reusable_by_type.len())
313 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
314 .field("precomposed_nodes", &self.precomposed_nodes)
315 .field("current_index", &self.current_index)
316 .field("reusable_count", &self.reusable_count)
317 .field("precomposed_count", &self.precomposed_count)
318 .field("slot_compositions_count", &self.slot_compositions.len())
319 .finish()
320 }
321}
322
323impl Default for SubcomposeState {
324 fn default() -> Self {
325 Self::new(Box::new(DefaultSlotReusePolicy))
326 }
327}
328
329const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
332
333const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
337
338impl SubcomposeState {
339 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
341 Self {
342 mapping: NodeSlotMapping::default(),
343 active_order: Vec::new(),
344 live_slots: HashSet::default(),
345 current_pass_active_slots: HashSet::default(),
346 reusable_by_type: HashMap::default(),
347 reusable_nodes_untyped: VecDeque::new(),
348 reusable_node_counts: HashMap::default(),
349 slot_content_types: HashMap::default(),
350 precomposed_nodes: HashMap::default(),
351 policy,
352 current_index: 0,
353 reusable_count: 0,
354 precomposed_count: 0,
355 slot_compositions: HashMap::default(),
356 slot_callbacks: HashMap::default(),
357 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
358 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
359 last_slot_reused: None,
360 }
361 }
362
363 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
365 self.policy = policy;
366 }
367
368 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
375 self.slot_content_types.insert(slot_id, content_type);
376 self.policy.register_content_type(slot_id, content_type);
377 }
378
379 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
385 match content_type {
386 Some(ct) => self.register_content_type(slot_id, ct),
387 None => {
388 self.slot_content_types.remove(&slot_id);
389 self.policy.remove_content_type(slot_id);
390 }
391 }
392 }
393
394 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
396 self.slot_content_types.get(&slot_id).copied()
397 }
398
399 pub fn begin_pass(&mut self) {
404 self.current_index = 0;
405 self.current_pass_active_slots.clear();
406 }
407
408 pub fn finish_pass(&mut self) -> Vec<NodeId> {
410 self.dispose_or_reuse_starting_from_index(self.current_index)
411 }
412
413 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
417 Rc::clone(
418 self.slot_compositions
419 .entry(slot_id)
420 .or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
421 )
422 }
423
424 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
426 self.slot_callbacks.entry(slot_id).or_default().clone()
427 }
428
429 pub fn register_active(
432 &mut self,
433 slot_id: SlotId,
434 node_ids: &[NodeId],
435 scopes: &[RecomposeScope],
436 ) {
437 let was_reused = self.mapping.get_nodes(&slot_id).is_some();
438 self.last_slot_reused = Some(was_reused);
439 self.live_slots.insert(slot_id);
440 self.current_pass_active_slots.insert(slot_id);
441
442 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
443 if position < self.current_index {
444 for scope in scopes {
445 scope.reactivate();
446 }
447 self.mapping.set_nodes(slot_id, node_ids);
448 self.mapping.set_scopes(slot_id, scopes);
449 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
450 let before_len = nodes.len();
451 nodes.retain(|node| !node_ids.contains(node));
452 let removed = before_len - nodes.len();
453 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
454 if nodes.is_empty() {
455 self.precomposed_nodes.remove(&slot_id);
456 }
457 }
458 return;
459 }
460 self.active_order.remove(position);
461 }
462 for scope in scopes {
463 scope.reactivate();
464 }
465 self.mapping.set_nodes(slot_id, node_ids);
466 self.mapping.set_scopes(slot_id, scopes);
467 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
468 let before_len = nodes.len();
469 nodes.retain(|node| !node_ids.contains(node));
470 let removed = before_len - nodes.len();
471 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
472 if nodes.is_empty() {
473 self.precomposed_nodes.remove(&slot_id);
474 }
475 }
476 let insert_at = self.current_index.min(self.active_order.len());
477 self.active_order.insert(insert_at, slot_id);
478 self.current_index += 1;
479 }
480
481 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
484 self.precomposed_nodes
485 .entry(slot_id)
486 .or_default()
487 .push(node_id);
488 self.precomposed_count += 1;
489 }
490
491 fn increment_reusable_slot(&mut self, slot_id: SlotId) {
492 *self.reusable_node_counts.entry(slot_id).or_insert(0) += 1;
493 self.reusable_count += 1;
494 }
495
496 fn decrement_reusable_slot(&mut self, slot_id: SlotId) {
497 let Some(entry) = self.reusable_node_counts.get_mut(&slot_id) else {
498 self.reusable_count = self.reusable_count.saturating_sub(1);
499 return;
500 };
501 *entry = entry.saturating_sub(1);
502 if *entry == 0 {
503 self.reusable_node_counts.remove(&slot_id);
504 }
505 self.reusable_count = self.reusable_count.saturating_sub(1);
506 }
507
508 fn prune_slot_if_unused(&mut self, slot_id: SlotId) {
509 if self.live_slots.contains(&slot_id) || self.reusable_node_counts.contains_key(&slot_id) {
510 return;
511 }
512
513 debug_assert!(
514 self.mapping.get_nodes(&slot_id).is_none(),
515 "inactive slot {slot_id:?} still has mapped nodes",
516 );
517
518 self.slot_compositions.remove(&slot_id);
519 self.slot_callbacks.remove(&slot_id);
520 self.slot_content_types.remove(&slot_id);
521 self.policy.remove_content_type(slot_id);
522 if let Some(nodes) = self.precomposed_nodes.remove(&slot_id) {
523 self.precomposed_count = self.precomposed_count.saturating_sub(nodes.len());
524 }
525 }
526
527 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<NodeId> {
536 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
537 let first_node = nodes.first().copied();
538 if let Some(node_id) = first_node {
539 let _ = self.remove_from_reusable_pools(node_id);
540 return Some(node_id);
541 }
542 }
543
544 let content_type = self.slot_content_types.get(&slot_id).copied();
545
546 if let Some(ct) = content_type {
547 if let Some((old_slot, node_id)) = self.take_compatible_typed_reusable(ct, slot_id) {
548 self.decrement_reusable_slot(old_slot);
549 self.move_node_to_slot(node_id, old_slot, slot_id);
550 return Some(node_id);
551 }
552 }
553
554 let position = self
555 .reusable_nodes_untyped
556 .iter()
557 .position(|(existing_slot, _)| self.policy.are_compatible(*existing_slot, slot_id));
558
559 if let Some(index) = position {
560 if let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index) {
561 self.decrement_reusable_slot(old_slot);
562 self.move_node_to_slot(node_id, old_slot, slot_id);
563 return Some(node_id);
564 }
565 }
566
567 None
568 }
569
570 fn take_compatible_typed_reusable(
571 &mut self,
572 content_type: u64,
573 requested_slot: SlotId,
574 ) -> Option<(SlotId, NodeId)> {
575 let reused = {
576 let policy = &self.policy;
577 let pool = self.reusable_by_type.get_mut(&content_type)?;
578 let index = pool.iter().position(|(existing_slot, _)| {
579 policy.are_compatible(*existing_slot, requested_slot)
580 })?;
581 pool.remove(index)
582 };
583
584 if self
585 .reusable_by_type
586 .get(&content_type)
587 .map(|pool| pool.is_empty())
588 .unwrap_or(false)
589 {
590 self.reusable_by_type.remove(&content_type);
591 }
592
593 reused
594 }
595
596 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> Option<SlotId> {
598 let mut typed_match = None;
600 for (&content_type, pool) in &self.reusable_by_type {
601 if let Some(position) = pool
602 .iter()
603 .position(|(_, pooled_node)| *pooled_node == node_id)
604 {
605 typed_match = Some((content_type, position));
606 break;
607 }
608 }
609 if let Some((content_type, position)) = typed_match {
610 let pool = self.reusable_by_type.get_mut(&content_type)?;
611 let (slot, _) = pool.remove(position)?;
612 if pool.is_empty() {
613 self.reusable_by_type.remove(&content_type);
614 }
615 self.decrement_reusable_slot(slot);
616 return Some(slot);
617 }
618 if let Some(position) = self
620 .reusable_nodes_untyped
621 .iter()
622 .position(|(_, n)| *n == node_id)
623 {
624 if let Some((slot, _)) = self.reusable_nodes_untyped.remove(position) {
625 self.decrement_reusable_slot(slot);
626 return Some(slot);
627 }
628 }
629 None
630 }
631
632 fn move_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
634 self.mapping.remove_by_node(&node_id);
635 self.mapping.add_node(new_slot, node_id);
636 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
637 let before_len = nodes.len();
638 nodes.retain(|candidate| *candidate != node_id);
639 let removed = before_len - nodes.len();
640 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
641 if nodes.is_empty() {
642 self.precomposed_nodes.remove(&old_slot);
643 }
644 }
645 self.prune_slot_if_unused(old_slot);
646 }
647
648 pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
652 if start_index >= self.active_order.len() {
653 return Vec::new();
654 }
655
656 let retain = self
657 .policy
658 .get_slots_to_retain(&self.active_order[start_index..]);
659 let mut retained = Vec::new();
660 while self.active_order.len() > start_index {
661 let Some(slot) = self.active_order.pop() else {
662 break;
663 };
664 if retain.contains(&slot) {
665 retained.push(slot);
666 continue;
667 }
668 self.live_slots.remove(&slot);
669 self.mapping.deactivate_slot(slot);
670
671 let content_type = self.slot_content_types.get(&slot).copied();
673 let nodes: SmallVec<[NodeId; 4]> = self
674 .mapping
675 .get_nodes(&slot)
676 .into_iter()
677 .flatten()
678 .copied()
679 .collect();
680 for node in nodes {
681 if let Some(ct) = content_type {
682 self.reusable_by_type
683 .entry(ct)
684 .or_default()
685 .push_back((slot, node));
686 } else {
687 self.reusable_nodes_untyped.push_back((slot, node));
688 }
689 self.increment_reusable_slot(slot);
690 }
691 }
692 retained.reverse();
693 self.active_order.extend(retained);
694
695 let mut disposed = Vec::new();
697
698 let mut typed_disposals = Vec::new();
700 for pool in self.reusable_by_type.values_mut() {
701 while pool.len() > self.max_reusable_per_type {
702 if let Some((slot, node_id)) = pool.pop_front() {
703 typed_disposals.push((slot, node_id));
704 }
705 }
706 }
707 for (slot, node_id) in typed_disposals {
708 self.decrement_reusable_slot(slot);
709 self.mapping.remove_by_node(&node_id);
710 self.prune_slot_if_unused(slot);
711 disposed.push(node_id);
712 }
713
714 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
716 if let Some((slot, node_id)) = self.reusable_nodes_untyped.pop_front() {
717 self.decrement_reusable_slot(slot);
718 self.mapping.remove_by_node(&node_id);
719 self.prune_slot_if_unused(slot);
720 disposed.push(node_id);
721 }
722 }
723
724 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
725 disposed
726 }
727
728 pub fn reusable(&self) -> Vec<NodeId> {
730 let mut nodes: Vec<NodeId> = self
731 .reusable_by_type
732 .values()
733 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
734 .collect();
735 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
736 nodes
737 }
738
739 pub fn active_slots_count(&self) -> usize {
744 self.active_order.len()
745 }
746
747 pub fn reusable_slots_count(&self) -> usize {
752 self.reusable_count
753 }
754
755 pub fn invalidate_scopes(&self) {
761 self.mapping.invalidate_scopes();
762 }
763
764 pub fn was_last_slot_reused(&self) -> Option<bool> {
772 self.last_slot_reused
773 }
774
775 #[doc(hidden)]
776 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
777 self.mapping
778 .slot_to_scopes
779 .iter()
780 .map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
781 .collect()
782 }
783
784 #[doc(hidden)]
785 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<crate::SlotDebugEntry>> {
786 let slots = self.slot_compositions.get(&slot_id)?;
787 Some(slots.borrow().debug_dump_slot_entries())
788 }
789
790 #[doc(hidden)]
791 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
792 let slots = self.slot_compositions.get(&slot_id)?;
793 Some(slots.borrow().debug_dump_groups())
794 }
795
796 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
798 &self.precomposed_nodes
799 }
800
801 pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
804 let mut disposed = Vec::new();
805 let mut empty_slots = Vec::new();
806 for (slot, nodes) in self.precomposed_nodes.iter_mut() {
807 if !self.current_pass_active_slots.contains(slot) {
808 disposed.extend(nodes.iter().copied());
809 empty_slots.push(*slot);
810 }
811 }
812 for slot in empty_slots {
813 self.precomposed_nodes.remove(&slot);
814 self.prune_slot_if_unused(slot);
815 }
816 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
818 disposed
819 }
820}
821
822#[cfg(test)]
823#[path = "tests/subcompose_tests.rs"]
824mod tests;