AndersMalmgren / FreePIE

Programmable Input Emulator
635 stars 145 forks source link

Add Force Feedback support to vjoy and joystick plugin #48

Open AndersMalmgren opened 9 years ago

AndersMalmgren commented 9 years ago

I have not checked if the C# SDK has the support yet, but here is a demo written in C http://sourceforge.net/p/vjoystick/code/HEAD/tree/branches/Incompatible/ForceFB/apps/FfbMon/FfbMon.cpp

http://vjoystick.sourceforge.net/site/index.php/forum/5-Discussion/393-force-feedback-support?start=20

AndersMalmgren commented 7 years ago

The console output is designed to be used for output from script only. We do not want background code to output to the console window. When developing use Diagnostic. Debug.WriteLine instead

MarijnS95 commented 7 years ago

Thans Anders, didn't know that. I'll change all the logging to use that, but will it show up on the screen for easy debugging as well?

Anyway, that still means the script does it's logging (and potential modification) on a different thread than the one (bgworker) that generates the text.

AndersMalmgren commented 7 years ago

It will show up in the output window in visual studio

AndersMalmgren commented 7 years ago

ToArray is not thread safe no, but because of the 100 ms sleep its very unkly this should ever happen, if this happens every time or often I would look elsewere for the problem.

MarijnS95 commented 7 years ago

It will show up in the output window in visual studio

Ah I see now (wasn't near the project to check); that debug is part of the CLR. Whilst it would be appropriate to use, it prevents me from sending out FreePIE to people for trying out FFB. All the debugging lines will disappear eventually, once I'm satisfied it works.

ToArray is not thread safe no, but because of the 100 ms sleep its very unkly this should ever happen, if this happens every time or often I would look elsewere for the problem.

The problem is mostly in my code, it outputs a ridiculous amount of lines. If someone does something similar in their script, it'll cause crashes as well. But as long as no one reports this as an issue, accepting it's unlikely to happen seems the way to go.

AndersMalmgren commented 7 years ago

Sure, just make sure you lock wisly, not for a long time etc

erik-smit commented 7 years ago

I added some locking to synchronize access (on the Ffb branch), it should work without crashing now.

No crashes yet!

Also, I'm trying to capture what's going wrong when I alt-tab in/out of Spintires, but it looks like the amount of Console lines is limitted to ~1928 and older gets discarded. This makes it hard for me to get a complete overview.

Also, I added the following to PacketAction.cs to help correlate:

            Console.WriteLine("{0}", DateTime.UtcNow.ToString("yyyy-MM-dd HH:mm:ss.fff",
                                            CultureInfo.InvariantCulture));

Also, there's a typo in the word Exception here

Usually no biggy, but annoying if you know you saw something fly past, but then can't find it back in the log. :)

this is a log where I start spintires and then quickly alt-tab out/into it.

I think this is when I alt-tab back into it. And I think this is where it goes wrong because it's the only exception I see.

erik-smit commented 7 years ago

That makes sense: FreePIE needs to have exclusive access to the DFGT in order to send FFB commands; if the game still has that exclusive access FreePIE will fail.

Spintires has no problem sending Ffb to the DFGT if FreePIE is using the DFGT. But FreePIE does have problems sending Ffb to the DFGT if Spintires has used it.

Would it be possible to get FreePIE exclusive access to the DFGT? Now, I need to start Spintires with vJoy as controls. If I just switch Spintires from DFGT to vJoy, I get these exclusive errors.

MarijnS95 commented 7 years ago

No crashes yet!

I know how locks work ;)

Also, I'm trying to capture what's going wrong when I alt-tab in/out of Spintires, but it looks like the amount of Console lines is limitted to ~1928 and older gets discarded. This makes it hard for me to get a complete overview.

It should be limited to exactly 1000 ~lines~ calls to Console.WriteLine (I do print information with \n's in it, that's why it's more).

Also, there's a typo in the word Exception here

Then to imagine that the typo has been there for ages... :D

I think this is when I alt-tab back into it.

Very likely: just above it I see the Stop EOperation and PidBlockFree.

I'm specifically checking whether that object is null (see code comments for explanation), but I think an object used inside SetParameters is null and causing the issue. Most likely because the Effect has been disposed/stopped etc. Even then the exception should not matter, as directly thereafter the effect is created and that function is called many times again to update the force. I added the disposed check now, see whether that at least gets rid of the Exception (and a quick guess is that this could've broken the internal state of Dx, being the reason for Ffb disappearing despite no followup exceptions).

