mirror of
https://github.com/ares-emulator/ares.git
synced 2026-10-12 02:11:04 +00:00
input: preserve relative mouse motion between polls (#2593)
## Changes - Accumulate relative mouse movement until it is read and consumed by an input mapping. - Maintain an independent read position for each `InputRelative` binding. - Synchronize relative input positions when input bindings or mouse capture state changes. - Apply the same relative-motion handling to the Raw Input, NSEvent, and Xlib mouse drivers. ## Motivation In my testing, `InputManager::poll()` is called approximately once every 8ms on my system, with observed intervals generally no shorter than 5ms. At 60Hz, each frame lasts approximately 16.67ms, so input is normally polled twice per frame. Each poll retrieves the mouse-axis movement accumulated during the preceding approximately 8ms. However, emulator cores generally read relative input only once per frame. This means that `InputManager` may complete its second poll before the core reads the movement value. The previous implementation retained only the mouse-axis movement reported by the most recent poll. The second poll therefore overwrote the result of the first, allowing the core to observe only the movement from the latest approximately 8ms. Mouse movement from the earlier approximately 8ms of the frame was lost. This change accumulates all relative mouse movement produced between consecutive core input reads. As a result, even when `InputManager::poll()` runs multiple times within a single frame, the core can still retrieve the complete mouse-axis movement accumulated during that period.
This commit is contained in:
@@ -16,10 +16,10 @@ auto InputManager::createHotkeys() -> void {
|
||||
hotkeys.push_back(InputHotkey("Toggle Mouse Capture").onPress([&] {
|
||||
Program::Guard guard;
|
||||
if(!emulator) return;
|
||||
if(!ruby::input.acquired()) {
|
||||
ruby::input.acquire();
|
||||
if(!inputManager.acquired()) {
|
||||
inputManager.acquire();
|
||||
} else {
|
||||
ruby::input.release();
|
||||
inputManager.release();
|
||||
}
|
||||
}));
|
||||
|
||||
|
||||
+70
-24
@@ -179,7 +179,7 @@ auto InputDigital::value() -> s16 {
|
||||
output = value != 0;
|
||||
}
|
||||
|
||||
if(device->isMouse() && groupID == HID::Mouse::GroupID::Button && ruby::input.acquired()) {
|
||||
if(device->isMouse() && groupID == HID::Mouse::GroupID::Button && inputManager.acquired()) {
|
||||
output = value != 0;
|
||||
}
|
||||
|
||||
@@ -223,7 +223,7 @@ auto InputHotkey::value() -> s16 {
|
||||
output = value != 0;
|
||||
}
|
||||
|
||||
if(device->isMouse() && groupID == HID::Mouse::GroupID::Button && ruby::input.acquired()) {
|
||||
if(device->isMouse() && groupID == HID::Mouse::GroupID::Button && inputManager.acquired()) {
|
||||
output = value != 0;
|
||||
}
|
||||
|
||||
@@ -363,7 +363,7 @@ auto InputAbsolute::value() -> s16 {
|
||||
if (device->isKeyboard() && program.keyboardCaptured) continue;
|
||||
s16 value = device->group(groupID).input(inputID).value();
|
||||
|
||||
if(device->isMouse() && groupID == HID::Joypad::GroupID::Axis && ruby::input.acquired()) {
|
||||
if(device->isMouse() && groupID == HID::Joypad::GroupID::Axis && inputManager.acquired()) {
|
||||
result += value;
|
||||
}
|
||||
|
||||
@@ -389,45 +389,51 @@ auto InputRelative::bind(u32 binding, std::shared_ptr<HID::Device> device, u32 g
|
||||
return unbind(binding), true;
|
||||
}
|
||||
|
||||
if(device->isMouse() && groupID == HID::Mouse::GroupID::Axis) {
|
||||
return bind(binding, assignment), true;
|
||||
bool accepted = device->isMouse() && groupID == HID::Mouse::GroupID::Axis;
|
||||
if(device->isJoypad() && groupID == HID::Joypad::GroupID::Axis) {
|
||||
if(oldValue >= -16384 && newValue < -16384) accepted = true;
|
||||
if(oldValue <= +16384 && newValue > +16384) accepted = true;
|
||||
}
|
||||
if(!accepted) return false;
|
||||
|
||||
if(device->isJoypad() && groupID == HID::Joypad::GroupID::Axis
|
||||
&& oldValue >= -16384 && newValue < -16384
|
||||
) {
|
||||
return bind(binding, assignment), true;
|
||||
InputMapping::bind(binding, assignment);
|
||||
synchronize();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto InputRelative::synchronize() -> void {
|
||||
lock_guard<recursive_mutex> inputLock(program.inputMutex);
|
||||
for(u32 index : range(BindingLimit)) {
|
||||
auto& binding = bindings[index];
|
||||
if(!binding.device || !binding.device->isMouse()) continue;
|
||||
if(binding.groupID != HID::Mouse::GroupID::Axis) continue;
|
||||
|
||||
auto& mouse = static_cast<HID::Mouse&>(*binding.device);
|
||||
if(binding.inputID >= mouse.axes().size()) continue;
|
||||
mouse.motion(binding.inputID).synchronize(previous[index]);
|
||||
}
|
||||
|
||||
if(device->isJoypad() && groupID == HID::Joypad::GroupID::Axis
|
||||
&& oldValue <= +16384 && newValue > +16384
|
||||
) {
|
||||
return bind(binding, assignment), true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
auto InputRelative::value() -> s16 {
|
||||
lock_guard<recursive_mutex> inputLock(program.inputMutex);
|
||||
s32 result = 0;
|
||||
s64 result = 0;
|
||||
|
||||
for(auto& binding : bindings) {
|
||||
for(u32 index : range(BindingLimit)) {
|
||||
auto& binding = bindings[index];
|
||||
if(!binding.device) continue; //unbound
|
||||
|
||||
auto& device = binding.device;
|
||||
auto& groupID = binding.groupID;
|
||||
auto& inputID = binding.inputID;
|
||||
auto& qualifier = binding.qualifier;
|
||||
if (device->isKeyboard() && program.keyboardCaptured) continue;
|
||||
s16 value = device->group(groupID).input(inputID).value();
|
||||
|
||||
if(device->isMouse() && groupID == HID::Joypad::GroupID::Axis && ruby::input.acquired()) {
|
||||
result += value;
|
||||
if(device->isMouse() && groupID == HID::Mouse::GroupID::Axis && inputManager.acquired()) {
|
||||
auto& mouse = static_cast<HID::Mouse&>(*device);
|
||||
result += mouse.motion(inputID).read(previous[index]);
|
||||
}
|
||||
|
||||
if(device->isJoypad() && groupID == HID::Joypad::GroupID::Axis) {
|
||||
result += value;
|
||||
result += device->group(groupID).input(inputID).value();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -535,6 +541,46 @@ auto InputManager::bind() -> void {
|
||||
}
|
||||
}
|
||||
for(auto& mapping : hotkeys) mapping.bind();
|
||||
synchronize();
|
||||
}
|
||||
|
||||
auto InputManager::acquired() -> bool {
|
||||
return ruby::input.acquired();
|
||||
}
|
||||
|
||||
auto InputManager::acquire() -> bool {
|
||||
lock_guard<recursive_mutex> inputLock(program.inputMutex);
|
||||
if(!ruby::input.acquire()) return false;
|
||||
synchronize();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto InputManager::release() -> bool {
|
||||
lock_guard<recursive_mutex> inputLock(program.inputMutex);
|
||||
if(!ruby::input.release()) return false;
|
||||
synchronize();
|
||||
return true;
|
||||
}
|
||||
|
||||
auto InputManager::synchronize() -> void {
|
||||
lock_guard<recursive_mutex> inputLock(program.inputMutex);
|
||||
for(auto& port : virtualPorts) {
|
||||
for(auto& input : port.mouse.inputs) {
|
||||
if(input.type == InputNode::Type::Relative) static_cast<InputRelative&>(*input.mapping).synchronize();
|
||||
}
|
||||
}
|
||||
for(auto& emulator : emulators) {
|
||||
for(auto& port : emulator->ports) {
|
||||
for(auto& device : port.devices) {
|
||||
if(!device.hasDirectMappings()) continue;
|
||||
for(auto& input : device.inputs) {
|
||||
if(input.type == InputNode::Type::Relative) {
|
||||
static_cast<InputRelative&>(input.configuredMapping()).synchronize();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto InputManager::poll(bool force) -> void {
|
||||
|
||||
@@ -58,7 +58,11 @@ struct InputAbsolute : InputMapping {
|
||||
struct InputRelative : InputMapping {
|
||||
using InputMapping::bind;
|
||||
auto bind(u32 binding, std::shared_ptr<HID::Device>, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> bool override;
|
||||
auto synchronize() -> void;
|
||||
auto value() -> s16 override;
|
||||
|
||||
private:
|
||||
s64 previous[BindingLimit] = {};
|
||||
};
|
||||
|
||||
//specifies a target joypad for force feedback
|
||||
@@ -299,6 +303,10 @@ struct VirtualPort {
|
||||
struct InputManager {
|
||||
auto create() -> void;
|
||||
auto bind() -> void;
|
||||
auto acquired() -> bool;
|
||||
auto acquire() -> bool;
|
||||
auto release() -> bool;
|
||||
auto synchronize() -> void;
|
||||
auto poll(bool force = false) -> void;
|
||||
auto eventInput(std::shared_ptr<HID::Device>, u32 groupID, u32 inputID, s16 oldValue, s16 newValue) -> void;
|
||||
|
||||
|
||||
@@ -77,14 +77,14 @@ auto Program::videoFullScreenToggle() -> void {
|
||||
ruby::video.clear();
|
||||
if(!ruby::video.fullScreen()) {
|
||||
ruby::video.setFullScreen(true);
|
||||
if(!ruby::input.acquired()) {
|
||||
if(!inputManager.acquired()) {
|
||||
if(ruby::video.exclusive() || ruby::video.hasMonitors().size() == 1) {
|
||||
ruby::input.acquire();
|
||||
inputManager.acquire();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if(ruby::input.acquired()) {
|
||||
ruby::input.release();
|
||||
if(inputManager.acquired()) {
|
||||
inputManager.release();
|
||||
}
|
||||
ruby::video.setFullScreen(false);
|
||||
presentation.viewport.setFocused();
|
||||
@@ -99,13 +99,13 @@ auto Program::videoPseudoFullScreenToggle() -> void {
|
||||
if(!presentation.fullScreen()) {
|
||||
presentation.setFullScreen(true);
|
||||
presentation.menuBar.setVisible(false);
|
||||
if(!ruby::input.acquired() && ruby::video.hasMonitors().size() == 1) {
|
||||
ruby::input.acquire();
|
||||
if(!inputManager.acquired() && ruby::video.hasMonitors().size() == 1) {
|
||||
inputManager.acquire();
|
||||
}
|
||||
startPseudoFullScreen = true;
|
||||
} else {
|
||||
if(ruby::input.acquired()) {
|
||||
ruby::input.release();
|
||||
if(inputManager.acquired()) {
|
||||
inputManager.release();
|
||||
}
|
||||
if(!kiosk) presentation.menuBar.setVisible(true);
|
||||
presentation.setFullScreen(false);
|
||||
|
||||
@@ -117,10 +117,37 @@ struct Mouse : Device {
|
||||
enum : u16 { GenericVendorID = 0x0000, GenericProductID = 0x0002 };
|
||||
enum GroupID : u32 { Axis, Button };
|
||||
|
||||
struct Motion {
|
||||
auto move(s16 distance) -> void {
|
||||
position += distance;
|
||||
}
|
||||
|
||||
auto synchronize(s64& previous) const -> void {
|
||||
previous = position;
|
||||
}
|
||||
|
||||
auto read(s64& previous) const -> s64 {
|
||||
auto distance = position - previous;
|
||||
previous = position;
|
||||
return distance;
|
||||
}
|
||||
|
||||
private:
|
||||
s64 position = 0;
|
||||
};
|
||||
|
||||
Mouse() : Device("Mouse") { append("Axis"), append("Button"); }
|
||||
auto isMouse() const -> bool { return true; }
|
||||
auto axes() -> Group& { return group(GroupID::Axis); }
|
||||
auto buttons() -> Group& { return group(GroupID::Button); }
|
||||
auto appendAxis(const string& name) -> void {
|
||||
axes().append(name);
|
||||
_motion.emplace_back();
|
||||
}
|
||||
auto motion(u32 id) -> Motion& { return _motion[id]; }
|
||||
|
||||
private:
|
||||
std::vector<Motion> _motion;
|
||||
};
|
||||
|
||||
struct Joypad : Device {
|
||||
|
||||
@@ -13,6 +13,7 @@ struct InputMouseNS {
|
||||
|
||||
auto acquire() -> bool {
|
||||
[NSCursor hide];
|
||||
previousLocation = [NSEvent mouseLocation];
|
||||
isAcquired = true;
|
||||
return acquired();
|
||||
}
|
||||
@@ -25,9 +26,12 @@ struct InputMouseNS {
|
||||
|
||||
auto assign(u32 groupID, u32 inputID, s16 value) -> void {
|
||||
auto& group = hid->group(groupID);
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
auto& target = group.input(inputID);
|
||||
if(target.value() != value) {
|
||||
input.doChange(hid, groupID, inputID, target.value(), value);
|
||||
target.setValue(value);
|
||||
}
|
||||
if(groupID == HID::Mouse::GroupID::Axis) hid->motion(inputID).move(value);
|
||||
}
|
||||
|
||||
auto poll(std::vector<std::shared_ptr<HID::Device>>& devices) -> void {
|
||||
@@ -52,8 +56,8 @@ struct InputMouseNS {
|
||||
hid->setProductID(HID::Mouse::GenericProductID);
|
||||
hid->setPathID(0);
|
||||
|
||||
hid->axes().append("X");
|
||||
hid->axes().append("Y");
|
||||
hid->appendAxis("X");
|
||||
hid->appendAxis("Y");
|
||||
|
||||
hid->buttons().append("Left");
|
||||
hid->buttons().append("Middle");
|
||||
|
||||
@@ -88,6 +88,9 @@ struct InputMouseRawInput {
|
||||
auto extents = desktopExtents();
|
||||
WaitForSingleObject(rawinput.mutex, INFINITE);
|
||||
motion.refresh(extents);
|
||||
ms.relativeX = 0;
|
||||
ms.relativeY = 0;
|
||||
ms.relativeZ = 0;
|
||||
ReleaseMutex(rawinput.mutex);
|
||||
}
|
||||
|
||||
@@ -151,9 +154,10 @@ struct InputMouseRawInput {
|
||||
|
||||
auto assign(u32 groupID, u32 inputID, s16 value) -> void {
|
||||
auto& group = ms.hid->group(groupID);
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(ms.hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
auto& target = group.input(inputID);
|
||||
if(target.value() != value) input.doChange(ms.hid, groupID, inputID, target.value(), value);
|
||||
target.setValue(value);
|
||||
if(groupID == HID::Mouse::GroupID::Axis) ms.hid->motion(inputID).move(value);
|
||||
}
|
||||
|
||||
auto poll(std::vector<std::shared_ptr<HID::Device>>& devices) -> void {
|
||||
@@ -192,9 +196,9 @@ struct InputMouseRawInput {
|
||||
ms.hid->setProductID(HID::Mouse::GenericProductID);
|
||||
ms.hid->setPathID(0);
|
||||
|
||||
ms.hid->axes().append("X");
|
||||
ms.hid->axes().append("Y");
|
||||
ms.hid->axes().append("Z");
|
||||
ms.hid->appendAxis("X");
|
||||
ms.hid->appendAxis("Y");
|
||||
ms.hid->appendAxis("Z");
|
||||
|
||||
ms.hid->buttons().append("Left");
|
||||
ms.hid->buttons().append("Middle");
|
||||
|
||||
@@ -59,9 +59,12 @@ struct InputMouseXlib {
|
||||
|
||||
auto assign(u32 groupID, u32 inputID, s16 value) -> void {
|
||||
auto& group = hid->group(groupID);
|
||||
if(group.input(inputID).value() == value) return;
|
||||
input.doChange(hid, groupID, inputID, group.input(inputID).value(), value);
|
||||
group.input(inputID).setValue(value);
|
||||
auto& target = group.input(inputID);
|
||||
if(target.value() != value) {
|
||||
input.doChange(hid, groupID, inputID, target.value(), value);
|
||||
target.setValue(value);
|
||||
}
|
||||
if(groupID == HID::Mouse::GroupID::Axis) hid->motion(inputID).move(value);
|
||||
}
|
||||
|
||||
auto poll(std::vector<std::shared_ptr<HID::Device>>& devices) -> void {
|
||||
@@ -150,8 +153,8 @@ struct InputMouseXlib {
|
||||
hid->setProductID(HID::Mouse::GenericProductID);
|
||||
hid->setPathID(0);
|
||||
|
||||
hid->axes().append("X");
|
||||
hid->axes().append("Y");
|
||||
hid->appendAxis("X");
|
||||
hid->appendAxis("Y");
|
||||
|
||||
hid->buttons().append("Left");
|
||||
hid->buttons().append("Middle");
|
||||
|
||||
Reference in New Issue
Block a user