 9c004439b8
			
		
	
	
		9c004439b8
		
			
		
	
	
	
	
		
			
			# Objective The `States::variants` method was once used to construct `OnExit` and `OnEnter` schedules for every possible value of a given `States` type. [Since the switch to lazily initialized schedules](https://github.com/bevyengine/bevy/pull/8028/files#diff-b2fba3a0c86e496085ce7f0e3f1de5960cb754c7d215ed0f087aa556e529f97f), we no longer need to track every possible value. This also opens the door to `States` types that aren't enums. ## Solution - Remove the unused `States::variants` method and its associated type. - Remove the enum-only restriction on derived States types. --- ## Changelog - Removed `States::variants` and its associated type. - Derived `States` can now be datatypes other than enums. ## Migration Guide - `States::variants` no longer exists. If you relied on this function, consider using a library that provides enum iterators.
		
			
				
	
	
		
			165 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
			
		
		
	
	
			165 lines
		
	
	
		
			5.4 KiB
		
	
	
	
		
			Rust
		
	
	
	
	
	
| use std::fmt::Debug;
 | |
| use std::hash::Hash;
 | |
| use std::mem;
 | |
| use std::ops::Deref;
 | |
| 
 | |
| use crate as bevy_ecs;
 | |
| use crate::change_detection::DetectChangesMut;
 | |
| #[cfg(feature = "bevy_reflect")]
 | |
| use crate::reflect::ReflectResource;
 | |
| use crate::schedule::ScheduleLabel;
 | |
| use crate::system::Resource;
 | |
| use crate::world::World;
 | |
| #[cfg(feature = "bevy_reflect")]
 | |
| use bevy_reflect::std_traits::ReflectDefault;
 | |
| 
 | |
| pub use bevy_ecs_macros::States;
 | |
| 
 | |
| /// Types that can define world-wide states in a finite-state machine.
 | |
| ///
 | |
| /// The [`Default`] trait defines the starting state.
 | |
| /// Multiple states can be defined for the same world,
 | |
| /// allowing you to classify the state of the world across orthogonal dimensions.
 | |
| /// You can access the current state of type `T` with the [`State<T>`] resource,
 | |
| /// and the queued state with the [`NextState<T>`] resource.
 | |
| ///
 | |
| /// State transitions typically occur in the [`OnEnter<T::Variant>`] and [`OnExit<T:Variant>`] schedules,
 | |
| /// which can be run via the [`apply_state_transition::<T>`] system.
 | |
| ///
 | |
| /// # Example
 | |
| ///
 | |
| /// ```rust
 | |
| /// use bevy_ecs::prelude::States;
 | |
| ///
 | |
| /// #[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default, States)]
 | |
| /// enum GameState {
 | |
| ///  #[default]
 | |
| ///   MainMenu,
 | |
| ///   SettingsMenu,
 | |
| ///   InGame,
 | |
| /// }
 | |
| ///
 | |
| /// ```
 | |
| pub trait States: 'static + Send + Sync + Clone + PartialEq + Eq + Hash + Debug + Default {}
 | |
| 
 | |
| /// The label of a [`Schedule`](super::Schedule) that runs whenever [`State<S>`]
 | |
| /// enters this state.
 | |
| #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
 | |
| pub struct OnEnter<S: States>(pub S);
 | |
| 
 | |
| /// The label of a [`Schedule`](super::Schedule) that runs whenever [`State<S>`]
 | |
| /// exits this state.
 | |
| #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
 | |
| pub struct OnExit<S: States>(pub S);
 | |
| 
 | |
| /// The label of a [`Schedule`](super::Schedule) that **only** runs whenever [`State<S>`]
 | |
| /// exits the `from` state, AND enters the `to` state.
 | |
| ///
 | |
| /// Systems added to this schedule are always ran *after* [`OnExit`], and *before* [`OnEnter`].
 | |
| #[derive(ScheduleLabel, Clone, Debug, PartialEq, Eq, Hash)]
 | |
| pub struct OnTransition<S: States> {
 | |
|     /// The state being exited.
 | |
|     pub from: S,
 | |
|     /// The state being entered.
 | |
|     pub to: S,
 | |
| }
 | |
| 
 | |
| /// A finite-state machine whose transitions have associated schedules
 | |
| /// ([`OnEnter(state)`] and [`OnExit(state)`]).
 | |
| ///
 | |
