Need some help with multithreading and UBTTasks

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;
}