1use crate::collections::map::HashMap;
10use crate::collections::map::HashSet;
11use std::collections::VecDeque;
12use std::fmt;
13use std::rc::Rc;
14
15use crate::{CallbackHolder, NodeId, RecomposeScope, SlotTable, SlotsHost};
16
17pub type DebugSlotGroup = (usize, crate::Key, Option<usize>, usize);
18
19#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
24pub struct SlotId(pub u64);
25
26impl SlotId {
27 #[inline]
28 pub fn new(raw: u64) -> Self {
29 Self(raw)
30 }
31
32 #[inline]
33 pub fn raw(self) -> u64 {
34 self.0
35 }
36}
37
38pub trait SlotReusePolicy: 'static {
44 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId>;
48
49 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool;
57
58 fn register_content_type(&self, _slot_id: SlotId, _content_type: u64) {
65 }
67
68 fn remove_content_type(&self, _slot_id: SlotId) {
73 }
75
76 fn prune_slots(&self, _keep_slots: &HashSet<SlotId>) {
82 }
84}
85
86#[derive(Debug, Default)]
90pub struct DefaultSlotReusePolicy;
91
92impl SlotReusePolicy for DefaultSlotReusePolicy {
93 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
94 let _ = active;
95 HashSet::default()
96 }
97
98 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
99 existing == requested
100 }
101}
102
103pub struct ContentTypeReusePolicy {
125 slot_types: std::cell::RefCell<HashMap<SlotId, u64>>,
127}
128
129impl std::fmt::Debug for ContentTypeReusePolicy {
130 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
131 let types = self.slot_types.borrow();
132 f.debug_struct("ContentTypeReusePolicy")
133 .field("slot_types", &*types)
134 .finish()
135 }
136}
137
138impl Default for ContentTypeReusePolicy {
139 fn default() -> Self {
140 Self::new()
141 }
142}
143
144impl ContentTypeReusePolicy {
145 pub fn new() -> Self {
147 Self {
148 slot_types: std::cell::RefCell::new(HashMap::default()),
149 }
150 }
151
152 pub fn set_content_type(&self, slot: SlotId, content_type: u64) {
156 self.slot_types.borrow_mut().insert(slot, content_type);
157 }
158
159 pub fn remove_content_type(&self, slot: SlotId) {
161 self.slot_types.borrow_mut().remove(&slot);
162 }
163
164 pub fn clear(&self) {
166 self.slot_types.borrow_mut().clear();
167 }
168
169 pub fn get_content_type(&self, slot: SlotId) -> Option<u64> {
171 self.slot_types.borrow().get(&slot).copied()
172 }
173}
174
175impl SlotReusePolicy for ContentTypeReusePolicy {
176 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
177 let _ = active;
178 HashSet::default()
180 }
181
182 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
183 if existing == requested {
185 return true;
186 }
187
188 let types = self.slot_types.borrow();
190 match (types.get(&existing), types.get(&requested)) {
191 (Some(existing_type), Some(requested_type)) => existing_type == requested_type,
192 _ => false,
194 }
195 }
196
197 fn register_content_type(&self, slot_id: SlotId, content_type: u64) {
198 self.set_content_type(slot_id, content_type);
199 }
200
201 fn remove_content_type(&self, slot_id: SlotId) {
202 ContentTypeReusePolicy::remove_content_type(self, slot_id);
203 }
204
205 fn prune_slots(&self, keep_slots: &HashSet<SlotId>) {
206 self.slot_types
207 .borrow_mut()
208 .retain(|slot, _| keep_slots.contains(slot));
209 }
210}
211
212#[derive(Default, Clone)]
213
214struct NodeSlotMapping {
215 slot_to_nodes: HashMap<SlotId, Vec<NodeId>>,
216 node_to_slot: HashMap<NodeId, SlotId>,
217 slot_to_scopes: HashMap<SlotId, Vec<RecomposeScope>>,
218}
219
220impl fmt::Debug for NodeSlotMapping {
221 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
222 f.debug_struct("NodeSlotMapping")
223 .field("slot_to_nodes", &self.slot_to_nodes)
224 .field("node_to_slot", &self.node_to_slot)
225 .finish()
226 }
227}
228
229impl NodeSlotMapping {
230 fn set_nodes(&mut self, slot: SlotId, nodes: &[NodeId]) {
231 self.slot_to_nodes.insert(slot, nodes.to_vec());
232 for node in nodes {
233 self.node_to_slot.insert(*node, slot);
234 }
235 }
236
237 fn set_scopes(&mut self, slot: SlotId, scopes: &[RecomposeScope]) {
238 self.slot_to_scopes.insert(slot, scopes.to_vec());
239 }
240
241 fn add_node(&mut self, slot: SlotId, node: NodeId) {
242 self.slot_to_nodes.entry(slot).or_default().push(node);
243 self.node_to_slot.insert(node, slot);
244 }
245
246 fn remove_by_node(&mut self, node: &NodeId) -> Option<SlotId> {
247 if let Some(slot) = self.node_to_slot.remove(node) {
248 if let Some(nodes) = self.slot_to_nodes.get_mut(&slot) {
249 if let Some(index) = nodes.iter().position(|candidate| candidate == node) {
250 nodes.remove(index);
251 }
252 if nodes.is_empty() {
253 self.slot_to_nodes.remove(&slot);
254 self.slot_to_scopes.remove(&slot);
256 }
257 }
258 Some(slot)
259 } else {
260 None
261 }
262 }
263
264 fn get_nodes(&self, slot: &SlotId) -> Option<&[NodeId]> {
265 self.slot_to_nodes.get(slot).map(|nodes| nodes.as_slice())
266 }
267
268 fn get_slot(&self, node: &NodeId) -> Option<SlotId> {
269 self.node_to_slot.get(node).copied()
270 }
271
272 fn deactivate_slot(&self, slot: SlotId) {
273 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
274 for scope in scopes {
275 scope.deactivate();
276 }
277 }
278 }
279
280 fn invalidate_scopes(&self) {
281 for scopes in self.slot_to_scopes.values() {
282 for scope in scopes {
283 scope.invalidate();
284 }
285 }
286 }
287
288 fn retain_slots(&mut self, active: &HashSet<SlotId>) -> Vec<NodeId> {
289 let mut removed_nodes = Vec::new();
290 self.slot_to_nodes.retain(|slot, nodes| {
291 if active.contains(slot) {
292 true
293 } else {
294 removed_nodes.extend(nodes.iter().copied());
295 false
296 }
297 });
298 self.slot_to_scopes.retain(|slot, _| active.contains(slot));
299 for node in &removed_nodes {
300 self.node_to_slot.remove(node);
301 }
302 removed_nodes
303 }
304}
305
306pub struct SubcomposeState {
309 mapping: NodeSlotMapping,
310 active_order: Vec<SlotId>,
311 reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
315 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
317 slot_content_types: HashMap<SlotId, u64>,
319 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>,
320 policy: Box<dyn SlotReusePolicy>,
321 pub(crate) current_index: usize,
322 pub(crate) reusable_count: usize,
323 pub(crate) precomposed_count: usize,
324 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
328 slot_callbacks: HashMap<SlotId, CallbackHolder>,
330 max_reusable_per_type: usize,
332 max_reusable_untyped: usize,
334 last_slot_reused: Option<bool>,
337}
338
339impl fmt::Debug for SubcomposeState {
340 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
341 f.debug_struct("SubcomposeState")
342 .field("mapping", &self.mapping)
343 .field("active_order", &self.active_order)
344 .field("reusable_by_type_count", &self.reusable_by_type.len())
345 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
346 .field("precomposed_nodes", &self.precomposed_nodes)
347 .field("current_index", &self.current_index)
348 .field("reusable_count", &self.reusable_count)
349 .field("precomposed_count", &self.precomposed_count)
350 .field("slot_compositions_count", &self.slot_compositions.len())
351 .finish()
352 }
353}
354
355impl Default for SubcomposeState {
356 fn default() -> Self {
357 Self::new(Box::new(DefaultSlotReusePolicy))
358 }
359}
360
361const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
364
365const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
369
370impl SubcomposeState {
371 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
373 Self {
374 mapping: NodeSlotMapping::default(),
375 active_order: Vec::new(),
376 reusable_by_type: HashMap::default(),
377 reusable_nodes_untyped: VecDeque::new(),
378 slot_content_types: HashMap::default(),
379 precomposed_nodes: HashMap::default(),
380 policy,
381 current_index: 0,
382 reusable_count: 0,
383 precomposed_count: 0,
384 slot_compositions: HashMap::default(),
385 slot_callbacks: HashMap::default(),
386 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
387 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
388 last_slot_reused: None,
389 }
390 }
391
392 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
394 self.policy = policy;
395 }
396
397 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
404 self.slot_content_types.insert(slot_id, content_type);
405 self.policy.register_content_type(slot_id, content_type);
406 }
407
408 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
414 match content_type {
415 Some(ct) => self.register_content_type(slot_id, ct),
416 None => {
417 self.slot_content_types.remove(&slot_id);
418 self.policy.remove_content_type(slot_id);
419 }
420 }
421 }
422
423 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
425 self.slot_content_types.get(&slot_id).copied()
426 }
427
428 pub fn begin_pass(&mut self) {
433 self.current_index = 0;
434 }
435
436 pub fn finish_pass(&mut self) -> Vec<NodeId> {
438 let disposed = self.dispose_or_reuse_starting_from_index(self.current_index);
439 self.prune_inactive_slots();
440 disposed
441 }
442
443 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
447 Rc::clone(
448 self.slot_compositions
449 .entry(slot_id)
450 .or_insert_with(|| Rc::new(SlotsHost::new(SlotTable::new()))),
451 )
452 }
453
454 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
456 self.slot_callbacks.entry(slot_id).or_default().clone()
457 }
458
459 pub fn register_active(
462 &mut self,
463 slot_id: SlotId,
464 node_ids: &[NodeId],
465 scopes: &[RecomposeScope],
466 ) {
467 let was_reused =
469 self.mapping.get_nodes(&slot_id).is_some() || self.active_order.contains(&slot_id);
470 self.last_slot_reused = Some(was_reused);
471
472 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
473 if position < self.current_index {
474 for scope in scopes {
475 scope.reactivate();
476 }
477 self.mapping.set_nodes(slot_id, node_ids);
478 self.mapping.set_scopes(slot_id, scopes);
479 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
480 let before_len = nodes.len();
481 nodes.retain(|node| !node_ids.contains(node));
482 let removed = before_len - nodes.len();
483 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
484 if nodes.is_empty() {
485 self.precomposed_nodes.remove(&slot_id);
486 }
487 }
488 return;
489 }
490 self.active_order.remove(position);
491 }
492 for scope in scopes {
493 scope.reactivate();
494 }
495 self.mapping.set_nodes(slot_id, node_ids);
496 self.mapping.set_scopes(slot_id, scopes);
497 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
498 let before_len = nodes.len();
499 nodes.retain(|node| !node_ids.contains(node));
500 let removed = before_len - nodes.len();
501 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
502 if nodes.is_empty() {
503 self.precomposed_nodes.remove(&slot_id);
504 }
505 }
506 let insert_at = self.current_index.min(self.active_order.len());
507 self.active_order.insert(insert_at, slot_id);
508 self.current_index += 1;
509 }
510
511 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
514 self.precomposed_nodes
515 .entry(slot_id)
516 .or_default()
517 .push(node_id);
518 self.precomposed_count += 1;
519 }
520
521 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<NodeId> {
529 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
535 let first_node = nodes.first().copied();
536 if let Some(node_id) = first_node {
537 let _ = self.remove_from_reusable_pools(node_id);
539 self.update_reusable_count();
540 return Some(node_id);
541 }
542 }
543
544 let content_type = self.slot_content_types.get(&slot_id).copied();
546
547 if let Some(ct) = content_type {
549 if let Some(pool) = self.reusable_by_type.get_mut(&ct) {
550 if let Some((old_slot, node_id)) = pool.pop_front() {
551 self.migrate_node_to_slot(node_id, old_slot, slot_id);
552 self.update_reusable_count();
553 return Some(node_id);
554 }
555 }
556 }
557
558 let position = self
560 .reusable_nodes_untyped
561 .iter()
562 .position(|(existing_slot, _)| self.policy.are_compatible(*existing_slot, slot_id));
563
564 if let Some(index) = position {
565 if let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index) {
566 self.migrate_node_to_slot(node_id, old_slot, slot_id);
567 self.update_reusable_count();
568 return Some(node_id);
569 }
570 }
571
572 None
573 }
574
575 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> bool {
577 for pool in self.reusable_by_type.values_mut() {
579 if let Some(pos) = pool.iter().position(|(_, n)| *n == node_id) {
580 pool.remove(pos);
581 return true;
582 }
583 }
584 if let Some(pos) = self
586 .reusable_nodes_untyped
587 .iter()
588 .position(|(_, n)| *n == node_id)
589 {
590 self.reusable_nodes_untyped.remove(pos);
591 return true;
592 }
593 false
594 }
595
596 fn migrate_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
598 self.mapping.remove_by_node(&node_id);
599 self.mapping.add_node(new_slot, node_id);
600 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
601 nodes.retain(|candidate| *candidate != node_id);
602 if nodes.is_empty() {
603 self.precomposed_nodes.remove(&old_slot);
604 }
605 }
606 }
607
608 fn update_reusable_count(&mut self) {
610 self.reusable_count = self
611 .reusable_by_type
612 .values()
613 .map(|p| p.len())
614 .sum::<usize>()
615 + self.reusable_nodes_untyped.len();
616 }
617
618 pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
622 if start_index >= self.active_order.len() {
623 return Vec::new();
624 }
625
626 let retain = self
627 .policy
628 .get_slots_to_retain(&self.active_order[start_index..]);
629 let mut retained = Vec::new();
630 while self.active_order.len() > start_index {
631 let slot = self.active_order.pop().expect("active_order not empty");
632 if retain.contains(&slot) {
633 retained.push(slot);
634 continue;
635 }
636 self.mapping.deactivate_slot(slot);
637
638 let content_type = self.slot_content_types.get(&slot).copied();
640 if let Some(nodes) = self.mapping.get_nodes(&slot) {
641 for node in nodes {
642 if let Some(ct) = content_type {
643 self.reusable_by_type
644 .entry(ct)
645 .or_default()
646 .push_back((slot, *node));
647 } else {
648 self.reusable_nodes_untyped.push_back((slot, *node));
649 }
650 }
651 }
652 }
653 retained.reverse();
654 self.active_order.extend(retained);
655
656 let mut disposed = Vec::new();
658
659 for pool in self.reusable_by_type.values_mut() {
661 while pool.len() > self.max_reusable_per_type {
662 if let Some((_, node_id)) = pool.pop_front() {
663 self.mapping.remove_by_node(&node_id);
664 disposed.push(node_id);
665 }
666 }
667 }
668
669 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
671 if let Some((_, node_id)) = self.reusable_nodes_untyped.pop_front() {
672 self.mapping.remove_by_node(&node_id);
673 disposed.push(node_id);
674 }
675 }
676
677 self.update_reusable_count();
678 disposed
679 }
680
681 fn prune_inactive_slots(&mut self) {
682 let active: HashSet<SlotId> = self.active_order.iter().copied().collect();
683
684 let mut reusable_slots: HashSet<SlotId> = HashSet::default();
686 for pool in self.reusable_by_type.values() {
687 for (slot, _) in pool {
688 reusable_slots.insert(*slot);
689 }
690 }
691 for (slot, _) in &self.reusable_nodes_untyped {
692 reusable_slots.insert(*slot);
693 }
694
695 let mut keep_slots = active.clone();
696 keep_slots.extend(reusable_slots);
697 self.mapping.retain_slots(&keep_slots);
698
699 self.slot_compositions
703 .retain(|slot, _| keep_slots.contains(slot));
704 self.slot_callbacks
705 .retain(|slot, _| keep_slots.contains(slot));
706
707 self.slot_content_types
709 .retain(|slot, _| keep_slots.contains(slot));
710
711 self.policy.prune_slots(&keep_slots);
713
714 let before_count = self.precomposed_count;
716 let mut removed_from_precomposed = 0usize;
717 self.precomposed_nodes.retain(|slot, nodes| {
718 if active.contains(slot) {
719 true
720 } else {
721 removed_from_precomposed += nodes.len();
722 false
723 }
724 });
725
726 for pool in self.reusable_by_type.values_mut() {
728 pool.retain(|(_, node)| self.mapping.get_slot(node).is_some());
729 }
730 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
732
733 self.reusable_nodes_untyped
735 .retain(|(_, node)| self.mapping.get_slot(node).is_some());
736
737 self.update_reusable_count();
738 self.precomposed_count = before_count.saturating_sub(removed_from_precomposed);
739 }
740
741 pub fn reusable(&self) -> Vec<NodeId> {
743 let mut nodes: Vec<NodeId> = self
744 .reusable_by_type
745 .values()
746 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
747 .collect();
748 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
749 nodes
750 }
751
752 pub fn active_slots_count(&self) -> usize {
757 self.active_order.len()
758 }
759
760 pub fn reusable_slots_count(&self) -> usize {
765 self.reusable_count
766 }
767
768 pub fn invalidate_scopes(&self) {
774 self.mapping.invalidate_scopes();
775 }
776
777 pub fn was_last_slot_reused(&self) -> Option<bool> {
785 self.last_slot_reused
786 }
787
788 #[doc(hidden)]
789 pub fn debug_scope_ids_by_slot(&self) -> Vec<(u64, Vec<usize>)> {
790 self.mapping
791 .slot_to_scopes
792 .iter()
793 .map(|(slot, scopes)| (slot.raw(), scopes.iter().map(RecomposeScope::id).collect()))
794 .collect()
795 }
796
797 #[doc(hidden)]
798 pub fn debug_slot_table_for_slot(&self, slot_id: SlotId) -> Option<Vec<(usize, String)>> {
799 let slots = self.slot_compositions.get(&slot_id)?;
800 Some(slots.borrow().debug_dump_all_slots())
801 }
802
803 #[doc(hidden)]
804 pub fn debug_slot_table_groups_for_slot(&self, slot_id: SlotId) -> Option<Vec<DebugSlotGroup>> {
805 let slots = self.slot_compositions.get(&slot_id)?;
806 Some(slots.borrow().debug_dump_groups())
807 }
808
809 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
811 &self.precomposed_nodes
812 }
813
814 pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
817 let active: HashSet<SlotId> = self.active_order.iter().copied().collect();
818 let mut disposed = Vec::new();
819 let mut empty_slots = Vec::new();
820 for (slot, nodes) in self.precomposed_nodes.iter_mut() {
821 if !active.contains(slot) {
822 disposed.extend(nodes.iter().copied());
823 empty_slots.push(*slot);
824 }
825 }
826 for slot in empty_slots {
827 self.precomposed_nodes.remove(&slot);
828 }
829 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
831 disposed
832 }
833}
834
835#[cfg(test)]
836#[path = "tests/subcompose_tests.rs"]
837mod tests;