use std::collections::HashMap;
use crate::error::GgrsError;
use crate::frame_info::PlayerInput;
use crate::network::messages::ConnectionStatus;
use crate::sync_layer::SyncLayer;
use crate::{Config, Frame, GgrsRequest, PlayerHandle};
pub struct SyncTestSession<T>
where
T: Config,
{
num_players: usize,
max_prediction: usize,
check_distance: usize,
sync_layer: SyncLayer<T>,
dummy_connect_status: Vec<ConnectionStatus>,
checksum_history: HashMap<Frame, Option<u128>>,
local_inputs: HashMap<PlayerHandle, PlayerInput<T::Input>>,
}
impl<T: Config> SyncTestSession<T> {
pub(crate) fn new(
num_players: usize,
max_prediction: usize,
check_distance: usize,
input_delay: usize,
) -> Self {
let mut dummy_connect_status = Vec::new();
for _ in 0..num_players {
dummy_connect_status.push(ConnectionStatus::default());
}
let mut sync_layer = SyncLayer::new(num_players, max_prediction);
for i in 0..num_players {
sync_layer.set_frame_delay(i, input_delay);
}
Self {
num_players,
max_prediction,
check_distance,
sync_layer,
dummy_connect_status,
checksum_history: HashMap::new(),
local_inputs: HashMap::new(),
}
}
pub fn add_local_input(
&mut self,
player_handle: PlayerHandle,
input: T::Input,
) -> Result<(), GgrsError> {
if player_handle >= self.num_players {
return Err(GgrsError::InvalidRequest {
info: "The player handle you provided is not valid.".to_owned(),
});
}
let player_input = PlayerInput::<T::Input>::new(self.sync_layer.current_frame(), input);
self.local_inputs.insert(player_handle, player_input);
Ok(())
}
pub fn advance_frame(&mut self) -> Result<Vec<GgrsRequest<T>>, GgrsError> {
let mut requests = Vec::new();
let current_frame = self.sync_layer.current_frame();
if self.check_distance > 0 && current_frame > self.check_distance as i32 {
let oldest_frame_to_check = current_frame - self.check_distance as Frame;
let mismatched_frames: Vec<_> = (oldest_frame_to_check..=current_frame)
.filter(|frame_to_check| !self.checksums_consistent(*frame_to_check))
.collect();
if !mismatched_frames.is_empty() {
return Err(GgrsError::MismatchedChecksum {
current_frame,
mismatched_frames,
});
}
let frame_to = self.sync_layer.current_frame() - self.check_distance as i32;
self.adjust_gamestate(frame_to, &mut requests);
}
if self.num_players != self.local_inputs.len() {
return Err(GgrsError::InvalidRequest {
info: "Missing local input while calling advance_frame().".to_owned(),
});
}
for (&handle, &input) in self.local_inputs.iter() {
self.sync_layer.add_local_input(handle, input);
}
self.local_inputs.clear();
if self.check_distance > 0 {
requests.push(self.sync_layer.save_current_state());
}
let inputs = self
.sync_layer
.synchronized_inputs(&self.dummy_connect_status);
requests.push(GgrsRequest::AdvanceFrame { inputs });
self.sync_layer.advance_frame();
let safe_frame = self.sync_layer.current_frame() - self.check_distance as i32;
self.sync_layer.set_last_confirmed_frame(safe_frame, false);
for con_stat in &mut self.dummy_connect_status {
con_stat.last_frame = self.sync_layer.current_frame();
}
Ok(requests)
}
pub fn current_frame(&self) -> Frame {
self.sync_layer.current_frame()
}
pub fn num_players(&self) -> usize {
self.num_players
}
pub fn max_prediction(&self) -> usize {
self.max_prediction
}
pub fn check_distance(&self) -> usize {
self.check_distance
}
fn checksums_consistent(&mut self, frame_to_check: Frame) -> bool {
let oldest_allowed_frame = self.sync_layer.current_frame() - self.check_distance as i32;
self.checksum_history
.retain(|&k, _| k >= oldest_allowed_frame);
let Some(latest_cell) = self.sync_layer.saved_state_by_frame(frame_to_check) else {
return true;
};
if let Some(&cs) = self.checksum_history.get(&latest_cell.frame()) {
cs == latest_cell.checksum()
} else {
self.checksum_history
.insert(latest_cell.frame(), latest_cell.checksum());
true
}
}
fn adjust_gamestate(&mut self, frame_to: Frame, requests: &mut Vec<GgrsRequest<T>>) {
let start_frame = self.sync_layer.current_frame();
let count = start_frame - frame_to;
requests.push(self.sync_layer.load_frame(frame_to));
self.sync_layer.reset_prediction();
assert_eq!(self.sync_layer.current_frame(), frame_to);
for i in 0..count {
let inputs = self
.sync_layer
.synchronized_inputs(&self.dummy_connect_status);
if i > 0 {
requests.push(self.sync_layer.save_current_state());
}
self.sync_layer.advance_frame();
requests.push(GgrsRequest::AdvanceFrame { inputs });
}
assert_eq!(self.sync_layer.current_frame(), start_frame);
}
}