Skip to content

Commit 72adf2a

Browse files
Reduced TableRow as Casting (#10811)
# Objective - Fixes #10806 ## Solution Replaced `new` and `index` methods for both `TableRow` and `TableId` with `from_*` and `as_*` methods. These remove the need to perform casting at call sites, reducing the total number of casts in the Bevy codebase. Within these methods, an appropriate `debug_assertion` ensures the cast will behave in an expected manner (no wrapping, etc.). I am using a `debug_assertion` instead of an `assert` to reduce any possible runtime overhead, however minimal. This choice is something I am open to changing (or leaving up to another PR) if anyone has any strong arguments for it. --- ## Changelog - `ComponentSparseSet::sparse` stores a `TableRow` instead of a `u32` (private change) - Replaced `TableRow::new` and `TableRow::index` methods with `TableRow::from_*` and `TableRow::as_*`, with `debug_assertions` protecting any internal casting. - Replaced `TableId::new` and `TableId::index` methods with `TableId::from_*` and `TableId::as_*`, with `debug_assertions` protecting any internal casting. - All `TableId` methods are now `const` ## Migration Guide - `TableRow::new` -> `TableRow::from_usize` - `TableRow::index` -> `TableRow::as_usize` - `TableId::new` -> `TableId::from_usize` - `TableId::index` -> `TableId::as_usize` --- ## Notes I have chosen to remove the `index` and `new` methods for the following chain of reasoning: - Across the codebase, `new` was called with a mixture of `u32` and `usize` values. Likewise for `index`. - Choosing `new` to either be `usize` or `u32` would break half of these call-sites, requiring `as` casting at the site. - Adding a second method `new_u32` or `new_usize` avoids the above, bu looks visually inconsistent. - Therefore, they should be replaced with `from_*` and `as_*` methods instead. Worth noting is that by updating `ComponentSparseSet`, there are now zero instances of interacting with the inner value of `TableRow` as a `u32`, it is exclusively used as a `usize` value (due to interactions with methods like `len` and slice indexing). I have left the `as_u32` and `from_u32` methods as the "proper" constructors/getters.
1 parent 83ee6de commit 72adf2a

File tree

7 files changed

+195
-146
lines changed

7 files changed

+195
-146
lines changed

crates/bevy_ecs/src/query/fetch.rs

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -605,7 +605,7 @@ unsafe impl<T: Component> WorldQuery for &T {
605605
StorageType::Table => fetch
606606
.table_components
607607
.debug_checked_unwrap()
608-
.get(table_row.index())
608+
.get(table_row.as_usize())
609609
.deref(),
610610
StorageType::SparseSet => fetch
611611
.sparse_set
@@ -760,10 +760,10 @@ unsafe impl<'__w, T: Component> WorldQuery for Ref<'__w, T> {
760760
let (table_components, added_ticks, changed_ticks) =
761761
fetch.table_data.debug_checked_unwrap();
762762
Ref {
763-
value: table_components.get(table_row.index()).deref(),
763+
value: table_components.get(table_row.as_usize()).deref(),
764764
ticks: Ticks {
765-
added: added_ticks.get(table_row.index()).deref(),
766-
changed: changed_ticks.get(table_row.index()).deref(),
765+
added: added_ticks.get(table_row.as_usize()).deref(),
766+
changed: changed_ticks.get(table_row.as_usize()).deref(),
767767
this_run: fetch.this_run,
768768
last_run: fetch.last_run,
769769
},
@@ -927,10 +927,10 @@ unsafe impl<'__w, T: Component> WorldQuery for &'__w mut T {
927927
let (table_components, added_ticks, changed_ticks) =
928928
fetch.table_data.debug_checked_unwrap();
929929
Mut {
930-
value: table_components.get(table_row.index()).deref_mut(),
930+
value: table_components.get(table_row.as_usize()).deref_mut(),
931931
ticks: TicksMut {
932-
added: added_ticks.get(table_row.index()).deref_mut(),
933-
changed: changed_ticks.get(table_row.index()).deref_mut(),
932+
added: added_ticks.get(table_row.as_usize()).deref_mut(),
933+
changed: changed_ticks.get(table_row.as_usize()).deref_mut(),
934934
this_run: fetch.this_run,
935935
last_run: fetch.last_run,
936936
},

crates/bevy_ecs/src/query/filter.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -626,7 +626,7 @@ unsafe impl<T: Component> WorldQuery for Added<T> {
626626
StorageType::Table => fetch
627627
.table_ticks
628628
.debug_checked_unwrap()
629-
.get(table_row.index())
629+
.get(table_row.as_usize())
630630
.deref()
631631
.is_newer_than(fetch.last_run, fetch.this_run),
632632
StorageType::SparseSet => {
@@ -802,7 +802,7 @@ unsafe impl<T: Component> WorldQuery for Changed<T> {
802802
StorageType::Table => fetch
803803
.table_ticks
804804
.debug_checked_unwrap()
805-
.get(table_row.index())
805+
.get(table_row.as_usize())
806806
.deref()
807807
.is_newer_than(fetch.last_run, fetch.this_run),
808808
StorageType::SparseSet => {

crates/bevy_ecs/src/query/iter.rs

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -106,6 +106,11 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIter<'w, 's, Q, F> {
106106
where
107107
Func: FnMut(B, Q::Item<'w>) -> B,
108108
{
109+
assert!(
110+
rows.end <= u32::MAX as usize,
111+
"TableRow is only valid up to u32::MAX"
112+
);
113+
109114
Q::set_table(&mut self.cursor.fetch, &self.query_state.fetch_state, table);
110115
F::set_table(
111116
&mut self.cursor.filter,
@@ -117,7 +122,7 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIter<'w, 's, Q, F> {
117122
for row in rows {
118123
// SAFETY: Caller assures `row` in range of the current archetype.
119124
let entity = entities.get_unchecked(row);
120-
let row = TableRow::new(row);
125+
let row = TableRow::from_usize(row);
121126
// SAFETY: set_table was called prior.
122127
// Caller assures `row` in range of the current archetype.
123128
if !F::filter_fetch(&mut self.cursor.filter, *entity, row) {
@@ -707,7 +712,11 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIterationCursor<'w, 's
707712
let index = self.current_row - 1;
708713
if Self::IS_DENSE {
709714
let entity = self.table_entities.get_unchecked(index);
710-
Some(Q::fetch(&mut self.fetch, *entity, TableRow::new(index)))
715+
Some(Q::fetch(
716+
&mut self.fetch,
717+
*entity,
718+
TableRow::from_usize(index),
719+
))
711720
} else {
712721
let archetype_entity = self.archetype_entities.get_unchecked(index);
713722
Some(Q::fetch(
@@ -768,7 +777,7 @@ impl<'w, 's, Q: WorldQueryData, F: WorldQueryFilter> QueryIterationCursor<'w, 's
768777
// SAFETY: set_table was called prior.
769778
// `current_row` is a table row in range of the current table, because if it was not, then the if above would have been executed.
770779
let entity = self.table_entities.get_unchecked(self.current_row);
771-
let row = TableRow::new(self.current_row);
780+
let row = TableRow::from_usize(self.current_row);
772781
if !F::filter_fetch(&mut self.filter, *entity, row) {
773782
self.current_row += 1;
774783
continue;

crates/bevy_ecs/src/query/state.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -267,7 +267,7 @@ impl<Q: WorldQueryData, F: WorldQueryFilter> QueryState<Q, F> {
267267
self.matched_archetypes.set(archetype_index, true);
268268
self.matched_archetype_ids.push(archetype.id());
269269
}
270-
let table_index = archetype.table_id().index();
270+
let table_index = archetype.table_id().as_usize();
271271
if !self.matched_tables.contains(table_index) {
272272
self.matched_tables.grow(table_index + 1);
273273
self.matched_tables.set(table_index, true);

crates/bevy_ecs/src/storage/resource.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ impl<const SEND: bool> Drop for ResourceData<SEND> {
4040

4141
impl<const SEND: bool> ResourceData<SEND> {
4242
/// The only row in the underlying column.
43-
const ROW: TableRow = TableRow::new(0);
43+
const ROW: TableRow = TableRow::from_u32(0);
4444

4545
/// Validates the access to `!Send` resources is only done on the thread they were created from.
4646
///

crates/bevy_ecs/src/storage/sparse_set.rs

Lines changed: 34 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -124,7 +124,7 @@ pub struct ComponentSparseSet {
124124
entities: Vec<EntityIndex>,
125125
#[cfg(debug_assertions)]
126126
entities: Vec<Entity>,
127-
sparse: SparseArray<EntityIndex, u32>,
127+
sparse: SparseArray<EntityIndex, TableRow>,
128128
}
129129

130130
impl ComponentSparseSet {
@@ -171,13 +171,13 @@ impl ComponentSparseSet {
171171
) {
172172
if let Some(&dense_index) = self.sparse.get(entity.index()) {
173173
#[cfg(debug_assertions)]
174-
assert_eq!(entity, self.entities[dense_index as usize]);
175-
self.dense
176-
.replace(TableRow::new(dense_index as usize), value, change_tick);
174+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
175+
self.dense.replace(dense_index, value, change_tick);
177176
} else {
178177
let dense_index = self.dense.len();
179178
self.dense.push(value, ComponentTicks::new(change_tick));
180-
self.sparse.insert(entity.index(), dense_index as u32);
179+
self.sparse
180+
.insert(entity.index(), TableRow::from_usize(dense_index));
181181
#[cfg(debug_assertions)]
182182
assert_eq!(self.entities.len(), dense_index);
183183
#[cfg(not(debug_assertions))]
@@ -194,7 +194,7 @@ impl ComponentSparseSet {
194194
{
195195
if let Some(&dense_index) = self.sparse.get(entity.index()) {
196196
#[cfg(debug_assertions)]
197-
assert_eq!(entity, self.entities[dense_index as usize]);
197+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
198198
true
199199
} else {
200200
false
@@ -209,12 +209,11 @@ impl ComponentSparseSet {
209209
/// Returns `None` if `entity` does not have a component in the sparse set.
210210
#[inline]
211211
pub fn get(&self, entity: Entity) -> Option<Ptr<'_>> {
212-
self.sparse.get(entity.index()).map(|dense_index| {
213-
let dense_index = (*dense_index) as usize;
212+
self.sparse.get(entity.index()).map(|&dense_index| {
214213
#[cfg(debug_assertions)]
215-
assert_eq!(entity, self.entities[dense_index]);
214+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
216215
// SAFETY: if the sparse index points to something in the dense vec, it exists
217-
unsafe { self.dense.get_data_unchecked(TableRow::new(dense_index)) }
216+
unsafe { self.dense.get_data_unchecked(dense_index) }
218217
})
219218
}
220219

@@ -223,9 +222,9 @@ impl ComponentSparseSet {
223222
/// Returns `None` if `entity` does not have a component in the sparse set.
224223
#[inline]
225224
pub fn get_with_ticks(&self, entity: Entity) -> Option<(Ptr<'_>, TickCells<'_>)> {
226-
let dense_index = TableRow::new(*self.sparse.get(entity.index())? as usize);
225+
let dense_index = *self.sparse.get(entity.index())?;
227226
#[cfg(debug_assertions)]
228-
assert_eq!(entity, self.entities[dense_index.index()]);
227+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
229228
// SAFETY: if the sparse index points to something in the dense vec, it exists
230229
unsafe {
231230
Some((
@@ -243,69 +242,55 @@ impl ComponentSparseSet {
243242
/// Returns `None` if `entity` does not have a component in the sparse set.
244243
#[inline]
245244
pub fn get_added_tick(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> {
246-
let dense_index = *self.sparse.get(entity.index())? as usize;
245+
let dense_index = *self.sparse.get(entity.index())?;
247246
#[cfg(debug_assertions)]
248-
assert_eq!(entity, self.entities[dense_index]);
247+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
249248
// SAFETY: if the sparse index points to something in the dense vec, it exists
250-
unsafe {
251-
Some(
252-
self.dense
253-
.get_added_tick_unchecked(TableRow::new(dense_index)),
254-
)
255-
}
249+
unsafe { Some(self.dense.get_added_tick_unchecked(dense_index)) }
256250
}
257251

258252
/// Returns a reference to the "changed" tick of the entity's component value.
259253
///
260254
/// Returns `None` if `entity` does not have a component in the sparse set.
261255
#[inline]
262256
pub fn get_changed_tick(&self, entity: Entity) -> Option<&UnsafeCell<Tick>> {
263-
let dense_index = *self.sparse.get(entity.index())? as usize;
257+
let dense_index = *self.sparse.get(entity.index())?;
264258
#[cfg(debug_assertions)]
265-
assert_eq!(entity, self.entities[dense_index]);
259+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
266260
// SAFETY: if the sparse index points to something in the dense vec, it exists
267-
unsafe {
268-
Some(
269-
self.dense
270-
.get_changed_tick_unchecked(TableRow::new(dense_index)),
271-
)
272-
}
261+
unsafe { Some(self.dense.get_changed_tick_unchecked(dense_index)) }
273262
}
274263

275264
/// Returns a reference to the "added" and "changed" ticks of the entity's component value.
276265
///
277266
/// Returns `None` if `entity` does not have a component in the sparse set.
278267
#[inline]
279268
pub fn get_ticks(&self, entity: Entity) -> Option<ComponentTicks> {
280-
let dense_index = *self.sparse.get(entity.index())? as usize;
269+
let dense_index = *self.sparse.get(entity.index())?;
281270
#[cfg(debug_assertions)]
282-
assert_eq!(entity, self.entities[dense_index]);
271+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
283272
// SAFETY: if the sparse index points to something in the dense vec, it exists
284-
unsafe { Some(self.dense.get_ticks_unchecked(TableRow::new(dense_index))) }
273+
unsafe { Some(self.dense.get_ticks_unchecked(dense_index)) }
285274
}
286275

287276
/// Removes the `entity` from this sparse set and returns a pointer to the associated value (if
288277
/// it exists).
289278
#[must_use = "The returned pointer must be used to drop the removed component."]
290279
pub(crate) fn remove_and_forget(&mut self, entity: Entity) -> Option<OwningPtr<'_>> {
291280
self.sparse.remove(entity.index()).map(|dense_index| {
292-
let dense_index = dense_index as usize;
293281
#[cfg(debug_assertions)]
294-
assert_eq!(entity, self.entities[dense_index]);
295-
self.entities.swap_remove(dense_index);
296-
let is_last = dense_index == self.dense.len() - 1;
282+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
283+
self.entities.swap_remove(dense_index.as_usize());
284+
let is_last = dense_index.as_usize() == self.dense.len() - 1;
297285
// SAFETY: dense_index was just removed from `sparse`, which ensures that it is valid
298-
let (value, _) = unsafe {
299-
self.dense
300-
.swap_remove_and_forget_unchecked(TableRow::new(dense_index))
301-
};
286+
let (value, _) = unsafe { self.dense.swap_remove_and_forget_unchecked(dense_index) };
302287
if !is_last {
303-
let swapped_entity = self.entities[dense_index];
288+
let swapped_entity = self.entities[dense_index.as_usize()];
304289
#[cfg(not(debug_assertions))]
305290
let index = swapped_entity;
306291
#[cfg(debug_assertions)]
307292
let index = swapped_entity.index();
308-
*self.sparse.get_mut(index).unwrap() = dense_index as u32;
293+
*self.sparse.get_mut(index).unwrap() = dense_index;
309294
}
310295
value
311296
})
@@ -316,20 +301,21 @@ impl ComponentSparseSet {
316301
/// Returns `true` if `entity` had a component value in the sparse set.
317302
pub(crate) fn remove(&mut self, entity: Entity) -> bool {
318303
if let Some(dense_index) = self.sparse.remove(entity.index()) {
319-
let dense_index = dense_index as usize;
320304
#[cfg(debug_assertions)]
321-
assert_eq!(entity, self.entities[dense_index]);
322-
self.entities.swap_remove(dense_index);
323-
let is_last = dense_index == self.dense.len() - 1;
305+
assert_eq!(entity, self.entities[dense_index.as_usize()]);
306+
self.entities.swap_remove(dense_index.as_usize());
307+
let is_last = dense_index.as_usize() == self.dense.len() - 1;
324308
// SAFETY: if the sparse index points to something in the dense vec, it exists
325-
unsafe { self.dense.swap_remove_unchecked(TableRow::new(dense_index)) }
309+
unsafe {
310+
self.dense.swap_remove_unchecked(dense_index);
311+
}
326312
if !is_last {
327-
let swapped_entity = self.entities[dense_index];
313+
let swapped_entity = self.entities[dense_index.as_usize()];
328314
#[cfg(not(debug_assertions))]
329315
let index = swapped_entity;
330316
#[cfg(debug_assertions)]
331317
let index = swapped_entity.index();
332-
*self.sparse.get_mut(index).unwrap() = dense_index as u32;
318+
*self.sparse.get_mut(index).unwrap() = dense_index;
333319
}
334320
true
335321
} else {

0 commit comments

Comments
 (0)