Skip to content

Conversation

greeble-dev
Copy link
Contributor

@greeble-dev greeble-dev commented Oct 11, 2025

Objective

Add a component that's similar to Name, but intended only for debugging. The user can choose to (mostly) compile it out in production builds. The goal is to avoid the ambiguities and other issues that come from using Name as a debugging tool.

Why a draft?

The PR is unfinished. I suspect it might be controversial, so I'm filing an early version to test the waters. The remaining work is trying out an alternative implementation (based on DebugName), sorting out the feature structure, and documentation.

Background

The Name component is an ad-hoc way of identifying entities. Sometimes it's used as load bearing data - an app might have logic that expects certain entities to have certain names. Sometimes it's used as a debugging tool - a way for the user to recognize entities in an inspector or editor.

The dual purpose of Name can be problematic. For a detailed breakdown, see this proposal. The gist is:

Solution

This PR adds a new DebugTag component:

pub struct DebugTag {
    #[cfg(feature = "debug_tag")]
    tag: Cow<'static, str>,
}
commands.spawn((DebugTag::new("my simple entity"), Transform::IDENTITY));

The key differences from Name are:

  • It only stores a string (Name includes a hash for fast comparison).
  • If the debug_tag feature is not enabled then it becomes an empty struct.
    • (The feature should be enabled by default, but the PR doesn't do this yet. See FAQ for why the feature might change)
  • It only implements Debug. It does not implement Display, Deref, or comparisons.

There's also a debug_tag! macro which helps compile out awkward cases. DebugTag::new(format!(...)) will not be compiled out, but debug_tag!(format!(...)) will be. As far as I can tell, the common case of DebugTag::new("example") is compiled out.

The PR also updates the observer_propagation example. This might actually be wrong - the point of that example is to print out log messages, so maybe they should stay as Name - but it makes for a nice demo. The final PR should decide which examples to update, and whether that should be a separate PR.

FAQ

Why not call it DebugName?

One issue is that there's already a DebugName in bevy_utils. It's quite similar to DebugTag, but it's not a component and doesn't depend on bevy_ecs.

A second issue is that I feel DebugName is too close to Name, and perhaps a bit misleading - "name" usually means the primary method of identifying something. "Tag" feels a bit more secondary and optional, and it's nice and short.

Alternatives:

  • DebugDescription or Description.
  • DebugAnnotation or Annotation.
  • Flecs has multiple options with different roles: "brief", "detail" and "link".
    • An extension to this PR could be a separate DebugComment component that's presented in a multi-line editor.
    • So DebugTag is a name/sentence, while DebugComment is paragraph.

What's to stop someone misusing DebugTag as a load bearing name?

The string inside DebugTag is only exposed via the Debug trait and serialization. So the user has to work pretty hard to misuse it - even a simple comparison needs something like if format!("{tag1:?") == format!("{tag2:?}").

Why not implement Display?

Abundance of caution. On the other hand, implementing Display would be convenient and backwards compatible with Name.

What does Debug print when the debug_tag feature is disabled?

It prints [REDACTED], so the observer_propagation example log looks like:

Attack hit [REDACTED]
6 damage passed through [REDACTED]
Attack hit [REDACTED]
[REDACTED] has 11 HP

I like this as it's brief and hard to confuse with real output. It's also a bit whimsical, like someone's censored a top secret document.

DebugName takes a different approach and prints Enable the debug feature to see the name. I find this verbose and awkward - the example above would become:

Attack hit Enable the debug_tag feature to see the name
Enable the debug_tag feature to see the name has 11 HP

But it is nice that the user can work out how to enable the feature.

Maybe printing [DEBUG_TAG] would be a compromise - if the user searches for "debug_tag" then they'll probably come across the documentation.

How does this affect inspectors and editors?

I'd guess that inspectors should treat Name and DebugTag as equivalent, and display them together if the entity has both. Or maybe that's too verbose and one should take priority.

For editors, I'm guessing they should distinguish between Name and DebugTag to reduce confusion. Name would be the primary focus, and DebugTag would be secondary or displayed in a tooltip.

DebugTag isn't really compiled out - it becomes an empty struct. Why not compile it out entirely?

Ergonomics. Compiling it out completely would be safer, but also require users to apply a lot of #[cfg(feature = "debug_tag")].

I haven't tested efficiency. I'm assuming the ECS handles ZST components as well as it can, but two otherwise identical entities will have different archetypes if only one has a DebugTag. Maybe there's a way for the ECS to optimise this further based on the feature flag.

Another option might be to make DebugTag a QueryData instead of a component, so the actual component is hidden from the user and fully compiled out. But I suspect there might be complications or confusing behavior around adding the component. Maybe someone more familiar with the ECS could advise.

Couldn't DebugTag be implemented as struct DebugTag(DebugName)?

Perhaps - the implementations are currently very similar. Although DebugName seems specifically geared towards type names, so maybe there's a risk they diverge in the future. I don't feel strongly either way.

Why add a new debug_tag feature? Couldn't it reuse debug?

Probably, yeah. I'm mainly being cautious for now. I also recall some discussion of whether debug is a bit too vague.

This would become moot if the implementation is changed to use DebugName, since that's gated on debug.

Shouldn't NameOrEntity support DebugTag?

Probably. I haven't worked this through. Like inspectors, there's the question of what to print if there's both a Name and DebugTag.

What might come after this PR?

There's a few places where the engine currently adds Name components.

  • bevy_input adds a name to gamepad entities (gamepad_connection_system).
  • bevy_gltf adds a name to most of its entities.
    • Nodes and lights get their glTF name.
    • Mesh primitives are named as "mesh.material" (see primitive_name).

These should be changed to DebugTag or typed names. Maybe Name support could be kept as an option to ease migration, particularly for glTF.

Testing

cargo run --example observer_propagation
cargo run --example observer_propagation --features debug_tag
cargo test -p bevy_ecs --features "serialize debug_tag" -- debug_tag

@Freyja-moth
Copy link
Contributor

It prints [REDACTED], so the observer_propagation example log looks like:

It may be worth using [REDACTED - needs feature `debug_tag`] or something similar. It isn't as precise, but is more descriptive while still clearly being separated from actual text.

Which would turn the example into this

Attack hit [REDACTED - needs feature `debug_tag`]
6 damage passed through [REDACTED - needs feature `debug_tag`]
Attack hit [REDACTED - needs feature `debug_tag`]
[REDACTED - needs feature `debug_tag`] has 11 HP

@greeble-dev greeble-dev added C-Feature A new feature, making something new possible A-ECS Entities, components, systems, and events S-Needs-Design This issue requires design work to think about how it would best be accomplished labels Oct 11, 2025
@viridia
Copy link
Contributor

viridia commented Oct 11, 2025

If I might be permitted to engage in a bit of minor bikeshedding, another possible name for this could be Description:

  • Pro: It makes it clear that it's not intended to be used as a lookup id or query filter term (unlike "tag" or "name").
  • Neutral: It implies that it's ok to use text that isn't restricted to name syntax, e.g. you can put spaces or punctuation in it.
  • Con (minor): Without the word Debug, it's a bit less obvious that this won't be included in non-debug builds.
  • Con (very minor): People might confuse it for "caption" or "label", which are intended for end-users, not developers.

@greeble-dev
Copy link
Contributor Author

It may be worth using [REDACTED - needs feature `debug_tag`] or something similar. It isn't as precise, but is more descriptive while still clearly being separated from actual text.

Agreed. I was worried about brevity, but now I think that's a red herring. If someone's compiled out debug tags then they probably meant to compile out debug logs as well. So it's an exceptional situation and ok to be a bit verbose.

Something else I've realised is that I'm not sure how inspectors and BRP should handle compiled out debug tags. Currently it serializes the redacted message, but in that case an inspector should probably display nothing. I might need to change the string to be in an Option, so it can serialize as None when compiled out.

@greeble-dev
Copy link
Contributor Author

If I might be permitted to engage in a bit of minor bikeshedding, another possible name for this could be Description:

Bikeshedding welcome! I agree with your points, and that "tag" is not ideal. But I feel fairly strongly that the name should include Debug. So that gives us DebugDescription, which feels just a bit too verbose? Really depends on how much we value brevity.

Maybe DebugLabel? As you point out, "label" is also an end user thing so there's a risk of confusion. But it's brief and has a common interpretation as "a thing that can be peeled off".

@viridia
Copy link
Contributor

viridia commented Oct 12, 2025

I'm fine with DebugLabel.

@cart
Copy link
Member

cart commented Oct 13, 2025

I'd prefer to hold off on this sort of thing until we have upstream inspector / editor tools (and BSN), which will allow us to better understand and solve the problem in context. As it stands I'm not yet convinced that Name is not sufficient.

@viridia
Copy link
Contributor

viridia commented Oct 14, 2025

@cart I'm having a really hard time seeing why you aren't seeing what I am seeing :)

Let's take the case of names in skeletal meshes As I understand it, Name components are used to identify bones (maybe this is changed to some other kind of id, I haven't checked). This requires some degree of uniqueness: if bones had duplicate/conflicting names, then looking up bones by name would be useless.

Moreover, the producer of the bone names is not the same as the consumer. The bone names are determined by (a) the artist, and (b) the developer who wrote the asset importer. Both of these people have, in effect, promised the consumer (the person who is searching for bones by name) that bone names will be non-conflicting. It's an implied contract.

There are many other uses of names which require similar implied contracts between different creative roles: the consumer depends on the fact that the producer of the names are following certain rules. This implied contract isn't something that the producer and consumer agree to on an individual basis - there's no handshake meeting. Instead, it is generally understood by practitioners within the industry that certain rules must be followed. It's table stakes for being a participant in the ecosystem.

These kinds of contractual policies generally go hand-in-hand with a namespace. What's the difference between domains in the ".org" TLD and the ".gov" TLD? At the purely technical level there is none. However, what these TLDs represent are different policies: what kinds of organizations or institutions are permitted to register names in that space.

Let me repeat this: "namespaces are policies".

Back to our skeletal mesh example. One might say, "OK, so within the restricted context of armatures, we need to have a particular policy of how names are assigned, but outside that, it's anything goes." But that's confusing: it means that the developer needs to learn different rules for different contexts.