Besides, I'm wondering why Spintires sends out two Start operations interleaved with the actual force...

Edit regarding exclusiveness: This is a bit confusing to me as well; it sounds very unrealistic for ST to happily take the DFGT and send FFB to it when FreePIE has it acquired exclusively. FreePIE is actually acquiring the device in exclusive mode, however it does so lazily and only once. In other words, the device will only be acquired once a script needs it (by using methods/functions from joystick[i]), at which point it's put in a cache (any scripts you start from that point will use the same device descriptor, untill FreePIE is completely restarted).

Besides all that, now I'm wondering whether the function that has to acquire the device in exclusive mode just doesn't throw an exception when it can't do so, or that it's possible for a program (FreePIE in this case) to loose it's 'exclusivity' over time.

MarijnS95 commented 7 years ago

Thanks, specs:

Applications that select the background exclusive mode cooperative level are not guaranteed to retain access to the device if another application requests exclusive access. When a background exclusive mode application loses access, calls to DirectInput device methods will fail and return DIERR_NOTACQUIRED. The application can regain access to the device by manually unacquiring the device and reaquiring it.

So, I'll have to write some code to check and fix the exclusive access ;)

erik-smit commented 7 years ago

I added the disposed check now, see whether that at least gets rid of the Exception (and a quick guess is that this could've broken the internal state of Dx, being the reason for Ffb disappearing despite no followup exceptions).

Seems to get rid of the exception.

But doesn't change the behaviour. Ffb still stops working after an alt-tab out/in.

Here's the log: https://gist.github.com/erik-smit/ec9d50cb7d9941f6ec5ba2210ca5eda1

BTW, I love how you're constantly using links when you're referencing stuff. Seriously helps with clarity.

MarijnS95 commented 7 years ago

As mentioned in an earlier comment, I assume this has to do with disposing and recreating the effect (because the game effectively sends packets to do this). However, contrary to the game, FreePIE doesn't dispose and reacquire the device, so maybe that might be causing the issue. Hence, I commented this out temporarily (on the Ffb branch), let's see what it does.

BTW, I love how you're constantly using links when you're referencing stuff. Seriously helps with clarity.

You're welcome. Makes it easier for everyone (including myself) to know what I'm talking about or referring to. The line-based links to project files are a bit dangerous though, as soon as I commit a change on that branch they might become invalid/unrelated (I should post links to the file at a certain commit, instead of file at the head of a branch).

erik-smit commented 7 years ago

Hence, I commented this out temporarily (on the Ffb branch), let's see what it does.

Did you commit this?

The last change I see on the branch is https://github.com/MarijnS95/FreePIE/commit/56daac6a691629b5e3cd56a07a1599d9fcc9131a which I think is the "I added the disposed check now, "

MarijnS95 commented 7 years ago

Hmm I'm not trusting VisualStudio github integration anymore: I clicked sync which successfully pushed the commit, only to find out now it wasn't pushed :confused:. Welcome back, command line :wink:. Should be pushed now.

erik-smit commented 7 years ago

Now, after an alt-tab out/in, all forwarding breaks (I no longer see the in-game wheel move when I turn my DFGT) and after a few seconds FreePIE dies with:

System.AccessViolationException was unhandled
  HResult=-2147467261
  Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
  Source=SlimDX
  StackTrace:
       at SlimDX.DirectInput.Effect.Start(Int32 iterations)
       at FreePIE.Core.Plugins.Dx.Device.OperateEffect(Int32 blockIndex, EffectOperation effectOperation, Int32 loopCount) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\Dx\Device.cs:line 181
       at FreePIE.Core.Plugins.VJoy.PacketMapper.<>c.<SetupDefaultMap>b__5_2(Device d, EffectOperationPacket p) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\VJoy\PacketMapper.cs:line 33
       at FreePIE.Core.Plugins.VJoy.AsyncPacketData`1.Call(IList`1 registeredDevices) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\VJoy\PacketAction.cs:line 65
       at FreePIE.Core.Plugins.VJoy.VJoyFfbWrap.HandleQueuedPackets() in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\VJoy\VJoyFFBWrap.cs:line 72
       at FreePIE.Core.Plugins.VJoyPlugin.DoBeforeNextExecute() in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\VJoyPlugin.cs:line 46
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.<>c.<RunLoop>b__17_1(IPlugin p) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 171
       at FreePIE.Core.Common.Extensions.CollectionExtensions.ForEach[T](IEnumerable`1 collection, Action`1 action) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\Common\Extensions\CollectionExtensions.cs:line 15
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.<>c__DisplayClass17_0.<RunLoop>b__0() in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 171
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.ExecuteSafe(Action action, Boolean logToFile) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 228
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.RunLoop(String script, ScriptScope scope) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 165
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.<>c__DisplayClass16_0.<Start>b__0(Object obj1) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 156
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart(Object obj)
  InnerException: 
