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() } }