// Copyright Epic Games, Inc. All Rights Reserved. #include "NavigationData.h" #include "AI/NavDataGenerator.h" #include "AI/Navigation/NavAgentInterface.h" #include "AI/Navigation/NavAreaBase.h" #include "AI/Navigation/NavigationDirtyArea.h" #include "AssetCompilingManager.h" #include "Components/PrimitiveComponent.h" #include "Engine/Engine.h" #include "NavAreas/NavArea.h" #include "NavFilters/NavigationQueryFilter.h" #include "NavigationSystem.h" #include "VisualLogger/VisualLogger.h" #include "Misc/ScopeLock.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(NavigationData) // set to NAVMESHVER_LANDSCAPE_HEIGHT at the moment of refactoring navigation // code out of the engine module. No point in using RecastNavMesh versioning // for NavigationData #define NAVDATAVER_LATEST 13 namespace UE::Navigation::Private { static int32 bDestroyNavDataInCleanUpAndMarkPendingKill = 1; static FAutoConsoleVariableRef CVarDestroyNavDataInCleanUpAndMarkPendingKill( TEXT("ai.DestroyNavDataInCleanUpAndMarkPendingKill"), bDestroyNavDataInCleanUpAndMarkPendingKill, TEXT("If set to 1 NavData will be destroyed in CleanUpAndMarkPendingKill rather than being marked as garbage.\n"), ECVF_Default); // This prevents accumulating SuspendedDirtyAreas indefinitely and eventually running out of memory. That could happen if SetRebuildSuspended // is enabled for long periods of time. The limit cannot be too low because moving complex geometry made of hundreds or thousands of Actors // can quickly create several thousands of DirtyAreas. // Careful: Reaching this limit causes a Rebuild All for this navigation data (if it ever resumes generation). static int32 MaxDirtyAreasCountWhileSuspended = 32768; static FAutoConsoleVariableRef CVarMaxDirtyAreasCountWhileSuspended( TEXT("ai.navigation.MaxDirtyAreasCountWhileSuspended"), MaxDirtyAreasCountWhileSuspended, TEXT("If a navigation data accumulates too many dirty areas because it's been indefinitely suspended, we'll stop accumulating them and rebuild the entire navigation data if it ever resumes building.\n"), ECVF_Default); } //----------------------------------------------------------------------// // FPathFindingQuery //----------------------------------------------------------------------// FPathFindingQuery::FPathFindingQuery(const UObject* InOwner, const ANavigationData& InNavData, const FVector& Start, const FVector& End, FSharedConstNavQueryFilter SourceQueryFilter, FNavPathSharedPtr InPathInstanceToFill, const FVector::FReal CostLimit, const bool bInRequireNavigableEndLocation) : FPathFindingQueryData(InOwner, Start, End, SourceQueryFilter, 0 /*InNavDataFlags*/, true /*bInAllowPartialPaths*/, CostLimit, bInRequireNavigableEndLocation), NavData(&InNavData), PathInstanceToFill(InPathInstanceToFill), NavAgentProperties(InNavData.GetConfig()) { if (!QueryFilter.IsValid() && NavData.IsValid()) { QueryFilter = NavData->GetDefaultQueryFilter(); } } FPathFindingQuery::FPathFindingQuery(const INavAgentInterface& InNavAgent, const ANavigationData& InNavData, const FVector& Start, const FVector& End, FSharedConstNavQueryFilter SourceQueryFilter, FNavPathSharedPtr InPathInstanceToFill, const FVector::FReal CostLimit, const bool bInRequireNavigableEndLocation) : FPathFindingQueryData(Cast(&InNavAgent), Start, End, SourceQueryFilter, 0 /*InNavDataFlags*/, true /*bInAllowPartialPaths*/, CostLimit, bInRequireNavigableEndLocation), NavData(&InNavData), PathInstanceToFill(InPathInstanceToFill), NavAgentProperties(InNavAgent.GetNavAgentPropertiesRef()) { if (!QueryFilter.IsValid() && NavData.IsValid()) { QueryFilter = NavData->GetDefaultQueryFilter(); } } FPathFindingQuery::FPathFindingQuery(FNavPathSharedRef PathToRecalculate, const ANavigationData* NavDataOverride) : FPathFindingQueryData(PathToRecalculate->GetQueryData()), NavData(NavDataOverride ? NavDataOverride : PathToRecalculate->GetNavigationDataUsed()), PathInstanceToFill(PathToRecalculate), NavAgentProperties(FNavAgentProperties::DefaultProperties) { if (PathToRecalculate->ShouldUpdateStartPointOnRepath() && (PathToRecalculate->GetSourceActor() != nullptr)) { const FVector NewStartLocation = PathToRecalculate->GetPathFindingStartLocation(); if (FNavigationSystem::IsValidLocation(NewStartLocation)) { StartLocation = NewStartLocation; } } if (PathToRecalculate->ShouldUpdateEndPointOnRepath() && (PathToRecalculate->GetGoalActor() != nullptr)) { const FVector NewEndLocation = PathToRecalculate->GetGoalLocation(); if (FNavigationSystem::IsValidLocation(NewEndLocation)) { EndLocation = NewEndLocation; } } if (NavData.IsValid()) { if (!QueryFilter.IsValid()) { QueryFilter = NavData->GetDefaultQueryFilter(); } NavAgentProperties = NavData->GetConfig(); } } FVector::FReal FPathFindingQuery::ComputeCostLimitFromHeuristic(const FVector& StartPos, const FVector& EndPos, const FVector::FReal HeuristicScale, const FVector::FReal CostLimitFactor, const FVector::FReal MinimumCostLimit) { if (CostLimitFactor == FLT_MAX) { return FLT_MAX; } else { const FVector::FReal OriginalHeuristicEstimate = HeuristicScale * FVector::Dist(StartPos, EndPos); return FMath::Clamp(CostLimitFactor * OriginalHeuristicEstimate, MinimumCostLimit, TNumericLimits::Max()); } } //----------------------------------------------------------------------// // FAsyncPathFindingQuery //----------------------------------------------------------------------// uint32 FAsyncPathFindingQuery::LastPathFindingUniqueID = INVALID_NAVQUERYID; FAsyncPathFindingQuery::FAsyncPathFindingQuery(const UObject* InOwner, const ANavigationData& InNavData, const FVector& Start, const FVector& End, const FNavPathQueryDelegate& Delegate, FSharedConstNavQueryFilter SourceQueryFilter, const FVector::FReal CostLimit) : FPathFindingQuery(InOwner, InNavData, Start, End, SourceQueryFilter) , QueryID(GetUniqueID()) , OnDoneDelegate(Delegate) , Mode(EPathFindingMode::Regular) { } FAsyncPathFindingQuery::FAsyncPathFindingQuery(const FPathFindingQuery& Query, const FNavPathQueryDelegate& Delegate, const EPathFindingMode::Type QueryMode) : FPathFindingQuery(Query) , QueryID(GetUniqueID()) , OnDoneDelegate(Delegate) , Mode(QueryMode) { } //----------------------------------------------------------------------// // FSupportedAreaData //----------------------------------------------------------------------// FSupportedAreaData::FSupportedAreaData(TSubclassOf NavAreaClass, int32 InAreaID) : AreaID(InAreaID), AreaClass(NavAreaClass) { if (AreaClass != NULL) { AreaClassName = AreaClass->GetName(); } else { AreaClassName = TEXT("Invalid"); } } //----------------------------------------------------------------------// // ANavigationData //----------------------------------------------------------------------// ANavigationData::ANavigationData(const FObjectInitializer& ObjectInitializer) : Super(ObjectInitializer) , bEnableDrawing(false) , bForceRebuildOnLoad(false) , bAutoDestroyWhenNoNavigation(false) , bCanBeMainNavData(true) , bCanSpawnOnRebuild(true) , RuntimeGeneration(ERuntimeGenerationType::LegacyGeneration) //TODO: set to a valid value once bRebuildAtRuntime_DEPRECATED is removed , DataVersion(NAVDATAVER_LATEST) , FindPathImplementation(NULL) , FindHierarchicalPathImplementation(NULL) , bRegistered(false) , bRebuildingSuspended(false) #if WITH_EDITORONLY_DATA , bIsBuildingOnLoad(false) #endif , NavDataUniqueID(GetNextUniqueID()) { PrimaryActorTick.bCanEverTick = true; bNetLoadOnClient = false; SetCanBeDamaged(false); DefaultQueryFilter = MakeShareable(new FNavigationQueryFilter()); ObservedPathsTickInterval = 0.5; // by giving NavigationData a root component we can detect changes to // actor's location and react to it (see ARecastNavMesh::PostRegisterAllComponents) USceneComponent* SceneComponent = CreateDefaultSubobject(TEXT("SceneComp")); RootComponent = SceneComponent; RootComponent->Mobility = EComponentMobility::Static; #if WITH_EDITORONLY_DATA bIsSpatiallyLoaded = false; #endif } uint16 ANavigationData::GetNextUniqueID() { static FThreadSafeCounter StaticID(INVALID_NAVDATA); return IntCastChecked(StaticID.Increment()); } void ANavigationData::PostInitProperties() { Super::PostInitProperties(); if (!IsValidChecked(this)) { return; } if (HasAnyFlags(RF_ClassDefaultObject)) { if (RuntimeGeneration == ERuntimeGenerationType::LegacyGeneration) { RuntimeGeneration = bRebuildAtRuntime_DEPRECATED ? ERuntimeGenerationType::Dynamic : ERuntimeGenerationType::Static; } } else { bNetLoadOnClient = FNavigationSystem::ShouldLoadNavigationOnClient(*this); RequestRegistration(); #if UE_ENABLE_DEBUG_DRAWING RenderingComp = ConstructRenderingComponent(); #endif // UE_ENABLE_DEBUG_DRAWING } } void ANavigationData::PostInitializeComponents() { Super::PostInitializeComponents(); UWorld* MyWorld = GetWorld(); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(MyWorld); if (bAutoDestroyWhenNoNavigation && (MyWorld == nullptr || (MyWorld->GetNetMode() != NM_Client && NavSys == nullptr) || (MyWorld->GetNetMode() == NM_Client && !bNetLoadOnClient))) { UE_VLOG_UELOG(this, LogNavigation, Log, TEXT("Marking %s as PendingKill due to %s"), *GetName() , !MyWorld ? TEXT("No World") : (MyWorld->GetNetMode() == NM_Client ? TEXT("not creating navigation on clients") : TEXT("missing navigation system"))); CleanUpAndMarkPendingKill(); } } void ANavigationData::PostLoad() { Super::PostLoad(); InstantiateAndRegisterRenderingComponent(); bNetLoadOnClient = FNavigationSystem::ShouldLoadNavigationOnClient(*this); RequestRegistration(); } void ANavigationData::RequestRegistration() { if (IsRegistered() == false && HasAnyFlags(RF_ClassDefaultObject) == false) { UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (NavSys) { NavSys->RequestRegistrationDeferred(*this); } } } void ANavigationData::TickActor(float DeltaTime, enum ELevelTick TickType, FActorTickFunction& ThisTickFunction) { Super::TickActor(DeltaTime, TickType, ThisTickFunction); PurgeUnusedPaths(); INC_DWORD_STAT_BY(STAT_Navigation_ObservedPathsCount, ObservedPaths.Num()); if (NextObservedPathsTickInSeconds >= 0.f) { NextObservedPathsTickInSeconds -= DeltaTime; if (NextObservedPathsTickInSeconds <= 0.f) { RepathRequests.Reserve(ObservedPaths.Num()); for (int32 PathIndex = ObservedPaths.Num() - 1; PathIndex >= 0; --PathIndex) { if (ObservedPaths[PathIndex].IsValid()) { FNavPathSharedPtr SharedPath = ObservedPaths[PathIndex].Pin(); FNavigationPath* Path = SharedPath.Get(); EPathObservationResult::Type Result = Path->TickPathObservation(); switch (Result) { case EPathObservationResult::NoLongerObserving: ObservedPaths.RemoveAtSwap(PathIndex, EAllowShrinking::No); break; case EPathObservationResult::NoChange: // do nothing break; case EPathObservationResult::RequestRepath: RepathRequests.Add(FNavPathRecalculationRequest(SharedPath, ENavPathUpdateType::GoalMoved)); break; default: check(false && "unhandled EPathObservationResult::Type in ANavigationData::TickActor"); break; } } else { ObservedPaths.RemoveAtSwap(PathIndex, EAllowShrinking::No); } } if (ObservedPaths.Num() > 0) { NextObservedPathsTickInSeconds = ObservedPathsTickInterval; } } } if (RepathRequests.Num() > 0) { double TimeStamp = GetWorldTimeStamp(); const UWorld* World = GetWorld(); // @todo batch-process it! const int32 MaxProcessedRequests = 1000; // make a copy of path requests and reset (remove up to MaxProcessedRequests) from navdata's array // this allows storing new requests in the middle of loop (e.g. used by meta path corrections) TArray WorkQueue(RepathRequests); if (WorkQueue.Num() > MaxProcessedRequests) { UE_VLOG(this, LogNavigation, Error, TEXT("Too many repath requests! (%d/%d)"), WorkQueue.Num(), MaxProcessedRequests); WorkQueue.RemoveAt(MaxProcessedRequests, WorkQueue.Num() - MaxProcessedRequests); RepathRequests.RemoveAt(0, MaxProcessedRequests); } else { RepathRequests.Reset(); } for (int32 Idx = 0; Idx < WorkQueue.Num(); Idx++) { FNavPathRecalculationRequest& RecalcRequest = WorkQueue[Idx]; // check if it can be updated right now FNavPathSharedPtr PinnedPath = RecalcRequest.Path.Pin(); if (PinnedPath.IsValid() == false) { continue; } const UObject* PathQuerier = PinnedPath->GetQuerier(); const INavAgentInterface* PathNavAgent = Cast(PathQuerier); if (PathNavAgent && PathNavAgent->ShouldPostponePathUpdates()) { RepathRequests.Add(RecalcRequest); continue; } FPathFindingQuery Query(PinnedPath.ToSharedRef()); const FPathFindingResult Result = FindPath(Query.NavAgentProperties, Query.SetPathInstanceToUpdate(PinnedPath)); // update time stamp to give observers any means of telling if it has changed PinnedPath->SetTimeStamp(TimeStamp); // partial paths are still valid and can change to full path when moving goal gets back on navmesh if (Result.IsSuccessful() || Result.IsPartial()) { PinnedPath->UpdateLastRepathGoalLocation(); PinnedPath->DoneUpdating(RecalcRequest.Reason); if (RecalcRequest.Reason == ENavPathUpdateType::NavigationChanged) { RegisterActivePath(PinnedPath); } } else { PinnedPath->RePathFailed(); } } } } #if WITH_EDITOR void ANavigationData::RerunConstructionScripts() { Super::RerunConstructionScripts(); InstantiateAndRegisterRenderingComponent(); } #endif void ANavigationData::OnRegistered() { InstantiateAndRegisterRenderingComponent(); bRegistered = true; ConditionalConstructGenerator(); } void ANavigationData::OnUnregistered() { bRegistered = false; } void ANavigationData::InstantiateAndRegisterRenderingComponent() { #if UE_ENABLE_DEBUG_DRAWING PRAGMA_DISABLE_DEPRECATION_WARNINGS if (IsThisNotNull(this, "ANavigationData::InstantiateAndRegisterRenderingComponent") && IsValidChecked(this) && !IsValid(RenderingComp)) PRAGMA_ENABLE_DEPRECATION_WARNINGS { const bool bRootIsRenderComp = (RenderingComp == RootComponent); if (RenderingComp) { // rename the old rendering component out of the way RenderingComp->Rename(NULL, NULL, REN_DontCreateRedirectors | REN_ForceGlobalUnique | REN_DoNotDirty | REN_NonTransactional); } RenderingComp = ConstructRenderingComponent(); UWorld* World = GetWorld(); if (World && World->bIsWorldInitialized && RenderingComp) { RenderingComp->RegisterComponent(); } if (bRootIsRenderComp) { RootComponent = RenderingComp; } } #endif // UE_ENABLE_DEBUG_DRAWING } void ANavigationData::PurgeUnusedPaths() { check(IsInGameThread()); // Paths can be registered from async pathfinding thread // while unused paths are purged in main thread (actor tick) UE::TScopeLock PathLock(ActivePathsLock); const int32 Count = ActivePaths.Num(); for (int32 PathIndex = Count - 1; PathIndex >= 0; --PathIndex) { FNavPathWeakPtr* WeakPathPtr = &ActivePaths[PathIndex]; if (WeakPathPtr->IsValid() == false) { ActivePaths.RemoveAtSwap(PathIndex, EAllowShrinking::No); } } } void ANavigationData::RegisterActivePath(FNavPathSharedPtr SharedPath) { // Paths can be registered from main thread and async pathfinding thread UE::TScopeLock PathLock(ActivePathsLock); ActivePaths.Add(SharedPath); } #if WITH_EDITOR void ANavigationData::PostEditUndo() { // make sure that rendering component is not pending kill before trying to register all components InstantiateAndRegisterRenderingComponent(); Super::PostEditUndo(); UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (NavSys) { if (IsPendingKillPending()) { NavSys->UnregisterNavData(this); } else { NavSys->RequestRegistrationDeferred(*this); } } } #endif // WITH_EDITOR bool ANavigationData::DoesSupportAgent(const FNavAgentProperties& AgentProps) const { return NavDataConfig.IsEquivalent(AgentProps); } void ANavigationData::EndPlay(const EEndPlayReason::Type EndPlayReason) { UnregisterAndCleanUp(); Super::EndPlay(EndPlayReason); } void ANavigationData::Destroyed() { UnregisterAndCleanUp(); Super::Destroyed(); } void ANavigationData::UnregisterAndCleanUp() { if (bRegistered) { bRegistered = false; UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (NavSys) { NavSys->UnregisterNavData(this); } } // Cleanup is not tied to the registration state. CleanUp(); } void ANavigationData::CleanUp() { bRegistered = false; } void ANavigationData::ApplyWorldOffset(const FVector& InOffset, bool bWorldShift) { // do nothing, will be handled by NavigationSystem } void ANavigationData::CleanUpAndMarkPendingKill() { CleanUp(); /* Need to check if the world is valid since, when this is called from Serialize, the World won't be set and Destroy will do nothing, * in which case it will crash when it tries to register with the NavSystem in UNavigationSystemV1::ProcessRegistrationCandidates. */ if (UE::Navigation::Private::bDestroyNavDataInCleanUpAndMarkPendingKill && IsValid(GetWorld())) { Destroy(); } else { SetActorHiddenInGame(true); if (UWorld* World = GetWorld()) { // This part is not thread-safe and should only happen on the GT... check(IsInGameThread()); World->RemoveNetworkActor(this); } MarkAsGarbage(); MarkComponentsAsGarbage(); } } bool ANavigationData::SupportsRuntimeGeneration() const { return false; } bool ANavigationData::SupportsStreaming() const { return false; } void ANavigationData::ConditionalConstructGenerator() { } void ANavigationData::RebuildAll() { const double LoadTime = FPlatformTime::Seconds(); LoadBeforeGeneratorRebuild(); FAssetCompilingManager::Get().FinishAllCompilation(); UE_LOG(LogNavigationDataBuild, Display, TEXT(" %s load time: %.2fs"), ANSI_TO_TCHAR(__FUNCTION__), (FPlatformTime::Seconds() - LoadTime)); PostLoadPreRebuild(); ConditionalConstructGenerator(); //recreate generator if (NavDataGenerator.IsValid()) { #if WITH_EDITOR if (!IsBuildingOnLoad()) { MarkPackageDirty(); } #endif // WITH_EDITOR NavDataGenerator->RebuildAll(); } } void ANavigationData::EnsureBuildCompletion() { if (NavDataGenerator.IsValid()) { NavDataGenerator->EnsureBuildCompletion(); } } void ANavigationData::CancelBuild() { if (NavDataGenerator.IsValid()) { NavDataGenerator->CancelBuild(); } } void ANavigationData::OnNavigationBoundsChanged() { // Create generator if it wasn't yet if (NavDataGenerator.Get() == nullptr) { ConditionalConstructGenerator(); } if (NavDataGenerator.IsValid()) { NavDataGenerator->OnNavigationBoundsChanged(); } } void ANavigationData::TickAsyncBuild(float DeltaSeconds) { if (NavDataGenerator.IsValid()) { NavDataGenerator->TickAsyncBuild(DeltaSeconds); } } void ANavigationData::RebuildDirtyAreas(const TArray& DirtyAreas) { if (bRebuildingSuspended) { if (bReachedSuspendedAreasMaxCount) { return; // Already reached max count } const int32 TotalDirtyAreasCount = SuspendedDirtyAreas.Num() + DirtyAreas.Num(); if (TotalDirtyAreasCount > UE::Navigation::Private::MaxDirtyAreasCountWhileSuspended) { // Rebuilding has been suspended for too long and we're accumulating too many dirty areas. // Let's just stop accumulating them and remember that the whole navigation data might now be invalid. UE_VLOG_UELOG(this, LogNavigation, Display, TEXT("%s reached ai.navigation.MaxDirtyAreasCountWhileSuspended (%d). Ignoring dirty areas until rebuilding is unsuspended."), *GetName(), UE::Navigation::Private::MaxDirtyAreasCountWhileSuspended); bReachedSuspendedAreasMaxCount = true; SuspendedDirtyAreas.Empty(); } else { SuspendedDirtyAreas.Append(DirtyAreas); } } else { if (NavDataGenerator.IsValid()) { NavDataGenerator->RebuildDirtyAreas(DirtyAreas); } } } void ANavigationData::SetRebuildingSuspended(const bool bNewSuspend) { if (bRebuildingSuspended != bNewSuspend) { bRebuildingSuspended = bNewSuspend; UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s nav generation %s") , *GetName(), bNewSuspend ? TEXT("SUSPENDED") : TEXT("ACTIVE")); if (bNewSuspend == false) { if (bReachedSuspendedAreasMaxCount) { UE_VLOG_UELOG(this, LogNavigation, Warning, TEXT("%s resuming nav generation after MaxDirtyAreasCountWhileSuspended was reached. This will rebuild the entire navigation data."), *GetName()); // We stopped tracking dirty areas so we'll need to rebuild all. bReachedSuspendedAreasMaxCount = false; RebuildAll(); } else if (SuspendedDirtyAreas.Num() > 0) { UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s resuming nav generation with %d dirty areas") , *GetName(), SuspendedDirtyAreas.Num()); // resuming the generation so we need to utilize SuspendedDirtyAreas and clean it RebuildDirtyAreas(SuspendedDirtyAreas); SuspendedDirtyAreas.Empty(); } } } } TArray ANavigationData::GetNavigableBounds() const { TArray Result; const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (NavSys) { // @note this has been switched over from calling GetNavigationBounds // to get navigable bounds relevant to this one nav data instance // This implements the original intension of the function NavSys->GetNavigationBoundsForNavData(*this, Result); } return Result; } TArray ANavigationData::GetNavigableBoundsInLevel(ULevel* InLevel) const { TArray Result; const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); if (NavSys) { NavSys->GetNavigationBoundsForNavData(*this, Result, InLevel); } return Result; } void ANavigationData::DrawDebugPath(FNavigationPath* Path, const FColor PathColor, UCanvas* Canvas, const bool bPersistent, const float LifeTime, const uint32 NextPathPointIndex) const { Path->DebugDraw(this, PathColor, Canvas, bPersistent, LifeTime, NextPathPointIndex); } double ANavigationData::GetWorldTimeStamp() const { const UWorld* World = GetWorld(); return World ? World->GetTimeSeconds() : 0.f; } void ANavigationData::OnNavAreaAdded(const UClass* NavAreaClass, int32 AgentIndex) { // check if area can be added const UNavArea* DefArea = GetDefault(const_cast(NavAreaClass)); const bool bIsMetaArea = DefArea != nullptr && DefArea->IsMetaArea(); if (!DefArea || bIsMetaArea || !DefArea->IsSupportingAgent(AgentIndex)) { UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s discarded area %s (valid:%s meta:%s validAgent[%d]:%s)"), *GetName(), *GetNameSafe(NavAreaClass), DefArea ? TEXT("yes") : TEXT("NO"), bIsMetaArea ? TEXT("YES") : TEXT("no"), AgentIndex, (DefArea && DefArea->IsSupportingAgent(AgentIndex)) ? TEXT("yes") : TEXT("NO")); return; } // check if area is already on supported list FString AreaClassName = NavAreaClass->GetName(); for (int32 i = 0; i < SupportedAreas.Num(); i++) { if (SupportedAreas[i].AreaClassName == AreaClassName) { SupportedAreas[i].AreaClass = NavAreaClass; AreaClassToIdMap.Add(NavAreaClass, SupportedAreas[i].AreaID); UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s: updated area %s with ID %d"), *GetFullName(), *AreaClassName, SupportedAreas[i].AreaID); return; } } // try adding new one const int32 MaxSupported = GetMaxSupportedAreas(); if (SupportedAreas.Num() >= MaxSupported) { UE_VLOG_UELOG(this, LogNavigation, Error, TEXT("%s can't support area %s - limit reached! (%d)"), *GetName(), *AreaClassName, MaxSupported); return; } FSupportedAreaData NewAgentData; NewAgentData.AreaClass = NavAreaClass; NewAgentData.AreaClassName = AreaClassName; NewAgentData.AreaID = GetNewAreaID(NavAreaClass); SupportedAreas.Add(NewAgentData); AreaClassToIdMap.Add(NavAreaClass, NewAgentData.AreaID); UE_VLOG_UELOG(this, LogNavigation, Verbose, TEXT("%s registered area %s with ID %d"), *GetName(), *AreaClassName, NewAgentData.AreaID); } void ANavigationData::OnNavAreaEvent(const UClass* NavAreaClass, ENavAreaEvent::Type Event) { if (Event == ENavAreaEvent::Registered) { const UNavigationSystemV1* NavSys = FNavigationSystem::GetCurrent(GetWorld()); const int32 AgentIndex = NavSys->GetSupportedAgentIndex(this); OnNavAreaAdded(NavAreaClass, AgentIndex); } else // Unregistered { OnNavAreaRemoved(NavAreaClass); } OnNavAreaChanged(); } void ANavigationData::OnNavAreaRemoved(const UClass* NavAreaClass) { for (int32 i = 0; i < SupportedAreas.Num(); i++) { if (SupportedAreas[i].AreaClass == NavAreaClass) { AreaClassToIdMap.Remove(NavAreaClass); SupportedAreas.RemoveAt(i); break; } } } void ANavigationData::OnNavAreaChanged() { // empty in base class } void ANavigationData::ProcessNavAreas(const TSet& AreaClasses, int32 AgentIndex) { for (const UClass* AreaClass : AreaClasses) { OnNavAreaAdded(AreaClass, AgentIndex); } OnNavAreaChanged(); } int32 ANavigationData::GetNewAreaID(const UClass* AreaClass) const { int TestId = 0; while (TestId < SupportedAreas.Num()) { const bool bIsTaken = IsAreaAssigned(TestId); if (bIsTaken) { TestId++; } else { break; } } return TestId; } const UClass* ANavigationData::GetAreaClass(int32 AreaID) const { for (int32 i = 0; i < SupportedAreas.Num(); i++) { if (SupportedAreas[i].AreaID == AreaID) { return SupportedAreas[i].AreaClass; } } return NULL; } bool ANavigationData::IsAreaAssigned(int32 AreaID) const { for (int32 i = 0; i < SupportedAreas.Num(); i++) { if (SupportedAreas[i].AreaID == AreaID) { return true; } } return false; } int32 ANavigationData::GetAreaID(const UClass* AreaClass) const { const int32* PtrId = AreaClassToIdMap.Find(AreaClass); return PtrId ? *PtrId : INDEX_NONE; } void ANavigationData::SetNavRenderingEnabled(bool bEnable) { if (bEnableDrawing != bEnable) { bEnableDrawing = bEnable; MarkComponentsRenderStateDirty(); } } void ANavigationData::UpdateCustomLink(const INavLinkCustomInterface* CustomLink) { // no implementation for abstract class } FSharedConstNavQueryFilter ANavigationData::GetQueryFilter(TSubclassOf FilterClass) const { return QueryFilters.FindRef(FilterClass); } void ANavigationData::StoreQueryFilter(TSubclassOf FilterClass, FSharedConstNavQueryFilter NavFilter) { QueryFilters.Add(FilterClass, NavFilter); } void ANavigationData::RemoveQueryFilter(TSubclassOf FilterClass) { QueryFilters.Remove(FilterClass); } uint32 ANavigationData::LogMemUsed() const { SIZE_T ActivePathsMemSize = 0; { // Paths can be registered from async pathfinding thread // while logging is requested on main thread (console command) UE::TScopeLock PathLock(ActivePathsLock); ActivePathsMemSize = ActivePaths.GetAllocatedSize(); } const uint32 MemUsed = IntCastChecked(ActivePathsMemSize + SupportedAreas.GetAllocatedSize() + QueryFilters.GetAllocatedSize() + AreaClassToIdMap.GetAllocatedSize()); UE_VLOG_UELOG(this, LogNavigation, Display, TEXT("%s: ANavigationData: %u\n self: %d"), *GetName(), MemUsed, sizeof(ANavigationData)); if (NavDataGenerator.IsValid()) { NavDataGenerator->LogMemUsed(); } return MemUsed; } void ANavigationData::SetConfig(const FNavDataConfig& Src) { SetNavAgentProperties(Src); NavDataConfig = Src; }