mirror of
https://github.com/spice2x/spice2x.github.io.git
synced 2026-08-01 14:20:42 -07:00
rawinput: avoid deadlock when binding analog axis as a button (#809)
## Link to GitHub Issue or related Pull Request, if one exists Regressed by #793 ## Description of change Due to lock inversion, when binding an analog axis as a button, spice deadlocks. Fix that. Also create a separate `unordered_map` that keeps track of device handles so that `WM_INPUT` handle can look up devices without having to acquire the larger `devices_mutex` which could be held by (potentially) lengthy operations like hotplug. Fix more synchronization issues around hotplug. Latent bug exposed by MIDI 2.0 issues. ## Testing
This commit is contained in:
@@ -626,6 +626,7 @@ set(SOURCE_FILES ${SOURCE_FILES}
|
|||||||
|
|
||||||
# rawinput
|
# rawinput
|
||||||
rawinput/rawinput.cpp
|
rawinput/rawinput.cpp
|
||||||
|
rawinput/rawinput_handles.cpp
|
||||||
rawinput/midi.cpp
|
rawinput/midi.cpp
|
||||||
rawinput/sextet.cpp
|
rawinput/sextet.cpp
|
||||||
rawinput/piuio.cpp
|
rawinput/piuio.cpp
|
||||||
|
|||||||
@@ -1356,8 +1356,9 @@ namespace overlay::windows {
|
|||||||
if (check_devices) {
|
if (check_devices) {
|
||||||
// iterate updated devices
|
// iterate updated devices
|
||||||
auto updated_devices = RI_MGR->devices_get_updated();
|
auto updated_devices = RI_MGR->devices_get_updated();
|
||||||
|
bool binding_finished = false;
|
||||||
for (auto device : updated_devices) {
|
for (auto device : updated_devices) {
|
||||||
std::lock_guard<std::mutex> lock(*device->mutex);
|
std::unique_lock<std::mutex> lock(*device->mutex);
|
||||||
switch (device->type) {
|
switch (device->type) {
|
||||||
case rawinput::MOUSE: {
|
case rawinput::MOUSE: {
|
||||||
auto mouse = device->mouseInfo;
|
auto mouse = device->mouseInfo;
|
||||||
@@ -1374,7 +1375,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1393,7 +1394,7 @@ namespace overlay::windows {
|
|||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
buttons_many_index = -1;
|
buttons_many_index = -1;
|
||||||
buttons_many_active = false;
|
buttons_many_active = false;
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1406,7 +1407,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1542,7 +1543,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
break;
|
break;
|
||||||
@@ -1590,7 +1591,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1614,7 +1615,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1641,7 +1642,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1668,7 +1669,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@@ -1694,7 +1695,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1715,7 +1716,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1745,7 +1746,7 @@ namespace overlay::windows {
|
|||||||
ImGui::CloseCurrentPopup();
|
ImGui::CloseCurrentPopup();
|
||||||
buttons_bind_active = false;
|
buttons_bind_active = false;
|
||||||
inc_buttons_many_index(button_it_max);
|
inc_buttons_many_index(button_it_max);
|
||||||
RI_MGR->devices_midi_freeze(false);
|
binding_finished = true;
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -1755,6 +1756,13 @@ namespace overlay::windows {
|
|||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
lock.unlock();
|
||||||
|
if (binding_finished) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (binding_finished) {
|
||||||
|
RI_MGR->devices_midi_freeze(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -197,8 +197,8 @@ namespace rawinput {
|
|||||||
HANDLE handle = INVALID_HANDLE_VALUE;
|
HANDLE handle = INVALID_HANDLE_VALUE;
|
||||||
DeviceType type = UNKNOWN;
|
DeviceType type = UNKNOWN;
|
||||||
DeviceInfo info;
|
DeviceInfo info;
|
||||||
std::mutex *mutex;
|
std::mutex *mutex = nullptr;
|
||||||
std::mutex *mutex_out;
|
std::mutex *mutex_out = nullptr;
|
||||||
bool updated = true;
|
bool updated = true;
|
||||||
bool output_pending = true;
|
bool output_pending = true;
|
||||||
bool output_enabled = false;
|
bool output_enabled = false;
|
||||||
@@ -213,5 +213,7 @@ namespace rawinput {
|
|||||||
double input_time = 0.0;
|
double input_time = 0.0;
|
||||||
double input_hz = 0.f;
|
double input_hz = 0.f;
|
||||||
double input_hz_max = 0.f;
|
double input_hz_max = 0.f;
|
||||||
|
|
||||||
|
// when adding or removing a field, update replace_device_slot to match
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -238,8 +238,6 @@ void rawinput::RawInputManager::devices_scan_midi() {
|
|||||||
midi_device.name = midi_identifier;
|
midi_device.name = midi_identifier;
|
||||||
midi_device.desc = to_string(midi_device_caps.szPname);
|
midi_device.desc = to_string(midi_device_caps.szPname);
|
||||||
midi_device.info = midi_device_info;
|
midi_device.info = midi_device_info;
|
||||||
midi_device.mutex = new std::mutex();
|
|
||||||
midi_device.mutex_out = new std::mutex();
|
|
||||||
midi_device.midiInfo = midi_device_midi_info;
|
midi_device.midiInfo = midi_device_midi_info;
|
||||||
|
|
||||||
// mutate the shared device list under lock (the slow WinMM calls above
|
// mutate the shared device list under lock (the slow WinMM calls above
|
||||||
@@ -256,10 +254,9 @@ void rawinput::RawInputManager::devices_scan_midi() {
|
|||||||
// carry over ID
|
// carry over ID
|
||||||
midi_device.id = device.id;
|
midi_device.id = device.id;
|
||||||
|
|
||||||
// destruct and replace, reusing the slot's existing mutexes
|
// destruct and replace the slot in place under its locks
|
||||||
this->devices_destruct(&device);
|
this->devices_destruct(&device);
|
||||||
reuse_device_mutexes(midi_device, device);
|
replace_device_slot(device, midi_device);
|
||||||
device = midi_device;
|
|
||||||
|
|
||||||
// notify change
|
// notify change
|
||||||
for (auto &cb : this->callback_change) {
|
for (auto &cb : this->callback_change) {
|
||||||
@@ -275,6 +272,8 @@ void rawinput::RawInputManager::devices_scan_midi() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add device to list
|
// add device to list
|
||||||
|
midi_device.mutex = new std::mutex();
|
||||||
|
midi_device.mutex_out = new std::mutex();
|
||||||
auto &device = this->devices.emplace_back(midi_device);
|
auto &device = this->devices.emplace_back(midi_device);
|
||||||
|
|
||||||
// notify add
|
// notify add
|
||||||
|
|||||||
@@ -44,18 +44,40 @@ namespace rawinput {
|
|||||||
bool OS_WINDOW_ACTIVE = false;
|
bool OS_WINDOW_ACTIVE = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// when replacing a device slot in place, keep the old slot's per-device mutexes
|
// serialize replacement with input and output users, matching teardown lock order
|
||||||
// instead of the freshly allocated pair on `replacement`. their addresses stay
|
void rawinput::RawInputManager::replace_device_slot(Device &existing, Device &replacement) {
|
||||||
// stable, so a thread still holding a snapshot pointer to the slot (e.g. the
|
if (existing.mutex == nullptr || existing.mutex_out == nullptr) {
|
||||||
// output thread blocked on mutex_out, which is taken without devices_mutex)
|
log_fatal("rawinput", "existing device slot has no mutexes");
|
||||||
// never locks freed memory. the freshly allocated pair is freed here rather
|
}
|
||||||
// than leaking the old one. the slot must already be destructed so both
|
if (replacement.mutex != nullptr || replacement.mutex_out != nullptr) {
|
||||||
// mutexes are unlocked
|
log_fatal("rawinput", "replacement device already has mutexes");
|
||||||
void rawinput::RawInputManager::reuse_device_mutexes(Device &replacement, const Device &existing) {
|
}
|
||||||
delete replacement.mutex;
|
std::lock_guard<std::mutex> lock_out(*existing.mutex_out);
|
||||||
delete replacement.mutex_out;
|
std::lock_guard<std::mutex> lock(*existing.mutex);
|
||||||
replacement.mutex = existing.mutex;
|
|
||||||
replacement.mutex_out = existing.mutex_out;
|
// copy every field except the stable mutex pointers: snapshot users may be blocked
|
||||||
|
// on those exact locks without devices_mutex, so rewriting the pointers could send a
|
||||||
|
// waiter to a different mutex. when adding a field, add it here too (see device.h)
|
||||||
|
existing.id = replacement.id;
|
||||||
|
existing.name = std::move(replacement.name);
|
||||||
|
existing.desc = std::move(replacement.desc);
|
||||||
|
existing.handle = replacement.handle;
|
||||||
|
existing.type = replacement.type;
|
||||||
|
existing.info = replacement.info;
|
||||||
|
existing.updated = replacement.updated;
|
||||||
|
existing.output_pending = replacement.output_pending;
|
||||||
|
existing.output_enabled = replacement.output_enabled;
|
||||||
|
existing.mouseInfo = replacement.mouseInfo;
|
||||||
|
existing.keyboardInfo = replacement.keyboardInfo;
|
||||||
|
existing.hidInfo = replacement.hidInfo;
|
||||||
|
existing.midiInfo = replacement.midiInfo;
|
||||||
|
existing.sextetInfo = replacement.sextetInfo;
|
||||||
|
existing.piuioDev = replacement.piuioDev;
|
||||||
|
existing.smxstageInfo = replacement.smxstageInfo;
|
||||||
|
existing.smxdedicabInfo = replacement.smxdedicabInfo;
|
||||||
|
existing.input_time = replacement.input_time;
|
||||||
|
existing.input_hz = replacement.input_hz;
|
||||||
|
existing.input_hz_max = replacement.input_hz_max;
|
||||||
}
|
}
|
||||||
|
|
||||||
rawinput::RawInputManager::RawInputManager() {
|
rawinput::RawInputManager::RawInputManager() {
|
||||||
@@ -277,8 +299,6 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
new_device.name = device_name;
|
new_device.name = device_name;
|
||||||
new_device.desc = device_description;
|
new_device.desc = device_description;
|
||||||
new_device.info = device_info;
|
new_device.info = device_info;
|
||||||
new_device.mutex = new std::mutex();
|
|
||||||
new_device.mutex_out = new std::mutex();
|
|
||||||
new_device.input_time = get_performance_seconds();
|
new_device.input_time = get_performance_seconds();
|
||||||
switch (device->dwType) {
|
switch (device->dwType) {
|
||||||
case RIM_TYPEMOUSE:
|
case RIM_TYPEMOUSE:
|
||||||
@@ -805,10 +825,10 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
// carry over old device ID
|
// carry over old device ID
|
||||||
new_device.id = prev_device.id;
|
new_device.id = prev_device.id;
|
||||||
|
|
||||||
// destruct and replace, reusing the slot's existing mutexes
|
// destruct and replace the slot in place under its locks
|
||||||
this->devices_destruct(&prev_device);
|
this->devices_destruct(&prev_device);
|
||||||
reuse_device_mutexes(new_device, prev_device);
|
replace_device_slot(prev_device, new_device);
|
||||||
prev_device = new_device;
|
this->rawinput_handles.add(&prev_device);
|
||||||
|
|
||||||
// notify change
|
// notify change
|
||||||
for (auto &cb : this->callback_change) {
|
for (auto &cb : this->callback_change) {
|
||||||
@@ -820,7 +840,10 @@ void rawinput::RawInputManager::devices_scan_rawinput(RAWINPUTDEVICELIST *device
|
|||||||
}
|
}
|
||||||
|
|
||||||
// add device to list
|
// add device to list
|
||||||
|
new_device.mutex = new std::mutex();
|
||||||
|
new_device.mutex_out = new std::mutex();
|
||||||
auto &added_device = this->devices.emplace_back(new_device);
|
auto &added_device = this->devices.emplace_back(new_device);
|
||||||
|
this->rawinput_handles.add(&added_device);
|
||||||
if (log) {
|
if (log) {
|
||||||
log_info("rawinput", "added device: {} / {}", added_device.desc, added_device.name);
|
log_info("rawinput", "added device: {} / {}", added_device.desc, added_device.name);
|
||||||
}
|
}
|
||||||
@@ -951,8 +974,6 @@ void rawinput::RawInputManager::devices_scan_xinput() {
|
|||||||
device.name = xinput::get_device_desc(player);
|
device.name = xinput::get_device_desc(player);
|
||||||
device.desc = fmt::format("XInput Gamepad P{}", player + 1);
|
device.desc = fmt::format("XInput Gamepad P{}", player + 1);
|
||||||
device.handle = reinterpret_cast<HANDLE>(player);
|
device.handle = reinterpret_cast<HANDLE>(player);
|
||||||
device.mutex = new std::mutex();
|
|
||||||
device.mutex_out = new std::mutex();
|
|
||||||
return device;
|
return device;
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -969,11 +990,10 @@ void rawinput::RawInputManager::devices_scan_xinput() {
|
|||||||
log_info("rawinput", "overwriting previously destroyed XInput device: {}", prev_device.name);
|
log_info("rawinput", "overwriting previously destroyed XInput device: {}", prev_device.name);
|
||||||
const auto old_id = prev_device.id;
|
const auto old_id = prev_device.id;
|
||||||
|
|
||||||
// replace in place, reusing the slot's existing mutexes
|
// replace the slot in place under its locks
|
||||||
auto replacement = create_device(player);
|
auto replacement = create_device(player);
|
||||||
reuse_device_mutexes(replacement, prev_device);
|
replacement.id = old_id;
|
||||||
prev_device = replacement;
|
replace_device_slot(prev_device, replacement);
|
||||||
prev_device.id = old_id;
|
|
||||||
|
|
||||||
// notify change
|
// notify change
|
||||||
for (auto &cb : this->callback_change) {
|
for (auto &cb : this->callback_change) {
|
||||||
@@ -989,6 +1009,8 @@ void rawinput::RawInputManager::devices_scan_xinput() {
|
|||||||
log_info("rawinput", "adding new XInput device: player {}", player + 1);
|
log_info("rawinput", "adding new XInput device: player {}", player + 1);
|
||||||
auto new_xinput_device = create_device(player);
|
auto new_xinput_device = create_device(player);
|
||||||
new_xinput_device.id = this->devices.size() + 1;
|
new_xinput_device.id = this->devices.size() + 1;
|
||||||
|
new_xinput_device.mutex = new std::mutex();
|
||||||
|
new_xinput_device.mutex_out = new std::mutex();
|
||||||
auto &device = this->devices.emplace_back(new_xinput_device);
|
auto &device = this->devices.emplace_back(new_xinput_device);
|
||||||
|
|
||||||
// notify add
|
// notify add
|
||||||
@@ -1438,6 +1460,8 @@ void rawinput::RawInputManager::devices_destruct() {
|
|||||||
|
|
||||||
void rawinput::RawInputManager::devices_destruct(Device *device, bool log) {
|
void rawinput::RawInputManager::devices_destruct(Device *device, bool log) {
|
||||||
|
|
||||||
|
this->rawinput_handles.remove(device);
|
||||||
|
|
||||||
// check if destroyed
|
// check if destroyed
|
||||||
if (device->type == DESTROYED) {
|
if (device->type == DESTROYED) {
|
||||||
return;
|
return;
|
||||||
@@ -1454,21 +1478,20 @@ void rawinput::RawInputManager::devices_destruct(Device *device, bool log) {
|
|||||||
// devices_mutex, so mutex_out is what actually serializes it against us
|
// devices_mutex, so mutex_out is what actually serializes it against us
|
||||||
std::lock_guard<std::mutex> lock_out(*device->mutex_out);
|
std::lock_guard<std::mutex> lock_out(*device->mutex_out);
|
||||||
|
|
||||||
// mark as destroyed
|
// mark the device destroyed while synchronized with in-flight input
|
||||||
auto device_type = device->type;
|
DeviceType device_type;
|
||||||
device->type = DESTROYED;
|
{
|
||||||
|
std::lock_guard<std::mutex> lock(*device->mutex);
|
||||||
|
device_type = device->type;
|
||||||
|
device->type = DESTROYED;
|
||||||
|
}
|
||||||
|
|
||||||
// notify change
|
// callbacks may lock the device mutex
|
||||||
for (auto &cb : this->callback_change) {
|
for (auto &cb : this->callback_change) {
|
||||||
cb.f(cb.data, device);
|
cb.f(cb.data, device);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
// wait for input users before releasing device resources
|
||||||
* lock device
|
|
||||||
* note: this is an exception to only locking devices when we acquire them from the list
|
|
||||||
* callbacks could want to lock the mutex as well and it isn't recursive
|
|
||||||
* this also means the device must be unlocked before calling this function
|
|
||||||
*/
|
|
||||||
std::lock_guard<std::mutex> lock(*device->mutex);
|
std::lock_guard<std::mutex> lock(*device->mutex);
|
||||||
|
|
||||||
// close device handles
|
// close device handles
|
||||||
@@ -1561,21 +1584,13 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
|
|
||||||
// find device
|
// find device
|
||||||
HANDLE device_handle = data->header.hDevice;
|
HANDLE device_handle = data->header.hDevice;
|
||||||
// lock the device list so a concurrent scan can't mutate it while we iterate
|
auto acquired_device = ref->rawinput_handles.acquire(device_handle);
|
||||||
std::lock_guard<std::recursive_mutex> devices_lock(ref->devices_mutex);
|
if (acquired_device.device != nullptr) {
|
||||||
for (auto &device : ref->devices_get()) {
|
auto &device = *acquired_device.device;
|
||||||
|
|
||||||
// skip if this is the wrong device
|
|
||||||
if (device.handle != device_handle) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
// get input time
|
// get input time
|
||||||
const auto input_time = get_performance_seconds();
|
const auto input_time = get_performance_seconds();
|
||||||
|
|
||||||
// lock device
|
|
||||||
device.mutex->lock();
|
|
||||||
|
|
||||||
// update hz
|
// update hz
|
||||||
double diff_time = input_time - device.input_time;
|
double diff_time = input_time - device.input_time;
|
||||||
if (diff_time > 0.0001) {
|
if (diff_time > 0.0001) {
|
||||||
@@ -1834,7 +1849,7 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
|
|
||||||
USAGE usage = usages[usage_num] - button_caps.Range.UsageMin;
|
USAGE usage = usages[usage_num] - button_caps.Range.UsageMin;
|
||||||
|
|
||||||
// guard against some buggy device sending an event for a usage below `UsageMin`
|
// guard against some buggy device sending an event for a usage below UsageMin
|
||||||
if (usage < button_count) {
|
if (usage < button_count) {
|
||||||
new_states[usage] = true;
|
new_states[usage] = true;
|
||||||
}
|
}
|
||||||
@@ -1937,11 +1952,7 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// free device
|
acquired_device.lock.unlock();
|
||||||
device.mutex->unlock();
|
|
||||||
|
|
||||||
// don't iterate through the other devices
|
|
||||||
break;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// update controller state ring buffers (DDR/MDXF)
|
// update controller state ring buffers (DDR/MDXF)
|
||||||
@@ -1973,6 +1984,9 @@ LRESULT CALLBACK rawinput::RawInputManager::input_wnd_proc(
|
|||||||
|
|
||||||
void rawinput::RawInputManager::device_write_output(Device *device, bool only_updated) {
|
void rawinput::RawInputManager::device_write_output(Device *device, bool only_updated) {
|
||||||
|
|
||||||
|
// lock device
|
||||||
|
std::lock_guard<std::mutex> lock(*device->mutex_out);
|
||||||
|
|
||||||
// check if output is enabled
|
// check if output is enabled
|
||||||
if (!device->output_enabled) {
|
if (!device->output_enabled) {
|
||||||
return;
|
return;
|
||||||
@@ -1983,9 +1997,6 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// lock device
|
|
||||||
device->mutex_out->lock();
|
|
||||||
|
|
||||||
// mark device as updated
|
// mark device as updated
|
||||||
device->output_pending = false;
|
device->output_pending = false;
|
||||||
|
|
||||||
@@ -2208,8 +2219,6 @@ void rawinput::RawInputManager::device_write_output(Device *device, bool only_up
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
// unlock device
|
|
||||||
device->mutex_out->unlock();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void rawinput::RawInputManager::devices_flush_output(bool optimized) {
|
void rawinput::RawInputManager::devices_flush_output(bool optimized) {
|
||||||
|
|||||||
@@ -12,6 +12,7 @@
|
|||||||
|
|
||||||
#include "device.h"
|
#include "device.h"
|
||||||
#include "hotplug.h"
|
#include "hotplug.h"
|
||||||
|
#include "rawinput_handles.h"
|
||||||
#include "rawinput/xinput.h"
|
#include "rawinput/xinput.h"
|
||||||
#include "util/scope_guard.h"
|
#include "util/scope_guard.h"
|
||||||
|
|
||||||
@@ -74,6 +75,9 @@ namespace rawinput {
|
|||||||
std::list<Device> devices;
|
std::list<Device> devices;
|
||||||
std::recursive_mutex devices_mutex;
|
std::recursive_mutex devices_mutex;
|
||||||
|
|
||||||
|
// separate lookup isolated from devices_mutex for the latency-sensitive WM_INPUT path
|
||||||
|
RawInputHandles rawinput_handles;
|
||||||
|
|
||||||
WNDCLASSEX input_hwnd_class {};
|
WNDCLASSEX input_hwnd_class {};
|
||||||
std::thread *input_thread = nullptr;
|
std::thread *input_thread = nullptr;
|
||||||
std::thread *midi_thread = nullptr;
|
std::thread *midi_thread = nullptr;
|
||||||
@@ -134,10 +138,9 @@ namespace rawinput {
|
|||||||
static void CALLBACK input_midi_proc(HMIDIIN, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
static void CALLBACK input_midi_proc(HMIDIIN, UINT, DWORD_PTR, DWORD_PTR, DWORD_PTR);
|
||||||
static DeviceInfo get_device_info(const std::string &device_name);
|
static DeviceInfo get_device_info(const std::string &device_name);
|
||||||
|
|
||||||
// shared by the rawinput / xinput / midi device scans when replacing a slot
|
// replace an already-destructed slot in place, holding both device locks so
|
||||||
// in place; keeps the existing slot's mutex pair so snapshot pointers held by
|
// concurrent pollers and the output thread never see a half-copied Device
|
||||||
// other threads stay valid (see the definition in rawinput.cpp)
|
static void replace_device_slot(Device &existing, Device &replacement);
|
||||||
static void reuse_device_mutexes(Device &replacement, const Device &existing);
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
|
||||||
@@ -193,6 +196,7 @@ namespace rawinput {
|
|||||||
std::lock_guard<std::recursive_mutex> lock(devices_mutex);
|
std::lock_guard<std::recursive_mutex> lock(devices_mutex);
|
||||||
for (auto &device : devices_get()) {
|
for (auto &device : devices_get()) {
|
||||||
if (device.type == MIDI) {
|
if (device.type == MIDI) {
|
||||||
|
std::lock_guard<std::mutex> device_lock(*device.mutex);
|
||||||
device.midiInfo->freeze = freeze;
|
device.midiInfo->freeze = freeze;
|
||||||
if (!freeze) {
|
if (!freeze) {
|
||||||
for (unsigned short index = 0; index < device.midiInfo->states.size(); index++) {
|
for (unsigned short index = 0; index < device.midiInfo->states.size(); index++) {
|
||||||
|
|||||||
@@ -0,0 +1,31 @@
|
|||||||
|
#include "rawinput_handles.h"
|
||||||
|
|
||||||
|
void rawinput::RawInputHandles::add(Device *device) {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
this->devices[device->handle] = device;
|
||||||
|
}
|
||||||
|
|
||||||
|
void rawinput::RawInputHandles::remove(Device *device) {
|
||||||
|
std::lock_guard<std::mutex> lock(this->mutex);
|
||||||
|
|
||||||
|
// teardown is shared by every device type, and handles can be reused; only erase
|
||||||
|
// an entry that still belongs to this exact RawInput device
|
||||||
|
auto it = this->devices.find(device->handle);
|
||||||
|
if (it != this->devices.end() && it->second == device) {
|
||||||
|
this->devices.erase(it);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
rawinput::RawInputHandles::AcquiredDevice rawinput::RawInputHandles::acquire(HANDLE handle) {
|
||||||
|
std::lock_guard<std::mutex> index_lock(this->mutex);
|
||||||
|
auto it = this->devices.find(handle);
|
||||||
|
if (it == this->devices.end()) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *device = it->second;
|
||||||
|
|
||||||
|
// take the device mutex while index_lock is still held so teardown can't free it
|
||||||
|
// between the lookup and the lock
|
||||||
|
return {device, std::unique_lock<std::mutex>(*device->mutex)};
|
||||||
|
}
|
||||||
@@ -0,0 +1,28 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <mutex>
|
||||||
|
#include <unordered_map>
|
||||||
|
|
||||||
|
#include <windows.h>
|
||||||
|
|
||||||
|
#include "device.h"
|
||||||
|
|
||||||
|
namespace rawinput {
|
||||||
|
|
||||||
|
class RawInputHandles {
|
||||||
|
public:
|
||||||
|
struct AcquiredDevice {
|
||||||
|
Device *device = nullptr;
|
||||||
|
std::unique_lock<std::mutex> lock;
|
||||||
|
};
|
||||||
|
|
||||||
|
void add(Device *device);
|
||||||
|
void remove(Device *device);
|
||||||
|
AcquiredDevice acquire(HANDLE handle);
|
||||||
|
|
||||||
|
private:
|
||||||
|
// non-owning index; RawInputManager owns the stable device slots
|
||||||
|
std::unordered_map<HANDLE, Device *> devices;
|
||||||
|
std::mutex mutex;
|
||||||
|
};
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user