Git Product home page Git Product logo

iyes_loopless's Introduction

This crate is now OBSOLETE!

Yes! Finally! Bevy has officially merged the "Stageless Rework". Coming with the Bevy 0.10 release!

The purpose of this crate was to provide some of the features that are part of Bevy 0.10 in a form that was compatible with older versions of Bevy.

This repo will be archived as soon as Bevy 0.10 is released.

Migration Guide

That said, here is some example code to help you migrate from iyes_loopless to Bevy 0.10 ("Stageless"):

Run Conditions

Creating them is the same: make a system that returns bool. Bevy 0.10 run conditions can only have read-only data access!

fn my_condition(/* system params */) -> bool {
    // ...
}

// loopless:
app.add_system(
    my_system
        .run_if(my_good_condition)
        .run_if_not(my_bad_condition)
);

// Bevy 0.10:
app.add_system(
    my_system
        .run_if(my_good_condition)
        .run_if(not(my_bad_condition))
)

Common (predefined) run conditions:

// loopless:
app.add_system(
    my_system
        .run_if_resource_exists::<GoodResource>()
        .run_unless_resource_exists::<BadResource>()

        .run_if_resource_equals(MyResource::Value)
        .run_unless_resource_equals(MyResource2::Value)

        .run_on_event::<MyEvent>()
        .run_if_resource_added::<AnotherResource>()
        .run_if_resource_removed::<AnotherResource>()
);

// Bevy 0.10:
app.add_system(
    my_system
        .run_if(resource_exists::<GoodResource>())
        .run_if(not(resource_exists::<BadResource>()))

        // panics if resources don't exist
        .run_if(resource_equals(MyResource::Value))
        .run_if(not(resource_equals(MyResource2::Value)))

        // does not panic if resources do not exist
        .run_if(resource_exists_and_equals(MyResource::Value))
        .run_if(not(resource_exists_and_equals(MyResource2::Value)))

        .run_if(on_event::<MyEvent>())
        .run_if(resource_added::<AnotherResource>())
        .run_if(resource_removed::<AnotherResource>())

        // see: https://dev-docs.bevyengine.org/bevy/ecs/schedule/common_conditions/index.html
);

States

The mental model and general feature set is the same.

If you need to apply state transitions elsewhere in the schedule (other than the default location, before Update), in Bevy 0.10, you can add state transition points by adding apply_state_transition systems.

You don't need to do anything different for the default configuration.

The CurrentState<T> resource is renamed to State<T>. NextState<T> is the same.

// Loopless:
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash)]
enum MyState {
    Loading,
    MainMenu,
    InGame,
}
app.add_loopless_state(MyState::Loading);
app.add_enter_system(MyState::MainMenu, setup_menu);
app.add_exit_system(MyState::MainMenu, cleanup_menu);
app.add_system(menu_buttons.run_in_state(MyState::MainMenu));

// Bevy 0.10:
#[derive(Debug, Clone, Copy, Eq, PartialEq, Hash, Default, States)]
enum MyState {
    #[default]
    Loading,
    MainMenu,
    InGame,
}
app.add_state::<MyState>();
app.add_system_to_schedule(OnEnter(MyState::MainMenu), setup_menu);
app.add_system_to_schedule(OnExit(MyState::MainMenu), cleanup_menu);
app.add_system(menu_buttons.in_set(OnUpdate(MyState::MainMenu)));
// or alternatively
app.add_system(menu_buttons.run_if(in_state(MyState::MainMenu)));

Fixed Timestep

Loopless allows you to add any number of independent fixed timesteps and place them where you like. If you need to, you can position the stage where you like, relative to other stages. The default is before CoreStage::Update. Fixed timesteps use string labels as an identifier, which you use to add systems.

Bevy 0.10 only has one fixed timestep. Its systems are in CoreSchedule::FixedUpdate, which is run by an exclusive system before CoreSet::Update. Simply add your systems there. If you need anything else, you are on your own.

Loopless supports "child stages"/sub-stages, so you can apply Commands in the middle of the fixed update. They are identified by integer ids. The combination of string labels and integer ids makes the api not very user-friendly.

Bevy 0.10 allows you to add your own Commands application points anywhere (regardless of fixed timestep or not) and systems are no longer organized into "stages". Just do that.

// Loopless:

// create a new fixed timestep
app.add_fixed_timestep(Duration::from_millis(100), "my_timestep_label");
// create a second sub-stage so we can have a Commands application point
app.add_fixed_timestep_child_stage("my_timestep_label");
// add a system to the first sub-stage
app.add_fixed_timestep_system("my_timestep_label", 0, spawn_entities);
// add a system to the second sub-stage
app.add_fixed_timestep_system("my_timestep_label", 1, move_entities);

// Bevy 0.10:

// configure the fixed timestep
app.insert_resource(FixedTime::new(Duration::from_millis(100)));
// add our two systems, with Commands application in between
app.add_systems_to_schedule(
    CoreSchedule::FixedUpdate,
    // quick and dirty; you should probably use sets and labels in larger projects
    // so you dont end up with many copies of `apply_system_buffers`
    (
        spawn_entities,
        apply_system_buffers,
        move_entities,
    ).chain()
);

Here is the old README:

Composable Alternatives to Bevy's RunCriteria, States, FixedTimestep

This crate offers alternatives to the Run Criteria, States, and FixedTimestep scheduling features currently offered by the Bevy game engine.

The ones provided by this crate do not use "looping stages", and can therefore be combined/composed together elegantly, solving some of the most annoying usability limitations of the respective APIs in Bevy.

Version Compatibility Table:

Bevy Version Crate Version
main bevy_main
0.9 main
0.9 0.9
0.8 0.7, 0.8
0.7 0.4, 0.5, 0.6
0.6 0.1, 0.2, 0.3

How does this relate to the Bevy Stageless RFC?

This crate draws very heavy inspiration from the "Stageless RFC" proposal for Bevy.

Big thanks to all the authors that have worked on that RFC and the designs described there.

I am making this crate, because I believe the APIs currently in Bevy are sorely in need of a usability improvement.

I figured out a way to implement the ideas from the Stageless RFC in a way that works within the existing framework of current Bevy, without requiring the complete scheduling API overhaul that the RFC proposes.

This way we can have something usable now, while the remaining Stageless work is still in progress.

Dependencies and Cargo Feature Flags

The "run conditions" functionality is always enabled, and depends only on bevy_ecs.

The "fixed timestep" functionality is optional ("fixedtimestep" cargo feature) and adds these dependencies:

  • bevy_time
  • bevy_utils

The "states" functionality is optional ("states" cargo feature) and adds these dependencies:

  • bevy_utils

The "app" cargo feature enables extension traits that add new builder methods to App, allowing more ergonomic access to the features of this crate. Adds a dependency on bevy_app.

The "bevy-compat" feature adds Run Conditions for compatibility with Bevy's legacy states implementation.

All of the optional cargo features are enabled by default.

Run Conditions

This crate provides an alternative to Bevy Run Criteria, called "Run Conditions".

The different name was chosen to avoid naming conflicts and confusion with the APIs in Bevy. Bevy Run Criteria are pretty deeply integrated into Bevy's scheduling model, and this crate does not touch/replace them. They are technically still there and usable.

How Run Conditions Work?

You can convert any Bevy system into a "conditional system". This allows you to add any number of "conditions" on it, by repeatedly calling the .run_if builder method.

Each condition is just a Bevy system that outputs (returns) a bool.

The conditional system will present itself to Bevy as a single big system (similar to Bevy's system piping), combining the system it was created from with all the condition systems attached.

When it runs, it will run each condition, and abort if any of them returns false. The main system will run only if all the conditions return true.

(see examples/conditions.rs for a more complete example)

use bevy::prelude::*;
use iyes_loopless::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_system(
            notify_server
                .run_if(in_multiplayer)
                .run_if(on_mytimer)
        )
        .run();
}

/// Condition checking our timer
fn on_mytimer(mytimer: Res<MyTimer>) -> bool {
    mytimer.timer.just_finished()
}

/// Condition checking if we are connected to multiplayer server
fn in_multiplayer(gamemode: Res<GameMode>, connected: Res<ServerState>) -> bool {
    *gamemode == GameMode::Multiplayer &&
    connected.is_active()
}

/// Some system that should only run on a timer in multiplayer
fn notify_server(/* ... */) {
    // ...
}

It is highly recommended that all your condition systems only access data immutably. Avoid mutable access or locals in condition systems, unless you are really sure about what you are doing. If you add the same condition to many systems, it will run with each one.

There are also some helper methods for easily adding common kinds of Run Conditions:

  • .run_if_not: invert the output of the condition
  • .run_on_event::<T>(): run if there are events of a given type
  • .run_if_resource_exists::<T>(): run if a resource of a given type exists
  • .run_unless_resource_exists::<T>(): run if a resource of a given type does not exist
  • .run_if_resource_equals(value): run if the value of a resource equals the one provided
  • .run_unless_resource_equals(value): run if the value of a resource does not equal the one provided

And if you are using States:

  • .run_in_state(state)
  • .run_not_in_state(state)

If you need to use classic Bevy States, you can use these adapters to check them with run conditions:

  • .run_in_bevy_state(state)
  • .run_not_in_bevy_state(state)

You can use Bevy labels for system ordering, as usual.

Note: conditional systems currently only support explicit labels, you cannot use Bevy's "ordering by function name" syntax. E.g: .after(another_system) does not work, you need to create a label.

There is also ConditionSet (similar to Bevy SystemSet): syntax sugar for easily applying conditions and labels that are common to many systems:

use bevy::prelude::*;
use iyes_loopless::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_system(
            notify_server
                .run_if(in_multiplayer)
                .run_if(on_mytimer)
                // use bevy labels for ordering, as usual :)
                // (must be added at the end, after the conditions)
                .label("thing")
                .before("thing2")
        )
        // You can easily apply many conditions to many systems
        // using a `ConditionSet`:
        .add_system_set(ConditionSet::new()
            // all the conditions, and any labels/ordering
            // must be added before adding the systems
            // (helps avoid confusion and accidents)
            // (makes it clear they apply to all systems in the set)
            .run_if(in_multiplayer)
            .run_if(other_condition)
            .label("thing2")
            .after("stuff")
            .with_system(system1)
            .with_system(system2)
            .with_system(system3)
            .into() // Converts into Bevy `SystemSet` (to add to App)
        )
        .run();
}

NOTE: Due to some limitations with Bevy, label/before/after are not supported on individual systems within a ConditionSet. You can only use labels and ordering on the entire set, to apply them to all member systems. If some systems need different ordering, just add them individually with .add_system.

Fixed Timestep

This crate offers a fixed timestep implementation that runs as a separate Stage in the Bevy schedule. This way, it does not conflict with any other functionality. You can easily use run conditions and states to control your fixed timestep systems.

It is possible to add multiple "sub-stages" within a fixed timestep, allowing you to apply Commands within a single timestep run. For example, if you want to spawn entities and then do something with them, on the same tick.

It is also possible to have multiple independent fixed timesteps, should you need to.

(see examples/fixedtimestep.rs for a more complex working example)

use bevy::prelude::*;
use iyes_loopless::prelude::*;

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // add the fixed timestep stage:
        // (in the default position, before CoreStage::Update)
        .add_fixed_timestep(
            Duration::from_millis(250),
            // we need to give it a string name, to refer to it
            "my_fixed_update",
        )
        // add fixed timestep systems:
        .add_fixed_timestep_system(
            "my_fixed_update", 0, // fixed timestep name, sub-stage index
            // it can be a conditional system!
            my_simulation
                .run_if(some_condition)
                .run_in_state(AppState::InGame)
                .after("some_label")
        )
        .run();
}

Every frame, the FixedTimestepStage will accumulate the time delta. When it goes over the set timestep value, it will run all the child stages. It will repeat the sequence of child stages multiple times if needed, if more than one timestep has accumulated.

Fixed Timestep Control

You can use the FixedTimesteps resource (make sure it is the one from this crate, not the one from Bevy with the same name) to access information about a fixed timestep and to control its parameters, like the timestep duration.

fn timestep_control(mut timesteps: ResMut<FixedTimestep>) {
    // we can access our timestep by name
    let info = timesteps.get_mut("my_fixed_update").unwrap();
    // set a different duration
    info.step = Duration::from_millis(125);
    // pause it
    info.paused = true;
}

