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 - Epic Developer Community 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(&Prereq,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.