Thread safe manipulation of game play objects/actors

Hello,

I´ve been trying to implement an FRunnable Thread that calls an Event (BlueprintImplementableEvent).

In a blank C++ project where I have implemented the plugin, this works (rather stable, with the help of appropriately timed sleep commands).

Today I tried to include this plugin functionality in a VehicleExample project, and here it was very unstable. The exception I get is the following:


UE4Editor_CoreUObject!TArray<FScriptTraceStackNode,FDefaultAllocator>::Emplace<FScriptTraceStackNode const & __ptr64>() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\core\public\containers\array.h:1735]
UE4Editor_CoreUObject!FFrame::FFrame() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\public\uobject\stack.h:192]
UE4Editor_CoreUObject!UObject::ProcessEvent() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:985]
UE4Editor_Engine!AActor::ProcessEvent() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\engine\private\actor.cpp:580]
UE4Editor_RudiPlugin!ARudiEvents::RowerEventIntensity() [c:\users\mf\documents\unreal projects\myproject12\plugins\rudiplugin\intermediate\build\win64\ue4editor\inc\rudiplugin\rudiplugin.generated.cpp:50]
UE4Editor_RudiPlugin!FRudiThread::Run() [c:\users\mf\documents\unreal projects\myproject12\plugins\rudiplugin\source\rudiplugin\private\rudithread.cpp:124]
UE4Editor_Core!FRunnableThreadWin::Run() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\core\private\windows\windowsrunnablethread.cpp:74]

When I try to debug in the blueprint graph, I get another exception:


Unknown exception - code 00000001 (first/second chance not available)

"Assertion failed: IsThreadSafeForSlateRendering() [File:D:\BuildFarm\buildmachine_++depot+UE4-Releases+4.10\Engine\Source\Runtime\SlateCore\Public\Application\SlateApplic

UE4Editor_Core!FDebug::AssertFailed() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\core\private\misc\outputdevice.cpp:374]
UE4Editor_SlateCore!FSlateApplicationBase::Get() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\slatecore\public\application\slateapplicationbase.h:390]
UE4Editor_SlateCore!SWidget::RegisterActiveTimer() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\slatecore\private\widgets\swidget.cpp:751]
UE4Editor_SlateCore!FCurveSequence::RegisterActiveTimerIfNeeded() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\slatecore\private\animation\curvesequence.cpp:265]
UE4Editor_SlateCore!FCurveSequence::Play() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\slatecore\private\animation\curvesequence.cpp:75]
UE4Editor_Slate!SDockTab::FlashTab() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\slate\private\widgets\docking\sdocktab.cpp:406]
UE4Editor_Slate!FTabManager::DrawAttention() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\slate\private\framework\docking	abmanager.cpp:864]
UE4Editor_Slate!FGlobalTabmanager::DrawAttentionToTabManager() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\slate\private\framework\docking	abmanager.cpp:1685]
UE4Editor_UnrealEd!SStandaloneAssetEditorToolkitHost::BringToFront() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\editor\unrealed\private	oolkits\sstandaloneasseteditortoolkithost.cpp:222]
UE4Editor_UnrealEd!FBaseToolkit::BringToolkitToFront() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\editor\unrealed\private	oolkits\basetoolkit.cpp:127]
UE4Editor_UnrealEd!FAssetEditorManager::FindEditorForAsset() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\editor\unrealed\private	oolkits\asseteditormanager.cpp:108]
UE4Editor_UnrealEd!FAssetEditorManager::OpenEditorForAsset() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\editor\unrealed\private	oolkits\asseteditormanager.cpp:261]
UE4Editor_UnrealEd!FKismetEditorUtilities::GetIBlueprintEditorForObject() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\editor\unrealed\private\kismet2\kismet2.cpp:1692]
UE4Editor_UnrealEd!FKismetEditorUtilities::BringKismetToFocusAttentionOnObject() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\editor\unrealed\private\kismet2\kismet2.cpp:1747]
UE4Editor_UnrealEd!FKismetDebugUtilities::AttemptToBreakExecution() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\editor\unrealed\private\kismet2\kismetdebugutilities.cpp:477]
UE4Editor_UnrealEd!FKismetDebugUtilities::OnScriptException() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\editor\unrealed\private\kismet2\kismetdebugutilities.cpp:266]
UE4Editor_UnrealEd!TBaseStaticDelegateInstance<void __cdecl(UObject const * __ptr64,FFrame const & __ptr64,FBlueprintExceptionInfo const & __ptr64)>::ExecuteIfSafe() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\core\public\delegates\delegateinstancesimpl_variadics.inl:921]
UE4Editor_CoreUObject!TBaseMulticastDelegate<void,UObject const * __ptr64,FFrame const & __ptr64,FBlueprintExceptionInfo const & __ptr64>::Broadcast() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\core\public\delegates\delegatesignatureimpl_variadics.inl:809]
UE4Editor_CoreUObject!UObject::execBreakpoint() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:1287]
UE4Editor_CoreUObject!UObject::ProcessInternal() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:698]
UE4Editor_CoreUObject!UObject::CallFunction() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:608]
UE4Editor_CoreUObject!UObject::execVirtualFunction() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:1842]
UE4Editor_CoreUObject!UObject::ProcessInternal() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:698]
UE4Editor_CoreUObject!UFunction::Invoke() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\private\uobject\class.cpp:4198]
UE4Editor_CoreUObject!UObject::ProcessEvent() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\coreuobject\private\uobject\scriptcore.cpp:1053]
UE4Editor_Engine!AActor::ProcessEvent() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\engine\private\actor.cpp:580]
UE4Editor_RudiPlugin!ARudiEvents::RowerEventIntensity() [c:\users\mf\documents\unreal projects\myproject12\plugins\rudiplugin\intermediate\build\win64\ue4editor\inc\rudiplugin\rudiplugin.generated.cpp:50]
UE4Editor_RudiPlugin!FRudiThread::Run() [c:\users\mf\documents\unreal projects\myproject12\plugins\rudiplugin\source\rudiplugin\private\rudithread.cpp:124]
UE4Editor_Core!FRunnableThreadWin::Run() [d:\buildfarm\buildmachine_++depot+ue4-releases+4.10\engine\source\runtime\core\private\windows\windowsrunnablethread.cpp:74]

