Didn’t find a link to UE-24374 bug, so I post it in this thread.
Just spent 6 hours on this one, lucky that someone else noticed that as well.
I will give you hints on what is going on there.
The code around mouse processing works in very weird way, having async where it shouldn’t be: it gets current mouse position values straight using WINAPI calls, not using what WM_MOUSEMOVE provides.
Thus, the result of very same code calls will depend on data synchronization between OS and UE4, and in case of big delays (low FPS issue) we have racing issue and the code breaks.
More specifics on the bug. It happens at FSlateApplication::ProcessMouseMoveEvent:
If no mouse button pressed, we run through common flow of broadcasting
CurWidget.Widget->OnMouseMove(…)
that, in turn, will update last mouse cursor position variable deep inside viewport code. And no problem.
Though, if we hold a mouse button, engine considers this as mouse capture case and things are very different now.
GOOD CASE:
- WM_MOUSEMOVE processing notice that mouse position changed from previous frame.
- FSlateApplication.ProcessMouseMoveEvent, bIsSynthetic=0 is called
- It invokes SViewport::OnMouseMove and CachedMousePos is updated
- FSlateApplication::PointerIndexLastPositionMap[CursorPointerIndex] is updated
- User code runs with updated mouse position
- FSlateApplication.ProcessMouseMoveEvent, bIsSynthetic=1 is called
- Mouse position is changed again(!), so FSlateApplication::PointerIndexLastPositionMap[CursorPointerIndex] is updated
- – next frame –
- WM_MOUSEMOVE processing notice that mouse position changed from previous frame…
Will be similar positive outback if two WM_MOUSEMOVE events will be processed same frame.
BAD CASE:
- WM_MOUSEMOVE processing sees that mouse position didn’t change.
- FSlateApplication::PointerIndexLastPositionMap[CursorPointerIndex] is updated, with latest Windows mouse location(!)
- User code runs with old mouse position (i.e. mouse didn’t move for user code)
- FSlateApplication.ProcessMouseMoveEvent, bIsSynthetic=1 is called
- FSlateApplication::PointerIndexLastPositionMap[CursorPointerIndex] is updated to latest Windows mouse location
- – next frame –
- WM_MOUSEMOVE processing notice that mouse position didn’t change since step 6…
Code solution could be to comment out
if ( !bIsSynthetic )
check at FSlateApplication.ProcessMouseMoveEvent. So final code should look like this:
FWidgetPath MouseCaptorPath;
if (MouseCaptor.HasCaptureForPointerIndex(MouseEvent.GetPointerIndex()))
{
//MouseCaptorPath = MouseCaptor.ToWidgetPath(MouseEvent.GetPointerIndex(), FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid);
MouseCaptorPath = MouseCaptor.ToWidgetPath(FWeakWidgetPath::EInterruptedPathHandling::ReturnInvalid, &MouseEvent );
}
if (MouseCaptorPath.IsValid())
{
// if ( !bIsSynthetic )
{
// Switch worlds widgets in the current path
FScopedSwitchWorldHack SwitchWorld( MouseCaptorPath );
FReply Reply = FEventRouter::Route<FReply>( this, FEventRouter::FToLeafmostPolicy(MouseCaptorPath), MouseEvent, [this]( const FArrangedWidget& MouseCaptorWidget, const FPointerEvent& Event )
{
As for non-captor case, now the MouseMove will be called twice, but only one will make it to the Viewport, as Viewport has internal check for cursor position change as well.
Please keep me informed what would be the correct solution on the problem. I would recommend to run refactoring and get rid of direct calls to FWindowsCursor::GetPosition()