-
-
Notifications
You must be signed in to change notification settings - Fork 4.2k
Track spawn Tick
of entities, offer methods, query data SpawnDetails
and query filter Spawned
#19047
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
Conversation
I really like this idea: it's useful and consistent. That said, we should see how this affects spawning performance, both in the worst case and on more realistic cases. |
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.
Over all, looks good. Just some minor questions, etc.
One thing we should consider is benching this. I have a feeling that having a separate vec for SpawnedOrDespawned
, separate from EntityMeta
would be faster. Almost every iteration cares about where the entity is. Asking when it was spawned and by who is more like a map thing IIUC. But this might be better as a separate PR.
Another option is making an observer and event for Spawned
. I feel like that's going to be the primary use of this, and exposing an observer might be better anyway for this use case. Then again, it makes sense to ask when an entity was spawned. Ex: This particle entity has been around for 100 ticks; despawn it. (Not that each particle should be an entity.) In other words, this might be a cheaper alternative to a timer component in some cases.
Anyway, that's my thoughts. I'm generally on board with this, provided the separate vecs and observer APIs are investigated (maybe in future prs).
Co-authored-by: Eagster <79881080+ElliottjPierce@users.noreply.github.com>
Co-authored-by: Eagster <79881080+ElliottjPierce@users.noreply.github.com>
Regarding introducing an event for observers, I considered it but I think the PR is big enough as is. I also imagine spawns triggering observers will have a more noticeable performance impact on all spawns than what I did here. |
…_spawn_despawn as a variant of Entities::set
…into track-spawn-tick
I did another change. Previously, However, since with this change the method is no longer noop even when you deactivate tracking locations, this always added to the number of accesses on So I removed that method entirely and added
When doing spawn benchmarks, this change greatly reduced the regressions from up to +6% down to +/-1%. I will do a separate post with benchmarks once I am sure I do them correctly. |
I wrote release notes. I wanted to do it detailed, I hope not too much. rendered (up to date to following commits) I was also close to give a short recap that |
Oh these are perfect! Thank you!!! |
Made a sentence more clear
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 stuff! Thanks for the release note; it'll need an editing pass but capturing the motivation and usage is the key thing at this stage.
Merge conflicts, sorry! |
@alice-i-cecile resolved them. 👍 |
…ls` and query filter `Spawned` (bevyengine#19047) In my own project I was encountering the issue to find out which entities were spawned after applying commands. I began maintaining a vector of all entities with generational information before and after applying the command and diffing it. This was awfully complicated though and has no constant complexity but grows with the number of entities. Looking at `EntyMeta` it seemed obvious to me that struct can track the tick just as it does with `MaybeLocation`, updated from the same call. After that it became almost a given to also introduce query data `SpawnDetails` which offers methods to get the spawn tick and location, and query filter `Spawned` that filters entities out that were not spawned since the last run. I expanded a few tests and added new ones, though maybe I forgot a group of tests that should be extended too. I basically searched `bevy_ecs` for mentions of `Changed` and `Added` to see where the tests and docs are. Benchmarks of spawn/despawn can be found [here](bevyengine#19047 (comment)). --- From the added docs, systems with equal complexity since the filter is not archetypal: ```rs fn system1(q: Query<Entity, Spawned>) { for entity in &q { /* entity spawned */ } } fn system2(query: Query<(Entity, SpawnDetails)>) { for (entity, spawned) in &query { if spawned.is_spawned() { /* entity spawned */ } } } ``` `SpawnedDetails` has a few more methods: ```rs fn print_spawn_details(query: Query<(Entity, SpawnDetails)>) { for (entity, spawn_details) in &query { if spawn_details.is_spawned() { print!("new "); } println!( "entity {:?} spawned at {:?} by {:?}", entity, spawn_details.spawned_at(), spawn_details.spawned_by() ); } } ``` No public api was changed, I only added to it. That is why I added no migration guide. - query data `SpawnDetails` - query filter `Spawned` - method `Entities::entity_get_spawned_or_despawned_at` - method `EntityRef::spawned_at` - method `EntityMut::spawned_at` - method `EntityWorldMut::spawned_at` - method `UnsafeEntityCell::spawned_at` - method `FilteredEntityRef::spawned_at` - method `FilteredEntityMut::spawned_at` - method `EntityRefExcept::spawned_at` - method `EntityMutExcept::spawned_at` --------- Co-authored-by: Eagster <79881080+ElliottjPierce@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
…ls` and query filter `Spawned` (bevyengine#19047) # Objective In my own project I was encountering the issue to find out which entities were spawned after applying commands. I began maintaining a vector of all entities with generational information before and after applying the command and diffing it. This was awfully complicated though and has no constant complexity but grows with the number of entities. ## Solution Looking at `EntyMeta` it seemed obvious to me that struct can track the tick just as it does with `MaybeLocation`, updated from the same call. After that it became almost a given to also introduce query data `SpawnDetails` which offers methods to get the spawn tick and location, and query filter `Spawned` that filters entities out that were not spawned since the last run. ## Testing I expanded a few tests and added new ones, though maybe I forgot a group of tests that should be extended too. I basically searched `bevy_ecs` for mentions of `Changed` and `Added` to see where the tests and docs are. Benchmarks of spawn/despawn can be found [here](bevyengine#19047 (comment)). --- ## Showcase From the added docs, systems with equal complexity since the filter is not archetypal: ```rs fn system1(q: Query<Entity, Spawned>) { for entity in &q { /* entity spawned */ } } fn system2(query: Query<(Entity, SpawnDetails)>) { for (entity, spawned) in &query { if spawned.is_spawned() { /* entity spawned */ } } } ``` `SpawnedDetails` has a few more methods: ```rs fn print_spawn_details(query: Query<(Entity, SpawnDetails)>) { for (entity, spawn_details) in &query { if spawn_details.is_spawned() { print!("new "); } println!( "entity {:?} spawned at {:?} by {:?}", entity, spawn_details.spawned_at(), spawn_details.spawned_by() ); } } ``` ## Changes No public api was changed, I only added to it. That is why I added no migration guide. - query data `SpawnDetails` - query filter `Spawned` - method `Entities::entity_get_spawned_or_despawned_at` - method `EntityRef::spawned_at` - method `EntityMut::spawned_at` - method `EntityWorldMut::spawned_at` - method `UnsafeEntityCell::spawned_at` - method `FilteredEntityRef::spawned_at` - method `FilteredEntityMut::spawned_at` - method `EntityRefExcept::spawned_at` - method `EntityMutExcept::spawned_at` --------- Co-authored-by: Eagster <79881080+ElliottjPierce@users.noreply.github.com> Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
# Objective #19047 added an `MaybeUninit` field to `EntityMeta`, but did not guarantee that it will be initialized before access: ```rust let mut world = World::new(); let id = world.entities().reserve_entity(); world.flush(); world.entity(id); ``` <details> <summary>Miri Error</summary> ``` error: Undefined Behavior: using uninitialized data, but this operation requires initialized memory --> /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/entity/mod.rs:1121:26 | 1121 | unsafe { meta.spawned_or_despawned.assume_init() } | ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ using uninitialized data, but this operation requires initialized memory | = help: this indicates a bug in the program: it performed an invalid operation, and caused Undefined Behavior = help: see https://doc.rust-lang.org/nightly/reference/behavior-considered-undefined.html for further information = note: BACKTRACE: = note: inside closure at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/entity/mod.rs:1121:26: 1121:65 = note: inside `std::option::Option::<&bevy_ecs::entity::EntityMeta>::map::<bevy_ecs::entity::SpawnedOrDespawned, {closure@bevy_ecs::entity::Entities::entity_get_spawned_or_despawned::{closure#1}}>` at /home/vj/.rustup/toolchains/nightly-x86_64-unknown-linux-gnu/lib/rustlib/src/rust/library/core/src/option.rs:1144:29: 1144:33 = note: inside `bevy_ecs::entity::Entities::entity_get_spawned_or_despawned` at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/entity/mod.rs:1112:9: 1122:15 = note: inside closure at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/entity/mod.rs:1094:13: 1094:57 = note: inside `bevy_ecs::change_detection::MaybeLocation::<std::option::Option<&std::panic::Location<'_>>>::new_with_flattened::<{closure@bevy_ecs::entity::Entities::entity_get_spawned_or_despawned_by::{closure#0}}>` at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/change_detection.rs:1371:20: 1371:24 = note: inside `bevy_ecs::entity::Entities::entity_get_spawned_or_despawned_by` at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/entity/mod.rs:1093:9: 1096:11 = note: inside `bevy_ecs::entity::Entities::entity_does_not_exist_error_details` at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/entity/mod.rs:1163:23: 1163:70 = note: inside `bevy_ecs::entity::EntityDoesNotExistError::new` at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/entity/mod.rs:1182:22: 1182:74 = note: inside `bevy_ecs::world::unsafe_world_cell::UnsafeWorldCell::<'_>::get_entity` at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/world/unsafe_world_cell.rs:368:20: 368:73 = note: inside `<bevy_ecs::entity::Entity as bevy_ecs::world::WorldEntityFetch>::fetch_ref` at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/world/entity_fetch.rs:207:21: 207:42 = note: inside `bevy_ecs::world::World::get_entity::<bevy_ecs::entity::Entity>` at /home/vj/workspace/rust/bevy/crates/bevy_ecs/src/world/mod.rs:911:18: 911:42 note: inside `main` --> src/main.rs:12:15 | 12 | world.entity(id); | ``` </details> ## Solution - remove the existing `MaybeUninit` in `EntityMeta.spawned_or_despawned` - initialize during flush. This is not needed for soundness, but not doing this means we can't return a sensible location/tick for flushed entities. ## Testing Test via the snippet above (also added equivalent test). --------- Co-authored-by: urben1680 <55257931+urben1680@users.noreply.github.com>
unsafe { | ||
world | ||
.entities_mut() | ||
.set_spawned_or_despawned_by(self.entity.index(), caller); |
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.
caller is no longer set for entities not swap_remove
d? #21293
(sorry, I commented this on merge commit initially)
# Objective Fixes #21293 Fixes #17314 to ensure that this is tested correctly. ## Solution when despawning an entity, previously the swapped in (archetype) or moved in entity (table) (which both require extra bookkeeping to update archetype or table rows) were marked as `spawned_or_despawned` by the location and tick that the to-be-removed entity was meant to be marked, while the to-be-removed entity wasn't marked. As pointed out by @akimakinai in [#19047](#19047), I've re-added the correct `mark_spawn_despawn` call to `despawn_with_caller`. ## Testing I've added a test `spawned_after_swap_remove` that ensures that despawning an entity by swapping doesn't effect other entities `spawned_or_despawned` location, and that it does effect the despawned entity's index's `spawned_or_despawned` location. Co-Authored By: waterwhisperer24@qq.com --------- Co-authored-by: WaterWhisperer <waterwhisperer24@qq.com>
# Objective Fixes #21293 Fixes #17314 to ensure that this is tested correctly. ## Solution when despawning an entity, previously the swapped in (archetype) or moved in entity (table) (which both require extra bookkeeping to update archetype or table rows) were marked as `spawned_or_despawned` by the location and tick that the to-be-removed entity was meant to be marked, while the to-be-removed entity wasn't marked. As pointed out by @akimakinai in [#19047](#19047), I've re-added the correct `mark_spawn_despawn` call to `despawn_with_caller`. ## Testing I've added a test `spawned_after_swap_remove` that ensures that despawning an entity by swapping doesn't effect other entities `spawned_or_despawned` location, and that it does effect the despawned entity's index's `spawned_or_despawned` location. Co-Authored By: waterwhisperer24@qq.com --------- Co-authored-by: WaterWhisperer <waterwhisperer24@qq.com>
…1364) # Objective Fixes bevyengine#21293 Fixes bevyengine#17314 to ensure that this is tested correctly. ## Solution when despawning an entity, previously the swapped in (archetype) or moved in entity (table) (which both require extra bookkeeping to update archetype or table rows) were marked as `spawned_or_despawned` by the location and tick that the to-be-removed entity was meant to be marked, while the to-be-removed entity wasn't marked. As pointed out by @akimakinai in [bevyengine#19047](bevyengine#19047), I've re-added the correct `mark_spawn_despawn` call to `despawn_with_caller`. ## Testing I've added a test `spawned_after_swap_remove` that ensures that despawning an entity by swapping doesn't effect other entities `spawned_or_despawned` location, and that it does effect the despawned entity's index's `spawned_or_despawned` location. Co-Authored By: waterwhisperer24@qq.com --------- Co-authored-by: WaterWhisperer <waterwhisperer24@qq.com>
Objective
In my own project I was encountering the issue to find out which entities were spawned after applying commands. I began maintaining a vector of all entities with generational information before and after applying the command and diffing it. This was awfully complicated though and has no constant complexity but grows with the number of entities.
Solution
Looking at
EntyMeta
it seemed obvious to me that struct can track the tick just as it does withMaybeLocation
, updated from the same call. After that it became almost a given to also introduce query dataSpawnDetails
which offers methods to get the spawn tick and location, and query filterSpawned
that filters entities out that were not spawned since the last run.Testing
I expanded a few tests and added new ones, though maybe I forgot a group of tests that should be extended too. I basically searched
bevy_ecs
for mentions ofChanged
andAdded
to see where the tests and docs are.Benchmarks of spawn/despawn can be found here.
Showcase
From the added docs, systems with equal complexity since the filter is not archetypal:
SpawnedDetails
has a few more methods:Changes
No public api was changed, I only added to it. That is why I added no migration guide.
SpawnDetails
Spawned
Entities::entity_get_spawned_or_despawned_at
EntityRef::spawned_at
EntityMut::spawned_at
EntityWorldMut::spawned_at
UnsafeEntityCell::spawned_at
FilteredEntityRef::spawned_at
FilteredEntityMut::spawned_at
EntityRefExcept::spawned_at
EntityMutExcept::spawned_at