use std::collections::HashMap;
use instant::Duration;
use crate::{
network::protocol::UdpProtocol, sessions::p2p_session::PlayerRegistry, Config, DesyncDetection,
GgrsError, NonBlockingSocket, P2PSession, PlayerHandle, PlayerType, SpectatorSession,
SyncTestSession,
};
pub(crate) const SPECTATOR_BUFFER_SIZE: usize = 60;
const DEFAULT_PLAYERS: usize = 2;
const DEFAULT_SAVE_MODE: bool = false;
const DEFAULT_DETECTION_MODE: DesyncDetection = DesyncDetection::Off;
const DEFAULT_INPUT_DELAY: usize = 0;
const DEFAULT_DISCONNECT_TIMEOUT: Duration = Duration::from_millis(2000);
const DEFAULT_DISCONNECT_NOTIFY_START: Duration = Duration::from_millis(500);
const DEFAULT_FPS: usize = 60;
const DEFAULT_MAX_PREDICTION_FRAMES: usize = 8;
const DEFAULT_CHECK_DISTANCE: usize = 2;
const DEFAULT_MAX_FRAMES_BEHIND: usize = 10;
const DEFAULT_CATCHUP_SPEED: usize = 1;
pub(crate) const MAX_EVENT_QUEUE_SIZE: usize = 100;
#[must_use]
#[derive(Debug)]
pub struct SessionBuilder<T>
where
T: Config,
{
num_players: usize,
local_players: usize,
max_prediction: usize,
fps: usize,
sparse_saving: bool,
desync_detection: DesyncDetection,
disconnect_timeout: Duration,
disconnect_notify_start: Duration,
player_reg: PlayerRegistry<T>,
input_delay: usize,
check_dist: usize,
max_frames_behind: usize,
catchup_speed: usize,
}
impl<T: Config> Default for SessionBuilder<T> {
fn default() -> Self {
Self::new()
}
}
impl<T: Config> SessionBuilder<T> {
pub fn new() -> Self {
Self {
player_reg: PlayerRegistry::new(),
local_players: 0,
num_players: DEFAULT_PLAYERS,
max_prediction: DEFAULT_MAX_PREDICTION_FRAMES,
fps: DEFAULT_FPS,
sparse_saving: DEFAULT_SAVE_MODE,
desync_detection: DEFAULT_DETECTION_MODE,
disconnect_timeout: DEFAULT_DISCONNECT_TIMEOUT,
disconnect_notify_start: DEFAULT_DISCONNECT_NOTIFY_START,
input_delay: DEFAULT_INPUT_DELAY,
check_dist: DEFAULT_CHECK_DISTANCE,
max_frames_behind: DEFAULT_MAX_FRAMES_BEHIND,
catchup_speed: DEFAULT_CATCHUP_SPEED,
}
}
pub fn add_player(
mut self,
player_type: PlayerType<T::Address>,
player_handle: PlayerHandle,
) -> Result<Self, GgrsError> {
if self.player_reg.handles.contains_key(&player_handle) {
return Err(GgrsError::InvalidRequest {
info: "Player handle already in use.".to_owned(),
});
}
match player_type {
PlayerType::Local => {
self.local_players += 1;
if player_handle >= self.num_players {
return Err(GgrsError::InvalidRequest {
info: "The player handle you provided is invalid. For a local player, the handle should be between 0 and num_players".to_owned(),
});
}
}
PlayerType::Remote(_) => {
if player_handle >= self.num_players {
return Err(GgrsError::InvalidRequest {
info: "The player handle you provided is invalid. For a remote player, the handle should be between 0 and num_players".to_owned(),
});
}
}
PlayerType::Spectator(_) => {
if player_handle < self.num_players {
return Err(GgrsError::InvalidRequest {
info: "The player handle you provided is invalid. For a spectator, the handle should be num_players or higher".to_owned(),
});
}
}
}
self.player_reg.handles.insert(player_handle, player_type);
Ok(self)
}
pub fn with_max_prediction_window(mut self, window: usize) -> Self {
self.max_prediction = window;
self
}
pub fn with_input_delay(mut self, delay: usize) -> Self {
self.input_delay = delay;
self
}
pub fn with_num_players(mut self, num_players: usize) -> Result<Self, GgrsError> {
if num_players == 0 {
return Err(GgrsError::InvalidRequest {
info: "Number of players must be at least 1.".to_owned(),
});
}
self.num_players = num_players;
Ok(self)
}
pub fn with_sparse_saving_mode(mut self, sparse_saving: bool) -> Self {
self.sparse_saving = sparse_saving;
self
}
pub fn with_desync_detection_mode(mut self, desync_detection: DesyncDetection) -> Self {
self.desync_detection = desync_detection;
self
}
pub fn with_disconnect_timeout(mut self, timeout: Duration) -> Self {
self.disconnect_timeout = timeout;
self
}
pub fn with_disconnect_notify_delay(mut self, notify_delay: Duration) -> Self {
self.disconnect_notify_start = notify_delay;
self
}
pub fn with_fps(mut self, fps: usize) -> Result<Self, GgrsError> {
if fps == 0 {
return Err(GgrsError::InvalidRequest {
info: "FPS should be higher than 0.".to_owned(),
});
}
self.fps = fps;
Ok(self)
}
pub fn with_check_distance(mut self, check_distance: usize) -> Self {
self.check_dist = check_distance;
self
}
pub fn with_max_frames_behind(mut self, max_frames_behind: usize) -> Result<Self, GgrsError> {
if max_frames_behind < 1 {
return Err(GgrsError::InvalidRequest {
info: "Max frames behind cannot be smaller than 1.".to_owned(),
});
}
if max_frames_behind >= SPECTATOR_BUFFER_SIZE {
return Err(GgrsError::InvalidRequest {
info: format!(
"Max frames behind cannot be larger or equal than the Spectator buffer size ({SPECTATOR_BUFFER_SIZE})"
),
});
}
self.max_frames_behind = max_frames_behind;
Ok(self)
}
pub fn with_catchup_speed(mut self, catchup_speed: usize) -> Result<Self, GgrsError> {
if catchup_speed < 1 {
return Err(GgrsError::InvalidRequest {
info: "Catchup speed cannot be smaller than 1.".to_owned(),
});
}
if catchup_speed >= self.max_frames_behind {
return Err(GgrsError::InvalidRequest {
info: "Catchup speed cannot be larger or equal than the allowed maximum frames behind host"
.to_owned(),
});
}
self.catchup_speed = catchup_speed;
Ok(self)
}
pub fn start_p2p_session(
mut self,
socket: impl NonBlockingSocket<T::Address> + 'static,
) -> Result<P2PSession<T>, GgrsError> {
for player_handle in 0..self.num_players {
if !self.player_reg.handles.contains_key(&player_handle) {
return Err(GgrsError::InvalidRequest{
info: "Not enough players have been added. Keep registering players up to the defined player number.".to_owned(),
});
}
}
let mut addr_count = HashMap::<PlayerType<T::Address>, Vec<PlayerHandle>>::new();
for (handle, player_type) in &self.player_reg.handles {
match player_type {
PlayerType::Remote(_) | PlayerType::Spectator(_) => addr_count
.entry(player_type.clone())
.or_insert_with(Vec::new)
.push(*handle),
PlayerType::Local => (),
}
}
for (player_type, handles) in addr_count {
match player_type {
PlayerType::Remote(peer_addr) => {
self.player_reg.remotes.insert(
peer_addr.clone(),
self.create_endpoint(handles, peer_addr.clone(), self.local_players),
);
}
PlayerType::Spectator(peer_addr) => {
self.player_reg.spectators.insert(
peer_addr.clone(),
self.create_endpoint(handles, peer_addr.clone(), self.num_players), );
}
PlayerType::Local => (),
}
}
Ok(P2PSession::<T>::new(
self.num_players,
self.max_prediction,
Box::new(socket),
self.player_reg,
self.sparse_saving,
self.desync_detection,
self.input_delay,
))
}
pub fn start_spectator_session(
self,
host_addr: T::Address,
socket: impl NonBlockingSocket<T::Address> + 'static,
) -> SpectatorSession<T> {
let mut host = UdpProtocol::new(
(0..self.num_players).collect(),
host_addr,
self.num_players,
1, self.max_prediction,
self.disconnect_timeout,
self.disconnect_notify_start,
self.fps,
DesyncDetection::Off,
);
host.synchronize();
SpectatorSession::new(
self.num_players,
Box::new(socket),
host,
self.max_frames_behind,
self.catchup_speed,
)
}
pub fn start_synctest_session(self) -> Result<SyncTestSession<T>, GgrsError> {
if self.check_dist >= self.max_prediction {
return Err(GgrsError::InvalidRequest {
info: "Check distance too big.".to_owned(),
});
}
if self.sparse_saving {
return Err(GgrsError::InvalidRequest {
info: "Sparse saving is not supported for synctest sessions.".to_owned(),
});
}
Ok(SyncTestSession::new(
self.num_players,
self.max_prediction,
self.check_dist,
self.input_delay,
))
}
fn create_endpoint(
&self,
handles: Vec<PlayerHandle>,
peer_addr: T::Address,
local_players: usize,
) -> UdpProtocol<T> {
let mut endpoint = UdpProtocol::new(
handles,
peer_addr,
self.num_players,
local_players,
self.max_prediction,
self.disconnect_timeout,
self.disconnect_notify_start,
self.fps,
self.desync_detection,
);
endpoint.synchronize();
endpoint
}
}