XOutput
XOutput copied to clipboard
Bug fix: XOutputDevice.RefreshInput() is not thread safe.
Originally observed issue:
- When moving 2 joysticks on 2 different input devices mapped to 1 XOutput device at the same time, occasionally one of the joystick inputs would be lost/disconnect.
- In game, this caused the joystick to remain pressed in some last-polled direction.
- This could be resolved in the XOutput app by hitting the "Force Refresh" button.
Debug/conclusion process:
- Log would show a "WARN Poll failed for ..." when this occurs.
- Log event is from DirectDevice.cs line 319 (
logger.Warning($"Poll failed for {ToString()}");) - The exception thrown to cause the log event was a "Collection was modified ..."
- Exception was thrown from line 313 (
InputChanged?.Invoke(this, deviceInputChangedEventArgs);) - ...which was invoking
XOutputDevice.SourceInputChange()which then callsRefreshInput() - Debugging calls in the function, it appeared to be the call to
changes.Any()that was throwing the exception. - Looking online for other instances of when such an exception could occur, the quick solution is to make a copy of the Collection for iteration. Adding
ToList()to line 119 (changes.GetChanges(force);) appeared to fix the issue, but is more masking the problem than fixing it. - How is the Collection being changed? Each
DirectDeviceobject that eventually calls intoXOutputDevice.RefreshInput()has a thread.RefreshInput()makes changes to thestatevariable ofXOutputDevice. It is possible for more than one thread to be callingRefreshInput()and thus making asynchronous changes to thestatevariable. - Excluding it's initialization,
stateis only used/of-use inRefreshInput(). - Solution: Make
statea local variable ofRefreshInput().
This does mean a DeviceState is instantiated for each call to RefreshInput() which is inefficient.
Originally branched off the 3.32 tag. The forced push was to rebase on top of 3.x branch.