Files
UnrealEngineUWP/Engine/Source/Runtime/NavigationSystem/Private/NavigationDataHandler.cpp
yoan stamant a703fee91b NavigationDataHandler: prevent registration of an element that should not be registered when processing the parent chain.
One problematic scenario is when all components from an actor are unregistered and one of them is the navigation parent of the others. In this case, the parent may be unregistered before some of its children and then calls to UpdateNavOctreeParentChain from a child may queue back the unregistered parent. UpdateNavOctreeParentChain will now consider the actual status of the parent (Added or queued for add) before registering it or its children.

[at]mieszko.zielinski [at]guillaume.guay
#rb mieszko.zielinski, guillaume.guay


#ROBOMERGE-SOURCE: CL 8552772 via CL 8557168
#ROBOMERGE-BOT: (v406-8472469)

[CL 8557314 by yoan stamant in Main branch]
2019-09-06 16:30:54 -04:00

506 lines
17 KiB
C++

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "NavigationDataHandler.h"
#include "NavMesh/RecastNavMeshGenerator.h"
DEFINE_LOG_CATEGORY_STATIC(LogNavOctree, Warning, All);
namespace
{
int32 GetDirtyFlagHelper(int32 UpdateFlags, int32 DefaultValue)
{
return ((UpdateFlags & FNavigationOctreeController::OctreeUpdate_Geometry) != 0) ? ENavigationDirtyFlag::All :
((UpdateFlags & FNavigationOctreeController::OctreeUpdate_Modifiers) != 0) ? ENavigationDirtyFlag::DynamicModifier :
DefaultValue;
}
}
FNavigationDataHandler::FNavigationDataHandler(FNavigationOctreeController& InOctreeController, FNavigationDirtyAreasController& InDirtyAreasController)
: OctreeController(InOctreeController), DirtyAreasController(InDirtyAreasController)
{}
void FNavigationDataHandler::RemoveNavOctreeElementId(const FOctreeElementId& ElementId, int32 UpdateFlags)
{
if (ensure(OctreeController.IsValidElement(ElementId)))
{
const FNavigationOctreeElement& ElementData = OctreeController.NavOctree->GetElementById(ElementId);
const int32 DirtyFlag = GetDirtyFlagHelper(UpdateFlags, ElementData.Data->GetDirtyFlag());
// mark area occupied by given actor as dirty
DirtyAreasController.AddArea(ElementData.Bounds.GetBox(), DirtyFlag);
OctreeController.NavOctree->RemoveNode(ElementId);
}
}
FSetElementId FNavigationDataHandler::RegisterNavOctreeElement(UObject& ElementOwner, INavRelevantInterface& ElementInterface, int32 UpdateFlags)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_RegisterNavOctreeElement);
FSetElementId SetId;
if (OctreeController.NavOctree.IsValid() == false)
{
return SetId;
}
if (OctreeController.IsNavigationOctreeLocked())
{
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(RegisterNavOctreeElement) %s"), *GetPathNameSafe(&ElementOwner));
return SetId;
}
const bool bIsRelevant = ElementInterface.IsNavigationRelevant();
UE_LOG(LogNavOctree, Log, TEXT("REG %s %s"), *ElementOwner.GetName(), bIsRelevant ? TEXT("[relevant]") : TEXT("[skip]"));
if (bIsRelevant)
{
bool bCanAdd = false;
UObject* ParentNode = ElementInterface.GetNavigationParent();
if (ParentNode)
{
OctreeController.OctreeChildNodesMap.AddUnique(ParentNode, FWeakObjectPtr(&ElementOwner));
bCanAdd = true;
}
else
{
bCanAdd = (OctreeController.HasObjectsNavOctreeId(ElementOwner) == false);
}
if (bCanAdd)
{
FNavigationDirtyElement UpdateInfo(&ElementOwner, &ElementInterface, GetDirtyFlagHelper(UpdateFlags, 0));
SetId = OctreeController.PendingOctreeUpdates.FindId(UpdateInfo);
if (SetId.IsValidId())
{
// make sure this request stays, in case it has been invalidated already
OctreeController.PendingOctreeUpdates[SetId] = UpdateInfo;
}
else
{
SetId = OctreeController.PendingOctreeUpdates.Add(UpdateInfo);
}
}
}
return SetId;
}
void FNavigationDataHandler::AddElementToNavOctree(const FNavigationDirtyElement& DirtyElement)
{
check(OctreeController.NavOctree.IsValid());
// handle invalidated requests first
if (DirtyElement.bInvalidRequest)
{
if (DirtyElement.bHasPrevData)
{
DirtyAreasController.AddArea(DirtyElement.PrevBounds, DirtyElement.PrevFlags);
}
return;
}
UObject* ElementOwner = DirtyElement.Owner.Get();
if (ElementOwner == nullptr || ElementOwner->IsPendingKill() || DirtyElement.NavInterface == nullptr)
{
return;
}
FNavigationOctreeElement GeneratedData(*ElementOwner);
const FBox ElementBounds = DirtyElement.NavInterface->GetNavigationBounds();
UObject* NavigationParent = DirtyElement.NavInterface->GetNavigationParent();
if (NavigationParent)
{
// check if parent node is waiting in queue
const FSetElementId ParentRequestId = OctreeController.PendingOctreeUpdates.FindId(FNavigationDirtyElement(NavigationParent));
const FOctreeElementId* ParentId = OctreeController.GetObjectsNavOctreeId(*NavigationParent);
if (ParentRequestId.IsValidId() && ParentId == nullptr)
{
FNavigationDirtyElement& ParentNode = OctreeController.PendingOctreeUpdates[ParentRequestId];
AddElementToNavOctree(ParentNode);
// mark as invalid so it won't be processed twice
ParentNode.bInvalidRequest = true;
}
const FOctreeElementId* ElementId = ParentId ? ParentId : OctreeController.GetObjectsNavOctreeId(*NavigationParent);
if (ElementId && ensure(OctreeController.IsValidElement(*ElementId)))
{
UE_LOG(LogNavOctree, Log, TEXT("ADD %s to %s"), *GetNameSafe(ElementOwner), *GetNameSafe(NavigationParent));
OctreeController.NavOctree->AppendToNode(*ElementId, DirtyElement.NavInterface, ElementBounds, GeneratedData);
}
else
{
UE_LOG(LogNavOctree, Warning, TEXT("Can't add node [%s] - parent [%s] not found in octree!"), *GetNameSafe(ElementOwner), *GetNameSafe(NavigationParent));
}
}
else
{
UE_LOG(LogNavOctree, Log, TEXT("ADD %s"), *GetNameSafe(ElementOwner));
OctreeController.NavOctree->AddNode(ElementOwner, DirtyElement.NavInterface, ElementBounds, GeneratedData);
}
const FBox BBox = GeneratedData.Bounds.GetBox();
const bool bValidBBox = BBox.IsValid && !BBox.GetSize().IsNearlyZero();
if (bValidBBox && !GeneratedData.IsEmpty())
{
const int32 DirtyFlag = DirtyElement.FlagsOverride ? DirtyElement.FlagsOverride : GeneratedData.Data->GetDirtyFlag();
DirtyAreasController.AddArea(BBox, DirtyFlag);
}
}
bool FNavigationDataHandler::UnregisterNavOctreeElement(UObject& ElementOwner, INavRelevantInterface& ElementInterface, int32 UpdateFlags)
{
SCOPE_CYCLE_COUNTER(STAT_Navigation_UnregisterNavOctreeElement);
if (OctreeController.NavOctree.IsValid() == false)
{
return false;
}
if (OctreeController.IsNavigationOctreeLocked())
{
#if !WITH_EDITOR
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UnregisterNavOctreeElement) %s"), *GetPathNameSafe(&ElementOwner));
#endif // WITH_EDITOR
return false;
}
bool bUnregistered = false;
const FOctreeElementId* ElementId = OctreeController.GetObjectsNavOctreeId(ElementOwner);
UE_LOG(LogNavOctree, Log, TEXT("UNREG %s %s"), *ElementOwner.GetName(), ElementId ? TEXT("[exists]") : TEXT("[does\'t exist]"));
if (ElementId != nullptr)
{
RemoveNavOctreeElementId(*ElementId, UpdateFlags);
OctreeController.RemoveObjectsNavOctreeId(ElementOwner);
bUnregistered = true;
}
else
{
const bool bCanRemoveChildNode = (UpdateFlags & FNavigationOctreeController::OctreeUpdate_ParentChain) == 0;
UObject* ParentNode = ElementInterface.GetNavigationParent();
if (ParentNode && bCanRemoveChildNode)
{
// if node has navigation parent (= doesn't exists in octree on its own)
// and it's not part of parent chain update
// remove it from map and force update on parent to rebuild octree element
OctreeController.OctreeChildNodesMap.RemoveSingle(ParentNode, FWeakObjectPtr(&ElementOwner));
UpdateNavOctreeParentChain(*ParentNode);
}
}
// mark pending update as invalid, it will be dirtied according to currently active settings
const bool bCanInvalidateQueue = (UpdateFlags & FNavigationOctreeController::OctreeUpdate_Refresh) == 0;
if (bCanInvalidateQueue)
{
const FSetElementId RequestId = OctreeController.PendingOctreeUpdates.FindId(FNavigationDirtyElement(&ElementOwner));
if (RequestId.IsValidId())
{
FNavigationDirtyElement& DirtyElement = OctreeController.PendingOctreeUpdates[RequestId];
// Only consider as unregistered when pending update was not already invalidated since return value must indicate
// that ElementOwner was fully added or about to be added (valid pending update).
bUnregistered |= (DirtyElement.bInvalidRequest == false);
DirtyElement.bInvalidRequest = true;
}
}
return bUnregistered;
}
void FNavigationDataHandler::UpdateNavOctreeElement(UObject& ElementOwner, INavRelevantInterface& ElementInterface, int32 UpdateFlags)
{
INC_DWORD_STAT(STAT_Navigation_UpdateNavOctree);
if (OctreeController.IsNavigationOctreeLocked())
{
UE_LOG(LogNavOctree, Log, TEXT("IGNORE(UpdateNavOctreeElement) %s"), *ElementOwner.GetPathName());
return;
}
// grab existing octree data
FBox CurrentBounds;
int32 CurrentFlags;
const bool bAlreadyExists = OctreeController.GetNavOctreeElementData(ElementOwner, CurrentFlags, CurrentBounds);
// don't invalidate pending requests
UpdateFlags |= FNavigationOctreeController::OctreeUpdate_Refresh;
// always try to unregister, even if element owner doesn't exists in octree (parent nodes)
UnregisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
const FSetElementId RequestId = RegisterNavOctreeElement(ElementOwner, ElementInterface, UpdateFlags);
// add original data to pending registration request
// so it could be dirtied properly when system receive unregister request while actor is still queued
if (RequestId.IsValidId())
{
FNavigationDirtyElement& UpdateInfo = OctreeController.PendingOctreeUpdates[RequestId];
UpdateInfo.PrevFlags = CurrentFlags;
if (UpdateInfo.PrevBounds.IsValid)
{
// Is we have something stored already we want to
// sum it up, since we care about the whole bounding
// box of changes that potentially took place
UpdateInfo.PrevBounds += CurrentBounds;
}
else
{
UpdateInfo.PrevBounds = CurrentBounds;
}
UpdateInfo.bHasPrevData = bAlreadyExists;
}
UpdateNavOctreeParentChain(ElementOwner, /*bSkipElementOwnerUpdate=*/ true);
}
void FNavigationDataHandler::UpdateNavOctreeParentChain(UObject& ElementOwner, bool bSkipElementOwnerUpdate)
{
const int32 UpdateFlags = FNavigationOctreeController::OctreeUpdate_ParentChain | FNavigationOctreeController::OctreeUpdate_Refresh;
TArray<FWeakObjectPtr> ChildNodes;
OctreeController.OctreeChildNodesMap.MultiFind(&ElementOwner, ChildNodes);
auto ElementOwnerUpdateFunc = [&]()->bool
{
bool bShouldRegisterChildren = true;
if (bSkipElementOwnerUpdate == false)
{
INavRelevantInterface* ElementInterface = Cast<INavRelevantInterface>(&ElementOwner);
if (ElementInterface != nullptr)
{
// We don't want to register NavOctreeElement if owner was not already registered or queued
// so we use Unregister/Register combo instead of UpdateNavOctreeElement
if (UnregisterNavOctreeElement(ElementOwner, *ElementInterface, UpdateFlags))
{
FSetElementId NewId = RegisterNavOctreeElement(ElementOwner, *ElementInterface, UpdateFlags);
bShouldRegisterChildren = NewId.IsValidId();
}
else
{
bShouldRegisterChildren = false;
}
}
}
return bShouldRegisterChildren;
};
if (ChildNodes.Num() == 0)
{
// Last child was removed, only need to rebuild owner's NavOctreeElement
ElementOwnerUpdateFunc();
return;
}
TArray<INavRelevantInterface*> ChildNavInterfaces;
ChildNavInterfaces.AddZeroed(ChildNodes.Num());
for (int32 Idx = 0; Idx < ChildNodes.Num(); Idx++)
{
if (ChildNodes[Idx].IsValid())
{
UObject* ChildNodeOb = ChildNodes[Idx].Get();
ChildNavInterfaces[Idx] = Cast<INavRelevantInterface>(ChildNodeOb);
if (ChildNodeOb && ChildNavInterfaces[Idx])
{
UnregisterNavOctreeElement(*ChildNodeOb, *ChildNavInterfaces[Idx], UpdateFlags);
}
}
}
const bool bShouldRegisterChildren = ElementOwnerUpdateFunc();
if (bShouldRegisterChildren)
{
for (int32 Idx = 0; Idx < ChildNodes.Num(); Idx++)
{
UObject* ChildElement = ChildNodes[Idx].Get();
if (ChildElement && ChildNavInterfaces[Idx])
{
RegisterNavOctreeElement(*ChildElement, *ChildNavInterfaces[Idx], UpdateFlags);
}
}
}
}
bool FNavigationDataHandler::UpdateNavOctreeElementBounds(UActorComponent& Comp, const FBox& NewBounds, const FBox& DirtyArea)
{
const FOctreeElementId* ElementId = OctreeController.GetObjectsNavOctreeId(Comp);
if (ElementId != nullptr && ensure(OctreeController.IsValidElement(*ElementId)))
{
OctreeController.NavOctree->UpdateNode(*ElementId, NewBounds);
// Add dirty area
if (DirtyArea.IsValid)
{
// Refresh ElementId since components may be stored in a different node after updating bounds
ElementId = OctreeController.GetObjectsNavOctreeId(Comp);
if (ElementId != nullptr && ensure(OctreeController.IsValidElement(*ElementId)))
{
FNavigationOctreeElement& ElementData = OctreeController.NavOctree->GetElementById(*ElementId);
DirtyAreasController.AddArea(DirtyArea, ElementData.Data->GetDirtyFlag());
}
}
return true;
}
return false;
}
void FNavigationDataHandler::FindElementsInNavOctree(const FBox& QueryBox, const FNavigationOctreeFilter& Filter, TArray<FNavigationOctreeElement>& Elements)
{
if (OctreeController.NavOctree.IsValid() == false)
{
UE_LOG(LogNavOctree, Warning, TEXT("FNavigationDataHandler::FindElementsInNavOctree gets called while NavOctree is null"));
return;
}
for (FNavigationOctree::TConstElementBoxIterator<> It(*OctreeController.NavOctree, QueryBox); It.HasPendingElements(); It.Advance())
{
const FNavigationOctreeElement& Element = It.GetCurrentElement();
if (Element.IsMatchingFilter(Filter))
{
Elements.Add(Element);
}
}
}
bool FNavigationDataHandler::ReplaceAreaInOctreeData(const UObject& Object, TSubclassOf<UNavArea> OldArea, TSubclassOf<UNavArea> NewArea, bool bReplaceChildClasses)
{
FNavigationRelevantData* Data = OctreeController.GetMutableDataForObject(Object);
if (Data == nullptr || Data->HasModifiers() == false)
{
return false;
}
for (FAreaNavModifier& AreaModifier : Data->Modifiers.GetMutableAreas())
{
if (AreaModifier.GetAreaClass() == OldArea
|| (bReplaceChildClasses && AreaModifier.GetAreaClass()->IsChildOf(OldArea)))
{
AreaModifier.SetAreaClass(NewArea);
}
}
for (FSimpleLinkNavModifier& SimpleLink : Data->Modifiers.GetSimpleLinks())
{
for (FNavigationLink& Link : SimpleLink.Links)
{
if (Link.GetAreaClass() == OldArea
|| (bReplaceChildClasses && Link.GetAreaClass()->IsChildOf(OldArea)))
{
Link.SetAreaClass(NewArea);
}
}
for (FNavigationSegmentLink& Link : SimpleLink.SegmentLinks)
{
if (Link.GetAreaClass() == OldArea
|| (bReplaceChildClasses && Link.GetAreaClass()->IsChildOf(OldArea)))
{
Link.SetAreaClass(NewArea);
}
}
}
for (FCustomLinkNavModifier& CustomLink : Data->Modifiers.GetCustomLinks())
{
ensureMsgf(false, TEXT("Not implemented yet"));
}
return true;
}
void FNavigationDataHandler::AddLevelCollisionToOctree(ULevel& Level)
{
#if WITH_RECAST
if (OctreeController.NavOctree.IsValid() &&
OctreeController.NavOctree->GetNavGeometryStoringMode() == FNavigationOctree::StoreNavGeometry)
{
const TArray<FVector>* LevelGeom = Level.GetStaticNavigableGeometry();
const FOctreeElementId* ElementId = OctreeController.GetObjectsNavOctreeId(Level);
if (!ElementId && LevelGeom && LevelGeom->Num() > 0)
{
FNavigationOctreeElement BSPElem(Level);
FRecastNavMeshGenerator::ExportVertexSoupGeometry(*LevelGeom, *BSPElem.Data);
const FBox& Bounds = BSPElem.Data->Bounds;
if (!Bounds.GetExtent().IsNearlyZero())
{
OctreeController.NavOctree->AddNode(&Level, nullptr, Bounds, BSPElem);
DirtyAreasController.AddArea(Bounds, ENavigationDirtyFlag::All);
UE_LOG(LogNavOctree, Log, TEXT("ADD %s"), *Level.GetName());
}
}
}
#endif// WITH_RECAST
}
void FNavigationDataHandler::RemoveLevelCollisionFromOctree(ULevel& Level)
{
if (OctreeController.NavOctree.IsValid())
{
const FOctreeElementId* ElementId = OctreeController.GetObjectsNavOctreeId(Level);
UE_LOG(LogNavOctree, Log, TEXT("UNREG %s %s"), *Level.GetName(), ElementId ? TEXT("[exists]") : TEXT(""));
if (ElementId != nullptr)
{
if (ensure(OctreeController.IsValidElement(*ElementId)))
{
// mark area occupied by given actor as dirty
FNavigationOctreeElement& ElementData = OctreeController.NavOctree->GetElementById(*ElementId);
DirtyAreasController.AddArea(ElementData.Bounds.GetBox(), ENavigationDirtyFlag::All);
}
OctreeController.NavOctree->RemoveNode(*ElementId);
OctreeController.RemoveObjectsNavOctreeId(Level);
}
}
}
void FNavigationDataHandler::UpdateActorAndComponentsInNavOctree(AActor& Actor)
{
INavRelevantInterface* NavElement = Cast<INavRelevantInterface>(&Actor);
if (NavElement)
{
UpdateNavOctreeElement(Actor, *NavElement, FNavigationOctreeController::OctreeUpdate_Default);
}
for (UActorComponent* Component : Actor.GetComponents())
{
INavRelevantInterface* CompNavElement = Cast<INavRelevantInterface>(Component);
if (CompNavElement)
{
// Component != null is implied by successful INavRelevantInterface cast
if (Actor.IsComponentRelevantForNavigation(Component))
{
UpdateNavOctreeElement(*Component, *CompNavElement, FNavigationOctreeController::OctreeUpdate_Default);
}
else
{
UnregisterNavOctreeElement(*Component, *CompNavElement, FNavigationOctreeController::OctreeUpdate_Default);
}
}
}
}
void FNavigationDataHandler::ProcessPendingOctreeUpdates()
{
if (OctreeController.PendingOctreeUpdates.Num() && OctreeController.NavOctree)
{
for (TSet<FNavigationDirtyElement>::TIterator It(OctreeController.PendingOctreeUpdates); It; ++It)
{
AddElementToNavOctree(*It);
}
}
OctreeController.PendingOctreeUpdates.Empty(32);
}