I have a Code Plugin that provides a BlueprintFunctionLibrary. The BlueprintFunctionLibrary contains UFUNCTIONs that in turn call functions provided by a third party library.
The plugin is loading a third party library by referencing the .lib/.dll/include locations in the Build.cs file. The library is SDL – UE4’s built-in SDL only seems to work for linux and html5, not plain windows, so my plugin brings its own copy of SDL.
It works just fine for all synchronous actions like polling buttons. However, any async calls, such as force feedback, will work correctly up until the point that my UFUNCTION is done executing, then suddenly stop, even if the library call was made in a separate thread. Even if the thread was not spawned by my UFUNCTION, calls to async SDL functions will work while a UFUNCTION in the plugin is executing, and not work otherwise. In all cases the function returns a successful code but doesn’t actually activate the hardware.
At first I thought I might be doing something dumb like having pointers to stack memory but even after moving everything out of the UFUNCTION I get the same result.
I believe UE4 is doing some dynamic loading and unloading of resources each time one of my UFUNCTIONs is called – maybe it’s the DLL, maybe it’s my BlueprintFunctionLibrary’s static variables which store plugin state, I am not sure. But something is “going away” when my UFUNCTION returns, and I need it to stick around for the asynchronous library calls.
PS adding a wait to the UFUNCTION, which does make it work, is of course not acceptable since it blocks the main thread.
This sounds as though it may actually be an issue with the third-party library itself. There is no “generic” async function setup, and it is possible that the way the library’s async functions are setup does not work well with the Engine. Do you have access to the library’s source code? If so, would it be possible to get a sample project that shows how your plugin is accessing one of the library’s async functions?
Here is the project: https://dl.dropboxusercontent.com/u/43725910/MyProject.zip
Connect an xbox controller before starting the editor to test. It provides a UMG button to activate the rumble.
In controlysis.cpp on line 134 there is a call to SDL_Delay, which if removed, will cause the rumble to not work.
Lines 137 and 138 clean up resources which allow the button to be clicked more than once in a single PIE run. They may seem like a potential contributor to the problem but if you also remove those lines the rumble is still cut short.
I apologize for the delay in getting back to you on this post. I just wanted to check and see if you are still experiencing the same issue. Please let us know if you are still seeing this happening.
Yes, same result under UE 4.17.1
It looks like at some point I had deleted the test project you provided previously, and the link no longer works. Would it be possible to get another copy of that test project?
Here’s a copy of the full demo project for engine 4.18
Just load it up and press Play. Controller with rumble must be connected before starting the editor
I had some time today to dig into this a bit further. The current example that you provided looks like it is a normal implementation of the SDL Haptic subsystem. I don’t see anything in the project that would indicate that you are calling the Rumble function asynchronously, and from what I can find about the SDL Haptic subsystem, it does not appear to run asynchronously by default (though I may have missed some bit of information that states otherwise). Your implementation seems to be quite typical for other uses of the subsystem that I have seen:
SDL_HapticNewEffect() to create a new haptic effect.
SDL_HapticRunEffect() to run the haptic effect.
SDL_Delay() to wait the desired time for the haptic effect to run.
SDL_HapticDestroyEffect() to stop and destroy the haptic effect.
While doing some debugging (with the
SDL_Delay() call commented out), the rumble would run until the
SDL_HapticDestroyEffect() call was made. That would be expected in this case.
With the call to
SDL_HapticDestroyEffect() commented out, the rumble would continue briefly past the end of the function, but would stop at some point while execution is moving back up the callstack. The exact point at which it stopped seemed to vary with each execution. I suspect what was happening in this case was that the
ff device was being lost due to it being out of scope once the function execution ended.
Have you already tried creating a
FRunnableThread to create the rumble effect and let it continue on that thread until you need it to end?
I had tried creating a thread for the rumble to continue on, but I used a SDL_Thread, not a FRunnableThread. (After your last reply I also tried a FRunnableThread with the same results.)
Using a thread didn’t help, though I am not surprised – the evidence that SDL_HapticRunEffect is already asynchronus is that it produces different behavior when you put a delay after it.
I may be missing something but I thought it shouldn’t matter that the variable ff goes out of scope because all it contains is a pointer to static memory. The pointer itself is passed into SDL_HapticRunEffect by value, and I know the memory it points to hasn’t gone away since the polling functions are accessing it the same way on every tick.
Looking into this again some more, I am still not certain that this issue is being caused by the Engine. With the call to
SDL_Delay() removed, the rumble begins with the call to
SDL_HapticRunEffect() and then is immediately ended by the call to
SDL_HapticDestroyEffect(). That seems to be expected behavior in this case.
If the call to
SDL_HapticDestroyEffect() is also removed (as well as the line deleting the pointer to the
SDL_HapticEffect), the rumble continues past the end of the function until at some point it has used up all available free space, at which point it stops and the rumble can no longer be triggered again (an error message is displayed instead of the rumble occurring). This also seems to be expected behavior.
With the code as it is written in the sample project that you provided (the
SDL_Delay() call is being made), the rumble plays for 1000 milliseconds as expected before being destroyed, but it does not appear to be happening asynchronously since any other inputs that are made on the controller while it is playing appear to be queued and are only applied after the rumble stops. For example, clicking one of the rumble buttons rapidly several times and then pressing and holding a button on the controller will only register the button press once the rumble for all of the queued clicks are completed.
As I mentioned previously, all of the example usages of SDL_Haptics that I have seen have used
SDL_Delay(), so I believe that is the correct way to accomplish this. I would definitely be interested in finding out if you see substantially different behavior with functionally identical code that is not using the Engine.
We have not heard back from you for a while, so I will be marking this post as resolved for internal tracking purposes.
As I mentioned previously, it looks like your sample implementation of the SDL_Haptics subsystem appears to be correct when the SDL_Delay call is included, since all of the example implementations that I have seen include the SDL_Delay call. I am still not certain about the SDL_Haptics subsystem being asynchronous by default, though.
I managed to do what you asked. In this file there are two VS solutions – one a standalone SDL program that invokes my plugin and doesn’t use the engine, the other a UE4 project that also uses my plugin.
In both of them, the plugin uses a separate thread for haptic API calls. The separate thread will start the rumble, then call SDL_Delay, then destroy the effect. Meanwhile the main thread continues doing other things and never calls SDL_Delay.
In the standalone program this works fine. In the UE4 project it doesn’t – nothing happens at all.
On controlysis.cpp line 135 there is a commented call to SDL_Delay on the main thread, if you uncomment it in the UE4 project then this allows the rumble to happen at the expense of freezing the main thread.
In conclusion, the same plugin code has different behavior in and out of UE4. I think the engine is doing something upon conclusion of the UFUNCTION that makes the rumble stop, even though it’s going on in another thread.
Looks finally fixed in UE 4.23