MarijnS95 commented 7 years ago

That's very interesting, there is a try/catch block around that piece of code 😕.

Maybe something going wrong internally in SlimDX because of the looping parameter. I'll see if I can come up with some debugging code. This might indeed have to do with incorrrect disposing (or not disposing) of resources.

erik-smit commented 7 years ago

I just noticed some output in the Visual Studio output:

https://gist.github.com/erik-smit/9aabae9eafa06c8838cb1268b112962a

MarijnS95 commented 7 years ago

I'm sorry, totally forgot to look into this.

As the exceptions don't tell a whole lot, perhaps it's better to temporarily remove some try/catch blocks, add some more logging to it or tell VS to immediately break on all exceptions. Right now I have no idea which calls are causing the issues and what the Exception contents are. Can you modify the code to do this, or shall I push some code with more debugging options?

erik-smit commented 7 years ago

I'm sorry, totally forgot to look into this.

No worries.

Can you modify the code to do this, or shall I push some code with more debugging options?

I don't really understand how removing try/catch blocks would influence this issue.

As far as I understand, System.AccessViolationException means stuff got corrupted on a very low level. Like, pointers in C code pointing the wrong way.

As far as I understand, this shouldn't be possible in managed code world, so would only happen somewhere where raw memory pointers are being exchanged, probably somewhere where unmanaged code is being called?

I'll try to get VS to break 'sooner'.

erik-smit commented 7 years ago

I don't know yet how to make VS break on all exceptions, but I was able to add a bunch.

Now I get:

System.AccessViolationException was unhandled
  HResult=-2147467261
  Message=Attempted to read or write protected memory. This is often an indication that other memory is corrupt.
  Source=SlimDX
  StackTrace:
       at SlimDX.DirectInput.Effect.SetParameters(EffectParameters parameters, EffectParameterFlags flags)
       at FreePIE.Core.Plugins.Dx.Device.SetConstantForce(Int32 blockIndex, Int32 magnitude) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\Dx\Device.cs:line 166
       at FreePIE.Core.Plugins.VJoy.PacketMapper.<>c.<SetupDefaultMap>b__5_1(Device d, ConstantForcePacket p) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\VJoy\PacketMapper.cs:line 29
       at FreePIE.Core.Plugins.VJoy.AsyncPacketData`1.Call(IList`1 registeredDevices) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\VJoy\PacketAction.cs:line 65
       at FreePIE.Core.Plugins.VJoy.VJoyFfbWrap.HandleQueuedPackets() in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\VJoy\VJoyFFBWrap.cs:line 72
       at FreePIE.Core.Plugins.VJoyPlugin.DoBeforeNextExecute() in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\VJoyPlugin.cs:line 46
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.<>c.<RunLoop>b__17_1(IPlugin p) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 171
       at FreePIE.Core.Common.Extensions.CollectionExtensions.ForEach[T](IEnumerable`1 collection, Action`1 action) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\Common\Extensions\CollectionExtensions.cs:line 15
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.<>c__DisplayClass17_0.<RunLoop>b__0() in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 171
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.ExecuteSafe(Action action, Boolean logToFile) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 228
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.RunLoop(String script, ScriptScope scope) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 165
       at FreePIE.Core.ScriptEngine.Python.PythonScriptEngine.<>c__DisplayClass16_0.<Start>b__0(Object obj1) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core\ScriptEngine\Python\PythonScriptEngine.cs:line 156
       at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
       at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
       at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
       at System.Threading.ThreadHelper.ThreadStart(Object obj)
  InnerException: 

I'd say FreePIE is trying to SetParameters on an Effect that's no longer existing.

Right before that, I get a bunch of

