Skip to main content

tau_cli_term_raw/
presentation_observation_state.rs

1//! Bounded process-local correlation between opaque presentation facts and
2//! redraws.
3
4use std::collections::VecDeque;
5use std::time::Instant;
6
7use super::RendererDeliveryId;
8use crate::presentation_mutation_generation::PresentationMutationGeneration;
9
10/// Maximum number of exact selected-presentation facts retained for one redraw.
11pub(super) const MAX_PENDING_PRESENTATION_OBSERVATIONS: usize = 64;
12/// Number of caller-defined opaque invalidation keys represented by one mask.
13const PRESENTATION_KIND_BITS: usize = u64::BITS as usize;
14
15/// Validated opaque key used only for caller-owned presentation invalidation.
16#[derive(Clone, Copy, Debug, Eq, PartialEq)]
17pub struct PresentationObservationKey(u8);
18
19impl PresentationObservationKey {
20    /// Creates a key representable by one invalidation mask.
21    pub fn new(value: u8) -> Option<Self> {
22        (usize::from(value) < PRESENTATION_KIND_BITS).then_some(Self(value))
23    }
24}
25
26/// Named invalidation mask for opaque predecessor keys.
27#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
28pub struct PresentationInvalidation(u64);
29
30impl PresentationInvalidation {
31    /// Creates an empty invalidation set.
32    pub const fn none() -> Self {
33        Self(0)
34    }
35
36    /// Adds one opaque predecessor key.
37    pub const fn with(mut self, key: PresentationObservationKey) -> Self {
38        self.0 |= 1_u64 << key.0;
39        self
40    }
41
42    /// Returns whether this invalidation set contains one opaque key.
43    const fn contains(self, key: PresentationObservationKey) -> bool {
44        self.0 & (1_u64 << key.0) != 0
45    }
46}
47
48/// One caller-owned opaque, content-free fact accepted by raw correlation.
49#[derive(Clone, Copy, Debug, Eq, PartialEq)]
50pub struct OpaquePresentationFact {
51    /// Stable content-free operational trace label.
52    label: &'static str,
53    /// Opaque key used only for invalidation.
54    key: PresentationObservationKey,
55    /// Opaque predecessors superseded atomically by this fact.
56    invalidates: PresentationInvalidation,
57}
58
59impl OpaquePresentationFact {
60    /// Creates one validated opaque fact.
61    pub const fn new(
62        label: &'static str,
63        key: PresentationObservationKey,
64        invalidates: PresentationInvalidation,
65    ) -> Self {
66        Self {
67            label,
68            key,
69            invalidates,
70        }
71    }
72}
73
74/// One content-free process-local fact awaiting a successful terminal flush.
75pub(super) struct PresentationObservation {
76    /// Socket delivery identity scoped to this CLI process.
77    pub(super) delivery_id: RendererDeliveryId,
78    /// Caller-owned stable content-free label.
79    pub(super) fact: &'static str,
80    /// Caller-owned validated invalidation key.
81    key: PresentationObservationKey,
82    /// Monotonic selected-presentation generation assigned at registration.
83    pub(super) generation: PresentationMutationGeneration,
84    /// Monotonic time at which the selected handler completed its mutation.
85    pub(super) observed_at: Instant,
86}
87
88/// Observations captured atomically with one prepared redraw.
89pub(super) struct CapturedPresentationObservations {
90    /// Exact retained observations in registration order.
91    pub(super) facts: Vec<PresentationObservation>,
92    /// Number of exact observations omitted by the fixed pending bound.
93    pub(super) omitted: u64,
94    /// Latest selected-presentation generation visible to the prepared frame.
95    pub(super) generation: PresentationMutationGeneration,
96}
97
98/// Count-only overflow retained for one caller-owned opaque key.
99struct OmittedPresentationObservations {
100    /// Validated key shared by all omitted observations in this aggregate.
101    key: PresentationObservationKey,
102    /// Saturating number of omitted observations with this key.
103    count: u64,
104}
105
106impl OmittedPresentationObservations {
107    /// Creates the first omitted observation aggregate for one key.
108    const fn new(key: PresentationObservationKey) -> Self {
109        Self { key, count: 1 }
110    }
111
112    /// Returns whether this aggregate represents one opaque key.
113    fn has_key(&self, key: PresentationObservationKey) -> bool {
114        self.key == key
115    }
116
117    /// Returns whether an invalidation set supersedes this aggregate.
118    const fn is_invalidated_by(&self, invalidates: PresentationInvalidation) -> bool {
119        invalidates.contains(self.key)
120    }
121
122    /// Adds one omitted observation without overflowing the bounded diagnostic.
123    fn increment(&mut self) {
124        self.count = self.count.saturating_add(1);
125    }
126}
127
128/// Coherent bounded opaque correlation state protected by `SharedState`.
129pub(super) struct PresentationObservationState {
130    /// Latest selected-presentation mutation generation.
131    generation: PresentationMutationGeneration,
132    /// Exact observations awaiting capture by a redraw pass.
133    pending: VecDeque<PresentationObservation>,
134    /// Count-only overflow retained independently for each opaque key.
135    omitted_by_key: Vec<OmittedPresentationObservations>,
136    /// Saturating total of all count-only overflow.
137    omitted_total: u64,
138    /// Successful pass receipts retained only for focused unit-test assertions.
139    #[cfg(test)]
140    pub(super) successful_test_passes:
141        Vec<Vec<(RendererDeliveryId, PresentationMutationGeneration)>>,
142}
143
144impl PresentationObservationState {
145    /// Creates empty process-local observation state without heap allocation.
146    pub(super) fn new() -> Self {
147        Self {
148            generation: PresentationMutationGeneration::default(),
149            pending: VecDeque::new(),
150            omitted_by_key: Vec::new(),
151            omitted_total: 0,
152            #[cfg(test)]
153            successful_test_passes: Vec::new(),
154        }
155    }
156
157    /// Returns whether capture can skip all correlation work.
158    pub(super) fn is_empty(&self) -> bool {
159        self.pending.is_empty() && self.omitted_total == 0
160    }
161
162    /// Registers one caller-classified opaque presentation mutation.
163    pub(super) fn register(
164        &mut self,
165        delivery_id: RendererDeliveryId,
166        fact: OpaquePresentationFact,
167        observed_at: Instant,
168    ) {
169        self.pending
170            .retain(|pending| !fact.invalidates.contains(pending.key));
171        self.omitted_by_key.retain(|omitted| {
172            if omitted.is_invalidated_by(fact.invalidates) {
173                self.omitted_total = self.omitted_total.saturating_sub(omitted.count);
174                false
175            } else {
176                true
177            }
178        });
179        self.generation.advance();
180        if self.pending.len() == MAX_PENDING_PRESENTATION_OBSERVATIONS {
181            if let Some(omitted) = self
182                .omitted_by_key
183                .iter_mut()
184                .find(|omitted| omitted.has_key(fact.key))
185            {
186                omitted.increment();
187            } else {
188                self.omitted_by_key
189                    .push(OmittedPresentationObservations::new(fact.key));
190            }
191            self.omitted_total = self.omitted_total.saturating_add(1);
192            return;
193        }
194        self.pending.push_back(PresentationObservation {
195            delivery_id,
196            fact: fact.label,
197            key: fact.key,
198            generation: self.generation,
199            observed_at,
200        });
201    }
202
203    /// Captures and clears all observations represented by a prepared frame.
204    pub(super) fn capture(&mut self) -> CapturedPresentationObservations {
205        self.omitted_by_key.clear();
206        CapturedPresentationObservations {
207            facts: self.pending.drain(..).collect(),
208            omitted: std::mem::take(&mut self.omitted_total),
209            generation: self.generation,
210        }
211    }
212
213    /// Retains one successful pass receipt for focused unit tests.
214    #[cfg(test)]
215    pub(super) fn record_success_for_test(
216        &mut self,
217        observations: &CapturedPresentationObservations,
218    ) {
219        self.successful_test_passes.push(
220            observations
221                .facts
222                .iter()
223                .map(|fact| (fact.delivery_id, fact.generation))
224                .collect(),
225        );
226    }
227}
228
229#[cfg(test)]
230#[path = "presentation_observation_state_tests.rs"]
231mod presentation_observation_state_tests;