-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
[Merged by Bors] - Immutable sparse sets for metadata storage #4928
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
Changes from 12 commits
767753a
7e85fe6
b49848f
c759212
c46c186
c723c1c
6c7a269
7c9ffed
43f2b0e
89dfb58
3193bc7
0694407
4a881a7
c90b998
84ba5d4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -2,7 +2,7 @@ use crate::{ | |
component::{ComponentId, ComponentInfo, ComponentTicks, Components}, | ||
entity::Entity, | ||
query::debug_checked_unreachable, | ||
storage::{blob_vec::BlobVec, SparseSet}, | ||
storage::{blob_vec::BlobVec, ImmutableSparseSet, SparseSet}, | ||
}; | ||
use bevy_ptr::{OwningPtr, Ptr, PtrMut}; | ||
use bevy_utils::HashMap; | ||
|
@@ -262,31 +262,68 @@ impl Column { | |
} | ||
} | ||
|
||
pub struct Table { | ||
/// A builder type for constructing [`Table`]s. | ||
/// | ||
/// - Use [`with_capacity`] to initialize the builder. | ||
/// - Repeatedly call [`add_column`] to add columns for components. | ||
/// - Finalize with [`build`] to get the constructed [`Table`]. | ||
/// | ||
/// [`with_capacity`]: Self::with_capacity | ||
/// [`add_column`]: Self::add_column | ||
/// [`build`]: Self::build | ||
pub(crate) struct TableBuilder { | ||
columns: SparseSet<ComponentId, Column>, | ||
entities: Vec<Entity>, | ||
capacity: usize, | ||
} | ||
|
||
impl Table { | ||
pub(crate) fn with_capacity(capacity: usize, column_capacity: usize) -> Table { | ||
impl TableBuilder { | ||
/// Creates a blank [`Table`], allocating space for `column_capacity` columns | ||
/// with the capacity to hold `capacity` entities worth of components each. | ||
pub fn with_capacity(capacity: usize, column_capacity: usize) -> Self { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is there a chance that we end up realloc-ing our tables on construction in some / all cases, due to the fact that with_capacity isn't guaranteed to allocate exactly There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is, but unless we're constantly making new There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The frequency might go up drastically if we start doing "fragmenting relations". In that context the bytes saved might not be worth it. Hard to say what to value more. I guess if/when we implement fragmenting relations we can let benchmarks decide. But this level of nuance is easy to forget about. |
||
Self { | ||
columns: SparseSet::with_capacity(column_capacity), | ||
entities: Vec::with_capacity(capacity), | ||
capacity, | ||
} | ||
} | ||
|
||
#[inline] | ||
pub fn entities(&self) -> &[Entity] { | ||
&self.entities | ||
} | ||
|
||
pub(crate) fn add_column(&mut self, component_info: &ComponentInfo) { | ||
pub fn add_column(&mut self, component_info: &ComponentInfo) { | ||
self.columns.insert( | ||
component_info.id(), | ||
Column::with_capacity(component_info, self.entities.capacity()), | ||
Column::with_capacity(component_info, self.capacity), | ||
); | ||
} | ||
|
||
pub fn build(self) -> Table { | ||
Table { | ||
columns: self.columns.into_immutable(), | ||
entities: Vec::with_capacity(self.capacity), | ||
} | ||
} | ||
} | ||
|
||
/// A column-oriented [structure-of-arrays] based storage for [`Component`]s of entities | ||
/// in a [`World`]. | ||
/// | ||
/// Conceptually, a `Table` can be thought of as an `HashMap<ComponentId, Column>`, where | ||
/// each `Column` is a type-erased `Vec<T: Component>`. Each row corresponds to a single entity | ||
/// (i.e. index 3 in Column A and index 3 in Column B point to different components on the same | ||
/// entity). Fetching components from a table involves fetching the associated column for a | ||
/// component type (via it's [`ComponentId`]), then fetching the entity's row within that column. | ||
/// | ||
/// [structure-of-arrays]: https://en.wikipedia.org/wiki/AoS_and_SoA#Structure_of_arrays | ||
/// [`Component`]: crate::component::Component | ||
/// [`World`]: crate::world::World | ||
pub struct Table { | ||
james7132 marked this conversation as resolved.
Show resolved
Hide resolved
|
||
columns: ImmutableSparseSet<ComponentId, Column>, | ||
entities: Vec<Entity>, | ||
} | ||
|
||
impl Table { | ||
#[inline] | ||
pub fn entities(&self) -> &[Entity] { | ||
&self.entities | ||
} | ||
|
||
/// Removes the entity at the given row and returns the entity swapped in to replace it (if an | ||
/// entity was swapped in) | ||
/// | ||
|
@@ -457,11 +494,6 @@ impl Table { | |
self.entities.capacity() | ||
} | ||
|
||
#[inline] | ||
pub fn component_capacity(&self) -> usize { | ||
self.columns.capacity() | ||
} | ||
|
||
#[inline] | ||
pub fn is_empty(&self) -> bool { | ||
self.entities.is_empty() | ||
|
@@ -495,7 +527,7 @@ pub struct Tables { | |
|
||
impl Default for Tables { | ||
fn default() -> Self { | ||
let empty_table = Table::with_capacity(0, 0); | ||
let empty_table = TableBuilder::with_capacity(0, 0).build(); | ||
Tables { | ||
tables: vec![empty_table], | ||
table_ids: HashMap::default(), | ||
|
@@ -548,11 +580,11 @@ impl Tables { | |
.raw_entry_mut() | ||
.from_key(component_ids) | ||
.or_insert_with(|| { | ||
let mut table = Table::with_capacity(0, component_ids.len()); | ||
let mut table = TableBuilder::with_capacity(0, component_ids.len()); | ||
for component_id in component_ids { | ||
table.add_column(components.get_info_unchecked(*component_id)); | ||
} | ||
tables.push(table); | ||
tables.push(table.build()); | ||
(component_ids.to_vec(), TableId(tables.len() - 1)) | ||
}); | ||
|
||
|
@@ -601,7 +633,7 @@ mod tests { | |
use crate::{ | ||
component::{ComponentTicks, Components}, | ||
entity::Entity, | ||
storage::Table, | ||
storage::TableBuilder, | ||
}; | ||
#[derive(Component)] | ||
struct W<T>(T); | ||
|
@@ -612,8 +644,9 @@ mod tests { | |
let mut storages = Storages::default(); | ||
let component_id = components.init_component::<W<usize>>(&mut storages); | ||
let columns = &[component_id]; | ||
let mut table = Table::with_capacity(0, columns.len()); | ||
table.add_column(components.get_info(component_id).unwrap()); | ||
let mut builder = TableBuilder::with_capacity(0, columns.len()); | ||
builder.add_column(components.get_info(component_id).unwrap()); | ||
let mut table = builder.build(); | ||
let entities = (0..200).map(Entity::from_raw).collect::<Vec<_>>(); | ||
for entity in &entities { | ||
// SAFETY: we allocate and immediately set data afterwards | ||
|
Uh oh!
There was an error while loading. Please reload this page.