-
-
Notifications
You must be signed in to change notification settings - Fork 3.7k
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
Prevent creation of superfluous empty table #16935
Prevent creation of superfluous empty table #16935
Conversation
Welcome, new contributor! Please make sure you've read our contributing guide and we look forward to reviewing your pull request shortly ✨ |
8857d67
to
a571d5b
Compare
a571d5b
to
2583764
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good find, and good tests! Very weird about the cargo test output!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Looks good!
# Objective - To fix a tiny bug in `bevy_ecs::storage::Tables` that, in one case, means it accidentally allocates an additional "empty" `Table`, resulting in two "empty" `Table`s: - The one pre-allocated empty table at index 0 whose index is designed to match up with `TableId::empty()` - One extra empty table, at some non-0 index, that does not match up with `TableId::empty()`. - This PR aims to prevent this extraneous `Table`, ensuring that entities with no components in table-storage reliably have their archetype's table ID be equal to `TableId::empty()`. ## Solution ### Background The issue occurs because: - `Tables` contains: - `tables: Vec<Table>` - The set of all `Table`s allocated in the world. - `table_ids: HashMap<Box<[ComponentId]>, TableId>` - An index to rapidly lookup the `Table` in `tables` by a set of `ComponentId`s. - When `Tables` is constructed it pre-populates the `tables` `Vec` with an empty `Table`. - This ensures that the first entry (index 0) is always the `Table` for entities with no components in table storage. - In particular, `TableId::empty()` is a utility that returns a `TableId` of `0`. - However, the `table_ids` map is not initialised to associate an empty `[ComponentId]` with `TableId` `0`. - This means, the first time a structural change tries to access a `Table` for an archetype with 0 table components: - `Tables::get_id_or_insert` is used to retrieve the target `Table` - The function attempts to lookup the entry in the `table_ids` `HashMap` whose key is the empty `ComponentId` set - The empty `Table` created at startup won't be found, because it was never inserted into `table_ids` - It will instead create a new table, insert it into the `HashMap` (preventing further instances of this issue), and return it. ### Changes - I considered simply initialising the `table_ids` `HashMap` to know about the pre-allocated `Table` - However, I ended up using the proposed solution discussed on Discord [#ecs-dev](https://discord.com/channels/691052431525675048/749335865876021248/1320430933152759958): - Make `Tables::get_id_or_insert` simply early-exit if the requested `component_ids` was empty. - This avoids unnecessarily hashing the empty slice and looking it up in the `HashMap`. - The `table_ids` `HashMap` is not exposed outside this struct, and is only used within `get_id_or_insert`, so it seems wasteful to defensively populate it with the empty `Table`. ## Testing This is my first Bevy contribution, so I don't really know the processes that well. That said: - I have introduced a little test that exercises the original issue and shows that it is now resolved. - I have run the `bevy_ecs` tests locally, so I have reasonable confidence I haven't broken that. - I haven't run any further test suites, mostly as when I tried to run test suites for the whole project it filled my entire SSD with >600GB of target directory output 😱😱😱
Objective
bevy_ecs::storage::Tables
that, in one case, means it accidentally allocates an additional "empty"Table
, resulting in two "empty"Table
s:TableId::empty()
TableId::empty()
.Table
, ensuring that entities with no components in table-storage reliably have their archetype's table ID be equal toTableId::empty()
.Solution
Background
The issue occurs because:
Tables
contains:tables: Vec<Table>
- The set of allTable
s allocated in the world.table_ids: HashMap<Box<[ComponentId]>, TableId>
- An index to rapidly lookup theTable
intables
by a set ofComponentId
s.Tables
is constructed it pre-populates thetables
Vec
with an emptyTable
.Table
for entities with no components in table storage.TableId::empty()
is a utility that returns aTableId
of0
.table_ids
map is not initialised to associate an empty[ComponentId]
withTableId
0
.Table
for an archetype with 0 table components:Tables::get_id_or_insert
is used to retrieve the targetTable
table_ids
HashMap
whose key is the emptyComponentId
setTable
created at startup won't be found, because it was never inserted intotable_ids
HashMap
(preventing further instances of this issue), and return it.Changes
table_ids
HashMap
to know about the pre-allocatedTable
Tables::get_id_or_insert
simply early-exit if the requestedcomponent_ids
was empty.HashMap
.table_ids
HashMap
is not exposed outside this struct, and is only used withinget_id_or_insert
, so it seems wasteful to defensively populate it with the emptyTable
.Testing
This is my first Bevy contribution, so I don't really know the processes that well. That said:
bevy_ecs
tests locally, so I have reasonable confidence I haven't broken that.