sharpdx / SharpDX

SharpDX GitHub Repository
http://sharpdx.org
MIT License
1.69k stars 641 forks source link

Seeking advice on best way to gracefully handle device unplug / replug in DirectInput #979

Open evilC opened 6 years ago

evilC commented 6 years ago

It seems that if you are in a polling loop, eg repeatedly calling data = _joystick.GetBufferedData();, then if the stick is unplugged, an exception is thrown.
This is no problem, but my problem is how best to deal with this scenario.
If / when this happens, it seems that you are also unable to call UnAquire, I guess this is not an issue?
My main problem is how to detect that the device was plugged back in, if possible without having to enter a try catch block, as it would be nice for the poll loop to also be able to check for reconnect without having the perf overhead of trying to handle exceptions.
However, it seems that calling DiInstance.IsDeviceAttached(<guid>) (certainly when the device has just been unplugged) also throws.
I would also like to handle the scenario of a stick being plugged in after my poll loop starts, but the user could have declared bindings to say 10 sticks which are not plugged in yet, so how best to check, as rapidly as possible, with as little perf hit as possible, "did this stick get plugged in"?
At some point I plan on implementing device plug / unplug detection using USBHID or something, but for now if I can handle it reasonably gracefully from with SharpDX, that would be great.
Any thoughts?

xoofx commented 6 years ago

The usual way to do it is, on every frame:

evilC commented 6 years ago

Oh really, I have been keeping the instance between polls.
This explains a lot, thank you!

evilC commented 6 years ago

Hmm, I just cannot seem to get it to work that way. I can get it vaguely reliable if using a latching acquire / poll loop with try/catches but cannot get it working when acquiring and relinquishing each poll.
ie this is fine in normal use and semi-ok on plug/unplug:

        protected override void PollThread()
        {
            Joystick joystick = null;
            while (true)
            {
                //JoystickUpdate[] data = null;
                try
                {
                    while (true)    // Main poll loop
                    {
                        while (true) // Not Acquired loop
                        {
                            while (!DiHandler.DiInstance.IsDeviceAttached(_instanceGuid))
                            {
                                Thread.Sleep(100);
                            }
                            joystick = new Joystick(DiHandler.DiInstance, _instanceGuid);
                            joystick.Properties.BufferSize = 128;
                            joystick.Acquire();
                            break;
                        }

                        while (true)  // Acquired loop
                        {
                            var data = joystick.GetBufferedData();
                            foreach (var state in data)
                            {
                                int offset = (int)state.Offset;
                                var bindingType = Lookups.OffsetToType(state.Offset);
                                if (BindingDictionary.ContainsKey(bindingType) && BindingDictionary[bindingType].ContainsKey(offset))
                                {
                                    BindingDictionary[bindingType][offset].Poll(state.Value);
                                }
                            }
                            Thread.Sleep(10);
                        }
                    }

                }
                catch
                {
                    try
                    {
                        joystick.Dispose();
                    }
                    catch
                    {

                    }

                    joystick = null;
                }

                Thread.Sleep(10);
            }
        }

But this does not work at all, ever.

        protected override void PollThread()
        {
            Joystick joystick = null;
            while (true)
            {
                try
                {
                    while (true)    // Main poll loop
                    {
                        while (!DiHandler.DiInstance.IsDeviceAttached(_instanceGuid))
                        {
                            Thread.Sleep(100);
                        }
                        joystick = new Joystick(DiHandler.DiInstance, _instanceGuid);
                        joystick.Properties.BufferSize = 128;
                        joystick.Acquire();
                        var data = joystick.GetBufferedData();
                        foreach (var state in data)
                        {
                            int offset = (int)state.Offset;
                            var bindingType = Lookups.OffsetToType(state.Offset);
                            if (BindingDictionary.ContainsKey(bindingType) && BindingDictionary[bindingType].ContainsKey(offset))
                            {
                                BindingDictionary[bindingType][offset].Poll(state.Value);
                            }
                        }
                        joystick.Unacquire();
                        Thread.Sleep(10);
                    }

                }
                catch
                {
                    try
                    {
                        joystick.Dispose();
                    }
                    catch
                    {

                    }

                    joystick = null;
                }

                Thread.Sleep(10);
            }
        }