/// Print info about the fixed timestep this system runs in
fn debug_fixed(timesteps: Res<FixedTimesteps>) {
    // from within a system that runs inside the fixed timestep,
    // you can use `.get_current`, no need for the timestep name:
    let info = timesteps.get_current().unwrap();
    println!("Fixed timestep duration: {:?} ({} Hz).", info.timestep(), info.rate());
    println!("Overstepped by {:?} ({}%).", info.remaining(), info.overstep() * 100.0);
}

States

(see examples/menu.rs for a complete example)

This crate offers a states abstraction that works as follows:

You create one (or more) state types, usually enums, just like when using Bevy States.

However, here we track states using two resource types:

  • CurrentState(T): the current state you are in
  • NextState(T): insert this (using Commands) whenever you want to change state

Registering the state type

You need to add the state to your App using .add_loopless_state(value) with the initial state value. This helper method adds a special stage type (StateTransitionStage) that is responsible for performing state transitions. By default, it is added before CoreStage::Update. If you would like the transitions to be executed elsewhere in the app schedule, there are other helper methods that let you specify the position.

For advanced use cases, you could construct and add the StateTransitionStage manually, without the helper method.

Enter/Exit Systems

You can add enter/exit systems to be executed on state transitions, using .add_enter_system(state, system) and .add_exit_system(state, system).

For advanced scenarios, you could add a custom stage type instead, using .set_enter_stage(state, stage) and .set_exit_stage(state, stage).

State Transition

When the StateTransitionStage runs, it will check if a NextState resource exists. If yes, it will remove it and perform a transition:

  • run the "exit stage" (if any) for the current state
  • change the value of CurrentState
  • run the "enter stage" (if any) for the next state

If you want to perform a state transition, simply insert a NextState<T>. If you mutate CurrentState<T>, you will effectively change state without running the exit/enter systems (you probably don't want to do this).

Multiple state transitions can be performed in a single frame, if you insert a new instance of NextState from within an exit/enter stage.

Update systems

For the systems that you want to run every frame, we provide a .run_in_state(state) and .run_not_in_state(state) run conditions.

You can add systems anywhere, to any stage (incl. behind fixed timestep), and make them conditional on one or more states, using those helper methods.

use bevy::prelude::*;
use iyes_loopless::prelude::*;

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
enum GameState {
    MainMenu,
    InGame,
}

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        // Add our state type
        .add_loopless_state(GameState::MainMenu)
        // If we had more state types, we would add them too...

        // Add a FixedTimestep, cuz we can!
        .add_fixed_timestep(
            Duration::from_millis(250),
            "my_fixed_update",
        )
        .add_fixed_timestep_system(
            "my_fixed_update", 0,
            my_simulation
                .run_in_state(AppState::InGame)
        )

        // Add our various systems
        .add_system(menu_stuff.run_in_state(GameState::MainMenu))
        .add_system(animate.run_in_state(GameState::InGame))

        // On states Enter and Exit
        .add_enter_system(GameState::MainMenu, setup_menu)
        .add_exit_system(GameState::MainMenu, despawn_menu)
        .add_enter_system(GameState::InGame, setup_game)

        .run();
}

State transitions under fixed timestep

If you have a state type that you are using for controlling fixed timestep stuff, you might want state transitions to happen only on fixed timestep (not just on any frame).

To accomplish that, you can add the StateTransitionStage as a child stage at the beginning of your FixedTimestepStage.

The stage types from this crate are composable like that! :) They accept any stage type.

iyes_loopless's People

Contributors

aceeri avatar barsoosayque avatar inodentry avatar jakobhellermann avatar niklasei avatar pascualex avatar piturnah avatar shatur avatar sullyj3 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar

iyes_loopless's Issues

RC Helpers

Implement some helper methods to easily add common Run Conditions.

  • .run_on_event::<Event>()
  • .run_if_resource_exists::<Res>()
  • .run_if_resource_equals(value)
  • .run_unless_resource_exists::<Res>()
  • .run_unless_resource_equals(value)

Ambiguity when mixing exclusive and normal systems

It seems I must manually bring in iyes_loopless::condition::IntoConditionalExclusiveSystem to support exclusive systems with conditions, however this appears to cause an ambiguity problem with non-exclusive systems if I bring it into the same scope. This example demonstrates my issue:

use bevy::prelude::*;
use iyes_loopless::prelude::*;
use iyes_loopless::condition::IntoConditionalExclusiveSystem;

fn main() {
    App::new()
        .add_plugins(MinimalPlugins)
        .add_loopless_state(DevState::Loading)
        .add_enter_system(DevState::Loading, setup)
        .add_system(ready
            .run_in_state(DevState::Ready)
            .run_unless_resource_exists::<Thing>()
        )
        .add_system(process
            .run_in_state(DevState::Loading)
            .run_if_resource_exists::<Thing>()
            .at_end()
        )
        .run()
    ;
}

#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum DevState {
    Loading,
    Ready,
}

#[derive(Debug, Clone, Copy)]
pub struct Thing(pub u32);

fn setup(mut commands: Commands) {
    commands.insert_resource(Thing(1));
}

fn ready(mut commands: Commands) {
    println!("ready");
    commands.insert_resource(Thing(2));
}

fn process(mut world: &mut World) {
    println!("removed {:?}", world.remove_resource::<Thing>());
    world.insert_resource(NextState(DevState::Ready));
}
   --> examples\test.rs:11:14
    |
11  |             .run_in_state(DevState::Loading)
    |              ^^^^^^^^^^^^ multiple `run_in_state` found
    |
    = note: candidate #1 is defined in an impl of the trait `iyes_loopless::condition::IntoConditionalSystem` for the type `S`
    = note: candidate #2 is defined in an impl of the trait `IntoConditionalExclusiveSystem` for the type `S`
note: candidate #3 is defined in the trait `iyes_loopless::condition::ConditionHelpers`
   --> C:\Users\s33n\.cargo\registry\src\github.com-1ecc6299db9ec823\iyes_loopless-0.5.1\src\condition.rs:366:5
    |
