mirror of
https://github.com/tangerineturtle1/NX2navi.git
synced 2026-11-15 17:09:57 -04:00
gfx_vsync_bind kept every SystemEventType address the game ever passed to GetDisplayVsyncEvent, never dropped one, and called rt_fatal on the 17th distinct address. A destroyed event also stayed on the list the vsync thread signals 60 times a second. kernel.c now exports kern_system_event_live (mapped guest memory with a nonzero state byte), and with the list full gfx_vsync_bind gives the first slot whose event is no longer live to the new one, which also takes the destroyed event off the vsync thread's list; it stays fatal only when all 16 are live. Each new listing logs "gfx: vsync event ... bound in slot N, M listed", so a long session's log shows how often new addresses come. Not reproduced before the change: the fresh 0.10.0 session's system event count stayed at 9-10 across its area loads, and no log here covers returns to the title. tests/vsync_bind_check.py cuts gfx_vsync_bind from gfx_present.c with a stub liveness check: a 17th event takes the first destroyed slot, an 18th the next, and with 16 live events the 17th still ends with the fatal. system_event_check now also checks kern_system_event_live. The compile check of every runtime file passes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
103 lines
4.2 KiB
Python
103 lines
4.2 KiB
Python
"""The vsync event list reuses the slots of destroyed events (runtime/gfx_present.c gfx_vsync_bind): builds
|
|
gfx_vsync_bind, cut from gfx_present.c by its text, with stand-ins for the lock, the log, the vsync thread start and
|
|
kern_system_event_live, and checks that
|
|
|
|
- binding an address already listed changes nothing;
|
|
- with 16 live events listed, a 17th takes the slot of the first destroyed one and keeps the others;
|
|
- with all 16 live, a 17th still ends the process with the "more than 16 live vsync events" fatal.
|
|
|
|
No game files. Usage: python tests/vsync_bind_check.py [--cc <clang>]
|
|
"""
|
|
import argparse
|
|
import os
|
|
import subprocess
|
|
import sys
|
|
import tempfile
|
|
from pathlib import Path
|
|
|
|
ROOT = Path(__file__).resolve().parents[1]
|
|
MSYS_BIN = Path(r'C:\msys64\ucrt64\bin')
|
|
|
|
HARNESS = r'''
|
|
#include <stdint.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
typedef int PlatMutex;
|
|
#define PLAT_MUTEX_INIT 0
|
|
static void plat_mutex_lock(PlatMutex *m) { (void)m; }
|
|
static void plat_mutex_unlock(PlatMutex *m) { (void)m; }
|
|
static void rt_log(const char *fmt, ...) { (void)fmt; }
|
|
static void rt_fatal(const char *fmt, ...) { printf("FATAL: %%s\n", fmt); fflush(stdout); exit(3); }
|
|
static int dead[64]; // events the game destroyed, by address (1..63)
|
|
static int kern_system_event_live(uint64_t ev) { return ev < 64 && !dead[ev]; }
|
|
static int ensured;
|
|
static void vsync_ensure(void) { ensured++; }
|
|
static PlatMutex g_vs_lock = PLAT_MUTEX_INIT;
|
|
static uint64_t g_vs_ev[16];
|
|
static int g_vs_nev;
|
|
%s
|
|
static int fails;
|
|
#define CHECK(cond, ...) do { if (!(cond)) { printf("FAIL: " __VA_ARGS__); putchar('\n'); fails++; } } while (0)
|
|
static int listed(uint64_t ev) {
|
|
for (int i = 0; i < g_vs_nev; i++)
|
|
if (g_vs_ev[i] == ev) return 1;
|
|
return 0;
|
|
}
|
|
int main(int argc, char **argv) {
|
|
for (uint64_t ev = 1; ev <= 16; ev++) gfx_vsync_bind(ev);
|
|
gfx_vsync_bind(5);
|
|
CHECK(g_vs_nev == 16, "16 events listed, an address bound again is not listed twice (%%d)", g_vs_nev);
|
|
CHECK(ensured == 17, "every bind makes sure the vsync thread runs (%%d)", ensured);
|
|
if (argc > 1 && !strcmp(argv[1], "full")) {
|
|
gfx_vsync_bind(17); // every listed event is live
|
|
printf("no fatal\n");
|
|
return 0;
|
|
}
|
|
dead[7] = dead[9] = 1;
|
|
gfx_vsync_bind(17);
|
|
CHECK(g_vs_nev == 16 && listed(17) && !listed(7) && listed(9), "a 17th takes the first destroyed slot only");
|
|
for (uint64_t ev = 1; ev <= 16; ev++)
|
|
if (ev != 7) CHECK(listed(ev), "event %%llu stays listed", (unsigned long long)ev);
|
|
gfx_vsync_bind(18);
|
|
CHECK(listed(18) && !listed(9), "an 18th takes the next destroyed slot");
|
|
printf(fails ? "vsync bind: %%d failures\n" : "vsync bind: ok\n", fails);
|
|
return fails != 0;
|
|
}
|
|
'''
|
|
|
|
|
|
def cut(text, start, end_marker):
|
|
a = text.index(start)
|
|
b = text.index(end_marker, a)
|
|
return text[a:b]
|
|
|
|
|
|
def main():
|
|
ap = argparse.ArgumentParser(description=__doc__)
|
|
ap.add_argument('--cc', help='the C compiler (default: MSYS2 UCRT64 clang on Windows, clang elsewhere)')
|
|
args = ap.parse_args()
|
|
win = os.name == 'nt'
|
|
cc = args.cc or (str(MSYS_BIN / 'clang.exe') if win and MSYS_BIN.exists() else 'clang')
|
|
env = dict(os.environ)
|
|
if win and MSYS_BIN.exists():
|
|
env['PATH'] = str(MSYS_BIN) + os.pathsep + env.get('PATH', '')
|
|
src = (ROOT / 'runtime' / 'gfx_present.c').read_text(encoding='utf-8')
|
|
body = cut(src, 'void gfx_vsync_bind(uint64_t system_event) {', '\nvoid gfx_vsync_bind_counter(')
|
|
with tempfile.TemporaryDirectory(prefix='nx2navi-vsync-bind-') as tmp:
|
|
c = Path(tmp) / 'check.c'
|
|
c.write_text(HARNESS % body, encoding='utf-8')
|
|
exe = Path(tmp) / ('check.exe' if win else 'check')
|
|
subprocess.check_call([cc, '-O2', '-Wall', '-Wno-unused-function', str(c), '-o', str(exe)], env=env)
|
|
rc = subprocess.call([str(exe)])
|
|
p = subprocess.run([str(exe), 'full'], capture_output=True, text=True)
|
|
if p.returncode != 3 or 'more than 16 live vsync events' not in p.stdout:
|
|
print(f'FAIL: a 17th live vsync event must end with the fatal (exit {p.returncode}: {p.stdout.strip()})')
|
|
return 1
|
|
print('vsync bind full: ok (fatal with 16 live events)')
|
|
return rc
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main())
|