Skip to content

Revisit labels again #5569

@maniwani

Description

@maniwani

What problem does this solve or what need does it fill?

#4957 made labels much cheaper, but also made them much less capable. By nature, the design moves away from using Hash and PartialEq impls to using (TypeId, &'static str) tuples for comparisons. (#5377 recovers constant-time performance by adding a u64, so the &'static str or formatter can just be there for log/debug purposes).

Beyond simple fieldless enum variants, this change basically prevents using different values to mean different labels, e.g. MyLabel(T), which I think is a big ergonomics hit.

Under bevyengine/rfcs#45, the plan for state machines is to facilitate linking each state to a pair of on-enter and on-exit schedules, which would run from within an exclusive system handling state transitions. The implementation for this in #4391 was looking extremely simple.

pub struct CurrentState(pub S);
pub struct NextState(pub S);
pub struct PrevState(pub S);

#[derive(SystemLabel)]
pub struct OnEnter<S: State>(pub S);

#[derive(SystemLabel)]
pub struct OnExit<S: State>(pub S);

pub fn apply_state_transition<S: State>(world: &mut World) {
    world.resource_scope(|world, mut state: Mut<CurrentState<S>>| {
        if world.resource::<NextState<S>>().0.is_some() {
            let new_state = world.resource_mut::<NextState<S>>().0.take().unwrap();
            let old_state = std::mem::replace(&mut state.0, new_state.clone());
            world.resource_mut::<PrevState<S>>().0 = Some(old_state.clone());
            run_schedule(OnExit(old_state), world);
            run_schedule(OnEnter(new_state), world);
        }
    });
}
pub fn run_schedule(system_set: impl SystemLabel, world: &mut World) {
    let systems = world.resource_mut::<Systems>();
    let schedule = systems.take_schedule(&system_set));
    
    schedule.run(world);

    let systems = world.resource_mut::<Systems>();
    systems.return_schedule(&system_set, schedule);
}

The apply_state_transition system just reads the state resources, then loads and runs their appropriate schedules (if they exist). What was really nice was that it was just a convenient export of something users could do on their own.

However, this is no longer possible, and I'm not sure how to adapt it for the current 0.8 build. Since I don't know S, I don't know how to produce a unique &'static str / u64 for each label value. There's probably some way to do it, but having to do a special manual impl for a simple wrapper type feels ridiculous. As one of its main authors, I believe that what's coming with stageless will lead to apps having more labels, so increasing friction here by reducing what users can do with even POD types seems counterproductive.

It's true that schedule construction had too much boxing, but those costs were infrequent, basically once at startup, so I'm skeptical the tradeoff we made was worth it.

What solution would you like?

I haven't figured out an obvious solution yet, but the essence of the problem is that:

  1. bevy wants plain integers for efficient schedule construction
  2. user wants names for logging/debugging

All the boxing happened in the descriptor API methods, where we had to collect all SystemLabel trait objects into a struct before handing it to the SystemStage, so we ended up boxing each and every use of a label.

(edit) Removing that excessive boxing seems to have been the main reason people supported the new scheme.

Since add_system is a method on SystemStage, could we add a HashMap<SystemLabelId, Box<dyn SystemLabel>> field and somehow pass a &mut reference to it into the descriptor process? If we can do that, we'd only box each label once and different values could mean different labels again. The *LabelId types can just wrap (type_id, value_hash) tuples (currently same size as a u128).

What alternative(s) have you considered?

  1. Same solution as above, except initialize the map as a static variable, e.g. using a OnceCell, and have the descriptor methods access that. It's fine for a system label value to have the same ID, program-wide.
  2. Revert the changes. Slightly faster schedule construction isn't all that important if it only happens once or twice.
  3. Stay the course and add some magic macro attributes to enable some common patterns.

Metadata

Metadata

Assignees

No one assigned

    Labels

    A-ECSEntities, components, systems, and eventsC-UsabilityA targeted quality-of-life change that makes Bevy easier to use

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions