mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-05 08:10:40 -07:00
rawinput: handle midi scanning asynchronously, fix midi hotplug (#793)
## Link to GitHub Issue or related Pull Request, if one exists Fixes #603 ## Description of change Scanning for midi devices can take a while on Windows 11 with MIDI 2.0 service. Sometimes it'll take a couple seconds. Sometimes, on a PC with zero MIDI devices, it takes 10-11 seconds. This was causing two issues: 1. slow startup time 2. crash on invalid memory access due to a race condition Address both. Also fix a bug: when a device change event fires, we do a MIDI scan, and invalidated all existing MIDI devices in favor of creating new handles. Stop doing this, and instead check for duplicates by matching the ID and keep existing device handles alone. Properly clean up devices on unplug. Also: fix MIDI buttons being stuck on when unplugged while holding a key. ## Testing Tested with Nostroller in MIDI mode. rtpMIDI works too.
This commit is contained in:
@@ -3,6 +3,8 @@
|
||||
#include <functional>
|
||||
#include <thread>
|
||||
#include <condition_variable>
|
||||
#include <list>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include <windows.h>
|
||||
@@ -63,10 +65,38 @@ namespace rawinput {
|
||||
class RawInputManager {
|
||||
private:
|
||||
|
||||
HotplugManager *hotplug;
|
||||
std::vector<Device> devices;
|
||||
HotplugManager *hotplug = nullptr;
|
||||
|
||||
// std::list (not std::vector) so adding/removing a device never invalidates
|
||||
// pointers to other elements. devices_get() hands out pointers that escape the
|
||||
// lock and are dereferenced later by pollers, so a concurrent scan must not
|
||||
// relocate existing devices
|
||||
std::list<Device> devices;
|
||||
std::recursive_mutex devices_mutex;
|
||||
|
||||
WNDCLASSEX input_hwnd_class {};
|
||||
std::thread *input_thread = nullptr;
|
||||
std::thread *midi_thread = nullptr;
|
||||
|
||||
// guards the MIDI scan scheduler flags below. only held around the flag
|
||||
// checks, never across the (slow) enumeration, so it makes "is a scan
|
||||
// running?" and the worker's "exit or rescan?" a single atomic decision -
|
||||
// a request can never slip into the gap between the two
|
||||
std::mutex midi_scan_m;
|
||||
|
||||
// true while a scan worker thread is running. only one runs at a time
|
||||
bool midi_scan_active = false;
|
||||
|
||||
// set when a scan is requested while one is already running, so the worker
|
||||
// rescans once instead of dropping the request (events that arrive during
|
||||
// the slow initial scan would otherwise be lost)
|
||||
bool midi_scan_pending = false;
|
||||
|
||||
// MIDI handles pending close. devices_destruct() runs under devices_mutex but
|
||||
// midiInReset/midiInClose must not (they block on the MIDI callback, which
|
||||
// also takes devices_mutex), so handles are queued here and closed by
|
||||
// midi_close_deferred_flush() once the lock is released
|
||||
std::vector<HMIDIIN> midi_close_deferred;
|
||||
std::thread *flush_thread = nullptr;
|
||||
bool flush_thread_running = false;
|
||||
std::mutex flush_thread_m;
|
||||
@@ -83,6 +113,8 @@ namespace rawinput {
|
||||
void input_hwnd_create();
|
||||
void input_hwnd_destroy();
|
||||
void devices_reload();
|
||||
void midi_scan_join();
|
||||
void midi_close_deferred_flush();
|
||||
void devices_scan_rawinput(RAWINPUTDEVICELIST *device, bool log = true);
|
||||
void devices_scan_piuio();
|
||||
void devices_scan_smxstage();
|
||||
@@ -93,6 +125,7 @@ namespace rawinput {
|
||||
void flush_stop();
|
||||
void output_start();
|
||||
void output_stop();
|
||||
void devices_write_output_snapshot(bool only_updated);
|
||||
|
||||
static std::string rawinput_get_device_name(HANDLE hDevice);
|
||||
static std::string rawinput_get_device_description(const DeviceInfo& info, const std::string &device_name);
|
||||
@@ -118,6 +151,7 @@ namespace rawinput {
|
||||
|
||||
void devices_scan_rawinput(const std::string &device_name = "");
|
||||
void devices_scan_midi();
|
||||
void midi_scan_start();
|
||||
void devices_scan_xinput();
|
||||
void devices_remove(const std::string &name);
|
||||
|
||||
@@ -130,11 +164,12 @@ namespace rawinput {
|
||||
void __stdcall devices_print();
|
||||
Device *devices_get(const std::string &name, bool updated = false);
|
||||
|
||||
inline std::vector<Device> &devices_get() {
|
||||
inline std::list<Device> &devices_get() {
|
||||
return devices;
|
||||
}
|
||||
|
||||
inline std::vector<Device *> devices_get_updated() {
|
||||
std::lock_guard<std::recursive_mutex> lock(devices_mutex);
|
||||
std::vector<Device *> updated;
|
||||
for (auto &device : devices_get()) {
|
||||
device.mutex->lock();
|
||||
@@ -150,6 +185,7 @@ namespace rawinput {
|
||||
}
|
||||
|
||||
inline void devices_midi_freeze(bool freeze) {
|
||||
std::lock_guard<std::recursive_mutex> lock(devices_mutex);
|
||||
for (auto &device : devices_get()) {
|
||||
if (device.type == MIDI) {
|
||||
device.midiInfo->freeze = freeze;
|
||||
|
||||
Reference in New Issue
Block a user