Consider than in HTML you have both "id" and "class". Both are used as identifiers and both can be used as filter terms. However, "id" has an explicit policy of uniqueness. Even though it's not enforced by the browser, every web developer knows how ids are supposed to be used. Not only do the tutorials say this, but the web APIs strongly encourage uniqueness. There's no guesswork about which contexts require unique ids and which do not. The rules are simple and universal: if you want non-unique names, then use a different attribute (like "class").

Bevy, on the other hand, has a policy of "no policy". Do whatever you want with Name. Use it as a lookup id. Use it as a query filter. Use it as a debug label. Heck, use it as a comment. Anything goes!

What this means, then, is that as a consumer of Name in Bevy, I can only depend on sanity from names that I personally have created, or from producers who have explicitly explained how they use names (and only in contexts where that producer's names aren't mixed with names from other producers). This adds additional cognitive overhead to the use of Name as a communicative tool between parties. Moreover, it disincentives the use of names as lookup ids generally, except for names that you create for your own use.

I mentioned that I had made a mistake of using Name incorrectly: going all the way back to Quill v1, I had every widget insert a Name component explaining what kind of widget it was Button, Slider, etc. Essentially I was using Name as a DebugLabel. This means that the whole UI was filled with a mass of duplicate names. I didn't realize at the time that one of the uses of names was to be able to search for entities. If a user wanted to name their button Button and look it up by id, they'd be screwed; and they wouldn't know that they are screwed until they understood what my widgets were doing behind their back.

If I had known that, I would never have created so many duplicates. But I didn't know this, because there's no policy.

@cart
Copy link
Member

cart commented Oct 15, 2025

Bevy, on the other hand, has a policy of "no policy". Do whatever you want with Name. Use it as a lookup id. Use it as a query filter. Use it as a debug label. Heck, use it as a comment. Anything goes!

I would argue that there is a policy, just not the one that you want. The policy is:

  • Names can be used to identify entities:
    • Specifying their purpose, as seen in inspectors or scene editors (ex: "OkButton" vs "CancelButton")
    • Querying for entities within a hierarchy
  • When defining a scene for a given "type" of entity, it is good to give it a name, as this will serve as the "default" name for spawned instances of that scene. Developers can override the default if they desire a more specific name.
  • "Uniqueness" can be established within a scope of the hierarchy that you "own." Ex: a scene you make can constrain itself to using unique names within its hierarchy, a gltf loader can enforce unique names for the scenes (or bones) that it loads, a Blender exporter can enforce unique names, etc. If you are spawning a scene you don't own and you need uniqueness within your current context, don't rely on the default name. Set the name manually so it is fully in your control.
  • If you are using #Name syntax in the context of bsn! macros, uniqueness is required / enforced across all levels of the hierarchy that use #Name in the current bsn! scope.
  • Defining a non-unique name will not crash your program. This is a "valid" state (and a common one, given that many scenes define a default name).
  • You can query for names within a level of the hierarchy. If your query matches more than one entity, we either return them all, return the first one, or return none, based on the API you choose to call
  • Names are not indexed by default. There is no global cheap Name("OkButton") -> Entity mapping. Resolving names requires scanning within a given context (ex: scanning children). If you want some sort of global unique identifier, define your own type, or (better yet) use a marker component and a Single query. In this way, conflicting names among entities in a "global" namespace is not a problem, as uniqueness in that context was never a part of the deal.

This "policy" largely stems from the following constraints:

  • Building indices is expensive, especially by default
  • Erroring out at runtime when names happen to conflict is not good UX. Most of the time people won't care, and most scenes benefit from having a default name.
  • Defining multiple names, one for "debug" purposes and one for "identity" purposes is boilerplatey. People won't enjoy writing things twice (or reading them twice), and I expect to see that a lot. This type of split is not present in any of the other engines or content authoring tools that I'm aware of.

I think the current state of things is a reasonable constraint solve. I would be most receptive to the following alternatives:

  1. Layer on forced uniqueness at "composition time", in the context of things like the visual BSN editor. Leave runtime as it is.
  2. Embrace the "index everything" lifestyle, enforce uniqueness within each level of the hierarchy, pay the cost of building the index everywhere, log errors when there are conflicts.

Just embracing a "policy" of "please make these unique or you're doing bad things" doesn't feel particularly trust-able as a user.

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-Feature A new feature, making something new possible S-Needs-Design This issue requires design work to think about how it would best be accomplished

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants