// 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& Args, const UWorld* World, FOutputDevice& OutputDevice) { if (const UNavigationObjectRepository* Repository = World->GetSubsystem()) { int32 NumElements = 0; Repository->ForEachNavigationElement([&OutputDevice, &NumElements](const TSharedRef& 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 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(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 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&)> PerElementCallback) const { UE_MT_SCOPED_READ_ACCESS(NavElementAccessDetector); for (auto It = NavRelevantElements.CreateConstIterator(); It; ++It) { if (const TSharedPtr& Element = It.Value()) { PerElementCallback(Element.ToSharedRef()); } } } TSharedPtr UNavigationObjectRepository::RegisterNavRelevantObject(const INavRelevantInterface& NavRelevantObject) { return RegisterNavRelevantObjectInternal(NavRelevantObject, *Cast(&NavRelevantObject), ENotifyOnSuccess::Yes); } bool UNavigationObjectRepository::ShouldCreateSubsystem(UObject* Outer) const { return (Super::ShouldCreateSubsystem(Outer)) && GetDefault()->ShouldCreateNavigationSystemInstance(Cast(Outer)); } TSharedPtr 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 ElementPtr = GetNavigationElementForUObject(&NavRelevantObject)) { const TSharedRef 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 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(&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 UNavigationObjectRepository::GetNavigationElementForHandle(const FNavigationElementHandle Handle) const { UE_MT_SCOPED_READ_ACCESS(NavElementAccessDetector); if (const TSharedPtr* 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(NavRelevantObject)))) { return *Handle; } return FNavigationElementHandle::Invalid; } TSharedPtr UNavigationObjectRepository::GetNavigationElementForUObject(const UObject* NavRelevantObject) { UE_MT_SCOPED_READ_ACCESS(NavElementAccessDetector); if (const FNavigationElementHandle* Handle = ObjectsToHandleMap.Find(FObjectKey(NavRelevantObject))) { if (const TSharedPtr* Element = NavRelevantElements.Find(*Handle)) { return *Element; } } return nullptr; } TSharedPtr 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); }