ggrs

repository·main·Indexed 20 days ago

https://github.com/gschup/ggrs

A high-performance, 100% safe Rust implementation of a P2P rollback networking system inspired by GGPO. Version 0.13.0 features a request-based API instead of callbacks, providing a predictable control flow for game engines. It supports P2P and spectator modes, includes a SyncTestSession for simulating rollbacks without network latency, and offers integrations for the Bevy engine, WASM/WebRTC via Matchbox, and Godot.

Tokens
17.7K
Snippets
45
Records
76
Agent score
69%

What's inside ggrs

  1. What is Sparse Saving and when should I use it?

    main

    By default, GGRS saves the game state on every frame advance to ensure a nearby save point is available for any rollback within the max_prediction_window.

    Sparse Saving changes this behavior so that GGRS only saves at the last confirmed frame (the most recent frame where all clients have provided real, non-predicted inputs).

    Trade-offs

    • Pros: Significantly fewer SaveGameState requests (at most one per update tick instead of one per frame). This is ideal if saving state is computationally expensive or involves large buffers.
    • Cons: Potentially longer rollbacks. If a misprediction occurs, GGRS must re-simulate from the last confirmed save point rather than a more recent frame.

    Recommendation: Only use sparse saving if your state save is expensive. If your state save is cheap and fast, the default behavior is likely more efficient as it reduces rollback re-simulation costs.

  2. What is GGRS and how does its API work?

    main

    GGRS (good game rollback system) is a P2P rollback networking library written in 100% safe Rust. It is a reimagination of the GGPO network SDK.

    Unlike the original GGPO library which uses a callback-style API, GGRS uses a request-based control flow. Instead of registering callback functions, GGRS returns a list of requests that the user must fulfill within their game loop. This provides a simpler and more predictable control flow for integrating rollback networking into a game engine.

  3. SyncTestSession: Testing determinism

    main
    A local-only session used to verify determinism during development. It does not use networking. On every frame, GGRS simulates a rollback and re-runs the last n frames (defined by the check distance) to compare checksums. Use this to ensure your save/load/advance logic is correct.
  4. Understand GGRS requirements for NAT traversal and determinism

    main

    NAT Traversal

    GGRS does not handle NAT traversal or provide a signaling server. It assumes you already possess the socket addresses of every client you wish to connect to. You must exchange these addresses before creating a session. For WebRTC/browser networking, use Matchbox.

    Determinism

    GGRS requires your game to be strictly deterministic: the same game state and inputs must always produce the same next state across all clients and CPU architectures.

    Common pitfalls to avoid:

    • Floating-point arithmetic: Results can vary between architectures (e.g., x86 vs ARM) or build profiles. Use fixed-point or integer arithmetic instead.
    • HashMap/HashSet iteration: Rust's default iteration order is non-deterministic. Use BTreeMap or ensure order does not affect logic.
    • Random number generators: Do not use system entropy. Seed your RNG from the game state so it advances deterministically.
  5. Choose an Input Prediction strategy

    main

    When remote inputs are delayed, GGRS uses an InputPredictor (defined in your Config implementation) to guess the player's input.

    GGRS provides two built-in predictors:

    • ggrs::PredictRepeatLast: Predicts that the player will repeat their last known input. Best for action games where inputs represent held states (e.g., holding a button).
    • ggrs::PredictDefault: Always predicts Input::default(). Best for transition-based inputs where inputs are one-off events (e.g., a single frame button press).

    You can also implement the InputPredictor trait manually to create custom logic, such as input quantization.

  6. Synchronize time with WaitRecommendation and frames_ahead()

    main

    To prevent one-sided rollbacks caused by running ahead of remote peers, use one of these two methods:

    1. WaitRecommendation event: GGRS fires this when you are consistently ahead by 3+ frames for 60 consecutive frames. When received, skip the recommended number of frames.
    2. frames_ahead(): Returns a signed integer representing how many frames ahead (positive) or behind (negative) your session is. You can use this value to adjust your frame delta to drift back into sync without hard skips.
  7. Determinism Warning for Floating-Point Math

    main

    The ExGame example uses floating-point math (including sin, cos, and sqrt). Because floating-point operations can produce different results across different architectures or platforms, this example is expected to desync.

    Note for developers: When implementing your own deterministic game with GGRS, you must account for floating-point imprecisions and non-deterministic results to prevent desyncs.

  8. P2PSession: Peer-to-peer multiplayer

    main
    The primary session type for multiplayer games. In a P2PSession, every client creates its own instance and connects to others in a peer-to-peer mesh. Each client is responsible for sending its own local inputs. GGRS handles prediction and rollback transparently based on the InputPredictor configured in your Config.
  9. SpectatorSession: Observing a game

    main
    A SpectatorSession connects to an existing host running a P2PSession. The host broadcasts all confirmed inputs to the spectator. This allows the spectator to reproduce the game state locally without contributing any input or affecting the game state.
  10. Handle synchronization phase errors

    main

    During the SessionState::Synchronizing phase, advance_frame() will return Err(GgrsError::NotSynchronized). You can handle this by checking current_state() or by treating the error as a non-fatal skip during your loop.

    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),
    }
  11. Get started with GGRS

    main

    To integrate GGRS into your project, refer to the following resources:

    • Documentation: Detailed guides covering setup, sessions, the main loop, requests/events, and time synchronization can be found in the docs/ directory.
    • Examples: Runnable demonstrations for P2P, spectator mode, and sync-testing are available in the examples/ directory.
    • API Reference: The full technical API documentation is available on docs.rs.