#![warn(missing_docs)]
#![allow(clippy::type_complexity)]
use bevy::ecs::intern::Interned;
use bevy::ecs::schedule::SingleThreadedExecutor;
use bevy::{
ecs::schedule::{LogLevel, ScheduleBuildSettings, ScheduleLabel},
input::InputSystems,
platform::collections::HashMap,
prelude::*,
};
use core::time::Duration;
pub use ggrs;
use ggrs::{Config, InputStatus, P2PSession, PlayerHandle, SpectatorSession, SyncTestSession};
use serde::{Deserialize, Serialize};
use std::{fmt::Debug, hash::Hash, marker::PhantomData, net::SocketAddr};
pub use snapshot::*;
pub use time::*;
pub(crate) mod schedule_systems;
pub(crate) mod snapshot;
pub(crate) mod time;
pub mod prelude {
pub use crate::{
GgrsConfig, GgrsPlugin, GgrsSchedule, GgrsTime, PlayerInputs, ReadInputs, Rollback,
RollbackApp, RollbackFrameRate, RollbackId, Session, SyncTestMismatch,
snapshot::prelude::*,
};
pub use ggrs::{GgrsEvent, PlayerType, SessionBuilder};
}
#[derive(Debug)]
pub struct GgrsConfig<Input, Address = SocketAddr, State = u8> {
_phantom: PhantomData<(Input, Address, State)>,
}
impl<Input, Address, State> Config for GgrsConfig<Input, Address, State>
where
Self: 'static,
Input: Send + Sync + PartialEq + Serialize + for<'a> Deserialize<'a> + Default + Copy,
Address: Send + Sync + Debug + Hash + Eq + Clone,
State: Send + Sync + Clone,
{
type Input = Input;
type State = State;
type Address = Address;
type InputPredictor = ggrs::PredictRepeatLast;
}
const DEFAULT_FPS: usize = 60;
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
pub struct GgrsSchedule;
#[allow(clippy::large_enum_variant)]
#[derive(Resource)]
pub enum Session<T: Config> {
SyncTest(SyncTestSession<T>),
P2P(P2PSession<T>),
Spectator(SpectatorSession<T>),
}
#[derive(Resource, Deref, DerefMut)]
pub struct PlayerInputs<T: Config>(Vec<(T::Input, InputStatus)>);
#[derive(Resource, Copy, Clone, Debug)]
struct FixedTimestepData {
accumulator: Duration,
run_slow: bool,
}
impl Default for FixedTimestepData {
fn default() -> Self {
Self {
accumulator: Duration::ZERO,
run_slow: false,
}
}
}
#[derive(Resource, Debug, Default, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct MaxPredictionWindow(usize);
#[derive(Event, Debug, Clone)]
pub struct SyncTestMismatch {
pub current_frame: ggrs::Frame,
pub mismatched_frames: Vec<ggrs::Frame>,
}
#[derive(Resource)]
pub struct LocalInputs<C: Config>(pub HashMap<PlayerHandle, C::Input>);
#[derive(Resource, Default)]
pub struct LocalPlayers(pub Vec<PlayerHandle>);
#[derive(ScheduleLabel, Debug, Hash, PartialEq, Eq, Clone)]
pub struct ReadInputs;
#[derive(SystemSet, Hash, Debug, PartialEq, Eq, Clone)]
pub struct RunGgrsSystems;
pub struct GgrsPlugin<C: Config> {
schedule: Interned<dyn ScheduleLabel>,
_marker: PhantomData<C>,
}
impl<C: Config> GgrsPlugin<C> {
pub fn new(schedule: impl ScheduleLabel) -> Self {
Self {
schedule: schedule.intern(),
_marker: default(),
}
}
}
impl<C: Config> Default for GgrsPlugin<C> {
fn default() -> Self {
Self {
schedule: PreUpdate.intern(),
_marker: default(),
}
}
}
impl<C: Config> Plugin for GgrsPlugin<C> {
fn build(&self, app: &mut App) {
app.add_plugins(SnapshotPlugin)
.init_resource::<MaxPredictionWindow>()
.init_resource::<LocalPlayers>()
.init_resource::<FixedTimestepData>()
.init_schedule(ReadInputs)
.edit_schedule(AdvanceWorld, |schedule| {
schedule.set_executor(SingleThreadedExecutor::new());
})
.edit_schedule(GgrsSchedule, |schedule| {
schedule.set_build_settings(ScheduleBuildSettings {
ambiguity_detection: LogLevel::Error,
..default()
});
})
.add_systems(
AdvanceWorld,
(|world: &mut World| world.run_schedule(GgrsSchedule))
.in_set(AdvanceWorldSystems::Main),
)
.add_systems(
self.schedule,
schedule_systems::run_ggrs_schedules::<C>
.in_set(RunGgrsSystems)
.after(InputSystems), )
.add_plugins((ChecksumPlugin, EntityChecksumPlugin, GgrsTimePlugin));
}
}