1use crate::collections::map::HashMap; use crate::collections::map::HashSet;
11use std::collections::VecDeque;
12use std::fmt;
13use std::rc::Rc;
14
15use crate::{NodeId, RecomposeScope, SlotTable, SlotsHost};
16
17#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, PartialOrd, Ord)]
22pub struct SlotId(pub u64);
23
24impl SlotId {
25 #[inline]
26 pub fn new(raw: u64) -> Self {
27 Self(raw)
28 }
29
30 #[inline]
31 pub fn raw(self) -> u64 {
32 self.0
33 }
34}
35
36pub trait SlotReusePolicy: 'static {
42 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId>;
46
47 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool;
55
56 fn register_content_type(&self, _slot_id: SlotId, _content_type: u64) {
63 }
65
66 fn remove_content_type(&self, _slot_id: SlotId) {
71 }
73
74 fn prune_slots(&self, _keep_slots: &HashSet<SlotId>) {
80 }
82}
83
84#[derive(Debug, Default)]
88pub struct DefaultSlotReusePolicy;
89
90impl SlotReusePolicy for DefaultSlotReusePolicy {
91 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
92 let _ = active;
93 HashSet::default()
94 }
95
96 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
97 existing == requested
98 }
99}
100
101pub struct ContentTypeReusePolicy {
123 slot_types: std::cell::RefCell<HashMap<SlotId, u64>>,
125}
126
127impl std::fmt::Debug for ContentTypeReusePolicy {
128 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
129 let types = self.slot_types.borrow();
130 f.debug_struct("ContentTypeReusePolicy")
131 .field("slot_types", &*types)
132 .finish()
133 }
134}
135
136impl Default for ContentTypeReusePolicy {
137 fn default() -> Self {
138 Self::new()
139 }
140}
141
142impl ContentTypeReusePolicy {
143 pub fn new() -> Self {
145 Self {
146 slot_types: std::cell::RefCell::new(HashMap::default()),
147 }
148 }
149
150 pub fn set_content_type(&self, slot: SlotId, content_type: u64) {
154 self.slot_types.borrow_mut().insert(slot, content_type);
155 }
156
157 pub fn remove_content_type(&self, slot: SlotId) {
159 self.slot_types.borrow_mut().remove(&slot);
160 }
161
162 pub fn clear(&self) {
164 self.slot_types.borrow_mut().clear();
165 }
166
167 pub fn get_content_type(&self, slot: SlotId) -> Option<u64> {
169 self.slot_types.borrow().get(&slot).copied()
170 }
171}
172
173impl SlotReusePolicy for ContentTypeReusePolicy {
174 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
175 let _ = active;
176 HashSet::default()
178 }
179
180 fn are_compatible(&self, existing: SlotId, requested: SlotId) -> bool {
181 if existing == requested {
183 return true;
184 }
185
186 let types = self.slot_types.borrow();
188 match (types.get(&existing), types.get(&requested)) {
189 (Some(existing_type), Some(requested_type)) => existing_type == requested_type,
190 _ => false,
192 }
193 }
194
195 fn register_content_type(&self, slot_id: SlotId, content_type: u64) {
196 self.set_content_type(slot_id, content_type);
197 }
198
199 fn remove_content_type(&self, slot_id: SlotId) {
200 ContentTypeReusePolicy::remove_content_type(self, slot_id);
201 }
202
203 fn prune_slots(&self, keep_slots: &HashSet<SlotId>) {
204 self.slot_types
205 .borrow_mut()
206 .retain(|slot, _| keep_slots.contains(slot));
207 }
208}
209
210#[derive(Default, Clone)]
211
212struct NodeSlotMapping {
213 slot_to_nodes: HashMap<SlotId, Vec<NodeId>>, node_to_slot: HashMap<NodeId, SlotId>, slot_to_scopes: HashMap<SlotId, Vec<RecomposeScope>>, }
217
218impl fmt::Debug for NodeSlotMapping {
219 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
220 f.debug_struct("NodeSlotMapping")
221 .field("slot_to_nodes", &self.slot_to_nodes)
222 .field("node_to_slot", &self.node_to_slot)
223 .finish()
224 }
225}
226
227impl NodeSlotMapping {
228 fn set_nodes(&mut self, slot: SlotId, nodes: &[NodeId]) {
229 self.slot_to_nodes.insert(slot, nodes.to_vec());
230 for node in nodes {
231 self.node_to_slot.insert(*node, slot);
232 }
233 }
234
235 fn set_scopes(&mut self, slot: SlotId, scopes: &[RecomposeScope]) {
236 self.slot_to_scopes.insert(slot, scopes.to_vec());
237 }
238
239 fn add_node(&mut self, slot: SlotId, node: NodeId) {
240 self.slot_to_nodes.entry(slot).or_default().push(node);
241 self.node_to_slot.insert(node, slot);
242 }
243
244 fn remove_by_node(&mut self, node: &NodeId) -> Option<SlotId> {
245 if let Some(slot) = self.node_to_slot.remove(node) {
246 if let Some(nodes) = self.slot_to_nodes.get_mut(&slot) {
247 if let Some(index) = nodes.iter().position(|candidate| candidate == node) {
248 nodes.remove(index);
249 }
250 if nodes.is_empty() {
251 self.slot_to_nodes.remove(&slot);
252 self.slot_to_scopes.remove(&slot);
254 }
255 }
256 Some(slot)
257 } else {
258 None
259 }
260 }
261
262 fn get_nodes(&self, slot: &SlotId) -> Option<&[NodeId]> {
263 self.slot_to_nodes.get(slot).map(|nodes| nodes.as_slice())
264 }
265
266 fn get_slot(&self, node: &NodeId) -> Option<SlotId> {
267 self.node_to_slot.get(node).copied()
268 }
269
270 fn deactivate_slot(&self, slot: SlotId) {
271 if let Some(scopes) = self.slot_to_scopes.get(&slot) {
272 for scope in scopes {
273 scope.deactivate();
274 }
275 }
276 }
277
278 fn retain_slots(&mut self, active: &HashSet<SlotId>) -> Vec<NodeId> {
279 let mut removed_nodes = Vec::new();
280 self.slot_to_nodes.retain(|slot, nodes| {
281 if active.contains(slot) {
282 true
283 } else {
284 removed_nodes.extend(nodes.iter().copied());
285 false
286 }
287 });
288 self.slot_to_scopes.retain(|slot, _| active.contains(slot));
289 for node in &removed_nodes {
290 self.node_to_slot.remove(node);
291 }
292 removed_nodes
293 }
294}
295
296pub struct SubcomposeState {
299 mapping: NodeSlotMapping,
300 active_order: Vec<SlotId>, reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
305 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
307 slot_content_types: HashMap<SlotId, u64>,
309 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>, policy: Box<dyn SlotReusePolicy>,
311 pub(crate) current_index: usize,
312 pub(crate) reusable_count: usize,
313 pub(crate) precomposed_count: usize,
314 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
318 max_reusable_per_type: usize,
320 max_reusable_untyped: usize,
322 last_slot_reused: Option<bool>,
325}
326
327impl fmt::Debug for SubcomposeState {
328 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
329 f.debug_struct("SubcomposeState")
330 .field("mapping", &self.mapping)
331 .field("active_order", &self.active_order)
332 .field("reusable_by_type_count", &self.reusable_by_type.len())
333 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
334 .field("precomposed_nodes", &self.precomposed_nodes)
335 .field("current_index", &self.current_index)
336 .field("reusable_count", &self.reusable_count)
337 .field("precomposed_count", &self.precomposed_count)
338 .field("slot_compositions_count", &self.slot_compositions.len())
339 .finish()
340 }
341}
342
343impl Default for SubcomposeState {
344 fn default() -> Self {
345 Self::new(Box::new(DefaultSlotReusePolicy))
346 }
347}
348
349const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
352
353const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
357
358impl SubcomposeState {
359 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
361 Self {
362 mapping: NodeSlotMapping::default(),
363 active_order: Vec::new(),
364 reusable_by_type: HashMap::default(),
365 reusable_nodes_untyped: VecDeque::new(),
366 slot_content_types: HashMap::default(),
367 precomposed_nodes: HashMap::default(), policy,
369 current_index: 0,
370 reusable_count: 0,
371 precomposed_count: 0,
372 slot_compositions: HashMap::default(),
373 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
374 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
375 last_slot_reused: None,
376 }
377 }
378
379 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
381 self.policy = policy;
382 }
383
384 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
391 self.slot_content_types.insert(slot_id, content_type);
392 self.policy.register_content_type(slot_id, content_type);
393 }
394
395 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
401 match content_type {
402 Some(ct) => self.register_content_type(slot_id, ct),
403 None => {
404 self.slot_content_types.remove(&slot_id);
405 self.policy.remove_content_type(slot_id);
406 }
407 }
408 }
409
410 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
412 self.slot_content_types.get(&slot_id).copied()
413 }
414
415 pub fn begin_pass(&mut self) {
420 self.current_index = 0;
421 }
422
423 pub fn finish_pass(&mut self) -> Vec<NodeId> {
425 let disposed = self.dispose_or_reuse_starting_from_index(self.current_index);
426 self.prune_inactive_slots();
427 disposed
428 }
429
430 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
434 Rc::clone(self.slot_compositions.entry(slot_id).or_insert_with(|| {
435 Rc::new(SlotsHost::new(crate::slot_backend::SlotBackend::Baseline(
436 SlotTable::new(),
437 )))
438 }))
439 }
440
441 pub fn register_active(
444 &mut self,
445 slot_id: SlotId,
446 node_ids: &[NodeId],
447 scopes: &[RecomposeScope],
448 ) {
449 let was_reused =
451 self.mapping.get_nodes(&slot_id).is_some() || self.active_order.contains(&slot_id);
452 self.last_slot_reused = Some(was_reused);
453
454 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
455 if position < self.current_index {
456 for scope in scopes {
457 scope.reactivate();
458 }
459 self.mapping.set_nodes(slot_id, node_ids);
460 self.mapping.set_scopes(slot_id, scopes);
461 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
462 let before_len = nodes.len();
463 nodes.retain(|node| !node_ids.contains(node));
464 let removed = before_len - nodes.len();
465 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
466 if nodes.is_empty() {
467 self.precomposed_nodes.remove(&slot_id);
468 }
469 }
470 return;
471 }
472 self.active_order.remove(position);
473 }
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 let insert_at = self.current_index.min(self.active_order.len());
489 self.active_order.insert(insert_at, slot_id);
490 self.current_index += 1;
491 }
492
493 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
496 self.precomposed_nodes
497 .entry(slot_id)
498 .or_default()
499 .push(node_id);
500 self.precomposed_count += 1;
501 }
502
503 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<NodeId> {
511 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
517 let first_node = nodes.first().copied();
518 if let Some(node_id) = first_node {
519 let _ = self.remove_from_reusable_pools(node_id);
521 self.update_reusable_count();
522 return Some(node_id);
523 }
524 }
525
526 let content_type = self.slot_content_types.get(&slot_id).copied();
528
529 if let Some(ct) = content_type {
531 if let Some(pool) = self.reusable_by_type.get_mut(&ct) {
532 if let Some((old_slot, node_id)) = pool.pop_front() {
533 self.migrate_node_to_slot(node_id, old_slot, slot_id);
534 self.update_reusable_count();
535 return Some(node_id);
536 }
537 }
538 }
539
540 let position = self
542 .reusable_nodes_untyped
543 .iter()
544 .position(|(existing_slot, _)| self.policy.are_compatible(*existing_slot, slot_id));
545
546 if let Some(index) = position {
547 if let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index) {
548 self.migrate_node_to_slot(node_id, old_slot, slot_id);
549 self.update_reusable_count();
550 return Some(node_id);
551 }
552 }
553
554 None
555 }
556
557 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> bool {
559 for pool in self.reusable_by_type.values_mut() {
561 if let Some(pos) = pool.iter().position(|(_, n)| *n == node_id) {
562 pool.remove(pos);
563 return true;
564 }
565 }
566 if let Some(pos) = self
568 .reusable_nodes_untyped
569 .iter()
570 .position(|(_, n)| *n == node_id)
571 {
572 self.reusable_nodes_untyped.remove(pos);
573 return true;
574 }
575 false
576 }
577
578 fn migrate_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
580 self.mapping.remove_by_node(&node_id);
581 self.mapping.add_node(new_slot, node_id);
582 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
583 nodes.retain(|candidate| *candidate != node_id);
584 if nodes.is_empty() {
585 self.precomposed_nodes.remove(&old_slot);
586 }
587 }
588 }
589
590 fn update_reusable_count(&mut self) {
592 self.reusable_count = self
593 .reusable_by_type
594 .values()
595 .map(|p| p.len())
596 .sum::<usize>()
597 + self.reusable_nodes_untyped.len();
598 }
599
600 pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
604 if start_index >= self.active_order.len() {
606 return Vec::new();
607 }
608
609 let retain = self
610 .policy
611 .get_slots_to_retain(&self.active_order[start_index..]);
612 let mut retained = Vec::new();
613 while self.active_order.len() > start_index {
614 let slot = self.active_order.pop().expect("active_order not empty");
615 if retain.contains(&slot) {
616 retained.push(slot);
617 continue;
618 }
619 self.mapping.deactivate_slot(slot);
620
621 let content_type = self.slot_content_types.get(&slot).copied();
623 if let Some(nodes) = self.mapping.get_nodes(&slot) {
624 for node in nodes {
625 if let Some(ct) = content_type {
626 self.reusable_by_type
627 .entry(ct)
628 .or_default()
629 .push_back((slot, *node));
630 } else {
631 self.reusable_nodes_untyped.push_back((slot, *node));
632 }
633 }
634 }
635 }
636 retained.reverse();
637 self.active_order.extend(retained);
638
639 let mut disposed = Vec::new();
641
642 for pool in self.reusable_by_type.values_mut() {
644 while pool.len() > self.max_reusable_per_type {
645 if let Some((_, node_id)) = pool.pop_front() {
646 self.mapping.remove_by_node(&node_id);
647 disposed.push(node_id);
648 }
649 }
650 }
651
652 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
654 if let Some((_, node_id)) = self.reusable_nodes_untyped.pop_front() {
655 self.mapping.remove_by_node(&node_id);
656 disposed.push(node_id);
657 }
658 }
659
660 self.update_reusable_count();
661 disposed
662 }
663
664 fn prune_inactive_slots(&mut self) {
665 let active: HashSet<SlotId> = self.active_order.iter().copied().collect();
666
667 let mut reusable_slots: HashSet<SlotId> = HashSet::default();
669 for pool in self.reusable_by_type.values() {
670 for (slot, _) in pool {
671 reusable_slots.insert(*slot);
672 }
673 }
674 for (slot, _) in &self.reusable_nodes_untyped {
675 reusable_slots.insert(*slot);
676 }
677
678 let mut keep_slots = active.clone();
679 keep_slots.extend(reusable_slots);
680 self.mapping.retain_slots(&keep_slots);
681
682 self.slot_compositions
686 .retain(|slot, _| keep_slots.contains(slot));
687
688 self.slot_content_types
690 .retain(|slot, _| keep_slots.contains(slot));
691
692 self.policy.prune_slots(&keep_slots);
694
695 let before_count = self.precomposed_count;
697 let mut removed_from_precomposed = 0usize;
698 self.precomposed_nodes.retain(|slot, nodes| {
699 if active.contains(slot) {
700 true
701 } else {
702 removed_from_precomposed += nodes.len();
703 false
704 }
705 });
706
707 for pool in self.reusable_by_type.values_mut() {
709 pool.retain(|(_, node)| self.mapping.get_slot(node).is_some());
710 }
711 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
713
714 self.reusable_nodes_untyped
716 .retain(|(_, node)| self.mapping.get_slot(node).is_some());
717
718 self.update_reusable_count();
719 self.precomposed_count = before_count.saturating_sub(removed_from_precomposed);
720 }
721
722 pub fn reusable(&self) -> Vec<NodeId> {
724 let mut nodes: Vec<NodeId> = self
725 .reusable_by_type
726 .values()
727 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
728 .collect();
729 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
730 nodes
731 }
732
733 pub fn active_slots_count(&self) -> usize {
738 self.active_order.len()
739 }
740
741 pub fn reusable_slots_count(&self) -> usize {
746 self.reusable_count
747 }
748
749 pub fn was_last_slot_reused(&self) -> Option<bool> {
757 self.last_slot_reused
758 }
759
760 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
762 &self.precomposed_nodes
764 }
765
766 pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
769 let active: HashSet<SlotId> = self.active_order.iter().copied().collect();
771 let mut disposed = Vec::new();
772 let mut empty_slots = Vec::new();
773 for (slot, nodes) in self.precomposed_nodes.iter_mut() {
774 if !active.contains(slot) {
775 disposed.extend(nodes.iter().copied());
776 empty_slots.push(*slot);
777 }
778 }
779 for slot in empty_slots {
780 self.precomposed_nodes.remove(&slot);
781 }
782 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
784 disposed
785 }
786}
787
788#[cfg(test)]
789#[path = "tests/subcompose_tests.rs"]
790mod tests;