mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-25 18:05:12 -04:00
Integrate retcomm-rbengine as shared rollback host policy.
Wire lib/retcomm-rbengine beside recomp-net and replace MotK in-tree sched, hash_confirm, input hist, snap ring, and rb_post with thin PSX shims so the ecosystem can share invent/pacing policy outside each title facade. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -1,3 +1,9 @@
|
||||
[submodule "lib/recomp-net"]
|
||||
path = lib/recomp-net
|
||||
url = https://github.com/TechnicallyComputers/recomp-net.git
|
||||
branch = main
|
||||
|
||||
[submodule "lib/retcomm-rbengine"]
|
||||
path = lib/retcomm-rbengine
|
||||
url = https://github.com/TechnicallyComputers/retcomm-rbengine.git
|
||||
branch = main
|
||||
|
||||
Submodule
+1
Submodule lib/retcomm-rbengine added at 5434467f39
+54
-36
@@ -278,20 +278,61 @@ if(BUILD_TESTING)
|
||||
add_test(NAME gpu_vk_upload_alignment_test
|
||||
COMMAND gpu_vk_upload_alignment_test)
|
||||
|
||||
add_executable(netplay_hash_confirm_test
|
||||
tests/test_netplay_hash_confirm.c
|
||||
src/netplay_hash_confirm.c)
|
||||
target_include_directories(netplay_hash_confirm_test PRIVATE include)
|
||||
add_test(NAME netplay_hash_confirm_test COMMAND netplay_hash_confirm_test)
|
||||
# Portable rollback helpers (retcomm-rbengine) — required for MotK netplay
|
||||
# unit tests that use the netplay_* compatibility shims.
|
||||
set(_RBE_ROOT "${PSXRECOMP_ROOT}/lib/retcomm-rbengine")
|
||||
set(_RNET_ROOT "${PSXRECOMP_ROOT}/lib/recomp-net")
|
||||
if(EXISTS "${_RBE_ROOT}/CMakeLists.txt" AND EXISTS "${_RNET_ROOT}/CMakeLists.txt")
|
||||
if(NOT TARGET recomp_net)
|
||||
set(RNET_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
|
||||
set(RNET_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(RNET_ENABLE_ICE OFF CACHE BOOL "" FORCE)
|
||||
add_subdirectory("${_RNET_ROOT}" "${CMAKE_BINARY_DIR}/recomp-net-tests")
|
||||
endif()
|
||||
if(NOT TARGET retcomm_rbengine)
|
||||
set(RBE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
set(RECOMP_NET_ROOT "${_RNET_ROOT}" CACHE PATH "" FORCE)
|
||||
add_subdirectory("${_RBE_ROOT}" "${CMAKE_BINARY_DIR}/retcomm-rbengine-tests")
|
||||
endif()
|
||||
set(PSXRECOMP_TEST_HAS_RBENGINE TRUE)
|
||||
else()
|
||||
set(PSXRECOMP_TEST_HAS_RBENGINE FALSE)
|
||||
message(STATUS
|
||||
"netplay_*_tests: lib/retcomm-rbengine or lib/recomp-net absent - skipped")
|
||||
endif()
|
||||
|
||||
# Single TU: the test #includes src/netplay_snap_ring.c and stubs
|
||||
# boot_state, so the .c must NOT also be listed here (that was PR #134's
|
||||
# duplicate-symbol bug, in its documented command).
|
||||
add_executable(netplay_snap_ring_test tests/test_netplay_snap_ring.c)
|
||||
target_include_directories(netplay_snap_ring_test PRIVATE include)
|
||||
target_compile_definitions(netplay_snap_ring_test PRIVATE
|
||||
NETPLAY_SNAP_RING_TEST_STUB_BOOT_STATE)
|
||||
add_test(NAME netplay_snap_ring_test COMMAND netplay_snap_ring_test)
|
||||
if(PSXRECOMP_TEST_HAS_RBENGINE)
|
||||
add_executable(netplay_hash_confirm_test
|
||||
tests/test_netplay_hash_confirm.c)
|
||||
target_include_directories(netplay_hash_confirm_test PRIVATE include)
|
||||
target_link_libraries(netplay_hash_confirm_test PRIVATE retcomm_rbengine)
|
||||
add_test(NAME netplay_hash_confirm_test COMMAND netplay_hash_confirm_test)
|
||||
|
||||
# save/load glue #included from the test; ring ops come from rbengine.
|
||||
add_executable(netplay_snap_ring_test tests/test_netplay_snap_ring.c)
|
||||
target_include_directories(netplay_snap_ring_test PRIVATE include)
|
||||
target_compile_definitions(netplay_snap_ring_test PRIVATE
|
||||
NETPLAY_SNAP_RING_TEST_STUB_BOOT_STATE
|
||||
PSX_HAS_RECOMP_NET=1)
|
||||
target_link_libraries(netplay_snap_ring_test PRIVATE retcomm_rbengine)
|
||||
add_test(NAME netplay_snap_ring_test COMMAND netplay_snap_ring_test)
|
||||
|
||||
add_executable(rb_post_tip_filter_test tests/test_rb_post_tip_filter.c)
|
||||
target_include_directories(rb_post_tip_filter_test PRIVATE include)
|
||||
target_link_libraries(rb_post_tip_filter_test PRIVATE retcomm_rbengine)
|
||||
add_test(NAME rb_post_tip_filter_test COMMAND rb_post_tip_filter_test)
|
||||
|
||||
add_executable(netplay_input_hist_test
|
||||
tests/test_netplay_input_hist.c
|
||||
src/netplay_input_hist.c)
|
||||
target_include_directories(netplay_input_hist_test PRIVATE
|
||||
include
|
||||
${_RNET_ROOT}/include)
|
||||
target_compile_definitions(netplay_input_hist_test PRIVATE
|
||||
PSX_HAS_RECOMP_NET=1)
|
||||
target_link_libraries(netplay_input_hist_test PRIVATE retcomm_rbengine)
|
||||
add_test(NAME netplay_input_hist_test COMMAND netplay_input_hist_test)
|
||||
endif()
|
||||
|
||||
add_executable(overlay_store_cycle_barrier_test
|
||||
tests/test_overlay_store_cycle_barrier.c)
|
||||
@@ -306,10 +347,6 @@ if(BUILD_TESTING)
|
||||
add_test(NAME psx_cycle_event_boundaries_test
|
||||
COMMAND psx_cycle_event_boundaries_test)
|
||||
|
||||
add_executable(rb_post_tip_filter_test tests/test_rb_post_tip_filter.c)
|
||||
target_include_directories(rb_post_tip_filter_test PRIVATE include)
|
||||
add_test(NAME rb_post_tip_filter_test COMMAND rb_post_tip_filter_test)
|
||||
|
||||
add_executable(spu_end_without_repeat_test
|
||||
tests/test_spu_end_without_repeat.c)
|
||||
target_include_directories(spu_end_without_repeat_test PRIVATE include)
|
||||
@@ -361,25 +398,6 @@ if(BUILD_TESTING)
|
||||
message(STATUS "overlay_posix_test: bash not found - skipped")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# recomp-net is an optional submodule; the netplay input-history contract
|
||||
# cannot be compiled without it. Conditional, not skipped silently: the
|
||||
# registration guard is told about it via the same EXISTS check.
|
||||
if(EXISTS "${PSXRECOMP_ROOT}/lib/recomp-net/include/recomp_net/rollback.h")
|
||||
add_executable(netplay_input_hist_test
|
||||
tests/test_netplay_input_hist.c
|
||||
src/netplay_input_hist.c
|
||||
${PSXRECOMP_ROOT}/lib/recomp-net/src/input/rnet_input_contract.c)
|
||||
target_include_directories(netplay_input_hist_test PRIVATE
|
||||
include
|
||||
${PSXRECOMP_ROOT}/lib/recomp-net/include)
|
||||
target_compile_definitions(netplay_input_hist_test PRIVATE
|
||||
PSX_HAS_RECOMP_NET=1)
|
||||
add_test(NAME netplay_input_hist_test COMMAND netplay_input_hist_test)
|
||||
else()
|
||||
message(STATUS
|
||||
"netplay_input_hist_test: lib/recomp-net submodule absent - skipped")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# ---- test-registration completeness guard (must stay LAST) ------------------
|
||||
|
||||
@@ -2,69 +2,29 @@
|
||||
#define PSX_NETPLAY_HASH_CONFIRM_H
|
||||
|
||||
/*
|
||||
* Local digest ring + peer FRAME_COMMIT matching → resolved_through watermark.
|
||||
*
|
||||
* hash_confirm_through(T) is 1 iff every tick in (prev_resolved, T] has a
|
||||
* local digest that matched a peer FRAME_COMMIT (contiguous from the prior
|
||||
* watermark). Used as RNetInputContractHostGates.hash_confirm_promote /
|
||||
* RNetRollbackVTable.hash_confirm_through.
|
||||
* MotK compatibility shim → retcomm-rbengine hash_confirm.
|
||||
* Prefer rbe_hc_* in new code.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "retcomm_rbengine/hash_confirm.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NETPLAY_HC_RING 128u
|
||||
#define NETPLAY_HC_RING RBE_HC_RING
|
||||
typedef RbeHashConfirm NetplayHashConfirm;
|
||||
|
||||
typedef struct NetplayHashConfirm {
|
||||
uint32_t local_tick[NETPLAY_HC_RING];
|
||||
uint32_t local_digest[NETPLAY_HC_RING];
|
||||
uint8_t local_valid[NETPLAY_HC_RING];
|
||||
uint32_t peer_tick[NETPLAY_HC_RING];
|
||||
uint32_t peer_digest[NETPLAY_HC_RING];
|
||||
uint8_t peer_valid[NETPLAY_HC_RING];
|
||||
uint32_t resolved_through; /* inclusive; 0 = none yet (tick 0 may match) */
|
||||
uint8_t resolved_valid; /* 0 until first match advances watermark */
|
||||
} NetplayHashConfirm;
|
||||
|
||||
void netplay_hc_reset(NetplayHashConfirm* hc);
|
||||
|
||||
/* Clear the ring and set resolved_through = last_ok so the next compared tick
|
||||
* is last_ok+1. Used at Replay entry to drop live invent FRAME_COMMITs that
|
||||
* would false-trigger mid-resim diverge aborts. */
|
||||
void netplay_hc_prime_after(NetplayHashConfirm* hc, uint32_t last_ok);
|
||||
|
||||
/* Record our digest after sim tick T completes. */
|
||||
void netplay_hc_note_local(NetplayHashConfirm* hc, uint32_t tick, uint32_t digest);
|
||||
|
||||
/* Record peer RB_FRAME_COMMIT (through_tick, state_hash). Advances watermark
|
||||
* when it matches our local digest for that tick. */
|
||||
void netplay_hc_note_peer(NetplayHashConfirm* hc, uint32_t tick, uint32_t digest);
|
||||
|
||||
uint32_t netplay_hc_resolved_through(const NetplayHashConfirm* hc);
|
||||
uint8_t netplay_hc_confirm_through(const NetplayHashConfirm* hc, uint32_t tick);
|
||||
|
||||
/* Peek local digest for tick; returns 0 if missing. */
|
||||
uint8_t netplay_hc_local_digest(const NetplayHashConfirm* hc, uint32_t tick,
|
||||
uint32_t* digest_out);
|
||||
|
||||
/* Peek peer FRAME_COMMIT digest for tick; returns 0 if missing. */
|
||||
uint8_t netplay_hc_peer_digest(const NetplayHashConfirm* hc, uint32_t tick,
|
||||
uint32_t* digest_out);
|
||||
|
||||
/* 1 if the next tick after resolved_through has both digests but they differ.
|
||||
* Fills tick/local/peer when non-NULL. Used to log first live core fork. */
|
||||
uint8_t netplay_hc_peek_mismatch(const NetplayHashConfirm* hc, uint32_t* tick_out,
|
||||
uint32_t* local_out, uint32_t* peer_out);
|
||||
|
||||
/* Heal a stuck watermark when the next tick after resolved_through has aged
|
||||
* out of the ring (slot reused) and is no longer a live mismatch. Scans the
|
||||
* ring for the highest tick where local==peer with no newer mismatch present,
|
||||
* then advances resolved_through there (ring contents kept).
|
||||
* Returns 1 if the watermark moved. Call from FRAME_COMMIT ingress. */
|
||||
uint8_t netplay_hc_heal_stale_gap(NetplayHashConfirm* hc);
|
||||
#define netplay_hc_reset rbe_hc_reset
|
||||
#define netplay_hc_prime_after rbe_hc_prime_after
|
||||
#define netplay_hc_note_local rbe_hc_note_local
|
||||
#define netplay_hc_note_peer rbe_hc_note_peer
|
||||
#define netplay_hc_resolved_through rbe_hc_resolved_through
|
||||
#define netplay_hc_confirm_through rbe_hc_confirm_through
|
||||
#define netplay_hc_local_digest rbe_hc_local_digest
|
||||
#define netplay_hc_peer_digest rbe_hc_peer_digest
|
||||
#define netplay_hc_peek_mismatch rbe_hc_peek_mismatch
|
||||
#define netplay_hc_heal_stale_gap rbe_hc_heal_stale_gap
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -2,60 +2,37 @@
|
||||
#define PSX_NETPLAY_INPUT_HIST_H
|
||||
|
||||
/*
|
||||
* Per-slot tick → RNetRbFrame history for MotK rollback invent/contract.
|
||||
*
|
||||
* Local authority rows: is_predicted = 0.
|
||||
* Invent (hold-last) remotes: is_predicted = 1.
|
||||
* Late wire promote: replace the row in place (clears predicted).
|
||||
*
|
||||
* Only available when PSX_HAS_RECOMP_NET is defined (linked recomp-net).
|
||||
* MotK input history: portable ring/invent in retcomm-rbengine; PSX pad
|
||||
* conversion stays in netplay_input_hist.c.
|
||||
*/
|
||||
|
||||
#if defined(PSX_HAS_RECOMP_NET)
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include "recomp_net/rollback.h"
|
||||
#include "psx_netplay.h"
|
||||
#include "retcomm_rbengine/input_hist.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define NETPLAY_INPUT_HIST_DEPTH 128u
|
||||
#define NETPLAY_INPUT_HIST_MAX_SLOTS 8
|
||||
#define NETPLAY_INPUT_HIST_DEPTH RBE_INPUT_HIST_DEPTH
|
||||
#define NETPLAY_INPUT_HIST_MAX_SLOTS RBE_INPUT_HIST_MAX_SLOTS
|
||||
typedef RbeInputHist NetplayInputHist;
|
||||
|
||||
typedef struct NetplayInputHist {
|
||||
RNetRbFrame rows[NETPLAY_INPUT_HIST_MAX_SLOTS][NETPLAY_INPUT_HIST_DEPTH];
|
||||
int slot_count;
|
||||
uint32_t invent_count;
|
||||
uint32_t promote_count;
|
||||
uint32_t rewind_count;
|
||||
} NetplayInputHist;
|
||||
|
||||
void netplay_ih_reset(NetplayInputHist *h, int slot_count);
|
||||
#define netplay_ih_reset rbe_ih_reset
|
||||
#define netplay_ih_frame_to_contract rbe_ih_frame_to_contract
|
||||
#define netplay_ih_put rbe_ih_put
|
||||
#define netplay_ih_get rbe_ih_get
|
||||
#define netplay_ih_invent_hold_last rbe_ih_invent_hold_last
|
||||
#define netplay_ih_invent_idle rbe_ih_invent_idle
|
||||
#define netplay_ih_promote rbe_ih_promote
|
||||
|
||||
/* PsxNetPad ↔ RNetRbFrame (LX/LY only; RX/RY stay on the pad blob path). */
|
||||
void netplay_ih_pad_to_frame(const PsxNetPad *pad, uint32_t tick, uint8_t predicted,
|
||||
RNetRbFrame *out);
|
||||
void netplay_ih_frame_to_pad(const RNetRbFrame *frame, PsxNetPad *pad);
|
||||
void netplay_ih_frame_to_contract(const RNetRbFrame *frame, RNetInputContractFrame *out);
|
||||
|
||||
int netplay_ih_put(NetplayInputHist *h, int slot, const RNetRbFrame *frame);
|
||||
int netplay_ih_get(const NetplayInputHist *h, int slot, uint32_t tick, RNetRbFrame *out);
|
||||
|
||||
/* Hold-last invent for missing remote at tick. Uses prior valid row for slot,
|
||||
* else neutral (buttons 0xFFFF, sticks 0). Marks is_predicted=1 and stores. */
|
||||
int netplay_ih_invent_hold_last(NetplayInputHist *h, int slot, uint32_t tick,
|
||||
RNetRbFrame *out);
|
||||
|
||||
/* Neutral invent (buttons 0xFFFF). Seal gap-fill only — live MotK admit uses
|
||||
* hold-last so a held D-pad does not re-episode every tick. */
|
||||
int netplay_ih_invent_idle(NetplayInputHist *h, int slot, uint32_t tick,
|
||||
RNetRbFrame *out);
|
||||
|
||||
/* Replace a published predicted/auth row with authoritative wire (promote). */
|
||||
int netplay_ih_promote(NetplayInputHist *h, int slot, const RNetRbFrame *wire);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -2,23 +2,16 @@
|
||||
#define PSX_NETPLAY_RB_POST_H
|
||||
|
||||
/*
|
||||
* RB_POST already carries target_tick on the wire. After tip-extend the peer
|
||||
* may still deliver a POST for the prior tip while we Verify the new tip —
|
||||
* latching that digest caused false post diverge (local@T+1 vs peer@T).
|
||||
*
|
||||
* Accept only when peer tip equals the current episode tip.
|
||||
* MotK compatibility shim → retcomm-rbengine rb_post tip filter.
|
||||
*/
|
||||
|
||||
#include <stdint.h>
|
||||
#include "retcomm_rbengine/rb_post.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
static inline int netplay_rb_peer_post_tip_ok(uint32_t peer_target, uint32_t episode_tip)
|
||||
{
|
||||
return peer_target == episode_tip;
|
||||
}
|
||||
#define netplay_rb_peer_post_tip_ok rbe_rb_peer_post_tip_ok
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
||||
@@ -2,65 +2,45 @@
|
||||
#define PSX_NETPLAY_SNAP_RING_H
|
||||
|
||||
/*
|
||||
* Tick-addressable in-memory .pst ring for rollback netplay.
|
||||
*
|
||||
* Depth defaults to 64 (enough for early soaks; raise toward
|
||||
* RNET_RB_SEAL_MAX_SPAN=128 when episodes deepen). Each slot owns a
|
||||
* boot_state_save_buffer_raw blob (uncompressed — FPS). save/load call the
|
||||
* full-machine serializer; store/peek are for tests / pre-serialized blobs.
|
||||
* MotK snap ring: opaque blob ring from retcomm-rbengine; save/load wrap
|
||||
* boot_state serialize (PSX-specific).
|
||||
*/
|
||||
|
||||
#include <stddef.h>
|
||||
#include <stdint.h>
|
||||
|
||||
#include "cpu_state.h"
|
||||
#include "retcomm_rbengine/snap_ring.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* §58: 64 → 80 — dense tip snaps (PSX_NET_SNAP_DENSE window) occupy up to
|
||||
* ~24 extra slots alongside the interval snaps; keep the interval history
|
||||
* span (~1024 ticks at iv=16) from shrinking under dense pressure. */
|
||||
#define NETPLAY_SNAP_RING_DEFAULT_DEPTH 80u
|
||||
#define NETPLAY_SNAP_RING_DEFAULT_DEPTH RBE_SNAP_RING_DEFAULT_DEPTH
|
||||
typedef RbeSnapRing NetplaySnapRing;
|
||||
|
||||
typedef struct NetplaySnapRing NetplaySnapRing;
|
||||
|
||||
NetplaySnapRing* netplay_snap_ring_create(uint32_t depth);
|
||||
void netplay_snap_ring_destroy(NetplaySnapRing* r);
|
||||
void netplay_snap_ring_clear(NetplaySnapRing* r);
|
||||
|
||||
uint32_t netplay_snap_ring_depth(const NetplaySnapRing* r);
|
||||
uint32_t netplay_snap_ring_count(const NetplaySnapRing* r);
|
||||
int netplay_snap_ring_has(const NetplaySnapRing* r, uint32_t tick);
|
||||
#define netplay_snap_ring_create rbe_snap_ring_create
|
||||
#define netplay_snap_ring_destroy rbe_snap_ring_destroy
|
||||
#define netplay_snap_ring_clear rbe_snap_ring_clear
|
||||
#define netplay_snap_ring_depth rbe_snap_ring_depth
|
||||
#define netplay_snap_ring_count rbe_snap_ring_count
|
||||
#define netplay_snap_ring_has rbe_snap_ring_has
|
||||
#define netplay_snap_ring_store rbe_snap_ring_store
|
||||
#define netplay_snap_ring_peek rbe_snap_ring_peek
|
||||
#define netplay_snap_ring_drop_after rbe_snap_ring_drop_after
|
||||
#define netplay_snap_ring_drop_tick rbe_snap_ring_drop_tick
|
||||
#define netplay_snap_ring_oldest_tick rbe_snap_ring_oldest_tick
|
||||
#define netplay_snap_ring_newest_tick rbe_snap_ring_newest_tick
|
||||
|
||||
/* Serialize live machine into the ring at tick (overwrites same tick). */
|
||||
int netplay_snap_ring_save(NetplaySnapRing* r, uint32_t tick,
|
||||
const CPUState* cpu, uint32_t bios_checksum,
|
||||
int netplay_snap_ring_save(NetplaySnapRing *r, uint32_t tick,
|
||||
const CPUState *cpu, uint32_t bios_checksum,
|
||||
uint32_t entry_pc);
|
||||
|
||||
/* Restore machine from the snap at tick. Returns 0 if missing/reject. */
|
||||
int netplay_snap_ring_load(NetplaySnapRing* r, uint32_t tick, CPUState* cpu,
|
||||
int netplay_snap_ring_load(NetplaySnapRing *r, uint32_t tick, CPUState *cpu,
|
||||
uint32_t bios_checksum, uint32_t entry_pc);
|
||||
|
||||
/* Take ownership of data on success (caller must not free). For tests /
|
||||
* pre-serialized blobs. Overwrites an existing entry for the same tick. */
|
||||
int netplay_snap_ring_store(NetplaySnapRing* r, uint32_t tick,
|
||||
uint8_t* data, size_t size);
|
||||
|
||||
/* Non-owning peek; NULL if missing. */
|
||||
const uint8_t* netplay_snap_ring_peek(const NetplaySnapRing* r, uint32_t tick,
|
||||
size_t* size_out);
|
||||
|
||||
/* Oldest/newest occupied tick, or 0 if empty (ambiguous with tick 0 — use
|
||||
* count() first). */
|
||||
/* Invalidate every snapshot with tick > tick (dead-timeline snaps after an
|
||||
* abort realign). Returns the number of slots dropped. */
|
||||
uint32_t netplay_snap_ring_drop_after(NetplaySnapRing* r, uint32_t tick);
|
||||
/* Drop the single snap at tick (dense-window eviction). 1 if dropped. */
|
||||
int netplay_snap_ring_drop_tick(NetplaySnapRing* r, uint32_t tick);
|
||||
uint32_t netplay_snap_ring_oldest_tick(const NetplaySnapRing* r);
|
||||
uint32_t netplay_snap_ring_newest_tick(const NetplaySnapRing* r);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
@@ -1,9 +1,10 @@
|
||||
/* psx_netplay_sched.h — rollback admission scheduler (policy only).
|
||||
/* psx_netplay_sched.h — MotK facade over retcomm-rbengine (rbe_sched_*).
|
||||
*
|
||||
* Split out of psx_netplay.c (§44) so the pacing/invent/delay policy can be
|
||||
* reviewed, tuned and ported independently of the admission mechanics (ring
|
||||
* peeks, input-history puts, tip-hold bookkeeping), which stay in
|
||||
* psx_netplay.c.
|
||||
* Policy lives in lib/retcomm-rbengine. This header keeps the historical
|
||||
* np_sched_* / PsxNpSchedBridge surface; MotK dig0/FMV/RTT enter through
|
||||
* gates in psx_netplay_sched.c.
|
||||
*
|
||||
* Env knobs: RBE_RB_* (preferred) with MotK PSX_RB_* fallback in rbengine.
|
||||
*
|
||||
* The scheduler's job (design contract, 2026-08-02):
|
||||
* - keep both peers at the same simulation time (mispredict-driven pacing,
|
||||
|
||||
+40
-1
@@ -259,7 +259,6 @@ set(PSXRECOMP_RUNTIME_SOURCES
|
||||
${PSXRECOMP_ROOT}/runtime/src/boot_state.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/netplay_snap_ring.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/netplay_state_digest.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/netplay_hash_confirm.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/netplay_input_hist.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_netplay_rb.c
|
||||
${PSXRECOMP_ROOT}/runtime/src/psx_netplay_sched.c
|
||||
@@ -377,6 +376,43 @@ else()
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Portable rollback host policy (sched/hist/hash_confirm/snap). Sits next to
|
||||
# recomp-net; MotK keeps thin PSX glue (pad↔frame, boot_state, FMV/dig0 gates).
|
||||
set(RECOMP_RBENGINE_ROOT "" CACHE PATH "Path to retcomm-rbengine; empty = auto-discover")
|
||||
if(PSXRECOMP_HAS_RECOMP_NET AND NOT RECOMP_RBENGINE_ROOT)
|
||||
foreach(_cand
|
||||
"${PSXRECOMP_ROOT}/lib/retcomm-rbengine"
|
||||
"${CMAKE_SOURCE_DIR}/../retcomm-rbengine"
|
||||
"${PSXRECOMP_ROOT}/../retcomm-rbengine")
|
||||
get_filename_component(_abs "${_cand}" ABSOLUTE)
|
||||
if(EXISTS "${_abs}/CMakeLists.txt")
|
||||
set(RECOMP_RBENGINE_ROOT "${_abs}" CACHE PATH
|
||||
"Path to retcomm-rbengine; empty = auto-discover" FORCE)
|
||||
break()
|
||||
endif()
|
||||
endforeach()
|
||||
endif()
|
||||
if(PSXRECOMP_HAS_RECOMP_NET AND RECOMP_RBENGINE_ROOT
|
||||
AND EXISTS "${RECOMP_RBENGINE_ROOT}/CMakeLists.txt")
|
||||
if(NOT TARGET retcomm_rbengine)
|
||||
set(RBE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
|
||||
# recomp_net already added above; rbengine skips nested add_subdirectory.
|
||||
set(RECOMP_NET_ROOT "${RECOMP_NET_ROOT}" CACHE PATH "" FORCE)
|
||||
add_subdirectory("${RECOMP_RBENGINE_ROOT}"
|
||||
"${CMAKE_BINARY_DIR}/retcomm-rbengine")
|
||||
endif()
|
||||
set(PSXRECOMP_HAS_RBENGINE TRUE)
|
||||
message(STATUS "psxrecomp: retcomm-rbengine enabled (${RECOMP_RBENGINE_ROOT})")
|
||||
else()
|
||||
set(PSXRECOMP_HAS_RBENGINE FALSE)
|
||||
if(PSXRECOMP_HAS_RECOMP_NET)
|
||||
message(FATAL_ERROR
|
||||
"psxrecomp: PSX_NETPLAY needs retcomm-rbengine.\n"
|
||||
" git submodule update --init lib/retcomm-rbengine\n"
|
||||
" or -DRECOMP_RBENGINE_ROOT=/path/to/retcomm-rbengine")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Lobby WebSocket client helpers are vendored under runtime/src/lobby_ws/
|
||||
# (protocol talks to the proprietary recomp-net-server, not recomp-net).
|
||||
set(PSXRECOMP_LOBBY_WS_DIR "${PSXRECOMP_ROOT}/runtime/src/lobby_ws")
|
||||
@@ -1076,6 +1112,9 @@ function(psxrecomp_add_runtime_target target)
|
||||
if(PSXRECOMP_HAS_RECOMP_NET)
|
||||
target_compile_definitions(${target} PRIVATE PSX_HAS_RECOMP_NET=1)
|
||||
target_link_libraries(${target} PRIVATE recomp_net)
|
||||
if(PSXRECOMP_HAS_RBENGINE)
|
||||
target_link_libraries(${target} PRIVATE retcomm_rbengine)
|
||||
endif()
|
||||
endif()
|
||||
if(PSXRECOMP_HAS_LOBBY_CLIENT)
|
||||
target_compile_definitions(${target} PRIVATE PSX_HAS_LOBBY_CLIENT=1)
|
||||
|
||||
@@ -1,163 +0,0 @@
|
||||
#include "netplay_hash_confirm.h"
|
||||
|
||||
#include <string.h>
|
||||
|
||||
static uint32_t slot_of(uint32_t tick) {
|
||||
return tick % NETPLAY_HC_RING;
|
||||
}
|
||||
|
||||
void netplay_hc_reset(NetplayHashConfirm* hc) {
|
||||
if (!hc) return;
|
||||
memset(hc, 0, sizeof(*hc));
|
||||
}
|
||||
|
||||
void netplay_hc_prime_after(NetplayHashConfirm* hc, uint32_t last_ok) {
|
||||
if (!hc) return;
|
||||
memset(hc, 0, sizeof(*hc));
|
||||
hc->resolved_through = last_ok;
|
||||
hc->resolved_valid = 1u;
|
||||
}
|
||||
|
||||
static void try_advance(NetplayHashConfirm* hc);
|
||||
|
||||
void netplay_hc_note_local(NetplayHashConfirm* hc, uint32_t tick, uint32_t digest) {
|
||||
uint32_t i;
|
||||
if (!hc) return;
|
||||
i = slot_of(tick);
|
||||
hc->local_tick[i] = tick;
|
||||
hc->local_digest[i] = digest;
|
||||
hc->local_valid[i] = 1u;
|
||||
try_advance(hc);
|
||||
}
|
||||
|
||||
static int local_at(const NetplayHashConfirm* hc, uint32_t tick, uint32_t* dig) {
|
||||
uint32_t i = slot_of(tick);
|
||||
if (!hc->local_valid[i] || hc->local_tick[i] != tick) return 0;
|
||||
if (dig) *dig = hc->local_digest[i];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static int peer_at(const NetplayHashConfirm* hc, uint32_t tick, uint32_t* dig) {
|
||||
uint32_t i = slot_of(tick);
|
||||
if (!hc->peer_valid[i] || hc->peer_tick[i] != tick) return 0;
|
||||
if (dig) *dig = hc->peer_digest[i];
|
||||
return 1;
|
||||
}
|
||||
|
||||
static void try_advance(NetplayHashConfirm* hc) {
|
||||
/* Advance contiguous matches starting just after current watermark. */
|
||||
for (;;) {
|
||||
uint32_t next;
|
||||
uint32_t ld = 0, pd = 0;
|
||||
if (!hc->resolved_valid)
|
||||
next = 0u;
|
||||
else {
|
||||
if (hc->resolved_through == 0xffffffffu) return;
|
||||
next = hc->resolved_through + 1u;
|
||||
}
|
||||
if (!local_at(hc, next, &ld) || !peer_at(hc, next, &pd))
|
||||
return;
|
||||
if (ld != pd)
|
||||
return;
|
||||
hc->resolved_through = next;
|
||||
hc->resolved_valid = 1u;
|
||||
}
|
||||
}
|
||||
|
||||
void netplay_hc_note_peer(NetplayHashConfirm* hc, uint32_t tick, uint32_t digest) {
|
||||
uint32_t i;
|
||||
if (!hc) return;
|
||||
i = slot_of(tick);
|
||||
hc->peer_tick[i] = tick;
|
||||
hc->peer_digest[i] = digest;
|
||||
hc->peer_valid[i] = 1u;
|
||||
try_advance(hc);
|
||||
}
|
||||
|
||||
uint32_t netplay_hc_resolved_through(const NetplayHashConfirm* hc) {
|
||||
if (!hc || !hc->resolved_valid) return 0u;
|
||||
return hc->resolved_through;
|
||||
}
|
||||
|
||||
uint8_t netplay_hc_confirm_through(const NetplayHashConfirm* hc, uint32_t tick) {
|
||||
if (!hc || !hc->resolved_valid) return 0u;
|
||||
return (tick <= hc->resolved_through) ? 1u : 0u;
|
||||
}
|
||||
|
||||
uint8_t netplay_hc_local_digest(const NetplayHashConfirm* hc, uint32_t tick,
|
||||
uint32_t* digest_out) {
|
||||
uint32_t d = 0;
|
||||
if (!hc || !local_at(hc, tick, &d)) return 0u;
|
||||
if (digest_out) *digest_out = d;
|
||||
return 1u;
|
||||
}
|
||||
|
||||
uint8_t netplay_hc_peer_digest(const NetplayHashConfirm* hc, uint32_t tick,
|
||||
uint32_t* digest_out) {
|
||||
uint32_t d = 0;
|
||||
if (!hc || !peer_at(hc, tick, &d)) return 0u;
|
||||
if (digest_out) *digest_out = d;
|
||||
return 1u;
|
||||
}
|
||||
|
||||
uint8_t netplay_hc_peek_mismatch(const NetplayHashConfirm* hc, uint32_t* tick_out,
|
||||
uint32_t* local_out, uint32_t* peer_out) {
|
||||
uint32_t next;
|
||||
uint32_t ld = 0, pd = 0;
|
||||
if (!hc) return 0u;
|
||||
if (!hc->resolved_valid)
|
||||
next = 0u;
|
||||
else {
|
||||
if (hc->resolved_through == 0xffffffffu) return 0u;
|
||||
next = hc->resolved_through + 1u;
|
||||
}
|
||||
if (!local_at(hc, next, &ld) || !peer_at(hc, next, &pd))
|
||||
return 0u;
|
||||
if (ld == pd)
|
||||
return 0u;
|
||||
if (tick_out) *tick_out = next;
|
||||
if (local_out) *local_out = ld;
|
||||
if (peer_out) *peer_out = pd;
|
||||
return 1u;
|
||||
}
|
||||
|
||||
uint8_t netplay_hc_heal_stale_gap(NetplayHashConfirm* hc) {
|
||||
uint32_t next;
|
||||
uint32_t best = 0u;
|
||||
uint8_t have_best = 0u;
|
||||
uint32_t i;
|
||||
uint32_t ld = 0, pd = 0;
|
||||
if (!hc || !hc->resolved_valid)
|
||||
return 0u;
|
||||
if (hc->resolved_through == 0xffffffffu)
|
||||
return 0u;
|
||||
next = hc->resolved_through + 1u;
|
||||
/* Still have both digests for next: let try_advance / peek_mismatch own it. */
|
||||
if (local_at(hc, next, &ld) && peer_at(hc, next, &pd))
|
||||
return 0u;
|
||||
/* Incomplete (one side only) — wait; do not jump over a pending commit. */
|
||||
if (local_at(hc, next, NULL) || peer_at(hc, next, NULL))
|
||||
return 0u;
|
||||
/* next aged out of the ring. Refuse heal if any ring slot above the
|
||||
* watermark still records a local≠peer pair (live fork still visible). */
|
||||
for (i = 0; i < NETPLAY_HC_RING; i++) {
|
||||
uint32_t t;
|
||||
if (!hc->local_valid[i] || !hc->peer_valid[i])
|
||||
continue;
|
||||
if (hc->local_tick[i] != hc->peer_tick[i])
|
||||
continue;
|
||||
t = hc->local_tick[i];
|
||||
if (t <= hc->resolved_through)
|
||||
continue;
|
||||
if (hc->local_digest[i] != hc->peer_digest[i])
|
||||
return 0u; /* known fork still in ring */
|
||||
if (!have_best || t > best) {
|
||||
best = t;
|
||||
have_best = 1u;
|
||||
}
|
||||
}
|
||||
if (!have_best || best <= hc->resolved_through)
|
||||
return 0u;
|
||||
hc->resolved_through = best;
|
||||
return 1u;
|
||||
}
|
||||
@@ -6,6 +6,8 @@
|
||||
|
||||
#include <string.h>
|
||||
|
||||
/* PSX pad ↔ RNetRbFrame only. Ring/invent/promote live in retcomm-rbengine. */
|
||||
|
||||
static int8_t u8_to_i8_stick(uint8_t v)
|
||||
{
|
||||
int d = (int)v - 0x80;
|
||||
@@ -19,16 +21,6 @@ static uint8_t i8_to_u8_stick(int8_t v)
|
||||
return (uint8_t)((int)v + 0x80);
|
||||
}
|
||||
|
||||
void netplay_ih_reset(NetplayInputHist *h, int slot_count)
|
||||
{
|
||||
if (!h) return;
|
||||
memset(h, 0, sizeof(*h));
|
||||
if (slot_count < 1) slot_count = 1;
|
||||
if (slot_count > NETPLAY_INPUT_HIST_MAX_SLOTS)
|
||||
slot_count = NETPLAY_INPUT_HIST_MAX_SLOTS;
|
||||
h->slot_count = slot_count;
|
||||
}
|
||||
|
||||
void netplay_ih_pad_to_frame(const PsxNetPad *pad, uint32_t tick, uint8_t predicted,
|
||||
RNetRbFrame *out)
|
||||
{
|
||||
@@ -67,104 +59,4 @@ void netplay_ih_frame_to_pad(const RNetRbFrame *frame, PsxNetPad *pad)
|
||||
psx_netplay_normalize_pad(pad);
|
||||
}
|
||||
|
||||
void netplay_ih_frame_to_contract(const RNetRbFrame *frame, RNetInputContractFrame *out)
|
||||
{
|
||||
if (!out) return;
|
||||
memset(out, 0, sizeof(*out));
|
||||
if (!frame) return;
|
||||
out->tick = frame->tick;
|
||||
out->buttons = frame->buttons;
|
||||
out->stick_x = frame->stick_x;
|
||||
out->stick_y = frame->stick_y;
|
||||
out->is_predicted = frame->is_predicted ? 1u : 0u;
|
||||
}
|
||||
|
||||
int netplay_ih_put(NetplayInputHist *h, int slot, const RNetRbFrame *frame)
|
||||
{
|
||||
RNetRbFrame *dst;
|
||||
if (!h || !frame || !frame->is_valid) return 0;
|
||||
if (slot < 0 || slot >= h->slot_count) return 0;
|
||||
dst = &h->rows[slot][frame->tick % NETPLAY_INPUT_HIST_DEPTH];
|
||||
*dst = *frame;
|
||||
dst->is_valid = 1u;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int netplay_ih_get(const NetplayInputHist *h, int slot, uint32_t tick, RNetRbFrame *out)
|
||||
{
|
||||
const RNetRbFrame *src;
|
||||
if (!h || !out) return 0;
|
||||
if (slot < 0 || slot >= h->slot_count) return 0;
|
||||
src = &h->rows[slot][tick % NETPLAY_INPUT_HIST_DEPTH];
|
||||
if (!src->is_valid || src->tick != tick) return 0;
|
||||
*out = *src;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int netplay_ih_invent_hold_last(NetplayInputHist *h, int slot, uint32_t tick,
|
||||
RNetRbFrame *out)
|
||||
{
|
||||
RNetRbFrame invented;
|
||||
RNetRbFrame prev;
|
||||
uint32_t look;
|
||||
|
||||
if (!h || slot < 0 || slot >= h->slot_count) return 0;
|
||||
|
||||
memset(&invented, 0, sizeof(invented));
|
||||
invented.tick = tick;
|
||||
invented.buttons = 0xFFFFu;
|
||||
invented.analog = 0u;
|
||||
invented.is_predicted = 1u;
|
||||
invented.is_valid = 1u;
|
||||
|
||||
for (look = 1; look < NETPLAY_INPUT_HIST_DEPTH; look++) {
|
||||
if (tick < look) break;
|
||||
if (netplay_ih_get(h, slot, tick - look, &prev)) {
|
||||
invented.buttons = prev.buttons;
|
||||
invented.stick_x = prev.stick_x;
|
||||
invented.stick_y = prev.stick_y;
|
||||
invented.analog = prev.analog ? 1u : 0u;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!netplay_ih_put(h, slot, &invented)) return 0;
|
||||
h->invent_count++;
|
||||
if (out) *out = invented;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int netplay_ih_invent_idle(NetplayInputHist *h, int slot, uint32_t tick,
|
||||
RNetRbFrame *out)
|
||||
{
|
||||
RNetRbFrame invented;
|
||||
|
||||
if (!h || slot < 0 || slot >= h->slot_count) return 0;
|
||||
|
||||
memset(&invented, 0, sizeof(invented));
|
||||
invented.tick = tick;
|
||||
invented.buttons = 0xFFFFu;
|
||||
invented.analog = 0u;
|
||||
invented.is_predicted = 1u;
|
||||
invented.is_valid = 1u;
|
||||
/* stick_x/y = 0 → neutral 0x80 via frame_to_pad */
|
||||
|
||||
if (!netplay_ih_put(h, slot, &invented)) return 0;
|
||||
h->invent_count++;
|
||||
if (out) *out = invented;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int netplay_ih_promote(NetplayInputHist *h, int slot, const RNetRbFrame *wire)
|
||||
{
|
||||
RNetRbFrame auth;
|
||||
if (!h || !wire || !wire->is_valid) return 0;
|
||||
auth = *wire;
|
||||
auth.is_predicted = 0u;
|
||||
auth.is_valid = 1u;
|
||||
if (!netplay_ih_put(h, slot, &auth)) return 0;
|
||||
h->promote_count++;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#endif /* PSX_HAS_RECOMP_NET */
|
||||
|
||||
+17
-219
@@ -1,9 +1,9 @@
|
||||
/* clock_gettime/CLOCK_MONOTONIC are POSIX, not ISO C. Under a strict -std=cNN
|
||||
* (no GNU extensions) glibc hides them unless _POSIX_C_SOURCE is set, so this
|
||||
* TU fails to compile — loudly under -Werror, silently as an implicit
|
||||
* declaration without it. The runtime build happens to use -std=gnu99, which
|
||||
* is why this never surfaced there. Must precede the first libc header:
|
||||
* glibc's <features.h> latches the feature-test macros on first inclusion. */
|
||||
/* MotK snap save/load over retcomm-rbengine ring + boot_state serialize. */
|
||||
|
||||
#if !defined(PSX_HAS_RECOMP_NET)
|
||||
/* Empty TU when netplay/rbengine is not linked. */
|
||||
#else
|
||||
|
||||
#ifndef _POSIX_C_SOURCE
|
||||
# define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
@@ -12,8 +12,6 @@
|
||||
#include "boot_state.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#if defined(_WIN32)
|
||||
# include <windows.h>
|
||||
#else
|
||||
@@ -35,161 +33,11 @@ static double snap_mono_ms(void)
|
||||
#endif
|
||||
}
|
||||
|
||||
typedef struct NetplaySnapSlot {
|
||||
int valid;
|
||||
uint32_t tick;
|
||||
uint8_t* data;
|
||||
size_t size;
|
||||
} NetplaySnapSlot;
|
||||
|
||||
struct NetplaySnapRing {
|
||||
NetplaySnapSlot* slots;
|
||||
uint32_t depth;
|
||||
uint32_t count;
|
||||
/* Next write index in the circular buffer (0 .. depth-1). */
|
||||
uint32_t head;
|
||||
};
|
||||
|
||||
static void slot_clear(NetplaySnapSlot* s) {
|
||||
free(s->data);
|
||||
s->data = NULL;
|
||||
s->size = 0;
|
||||
s->tick = 0;
|
||||
s->valid = 0;
|
||||
}
|
||||
|
||||
static int find_slot(const NetplaySnapRing* r, uint32_t tick) {
|
||||
uint32_t i;
|
||||
if (!r) return -1;
|
||||
for (i = 0; i < r->depth; i++) {
|
||||
if (r->slots[i].valid && r->slots[i].tick == tick)
|
||||
return (int)i;
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
NetplaySnapRing* netplay_snap_ring_create(uint32_t depth) {
|
||||
NetplaySnapRing* r;
|
||||
if (depth == 0)
|
||||
depth = NETPLAY_SNAP_RING_DEFAULT_DEPTH;
|
||||
if (depth > 512u)
|
||||
depth = 512u;
|
||||
r = (NetplaySnapRing*)calloc(1, sizeof(*r));
|
||||
if (!r) return NULL;
|
||||
r->slots = (NetplaySnapSlot*)calloc(depth, sizeof(NetplaySnapSlot));
|
||||
if (!r->slots) {
|
||||
free(r);
|
||||
return NULL;
|
||||
}
|
||||
r->depth = depth;
|
||||
return r;
|
||||
}
|
||||
|
||||
void netplay_snap_ring_destroy(NetplaySnapRing* r) {
|
||||
if (!r) return;
|
||||
netplay_snap_ring_clear(r);
|
||||
free(r->slots);
|
||||
free(r);
|
||||
}
|
||||
|
||||
void netplay_snap_ring_clear(NetplaySnapRing* r) {
|
||||
uint32_t i;
|
||||
if (!r) return;
|
||||
for (i = 0; i < r->depth; i++)
|
||||
slot_clear(&r->slots[i]);
|
||||
r->count = 0;
|
||||
r->head = 0;
|
||||
}
|
||||
|
||||
uint32_t netplay_snap_ring_depth(const NetplaySnapRing* r) {
|
||||
return r ? r->depth : 0u;
|
||||
}
|
||||
|
||||
uint32_t netplay_snap_ring_count(const NetplaySnapRing* r) {
|
||||
return r ? r->count : 0u;
|
||||
}
|
||||
|
||||
int netplay_snap_ring_has(const NetplaySnapRing* r, uint32_t tick) {
|
||||
return find_slot(r, tick) >= 0;
|
||||
}
|
||||
|
||||
int netplay_snap_ring_store(NetplaySnapRing* r, uint32_t tick,
|
||||
uint8_t* data, size_t size) {
|
||||
int existing;
|
||||
NetplaySnapSlot* s;
|
||||
if (!r || !data || !size) return 0;
|
||||
|
||||
existing = find_slot(r, tick);
|
||||
if (existing >= 0) {
|
||||
s = &r->slots[existing];
|
||||
free(s->data);
|
||||
s->data = data;
|
||||
s->size = size;
|
||||
s->tick = tick;
|
||||
s->valid = 1;
|
||||
return 1;
|
||||
}
|
||||
|
||||
if (r->count < r->depth) {
|
||||
/* Prefer an unused slot; walk from head for locality. */
|
||||
uint32_t i;
|
||||
for (i = 0; i < r->depth; i++) {
|
||||
uint32_t idx = (r->head + i) % r->depth;
|
||||
if (!r->slots[idx].valid) {
|
||||
s = &r->slots[idx];
|
||||
s->data = data;
|
||||
s->size = size;
|
||||
s->tick = tick;
|
||||
s->valid = 1;
|
||||
r->count++;
|
||||
r->head = (idx + 1u) % r->depth;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Evict the oldest (lowest tick) occupied slot. */
|
||||
{
|
||||
uint32_t i;
|
||||
int victim = -1;
|
||||
uint32_t oldest = 0xffffffffu;
|
||||
for (i = 0; i < r->depth; i++) {
|
||||
if (!r->slots[i].valid) continue;
|
||||
if (r->slots[i].tick <= oldest) {
|
||||
oldest = r->slots[i].tick;
|
||||
victim = (int)i;
|
||||
}
|
||||
}
|
||||
if (victim < 0) {
|
||||
free(data);
|
||||
return 0;
|
||||
}
|
||||
s = &r->slots[victim];
|
||||
free(s->data);
|
||||
s->data = data;
|
||||
s->size = size;
|
||||
s->tick = tick;
|
||||
s->valid = 1;
|
||||
r->head = ((uint32_t)victim + 1u) % r->depth;
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
const uint8_t* netplay_snap_ring_peek(const NetplaySnapRing* r, uint32_t tick,
|
||||
size_t* size_out) {
|
||||
int idx = find_slot(r, tick);
|
||||
if (idx < 0) {
|
||||
if (size_out) *size_out = 0;
|
||||
return NULL;
|
||||
}
|
||||
if (size_out) *size_out = r->slots[idx].size;
|
||||
return r->slots[idx].data;
|
||||
}
|
||||
|
||||
int netplay_snap_ring_save(NetplaySnapRing* r, uint32_t tick,
|
||||
const CPUState* cpu, uint32_t bios_checksum,
|
||||
uint32_t entry_pc) {
|
||||
uint8_t* data = NULL;
|
||||
int netplay_snap_ring_save(NetplaySnapRing *r, uint32_t tick,
|
||||
const CPUState *cpu, uint32_t bios_checksum,
|
||||
uint32_t entry_pc)
|
||||
{
|
||||
uint8_t *data = NULL;
|
||||
size_t len = 0;
|
||||
double t0, dt;
|
||||
static uint32_t s_tele_n;
|
||||
@@ -198,8 +46,6 @@ int netplay_snap_ring_save(NetplaySnapRing* r, uint32_t tick,
|
||||
static uint64_t s_tele_dirty_sum;
|
||||
static uint32_t s_tele_incr_n;
|
||||
if (!r || !cpu) return 0;
|
||||
/* Raw (no zlib): compress2 on RAM+VRAM+SPU dominated live FPS. Load
|
||||
* accepts uncompressed v4 sections. §96: dirty-scanline VRAM mirror. */
|
||||
t0 = snap_mono_ms();
|
||||
if (!boot_state_save_buffer_raw(cpu, bios_checksum, entry_pc, &data, &len))
|
||||
return 0;
|
||||
@@ -210,7 +56,6 @@ int netplay_snap_ring_save(NetplaySnapRing* r, uint32_t tick,
|
||||
s_tele_dirty_sum += (uint64_t)boot_state_last_vram_dirty_rows();
|
||||
if (boot_state_last_vram_incremental())
|
||||
s_tele_incr_n++;
|
||||
/* Heartbeat every 64 saves — visible during FMV dense / media windows. */
|
||||
if ((s_tele_n & 63u) == 0u) {
|
||||
fprintf(stderr,
|
||||
"psxrecomp: rb snap tele n=%u last=%.2fms avg=%.2fms "
|
||||
@@ -225,64 +70,17 @@ int netplay_snap_ring_save(NetplaySnapRing* r, uint32_t tick,
|
||||
fflush(stderr);
|
||||
}
|
||||
if (!netplay_snap_ring_store(r, tick, data, len))
|
||||
return 0; /* store frees data on hard failure */
|
||||
return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
int netplay_snap_ring_load(NetplaySnapRing* r, uint32_t tick, CPUState* cpu,
|
||||
uint32_t bios_checksum, uint32_t entry_pc) {
|
||||
int netplay_snap_ring_load(NetplaySnapRing *r, uint32_t tick, CPUState *cpu,
|
||||
uint32_t bios_checksum, uint32_t entry_pc)
|
||||
{
|
||||
size_t size = 0;
|
||||
const uint8_t* data = netplay_snap_ring_peek(r, tick, &size);
|
||||
const uint8_t *data = netplay_snap_ring_peek(r, tick, &size);
|
||||
if (!data || !size || !cpu) return 0;
|
||||
return boot_state_load_buffer(data, size, bios_checksum, entry_pc, cpu);
|
||||
}
|
||||
|
||||
int netplay_snap_ring_drop_tick(NetplaySnapRing* r, uint32_t tick) {
|
||||
int idx = find_slot(r, tick);
|
||||
if (idx < 0) return 0;
|
||||
slot_clear(&r->slots[idx]);
|
||||
if (r->count) r->count--;
|
||||
return 1;
|
||||
}
|
||||
|
||||
uint32_t netplay_snap_ring_drop_after(NetplaySnapRing* r, uint32_t tick) {
|
||||
uint32_t i, n = 0;
|
||||
if (!r) return 0;
|
||||
for (i = 0; i < r->depth; i++) {
|
||||
if (!r->slots[i].valid) continue;
|
||||
if (r->slots[i].tick > tick) {
|
||||
slot_clear(&r->slots[i]);
|
||||
if (r->count) r->count--;
|
||||
n++;
|
||||
}
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
uint32_t netplay_snap_ring_oldest_tick(const NetplaySnapRing* r) {
|
||||
uint32_t i, oldest = 0xffffffffu;
|
||||
int any = 0;
|
||||
if (!r) return 0;
|
||||
for (i = 0; i < r->depth; i++) {
|
||||
if (!r->slots[i].valid) continue;
|
||||
if (!any || r->slots[i].tick < oldest) {
|
||||
oldest = r->slots[i].tick;
|
||||
any = 1;
|
||||
}
|
||||
}
|
||||
return any ? oldest : 0u;
|
||||
}
|
||||
|
||||
uint32_t netplay_snap_ring_newest_tick(const NetplaySnapRing* r) {
|
||||
uint32_t i, newest = 0;
|
||||
int any = 0;
|
||||
if (!r) return 0;
|
||||
for (i = 0; i < r->depth; i++) {
|
||||
if (!r->slots[i].valid) continue;
|
||||
if (!any || r->slots[i].tick > newest) {
|
||||
newest = r->slots[i].tick;
|
||||
any = 1;
|
||||
}
|
||||
}
|
||||
return any ? newest : 0u;
|
||||
}
|
||||
#endif /* PSX_HAS_RECOMP_NET */
|
||||
|
||||
+155
-1988
File diff suppressed because it is too large
Load Diff
@@ -21,12 +21,16 @@
|
||||
# define _POSIX_C_SOURCE 200809L
|
||||
#endif
|
||||
|
||||
#ifndef PSX_HAS_RECOMP_NET
|
||||
# define PSX_HAS_RECOMP_NET 1
|
||||
#endif
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
/* Stub boot_state so the ring TU links without the full runtime. */
|
||||
/* Stub boot_state so the MotK save/load glue links without the full runtime. */
|
||||
#ifdef NETPLAY_SNAP_RING_TEST_STUB_BOOT_STATE
|
||||
#include "cpu_state.h"
|
||||
int boot_state_save_buffer(const CPUState* cpu, uint32_t bios_checksum,
|
||||
@@ -55,6 +59,7 @@ uint32_t boot_state_last_vram_dirty_rows(void) { return 0; }
|
||||
int boot_state_last_vram_incremental(void) { return 0; }
|
||||
#endif
|
||||
|
||||
#include "netplay_snap_ring.h"
|
||||
#include "../src/netplay_snap_ring.c"
|
||||
|
||||
static int failures;
|
||||
|
||||
@@ -36,3 +36,4 @@ pin() {
|
||||
pin psxrecomp psxrecomp
|
||||
pin recomp-ui recomp-ui
|
||||
pin recomp-net psxrecomp/lib/recomp-net
|
||||
pin retcomm-rbengine psxrecomp/lib/retcomm-rbengine
|
||||
|
||||
@@ -46,6 +46,7 @@ path_for() {
|
||||
psxrecomp) echo psxrecomp ;;
|
||||
recomp-ui) echo recomp-ui ;;
|
||||
recomp-net) echo psxrecomp/lib/recomp-net ;;
|
||||
retcomm-rbengine) echo psxrecomp/lib/retcomm-rbengine ;;
|
||||
*) echo "" ;;
|
||||
esac
|
||||
}
|
||||
|
||||
@@ -630,6 +630,9 @@ fi
|
||||
if [ "$ENABLE_NETPLAY" -eq 1 ] && [ ! -f psxrecomp/lib/recomp-net/CMakeLists.txt ]; then
|
||||
echo "warning: netplay enabled but psxrecomp/lib/recomp-net missing after recursive update." >&2
|
||||
fi
|
||||
if [ "$ENABLE_NETPLAY" -eq 1 ] && [ ! -f psxrecomp/lib/retcomm-rbengine/CMakeLists.txt ]; then
|
||||
echo "warning: netplay enabled but psxrecomp/lib/retcomm-rbengine missing after recursive update." >&2
|
||||
fi
|
||||
|
||||
echo "== Packager stub =="
|
||||
fill_template "$TEMPLATE_DIR/package_setup_release.sh.in" \
|
||||
|
||||
Reference in New Issue
Block a user