Files
UnrealEngine/Engine/Source/Editor/Kismet/Private/BlueprintNamespaceRegistry.cpp
2025-05-18 13:04:45 +08:00

396 lines
13 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "BlueprintNamespaceRegistry.h"
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/AssetData.h"
#include "AssetRegistry/AssetRegistryModule.h"
#include "AssetRegistry/IAssetRegistry.h"
#include "BlueprintNamespacePathTree.h"
#include "BlueprintNamespaceUtilities.h"
#include "Containers/EnumAsByte.h"
#include "Containers/Map.h"
#include "Containers/StringFwd.h"
#include "CoreGlobals.h"
#include "Delegates/Delegate.h"
#include "EdGraphSchema_K2.h"
#include "Engine/Blueprint.h"
#include "Engine/BlueprintGeneratedClass.h"
#include "Engine/UserDefinedEnum.h"
#include "StructUtils/UserDefinedStruct.h"
#include "HAL/IConsoleManager.h"
#include "HAL/Platform.h"
#include "HAL/PlatformCrt.h"
#include "Kismet/BlueprintFunctionLibrary.h"
#include "Logging/LogCategory.h"
#include "Logging/LogMacros.h"
#include "Misc/StringBuilder.h"
#include "Modules/ModuleManager.h"
#include "Templates/SharedPointer.h"
#include "Templates/UnrealTemplate.h"
#include "Trace/Detail/Channel.h"
#include "UObject/Class.h"
#include "UObject/NameTypes.h"
#include "UObject/TopLevelAssetPath.h"
#include "UObject/UObjectIterator.h"
class UObject;
DEFINE_LOG_CATEGORY_STATIC(LogNamespace, Log, All);
FBlueprintNamespaceRegistry::FBlueprintNamespaceRegistry()
: bIsInitialized(false)
{
IConsoleManager::Get().RegisterConsoleCommand
(
TEXT("BP.ToggleUsePackagePathAsDefaultNamespace"),
TEXT("Toggle the use of a type's package path as its default namespace when not explicitly assigned. Otherwise, all types default to the global namespace."),
FConsoleCommandDelegate::CreateRaw(this, &FBlueprintNamespaceRegistry::ToggleDefaultNamespace),
ECVF_Default
);
IConsoleManager::Get().RegisterConsoleCommand
(
TEXT("BP.DumpAllRegisteredNamespacePaths"),
TEXT("Dumps all registered namespace paths."),
FConsoleCommandDelegate::CreateRaw(this, &FBlueprintNamespaceRegistry::DumpAllRegisteredPaths),
ECVF_Default
);
}
FBlueprintNamespaceRegistry::~FBlueprintNamespaceRegistry()
{
Shutdown();
}
void FBlueprintNamespaceRegistry::Initialize()
{
if (bIsInitialized)
{
return;
}
PathTree = MakeUnique<FBlueprintNamespacePathTree>();
// Skip namespace harvesting if we're not inside an interactive editor context.
if(GIsEditor && !IsRunningCommandlet())
{
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
if (AssetRegistry.IsLoadingAssets())
{
OnFilesLoadedDelegateHandle = AssetRegistry.OnFilesLoaded().AddRaw(this, &FBlueprintNamespaceRegistry::OnAssetRegistryFilesLoaded);
}
else
{
OnAssetRegistryFilesLoaded();
}
OnReloadCompleteDelegateHandle = FCoreUObjectDelegates::ReloadCompleteDelegate.AddRaw(this, &FBlueprintNamespaceRegistry::OnReloadComplete);
OnDefaultNamespaceTypeChangedDelegateHandle = FBlueprintNamespaceUtilities::OnDefaultBlueprintNamespaceTypeChanged().AddRaw(this, &FBlueprintNamespaceRegistry::OnDefaultNamespaceTypeChanged);
}
bIsInitialized = true;
}
void FBlueprintNamespaceRegistry::Shutdown()
{
if (!bIsInitialized)
{
return;
}
FCoreUObjectDelegates::ReloadCompleteDelegate.Remove(OnReloadCompleteDelegateHandle);
FBlueprintNamespaceUtilities::OnDefaultBlueprintNamespaceTypeChanged().Remove(OnDefaultNamespaceTypeChangedDelegateHandle);
if (FModuleManager::Get().IsModuleLoaded(TEXT("AssetRegistry")))
{
IAssetRegistry* AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).TryGet();
if (AssetRegistry)
{
AssetRegistry->OnAssetAdded().Remove(OnAssetAddedDelegateHandle);
AssetRegistry->OnAssetRemoved().Remove(OnAssetRemovedDelegateHandle);
AssetRegistry->OnAssetRenamed().Remove(OnAssetRenamedDelegateHandle);
AssetRegistry->OnFilesLoaded().Remove(OnFilesLoadedDelegateHandle);
}
}
bIsInitialized = false;
}
void FBlueprintNamespaceRegistry::OnAssetAdded(const FAssetData& AssetData)
{
if(const UClass* AssetClass = AssetData.GetClass())
{
if (AssetClass->IsChildOf<UBlueprint>()
|| AssetClass->IsChildOf<UBlueprintGeneratedClass>()
|| AssetClass->IsChildOf<UUserDefinedEnum>()
|| AssetClass->IsChildOf<UUserDefinedStruct>()
|| AssetClass->IsChildOf<UBlueprintFunctionLibrary>())
{
RegisterNamespace(AssetData);
}
}
}
void FBlueprintNamespaceRegistry::OnAssetRemoved(const FAssetData& AssetData)
{
// Remove the asset's current namespace if it's non-empty and was previously registered.
FString AssetNamespace = FBlueprintNamespaceUtilities::GetAssetNamespace(AssetData);
if (!AssetNamespace.IsEmpty() && IsRegisteredPath(AssetNamespace))
{
PathTree->RemovePath(AssetNamespace);
// Add the removed asset to the exclusion set so we don't re-register it.
FSoftObjectPath RemovedObjectPath = AssetData.ToSoftObjectPath();
ExcludedObjectPaths.Add(RemovedObjectPath);
// Refresh the registry in case the same namespace is in use by another asset.
FindAndRegisterAllNamespaces();
// Clear the removed asset from the exclusion set.
ExcludedObjectPaths.Remove(RemovedObjectPath);
}
}
void FBlueprintNamespaceRegistry::OnAssetRenamed(const FAssetData& AssetData, const FString& InOldPath)
{
FString OldDefaultNamespacePath;
FBlueprintNamespaceUtilities::ConvertPackagePathToNamespacePath(InOldPath, OldDefaultNamespacePath);
// Remove the old path if it was explicitly registered as the default namespace.
if (IsRegisteredPath(OldDefaultNamespacePath))
{
PathTree->RemovePath(OldDefaultNamespacePath);
}
// Register the asset's new package path if we are using it as the default namespace.
if (FBlueprintNamespaceUtilities::GetDefaultBlueprintNamespaceType() == EDefaultBlueprintNamespaceType::UsePackagePathAsDefaultNamespace)
{
FString NewDefaultNamespacePath;
FBlueprintNamespaceUtilities::ConvertPackagePathToNamespacePath(AssetData.PackageName.ToString(), NewDefaultNamespacePath);
PathTree->AddPath(NewDefaultNamespacePath);
}
}
void FBlueprintNamespaceRegistry::OnAssetRegistryFilesLoaded()
{
// Find available namespaces once all assets are registered.
FindAndRegisterAllNamespaces();
// Get notified for new asset events that occur going forward.
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
OnAssetAddedDelegateHandle = AssetRegistry.OnAssetAdded().AddRaw(this, &FBlueprintNamespaceRegistry::OnAssetAdded);
OnAssetRemovedDelegateHandle = AssetRegistry.OnAssetRemoved().AddRaw(this, &FBlueprintNamespaceRegistry::OnAssetRemoved);
OnAssetRenamedDelegateHandle = AssetRegistry.OnAssetRenamed().AddRaw(this, &FBlueprintNamespaceRegistry::OnAssetRenamed);
}
void FBlueprintNamespaceRegistry::OnReloadComplete(EReloadCompleteReason InReason)
{
// Rebuild the registry to reflect the appropriate default namespace identifiers for any reloaded types.
Rebuild();
}
bool FBlueprintNamespaceRegistry::IsInclusivePath(const FString& InPath) const
{
// A path is considered inclusive if it represents any valid subpath in the tree.
TSharedPtr<FBlueprintNamespacePathTree::FNode> Node = PathTree->FindPathNode(InPath);
return Node.IsValid();
}
bool FBlueprintNamespaceRegistry::IsRegisteredPath(const FString& InPath) const
{
// A path is considered to be registered only if it was explicitly added to the tree.
TSharedPtr<FBlueprintNamespacePathTree::FNode> Node = PathTree->FindPathNode(InPath);
return Node.IsValid() && Node->bIsAddedPath;
}
void FBlueprintNamespaceRegistry::GetNamesUnderPath(const FString& InPath, TArray<FName>& OutNames) const
{
TSharedPtr<FBlueprintNamespacePathTree::FNode> Node = PathTree->FindPathNode(InPath);
if (Node.IsValid())
{
for (auto ChildIt = Node->Children.CreateConstIterator(); ChildIt; ++ChildIt)
{
OutNames.Add(ChildIt.Key());
}
}
}
void FBlueprintNamespaceRegistry::GetAllRegisteredPaths(TArray<FString>& OutPaths) const
{
PathTree->ForeachNode([&OutPaths](const TArray<FName>& CurrentPath, TSharedRef<const FBlueprintNamespacePathTree::FNode> Node)
{
if (Node->bIsAddedPath)
{
// Note: This is not a hard limit on namespace path identifier string length, it's an optimization to try and avoid reallocation during path construction.
TStringBuilder<128> PathBuilder;
for (FName PathSegment : CurrentPath)
{
if (PathBuilder.Len() > 0)
{
PathBuilder += FBlueprintNamespacePathTree::PathSeparator;
}
PathBuilder += PathSegment.ToString();
}
FString FullPath = PathBuilder.ToString();
OutPaths.Add(MoveTemp(FullPath));
}
});
}
void FBlueprintNamespaceRegistry::FindAndRegisterAllNamespaces()
{
TRACE_CPUPROFILER_EVENT_SCOPE(FBlueprintNamespaceRegistry::FindAndRegisterAllNamespaces);
// Resolve the subset of excluded objects that are loaded.
TSet<const UObject*> ExcludedObjects;
for (const FSoftObjectPath& ExcludedObjectPath : ExcludedObjectPaths)
{
if (const UObject* ExcludedObject = ExcludedObjectPath.ResolveObject())
{
ExcludedObjects.Add(ExcludedObject);
}
}
// Register loaded class type namespace identifiers.
for (TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
const UClass* ClassObject = *ClassIt;
// Include native function library class objects as well.
const bool bShouldRegisterClassObject =
UEdGraphSchema_K2::IsAllowableBlueprintVariableType(ClassObject)
|| ClassObject->IsChildOf<UBlueprintFunctionLibrary>();
const UBlueprint* BlueprintObject = UBlueprint::GetBlueprintFromClass(ClassObject);
if (bShouldRegisterClassObject && !ExcludedObjects.Contains(ClassObject) && (!BlueprintObject || !ExcludedObjects.Contains(BlueprintObject)))
{
RegisterNamespace(ClassObject);
}
}
// Register loaded macro asset namespace identifiers.
for (TObjectIterator<UBlueprint> BlueprintIt; BlueprintIt; ++BlueprintIt)
{
const UBlueprint* BlueprintObject = *BlueprintIt;
if (BlueprintObject->BlueprintType == BPTYPE_MacroLibrary && !ExcludedObjects.Contains(BlueprintObject))
{
RegisterNamespace(BlueprintObject);
}
}
// Register loaded struct type namespace identifiers.
for (TObjectIterator<UScriptStruct> StructIt; StructIt; ++StructIt)
{
const UScriptStruct* StructObject = *StructIt;
if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(StructObject) && !ExcludedObjects.Contains(StructObject))
{
RegisterNamespace(StructObject);
}
}
// Register loaded enum type namespace identifiers.
for (TObjectIterator<UEnum> EnumIt; EnumIt; ++EnumIt)
{
const UEnum* EnumObject = *EnumIt;
if (UEdGraphSchema_K2::IsAllowableBlueprintVariableType(EnumObject) && !ExcludedObjects.Contains(EnumObject))
{
RegisterNamespace(EnumObject);
}
}
FARFilter ClassFilter;
ClassFilter.ClassPaths.Add(UBlueprint::StaticClass()->GetClassPathName());
ClassFilter.ClassPaths.Add(UBlueprintGeneratedClass::StaticClass()->GetClassPathName());
ClassFilter.ClassPaths.Add(UUserDefinedStruct::StaticClass()->GetClassPathName());
ClassFilter.ClassPaths.Add(UUserDefinedEnum::StaticClass()->GetClassPathName());
ClassFilter.ClassPaths.Add(UBlueprintFunctionLibrary::StaticClass()->GetClassPathName());
ClassFilter.bRecursiveClasses = true;
// Register unloaded type namespace identifiers.
TArray<FAssetData> BlueprintAssets;
IAssetRegistry& AssetRegistry = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry")).Get();
AssetRegistry.GetAssets(ClassFilter, BlueprintAssets);
for (const FAssetData& BlueprintAsset : BlueprintAssets)
{
if (!BlueprintAsset.IsAssetLoaded() && !ExcludedObjectPaths.Contains(BlueprintAsset.ToSoftObjectPath()))
{
RegisterNamespace(BlueprintAsset);
}
}
}
void FBlueprintNamespaceRegistry::RegisterNamespace(const FString& InPath)
{
if (!InPath.IsEmpty())
{
PathTree->AddPath(InPath);
}
}
void FBlueprintNamespaceRegistry::RegisterNamespace(const UObject* InObject)
{
FString ObjectNamespace = FBlueprintNamespaceUtilities::GetObjectNamespace(InObject);
RegisterNamespace(ObjectNamespace);
}
void FBlueprintNamespaceRegistry::RegisterNamespace(const FAssetData& AssetData)
{
FString AssetNamespace = FBlueprintNamespaceUtilities::GetAssetNamespace(AssetData);
RegisterNamespace(AssetNamespace);
}
void FBlueprintNamespaceRegistry::ToggleDefaultNamespace()
{
const EDefaultBlueprintNamespaceType OldType = FBlueprintNamespaceUtilities::GetDefaultBlueprintNamespaceType();
if (OldType == EDefaultBlueprintNamespaceType::DefaultToGlobalNamespace)
{
FBlueprintNamespaceUtilities::SetDefaultBlueprintNamespaceType(EDefaultBlueprintNamespaceType::UsePackagePathAsDefaultNamespace);
}
else if(OldType == EDefaultBlueprintNamespaceType::UsePackagePathAsDefaultNamespace)
{
FBlueprintNamespaceUtilities::SetDefaultBlueprintNamespaceType(EDefaultBlueprintNamespaceType::DefaultToGlobalNamespace);
}
}
void FBlueprintNamespaceRegistry::DumpAllRegisteredPaths()
{
if (!bIsInitialized)
{
Initialize();
}
TArray<FString> AllPaths;
GetAllRegisteredPaths(AllPaths);
UE_LOG(LogNamespace, Log, TEXT("=== Registered Blueprint namespace paths:"));
for (const FString& Path : AllPaths)
{
UE_LOG(LogNamespace, Log, TEXT("%s"), *Path);
}
UE_LOG(LogNamespace, Log, TEXT("=== (end) %d total paths ==="), AllPaths.Num());
}
void FBlueprintNamespaceRegistry::OnDefaultNamespaceTypeChanged()
{
// Rebuild the registry to reflect the appropriate default namespace identifiers for all known types.
Rebuild();
}
void FBlueprintNamespaceRegistry::Rebuild()
{
PathTree = MakeUnique<FBlueprintNamespacePathTree>();
FindAndRegisterAllNamespaces();
}
FBlueprintNamespaceRegistry& FBlueprintNamespaceRegistry::Get()
{
static FBlueprintNamespaceRegistry* Singleton = new FBlueprintNamespaceRegistry();
return *Singleton;
}