| /// The current state value can be accessed through this resource. To *change* the state,
 | |
| /// queue a transition in the [`NextState<S>`] resource, and it will be applied by the next
 | |
| /// [`apply_state_transition::<S>`] system.
 | |
| ///
 | |
| /// The starting state is defined via the [`Default`] implementation for `S`.
 | |
| #[derive(Resource, Default, Debug)]
 | |
| #[cfg_attr(
 | |
|     feature = "bevy_reflect",
 | |
|     derive(bevy_reflect::Reflect),
 | |
|     reflect(Resource, Default)
 | |
| )]
 | |
| pub struct State<S: States>(S);
 | |
| 
 | |
| impl<S: States> State<S> {
 | |
|     /// Creates a new state with a specific value.
 | |
|     ///
 | |
|     /// To change the state use [`NextState<S>`] rather than using this to modify the `State<S>`.
 | |
|     pub fn new(state: S) -> Self {
 | |
|         Self(state)
 | |
|     }
 | |
| 
 | |
|     /// Get the current state.
 | |
|     pub fn get(&self) -> &S {
 | |
|         &self.0
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<S: States> PartialEq<S> for State<S> {
 | |
|     fn eq(&self, other: &S) -> bool {
 | |
|         self.get() == other
 | |
|     }
 | |
| }
 | |
| 
 | |
| impl<S: States> Deref for State<S> {
 | |
|     type Target = S;
 | |
| 
 | |
|     fn deref(&self) -> &Self::Target {
 | |
|         self.get()
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// The next state of [`State<S>`].
 | |
| ///
 | |
| /// To queue a transition, just set the contained value to `Some(next_state)`.
 | |
| /// Note that these transitions can be overridden by other systems:
 | |
| /// only the actual value of this resource at the time of [`apply_state_transition`] matters.
 | |
| #[derive(Resource, Default, Debug)]
 | |
| #[cfg_attr(
 | |
|     feature = "bevy_reflect",
 | |
|     derive(bevy_reflect::Reflect),
 | |
|     reflect(Resource, Default)
 | |
| )]
 | |
| pub struct NextState<S: States>(pub Option<S>);
 | |
| 
 | |
| impl<S: States> NextState<S> {
 | |
|     /// Tentatively set a planned state transition to `Some(state)`.
 | |
|     pub fn set(&mut self, state: S) {
 | |
|         self.0 = Some(state);
 | |
|     }
 | |
| }
 | |
| 
 | |
| /// Run the enter schedule (if it exists) for the current state.
 | |
| pub fn run_enter_schedule<S: States>(world: &mut World) {
 | |
|     world
 | |
|         .try_run_schedule(OnEnter(world.resource::<State<S>>().0.clone()))
 | |
|         .ok();
 | |
| }
 | |
| 
 | |
| /// If a new state is queued in [`NextState<S>`], this system:
 | |
| /// - Takes the new state value from [`NextState<S>`] and updates [`State<S>`].
 | |
| /// - Runs the [`OnExit(exited_state)`] schedule, if it exists.
 | |
| /// - Runs the [`OnTransition { from: exited_state, to: entered_state }`](OnTransition), if it exists.
 | |
| /// - Runs the [`OnEnter(entered_state)`] schedule, if it exists.
 | |
| pub fn apply_state_transition<S: States>(world: &mut World) {
 | |
|     // We want to take the `NextState` resource,
 | |
|     // but only mark it as changed if it wasn't empty.
 | |
|     let mut next_state_resource = world.resource_mut::<NextState<S>>();
 | |
|     if let Some(entered) = next_state_resource.bypass_change_detection().0.take() {
 | |
|         next_state_resource.set_changed();
 | |
| 
 | |
|         let mut state_resource = world.resource_mut::<State<S>>();
 | |
|         if *state_resource != entered {
 | |
|             let exited = mem::replace(&mut state_resource.0, entered.clone());
 | |
|             // Try to run the schedules if they exist.
 | |
|             world.try_run_schedule(OnExit(exited.clone())).ok();
 | |
|             world
 | |
|                 .try_run_schedule(OnTransition {
 | |
|                     from: exited,
 | |
|                     to: entered.clone(),
 | |
|                 })
 | |
|                 .ok();
 | |
|             world.try_run_schedule(OnEnter(entered)).ok();
 | |
|         }
 | |
|     }
 | |
| }
 |