#![forbid(unsafe_code)]
use bevy::{
ecs::system::{Command, Resource},
prelude::*,
reflect::{FromType, GetTypeRegistration},
};
use ggrs::{P2PSession, P2PSpectatorSession, PlayerHandle, SessionState, SyncTestSession};
use ggrs_stage::{GGRSStage, GGRSStageResetSession};
use reflect_resource::ReflectResource;
pub(crate) mod ggrs_stage;
pub(crate) mod reflect_resource;
pub(crate) mod world_snapshot;
pub const GGRS_UPDATE: &str = "ggrs_update";
pub enum SessionType {
SyncTestSession,
P2PSession,
P2PSpectatorSession,
}
impl Default for SessionType {
fn default() -> Self {
SessionType::SyncTestSession
}
}
#[derive(Component)]
pub struct Rollback {
id: u32,
}
impl Rollback {
pub fn new(id: u32) -> Self {
Self { id }
}
pub const fn id(&self) -> u32 {
self.id
}
}
#[derive(Default)]
pub struct RollbackIdProvider {
next_id: u32,
}
impl RollbackIdProvider {
pub fn next_id(&mut self) -> u32 {
if self.next_id == u32::MAX {
panic!("RollbackIdProvider: u32::MAX has been reached.");
}
let ret = self.next_id;
self.next_id += 1;
ret
}
}
pub struct GGRSPlugin;
impl Plugin for GGRSPlugin {
fn build(&self, app: &mut App) {
app.add_stage_before(CoreStage::Update, GGRS_UPDATE, GGRSStage::new());
app.insert_resource(RollbackIdProvider::default());
}
}
pub trait GGRSApp {
fn with_synctest_session(&mut self, sess: SyncTestSession) -> &mut Self;
fn with_p2p_session(&mut self, sess: P2PSession) -> &mut Self;
fn with_p2p_spectator_session(&mut self, sess: P2PSpectatorSession) -> &mut Self;
fn with_rollback_schedule(&mut self, schedule: Schedule) -> &mut Self;
fn with_input_system<Params>(
&mut self,
input_system: impl IntoSystem<PlayerHandle, Vec<u8>, Params>,
) -> &mut Self;
fn with_update_frequency(&mut self, update_frequency: u32) -> &mut Self;
fn register_rollback_type<T>(&mut self) -> &mut Self
where
T: GetTypeRegistration + Reflect + Default + Component;
fn insert_rollback_resource<T>(&mut self, resource: T) -> &mut Self
where
T: GetTypeRegistration + Reflect + Default + Component + Resource;
}
impl GGRSApp for App {
fn with_synctest_session(&mut self, session: SyncTestSession) -> &mut Self {
self.insert_resource(SessionType::SyncTestSession);
self.insert_resource(session);
self
}
fn with_p2p_session(&mut self, session: P2PSession) -> &mut Self {
self.insert_resource(SessionType::P2PSession);
self.insert_resource(session);
self
}
fn with_p2p_spectator_session(&mut self, session: P2PSpectatorSession) -> &mut Self {
self.insert_resource(SessionType::P2PSpectatorSession);
self.insert_resource(session);
self
}
fn with_rollback_schedule(&mut self, schedule: Schedule) -> &mut Self {
let ggrs_stage = self
.schedule
.get_stage_mut::<GGRSStage>(&GGRS_UPDATE)
.expect("No GGRSStage found! Did you install the GGRSPlugin?");
ggrs_stage.set_schedule(schedule);
self
}
fn with_input_system<Params>(
&mut self,
input_system: impl IntoSystem<PlayerHandle, Vec<u8>, Params>,
) -> &mut Self {
let mut input_system = input_system.system();
input_system.initialize(&mut self.world);
let ggrs_stage = self
.schedule
.get_stage_mut::<GGRSStage>(&GGRS_UPDATE)
.expect("No GGRSStage found! Did you install the GGRSPlugin?");
ggrs_stage.input_system = Some(Box::new(input_system));
self
}
fn with_update_frequency(&mut self, update_frequency: u32) -> &mut Self {
let ggrs_stage = self
.schedule
.get_stage_mut::<GGRSStage>(&GGRS_UPDATE)
.expect("No GGRSStage found! Did you install the GGRSPlugin?");
ggrs_stage.set_update_frequency(update_frequency);
self
}
fn register_rollback_type<T>(&mut self) -> &mut Self
where
T: GetTypeRegistration + Reflect + Default + Component,
{
let ggrs_stage = self
.schedule
.get_stage_mut::<GGRSStage>(&GGRS_UPDATE)
.expect("No GGRSStage found! Did you install the GGRSPlugin?");
let mut registry = ggrs_stage.type_registry.write();
registry.register::<T>();
let registration = registry.get_mut(std::any::TypeId::of::<T>()).unwrap();
registration.insert(<ReflectComponent as FromType<T>>::from_type());
registration.insert(<ReflectResource as FromType<T>>::from_type());
drop(registry);
self
}
fn insert_rollback_resource<T>(&mut self, resource: T) -> &mut Self
where
T: GetTypeRegistration + Reflect + Default + Component + Resource,
{
self.insert_resource(resource).register_rollback_type::<T>()
}
}
pub trait CommandsExt {
fn start_p2p_session(&mut self, session: P2PSession);
fn start_p2p_spectator_session(&mut self, session: P2PSpectatorSession);
fn start_synctest_session(&mut self, session: SyncTestSession);
fn stop_session(&mut self);
}
impl CommandsExt for Commands<'_, '_> {
fn start_p2p_session(&mut self, session: P2PSession) {
self.add(StartP2PSessionCommand(session));
}
fn start_p2p_spectator_session(&mut self, session: P2PSpectatorSession) {
self.add(StartP2PSpectatorSessionCommand(session));
}
fn start_synctest_session(&mut self, session: SyncTestSession) {
self.add(StartSyncTestSessionCommand(session));
}
fn stop_session(&mut self) {
self.add(StopSessionCommand);
}
}
struct StartP2PSpectatorSessionCommand(P2PSpectatorSession);
impl Command for StartP2PSessionCommand {
fn write(mut self, world: &mut World) {
if self.0.current_state() == SessionState::Initializing {
self.0.start_session().unwrap();
}
world.insert_resource(self.0);
world.insert_resource(SessionType::P2PSession);
}
}
struct StartP2PSessionCommand(P2PSession);
impl Command for StartP2PSpectatorSessionCommand {
fn write(mut self, world: &mut World) {
if self.0.current_state() == SessionState::Initializing {
self.0.start_session().unwrap();
}
world.insert_resource(self.0);
world.insert_resource(SessionType::P2PSpectatorSession);
}
}
struct StartSyncTestSessionCommand(SyncTestSession);
impl Command for StartSyncTestSessionCommand {
fn write(self, world: &mut World) {
world.insert_resource(self.0);
world.insert_resource(SessionType::SyncTestSession);
}
}
struct StopSessionCommand;
impl Command for StopSessionCommand {
fn write(self, world: &mut World) {
world.remove_resource::<SessionType>();
world.remove_resource::<P2PSession>();
world.remove_resource::<SyncTestSession>();
world.remove_resource::<P2PSpectatorSession>();
world.insert_resource(GGRSStageResetSession);
}
}