Proposal: Prefer Typed And Structured Names #20693
greeble-dev
started this conversation in
Ideas
Replies: 2 comments 1 reply
-
In case it's useful, here's some random notes I accumulated about names in other systems:
|
Beta Was this translation helpful? Give feedback.
1 reply
-
Prior art from Flecs, which is probably closer to Bevy than the other mentioned solutions:
|
Beta Was this translation helpful? Give feedback.
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
-
This is a proposal to nudge Bevy away from the ad-hoc
Name
component and towards typed and structured names. The goal is to let users opt intoName
components if they want to, but avoid imposing them on users with other preferences.The gist of the proposal is:
Name
as it is.Name
components if the user prefers.Name
components.Background
Most 2D/3D engines, file formats and DCCs have some concept of "entity" or "object" or "node". These things can often be assigned a name. Names can be represented by a human readable string, but beyond that each system has different rules:
namespace:name
convention (e.g.player_character:head_joint
).path/to/asset:object1.subobject2.foo
, wherefoo
is its short name.Problems
Names can be a convenient and powerful tool for for identifying things. When the engine creates them by default they're a standard way to reference and debug anything. When created by the user they're a flexible way to connect all kinds of things together.
But there's a price - in some situations names can be unreliable and inefficient, and that's because they're convenient and powerful.
player_1
andplayer_2
and doinglet is_player = object.name.starts_with("player_")
.player_spawn_point
.Names are not inherently bad. But they can become a problem when systems rely on or encourage names as the primary form of identification.
Alternatives
Many systems have more structured ways to identify things while still using human readable strings.
GltfMaterialName
.["cat", "animal"]
can be found by searching for"animal"
or"cat OR dog"
.Animal.Mammal.Cat
can be found by searching forAnimal
orAnimal.Mammal
or*.Cat
.Names In Bevy
Bevy entities are not required to have a name. They're usually identified by the
Entity
struct, which is an opaque ID.Bevy does provide a generic
Name
component as an optional identifier. AName
is an arbitrary UTF-8 string stored in aCow<'static, str>
, plus a hash for fast equality. Each entity can only have a singleName
component, and names are not required to be unique.Name
components are often used as a debugging tool - most inspectors will default to displaying them as the main way to identify entities. But there's nothing to stop users from programmatically identifying entities withName
components.Bevy engine code does not rely on
Name
components as a way of identifying entities (except for debugging and examples). But there are some cases where the engine automatically adds them:bevy_input
adds a name to gamepad entities (gamepad_connection_system
).bevy_gltf
adds a name to most of its entities.primitive_name
).Name
,bevy_gltf
also uses typed name components likeGltfMaterialName
.The current
bevy_gltf
use ofName
has caused at least one bug with examples:GltfMeshName
component that behaved like the old name (#19331).Names In
bevy_animation
Skeletal animation systems often rely on names to identify joints. A branch of the scene that's (usually) skinned to a mesh and animated as a unit is defined as a "skeleton". A simple character skeleton might look like this:
torso
head
left_upperarm
left_forearm
left_hand
right_upperarm
Things within the skeleton are often required to have unique names, even when the system technically supports duplicate names. This is convenient for the user - a name will always resolve to a single joint. It's also common for names to be standardised across skeletons, so several different character skeletons might have different proportions but will always have a "head" bone. This allows sharing animations and other logic across multiple skeletons.
Bevy's animation system is unusual in that it doesn't define a skeleton - animations can be bound to any entity in the world. Animatable entities are identified by
AnimationTargetId
, which is a semi-opaque UUID. For glTF skeletal animations, the UUID is a hash of the joint's name and the names of all its ancestors within the glTF scene. So in the example skeleton, the id ofhead
would be the hash ofhead
andtorso
. This method means theAnimationTargetId
will be unique within the glTF scene as long as there are no duplicate names among siblings. It also means that a different glTF with the same hierarchy will result in the sameAnimationTargetId
, so animations can be shared.The glTF
AnimationTargetId
doesn't technically depend on entities having aName
component. The loader uses theName
struct as temporary storage, but it could easily be replaced byString
(proof of concept).AnimationTargetId
is powerful since it's less reliant on name uniqueness. But it can be inconvenient - the glTFAnimationTargetId
requires looking at the hierarchy (example), where other animation systems would only need a single joint/attribute name. It could also be a problem when animations are shared but limited to a sub-branch of the skeleton hierarchy (e.g. a face animation's root might be the head rather than the body root, leading to a differentAnimationTargetId
).The glTF loader also outputs the joint name as a
Name
component. This is not used bybevy_animation
directly. But it's available to the user for simpler lookups, as long as they identify the right branch of the hierarchy and deal with uniqueness.Bevy's Future
There's been some discussion on Discord about the current
Name
situation. I get the impression there's two divergent views:Name
as something the engine will automatically generate, and encourage users to build on it. Particularly for the editor, where naming entities in scenes is a common design for DCCs.Name
due to its limitations. Explore debug only names and more structured alternatives.This proposal advocates for option 2.
Proposal
Keep
Name
As It IsEven though there's many ways to misuse it,
Name
can be the best choice for some cases. A game jam entry or a solo dev might not care about making their code scalable and reliable and performant - they can get away withif name.starts_with("player")
just fine. So having a built-in and reasonably performant name component is a good thing.Add A Debug-Only Name Component
The engine should have a component that's similar to
Name
, but for debugging only - it shouldn't be used as a programmatic identifier.Accidentally using a debug name as an identifier will be discouraged by a limited interface, and user will have the option to compile it out. Inspectors and other debug tools should be changed to prefer this component over
Name
, or display them side by side.I've had a crack at a proof of concept here: main...greeble-dev:bevy:debug-name-poc-zst
DebugTag
.DebugName
is taken by a similar struct inbevy_util
.Cow<'static, str>
.Name
but without the hash.String
or other string internment options.debug_tag
feature is not enabled,DebugTag
becomes an empty struct.cfg
s.commands.spawn((DebugTag::new("example"), Transform::IDENTITY))
Query<(Entity, &DebugTag, Transform)>
DebugTag::new("example")
is efficient - the "example" string is compiled out.DebugTag::new(format!(...))
are not compiled out.debug_tag!
macro that behaves likeDebugTag::new
but is guaranteed to compile out anything inside the macro.let f = format!(...); let n = debug_name!(f)
.DebugTag
only implementsDebug
.ToString
, noDisplay
, maybe not evenEq
(controversial).Debug
prints"[REDACTED]"
(bike-sheddable)Don't Create
Name
s In Engine Code By DefaultEngine code should never create
Name
components by default. The goal is thatName
should be opt-in - any users who don't like it shouldn't have to worry about misuse creeping in.The current uses of
Name
would be changed to typed names orDebugTag
. This will be a bit annoying for users - they will have to track down uses ofName
in their code. The blow could be softened by adding an option for glTF to generateName
s as before.Explore Alternatives
Name
andDebugTag
can exist alongside various alternatives:struct MyString(String)
component.Objection!
Wouldn't it be better to let users opt-out if they don't want
Name
components? Why make the simple case harder?If the engine encourages using
Name
everywhere then in practice opt-out will become difficult. It'll be too tempting for the engine and third party crates to start relying on them. So that opt-out would become "well... you can try, but these crates you're using might randomly break".Isn't this bad for inspectors and editors? What will they show instead of a name?
An engine that assigns names to everything does simplify the job of editors and inspectors - the name is usually the best thing to show. Object oriented engines have an obvious solution - use the class name instead. But Bevy is too flexible for that.
Maybe the solution is to slap
DebugTag
s on everything? That could work... but it's annoying for users to do on their own entities, and might stress performance in development builds. Maybe it could be an opt-in.Another option is heuristics that try to find a sensible description based on an entity's components - e.g. an entity with a
Mesh3d
and aSkinnedMesh
can be usefully described byformat!("SkinnedMesh:{:?}", mesh.handle)
. Plugins could register heuristics, and the engine expose a standard way for inspectors to use them. This could also tie into linting heuristics, like warning if an entity has aMesh3d
but no material component.Don't typed names like
GltfMeshMesh
increase Bevy's coupling to glTF?Yes. I don't have a great answer to this. Two options:
MeshName
andMaterialName
inbevy_mesh
andbevy_material
. These can be reused by all loaders.Name
like before, defaulting to off.Summary
Nudging the engine away from
Name
could be a good thing. A debug-only name seems feasible, but there's a tension between safety and ergonomics. Questions remain about backwards compatibility, inspector/editor support, and alternative naming schemes.Beta Was this translation helpful? Give feedback.
All reactions