-
-
Notifications
You must be signed in to change notification settings - Fork 3.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Computed State & Sub States #11426
Computed State & Sub States #11426
Conversation
…nto optional-state
…nto optional-state
…nd "Remove". Add matching functions to NextState impl. Remove RemoveState struct. Remove unneeded apply_deferred system calls from state derivation systems.
@alice-i-cecile & @MiniaczQ - just wanted to ping you for this new PR, as promised a few weeks ago here: #10088 (comment) |
The generated |
); | ||
} | ||
} | ||
/// Trait defining a state whose value is automatically computed from other [`States`]. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Copy paste from ComputedStates
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
thanks
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like this solution. The state machine logic isn't integral to the ECS operations, so this solution being so self-contained (not inventing new ECS machinery) aligns with other major ECS work (observers, relationships, etc.).
I haven't followed all the discussions that led to this point, but looking at the code and examples, usage seems easy to understand and I know the author has been iterating on this feature for a long time (and incorporating a lot of feedback), so I trust their judgement.
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
Co-authored-by: MiniaczQ <xnetroidpl@gmail.com>
Thank you to everyone involved with the authoring or reviewing of this PR! This work is relatively important and needs release notes! Head over to bevyengine/bevy-website#1308 if you'd like to help out. |
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:
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 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 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 acompute
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 anInAMenu
computed state derived from a source state that defines multiple distinct menus. See the computed state example 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 (#10088 and #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, whileSubStates
are more of a state mutation related feature - but one that is easy to add with the help of the machinery introduced byComputedState
, it was added here as well. The relevant discussion is here: https://discord.com/channels/691052431525675048/1200556329803186316Solution
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 forStates
. This allows us to limit use ofNextState<S>
to states that are actually mutable, preventing mis-use ofComputedStates
.Changelog
ComputedStates
traitFreelyMutableState
traitNextState
resource to an Enum, withUnchanged
andPending
App::add_computed_state::<S: ComputedStates>()
, to allow for easily adding derived states to an App.StateTransition
schedule label frombevy_app
tobevy_ecs
- but maintained the export inbevy_app
for continuity.apply_state_transition
system that can be added anywhere, we now have a multi-stage process that has to run within theStateTransition
label. First, all the state changes are calculated - manual transitions rely onapply_state_transition
, while computed transitions run their computation process before both callinternal_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.SubStates
traitapply_state_transition
to be a no-op if theState<S>
resource doesn't existMigration 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:
NextState(Some(S))
- they should now useNextState::Pending(S)
NextState(None)
-they should now useNextState::Unchanged
NextState
value, they would need to make the adjustments aboveIf the user manually utilized
apply_state_transition
, they should instead use systems that trigger theStateTransition
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 theState
related mechanisms all utilize options (Optional state #11417), it's fairly easy to add support for explicit state removal. And whileComputedStates
can add and remove themselves, right nowFreelyMutableState
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: Remove state lee-orr/bevy#5NextState::ReEnter
- this would allow you to trigger exit & entry systems for the current state type. We can potentially also add aNextState::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 theRes<Option<State<S>>>
resource in your system, clone the state, mutate it, and then useNextState.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:IsPaused
state, and it would attempt to pause or unpause the game by modifying theAppState
as needed.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.NextState
mechanism or the Event mechanism.this feature is now part of this PR. See above.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.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 theschedule
module into astates
module. Move states into their own crate #11087As 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.