bevyengine / bevy

A refreshingly simple data-driven game engine built in Rust
https://bevyengine.org
Apache License 2.0
35.64k stars 3.52k forks source link

`Commands::get_or_spawn` can return invalid `EntityCommands` #5960

Closed Nilirad closed 2 years ago

Nilirad commented 2 years ago

Bevy version: main.

While working on #5955 i noted that Commands::get_or_spawn had a strange implementation. I did some experiments and I found that the method can return EntityCommands for an invalid entity if the Entity passed as argument has been deleted replaced by a newer generation with the same id.

Code for reproduction

```rust use bevy::prelude::*; #[derive(StageLabel)] struct Stage1; #[derive(StageLabel)] struct Stage2; #[derive(StageLabel)] struct Stage3; #[derive(StageLabel)] struct Stage4; #[derive(Resource, Default)] struct Gen0Entity(Option); #[derive(Resource, Default)] struct Gen1Entity(Option); fn main() { App::empty() .init_resource::() .init_resource::() .add_stage(Stage1, SystemStage::single(stage_1)) .add_stage(Stage2, SystemStage::single(stage_2)) .add_stage(Stage3, SystemStage::single(stage_3)) .add_stage(Stage4, SystemStage::single(stage_4)) .run(); } /// Spawns entity 0v0. fn stage_1(mut commands: Commands, mut gen_0_entity: ResMut) { let id = commands.spawn().id(); gen_0_entity.0 = Some(id); dbg!(id); } /// Despawns entity 0v0. fn stage_2(mut commands: Commands, gen_0_entity: Res) { commands.entity(gen_0_entity.0.unwrap()).despawn(); } /// Spawns entity 0v1. fn stage_3(mut commands: Commands, mut gen_1_entity: ResMut) { let id = commands.spawn().id(); gen_1_entity.0 = Some(id); dbg!(id); } /// Successfully gets invalid `EntityCommands` for despawned 0v0, while 0v1 exists. fn stage_4(mut commands: Commands, gen_0_entity: Res) { dbg!(commands.get_or_spawn(gen_0_entity.0.unwrap()).id()); } ```

Possible solution

Return an Option just like World::get_or_spawn does.

Nilirad commented 2 years ago

As explained in this comment, this is a non-issue.