Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Optimize memory size of SparseArray and integer-based ECS IDs #3678

Closed
wants to merge 71 commits into from
Closed
Show file tree
Hide file tree
Changes from 19 commits
Commits
Show all changes
71 commits
Select commit Hold shift + click to select a range
6620dd9
Crude initial implementation
james7132 Jan 15, 2022
37bd31f
Use nonmax instead.
james7132 Jan 15, 2022
28fe7a0
Implement it for BundleId
james7132 Jan 15, 2022
8bca091
Fix tests
james7132 Jan 15, 2022
f01f1d2
Add missing safety and panic docs.
james7132 Jan 15, 2022
d2ebb01
Remove dependency on static_assertions
james7132 Jan 15, 2022
1c2525c
Fix formatting
james7132 Jan 15, 2022
6aa4a97
Fix test
james7132 Jan 15, 2022
ab57a2b
Optimize space for ArchetypeId
james7132 Jan 26, 2022
85fc10c
Update docs
james7132 Jan 27, 2022
eea5465
Provide better panic messages
james7132 Jan 27, 2022
41d7c08
Address safety comment review
james7132 Mar 6, 2022
db30528
Remvoe ArchetypeId by niching EntityLocation
james7132 Mar 6, 2022
351ffea
Merge branch 'main' into ecs-id-size-opt
james7132 Mar 6, 2022
f01b97a
Remove comment about Entity niching
james7132 Mar 6, 2022
692b4a0
Fix up existing safety doc comments
james7132 Mar 6, 2022
307775e
Fix broken doc reference
james7132 Mar 7, 2022
1e19f0e
Merge branch 'main' into ecs-id-size-opt
james7132 May 20, 2022
ceeea05
Formatting
james7132 May 20, 2022
a1bd8c4
Per-ID type Repr
james7132 May 22, 2022
fd34430
Formatting
james7132 May 22, 2022
77f9a15
Shrink ComponentId to u32
james7132 May 22, 2022
96ddad1
Shrink ArchetypeId to u32
james7132 May 22, 2022
0a275db
Shrink BundleId to u32
james7132 May 22, 2022
a0edde8
Shrink ArchetypeComponentId
james7132 May 22, 2022
3ae86f0
cleanup ComponentId mappingsk
james7132 May 25, 2022
83a8b57
Optimize ComponentSparseSet
james7132 May 25, 2022
e74dc1f
Formatting
james7132 May 25, 2022
3c07935
Merge branch 'main' into ecs-id-size-opt
james7132 May 31, 2022
c70b98b
Fix CI
james7132 May 31, 2022
66d2c4d
Merge branch 'main' into ecs-id-size-opt
james7132 Jun 1, 2022
7e7c85e
Merge branch 'main' into ecs-id-size-opt
james7132 Jun 3, 2022
ff8cb07
Merge branch 'main' into ecs-id-size-opt
james7132 Jun 20, 2022
ebf8f9c
Merge branch 'main' into ecs-id-size-opt
james7132 Nov 20, 2022
675cdca
Fix CI
james7132 Nov 20, 2022
d2648e1
Merge branch 'main' into ecs-id-size-opt
james7132 Nov 23, 2022
e23485b
Use NonZeroU32 for Components
james7132 Nov 23, 2022
ef3082c
NonMax -> NonZero
james7132 Nov 23, 2022
ea2d275
De-niche ArchetypeComponentId
james7132 Nov 23, 2022
b8219fa
Make repr_to_index unsafe
james7132 Nov 23, 2022
92ca951
Zero-index pad Archetypes and Bundles
james7132 Nov 23, 2022
2aa3fe0
Delete outdated test
james7132 Nov 23, 2022
e28d7ac
Fix CI
james7132 Nov 23, 2022
e477f50
Merge branch 'main' into ecs-id-size-opt
james7132 Jan 6, 2023
e945015
Fix build
james7132 Jan 6, 2023
c8cddc4
Merge branch 'main' into ecs-id-size-opt
james7132 Jan 6, 2023
3388b4a
Fix safety comment.
james7132 Jan 7, 2023
79c65da
Discard incorrect test
james7132 Jan 11, 2023
83b03f8
Added missing details to SystemParam Local documentation. (#7106)
iiYese Jan 6, 2023
b0b7e58
Fix doc comment "Turbo" -> "Extreme" (#7091)
A-Walrus Jan 6, 2023
bebfb01
add `Axis::devices` to get all the input devices (#5400)
1e1001 Jan 6, 2023
5fa57ab
Update an outdated example for `Mut::map_unchanged` (#7115)
JoJoJet Jan 6, 2023
563cb2e
Remove the `SystemParamState` trait and remove types like `ResState` …
JoJoJet Jan 7, 2023
8340a3b
bevy_render: Run calculate_bounds in the end-of-update exclusive syst…
superdump Jan 9, 2023
025010a
Support storage buffers in derive `AsBindGroup` (#6129)
IceSentry Jan 9, 2023
a15827b
Expose symphonia features from rodio in bevy_audio and bevy (#6388)
Ian-Yy Jan 9, 2023
49e76e6
Smooth Transition between Animations (#6922)
smessmer Jan 9, 2023
32dac8a
Gamepad events refactor (#6965)
DevinLeamy Jan 9, 2023
45ee432
Separate Extract from Sub App Schedule (#7046)
hymm Jan 9, 2023
d823da0
Reduce branching in TrackedRenderPass (#7053)
james7132 Jan 9, 2023
4343f2e
reflect: add `insert` and `remove` methods to `List` (#7063)
soqb Jan 9, 2023
845b956
Panic on dropping NonSend in non-origin thread. (#6534)
james7132 Jan 9, 2023
a151fbf
Relax `Sync` bound on `Local<T> as ExclusiveSystemParam` (#7040)
JoJoJet Jan 9, 2023
1ad542a
add rust-version for MSRV and CI job to check (#6852)
mockersf Jan 9, 2023
dc857c0
Implement `SparseSetIndex` for `WorldId` (#7125)
2ne1ugly Jan 9, 2023
6c4acf9
Fix doc in `App::add_sub_app` (#7139)
2ne1ugly Jan 9, 2023
1246fdf
Fix overflow scaling for images (#7142)
mockersf Jan 9, 2023
5cda426
Add `TypeRegistrationDeserializer` and remove `BorrowedStr` (#7094)
SkiFire13 Jan 9, 2023
b7f1421
Add `Mut::reborrow` (#7114)
JoJoJet Jan 9, 2023
0ae7f77
Merge branch 'main' into ecs-id-size-opt
james7132 Jan 21, 2023
1029b0b
Fix build
james7132 Jan 21, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ async-channel = "1.4"
fixedbitset = "0.4"
fxhash = "0.2"
downcast-rs = "1.2"
nonmax = "0.5"
serde = "1"

[dev-dependencies]
Expand Down
74 changes: 58 additions & 16 deletions crates/bevy_ecs/src/archetype.rs
Original file line number Diff line number Diff line change
Expand Up @@ -7,28 +7,43 @@ use crate::{
entity::{Entity, EntityLocation},
storage::{Column, SparseArray, SparseSet, SparseSetIndex, TableId},
};
use nonmax::NonMaxUsize;
use std::{
collections::HashMap,
hash::Hash,
ops::{Index, IndexMut},
};

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ArchetypeId(usize);
pub struct ArchetypeId(NonMaxUsize);

impl ArchetypeId {
pub const EMPTY: ArchetypeId = ArchetypeId(0);
pub const RESOURCE: ArchetypeId = ArchetypeId(1);
pub const INVALID: ArchetypeId = ArchetypeId(usize::MAX);
pub const EMPTY: ArchetypeId = unsafe { ArchetypeId::new_unchecked(0) };
james7132 marked this conversation as resolved.
Show resolved Hide resolved
pub const RESOURCE: ArchetypeId = unsafe { ArchetypeId::new_unchecked(1) };

/// Creates a new [`ArchetypeId`].
///
/// # Panics
/// This will panic if `index` is `usize::MAX`.
#[inline]
pub const fn new(index: usize) -> Self {
ArchetypeId(index)
pub fn new(index: usize) -> Self {
Self(NonMaxUsize::new(index).expect("ArchetypeID cannot be created from usize::MAX"))
}

/// Creates a new [`ArchetypeId`] without checking for validity of the
/// provided index.
///
/// # Safety
/// `index` must not be `usize::MAX`
#[inline]
pub fn index(self) -> usize {
self.0
pub const unsafe fn new_unchecked(index: usize) -> Self {
james7132 marked this conversation as resolved.
Show resolved Hide resolved
Self(NonMaxUsize::new_unchecked(index))
}

/// Gets the corresponding index for the ID.
#[inline]
pub const fn index(self) -> usize {
self.0.get()
}
}

Expand Down Expand Up @@ -335,28 +350,42 @@ pub struct ArchetypeIdentity {
}

#[derive(Debug, Copy, Clone, Eq, PartialEq, Hash)]
pub struct ArchetypeComponentId(usize);
pub struct ArchetypeComponentId(NonMaxUsize);

impl ArchetypeComponentId {
/// Creates a new [`ArchetypeComponentId`] from an index without
/// checking for the type's invariants.
///
/// # Safety
/// `index` must not be [`usize::MAX`].
#[inline]
pub const fn new(index: usize) -> Self {
Self(index)
pub const unsafe fn new_unchecked(index: usize) -> Self {
Self(NonMaxUsize::new_unchecked(index))
}

/// Creates a new [`ArchetypeComponentId`] from an index.
///
/// # Panic
/// This function will panic if `index` is equal to [`usize::MAX`].
#[inline]
pub fn new(index: usize) -> Self {
Self(NonMaxUsize::new(index).unwrap())
}

#[inline]
pub fn index(self) -> usize {
self.0
self.0.get()
}
}

impl SparseSetIndex for ArchetypeComponentId {
#[inline]
fn sparse_set_index(&self) -> usize {
self.0
self.index()
}

fn get_sparse_set_index(value: usize) -> Self {
Self(value)
Self::new(value)
}
}

Expand Down Expand Up @@ -486,15 +515,15 @@ impl Archetypes {
let archetypes = &mut self.archetypes;
let archetype_component_count = &mut self.archetype_component_count;
let mut next_archetype_component_id = move || {
let id = ArchetypeComponentId(*archetype_component_count);
let id = ArchetypeComponentId::new(*archetype_component_count);
*archetype_component_count += 1;
id
};
*self
.archetype_ids
.entry(archetype_identity)
.or_insert_with(move || {
let id = ArchetypeId(archetypes.len());
let id = ArchetypeId::new(archetypes.len());
james7132 marked this conversation as resolved.
Show resolved Hide resolved
let table_archetype_components = (0..table_components.len())
.map(|_| next_archetype_component_id())
.collect();
Expand Down Expand Up @@ -540,3 +569,16 @@ impl IndexMut<ArchetypeId> for Archetypes {
&mut self.archetypes[index.index()]
}
}

#[cfg(test)]
mod test {
use super::ArchetypeComponentId;

#[test]
pub fn test_archetyype_component_id_size_optimized() {
assert_eq!(
core::mem::size_of::<ArchetypeComponentId>(),
core::mem::size_of::<Option<ArchetypeComponentId>>(),
);
}
}
53 changes: 43 additions & 10 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ use crate::{
};
use bevy_ecs_macros::all_tuples;
use bevy_ptr::OwningPtr;
use nonmax::NonMaxUsize;
use std::{any::TypeId, collections::HashMap};

/// An ordered collection of [`Component`]s.
Expand Down Expand Up @@ -130,12 +131,31 @@ macro_rules! tuple_impl {
all_tuples!(tuple_impl, 0, 15, C);

#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
pub struct BundleId(usize);
pub struct BundleId(NonMaxUsize);

impl BundleId {
/// Creates a new [`BundleId`] from an index without checking for the
/// type's invariants.
///
/// # Safety
/// `value` must not be [`usize::MAX`].
#[inline]
pub const unsafe fn new_unchecked(value: usize) -> Self {
Self(NonMaxUsize::new_unchecked(value))
}

/// Creates a new [`BundleId`] from an index.
///
/// # Panic
/// This function will panic if `value` is equal to [`usize::MAX`].
#[inline]
pub fn new(value: usize) -> Self {
Self(NonMaxUsize::new(value).unwrap())
}

#[inline]
pub fn index(self) -> usize {
self.0
self.0.get()
}
}

Expand All @@ -146,7 +166,7 @@ impl SparseSetIndex for BundleId {
}

fn get_sparse_set_index(value: usize) -> Self {
Self(value)
Self::new(value)
}
}

Expand Down Expand Up @@ -445,10 +465,10 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
InsertBundleResult::NewArchetypeSameTable { new_archetype } => {
let result = self.archetype.swap_remove(location.index);
if let Some(swapped_entity) = result.swapped_entity {
self.entities.meta[swapped_entity.id as usize].location = location;
self.entities.meta[swapped_entity.id as usize].location = Some(location);
}
let new_location = new_archetype.allocate(entity, result.table_row);
self.entities.meta[entity.id as usize].location = new_location;
self.entities.meta[entity.id as usize].location = Some(new_location);

// PERF: this could be looked up during Inserter construction and stored (but borrowing makes this nasty)
let add_bundle = self
Expand All @@ -473,15 +493,15 @@ impl<'a, 'b> BundleInserter<'a, 'b> {
} => {
let result = self.archetype.swap_remove(location.index);
if let Some(swapped_entity) = result.swapped_entity {
self.entities.meta[swapped_entity.id as usize].location = location;
self.entities.meta[swapped_entity.id as usize].location = Some(location);
}
// PERF: store "non bundle" components in edge, then just move those to avoid
// redundant copies
let move_result = self
.table
.move_to_superset_unchecked(result.table_row, *new_table);
let new_location = new_archetype.allocate(entity, move_result.new_row);
self.entities.meta[entity.id as usize].location = new_location;
self.entities.meta[entity.id as usize].location = Some(new_location);

// if an entity was moved into this entity's table spot, update its table row
if let Some(swapped_entity) = move_result.swapped_entity {
Expand Down Expand Up @@ -557,7 +577,7 @@ impl<'a, 'b> BundleSpawner<'a, 'b> {
self.change_tick,
bundle,
);
self.entities.meta[entity.id as usize].location = location;
self.entities.meta[entity.id as usize].location = Some(location);

location
}
Expand Down Expand Up @@ -598,7 +618,7 @@ impl Bundles {
let bundle_infos = &mut self.bundle_infos;
let id = self.bundle_ids.entry(TypeId::of::<T>()).or_insert_with(|| {
let component_ids = T::component_ids(components, storages);
let id = BundleId(bundle_infos.len());
let id = BundleId::new(bundle_infos.len());
// SAFE: T::component_id ensures info was created
let bundle_info = unsafe {
initialize_bundle(std::any::type_name::<T>(), component_ids, id, components)
Expand All @@ -607,7 +627,7 @@ impl Bundles {
id
});
// SAFE: index either exists, or was initialized
unsafe { self.bundle_infos.get_unchecked(id.0) }
unsafe { self.bundle_infos.get_unchecked(id.index()) }
}
}

Expand Down Expand Up @@ -643,3 +663,16 @@ unsafe fn initialize_bundle(
storage_types,
}
}

#[cfg(test)]
mod test {
use super::BundleId;

#[test]
pub fn test_bundle_id_size_optimized() {
assert_eq!(
core::mem::size_of::<BundleId>(),
core::mem::size_of::<Option<BundleId>>(),
);
}
}
Loading