Exception thrown: 'SlimDX.DirectInput.DirectInputException' in SlimDX.dll
Exception thrown: 'SlimDX.DirectInput.DirectInputException' in SlimDX.dll
Exception thrown: 'SlimDX.DirectInput.DirectInputException' in SlimDX.dll
Exception thrown: 'SlimDX.DirectInput.DirectInputException' in SlimDX.dll

which I'm not able to break on yet.

erik-smit commented 7 years ago

I was able to break on the DirectInputException in SlimDX.dll

SlimDX.DirectInput.DirectInputException occurred
  HResult=-2146233088
  Message=ERROR_NOT_READY: This object has not been initialized (-2147024875)
  Source=SlimDX
  StackTrace:
       at SlimDX.Result.Throw[T](Object dataKey, Object dataValue)
       at SlimDX.Result.Record[T](Int32 hr, Boolean failed, Object dataKey, Object dataValue)
       at SlimDX.DirectInput.Effect.get_Guid()
       at FreePIE.Core.Plugins.Dx.Device.<>c__DisplayClass34_0.<CreateEffect>b__0(Effect e) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\Dx\Device.cs:line 238
  InnerException: 

I'm guessing Effects get disposed, but the variable doesn't get unset in FreePIE maybe?

MarijnS95 commented 7 years ago

I don't really understand how removing try/catch blocks would influence this issue.

The try-catch blocks in the code actually "hide" exceptions. This is a good thing if an exception is expected and you do not want it to break the application, but it's annoying if they are catched just when you want to know about them. Knowing that such an exception has occured is not when debugging - it is important to know at least where it came from.

As far as I understand, System.AccessViolationException means stuff got corrupted on a very low level. Like, pointers in C code pointing the wrong way. As far as I understand, this shouldn't be possible in managed code world, so would only happen somewhere where raw memory pointers are being exchanged, probably somewhere where unmanaged code is being called?

Most function calls, either to SlimDX/DirectX or vJoy, end up in unmanaged code. So yes, probably a broken/NULL handle/reference in DirectInput, either a parameter or a pointer to some disposed memory (or any combination really ;)).

I'll try to get VS to break 'sooner'. I don't know yet how to make VS break on all exceptions, but I was able to add a bunch.

In the search bar, look for exception settings. Click the checkbox at Common Language Runtime Exceptions a couple times, until a checkmark is seen. VS will now break on all types of exceptions, which might be a little overkill ;). Press F5 to continue execution (which makes the exception bubble up to the nearest catch block that matches the exception).

I'd say FreePIE is trying to SetParameters on an Effect that's no longer existing.

When it breaks you should be able to check the parameters.

I'm guessing Effects get disposed, but the variable doesn't get unset in FreePIE maybe?

Probably, the dispose-code is a mess. You can tinker around with it, maybe you can figure out what is causing the problem. I don't have much time on my hands and all I can do without an FFB device is educated guesses. If you have some info on the parameters at the point of crashing, I might be able to write something up ;)

erik-smit commented 7 years ago

To be clear, these crashes are happening in your latest commit where you commented out the Dispose in an attempt to check if this fixes the issue of forcefeedback only working once in FreePIE and stopping to work once I've alt-tabbed out/in of Spintires.

If I uncomment this, the crashes stop.

erik-smit commented 7 years ago

Interestingly, regarding this issue, I don't have to restart Spintires to make forcefeedback work again. I just need to restart FreePIE, and then alt-tab back into my running Spintires. So I believe this means that FreePIE is retaining some state that causes the next time forcefeedback is applied to malfunction.

Also, by restart I mean a full quit and run of the FreePIE applicatioin. Just stopping and starting the script inside of FreePIE is not enough.

erik-smit commented 7 years ago

FWIW, with the PidBlockFree/Dispose action commented out, the next CreatedEffects crashes with the following parameters:

image

MarijnS95 commented 7 years ago

So I believe this means that FreePIE is retaining some state that causes the next time forcefeedback is applied to malfunction.

Yes, either FreePIE, SlimDX or both. I'm still in the process of figuring out what, but I'm really out of guesses now.

FWIW, with the PidBlockFree/Dispose action commented out, the next CreatedEffects crashes with the following parameters:

Guid inaccessible, but the object is not disposed? 😕 Useful information nevertheless: this is exactly one of those cases where I would've never guessed the problem would be at that line.

erik-smit commented 7 years ago

