1use crate::collections::map::HashMap; use crate::collections::map::HashSet;
11use std::collections::VecDeque;
12use std::fmt;
13use std::rc::Rc;
14
15use crate::{CallbackHolder, 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 invalidate_scopes(&self) {
279 for scopes in self.slot_to_scopes.values() {
280 for scope in scopes {
281 scope.invalidate();
282 }
283 }
284 }
285
286 fn retain_slots(&mut self, active: &HashSet<SlotId>) -> Vec<NodeId> {
287 let mut removed_nodes = Vec::new();
288 self.slot_to_nodes.retain(|slot, nodes| {
289 if active.contains(slot) {
290 true
291 } else {
292 removed_nodes.extend(nodes.iter().copied());
293 false
294 }
295 });
296 self.slot_to_scopes.retain(|slot, _| active.contains(slot));
297 for node in &removed_nodes {
298 self.node_to_slot.remove(node);
299 }
300 removed_nodes
301 }
302}
303
304pub struct SubcomposeState {
307 mapping: NodeSlotMapping,
308 active_order: Vec<SlotId>, reusable_by_type: HashMap<u64, VecDeque<(SlotId, NodeId)>>,
313 reusable_nodes_untyped: VecDeque<(SlotId, NodeId)>,
315 slot_content_types: HashMap<SlotId, u64>,
317 precomposed_nodes: HashMap<SlotId, Vec<NodeId>>, policy: Box<dyn SlotReusePolicy>,
319 pub(crate) current_index: usize,
320 pub(crate) reusable_count: usize,
321 pub(crate) precomposed_count: usize,
322 slot_compositions: HashMap<SlotId, Rc<SlotsHost>>,
326 slot_callbacks: HashMap<SlotId, CallbackHolder>,
328 max_reusable_per_type: usize,
330 max_reusable_untyped: usize,
332 last_slot_reused: Option<bool>,
335}
336
337impl fmt::Debug for SubcomposeState {
338 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
339 f.debug_struct("SubcomposeState")
340 .field("mapping", &self.mapping)
341 .field("active_order", &self.active_order)
342 .field("reusable_by_type_count", &self.reusable_by_type.len())
343 .field("reusable_untyped_count", &self.reusable_nodes_untyped.len())
344 .field("precomposed_nodes", &self.precomposed_nodes)
345 .field("current_index", &self.current_index)
346 .field("reusable_count", &self.reusable_count)
347 .field("precomposed_count", &self.precomposed_count)
348 .field("slot_compositions_count", &self.slot_compositions.len())
349 .finish()
350 }
351}
352
353impl Default for SubcomposeState {
354 fn default() -> Self {
355 Self::new(Box::new(DefaultSlotReusePolicy))
356 }
357}
358
359const DEFAULT_MAX_REUSABLE_PER_TYPE: usize = 5;
362
363const DEFAULT_MAX_REUSABLE_UNTYPED: usize = 10;
367
368impl SubcomposeState {
369 pub fn new(policy: Box<dyn SlotReusePolicy>) -> Self {
371 Self {
372 mapping: NodeSlotMapping::default(),
373 active_order: Vec::new(),
374 reusable_by_type: HashMap::default(),
375 reusable_nodes_untyped: VecDeque::new(),
376 slot_content_types: HashMap::default(),
377 precomposed_nodes: HashMap::default(), policy,
379 current_index: 0,
380 reusable_count: 0,
381 precomposed_count: 0,
382 slot_compositions: HashMap::default(),
383 slot_callbacks: HashMap::default(),
384 max_reusable_per_type: DEFAULT_MAX_REUSABLE_PER_TYPE,
385 max_reusable_untyped: DEFAULT_MAX_REUSABLE_UNTYPED,
386 last_slot_reused: None,
387 }
388 }
389
390 pub fn set_policy(&mut self, policy: Box<dyn SlotReusePolicy>) {
392 self.policy = policy;
393 }
394
395 pub fn register_content_type(&mut self, slot_id: SlotId, content_type: u64) {
402 self.slot_content_types.insert(slot_id, content_type);
403 self.policy.register_content_type(slot_id, content_type);
404 }
405
406 pub fn update_content_type(&mut self, slot_id: SlotId, content_type: Option<u64>) {
412 match content_type {
413 Some(ct) => self.register_content_type(slot_id, ct),
414 None => {
415 self.slot_content_types.remove(&slot_id);
416 self.policy.remove_content_type(slot_id);
417 }
418 }
419 }
420
421 pub fn get_content_type(&self, slot_id: SlotId) -> Option<u64> {
423 self.slot_content_types.get(&slot_id).copied()
424 }
425
426 pub fn begin_pass(&mut self) {
431 self.current_index = 0;
432 }
433
434 pub fn finish_pass(&mut self) -> Vec<NodeId> {
436 let disposed = self.dispose_or_reuse_starting_from_index(self.current_index);
437 self.prune_inactive_slots();
438 disposed
439 }
440
441 pub fn get_or_create_slots(&mut self, slot_id: SlotId) -> Rc<SlotsHost> {
445 Rc::clone(self.slot_compositions.entry(slot_id).or_insert_with(|| {
446 Rc::new(SlotsHost::new(crate::slot_backend::SlotBackend::Baseline(
447 SlotTable::new(),
448 )))
449 }))
450 }
451
452 pub fn callback_holder(&mut self, slot_id: SlotId) -> CallbackHolder {
454 self.slot_callbacks.entry(slot_id).or_default().clone()
455 }
456
457 pub fn register_active(
460 &mut self,
461 slot_id: SlotId,
462 node_ids: &[NodeId],
463 scopes: &[RecomposeScope],
464 ) {
465 let was_reused =
467 self.mapping.get_nodes(&slot_id).is_some() || self.active_order.contains(&slot_id);
468 self.last_slot_reused = Some(was_reused);
469
470 if let Some(position) = self.active_order.iter().position(|slot| *slot == slot_id) {
471 if position < self.current_index {
472 for scope in scopes {
473 scope.reactivate();
474 }
475 self.mapping.set_nodes(slot_id, node_ids);
476 self.mapping.set_scopes(slot_id, scopes);
477 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
478 let before_len = nodes.len();
479 nodes.retain(|node| !node_ids.contains(node));
480 let removed = before_len - nodes.len();
481 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
482 if nodes.is_empty() {
483 self.precomposed_nodes.remove(&slot_id);
484 }
485 }
486 return;
487 }
488 self.active_order.remove(position);
489 }
490 for scope in scopes {
491 scope.reactivate();
492 }
493 self.mapping.set_nodes(slot_id, node_ids);
494 self.mapping.set_scopes(slot_id, scopes);
495 if let Some(nodes) = self.precomposed_nodes.get_mut(&slot_id) {
496 let before_len = nodes.len();
497 nodes.retain(|node| !node_ids.contains(node));
498 let removed = before_len - nodes.len();
499 self.precomposed_count = self.precomposed_count.saturating_sub(removed);
500 if nodes.is_empty() {
501 self.precomposed_nodes.remove(&slot_id);
502 }
503 }
504 let insert_at = self.current_index.min(self.active_order.len());
505 self.active_order.insert(insert_at, slot_id);
506 self.current_index += 1;
507 }
508
509 pub fn register_precomposed(&mut self, slot_id: SlotId, node_id: NodeId) {
512 self.precomposed_nodes
513 .entry(slot_id)
514 .or_default()
515 .push(node_id);
516 self.precomposed_count += 1;
517 }
518
519 pub fn take_node_from_reusables(&mut self, slot_id: SlotId) -> Option<NodeId> {
527 if let Some(nodes) = self.mapping.get_nodes(&slot_id) {
533 let first_node = nodes.first().copied();
534 if let Some(node_id) = first_node {
535 let _ = self.remove_from_reusable_pools(node_id);
537 self.update_reusable_count();
538 return Some(node_id);
539 }
540 }
541
542 let content_type = self.slot_content_types.get(&slot_id).copied();
544
545 if let Some(ct) = content_type {
547 if let Some(pool) = self.reusable_by_type.get_mut(&ct) {
548 if let Some((old_slot, node_id)) = pool.pop_front() {
549 self.migrate_node_to_slot(node_id, old_slot, slot_id);
550 self.update_reusable_count();
551 return Some(node_id);
552 }
553 }
554 }
555
556 let position = self
558 .reusable_nodes_untyped
559 .iter()
560 .position(|(existing_slot, _)| self.policy.are_compatible(*existing_slot, slot_id));
561
562 if let Some(index) = position {
563 if let Some((old_slot, node_id)) = self.reusable_nodes_untyped.remove(index) {
564 self.migrate_node_to_slot(node_id, old_slot, slot_id);
565 self.update_reusable_count();
566 return Some(node_id);
567 }
568 }
569
570 None
571 }
572
573 fn remove_from_reusable_pools(&mut self, node_id: NodeId) -> bool {
575 for pool in self.reusable_by_type.values_mut() {
577 if let Some(pos) = pool.iter().position(|(_, n)| *n == node_id) {
578 pool.remove(pos);
579 return true;
580 }
581 }
582 if let Some(pos) = self
584 .reusable_nodes_untyped
585 .iter()
586 .position(|(_, n)| *n == node_id)
587 {
588 self.reusable_nodes_untyped.remove(pos);
589 return true;
590 }
591 false
592 }
593
594 fn migrate_node_to_slot(&mut self, node_id: NodeId, old_slot: SlotId, new_slot: SlotId) {
596 self.mapping.remove_by_node(&node_id);
597 self.mapping.add_node(new_slot, node_id);
598 if let Some(nodes) = self.precomposed_nodes.get_mut(&old_slot) {
599 nodes.retain(|candidate| *candidate != node_id);
600 if nodes.is_empty() {
601 self.precomposed_nodes.remove(&old_slot);
602 }
603 }
604 }
605
606 fn update_reusable_count(&mut self) {
608 self.reusable_count = self
609 .reusable_by_type
610 .values()
611 .map(|p| p.len())
612 .sum::<usize>()
613 + self.reusable_nodes_untyped.len();
614 }
615
616 pub fn dispose_or_reuse_starting_from_index(&mut self, start_index: usize) -> Vec<NodeId> {
620 if start_index >= self.active_order.len() {
622 return Vec::new();
623 }
624
625 let retain = self
626 .policy
627 .get_slots_to_retain(&self.active_order[start_index..]);
628 let mut retained = Vec::new();
629 while self.active_order.len() > start_index {
630 let slot = self.active_order.pop().expect("active_order not empty");
631 if retain.contains(&slot) {
632 retained.push(slot);
633 continue;
634 }
635 self.mapping.deactivate_slot(slot);
636
637 let content_type = self.slot_content_types.get(&slot).copied();
639 if let Some(nodes) = self.mapping.get_nodes(&slot) {
640 for node in nodes {
641 if let Some(ct) = content_type {
642 self.reusable_by_type
643 .entry(ct)
644 .or_default()
645 .push_back((slot, *node));
646 } else {
647 self.reusable_nodes_untyped.push_back((slot, *node));
648 }
649 }
650 }
651 }
652 retained.reverse();
653 self.active_order.extend(retained);
654
655 let mut disposed = Vec::new();
657
658 for pool in self.reusable_by_type.values_mut() {
660 while pool.len() > self.max_reusable_per_type {
661 if let Some((_, node_id)) = pool.pop_front() {
662 self.mapping.remove_by_node(&node_id);
663 disposed.push(node_id);
664 }
665 }
666 }
667
668 while self.reusable_nodes_untyped.len() > self.max_reusable_untyped {
670 if let Some((_, node_id)) = self.reusable_nodes_untyped.pop_front() {
671 self.mapping.remove_by_node(&node_id);
672 disposed.push(node_id);
673 }
674 }
675
676 self.update_reusable_count();
677 disposed
678 }
679
680 fn prune_inactive_slots(&mut self) {
681 let active: HashSet<SlotId> = self.active_order.iter().copied().collect();
682
683 let mut reusable_slots: HashSet<SlotId> = HashSet::default();
685 for pool in self.reusable_by_type.values() {
686 for (slot, _) in pool {
687 reusable_slots.insert(*slot);
688 }
689 }
690 for (slot, _) in &self.reusable_nodes_untyped {
691 reusable_slots.insert(*slot);
692 }
693
694 let mut keep_slots = active.clone();
695 keep_slots.extend(reusable_slots);
696 self.mapping.retain_slots(&keep_slots);
697
698 self.slot_compositions
702 .retain(|slot, _| keep_slots.contains(slot));
703 self.slot_callbacks
704 .retain(|slot, _| keep_slots.contains(slot));
705
706 self.slot_content_types
708 .retain(|slot, _| keep_slots.contains(slot));
709
710 self.policy.prune_slots(&keep_slots);
712
713 let before_count = self.precomposed_count;
715 let mut removed_from_precomposed = 0usize;
716 self.precomposed_nodes.retain(|slot, nodes| {
717 if active.contains(slot) {
718 true
719 } else {
720 removed_from_precomposed += nodes.len();
721 false
722 }
723 });
724
725 for pool in self.reusable_by_type.values_mut() {
727 pool.retain(|(_, node)| self.mapping.get_slot(node).is_some());
728 }
729 self.reusable_by_type.retain(|_, pool| !pool.is_empty());
731
732 self.reusable_nodes_untyped
734 .retain(|(_, node)| self.mapping.get_slot(node).is_some());
735
736 self.update_reusable_count();
737 self.precomposed_count = before_count.saturating_sub(removed_from_precomposed);
738 }
739
740 pub fn reusable(&self) -> Vec<NodeId> {
742 let mut nodes: Vec<NodeId> = self
743 .reusable_by_type
744 .values()
745 .flat_map(|pool| pool.iter().map(|(_, n)| *n))
746 .collect();
747 nodes.extend(self.reusable_nodes_untyped.iter().map(|(_, n)| *n));
748 nodes
749 }
750
751 pub fn active_slots_count(&self) -> usize {
756 self.active_order.len()
757 }
758
759 pub fn reusable_slots_count(&self) -> usize {
764 self.reusable_count
765 }
766
767 pub fn invalidate_scopes(&self) {
773 self.mapping.invalidate_scopes();
774 }
775
776 pub fn was_last_slot_reused(&self) -> Option<bool> {
784 self.last_slot_reused
785 }
786
787 pub fn precomposed(&self) -> &HashMap<SlotId, Vec<NodeId>> {
789 &self.precomposed_nodes
791 }
792
793 pub fn drain_inactive_precomposed(&mut self) -> Vec<NodeId> {
796 let active: HashSet<SlotId> = self.active_order.iter().copied().collect();
798 let mut disposed = Vec::new();
799 let mut empty_slots = Vec::new();
800 for (slot, nodes) in self.precomposed_nodes.iter_mut() {
801 if !active.contains(slot) {
802 disposed.extend(nodes.iter().copied());
803 empty_slots.push(*slot);
804 }
805 }
806 for slot in empty_slots {
807 self.precomposed_nodes.remove(&slot);
808 }
809 self.precomposed_count = self.precomposed_count.saturating_sub(disposed.len());
811 disposed
812 }
813}
814
815#[cfg(test)]
816#[path = "tests/subcompose_tests.rs"]
817mod tests;