Desync auto-resync: host sends state on desync event (#6) #10
No reviewers
Labels
No Label
blocked
bounced
in-progress
in-review
manual-only
needs-guidance
netplay
p1
p2
p3
rust
No Milestone
No project
No Assignees
1 Participants
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: pbwraith/gopher64-netplay#10
Loading…
Reference in New Issue
Block a user
No description provided.
Delete Branch "issue-6"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Desync auto-resync — host sends state on desync event (#6)
Problem
When GGRS fires
DesyncDetected, the current handler only prints a message and shows an onscreen notification. There is no automatic recovery — the session continues in a desynced state.Solution
When a desync is detected, the host (player_number==0) snapshots the current emulator state and ships it to the desynced peer(s) via the reliable channel. Both sides then reset their GGRS frame counters (using #5's
reset_frame) and resume.Implementation
ResyncState enum added to
Netplaystruct:Idle— no resync in progress (default)HostWaiting { state_data, state_frame, started_at, acked }— host has sent state, waiting for peer acksClientLoading { started_at }— non-host received state, loading itDesyncDetected handler (
handle_desync):create_savestate_in_memory, sendsresync_statemessage to all peers, entersHostWaitingstateMessage handling (
check_resync_messages):resync_state, entersClientLoadingresync_readyacks, marks peers as acked; when all acked, callsreset_frameand resumesState loading (
process_resync_load): non-host decompresses and loads the received state into the device (same swap pattern assavestates::load_savestate), callsreset_frame, sendsresync_readyack, resumes. Called fromvi.rsupdate_screenbefore normal frame processing.Frame loop pause (
advance_frame): during resync, the frame loop pauses (pushes default AdvanceFrame with Disconnected inputs, like the existing disconnected path). 5-second timeout drops to disconnect behavior.In-memory savestate (
create_savestate_in_memory): serializes Device via postcard + compress, without file I/O. Suitable for sending over the reliable channel in 16KB chunks.Dependencies
issue-5and usesreset_frameand the patched ggrsset_current_frame. The PR includes #5's changes.NetplayMessage { name, data }struct with string names "resync_state" and "resync_ready". No new message type enum needed.create_savestate_in_memoryimplements a simplified version that serializes directly from the Device without the static clone buffer.Tests
7 new tests:
test_resync_state_default_is_idle— verifies Default traittest_resync_state_host_waiting— verifies HostWaiting fieldstest_resync_state_client_loading— verifies ClientLoading fieldstest_resync_message_serialization— resync_state message round-triptest_resync_ready_message_serialization— resync_ready message round-triptest_resync_timeout— 5s timeout logic for HostWaitingtest_resync_all_acked— ack tracking logicNote: Full P2P flow tests require networking and cannot be run in this environment. The tests verify the state machine logic, message serialization, and timeout/ack tracking.
Build note
The full gopher64 project cannot be compiled in this WSL environment due to missing SDL3/X11/Wayland development libraries. The ggrs crate tests (from #5) all pass.
Closes #6
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 #5Not merged this sweep, and not bounced — this PR has not been found faulty.
Two things block a verdict, both recorded in gopher64-netplay#16:
cargo checkfails on aclean
origin/mainbefore compiling any of this repo's code — the CPU ispre-AVX2 and
.cargo/config.tomlforcestarget-cpu=x86-64-v3, whichmiscompiles dependency build scripts; past that,
CC = "clang"is notinstalled. So I cannot honestly verify this PR, and this sweep does not merge
what it cannot verify.
src/netplay.rs. Measured merge order and the conflicting file sets are in#16; the short version is that
issue-1->issue-2->issue-8landcleanly and
issue-3plus theissue-5/6/7stack then need a real rebase.No action needed from you until #16 is resolved. Deliberately leaving this
unlabelled:
bouncedwould put it in the rework queue, and there is nothinghere to rework yet.
Parked on #23 -- and this PR is subsumed by !11
When #23 lands, rework !11 (which already contains this branch's commit
48598f4) and close this one as subsumed rather than repairing both.Two things to fix in ONE pass, not two
1. You now conflict with
main. !14 (issue-1, state-message types)merged a few minutes ago. Re-measured against the new
main(c5609be) witha real
git mergein a throwaway worktree -- not themergeableflag, whichstill reads
true:Both branches merged cleanly before !14 landed, so this is a fresh, legitimate
rebase need and not something you got wrong.
2. A rebase alone will not make the gate green.
cargo test --lib(gatestep 3) currently aborts the entire test binary with a stack overflow on any
test that constructs a
Device-- measured on !15 and !18, root-caused andfiled as #23. Your branches touch
src/device.rsandsrc/savestates.rs,so this very likely reaches you too.
I did not gate !10/!11 -- I stopped once the conflict was established,
because a gate against a tree I would have had to hand-resolve proves nothing
about your branch. So treat point 2 as "expected, unverified for these two",
not as a measured failure of your code.
Waiting for #23 before reworking means one rebase against a
mainthat canactually run tests, instead of a rebase now and another after #23 lands.
Relationship between these two PRs
git merge-base --is-ancestor origin/issue-6 origin/issue-7-> true.!11 already contains !10's commit
48598f4. Rework !11 and close !10 assubsumed; do not repair both.
Closing as subsumed, now that the thing this was parked on has landed.
#23is CLOSED --!24merged this sweep, so the step-3 stack-overflow abortthat parked this whole cluster is gone. Re-verified the containment claim from
the earlier review rather than trusting the comment:
So
!11already carries every commit onissue-6; your desync auto-resync host half (#6) work is notbeing dropped, it ships there. Repairing both branches would mean resolving the
same
src/netplay.rsconflict twice and then reconciling two rebases of thesame commits.
The branch
issue-6is untouched -- I have not deleted it, because deleting aGitea PR source branch auto-closes the PR and recreating the branch does not
reopen it. If the containment ever needs re-checking, the branch is still there.
Follow the work on
!11, which I have bounced with the specific rebaseit needs.