366 |     fn run_in_state<T: bevy_ecs::schedule::StateData>(self, state: T) -> Self {
    |     ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
help: disambiguate the associated function for candidate #1
    |
10  |         .add_system(iyes_loopless::condition::IntoConditionalSystem::run_in_state(ready, DevState::Loading)
    |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: disambiguate the associated function for candidate #2
    |
10  |         .add_system(IntoConditionalExclusiveSystem::run_in_state(ready, DevState::Loading)
    |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
help: disambiguate the associated function for candidate #3
    |
10  |         .add_system(iyes_loopless::condition::ConditionHelpers::run_in_state(ready, DevState::Loading)
    |                     ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~

One option I see is to fully qualify the extension for each exclusive system I add. This works, but seems verbose and ugly:

.add_system(iyes_loopless::condition::IntoConditionalExclusiveSystem::run_in_state(process,DevState::Loading)
    .run_if_resource_exists::<Thing>()
    .at_end()
)

Another option I've found is to scope the exclusive trait. This at least preserves the look and feel of the builder API, but still feels quite verbose:

use bevy::prelude::*;
use iyes_loopless::prelude::*;

fn main() {
    let mut app = App::new();
    app.add_plugins(MinimalPlugins)
        .add_loopless_state(DevState::Loading)
        .add_enter_system(DevState::Loading, setup)
        .add_system(ready
            .run_in_state(DevState::Ready)
            .run_unless_resource_exists::<Thing>()
        )
    ;
    // scope for exclusive systems
    {
        use iyes_loopless::condition::IntoConditionalExclusiveSystem;

        app.add_system(process
            .run_in_state(DevState::Loading)
            .run_if_resource_exists::<Thing>()
            .at_end()
        );
    }
    app.run();
}

Any better options?

Loopless Stage

Implement an alternative to use instead of Bevy's SystemStage, which:

  • does not support Bevy Run Criteria
  • natively supports our Run Conditions
  • evaluates run conditions in-line and removes the perf overhead of unnecessarily spawning tasks

Is there alternative to bevy's state stack?

I've switched from bevy state system into loopless, because there wasn't any information about "setup" systems in it, only loop, but I've found such implementation there. But after I found out such realization in bevy cheatbook (which I've not remarked somehow), I've also seen such thing as "stacks" which lets you make implementation of "pause" state etc. Does similar thing exists in loopless, or I need to get back to bevy systems?

P.S.: Or is there another way to implement pause menu or smth like this? Thx

Build fails without "states" feature

   Compiling iyes_loopless v0.7.1
error[E0433]: failed to resolve: could not find `state` in the crate root
  --> C:\Users\Johan\.cargo\registry\src\github.com-1ecc6299db9ec823\iyes_loopless-0.7.1\src\lib.rs:13:20
   |
13 |     pub use crate::state::schedule::ScheduleLooplessStateExt;
   |                    ^^^^^ could not find `state` in the crate root

For more information about this error, try `rustc --explain E0433`.
error: could not compile `iyes_loopless` due to previous error
Error: cargo command failed
error: process didn't exit successfully: `target\debug\xtask.exe dist ultra_bevy` (exit code: 1)
\"[Finished running. Exit status: 0]\"

Compile failure when default features are turned off

When trying to compile the crate without default features this error shows up.

  --> iyes_loopless/src/lib.rs:13:20
13 |     pub use crate::state::schedule::ScheduleLooplessStateExt;
   |                    ^^^^^ could not find `state` in the crate root

The #cfg(feature = "app") in lib.rs only applies to the first import. So the socond one gets included even if app isn't enabled.
Also the app feature should depend on the states feature because the crate also does not compile when only app is enabled.

ConditionSet systems without run_if conditions require into_conditional

I'm migrating a playground project to use this library and I have a set of systems that run using a fixed timer only during a state, previously using a SystemSet. The systems run in a certain order using labels, but they don't have conditionals on them.

app
    .add_system_set(
        ConditionSet::new()
            .after("FixedTimestep")
            .run_in_state(AppState::Playing)
            .with_system(snake_movement.label(SnakeMovement::Movement))
            .with_system(
                snake_eating
                    .label(SnakeMovement::Eating)
                    .after(SnakeMovement::Movement),
            )
            .with_system(
                snake_growth
                    .label(SnakeMovement::Growth)
                    .after(SnakeMovement::Eating),
            )
            .into(),
    )

It causes this error:

error[E0277]: the trait bound `ParallelSystemDescriptor: IntoSystem<(), (), _>` is not satisfied

error[E0277]: the trait bound `ParallelSystemDescriptor: IntoSystem<(), (), _>` is not satisfied
   --> src\main.rs:442:30
    |
442 |                 .with_system(snake_movement.label(SnakeMovement::Movement))
    |                  ----------- ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ the trait `IntoSystem<(), (), _>` is not implemented for `ParallelSystemDescriptor`
    |                  |
    |                  required by a bound introduced by this call
    |
    = help: the trait `AddConditionalToSet` is implemented for `ConditionalSystemDescriptor`
    = note: required for `ParallelSystemDescriptor` to implement `iyes_loopless::condition::IntoConditionalSystem<_>`
    = note: required for `ParallelSystemDescriptor` to implement `AddConditionalToSet`
note: required by a bound in `iyes_loopless::condition::ConditionSet::with_system`
   --> C:\Users\osbornm\scoop\persist\rustup\.cargo\registry\src\github.com-1ecc6299db9ec823\iyes_loopless-0.7.1\src\condition.rs:652:12
    |
652 |         S: AddConditionalToSet,
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `iyes_loopless::condition::ConditionSet::with_system`

For more information about this error, try rustc --explain E0277.

The solution I found is to add into_conditional() to each system:

app
    .add_system_set(
        ConditionSet::new()
            .after("FixedTimestep")
            .run_in_state(AppState::Playing)
            .with_system(
                snake_movement
                    .into_conditional()
                    .label(SnakeMovement::Movement)
            )
            .with_system(
                snake_eating
                    .into_conditional()
                    .label(SnakeMovement::Eating)
                    .after(SnakeMovement::Movement),
            )
            .with_system(
                snake_growth
                    .into_conditional()
                    .label(SnakeMovement::Growth)
                    .after(SnakeMovement::Eating),
            )
            .into(),
    )

But that seems kind of clunky and like it may be able to be fixed easily.

Do not require `.into_conditional()`

Do some trait magic to expose .run_if (and any other methods added for #1 and #2, and states, etc) directly on anything that impls IntoSystem, as well as on ConditionalSystem.

This will eliminate the need for .into_conditional(), making the user-facing API simpler.

Tick timers

It would be cool to provide a "tick timers" implementation that works with our FixedTimestep. Timers/Stopwatches that count timesteps, instead of real time durations.

How to get the new methods on App

Hello

Some background first; Im pretty new to Rust and Bevy.

I've added iyes_loopless to my dependencies (0.3.0 as I use bevy 0.6 still).

However, when trying to use a method like .add_enter_system or add_loopless_state on the App::new() from Bevy I get this error from the compiler:
no method named add_enter_system found for mutable reference &mut bevy::prelude::App in the current scope

I've looked at your examples in this repository and I can't see anything specific I'm missing. My dependency is specified like this:
[dependencies.iyes_loopless] version = "0.3.0"
in my Cargo.toml

I did a cargo tree -f "{p} {f}" just to see if there was maybe something with the features, but this was the output:
โ”œโ”€โ”€ iyes_loopless v0.3.0 bevy-compat,bevy_core,bevy_utils,default,fixedtimestep,states โ”‚ โ”œโ”€โ”€ bevy_core v0.6.0 (*) โ”‚ โ”œโ”€โ”€ bevy_ecs v0.6.1 bevy_reflect,default (*) โ”‚ โ””โ”€โ”€ bevy_utils v0.6.0 (*)

Am a bit lost now, would really like to try this way of doing it before just using the Bevy built-in way of doing it.

Do not remove `NextState`

Actually ... the NextState resource doesn't have to keep being inserted/removed ... it can just be kept at all times. Just perform state transitions when its value is changed.

Change fixed timestep duration at runtime

My problem is that I need to be able to change the simulation speed of my game but it seems there is no way of changing the timestep after creating the FixedTimestepStage.

.add_stage_before(
    CoreStage::Update,
    "Enviroment",
    FixedTimestepStage::from_stage(Duration::from_millis( 1.0 / BASE_FPS), enviromentStage),
)
fn sync_game_speed(
    game_speed: Res<GameSpeed>,
    mut rapier_config: ResMut<RapierConfiguration>,
){
    rapier_config.timestep_mode = TimestepMode::Interpolated { dt: 1.0 / BASE_FPS as f32, time_scale: game_speed.0, substeps: 1 }
 <<HERE I NEED TO CHANGE THE FixedTimestepStage TO THE SAME SPEED AS THE PHYSICS>>
}

Is this possible to do? Or should one use somthing like:

.add_system_set(
            ConditionSet::new()
                .run_in_state(AppState::Gaming)
                .run_if(shouldRunSimulationTick) <- manual accumulator that can be dependent on GameSpeed resource
                .with_system(simulation_system1)
                .with_system(simulation_system2...)
                .into()

"State Transition not found" when adding an enter_system

I have a barebones StatePlugin that crashes whenever I try to add_enter_system(). I saw another opened issue mentioning that the API may have some issue with the Plugin architecture, so I tried moving the add_enter_system to main but did not work either.

this is the error I get:

thread 'main' panicked at 'State Transiton Stage not found (assuming auto-added label)', /{my_directory}/github.com-1ecc6299db9ec823/iyes_loopless-0.6.1/src/state.rs:306:18

Here's a snippet of my main.rs file

fn main() {
    let mut app = App::new();
    app.insert_resource(WindowDescriptor {
        // some not relevant code
    })
    .add_plugins(DefaultPlugins)
    .add_plugin(CameraPlugin)
    .add_plugin(StatePlugin)
    .add_plugin(UiPlugin)
    .add_plugin(GamePlugin);

    app.run();
}

and the StatePlugin file:

use bevy::prelude::*;
use iyes_loopless::prelude::*;

pub const STARTING_GAME_STATE: GameState = GameState::MainMenu;

#[derive(Debug, Clone, Eq, PartialEq, Hash)]
pub enum GameState {
    MainMenu,
    Options,
    InGame,
}

pub struct StatePlugin;
impl Plugin for StatePlugin {
    fn build(&self, app: &mut App) {
        app
            .add_loopless_state(STARTING_GAME_STATE)
            .add_system(stage_key_bindings)
            .add_enter_system(&GameState::InGame, setup_game); // adding this line crashes the game
    }
}

// This works fine
fn stage_key_bindings(mut commands: Commands, kb_input: Res<Input<KeyCode>>) {
    if kb_input.just_pressed(KeyCode::Escape) {
        commands.insert_resource(NextState(GameState::MainMenu));
    }
}

// Mockup reset_game function when entering a new game
fn setup_game(mut commands: Commands) {
    dbg!("reset_game!");
    commands.insert_resource(NextState(GameState::InGame));
}

What am I doing wrong? Thanks!

Trait bounds not satisfied in main?

Using bevy main and the bevy_main branch together I get:

   |                 ------------------- this is a function, perhaps you wish to call it
35 |                     .run_on_event::<WindowResized>()
   |                      ^^^^^^^^^^^^ method cannot be called on `for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6> fn(bevy::prelude::Commands<'r, 's>, bevy::prelude::EventReader<'t0, 't1, WindowResized>, bevy::prelude::ResMut<'t2, bevy::prelude::Assets<bevy::prelude::Image>>, std::option::Option<bevy::prelude::Res<'t3, OutputImage>>, bevy::prelude::Query<'t4, 't5, &'t6 mut bevy::prelude::Sprite, bevy::prelude::With<output_image::RenderTarget>>) {resize_output_image}` due to unsatisfied trait bounds

The code causing this is fairly simple:

        app.add_plugin(ExtractResourcePlugin::<OutputImage>::default())
            .add_system(
                resize_output_image
                    .run_on_event::<WindowResized>()
                    .run_if_resource_exists::<OutputImage>(),
            )
            .add_system(
                create_output_image
                    .run_on_event::<WindowResized>()
                    .run_unless_resource_exists::<OutputImage>(),
            );
    }

Where I'm playing around with having different things happen if a particular resource already exists or not.

I'm relatively new to rust, so am not sure if I'm doing something wrong here.

Panic: State Transition Stage not found (assuming auto-added label)

After upgrade from 0.7 to 0.8 I started to get the following error:

thread 'main' panicked at 'State Transition Stage not found (assuming auto-added label)', /home/indy/.cargo/registry/src/github.com-1ecc6299db9ec823/iyes_loopless-0.8.0/src/state.rs:343:25
stack backtrace:
   0: rust_begin_unwind
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/std/src/panicking.rs:584:5
   1: core::panicking::panic_fmt
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:142:14
   2: core::panicking::panic_display
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:72:5
   3: core::panicking::panic_str
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/panicking.rs:56:5
   4: core::option::expect_failed
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/option.rs:1880:5
   5: core::option::Option<T>::expect
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/option.rs:738:21
   6: <bevy_app::app::App as iyes_loopless::state::app::AppLooplessStateExt>::add_enter_system
             at /home/indy/.cargo/registry/src/github.com-1ecc6299db9ec823/iyes_loopless-0.8.0/src/state.rs:343:25
   7: <de_menu::mainmenu::MainMenuPlugin as bevy_app::plugin::Plugin>::build
             at ./crates/menu/src/mainmenu.rs:16:9
   8: bevy_app::plugin_group::PluginGroupBuilder::finish
             at /home/indy/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_app-0.8.1/src/plugin_group.rs:135:21
   9: bevy_app::app::App::add_plugins
             at /home/indy/.cargo/registry/src/github.com-1ecc6299db9ec823/bevy_app-0.8.1/src/app.rs:810:9
  10: de_game::main
             at ./src/main.rs:27:5
  11: core::ops::function::FnOnce::call_once
             at /rustc/897e37553bba8b42751c67658967889d11ecd120/library/core/src/ops/function.rs:248:5
note: Some details are omitted, run with `RUST_BACKTRACE=full` for a verbose backtrace.

Note that I do this in a plugin (which is executed first):

app.add_loopless_state(AppState::InMenu)
    .add_loopless_state(GameState::None);

and this in another plugin (which executes later):

app.add_enter_system(AppState::InMenu, setup)
    .add_exit_system(AppState::InMenu, cleanup)

ConditionSet::label does not compile

Using the .label method of ConditionSet, using either a string or a basic derived SystemLabel, results in a compile-time error:

the method `label` exists for struct `ConditionSystemSet`, but its trait bounds were not satisfied

To reproduce, add a .label call to a ConditionSet.

Discussed previously in this Discord thread.

Missing Downcast: StateTransitionStageLabel

Whenever I start up my game, I get a message in the console about how my states are missing downcasts. I have no idea what it means, and I'm not sure it causes problems.

Support for Custom Command Queues? ( I have an Implementation )

Hey there!

For my game I implemented a custom command queue that can be used to flush certain sets of commands before the end of the frame. This is something that was mentioned in the Bevy stageless design, and I wasn't sure if it was something you might want to add to this crate.

I feel like it's a feature other people might want to use, and it'd be good to have in a crate, but I wasn't sure if a new crate would be warranted, or if it'd fit in here, because it's kind of a preview to the stageless bevy design.

Here's a gist with the implementation and usage example. It's really quite simple:

https://gist.github.com/zicklag/3446143d563657d54e292cc7e2c15378

Cannot use Run_If with systems taking Local<LocalStateType>

I would love it if run_if could take Local Arguments in. With the below code I get the following error:
.add_system(self::gamelobby::lobby.run_if(show_lobby_screen))

System:

use super::{UIState, UIStateRes};

pub struct LobbyStateRes{
    pub selected_map: String
}

pub fn lobby(mut egui_context: ResMut<EguiContext>, mut ui_state: ResMut<UIStateRes>, mut maps_manifest: ResMut<MapManifest>, mut lobby_state: Local<LobbyStateRes>) {
    Window::new("Game Lobby")
        .anchor(Align2::CENTER_CENTER, egui::vec2(0.0, -50.0))
        .show(egui_context.ctx_mut(), |ui| {
            egui::ScrollArea::vertical().show(ui, |ui|{
                for map in &maps_manifest.map_files{
                    let sel_map_btn = ui.radio_value(&mut lobby_state.selected_map, map.clone(), map);
                }
            });

            let play_btn = ui.button("Launch Game");

            if play_btn.clicked() && lobby_state.selected_map != *"".to_string(){ //Such unrust shall not stand!
                ui_state.current_state = UIState::Game
            }

            let back_btn = ui.button("Main Menu");

            if back_btn.clicked() {
                ui_state.current_state = UIState::MainMenu;
            }
        });
}

Error

[{
	"resource": "/e:/rust/macrophage/src/ui/mod.rs",
	"owner": "rustc",
	"code": {
		"value": "E0599",
		"target": {
			"$mid": 1,
			"external": "https://doc.rust-lang.org/error-index.html#E0599",
			"path": "/error-index.html",
			"scheme": "https",
			"authority": "doc.rust-lang.org",
			"fragment": "E0599"
		}
	},
	"severity": 8,
	"message": "the method `run_if` exists for fn item `for<'r, 's, 't0, 't1> fn(bevy::prelude::ResMut<'r, bevy_egui::EguiContext>, bevy::prelude::ResMut<'s, ui::UIStateRes>, bevy::prelude::ResMut<'t0, util::MapManifest>, bevy::prelude::Local<'t1, ui::gamelobby::LobbyStateRes>) {ui::gamelobby::lobby}`, but its trait bounds were not satisfied\nthe following trait bounds were not satisfied:\n`for<'r, 's, 't0, 't1> fn(bevy::prelude::ResMut<'r, bevy_egui::EguiContext>, bevy::prelude::ResMut<'s, ui::UIStateRes>, bevy::prelude::ResMut<'t0, util::MapManifest>, bevy::prelude::Local<'t1, ui::gamelobby::LobbyStateRes>) {ui::gamelobby::lobby}: bevy::prelude::IntoSystem<(), (), _>`\nwhich is required by `for<'r, 's, 't0, 't1> fn(bevy::prelude::ResMut<'r, bevy_egui::EguiContext>, bevy::prelude::ResMut<'s, ui::UIStateRes>, bevy::prelude::ResMut<'t0, util::MapManifest>, bevy::prelude::Local<'t1, ui::gamelobby::LobbyStateRes>) {ui::gamelobby::lobby}: iyes_loopless::condition::IntoConditionalSystem<_>`\n`&for<'r, 's, 't0, 't1> fn(bevy::prelude::ResMut<'r, bevy_egui::EguiContext>, bevy::prelude::ResMut<'s, ui::UIStateRes>, bevy::prelude::ResMut<'t0, util::MapManifest>, bevy::prelude::Local<'t1, ui::gamelobby::LobbyStateRes>) {ui::gamelobby::lobby}: bevy::prelude::IntoSystem<(), (), _>`\nwhich is required by `&for<'r, 's, 't0, 't1> fn(bevy::prelude::ResMut<'r, bevy_egui::EguiContext>, bevy::prelude::ResMut<'s, ui::UIStateRes>, bevy::prelude::ResMut<'t0, util::MapManifest>, bevy::prelude::Local<'t1, ui::gamelobby::LobbyStateRes>) {ui::gamelobby::lobby}: iyes_loopless::condition::IntoConditionalSystem<_>`\n`&mut for<'r, 's, 't0, 't1> fn(bevy::prelude::ResMut<'r, bevy_egui::EguiContext>, bevy::prelude::ResMut<'s, ui::UIStateRes>, bevy::prelude::ResMut<'t0, util::MapManifest>, bevy::prelude::Local<'t1, ui::gamelobby::LobbyStateRes>) {ui::gamelobby::lobby}: bevy::prelude::IntoSystem<(), (), _>`\nwhich is required by `&mut for<'r, 's, 't0, 't1> fn(bevy::prelude::ResMut<'r, bevy_egui::EguiContext>, bevy::prelude::ResMut<'s, ui::UIStateRes>, bevy::prelude::ResMut<'t0, util::MapManifest>, bevy::prelude::Local<'t1, ui::gamelobby::LobbyStateRes>) {ui::gamelobby::lobby}: iyes_loopless::condition::IntoConditionalSystem<_>`",
	"source": "rustc",
	"startLineNumber": 45,
	"startColumn": 44,
	"endLineNumber": 45,
	"endColumn": 50,
	"relatedInformation": [
		{
			"startLineNumber": 45,
			"startColumn": 21,
			"endLineNumber": 45,
			"endColumn": 43,
			"message": "this is a function, perhaps you wish to call it",
			"resource": "/e:/rust/macrophage/src/ui/mod.rs"
		}
	]
}]

Bevy 0.9 support

I attempted to update my project to Bevy 0.9, which was just released a few days ago, and the majority of the iyes_loopless functionality broke.

Specifically, it seems most of the new methods added to App are not being found, like add_loopless_state, add_exit_system and add_enter_system. These all have the very simple error in the form:

no method named `add_loopless_state` found for struct `bevy::prelude::App` in the current scope

It also can't find the run_in_state method when I try to call it on one of my systems when adding it to the app. It works when called directly on a ConditionSet (or at least I'm not seeing a compilation error on it yet). The error I get on this is much more complicated and hard to make sense of, given the extensive layered generics. This is an example:

the method `run_in_state` exists for fn item `for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'r, 's>, bevy::prelude::Query<'t0, 't1, (&'t2 mut ButtonInteraction, &'t3 PowerButton)>, bevy::prelude::Query<'t4, 't5, &'t6 mut PowerMenu>, bevy::prelude::Res<'t7, bevy::prelude::Input<bevy::prelude::KeyCode>>) {power_menu_ui_system}`, but its trait bounds were not satisfied
the following trait bounds were not satisfied:
`for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'r, 's>, bevy::prelude::Query<'t0, 't1, (&'t2 mut ButtonInteraction, &'t3 PowerButton)>, bevy::prelude::Query<'t4, 't5, &'t6 mut PowerMenu>, bevy::prelude::Res<'t7, bevy::prelude::Input<bevy::prelude::KeyCode>>) {power_menu_ui_system}: bevy_ecs::system::function_system::IntoSystem<(), (), _>`
which is required by `for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'r, 's>, bevy::prelude::Query<'t0, 't1, (&'t2 mut ButtonInteraction, &'t3 PowerButton)>, bevy::prelude::Query<'t4, 't5, &'t6 mut PowerMenu>, bevy::prelude::Res<'t7, bevy::prelude::Input<bevy::prelude::KeyCode>>) {power_menu_ui_system}: iyes_loopless::condition::IntoConditionalSystem<_>`
`&for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'r, 's>, bevy::prelude::Query<'t0, 't1, (&'t2 mut ButtonInteraction, &'t3 PowerButton)>, bevy::prelude::Query<'t4, 't5, &'t6 mut PowerMenu>, bevy::prelude::Res<'t7, bevy::prelude::Input<bevy::prelude::KeyCode>>) {power_menu_ui_system}: bevy_ecs::system::function_system::IntoSystem<(), (), _>`
which is required by `&for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'r, 's>, bevy::prelude::Query<'t0, 't1, (&'t2 mut ButtonInteraction, &'t3 PowerButton)>, bevy::prelude::Query<'t4, 't5, &'t6 mut PowerMenu>, bevy::prelude::Res<'t7, bevy::prelude::Input<bevy::prelude::KeyCode>>) {power_menu_ui_system}: iyes_loopless::condition::IntoConditionalSystem<_>`
`&mut for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'r, 's>, bevy::prelude::Query<'t0, 't1, (&'t2 mut ButtonInteraction, &'t3 PowerButton)>, bevy::prelude::Query<'t4, 't5, &'t6 mut PowerMenu>, bevy::prelude::Res<'t7, bevy::prelude::Input<bevy::prelude::KeyCode>>) {power_menu_ui_system}: bevy_ecs::system::function_system::IntoSystem<(), (), _>`
which is required by `&mut for<'r, 's, 't0, 't1, 't2, 't3, 't4, 't5, 't6, 't7> fn(bevy::prelude::Commands<'r, 's>, bevy::prelude::Query<'t0, 't1, (&'t2 mut ButtonInteraction, &'t3 PowerButton)>, bevy::prelude::Query<'t4, 't5, &'t6 mut PowerMenu>, bevy::prelude::Res<'t7, bevy::prelude::Input<bevy::prelude::KeyCode>>) {power_menu_ui_system}: iyes_loopless::condition::IntoConditionalSystem<_>`

Also, Bevy now requires all resources to explicitly #[derive(Resource)]. This is breaking the NextState resource.

Further, the trait bounds that ConditionalSet::with_system uses seems to be broken, as every single system I try to add that way is now generating an error that it does not implement the required trait bounds. The error message is again a mess of layered generics that's hard to make sense of, but this is an example, pulled from one of my simplest systems:

the trait bound `for<'r, 's, 't0, 't1, 't2> fn(bevy::prelude::Res<'r, bevy::prelude::Time>, bevy::prelude::Query<'s, 't0, (&'t1 Drag, &'t2 mut Velocity)>) {drag_system}: bevy_ecs::system::function_system::IntoSystem<(), (), _>` is not satisfied
the trait `AddConditionalToSet<ConditionSystemSet, ()>` is implemented for `ConditionalSystemDescriptor`
required for `for<'r, 's, 't0, 't1, 't2> fn(bevy::prelude::Res<'r, bevy::prelude::Time>, bevy::prelude::Query<'s, 't0, (&'t1 Drag, &'t2 mut Velocity)>) {drag_system}` to implement `iyes_loopless::condition::IntoConditionalSystem<_>`
required for `for<'r, 's, 't0, 't1, 't2> fn(bevy::prelude::Res<'r, bevy::prelude::Time>, bevy::prelude::Query<'s, 't0, (&'t1 Drag, &'t2 mut Velocity)>) {drag_system}` to implement `AddConditionalToSet<ConditionSystemSet, _>`r

I'm sure there are other incompatibilities as well, but these are the ones I'm currently seeing.

Explore how to make APIs play nicer with Plugins

Because of how our fixed timestep and states implementations rely on the user adding custom stages, that nest other stages, it is difficult to make this play nicely with Bevy's Plugin API.

If a plugin needs to add stuff to happen on state transitions, or on fixed timestep, it has no way of accessing the actual SystemStages to add systems to. Labels don't help, because Bevy's SystemLabels are a top-level Schedule thing, and cannot refer to nested stages.

The only way right now seems to be to sidestep the Plugin API altogether, and just use functions that take &mut SystemStage (for whatever they need access to) instead.

This might not be solvable while our stuff is in an external crate, and I cannot think of a better design. I'm open to ideas.

Mass-applying conditions

Currently, conditions have to be applied to each individual system, resulting in a lot of repetitive boilerplate.

Investigate what could be done to make it possible to apply conditions to many systems at once.

Bevy's system labeling apis may be a source of inspiration.

Possible for ConditionSet on exit?

In bevy you can have a system set run on state exit like so:

.add_system_set(
    SystemSet::on_exit(GameState::MainMenu)
        .label("spawn")
        .with_system(spawn_player)
        .with_system(spawn_enemies),
);

I couldnt find a way to do this with ConditionSet without having to add multiple exist systems

app
    .add_exit_system(GameState::MainMenu, spawn_player)
    .add_exit_system(GameState::MainMenu, spawn_enemies)

Adding systems to one of the stages in a `FixedTimestepStage`?

TL;DR: Is there a way to add system sets to the stages of a FixedTimestepStage that has been added to my App elsewhere?

How I understand the documentation one adds the FixedTimestepStage into the scheduler, e.g. before the CoreStage::Update. Then FixedTimestepStage has sub-stages which are executed at the time-step. In a sub-stage, I can use labels to order the execution of my systems using before or after. But say I want to make a plugin that adds a system set to one of these sub-stages, to be able to have access to these labels, how do I do that?

More concretely: I'm looking after a design in which I can have a plugin handling A components, one plugin handling B, and then one plugin handling their interaction. The interaction should take place after A and B have both been updated, so I can label the ConditionSets I have defined for A and B, but to have access to those labels it appears that all systems needs to be part of the same sub-stage. That is I imagine to be able to do something along these lines

impl Plugin for InitFixedTimestepStage {
    fn build(&self, app: &mut App) {
        app.add_stage_before(
            CoreStage::Update,
            "ft_label",
            FixedTimestepStage::new(Duration::from_secs_f32(TIME_STEP))
                .add_stage_with_label( // suggested method
                    "interaction_label",
                    SystemStage::parallel() // adding empty stage to add systems to later
                )
        )
    }
}

impl Plugin for UpdateComponentA {
    fn build(&self, app: &mut App) {
        app.schedule
            .get_stage_mut("ft_label").unwrap()
            .get_stage_mut("interaction_label").unwrap() // suggested method
            .add_system_set(
                ConditionSet::new()
                    .label("update_A")
                    .with_system(update_A)
                    .into()
            );
    }
}

// same for B... then:

impl Plugin for InteractionAB {
    fn build(&self, app: &mut App) {
        app.schedule
            .get_stage_mut("ft_label").unwrap()
            .get_stage_mut("interaction_label").unwrap() // suggested method
            .add_system_set(
                ConditionSet::new()
                    .after("update_A") // This label...
                    .after("update_B") // ... and this label is now available as we are in stage "interaction_label"
                    .label("interact_AB")
                    .with_system(interact_AB)
                    .into()
            );
    }
}

This would make the plugins more modular, otherwise I'd have to design large plugins that has access to all systems at once.

Or is there a way to do this that I'm just missing?

Cannot use before/after in ConditionSet

I don't know if it's expected, but it looks like we cannot use the before/after functions inside a condition set?

If i try to run

.add_system_set(
                ConditionSet::new()
                    .run_in_state(InGameState::Game)
                    .with_system(food_contact_event)
                    .with_system(grow_snake_event.after(food_contact_event))
                    .into()
            );

I get the error:

the trait `IntoSystem<(), (), _>` is not implemented for `SystemDescriptor`

631 |         S: AddConditionalToSet<ConditionSystemSet, P>,
    |            ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ required by this bound in `ConditionSystemSet::with_system`

Looks like it's because some trait bounds are missing?

Maybe i'm approaching things incorrectly and there's a different way to do this.

Can't use system.after() within a ConditionSet

Hello, I have an issue with systems ordering within a ConditionSet.

With a standard SystemSet, this works:

.with_system_set(
    SystemSet::new()
        .label("update")
        .with_system(movement::apply_velocity)
        .with_system(movement::constrain_entities.after(movement::apply_velocity)),
)

But with a ConditionSet this doesn't:

.with_system_set(
    ConditionSet::new()
        .label("update")
        .run_if(state_is_in_game)
        .with_system(movement::apply_velocity)
        .with_system(movement::constrain_entities.after(movement::apply_velocity)) // Error on this line
        .into(),
)

the trait bound ParallelSystemDescriptor: IntoSystem<(), (), _> is not satisfied
the trait AddConditionalToSet<ConditionSystemSet, ()> is implemented for ConditionalSystemDescriptor
required because of the requirements on the impl of IntoConditionalSystem<_> for ParallelSystemDescriptor
required because of the requirements on the impl of AddConditionalToSet<ConditionSystemSet, _> for ParallelSystemDescriptorrustcE0277

If I use a .into() after the .after(), the error changes to

type annotations needed
cannot infer type of the type parameter S declared on the associated function with_systemrustcE0282
mod.rs(52, 37): consider specifying the generic arguments: ::<S, P>

Edit: I can use .chain() for this specific case, but being able to use .after() and .before() within a ConditionSet would be better

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.