What would be a safe approach to call a Blueprint Event from inside a running Thread (currently I pass the Blueprint Actor into the FThread class and raise the event from inside the Run method)?

I ran into this issue too!

You could try using a thread safe counter that the game thread checks for and then runs, but in general BP itself doesnt like multi threading so its only good for feedback not for core implementation.

I implemented a multi-threaded pathing system that sends FString data to a UMG visual log in Blueprints using a FString Array and just indicating to the game thread when it was safe to read from this FString data.

Multi-threaded pathing system with async generation / loading / UMG log

I used threadsafecounters to fire off BP events as needed, but these did not have any parameters.

If you need to send data to BP one of my two methods above should work.

One thing that wont work is trying to do BP stuff inside the thread, you can access data or trigger BP events on next game tick, but really dont think there’s a way to multi thread BP logic itself right now.

Relevant code:



FThreadSafeCounter ThreadSafeGenCounter_TotalPairs;

UFUNCTION(BlueprintPure,Category="Joy Pathing Map")
int32 PathMap_Gen_GetTotalPairs()
{ 
	return ThreadSafeGenCounter_TotalPairs.GetValue();
} 

//.cpp
ThreadSafeGenCounter_TotalPairs.Increment();


You could use threadsafe counter 0 or 1 to indicate when safe to read a BP exposed FString array in UMG.

But none of this has anything to do with actually running BP logic from inside a multi-threaded context cause I dont think that can be done yet :slight_smile:

I think it will be one day though!

Video of Async Calc/Loading Exposed to BP/UMG

Actually There Must Be a Way

On second thought there must be a way to safely send message to Blueprints, because I am doing it via a UDP Socket thread in this wiki tutorial:

If you use a delegate that is sending threadsafe data you should be fine

//UdpSocketReceiver.h



/**
 * Asynchronously receives data from an UDP socket.
 */
class FUdpSocketReceiver
	: public FRunnable




/**
 * Temporary fix for concurrency crashes. This whole class will be redesigned.
 */
typedef TSharedPtr<FArrayReader, **ESPMode::ThreadSafe**> FArrayReaderPtr;

/**
 * Delegate type for received data.
 *
 * The first parameter is the received data.
 * The second parameter is sender's IP endpoint.
 */
DECLARE_DELEGATE_TwoParams(FOnSocketDataReceived, const FArrayReaderPtr&, const FIPv4Endpoint&);


