Hi, I am trying to put the heavy logic from one of my BTTasks on a seperate thread, but the Node doesn’t receive valid values and the task will never be completed. Maybe someone can tell me what I am missing…
.h
#pragma once
#include "CoreMinimal.h"
#include "BehaviorTree/BTTaskNode.h"
#include "Runnable.h"
#include "SearchForCover.generated.h"
/**
*
*/
UCLASS()
class MYPROJECT_API USearchForCover : public UBTTaskNode
{
GENERATED_BODY()
USearchForCover();
public:
void FindValidLocation(UBehaviorTreeComponent& OwnerComp, EBTNodeResult::Type& NodeResult);
EBTNodeResult::Type ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory) override;
float ExamineValidObject(FVector& OwnerLocation, FVector& PlayerLocation, FVector& Arrow1, FVector& Arrow2, bool& CheckSuccess);
protected:
UPROPERTY(EditAnywhere, Category = "Blackboard Keys")
FBlackboardKeySelector IsCoverAvailableKey;
UPROPERTY(EditAnywhere, Category = "Blackboard Keys")
FBlackboardKeySelector PlayerKey;
UPROPERTY(EditAnywhere, Category = "Blackboard Keys")
FBlackboardKeySelector MoveLocationKey;
UPROPERTY(EditAnywhere, Category = "Values")
float MaxAllowedRadiusToCover = 3000;
UPROPERTY(EditAnywhere, Category = "Values")
float MaxAllowedDistanceToPlayer = 400;
UPROPERTY(EditAnywhere, Category = "Values")
TSubclassOf<class ACoverObjectBase> CoverObject;
FVector TargetLocation;
bool bIsSuccessful;
bool bTargetArrow1;
bool bTargetArrow2;
};
class ExecuteSearchForCoverTask : public FRunnable
{
FRunnableThread* Thread;
FThreadSafeCounter StopTaskCounter;
public:
ExecuteSearchForCoverTask(UBehaviorTreeComponent* OwnerComp, EBTNodeResult::Type* NodeResult, USearchForCover* TaskNode);
virtual ~ExecuteSearchForCoverTask();
static ExecuteSearchForCoverTask* Runnable;
virtual bool Init();
virtual uint32 Run();
virtual void Stop();
void EnsureCompletion();
static ExecuteSearchForCoverTask* JoyInit(UBehaviorTreeComponent* OwnerComp, EBTNodeResult::Type* NodeResult, USearchForCover* TaskNode);
static void ShutDown();
static bool IsThreadFinished();
private:
USearchForCover* TaskNode;
UBehaviorTreeComponent* OwnerComp;
EBTNodeResult::Type* NodeResult;
};
.cpp
#include "SearchForCover.h"
#include "AIController.h"
#include "AICharacter.h"
#include "BehaviorTree/BlackboardComponent.h"
#include "Kismet/KismetSystemLibrary.h"
#include "CoverObjectBase.h"
#include "RunnableThread.h"
USearchForCover::USearchForCover()
{
bCreateNodeInstance = true;
}
void USearchForCover::FindValidLocation(UBehaviorTreeComponent& OwnerComp, EBTNodeResult::Type& NodeResult)
{
// refresh values
bTargetArrow1 = false;
bTargetArrow2 = false;
bIsSuccessful = false;
// Cache AI relevant data
AAIController* AIController = static_cast<AAIController*>(OwnerComp.GetAIOwner());
AAICharacter* AICharacter = static_cast<AAICharacter*>(AIController->GetPawn());
if (!ensure(AICharacter)) { NodeResult = EBTNodeResult::Failed; FinishLatentTask(OwnerComp, NodeResult); return; }
UBlackboardComponent* BlackboardComponent = OwnerComp.GetBlackboardComponent();
BlackboardComponent->SetValueAsBool(IsCoverAvailableKey.SelectedKeyName, false);
AActor* Player = static_cast<AActor*>(BlackboardComponent->GetValueAsObject(PlayerKey.SelectedKeyName));
if (!ensure(Player)) { NodeResult = EBTNodeResult::Failed; FinishLatentTask(OwnerComp, NodeResult); return; }
// Setting the Max distance for the cover location evaluation
float MaxDistanceSquared = MaxAllowedRadiusToCover * MaxAllowedRadiusToCover;
// Performing a radius check for valid cover objects
FVector OwnerLocation = AICharacter->GetActorLocation();
FVector PlayerLocation = Player->GetActorLocation();
TArray<TEnumAsByte<EObjectTypeQuery>> ObjectTypes;
TArray<AActor*> IgnoreList;
TArray<AActor*> RelevantResults;
UKismetSystemLibrary::SphereOverlapActors(this, OwnerLocation, MaxAllowedRadiusToCover, ObjectTypes, CoverObject, IgnoreList, RelevantResults);
// No targets found, means there is no cover in range
if (RelevantResults.Num() == 0) { NodeResult = EBTNodeResult::Succeeded; FinishLatentTask(OwnerComp, NodeResult); return; }
// Setting up the Max allowed distance between player and cover to be evaluated, also check the relation between player and ai in space
float MaxDistanceToPlayerSquared = MaxAllowedDistanceToPlayer * MaxAllowedDistanceToPlayer;
float ActualDistanceToPlayerSquared;
float RelationAiToPlayer = FVector::DotProduct(AICharacter->GetActorForwardVector(), (PlayerLocation - OwnerLocation).GetSafeNormal());
// Caching values as starting point for searching
ACoverObjectBase* FirstElement = static_cast<ACoverObjectBase*>(RelevantResults[0]);
if (!ensure(FirstElement)) { NodeResult = EBTNodeResult::Failed; FinishLatentTask(OwnerComp, NodeResult); return; }
ActualDistanceToPlayerSquared = (PlayerLocation - FirstElement->GetActorLocation()).SizeSquared();
FVector ArrowOne = FirstElement->GetArrowOneFwd();
FVector ArrowTwo = FirstElement->GetArrowTwoFwd();
float ShortestDistance = MaxDistanceSquared;
// AI is already at this location and the player is far enough away from it, meaning there is no need for a new evaluation
if (FirstElement->GetBlockingAI() == AICharacter && ActualDistanceToPlayerSquared > MaxDistanceToPlayerSquared && RelationAiToPlayer > 0)
{
BlackboardComponent->SetValueAsBool(IsCoverAvailableKey.SelectedKeyName, true);
NodeResult = EBTNodeResult::Succeeded;
FinishLatentTask(OwnerComp, NodeResult);
return;
}
// The first found cover is not blocked by another AI and the player is far enough away
if (!FirstElement->GetIsCoverBlocked() && ActualDistanceToPlayerSquared > MaxDistanceToPlayerSquared)
{
// Setting the first found element as temporary target for orientation purpose and as a starting point to start actual evaluation
ShortestDistance = ExamineValidObject(OwnerLocation, PlayerLocation, ArrowOne, ArrowTwo, bIsSuccessful);
if (bIsSuccessful)
{
BlackboardComponent->SetValueAsBool(IsCoverAvailableKey.SelectedKeyName, true);
if (bTargetArrow1)
{
TargetLocation = FirstElement->GetArrowOneLocation();
}
else if (bTargetArrow2)
{
TargetLocation = FirstElement->GetArrowTwoLocation();
}
else { NodeResult = EBTNodeResult::Failed; FinishLatentTask(OwnerComp, NodeResult); return; }
BlackboardComponent->SetValueAsVector(MoveLocationKey.SelectedKeyName, TargetLocation);
}
else {
UE_LOG(LogTemp, Warning, TEXT("No valid position was found even though one was in range!")) NodeResult = EBTNodeResult::Failed; FinishLatentTask(OwnerComp, NodeResult); return;
}
// If this is the only location then no further evaluation processing needs to be done
if (RelevantResults.Num() == 1 && RelationAiToPlayer > 0) { FirstElement->ReserveCover(AICharacter); NodeResult = EBTNodeResult::Succeeded; FinishLatentTask(OwnerComp, NodeResult); return;
}
}
// searching for the most valid cover position
ACoverObjectBase* Destination = FirstElement;
for (int32 i = 1; i < RelevantResults.Num(); i++)
{
ACoverObjectBase* CoverObject = static_cast<ACoverObjectBase*>(RelevantResults[i]);
if (CoverObject)
{
ActualDistanceToPlayerSquared = (PlayerLocation - CoverObject->GetActorLocation()).SizeSquared();
if (CoverObject->GetBlockingAI() == AICharacter && ActualDistanceToPlayerSquared > MaxDistanceToPlayerSquared && RelationAiToPlayer > 0)
{
BlackboardComponent->SetValueAsBool(IsCoverAvailableKey.SelectedKeyName, true);
NodeResult = EBTNodeResult::Succeeded;
FinishLatentTask(OwnerComp, NodeResult);
return;
}
if (CoverObject->GetIsCoverBlocked() || ActualDistanceToPlayerSquared < MaxDistanceToPlayerSquared) {continue;}
ArrowOne = CoverObject->GetArrowOneFwd();
ArrowTwo = CoverObject->GetArrowTwoFwd();
float Distance = ExamineValidObject(OwnerLocation, PlayerLocation, ArrowOne, ArrowTwo, bIsSuccessful);
if (Distance < ShortestDistance && bIsSuccessful)
{
Destination = CoverObject;
ShortestDistance = Distance;
if (bTargetArrow1)
{
TargetLocation = CoverObject->GetArrowOneLocation();
}
else if (bTargetArrow2)
{
TargetLocation = CoverObject->GetArrowTwoLocation();
}
else { NodeResult = EBTNodeResult::Failed; FinishLatentTask(OwnerComp, NodeResult); return; }
BlackboardComponent->SetValueAsVector(MoveLocationKey.SelectedKeyName, TargetLocation);
BlackboardComponent->SetValueAsBool(IsCoverAvailableKey.SelectedKeyName, true);
}
}
}
if (Destination)
{
Destination->ReserveCover(AICharacter);
}
NodeResult = EBTNodeResult::Succeeded;
FinishLatentTask(OwnerComp, NodeResult);
}
EBTNodeResult::Type USearchForCover::ExecuteTask(UBehaviorTreeComponent& OwnerComp, uint8* NodeMemory)
{
EBTNodeResult::Type Result;
ExecuteSearchForCoverTask::JoyInit(&OwnerComp, &Result, this);
return EBTNodeResult::InProgress;
}
float USearchForCover::ExamineValidObject(FVector& OwnerLocation, FVector& PlayerLocation, FVector& Arrow1, FVector& Arrow2, bool& CheckSuccess)
{
if (FVector::DotProduct((PlayerLocation - OwnerLocation).GetSafeNormal(), Arrow1) > 0)
{
CheckSuccess = true;
bTargetArrow1 = true;
bTargetArrow2 = false;
return (Arrow1 - OwnerLocation).SizeSquared();
}
else if (FVector::DotProduct((PlayerLocation - OwnerLocation).GetSafeNormal(), Arrow2) > 0)
{
CheckSuccess = true;
bTargetArrow1 = false;
bTargetArrow2 = true;
return (Arrow2 - OwnerLocation).SizeSquared();
}
CheckSuccess = false;
bTargetArrow1 = false;
bTargetArrow2 = false;
return MaxAllowedRadiusToCover * MaxAllowedRadiusToCover;
}
//=====================================================================================================================================================//
ExecuteSearchForCoverTask* ExecuteSearchForCoverTask::Runnable = nullptr;
ExecuteSearchForCoverTask::ExecuteSearchForCoverTask(UBehaviorTreeComponent* OwnerComp, EBTNodeResult::Type* NodeResult, USearchForCover* TaskNode)
{
this->OwnerComp = OwnerComp;
this->NodeResult = NodeResult;
this->TaskNode = TaskNode;
Thread = FRunnableThread::Create(this, TEXT("ExecuteSearchForCoverTask"), 0, TPri_BelowNormal);
}
ExecuteSearchForCoverTask::~ExecuteSearchForCoverTask()
{
delete Thread;
Thread = nullptr;
}
bool ExecuteSearchForCoverTask::Init()
{
return true;
}
uint32 ExecuteSearchForCoverTask::Run()
{
FPlatformProcess::Sleep(0.03);
if (StopTaskCounter.GetValue() == 0)
{
TaskNode->FindValidLocation(*OwnerComp, *NodeResult);
}
return 0;
}
void ExecuteSearchForCoverTask::Stop()
{
StopTaskCounter.Increment();
}
void ExecuteSearchForCoverTask::EnsureCompletion()
{
Stop();
Thread->WaitForCompletion();
}
ExecuteSearchForCoverTask* ExecuteSearchForCoverTask::JoyInit(UBehaviorTreeComponent* OwnerComp,
EBTNodeResult::Type* NodeResult, USearchForCover* TaskNode)
{
if (!Runnable && FPlatformProcess::SupportsMultithreading())
{
Runnable = new ExecuteSearchForCoverTask(OwnerComp, NodeResult, TaskNode);
}
return Runnable;
}
void ExecuteSearchForCoverTask::ShutDown()
{
if (Runnable)
{
Runnable->EnsureCompletion();
delete Runnable;
Runnable = nullptr;
}
}
bool ExecuteSearchForCoverTask::IsThreadFinished()
{
if (Runnable)
{
return Runnable->IsThreadFinished();
}
return true;
}