mirror of
https://github.com/RetroPortingToolKit/psxrecomp
synced 2026-10-26 10:19:47 -04:00
465f926ecf
Four-peer rollback desynced under load (R4, host behind): the guests predicted the slow host's pad, hash-confirmed the tick among themselves through the one shared peer ring, then promoted the host's real pad into history only (hc-silent promote) while the host simulated it. The host stayed forked; its recovery episode was then refused as a "zombie load" behind the guests' (wrongly) agreed tip. - Hash confirm is a quorum over every occupied peer seat (rnet_hc_set_peer_mask / note_peer_from, recomp-net) for three or more seats; two seats keep the one-peer chain. - A peer more than 4 ticks behind the others' inputs sheds its own view (psx_mod_render_local_view status FAST_FORWARD, held 60 ticks), so the presentation-only pass goes before the simulation slips. PSX_NET_LOCAL_VIEW_SHED=0 keeps it. - The admit-stall watchdog counts time without progress: a remote input tip that advances restarts it, so a slow peer is waited for. PSX_NET_ADMIT_STALL_MS (20000) and PSX_NET_LIVENESS_MS (1500) configure it. - Pins recomp-net fix/rollback-span-stall. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
113 lines
4.4 KiB
C
113 lines
4.4 KiB
C
#ifndef NETPLAY_LOCAL_VIEW_H
|
|
#define NETPLAY_LOCAL_VIEW_H
|
|
|
|
#include <stdint.h>
|
|
#include <string.h>
|
|
|
|
/* Host-only record of the display sub-rectangle a title asked the present
|
|
* path to show for the local netplay seat (psx_netplay_present_local_view).
|
|
* Never serialized and never visible to the guest: every peer still renders
|
|
* and digests the identical full frame. A request covers the simulation tick
|
|
* it was made on plus PSX_NETPLAY_LOCAL_VIEW_HOLD later ticks, so a title only
|
|
* has to renew it while its multi-view screen is up; menus fall back to the
|
|
* full frame by themselves. Rollback resimulation repeats ticks up to
|
|
* PSX_NETPLAY_LOCAL_VIEW_REPLAY behind the newest request; those never move
|
|
* the stamp backwards. A larger step back (a load) restarts it. */
|
|
#define PSX_NETPLAY_LOCAL_VIEW_HOLD 8u
|
|
#define PSX_NETPLAY_LOCAL_VIEW_REPLAY 64u
|
|
|
|
typedef struct PsxNetplayLocalView {
|
|
uint32_t x, y, w, h;
|
|
uint32_t tick;
|
|
uint8_t valid;
|
|
} PsxNetplayLocalView;
|
|
|
|
static inline void psx_netplay_local_view_reset(PsxNetplayLocalView *view)
|
|
{
|
|
if (view) memset(view, 0, sizeof(*view));
|
|
}
|
|
|
|
/* Rejects empty rectangles and anything outside 1024x512 VRAM space. */
|
|
static inline int psx_netplay_local_view_set(PsxNetplayLocalView *view,
|
|
uint32_t tick, uint32_t x,
|
|
uint32_t y, uint32_t w,
|
|
uint32_t h)
|
|
{
|
|
if (!view || w == 0u || h == 0u || x >= 1024u || y >= 512u ||
|
|
w > 1024u - x || h > 512u - y)
|
|
return 0;
|
|
if (view->valid && (int32_t)(tick - view->tick) < 0 &&
|
|
view->tick - tick <= PSX_NETPLAY_LOCAL_VIEW_REPLAY)
|
|
tick = view->tick;
|
|
view->x = x;
|
|
view->y = y;
|
|
view->w = w;
|
|
view->h = h;
|
|
view->tick = tick;
|
|
view->valid = 1u;
|
|
return 1;
|
|
}
|
|
|
|
/* 1 and the rectangle when a request is still current at `now` and fits a
|
|
* display of display_w x display_h; 0 otherwise. */
|
|
static inline int psx_netplay_local_view_get(const PsxNetplayLocalView *view,
|
|
uint32_t now, uint32_t display_w,
|
|
uint32_t display_h, uint32_t *x,
|
|
uint32_t *y, uint32_t *w,
|
|
uint32_t *h)
|
|
{
|
|
if (!view || !view->valid)
|
|
return 0;
|
|
const int32_t age = (int32_t)(now - view->tick);
|
|
if (age < 0 ? view->tick - now > PSX_NETPLAY_LOCAL_VIEW_REPLAY
|
|
: (uint32_t)age > PSX_NETPLAY_LOCAL_VIEW_HOLD)
|
|
return 0;
|
|
if (view->x + view->w > display_w || view->y + view->h > display_h)
|
|
return 0;
|
|
if (x) *x = view->x;
|
|
if (y) *y = view->y;
|
|
if (w) *w = view->w;
|
|
if (h) *h = view->h;
|
|
return 1;
|
|
}
|
|
|
|
/* Load shedding for psx_mod_render_local_view. A peer's own view is
|
|
* presentation only, so when this peer falls behind the others' inputs it
|
|
* goes first, before the simulation itself slips further (a slow peer forces
|
|
* deeper rollbacks on everyone). "Behind" is remote_lead - input delay: the
|
|
* newest remote input is normally about D ticks ahead of the simulation.
|
|
* Shedding starts at PSX_NETPLAY_LOCAL_VIEW_SHED_BEHIND ticks behind and
|
|
* lasts at least PSX_NETPLAY_LOCAL_VIEW_SHED_HOLD ticks and until the peer is
|
|
* within PSX_NETPLAY_LOCAL_VIEW_SHED_CAUGHT_UP ticks again, so the view does
|
|
* not flicker between the own view and the fallback every other frame. */
|
|
#define PSX_NETPLAY_LOCAL_VIEW_SHED_BEHIND 4
|
|
#define PSX_NETPLAY_LOCAL_VIEW_SHED_CAUGHT_UP 1
|
|
#define PSX_NETPLAY_LOCAL_VIEW_SHED_HOLD 60u
|
|
|
|
typedef struct PsxNetplayLocalViewShed {
|
|
uint32_t until;
|
|
uint8_t on;
|
|
} PsxNetplayLocalViewShed;
|
|
|
|
static inline int psx_netplay_local_view_shed_step(PsxNetplayLocalViewShed *s,
|
|
uint32_t sim,
|
|
int remote_lead, int delay)
|
|
{
|
|
const int behind = remote_lead - (delay > 0 ? delay : 0);
|
|
if (!s)
|
|
return 0;
|
|
if (behind >= PSX_NETPLAY_LOCAL_VIEW_SHED_BEHIND) {
|
|
s->on = 1u;
|
|
s->until = sim + PSX_NETPLAY_LOCAL_VIEW_SHED_HOLD;
|
|
} else if (s->on && (int32_t)(sim - s->until) >= 0 &&
|
|
behind <= PSX_NETPLAY_LOCAL_VIEW_SHED_CAUGHT_UP) {
|
|
s->on = 0u;
|
|
} else if (s->on &&
|
|
(uint32_t)(s->until - sim) > 2u * PSX_NETPLAY_LOCAL_VIEW_SHED_HOLD) {
|
|
s->until = sim + PSX_NETPLAY_LOCAL_VIEW_SHED_HOLD; /* stepped back (load) */
|
|
}
|
|
return s->on;
|
|
}
|
|
|
|
#endif
|