feat(build): a terminal dashboard for the build's steps and files #425
No reviewers
Labels
No labels
area/accessibility
area/audio
area/input
area/install
area/menu
area/renderer
area/saves
area/upscaling
beanstalk: approved @ 1697603
beanstalk: approved @ 2c5a62e
beanstalk: approved @ 4fd82b7
beanstalk: approved @ 64d6d09
beanstalk: approved @ 6f91fae
beanstalk: approved @ 70a78f9
beanstalk: approved @ 8d361dc
beanstalk: approved @ 9d8356e
beanstalk: approved @ abf2227
beanstalk: approved @ d82f202
beanstalk: approved @ e5f1e40
beanstalk: approved @ ee8ab5f
ci: approved
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/macos
platform/steam-deck
platform/windows
preview
priority
critical
priority
high
priority
low
regression
status
blocked
status
confirmed
status
duplicate
status
invalid
status
needs-info
status
wontfix
testing wanted
ci: approved
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
MagicBeanDev/Longshot!425
Loading…
Reference in a new issue
No description provided.
Delete branch "suporte_de_cadeira/Longshot:feat/build-dashboard"
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 build started from the command line prints one line a file and says nothing about which file is compiling now, so a long first build is opaque: it is hard to tell whether it is stuck, how far the compile is, or which worker is on what. This adds a terminal dashboard that reads the
@@lines the builder already prints.What changed
nx2/build_ui.py: runsbuild.py --standalone(ormake_game.pywith--make); full screen on a terminal, plain lines elsewhere;--attachfollows a log another build writes and ends when the log says the build ended;--demoshows the screen without a game;Ctrl+Cstops the build and its compilers; no new dependencies.nx2/build.py: prints@@work <name> <MB> <worker>when a module's compiler starts, inside thetrywhosefinallyreleases the memory-gate lease, and writes every@@line under one lock so two compilers cannot interleave one;@@filestill says when it ends. This is the only new protocol line.nx2/make_game.py: the protocol documents@@work.tests/test_build_ui.py: the parser, the counts, the screen text, the full-screen redraw, more than six workers, two sources with one base name, the attached log's end andLONGSHOT_TIMES.tests/test_compiler_failure.pyandtests/test_compile_retry_jobs.py: their fixtures cutcompile_one, which now usesthreadingand prints inside itstry; the failure test also checks that a closed stdout releases the memory-gate lease, and that 200 threads get whole@@lines throughannounce.docs/development.md: the dashboard's command beside the standalone build.Flags and settings
The dashboard is a command-line tool; it adds no new environment variable, menu row or options-file key. It sets the existing
LONGSHOT_TIMESfor thebuild.pyit starts, asmake_game.pydoes, so the build reports its plan and per-file times; a value already in the environment is kept.LONGSHOT_TIMESbuild_ui.pypoints thebuild.pyit starts at<work>/build_times.jsonbuild_ui.pywhen it runsbuild.pyalone;make_game.pysets it itselfbuild.pyrun by hand (unchanged)Measurements
This is a build-time tool; the game does not run it. As CONTRIBUTING and AGENTS say for a game-free change, the game-free checks and the build they drive are the measurement. The full game build below is the end-to-end test.
main:
7ff5256e(2026-11-14)this PR: feat/build-dashboard @
5a2cbd6bHardware: Windows 11, RTX 3080 10 GB, Python 3.14.4, the pinned llvm-mingw 20261104; mooki's macOS 26.7.1 run on the Mac integration branch
Scene: the full game build from the installed dump (romfs + exefs), 643 files at 6 jobs; the built game's first start at 1280x720
Method:
tests/run_all.py, the dashboard's tests, the full build throughbuild_ui.py --make --jobs 6, and the built game's first startHow to reproduce:
python -I -B tests/test_build_ui.py;python -I -B tests/run_all.py;python -I nx2/build_ui.py --make --jobs 6 -- --romfs <dump>/romfs --exefs <dump>/exefs --out <folder> --work <work> --language en-US --compile-shaders most; then<folder>/longshot.exe7ff5256e5a2cbd6btests/run_all.pytests/test_build_ui.pytests/test_compiler_failure.pybuild_ui.pybuild_ui.py --makeTesting
Tested: Windows 11, Python 3.14.4.
tests/run_all.pygives 492 passed, 8 failed, 72 skipped in 148 s. The 8 also fail on unmodified main7ff5256e, each run alone:test_fpx_stickyandtest_spawn_emit(the a64emit build),test_bug_report(NoneTypehas nosplitlines),test_oot_state_tap(no game listens),test_tested_configurations,test_release(openssl missing),test_cpm_discovery, andtest_walk_key. The dashboard's tests pass, including the broken-pipe lease test, which fails on the old print placement and passes with it inside thetry.A real renderer build through
build_ui.py --plain --jobs 6compiled 205 files at 6 jobs, 0 failed, and linked;LONGSHOT_TIMESgave@@plan 205 205, 205@@work, 205@@fileand 206@@etalines, and--plainprinted no@@etachatter. The first such run gave 204@@workfor 205@@file: the workers' prints could interleave, soannounce()now writes every@@line under one lock, and a test starts 200 threads through it.--attachon that log ended at thelinkedline and reported the compile's 01:41 fromcompiled in 101s.A full game build through
build_ui.py --make --jobs 6, from the installed dump's romfs and exefs, ran read, lift, compile, finish and shaders: 643 files, 0 failed, 33:25 in all (compile 16:28), and the shaders' preparation built 24,453 pipelines in 430.7 s. It wrotelongshot.exe(454,863,360 bytes) andlongshot_vk.dll(13,630,464 bytes) into a new folder. The built game started and ran at 60.0 fps (600 presents, 1280x720); its screenshot is below. T-RVZR passed with this run.mooki's macOS run on the Mac integration branch built 212 renderer files and exercised the full screen, the six workers, a live resize,
Ctrl+Cand the plain output; its five defects are the fix commit's subject. The 16-row screenshot below repeats the full-screen case after the scroll fix.Not tested: an extended gameplay session beyond the first start (the field rendered at 60 fps; no save was played); Linux and macOS full game builds; Terminal.app and iTerm2 by eye; the SIGHUP/SIGTERM path on a real terminal (the handler is in place, and the broken-pipe lease test covers the leak it prevents).
Needs other testers: None.
Screenshots
The demo (no game files) at 110x34: the compile with six workers in flight, and the finished state. The third is the same demo in a 16-row terminal after the scroll fix: the title stays visible and the workers beyond the rows are counted.
The real build through
build_ui.py --make --jobs 6: the compiler with six workers, and[Compile ok]with 643/643 while the shaders prepare. The command line and log are cropped, so no local paths are shown.The built game's first start at 1280x720:
Risk and rollback
The dashboard is terminal-only and runs the existing builder; the game, the renderer and the build results are unchanged. The one new protocol line is additive, and a reader that does not know it ignores it. Reverting the commit is enough; there is no flag to turn off.
Reviewer notes
The fix commit
8bf1493caddresses the review's points in order: the@@workprint insidecompile_one'stryand SIGHUP/SIGTERM (1);LONGSHOT_TIMESfrombuild_ui.pywhen it runsbuild.pyalone (2); no newline after the last screen line (3);--attachends on@@done/@@error, abuild.pylog'slinkedline, or amake_game.py@@times, reads the compile time fromcompiled in Ns, says whatCtrl+Cdoes, and names a missing path (4); all workers shown or counted, one row cleared per@@filewhen two sources share a base name,--plainwithout@@etaand withbuild_ui:for its own lines, and the log underOOT_WORK(5). The last commit5a2cbd6bmakes each@@line one write after a clean build showed 204@@workfor 205 files. The broken-pipe test fails on the old placement and passes now. End-to-end, a full game build from the dump throughbuild_ui.py --make --jobs 6(643 files, 0 failed, 33:25, compile 16:28) produced the game that started at 60.0 fps; T-RVZR passed.Issues
None.
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
mainand passing here.tests/that reads changed source updated in this PR, not weakened.Added the dashboard's row to the development command table in docs/development.md (
0771bec5); no behavior change, and the screenshots and test results above still apply.I tested T-RVZR on a Mac, the only machine here that builds the game. In short: the six workers and their files
move, and Ctrl+C stops the build and all of its compilers, both full screen and piped. I found one real bug: closing
the terminal leaves
build.pyhung for ever, and the root cause is in the new@@workline. There are also a fewdisplay problems. Repros are below.
Platform
target 14.4, arm64.
pty.fork, 110×34,TERM=xterm-256color), driven by a script. Ctrl+C was the 0x03byte written to the pty, so the pty's line discipline delivered SIGINT exactly as a terminal does. I did not watch
it by eye in Terminal.app or iTerm2.
Base, and why
macos/integrated1c53cff1) plus thisPR's two commits, which cherry-picked cleanly.
commits between the Mac branch's base and
5e072fc8, and those are what conflict.build_ui.py --jobs 6, which runsbuild.py --standalone --jobs 6, withRX_VK_ONLY=1and a freshOOT_WORK. Every renderer source really compiles (212 files, 50–60 s at 6 jobs) andlongshot_vk.dyliblinks.The dashboard's code path is the same; only the files differ.
What worked
tests/test_build_ui.py,tests/test_compiler_failure.pyandtests/test_compile_retry_jobs.pypass on macOS.0.07–0.10 s).
psshowed six compilers in the build's process group most of the time.--demo), the nextredraws fit within half a second.
LONGSHOT_TIMESset, as make_game sets it:build done in 01:02 (compile 00:58).build cancelled in 00:21 (compile --:--), and the screen and cursor restored;returned), and nothing of the build was left at 1, 3 and 6 s;
.sha1stamps as objects (77/77, 29/29), so no half-written object is kept.exited 130 in 0.07 s, left nothing running, and kept 114/114 objects and stamps. The plain output is the
build_ui:command line, build.py's own lines and the summary.Defects
Closing the terminal window, or
kill <build_ui pid>, leavesbuild.pyrunning for ever.build.py is started in a session of its own (
start_new_session=True), so the terminal's hangup never reachesit, and
stop_treeruns only onKeyboardInterrupt.The compilers in flight finish their files (with lifted files that would be minutes and GBs each), then
build.py hangs. It was still there (parent 1, sleeping, its six executor threads waiting) 5 minutes after a
SIGHUP and 60 s after a SIGTERM, until I killed it.
Why it hangs: in
compile_one, the newprint("@@work …")comes afterGATE.enter(src)and before thetry:whose
finallycallsGATE.leave(lease). Once the reader of build.py's stdout is gone, the print raisesBrokenPipeError, and the memory-gate lease is never given back. After enough leaked leases,MemoryGate.enter()waits for ever, and theThreadPoolExecutorshutdown waits on it.Repro in a terminal: run
python -I nx2/build_ui.py --jobs 6; while it compiles, close the terminal window (orkill -TERMbuild_ui); then runpgrep -fl 'nx2/build.py --standalone'.Game-free repro, no compiler:
compile_one,MemoryGate,mem_limit_mb,mem_needandrss_mbfrom build.py, astests/test_compiler_failure.pytakescompile_one.LONGSHOT_MEM_LIMIT_GB=2and makesys.stdout.writeraiseBrokenPipeError.compile_onetwice on a.cppjob.Result: both leases stay held, and a third
GATE.enter()never returns. On the Mac branch without this PR,0 leases are held. With the print moved to the first line inside the
try:, 0 are held too. That makes a testthat fails on this branch and passes with the fix.
Suggested fix:
try:;Not run: I expect any other reader of build.py's output that goes away first (make_game ended alone, or a cancel
that ends make_game but not its children) to leave build.py hung the same way.
The test request's own command shows no counts and leaves stale rows.
@@plan,@@fileand@@etaonly whenLONGSHOT_TIMESis set. make_game sets it;build_ui.pydoes not.python -I nx2/build_ui.py --jobs 6:[Compile]never shows as current or done, while[Link]and[Finish]turn ok;--:--, and the summary sayscompile --:--;done, the six worker rows are still listed with the last file each worker compiled (aged 4–8 s), becauseonly
@@fileclears a row.LONGSHOT_TIMESpointing at a file that does not exist, all of this works (theLONGSHOT_TIMESrun above),and build.py does not create that file. Passing it from build_ui when it runs build.py itself would fix this.
The title row scrolls off once the screen is full.
Screen.drawwritesESC[K\nafter every line, including the last one.render_linesreturns exactlyheightlines: 34 lines at 34 rows in a realbuild.
(
Longshot build … elapsed … ETA) is never visible, and the bottom row is blank.redraws of a real build at 110×34 scrolled and lost the title, every redraw after the log filled the screen.
python -I -B nx2/build_ui.py --demoin a terminal 16 rows high.height - 1lines.--attachnever ends.build.pylog, which has no@@done, the status staysrunning.compile 00:12for a 58 s compile.Ctrl+C stops the build, but in attach mode Ctrl+C leaves the build alone.Smaller:
min(6, …)), and nothing says more are running. A game-free check onrender_lineswith 8 files in flight drew 6 rows and no mention of the other 2. On a 16-core Mac,--jobsabove 6 is likely.
opcodes.cpptwice ingpu/vk/eden;type.cppingpu/vk/edenandgpu/vk/sirit). So the@@fileof one clears both rows: 210 names were seenfor 212 files.
LONGSHOT_TIMES, abuild: eta Nline follows every file: 213 of the 436 lines. build.py's ownbuild: macOS arm64 (baseline ISA)line carries the samebuild:prefix as the dashboard's lines.<repo>/work/build_ui.logeven whenOOT_WORKpoints elsewhere.@@workunder make_game's Mac path: make_game prints every line build.py prints. So@@workshould reachbuild_ui.py --makeon the Mac too, and it should also appear raw in make_game's own console, beside the@@filelines that console already shows. This is from reading the source; I did not run
--make.Not tested
--make/ make_game.failed/again); real compiles at other--jobs;--no-color/NO_COLOR.tests/run_all.py(only the three tests above); Windows and Linux.80c5cb53e38bf1493cffReworked in
8bf1493cafter mooki's Mac run: @@work is inside compile_one's try, so a closed stdout releases the memory-gate lease, and build_ui stops the build on SIGHUP/SIGTERM as on Ctrl+C (the new test fails on the old placement); build_ui sets LONGSHOT_TIMES when it runs build.py alone, so the counts, ETA and cleared rows appear (a real 205-file renderer build gave plan 205 205, 0 failed); the last screen line has no newline, so a full screen keeps its title (the 16-row screenshot is in the description); --attach ends on @@done/@@error, a build.py linked line or make_game's @@times, reads the compile time from 'compiled in Ns', says Ctrl+C stops following, and names a missing path; all workers are shown or counted, duplicate base names clear one row, --plain drops @@eta and uses build_ui:, and the log follows OOT_WORK. Rebased onto7ff5256e; run_all is 492 passed, 8 failed (the same 8 on main), 72 skipped.WIP: feat(build): a terminal dashboard for the build's steps and filesto feat(build): a terminal dashboard for the build's steps and filesA clean 205-file build after the review fixes showed 205 @@file but 204 @@work: the workers' prints could interleave, and one line was lost. announce() now writes every @@ line under one lock; a clean build gives 205/205 and a test starts 200 threads through it. Head
5a2cbd6b; run_all is 492 passed, 8 failed (the same 8 on main7ff5256e), 72 skipped.Full end-to-end run passed on Windows: build_ui.py --make --jobs 6 from the installed dump built 643 files in 33:25 (compile 16:28), 0 failed, and prepared 24,453 shader pipelines; the built game started at 60.0 fps (600 presents, 1280x720). The description now has the real-build and game screenshots; T-RVZR passes.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.