Skip to content

(feat) Retro Games - Native core thread safety, crash guards and input reliability enhancements - #1398

Draft
WizardOfYendor1 wants to merge 4 commits into
Moonfin-Client:mainfrom
WizardOfYendor1:feat/libretro-input-reliability
Draft

(feat) Retro Games - Native core thread safety, crash guards and input reliability enhancements#1398
WizardOfYendor1 wants to merge 4 commits into
Moonfin-Client:mainfrom
WizardOfYendor1:feat/libretro-input-reliability

Conversation

@WizardOfYendor1

Copy link
Copy Markdown
Contributor

Pull Request

Summary

Yet more major improvements in input reliability and analog mode correctness for software rendered native cores, plus thread safety hardening and an N64 core caused crash fix(s).

During testing and debugging I noticed that when hammering on a button, for example on the 2600 asteroids game, it would sometimes drop button presses - not something you'd notice in ordinary play of other games that you're not button mashing to the max. Tracing it led to reworking the "latch" logic. Basically, SOME times keypress states were becoming "merged" within a polling window. Adding instrumentation logging, I measured about 14% got dropped as a result with my old, slow fingers. The host now records one pending press and one pending release per button and advances at most one transition per frame. After the changes, I remeasured over multiple asteroids mash sessions, ALL press/release pairs were delivered and acknowledged with none expiring (aka dropped).

That work also exposed a wrong assumption I had or understood wrong anyways. The host taks for granted that input was latched and read on the same thread. Generally true however in the case of the N64 emulator (for example), if I tinkered with the emulator settings with threaded rendering, buttons when dead. The mupen64plus-next with its threaded renderer, reads input from a thread the host had never latched on. I refactored the published input state to using atomics, so a read on one thread can't lose an acknowledgment written on another, and the host now counts off-thread reads so the next core that does this is identifiable rather than non deterministic behavior happening. Hardware rendering (which I'm testing also :-)), exposes quite nicely. I believe using atomics was the right way to go here...but boy I'm really pushing my C knowledge/expertise here.

More rounds of N64 tinkering hard crashed moonfin outright - seg faulting it, repeatedly.Root cause was a missing environment callback: cores call GET_CLEAR_ALL_THREAD_WAITS_CB without a null check, and we didn't provide it, giving one fixes the crash (this was SO FUN to hunt down!).

Another REAL gem of finding was that thread IDs were climbing about sixty times a second. I really don't know how I didn't notice this before but... turns out the render thread was getting a brand new thread name at roughly 60x per second(!). Chased it down with logging/debugger etc and it was attaching and detaching our otherwise detached render thread once per posted frame, creating a fresh thread every time. There's now one render thread, "moonfin.retro", attached once for the session and detached when the loop ends, so it just finds it already attached and uses it. That removes a per-frame thread registration from the render path; I would EXPECT it to have some positive impact on GC or memory headroom, more so on "lighter" hardware, but that's unmeasured. Regardless, this is more "right" IMO (the get geometry pattern).

Plus bunches of tests to cover all of this.

In a nutshell this took a lot of iterative work with diagnostic logging. The temporary counters, diagnostics etc came out before my commits, but I left the off thread input read "detector" and the unhanded environment logging. i.e. if a core asks for something we don't provide, log it. Helps future issues with cores easier to hunt down.

Type of Change

  • Bug fix
  • New feature
  • Refactor
  • Performance improvement
  • UI/UX update
  • Documentation update
  • Build/CI change
  • Other (describe):

Platform

  • Android
  • iOS
  • tvOS
  • Web
  • macOS
  • Windows
  • Linux
  • All / Shared code

Testing

Live, interactive debugging/profiling with real devices (ADB). Playing real games, doing everything I could think of to push the devices over the edge.

  • Tested on emulator / simulator
  • Tested on physical device
  • Manual testing completed
  • Not tested (explain why):

Checklist

  • Code builds successfully
  • Code follows project style and conventions
  • No unnecessary commented-out code
  • No new warnings introduced

@WizardOfYendor1

Copy link
Copy Markdown
Contributor Author

I'm probably going to leave this in draft for a few days as I test more.... and get any feedback.

@github-actions

github-actions Bot commented Sep 4, 2026

Copy link
Copy Markdown

❌ Checks Failed

All platform builds passed, but the test run did not.

Tests ❌ 1 failed, 2048 passed

Failing tests
  • app: BouncingBox moves without rebuilding its child on every frame
Platform Status Artifact
Android ✅ Passed Moonfin_Android_v* + Moonfin_AndroidTV_v*
iOS ✅ Passed Moonfin_iOS_v*_unsigned.ipa
macOS ✅ Passed Moonfin_macOS_v*.dmg
tvOS ✅ Passed Moonfin_tvOS_v*_unsigned.ipa
Windows x64 ✅ Passed Moonfin_Windows_v*.exe
Windows ARM64 ✅ Passed Moonfin_WindowsARM64_v*.exe
Linux x64 ✅ Passed Moonfin_Linux_v* (deb/rpm/AppImage/snap/flatpak/tar.gz)
Linux ARM64 ✅ Passed Moonfin_LinuxARM64_v* (deb/rpm/AppImage/snap/flatpak/tar.gz)
Property Value
Commit bd47023
Workflow run Build #1346

…tency against its own low-latency request. Tweak to cut measured lag from 147ms to 115ms.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant