Skip to content

Commit 530be10

Browse files
committed
Newtype ArchetypeRow and TableRow (#4878)
# Objective Prevent future unsoundness that was seen in #6623. ## Solution Newtype both indexes in `Archetype` and `Table` as `ArchetypeRow` and `TableRow`. This avoids weird numerical manipulation on the indices, and can be stored and treated opaquely. Also enforces the source and destination of where these indices at a type level. --- ## Changelog Changed: `Archetype` indices and `Table` rows have been newtyped as `ArchetypeRow` and `TableRow`.
1 parent a3f203b commit 530be10

File tree

13 files changed

+310
-213
lines changed

13 files changed

+310
-213
lines changed

crates/bevy_ecs/macros/src/fetch.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -283,7 +283,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
283283
unsafe fn fetch<'__w>(
284284
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
285285
_entity: #path::entity::Entity,
286-
_table_row: usize
286+
_table_row: #path::storage::TableRow,
287287
) -> <Self as #path::query::WorldQuery>::Item<'__w> {
288288
Self::Item {
289289
#(#field_idents: <#field_types>::fetch(&mut _fetch.#field_idents, _entity, _table_row),)*
@@ -296,7 +296,7 @@ pub fn derive_world_query_impl(ast: DeriveInput) -> TokenStream {
296296
unsafe fn filter_fetch<'__w>(
297297
_fetch: &mut <Self as #path::query::WorldQuery>::Fetch<'__w>,
298298
_entity: #path::entity::Entity,
299-
_table_row: usize
299+
_table_row: #path::storage::TableRow,
300300
) -> bool {
301301
true #(&& <#field_types>::filter_fetch(&mut _fetch.#field_idents, _entity, _table_row))*
302302
}

crates/bevy_ecs/src/archetype.rs

Lines changed: 47 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,41 @@ use crate::{
2323
bundle::BundleId,
2424
component::{ComponentId, StorageType},
2525
entity::{Entity, EntityLocation},
26-
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId},
26+
storage::{ImmutableSparseSet, SparseArray, SparseSet, SparseSetIndex, TableId, TableRow},
2727
};
2828
use std::{
2929
collections::HashMap,
3030
hash::Hash,
3131
ops::{Index, IndexMut},
3232
};
3333

