Files
UnrealEngine/Engine/Source/Runtime/NavigationSystem/Private/NavigationObjectRepository.cpp
2025-05-18 13:04:45 +08:00

253 lines
9.2 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "NavigationObjectRepository.h"
#include "Misc/OutputDevice.h"
#include "NavigationSystem.h"
#include "NavLinkCustomInterface.h"
#include "AI/NavigationSystemBase.h"
#include "AI/Navigation/NavigationElement.h"
#include "AI/Navigation/NavRelevantInterface.h"
#include "UObject/ObjectKey.h"
namespace UE::Navigation::Private
{
static FAutoConsoleCommandWithWorldArgsAndOutputDevice CmdDumpRepositoryElements(
TEXT("ai.debug.nav.DumpRepositoryElements"),
TEXT("Logs details about each element stored in the navigation repository to the output device."),
FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateLambda([](const TArray<FString>& Args, const UWorld* World, FOutputDevice& OutputDevice)
{
if (const UNavigationObjectRepository* Repository = World->GetSubsystem<UNavigationObjectRepository>())
{
int32 NumElements = 0;
Repository->ForEachNavigationElement([&OutputDevice, &NumElements](const TSharedRef<const FNavigationElement>& Element)
{
NumElements++;
OutputDevice.Logf(ELogVerbosity::Log, TEXT("%s bounds: [%s] parent:'%s'"),
*Element->GetPathName(),
*Element->GetBounds().ToString(),
*GetNameSafe(Element->GetNavigationParent().Get()));
});
OutputDevice.Logf(ELogVerbosity::Log, TEXT("Total: %d elements"), NumElements);
}
else
{
OutputDevice.Log(ELogVerbosity::Error, TEXT("Command failed since it was unable to find the navigation repository"));
}
})
);
} // UE::Navigation::Private
TSharedPtr<const FNavigationElement> UNavigationObjectRepository::AddNavigationElement(FNavigationElement&& Element, const ENotifyOnSuccess NotifyOnSuccess /*= ENotifyOnSuccess::Yes*/)
{
#if DO_ENSURE // We don't want to execute the Find at all for targets where ensures are disabled
{
UE_MT_SCOPED_READ_ACCESS(NavElementAccessDetector);
if (!ensureMsgf(NavRelevantElements.Find(Element.GetHandle()) == nullptr, TEXT("Same element can't be registered twice.")))
{
return nullptr;
}
}
#endif
const TSharedRef SharedElement(MakeShared<FNavigationElement>(MoveTemp(Element)));
{
UE_MT_SCOPED_WRITE_ACCESS(NavElementAccessDetector);
NavRelevantElements.Emplace(Element.GetHandle(), SharedElement);
}
if (NotifyOnSuccess == ENotifyOnSuccess::Yes)
{
(void)OnNavigationElementAddedDelegate.ExecuteIfBound(SharedElement);
}
return SharedElement.ToSharedPtr();
}
void UNavigationObjectRepository::RemoveNavigationElement(const FNavigationElementHandle Handle)
{
UE_MT_SCOPED_WRITE_ACCESS(NavElementAccessDetector);
TSharedPtr<const FNavigationElement> Element;
if (ensureMsgf(NavRelevantElements.RemoveAndCopyValue(Handle, Element),
TEXT("Navigation element can't be removed since it was not registered or already unregistered)")))
{
(void)OnNavigationElementRemovedDelegate.ExecuteIfBound(Element.ToSharedRef());
}
}
void UNavigationObjectRepository::ForEachNavigationElement(TFunctionRef<void(const TSharedRef<const FNavigationElement>&)> PerElementCallback) const
{
UE_MT_SCOPED_READ_ACCESS(NavElementAccessDetector);
for (auto It = NavRelevantElements.CreateConstIterator(); It; ++It)
{
if (const TSharedPtr<const FNavigationElement>& Element = It.Value())
{
PerElementCallback(Element.ToSharedRef());
}
}
}
TSharedPtr<const FNavigationElement> UNavigationObjectRepository::RegisterNavRelevantObject(const INavRelevantInterface& NavRelevantObject)
{
return RegisterNavRelevantObjectInternal(NavRelevantObject, *Cast<UObject>(&NavRelevantObject), ENotifyOnSuccess::Yes);
}
bool UNavigationObjectRepository::ShouldCreateSubsystem(UObject* Outer) const
{
return (Super::ShouldCreateSubsystem(Outer))
&& GetDefault<UNavigationSystemV1>()->ShouldCreateNavigationSystemInstance(Cast<UWorld>(Outer));
}
TSharedPtr<const FNavigationElement> UNavigationObjectRepository::RegisterNavRelevantObjectInternal(
const INavRelevantInterface& NavRelevantInterface,
const UObject& NavRelevantObject,
const ENotifyOnSuccess NotifyOnSuccess)
{
// In AActor/UActorComponent code paths it is possible that a component registration is performed more than once
// (i.e., Actor registering its component, then individual component registers too)
// In such case we update with the latest.
if (const TSharedPtr<const FNavigationElement> ElementPtr = GetNavigationElementForUObject(&NavRelevantObject))
{
const TSharedRef<const FNavigationElement> NewElement = FNavigationElement::CreateFromNavRelevantInterface(NavRelevantInterface);
{
UE_MT_SCOPED_WRITE_ACCESS(NavElementAccessDetector);
NavRelevantElements[ElementPtr->GetHandle()] = NewElement;
}
if (NotifyOnSuccess == ENotifyOnSuccess::Yes)
{
(void)OnNavigationElementAddedDelegate.ExecuteIfBound(NewElement);
}
UE_LOG(LogNavigation, Verbose, TEXT("%hs [already registered - updating] (%s:%s) Bounds: [%s]->[%s]"), __FUNCTION__,
*GetNameSafe(NavRelevantObject.GetOuter()), *GetNameSafe(&NavRelevantObject),
*ElementPtr->GetBounds().ToString(), *NewElement->GetBounds().ToString());
return NewElement.ToSharedPtr();
}
if (NavRelevantInterface.IsNavigationRelevant())
{
if (const TSharedPtr<const FNavigationElement> SharedElement = AddNavigationElement(FNavigationElement(NavRelevantInterface), NotifyOnSuccess))
{
UE_MT_SCOPED_WRITE_ACCESS(NavElementAccessDetector);
ObjectsToHandleMap.Emplace(FObjectKey(&NavRelevantObject), SharedElement->GetHandle());
UE_LOG(LogNavigation, Verbose, TEXT("%hs [registered] (%s:%s) Bounds: [%s]"), __FUNCTION__,
*GetNameSafe(NavRelevantObject.GetOuter()), *GetNameSafe(&NavRelevantObject),
*NavRelevantInterface.GetNavigationBounds().ToString());
return SharedElement;
}
return nullptr;
}
UE_LOG(LogNavigation, VeryVerbose, TEXT("%hs [skipped: not relevant] (%s:%s)"), __FUNCTION__,
*GetNameSafe(NavRelevantObject.GetOuter()), *GetNameSafe(&NavRelevantObject));
return nullptr;
}
void UNavigationObjectRepository::UnregisterNavRelevantObject(const INavRelevantInterface& NavRelevantObject)
{
UnregisterNavRelevantObject(Cast<UObject>(&NavRelevantObject));
}
void UNavigationObjectRepository::UnregisterNavRelevantObject(const UObject* NavRelevantObject)
{
UE_LOG(LogNavigation, Verbose, TEXT("%hs (%s:%s)"), __FUNCTION__,
NavRelevantObject ? *GetNameSafe(NavRelevantObject->GetOuter()) : TEXT("null outer"),
*GetNameSafe(NavRelevantObject));
FNavigationElementHandle Handle;
{
UE_MT_SCOPED_WRITE_ACCESS(NavElementAccessDetector);
ObjectsToHandleMap.RemoveAndCopyValue(FObjectKey(NavRelevantObject), Handle);
}
if (Handle)
{
RemoveNavigationElement(Handle);
}
}
TSharedPtr<const FNavigationElement> UNavigationObjectRepository::GetNavigationElementForHandle(const FNavigationElementHandle Handle) const
{
UE_MT_SCOPED_READ_ACCESS(NavElementAccessDetector);
if (const TSharedPtr<const FNavigationElement>* Element = NavRelevantElements.Find(Handle))
{
return *Element;
}
return nullptr;
}
FNavigationElementHandle UNavigationObjectRepository::GetNavigationElementHandleForUObject(const UObject* NavRelevantObject)
{
UE_MT_SCOPED_READ_ACCESS(NavElementAccessDetector);
if (const FNavigationElementHandle* Handle = ObjectsToHandleMap.Find(FObjectKey(Cast<UObject>(NavRelevantObject))))
{
return *Handle;
}
return FNavigationElementHandle::Invalid;
}
TSharedPtr<const FNavigationElement> UNavigationObjectRepository::GetNavigationElementForUObject(const UObject* NavRelevantObject)
{
UE_MT_SCOPED_READ_ACCESS(NavElementAccessDetector);
if (const FNavigationElementHandle* Handle = ObjectsToHandleMap.Find(FObjectKey(NavRelevantObject)))
{
if (const TSharedPtr<const FNavigationElement>* Element = NavRelevantElements.Find(*Handle))
{
return *Element;
}
}
return nullptr;
}
TSharedPtr<const FNavigationElement> UNavigationObjectRepository::UpdateNavigationElementForUObject(
const INavRelevantInterface& NavRelevantInterface,
const UObject& NavRelevantObject)
{
// This method is called by the navigation system to make sure an up-to-date navigation element exists for a
// given navigation relevant UObject.
// In this case we only need to create, or update, the navigation element without sending
// notification (i.e. ENotifyOnSuccess::No) since the caller (NavigationSystem) is already in the process of updating.
return RegisterNavRelevantObjectInternal(NavRelevantInterface, NavRelevantObject, ENotifyOnSuccess::No);
}
void UNavigationObjectRepository::RegisterCustomNavLinkObject(INavLinkCustomInterface& CustomNavLinkObject)
{
{
UE_MT_SCOPED_WRITE_ACCESS(NavElementAccessDetector);
#if DO_ENSURE // We don't want to execute the Find at all for targets where ensures are disabled
if (!ensureMsgf(CustomLinkObjects.Find(&CustomNavLinkObject) == INDEX_NONE, TEXT("Same interface pointer can't be registered twice.")))
{
return;
}
#endif
CustomLinkObjects.Emplace(&CustomNavLinkObject);
}
OnCustomNavLinkObjectRegistered.ExecuteIfBound(CustomNavLinkObject);
}
void UNavigationObjectRepository::UnregisterCustomNavLinkObject(INavLinkCustomInterface& CustomNavLinkObject)
{
{
UE_MT_SCOPED_WRITE_ACCESS(NavElementAccessDetector);
ensureMsgf(CustomLinkObjects.Remove(&CustomNavLinkObject) > 0, TEXT("Interface can't be removed since it was not registered or already unregistered)"));
}
OnCustomNavLinkObjectUnregistered.ExecuteIfBound(CustomNavLinkObject);
}