I'm running into an issue that I'm not sure is solvable in the way I want to solve it. I have a problem with a race condition.
I have one project running as a C++ dll (the main engine). Then I have a second C# process that uses C++/CLI to communicate with the main engine (the editor).
The editor is hosting the engine window as a child window. The result of this is that the child window receives input messages async (see RiProcessMouseMessage()
). Normally this only happens when I call window->PollEvents();
.
main engine loop {
RiProcessMouseMessage(); // <- Called by the default windows message poll function from the child window
foreach(inputDevice)
inputDevice->UpdateState();
otherCode->UseCurrentInput();
}
The main editor loop is the WPF loop which I don't control. Basically it does this:
main editor loop {
RiProcessMouseMessage(); // <- This one is called by the editor (parent) window, but is using the message loop of the (child) engine window
}
The RawInput processor which is called sync by the engine and async by the editor:
void Win32RawInput::RiProcessMouseMessage(const RAWMOUSE& rmouse, HWND hWnd) {
MouseState& state = Input::mouse._GetGatherState();
// Check Mouse Position Relative Motion
if (rmouse.usFlags == MOUSE_MOVE_RELATIVE) {
vec2f delta((float)rmouse.lLastX, (float)rmouse.lLastY);
delta *= MOUSE_SCALE;
state.movement += delta;
POINT p;
GetCursorPos(&p);
state.cursorPosGlobal = vec2i(p.x, p.y);
ScreenToClient(hWnd, &p);
state.cursorPos = vec2i(p.x, p.y);
}
// Check Mouse Wheel Relative Motion
if (rmouse.usButtonFlags & RI_MOUSE_WHEEL)
state.scrollMovement.y += ((float)(short)rmouse.usButtonData) / WHEEL_DELTA;
if (rmouse.usButtonFlags & RI_MOUSE_HWHEEL)
state.scrollMovement.x += ((float)(short)rmouse.usButtonData) / WHEEL_DELTA;
// Store Mouse Button States
for (int i = 0; i < 5; i++) {
if (rmouse.usButtonFlags & maskDown_[i]) {
state.mouseButtonState[i].pressed = true;
state.mouseButtonState[i].changedThisFrame = true;
} else if (rmouse.usButtonFlags & maskUp_[i]) {
state.mouseButtonState[i].pressed = false;
state.mouseButtonState[i].changedThisFrame = true;
}
}
}
UpdateState()
is called only by the engine. It basically swaps the RawInput to the currently used input. This is to prevent input updating in the middle of a frame loop (aka. during otherCode->UseCurrentInput();
)
void UpdateState() {
currentState = gatherState; // Copy gather state to current
Reset(gatherState); // Reset the old buffer so the next time the buffer it's used it's all good
// Use current state to check stuff
// For the rest of this frame currentState should be used
}
MouseState& _GetGatherState() { return gatherState; }
void Reset(MouseState& state) { // Might need a lock around gatherState :(
state.movement = vec2f::zero;
state.scrollMovement = vec2f::zero;
for (int i = 0; i < 5; ++i)
state.mouseButtonState[i].changedThisFrame = false;
}
So as you can see the race condition happens when RiProcessMouseMessage()
is called while Reset()
was called in the main engine loop. If it wasn't clear: The Reset()
function is required to reset state back to it's frames default data so that the data is read correctly every frame.
Now I'm very much aware I can fix this easily by adding a mutex
around the gatherState updates but I would like to avoid this if possible. Basically I'm asking is it possible to redesign this code to be lock free?