1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
//! Archetype tables (DECISIONS #8): entities with the same component set share a table whose
//! columns are parallel to its `entities` row list.
use crate::hash::TypeIdMap;
use std::any::TypeId;
use crate::component::{Column, ColumnData, Component, Ticks};
use crate::{EcsError, Entity};
pub(crate) struct Archetype {
/// Component types, sorted and unique.
types: Vec<TypeId>,
/// One column per entry of `types`, in the same order.
columns: Vec<Box<dyn Column>>,
/// The entity of every row.
entities: Vec<Entity>,
/// Cached transitions: the archetype reached by adding / removing one component type.
add_edges: TypeIdMap<TypeId, usize>,
remove_edges: TypeIdMap<TypeId, usize>,
/// Cached transitions by bundle type: the archetype reached by inserting the bundle
/// (DECISIONS #35). From the empty archetype, the table a bundle spawns into.
bundle_edges: TypeIdMap<TypeId, usize>,
}
impl Archetype {
/// A table for `types` (sorted, unique) with `columns` parallel to them.
pub(crate) fn new(types: Vec<TypeId>, columns: Vec<Box<dyn Column>>) -> Self {
debug_assert!(types.windows(2).all(|w| matches!(w, [a, b] if a < b)));
debug_assert_eq!(types.len(), columns.len());
Self {
types,
columns,
entities: Vec::new(),
add_edges: TypeIdMap::default(),
remove_edges: TypeIdMap::default(),
bundle_edges: TypeIdMap::default(),
}
}
pub(crate) fn types(&self) -> &[TypeId] {
&self.types
}
pub(crate) fn columns(&self) -> &[Box<dyn Column>] {
&self.columns
}
pub(crate) fn entities(&self) -> &[Entity] {
&self.entities
}
/// Number of rows.
pub(crate) fn len(&self) -> usize {
self.entities.len()
}
/// The component types, the columns (mutably) and the row entities at once, so that a query
/// can borrow several columns of the table (DECISIONS #13).
pub(crate) fn parts_mut(&mut self) -> (&[TypeId], &mut [Box<dyn Column>], &[Entity]) {
(&self.types, &mut self.columns, &self.entities)
}
pub(crate) fn has(&self, ty: TypeId) -> bool {
self.types.binary_search(&ty).is_ok()
}
pub(crate) fn get<T: Component>(&self, row: usize) -> Option<&T> {
let column = self.types.binary_search(&TypeId::of::<T>()).ok()?;
self.columns
.get(column)?
.as_any()
.downcast_ref::<ColumnData<T>>()?
.values()
.get(row)
}
/// The component of `T` at `row`, its changed tick stamped with `tick` (DECISIONS #28).
pub(crate) fn get_mut<T: Component>(&mut self, row: usize, tick: u64) -> Option<&mut T> {
let (value, changed) = self.column_mut::<T>()?.get_mut(row)?;
*changed = tick;
Some(value)
}
/// The whole column of `T`, to lend it to a frame (DECISIONS #18). Callers must not change
/// its length.
pub(crate) fn column_mut<T: Component>(&mut self) -> Option<&mut ColumnData<T>> {
let column = self.types.binary_search(&TypeId::of::<T>()).ok()?;
self.columns
.get_mut(column)?
.as_any_mut()
.downcast_mut::<ColumnData<T>>()
}
/// The change ticks of the column of type `ty`, if the table has one.
pub(crate) fn ticks(&self, ty: TypeId) -> Option<Ticks<'_>> {
let column = self.types.binary_search(&ty).ok()?;
Some(self.columns.get(column)?.ticks())
}
pub(crate) fn add_edge(&self, ty: TypeId) -> Option<usize> {
self.add_edges.get(&ty).copied()
}
pub(crate) fn remove_edge(&self, ty: TypeId) -> Option<usize> {
self.remove_edges.get(&ty).copied()
}
pub(crate) fn bundle_edge(&self, bundle: TypeId) -> Option<usize> {
self.bundle_edges.get(&bundle).copied()
}
/// Caches the transition by a bundle; skipped if out of memory, like
/// [`cache_edge`](Self::cache_edge).
pub(crate) fn cache_bundle_edge(&mut self, bundle: TypeId, target: usize) {
if self.bundle_edges.try_reserve(1).is_ok() {
self.bundle_edges.insert(bundle, target);
}
}
/// Caches a transition. A cache is an optimization: running out of memory just skips it.
pub(crate) fn cache_edge(&mut self, add: bool, ty: TypeId, target: usize) {
let edges = if add {
&mut self.add_edges
} else {
&mut self.remove_edges
};
if edges.try_reserve(1).is_ok() {
edges.insert(ty, target);
}
}
/// Reserves room for one more row in every column, so that the next push does not allocate.
pub(crate) fn reserve_row(&mut self) -> Result<(), EcsError> {
let entities = self.entities.len().saturating_add(1);
let oom = |_| EcsError::OutOfMemory { entities };
self.entities.try_reserve(1).map_err(oom)?;
for column in &mut self.columns {
column.reserve_rows(1).map_err(oom)?;
}
Ok(())
}
/// Appends a row for `entity` whose columns are already filled; returns its row.
///
/// Callers reserve the room first ([`reserve_row`](Self::reserve_row)).
pub(crate) fn push_entity(&mut self, entity: Entity) -> usize {
let row = self.entities.len();
self.entities.push(entity);
row
}
/// Pushes `value`, added at `tick`, into the column of `T`; `false` if the table has no such
/// column.
pub(crate) fn push_component<T: Component>(&mut self, value: T, tick: u64) -> bool {
match self.column_mut::<T>() {
Some(column) => {
column.push(value, tick, tick);
true
}
None => false,
}
}
/// Writes `value` at `row` of the column of `T`: replaces the component there (stamped as
/// changed at `tick`) or, if the column does not reach `row` yet, appends it (added and
/// changed at `tick`; callers reserve the room first). `false` if the table has no such
/// column.
pub(crate) fn put_component<T: Component>(&mut self, row: usize, value: T, tick: u64) -> bool {
let Some(column) = self.column_mut::<T>() else {
return false;
};
match column.get_mut(row) {
Some((slot, changed)) => {
*slot = value;
*changed = tick;
}
None => {
debug_assert_eq!(column.len(), row, "a bundle writes the next row");
column.push(value, tick, tick);
}
}
true
}
/// Whether every column has exactly one row per entity (a debug check).
pub(crate) fn is_aligned(&self) -> bool {
let rows = self.entities.len();
self.columns.iter().all(|column| {
let ticks = column.ticks();
ticks.added.len() == rows && ticks.changed.len() == rows
})
}
/// Removes `row` from the entity list (the last row takes its place) and returns the entity
/// now at `row`, if any, so the caller can update its location. Columns are handled separately.
pub(crate) fn swap_remove_entity(&mut self, row: usize) -> Option<Entity> {
if row >= self.entities.len() {
return None;
}
self.entities.swap_remove(row);
self.entities.get(row).copied()
}
/// Drops every component of `row` (the last row takes its place).
pub(crate) fn drop_row(&mut self, row: usize) {
for column in &mut self.columns {
let dropped = column.swap_remove_drop(row);
debug_assert!(dropped, "row {row} out of range");
}
}
/// Moves the components of `row` into `dst` (room reserved): columns present in both tables
/// are moved, the others are handed to `leftover` with their type. The last row of `self`
/// takes the place of `row`. The entity list is left to the caller.
pub(crate) fn move_row(
&mut self,
row: usize,
dst: &mut Self,
mut leftover: impl FnMut(TypeId, &mut dyn Column),
) {
for (ty, column) in self.types.iter().zip(&mut self.columns) {
match dst
.types
.binary_search(ty)
.ok()
.and_then(|i| dst.columns.get_mut(i))
{
Some(target) => {
let moved = column.swap_remove_into(row, target.as_mut());
debug_assert!(moved, "row {row} out of range or column type mismatch");
}
None => leftover(*ty, column.as_mut()),
}
}
}
}
/// Mutable references to two different elements of a slice; `None` if `a == b` or out of range.
pub(crate) fn two_mut<T>(slice: &mut [T], a: usize, b: usize) -> Option<(&mut T, &mut T)> {
if a < b {
let (left, right) = slice.split_at_mut_checked(b)?;
Some((left.get_mut(a)?, right.first_mut()?))
} else if b < a {
let (left, right) = slice.split_at_mut_checked(a)?;
let (eb, ea) = (left.get_mut(b)?, right.first_mut()?);
Some((ea, eb))
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn two_mut_rejects_aliasing_and_out_of_range() {
let mut v = [1, 2, 3];
assert!(two_mut(&mut v, 1, 1).is_none());
assert!(two_mut(&mut v, 0, 3).is_none());
let (a, b) = two_mut(&mut v, 2, 0).unwrap();
std::mem::swap(a, b);
assert_eq!(v, [3, 2, 1]);
}
}