Skip to content

Check the action queue when scheduling auto save - #1254

Merged
GregHib merged 1 commit into
GregHib:mainfrom
HarleyGilpin:fix/autosave-scheduling
Sep 2, 2026
Merged

Check the action queue when scheduling auto save#1254
GregHib merged 1 commit into
GregHib:mainfrom
HarleyGilpin:fix/autosave-scheduling

Conversation

@HarleyGilpin

Copy link
Copy Markdown
Contributor

Fixes #1251.

AutoSave guarded its scheduling on World.contains("auto_save"). World is declared object World : Entity, VariableStore, Runnable, KoinComponent, so that resolves to VariableStore.contains(key) = variables.contains(key), reading a world variable rather than the actions map World.queue writes to. Nothing sets a variable by that name, so it was always false. The intended check is World.containsQueue (World.kt:48).

With auto save enabled, the else if branch fired on every ::reload settings, and actions is keyed by name:

fun queue(name: String, initialDelay: Int = 0, block: () -> Unit) {
    actions[name] = (GameLoop.tick + initialDelay) to block
}

so the existing entry was replaced with a fresh deadline rather than duplicated. Reloading settings more often than storage.autoSave.minutes meant auto save never ran. Easy to hit on a dev server while testing, and the first symptom is a rolled back save.

With it disabled, neither branch could fire, so World.clearQueue("auto_save") was unreachable and setting storage.autoSave.minutes to 0 at runtime left the queued action to fire once more before the chain ended on its own.

The queue check now sits in autoSave() rather than at the call site, so worldSpawn, settingsReload and the block's own rescheduling all go through it. Rescheduling from inside the queued block still works, since World.run() removes the entry before invoking it:

iterator.remove()
try {
    block.invoke()
}

AutoSaveTest covers the unreachable branch and fails without the change with Disabling auto save left the queue running.

I didn't add a test for the deadline being pushed out. World.actions is private, so observing the deadline needs either mockkObject(World) or a 100+ tick test driving real file IO, and neither seemed worth the flakiness for a defect that shares its root cause and fix with the one that is covered. Happy to add one if you'd rather have it.

Full suite green.

AutoSave guarded on World.contains("auto_save"). World is a VariableStore,
so that reads a world variable of that name rather than the action queue
World.queue writes to. Nothing sets such a variable, so it was always
false and the queue check was World.containsQueue all along.

Two effects. With auto save enabled every ::reload settings re-queued,
and since actions are keyed by name that replaced the entry with a fresh
deadline, so reloading more often than the interval meant auto save never
fired. With it disabled the clearQueue branch was unreachable, so turning
auto save off at runtime left the queued action to fire once more.

The queue check now lives in autoSave() so every caller is covered.
Rescheduling from inside the queued block still works because World.run()
removes the entry before invoking it.
@GregHib
GregHib merged commit 3c43841 into GregHib:main Sep 2, 2026
2 checks passed
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.

Autosave scheduling checks a world variable instead of the action queue, so ::reload settings pushes the deadline out

2 participants