Skip to content

ECS: put strings only used for debug behind a feature #19558

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
merged 21 commits into from
Jun 18, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -95,7 +95,7 @@ jobs:
- name: CI job
# To run the tests one item at a time for troubleshooting, use
# cargo --quiet test --lib -- --list | sed 's/: test$//' | MIRIFLAGS="-Zmiri-disable-isolation -Zmiri-disable-weak-memory-emulation" xargs -n1 cargo miri test -p bevy_ecs --lib -- --exact
run: cargo miri test -p bevy_ecs
run: cargo miri test -p bevy_ecs --features bevy_utils/debug
env:
# -Zrandomize-layout makes sure we dont rely on the layout of anything that might change
RUSTFLAGS: -Zrandomize-layout
Expand Down
10 changes: 9 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -165,6 +165,7 @@ default = [
"vorbis",
"webgl2",
"x11",
"debug",
]

# Recommended defaults for no_std applications
Expand Down Expand Up @@ -507,7 +508,10 @@ file_watcher = ["bevy_internal/file_watcher"]
embedded_watcher = ["bevy_internal/embedded_watcher"]

# Enable stepping-based debugging of Bevy systems
bevy_debug_stepping = ["bevy_internal/bevy_debug_stepping"]
bevy_debug_stepping = [
"bevy_internal/bevy_debug_stepping",
"bevy_internal/debug",
]

# Enables the meshlet renderer for dense high-poly scenes (experimental)
meshlet = ["bevy_internal/meshlet"]
Expand Down Expand Up @@ -551,6 +555,9 @@ web = ["bevy_internal/web"]
# Enable hotpatching of Bevy systems
hotpatching = ["bevy_internal/hotpatching"]

# Enable collecting debug information about systems and components to help with diagnostics
debug = ["bevy_internal/debug"]

[dependencies]
bevy_internal = { path = "crates/bevy_internal", version = "0.16.0-dev", default-features = false }
tracing = { version = "0.1", default-features = false, optional = true }
Expand Down Expand Up @@ -2098,6 +2105,7 @@ wasm = false
name = "dynamic"
path = "examples/ecs/dynamic.rs"
doc-scrape-examples = true
required-features = ["debug"]

[package.metadata.example.dynamic]
name = "Dynamic ECS"
Expand Down
5 changes: 2 additions & 3 deletions crates/bevy_ecs/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ backtrace = ["std"]

## Enables `tracing` integration, allowing spans and other metrics to be reported
## through that framework.
trace = ["std", "dep:tracing"]
trace = ["std", "dep:tracing", "bevy_utils/debug"]

## Enables a more detailed set of traces which may be noisy if left on by default.
detailed_trace = ["trace"]
Expand Down Expand Up @@ -63,9 +63,9 @@ std = [
"bevy_reflect?/std",
"bevy_tasks/std",
"bevy_utils/parallel",
"bevy_utils/std",
"bitflags/std",
"concurrent-queue/std",
"disqualified/alloc",
"fixedbitset/std",
"indexmap/std",
"serde?/std",
Expand Down Expand Up @@ -98,7 +98,6 @@ bevy_platform = { path = "../bevy_platform", version = "0.16.0-dev", default-fea
] }

bitflags = { version = "2.3", default-features = false }
disqualified = { version = "1.0", default-features = false }
fixedbitset = { version = "0.5", default-features = false }
serde = { version = "1", default-features = false, features = [
"alloc",
Expand Down
5 changes: 2 additions & 3 deletions crates/bevy_ecs/src/bundle.rs
Original file line number Diff line number Diff line change
Expand Up @@ -550,10 +550,9 @@ impl BundleInfo {
// SAFETY: the caller ensures component_id is valid.
unsafe { components.get_info_unchecked(id).name() }
})
.collect::<Vec<_>>()
.join(", ");
.collect::<Vec<_>>();

panic!("Bundle {bundle_type_name} has duplicate components: {names}");
panic!("Bundle {bundle_type_name} has duplicate components: {names:?}");
}

