Files
UnrealEngineUWP/Engine/Source/Developer/FunctionalTesting/Private/FunctionalAITest.cpp
yoan stamant 3ee85d0283 [NavigationSystem] updating API and various part of the navigation framework to support registration of non UObject elements.
- Added FNavigationRelevantElement as the main structure used to provide required information. The struct is meant to remove the dependencies on INavRelevantInterface and UObject. It currently provides constructors dedicated to handle current components and other UObjects implementing the interface.

#rb Aris.Theophanidis

[CL 35531152 by yoan stamant in ue5-main branch]
2024-08-14 08:49:00 -04:00

539 lines
14 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "FunctionalAITest.h"
#include "TimerManager.h"
#include "Engine/World.h"
#include "FunctionalTestingModule.h"
#include "FunctionalTestingManager.h"
#include "NavigationSystem.h"
#include "AI/Navigation/NavAreaBase.h"
#include "AI/Navigation/NavigationElement.h"
#include "AIController.h"
#include "Blueprint/AIBlueprintHelperLibrary.h"
#include "NavMesh/RecastNavMesh.h"
#include "NavigationOctree.h"
#include UE_INLINE_GENERATED_CPP_BY_NAME(FunctionalAITest)
AFunctionalAITestBase::AFunctionalAITestBase( const FObjectInitializer& ObjectInitializer )
: Super(ObjectInitializer)
, CurrentSpawnSetIndex(INDEX_NONE)
, bSingleSetRun(false)
{
SpawnLocationRandomizationRange = 0.f;
bWaitForNavMesh = true;
bDebugNavMeshOnTimeout = false;
}
bool AFunctionalAITestBase::IsOneOfSpawnedPawns(AActor* Actor)
{
APawn* Pawn = Cast<APawn>(Actor);
return Pawn != NULL && SpawnedPawns.Contains(Pawn);
}
void AFunctionalAITestBase::BeginPlay()
{
// do a post-load step and remove all disabled spawn sets
RemoveSpawnSetIfPredicate([&](FAITestSpawnSetBase& SpawnSet) {
if (SpawnSet.bEnabled == false)
{
UE_LOG(LogFunctionalTest, Log, TEXT("Removing disabled spawn set \'%s\'."), *SpawnSet.Name.ToString());
return true;
}
return false;
});
// update all spawn info that doesn't have spawn location set, and set spawn set name
ForEachSpawnSet([&](FAITestSpawnSetBase& SpawnSet) {
SpawnSet.ForEachSpawnInfo([&](FAITestSpawnInfoBase& SpawnInfo) {
SpawnInfo.SpawnSetName = SpawnSet.Name;
if (SpawnInfo.SpawnLocation == NULL)
{
SpawnInfo.SpawnLocation = SpawnSet.FallbackSpawnLocation ? SpawnSet.FallbackSpawnLocation : this;
}
});
});
Super::BeginPlay();
}
bool AFunctionalAITestBase::RunTest(const TArray<FString>& Params)
{
KillOffSpawnedPawns();
ClearPendingDelayedSpawns();
RandomNumbersStream.Reset();
bSingleSetRun = Params.Num() > 0;
if (bSingleSetRun)
{
TTypeFromString<int32>::FromString(CurrentSpawnSetIndex, *Params[0]);
}
else
{
++CurrentSpawnSetIndex;
}
if (!IsValidSpawnSetIndex(CurrentSpawnSetIndex))
{
return false;
}
return Super::RunTest(Params);
}
void AFunctionalAITestBase::StartTest()
{
Super::StartTest();
StartSpawning();
}
bool AFunctionalAITestBase::IsReady_Implementation()
{
return Super::IsReady_Implementation() && (bWaitForNavMesh == false || IsNavMeshReady());
}
void AFunctionalAITestBase::OnTimeout()
{
// tracking for FORT-42587, FORT-42994
// - log pending navmesh rebuilds / dirty areas
// - check if area modifiers from navoctree were applied
UNavigationSystemV1* NavSys = bDebugNavMeshOnTimeout ? FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld()) : nullptr;
if (NavSys)
{
const ARecastNavMesh* Navmesh = NavSys ? Cast<ARecastNavMesh>(NavSys->GetDefaultNavDataInstance()) : nullptr;
UE_LOG(LogFunctionalTest, Log, TEXT("Test timed out, log details for: %s"), *GetNameSafe(Navmesh));
UE_LOG(LogFunctionalTest, Log, TEXT("> dirty areas? %s"), NavSys->HasDirtyAreasQueued() ? TEXT("YES") : TEXT("no"));
const FNavigationOctree* NavigationOctree = NavSys->GetNavOctree();
FNavigationOctreeFilter AreaFilter;
AreaFilter.bIncludeAreas = true;
AreaFilter.bIncludeGeometry = false;
AreaFilter.bIncludeMetaAreas = true;
AreaFilter.bIncludeOffmeshLinks = false;
const FVector TransformedOrigin = GetTransform().TransformPosition(NavMeshDebugOrigin);
const FBox DebugBounds = FBox::BuildAABB(TransformedOrigin, NavMeshDebugExtent);
NavigationOctree->FindElementsWithBoundsTest(DebugBounds, [&AreaFilter, &Navmesh](const FNavigationOctreeElement& Element)
{
if (Element.IsMatchingFilter(AreaFilter))
{
const FCompositeNavModifier NavModifier = Element.GetModifierForAgent(&Navmesh->GetConfig());
const TArray<FAreaNavModifier> AreaMods = NavModifier.GetAreas();
FString DebugAreaNames;
for (int32 Idx = 0; Idx < AreaMods.Num(); Idx++)
{
DebugAreaNames += GetNameSafe(AreaMods[Idx].GetAreaClass().Get());
DebugAreaNames += TEXT(',');
}
UE_LOG(LogFunctionalTest, Log, TEXT("> modifier, owner:%s areas:%s"), *Element.GetSourceElement()->GetName(), *DebugAreaNames);
}
});
}
Super::OnTimeout();
}
void AFunctionalAITestBase::StartSpawning()
{
if (bWaitForNavMesh && !IsNavMeshReady())
{
GetWorldTimerManager().SetTimer(NavmeshDelayTimer, this, &AFunctionalAITestBase::StartSpawning, 0.5f, false);
return;
}
FAITestSpawnSetBase* SpawnSet = GetSpawnSet(CurrentSpawnSetIndex);
if (!SpawnSet)
{
FinishTest(EFunctionalTestResult::Failed, FString::Printf(TEXT("Unable to use spawn set: %d"), CurrentSpawnSetIndex));
return;
}
UWorld* World = GetWorld();
check(World);
bool bSuccessfullySpawnedAll = true;
// NOTE: even if some pawns fail to spawn we don't stop spawning to find all spawns that will fails.
// all spawned pawns get filled off in case of failure.
CurrentSpawnSetName = SpawnSet->Name.ToString();
int32 SpawnInfoIndex = 0;
SpawnSet->ForEachSpawnInfo([&](FAITestSpawnInfoBase& SpawnInfo) {
if (SpawnInfo.IsValid())
{
if (SpawnInfo.PreSpawnDelay > 0)
{
PendingDelayedSpawns.Add(FPendingDelayedSpawn(CurrentSpawnSetIndex, SpawnInfoIndex, SpawnInfo.NumberToSpawn, SpawnInfo.PreSpawnDelay));
}
else if (SpawnInfo.SpawnDelay == 0.0)
{
for (int32 SpawnedCount = 0; SpawnedCount < SpawnInfo.NumberToSpawn; ++SpawnedCount)
{
bSuccessfullySpawnedAll &= SpawnInfo.Spawn(this);
}
}
else
{
bSuccessfullySpawnedAll &= SpawnInfo.Spawn(this);
if (SpawnInfo.NumberToSpawn > 1)
{
PendingDelayedSpawns.Add(FPendingDelayedSpawn(CurrentSpawnSetIndex, SpawnInfoIndex, SpawnInfo.NumberToSpawn - 1, SpawnInfo.SpawnDelay));
}
}
}
else
{
const FString SpawnFailureMessage = FString::Printf(TEXT("Spawn set \'%s\' contains invalid entry at index %d")
, *SpawnSet->Name.ToString()
, SpawnInfoIndex);
UE_LOG(LogFunctionalTest, Warning, TEXT("%s"), *SpawnFailureMessage);
bSuccessfullySpawnedAll = false;
}
++SpawnInfoIndex;
});
if (bSuccessfullySpawnedAll == false)
{
KillOffSpawnedPawns();
// wait a bit if it's in the middle of StartTest call
FTimerHandle DummyHandle;
World->GetTimerManager().SetTimer(DummyHandle, this, &AFunctionalAITestBase::OnSpawningFailure, 0.1f, false);
}
else
{
if (PendingDelayedSpawns.Num() > 0)
{
SetActorTickEnabled(true);
}
}
}
void AFunctionalAITestBase::OnSpawningFailure()
{
FinishTest(EFunctionalTestResult::Failed, TEXT("Unable to spawn AI"));
}
bool AFunctionalAITestBase::WantsToRunAgain() const
{
return bSingleSetRun == false && IsValidSpawnSetIndex(CurrentSpawnSetIndex + 1);
}
void AFunctionalAITestBase::GatherRelevantActors(TArray<AActor*>& OutActors) const
{
Super::GatherRelevantActors(OutActors);
ForEachSpawnSet([&OutActors](const FAITestSpawnSetBase& SpawnSet) {
if (SpawnSet.FallbackSpawnLocation)
{
OutActors.AddUnique(SpawnSet.FallbackSpawnLocation);
}
SpawnSet.ForEachSpawnInfo([&OutActors](const FAITestSpawnInfoBase& SpawnInfo) {
if (SpawnInfo.SpawnLocation)
{
OutActors.AddUnique(SpawnInfo.SpawnLocation);
}
});
});
for (auto Pawn : SpawnedPawns)
{
if (Pawn)
{
OutActors.Add(Pawn);
}
}
}
void AFunctionalAITestBase::CleanUp()
{
Super::CleanUp();
CurrentSpawnSetIndex = INDEX_NONE;
KillOffSpawnedPawns();
ClearPendingDelayedSpawns();
}
FString AFunctionalAITestBase::GetAdditionalTestFinishedMessage(EFunctionalTestResult TestResult) const
{
FString ResultStr;
if (SpawnedPawns.Num() > 0)
{
if (CurrentSpawnSetName.Len() > 0 && CurrentSpawnSetName != TEXT("None"))
{
ResultStr = FString::Printf(TEXT("spawn set \'%s\', pawns: "), *CurrentSpawnSetName);
}
else
{
ResultStr = TEXT("pawns: ");
}
for (int32 PawnIndex = 0; PawnIndex < SpawnedPawns.Num(); ++PawnIndex)
{
ResultStr += FString::Printf(TEXT("%s, "), *GetNameSafe(SpawnedPawns[PawnIndex]));
}
}
return ResultStr;
}
FString AFunctionalAITestBase::GetReproString() const
{
return FString::Printf(TEXT("%s%s%d"), *(GetFName().ToString())
, FFunctionalTesting::ReproStringParamsSeparator
, CurrentSpawnSetIndex);
}
void AFunctionalAITestBase::KillOffSpawnedPawns()
{
for (int32 PawnIndex = 0; PawnIndex < SpawnedPawns.Num(); ++PawnIndex)
{
if (SpawnedPawns[PawnIndex])
{
SpawnedPawns[PawnIndex]->Destroy();
}
}
SpawnedPawns.Reset();
}
void AFunctionalAITestBase::ClearPendingDelayedSpawns()
{
SetActorTickEnabled(false);
PendingDelayedSpawns.Reset();
}
void AFunctionalAITestBase::Tick(float DeltaSeconds)
{
Super::Tick(DeltaSeconds);
for (auto& DelayedSpawn : PendingDelayedSpawns)
{
DelayedSpawn.Tick(DeltaSeconds, this);
}
}
void AFunctionalAITestBase::AddSpawnedPawn(APawn& SpawnedPawn)
{
SpawnedPawns.Add(&SpawnedPawn);
OnAISpawned.Broadcast(Cast<AAIController>(SpawnedPawn.GetController()), &SpawnedPawn);
}
FVector AFunctionalAITestBase::GetRandomizedLocation(const FVector& Location) const
{
return Location + FVector(RandomNumbersStream.FRandRange(-SpawnLocationRandomizationRange, SpawnLocationRandomizationRange), RandomNumbersStream.FRandRange(-SpawnLocationRandomizationRange, SpawnLocationRandomizationRange), 0);
}
bool AFunctionalAITestBase::IsNavMeshReady() const
{
UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent<UNavigationSystemV1>(GetWorld());
if (NavSys && NavSys->NavDataSet.Num() > 0 && !NavSys->IsNavigationBuildInProgress())
{
return true;
}
return false;
}
const FAITestSpawnInfoBase* AFunctionalAITestBase::GetSpawnInfo(const int32 SpawnSetIndex, const int32 SpawnInfoIndex) const
{
const FAITestSpawnSetBase* SpawnSet = GetSpawnSet(SpawnSetIndex);
return SpawnSet ? SpawnSet->GetSpawnInfo(SpawnInfoIndex) : nullptr;
}
FAITestSpawnInfoBase* AFunctionalAITestBase::GetSpawnInfo(const int32 SpawnSetIndex, const int32 SpawnInfoIndex)
{
FAITestSpawnSetBase* SpawnSet = GetSpawnSet(SpawnSetIndex);
return SpawnSet ? SpawnSet->GetSpawnInfo(SpawnInfoIndex) : nullptr;
}
bool AFunctionalAITestBase::Spawn(const int32 SpawnSetIndex, const int32 SpawnInfoIndex)
{
const FAITestSpawnInfoBase* SpawnInfo = GetSpawnInfo(SpawnSetIndex, SpawnInfoIndex);
return SpawnInfo ? SpawnInfo->Spawn(this) : false;
}
//----------------------------------------------------------------------//
// FAITestSpawnInfo
//----------------------------------------------------------------------//
bool FAITestSpawnInfo::Spawn(AFunctionalAITestBase* AITest) const
{
check(AITest);
bool bSuccessfullySpawned = false;
APawn* SpawnedPawn = UAIBlueprintHelperLibrary::SpawnAIFromClass(AITest->GetWorld(), PawnClass, BehaviorTree
, AITest->GetRandomizedLocation(SpawnLocation->GetActorLocation())
, SpawnLocation->GetActorRotation()
, /*bNoCollisionFail=*/true);
if (SpawnedPawn == NULL)
{
FString FailureMessage = FString::Printf(TEXT("Failed to spawn \'%s\' pawn (\'%s\' set) ")
, *GetNameSafe(PawnClass)
, *SpawnSetName.ToString());
UE_LOG(LogFunctionalTest, Warning, TEXT("%s"), *FailureMessage);
}
else if (SpawnedPawn->GetController() == NULL)
{
FString FailureMessage = FString::Printf(TEXT("Spawned Pawn %s (\'%s\' set) has no controller ")
, *GetNameSafe(SpawnedPawn)
, *SpawnSetName.ToString());
UE_LOG(LogFunctionalTest, Warning, TEXT("%s"), *FailureMessage);
}
else
{
IGenericTeamAgentInterface* TeamAgent = Cast<IGenericTeamAgentInterface>(SpawnedPawn);
if (TeamAgent == nullptr)
{
TeamAgent = Cast<IGenericTeamAgentInterface>(SpawnedPawn->GetController());
}
if (TeamAgent != nullptr)
{
TeamAgent->SetGenericTeamId(TeamID);
}
AITest->AddSpawnedPawn(*SpawnedPawn);
bSuccessfullySpawned = true;
}
return bSuccessfullySpawned;
}
//----------------------------------------------------------------------//
//
//----------------------------------------------------------------------//
void FPendingDelayedSpawn::Tick(float TimeDelta, AFunctionalAITestBase* AITest)
{
if (bFinished || !AITest)
{
return;
}
TimeToNextSpawn -= TimeDelta;
if (TimeToNextSpawn <= 0)
{
AITest->Spawn(SpawnSetIndex, SpawnInfoIndex);
if (--NumberToSpawnLeft <= 0)
{
bFinished = true;
}
else if (const FAITestSpawnInfoBase* SpawnInfo = AITest->GetSpawnInfo(SpawnSetIndex, SpawnInfoIndex))
{
TimeToNextSpawn = SpawnInfo->SpawnDelay;
}
}
}
const FAITestSpawnInfoBase* FAITestSpawnSet::GetSpawnInfo(const int32 SpawnInfoIndex) const
{
if (SpawnInfoContainer.IsValidIndex(SpawnInfoIndex))
{
return &SpawnInfoContainer[SpawnInfoIndex];
}
return nullptr;
}
FAITestSpawnInfoBase* FAITestSpawnSet::GetSpawnInfo(const int32 SpawnInfoIndex)
{
if (SpawnInfoContainer.IsValidIndex(SpawnInfoIndex))
{
return &SpawnInfoContainer[SpawnInfoIndex];
}
return nullptr;
}
bool FAITestSpawnSet::IsValidSpawnInfoIndex(const int32 Index) const
{
return SpawnInfoContainer.IsValidIndex(Index);
}
void FAITestSpawnSet::ForEachSpawnInfo(TFunctionRef<void(FAITestSpawnInfoBase&)> Predicate)
{
for (FAITestSpawnInfo& SpawnInfo : SpawnInfoContainer)
{
Predicate(SpawnInfo);
}
}
void FAITestSpawnSet::ForEachSpawnInfo(TFunctionRef<void(const FAITestSpawnInfoBase&)> Predicate) const
{
for (const FAITestSpawnInfo& SpawnInfo : SpawnInfoContainer)
{
Predicate(SpawnInfo);
}
}
void AFunctionalAITest::ForEachSpawnSet(TFunctionRef<void(const FAITestSpawnSetBase&)> Predicate) const
{
for (int32 Index = 0; Index < SpawnSets.Num(); ++Index)
{
Predicate(SpawnSets[Index]);
}
}
void AFunctionalAITest::ForEachSpawnSet(TFunctionRef<void(FAITestSpawnSetBase&)> Predicate)
{
for (int32 Index = 0; Index < SpawnSets.Num(); ++Index)
{
Predicate(SpawnSets[Index]);
}
}
void AFunctionalAITest::RemoveSpawnSetIfPredicate(TFunctionRef<bool(FAITestSpawnSetBase&)> Predicate)
{
bool bRemovedEntry = false;
for (int32 Index = SpawnSets.Num() - 1; Index >= 0; --Index)
{
if (Predicate(SpawnSets[Index]))
{
SpawnSets.RemoveAt(Index, EAllowShrinking::No);
bRemovedEntry = true;
}
}
if (bRemovedEntry)
{
SpawnSets.Shrink();
}
}
const FAITestSpawnSetBase* AFunctionalAITest::GetSpawnSet(const int32 SpawnSetIndex) const
{
if (SpawnSets.IsValidIndex(SpawnSetIndex))
{
return &SpawnSets[SpawnSetIndex];
}
return nullptr;
}
FAITestSpawnSetBase* AFunctionalAITest::GetSpawnSet(const int32 SpawnSetIndex)
{
if (SpawnSets.IsValidIndex(SpawnSetIndex))
{
return &SpawnSets[SpawnSetIndex];
}
return nullptr;
}
bool AFunctionalAITest::IsValidSpawnSetIndex(const int32 Index) const
{
return SpawnSets.IsValidIndex(Index);
}