34+
/// An opaque location within a [`Archetype`].
35+
///
36+
/// This can be used in conjunction with [`ArchetypeId`] to find the exact location
37+
/// of an [`Entity`] within a [`World`]. An entity's archetype and index can be
38+
/// retrieved via [`Entities::get`].
39+
///
40+
/// [`World`]: crate::world::World
41+
/// [`Entities::get`]: crate::entity::Entities
42+
#[derive(Debug, Copy, Clone, Eq, PartialEq)]
43+
#[repr(transparent)]
44+
pub struct ArchetypeRow(usize);
45+
46+
impl ArchetypeRow {
47+
pub const INVALID: ArchetypeRow = ArchetypeRow(usize::MAX);
48+
49+
/// Creates a `ArchetypeRow`.
50+
pub const fn new(index: usize) -> Self {
51+
Self(index)
52+
}
53+
54+
/// Gets the index of the row.
55+
#[inline]
56+
pub const fn index(self) -> usize {
57+
self.0
58+
}
59+
}
60+
3461
/// An opaque unique ID for a single [`Archetype`] within a [`World`].
3562
///
3663
/// Archetype IDs are only valid for a given World, and are not globally unique.
@@ -226,7 +253,7 @@ impl Edges {
226253
/// Metadata about an [`Entity`] in a [`Archetype`].
227254
pub struct ArchetypeEntity {
228255
entity: Entity,
229-
table_row: usize,
256+
table_row: TableRow,
230257
}
231258

232259
impl ArchetypeEntity {
@@ -240,14 +267,14 @@ impl ArchetypeEntity {
240267
///
241268
/// [`Table`]: crate::storage::Table
242269
#[inline]
243-
pub const fn table_row(&self) -> usize {
270+
pub const fn table_row(&self) -> TableRow {
244271
self.table_row
245272
}
246273
}
247274

248275
pub(crate) struct ArchetypeSwapRemoveResult {
249276
pub(crate) swapped_entity: Option<Entity>,
250-
pub(crate) table_row: usize,
277+
pub(crate) table_row: TableRow,
251278
}
252279

253280
/// Internal metadata for a [`Component`] within a given [`Archetype`].
@@ -380,18 +407,18 @@ impl Archetype {
380407
/// Fetches the row in the [`Table`] where the components for the entity at `index`
381408
/// is stored.
382409
///
383-
/// An entity's archetype index can be fetched from [`EntityLocation::index`], which
410+
/// An entity's archetype index can be fetched from [`EntityLocation::archetype_row`], which
384411
/// can be retrieved from [`Entities::get`].
385412
///
386413
/// # Panics
387414
/// This function will panic if `index >= self.len()`.
388415
///
389416
/// [`Table`]: crate::storage::Table
390-
/// [`EntityLocation`]: crate::entity::EntityLocation::index
417+
/// [`EntityLocation`]: crate::entity::EntityLocation::archetype_row
391418
/// [`Entities::get`]: crate::entity::Entities::get
392419
#[inline]
393-
pub fn entity_table_row(&self, index: usize) -> usize {
394-
self.entities[index].table_row
420+
pub fn entity_table_row(&self, index: ArchetypeRow) -> TableRow {
421+
self.entities[index.0].table_row
395422
}
396423

397424
/// Updates if the components for the entity at `index` can be found
@@ -400,21 +427,25 @@ impl Archetype {
400427
/// # Panics
401428
/// This function will panic if `index >= self.len()`.
402429
#[inline]
403-
pub(crate) fn set_entity_table_row(&mut self, index: usize, table_row: usize) {
404-
self.entities[index].table_row = table_row;
430+
pub(crate) fn set_entity_table_row(&mut self, index: ArchetypeRow, table_row: TableRow) {
431+
self.entities[index.0].table_row = table_row;
405432
}
406433

407434
/// Allocates an entity to the archetype.
408435
///
409436
/// # Safety
410437
/// valid component values must be immediately written to the relevant storages
411438
/// `table_row` must be valid
412-
pub(crate) unsafe fn allocate(&mut self, entity: Entity, table_row: usize) -> EntityLocation {
439+
pub(crate) unsafe fn allocate(
440+
&mut self,
441+
entity: Entity,
442+
table_row: TableRow,
443+
) -> EntityLocation {
413444
self.entities.push(ArchetypeEntity { entity, table_row });
414445

415446
EntityLocation {
416447
archetype_id: self.id,
417-
index: self.entities.len() - 1,
448+
archetype_row: ArchetypeRow(self.entities.len() - 1),
418449
}
419450
}
420451

@@ -427,14 +458,14 @@ impl Archetype {
427458
///
428459
/// # Panics
429460
/// This function will panic if `index >= self.len()`
430-
pub(crate) fn swap_remove(&mut self, index: usize) -> ArchetypeSwapRemoveResult {
431-
let is_last = index == self.entities.len() - 1;
432-
let entity = self.entities.swap_remove(index);
461+
pub(crate) fn swap_remove(&mut self, index: ArchetypeRow) -> ArchetypeSwapRemoveResult {
462+
let is_last = index.0 == self.entities.len() - 1;
463+
let entity = self.entities.swap_remove(index.0);
433464
ArchetypeSwapRemoveResult {
434465
swapped_entity: if is_last {
435466
None
436467
} else {
437-
Some(self.entities[index].entity)
468+
Some(self.entities[index.0].entity)
438469
},
439470
table_row: entity.table_row,
440471
}

crates/bevy_ecs/src/bundle.rs

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@ pub use bevy_ecs_macros::Bundle;
66

77
use crate::{
88
archetype::{
9-
Archetype, ArchetypeId, Archetypes, BundleComponentStatus, ComponentStatus,
9+
Archetype, ArchetypeId, ArchetypeRow, Archetypes, BundleComponentStatus, ComponentStatus,
1010
SpawnBundleStatus,
1111
},
1212
component::{Component, ComponentId, Components, StorageType, Tick},
1313
entity::{Entities, Entity, EntityLocation},
14-
storage::{SparseSetIndex, SparseSets, Storages, Table},
14+
storage::{SparseSetIndex, SparseSets, Storages, Table, TableRow},
1515
};
1616
use bevy_ecs_macros::all_tuples;
1717
use bevy_ptr::OwningPtr;
@@ -379,7 +379,7 @@ impl BundleInfo {
379379
sparse_sets: &mut SparseSets,
380380
bundle_component_status: &S,
381381
entity: Entity,
382-
table_row: usize,
382+
table_row: TableRow,
383383
change_tick: u32,
384384
bundle: T,
385385
) {
@@ -518,17 +518,17 @@ pub(crate) enum InsertBundleResult<'a> {
518518

519519
impl<'a, 'b> BundleInserter<'a, 'b> {
520520
/// # Safety
521-
/// `entity` must currently exist in the source archetype for this inserter. `archetype_index`
521+
/// `entity` must currently exist in the source archetype for this inserter. `archetype_row`
522522
/// must be `entity`'s location in the archetype. `T` must match this [`BundleInfo`]'s type
523523
#[inline]
524524
pub unsafe fn insert<T: Bundle>(
525525
&mut self,
526526
entity: Entity,
527-
archetype_index: usize,
527+
archetype_row: ArchetypeRow,
528528
bundle: T,
529529
) -> EntityLocation {
530530
let location = EntityLocation {
531-
index: archetype_index,
531+
archetype_row,
532532
archetype_id: self.archetype.id(),
533533
};
534534
match &mut self.result {
@@ -544,14 +544,14 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
544544
self.sparse_sets,
545545
add_bundle,
546546
entity,
547-
self.archetype.entity_table_row(archetype_index),
547+
self.archetype.entity_table_row(archetype_row),
548548
self.change_tick,
549549
bundle,
550550
);
551551
location
552552
}
553553
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
554-
let result = self.archetype.swap_remove(location.index);
554+
let result = self.archetype.swap_remove(location.archetype_row);
555555
if let Some(swapped_entity) = result.swapped_entity {
556556
self.entities.set(swapped_entity.index(), location);
557557
}
@@ -579,7 +579,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
579579
new_archetype,
580580
new_table,
581581
} => {
582-
let result = self.archetype.swap_remove(location.index);
582+
let result = self.archetype.swap_remove(location.archetype_row);
583583
if let Some(swapped_entity) = result.swapped_entity {
584584
self.entities.set(swapped_entity.index(), location);
585585
}
@@ -607,7 +607,7 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
607607
};
608608

609609
swapped_archetype
610-
.set_entity_table_row(swapped_location.index, result.table_row);
610+
.set_entity_table_row(swapped_location.archetype_row, result.table_row);
611611
}
612612

613613
// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)

crates/bevy_ecs/src/entity/mod.rs

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,10 @@ mod map_entities;
3434

3535
pub use map_entities::*;
3636

37-
use crate::{archetype::ArchetypeId, storage::SparseSetIndex};
37+
use crate::{
38+
archetype::{ArchetypeId, ArchetypeRow},
39+
storage::SparseSetIndex,
40+
};
3841
use serde::{Deserialize, Serialize};
3942
use std::{convert::TryFrom, fmt, mem, sync::atomic::Ordering};
4043

@@ -727,7 +730,7 @@ impl EntityMeta {
727730
generation: 0,
728731
location: EntityLocation {
729732
archetype_id: ArchetypeId::INVALID,
730-
index: usize::MAX, // dummy value, to be filled in
733+
archetype_row: ArchetypeRow::INVALID, // dummy value, to be filled in
731734
},
732735
};
733736
}
@@ -740,7 +743,7 @@ pub struct EntityLocation {
740743
pub archetype_id: ArchetypeId,
741744

742745
/// The index of the entity in the archetype
743-
pub index: usize,
746+
pub archetype_row: ArchetypeRow,
744747
}
745748

746749
#[cfg(test)]

0 commit comments

Comments
 (0)