It's probably me doing something stupid though.
FYI, this is not for a game, it is for a remapping application.

h1cks commented 6 years ago

If you solution only requires joystick input then I would suggest using the XInput interface, it handles the connect/disconnect alot more gracefully (from my experience of both). Feel free to ask these questions over at https://gamedev.stackexchange.com/ as you will get more community input into a common implementation question.

evilC commented 6 years ago

If I were only going to support one input API, XInput is the absolute last API I would support - it is awful. Max 6 axes, 12 buttons, 1 POV, no proper FFB, max 4 devices.
Also, XI devices have a DI equivalent but not the other way around. If you are only going to support one input API, then DI is it (Or maybe RawInput).
I am writing a remapping application that can be extended with plugins to support pretty much any input or output API.
I already support DirectInput, XInput, Tobii Eye Tracker API, Titan One, DualShock 4 custom driver, Interception keyboard driver API, plus many more in the works (I just got an Arduino, looking at Fermata API), and that is just the input APIs.
https://github.com/evilC/IOWrapper (Back end, the bit that supports DI/XI etc)
https://github.com/Snoothy/UCR (Front end, totally agnostic about input APIs. Main documentation here)

h1cks commented 6 years ago

You have the other challenge with the deprecated DirectInput, vs the XInput. According the Directinput MSDN page, if you are wanting to use MS App store then you are faced with XInput only. I can understand that MS wants to push to a newer api (and that XInput supports the trigger buttons on XBOX controllers whereas DInput doesn't).

The MSDN page seems to specifically mention that XInput compatible devices should be "filtered" out if you are using DInput and support under their intended library. But as you are doing a IOWrapper, your challenges are certainly big.

evilC commented 6 years ago

Reports of the demise of DI are greatly over-exaggerated.
MS simply cannot deprecate DI, as XI is incapable of replacing it.
XI on PC has no force feedback (Proper FFB is a force applied to an axis, as in an FFB steering wheel) so you instantly kill off the racing scene on the PC and put a bunch of manufacturers out of business.
XI has a max of 6 axes, 12 buttons, 1 POV, and round gate axes (ie totally inappropriate for flight sims), so in order to kill off XI, MS would also put all of the flightstick manufacturers out of business and kill off the whole flightsim scene on the PC, including MS Flight Simulator, Elite Dangerous and Star Citizen - projects worth billions.
So, until you see MS announcing that Flight Sim is going to be using XInput only, then maybe at that point DI is dead, but do not bank on it this side of hell freezing over.

h1cks commented 6 years ago

I had read somewhere the FF steering wheel support was coming. But yes, its a weird situation.

evilC commented 6 years ago

Steering wheel only, or full FFB?
Steering wheels only have one FFB axis, whereas DI supports an arbitrary number.
I would not be surprised if the FFB implementation in Xbox XI is limited to one FFB axis (I am not aware of any Xbox FFB joysticks, only FFB steering wheels), in which case it would not be capable of replacing DI FFB.
Seeing as MS paid Immersion $26 million for the rights to use Force Feedback, then I doubt they are just going to discontinue support for it.

evilC commented 6 years ago

Also, AFAIK, Windows Store apps are all UWP, and UWP uses the new Windows.Gaming.Input API, which I suppose may be slated to replace DI.
However, last time I checked, UWP does not support SLI GPUs, and just look at the horrid state of Forza on windows - you need to jump through fiery hoops to use FFB wheels with that.
UWP is a steaming pile of turd when it comes to games:
No exclusive fullscreen
No Freesync
No SLI
No screen overlays (Steam, overwolf etc)
Much less modding possibilities

Yeah, good luck MS persuading developers to ditch Steam / Win32 APIs and adopt UWP