mirror of
https://github.com/hrydgard/ppsspp.git
synced 2026-11-02 12:32:03 +01:00
adhoc_session.py: Wait in emulated time, and start instances at a speed
wait, press --every, stick, type and until count the instances' emulated time (the slowest one's), so a script means the same at any speed, and start --speed PERCENT runs the instances sped up. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
acb2acf0b4
commit
1f42161f0e
1 file changed
+49
-14
+49
-14
@@ -11,9 +11,11 @@
|
||||
# python3 Tools/adhoc_session.py run script.txt (lines like the commands above, without the prefix)
|
||||
# python3 Tools/adhoc_session.py stop
|
||||
#
|
||||
# The instances run freely at PSP speed (--realtime, and --lag-sync unless --no-lag-sync), and each one
|
||||
# gets its own memory stick with a copy of the real one's savedata, so they can't overwrite each other's
|
||||
# (or your) saves. The firmware comes from the real memory stick's NAND.
|
||||
# The instances run freely at PSP speed (--realtime, and --lag-sync unless --no-lag-sync), or at
|
||||
# --speed PERCENT of it, and each one gets its own memory stick with a copy of the real one's savedata,
|
||||
# so they can't overwrite each other's (or your) saves. The firmware comes from the real memory stick's
|
||||
# NAND. Times in commands (wait, --every, --timeout, --poll) are emulated seconds, so a script runs the
|
||||
# same at any speed.
|
||||
|
||||
import argparse
|
||||
import base64
|
||||
@@ -162,6 +164,34 @@ def selected(session, which):
|
||||
return [inst for inst in session["instances"] if inst["n"] in wanted]
|
||||
|
||||
|
||||
def emulated_seconds(session):
|
||||
# The slowest instance's clock: the one a script is waiting for.
|
||||
times = []
|
||||
for inst in session["instances"]:
|
||||
dbg = Debugger(inst["port"])
|
||||
times.append(dbg.request("cpu.status")["us"] / 1e6)
|
||||
dbg.close()
|
||||
return min(times)
|
||||
|
||||
|
||||
def wall_seconds(session, emulated):
|
||||
# About how long `emulated` seconds take, to pace polling.
|
||||
return emulated * 100.0 / session.get("speed", 100)
|
||||
|
||||
|
||||
def emulated_sleep(session, seconds):
|
||||
# Script times are emulated seconds, so a script means the same at any --speed (and when the
|
||||
# instances run slower than asked). A clock that stops (a paused instance) ends it after a while.
|
||||
start = emulated_seconds(session)
|
||||
give_up = time.time() + wall_seconds(session, seconds) * 20 + 30
|
||||
while time.time() < give_up:
|
||||
left = seconds - (emulated_seconds(session) - start)
|
||||
if left <= 0:
|
||||
return
|
||||
time.sleep(min(0.05, max(0.005, wall_seconds(session, left))))
|
||||
print("wait: the emulated clock stopped advancing", file=sys.stderr)
|
||||
|
||||
|
||||
def find_headless():
|
||||
candidates = glob.glob(os.path.join(ROOT, "build*", "PPSSPPHeadless")) + glob.glob(os.path.join(ROOT, "build*", "PPSSPPHeadless.exe"))
|
||||
if not candidates:
|
||||
@@ -206,6 +236,8 @@ def cmd_start(args):
|
||||
"--appendconfig=" + ini, "--debugger-run=0", "--timeout-wall=%d" % args.timeout]
|
||||
if not args.no_lag_sync:
|
||||
cmd.append("--lag-sync")
|
||||
if args.speed != 100:
|
||||
cmd.append("--speed=%d" % args.speed)
|
||||
if n == 1:
|
||||
cmd.append("--adhoc-server")
|
||||
cmd.append("--nickname=" + (names[n - 1] if n <= len(names) else "PPSSPP %d" % n))
|
||||
@@ -232,7 +264,7 @@ def cmd_start(args):
|
||||
time.sleep(0.1)
|
||||
if inst["port"] is None:
|
||||
sys.exit("Instance %d didn't open a debugger port; see %s" % (inst["n"], inst["log"]))
|
||||
session = {"game": args.game, "instances": instances, "lagSync": not args.no_lag_sync, "p2p": args.p2p}
|
||||
session = {"game": args.game, "instances": instances, "lagSync": not args.no_lag_sync, "p2p": args.p2p, "speed": args.speed}
|
||||
with open(session_file(args), "w") as f:
|
||||
json.dump(session, f, indent=1)
|
||||
print("Started %d instances (%s), session in %s" % (args.n, "P2P" if args.p2p else "relay", args.dir))
|
||||
@@ -257,7 +289,7 @@ def cmd_press(args):
|
||||
session = load_session(args)
|
||||
for t in range(args.times):
|
||||
if t > 0:
|
||||
time.sleep(args.every)
|
||||
emulated_sleep(session, args.every)
|
||||
for inst in selected(session, args.i):
|
||||
dbg = Debugger(inst["port"])
|
||||
# The answer only comes once the press is over, so don't wait for it.
|
||||
@@ -284,7 +316,7 @@ def cmd_type(args):
|
||||
dbg = Debugger(inst["port"])
|
||||
dbg.send("input.buttons.press", button=button, duration=3)
|
||||
dbg.close()
|
||||
time.sleep(0.15)
|
||||
emulated_sleep(session, 0.15)
|
||||
|
||||
row, col = 0, 0
|
||||
for ch in args.text:
|
||||
@@ -317,7 +349,7 @@ def cmd_stick(args):
|
||||
dbg = Debugger(inst["port"])
|
||||
dbg.request("input.analog.send", x=args.x, y=args.y)
|
||||
dbg.close()
|
||||
time.sleep(args.seconds)
|
||||
emulated_sleep(session, args.seconds)
|
||||
for inst in insts:
|
||||
dbg = Debugger(inst["port"])
|
||||
dbg.request("input.analog.send", x=0, y=0)
|
||||
@@ -325,7 +357,7 @@ def cmd_stick(args):
|
||||
|
||||
|
||||
def cmd_wait(args):
|
||||
time.sleep(args.seconds)
|
||||
emulated_sleep(load_session(args), args.seconds)
|
||||
|
||||
|
||||
def grab_png(inst, attempts=3):
|
||||
@@ -415,24 +447,26 @@ def cmd_until(args):
|
||||
sys.exit("until needs a reference image or --pixel")
|
||||
session = load_session(args)
|
||||
waiting = list(selected(session, args.i))
|
||||
end = time.time() + args.timeout
|
||||
last_press = 0.0
|
||||
# --timeout, --every and --poll are emulated seconds, like wait's.
|
||||
start = emulated_seconds(session)
|
||||
last_press = None
|
||||
while waiting:
|
||||
if time.time() > end:
|
||||
now = emulated_seconds(session)
|
||||
if now - start > args.timeout:
|
||||
for inst in waiting:
|
||||
path = os.path.join(args.dir, "until_failed_%d.png" % inst["n"])
|
||||
with open(path, "wb") as f:
|
||||
f.write(grab_png(inst))
|
||||
sys.exit("until: instances %s never matched; their screens are in until_failed_N.png" % ",".join(str(i["n"]) for i in waiting))
|
||||
if args.press and time.time() - last_press >= args.every:
|
||||
if args.press and (last_press is None or now - last_press >= args.every):
|
||||
for inst in waiting:
|
||||
dbg = Debugger(inst["port"])
|
||||
dbg.send("input.buttons.press", button=args.press, duration=6)
|
||||
dbg.close()
|
||||
last_press = time.time()
|
||||
last_press = now
|
||||
waiting = [inst for inst in waiting if not screen_matches(args, Image.open(io.BytesIO(grab_png(inst))))]
|
||||
if waiting:
|
||||
time.sleep(args.poll)
|
||||
time.sleep(wall_seconds(session, args.poll))
|
||||
|
||||
|
||||
def cmd_crop(args):
|
||||
@@ -504,6 +538,7 @@ def build_parser():
|
||||
p.add_argument("--p2p", action="store_true", help="Talk directly instead of through the relay (needs a loopback address per instance)")
|
||||
p.add_argument("--names", help="Comma-separated nicknames for the instances (default: PPSSPP 1, PPSSPP 2, ...)")
|
||||
p.add_argument("--no-lag-sync", action="store_true", help="Pace once per frame, like the app's default")
|
||||
p.add_argument("--speed", type=int, default=100, help="Run at this percentage of PSP speed (scripts count emulated time)")
|
||||
p.add_argument("--state", help="Savestate to load at boot in every instance")
|
||||
p.add_argument("--memstick", default=DEFAULT_MEMSTICK, help="Memory stick to take the firmware and savedata from")
|
||||
p.add_argument("--graphics", default="vulkan")
|
||||
|
||||
Reference in new issue
Block a user