Repository navigation
Let settings change and save while the game runs - #65
Merged
Merged
Conversation
ConfigChange(config) is the one entry point: it takes the settings as config.json would read them back, makes them current, saves the file and hands the old and new settings to the change hooks, which apply what changed. main.cpp's hook applies the host's settings live: master volume, the input section, the tick rate, interpolation, max_fps and show_fps, the present mode (gfx::SetPresentMode recreates the swapchain), the window's size and fullscreen state (WindowSetMode, through the same pixel-size event as a resize by hand), and aspect and ui_scale (gfx::SetFrameLayout at the next pump outside a frame). Only game.debug_mode waits for the next start, which ConfigAppliesOnRestart tells a settings screen. ConfigSave writes config.json.tmp and renames it over config.json, so a crash cannot leave a truncated file. A rewrite drops comments.
A failed save left the new settings current, so the same settings again returned true without writing. ConfigChange now tracks whether config.json holds the current settings and retries the save for equal settings, without running the hooks again. The hooks are run over a copy of the list, so one may add or remove hooks (they count from the next change), and a ConfigChange from inside a hook is refused, so the hooks after it never see settings other than the ones they were passed. Leaving fullscreen for a windowed size restores a maximized window first, which otherwise ignores the size and position, and the window calls report their failures. The debug chord's permission is latched from the config at start beside DebugMode, so game.debug_mode is wholly a next-start setting.
On Linux an ifstream opens a directory and reads it as empty, so ConfigLoad took a directory named config.json for a file holding the defaults and marked the settings saved. It now reads only a regular file; anything else falls back to the defaults and a save attempt, as a missing file does. The change test cleans up its hooks and directory even when an assertion stops it early.
Adubbz
added a commit
that referenced
this pull request
Oct 6, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The groundwork the settings menu needs:
config.jsoncan now change while the game runs, applies at once, and is saved safely. Four commits on currentport. Nothing underps2/changes, and there's no menu yet; the Options screen is the next PR, on top of this one.What it is
One entry point.
ConfigChange(Config)(port/src/platform/config.hpp) takes a wholeConfig, typicallyConfigGet()with some fields edited. It does five things:config.json: written toconfig.json.tmp, then renamed over the old file, so a crash can't leave half a file.A failed save is retried the next time the same settings come in.
ConfigChangeand the hooks run on the main thread only; a hook that callsConfigChangeis refused.Change hooks.
ConfigAddChangeHook/ConfigRemoveChangeHook. They run over a copy of the list, so a hook may remove itself. When the game's own options move intoconfig.json(the Options PR), they register a hook;ConfigChangedoesn't change.What applies live (
ApplyConfigChangeinmain.cpp, where start-up already applied these):InputApplyConfig, split out ofInputInit; physical key state survives a rebind);max_fpsandshow_fps;gfx::SetPresentModerecreates the swapchain as a resize does);WindowSetMode, the same placement asWindowInit; a maximised window is restored first);aspectandui_scale(gfx::SetFrameLayout, held until the next pump outside a frame).The detail and shadow distances already read live.
Restart only.
game.debug_modeapplies on the next start, andConfigAppliesOnRestartsays so. The debug-toggle chord now reads the value from start-up as well.--width,--heightand--show-fpsstill win over the file, and a headless window keeps its size.As you said: no migration and no version field. A saved
tick_rateof 50 still loads as 50, and that test is unchanged. Rewriting the file drops comments, whichdocs/PC.mdnow says. A separate commit fixesdocs/PC.mdwhere it still gave the tick rate as 50 Hz.Testing
Windows (Debug, clang 23): 102 of 102. Linux (clang 23): a clean build;
ctest95 of 95 on RADV and on lavapipe;truncations.py --checkpasses; Doxygen gives no new warning; the two recorded replays (a debug item grant, a dungeon floor) run to the end. The new testPlatformConfig.ChangeAppliesAndSavesruns in about 0.06 s. It covers:config.jsongoes, before and after load;.tmpbeing left behind;Changing settings live was exercised through a temporary hook in a running game, which is not part of this PR:
ui_scale1.5 and back;macOS is not built here.
Things you may want different
tick_rate50 stuck), but that is a separate policy.git clang-formatwants<nlohmann/json.hpp>ahead of the standard headers inconfig.cpp. That is the existing order onport; this only adds<utility>next to them, so I left it.