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
use crate::{
    input::GameInput,
    sync::{SavedCell, SavedFrame},
    Config, Event, Frame,
};
use std::{
    collections::hash_map::DefaultHasher,
    hash::{Hash, Hasher},
};
use tracing::{debug, error};

/// A singular command for a Backroll session client to execute.
///
/// Proper execution of the command is not optional, and must be done in the exact
/// order [Commands] returns it in. Filtering or altering the order in which commands
/// are executed, or dropping commands without executing them may result incorrect
/// simulation and/or panics.
///
/// [Commands]: self::Commands
pub enum Command<T>
where
    T: Config,
{
    /// The client should copy the entire contents of the current game state into a
    ///  new state struct and return it.
    ///
    /// Optionally, the client can compute a 64-bit checksum of the data and return it.
    Save(SaveState<T>),

    /// Backroll will issue this command at the beginning of a rollback. The argument
    /// provided will be a previously saved state returned from the save_state function.  
    /// The client should make the current game state match the state contained in the
    /// argument.
    Load(LoadState<T>),

    /// Clients should advance the game state by exactly one frame.  
    /// The provided inputs will contain the inputs you should use for the given frame.
    AdvanceFrame(GameInput<T::Input>),

    /// Notification that something has happened in the lower level protocols. See the
    /// `[Event]` struct for more information.
    Event(Event),
}

/// A command for saving the state of the game.
///
/// Consumers MUST save before the command is dropped. Failure to do so will
/// result in a panic.
pub struct SaveState<T>
where
    T: Config,
{
    pub(crate) cell: SavedCell<T>,
    pub(crate) frame: Frame,
}

impl<T: Config> SaveState<T> {
    /// Saves a single frame's state to the session's state buffer and uses
    /// the hash of the state as the checksum. This uses the
    /// [DefaultHasher] implementation.
    ///
    /// This consumes the SaveState, saving multiple times is not allowed.
    ///
    /// [DefaultHasher]: std::collections::hash_map::DefaultHasher
    pub fn save(self, state: T::State) {
        let mut hasher = DefaultHasher::new();
        state.hash(&mut hasher);
        self.save_with_hash(state, hasher.finish());
    }

    /// Saves a single frame's state to the session's state buffer without
    /// a saved checksum.
    ///
    /// This consumes the SaveState, saving multiple times is not allowed.
    pub fn save_without_hash(self, state: T::State) {
        self.save_state(state, None);
    }

    /// Saves a single frame's state to the session's state buffer with a
    /// provided checksum.
    ///
    /// This consumes the SaveState, saving multiple times is not allowed.
    pub fn save_with_hash(self, state: T::State, checksum: u64) {
        self.save_state(state, Some(checksum));
    }

    fn save_state(self, state: T::State, checksum: Option<u64>) {
        debug!(
            "=== Saved frame state {} (checksum: {:08x}).",
            self.frame,
            checksum.unwrap_or(0)
        );
        self.cell.save(SavedFrame::<T> {
            frame: self.frame,
            data: Some(Box::new(state)),
            checksum,
        });
        assert!(self.cell.is_valid());
    }
}

impl<T: Config> Drop for SaveState<T> {
    fn drop(&mut self) {
        if !self.cell.is_valid() {
            error!("A SaveState command was dropped without saving a valid state.");
        }
    }
}

/// A command for loading a saved state of the game.
pub struct LoadState<T>
where
    T: Config,
{
    pub(crate) cell: SavedCell<T>,
}

impl<T: Config> LoadState<T> {
    /// Loads the saved state of the game.
    ///
    /// This will clone the internal copy ofthe save state. For games with
    /// potentially large save state, this might be expensive.
    ///
    /// Note this consumes the LoadState, loading multiple times is
    /// not allowed.
    pub fn load(self) -> T::State {
        self.cell.load()
    }
}

/// An ordered container of commands for clients to execute.
///
/// Proper execution of the command is not optional, and must be done in the exact
/// order they are returned in. Filtering or altering the order in which commands
/// are executed, or dropping commands without executing them may result incorrect
/// simulation and/or panics.
pub struct Commands<T>
where
    T: Config,
{
    commands: Vec<Command<T>>,
}

impl<T: Config> Commands<T> {
    pub(crate) fn push(&mut self, command: Command<T>) {
        self.commands.push(command);
    }
}

impl<T: Config> Default for Commands<T> {
    fn default() -> Self {
        Self {
            commands: Vec::new(),
        }
    }
}

impl<T: Config> IntoIterator for Commands<T> {
    type Item = Command<T>;
    type IntoIter = std::vec::IntoIter<Command<T>>;
    fn into_iter(self) -> Self::IntoIter {
        self.commands.into_iter()
    }
}