feat(control): relaunch, the game started again over the bridge as the Insert menu's Restart now does #97
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
good first issue
gpu/amd
gpu/igpu
gpu/intel
gpu/nvidia
help wanted
kind
bug
kind
chore
kind
docs
kind
feature
kind
performance
kind
question
needs measurements
platform/linux
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!97
Loading…
Reference in a new issue
No description provided.
Delete branch "dyst/Longshot:feat/relaunch"
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?
Why
A test script that needs a fresh game process, to load a renderer built since or to start a measurement clean, has to quit the game and launch it again itself: the right environment, the saves, a new socket. This lets it ask the running game to start again, the way the Insert menu's Restart now does, and carry on over the same socket.
What changed
cmd_relaunchinhost/control.c(therelaunchcommand) replies first, lets go of its socket (transport_cleanup), then calls the game'srelaunch; a game without one answersunavailable.game_relaunchinhost/control.cdoes whatquitdoes first (rx_close_asked), then the Insert menu's Restart now path (hos_relaunch_ask,hos_window_closed_exitinhost/hos_relaunch.c): the same command and the environment the game started with, soHOS_CONTROLnames the same socket. Linux: anexecveunder the same pid; Windows: a new process that waits for the old one to end (HOS_RESTART_OF).HosControlGame.relaunch(host/control.h), added last.relaunch [--timeout 120]inscripts/longshot_ctl.py(relaunch()) waits until astatusanswers from the process started again: one whoseuptimeis no longer than the wait. The old process can answer for a moment after its reply, and is not taken for the new one (a bug the first version had; the tests now cover it). A connection the old process drops mid-request is tried again: on Windows the client can meet the old game's pipe as it closes, and the write fails withOSError(EINVAL), which the wait loop let through as a traceback (seen once in a wholerun_allwith the PC busy; fixed in the second commit).tests/control_test.candtests/test_control.py(a fake game's relaunch: the reply, the socket let go of, then its relaunch; refused by a game without one);tests/test_longshot_ctl.py(a stand-in that really starts again,os.execv, twice back to back; andtest_relaunch_through_a_dropped_connection, a bridge whose firststatusfails that way, which fails without the fix).Flags and settings
None (a bridge command and its client command; no switch, menu row or ini key).
Measurements
main:
d725b363(2026-11-13)this PR: feat/relaunch @ 84411238
Hardware: RTX 3080 (NVIDIA 621.14), Ryzen 9 3900X, 64 GB, Windows 11
Scene: from the title (the save's Continue) into play in Kokiri Forest; headless, muted; both builds from one work folder (only the host differs)
Method: two interleaved rounds each (main, PR, main, PR). main: what a script does today:
quit, wait for the process to end,launchagain, press A untilstatussays play. This PR:relaunch, then the same presses. Times from the script's clock, from the ask.How to reproduce: build both into their own game folders and copy a save in. main:
longshot_ctl.py launch <game> --headless, press A until play, thenquit,launchagain and press A to play. This PR: the same first launch, thenlongshot_ctl.py relaunchand press A to play.d725b363The bridge is back sooner; the time to play is the game's own start and the presses either way (one slow PR round; two rounds don't separate them). The point is that a script needs no launch logic of its own, not speed. Rebased since onto main
c67cd4a0: none of the commits sinced725b363touchhost/control.c,host/control.h,host/hos_relaunch.cor the client (the forge's scripts, releases, the builders, the website, shader preparation, a lift fix, menu rows), and the second commit changes only what the client does when a connection fails, so the numbers hold.Testing
Tested: on the Windows PC above: the relaunch in the game (two rounds, the old process logging "window closed for a restart", the new one serving the same pipe, Continue to Kokiri Forest, no errors in the logs);
tests/run_all.py, the whole suite, on this branch and on mainc67cd4a0(496 passed and 5 failed on both, no failure main doesn't have: test_builder_dlss_update, test_check_third_party, test_fpx_sticky, test_release and test_spawn_emit fail on main too);tests/test_control.pyandtests/test_longshot_ctl.pyalso on Linux (Docker, python:3.12 with gcc), three times on this branch's head.Not tested: the game's relaunch on Linux (an
execveof the same pid; the bridge and the client's sides ran there, the restart path itself is the Insert menu's, unchanged).Needs other testers:
longshot_ctl.py launch <game>, thenlongshot_ctl.py relaunch, expecting "relaunched: answering again after N s" and the game back at its title, the log saying "window closed for a restart" (T-C36M)Screenshots
Nothing on screen.
Risk and rollback
A script that sends
relaunchto a game whose restart fails leaves it exited, as the Insert menu's Restart now would ("restart: the game could not be started again" in the log). Nothing changes for players: the command needs the control bridge (HOS_CONTROL), which a player's game does not start. Reverting the two commits removes it.Reviewer notes
The dev-loop proposal's B1, its first part (https://github.com/NotRexed/Hookshot/pull/2636). The second part, a renderer built since taken at the next start (
HOS_DEV_DLL_SWAP,scripts/stage_renderer.py), is its own PR and uses this.Issues
See https://github.com/NotRexed/Hookshot/pull/2636
Checklist
For everyone
type(scope): description).main.python -I -B tests/run_all.pyrun and passing (any failure that also fails onmainis named above).third_party/untouched (a new folder with aPROVENANCE.mdinstead).If applicable
Assisted-by:/Co-Authored-By:trailers.mainand passing here.tests/that reads changed source updated in this PR, not weakened.🤖 Generated with Claude Code
Test request T-C36M opened for the Linux run (it needs the 'testing wanted' label to be claimed).
WIP: feat(control): relaunch, the game started again over the bridge as the Insert menu's Restart now doesto feat(control): relaunch, the game started again over the bridge as the Insert menu's Restart now does0fa72f49f08a323bf0b4Rebased onto main
c67cd4a0and a fix added: on Windows the relaunch client could meet the old game's pipe as it closed, and the write's OSError (EINVAL) ended relaunch in a traceback instead of a retry (seen once in a whole run_all with the PC busy). It now tries again; tests/test_longshot_ctl.py has a check for it that fails without the fix. Whole suite on this head and on main: 496 passed, 5 failed on both, no new failure; test_control.py and test_longshot_ctl.py three times on Linux. The description follows the template after #76 and #106 (only the If applicable boxes that apply, GitHub references as full URLs).View command line instructions
Manual merge helper
Use this merge commit message when completing the merge manually.
Checkout
From your project repository, check out a new branch and test the changes.