Skip to content

Device: run inside PyUI's process instead of launch.sh - #29

Merged
abalakh merged 1 commit into
spruceUI:masterfrom
abalakh:native_cheevos
Oct 30, 2026
Merged

abalakh merged 1 commit into
spruceUI:masterfrom
abalakh:native_cheevos

Conversation

@abalakh

@abalakh abalakh commented Oct 30, 2026 •

Copy link
Copy Markdown
Collaborator

Why

Opening Cheevos made PyUI exit so principal.sh could run launch.sh. That started a second Python and brought up a PyUI of Cheevos's own: display, theme and controller. When you closed it, PyUI started again from scratch. That's two PyUI start-ups per visit. launch.sh also had to copy App/PyUI/launch.sh's per-platform set-up (SDL libraries, drivers, device names), and our bootstrap carried workarounds for things PyUI only does in its own launcher.

spruceUI/spruceOS#1949 lets PyUI run an app inside its own process, like the Activity Tracker. Cheevos now opens from the menu that's already running and hands it back when it closes.

What changed

Entry point. config.json has "pyuiEntry": "cheevos.native:run" instead of "launch". PyUI imports cheevos.native from App/Cheevos and calls run() from the Apps menu, or run(game_id=N) for "View achievement progress", where B goes straight back to the game menu. When run() returns, PyUI drops every module loaded from the app's folder.

Leaving nothing behind. Unloading only frees memory if nothing in PyUI still refers to Cheevos, so native.session() takes out everything the app put in, in reverse order:

  • the texture budget on PyUI's caches (freed and replaced with plain caches);
  • the API-key redaction on the log record factory, which is process-wide;
  • the cheevos-* workers, which get 2 s to finish.

The app logs to Saves/spruce/cheevos-<platform>.log through its own handler, without propagating, so its records don't mix with PyUI's. An error is logged, then shown as "Cheevos stopped after an error. Details: " for 5 s, and you're back in Spruce's menu. If Cheevos can't even be imported, PyUI shows "The app stopped after an error." and logs it to pyui.log.

Spruce's Web API Key. The key is copied to that setting through PyUI's CfwSystemConfig (ui/pyui/host.py). launch.sh edited the file with jq, which PyUI, holding the settings in memory, would now overwrite.

Removed. launch.sh, cheevos/__main__.py, platform/arguments.py, ui/pyui/bootstrap.py and their tests. The device workarounds go with them, since PyUI's menus already do these: the Wi-Fi status warm-up, the Mini's backlight helper, and our own pyui-state.json. The desktop runner's PyUI start-up moved to platform/desktop/pyui_setup.py, which isn't packaged. The package now has no PyUI start-up code at all.

Dev launcher. make launch [GAME=<id>] opens Cheevos on a device the way the menus do, without tapping through them. device.sh saves the running PyUI's environment and command line from /proc, then asks it to exit. principal.sh runs scripts/device_session.py with PyUI's own interpreter, which restarts itself with that environment, brings PyUI up as mainui.py does, and calls PyUI's native_app.run. Nothing is per-platform. make stop closes it, and DEVICE_PYTHON_FLAGS passes flags such as -X importtime.

Release pipeline. Cheevos without launch.sh needs PyUI's runner, so the spruceos job checks the target branch for App/PyUI/main-ui/apps/native_app.py. It fails without opening a PR until that's merged; rerun the job afterwards (RELEASING.md).

Also changed

  • The empty /tmp/cheevos scratch folder is removed on exit.
  • Docs: .agents/pyui.md ("In-process sessions"), device.md, integration.md, product.md, TESTING.md, RELEASING.md, and the wiki's Installation and Troubleshooting pages.

Testing

make check passes (1,098 tests). tests/screens/test_native_sessions.py runs two sessions back to back in a desktop PyUI, unloads Cheevos as PyUI does, and checks that no Cheevos module, object, thread or PyUI hook is left. tests/unit/test_native.py covers errors, the log handler, the clean-up order, the key copy, the workers and the redaction.

On devices, with the matching Spruce change:

  • Miyoo Mini Plus (20-game account): opened from Apps and with make launch / make launch GAME=519. It opens in about 0.5 s and is back in the menu in about 0.1 s; the first open after an update takes about 3 s while Python compiles the bytecode. While open, it uses the same memory as the old separate process. After closing, PyUI keeps 6–7 MB more (Python's allocators hold freed memory for reuse), flat over six sessions in a row, and PyUI's restart before every game gives it back.
  • RG SP (BaseOS 1.4.0, Spruce 4.5.6): Apps twice and "View achievement progress" twice. That's about 0.9 s from A to the home screen and 0.2 s back to the list. Once a ROM is identified, the game opens in about 0.85 s; the first time adds about 2 s for identification. PyUI unloaded 72 modules after each Apps session and 76 after each game session, and none of Cheevos's threads were left in PyUI.

Merge spruceUI/spruceOS#NNNN first.

Opening Cheevos used to make PyUI exit so principal.sh could run
launch.sh, which started a second Python, brought up its own PyUI for
the platform, and left PyUI to start again from scratch on the way
back. With spruceUI/spruceOS#NNNN, PyUI runs the app in its own process
instead: config.json names an entry point ("pyuiEntry":
"cheevos.native:run"), PyUI calls it from the Apps menu, or with
game_id from the game menu's "View achievement progress", and unloads
every module from App/Cheevos when it returns.

- Add cheevos.native: run() logs to the app's own log and turns an
  error into a message; session() takes out everything the app puts
  into PyUI (texture budget, log redaction factory), waits for the
  cheevos-* workers, and copies the API key to Spruce's setting.
- Add ui/pyui/host.py for what a session asks of PyUI. Spruce's Web
  API Key setting is now changed through PyUI's CfwSystemConfig:
  launch.sh's jq edit would be overwritten by PyUI's copy in memory.
- Remove launch.sh, cheevos/__main__.py, platform/arguments.py and
  ui/pyui/bootstrap.py, with its device workarounds (Wi-Fi status
  warm-up, the Mini's backlight helper, our own PyUI state file). The
  desktop runner's PyUI start-up moves to
  platform/desktop/pyui_setup.py.
- make launch [GAME=id] opens Cheevos on a device the way the menus
  do: scripts/device_session.py (dev only, not packaged) restarts with
  the running PyUI's environment and calls PyUI's own runner.
- CI's spruceos job refuses to open the mirror PR until the target
  branch has PyUI's runner (App/PyUI/main-ui/apps/native_app.py).
- tests/screens/test_native_sessions.py runs two sessions in a desktop
  PyUI and checks that no Cheevos module, object, thread or hook is
  left behind.

On a Miyoo Mini Plus, Cheevos opens in about 0.5 s and closes in about
0.1 s. After closing, PyUI keeps 6-7 MB more, which stayed flat over
six sessions.
@abalakh
abalakh merged commit 25f37ca into spruceUI:master Oct 30, 2026
4 checks passed
@abalakh
abalakh deleted the native_cheevos branch October 30, 2026 20:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant