1use std::marker::PhantomData;
2
3use bevy::{ecs::schedule::ScheduleLabel, prelude::*, state::state::FreelyMutableState};
4
5mod frame_count;
6
7pub use frame_count::{increase_frame_count, RollFrameCount};
9
10pub mod prelude {
11 pub use super::RollApp;
12}
13
14pub trait RollApp {
15 fn init_roll_state<S: States + FromWorld + FreelyMutableState>(
17 &mut self,
18 schedule: impl ScheduleLabel,
19 ) -> &mut Self;
20
21 #[cfg(feature = "bevy_ggrs")]
22 fn init_ggrs_state<S: States + FromWorld + Clone + FreelyMutableState>(&mut self) -> &mut Self;
24
25 #[cfg(feature = "bevy_ggrs")]
26 fn init_ggrs_state_in_schedule<S: States + FromWorld + Clone + FreelyMutableState>(
28 &mut self,
29 schedule: impl ScheduleLabel,
30 ) -> &mut Self;
31}
32
33impl RollApp for App {
34 fn init_roll_state<S: States + FromWorld + FreelyMutableState>(
35 &mut self,
36 schedule: impl ScheduleLabel,
37 ) -> &mut Self {
38 if !self.world().contains_resource::<State<S>>() {
39 self.init_resource::<State<S>>()
40 .init_resource::<NextState<S>>()
41 .init_resource::<InitialStateEntered<S>>()
42 .add_event::<StateTransitionEvent<S>>()
44 .add_systems(
45 schedule,
46 (
47 run_enter_schedule::<S>
48 .run_if(resource_equals(InitialStateEntered::<S>(false, default()))),
49 mark_state_initialized::<S>
50 .run_if(resource_equals(InitialStateEntered::<S>(false, default()))),
51 apply_state_transition::<S>,
52 )
53 .chain(),
54 );
55 } else {
56 let name = std::any::type_name::<S>();
57 warn!("State {} is already initialized.", name);
58 }
59
60 self
61 }
62
63 #[cfg(feature = "bevy_ggrs")]
64 fn init_ggrs_state<S: States + FromWorld + Clone + FreelyMutableState>(&mut self) -> &mut Self {
65 use bevy_ggrs::GgrsSchedule;
66 self.init_ggrs_state_in_schedule::<S>(GgrsSchedule)
67 }
68
69 #[cfg(feature = "bevy_ggrs")]
70 fn init_ggrs_state_in_schedule<S: States + FromWorld + Clone + FreelyMutableState>(
71 &mut self,
72 schedule: impl ScheduleLabel,
73 ) -> &mut Self {
74 use crate::ggrs_support::{NextStateStrategy, StateStrategy};
75 use bevy_ggrs::{CloneStrategy, ResourceSnapshotPlugin};
76
77 self.init_roll_state::<S>(schedule).add_plugins((
78 ResourceSnapshotPlugin::<StateStrategy<S>>::default(),
79 ResourceSnapshotPlugin::<NextStateStrategy<S>>::default(),
80 ResourceSnapshotPlugin::<CloneStrategy<InitialStateEntered<S>>>::default(),
81 ))
82 }
83}
84
85#[cfg(feature = "bevy_ggrs")]
86mod ggrs_support {
87 use bevy::{prelude::*, state::state::FreelyMutableState};
88 use bevy_ggrs::Strategy;
89 use std::marker::PhantomData;
90
91 pub(crate) struct StateStrategy<S: States>(PhantomData<S>);
92
93 impl<S: States> Strategy for StateStrategy<S> {
95 type Target = State<S>;
96 type Stored = S;
97
98 fn store(target: &Self::Target) -> Self::Stored {
99 target.get().to_owned()
100 }
101
102 fn load(stored: &Self::Stored) -> Self::Target {
103 State::new(stored.to_owned())
104 }
105 }
106
107 pub(crate) struct NextStateStrategy<S: States>(PhantomData<S>);
108
109 impl<S: States + FreelyMutableState> Strategy for NextStateStrategy<S> {
111 type Target = NextState<S>;
112 type Stored = Option<S>;
113
114 fn store(target: &Self::Target) -> Self::Stored {
115 match target {
116 NextState::Unchanged => None,
117 NextState::Pending(s) => Some(s.to_owned()),
118 }
119 }
120
121 fn load(stored: &Self::Stored) -> Self::Target {
122 match stored {
123 None => NextState::Unchanged,
124 Some(s) => NextState::Pending(s.to_owned()),
125 }
126 }
127 }
128}
129
130#[derive(Resource, Debug, Reflect, Eq, PartialEq, Clone)]
131#[reflect(Resource)]
132pub struct InitialStateEntered<S: States>(bool, PhantomData<S>);
133
134impl<S: States> Default for InitialStateEntered<S> {
135 fn default() -> Self {
136 Self(false, default())
137 }
138}
139
140fn mark_state_initialized<S: States + FromWorld>(
141 mut state_initialized: ResMut<InitialStateEntered<S>>,
142) {
143 state_initialized.0 = true;
144}
145
146pub fn run_enter_schedule<S: States>(world: &mut World) {
148 let Some(state) = world.get_resource::<State<S>>() else {
149 return;
150 };
151 world.try_run_schedule(OnEnter(state.get().clone())).ok();
152}
153
154pub fn apply_state_transition<S: States + FreelyMutableState>(world: &mut World) {
161 let Some(mut next_state_resource) = world.get_resource_mut::<NextState<S>>() else {
164 return;
165 };
166 if let NextState::Pending(entered) = next_state_resource.bypass_change_detection() {
167 let entered = entered.clone();
168 *next_state_resource = NextState::Unchanged;
169 match world.get_resource_mut::<State<S>>() {
170 Some(mut state_resource) => {
171 if *state_resource != entered {
172 let exited = state_resource.get().clone();
173 *state_resource = State::new(entered.clone());
174 world.send_event(StateTransitionEvent {
175 exited: Some(exited.clone()),
176 entered: Some(entered.clone()),
177 });
178 world.try_run_schedule(OnExit(exited.clone())).ok();
180 world
181 .try_run_schedule(OnTransition {
182 exited,
183 entered: entered.clone(),
184 })
185 .ok();
186 world.try_run_schedule(OnEnter(entered)).ok();
187 }
188 }
189 None => {
190 world.insert_resource(State::new(entered.clone()));
191 world.try_run_schedule(OnEnter(entered)).ok();
192 }
193 };
194 }
195}