I thought that maybe the issue could be that Effects[blockIndex] remained set after the Dispose() on https://github.com/MarijnS95/FreePIE/blob/Ffb/FreePIE.Core.Plugins/Dx/Device.cs#L284. But adding a 'Effects[blockIndex] = null;' after this doesn't fix ffb failing to work after alt-tab out/in Spintires.

Guid inaccessible, but the object is not disposed? 😕 Useful information nevertheless: this is exactly one of those cases where I would've never guessed the problem would be at that line.

These createdEffects come from joystick.CreatedEffects.ToArray();

So this is not the list FreePIE maintains, but this is a list maintained by SlimDX? So, with this code you try to see if the Effect was already created, and re-using it since we're not Disposing them anymore?

I wanted to ask why you don't just use the Effects[] maintained by FreePIE, but I notice this has the same issue:

image

So I guess whatever is returned by joystick.CreatedEffects.ToArray() is a reference to the same 'thing' that is stored in Effects[1]?

Now I'm curious if Effects[1] has a GUID when it's created the first time.

The answer is yes, but curiously, when I clicked on the > to expand the Effect in VS, VS hung for 10-15 seconds, and Status times out evaluation:

image

Maybe we should maintain a private array of data/EffectGUIDs to check against so we're not reliant on SlimDX to provide usable info? Or are we fixing the wrong problem then?

erik-smit commented 7 years ago

I just realized. It is quite interesting, that these exceptions happen with this[PacketType.PidBlockFree] = new PacketAction<BasePacket>((d, p) => d.DisposeEffect(p.BlockIndex)); commented out.

They don't happen when it's not commented out. So it looks like that even though DisposeEffect doesn't get passed through, something else comes through that makes the Guid of the Effect inaccessible?

MarijnS95 commented 7 years ago

So this is not the list FreePIE maintains, but this is a list maintained by SlimDX? So, with this code you try to see if the Effect was already created, and re-using it since we're not Disposing them anymore?

Exectly. Based on some earlier feedback from others I assumed DirectInput doesn't allow duplicate effects, hence the check for an existing (and hopefully valid) one to reuse.

I wanted to ask why you don't just use the Effects[] maintained by FreePIE, but I notice this has the same issue:

It's a bit of a mess. As far as I know BlockIndex is used so the packets can tell for which effect they are - if an effect is (told) to be created in block 0, all changes (parameter updates) for that effect need to be delegated to the effect at block 0.

So I guess whatever is returned by joystick.CreatedEffects.ToArray() is a reference to the same 'thing' that is stored in Effects[1]?

I think so, yes. You could use Object.ReferenceEquals(a, b) to find out.

Now I'm curious if Effects[1] has a GUID when it's created the first time.

The GUID should be set to eGuid when new Effect is called. It's actually a GUID that determines the type of the effect (see EffectTypeGuidMap in Device.cs).

The answer is yes, but curiously, when I clicked on the > to expand the Effect in VS, VS hung for 10-15 seconds, and Status times out evaluation:

Hmmm....

Maybe we should maintain a private array of data/EffectGUIDs to check against so we're not reliant on SlimDX to provide usable info? Or are we fixing the wrong problem then?

Maybe, but it'll probably suffer from the same issues since they are the same references AFAIK.

It is quite interesting, that these exceptions happen with this[PacketType.PidBlockFree] = new PacketAction<BasePacket>((d, p) => d.DisposeEffect(p.BlockIndex)); commented out. They don't happen when it's not commented out. So it looks like that even though DisposeEffect doesn't get passed through, something else comes through that makes the Guid of the Effect inaccessible?

Yep, I think figuring out how/why is important to solve this issue. Something is causing the effects to be lost, but they still show up in joystick.CreatedEffects. When explicitly disposed, they either don't show up at all, or have their disposed bool set.

erik-smit commented 7 years ago

I think so, yes. You could use Object.ReferenceEquals(a, b) to find out.

