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
//! # GGRS
//! GGRS (good game rollback system) is a reimagination of the GGPO network SDK written in 100% safe Rust 🦀.
//! The callback-style API from the original library has been replaced with a much saner, simpler control flow.
//! Instead of registering callback functions, GGRS returns a list of requests for the user to fulfill.
#![forbid(unsafe_code)] // let us try
//#![warn(clippy::all, clippy::pedantic, clippy::nursery, clippy::cargo)]
pub use error::GGRSError;
pub use frame_info::{GameInput, GameState};
pub use network::network_stats::NetworkStats;
pub use network::non_blocking_socket::NonBlockingSocket;
pub use network::udp_msg::UdpMessage;
pub use sessions::p2p_session::P2PSession;
pub use sessions::p2p_spectator_session::P2PSpectatorSession;
pub use sessions::sync_test_session::SyncTestSession;
pub use sync_layer::GameStateCell;
pub(crate) mod error;
pub(crate) mod frame_info;
pub(crate) mod input_queue;
pub(crate) mod sync_layer;
pub(crate) mod time_sync;
pub(crate) mod sessions {
pub(crate) mod p2p_session;
pub(crate) mod p2p_spectator_session;
pub(crate) mod sync_test_session;
}
pub(crate) mod network {
pub(crate) mod compression;
pub(crate) mod network_stats;
pub(crate) mod non_blocking_socket;
pub(crate) mod udp_msg;
pub(crate) mod udp_protocol;
}
// #############
// # CONSTANTS #
// #############
/// The maximum number of players allowed. Theoretically, higher player numbers should work, but are not well-tested.
pub const MAX_PLAYERS: u32 = 4;
/// The maximum number of frames GGRS will roll back. Every gamestate older than this is guaranteed to be correct if the players did not desync.
pub const MAX_PREDICTION_FRAMES: u32 = 8;
/// The maximum number of bytes the input of a single player can consist of. This corresponds to the size of `usize`.
/// Higher values should be possible, but are not tested.
pub const MAX_INPUT_BYTES: usize = 8;
/// Internally, -1 represents no frame / invalid frame.
pub const NULL_FRAME: i32 = -1;
pub type Frame = i32;
pub type PlayerHandle = usize;
// #############
// # ENUMS #
// #############
/// Defines the three types of players that GGRS considers:
/// - local players, who play on the local device,
/// - remote players, who play on other devices and
/// - spectators, who are remote players that do not contribute to the game input.
/// Both `Remote` and `Spectator` have a socket address associated with them.
#[derive(Debug, Copy, Clone, Hash, PartialEq, Eq)]
pub enum PlayerType {
/// This player plays on the local device.
Local,
/// This player plays on a remote device identified by the socket address.
Remote(std::net::SocketAddr),
/// This player spectates on a remote device identified by the socket address. They do not contribute to the game input.
Spectator(std::net::SocketAddr),
}
impl Default for PlayerType {
fn default() -> Self {
Self::Local
}
}
/// A session is always in one of these states. You can query the current state of a session via `current_state()`.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum SessionState {
/// When initializing, you must add all necessary players and start the session to continue.
Initializing,
/// When synchronizing, the session attempts to establish a connection to the remote clients.
Synchronizing,
/// When running, the session has synchronized and is ready to take and transmit player input.
Running,
}
/// Notifications that you can receive from the session. Handling them is up to the user.
#[derive(Debug, Copy, Clone, PartialEq, Eq)]
pub enum GGRSEvent {
/// The session made progress in synchronizing. After `total` roundtrips, the session are synchronized.
Synchronizing {
player_handle: PlayerHandle,
total: u32,
count: u32,
},
/// The session is now synchronized with the remote client.
Synchronized { player_handle: PlayerHandle },
/// The remote client has disconnected.
Disconnected { player_handle: PlayerHandle },
/// The session has not received packets from the remote client for some time and will disconnect the remote in `disconnect_timeout` ms.
NetworkInterrupted {
player_handle: PlayerHandle,
disconnect_timeout: u128,
},
/// Sent only after a `NetworkInterrupted` event, if communication with that player has resumed.
NetworkResumed { player_handle: PlayerHandle },
/// Sent out if GGRS recommends skipping a few frames to let clients catch up. If you receive this, consider waiting `skip_frames` number of frames.
WaitRecommendation { skip_frames: u32 },
}
/// Requests that you can receive from the session. Handling them is mandatory.
#[derive(Debug)]
pub enum GGRSRequest {
/// You should save the current gamestate in the `cell` provided to you. The given `frame` is a sanity check: The gamestate you save should be from that frame.
SaveGameState { cell: GameStateCell, frame: Frame },
/// You should load the gamestate in the `cell` provided to you.
LoadGameState { cell: GameStateCell },
/// You should advance the gamestate with the `inputs` provided to you.
/// Disconnected players are indicated by having `NULL_FRAME` instead of the correct current frame in their input.
AdvanceFrame { inputs: Vec<GameInput> },
}