backroll/lib.rs
1use std::time::Duration;
2use thiserror::Error;
3
4mod backend;
5pub mod command;
6mod input;
7mod protocol;
8mod sync;
9mod time_sync;
10
11pub use backend::*;
12pub use backroll_transport as transport;
13pub use input::GameInput;
14
15/// The maximum number of players supported in a single game.
16pub const MAX_PLAYERS: usize = 8;
17// Approximately 2 seconds of frames.
18const MAX_ROLLBACK_FRAMES: usize = 120;
19
20type Frame = i32;
21const NULL_FRAME: Frame = -1;
22
23fn is_null(frame: Frame) -> bool {
24 frame < 0
25}
26
27/// A handle for a player in a Backroll session.
28#[derive(Copy, Clone, Debug)]
29pub struct PlayerHandle(pub usize);
30
31/// Players within a Backroll session.
32#[derive(Clone)]
33pub enum Player {
34 /// The local player. Backroll currently only supports one local player per machine.
35 Local,
36 /// A remote player that is not on the local session.
37 Remote(transport::Peer),
38}
39
40impl Player {
41 pub(crate) fn is_local(&self) -> bool {
42 matches!(self, Self::Local)
43 }
44}
45
46impl Default for Player {
47 fn default() -> Self {
48 Self::Local
49 }
50}
51
52/// Compile time parameterization for Backroll sessions.
53pub trait Config: 'static {
54 /// The input type for a Backroll session. This is the only game-related data
55 /// transmitted over the network.
56 ///
57 /// Reminder: Types implementing [Pod] may not have the same byte representation
58 /// on platforms with different endianness. Backroll assumes that all players are
59 /// running with the same endianness when encoding and decoding inputs. It may be
60 /// worthwhile to ensure that all players are running with the same endianess.
61 ///
62 /// [Pod]: bytemuck::Pod
63 type Input: PartialEq + bytemuck::Pod + bytemuck::Zeroable + Send + Sync;
64
65 /// The save state type for the session. This type must be safe to send across
66 /// threads and have a 'static lifetime. This type is also responsible for
67 /// dropping any internal linked state via [Drop].
68 ///
69 /// [Drop]: std::ops::Drop
70 type State: Clone + Send + Sync + 'static;
71}
72
73#[derive(Clone, Debug, Error)]
74pub enum BackrollError {
75 #[error("Multiple players ")]
76 MultipleLocalPlayers,
77 #[error("Action cannot be taken while in rollback.")]
78 InRollback,
79 #[error("The session has not been synchronized yet.")]
80 NotSynchronized,
81 #[error("The simulation has reached the prediction barrier.")]
82 ReachedPredictionBarrier,
83 #[error("Invalid player handle: {:?}", .0)]
84 InvalidPlayer(PlayerHandle),
85 #[error("Player already disconnected: {:?}", .0)]
86 PlayerDisconnected(PlayerHandle),
87}
88
89pub type BackrollResult<T> = Result<T, BackrollError>;
90
91#[derive(Clone, Debug, Default)]
92/// Event that occurs during the course of a session.
93pub struct NetworkStats {
94 /// The round time trip duration between the local player and the
95 /// remote.
96 pub ping: Duration,
97 /// The number of outgoing messages currently not sent.
98 pub send_queue_len: usize,
99 /// The number of incoming messages currently not processed.
100 pub recv_queue_len: usize,
101 /// The number of kilobytes sent per second, a rolling average.
102 pub kbps_sent: u32,
103
104 /// The local frame advantage relative to the associated peer.
105 pub local_frames_behind: Frame,
106 /// The remote frame advantage of the associated peer relative to the local player.
107 pub remote_frames_behind: Frame,
108}
109
110#[derive(Clone, Debug)]
111/// Event that occurs during the course of a session.
112pub enum Event {
113 /// A initial response packet from the remote player has been recieved.
114 Connected(PlayerHandle),
115 /// A response from a remote player has been recieved during the initial
116 /// synchronization handshake.
117 Synchronizing {
118 player: PlayerHandle,
119 count: u8,
120 total: u8,
121 },
122 /// The initial synchronization handshake has been completed. The connection
123 /// is considered live now.
124 Synchronized(PlayerHandle),
125 /// All remote peers are now synchronized, the session is can now start
126 /// running.
127 Running,
128 /// The connection with a remote player has been disconnected.
129 Disconnected(PlayerHandle),
130 /// The local client is several frames ahead of all other peers. Might need
131 /// to stall a few frames to allow others to catch up.
132 TimeSync { frames_ahead: u8 },
133 /// The connection with a remote player has been temporarily interrupted.
134 ConnectionInterrupted {
135 player: PlayerHandle,
136 disconnect_timeout: Duration,
137 },
138 /// The connection with a remote player has been resumed after being interrupted.
139 ConnectionResumed(PlayerHandle),
140}