I tested with:

            foreach (var createdEffect in createdEffects)
            {
                if (Object.ReferenceEquals(createdEffect, Effects[blockIndex]))
                {
                    Console.WriteLine("createdEffects reference equals to Effects[]");
                }
                else
                {
                    Console.WriteLine("createdEffects reference does not equal to Effects[]");
                }

and they are equal.

I've been fiddling with the thought that maybe our fiddling with effectParams before checking if the effect already exists (https://github.com/MarijnS95/FreePIE/blob/Ffb/FreePIE.Core.Plugins/Dx/Device.cs#L127) is affecting the created effect.

While trying to debug this, I now sometimes get the following exception when alt-tabbing into Spintires for the first time.

System.Exception occurred
  HResult=-2146233088
  Message=No effect has been created in block 1
  Source=FreePIE.Core.Plugins
  StackTrace:
       at FreePIE.Core.Plugins.Dx.Device.OperateEffect(Int32 blockIndex, EffectOperation effectOperation, Int32 loopCount) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\Dx\Device.cs:line 178
  InnerException: 

Maybe alt-tabbing out/in Spintires with is not a valid 'test-case', and maybe whenever I start FreePIE new, I should also start Spintires new?

erik-smit commented 7 years ago

I'm not if I'm really understanding stuff correctly, this is too weird for me.

On this line, during when a Effect gets created when it already exists in FreePIE, the Guid is still fine, right?

image

Then when I press F10 twice and I look at Guid again, it says:

image

To my untrained eye, it looks like calling joystick.CreatedEffects somehow invalidates/breaks the Guid?

MarijnS95 commented 7 years ago

It's dazzling me as well. Fetching CreatedEffects shouldn't break anything, especially since it returns exactly the same object with the same native instance.

Anyway, since Spintires uses a single effect, I think it's safe to comment the CreatedEffects stuff, and add some code to check whether Effects[blockIndex] exists and is 'valid' (!Effects[blockIndex].Disposed).

erik-smit commented 7 years ago

CreatedEffects is a variable in the joystick object, and the only thing you're doing is turning it into an array, right? And this is just a managed function. Could it be that at that point in time, something has already messed with unmanaged structures in the background, and calling toArray() over it ends up tripping over and corrupting stuff?

In the past I've dabbled with Valgrind on Linux to spot unmanaged memory corruption stuff, do you have any experience with something like this on Windows and/or think it could be useful for me to investigate this path?

MarijnS95 commented 7 years ago

CreatedEffects is actually a property, which calls a DirectInput method every time you read from the property. As you can see, it calls that DirectInput method with a callback function, which is called for every effect. As you can see, it's simply added to a list which is then returned by the property.

toArray shouldn't cause any harm, all it does is copy over the references to the Effect objects. As far as I remember, I used toArray because I thought CreatedEffects was an IEnumerable. Just looked, it's an IList so it can be removed...

I'm not sure if memory debugging is appropriate already, as we are then looking at the internals of SlimDX and DirectInput. Maybe we can first check whether getting rid of evaluating CreatedEffects makes a difference.

erik-smit commented 7 years ago

CreatedEffects is actually a property, which calls a DirectInput method every time you read from the property. As you can see, it calls that DirectInput method with a callback function, which is called for every effect. As you can see, it's simply added to a list which is then returned by the property.

Right... The property of this object is not a pointer to a piece of data stored in memory somewhere, but the property has a 'get' function that obtains the data when requested.

I don't really understand the callback stuff. Usually when I see callbacks involved, this means stuff is running asynchronous. But in this case the EnumCreatedEffectObjects only returns once the callback has been called for every Effect?

I'm not sure if memory debugging is appropriate already, as we are then looking at the internals of SlimDX and DirectInput.

Right.

Maybe we can first check whether getting rid of evaluating CreatedEffects makes a difference.

I've made the following change: https://github.com/erik-smit/FreePIE/commit/63030cad391b8acd1435b6c579ab02e98a19ec4e

The effect is that FreePIE no longer exceptions when alt-tabbing out/in of Spintires. But the effect does stop working, just like when we process Disposes correctly.

It exceptions when I Shift-F5 in FreePIE and tries to stop the effects, right after calling CreatedEffects:

SlimDX.DirectInput.DirectInputException occurred
  HResult=-2146233088
  Message=ERROR_NOT_READY: This object has not been initialized (-2147024875)
  Source=SlimDX
  StackTrace:
       at SlimDX.Result.Throw[T](Object dataKey, Object dataValue)
       at SlimDX.Result.Record[T](Int32 hr, Boolean failed, Object dataKey, Object dataValue)
       at SlimDX.DirectInput.Effect.get_Guid()
       at FreePIE.Core.Plugins.Dx.Device.Stop() in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\Dx\Device.cs:line 196
  InnerException:
erik-smit commented 7 years ago

FWIW, here's a current console output of an alt-tab into spintires, some movement left/right, alt-tab out/in, and some more movement left/right: https://gist.github.com/erik-smit/5c5ce93a24a30d9e8cfd521d4e3d1f93

I logged this to see if there's anything I notice that could influence the effect from no longer working.

erik-smit commented 7 years ago

I'm fiddling a bit with FFBInspector, and sometimes I get FreePIE to exception on:

SlimDX.DirectInput.DirectInputException occurred
  HResult=-2146233088
  Message=DIERR_NOTEXCLUSIVEACQUIRED & VFW_E_FILTER_ACTIVE & DMO_E_TYPE_NOT_ACCEPTED: The operation cannot be performed unless the device is acquired in DISCL_EXCLUSIVE mode. & This operation cannot be performed because the filter is active. (-2147220987)
  Source=SlimDX
  StackTrace:
       at SlimDX.Result.Throw[T](Object dataKey, Object dataValue)
       at SlimDX.Result.Record[T](Int32 hr, Boolean failed, Object dataKey, Object dataValue)
       at SlimDX.DirectInput.Effect.SetParameters(EffectParameters parameters, EffectParameterFlags flags)
       at FreePIE.Core.Plugins.Dx.Device.SetConstantForce(Int32 blockIndex, Int32 magnitude) in C:\Users\zoiah\Documents\FreePIE\FreePIE\FreePIE.Core.Plugins\Dx\Device.cs:line 169
  InnerException: 

A thought goes through my mind, maybe the effects become invalidated because FreePIE loses either the active window handle to the or maybe because FreePIE loses its exclusive acquire of the joystick?

erik-smit commented 7 years ago

FWIW, a single ConstantForce effect that works when send directly from FFBInspector to the DFGT, doesn't work when sent through FreePIE.

Here's the console output: https://gist.github.com/erik-smit/ea9156ce3d3c4bf956d599d8f73753b9

I guess since I've got the sourcecode of FFBInspector and FFBInspector also uses SlimDX, I guess I should try to compare whatever FFBInspector sends to the joystick vs. what FreePIE sends to the joystick?

erik-smit commented 7 years ago

FWIW, the single ConstantForce effect that didn't work before: It works if I first start FFBInspector, and then start FreePIE. Then it works. But not the first time I start an effect in FFBInspector. Just the second time I start an effect in FFBInspector. Then it keeps working until I switch focus from FFBInspector to FreePIE and back. Then it stops working again, until I stop/start FreePIE.

But it doesn't stop working entirely. When I stop/start the effect in FFBInspector I still hear a click in my wheel, just not the push behind it.

Anyhow, this focus breaking stuff does make debugging/breaking/inspecting quite the hassle. :)

