Download

How do I send tasks to be completed on different threads?(Multi threading implementation)

Hello, I’ve followed Rama’s guide of implementing multi threading and sending them to tasks in C++, available from here A new, community-hosted Unreal Engine Wiki - Announcements and Releases - Unreal Engine Forums , but what I noticed is that there is either no real different or it the game may perform worse than before.

This is my code: static FGraphEventRef MultiThreadTestTask;

class FMultiThreadingTest { //what is needed in order to initiate the vision process in this class UVisionSystemComponent* _VisionSystem; UWorld* _WorldObject; FVector _WorldEyePosition; FRotator _WorldEyeRotation; AActor* _Actor; float _deltaTime;

public: //Constructor FMultiThreadingTest(UVisionSystemComponent* VisionSystem, UWorld* WorldObject, FVector WorldEyePosition, FRotator WorldEyeRotation, AActor* Actor, float deltaTime) { _VisionSystem = VisionSystem; _WorldObject = WorldObject; _WorldEyePosition = WorldEyePosition; _WorldEyeRotation = WorldEyeRotation; _Actor = Actor; _deltaTime = deltaTime; }

//task name
static const TCHAR* GetTaskName()
{
return TEXT(“FMultiThreadingTest”);
}

FORCEINLINE static TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FMultiThreadingTest, STATGROUP_TaskGraphTasks);
}

//return thread for the task
static ENamedThreads::Type GetDesiredThread()
{
return ENamedThreads::AnyThread;
}

static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
}

//Main Function
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
_VisionSystem->Process(_WorldObject, _WorldEyePosition, _WorldEyeRotation, _Actor, _deltaTime);
}
};

and I create the thread in the following way:

FGraphEventArray Prereq; MultiThreadTestTask = TGraphTask::CreateTask(⪻ereq,ENamedThreads::AnyThread).ConstructAndDispatchWhenReady(VisionSystem, GetWorld(), WorldEyePosition, WorldEyeRotation, this, deltaTime);

and the Vision System is:

void UVisionSystemComponent::Process(UWorld* world, FVector position, FRotator rotation, AActor* playerActor, float timeDelta) { SCOPE_CYCLE_COUNTER(STAT_TA_VisionProcess);

// if the vision system is enabled
if (ActiveSystem)
{
AInteractiveObject* IObject = FindBestObject(world, position, rotation, playerActor, FocusPoint);

 FGraphEventArray Prereq;
 VisionGameThreadTask = TGraphTask<FVisionGameThread>::CreateTask(&Prereq, ENamedThreads::AnyThread).ConstructAndDispatchWhenReady(ActiveSystem, world, &SpawnedReticle, position, rotation, ReticleMaximumDistanceVR, ReticleMaximumDistance2D, ReticleFloatDistance, ReticleFadeTimer, ReticleFadeTime, ReticleVisible, playerActor, Debug, DisableFocusReticle, IObject, ReticleMaterial, timeDelta, ReticleBlueprint, &HighlightedObject, &FocusedObject, HighlightTimer, UnFocusTimer, PSMoveTriggerDown, FocusTime, FocusPoint);

}

AuditNPCProximityList(world, position, rotation, playerActor);
}

with this being the thread setup:

static FGraphEventRef VisionGameThreadTask;

