-
-
Notifications
You must be signed in to change notification settings - Fork 3.9k
Remove invalid entity locations #19433
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
base: main
Are you sure you want to change the base?
Remove invalid entity locations #19433
Conversation
crates/bevy_ecs/src/entity/mod.rs
Outdated
if meta.generation != EntityGeneration::FIRST | ||
|| meta.location.archetype_id != ArchetypeId::INVALID | ||
{ | ||
if meta.location.is_some() { |
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.
I think this is right, but honestly, not even sure why we need the if.
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.
I think this is not right, the tick may also be indirectly returned by entity_get_spawned_or_despawned
even if meta.location
is None
in some cases.
If the the tick is considered valid in such a case, then it needs to be checked upon in this method here too.
It exposes that we somehow lost the ability to reliably detect when the tick is indeed unused and only a default value. But honestly I would not bother given how cold this method here is. Just check all ticks in here and with that be sure all edge cases are covered.
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.
I agree. I'll remove the if.
crates/bevy_ecs/src/entity/mod.rs
Outdated
@@ -973,7 +972,7 @@ impl Entities { | |||
/// - `location` must be valid for the entity at `index` or immediately made valid afterwards | |||
/// before handing control to unknown code. | |||
#[inline] | |||
pub(crate) unsafe fn set(&mut self, index: u32, location: EntityLocation) { | |||
pub(crate) unsafe fn set(&mut self, index: u32, location: Option<EntityLocation>) { |
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.
It seems that this method is always called with Some
location. Do you expect calls with None
in the future or another PR?
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.
I added it for completeness but yes.
We used to have alloc_at which let you respawn an entity and reuse its index. There have been requests to support that again. That's one of the next steps in my tracking issue. Basically, we'll set the location to None instead of fully despawning.
pub(crate) fn assert_not_despawned(&self) { | ||
if self.location.archetype_id == ArchetypeId::INVALID { | ||
self.panic_despawned(); | ||
pub(crate) fn assert_not_despawned(&self) -> EntityLocation { |
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.
Since this and EntityWorldMut::location
do the same now, this one can probably be removed and usages switch to the location
method.
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.
That's a good idea.
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.
Turns out it's used for other things too, so I've kept it but reverted to how it was.
crates/bevy_ecs/src/entity/mod.rs
Outdated
if meta.generation != EntityGeneration::FIRST | ||
|| meta.location.archetype_id != ArchetypeId::INVALID | ||
{ | ||
if meta.location.is_some() { |
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.
I think this is not right, the tick may also be indirectly returned by entity_get_spawned_or_despawned
even if meta.location
is None
in some cases.
If the the tick is considered valid in such a case, then it needs to be checked upon in this method here too.
It exposes that we somehow lost the ability to reliably detect when the tick is indeed unused and only a default value. But honestly I would not bother given how cold this method here is. Just check all ticks in here and with that be sure all edge cases are covered.
impl EntityLocation { | ||
/// location for **pending entity** and **invalid entity** | ||
pub(crate) const INVALID: EntityLocation = EntityLocation { | ||
archetype_id: ArchetypeId::INVALID, | ||
archetype_row: ArchetypeRow::INVALID, | ||
table_id: TableId::INVALID, | ||
table_row: TableRow::INVALID, | ||
}; | ||
} |
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.
feels_good_man.jpg
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! Just left some style suggestions.
crates/bevy_ecs/src/entity/mod.rs
Outdated
Some(meta.location) | ||
(meta.generation == entity.generation) | ||
.then_some(meta.location) | ||
.flatten() |
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.
then_some().flatten()
is a little odd. I think that might be Option::filter()
?
Hmm, or could this whole thing be replaced with filter().and_then()
like
self.meta.get(entity.index() as usize)
.filter(|meta| meta.generation == entity.generation)
.and_then(|meta| meta.location)
?
(Sorry, I can't get GitHub to let me write that as a suggestion.)
crates/bevy_ecs/src/entity/mod.rs
Outdated
/// Must not be called while reserved entities are awaiting `flush()`. | ||
pub fn free(&mut self, entity: Entity) -> Option<EntityLocation> { | ||
pub fn free(&mut self, entity: Entity) -> Option<Option<EntityLocation>> { |
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.
Option<Option<T>>
can be hard to reason about. Does anyone care about Some(None)
vs None
, or could we flatten this and return a plain Option<EntityLocation>
? It looks like the only place this is called changed from .expect()
to .expect().expect()
, so it doesn't seem to care.
Otherwise, it might be worth returning a Result
with some new enum
that gives names to the two kinds of None
.
Co-Authored-By: Chris Russell <[email protected]>
Co-Authored-By: Chris Russell <[email protected]>
As far as But as a heads up, I think IMO we should expose this fully. Maybe a |
Update: unfortunately tests do seem to need the full location information, so I have added it back. But I used a type alias to hopefully make things a little more clear. |
An alternative could also be |
|
/// | ||
/// Setting a location to `None` is often helpful when you want to destruct an entity or yank it from the ECS without allowing another system to reuse the id for something else. | ||
/// It is also useful for reserving an id; commands will often allocate an `Entity` but not provide it a location until the command is applied. | ||
pub type EntityIdLocation = Option<EntityLocation>; |
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.
I'm not sure if this name is very clear ("location of an entity" versus "location of an entity identifier"). Then again, I'm not sure of a better name (nor am I a maintaner or anything like that).
Objective
This is the first step of #19430 and is a follow up for #19132.
Now that
ArchetypeRow
has a niche, we can useOption
instead of needingINVALID
everywhere.This was especially concerning since
INVALID
really was valid!Using options here made the code clearer and more data-driven.
Solution
Replace all uses of
INVALID
entity locations (and archetype/table rows) withNone
.Testing
CI