use std::marker::PhantomData;
use bevy::{ecs::schedule::ScheduleLabel, prelude::*, state::state::FreelyMutableState};
mod frame_count;
pub use frame_count::{increase_frame_count, RollFrameCount};
pub mod prelude {
pub use super::RollApp;
}
pub trait RollApp {
fn init_roll_state<S: States + FromWorld + FreelyMutableState>(
&mut self,
schedule: impl ScheduleLabel,
) -> &mut Self;
#[cfg(feature = "bevy_ggrs")]
fn init_ggrs_state<S: States + FromWorld + Clone + FreelyMutableState>(&mut self) -> &mut Self;
#[cfg(feature = "bevy_ggrs")]
fn init_ggrs_state_in_schedule<S: States + FromWorld + Clone + FreelyMutableState>(
&mut self,
schedule: impl ScheduleLabel,
) -> &mut Self;
}
impl RollApp for App {
fn init_roll_state<S: States + FromWorld + FreelyMutableState>(
&mut self,
schedule: impl ScheduleLabel,
) -> &mut Self {
if !self.world().contains_resource::<State<S>>() {
self.init_resource::<State<S>>()
.init_resource::<NextState<S>>()
.init_resource::<InitialStateEntered<S>>()
.add_event::<StateTransitionEvent<S>>()
.add_systems(
schedule,
(
run_enter_schedule::<S>
.run_if(resource_equals(InitialStateEntered::<S>(false, default()))),
mark_state_initialized::<S>
.run_if(resource_equals(InitialStateEntered::<S>(false, default()))),
apply_state_transition::<S>,
)
.chain(),
);
} else {
let name = std::any::type_name::<S>();
warn!("State {} is already initialized.", name);
}
self
}
#[cfg(feature = "bevy_ggrs")]
fn init_ggrs_state<S: States + FromWorld + Clone + FreelyMutableState>(&mut self) -> &mut Self {
use bevy_ggrs::GgrsSchedule;
self.init_ggrs_state_in_schedule::<S>(GgrsSchedule)
}
#[cfg(feature = "bevy_ggrs")]
fn init_ggrs_state_in_schedule<S: States + FromWorld + Clone + FreelyMutableState>(
&mut self,
schedule: impl ScheduleLabel,
) -> &mut Self {
use crate::ggrs_support::{NextStateStrategy, StateStrategy};
use bevy_ggrs::{CloneStrategy, ResourceSnapshotPlugin};
self.init_roll_state::<S>(schedule).add_plugins((
ResourceSnapshotPlugin::<StateStrategy<S>>::default(),
ResourceSnapshotPlugin::<NextStateStrategy<S>>::default(),
ResourceSnapshotPlugin::<CloneStrategy<InitialStateEntered<S>>>::default(),
))
}
}
#[cfg(feature = "bevy_ggrs")]
mod ggrs_support {
use bevy::{prelude::*, state::state::FreelyMutableState};
use bevy_ggrs::Strategy;
use std::marker::PhantomData;
pub(crate) struct StateStrategy<S: States>(PhantomData<S>);
impl<S: States> Strategy for StateStrategy<S> {
type Target = State<S>;
type Stored = S;
fn store(target: &Self::Target) -> Self::Stored {
target.get().to_owned()
}
fn load(stored: &Self::Stored) -> Self::Target {
State::new(stored.to_owned())
}
}
pub(crate) struct NextStateStrategy<S: States>(PhantomData<S>);
impl<S: States + FreelyMutableState> Strategy for NextStateStrategy<S> {
type Target = NextState<S>;
type Stored = Option<S>;
fn store(target: &Self::Target) -> Self::Stored {
match target {
NextState::Unchanged => None,
NextState::Pending(s) => Some(s.to_owned()),
}
}
fn load(stored: &Self::Stored) -> Self::Target {
match stored {
None => NextState::Unchanged,
Some(s) => NextState::Pending(s.to_owned()),
}
}
}
}
#[derive(Resource, Debug, Reflect, Eq, PartialEq, Clone)]
#[reflect(Resource)]
pub struct InitialStateEntered<S: States>(bool, PhantomData<S>);
impl<S: States> Default for InitialStateEntered<S> {
fn default() -> Self {
Self(false, default())
}
}
fn mark_state_initialized<S: States + FromWorld>(
mut state_initialized: ResMut<InitialStateEntered<S>>,
) {
state_initialized.0 = true;
}
pub fn run_enter_schedule<S: States>(world: &mut World) {
let Some(state) = world.get_resource::<State<S>>() else {
return;
};
world.try_run_schedule(OnEnter(state.get().clone())).ok();
}
pub fn apply_state_transition<S: States + FreelyMutableState>(world: &mut World) {
let Some(mut next_state_resource) = world.get_resource_mut::<NextState<S>>() else {
return;
};
if let NextState::Pending(entered) = next_state_resource.bypass_change_detection() {
let entered = entered.clone();
*next_state_resource = NextState::Unchanged;
match world.get_resource_mut::<State<S>>() {
Some(mut state_resource) => {
if *state_resource != entered {
let exited = state_resource.get().clone();
*state_resource = State::new(entered.clone());
world.send_event(StateTransitionEvent {
exited: Some(exited.clone()),
entered: Some(entered.clone()),
});
world.try_run_schedule(OnExit(exited.clone())).ok();
world
.try_run_schedule(OnTransition {
exited,
entered: entered.clone(),
})
.ok();
world.try_run_schedule(OnEnter(entered)).ok();
}
}
None => {
world.insert_resource(State::new(entered.clone()));
world.try_run_schedule(OnEnter(entered)).ok();
}
};
}
}