class FVisionGameThread { //what is needed in order to initiate the vision process in this class bool _ActiveSystem; UWorld* _WorldObject; AActor*& _SpawnedReticle; FVector _Position; FRotator _Rotation; float _ReticleMaxDistanceVR; float _ReticleMaxDistance2D; AActor* _PlayerActor; float _ReticleFloatDistance; float _ReticleFadeTimer; float _ReticleFadeTime; bool _Debug; bool _ReticleVisible; bool _DisableFocusReticle; AInteractiveObject* _IObject; UMaterialInstanceDynamic* _ReticleMaterial; float _DeltaTime; TSubclassOf _ReticleBlueprint; AInteractiveObject*& _HighlightedObject; AInteractiveObject*& _FocusedObject; float _HighlightTimer; float _UnFocusTimer; bool _PSMoveTriggerDown; float _FocusTime; FVector _FocusPoint;

public: //Constructor FVisionGameThread(bool ActiveSystem, UWorld* WorldObject, AActor SpawnedReticle, FVector Position, FRotator Rotation, float ReticleMaxDistanceVR, float ReticleMaxDistance2D, float ReticleFloatDistance,float& ReticleFadeTimer, float ReticleFadeTime, bool& ReticleVisible, AActor PlayerActor, bool Debug, bool DisableFocusReticle, AInteractiveObject IObject, UMaterialInstanceDynamic* ReticleMaterial, float DeltaTime, TSubclassOf ReticleBlueprint, AInteractiveObject **HighlightedObject, AInteractiveObject **FocusedObject, float& HighlightTimer, float& UnFocusTimer, bool PSMoveTriggerDown, float FocusTime, FVector FocusPoint) : _SpawnedReticle(*SpawnedReticle), _HighlightedObject(*HighlightedObject), _FocusedObject(*FocusedObject)

{
_ActiveSystem = ActiveSystem;
_WorldObject = WorldObject;
_Position = Position;
_Rotation = Rotation;
_ReticleMaxDistanceVR = ReticleMaxDistanceVR;
_ReticleMaxDistance2D = ReticleMaxDistance2D;
_PlayerActor = PlayerActor;
_ReticleFloatDistance = ReticleFloatDistance;
_ReticleFadeTimer = ReticleFadeTimer;
_ReticleFadeTimer = ReticleFadeTime;
_Debug = Debug;
_ReticleVisible = ReticleVisible;
_DisableFocusReticle = DisableFocusReticle;
_IObject = IObject;
_ReticleMaterial = ReticleMaterial;
_DeltaTime = DeltaTime;
_ReticleBlueprint = ReticleBlueprint;
_HighlightTimer = HighlightTimer;
_UnFocusTimer = UnFocusTimer;
_PSMoveTriggerDown = PSMoveTriggerDown;
_FocusTime = FocusTime;
_FocusPoint = FocusPoint;
}

//task name
static const TCHAR* GetTaskName()
{
return TEXT(“FVisionGameThread”);
}

FORCEINLINE static TStatId GetStatId()
{
RETURN_QUICK_DECLARE_CYCLE_STAT(FMultiThreadingTest, STATGROUP_TaskGraphTasks);
}

//return thread for the task
static ENamedThreads::Type GetDesiredThread()
{
return ENamedThreads::GameThread;
}

static ESubsequentsMode::Type GetSubsequentsMode()
{
return ESubsequentsMode::TrackSubsequents;
}

//Main Function
void DoTask(ENamedThreads::Type CurrentThread, const FGraphEventRef& MyCompletionGraphEvent)
{
if (_ActiveSystem)
{
// make sure current highlighted object is still in active objects list
// this happens when an object is focused on when it unregisters itself
if (_HighlightedObject != NULL && (!AInteractiveObject::InteractiveObjects.Contains(_HighlightedObject) || !_HighlightedObject->IsInteractable()))
{
// look away from object and unset
if (_HighlightedObject->VirtualHandEngaged)
{
if (_FocusedObject != NULL)
{
_FocusedObject->PSMoveDisengage();
}
}
_HighlightedObject->OnLookAway();
_HighlightedObject = NULL;
}

     // same as above but for focused object
     if (_FocusedObject != NULL && (!AInteractiveObject::InteractiveObjects.Contains(_FocusedObject) || !_FocusedObject->IsInteractable()))
     {
         // look away from object and unset
         if (_FocusedObject->VirtualHandEngaged)
         {
             if (_FocusedObject != NULL)
             {
                 _FocusedObject->PSMoveDisengage();
             }
         }
         _FocusedObject->OnLookAway();
         _FocusedObject = NULL;
     }

     /** Sort out highlighting/focusing on objects */
     if (_IObject == NULL)
     {
         // not looking at any objects, sort out any objects we were looking at

         // un-highlight object immediately if not looking at anything
         if (_HighlightedObject != NULL)
         {
             if (_HighlightedObject->VirtualHandEngaged)
             {
                 if (_FocusedObject != NULL)
                 {
                     _FocusedObject->PSMoveDisengage();
                 }
             }
             _HighlightedObject->OnLookAway();
             _HighlightedObject = NULL;
         }

         // un-focus on focused item if we're not looking at anything
         if (_FocusedObject != NULL)
         {
             _UnFocusTimer += _DeltaTime;
             if (_UnFocusTimer >= 0)
             {
                 if (_FocusedObject->VirtualHandEngaged)
                 {
                     if (_FocusedObject != NULL)
                     {
                         _FocusedObject->PSMoveDisengage();
                     }
                 }
                 _FocusedObject->OnLookAway();
                 _FocusedObject = NULL;
             }
         }
     }
     else 
     {

         // reset un-focus timer if we're looking at something
         _UnFocusTimer = 0;

         // still looking at a new object
         if (_HighlightedObject == _IObject)
         {
             _HighlightTimer += _DeltaTime;
             if (_HighlightTimer > _FocusTime)
             {
                 // new focused item!
                 if (_FocusedObject != NULL) 
                 {
                     // unset existing item first
                     if (_FocusedObject->VirtualHandEngaged)
                     {
                         if (_FocusedObject != NULL)
                         {
                             _FocusedObject->PSMoveDisengage();
                         }
                     }
                     _FocusedObject->OnLookAway();
                 }

                 // update new focused object and unset highlighted pointer
                 _FocusedObject = _IObject;
                 _HighlightedObject = NULL;

                 // send onfocus event
                 if (_PSMoveTriggerDown)
                 {
                     if (_FocusedObject != NULL)
                     {
                         _FocusedObject->PSMoveEngage();
                     }
                 }
                 _FocusedObject->OnFocus();
             }
         }
         else
         {
             // looking at something that isn't the current focused or highlighted object
             // unset existing Highlighted Object if exists
             if (_HighlightedObject != NULL) 
             {
                 if (_HighlightedObject->VirtualHandEngaged)
                 {
                     if (_FocusedObject != NULL)
                     {
                         _FocusedObject->PSMoveDisengage();
                     }
                 }
                 _HighlightedObject->OnLookAway();
                 _HighlightedObject = NULL;
             }

             // set new highlighted object (if it's not the currently focused object)
             if (_IObject != _FocusedObject)
             {
                 _HighlightedObject = _IObject;
                 _HighlightedObject->OnHighlight();
                 if (_FocusedObject != NULL)
                 {
                     if (_FocusedObject->VirtualHandEngaged) 
                         if (_FocusedObject != NULL) 
                         {
                             _FocusedObject->PSMoveDisengage();
                         }
                     _FocusedObject->OnLookAway();
                     _FocusedObject = NULL;
                 }
             }
             _HighlightTimer = 0;
         }
     }

     bool check = false;
     // return true if reticle already present! (and not disabled)
     if (_SpawnedReticle != NULL)
     {
         check = true;
         if (_DisableFocusReticle)
         {
             // hide already existing reticle
             _SpawnedReticle->SetActorHiddenInGame(true);

             // return false to stop processing the reticle
             check = false;
         }
     }
     else
     {
         // early out if the reticle isn't meant to be enabled anyway
         if (_DisableFocusReticle) check=false;
         else
         {

             // return false if reticle not set
             if (_ReticleBlueprint == NULL) check=false;
             else
             {
                 // spawn our reticle Blueprint!
                 _SpawnedReticle = _WorldObject->SpawnActor(_ReticleBlueprint);
                 if (_SpawnedReticle)
                 {
                     _SpawnedReticle->SetActorHiddenInGame(true);
                     _ReticleVisible = false;

                     // find player camera
                     ACharacter* Player = UGameplayStatics::GetPlayerCharacter(_WorldObject, 0);
                     if (Player)
                     {
                         // cast to our character class
                         ATheAssemblyCharacter* PlayerCharacter = Cast<ATheAssemblyCharacter>(Player);
                         if (PlayerCharacter)
                         {
                             // attach reticle to camera so it gets updated with head rotation/position
                             _SpawnedReticle->AttachRootComponentTo(PlayerCharacter->FirstPersonCameraComponent);
                         }
                     }


                     // make an instance of it's material
                     TArray<UActorComponent*> Meshes = _SpawnedReticle->GetComponentsByClass(UMeshComponent::StaticClass());
                     for (int meshIdx = 0; meshIdx < Meshes.Num(); meshIdx++)
                     {
                         UMeshComponent* Mesh = Cast<UMeshComponent>(Meshes[meshIdx]);
                         if (Mesh)
                         {
                             // find a suitable material on the reticle
                             for (int matIdx = 0; matIdx < Mesh->GetNumMaterials(); matIdx++)
                             {
                                 // check that the material contains a definition for FocusEffectIntensity before making a dynamic version
                                 UMaterialInterface* Mat = Mesh->GetMaterial(matIdx);
                                 float tmp;
                                 if (Mat && Mat->GetScalarParameterValue("Alpha", tmp))
                                 {
                                     // create new dynamic material from existing one
                                     _ReticleMaterial = Mesh->CreateAndSetMaterialInstanceDynamic(matIdx);
                                     _ReticleMaterial->SetScalarParameterValue("Alpha", .0f);

                                     // return early now we have one valid material!
                                     check = true;
                                 }
                             }
                         }
                     }
                     check = true;
                 }
                 else
                 {
                     check=false;
                 }
             }
         }
     }
     if (check)
     {
         // setup trace parameters
         FCollisionQueryParams TraceParams(FName(TEXT("VisionSystem Trace")), true);
         TraceParams.bTraceComplex = true;
         // ignore the player when tracing
         TraceParams.AddIgnoredActor(_PlayerActor);

         bool VREnabled = GEngine && GEngine->IsStereoscopic3D();

         // Hit result object
         FHitResult HitData(ForceInit);
         if (_WorldObject->LineTraceSingleByChannel(
             HitData,
             _Position,
             _Position + (_Rotation.Vector() * (VREnabled ? _ReticleMaxDistanceVR : _ReticleMaxDistance2D)),
             // trace by visibility
             ECC_Visibility,
             TraceParams
             ))
         {
             // move reticle to slightly in front of hit point
             FVector Direction = _Position - HitData.ImpactPoint;
             Direction.Normalize();
             // float ReticleFloatDistance cm in front of target point
             FVector Position = HitData.ImpactPoint + (_ReticleFloatDistance * Direction);
             _SpawnedReticle->SetActorLocation(Position);
         }
         else
         {

if WITH_EDITOR
if (_Debug && HitData.Actor != NULL)
{
//hit something else! Debug.
//DrawDebugString(_WorldObject, HitData.Location, FString::Printf(TEXT(“Wanted to hit %s -> got %s”), *_IObject->GetHumanReadableName(), *HitData.GetActor()->GetHumanReadableName()), NULL, FColor::Magenta, 0.1f);
}
endif
// just place reticle as far away as we allow (so it isn’t in the way of the player’s depth perception)
_SpawnedReticle->SetActorLocation(_Position + (_Rotation.Vector() * (VREnabled ? _ReticleMaxDistanceVR : _ReticleMaxDistance2D)));
}

         // make sure reticle is visible
         if (_DisableFocusReticle)
         {
             _SpawnedReticle->SetActorHiddenInGame(true);
             _ReticleVisible = true;
         }
         else
         {
             _SpawnedReticle->SetActorHiddenInGame(false);
             _ReticleVisible = false;
         }
     }
 }
 else
 {
     // make sure reticle is hidden if vision system is disabled
     if (_SpawnedReticle != NULL)
     {
         _SpawnedReticle->SetActorHiddenInGame(true);
         _ReticleFadeTimer = .0f;
         _ReticleVisible = false;
     }
 }// end: if (ActiveSystem) {    

 if (_SpawnedReticle != NULL && _ReticleVisible)
 {
     if (_ReticleMaterial != NULL)
     {
         // if fade time is zero, immediately make the reticle visible
         if (_ReticleFadeTime == 0)
         {
             _ReticleMaterial->SetScalarParameterValue("Alpha", 1.0f);
         }
         else
         {
             // process time passing
             _ReticleFadeTimer += _DeltaTime;

             if (_ReticleFadeTimer >= _ReticleFadeTime)
             {
                 _ReticleFadeTimer = _ReticleFadeTime;
             }

             _ReticleMaterial->SetScalarParameterValue("Alpha", _ReticleFadeTimer / _ReticleFadeTime);
         }
     }
 }
 else
 {
     // reset timer
     _ReticleFadeTimer = .0f;
 }

}

};

The vision system has been setup up like that because the threaded bit needs to run on the game thread. One thing I can notice by myself is the fact I use prereq as I can just pass NULL and be done with it.
The game runs with the above code and I can find the threads while in-game using stats.

I’m using Unreal Engine 4.11.