Skip to main content

concinnity_core/ecs/
column.rs

1// A typed component column: the per-type storage primitive the closed-world
2// ComponentStorage is built from. It bundles the component data with a
3// row-aligned Entity id per row, a row-aligned change tick, and column-level
4// tick stamps. All structural edits go through helpers that keep the data, id,
5// and tick vectors the same length (checked with a debug assertion).
6//
7// Column derefs to its data slice, so read paths (iteration, indexing) behave
8// like a plain Vec. Whole-column mutable access goes through `values_mut`,
9// which stamps the bulk tick because any element may be written; a write aimed
10// at one row goes through `value_mut`, which stamps only that row, so a
11// consumer can recover exactly which entities were touched.
12
13use alloc::vec::Vec;
14
15use core::ops::Deref;
16
17use crate::ecs::entity::Entity;
18use crate::ecs::tick::Tick;
19
20/// The tick stamps a column keeps. `changed` is the maximum over every kind of
21/// write and drives whole-column change detection. `added` marks the last
22/// appended row. `bulk` marks the last whole-column mutable access, after which
23/// every row must be assumed written. `structural` marks the last row add or
24/// removal, after which row positions and membership have moved. A consumer
25/// that tracks rows individually reads `bulk` and `structural` to decide whether
26/// the per-row stamps alone still describe what changed.
27#[derive(Clone, Copy, PartialEq, Eq, Debug, Default)]
28pub struct ColumnTicks {
29    /// Maximum over every kind of write.
30    pub changed: Tick,
31    /// Last appended row.
32    pub added: Tick,
33    /// Last whole-column mutable access.
34    pub bulk: Tick,
35    /// Last row add or removal.
36    pub structural: Tick,
37}
38
39#[derive(Debug)]
40/// A dense column of one component type, with its per-row change stamps.
41pub struct Column<T> {
42    data: Vec<T>,
43    entities: Vec<Entity>,
44    row_changed: Vec<Tick>,
45    changed: Tick,
46    added: Tick,
47    bulk: Tick,
48    structural: Tick,
49}
50
51impl<T> Default for Column<T> {
52    fn default() -> Column<T> {
53        Column {
54            data: Vec::new(),
55            entities: Vec::new(),
56            row_changed: Vec::new(),
57            changed: Tick::ZERO,
58            added: Tick::ZERO,
59            bulk: Tick::ZERO,
60            structural: Tick::ZERO,
61        }
62    }
63}
64
65impl<T> Column<T> {
66    /// An empty column.
67    pub fn new() -> Column<T> {
68        Column::default()
69    }
70
71    /// The Entity owning each row, aligned with the data slice.
72    pub fn entities(&self) -> &[Entity] {
73        &self.entities
74    }
75
76    /// The column's coarse change tick.
77    pub fn changed_tick(&self) -> Tick {
78        self.changed
79    }
80
81    #[cfg(test)]
82    pub(crate) fn added_tick(&self) -> Tick {
83        self.added
84    }
85
86    /// Every tick stamp at once, for a consumer that needs more than the coarse
87    /// change tick to decide how much of the column to re-examine.
88    pub fn ticks(&self) -> ColumnTicks {
89        ColumnTicks {
90            changed: self.changed,
91            added: self.added,
92            bulk: self.bulk,
93            structural: self.structural,
94        }
95    }
96
97    // The change tick of each row, aligned with the data and entity slices.
98    #[cfg(test)]
99    pub(crate) fn row_ticks(&self) -> &[Tick] {
100        &self.row_changed
101    }
102
103    /// Pre-allocate capacity for `additional` more rows (data + entity ids),
104    /// ahead of a bulk load.
105    pub fn reserve(&mut self, additional: usize) {
106        self.data.reserve(additional);
107        self.entities.reserve(additional);
108        self.row_changed.reserve(additional);
109    }
110
111    /// Rows the column can hold without reallocating.
112    pub fn capacity(&self) -> usize {
113        self.data.capacity()
114    }
115
116    /// Append a row. Stamps every tick: the row is newly added, the column grew,
117    /// and the new row is (trivially) changed this tick.
118    pub fn push(&mut self, entity: Entity, value: T, tick: Tick) {
119        self.data.push(value);
120        self.entities.push(entity);
121        self.row_changed.push(tick);
122        self.added = tick;
123        self.changed = tick;
124        self.structural = tick;
125        debug_assert_eq!(self.data.len(), self.entities.len());
126        debug_assert_eq!(self.data.len(), self.row_changed.len());
127    }
128
129    /// Remove row `index`, moving the last row into its place. Returns the
130    /// removed value. O(1), but reorders the column: a caller that keys on a row
131    /// position must treat that position as invalidated. The moved row keeps its
132    /// own change tick, which travels with it.
133    pub fn swap_remove(&mut self, index: usize, tick: Tick) -> T {
134        let value = self.data.swap_remove(index);
135        self.entities.swap_remove(index);
136        self.row_changed.swap_remove(index);
137        self.changed = tick;
138        self.structural = tick;
139        debug_assert_eq!(self.data.len(), self.entities.len());
140        debug_assert_eq!(self.data.len(), self.row_changed.len());
141        value
142    }
143
144    /// Take all values, leaving the column empty. Stamps the change tick.
145    pub fn drain(&mut self, tick: Tick) -> Vec<T> {
146        self.entities.clear();
147        self.row_changed.clear();
148        self.changed = tick;
149        self.structural = tick;
150        core::mem::take(&mut self.data)
151    }
152
153    /// Empty the column without returning the values.
154    pub fn clear(&mut self, tick: Tick) {
155        self.data.clear();
156        self.entities.clear();
157        self.row_changed.clear();
158        self.changed = tick;
159        self.structural = tick;
160    }
161
162    /// Mutable access to the values. Stamps the bulk tick because the caller may
163    /// write any element, which leaves the per-row stamps unable to describe the
164    /// change on their own.
165    pub fn values_mut(&mut self, tick: Tick) -> &mut [T] {
166        self.changed = tick;
167        self.bulk = tick;
168        &mut self.data
169    }
170
171    /// Mutable access to one row, stamping only that row. The targeted
172    /// counterpart of `values_mut`: a consumer comparing row ticks against its
173    /// last run recovers exactly which entities were written.
174    pub fn value_mut(&mut self, row: usize, tick: Tick) -> Option<&mut T> {
175        let value = self.data.get_mut(row)?;
176        self.row_changed[row] = tick;
177        self.changed = tick;
178        Some(value)
179    }
180
181    /// Iterate rows paired with their owning entity.
182    pub fn iter_with_entities(&self) -> impl Iterator<Item = (Entity, &T)> {
183        self.entities.iter().copied().zip(self.data.iter())
184    }
185
186    /// Iterate rows mutably, paired with their owning entity. Stamps the bulk
187    /// tick because any element may be written.
188    pub fn iter_mut_with_entities(&mut self, tick: Tick) -> impl Iterator<Item = (Entity, &mut T)> {
189        self.changed = tick;
190        self.bulk = tick;
191        self.entities.iter().copied().zip(self.data.iter_mut())
192    }
193
194    /// Rows whose own change tick is newer than `last_run`, paired with their
195    /// owning entity. Only meaningful when neither `bulk` nor `structural` moved
196    /// since `last_run`; past either of those the per-row stamps no longer
197    /// describe the whole change.
198    pub fn changed_rows(&self, last_run: Tick) -> impl Iterator<Item = (Entity, &T)> {
199        self.row_changed
200            .iter()
201            .zip(self.entities.iter().copied().zip(self.data.iter()))
202            .filter_map(move |(row, pair)| row.is_newer_than(last_run).then_some(pair))
203    }
204
205    // Whether the column changed since a system's last run, wrap-safe.
206    #[cfg(test)]
207    pub(crate) fn changed_since(&self, last_run: Tick) -> bool {
208        self.changed.is_newer_than(last_run)
209    }
210}
211
212impl<T> Deref for Column<T> {
213    type Target = [T];
214
215    fn deref(&self) -> &[T] {
216        &self.data
217    }
218}
219
220#[cfg(test)]
221mod tests {
222    use super::*;
223    use crate::ecs::entity::Entities;
224    use alloc::vec;
225
226    fn three() -> (Entities, [Entity; 3]) {
227        let mut entities = Entities::new();
228        let ids = [entities.alloc(), entities.alloc(), entities.alloc()];
229        (entities, ids)
230    }
231
232    #[test]
233    fn push_keeps_rows_aligned_and_stamps_ticks() {
234        let (_e, ids) = three();
235        let mut col: Column<u32> = Column::new();
236        col.push(ids[0], 10, Tick(1));
237        col.push(ids[1], 20, Tick(2));
238        assert_eq!(col.len(), 2);
239        assert_eq!(&col[..], &[10, 20]);
240        assert_eq!(col.entities(), &[ids[0], ids[1]]);
241        assert_eq!(col.added_tick(), Tick(2));
242        assert_eq!(col.changed_tick(), Tick(2));
243    }
244
245    #[test]
246    fn swap_remove_reorders_and_returns_value() {
247        let (_e, ids) = three();
248        let mut col: Column<u32> = Column::new();
249        col.push(ids[0], 10, Tick(1));
250        col.push(ids[1], 20, Tick(1));
251        col.push(ids[2], 30, Tick(1));
252        let removed = col.swap_remove(0, Tick(5));
253        assert_eq!(removed, 10);
254        // Last row moved into slot 0; data and entity stay aligned.
255        assert_eq!(&col[..], &[30, 20]);
256        assert_eq!(col.entities(), &[ids[2], ids[1]]);
257        assert_eq!(col.changed_tick(), Tick(5));
258    }
259
260    #[test]
261    fn drain_empties_and_returns_data() {
262        let (_e, ids) = three();
263        let mut col: Column<u32> = Column::new();
264        col.push(ids[0], 10, Tick(1));
265        col.push(ids[1], 20, Tick(1));
266        let drained = col.drain(Tick(9));
267        assert_eq!(drained, vec![10, 20]);
268        assert!(col.is_empty());
269        assert!(col.entities().is_empty());
270        assert_eq!(col.changed_tick(), Tick(9));
271    }
272
273    #[test]
274    fn values_mut_stamps_change() {
275        let (_e, ids) = three();
276        let mut col: Column<u32> = Column::new();
277        col.push(ids[0], 10, Tick(1));
278        for v in col.values_mut(Tick(7)) {
279            *v += 1;
280        }
281        assert_eq!(&col[..], &[11]);
282        assert!(col.changed_since(Tick(6)));
283        assert!(!col.changed_since(Tick(7)));
284    }
285
286    #[test]
287    fn iter_with_entities_pairs_rows() {
288        let (_e, ids) = three();
289        let mut col: Column<&str> = Column::new();
290        col.push(ids[0], "a", Tick(1));
291        col.push(ids[1], "b", Tick(1));
292        let pairs: Vec<(Entity, &str)> = col.iter_with_entities().map(|(e, v)| (e, *v)).collect();
293        assert_eq!(pairs, vec![(ids[0], "a"), (ids[1], "b")]);
294    }
295
296    #[test]
297    fn value_mut_stamps_only_its_own_row() {
298        let (_e, ids) = three();
299        let mut col: Column<u32> = Column::new();
300        col.push(ids[0], 10, Tick(1));
301        col.push(ids[1], 20, Tick(1));
302        col.push(ids[2], 30, Tick(1));
303
304        *col.value_mut(1, Tick(7)).unwrap() = 99;
305        assert_eq!(&col[..], &[10, 99, 30]);
306        assert_eq!(col.row_ticks(), &[Tick(1), Tick(7), Tick(1)]);
307        // The column tick still moves, so coarse consumers are unaffected.
308        assert_eq!(col.changed_tick(), Tick(7));
309        // No whole-column write happened, so the bulk stamp stays put.
310        assert_eq!(col.ticks().bulk, Tick::ZERO);
311
312        let changed: Vec<(Entity, u32)> = col.changed_rows(Tick(1)).map(|(e, v)| (e, *v)).collect();
313        assert_eq!(changed, vec![(ids[1], 99)]);
314    }
315
316    #[test]
317    fn value_mut_returns_none_past_the_end() {
318        let (_e, ids) = three();
319        let mut col: Column<u32> = Column::new();
320        col.push(ids[0], 10, Tick(1));
321        assert!(col.value_mut(1, Tick(5)).is_none());
322        // A miss stamps nothing.
323        assert_eq!(col.changed_tick(), Tick(1));
324    }
325
326    #[test]
327    fn values_mut_stamps_the_bulk_tick_and_leaves_rows_alone() {
328        let (_e, ids) = three();
329        let mut col: Column<u32> = Column::new();
330        col.push(ids[0], 10, Tick(1));
331        col.push(ids[1], 20, Tick(1));
332        for v in col.values_mut(Tick(6)) {
333            *v += 1;
334        }
335        // Rows are not individually stamped; `bulk` is what says they all moved.
336        assert_eq!(col.row_ticks(), &[Tick(1), Tick(1)]);
337        assert_eq!(col.ticks().bulk, Tick(6));
338        assert_eq!(col.ticks().changed, Tick(6));
339    }
340
341    #[test]
342    fn push_and_remove_stamp_the_structural_tick() {
343        let (_e, ids) = three();
344        let mut col: Column<u32> = Column::new();
345        col.push(ids[0], 10, Tick(1));
346        assert_eq!(col.ticks().structural, Tick(1));
347        // A targeted write is not structural.
348        col.value_mut(0, Tick(2));
349        assert_eq!(col.ticks().structural, Tick(1));
350        col.push(ids[1], 20, Tick(3));
351        col.swap_remove(0, Tick(4));
352        assert_eq!(col.ticks().structural, Tick(4));
353        // The surviving row kept the tick it was pushed with.
354        assert_eq!(col.row_ticks(), &[Tick(3)]);
355        col.clear(Tick(5));
356        assert_eq!(col.ticks().structural, Tick(5));
357        assert!(col.row_ticks().is_empty());
358    }
359
360    #[test]
361    fn changed_rows_survives_tick_wraparound() {
362        let (_e, ids) = three();
363        let mut col: Column<u32> = Column::new();
364        col.push(ids[0], 10, Tick(u32::MAX - 1));
365        col.push(ids[1], 20, Tick(u32::MAX - 1));
366        // A write just past the wrap is still newer than the pre-wrap stamp.
367        *col.value_mut(0, Tick(2)).unwrap() = 11;
368        let changed: Vec<Entity> = col
369            .changed_rows(Tick(u32::MAX - 1))
370            .map(|(e, _)| e)
371            .collect();
372        assert_eq!(changed, vec![ids[0]]);
373    }
374
375    #[test]
376    fn iter_mut_with_entities_pairs_rows_and_stamps_change() {
377        let (_e, ids) = three();
378        let mut col: Column<u32> = Column::new();
379        col.push(ids[0], 10, Tick(1));
380        col.push(ids[1], 20, Tick(1));
381        let seen: Vec<Entity> = col
382            .iter_mut_with_entities(Tick(4))
383            .map(|(e, v)| {
384                *v += 1;
385                e
386            })
387            .collect();
388        assert_eq!(seen, vec![ids[0], ids[1]]);
389        assert_eq!(&col[..], &[11, 21]);
390        assert!(col.changed_since(Tick(3)));
391        assert!(!col.changed_since(Tick(4)));
392    }
393}