Remove the ability to use strings as labels
Objective
- Fixes #4341.
- Strings as labels are convenient, but are error prone, undiscoverable and uncontrollably public.
- #4224 eliminated the dominant use of these (quick and easy system labels), and so we can now safely remove them.
Solution
- Remove the ability for string-like objects to be used as labels.
- Yeet!
Changelog
- In order to promote more reliable, higher quality code, string-like types can no longer be used as labels for
AppLabel,StageLabel,SystemLabel,AmbiguitySetLabelorRunCriteriaLabel.
Migration Guide
String-like types can no longer be used as labels for AppLabel, StageLabel, SystemLabel, AmbiguitySetLabelorRunCriteriaLabel. Use a constructed type (including a type that stores data) instead. In the most common case of a SystemLabel, you should strongly consider using an implicit SystemTypeIdLabel` instead.
For example, suppose your initial code was:
app
.add_system(gravity.label("gravity"))
.add_system(forces.after("gravity");
This could either become:
app
.add_system(gravity)
.add_system(forces.after(gravity);
Or:
#[derive(SystemLabel, Debug, PartialEq, Eq, Clone, Hash)]
enum PhysicsLabels {
Gravity,
// Generally, related labels are grouped together in an enum
// to provide helpful structure to the end consumer
...
}
app
.add_system(gravity.label(PhysicsLabels::Gravity))
.add_system(forces.after(PhysicsLabels::Gravity);
Prefer implicit system labels over explicit ones (for clarity and brevity) whenever they do not need to be exposed to an external consumer. By contrast, explicit system labels can be useful to expose a thoughtful and stable external API that consumers can rely upon without excessive breakage when you modify the internals of your plugin.
Status
- [ ] Reach basic consensus that this is the path forward
- [ ] Fix the 493 errors in failing tests. This will be easier if #4299 is merged, as a large block of tests using stringly-typed labels was replaced in that PR.
I think I'm onboard with this change. Can you flesh out the example in the migration guide section, so that we can see exactly what the extra boilerplate over using strings would entail?
@hymm done :) In most cases the boilerplate change is negative: explicit labels should be reserved for when you're creating an API for others to hook into a plugin (in Bevy, the ecosystem or in a large project).
Firstly, I don't like the argument that the existence of stringly labels encourages bad code, and that users need to be forced to use typed labels. I think everyone is well aware of the downsides and upsides.
Now, I am personally firmly against removing stringly labels for the time being, at least until we at least land a few other important changes.
So, there are currently 4 things (afaik) where labels are used:
- Systems
- Stages
- RunCriteria
- Ambiguity Sets
All your current discussion so far seems to be focused on systems. I understand the argument that we now have an even more convenient way of ordering systems, using the function. No complaints here.
Run Criteria are going to be completely redesigned with Stageless (and will no longer need labels), and in their current state are largely unusable in practice, so nothing is lost here.
Ambiguity Sets I cannot comment on, because I have never personally used any Bevy APIs related to ambiguities.
Stages, however, are still a necessary reality of life. Until Stageless is done, they are the only way to accomplish a lot of practical use cases. Personally, this is still my major use case for stringly labels, as it can be a big productivity booster. Saves a lot of time while prototyping, and greatly improved my game jam experience. :)
I propose we do not remove stringly labels until after Stageless.
To counter arguments like "it's just a little more typing!" and "you already don't mind derive component", I responded to that in Discord, and I will quote it here:
i guess what really annoys me isn't merely that boilerplate requires more typing ... it is the cognitive overhead and also jumping around the codebase
- if i am creating a new component type, i have to define and name a new type anyway, and i know im going to be using it as a component ... so ...
just one
#[derive(Component)]is fine, i don't have to go out of my way to think about it, and also it goes right where my cursor already is, where im already typing (i'm creating a struct, it goes on the struct)
- if i want a quick and dirty label for something ...
now, instead of just a name for the label, i also have to think about creating and naming a type, and remember what the exact long list of derives it required was ...
and i have to jump to a different place in my text editor to define the type, because now i have to do something in 2 different places ... so i have to move away from wherever im currently typing (in the app builder stuff) ... and copypasting the derives is not an improvement ... it makes this even worse (have to go find a place to copy them from)
- derive component takes like 2 more seconds of typing
- typed labels take more like 15+ seconds of performing multiple text editor actions, and also having to think about unrelated distracting stuff (like what derives it needs)
it's annoying
To improve the user experience, we should focus on addressing these usability issues with typed labels, first.
If typed labels (and state types for that matter) didn't require a long magic list of derives, that would be a pretty big usability improvement.
&str no longer implements label (SystemSet) after #6587