Here's a console log from when it first works, until I switch Focus (DeviceReset) and it then it no longer works:

https://gist.github.com/erik-smit/f119eea549138bad136091f2f5f5b4f5

erik-smit commented 7 years ago

Out of curiousity, I've tested if just the focus of FFBInspector is relevant or if the focus of FreePIE is also relevant. No, it's just the focus of FFBInspector.

If FFBInspector uses vJoy as device, it only works on a clean FreePIE. And only for the second and following Start/Stop of the Effect. Not on the first start/stop of the effect. And then only for the duration that FFBInspector has focus. Once FFBInspector loses focus, the effect no longer works and until I stop/start the script in FreePIE. And then only for as long as FFBInspector has focus again. Once FFBInspector loses focus, it no longer works and I need to stop/start the script in FreePIE again.

I hope my description is understandable/clear. :)

So, the focus of the application driving vJoy is somehow affecting FreePIE.

I believe this to be the same as the issue we're having with Spintires effects no longer working.

As a test, I changed the SetCooperativeLevel for FFBInspector from Foreground to Background, but this makes no change.

erik-smit commented 7 years ago

!!!

If I comment out this[PacketType.DeviceGain] = new PacketAction<DeviceGainPacket>((d, p) => d.Gain = p.NormalizedGain);, it no longer breaks after FFBInspector lost focus!

I noticed that it was doing two things on a focus lost/regain. DeviceReset/PIDBlockFree. and DeviceGain. We had already disabled the DeviceReset.

Also, it remains 'working' when I uncomment the PIDBlockFree. I say 'working', because the first effect after a focusloss still doesn't work, but the rest does, so yay, progress.