// handle explicit components
Expand Down
32 changes: 14 additions & 18 deletions crates/bevy_ecs/src/component.rs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ use bevy_platform::{
use bevy_ptr::{OwningPtr, UnsafeCellDeref};
#[cfg(feature = "bevy_reflect")]
use bevy_reflect::Reflect;
use bevy_utils::TypeIdMap;
use bevy_utils::{prelude::DebugName, TypeIdMap};
use core::{
alloc::Layout,
any::{Any, TypeId},
Expand All @@ -34,7 +34,6 @@ use core::{
mem::needs_drop,
ops::{Deref, DerefMut},
};
use disqualified::ShortName;
use smallvec::SmallVec;
use thiserror::Error;

Expand Down Expand Up @@ -678,8 +677,8 @@ impl ComponentInfo {

/// Returns the name of the current component.
#[inline]
pub fn name(&self) -> &str {
&self.descriptor.name
pub fn name(&self) -> DebugName {
self.descriptor.name.clone()
}

/// Returns `true` if the current component is mutable.
Expand Down Expand Up @@ -836,7 +835,7 @@ impl SparseSetIndex for ComponentId {
/// A value describing a component or resource, which may or may not correspond to a Rust type.
#[derive(Clone)]
pub struct ComponentDescriptor {
name: Cow<'static, str>,
name: DebugName,
// SAFETY: This must remain private. It must match the statically known StorageType of the
// associated rust component type if one exists.
storage_type: StorageType,
Expand Down Expand Up @@ -882,7 +881,7 @@ impl ComponentDescriptor {
/// Create a new `ComponentDescriptor` for the type `T`.
pub fn new<T: Component>() -> Self {
Self {
name: Cow::Borrowed(core::any::type_name::<T>()),
name: DebugName::type_name::<T>(),
storage_type: T::STORAGE_TYPE,
is_send_and_sync: true,
type_id: Some(TypeId::of::<T>()),
Expand All @@ -907,7 +906,7 @@ impl ComponentDescriptor {
clone_behavior: ComponentCloneBehavior,
) -> Self {
Self {
name: name.into(),
name: name.into().into(),
storage_type,
is_send_and_sync: true,
type_id: None,
Expand All @@ -923,7 +922,7 @@ impl ComponentDescriptor {
/// The [`StorageType`] for resources is always [`StorageType::Table`].
pub fn new_resource<T: Resource>() -> Self {
Self {
name: Cow::Borrowed(core::any::type_name::<T>()),
name: DebugName::type_name::<T>(),
// PERF: `SparseStorage` may actually be a more
// reasonable choice as `storage_type` for resources.
storage_type: StorageType::Table,
Expand All @@ -938,7 +937,7 @@ impl ComponentDescriptor {

fn new_non_send<T: Any>(storage_type: StorageType) -> Self {
Self {
name: Cow::Borrowed(core::any::type_name::<T>()),
name: DebugName::type_name::<T>(),
storage_type,
is_send_and_sync: false,
type_id: Some(TypeId::of::<T>()),
Expand All @@ -964,8 +963,8 @@ impl ComponentDescriptor {

/// Returns the name of the current component.
#[inline]
pub fn name(&self) -> &str {
self.name.as_ref()
pub fn name(&self) -> DebugName {
self.name.clone()
}

/// Returns whether this component is mutable.
Expand Down Expand Up @@ -1854,13 +1853,10 @@ impl Components {
///
/// This will return an incorrect result if `id` did not come from the same world as `self`. It may return `None` or a garbage value.
#[inline]
pub fn get_name<'a>(&'a self, id: ComponentId) -> Option<Cow<'a, str>> {
pub fn get_name<'a>(&'a self, id: ComponentId) -> Option<DebugName> {
self.components
.get(id.0)
.and_then(|info| {
info.as_ref()
.map(|info| Cow::Borrowed(info.descriptor.name()))
})
.and_then(|info| info.as_ref().map(|info| info.descriptor.name()))
.or_else(|| {
let queued = self.queued.read().unwrap_or_else(PoisonError::into_inner);
// first check components, then resources, then dynamic
Expand Down Expand Up @@ -2813,13 +2809,13 @@ pub fn enforce_no_required_components_recursion(
"Recursive required components detected: {}\nhelp: {}",
recursion_check_stack
.iter()
.map(|id| format!("{}", ShortName(&components.get_name(*id).unwrap())))
.map(|id| format!("{}", components.get_name(*id).unwrap().shortname()))
.collect::<Vec<_>>()
.join(" → "),
if direct_recursion {
format!(
"Remove require({}).",
ShortName(&components.get_name(requiree).unwrap())
components.get_name(requiree).unwrap().shortname()
)
} else {
"If this is intentional, consider merging the components.".into()
Expand Down
4 changes: 3 additions & 1 deletion crates/bevy_ecs/src/entity/clone_entities.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use alloc::{borrow::ToOwned, boxed::Box, collections::VecDeque, vec::Vec};
use bevy_platform::collections::{HashMap, HashSet};
use bevy_ptr::{Ptr, PtrMut};
use bevy_utils::prelude::DebugName;
use bumpalo::Bump;
use core::any::TypeId;

Expand Down Expand Up @@ -171,7 +172,8 @@ impl<'a, 'b> ComponentCloneCtx<'a, 'b> {
/// - `ComponentId` of component being written does not match expected `ComponentId`.
pub fn write_target_component<C: Component>(&mut self, mut component: C) {
C::map_entities(&mut component, &mut self.mapper);
let short_name = disqualified::ShortName::of::<C>();
let debug_name = DebugName::type_name::<C>();
let short_name = debug_name.shortname();
if self.target_component_written {
panic!("Trying to write component '{short_name}' multiple times")
}
Expand Down
8 changes: 5 additions & 3 deletions crates/bevy_ecs/src/error/command_handling.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use core::{any::type_name, fmt};
use core::fmt;

use bevy_utils::prelude::DebugName;

use crate::{
entity::Entity,
Expand Down Expand Up @@ -31,7 +33,7 @@ where
Err(err) => (error_handler)(
err.into(),
ErrorContext::Command {
name: type_name::<C>().into(),
name: DebugName::type_name::<C>(),
},
),
}
Expand All @@ -43,7 +45,7 @@ where
Err(err) => world.default_error_handler()(
err.into(),
ErrorContext::Command {
name: type_name::<C>().into(),
name: DebugName::type_name::<C>(),
},
),
}
Expand Down
14 changes: 7 additions & 7 deletions crates/bevy_ecs/src/error/handler.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use core::fmt::Display;

use crate::{component::Tick, error::BevyError, prelude::Resource};
use alloc::borrow::Cow;
use bevy_utils::prelude::DebugName;
use derive_more::derive::{Deref, DerefMut};

/// Context for a [`BevyError`] to aid in debugging.
Expand All @@ -10,26 +10,26 @@ pub enum ErrorContext {
/// The error occurred in a system.
System {
/// The name of the system that failed.
name: Cow<'static, str>,
name: DebugName,
/// The last tick that the system was run.
last_run: Tick,
},
/// The error occurred in a run condition.
RunCondition {
/// The name of the run condition that failed.
name: Cow<'static, str>,
name: DebugName,
/// The last tick that the run condition was evaluated.
last_run: Tick,
},
/// The error occurred in a command.
Command {
/// The name of the command that failed.
name: Cow<'static, str>,
name: DebugName,
},
/// The error occurred in an observer.
Observer {
/// The name of the observer that failed.
name: Cow<'static, str>,
name: DebugName,
/// The last tick that the observer was run.
last_run: Tick,
},
Expand All @@ -54,12 +54,12 @@ impl Display for ErrorContext {

impl ErrorContext {
/// The name of the ECS construct that failed.
pub fn name(&self) -> &str {
pub fn name(&self) -> DebugName {
match self {
Self::System { name, .. }
| Self::Command { name, .. }
| Self::Observer { name, .. }
| Self::RunCondition { name, .. } => name,
| Self::RunCondition { name, .. } => name.clone(),
}
}

Expand Down
5 changes: 3 additions & 2 deletions crates/bevy_ecs/src/hierarchy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,9 @@ use alloc::{format, string::String, vec::Vec};
use bevy_reflect::std_traits::ReflectDefault;
#[cfg(all(feature = "serialize", feature = "bevy_reflect"))]
use bevy_reflect::{ReflectDeserialize, ReflectSerialize};
use bevy_utils::prelude::DebugName;
use core::ops::Deref;
use core::slice;
use disqualified::ShortName;
use log::warn;

/// Stores the parent entity of this child entity with this component.
Expand Down Expand Up @@ -461,11 +461,12 @@ pub fn validate_parent_has_component<C: Component>(
{
// TODO: print name here once Name lives in bevy_ecs
let name: Option<String> = None;
let debug_name = DebugName::type_name::<C>();
warn!(
"warning[B0004]: {}{name} with the {ty_name} component has a parent without {ty_name}.\n\
This will cause inconsistent behaviors! See: https://bevy.org/learn/errors/b0004",
caller.map(|c| format!("{c}: ")).unwrap_or_default(),
ty_name = ShortName::of::<C>(),
ty_name = debug_name.shortname(),
name = name.map_or_else(
|| format!("Entity {entity}"),
|s| format!("The {s} entity")
Expand Down
9 changes: 5 additions & 4 deletions crates/bevy_ecs/src/observer/runner.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use alloc::{borrow::Cow, boxed::Box, vec};
use alloc::{boxed::Box, vec};
use bevy_utils::prelude::DebugName;
use core::any::Any;

use crate::{
Expand Down Expand Up @@ -301,7 +302,7 @@ impl Observer {
}

/// Returns the name of the [`Observer`]'s system .
pub fn system_name(&self) -> Cow<'static, str> {
pub fn system_name(&self) -> DebugName {
self.system.system_name()
}
}
Expand Down Expand Up @@ -420,11 +421,11 @@ fn observer_system_runner<E: Event, B: Bundle, S: ObserverSystem<E, B>>(
}

trait AnyNamedSystem: Any + Send + Sync + 'static {
fn system_name(&self) -> Cow<'static, str>;
fn system_name(&self) -> DebugName;
}

impl<T: Any + System> AnyNamedSystem for T {
fn system_name(&self) -> Cow<'static, str> {
fn system_name(&self) -> DebugName {
self.name()
}
}
Expand Down
12 changes: 5 additions & 7 deletions crates/bevy_ecs/src/query/access.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ use crate::world::World;
use alloc::{format, string::String, vec, vec::Vec};
use core::{fmt, fmt::Debug, marker::PhantomData};
use derive_more::From;
use disqualified::ShortName;
use fixedbitset::FixedBitSet;
use thiserror::Error;

Expand Down Expand Up @@ -999,12 +998,11 @@ impl AccessConflicts {
.map(|index| {
format!(
"{}",
ShortName(
&world
.components
.get_name(ComponentId::get_sparse_set_index(index))
.unwrap()
)
world
.components
.get_name(ComponentId::get_sparse_set_index(index))
.unwrap()
.shortname()
)
})
.collect::<Vec<_>>()
Expand Down
Loading