Skip to content

hotkeys: don't query lua viewscreen selections on the render thread - #5908

Merged
ab9rf merged 5 commits into
DFHack:developfrom
Alistair-Afton:hotkey-render-thread
Sep 16, 2026
Merged

ab9rf merged 5 commits into
DFHack:developfrom
Alistair-Afton:hotkey-render-thread

Conversation

@Alistair-Afton

Copy link
Copy Markdown
Contributor

Summary

Fixes #5835.

When a keypress matches a bound hotkey, Plugin::can_invoke_hotkey runs the command's guard on DF's render thread before the command is queued. Guards that inspect the UI selection (Gui::any_item_hotkey, workshop_job_hotkey, etc.) call getSelected*() on the topmost viewscreen, and when that screen is a dfhack_lua_viewscreen the virtual enters Lua through CoreSuspender, which can never be acquired on the render thread. The result was an assertion failure in debug builds and a hang in production, e.g. with gui/reveal open but unfocused.

This change routes the selection query through a helper that checks the calling thread first:

  • On the render thread, a focused lua screen owns the selection, so we report none.
  • A defocused lua screen is transparent to the UI below, so the native checks still apply, which keeps hotkeys working against the underlying screen (e.g. during a designation with an unfocused overlay).
  • Off the render thread (command handlers running under the suspender) and for C++ plugin screens, the getSelected*() virtual is called as before.

Also adds Core::isRenderThread() so the check has a clear name.

Guards that never inspect the selection (anywhere_hotkey, default_hotkey, focus-based guards) are unaffected, as is Gui::getFocusStrings/matchFocusString (isFocused/getFocusString are pure C++ on lua screens).

Testing

  • Built and installed locally (win64, DF 53.16).
  • Reproduced the issue's setup in a live fort: opened gui/reveal, defocused it (focus resolves to dwarfmode/... through the screen), and pressed Ctrl-K (autodump-destroy-item). No assert, no hang; the key fell through to the screen as expected.
  • Confirmed a test hotkey bound to @dwarfmode still dispatched through the defocused lua screen.
  • Previously, the same sequence tripped the CoreSuspenderBase assertion (or hung, waiting on CoreSuspendMutex from the render thread).

Hotkey guards run on DF's render thread when validating a keypress, and
getAny*() would call getSelected*() on the topmost viewscreen. For a
lua-backed screen that virtual enters Lua through CoreSuspender, which
can never be acquired on the render thread, so pressing a hotkey while
a DFHack window was open asserted or deadlocked.

Skip the lua virtual on the render thread instead: a focused lua screen
owns the selection so report none, while a defocused one is transparent
and the native checks below still apply. Guards that don't inspect the
selection, and non-hotkey callers on other threads, are unaffected.

Fixes DFHack#5835
Comment thread library/modules/Gui.cpp Outdated
// still apply.
template<typename T>
static std::optional<T *> dfscreen_selection(df::viewscreen *top,
T * (dfhack_viewscreen::*method)())

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a reason why this template is restricted to returning a pointer?

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@ab9rf No good reason. It only needed the pointer return so the focused case could yield nullptr as the no selection answer, but deducing R directly does that just as well. Generalized it.

@ab9rf
ab9rf merged commit db8a783 into DFHack:develop Sep 16, 2026
14 checks passed
@Alistair-Afton
Alistair-Afton deleted the hotkey-render-thread branch September 16, 2026 01:52
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.

autodump hotkeys can run without acquiring a coresuspender under some conditions

2 participants