Skip to content
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

[Merged by Bors] - Add error messages for the spooky insertions #2581

Closed
wants to merge 3 commits into from

Conversation

DJMcNab
Copy link
Member

@DJMcNab DJMcNab commented Aug 1, 2021

Objective

Sometimes, the unwraps in entity_mut could fail here, if the entity was despawned before this command was applied.

The simplest case involves two command buffers:

use bevy::prelude::*;
fn b(mut commands1: Commands, mut commands2: Commands) {
    let id = commands2.spawn().insert_bundle(()).id();
    commands1.entity(id).despawn();
}
fn main() {
    App::build().add_system(b.system()).run();
}

However, a more complicated version arises in the case of ambiguity:

use std::time::Duration;

use bevy::{app::ScheduleRunnerPlugin, prelude::*};
use rand::Rng;

fn cleanup(mut e: ResMut<Option<Entity>>) {
    *e = None;
}

fn sleep_randomly() {
    let mut rng = rand::thread_rng();
    std::thread::sleep(Duration::from_millis(rng.gen_range(0..50)));
}

fn spawn(mut commands: Commands, mut e: ResMut<Option<Entity>>) {
    *e = Some(commands.spawn().insert_bundle(()).id());
}

fn despawn(mut commands: Commands, e: Res<Option<Entity>>) {
    let mut rng = rand::thread_rng();
    std::thread::sleep(Duration::from_millis(rng.gen_range(0..50)));
    if let Some(e) = *e {
        commands.entity(e).despawn();
    }
}

fn main() {
    App::build()
        .add_system(cleanup.system().label("cleanup"))
        .add_system(sleep_randomly.system().label("before_despawn"))
        .add_system(despawn.system().after("cleanup").after("before_despawn"))
        .add_system(sleep_randomly.system().label("before_spawn"))
        .add_system(spawn.system().after("cleanup").after("before_spawn"))
        .insert_resource(None::<Entity>)
        .add_plugin(ScheduleRunnerPlugin::default())
        .run();
}

In the cases where this example crashes, it's because despawn was ordered before spawn in the topological ordering of systems (which determines when buffers are applied). However, despawn actually ran after spawn, because these systems are ambiguous, so the jiggles in the sleeping time triggered a case where this works.

Solution

  • Give a better error message

@github-actions github-actions bot added the S-Needs-Triage This issue needs to be labelled label Aug 1, 2021
@alice-i-cecile alice-i-cecile added C-Usability A simple quality-of-life change that makes Bevy easier to use A-ECS Entities, components, systems, and events and removed S-Needs-Triage This issue needs to be labelled labels Aug 1, 2021
if let Some(mut entity) = world.get_entity_mut(self.entity) {
entity.insert_bundle(self.bundle);
} else {
panic!("Could not insert a bundle for entity {:?} because it no longer exists.\n\
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"no longer" implies that it did exist at some point, which isn't necessarily true (although this should generally be true)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Perhaps? I think that making error messages 'worse' because someone has manually created an entity id is not the tradeoff I'd prefer to make.

We can change this to "doesn't exist" though if you're adamant.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For docs and error messages I'd prefer to not say things that are "approximately true". Omitting information in the interest of clarity is ok, but I'd prefer to keep this crisp, especially given that the new renderer "spawns at specific entities" / "shares entities across worlds".

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Acted on

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair fair:
image

@alice-i-cecile
Copy link
Member

This should be added for despawn as well, as the entity can fail to exist in the same way.

@@ -162,6 +162,7 @@ pub struct Entities {
/// Once `flush()` is done, `free_cursor` will equal `pending.len()`.
pending: Vec<u32>,
free_cursor: AtomicI64,
/// Stores the number of free entities for [`len`](Entities::len)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wouldn't that Entities::len link just be a link to itself?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so? There's a function also called len, which this should point to

I can check though at some point. It's only an internal documentation anyway, since it's on a private field.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh I didn't see the method with the same name in this diff 👍

Anyway I can't get rust-analyser to correctly link to something from Entities, not sure why...

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rust-analyser's intra doc links syntax is a subset of valid links, from what I've found.

The way to try this is with cargo doc -- --document-private-items (or whatever the precise correct syntax is)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have confirmed that it links correctly

There are several other bugs which running cargo doc --open --document-private-items makes appear, but that's not the job of this PR.

Previously, it paniced when the value was
out of the range, it now returns an Option.

It was also erroniously marked as `unsafe`, even though it is
a safe method

I've renamed it - hopefully this rename makes sense

I've also reworked `contains` to use this method,
which allows it to be more precise about
out-of-range entities.
@cart
Copy link
Member

cart commented Sep 1, 2021

bors r+

bors bot pushed a commit that referenced this pull request Sep 1, 2021
# Objective

Sometimes, the unwraps in `entity_mut` could fail here, if the entity was despawned *before* this command was applied.

The simplest case involves two command buffers:
```rust
use bevy::prelude::*;
fn b(mut commands1: Commands, mut commands2: Commands) {
    let id = commands2.spawn().insert_bundle(()).id();
    commands1.entity(id).despawn();
}
fn main() {
    App::build().add_system(b.system()).run();
}
```

However, a more complicated version arises in the case of ambiguity:

```rust
use std::time::Duration;

use bevy::{app::ScheduleRunnerPlugin, prelude::*};
use rand::Rng;

fn cleanup(mut e: ResMut<Option<Entity>>) {
    *e = None;
}

fn sleep_randomly() {
    let mut rng = rand::thread_rng();
    std::thread::sleep(Duration::from_millis(rng.gen_range(0..50)));
}

fn spawn(mut commands: Commands, mut e: ResMut<Option<Entity>>) {
    *e = Some(commands.spawn().insert_bundle(()).id());
}

fn despawn(mut commands: Commands, e: Res<Option<Entity>>) {
    let mut rng = rand::thread_rng();
    std::thread::sleep(Duration::from_millis(rng.gen_range(0..50)));
    if let Some(e) = *e {
        commands.entity(e).despawn();
    }
}

fn main() {
    App::build()
        .add_system(cleanup.system().label("cleanup"))
        .add_system(sleep_randomly.system().label("before_despawn"))
        .add_system(despawn.system().after("cleanup").after("before_despawn"))
        .add_system(sleep_randomly.system().label("before_spawn"))
        .add_system(spawn.system().after("cleanup").after("before_spawn"))
        .insert_resource(None::<Entity>)
        .add_plugin(ScheduleRunnerPlugin::default())
        .run();
}
```

In the cases where this example crashes, it's because `despawn` was ordered before `spawn` in the topological ordering of systems (which determines when buffers are applied). However, `despawn` actually ran *after* `spawn`, because these systems are ambiguous, so the jiggles in the sleeping time triggered a case where this works.

## Solution

- Give a better error message
@bors bors bot changed the title Add error messages for the spooky insertions [Merged by Bors] - Add error messages for the spooky insertions Sep 1, 2021
@bors bors bot closed this Sep 1, 2021
@DJMcNab DJMcNab deleted the jeepers-creepers branch September 1, 2021 21:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
A-ECS Entities, components, systems, and events C-Usability A simple quality-of-life change that makes Bevy easier to use
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants