
# 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>
83 lines
3.5 KiB
Markdown
83 lines
3.5 KiB
Markdown
---
|
|
title: Entity Spawn Ticks
|
|
authors: ["@urben1680", "@specificprotagonist"]
|
|
pull_requests: [19047, 19350]
|
|
---
|
|
|
|
Keeping track which entities have been spawned since the last time a system ran could only be done indirectly by inserting marker components and do your logic on entities that match an `Added<MyMarker>` filter or in `MyMarker`'s `on_add` hook.
|
|
|
|
This has the issue however that not every add reacts on a spawn but also on insertions at existing entities. Sometimes you cannot even add your marker because the spawn call is hidden in some non-public API.
|
|
|
|
The new `SpawnDetails` query data and `Spawned` query filter enable you to find recently spawned entities without any marker components.
|
|
|
|
## `SpawnDetails`
|
|
|
|
Use this in your query when you want to get information about the entity's spawn. You might want to do that for debug purposes, using the struct's `Debug` implementation.
|
|
|
|
You can also get specific information via methods. The following example prints the entity id (prefixed with "new" if it showed up for the first time), the `Tick` it spawned at and, if the `track_location` feature is activated, the source code location where it was spawned. Said feature is not enabled by default because it comes with a runtime cost.
|
|
|
|
```rs
|
|
fn print_spawn_details(query: Query<(Entity, SpawnDetails)>) {
|
|
for (entity, spawn_details) in &query {
|
|
if spawn_details.is_spawned() {
|
|
print!("new ");
|
|
}
|
|
print!(
|
|
"entity {entity:?} spawned at {:?}",
|
|
spawn_details.spawned_at()
|
|
);
|
|
match spawn_details.spawned_by().into_option() {
|
|
Some(location) => println!(" by {location:?}"),
|
|
None => println!()
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## `Spawned`
|
|
|
|
Use this filter in your query if you are only interested in entities that were spawned after the last time your system ran.
|
|
|
|
Note that this, like `Added<T>` and `Changed<T>`, is a non-archetypal filter. This means that your query could still go through millions of entities without yielding any recently spawned ones. Unlike filters like `With<T>` which can easily skip all entities that do not have `T` without checking them one-by-one.
|
|
|
|
Because of this, these systems have roughly the same performance:
|
|
|
|
```rs
|
|
fn system1(query: Query<Entity, Spawned>) {
|
|
for entity in &query { /* entity spawned */ }
|
|
}
|
|
|
|
fn system2(query: Query<(Entity, SpawnDetails)>) {
|
|
for (entity, spawned) in &query {
|
|
if spawned.is_spawned() { /* entity spawned */ }
|
|
}
|
|
}
|
|
```
|
|
|
|
## Getter methods
|
|
|
|
Getting around this weakness of non-archetypal filters can be to check only specific entities for their spawn tick: The method `spawned_at` was added to all entity pointer structs, such as `EntityRef`, `EntityMut` and `EntityWorldMut`.
|
|
|
|
In this example we want to filter for entities that were spawned after a certain `tick`:
|
|
|
|
```rs
|
|
fn filter_spawned_after(
|
|
entities: impl IntoIterator<Item = Entity>,
|
|
world: &World,
|
|
tick: Tick,
|
|
) -> impl Iterator<Item = Entity> {
|
|
let now = world.last_change_tick();
|
|
entities.into_iter().filter(move |entity| world
|
|
.entity(*entity)
|
|
.spawned_at()
|
|
.is_newer_than(tick, now)
|
|
)
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
The tick is stored in `Entities`. It's method `entity_get_spawned_or_despawned_at` not only returns when a living entity spawned at, it also returns when a despawned entity found it's bitter end.
|
|
|
|
Note however that despawned entities can be replaced by Bevy at any following spawn. Then this method returns `None` for the despawned entity. The same is true if the entity is not even spawned yet, only allocated.
|