Files
UnrealEngineUWP/Engine/Source/Editor/ContentBrowser/Private/NativeClassHierarchy.cpp
Ryan Vance 7c51ff94af Merging //UE4/Dev-Main to Dev-VR (//UE4/Dev-VR)
CL 1 of 8
#rb integration

[CL 4748712 by Ryan Vance in Dev-VR branch]
2019-01-17 18:54:05 -05:00

587 lines
20 KiB
C++

// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
#include "NativeClassHierarchy.h"
#include "Modules/ModuleManager.h"
#include "UObject/Class.h"
#include "UObject/UObjectHash.h"
#include "UObject/UObjectIterator.h"
#include "UObject/Package.h"
#include "Misc/PackageName.h"
#include "ContentBrowserLog.h"
#include "Kismet2/KismetEditorUtilities.h"
#include "GameProjectGenerationModule.h"
#include "Misc/HotReloadInterface.h"
#include "SourceCodeNavigation.h"
#include "Interfaces/IPluginManager.h"
TSharedRef<FNativeClassHierarchyNode> FNativeClassHierarchyNode::MakeFolderEntry(FName InEntryName, FString InEntryPath)
{
TSharedRef<FNativeClassHierarchyNode> NewEntry = MakeShareable(new FNativeClassHierarchyNode());
NewEntry->Type = ENativeClassHierarchyNodeType::Folder;
NewEntry->Class = nullptr;
NewEntry->EntryName = InEntryName;
NewEntry->EntryPath = MoveTemp(InEntryPath);
return NewEntry;
}
TSharedRef<FNativeClassHierarchyNode> FNativeClassHierarchyNode::MakeClassEntry(UClass* InClass, FName InClassModuleName, FString InClassModuleRelativePath, FString InEntryPath)
{
TSharedRef<FNativeClassHierarchyNode> NewEntry = MakeShareable(new FNativeClassHierarchyNode());
NewEntry->Type = ENativeClassHierarchyNodeType::Class;
NewEntry->Class = InClass;
NewEntry->ClassModuleName = MoveTemp(InClassModuleName);
NewEntry->ClassModuleRelativePath = MoveTemp(InClassModuleRelativePath);
NewEntry->EntryName = InClass->GetFName();
NewEntry->EntryPath = MoveTemp(InEntryPath);
return NewEntry;
}
void FNativeClassHierarchyNode::AddChild(TSharedRef<FNativeClassHierarchyNode> ChildEntry)
{
check(Type == ENativeClassHierarchyNodeType::Folder);
Children.Add(FNativeClassHierarchyNodeKey(ChildEntry->EntryName, ChildEntry->Type), MoveTemp(ChildEntry));
}
FNativeClassHierarchy::FNativeClassHierarchy()
{
PopulateHierarchy();
// Register to be notified of module changes
FModuleManager::Get().OnModulesChanged().AddRaw(this, &FNativeClassHierarchy::OnModulesChanged);
// Register to be notified of hot reloads
IHotReloadInterface& HotReloadSupport = FModuleManager::LoadModuleChecked<IHotReloadInterface>("HotReload");
HotReloadSupport.OnHotReload().AddRaw(this, &FNativeClassHierarchy::OnHotReload);
}
FNativeClassHierarchy::~FNativeClassHierarchy()
{
FModuleManager::Get().OnModulesChanged().RemoveAll(this);
if(FModuleManager::Get().IsModuleLoaded("HotReload"))
{
IHotReloadInterface& HotReloadSupport = FModuleManager::GetModuleChecked<IHotReloadInterface>("HotReload");
HotReloadSupport.OnHotReload().RemoveAll(this);
}
}
bool FNativeClassHierarchy::HasClasses(const FName InClassPath, const bool bRecursive) const
{
TArray<TSharedRef<FNativeClassHierarchyNode>, TInlineAllocator<4>> NodesToSearch;
GatherMatchingNodesForPaths(TArrayView<const FName>(&InClassPath, 1), NodesToSearch);
for(const auto& NodeToSearch : NodesToSearch)
{
if(HasClassesRecursive(NodeToSearch, bRecursive))
{
return true;
}
}
return false;
}
bool FNativeClassHierarchy::HasFolders(const FName InClassPath, const bool bRecursive) const
{
TArray<TSharedRef<FNativeClassHierarchyNode>, TInlineAllocator<4>> NodesToSearch;
GatherMatchingNodesForPaths(TArrayView<const FName>(&InClassPath, 1), NodesToSearch);
for(const auto& NodeToSearch : NodesToSearch)
{
if(HasFoldersRecursive(NodeToSearch, bRecursive))
{
return true;
}
}
return false;
}
void FNativeClassHierarchy::GetMatchingClasses(const FNativeClassHierarchyFilter& Filter, TArray<UClass*>& OutClasses) const
{
TArray<TSharedRef<FNativeClassHierarchyNode>, TInlineAllocator<4>> NodesToSearch;
GatherMatchingNodesForPaths(Filter.ClassPaths, NodesToSearch);
for(const auto& NodeToSearch : NodesToSearch)
{
GetClassesRecursive(NodeToSearch, OutClasses, Filter.bRecursivePaths);
}
}
void FNativeClassHierarchy::GetMatchingFolders(const FNativeClassHierarchyFilter& Filter, TArray<FString>& OutFolders) const
{
TArray<TSharedRef<FNativeClassHierarchyNode>, TInlineAllocator<4>> NodesToSearch;
GatherMatchingNodesForPaths(Filter.ClassPaths, NodesToSearch);
for(const auto& NodeToSearch : NodesToSearch)
{
GetFoldersRecursive(NodeToSearch, OutFolders, Filter.bRecursivePaths);
}
}
void FNativeClassHierarchy::GetClassFolders(TArray<FName>& OutClassRoots, TArray<FString>& OutClassFolders, const bool bIncludeEngineClasses, const bool bIncludePluginClasses) const
{
static const FName EngineRootNodeName = "Classes_Engine";
static const FName GameRootNodeName = "Classes_Game";
for(const auto& RootNode : RootNodes)
{
bool bRootNodePassesFilter =
(RootNode.Key == GameRootNodeName) || // Always include game classes
(RootNode.Key == EngineRootNodeName && bIncludeEngineClasses) || // Only include engine classes if we were asked for them
(bIncludePluginClasses && RootNode.Value->LoadedFrom == EPluginLoadedFrom::Project) || // Only include Game plugin classes if we were asked for them
(bIncludePluginClasses && bIncludeEngineClasses && RootNode.Value->LoadedFrom == EPluginLoadedFrom::Engine); // Only include engine plugin classes if we were asked for them
if(bRootNodePassesFilter)
{
OutClassRoots.Add(RootNode.Key);
GetFoldersRecursive(RootNode.Value.ToSharedRef(), OutClassFolders);
}
}
}
bool FNativeClassHierarchy::HasClassesRecursive(const TSharedRef<FNativeClassHierarchyNode>& HierarchyNode, const bool bRecurse)
{
for(const auto& ChildNode : HierarchyNode->Children)
{
if(ChildNode.Value->Type == ENativeClassHierarchyNodeType::Class)
{
return true;
}
if(bRecurse && HasClassesRecursive(ChildNode.Value.ToSharedRef()))
{
return true;
}
}
return false;
}
bool FNativeClassHierarchy::HasFoldersRecursive(const TSharedRef<FNativeClassHierarchyNode>& HierarchyNode, const bool bRecurse)
{
for(const auto& ChildNode : HierarchyNode->Children)
{
if(ChildNode.Value->Type == ENativeClassHierarchyNodeType::Folder)
{
return true;
}
if(bRecurse && HasFoldersRecursive(ChildNode.Value.ToSharedRef()))
{
return true;
}
}
return false;
}
void FNativeClassHierarchy::GetClassesRecursive(const TSharedRef<FNativeClassHierarchyNode>& HierarchyNode, TArray<UClass*>& OutClasses, const bool bRecurse)
{
for(const auto& ChildNode : HierarchyNode->Children)
{
if(ChildNode.Value->Type == ENativeClassHierarchyNodeType::Class)
{
OutClasses.Add(ChildNode.Value->Class);
}
if(bRecurse)
{
GetClassesRecursive(ChildNode.Value.ToSharedRef(), OutClasses);
}
}
}
void FNativeClassHierarchy::GetFoldersRecursive(const TSharedRef<FNativeClassHierarchyNode>& HierarchyNode, TArray<FString>& OutFolders, const bool bRecurse)
{
for(const auto& ChildNode : HierarchyNode->Children)
{
if(ChildNode.Value->Type == ENativeClassHierarchyNodeType::Folder)
{
OutFolders.Add(ChildNode.Value->EntryPath);
}
if(bRecurse)
{
GetFoldersRecursive(ChildNode.Value.ToSharedRef(), OutFolders);
}
}
}
void FNativeClassHierarchy::GatherMatchingNodesForPaths(const TArrayView<const FName>& InClassPaths, TArray<TSharedRef<FNativeClassHierarchyNode>, TInlineAllocator<4>>& OutMatchingNodes) const
{
if(InClassPaths.Num() == 0)
{
// No paths means search all roots
OutMatchingNodes.Reserve(RootNodes.Num());
for(const auto& RootNode : RootNodes)
{
OutMatchingNodes.Add(RootNode.Value.ToSharedRef());
}
}
else
{
for(const FName& ClassPath : InClassPaths)
{
TSharedPtr<FNativeClassHierarchyNode> CurrentNode;
TArray<FString> ClassPathParts;
ClassPath.ToString().ParseIntoArray(ClassPathParts, TEXT("/"), true);
for(const FString& ClassPathPart : ClassPathParts)
{
// Try and find the node associated with this part of the path...
const FName ClassPathPartName = *ClassPathPart;
CurrentNode = (CurrentNode.IsValid()) ? CurrentNode->Children.FindRef(FNativeClassHierarchyNodeKey(ClassPathPartName, ENativeClassHierarchyNodeType::Folder)) : RootNodes.FindRef(ClassPathPartName);
// ... bail out if we didn't find a valid node
if(!CurrentNode.IsValid())
{
break;
}
}
if(CurrentNode.IsValid())
{
OutMatchingNodes.Add(CurrentNode.ToSharedRef());
}
}
}
}
void FNativeClassHierarchy::PopulateHierarchy()
{
FAddClassMetrics AddClassMetrics;
RootNodes.Empty();
TSet<FName> GameModules = GetGameModules();
TMap<FName, FNativeClassHierarchyPluginModuleInfo> PluginModules = GetPluginModules();
for(TObjectIterator<UClass> ClassIt; ClassIt; ++ClassIt)
{
UClass* const CurrentClass = *ClassIt;
AddClass(CurrentClass, GameModules, PluginModules, AddClassMetrics);
}
UE_LOG(LogContentBrowser, Log, TEXT("Native class hierarchy populated in %0.4f seconds. Added %d classes and %d folders."), FPlatformTime::Seconds() - AddClassMetrics.StartTime, AddClassMetrics.NumClassesAdded, AddClassMetrics.NumFoldersAdded);
ClassHierarchyUpdatedDelegate.Broadcast();
}
void FNativeClassHierarchy::AddClassesForModule(const FName& InModuleName)
{
FAddClassMetrics AddClassMetrics;
// Find the class package for this module
UPackage* const ClassPackage = FindPackage(nullptr, *(FString("/Script/") + InModuleName.ToString()));
if(!ClassPackage)
{
return;
}
TSet<FName> GameModules = GetGameModules();
TMap<FName, FNativeClassHierarchyPluginModuleInfo> PluginModules = GetPluginModules();
TArray<UObject*> PackageObjects;
GetObjectsWithOuter(ClassPackage, PackageObjects, false);
for(UObject* Object : PackageObjects)
{
UClass* const CurrentClass = Cast<UClass>(Object);
if(CurrentClass)
{
AddClass(CurrentClass, GameModules, PluginModules, AddClassMetrics);
}
}
UE_LOG(LogContentBrowser, Log, TEXT("Native class hierarchy updated for '%s' in %0.4f seconds. Added %d classes and %d folders."), *InModuleName.ToString(), FPlatformTime::Seconds() - AddClassMetrics.StartTime, AddClassMetrics.NumClassesAdded, AddClassMetrics.NumFoldersAdded);
ClassHierarchyUpdatedDelegate.Broadcast();
}
void FNativeClassHierarchy::RemoveClassesForModule(const FName& InModuleName)
{
// Modules always exist directly under a root
for(const auto& RootNode : RootNodes)
{
TSharedPtr<FNativeClassHierarchyNode> ModuleNode = RootNode.Value->Children.FindRef(FNativeClassHierarchyNodeKey(InModuleName, ENativeClassHierarchyNodeType::Folder));
if(ModuleNode.IsValid())
{
// Remove this module from its root
RootNode.Value->Children.Remove(FNativeClassHierarchyNodeKey(InModuleName, ENativeClassHierarchyNodeType::Folder));
// If this module was the only child of this root, then we need to remove the root as well
if(RootNode.Value->Children.Num() == 0)
{
RootNodes.Remove(RootNode.Key);
}
ClassHierarchyUpdatedDelegate.Broadcast();
// We've found the module - break
break;
}
}
}
void FNativeClassHierarchy::AddClass(UClass* InClass, const TSet<FName>& InGameModules, const TMap<FName, FNativeClassHierarchyPluginModuleInfo>& InPluginModules, FAddClassMetrics& AddClassMetrics)
{
// Ignore deprecated and temporary classes
if(InClass->HasAnyClassFlags(CLASS_Deprecated | CLASS_NewerVersionExists) || FKismetEditorUtilities::IsClassABlueprintSkeleton(InClass))
{
return;
}
const FName ClassModuleName = GetClassModuleName(InClass);
if(ClassModuleName.IsNone())
{
return;
}
static const FName ModuleRelativePathMetaDataKey = "ModuleRelativePath";
const FString& ClassModuleRelativeIncludePath = InClass->GetMetaData(ModuleRelativePathMetaDataKey);
if(ClassModuleRelativeIncludePath.IsEmpty())
{
return;
}
// Work out which root this class should go under
EPluginLoadedFrom WhereLoadedFrom;
const FName RootNodeName = GetClassPathRootForModule(ClassModuleName, InGameModules, InPluginModules, WhereLoadedFrom);
// Work out the final path to this class within the hierarchy (which isn't the same as the path on disk)
const FString ClassModuleRelativePath = ClassModuleRelativeIncludePath.Left(ClassModuleRelativeIncludePath.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd));
const FString ClassHierarchyPath = ClassModuleName.ToString() + TEXT("/") + ClassModuleRelativePath;
// Ensure we've added a valid root node
TSharedPtr<FNativeClassHierarchyNode>& RootNode = RootNodes.FindOrAdd(RootNodeName);
if(!RootNode.IsValid())
{
RootNode = FNativeClassHierarchyNode::MakeFolderEntry(RootNodeName, TEXT("/") + RootNodeName.ToString());
RootNode->LoadedFrom = WhereLoadedFrom;
++AddClassMetrics.NumFoldersAdded;
}
// Split the class path and ensure we have nodes for each part
TArray<FString> HierarchyPathParts;
ClassHierarchyPath.ParseIntoArray(HierarchyPathParts, TEXT("/"), true);
TSharedPtr<FNativeClassHierarchyNode> CurrentNode = RootNode;
for(const FString& HierarchyPathPart : HierarchyPathParts)
{
const FName HierarchyPathPartName = *HierarchyPathPart;
TSharedPtr<FNativeClassHierarchyNode>& ChildNode = CurrentNode->Children.FindOrAdd(FNativeClassHierarchyNodeKey(HierarchyPathPartName, ENativeClassHierarchyNodeType::Folder));
if(!ChildNode.IsValid())
{
ChildNode = FNativeClassHierarchyNode::MakeFolderEntry(HierarchyPathPartName, CurrentNode->EntryPath + TEXT("/") + HierarchyPathPart);
++AddClassMetrics.NumFoldersAdded;
}
CurrentNode = ChildNode;
}
// Now add the final entry for the class
CurrentNode->AddChild(FNativeClassHierarchyNode::MakeClassEntry(InClass, ClassModuleName, ClassModuleRelativePath, CurrentNode->EntryPath + TEXT("/") + InClass->GetName()));
++AddClassMetrics.NumClassesAdded;
}
void FNativeClassHierarchy::AddFolder(const FString& InClassPath)
{
bool bHasAddedFolder = false;
// Split the class path and ensure we have nodes for each part
TArray<FString> ClassPathParts;
InClassPath.ParseIntoArray(ClassPathParts, TEXT("/"), true);
TSharedPtr<FNativeClassHierarchyNode> CurrentNode;
for(const FString& ClassPathPart : ClassPathParts)
{
const FName ClassPathPartName = *ClassPathPart;
TSharedPtr<FNativeClassHierarchyNode>& ChildNode = (CurrentNode.IsValid()) ? CurrentNode->Children.FindOrAdd(FNativeClassHierarchyNodeKey(ClassPathPartName, ENativeClassHierarchyNodeType::Folder)) : RootNodes.FindOrAdd(ClassPathPartName);
if(!ChildNode.IsValid())
{
ChildNode = FNativeClassHierarchyNode::MakeFolderEntry(ClassPathPartName, CurrentNode->EntryPath + TEXT("/") + ClassPathPart);
bHasAddedFolder = true;
}
CurrentNode = ChildNode;
}
if(bHasAddedFolder)
{
ClassHierarchyUpdatedDelegate.Broadcast();
}
}
bool FNativeClassHierarchy::GetFileSystemPath(const FString& InClassPath, FString& OutFileSystemPath) const
{
// Split the class path into its component parts
TArray<FString> ClassPathParts;
InClassPath.ParseIntoArray(ClassPathParts, TEXT("/"), true);
// We need to have at least two sections (a root, and a module name) to be able to resolve a file system path
if(ClassPathParts.Num() < 2)
{
return false;
}
// Is this path using a known root?
TSharedPtr<FNativeClassHierarchyNode> RootNode = RootNodes.FindRef(*ClassPathParts[0]);
if(!RootNode.IsValid())
{
return false;
}
// Is this path using a known module within that root?
TSharedPtr<FNativeClassHierarchyNode> ModuleNode = RootNode->Children.FindRef(FNativeClassHierarchyNodeKey(*ClassPathParts[1], ENativeClassHierarchyNodeType::Folder));
if(!ModuleNode.IsValid())
{
return false;
}
// Get the base file path to the module, and then append any remaining parts of the class path (as the remaining parts mirror the file system)
if(FSourceCodeNavigation::FindModulePath(ClassPathParts[1], OutFileSystemPath))
{
for(int32 PathPartIndex = 2; PathPartIndex < ClassPathParts.Num(); ++PathPartIndex)
{
OutFileSystemPath /= ClassPathParts[PathPartIndex];
}
return true;
}
return false;
}
bool FNativeClassHierarchy::GetClassPath(UClass* InClass, FString& OutClassPath, const bool bIncludeClassName) const
{
const FName ClassModuleName = GetClassModuleName(InClass);
if(ClassModuleName.IsNone())
{
return false;
}
static const FName ModuleRelativePathMetaDataKey = "ModuleRelativePath";
const FString& ClassModuleRelativeIncludePath = InClass->GetMetaData(ModuleRelativePathMetaDataKey);
if(ClassModuleRelativeIncludePath.IsEmpty())
{
return false;
}
TSet<FName> GameModules = GetGameModules();
TMap<FName, FNativeClassHierarchyPluginModuleInfo> PluginModules = GetPluginModules();
// Work out which root this class should go under
EPluginLoadedFrom WhereLoadedFrom;
const FName RootNodeName = GetClassPathRootForModule(ClassModuleName, GameModules, PluginModules, WhereLoadedFrom);
// Work out the final path to this class within the hierarchy (which isn't the same as the path on disk)
const FString ClassModuleRelativePath = ClassModuleRelativeIncludePath.Left(ClassModuleRelativeIncludePath.Find(TEXT("/"), ESearchCase::CaseSensitive, ESearchDir::FromEnd));
OutClassPath = FString(TEXT("/")) + RootNodeName.ToString() + TEXT("/") + ClassModuleName.ToString();
if(!ClassModuleRelativePath.IsEmpty())
{
OutClassPath += TEXT("/") + ClassModuleRelativePath;
}
if(bIncludeClassName)
{
OutClassPath += TEXT("/") + InClass->GetName();
}
return true;
}
void FNativeClassHierarchy::OnModulesChanged(FName InModuleName, EModuleChangeReason InModuleChangeReason)
{
switch(InModuleChangeReason)
{
case EModuleChangeReason::ModuleLoaded:
AddClassesForModule(InModuleName);
break;
case EModuleChangeReason::ModuleUnloaded:
RemoveClassesForModule(InModuleName);
break;
default:
break;
}
}
void FNativeClassHierarchy::OnHotReload(bool bWasTriggeredAutomatically)
{
PopulateHierarchy();
}
FName FNativeClassHierarchy::GetClassModuleName(UClass* InClass)
{
UPackage* const ClassPackage = InClass->GetOuterUPackage();
if(ClassPackage)
{
return FPackageName::GetShortFName(ClassPackage->GetFName());
}
return NAME_None;
}
FName FNativeClassHierarchy::GetClassPathRootForModule(const FName& InModuleName, const TSet<FName>& InGameModules, const TMap<FName, FNativeClassHierarchyPluginModuleInfo>& InPluginModules, EPluginLoadedFrom& OutWhereLoadedFrom)
{
static const FName EngineRootNodeName = "Classes_Engine";
static const FName GameRootNodeName = "Classes_Game";
// Work out which root this class should go under (anything that isn't a game or plugin module goes under engine)
FName RootNodeName = EngineRootNodeName;
OutWhereLoadedFrom = EPluginLoadedFrom::Engine;
if(InGameModules.Contains(InModuleName))
{
RootNodeName = GameRootNodeName;
OutWhereLoadedFrom = EPluginLoadedFrom::Project;
}
else if(InPluginModules.Contains(InModuleName))
{
const FNativeClassHierarchyPluginModuleInfo PluginInfo = InPluginModules.FindRef(InModuleName);
RootNodeName = FName(*(FString(TEXT("Classes_")) + PluginInfo.Name.ToString()));
OutWhereLoadedFrom = PluginInfo.LoadedFrom;
}
return RootNodeName;
}
TSet<FName> FNativeClassHierarchy::GetGameModules()
{
FGameProjectGenerationModule& GameProjectModule = FGameProjectGenerationModule::Get();
// Build up a set of known game modules - used to work out which modules populate Classes_Game
TSet<FName> GameModules;
if(GameProjectModule.ProjectHasCodeFiles())
{
TArray<FModuleContextInfo> GameModulesInfo = GameProjectModule.GetCurrentProjectModules();
for(const auto& GameModuleInfo : GameModulesInfo)
{
GameModules.Add(FName(*GameModuleInfo.ModuleName));
}
}
return GameModules;
}
TMap<FName, FNativeClassHierarchyPluginModuleInfo> FNativeClassHierarchy::GetPluginModules()
{
IPluginManager& PluginManager = IPluginManager::Get();
// Build up a map of plugin modules -> plugin names - used to work out which modules populate a given Classes_PluginName
TMap<FName, FNativeClassHierarchyPluginModuleInfo> PluginModules;
{
TArray<TSharedRef<IPlugin>> Plugins = PluginManager.GetDiscoveredPlugins();
for(const TSharedRef<IPlugin>& Plugin: Plugins)
{
for(const FModuleDescriptor& PluginModule: Plugin->GetDescriptor().Modules)
{
FNativeClassHierarchyPluginModuleInfo Info;
Info.Name = FName(*Plugin->GetName());
Info.LoadedFrom = Plugin->GetLoadedFrom();
PluginModules.Add(PluginModule.Name, Info);
}
}
}
return PluginModules;
}