#![feature(generic_associated_types)]
use crystalorb::{
client::stage::{Stage, StageMut},
timestamp::Timestamp,
world::Tweened,
Config, TweeningMethod,
};
use pretty_assertions::assert_eq;
use test_log::test;
mod common;
use common::{MockClientServer, MockCommand, MockWorld};
#[test]
fn while_all_commands_originate_from_single_client_then_that_client_should_match_server_exactly() {
const TIMESTEP_SECONDS: f64 = 1.0 / 60.0;
for frames_per_update in &[1.0, 0.5, 1.0 / 3.0, 1.5, 2.0, 3.0, 4.0, 6.0] {
const FRAMES_TO_LAG_BEHIND: i32 = 12;
assert_eq!(
(FRAMES_TO_LAG_BEHIND as f64 / frames_per_update).fract(),
0.0,
"lag needs to be multiple of frames_per_update so the display states line up.",
);
let mut mock_client_server = MockClientServer::new(Config {
lag_compensation_latency: FRAMES_TO_LAG_BEHIND as f64 * TIMESTEP_SECONDS,
blend_latency: 0.2,
timestep_seconds: TIMESTEP_SECONDS,
clock_sync_needed_sample_count: 8,
clock_sync_request_period: 0.0,
clock_sync_assumed_outlier_rate: 0.2,
max_tolerable_clock_deviation: 0.1,
snapshot_send_period: 0.1,
update_delta_seconds_max: 0.5,
timestamp_skip_threshold_seconds: 1.0,
fastforward_max_per_step: 10,
tweening_method: TweeningMethod::MostRecentlyPassed,
});
mock_client_server.client_1_net.connect();
mock_client_server.client_2_net.connect();
mock_client_server.update_until_clients_ready(TIMESTEP_SECONDS * frames_per_update);
let mut commands = [
vec![0, 1, 2],
vec![3, 4],
vec![5],
vec![6, 7],
vec![],
vec![8, 9, 10, 11, 12],
];
let start_timestamp = match mock_client_server.client_1.stage() {
Stage::Ready(client) => client.simulating_timestamp(),
_ => unreachable!(),
};
let target_timestamp =
start_timestamp + (commands.len() as i16).max(*frames_per_update as i16);
let mut client_state_history: Vec<Tweened<MockWorld>> = Vec::new();
let mut server_state_history: Vec<Tweened<MockWorld>> = Vec::new();
while mock_client_server.server.display_state().timestamp() < target_timestamp {
let current_client_timestamp = match mock_client_server.client_1.stage() {
Stage::Ready(client) => {
Timestamp::default() + client.display_state().float_timestamp() as i16
}
_ => unreachable!(),
};
let update_client = current_client_timestamp < target_timestamp;
if update_client {
match mock_client_server.client_1.stage_mut() {
StageMut::Ready(mut client) => {
let current_index = (i16::from(current_client_timestamp - start_timestamp))
.clamp(0, commands.len() as i16 - 1)
as usize;
for commands_for_single_timestep in commands[0..=current_index].iter_mut() {
for command in commands_for_single_timestep.drain(..) {
client.issue_command(
MockCommand(command),
&mut mock_client_server.client_1_net,
);
}
}
}
_ => unreachable!(),
}
}
mock_client_server.update(TIMESTEP_SECONDS * frames_per_update);
server_state_history.push(mock_client_server.server.display_state().into());
if update_client {
match mock_client_server.client_1.stage_mut() {
StageMut::Ready(client) => {
client_state_history.push(client.display_state().clone())
}
_ => unreachable!(),
}
}
}
assert_eq!(
server_state_history[server_state_history.len() - client_state_history.len()..],
client_state_history[..],
"frames per update: {}",
frames_per_update
);
}
}
#[test]
fn while_no_commands_are_issued_then_all_clients_should_match_server_exactly() {
const TIMESTEP_SECONDS: f64 = 1.0 / 60.0;
for frames_per_update in &[1.0, 0.5, 1.0 / 3.0, 1.5, 2.0, 3.0, 4.0, 6.0] {
const FRAMES_TO_LAG_BEHIND: i32 = 12;
assert_eq!(
(FRAMES_TO_LAG_BEHIND as f64 / frames_per_update).fract(),
0.0,
"lag needs to be multiple of frames_per_update so the display states line up.",
);
let mut mock_client_server = MockClientServer::new(Config {
lag_compensation_latency: FRAMES_TO_LAG_BEHIND as f64 * TIMESTEP_SECONDS,
blend_latency: 0.2,
timestep_seconds: TIMESTEP_SECONDS,
clock_sync_needed_sample_count: 8,
clock_sync_request_period: 0.0,
clock_sync_assumed_outlier_rate: 0.2,
max_tolerable_clock_deviation: 0.1,
snapshot_send_period: 0.1,
update_delta_seconds_max: 0.5,
timestamp_skip_threshold_seconds: 1.0,
fastforward_max_per_step: 10,
tweening_method: TweeningMethod::MostRecentlyPassed,
});
mock_client_server.client_1_net.connect();
mock_client_server.client_2_net.connect();
mock_client_server
.server
.issue_command(MockCommand(123), &mut mock_client_server.server_net);
mock_client_server.update_until_clients_ready(TIMESTEP_SECONDS * frames_per_update);
let start_timestamp = match mock_client_server.client_1.stage() {
Stage::Ready(client) => client.simulating_timestamp(),
_ => unreachable!(),
};
let target_timestamp = start_timestamp + 100;
let mut client_1_state_history: Vec<Tweened<MockWorld>> = Vec::new();
let mut client_2_state_history: Vec<Tweened<MockWorld>> = Vec::new();
let mut server_state_history: Vec<Tweened<MockWorld>> = Vec::new();
while mock_client_server.server.display_state().timestamp() < target_timestamp {
let current_client_timestamp = match mock_client_server.client_1.stage() {
Stage::Ready(client) => {
Timestamp::default() + client.display_state().float_timestamp() as i16
}
_ => unreachable!(),
};
let update_client = current_client_timestamp < target_timestamp;
mock_client_server.update(TIMESTEP_SECONDS * frames_per_update);
server_state_history.push(mock_client_server.server.display_state().into());
if update_client {
match mock_client_server.client_1.stage_mut() {
StageMut::Ready(client) => {
client_1_state_history.push(client.display_state().clone())
}
_ => unreachable!(),
}
match mock_client_server.client_2.stage_mut() {
StageMut::Ready(client) => {
client_2_state_history.push(client.display_state().clone())
}
_ => unreachable!(),
}
}
}
assert_eq!(
server_state_history[server_state_history.len() - client_1_state_history.len()..],
client_1_state_history[..],
"client_1, frames per update: {}",
frames_per_update
);
assert_eq!(
server_state_history[server_state_history.len() - client_2_state_history.len()..],
client_2_state_history[..],
"client_2, frames per update: {}",
frames_per_update
);
}
}