I will go do some testing. :slight_smile:

Okay my testing results:

If I use a wait time with thread safe data before sending to BP, it works great.

But if I just let the thread go full throttle than it crashes Blueprints pretty quick.

Have you tried simply adding a wait time to your thread?

Even a wait time of 0.01 is not crashing.

0.001 or 0.0001 does crash though :slight_smile:

That will still have advantages over using game thread, even if the other thread is not running at infinite speeds.

You can try sleeping for various numbers of seconds, putting this inside your Run() (ensuring it will slow down whenever you are sending a message to BP)



FPlatformProcess::Sleep(seconds);


:heart:

1 Like

[

Have you tried simply adding a wait time to your thread?

[/QUOTE]

Hi , thanks for the insightful input, very cool. My Thread currently sleeps 0.1 seconds, but at the end of my Run(). I will try to sleep between Event calls and test.

I also stumbled upon Unreal Message Bus, this pattern would also fit my use-case (Serial device (Rower) in a Thread passing data to the game Thread). I will also investigate that, seems also like a clean thread-safe solution.

Regards

In principle a stable situation using Unreal Messaging Bus, but…

Ok, using UMB seems to be stable. I have now two message endpoints:

//TestComponent.cpp (This part subscribes to the message handle that is published in the external thread (FTestMessagingPing). In this case it returns the context, including the message address, to the external thread. It also defines the message handle that is used to receive the input from the external thread)


UTestComponent::UTestComponent(const FObjectInitializer& ObjectInitializer)
: Super(ObjectInitializer)
{

	// initialize messaging
	MessageEndpoint = FMessageEndpoint::Builder("FTestComponent")
		.Handling<FTestMessagingPing>(this, &UTestComponent::HandlePingMessage)
		.Handling<FTestMessagingInput>(this, &UTestComponent::HandleInputMessage)
		.ReceivingOnThread(ENamedThreads::GameThread);

	if (MessageEndpoint.IsValid())
	{
		MessageEndpoint->Subscribe<FTestMessagingPing>();
	}
}

void UTestComponent::HandlePingMessage(const FTestMessagingPing& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
	SendPong(Context); //sends Context to the external thread using FTestMessagingPong
}

void UTestComponent::SendPong(const IMessageContextRef& Context)
{
...
    FTestMessagingPong* Message = new FTestMessagingPong();
...
    MessageEndpoint->Send(Message, Context->GetSender());
...
}

void UTestComponent::HandleInputMessage(const FTestMessagingInput& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
...
        UTestComponent::DeviceInfo.DeviceIntensity = Message.CurrentIntensity;
...
	UTestComponent::TestEventsBP->DeviceEventIntensity(UTestComponent::DeviceInfo.DeviceIntensity);  //raises BP Event
...
}


//TestThread.cpp (this is used in the external thread to publish the message handle to find the subscriber (UTestComponent in the game thread). The subscriber will then send the message endpoint address so that both can communicate directly. In Run(), the input is sent to the game thread using the message endpoints.)


FTestThread::FTestThread()
{

	MessageEndpoint = FMessageEndpoint::Builder("FTestThread")
		.Handling<FTestMessagingPong>(this, &FTestThread::HandleMessage);

	if (MessageEndpoint.IsValid())
	{
		MessageEndpoint->Publish(new FTestMessagingPing(), EMessageScope::Network);
		Thread = FRunnableThread::Create(this, TEXT("FTestThread"), 0, TPri_Normal, FPlatformAffinity::GetPoolThreadMask());
	}
}

void FTestThread::HandleMessage(const FTestMessagingPong& Message, const TSharedRef<IMessageContext, ESPMode::ThreadSafe>& Context)
{
	TestComponentAddress = Context->GetSender(); //stores the message address from the UTestComponent (game thread) for direct communication
}

uint32 FTestThread::Run()
{
...
        FTestMessagingInput* Message = new FTestMessagingInput();
...
	MessageEndpoint->Send(Message, TestComponentAddress); //Sends the Message back to the game thread
...					
}


This works fine. The problem I have now is that inside Run() I am reading from the serial port using ReadFile() and extracting the relevant strings so that I can send in the messages. After a short while Run() just stops executing. References to the Thread are still there, but it is no longer running. Without ReadFile() this does not happen, so I suspect that this is the culprit. No idea how to catch that exception though. Will keep looking.