crystalorb 0.3.0

Network-agnostic, high-level game networking library
Documentation
#![feature(generic_associated_types)]

use crystalorb::{client::stage::Stage, Config, TweeningMethod};
use test_log::test;

mod common;

use common::MockClientServer;

#[test]
fn when_server_and_client_clocks_desync_then_client_should_resync_quickly() {
    const UPDATE_COUNT: usize = 200;
    const TIMESTEP_SECONDS: f64 = 1.0 / 64.0;

    for desync_seconds in &[
        0.0f64,
        0.5f64,
        -0.5f64,
        -1.0f64,
        -100.0f64,
        -1000.0f64,
        -10000.0f64,
    ] {
        // GIVEN a server and client in a perfect network.
        let mut mock_client_server = MockClientServer::new(Config {
            lag_compensation_latency: TIMESTEP_SECONDS * 16.0,
            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();

        // GIVEN that the client is ready and synced up.
        mock_client_server.update_until_clients_ready(TIMESTEP_SECONDS);
        match mock_client_server.client_1.stage() {
            Stage::Ready(client) => {
                assert_eq!(
                    client.last_completed_timestamp(),
                    mock_client_server
                        .server
                        .estimated_client_last_completed_timestamp()
                        // Note: + 1 since client is overshooting.
                        + 1,
                    "Precondition: clocks are initially synced up"
                );
            }
            _ => unreachable!(),
        }

        // WHEN the client and server clocks are desynchronized.
        mock_client_server.client_1_clock_offset = *desync_seconds;

        // THEN the client should quickly offset its own clock to agree with the server.
        for _ in 0..UPDATE_COUNT {
            mock_client_server.update(TIMESTEP_SECONDS);
        }
        match mock_client_server.client_1.stage() {
            Stage::Ready(client) => {
                assert_eq!(
                    client.last_completed_timestamp(),
                    mock_client_server
                        .server
                        .estimated_client_last_completed_timestamp()
                        // Note: + 1 since client is overshooting.
                        + 1,
                    "Condition: Client synced up after {} updates (desync by {})",
                    UPDATE_COUNT,
                    desync_seconds
                );
            }
            _ => unreachable!(),
        }
    }
}

#[test]
fn when_client_connects_then_client_calculates_correct_initial_clock_offset() {
    const TIMESTEP_SECONDS: f64 = 1.0 / 64.0;

    for desync_seconds in &[
        0.0f64,
        0.5f64,
        1.0f64,
        100.0f64,
        1000.0f64,
        10000.0f64,
        -0.5f64,
        -1.0f64,
        -100.0f64,
        -1000.0f64,
        -10000.0f64,
    ] {
        // GIVEN a server and client in a perfect network.
        let mut mock_client_server = MockClientServer::new(Config {
            lag_compensation_latency: TIMESTEP_SECONDS * 16.0,
            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();

        // GIVEN that the client and server clocks initially disagree.
        mock_client_server.client_1_clock_offset = *desync_seconds;

        // WHEN the client initially connects.
        mock_client_server.update_until_clients_ready(TIMESTEP_SECONDS);

        // THEN the client should accurately offset its own clock to agree with the server.
        match mock_client_server.client_1.stage() {
            Stage::Ready(client) => {
                assert_eq!(
                    client.last_completed_timestamp(),
                    mock_client_server
                        .server
                        .estimated_client_last_completed_timestamp()
                        // Note: + 1 since client is overshooting.
                        + 1,
                    "Precondition: clocks are initially synced up"
                );
            }
            _ => unreachable!(),
        }
    }
}