Skip to main content

tau_term_screen/
cell_row.rs

1#[cfg(debug_assertions)]
2use std::cell::Cell as Counter;
3use std::ops::Deref;
4use std::sync::Arc;
5
6use crate::Cell;
7
8#[cfg(debug_assertions)]
9thread_local! {
10    static METRICS: Counter<CellRowMetrics> = const { Counter::new(CellRowMetrics::ZERO) };
11}
12
13/// Counts immutable row-buffer ownership work on the current thread.
14///
15/// Counters are available in debug builds, where tests and manual ownership
16/// probes run. Release builds remove instrumentation from the row-clone hot
17/// path.
18#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
19pub struct CellRowMetrics {
20    /// Newly allocated immutable row buffers.
21    pub allocations: usize,
22    /// Cheap clones of an existing row-buffer pointer.
23    pub pointer_clones: usize,
24    /// Cells copied while normalizing caller-constructed rows.
25    pub cell_copies: usize,
26}
27
28impl CellRowMetrics {
29    #[cfg(debug_assertions)]
30    const ZERO: Self = Self {
31        allocations: 0,
32        pointer_clones: 0,
33        cell_copies: 0,
34    };
35}
36
37/// One normalized, immutable, cheaply shared physical terminal row.
38#[derive(Debug, PartialEq, Eq)]
39pub struct CellRow {
40    /// Shared normalized cells for this physical row.
41    cells: CellRowStorage,
42}
43
44/// Allocation-free empty storage or shared nonempty cells.
45#[derive(Debug, PartialEq, Eq)]
46enum CellRowStorage {
47    /// Canonical allocation-free empty row.
48    Empty,
49    /// Shared nonempty row cells.
50    Cells(Arc<Vec<Cell>>),
51}
52
53impl CellRow {
54    /// Normalizes and takes ownership of one physical terminal row.
55    pub fn new(mut cells: Vec<Cell>) -> Self {
56        if cells.is_empty() {
57            return Self {
58                cells: CellRowStorage::Empty,
59            };
60        }
61        let needs_normalization = cells
62            .iter()
63            .any(|cell| Cell::sanitized_char(cell.ch) != cell.ch);
64        if needs_normalization {
65            let copied = cells.len();
66            cells = cells.iter().map(Cell::normalized).collect();
67            record_cell_copies(copied);
68        }
69        record_allocation();
70        Self {
71            cells: CellRowStorage::Cells(Arc::new(cells)),
72        }
73    }
74
75    /// Returns debug-build row-buffer counters for the current thread.
76    ///
77    /// Release builds return `None` because they compile out hot-path
78    /// instrumentation.
79    pub fn metrics() -> Option<CellRowMetrics> {
80        metrics()
81    }
82
83    /// Resets debug-build row-buffer counters for the current thread.
84    ///
85    /// This is a no-op when [`Self::metrics`] returns `None`.
86    pub fn reset_metrics() {
87        reset_metrics();
88    }
89
90    /// Reports whether both rows share storage.
91    ///
92    /// All allocation-free empty rows share the canonical empty storage.
93    pub fn shares_buffer_with(&self, other: &Self) -> bool {
94        match (&self.cells, &other.cells) {
95            (CellRowStorage::Empty, CellRowStorage::Empty) => true,
96            (CellRowStorage::Cells(left), CellRowStorage::Cells(right)) => Arc::ptr_eq(left, right),
97            _ => false,
98        }
99    }
100}
101
102impl Clone for CellRow {
103    fn clone(&self) -> Self {
104        if matches!(self.cells, CellRowStorage::Cells(_)) {
105            record_pointer_clone();
106        }
107        Self {
108            cells: match &self.cells {
109                CellRowStorage::Empty => CellRowStorage::Empty,
110                CellRowStorage::Cells(cells) => CellRowStorage::Cells(Arc::clone(cells)),
111            },
112        }
113    }
114}
115
116impl Deref for CellRow {
117    type Target = [Cell];
118
119    fn deref(&self) -> &Self::Target {
120        match &self.cells {
121            CellRowStorage::Empty => &[],
122            CellRowStorage::Cells(cells) => cells.as_slice(),
123        }
124    }
125}
126
127impl AsRef<[Cell]> for CellRow {
128    fn as_ref(&self) -> &[Cell] {
129        self
130    }
131}
132
133impl From<Vec<Cell>> for CellRow {
134    fn from(cells: Vec<Cell>) -> Self {
135        Self::new(cells)
136    }
137}
138
139impl PartialEq<Vec<Cell>> for CellRow {
140    fn eq(&self, other: &Vec<Cell>) -> bool {
141        self.as_ref() == other.as_slice()
142    }
143}
144
145impl CellRowMetrics {
146    #[cfg(debug_assertions)]
147    fn with_allocation(mut self) -> Self {
148        self.allocations += 1;
149        self
150    }
151
152    #[cfg(debug_assertions)]
153    fn with_pointer_clone(mut self) -> Self {
154        self.pointer_clones += 1;
155        self
156    }
157
158    #[cfg(debug_assertions)]
159    fn with_cell_copies(mut self, count: usize) -> Self {
160        self.cell_copies += count;
161        self
162    }
163}
164
165#[cfg(debug_assertions)]
166fn record_allocation() {
167    METRICS.set(METRICS.get().with_allocation());
168}
169
170#[cfg(not(debug_assertions))]
171fn record_allocation() {}
172
173#[cfg(debug_assertions)]
174fn record_pointer_clone() {
175    METRICS.set(METRICS.get().with_pointer_clone());
176}
177
178#[cfg(not(debug_assertions))]
179fn record_pointer_clone() {}
180
181#[cfg(debug_assertions)]
182fn record_cell_copies(count: usize) {
183    METRICS.set(METRICS.get().with_cell_copies(count));
184}
185
186#[cfg(not(debug_assertions))]
187fn record_cell_copies(_count: usize) {}
188
189#[cfg(debug_assertions)]
190fn metrics() -> Option<CellRowMetrics> {
191    Some(METRICS.get())
192}
193
194#[cfg(not(debug_assertions))]
195fn metrics() -> Option<CellRowMetrics> {
196    None
197}
198
199#[cfg(debug_assertions)]
200fn reset_metrics() {
201    METRICS.set(CellRowMetrics::ZERO);
202}
203
204#[cfg(not(debug_assertions))]
205fn reset_metrics() {}