cranpose_core/subcompose/slot_reuse_policy.rs
1// The policies that decide which subcomposed slots a subcompose state keeps
2// for reuse and which slot's nodes another slot may take.
3
4use super::SlotId;
5use crate::collections::map::HashSet;
6
7/// Policy that decides which previously composed slots should be retained for
8/// potential reuse during the next subcompose pass.
9///
10/// Note: This trait does NOT require Send + Sync because the compose runtime
11/// is single-threaded (uses Rc/RefCell throughout).
12pub trait SlotReusePolicy: 'static {
13 /// Returns the subset of slots that should be retained for reuse after the
14 /// current measurement pass. Slots that are not part of the returned set
15 /// will be disposed. The default retains none.
16 fn get_slots_to_retain(&self, active: &[SlotId]) -> HashSet<SlotId> {
17 let _ = active;
18 HashSet::default()
19 }
20
21 /// Determines whether a node that previously rendered the slot `existing`
22 /// can be reused when the caller requests `requested`. Each slot comes
23 /// with the content type [`SubcomposeState`](super::SubcomposeState) holds for it.
24 ///
25 /// Implementations should document what constitutes compatibility (for
26 /// example, identical slot identifiers, matching layout classes, or node
27 /// types). Returning `true` allows [`SubcomposeState`](super::SubcomposeState) to move the node
28 /// across slots instead of disposing it.
29 fn are_compatible(&self, existing: TypedSlot, requested: TypedSlot) -> bool;
30}
31
32/// A slot with the content type registered for it, if any: what a
33/// [`SlotReusePolicy`] decides reuse by.
34#[derive(Debug, Clone, Copy, PartialEq, Eq)]
35pub struct TypedSlot {
36 /// The slot.
37 pub id: SlotId,
38 /// The content type registered for the slot.
39 pub content_type: Option<u64>,
40}
41
42/// Default reuse policy that mirrors Jetpack Compose behaviour: dispose
43/// everything from the tail so that the next measurement can decide which
44/// content to keep alive. Compatibility defaults to exact slot matches.
45#[derive(Debug, Default)]
46pub struct DefaultSlotReusePolicy;
47
48impl SlotReusePolicy for DefaultSlotReusePolicy {
49 fn are_compatible(&self, existing: TypedSlot, requested: TypedSlot) -> bool {
50 existing.id == requested.id
51 }
52}
53
54/// Reuse policy that allows cross-slot reuse when content types match.
55///
56/// This policy enables efficient recycling of layout nodes across different
57/// slot IDs when they share the same content type (e.g., list items with
58/// similar structure but different data). Two slots without a content type
59/// are compatible too.
60///
61/// # Example
62///
63/// ```
64/// use cranpose_core::{ContentTypeReusePolicy, SlotId, SlotReusePolicy, TypedSlot};
65///
66/// let policy = ContentTypeReusePolicy;
67/// let item = |id, content_type| TypedSlot {
68/// id: SlotId::new(id),
69/// content_type,
70/// };
71///
72/// // Slot 1 can reuse slot 2's node since they share content type 2
73/// assert!(policy.are_compatible(item(2, Some(2)), item(1, Some(2))));
74/// // A header cannot take an item's node
75/// assert!(!policy.are_compatible(item(2, Some(2)), item(0, Some(1))));
76/// ```
77#[derive(Debug, Default)]
78pub struct ContentTypeReusePolicy;
79
80impl SlotReusePolicy for ContentTypeReusePolicy {
81 fn are_compatible(&self, existing: TypedSlot, requested: TypedSlot) -> bool {
82 existing.id == requested.id || existing.content_type == requested.content_type
83 }
84}