And it also works from SpinTires!

erik-smit commented 7 years ago

image

I wonder if this is correct. It says the Gain is 135, even though I set a Gain of 10000 in FFBInspector. But the BlockIndex is 255 I'm looking for.

Maybe https://github.com/MarijnS95/FreePIE/blob/Ffb/FreePIE.Core.Plugins/VJoy/PacketData/DeviceGainPacket.cs#L11 is incorrect and this packet actually doesn't have a BlockIndex because it's meant as a device-setting instead of something for a particular effect?

I checked out the vJoy SDK in an attempt to see if there were some specs for that, but it looks like they just provide functions to parse the packets instead of telling you what's in them?

erik-smit commented 7 years ago

I've fixed DeviceGainPacket, and now both Spintires and FFBInspector keep working after losing focus!

I notice still in FFBInspector that the first effect doesn't work, but all those after that are fine.

So yay, progress!

MarijnS95 commented 7 years ago

Wow, lots of messages. Sorry I'm short on time for a while so can't take a good look.

Indeed, devgain is a package for the device, not for a specific effect in a specific block. Blockindex should be removed from the struct, and gain will take it's place. See here. Actually my bad for not wanting to work with the vjoy interface but rather read the data into structs myself.

Edit: see you fixed that already whilst I looked it up in the source code and typed that out on the phone. Nice :)

erik-smit commented 7 years ago

Actually my bad for not wanting to work with the vjoy interface but rather read the data into structs myself.

Why do you not want to work with the vjoy interface? I read they've changed the internal interface in the past, so it might be more future-proof to use the vJoy DLLs.

Would you be against me giving a go at trying to change the code into using the vJoy DLLs?

MarijnS95 commented 7 years ago

I've used it in the past and it caused a mess, having to call one of the specific functions every time to get the meaning of a package. Right now I save that hassle by dereferencing the packet directly into a C# struct. Using the original functions isn't hard, it's just less C#-ish imho. Anyway, I planned on committing some of this code to the vJoy repo, see what Shaul thinks of it, but I've never gotten to it.

erik-smit commented 7 years ago

Using the original functions isn't hard, it's just less C#-ish imho.

I see what you mean. I must say I find the current Mapper vs. Action, struct, etc. stuff very pretty.

Okay, so now there are a few things left.

Right?

Do we need @AndersMalmgren for the last one?

MarijnS95 commented 7 years ago

Kindof. I still want to finish the python side of things, so that FFB packets can be received there, and that FFB can be sent to a DirectInput device (if one wishes to do anything more advanced than the automagic "forward FFB from vJoy device x to DirectInput device y"). But that's not mandatory for an initial release.

Also, there's more FFB effects and parameters which haven't been forwarded yet. Spintires uses ConstantForce only, but there's more types (RampForce, PeriodicForce, Conditional force, and something custom), as well as the Envelope modifier (which specifies the strength of an effect over time).

erik-smit commented 7 years ago

but there's more types (RampForce, PeriodicForce, Conditional force, and something custom), as well as the Envelope modifier (which specifies the strength of an effect over time).

I've just tested with the FFBInspector on my DFGT, and except for Ramp Force, all of the following work on my DFGT:

Constant (13541c20-8e33-11d0-9ad0-00a0c9a06e35)
Ramp Force (13541c21-8e33-11d0-9ad0-00a0c9a06e35)
Square Wave (13541c22-8e33-11d0-9ad0-00a0c9a06e35)
Sine Wave (13541c23-8e33-11d0-9ad0-00a0c9a06e35)
Triangle Wave (13541c24-8e33-11d0-9ad0-00a0c9a06e35)
Sawtooth Up Wave (13541c25-8e33-11d0-9ad0-00a0c9a06e35)
Sawtooth Down Wave (13541c26-8e33-11d0-9ad0-00a0c9a06e35)
Damper (13541c28-8e33-11d0-9ad0-00a0c9a06e35)
Inertia (13541c29-8e33-11d0-9ad0-00a0c9a06e35)
Friction (13541c2a-8e33-11d0-9ad0-00a0c9a06e35)
CustomForce (13541c2b-8e33-11d0-9ad0-00a0c9a06e35)

The RampForce gives an E_INVALIDARG, so it might even be supported, but just given the wrong arguments.

cyberluke commented 1 year ago

Hi, what is current status please? Is this in main branch or fork only?