use parking_lot::{MappedMutexGuard, Mutex};
use std::ops::Deref;
use std::sync::Arc;
use crate::frame_info::{GameState, PlayerInput};
use crate::input_queue::InputQueue;
use crate::network::messages::ConnectionStatus;
use crate::{Config, Frame, GgrsRequest, InputStatus, PlayerHandle, NULL_FRAME};
pub struct GameStateCell<T>(Arc<Mutex<GameState<T>>>);
impl<T> GameStateCell<T> {
pub fn save(&self, frame: Frame, data: Option<T>, checksum: Option<u128>) {
let mut state = self.0.lock();
assert!(frame != NULL_FRAME);
state.frame = frame;
state.data = data;
state.checksum = checksum;
}
pub fn data(&self) -> Option<GameStateAccessor<'_, T>> {
parking_lot::MutexGuard::try_map(self.0.lock(), |state| state.data.as_mut())
.ok()
.map(GameStateAccessor)
}
pub(crate) fn frame(&self) -> Frame {
self.0.lock().frame
}
pub(crate) fn checksum(&self) -> Option<u128> {
self.0.lock().checksum
}
}
impl<T: Clone> GameStateCell<T> {
pub fn load(&self) -> Option<T> {
let data = self.data()?;
Some(data.clone())
}
}
impl<T> Default for GameStateCell<T> {
fn default() -> Self {
Self(Arc::new(Mutex::new(GameState::default())))
}
}
impl<T> Clone for GameStateCell<T> {
fn clone(&self) -> Self {
Self(self.0.clone())
}
}
impl<T> std::fmt::Debug for GameStateCell<T> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
let inner = self.0.lock();
f.debug_struct("GameStateCell")
.field("frame", &inner.frame)
.field("checksum", &inner.checksum)
.finish_non_exhaustive()
}
}
pub struct GameStateAccessor<'c, T>(MappedMutexGuard<'c, T>);
impl<T> Deref for GameStateAccessor<'_, T> {
type Target = T;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<T> GameStateAccessor<'_, T> {
pub fn as_mut_dangerous(&mut self) -> &mut T {
&mut self.0
}
}
pub(crate) struct SavedStates<T> {
pub states: Vec<GameStateCell<T>>,
}
impl<T> SavedStates<T> {
fn new(max_pred: usize) -> Self {
let num_cells = max_pred + 1;
let mut states = Vec::with_capacity(num_cells);
for _ in 0..num_cells {
states.push(GameStateCell::default());
}
Self { states }
}
fn get_cell(&self, frame: Frame) -> GameStateCell<T> {
assert!(frame >= 0);
let pos = frame as usize % self.states.len();
self.states[pos].clone()
}
}
pub(crate) struct SyncLayer<T>
where
T: Config,
{
num_players: usize,
max_prediction: usize,
saved_states: SavedStates<T::State>,
last_confirmed_frame: Frame,
last_saved_frame: Frame,
current_frame: Frame,
input_queues: Vec<InputQueue<T>>,
}
impl<T: Config> SyncLayer<T> {
pub(crate) fn new(num_players: usize, max_prediction: usize) -> Self {
let mut input_queues = Vec::new();
for _ in 0..num_players {
input_queues.push(InputQueue::new());
}
Self {
num_players,
max_prediction,
last_confirmed_frame: NULL_FRAME,
last_saved_frame: NULL_FRAME,
current_frame: 0,
saved_states: SavedStates::new(max_prediction),
input_queues,
}
}
pub(crate) fn current_frame(&self) -> Frame {
self.current_frame
}
pub(crate) fn advance_frame(&mut self) {
self.current_frame += 1;
}
pub(crate) fn save_current_state(&mut self) -> GgrsRequest<T> {
self.last_saved_frame = self.current_frame;
let cell = self.saved_states.get_cell(self.current_frame);
GgrsRequest::SaveGameState {
cell,
frame: self.current_frame,
}
}
pub(crate) fn set_frame_delay(&mut self, player_handle: PlayerHandle, delay: usize) {
assert!(player_handle < self.num_players as PlayerHandle);
self.input_queues[player_handle].set_frame_delay(delay);
}
pub(crate) fn reset_prediction(&mut self) {
for i in 0..self.num_players {
self.input_queues[i].reset_prediction();
}
}
pub(crate) fn load_frame(&mut self, frame_to_load: Frame) -> GgrsRequest<T> {
assert!(frame_to_load != NULL_FRAME, "cannot load null frame");
assert!(
frame_to_load < self.current_frame,
"must load frame in the past (frame to load is {}, current frame is {})",
frame_to_load,
self.current_frame
);
assert!(
frame_to_load >= self.current_frame - self.max_prediction as i32,
"cannot load frame outside of prediction window; \
(frame to load is {}, current frame is {}, max prediction is {})",
frame_to_load,
self.current_frame,
self.max_prediction
);
let cell = self.saved_states.get_cell(frame_to_load);
assert_eq!(cell.0.lock().frame, frame_to_load);
self.current_frame = frame_to_load;
GgrsRequest::LoadGameState {
cell,
frame: frame_to_load,
}
}
pub(crate) fn add_local_input(
&mut self,
player_handle: PlayerHandle,
input: PlayerInput<T::Input>,
) -> Frame {
assert_eq!(input.frame, self.current_frame);
self.input_queues[player_handle].add_input(input)
}
pub(crate) fn add_remote_input(
&mut self,
player_handle: PlayerHandle,
input: PlayerInput<T::Input>,
) {
self.input_queues[player_handle].add_input(input);
}
pub(crate) fn synchronized_inputs(
&mut self,
connect_status: &[ConnectionStatus],
) -> Vec<(T::Input, InputStatus)> {
let mut inputs = Vec::new();
for (i, con_stat) in connect_status.iter().enumerate() {
if con_stat.disconnected && con_stat.last_frame < self.current_frame {
inputs.push((T::Input::default(), InputStatus::Disconnected));
} else {
inputs.push(self.input_queues[i].input(self.current_frame));
}
}
inputs
}
pub(crate) fn confirmed_inputs(
&self,
frame: Frame,
connect_status: &[ConnectionStatus],
) -> Vec<PlayerInput<T::Input>> {
let mut inputs = Vec::new();
for (i, con_stat) in connect_status.iter().enumerate() {
if con_stat.disconnected && con_stat.last_frame < frame {
inputs.push(PlayerInput::blank_input(NULL_FRAME));
} else {
inputs.push(self.input_queues[i].confirmed_input(frame));
}
}
inputs
}
pub(crate) fn set_last_confirmed_frame(&mut self, mut frame: Frame, sparse_saving: bool) {
let mut first_incorrect: Frame = NULL_FRAME;
for handle in 0..self.num_players {
first_incorrect = std::cmp::max(
first_incorrect,
self.input_queues[handle].first_incorrect_frame(),
);
}
if sparse_saving {
frame = std::cmp::min(frame, self.last_saved_frame);
}
frame = std::cmp::min(frame, self.current_frame());
assert!(first_incorrect == NULL_FRAME || first_incorrect >= frame);
self.last_confirmed_frame = frame;
if self.last_confirmed_frame > 0 {
for i in 0..self.num_players {
self.input_queues[i].discard_confirmed_frames(frame - 1);
}
}
}
pub(crate) fn check_simulation_consistency(&self, mut first_incorrect: Frame) -> Frame {
for handle in 0..self.num_players {
let incorrect = self.input_queues[handle].first_incorrect_frame();
if incorrect != NULL_FRAME
&& (first_incorrect == NULL_FRAME || incorrect < first_incorrect)
{
first_incorrect = incorrect;
}
}
first_incorrect
}
pub(crate) fn saved_state_by_frame(&self, frame: Frame) -> Option<GameStateCell<T::State>> {
let cell = self.saved_states.get_cell(frame);
if cell.0.lock().frame == frame {
Some(cell)
} else {
None
}
}
pub(crate) fn last_saved_frame(&self) -> Frame {
self.last_saved_frame
}
pub(crate) fn last_confirmed_frame(&self) -> Frame {
self.last_confirmed_frame
}
}
#[cfg(test)]
mod sync_layer_tests {
use super::*;
use crate::PredictRepeatLast;
use serde::{Deserialize, Serialize};
use std::net::SocketAddr;
#[repr(C)]
#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize)]
struct TestInput {
inp: u8,
}
struct TestConfig;
impl Config for TestConfig {
type Input = TestInput;
type InputPredictor = PredictRepeatLast;
type State = u8;
type Address = SocketAddr;
}
#[test]
fn test_cell_default_frame_is_null() {
let cell = GameStateCell::<u8>::default();
assert_eq!(cell.frame(), NULL_FRAME);
}
#[test]
fn test_cell_save_and_frame() {
let cell = GameStateCell::<u8>::default();
cell.save(5, Some(42u8), None);
assert_eq!(cell.frame(), 5);
}
#[test]
fn test_cell_save_and_checksum() {
let cell = GameStateCell::<u8>::default();
cell.save(1, Some(0u8), Some(0xDEADBEEF));
assert_eq!(cell.checksum(), Some(0xDEADBEEF));
}
#[test]
fn test_cell_data_returns_none_before_save() {
let cell = GameStateCell::<u8>::default();
assert!(cell.data().is_none());
}
#[test]
fn test_cell_data_returns_some_after_save() {
let cell = GameStateCell::<u8>::default();
cell.save(1, Some(99u8), None);
let accessor = cell.data().expect("should have data");
assert_eq!(*accessor, 99u8);
}
#[test]
fn test_cell_load_clones_value() {
let cell = GameStateCell::<u8>::default();
cell.save(1, Some(77u8), None);
assert_eq!(cell.load(), Some(77u8));
}
#[test]
fn test_cell_clone_shares_state() {
let cell = GameStateCell::<u8>::default();
let clone = cell.clone();
cell.save(3, Some(55u8), None);
assert_eq!(clone.frame(), 3);
assert_eq!(clone.load(), Some(55u8));
}
#[test]
fn test_different_delays() {
let mut sync_layer = SyncLayer::<TestConfig>::new(2, 8);
let p1_delay = 2;
let p2_delay = 0;
sync_layer.set_frame_delay(0, p1_delay);
sync_layer.set_frame_delay(1, p2_delay);
let mut dummy_connect_status = Vec::new();
dummy_connect_status.push(ConnectionStatus::default());
dummy_connect_status.push(ConnectionStatus::default());
for i in 0..20 {
let game_input = PlayerInput::new(i, TestInput { inp: i as u8 });
sync_layer.add_remote_input(0, game_input);
sync_layer.add_remote_input(1, game_input);
dummy_connect_status[0].last_frame = i;
dummy_connect_status[1].last_frame = i;
if i >= 3 {
let sync_inputs = sync_layer.synchronized_inputs(&dummy_connect_status);
let player0_inputs = sync_inputs[0].0.inp;
let player1_inputs = sync_inputs[1].0.inp;
assert_eq!(player0_inputs, i as u8 - p1_delay as u8);
assert_eq!(player1_inputs, i as u8 - p2_delay as u8);
}
sync_layer.advance_frame();
}
}
fn make_connect_status(n: usize) -> Vec<ConnectionStatus> {
vec![ConnectionStatus::default(); n]
}
#[test]
fn test_advance_frame_increments_current_frame() {
let mut sync_layer = SyncLayer::<TestConfig>::new(1, 8);
assert_eq!(sync_layer.current_frame(), 0);
sync_layer.advance_frame();
assert_eq!(sync_layer.current_frame(), 1);
}
#[test]
fn test_save_current_state_updates_last_saved_frame() {
let mut sync_layer = SyncLayer::<TestConfig>::new(1, 8);
let req = sync_layer.save_current_state();
assert_eq!(sync_layer.last_saved_frame(), 0);
if let GgrsRequest::SaveGameState { cell, frame } = req {
cell.save(frame, Some(0u8), None);
}
}
#[test]
fn test_saved_state_by_frame_returns_none_before_save() {
let sync_layer = SyncLayer::<TestConfig>::new(1, 8);
assert!(sync_layer.saved_state_by_frame(0).is_none());
}
#[test]
fn test_saved_state_by_frame_returns_some_after_save() {
let mut sync_layer = SyncLayer::<TestConfig>::new(1, 8);
let req = sync_layer.save_current_state();
if let GgrsRequest::SaveGameState { cell, frame } = req {
cell.save(frame, Some(7u8), None);
}
assert!(sync_layer.saved_state_by_frame(0).is_some());
}
#[test]
fn test_load_frame_rewinds_current_frame() {
let mut sync_layer = SyncLayer::<TestConfig>::new(1, 8);
let req = sync_layer.save_current_state();
if let GgrsRequest::SaveGameState { cell, frame } = req {
cell.save(frame, Some(0u8), None);
}
sync_layer.advance_frame();
sync_layer.advance_frame();
sync_layer.advance_frame();
assert_eq!(sync_layer.current_frame(), 3);
let _req = sync_layer.load_frame(0);
assert_eq!(sync_layer.current_frame(), 0);
}
#[test]
fn test_check_simulation_consistency_no_mismatch() {
let mut sync_layer = SyncLayer::<TestConfig>::new(2, 8);
let connect_status = make_connect_status(2);
for i in 0..5 {
let inp = PlayerInput::new(i, TestInput { inp: i as u8 });
sync_layer.add_remote_input(0, inp);
sync_layer.add_remote_input(1, inp);
sync_layer.synchronized_inputs(&connect_status);
sync_layer.advance_frame();
}
assert_eq!(
sync_layer.check_simulation_consistency(NULL_FRAME),
NULL_FRAME
);
}
#[test]
fn test_check_simulation_consistency_finds_mismatch() {
let mut sync_layer = SyncLayer::<TestConfig>::new(1, 8);
sync_layer.add_remote_input(0, PlayerInput::new(0, TestInput { inp: 5 }));
let connect_status = make_connect_status(1);
sync_layer.synchronized_inputs(&connect_status); sync_layer.advance_frame();
sync_layer.synchronized_inputs(&connect_status); sync_layer.add_remote_input(0, PlayerInput::new(1, TestInput { inp: 99 }));
assert_eq!(sync_layer.check_simulation_consistency(NULL_FRAME), 1);
}
#[test]
fn test_set_last_confirmed_frame_updates_last_confirmed() {
let mut sync_layer = SyncLayer::<TestConfig>::new(1, 8);
for i in 0..10 {
sync_layer.add_remote_input(0, PlayerInput::new(i, TestInput { inp: i as u8 }));
sync_layer.advance_frame();
}
sync_layer.set_last_confirmed_frame(5, false);
assert_eq!(sync_layer.last_confirmed_frame(), 5);
}
#[test]
fn test_set_last_confirmed_frame_sparse_saving_caps_at_last_saved() {
let mut sync_layer = SyncLayer::<TestConfig>::new(1, 8);
let req = sync_layer.save_current_state();
if let GgrsRequest::SaveGameState { cell, frame } = req {
cell.save(frame, Some(0u8), None);
}
for i in 0..5 {
sync_layer.add_remote_input(0, PlayerInput::new(i, TestInput { inp: 0 }));
sync_layer.advance_frame();
}
sync_layer.set_last_confirmed_frame(4, true);
assert_eq!(sync_layer.last_confirmed_frame(), 0);
}
#[test]
fn test_disconnected_player_returns_default_input() {
let mut sync_layer = SyncLayer::<TestConfig>::new(2, 8);
let mut connect_status = make_connect_status(2);
connect_status[1].disconnected = true;
connect_status[1].last_frame = -1;
sync_layer.add_remote_input(0, PlayerInput::new(0, TestInput { inp: 42 }));
let inputs = sync_layer.synchronized_inputs(&connect_status);
assert_eq!(inputs[0].1, InputStatus::Confirmed);
assert_eq!(inputs[1].1, InputStatus::Disconnected);
assert_eq!(inputs[1].0.inp, 0); }
}