Skip to main content

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}