Computed State & Sub States (#11426)
## Summary/Description
This PR extends states to allow support for a wider variety of state
types and patterns, by providing 3 distinct types of state:
- Standard [`States`] can only be changed by manually setting the
[`NextState<S>`] resource. These states are the baseline on which the
other state types are built, and can be used on their own for many
simple patterns. See the [state
example](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/state.rs)
for a simple use case - these are the states that existed so far in
Bevy.
- [`SubStates`] are children of other states - they can be changed
manually using [`NextState<S>`], but are removed from the [`World`] if
the source states aren't in the right state. See the [sub_states
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/sub_states.rs)
for a simple use case based on the derive macro, or read the trait docs
for more complex scenarios.
- [`ComputedStates`] are fully derived from other states - they provide
a [`compute`](ComputedStates::compute) method that takes in the source
states and returns their derived value. They are particularly useful for
situations where a simplified view of the source states is necessary -
such as having an `InAMenu` computed state derived from a source state
that defines multiple distinct menus. See the [computed state
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/computed_states.rscomputed_states.rs)
to see a sampling of uses for these states.
# Objective
This PR is another attempt at allowing Bevy to better handle complex
state objects in a manner that doesn't rely on strict equality. While my
previous attempts (https://github.com/bevyengine/bevy/pull/10088 and
https://github.com/bevyengine/bevy/pull/9957) relied on complex matching
capacities at the point of adding a system to application, this one
instead relies on deterministically deriving simple states from more
complex ones.
As a result, it does not require any special macros, nor does it change
any other interactions with the state system once you define and add
your derived state. It also maintains a degree of distinction between
`State` and just normal application state - your derivations have to end
up being discreet pre-determined values, meaning there is less of a
risk/temptation to place a significant amount of logic and data within a
given state.
### Addition - Sub States
closes #9942
After some conversation with Maintainers & SMEs, a significant concern
was that people might attempt to use this feature as if it were
sub-states, and find themselves unable to use it appropriately. Since
`ComputedState` is mainly a state matching feature, while `SubStates`
are more of a state mutation related feature - but one that is easy to
add with the help of the machinery introduced by `ComputedState`, it was
added here as well. The relevant discussion is here:
https://discord.com/channels/691052431525675048/1200556329803186316
## Solution
closes #11358
The solution is to create a new type of state - one implementing
`ComputedStates` - which is deterministically tied to one or more other
states. Implementors write a function to transform the source states
into the computed state, and it gets triggered whenever one of the
source states changes.
In addition, we added the `FreelyMutableState` trait , which is
implemented as part of the derive macro for `States`. This allows us to
limit use of `NextState<S>` to states that are actually mutable,
preventing mis-use of `ComputedStates`.
---
## Changelog
- Added `ComputedStates` trait
- Added `FreelyMutableState` trait
- Converted `NextState` resource to an Enum, with `Unchanged` and
`Pending`
- Added `App::add_computed_state::<S: ComputedStates>()`, to allow for
easily adding derived states to an App.
- Moved the `StateTransition` schedule label from `bevy_app` to
`bevy_ecs` - but maintained the export in `bevy_app` for continuity.
- Modified the process for updating states. Instead of just having an
`apply_state_transition` system that can be added anywhere, we now have
a multi-stage process that has to run within the `StateTransition`
label. First, all the state changes are calculated - manual transitions
rely on `apply_state_transition`, while computed transitions run their
computation process before both call `internal_apply_state_transition`
to apply the transition, send out the transition event, trigger
dependent states, and record which exit/transition/enter schedules need
to occur. Once all the states have been updated, the transition
schedules are called - first the exit schedules, then transition
schedules and finally enter schedules.
- Added `SubStates` trait
- Adjusted `apply_state_transition` to be a no-op if the `State<S>`
resource doesn't exist
## Migration Guide
If the user accessed the NextState resource's value directly or created
them from scratch they will need to adjust to use the new enum variants:
- if they created a `NextState(Some(S))` - they should now use
`NextState::Pending(S)`
- if they created a `NextState(None)` -they should now use
`NextState::Unchanged`
- if they matched on the `NextState` value, they would need to make the
adjustments above
If the user manually utilized `apply_state_transition`, they should
instead use systems that trigger the `StateTransition` schedule.
---
## Future Work
There is still some future potential work in the area, but I wanted to
keep these potential features and changes separate to keep the scope
here contained, and keep the core of it easy to understand and use.
However, I do want to note some of these things, both as inspiration to
others and an illustration of what this PR could unlock.
- `NextState::Remove` - Now that the `State` related mechanisms all
utilize options (#11417), it's fairly easy to add support for explicit
state removal. And while `ComputedStates` can add and remove themselves,
right now `FreelyMutableState`s can't be removed from within the state
system. While it existed originally in this PR, it is a different
question with a separate scope and usability concerns - so having it as
it's own future PR seems like the best approach. This feature currently
lives in a separate branch in my fork, and the differences between it
and this PR can be seen here: https://github.com/lee-orr/bevy/pull/5
- `NextState::ReEnter` - this would allow you to trigger exit & entry
systems for the current state type. We can potentially also add a
`NextState::ReEnterRecirsive` to also re-trigger any states that depend
on the current one.
- More mechanisms for `State` updates - This PR would finally make
states that aren't a set of exclusive Enums useful, and with that comes
the question of setting state more effectively. Right now, to update a
state you either need to fully create the new state, or include the
`Res<Option<State<S>>>` resource in your system, clone the state, mutate
it, and then use `NextState.set(my_mutated_state)` to make it the
pending next state. There are a few other potential methods that could
be implemented in future PRs:
- Inverse Compute States - these would essentially be compute states
that have an additional (manually defined) function that can be used to
nudge the source states so that they result in the computed states
having a given value. For example, you could use set the `IsPaused`
state, and it would attempt to pause or unpause the game by modifying
the `AppState` as needed.
- Closure-based state modification - this would involve adding a
`NextState.modify(f: impl Fn(Option<S> -> Option<S>)` method, and then
you can pass in closures or function pointers to adjust the state as
needed.
- Message-based state modification - this would involve either creating
states that can respond to specific messages, similar to Elm or Redux.
These could either use the `NextState` mechanism or the Event mechanism.
- ~`SubStates` - which are essentially a hybrid of computed and manual
states. In the simplest (and most likely) version, they would work by
having a computed element that determines whether the state should
exist, and if it should has the capacity to add a new version in, but
then any changes to it's content would be freely mutated.~ this feature
is now part of this PR. See above.
- Lastly, since states are getting more complex there might be value in
moving them out of `bevy_ecs` and into their own crate, or at least out
of the `schedule` module into a `states` module. #11087
As mentioned, all these future work elements are TBD and are explicitly
not part of this PR - I just wanted to provide them as potential
explorations for the future.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Marcel Champagne <voiceofmarcel@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
2024-05-02 19:36:23 +00:00
|
|
|
use crate::{App, InternedAppLabel, Plugin, Plugins, PluginsState, Startup};
|
2024-03-31 03:16:10 +00:00
|
|
|
use bevy_ecs::{
|
Optimize Event Updates (#12936)
# Objective
Improve performance scalability when adding new event types to a Bevy
app. Currently, just using Bevy in the default configuration, all apps
spend upwards of 100+us in the `First` schedule, every app tick,
evaluating if it should update events or not, even if events are not
being used for that particular frame, and this scales with the number of
Events registered in the app.
## Solution
As `Events::update` is guaranteed `O(1)` by just checking if a
resource's value, swapping two Vecs, and then clearing one of them, the
actual cost of running `event_update_system` is *very* cheap. The
overhead of doing system dependency injection, task scheduling ,and the
multithreaded executor outweighs the cost of running the system by a
large margin.
Create an `EventRegistry` resource that keeps a number of function
pointers that update each event. Replace the per-event type
`event_update_system` with a singular exclusive system uses the
`EventRegistry` to update all events instead. Update `SubApp::add_event`
to use `EventRegistry` instead.
## Performance
This speeds reduces the cost of the `First` schedule in both many_foxes
and many_cubes by over 80%. Note this is with system spans on. The
majority of this is now context-switching costs from launching
`time_system`, which should be mostly eliminated with #12869.
![image](https://github.com/bevyengine/bevy/assets/3137680/037624be-21a2-4dc2-a42f-9d0bfa3e9b4a)
The actual `event_update_system` is usually *very* short, using only a
few microseconds on average.
![image](https://github.com/bevyengine/bevy/assets/3137680/01ff1689-3595-49b6-8f09-5c44bcf903e8)
---
## Changelog
TODO
## Migration Guide
TODO
---------
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-04-13 14:11:28 +00:00
|
|
|
event::EventRegistry,
|
2024-03-31 03:16:10 +00:00
|
|
|
prelude::*,
|
|
|
|
schedule::{
|
Computed State & Sub States (#11426)
## Summary/Description
This PR extends states to allow support for a wider variety of state
types and patterns, by providing 3 distinct types of state:
- Standard [`States`] can only be changed by manually setting the
[`NextState<S>`] resource. These states are the baseline on which the
other state types are built, and can be used on their own for many
simple patterns. See the [state
example](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/state.rs)
for a simple use case - these are the states that existed so far in
Bevy.
- [`SubStates`] are children of other states - they can be changed
manually using [`NextState<S>`], but are removed from the [`World`] if
the source states aren't in the right state. See the [sub_states
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/sub_states.rs)
for a simple use case based on the derive macro, or read the trait docs
for more complex scenarios.
- [`ComputedStates`] are fully derived from other states - they provide
a [`compute`](ComputedStates::compute) method that takes in the source
states and returns their derived value. They are particularly useful for
situations where a simplified view of the source states is necessary -
such as having an `InAMenu` computed state derived from a source state
that defines multiple distinct menus. See the [computed state
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/computed_states.rscomputed_states.rs)
to see a sampling of uses for these states.
# Objective
This PR is another attempt at allowing Bevy to better handle complex
state objects in a manner that doesn't rely on strict equality. While my
previous attempts (https://github.com/bevyengine/bevy/pull/10088 and
https://github.com/bevyengine/bevy/pull/9957) relied on complex matching
capacities at the point of adding a system to application, this one
instead relies on deterministically deriving simple states from more
complex ones.
As a result, it does not require any special macros, nor does it change
any other interactions with the state system once you define and add
your derived state. It also maintains a degree of distinction between
`State` and just normal application state - your derivations have to end
up being discreet pre-determined values, meaning there is less of a
risk/temptation to place a significant amount of logic and data within a
given state.
### Addition - Sub States
closes #9942
After some conversation with Maintainers & SMEs, a significant concern
was that people might attempt to use this feature as if it were
sub-states, and find themselves unable to use it appropriately. Since
`ComputedState` is mainly a state matching feature, while `SubStates`
are more of a state mutation related feature - but one that is easy to
add with the help of the machinery introduced by `ComputedState`, it was
added here as well. The relevant discussion is here:
https://discord.com/channels/691052431525675048/1200556329803186316
## Solution
closes #11358
The solution is to create a new type of state - one implementing
`ComputedStates` - which is deterministically tied to one or more other
states. Implementors write a function to transform the source states
into the computed state, and it gets triggered whenever one of the
source states changes.
In addition, we added the `FreelyMutableState` trait , which is
implemented as part of the derive macro for `States`. This allows us to
limit use of `NextState<S>` to states that are actually mutable,
preventing mis-use of `ComputedStates`.
---
## Changelog
- Added `ComputedStates` trait
- Added `FreelyMutableState` trait
- Converted `NextState` resource to an Enum, with `Unchanged` and
`Pending`
- Added `App::add_computed_state::<S: ComputedStates>()`, to allow for
easily adding derived states to an App.
- Moved the `StateTransition` schedule label from `bevy_app` to
`bevy_ecs` - but maintained the export in `bevy_app` for continuity.
- Modified the process for updating states. Instead of just having an
`apply_state_transition` system that can be added anywhere, we now have
a multi-stage process that has to run within the `StateTransition`
label. First, all the state changes are calculated - manual transitions
rely on `apply_state_transition`, while computed transitions run their
computation process before both call `internal_apply_state_transition`
to apply the transition, send out the transition event, trigger
dependent states, and record which exit/transition/enter schedules need
to occur. Once all the states have been updated, the transition
schedules are called - first the exit schedules, then transition
schedules and finally enter schedules.
- Added `SubStates` trait
- Adjusted `apply_state_transition` to be a no-op if the `State<S>`
resource doesn't exist
## Migration Guide
If the user accessed the NextState resource's value directly or created
them from scratch they will need to adjust to use the new enum variants:
- if they created a `NextState(Some(S))` - they should now use
`NextState::Pending(S)`
- if they created a `NextState(None)` -they should now use
`NextState::Unchanged`
- if they matched on the `NextState` value, they would need to make the
adjustments above
If the user manually utilized `apply_state_transition`, they should
instead use systems that trigger the `StateTransition` schedule.
---
## Future Work
There is still some future potential work in the area, but I wanted to
keep these potential features and changes separate to keep the scope
here contained, and keep the core of it easy to understand and use.
However, I do want to note some of these things, both as inspiration to
others and an illustration of what this PR could unlock.
- `NextState::Remove` - Now that the `State` related mechanisms all
utilize options (#11417), it's fairly easy to add support for explicit
state removal. And while `ComputedStates` can add and remove themselves,
right now `FreelyMutableState`s can't be removed from within the state
system. While it existed originally in this PR, it is a different
question with a separate scope and usability concerns - so having it as
it's own future PR seems like the best approach. This feature currently
lives in a separate branch in my fork, and the differences between it
and this PR can be seen here: https://github.com/lee-orr/bevy/pull/5
- `NextState::ReEnter` - this would allow you to trigger exit & entry
systems for the current state type. We can potentially also add a
`NextState::ReEnterRecirsive` to also re-trigger any states that depend
on the current one.
- More mechanisms for `State` updates - This PR would finally make
states that aren't a set of exclusive Enums useful, and with that comes
the question of setting state more effectively. Right now, to update a
state you either need to fully create the new state, or include the
`Res<Option<State<S>>>` resource in your system, clone the state, mutate
it, and then use `NextState.set(my_mutated_state)` to make it the
pending next state. There are a few other potential methods that could
be implemented in future PRs:
- Inverse Compute States - these would essentially be compute states
that have an additional (manually defined) function that can be used to
nudge the source states so that they result in the computed states
having a given value. For example, you could use set the `IsPaused`
state, and it would attempt to pause or unpause the game by modifying
the `AppState` as needed.
- Closure-based state modification - this would involve adding a
`NextState.modify(f: impl Fn(Option<S> -> Option<S>)` method, and then
you can pass in closures or function pointers to adjust the state as
needed.
- Message-based state modification - this would involve either creating
states that can respond to specific messages, similar to Elm or Redux.
These could either use the `NextState` mechanism or the Event mechanism.
- ~`SubStates` - which are essentially a hybrid of computed and manual
states. In the simplest (and most likely) version, they would work by
having a computed element that determines whether the state should
exist, and if it should has the capacity to add a new version in, but
then any changes to it's content would be freely mutated.~ this feature
is now part of this PR. See above.
- Lastly, since states are getting more complex there might be value in
moving them out of `bevy_ecs` and into their own crate, or at least out
of the `schedule` module into a `states` module. #11087
As mentioned, all these future work elements are TBD and are explicitly
not part of this PR - I just wanted to provide them as potential
explorations for the future.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Marcel Champagne <voiceofmarcel@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
2024-05-02 19:36:23 +00:00
|
|
|
setup_state_transitions_in_world, FreelyMutableState, InternedScheduleLabel,
|
|
|
|
ScheduleBuildSettings, ScheduleLabel,
|
2024-03-31 03:16:10 +00:00
|
|
|
},
|
|
|
|
system::SystemId,
|
|
|
|
};
|
|
|
|
#[cfg(feature = "trace")]
|
|
|
|
use bevy_utils::tracing::info_span;
|
2024-04-28 21:32:16 +00:00
|
|
|
use bevy_utils::{HashMap, HashSet};
|
2024-03-31 03:16:10 +00:00
|
|
|
use std::fmt::Debug;
|
|
|
|
|
|
|
|
type ExtractFn = Box<dyn Fn(&mut World, &mut World) + Send>;
|
|
|
|
|
|
|
|
/// A secondary application with its own [`World`]. These can run independently of each other.
|
|
|
|
///
|
|
|
|
/// These are useful for situations where certain processes (e.g. a render thread) need to be kept
|
|
|
|
/// separate from the main application.
|
|
|
|
///
|
|
|
|
/// # Example
|
|
|
|
///
|
|
|
|
/// ```
|
|
|
|
/// # use bevy_app::{App, AppLabel, SubApp, Main};
|
|
|
|
/// # use bevy_ecs::prelude::*;
|
|
|
|
/// # use bevy_ecs::schedule::ScheduleLabel;
|
|
|
|
///
|
|
|
|
/// #[derive(Resource, Default)]
|
|
|
|
/// struct Val(pub i32);
|
|
|
|
///
|
|
|
|
/// #[derive(Debug, Clone, Copy, Hash, PartialEq, Eq, AppLabel)]
|
|
|
|
/// struct ExampleApp;
|
|
|
|
///
|
|
|
|
/// // Create an app with a certain resource.
|
|
|
|
/// let mut app = App::new();
|
|
|
|
/// app.insert_resource(Val(10));
|
|
|
|
///
|
|
|
|
/// // Create a sub-app with the same resource and a single schedule.
|
|
|
|
/// let mut sub_app = SubApp::new();
|
|
|
|
/// sub_app.insert_resource(Val(100));
|
|
|
|
///
|
|
|
|
/// // Setup an extract function to copy the resource's value in the main world.
|
|
|
|
/// sub_app.set_extract(|main_world, sub_world| {
|
|
|
|
/// sub_world.resource_mut::<Val>().0 = main_world.resource::<Val>().0;
|
|
|
|
/// });
|
|
|
|
///
|
|
|
|
/// // Schedule a system that will verify extraction is working.
|
|
|
|
/// sub_app.add_systems(Main, |counter: Res<Val>| {
|
|
|
|
/// // The value will be copied during extraction, so we should see 10 instead of 100.
|
|
|
|
/// assert_eq!(counter.0, 10);
|
|
|
|
/// });
|
|
|
|
///
|
|
|
|
/// // Add the sub-app to the main app.
|
|
|
|
/// app.insert_sub_app(ExampleApp, sub_app);
|
|
|
|
///
|
|
|
|
/// // Update the application once (using the default runner).
|
|
|
|
/// app.run();
|
|
|
|
/// ```
|
|
|
|
pub struct SubApp {
|
|
|
|
/// The data of this application.
|
|
|
|
world: World,
|
2024-04-28 21:32:16 +00:00
|
|
|
/// List of plugins that have been added.
|
|
|
|
pub(crate) plugin_registry: Vec<Box<dyn Plugin>>,
|
|
|
|
/// The names of plugins that have been added to this app. (used to track duplicates and
|
|
|
|
/// already-registered plugins)
|
|
|
|
pub(crate) plugin_names: HashSet<String>,
|
2024-03-31 03:16:10 +00:00
|
|
|
/// Panics if an update is attempted while plugins are building.
|
|
|
|
pub(crate) plugin_build_depth: usize,
|
|
|
|
pub(crate) plugins_state: PluginsState,
|
|
|
|
/// The schedule that will be run by [`update`](Self::update).
|
|
|
|
pub update_schedule: Option<InternedScheduleLabel>,
|
|
|
|
/// A function that gives mutable access to two app worlds. This is primarily
|
|
|
|
/// intended for copying data from the main world to secondary worlds.
|
|
|
|
extract: Option<ExtractFn>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Debug for SubApp {
|
|
|
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
|
|
|
write!(f, "SubApp")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl Default for SubApp {
|
|
|
|
fn default() -> Self {
|
|
|
|
let mut world = World::new();
|
|
|
|
world.init_resource::<Schedules>();
|
|
|
|
Self {
|
|
|
|
world,
|
2024-04-28 21:32:16 +00:00
|
|
|
plugin_registry: Vec::default(),
|
|
|
|
plugin_names: HashSet::default(),
|
2024-03-31 03:16:10 +00:00
|
|
|
plugin_build_depth: 0,
|
|
|
|
plugins_state: PluginsState::Adding,
|
|
|
|
update_schedule: None,
|
|
|
|
extract: None,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SubApp {
|
|
|
|
/// Returns a default, empty [`SubApp`].
|
|
|
|
pub fn new() -> Self {
|
|
|
|
Self::default()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// This method is a workaround. Each [`SubApp`] can have its own plugins, but [`Plugin`]
|
|
|
|
/// works on an [`App`] as a whole.
|
|
|
|
fn run_as_app<F>(&mut self, f: F)
|
|
|
|
where
|
|
|
|
F: FnOnce(&mut App),
|
|
|
|
{
|
|
|
|
let mut app = App::empty();
|
|
|
|
std::mem::swap(self, &mut app.sub_apps.main);
|
|
|
|
f(&mut app);
|
|
|
|
std::mem::swap(self, &mut app.sub_apps.main);
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a reference to the [`World`].
|
|
|
|
pub fn world(&self) -> &World {
|
|
|
|
&self.world
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a mutable reference to the [`World`].
|
|
|
|
pub fn world_mut(&mut self) -> &mut World {
|
|
|
|
&mut self.world
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs the default schedule.
|
|
|
|
pub fn update(&mut self) {
|
|
|
|
if self.is_building_plugins() {
|
|
|
|
panic!("SubApp::update() was called while a plugin was building.");
|
|
|
|
}
|
|
|
|
|
|
|
|
if let Some(label) = self.update_schedule {
|
|
|
|
self.world.run_schedule(label);
|
|
|
|
}
|
|
|
|
self.world.clear_trackers();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Extracts data from `world` into the app's world using the registered extract method.
|
|
|
|
///
|
|
|
|
/// **Note:** There is no default extract method. Calling `extract` does nothing if
|
|
|
|
/// [`set_extract`](Self::set_extract) has not been called.
|
|
|
|
pub fn extract(&mut self, world: &mut World) {
|
|
|
|
if let Some(f) = self.extract.as_mut() {
|
|
|
|
f(world, &mut self.world);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Sets the method that will be called by [`extract`](Self::extract).
|
|
|
|
///
|
|
|
|
/// The first argument is the `World` to extract data from, the second argument is the app `World`.
|
|
|
|
pub fn set_extract<F>(&mut self, extract: F) -> &mut Self
|
|
|
|
where
|
|
|
|
F: Fn(&mut World, &mut World) + Send + 'static,
|
|
|
|
{
|
|
|
|
self.extract = Some(Box::new(extract));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::insert_resource`].
|
|
|
|
pub fn insert_resource<R: Resource>(&mut self, resource: R) -> &mut Self {
|
|
|
|
self.world.insert_resource(resource);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::init_resource`].
|
|
|
|
pub fn init_resource<R: Resource + FromWorld>(&mut self) -> &mut Self {
|
|
|
|
self.world.init_resource::<R>();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::add_systems`].
|
|
|
|
pub fn add_systems<M>(
|
|
|
|
&mut self,
|
|
|
|
schedule: impl ScheduleLabel,
|
|
|
|
systems: impl IntoSystemConfigs<M>,
|
|
|
|
) -> &mut Self {
|
|
|
|
let label = schedule.intern();
|
|
|
|
let mut schedules = self.world.resource_mut::<Schedules>();
|
|
|
|
if let Some(schedule) = schedules.get_mut(label) {
|
|
|
|
schedule.add_systems(systems);
|
|
|
|
} else {
|
|
|
|
let mut new_schedule = Schedule::new(label);
|
|
|
|
new_schedule.add_systems(systems);
|
|
|
|
schedules.insert(new_schedule);
|
|
|
|
}
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::register_system`].
|
|
|
|
pub fn register_system<I: 'static, O: 'static, M, S: IntoSystem<I, O, M> + 'static>(
|
|
|
|
&mut self,
|
|
|
|
system: S,
|
|
|
|
) -> SystemId<I, O> {
|
|
|
|
self.world.register_system(system)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::configure_sets`].
|
|
|
|
#[track_caller]
|
|
|
|
pub fn configure_sets(
|
|
|
|
&mut self,
|
|
|
|
schedule: impl ScheduleLabel,
|
|
|
|
sets: impl IntoSystemSetConfigs,
|
|
|
|
) -> &mut Self {
|
|
|
|
let label = schedule.intern();
|
|
|
|
let mut schedules = self.world.resource_mut::<Schedules>();
|
|
|
|
if let Some(schedule) = schedules.get_mut(label) {
|
|
|
|
schedule.configure_sets(sets);
|
|
|
|
} else {
|
|
|
|
let mut new_schedule = Schedule::new(label);
|
|
|
|
new_schedule.configure_sets(sets);
|
|
|
|
schedules.insert(new_schedule);
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::add_schedule`].
|
|
|
|
pub fn add_schedule(&mut self, schedule: Schedule) -> &mut Self {
|
|
|
|
let mut schedules = self.world.resource_mut::<Schedules>();
|
|
|
|
schedules.insert(schedule);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::init_schedule`].
|
|
|
|
pub fn init_schedule(&mut self, label: impl ScheduleLabel) -> &mut Self {
|
|
|
|
let label = label.intern();
|
|
|
|
let mut schedules = self.world.resource_mut::<Schedules>();
|
|
|
|
if !schedules.contains(label) {
|
|
|
|
schedules.insert(Schedule::new(label));
|
|
|
|
}
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::get_schedule`].
|
|
|
|
pub fn get_schedule(&self, label: impl ScheduleLabel) -> Option<&Schedule> {
|
|
|
|
let schedules = self.world.get_resource::<Schedules>()?;
|
|
|
|
schedules.get(label)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::get_schedule_mut`].
|
|
|
|
pub fn get_schedule_mut(&mut self, label: impl ScheduleLabel) -> Option<&mut Schedule> {
|
|
|
|
let schedules = self.world.get_resource_mut::<Schedules>()?;
|
|
|
|
// We must call `.into_inner` here because the borrow checker only understands reborrows
|
|
|
|
// using ordinary references, not our `Mut` smart pointers.
|
|
|
|
schedules.into_inner().get_mut(label)
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::edit_schedule`].
|
|
|
|
pub fn edit_schedule(
|
|
|
|
&mut self,
|
|
|
|
label: impl ScheduleLabel,
|
|
|
|
mut f: impl FnMut(&mut Schedule),
|
|
|
|
) -> &mut Self {
|
|
|
|
let label = label.intern();
|
|
|
|
let mut schedules = self.world.resource_mut::<Schedules>();
|
|
|
|
if !schedules.contains(label) {
|
|
|
|
schedules.insert(Schedule::new(label));
|
|
|
|
}
|
|
|
|
|
|
|
|
let schedule = schedules.get_mut(label).unwrap();
|
|
|
|
f(schedule);
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::configure_schedules`].
|
|
|
|
pub fn configure_schedules(
|
|
|
|
&mut self,
|
|
|
|
schedule_build_settings: ScheduleBuildSettings,
|
|
|
|
) -> &mut Self {
|
|
|
|
self.world_mut()
|
|
|
|
.resource_mut::<Schedules>()
|
|
|
|
.configure_schedules(schedule_build_settings);
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::allow_ambiguous_component`].
|
|
|
|
pub fn allow_ambiguous_component<T: Component>(&mut self) -> &mut Self {
|
|
|
|
self.world_mut().allow_ambiguous_component::<T>();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::allow_ambiguous_resource`].
|
|
|
|
pub fn allow_ambiguous_resource<T: Resource>(&mut self) -> &mut Self {
|
|
|
|
self.world_mut().allow_ambiguous_resource::<T>();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::ignore_ambiguity`].
|
|
|
|
#[track_caller]
|
|
|
|
pub fn ignore_ambiguity<M1, M2, S1, S2>(
|
|
|
|
&mut self,
|
|
|
|
schedule: impl ScheduleLabel,
|
|
|
|
a: S1,
|
|
|
|
b: S2,
|
|
|
|
) -> &mut Self
|
|
|
|
where
|
|
|
|
S1: IntoSystemSet<M1>,
|
|
|
|
S2: IntoSystemSet<M2>,
|
|
|
|
{
|
|
|
|
let schedule = schedule.intern();
|
|
|
|
let mut schedules = self.world.resource_mut::<Schedules>();
|
|
|
|
|
|
|
|
if let Some(schedule) = schedules.get_mut(schedule) {
|
|
|
|
let schedule: &mut Schedule = schedule;
|
|
|
|
schedule.ignore_ambiguity(a, b);
|
|
|
|
} else {
|
|
|
|
let mut new_schedule = Schedule::new(schedule);
|
|
|
|
new_schedule.ignore_ambiguity(a, b);
|
|
|
|
schedules.insert(new_schedule);
|
|
|
|
}
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::init_state`].
|
Computed State & Sub States (#11426)
## Summary/Description
This PR extends states to allow support for a wider variety of state
types and patterns, by providing 3 distinct types of state:
- Standard [`States`] can only be changed by manually setting the
[`NextState<S>`] resource. These states are the baseline on which the
other state types are built, and can be used on their own for many
simple patterns. See the [state
example](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/state.rs)
for a simple use case - these are the states that existed so far in
Bevy.
- [`SubStates`] are children of other states - they can be changed
manually using [`NextState<S>`], but are removed from the [`World`] if
the source states aren't in the right state. See the [sub_states
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/sub_states.rs)
for a simple use case based on the derive macro, or read the trait docs
for more complex scenarios.
- [`ComputedStates`] are fully derived from other states - they provide
a [`compute`](ComputedStates::compute) method that takes in the source
states and returns their derived value. They are particularly useful for
situations where a simplified view of the source states is necessary -
such as having an `InAMenu` computed state derived from a source state
that defines multiple distinct menus. See the [computed state
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/computed_states.rscomputed_states.rs)
to see a sampling of uses for these states.
# Objective
This PR is another attempt at allowing Bevy to better handle complex
state objects in a manner that doesn't rely on strict equality. While my
previous attempts (https://github.com/bevyengine/bevy/pull/10088 and
https://github.com/bevyengine/bevy/pull/9957) relied on complex matching
capacities at the point of adding a system to application, this one
instead relies on deterministically deriving simple states from more
complex ones.
As a result, it does not require any special macros, nor does it change
any other interactions with the state system once you define and add
your derived state. It also maintains a degree of distinction between
`State` and just normal application state - your derivations have to end
up being discreet pre-determined values, meaning there is less of a
risk/temptation to place a significant amount of logic and data within a
given state.
### Addition - Sub States
closes #9942
After some conversation with Maintainers & SMEs, a significant concern
was that people might attempt to use this feature as if it were
sub-states, and find themselves unable to use it appropriately. Since
`ComputedState` is mainly a state matching feature, while `SubStates`
are more of a state mutation related feature - but one that is easy to
add with the help of the machinery introduced by `ComputedState`, it was
added here as well. The relevant discussion is here:
https://discord.com/channels/691052431525675048/1200556329803186316
## Solution
closes #11358
The solution is to create a new type of state - one implementing
`ComputedStates` - which is deterministically tied to one or more other
states. Implementors write a function to transform the source states
into the computed state, and it gets triggered whenever one of the
source states changes.
In addition, we added the `FreelyMutableState` trait , which is
implemented as part of the derive macro for `States`. This allows us to
limit use of `NextState<S>` to states that are actually mutable,
preventing mis-use of `ComputedStates`.
---
## Changelog
- Added `ComputedStates` trait
- Added `FreelyMutableState` trait
- Converted `NextState` resource to an Enum, with `Unchanged` and
`Pending`
- Added `App::add_computed_state::<S: ComputedStates>()`, to allow for
easily adding derived states to an App.
- Moved the `StateTransition` schedule label from `bevy_app` to
`bevy_ecs` - but maintained the export in `bevy_app` for continuity.
- Modified the process for updating states. Instead of just having an
`apply_state_transition` system that can be added anywhere, we now have
a multi-stage process that has to run within the `StateTransition`
label. First, all the state changes are calculated - manual transitions
rely on `apply_state_transition`, while computed transitions run their
computation process before both call `internal_apply_state_transition`
to apply the transition, send out the transition event, trigger
dependent states, and record which exit/transition/enter schedules need
to occur. Once all the states have been updated, the transition
schedules are called - first the exit schedules, then transition
schedules and finally enter schedules.
- Added `SubStates` trait
- Adjusted `apply_state_transition` to be a no-op if the `State<S>`
resource doesn't exist
## Migration Guide
If the user accessed the NextState resource's value directly or created
them from scratch they will need to adjust to use the new enum variants:
- if they created a `NextState(Some(S))` - they should now use
`NextState::Pending(S)`
- if they created a `NextState(None)` -they should now use
`NextState::Unchanged`
- if they matched on the `NextState` value, they would need to make the
adjustments above
If the user manually utilized `apply_state_transition`, they should
instead use systems that trigger the `StateTransition` schedule.
---
## Future Work
There is still some future potential work in the area, but I wanted to
keep these potential features and changes separate to keep the scope
here contained, and keep the core of it easy to understand and use.
However, I do want to note some of these things, both as inspiration to
others and an illustration of what this PR could unlock.
- `NextState::Remove` - Now that the `State` related mechanisms all
utilize options (#11417), it's fairly easy to add support for explicit
state removal. And while `ComputedStates` can add and remove themselves,
right now `FreelyMutableState`s can't be removed from within the state
system. While it existed originally in this PR, it is a different
question with a separate scope and usability concerns - so having it as
it's own future PR seems like the best approach. This feature currently
lives in a separate branch in my fork, and the differences between it
and this PR can be seen here: https://github.com/lee-orr/bevy/pull/5
- `NextState::ReEnter` - this would allow you to trigger exit & entry
systems for the current state type. We can potentially also add a
`NextState::ReEnterRecirsive` to also re-trigger any states that depend
on the current one.
- More mechanisms for `State` updates - This PR would finally make
states that aren't a set of exclusive Enums useful, and with that comes
the question of setting state more effectively. Right now, to update a
state you either need to fully create the new state, or include the
`Res<Option<State<S>>>` resource in your system, clone the state, mutate
it, and then use `NextState.set(my_mutated_state)` to make it the
pending next state. There are a few other potential methods that could
be implemented in future PRs:
- Inverse Compute States - these would essentially be compute states
that have an additional (manually defined) function that can be used to
nudge the source states so that they result in the computed states
having a given value. For example, you could use set the `IsPaused`
state, and it would attempt to pause or unpause the game by modifying
the `AppState` as needed.
- Closure-based state modification - this would involve adding a
`NextState.modify(f: impl Fn(Option<S> -> Option<S>)` method, and then
you can pass in closures or function pointers to adjust the state as
needed.
- Message-based state modification - this would involve either creating
states that can respond to specific messages, similar to Elm or Redux.
These could either use the `NextState` mechanism or the Event mechanism.
- ~`SubStates` - which are essentially a hybrid of computed and manual
states. In the simplest (and most likely) version, they would work by
having a computed element that determines whether the state should
exist, and if it should has the capacity to add a new version in, but
then any changes to it's content would be freely mutated.~ this feature
is now part of this PR. See above.
- Lastly, since states are getting more complex there might be value in
moving them out of `bevy_ecs` and into their own crate, or at least out
of the `schedule` module into a `states` module. #11087
As mentioned, all these future work elements are TBD and are explicitly
not part of this PR - I just wanted to provide them as potential
explorations for the future.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Marcel Champagne <voiceofmarcel@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
2024-05-02 19:36:23 +00:00
|
|
|
pub fn init_state<S: FreelyMutableState + FromWorld>(&mut self) -> &mut Self {
|
2024-03-31 03:16:10 +00:00
|
|
|
if !self.world.contains_resource::<State<S>>() {
|
Computed State & Sub States (#11426)
## Summary/Description
This PR extends states to allow support for a wider variety of state
types and patterns, by providing 3 distinct types of state:
- Standard [`States`] can only be changed by manually setting the
[`NextState<S>`] resource. These states are the baseline on which the
other state types are built, and can be used on their own for many
simple patterns. See the [state
example](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/state.rs)
for a simple use case - these are the states that existed so far in
Bevy.
- [`SubStates`] are children of other states - they can be changed
manually using [`NextState<S>`], but are removed from the [`World`] if
the source states aren't in the right state. See the [sub_states
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/sub_states.rs)
for a simple use case based on the derive macro, or read the trait docs
for more complex scenarios.
- [`ComputedStates`] are fully derived from other states - they provide
a [`compute`](ComputedStates::compute) method that takes in the source
states and returns their derived value. They are particularly useful for
situations where a simplified view of the source states is necessary -
such as having an `InAMenu` computed state derived from a source state
that defines multiple distinct menus. See the [computed state
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/computed_states.rscomputed_states.rs)
to see a sampling of uses for these states.
# Objective
This PR is another attempt at allowing Bevy to better handle complex
state objects in a manner that doesn't rely on strict equality. While my
previous attempts (https://github.com/bevyengine/bevy/pull/10088 and
https://github.com/bevyengine/bevy/pull/9957) relied on complex matching
capacities at the point of adding a system to application, this one
instead relies on deterministically deriving simple states from more
complex ones.
As a result, it does not require any special macros, nor does it change
any other interactions with the state system once you define and add
your derived state. It also maintains a degree of distinction between
`State` and just normal application state - your derivations have to end
up being discreet pre-determined values, meaning there is less of a
risk/temptation to place a significant amount of logic and data within a
given state.
### Addition - Sub States
closes #9942
After some conversation with Maintainers & SMEs, a significant concern
was that people might attempt to use this feature as if it were
sub-states, and find themselves unable to use it appropriately. Since
`ComputedState` is mainly a state matching feature, while `SubStates`
are more of a state mutation related feature - but one that is easy to
add with the help of the machinery introduced by `ComputedState`, it was
added here as well. The relevant discussion is here:
https://discord.com/channels/691052431525675048/1200556329803186316
## Solution
closes #11358
The solution is to create a new type of state - one implementing
`ComputedStates` - which is deterministically tied to one or more other
states. Implementors write a function to transform the source states
into the computed state, and it gets triggered whenever one of the
source states changes.
In addition, we added the `FreelyMutableState` trait , which is
implemented as part of the derive macro for `States`. This allows us to
limit use of `NextState<S>` to states that are actually mutable,
preventing mis-use of `ComputedStates`.
---
## Changelog
- Added `ComputedStates` trait
- Added `FreelyMutableState` trait
- Converted `NextState` resource to an Enum, with `Unchanged` and
`Pending`
- Added `App::add_computed_state::<S: ComputedStates>()`, to allow for
easily adding derived states to an App.
- Moved the `StateTransition` schedule label from `bevy_app` to
`bevy_ecs` - but maintained the export in `bevy_app` for continuity.
- Modified the process for updating states. Instead of just having an
`apply_state_transition` system that can be added anywhere, we now have
a multi-stage process that has to run within the `StateTransition`
label. First, all the state changes are calculated - manual transitions
rely on `apply_state_transition`, while computed transitions run their
computation process before both call `internal_apply_state_transition`
to apply the transition, send out the transition event, trigger
dependent states, and record which exit/transition/enter schedules need
to occur. Once all the states have been updated, the transition
schedules are called - first the exit schedules, then transition
schedules and finally enter schedules.
- Added `SubStates` trait
- Adjusted `apply_state_transition` to be a no-op if the `State<S>`
resource doesn't exist
## Migration Guide
If the user accessed the NextState resource's value directly or created
them from scratch they will need to adjust to use the new enum variants:
- if they created a `NextState(Some(S))` - they should now use
`NextState::Pending(S)`
- if they created a `NextState(None)` -they should now use
`NextState::Unchanged`
- if they matched on the `NextState` value, they would need to make the
adjustments above
If the user manually utilized `apply_state_transition`, they should
instead use systems that trigger the `StateTransition` schedule.
---
## Future Work
There is still some future potential work in the area, but I wanted to
keep these potential features and changes separate to keep the scope
here contained, and keep the core of it easy to understand and use.
However, I do want to note some of these things, both as inspiration to
others and an illustration of what this PR could unlock.
- `NextState::Remove` - Now that the `State` related mechanisms all
utilize options (#11417), it's fairly easy to add support for explicit
state removal. And while `ComputedStates` can add and remove themselves,
right now `FreelyMutableState`s can't be removed from within the state
system. While it existed originally in this PR, it is a different
question with a separate scope and usability concerns - so having it as
it's own future PR seems like the best approach. This feature currently
lives in a separate branch in my fork, and the differences between it
and this PR can be seen here: https://github.com/lee-orr/bevy/pull/5
- `NextState::ReEnter` - this would allow you to trigger exit & entry
systems for the current state type. We can potentially also add a
`NextState::ReEnterRecirsive` to also re-trigger any states that depend
on the current one.
- More mechanisms for `State` updates - This PR would finally make
states that aren't a set of exclusive Enums useful, and with that comes
the question of setting state more effectively. Right now, to update a
state you either need to fully create the new state, or include the
`Res<Option<State<S>>>` resource in your system, clone the state, mutate
it, and then use `NextState.set(my_mutated_state)` to make it the
pending next state. There are a few other potential methods that could
be implemented in future PRs:
- Inverse Compute States - these would essentially be compute states
that have an additional (manually defined) function that can be used to
nudge the source states so that they result in the computed states
having a given value. For example, you could use set the `IsPaused`
state, and it would attempt to pause or unpause the game by modifying
the `AppState` as needed.
- Closure-based state modification - this would involve adding a
`NextState.modify(f: impl Fn(Option<S> -> Option<S>)` method, and then
you can pass in closures or function pointers to adjust the state as
needed.
- Message-based state modification - this would involve either creating
states that can respond to specific messages, similar to Elm or Redux.
These could either use the `NextState` mechanism or the Event mechanism.
- ~`SubStates` - which are essentially a hybrid of computed and manual
states. In the simplest (and most likely) version, they would work by
having a computed element that determines whether the state should
exist, and if it should has the capacity to add a new version in, but
then any changes to it's content would be freely mutated.~ this feature
is now part of this PR. See above.
- Lastly, since states are getting more complex there might be value in
moving them out of `bevy_ecs` and into their own crate, or at least out
of the `schedule` module into a `states` module. #11087
As mentioned, all these future work elements are TBD and are explicitly
not part of this PR - I just wanted to provide them as potential
explorations for the future.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Marcel Champagne <voiceofmarcel@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
2024-05-02 19:36:23 +00:00
|
|
|
setup_state_transitions_in_world(&mut self.world, Some(Startup.intern()));
|
2024-03-31 03:16:10 +00:00
|
|
|
self.init_resource::<State<S>>()
|
|
|
|
.init_resource::<NextState<S>>()
|
Computed State & Sub States (#11426)
## Summary/Description
This PR extends states to allow support for a wider variety of state
types and patterns, by providing 3 distinct types of state:
- Standard [`States`] can only be changed by manually setting the
[`NextState<S>`] resource. These states are the baseline on which the
other state types are built, and can be used on their own for many
simple patterns. See the [state
example](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/state.rs)
for a simple use case - these are the states that existed so far in
Bevy.
- [`SubStates`] are children of other states - they can be changed
manually using [`NextState<S>`], but are removed from the [`World`] if
the source states aren't in the right state. See the [sub_states
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/sub_states.rs)
for a simple use case based on the derive macro, or read the trait docs
for more complex scenarios.
- [`ComputedStates`] are fully derived from other states - they provide
a [`compute`](ComputedStates::compute) method that takes in the source
states and returns their derived value. They are particularly useful for
situations where a simplified view of the source states is necessary -
such as having an `InAMenu` computed state derived from a source state
that defines multiple distinct menus. See the [computed state
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/computed_states.rscomputed_states.rs)
to see a sampling of uses for these states.
# Objective
This PR is another attempt at allowing Bevy to better handle complex
state objects in a manner that doesn't rely on strict equality. While my
previous attempts (https://github.com/bevyengine/bevy/pull/10088 and
https://github.com/bevyengine/bevy/pull/9957) relied on complex matching
capacities at the point of adding a system to application, this one
instead relies on deterministically deriving simple states from more
complex ones.
As a result, it does not require any special macros, nor does it change
any other interactions with the state system once you define and add
your derived state. It also maintains a degree of distinction between
`State` and just normal application state - your derivations have to end
up being discreet pre-determined values, meaning there is less of a
risk/temptation to place a significant amount of logic and data within a
given state.
### Addition - Sub States
closes #9942
After some conversation with Maintainers & SMEs, a significant concern
was that people might attempt to use this feature as if it were
sub-states, and find themselves unable to use it appropriately. Since
`ComputedState` is mainly a state matching feature, while `SubStates`
are more of a state mutation related feature - but one that is easy to
add with the help of the machinery introduced by `ComputedState`, it was
added here as well. The relevant discussion is here:
https://discord.com/channels/691052431525675048/1200556329803186316
## Solution
closes #11358
The solution is to create a new type of state - one implementing
`ComputedStates` - which is deterministically tied to one or more other
states. Implementors write a function to transform the source states
into the computed state, and it gets triggered whenever one of the
source states changes.
In addition, we added the `FreelyMutableState` trait , which is
implemented as part of the derive macro for `States`. This allows us to
limit use of `NextState<S>` to states that are actually mutable,
preventing mis-use of `ComputedStates`.
---
## Changelog
- Added `ComputedStates` trait
- Added `FreelyMutableState` trait
- Converted `NextState` resource to an Enum, with `Unchanged` and
`Pending`
- Added `App::add_computed_state::<S: ComputedStates>()`, to allow for
easily adding derived states to an App.
- Moved the `StateTransition` schedule label from `bevy_app` to
`bevy_ecs` - but maintained the export in `bevy_app` for continuity.
- Modified the process for updating states. Instead of just having an
`apply_state_transition` system that can be added anywhere, we now have
a multi-stage process that has to run within the `StateTransition`
label. First, all the state changes are calculated - manual transitions
rely on `apply_state_transition`, while computed transitions run their
computation process before both call `internal_apply_state_transition`
to apply the transition, send out the transition event, trigger
dependent states, and record which exit/transition/enter schedules need
to occur. Once all the states have been updated, the transition
schedules are called - first the exit schedules, then transition
schedules and finally enter schedules.
- Added `SubStates` trait
- Adjusted `apply_state_transition` to be a no-op if the `State<S>`
resource doesn't exist
## Migration Guide
If the user accessed the NextState resource's value directly or created
them from scratch they will need to adjust to use the new enum variants:
- if they created a `NextState(Some(S))` - they should now use
`NextState::Pending(S)`
- if they created a `NextState(None)` -they should now use
`NextState::Unchanged`
- if they matched on the `NextState` value, they would need to make the
adjustments above
If the user manually utilized `apply_state_transition`, they should
instead use systems that trigger the `StateTransition` schedule.
---
## Future Work
There is still some future potential work in the area, but I wanted to
keep these potential features and changes separate to keep the scope
here contained, and keep the core of it easy to understand and use.
However, I do want to note some of these things, both as inspiration to
others and an illustration of what this PR could unlock.
- `NextState::Remove` - Now that the `State` related mechanisms all
utilize options (#11417), it's fairly easy to add support for explicit
state removal. And while `ComputedStates` can add and remove themselves,
right now `FreelyMutableState`s can't be removed from within the state
system. While it existed originally in this PR, it is a different
question with a separate scope and usability concerns - so having it as
it's own future PR seems like the best approach. This feature currently
lives in a separate branch in my fork, and the differences between it
and this PR can be seen here: https://github.com/lee-orr/bevy/pull/5
- `NextState::ReEnter` - this would allow you to trigger exit & entry
systems for the current state type. We can potentially also add a
`NextState::ReEnterRecirsive` to also re-trigger any states that depend
on the current one.
- More mechanisms for `State` updates - This PR would finally make
states that aren't a set of exclusive Enums useful, and with that comes
the question of setting state more effectively. Right now, to update a
state you either need to fully create the new state, or include the
`Res<Option<State<S>>>` resource in your system, clone the state, mutate
it, and then use `NextState.set(my_mutated_state)` to make it the
pending next state. There are a few other potential methods that could
be implemented in future PRs:
- Inverse Compute States - these would essentially be compute states
that have an additional (manually defined) function that can be used to
nudge the source states so that they result in the computed states
having a given value. For example, you could use set the `IsPaused`
state, and it would attempt to pause or unpause the game by modifying
the `AppState` as needed.
- Closure-based state modification - this would involve adding a
`NextState.modify(f: impl Fn(Option<S> -> Option<S>)` method, and then
you can pass in closures or function pointers to adjust the state as
needed.
- Message-based state modification - this would involve either creating
states that can respond to specific messages, similar to Elm or Redux.
These could either use the `NextState` mechanism or the Event mechanism.
- ~`SubStates` - which are essentially a hybrid of computed and manual
states. In the simplest (and most likely) version, they would work by
having a computed element that determines whether the state should
exist, and if it should has the capacity to add a new version in, but
then any changes to it's content would be freely mutated.~ this feature
is now part of this PR. See above.
- Lastly, since states are getting more complex there might be value in
moving them out of `bevy_ecs` and into their own crate, or at least out
of the `schedule` module into a `states` module. #11087
As mentioned, all these future work elements are TBD and are explicitly
not part of this PR - I just wanted to provide them as potential
explorations for the future.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Marcel Champagne <voiceofmarcel@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
2024-05-02 19:36:23 +00:00
|
|
|
.add_event::<StateTransitionEvent<S>>();
|
|
|
|
let schedule = self.get_schedule_mut(StateTransition).unwrap();
|
|
|
|
S::register_state(schedule);
|
2024-03-31 03:16:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::insert_state`].
|
Computed State & Sub States (#11426)
## Summary/Description
This PR extends states to allow support for a wider variety of state
types and patterns, by providing 3 distinct types of state:
- Standard [`States`] can only be changed by manually setting the
[`NextState<S>`] resource. These states are the baseline on which the
other state types are built, and can be used on their own for many
simple patterns. See the [state
example](https://github.com/bevyengine/bevy/blob/latest/examples/ecs/state.rs)
for a simple use case - these are the states that existed so far in
Bevy.
- [`SubStates`] are children of other states - they can be changed
manually using [`NextState<S>`], but are removed from the [`World`] if
the source states aren't in the right state. See the [sub_states
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/sub_states.rs)
for a simple use case based on the derive macro, or read the trait docs
for more complex scenarios.
- [`ComputedStates`] are fully derived from other states - they provide
a [`compute`](ComputedStates::compute) method that takes in the source
states and returns their derived value. They are particularly useful for
situations where a simplified view of the source states is necessary -
such as having an `InAMenu` computed state derived from a source state
that defines multiple distinct menus. See the [computed state
example](https://github.com/lee-orr/bevy/blob/derived_state/examples/ecs/computed_states.rscomputed_states.rs)
to see a sampling of uses for these states.
# Objective
This PR is another attempt at allowing Bevy to better handle complex
state objects in a manner that doesn't rely on strict equality. While my
previous attempts (https://github.com/bevyengine/bevy/pull/10088 and
https://github.com/bevyengine/bevy/pull/9957) relied on complex matching
capacities at the point of adding a system to application, this one
instead relies on deterministically deriving simple states from more
complex ones.
As a result, it does not require any special macros, nor does it change
any other interactions with the state system once you define and add
your derived state. It also maintains a degree of distinction between
`State` and just normal application state - your derivations have to end
up being discreet pre-determined values, meaning there is less of a
risk/temptation to place a significant amount of logic and data within a
given state.
### Addition - Sub States
closes #9942
After some conversation with Maintainers & SMEs, a significant concern
was that people might attempt to use this feature as if it were
sub-states, and find themselves unable to use it appropriately. Since
`ComputedState` is mainly a state matching feature, while `SubStates`
are more of a state mutation related feature - but one that is easy to
add with the help of the machinery introduced by `ComputedState`, it was
added here as well. The relevant discussion is here:
https://discord.com/channels/691052431525675048/1200556329803186316
## Solution
closes #11358
The solution is to create a new type of state - one implementing
`ComputedStates` - which is deterministically tied to one or more other
states. Implementors write a function to transform the source states
into the computed state, and it gets triggered whenever one of the
source states changes.
In addition, we added the `FreelyMutableState` trait , which is
implemented as part of the derive macro for `States`. This allows us to
limit use of `NextState<S>` to states that are actually mutable,
preventing mis-use of `ComputedStates`.
---
## Changelog
- Added `ComputedStates` trait
- Added `FreelyMutableState` trait
- Converted `NextState` resource to an Enum, with `Unchanged` and
`Pending`
- Added `App::add_computed_state::<S: ComputedStates>()`, to allow for
easily adding derived states to an App.
- Moved the `StateTransition` schedule label from `bevy_app` to
`bevy_ecs` - but maintained the export in `bevy_app` for continuity.
- Modified the process for updating states. Instead of just having an
`apply_state_transition` system that can be added anywhere, we now have
a multi-stage process that has to run within the `StateTransition`
label. First, all the state changes are calculated - manual transitions
rely on `apply_state_transition`, while computed transitions run their
computation process before both call `internal_apply_state_transition`
to apply the transition, send out the transition event, trigger
dependent states, and record which exit/transition/enter schedules need
to occur. Once all the states have been updated, the transition
schedules are called - first the exit schedules, then transition
schedules and finally enter schedules.
- Added `SubStates` trait
- Adjusted `apply_state_transition` to be a no-op if the `State<S>`
resource doesn't exist
## Migration Guide
If the user accessed the NextState resource's value directly or created
them from scratch they will need to adjust to use the new enum variants:
- if they created a `NextState(Some(S))` - they should now use
`NextState::Pending(S)`
- if they created a `NextState(None)` -they should now use
`NextState::Unchanged`
- if they matched on the `NextState` value, they would need to make the
adjustments above
If the user manually utilized `apply_state_transition`, they should
instead use systems that trigger the `StateTransition` schedule.
---
## Future Work
There is still some future potential work in the area, but I wanted to
keep these potential features and changes separate to keep the scope
here contained, and keep the core of it easy to understand and use.
However, I do want to note some of these things, both as inspiration to
others and an illustration of what this PR could unlock.
- `NextState::Remove` - Now that the `State` related mechanisms all
utilize options (#11417), it's fairly easy to add support for explicit
state removal. And while `ComputedStates` can add and remove themselves,
right now `FreelyMutableState`s can't be removed from within the state
system. While it existed originally in this PR, it is a different
question with a separate scope and usability concerns - so having it as
it's own future PR seems like the best approach. This feature currently
lives in a separate branch in my fork, and the differences between it
and this PR can be seen here: https://github.com/lee-orr/bevy/pull/5
- `NextState::ReEnter` - this would allow you to trigger exit & entry
systems for the current state type. We can potentially also add a
`NextState::ReEnterRecirsive` to also re-trigger any states that depend
on the current one.
- More mechanisms for `State` updates - This PR would finally make
states that aren't a set of exclusive Enums useful, and with that comes
the question of setting state more effectively. Right now, to update a
state you either need to fully create the new state, or include the
`Res<Option<State<S>>>` resource in your system, clone the state, mutate
it, and then use `NextState.set(my_mutated_state)` to make it the
pending next state. There are a few other potential methods that could
be implemented in future PRs:
- Inverse Compute States - these would essentially be compute states
that have an additional (manually defined) function that can be used to
nudge the source states so that they result in the computed states
having a given value. For example, you could use set the `IsPaused`
state, and it would attempt to pause or unpause the game by modifying
the `AppState` as needed.
- Closure-based state modification - this would involve adding a
`NextState.modify(f: impl Fn(Option<S> -> Option<S>)` method, and then
you can pass in closures or function pointers to adjust the state as
needed.
- Message-based state modification - this would involve either creating
states that can respond to specific messages, similar to Elm or Redux.
These could either use the `NextState` mechanism or the Event mechanism.
- ~`SubStates` - which are essentially a hybrid of computed and manual
states. In the simplest (and most likely) version, they would work by
having a computed element that determines whether the state should
exist, and if it should has the capacity to add a new version in, but
then any changes to it's content would be freely mutated.~ this feature
is now part of this PR. See above.
- Lastly, since states are getting more complex there might be value in
moving them out of `bevy_ecs` and into their own crate, or at least out
of the `schedule` module into a `states` module. #11087
As mentioned, all these future work elements are TBD and are explicitly
not part of this PR - I just wanted to provide them as potential
explorations for the future.
---------
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
Co-authored-by: Marcel Champagne <voiceofmarcel@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
2024-05-02 19:36:23 +00:00
|
|
|
pub fn insert_state<S: FreelyMutableState>(&mut self, state: S) -> &mut Self {
|
|
|
|
if !self.world.contains_resource::<State<S>>() {
|
|
|
|
setup_state_transitions_in_world(&mut self.world, Some(Startup.intern()));
|
|
|
|
self.insert_resource::<State<S>>(State::new(state))
|
|
|
|
.init_resource::<NextState<S>>()
|
|
|
|
.add_event::<StateTransitionEvent<S>>();
|
|
|
|
|
|
|
|
let schedule = self.get_schedule_mut(StateTransition).unwrap();
|
|
|
|
S::register_state(schedule);
|
|
|
|
}
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::add_computed_state`].
|
|
|
|
pub fn add_computed_state<S: ComputedStates>(&mut self) -> &mut Self {
|
|
|
|
if !self
|
|
|
|
.world
|
|
|
|
.contains_resource::<Events<StateTransitionEvent<S>>>()
|
|
|
|
{
|
|
|
|
setup_state_transitions_in_world(&mut self.world, Some(Startup.intern()));
|
|
|
|
self.add_event::<StateTransitionEvent<S>>();
|
|
|
|
let schedule = self.get_schedule_mut(StateTransition).unwrap();
|
|
|
|
S::register_computed_state_systems(schedule);
|
|
|
|
}
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::add_sub_state`].
|
|
|
|
pub fn add_sub_state<S: SubStates>(&mut self) -> &mut Self {
|
|
|
|
if !self
|
|
|
|
.world
|
|
|
|
.contains_resource::<Events<StateTransitionEvent<S>>>()
|
|
|
|
{
|
|
|
|
setup_state_transitions_in_world(&mut self.world, Some(Startup.intern()));
|
|
|
|
self.init_resource::<NextState<S>>();
|
|
|
|
self.add_event::<StateTransitionEvent<S>>();
|
|
|
|
let schedule = self.get_schedule_mut(StateTransition).unwrap();
|
|
|
|
S::register_sub_state_systems(schedule);
|
|
|
|
}
|
2024-03-31 03:16:10 +00:00
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::add_event`].
|
|
|
|
pub fn add_event<T>(&mut self) -> &mut Self
|
|
|
|
where
|
|
|
|
T: Event,
|
|
|
|
{
|
|
|
|
if !self.world.contains_resource::<Events<T>>() {
|
Optimize Event Updates (#12936)
# Objective
Improve performance scalability when adding new event types to a Bevy
app. Currently, just using Bevy in the default configuration, all apps
spend upwards of 100+us in the `First` schedule, every app tick,
evaluating if it should update events or not, even if events are not
being used for that particular frame, and this scales with the number of
Events registered in the app.
## Solution
As `Events::update` is guaranteed `O(1)` by just checking if a
resource's value, swapping two Vecs, and then clearing one of them, the
actual cost of running `event_update_system` is *very* cheap. The
overhead of doing system dependency injection, task scheduling ,and the
multithreaded executor outweighs the cost of running the system by a
large margin.
Create an `EventRegistry` resource that keeps a number of function
pointers that update each event. Replace the per-event type
`event_update_system` with a singular exclusive system uses the
`EventRegistry` to update all events instead. Update `SubApp::add_event`
to use `EventRegistry` instead.
## Performance
This speeds reduces the cost of the `First` schedule in both many_foxes
and many_cubes by over 80%. Note this is with system spans on. The
majority of this is now context-switching costs from launching
`time_system`, which should be mostly eliminated with #12869.
![image](https://github.com/bevyengine/bevy/assets/3137680/037624be-21a2-4dc2-a42f-9d0bfa3e9b4a)
The actual `event_update_system` is usually *very* short, using only a
few microseconds on average.
![image](https://github.com/bevyengine/bevy/assets/3137680/01ff1689-3595-49b6-8f09-5c44bcf903e8)
---
## Changelog
TODO
## Migration Guide
TODO
---------
Co-authored-by: Josh Matthews <josh@joshmatthews.net>
Co-authored-by: Alice Cecile <alice.i.cecile@gmail.com>
2024-04-13 14:11:28 +00:00
|
|
|
EventRegistry::register_event::<T>(self.world_mut());
|
2024-03-31 03:16:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::add_plugins`].
|
|
|
|
pub fn add_plugins<M>(&mut self, plugins: impl Plugins<M>) -> &mut Self {
|
|
|
|
self.run_as_app(|app| plugins.add_to_app(app));
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::is_plugin_added`].
|
|
|
|
pub fn is_plugin_added<T>(&self) -> bool
|
|
|
|
where
|
|
|
|
T: Plugin,
|
|
|
|
{
|
2024-04-28 21:32:16 +00:00
|
|
|
self.plugin_names.contains(std::any::type_name::<T>())
|
2024-03-31 03:16:10 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::get_added_plugins`].
|
|
|
|
pub fn get_added_plugins<T>(&self) -> Vec<&T>
|
|
|
|
where
|
|
|
|
T: Plugin,
|
|
|
|
{
|
2024-04-28 21:32:16 +00:00
|
|
|
self.plugin_registry
|
2024-03-31 03:16:10 +00:00
|
|
|
.iter()
|
|
|
|
.filter_map(|p| p.downcast_ref())
|
|
|
|
.collect()
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns `true` if there is no plugin in the middle of being built.
|
|
|
|
pub(crate) fn is_building_plugins(&self) -> bool {
|
|
|
|
self.plugin_build_depth > 0
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Return the state of plugins.
|
|
|
|
#[inline]
|
|
|
|
pub fn plugins_state(&mut self) -> PluginsState {
|
|
|
|
match self.plugins_state {
|
|
|
|
PluginsState::Adding => {
|
|
|
|
let mut state = PluginsState::Ready;
|
2024-04-28 21:32:16 +00:00
|
|
|
let plugins = std::mem::take(&mut self.plugin_registry);
|
2024-03-31 03:16:10 +00:00
|
|
|
self.run_as_app(|app| {
|
2024-04-28 21:32:16 +00:00
|
|
|
for plugin in &plugins {
|
2024-03-31 03:16:10 +00:00
|
|
|
if !plugin.ready(app) {
|
|
|
|
state = PluginsState::Adding;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
});
|
2024-04-28 21:32:16 +00:00
|
|
|
self.plugin_registry = plugins;
|
2024-03-31 03:16:10 +00:00
|
|
|
state
|
|
|
|
}
|
|
|
|
state => state,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs [`Plugin::finish`] for each plugin.
|
|
|
|
pub fn finish(&mut self) {
|
2024-04-28 21:32:16 +00:00
|
|
|
let plugins = std::mem::take(&mut self.plugin_registry);
|
2024-03-31 03:16:10 +00:00
|
|
|
self.run_as_app(|app| {
|
2024-04-28 21:32:16 +00:00
|
|
|
for plugin in &plugins {
|
2024-03-31 03:16:10 +00:00
|
|
|
plugin.finish(app);
|
|
|
|
}
|
|
|
|
});
|
2024-04-28 21:32:16 +00:00
|
|
|
self.plugin_registry = plugins;
|
2024-03-31 03:16:10 +00:00
|
|
|
self.plugins_state = PluginsState::Finished;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Runs [`Plugin::cleanup`] for each plugin.
|
|
|
|
pub fn cleanup(&mut self) {
|
2024-04-28 21:32:16 +00:00
|
|
|
let plugins = std::mem::take(&mut self.plugin_registry);
|
2024-03-31 03:16:10 +00:00
|
|
|
self.run_as_app(|app| {
|
2024-04-28 21:32:16 +00:00
|
|
|
for plugin in &plugins {
|
2024-03-31 03:16:10 +00:00
|
|
|
plugin.cleanup(app);
|
|
|
|
}
|
|
|
|
});
|
2024-04-28 21:32:16 +00:00
|
|
|
self.plugin_registry = plugins;
|
2024-03-31 03:16:10 +00:00
|
|
|
self.plugins_state = PluginsState::Cleaned;
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::register_type`].
|
|
|
|
#[cfg(feature = "bevy_reflect")]
|
|
|
|
pub fn register_type<T: bevy_reflect::GetTypeRegistration>(&mut self) -> &mut Self {
|
|
|
|
let registry = self.world.resource_mut::<AppTypeRegistry>();
|
|
|
|
registry.write().register::<T>();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
|
|
|
|
/// See [`App::register_type_data`].
|
|
|
|
#[cfg(feature = "bevy_reflect")]
|
|
|
|
pub fn register_type_data<
|
|
|
|
T: bevy_reflect::Reflect + bevy_reflect::TypePath,
|
|
|
|
D: bevy_reflect::TypeData + bevy_reflect::FromType<T>,
|
|
|
|
>(
|
|
|
|
&mut self,
|
|
|
|
) -> &mut Self {
|
|
|
|
let registry = self.world.resource_mut::<AppTypeRegistry>();
|
|
|
|
registry.write().register_type_data::<T, D>();
|
|
|
|
self
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/// The collection of sub-apps that belong to an [`App`].
|
|
|
|
#[derive(Default)]
|
|
|
|
pub struct SubApps {
|
|
|
|
/// The primary sub-app that contains the "main" world.
|
|
|
|
pub main: SubApp,
|
|
|
|
/// Other, labeled sub-apps.
|
|
|
|
pub sub_apps: HashMap<InternedAppLabel, SubApp>,
|
|
|
|
}
|
|
|
|
|
|
|
|
impl SubApps {
|
|
|
|
/// Calls [`update`](SubApp::update) for the main sub-app, and then calls
|
|
|
|
/// [`extract`](SubApp::extract) and [`update`](SubApp::update) for the rest.
|
|
|
|
pub fn update(&mut self) {
|
|
|
|
#[cfg(feature = "trace")]
|
|
|
|
let _bevy_update_span = info_span!("update").entered();
|
|
|
|
{
|
|
|
|
#[cfg(feature = "trace")]
|
|
|
|
let _bevy_frame_update_span = info_span!("main app").entered();
|
|
|
|
self.main.update();
|
|
|
|
}
|
|
|
|
for (_label, sub_app) in self.sub_apps.iter_mut() {
|
|
|
|
#[cfg(feature = "trace")]
|
|
|
|
let _sub_app_span = info_span!("sub app", name = ?_label).entered();
|
|
|
|
sub_app.extract(&mut self.main.world);
|
|
|
|
sub_app.update();
|
|
|
|
}
|
|
|
|
|
|
|
|
self.main.world.clear_trackers();
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns an iterator over the sub-apps (starting with the main one).
|
|
|
|
pub fn iter(&self) -> impl Iterator<Item = &SubApp> + '_ {
|
|
|
|
std::iter::once(&self.main).chain(self.sub_apps.values())
|
|
|
|
}
|
|
|
|
|
|
|
|
/// Returns a mutable iterator over the sub-apps (starting with the main one).
|
|
|
|
pub fn iter_mut(&mut self) -> impl Iterator<Item = &mut SubApp> + '_ {
|
|
|
|
std::iter::once(&mut self.main).chain(self.sub_apps.values_mut())
|
|
|
|
}
|
|
|
|
}
|