Skip to content

Commit 629bdba

Browse files
authored
Fixes issue o3de#14879 (o3de#17672)
The issue was caused by the fact that the mouse input device was checking a variable that was tracking how much time since the last mouse input event, and considering things to be a new input event if more than a certain amount of time passed. However, this function was being called for each axis on the Mouse (So X, Y, Z separately), but the "Time Since last event" variable was shared across the axes and thus would cause the X axis (the first) to act differently from the others. The change does 2 things - one, it makes a per-axis time, and two, it only emits a "no movement" event artificially if there were no events this frame (and does so at the end of the frame). This brings all the channels back into the same behavior - they all go into the "Pressed" state initially and if you keep moving the mouse they remain in the "Updated" state until you stop moving the mouse, at which point they go into the "released" state, followed by the "Idle" state. Signed-off-by: Nicholas Lawson <[email protected]>
1 parent 3cfa49d commit 629bdba

File tree

2 files changed

+35
-13
lines changed

2 files changed

+35
-13
lines changed

Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.cpp

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -190,8 +190,9 @@ namespace AzFramework
190190
, m_rawMovementSampleRate()
191191
, m_rawButtonEventQueuesById()
192192
, m_rawMovementEventQueuesById()
193-
, m_timeOfLastRawMovementSample(AZStd::chrono::steady_clock::now())
194193
{
194+
m_timeOfLastRawMovementSample[0] = m_timeOfLastRawMovementSample[1] = m_timeOfLastRawMovementSample[2] = AZStd::chrono::steady_clock::now();
195+
195196
SetRawMovementSampleRate(MovementSampleRateDefault);
196197
}
197198

@@ -213,22 +214,30 @@ namespace AzFramework
213214
void InputDeviceMouse::Implementation::QueueRawMovementEvent(const InputChannelId& inputChannelId,
214215
float rawMovementDelta)
215216
{
216-
auto now = AZStd::chrono::steady_clock::now();
217-
auto deltaTime = now - m_timeOfLastRawMovementSample;
217+
AZStd::chrono::steady_clock::time_point now = AZStd::chrono::steady_clock::now();
218+
219+
AZStd::chrono::steady_clock::time_point& lastSampleForThisAxis =
220+
inputChannelId == Movement::X ? m_timeOfLastRawMovementSample[0] :
221+
inputChannelId == Movement::Y ? m_timeOfLastRawMovementSample[1] :
222+
inputChannelId == Movement::Z ? m_timeOfLastRawMovementSample[2] :
223+
now; // read and write to 'now' if its not a movement axis which is why 'now' is non-const
224+
225+
auto deltaTime = now - lastSampleForThisAxis;
218226
auto& rawEventQueue = m_rawMovementEventQueuesById[inputChannelId];
219227

220228
// Depending on the movement sample rate, multiple mouse movements within a frame are either:
221229
if (rawEventQueue.empty() || deltaTime.count() > m_rawMovementSampleRate)
222230
{
223231
// queued (to give a better response at low frame rates)
224232
rawEventQueue.push_back(rawMovementDelta);
225-
m_timeOfLastRawMovementSample = now;
226233
}
227234
else
228235
{
229236
// or accumulated (to avoid flooding the event queue)
230237
rawEventQueue.back() += rawMovementDelta;
231238
}
239+
240+
lastSampleForThisAxis = now; // note: this is intentionally tautology when its not a movement axis.
232241
}
233242

234243
////////////////////////////////////////////////////////////////////////////////////////////////
@@ -240,19 +249,30 @@ namespace AzFramework
240249
m_inputDevice.m_cursorPositionData2D->m_normalizedPosition = newNormalizedPosition;
241250
m_inputDevice.m_cursorPositionData2D->m_normalizedPositionDelta = newNormalizedPosition - oldNormalizedPosition;
242251

243-
// Process all raw input events that were queued since the last call to this function
244-
ProcessRawInputEventQueues(m_rawButtonEventQueuesById, m_inputDevice.m_buttonChannelsById);
245-
ProcessRawInputEventQueues(m_rawMovementEventQueuesById, m_inputDevice.m_movementChannelsById);
252+
// One issue with the way mouse events propagate is that the driver only sends events when the mouse physically moves.
253+
// This can lead to the situation where the mouse starts moving, and so we get a movement "BEGIN" event,
254+
// and it continues moving smoothly, each frame, correctly leading to a "Update" event many frames,
255+
// but when it stops moving, no event occurs (from the driver). Because only events ending up in the queue
256+
// can cause transitions from UPDATE to IDLE, it can't ever settle and the "UPDATE" state will never end.
246257

247-
// Mouse movement events are distinct in that we may not receive an 'ended' event with delta
248-
// value of zero when the mouse stops moving, so queueing one here ensures the channels will
249-
// always correctly transition into the 'ended' state the next time this function is called,
250-
// unless another movement delta is queued above in which case it will simply be added to 0.
258+
// we can detect the situation where the mouse has not moved this tick by seeing if any events were in the
259+
// queue. If there was no event for this game tick, we can assume the mouse has stopped moving.
251260
for (const InputChannelId& movementChannelId : Movement::All)
252261
{
253-
QueueRawMovementEvent(movementChannelId, 0.0f);
262+
// if the channel was already idle, there's no reason to add synthetic events to it.
263+
if (!m_inputDevice.m_movementChannelsById[movementChannelId]->IsStateIdle())
264+
{
265+
if (m_rawMovementEventQueuesById[movementChannelId].empty())
266+
{
267+
m_rawMovementEventQueuesById[movementChannelId].push_back(0.0f);
268+
}
269+
}
254270
}
255271

272+
// Process all raw input events that were queued since the last call to this function
273+
ProcessRawInputEventQueues(m_rawButtonEventQueuesById, m_inputDevice.m_buttonChannelsById);
274+
ProcessRawInputEventQueues(m_rawMovementEventQueuesById, m_inputDevice.m_movementChannelsById);
275+
256276
// Finally, update the cursor position input channel, treating it as active if it has moved
257277
const float distanceMoved = newNormalizedPosition.GetDistance(oldNormalizedPosition);
258278
m_inputDevice.m_cursorPositionChannel->ProcessRawInputEvent(distanceMoved);

Code/Framework/AzFramework/AzFramework/Input/Devices/Mouse/InputDeviceMouse.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -302,6 +302,8 @@ namespace AzFramework
302302
///@{
303303
using RawButtonEventQueueByIdMap = AZStd::unordered_map<InputChannelId, AZStd::vector<bool>>;
304304
using RawMovementEventQueueByIdMap = AZStd::unordered_map<InputChannelId, AZStd::vector<float>>;
305+
using LastSampleTimeArray = AZStd::array<AZStd::chrono::steady_clock::time_point, InputDeviceMouse::Movement::All.size()>;
306+
305307
///@}
306308

307309
private:
@@ -311,7 +313,7 @@ namespace AzFramework
311313
AZStd::sys_time_t m_rawMovementSampleRate; //!< Raw movement sample rate
312314
RawButtonEventQueueByIdMap m_rawButtonEventQueuesById; //!< Raw button events by id
313315
RawMovementEventQueueByIdMap m_rawMovementEventQueuesById; //!< Raw movement events by id
314-
AZStd::chrono::steady_clock::time_point m_timeOfLastRawMovementSample; //!< Time of the last raw movement sample
316+
LastSampleTimeArray m_timeOfLastRawMovementSample; //!< Time of the last raw movement sample
315317
};
316318

317319
////////////////////////////////////////////////////////////////////////////////////////////

0 commit comments

Comments
 (0)