1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
use crate::error::GGRSError;
use crate::frame_info::GameInput;
use crate::network::udp_msg::ConnectionStatus;
use crate::sync_layer::SyncLayer;
use crate::{Frame, GGRSRequest};
use crate::{PlayerHandle, PlayerType, SessionState};

/// During a `SyncTestSession`, GGRS will simulate a rollback every frame and resimulate the last n states, where n is the given check distance. If you provide checksums
/// in your `save_game_state()` function, the `SyncTestSession` will compare the resimulated checksums with the original checksums and report if there was a mismatch.
#[derive(Debug)]
pub struct SyncTestSession {
    num_players: u32,
    input_size: usize,
    check_distance: u32,
    running: bool,
    sync_layer: SyncLayer,
    dummy_connect_status: Vec<ConnectionStatus>,
}

impl SyncTestSession {
    /// Creates a new `SyncTestSession` instance with given values.
    pub(crate) fn new(num_players: u32, input_size: usize, check_distance: u32) -> Self {
        let mut dummy_connect_status = Vec::new();
        for _ in 0..num_players {
            dummy_connect_status.push(ConnectionStatus::default());
        }
        Self {
            num_players,
            input_size,
            check_distance,
            running: false,
            sync_layer: SyncLayer::new(num_players, input_size),
            dummy_connect_status,
        }
    }

    /// Must be called for each player in the session (e.g. in a 3 player session, must be called 3 times).
    /// # Errors
    /// Will return `InvalidHandle` when the provided player handle is too big for the number of players.
    /// Will return `InvalidRequest` if a player with that handle has been added before.
    /// Will return `InvalidRequest` for any player type other than `Local`. `SyncTestSession` does not support remote players.
    pub fn add_player(
        &mut self,
        player_type: PlayerType,
        player_handle: PlayerHandle,
    ) -> Result<(), GGRSError> {
        if player_handle >= self.num_players as PlayerHandle {
            return Err(GGRSError::InvalidHandle);
        }
        if player_type != PlayerType::Local {
            return Err(GGRSError::InvalidRequest);
        }
        Ok(())
    }

    /// After you are done defining and adding all players, you should start the session. In a sync test, starting the session saves the initial game state and sets running to true.
    ///
    /// # Errors
    /// Return a `InvalidRequestError`, if the session is already running.
    pub fn start_session(&mut self) -> Result<(), GGRSError> {
        if self.running {
            return Err(GGRSError::InvalidRequest);
        }

        self.running = true;
        Ok(())
    }

    /// In a sync test, this will advance the state by a single frame and afterwards rollback `check_distance` amount of frames,
    /// resimulate and compare checksums with the original states. Returns an order-sensitive `Vec<GGRSRequest>`.
    /// You should fulfill all requests in the exact order they are provided. Failure to do so will cause panics later.
    ///
    /// # Errors
    /// - Returns `InvalidHandle` if the provided player handle is higher than the number of players.
    /// - Returns `MismatchedChecksumError` if checksums don't match after resimulation.
    /// - Returns `NotSynchronized` if the session has not been started yet.
    pub fn advance_frame(
        &mut self,
        player_handle: PlayerHandle,
        input: &[u8],
    ) -> Result<Vec<GGRSRequest>, GGRSError> {
        // player handle is invalid
        if player_handle > self.num_players as PlayerHandle {
            return Err(GGRSError::InvalidHandle);
        }
        // session has not been started
        if !self.running {
            return Err(GGRSError::NotSynchronized);
        }

        let mut requests = Vec::new();

        // manual simulated rollbacks without using the sync_layer, but only if we have enough frames in the past
        if self.check_distance > 0 && self.sync_layer.current_frame() > self.check_distance as i32 {
            let frame_to = self.sync_layer.current_frame() - self.check_distance as i32;
            self.adjust_gamestate(frame_to, &mut requests);
        }

        //create an input struct for current frame
        let mut current_input: GameInput =
            GameInput::new(self.sync_layer.current_frame(), self.input_size);
        current_input.copy_input(input);

        // send the input into the sync layer
        self.sync_layer
            .add_local_input(player_handle, current_input)?;

        // save the current frame in the syncronization layer
        requests.push(self.sync_layer.save_current_state());

        // get the correct inputs for all players from the sync layer
        let inputs = self
            .sync_layer
            .synchronized_inputs(&self.dummy_connect_status);
        for input in &inputs {
            assert_eq!(input.frame, self.sync_layer.current_frame());
        }

        // advance the frame
        requests.push(GGRSRequest::AdvanceFrame { inputs });
        self.sync_layer.advance_frame();

        // since this is a sync test, we "cheat" by setting the last confirmed state to the (current state - check_distance), so the sync layer wont complain about missing
        // inputs from other players
        let safe_frame = self.sync_layer.current_frame() - self.check_distance as i32;

        self.sync_layer.set_last_confirmed_frame(safe_frame);

        // also, we update the dummy connect status to pretend that we received inputs from all players
        for con_stat in &mut self.dummy_connect_status {
            con_stat.last_frame = self.sync_layer.current_frame();
        }

        Ok(requests)
    }

    /// Change the amount of frames GGRS will delay the inputs for a player.
    /// # Errors
    /// Returns `InvalidHandle` if the provided player handle is higher than the number of players.
    /// Returns `InvalidRequest` if the provided player handle refers to a remote player.
    pub fn set_frame_delay(
        &mut self,
        frame_delay: u32,
        player_handle: PlayerHandle,
    ) -> Result<(), GGRSError> {
        // player handle is invalid
        if player_handle > self.num_players as PlayerHandle {
            return Err(GGRSError::InvalidHandle);
        }
        self.sync_layer.set_frame_delay(player_handle, frame_delay);
        Ok(())
    }

    pub const fn current_state(&self) -> SessionState {
        if self.running {
            SessionState::Running
        } else {
            SessionState::Initializing
        }
    }

    fn adjust_gamestate(&mut self, frame_to: Frame, requests: &mut Vec<GGRSRequest>) {
        let current_frame = self.sync_layer.current_frame();
        let count = current_frame - frame_to;

        // rollback to the first incorrect state
        requests.push(self.sync_layer.load_frame(frame_to));
        self.sync_layer.reset_prediction(frame_to);
        assert_eq!(self.sync_layer.current_frame(), frame_to);

        // step forward to the previous current state
        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(), current_frame);
    }
}