bevy/crates/bevy_ecs/src/query/error.rs
François Mockers 4e694aea53
ECS: put strings only used for debug behind a feature (#19558)
# Objective

- Many strings in bevy_ecs are created but only used for debug: system
name, component name, ...
- Those strings make a significant part of the final binary and are no
use in a released game

## Solution

- Use [`strings`](https://linux.die.net/man/1/strings) to find ...
strings in a binary
- Try to find where they come from
- Many are made from `type_name::<T>()` and only used in error / debug
messages
- Add a new structure `DebugName` that holds no value if `debug` feature
is disabled
- Replace `core::any::type_name::<T>()` by `DebugName::type_name::<T>()`

## Testing

Measurements were taken without the new feature being enabled by
default, to help with commands

### File Size

I tried building the `breakout` example with `cargo run --release
--example breakout`

|`debug` enabled|`debug` disabled|
|-|-|
|81621776 B|77735728B|
|77.84MB|74.13MB|

### Compilation time

`hyperfine --min-runs 15 --prepare "cargo clean && sleep 5"
'RUSTC_WRAPPER="" cargo build --release --example breakout'
'RUSTC_WRAPPER="" cargo build --release --example breakout --features
debug'`

```
breakout' 'RUSTC_WRAPPER="" cargo build --release --example breakout --features debug'
Benchmark 1: RUSTC_WRAPPER="" cargo build --release --example breakout
  Time (mean ± σ):     84.856 s ±  3.565 s    [User: 1093.817 s, System: 32.547 s]
  Range (min … max):   78.038 s … 89.214 s    15 runs

Benchmark 2: RUSTC_WRAPPER="" cargo build --release --example breakout --features debug
  Time (mean ± σ):     92.303 s ±  2.466 s    [User: 1193.443 s, System: 33.803 s]
  Range (min … max):   90.619 s … 99.684 s    15 runs

Summary
  RUSTC_WRAPPER="" cargo build --release --example breakout ran
    1.09 ± 0.05 times faster than RUSTC_WRAPPER="" cargo build --release --example breakout --features debug
```
2025-06-18 20:15:25 +00:00

92 lines
3.0 KiB
Rust

use bevy_utils::prelude::DebugName;
use thiserror::Error;
use crate::{
archetype::ArchetypeId,
entity::{Entity, EntityDoesNotExistError},
};
/// An error that occurs when retrieving a specific [`Entity`]'s query result from [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState).
// TODO: return the type_name as part of this error
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum QueryEntityError {
/// The given [`Entity`]'s components do not match the query.
///
/// Either it does not have a requested component, or it has a component which the query filters out.
QueryDoesNotMatch(Entity, ArchetypeId),
/// The given [`Entity`] does not exist.
EntityDoesNotExist(EntityDoesNotExistError),
/// The [`Entity`] was requested mutably more than once.
///
/// See [`Query::get_many_mut`](crate::system::Query::get_many_mut) for an example.
AliasedMutability(Entity),
}
impl From<EntityDoesNotExistError> for QueryEntityError {
fn from(error: EntityDoesNotExistError) -> Self {
QueryEntityError::EntityDoesNotExist(error)
}
}
impl core::error::Error for QueryEntityError {}
impl core::fmt::Display for QueryEntityError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match *self {
Self::QueryDoesNotMatch(entity, _) => {
write!(f, "The query does not match entity {entity}")
}
Self::EntityDoesNotExist(error) => {
write!(f, "{error}")
}
Self::AliasedMutability(entity) => {
write!(
f,
"The entity with ID {entity} was requested mutably more than once"
)
}
}
}
}
/// An error that occurs when evaluating a [`Query`](crate::system::Query) or [`QueryState`](crate::query::QueryState) as a single expected result via
/// [`single`](crate::system::Query::single) or [`single_mut`](crate::system::Query::single_mut).
#[derive(Debug, Error)]
pub enum QuerySingleError {
/// No entity fits the query.
#[error("No entities fit the query {0}")]
NoEntities(DebugName),
/// Multiple entities fit the query.
#[error("Multiple entities fit the query {0}")]
MultipleEntities(DebugName),
}
#[cfg(test)]
mod test {
use crate::{prelude::World, query::QueryEntityError};
use bevy_ecs_macros::Component;
#[test]
fn query_does_not_match() {
let mut world = World::new();
#[derive(Component)]
struct Present1;
#[derive(Component)]
struct Present2;
#[derive(Component, Debug, PartialEq)]
struct NotPresent;
let entity = world.spawn((Present1, Present2));
let (entity, archetype_id) = (entity.id(), entity.archetype().id());
let result = world.query::<&NotPresent>().get(&world, entity);
assert_eq!(
result,
Err(QueryEntityError::QueryDoesNotMatch(entity, archetype_id))
);
}
}