Some checks failed
Rust / build-linux (linux-x86_64, X64, ubuntu-24.04, x86_64-unknown-linux-gnu) (pull_request) Failing after 5s
Rust / build-android (29.0.14206865, ubuntu-24.04, 33) (pull_request) Failing after 2s
Rust-lint / lint-linux (pull_request) Failing after 2s
Rust / build-linux (linux-aarch64, ARM64, ubuntu-24.04-arm, aarch64-unknown-linux-gnu) (pull_request) Has been cancelled
Rust / build-windows (windows-aarch64, woa64, windows-11-arm, aarch64-pc-windows-msvc) (pull_request) Has been cancelled
Rust / build-windows (windows-x86_64, win64, windows-2025, x86_64-pc-windows-msvc) (pull_request) Has been cancelled
Rust / build-macos (macos-aarch64, ARM64, aarch64-apple-darwin) (pull_request) Has been cancelled
Rust / Collect release artifacts (pull_request) Has been cancelled
After a netplay savestate load, the GGRS session's internal frame counter
must be reset to match the savestate's frame, or desync detection fires
immediately on resume.
Changes:
- Patch vendored ggrs 0.13.0 to expose set_current_frame(frame) on
P2PSession and SyncTestSession, with internal SyncLayer and InputQueue
reset methods to clear stale saved states, checksum history, and input
queues
- Add [patch.crates-io] ggrs = { path = 'vendor/ggrs' } in Cargo.toml
- Add netplay::reset_frame(session, frame) wrapper function
- Record current GGRS frame in Savestate.netplay_frame during
create_savestate when in netplay context
- Call reset_frame after load_savestate in netplay context
- Add tests: advance 120 frames, reset to 60, verify current_frame()==60
and clean resume with matching inputs; reset to 0; idempotent reset
Closes #5
101 lines
2.6 KiB
Rust
101 lines
2.6 KiB
Rust
use std::collections::hash_map::DefaultHasher;
|
|
use std::hash::{Hash, Hasher};
|
|
use std::net::{IpAddr, Ipv4Addr, SocketAddr};
|
|
|
|
use ggrs::{Config, Frame, GameStateCell, GgrsRequest, InputStatus, PredictRepeatLast};
|
|
use serde::{Deserialize, Serialize};
|
|
|
|
fn calculate_hash<T: Hash>(t: &T) -> u64 {
|
|
let mut s = DefaultHasher::new();
|
|
t.hash(&mut s);
|
|
s.finish()
|
|
}
|
|
|
|
pub struct GameStubEnum {
|
|
pub gs: StateStubEnum,
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
#[repr(u8)]
|
|
#[derive(Copy, Clone, PartialEq, Default, Serialize, Deserialize)]
|
|
pub enum EnumInput {
|
|
#[default]
|
|
Val1,
|
|
Val2,
|
|
}
|
|
|
|
pub struct StubEnumConfig;
|
|
|
|
impl Config for StubEnumConfig {
|
|
type Input = EnumInput;
|
|
type InputPredictor = PredictRepeatLast;
|
|
type State = StateStubEnum;
|
|
type Address = SocketAddr;
|
|
}
|
|
|
|
impl Default for GameStubEnum {
|
|
fn default() -> Self {
|
|
Self::new()
|
|
}
|
|
}
|
|
|
|
impl GameStubEnum {
|
|
#[allow(dead_code)]
|
|
pub fn new() -> GameStubEnum {
|
|
GameStubEnum {
|
|
gs: StateStubEnum { frame: 0, state: 0 },
|
|
}
|
|
}
|
|
|
|
#[allow(dead_code)]
|
|
pub fn handle_requests(&mut self, requests: Vec<GgrsRequest<StubEnumConfig>>) {
|
|
for request in requests {
|
|
match request {
|
|
GgrsRequest::LoadGameState { cell, .. } => self.load_game_state(cell),
|
|
GgrsRequest::SaveGameState { cell, frame } => self.save_game_state(cell, frame),
|
|
GgrsRequest::AdvanceFrame { inputs } => self.advance_frame(inputs),
|
|
}
|
|
}
|
|
}
|
|
|
|
fn save_game_state(&mut self, cell: GameStateCell<StateStubEnum>, frame: Frame) {
|
|
assert_eq!(self.gs.frame, frame);
|
|
let checksum = calculate_hash(&self.gs);
|
|
cell.save(frame, Some(self.gs), Some(checksum as u128));
|
|
}
|
|
|
|
fn load_game_state(&mut self, cell: GameStateCell<StateStubEnum>) {
|
|
self.gs = cell.load().unwrap();
|
|
}
|
|
|
|
fn advance_frame(&mut self, inputs: Vec<(EnumInput, InputStatus)>) {
|
|
self.gs.advance_frame(inputs);
|
|
}
|
|
}
|
|
|
|
#[derive(Default, Copy, Clone, Hash)]
|
|
pub struct StateStubEnum {
|
|
pub frame: i32,
|
|
pub state: i32,
|
|
}
|
|
|
|
/// Shorthand for a loopback `SocketAddr` on the given port.
|
|
#[allow(dead_code)]
|
|
pub fn localhost(port: u16) -> SocketAddr {
|
|
SocketAddr::new(IpAddr::V4(Ipv4Addr::new(127, 0, 0, 1)), port)
|
|
}
|
|
|
|
impl StateStubEnum {
|
|
fn advance_frame(&mut self, inputs: Vec<(EnumInput, InputStatus)>) {
|
|
let p0_inputs = inputs[0];
|
|
let p1_inputs = inputs[1];
|
|
|
|
if p0_inputs == p1_inputs {
|
|
self.state += 2;
|
|
} else {
|
|
self.state -= 1;
|
|
}
|
|
self.frame += 1;
|
|
}
|
|
}
|