### Start P2PSession Source: https://github.com/gschup/ggrs/wiki/2.-Sessions Start a P2PSession by configuring the SessionBuilder with the number of players, adding local and remote players, and then calling start_p2p_session with a bound socket. ```rust let local_port = 7001 let remote_addr: SocketAddr = "127.0.0.1:7002".parse()?; let socket = UdpNonBlockingSocket::bind_to_port(local_port)?; let mut sess = SessionBuilder::::new() .with_num_players(2) .add_player(PlayerType::Local, 0)? .add_player(PlayerType::Remote(remote_addr), 1)? .start_p2p_session(socket)?; ``` -------------------------------- ### Launch Two-Player Game with Spectator (P2P) Source: https://github.com/gschup/ggrs/blob/main/examples/README.md Run these commands in separate terminals to launch a two-player game with a spectator. The first command starts a player client with a spectator address, the second starts the other player client, and the third starts the spectator client. ```shell cargo run --example ex_game_p2p -- --local-port 7000 --players localhost 127.0.0.1:7001 --spectators 127.0.0.1:7002 cargo run --example ex_game_p2p -- --local-port 7001 --players 127.0.0.1:7000 localhost cargo run --example ex_game_spectator -- --local-port 7002 --num-players 2 --host 127.0.0.1:7000 ``` -------------------------------- ### Launch ExGame SyncTest Source: https://github.com/gschup/ggrs/blob/main/examples/README.md Run this command to launch the ExGame SyncTest example, which focuses on simulating rollbacks and comparing checksums without network functionality. Specify the number of players and the distance for rollback checks. ```shell cargo run --example ex_game_synctest -- --num-players 2 --check-distance 7 ``` -------------------------------- ### Launch Three-Player Game (P2P) Source: https://github.com/gschup/ggrs/blob/main/examples/README.md Run these commands in separate terminals to launch a three-player game where two players share a client. The first command starts a client with two local player instances and a connection to a remote player. The second command starts the remote player's client. ```shell cargo run --example ex_game_p2p -- --local-port 7000 --players localhost localhost 127.0.0.1:7001 cargo run --example ex_game_p2p -- --local-port 7001 --players 127.0.0.1:7000 127.0.0.1:7000 localhost ``` -------------------------------- ### Main Game Loop Example - GGRS Source: https://github.com/gschup/ggrs/wiki/3.-Main-Loop This example demonstrates a fixed-timestep game loop structure. It manages time accumulation, polls remote clients, processes GGRS events, and advances frames based on a calculated delta time. It also handles frame skipping due to prediction thresholds and renders the game state. ```rust // time variables for tick rate let mut last_update = Instant::now(); let mut accumulator = Duration::ZERO; loop { // communicate, receive and send packets sess.poll_remote_clients(); // print GGRS events for event in sess.events() { println!("Event: {:?}", event); } // this is to keep ticks between clients synchronized. // if a client is ahead, it will run frames slightly slower to allow catching up let mut fps_delta = 1. / FPS; if sess.frames_ahead() > 0 { fps_delta *= 1.1; } // get delta time from last iteration and accumulate it let delta = Instant::now().duration_since(last_update); accumulator = accumulator.saturating_add(delta); last_update = Instant::now(); // if enough time is accumulated, we run a frame while accumulator.as_secs_f64() > fps_delta { // decrease accumulator accumulator = accumulator.saturating_sub(Duration::from_secs_f64(fps_delta)); // frames are only happening if the sessions are synchronized if sess.current_state() == SessionState::Running { // add input for all local players for handle in sess.local_player_handles() { sess.add_local_input(handle, game.local_input())?; } match sess.advance_frame() { Ok(requests) => game.handle_requests(requests), Err(GGRSError::PredictionThreshold) => { println!("Frame {} skipped", sess.current_frame()) } Err(e) => return Err(Box::new(e)), } } } // render the game state game.render(); } ``` -------------------------------- ### Enable Sparse Saving with SessionBuilder Source: https://github.com/gschup/ggrs/blob/main/docs/sparse-saving.md Configure sparse saving by calling `with_sparse_saving_mode(true)` on the `SessionBuilder` before starting a session. This reduces `SaveGameState` requests to at most one per update tick. ```rust let session = SessionBuilder::::new() .with_num_players(2)? .with_sparse_saving_mode(true) // ... other options .start_p2p_session(socket)?; ``` -------------------------------- ### Enable Sparse Saving Mode in GGRS Source: https://github.com/gschup/ggrs/wiki/6.-Sparse-Saving Call this method before starting the session to enable sparse saving. No other code changes are required. ```rust sess.set_sparse_saving(true)?; ``` -------------------------------- ### Adjust Frame Timing with frames_ahead() Source: https://github.com/gschup/ggrs/blob/main/docs/time-synchronization.md Use P2PSession::frames_ahead() to get the frame difference and adjust game speed. This provides smoother gameplay by gradually closing the gap. ```rust let frames_ahead = session.frames_ahead(); let adjustment = if frames_ahead > 0 { // running ahead: slow down by 10% per frame ahead (up to some cap) fps_delta * (1.0 + 0.1 * frames_ahead.min(3) as f64) } else { fps_delta }; time_since_last_frame += last_tick.elapsed(); // advance only when adjusted_delta has passed while time_since_last_frame >= adjustment { time_since_last_frame -= adjustment; // ... advance frame } ``` -------------------------------- ### Build a Spectator Session with SessionBuilder Source: https://github.com/gschup/ggrs/blob/main/docs/sessions.md Creates a SpectatorSession that connects to an existing host. The spectator reproduces the game state without contributing input. ```rust let socket = UdpNonBlockingSocket::bind_to_port(7002) .expect("failed to bind socket"); let host_addr: std::net::SocketAddr = "127.0.0.1:7000".parse()?; let mut session = SessionBuilder::::new() .with_num_players(2)? .start_spectator_session(host_addr, socket); ``` -------------------------------- ### Build a P2P Session with SessionBuilder Source: https://github.com/gschup/ggrs/blob/main/docs/sessions.md Constructs a P2PSession for peer-to-peer multiplayer games. Requires binding a socket and adding local and remote players. ```rust use ggrs::{SessionBuilder, PlayerType, UdpNonBlockingSocket}; let socket = UdpNonBlockingSocket::bind_to_port(7000) .expect("failed to bind socket"); let mut session = SessionBuilder::::new() .with_num_players(2)? .with_fps(60)? .with_input_delay(2) .add_player(PlayerType::Local, 0)? .add_player(PlayerType::Remote("127.0.0.1:7001".parse()? as std::net::SocketAddr), 1)? .start_p2p_session(socket)?; ``` -------------------------------- ### Initialize SessionBuilder Source: https://github.com/gschup/ggrs/wiki/2.-Sessions Use SessionBuilder to construct any session type. The builder allows setting parameters like the number of players, prediction window, FPS, and input delay. ```rust let mut sess_build = SessionBuilder::::new() .with_num_players(NUM_PLAYERS) .with_max_prediction_window(MAX_PREDICTION) .with_fps(FPS)? .with_input_delay(INPUT_DELAY); //... and so forth ``` -------------------------------- ### Build a SyncTest Session with SessionBuilder Source: https://github.com/gschup/ggrs/blob/main/docs/sessions.md Initializes a SyncTestSession for local determinism testing. It simulates rollbacks and re-runs frames to compare checksums. ```rust let mut session = SessionBuilder::::new() .with_num_players(2)? .with_check_distance(7) .start_synctest_session()?; ``` -------------------------------- ### Add Multiple Local Players Source: https://github.com/gschup/ggrs/blob/main/docs/sessions.md Configures a P2P session to support multiple local players on the same machine by adding multiple PlayerType::Local entries with distinct handles. ```rust let session = SessionBuilder::::new() .with_num_players(3)? .add_player(PlayerType::Local, 0)? // local player 1 .add_player(PlayerType::Local, 1)? // local player 2 (same machine) .add_player(PlayerType::Remote(remote_addr), 2)? .start_p2p_session(socket?); ``` -------------------------------- ### Launch Two-Player Game (P2P) Source: https://github.com/gschup/ggrs/blob/main/examples/README.md Run this command in a terminal to launch a two-player peer-to-peer game. Ensure you run the corresponding command in another terminal for the second player. ```shell cargo run --example ex_game_p2p -- --local-port 7000 --players localhost 127.0.0.1:7001 cargo run --example ex_game_p2p -- --local-port 7001 --players 127.0.0.1:7000 localhost ``` -------------------------------- ### Handling Events Source: https://github.com/gschup/ggrs/wiki/4.-Requests-and-Events Events are notifications from the session for the user. It is recommended to fetch events after every update. Most events are simply of informative nature, requiring no special action from the user. ```APIDOC ## Handling Events Events are notifications from the session for the user. It is recommended to fetch events after every update. Most events are simply of informative nature, requiring no special action from the user. Please see the examples or refer to the documentation what kind of `GGRSEvent` can occur. One exception to this is the `WaitRecommendation` event, which GGRS gives out when your local clients runs too far ahead of remote clients, leading to a lot of one-sided rollbacks on your end. A simple way to mitigate this discrepancy by skipping the indicated amount of frames. More elaborate means to synchronize the clients are described in the associated section. ``` -------------------------------- ### Define GGRS Configuration Source: https://github.com/gschup/ggrs/wiki/2.-Sessions Construct a struct that defines the input, state, and address types for GGRS. Ensure types implement necessary traits like Clone, PartialEq, and bytemuck::Pod. ```rust pub struct GGRSConfig; impl Config for GGRSConfig { type Input = MyInput; // Copy + Clone + PartialEq + bytemuck::Pod + bytemuck::Zeroable type State = MyState; // Clone type Address = SocketAddr; // Clone + PartialEq + Eq + Hash } ``` -------------------------------- ### Load Game State Request Source: https://github.com/gschup/ggrs/blob/main/docs/requests-and-events.md Handle the `LoadGameState` request by restoring a previously saved state using `cell.load()`. This requires the game state to be `Clone`. ```rust GgrsRequest::LoadGameState { cell, frame } => { let state = cell.load().expect("no game state found"); game.restore_state(state); } ``` -------------------------------- ### Process WaitRecommendation Events Source: https://github.com/gschup/ggrs/wiki/5.-Time-Synchronization Iterate through session events to capture WaitRecommendation events and accumulate frames to skip. This is part of the event-based synchronization strategy. ```rust for event in sess.events() { if let GGRSEvent::WaitRecommendation { skip_frames } = event { frames_to_skip += skip_frames } println!("Event: {:?}", event); } ``` -------------------------------- ### Add GGRS Dependency Source: https://github.com/gschup/ggrs/wiki/1.-Setup Add GGRS to your project's Cargo.toml file to include it as a dependency. ```toml [dependencies] ggrs = "0.9" ``` -------------------------------- ### Handle GGRS Requests Source: https://github.com/gschup/ggrs/blob/main/docs/requests-and-events.md Iterate through requests returned by `advance_frame` and handle each type appropriately. Requests must be processed in order. ```rust for request in session.advance_frame()? { match request { GgrsRequest::SaveGameState { cell, frame } => { /* ... */ } GgrsRequest::LoadGameState { cell, frame } => { /* ... */ } GgrsRequest::AdvanceFrame { inputs } => { /* ... */ } } } ``` -------------------------------- ### Retrieve Network Statistics Source: https://github.com/gschup/ggrs/blob/main/docs/main-loop.md Fetch network statistics using `network_stats()`. Handle the `NotEnoughData` error gracefully, as it indicates the rolling one-second window is not yet full. This is distinct from `NotSynchronized`. ```rust match session.network_stats(player_handle) { Ok(stats) => { /* display ping, bandwidth, etc. */ } Err(GgrsError::NotEnoughData) => { /* still warming up — try again next tick */ } Err(e) => return Err(e), } ``` -------------------------------- ### Adjust Frame Rate Based on Frames Ahead Source: https://github.com/gschup/ggrs/wiki/5.-Time-Synchronization Manually control the game's frame rate by checking session.frames_ahead(). If the local client is ahead (positive value), slow down the game loop slightly to allow remote clients to catch up. ```rust let mut fps_delta = 1. / self.fps as f64; if self.run_slow { fps_delta *= 1.1; } ... self.run_slow = session.frames_ahead() > 0; ``` -------------------------------- ### Handle GGRS Events Source: https://github.com/gschup/ggrs/blob/main/docs/main-loop.md Iterate through the session's events after polling. Handle informational events and react to `WaitRecommendation` by skipping frames if your client is running ahead. ```rust for event in session.events() { match event { GgrsEvent::Synchronizing { addr, total, count } => { /* show progress */ } GgrsEvent::Synchronized { addr } => { /* peer connected */ } GgrsEvent::Disconnected { addr } => { /* handle disconnect */ } GgrsEvent::NetworkInterrupted { addr, disconnect_timeout } => { /* warn user */ } GgrsEvent::NetworkResumed { addr } => { /* connection restored */ } GgrsEvent::WaitRecommendation { skip_frames } => { // your client is running ahead; skip this many frames frames_to_skip += skip_frames; } GgrsEvent::DesyncDetected { frame, local_checksum, remote_checksum, addr } => { // checksums diverged — your game has a determinism bug } } } ``` -------------------------------- ### Implement GGRS Config Trait Source: https://github.com/gschup/ggrs/blob/main/docs/setup.md Implement the ggrs::Config trait to define your game's input, state, and address types. The Input type must implement Default for handling disconnected players. ```rust use serde::{Deserialize, Serialize}; #[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize)] pub struct Input { pub up: bool, pub down: bool, pub left: bool, pub right: bool, } pub struct GameState { // your game state here } pub struct GgrsConfig; impl ggrs::Config for GgrsConfig { type Input = Input; // transmitted over the network each frame type InputPredictor = ggrs::PredictRepeatLast; // how to predict missing remote inputs type State = GameState; // saved/loaded during rollbacks type Address = std::net::SocketAddr; } ``` -------------------------------- ### Advance Frame Request Source: https://github.com/gschup/ggrs/blob/main/docs/requests-and-events.md Process the `AdvanceFrame` request by applying inputs for each player and then stepping the game. Handle `Confirmed`, `Predicted`, and `Disconnected` input statuses. ```rust GgrsRequest::AdvanceFrame { inputs } => { // inputs is Vec<(T::Input, InputStatus)> // one entry per player, in handle order (0, 1, 2, ...) for (player_handle, (input, status)) in inputs.iter().enumerate() { match status { InputStatus::Confirmed => { /* actual received input */ } InputStatus::Predicted => { /* predicted by your InputPredictor — may be rolled back */ } InputStatus::Disconnected => { /* player disconnected; input is T::Input::default() */ } } game.apply_input(player_handle, input); } game.step(); } ``` -------------------------------- ### Time Accumulation and Frame Advancement Source: https://github.com/gschup/ggrs/blob/main/docs/main-loop.md Implement a fixed-timestep accumulator to manage frame timing. Accumulate delta time, add local input, and advance the frame, handling any resulting requests or errors like `PredictionThreshold`. ```rust let fps_delta = Duration::from_secs_f64(1.0 / TARGET_FPS); time_since_last_frame += last_tick.elapsed(); last_tick = Instant::now(); while time_since_last_frame >= fps_delta { time_since_last_frame -= fps_delta; // add input for each local player handle session.add_local_input(local_handle, current_input)?; match session.advance_frame() { Ok(requests) => { for request in requests { handle_ggrs_request(request); } } Err(GgrsError::PredictionThreshold) => { // remote peer is too far behind; skip this frame } Err(e) => return Err(e), } } ``` -------------------------------- ### Skip Frames Based on WaitRecommendation Source: https://github.com/gschup/ggrs/wiki/5.-Time-Synchronization In the game loop, decrement and skip frames if frames_to_skip is greater than zero, as indicated by a WaitRecommendation event. This helps synchronize clients by pausing execution. ```rust if frames_to_skip > 0 { frames_to_skip -= 1; println!("Frame {} skipped: WaitRecommendation", game.current_frame()); continue; } ``` -------------------------------- ### Handle WaitRecommendation Event in GGRS Source: https://github.com/gschup/ggrs/blob/main/docs/time-synchronization.md When GGRS detects a client is consistently ahead, it fires a WaitRecommendation event. Handle this by accumulating frames to skip. ```rust GgrsEvent::WaitRecommendation { skip_frames } => { frames_to_skip += skip_frames; } ``` -------------------------------- ### Check Session State Source: https://github.com/gschup/ggrs/blob/main/docs/sessions.md Verifies if the GGRS session is in the 'Running' state, indicating it's ready to accept inputs and advance frames. ```rust if session.current_state() == ggrs::SessionState::Running { // safe to advance frames } ``` -------------------------------- ### Add GGRS Dependency to Cargo.toml Source: https://github.com/gschup/ggrs/blob/main/docs/setup.md Add the GGRS crate to your project's dependencies in Cargo.toml. Ensure you specify the desired version. ```toml [dependencies] ggrs = "0.11" ``` -------------------------------- ### AdvanceFrame Request in GGRS Source: https://github.com/gschup/ggrs/wiki/4.-Requests-and-Events Handle AdvanceFrame requests by advancing the game state with the provided inputs. Disconnected players are indicated by NULL_FRAME. ```rust GGRSRequest::AdvanceFrame { /// Contains inputs and input status for each player. inputs: Vec<(T::Input, InputStatus)>, } ``` -------------------------------- ### Handle Synchronization and Prediction Threshold Errors Source: https://github.com/gschup/ggrs/blob/main/docs/main-loop.md When advancing frames during synchronization, treat `NotSynchronized` and `PredictionThreshold` errors as non-fatal skips. This prevents the game from halting if the session is not yet ready or if a peer is too far behind. ```rust match session.advance_frame() { Ok(requests) => { /* handle requests */ } Err(GgrsError::NotSynchronized) | Err(GgrsError::PredictionThreshold) => { // not ready or too far ahead — skip this tick } Err(e) => return Err(e), } ``` -------------------------------- ### Poll Remote Clients - GGRS Source: https://github.com/gschup/ggrs/wiki/3.-Main-Loop Call this function repeatedly to receive and handle incoming UDP packets and send queued packets to other remote clients. Frequent polling leads to timely communication between sessions. Use spare time between rendering and updating your game to poll remote clients. ```rust sess.poll_remote_clients(); ``` -------------------------------- ### Save Game State Request Source: https://github.com/gschup/ggrs/blob/main/docs/requests-and-events.md Implement the `SaveGameState` request by serializing the current game state and saving it using `cell.save()`. An optional checksum can be provided for desync detection. ```rust GgrsRequest::SaveGameState { cell, frame } => { assert_eq!(cell.frame(), frame); // sanity check let state = game.serialize_state(); let checksum = game.compute_checksum(); // optional, used for desync detection cell.save(frame, Some(state), Some(checksum)); } ``` -------------------------------- ### LoadGameState Request in GGRS Source: https://github.com/gschup/ggrs/wiki/4.-Requests-and-Events Load the game state using the provided GameStateCell and frame. The frame serves as a sanity check for the loaded state. ```rust GGRSRequest::LoadGameState { /// Use `cell.load()` to load your state. cell: GameStateCell, /// The given `frame` is a sanity check: The gamestate you load is from that frame. frame: Frame, }, ``` -------------------------------- ### GGRSRequest::AdvanceFrame Source: https://github.com/gschup/ggrs/wiki/4.-Requests-and-Events Advance the frame with the provided inputs. Disconnected players are indicated by having NULL_FRAME instead of the correct current frame in their input. ```APIDOC ## GGRSRequest::AdvanceFrame ### Description Advance the frame with the provided inputs. Disconnected players are indicated by having `NULL_FRAME` instead of the correct current frame in their input. ### Parameters #### Request Body - **inputs** (Vec<(T::Input, InputStatus)>) - Required - Contains inputs and input status for each player. ``` -------------------------------- ### Poll Remote Clients Source: https://github.com/gschup/ggrs/blob/main/docs/main-loop.md Call this function every iteration of your game loop to receive incoming UDP packets and dispatch outgoing ones. It should be called regardless of whether a frame is advanced. ```rust session.poll_remote_clients(); ``` -------------------------------- ### GGRSRequest::LoadGameState Source: https://github.com/gschup/ggrs/wiki/4.-Requests-and-Events Load the gamestate in the provided cell. The given 'frame' is a sanity check: The gamestate you load should be from that frame. ```APIDOC ## GGRSRequest::LoadGameState ### Description Load the gamestate in the provided cell. The given 'frame' is a sanity check: The gamestate you load should be from that frame. ### Parameters #### Request Body - **cell** (GameStateCell) - Required - Use `cell.load()` to load your state. - **frame** (Frame) - Required - The given `frame` is a sanity check: The gamestate you load is from that frame. ``` -------------------------------- ### SaveGameState Request in GGRS Source: https://github.com/gschup/ggrs/wiki/4.-Requests-and-Events Save the current game state using the provided GameStateCell and frame. The frame is a sanity check for the state being saved. ```rust GGRSRequest::SaveGameState { /// Use `cell.save(...)` to save your state. cell: GameStateCell, /// The given `frame` is a sanity check: The gamestate you save should be from that frame. frame: Frame, } ``` -------------------------------- ### Skip Frames in Game Loop Source: https://github.com/gschup/ggrs/blob/main/docs/time-synchronization.md In your game loop, decrement and skip frame advances when frames_to_skip is greater than zero. This can cause visible stutter. ```rust if frames_to_skip > 0 { frames_to_skip -= 1; // don't call advance_frame this tick continue; } ``` -------------------------------- ### Process GGRS Events Source: https://github.com/gschup/ggrs/blob/main/docs/requests-and-events.md Retrieve and process events from the GGRS session after polling remote clients. Ensure the event queue is drained to prevent older events from being dropped. ```rust for event in session.events() { match event { /* ... */ } } ``` -------------------------------- ### GGRSRequest::SaveGameState Source: https://github.com/gschup/ggrs/wiki/4.-Requests-and-Events Save the current gamestate in the provided cell. The given `frame` is a sanity check: The gamestate you save should be from that frame. ```APIDOC ## GGRSRequest::SaveGameState ### Description Save the current gamestate in the provided cell. The given `frame` is a sanity check: The gamestate you save should be from that frame. ### Parameters #### Request Body - **cell** (GameStateCell) - Required - Use `cell.save(...)` to save your state. - **frame** (Frame) - Required - The given `frame` is a sanity check: The gamestate you save should be from that frame. ``` === COMPLETE CONTENT === This response contains all available snippets from this library. No additional content exists. Do not make further requests.