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

384 lines
12 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "ReferencedAssetsUtils.h"
#include "Modules/ModuleManager.h"
#include "UObject/Class.h"
#include "UObject/UObjectIterator.h"
#include "UObject/Package.h"
#include "Engine/Level.h"
#include "AssetRegistry/AssetData.h"
#include "Editor.h"
#include "AssetRegistry/ARFilter.h"
#include "AssetRegistry/AssetRegistryModule.h"
DEFINE_LOG_CATEGORY_STATIC(LogReferncedAssetsBrowser, Log, All);
/**
* Constructor. Builds the list of items to ignore
*/
FFindReferencedAssets::FFindReferencedAssets(void)
{
OnEditorMapChangeDelegateHandle = FEditorDelegates::MapChange.AddRaw(this, &FFindReferencedAssets::OnEditorMapChange);
// Set up our ignore lists
IgnoreClasses.Add(ULevel::StaticClass());
IgnoreClasses.Add(UWorld::StaticClass());
// Load the asset registry module
FAssetRegistryModule& AssetRegistryModule = FModuleManager::LoadModuleChecked<FAssetRegistryModule>(TEXT("AssetRegistry"));
TArray<FAssetData> AssetData;
FARFilter Filter;
Filter.PackagePaths.Add(FName(TEXT("/Engine/EngineResources")));
Filter.PackagePaths.Add(FName(TEXT("/Engine/EngineFonts")));
Filter.PackagePaths.Add(FName(TEXT("/Engine/EngineMaterials")));
Filter.PackagePaths.Add(FName(TEXT("/Engine/EditorResources")));
Filter.PackagePaths.Add(FName(TEXT("/Engine/EditorMaterials")));
AssetRegistryModule.Get().GetAssets(Filter, AssetData);
for (int32 AssetIdx = 0; AssetIdx < AssetData.Num(); ++AssetIdx)
{
UPackage* Package = FindObject<UPackage>(NULL, *AssetData[AssetIdx].PackageName.ToString(), true);
if( Package != NULL )
{
IgnorePackages.Add(Package);
}
}
IgnorePackages.Add(GetTransientPackage());
}
/**
* Destructor
*/
FFindReferencedAssets::~FFindReferencedAssets(void)
{
FEditorDelegates::MapChange.Remove(OnEditorMapChangeDelegateHandle);
}
void FFindReferencedAssets::OnEditorMapChange( uint32 Flag )
{
if ( Flag != MapChangeEventFlags::Default )
{
Referencers.Empty();
ReferenceGraph.Empty();
}
}
/* === FGCObject interface === */
void FFindReferencedAssets::AddReferencedObjects( FReferenceCollector& Collector )
{
// serialize all of our object references
for( auto& Node : ReferenceGraph )
{
Collector.AddReferencedObject( Node.Key );
Collector.AddReferencedObjects( Node.Value );
}
Collector.AddReferencedObjects( IgnoreClasses );
Collector.AddReferencedObjects( IgnorePackages );
}
/**
* Checks an object to see if it should be included for asset searching
*
* @param Object the object in question
* @param ClassesToIgnore the list of classes to skip
* @param PackagesToIgnore the list of packages to skip
* @param bIncludeDefaults specify true to include content referenced through defaults
*
* @return true if it should be searched, false otherwise
*/
bool FFindReferencedAssets::ShouldSearchForAssets( const UObject* Object, const TArray<UClass*>& ClassesToIgnore, const TArray<UPackage*>& PackagesToIgnore, bool bIncludeDefaults/*=false*/ )
{
bool bShouldSearch = true;
//@Package name transition
if ( Object->HasAnyFlags(RF_ClassDefaultObject) && (Object->GetOutermost()->GetFName() == NAME_CoreUObject || Object->GetOutermost()->GetFName() == GLongCoreUObjectPackageName))
{
// ignore all class default objects for classes which are declared in Core
bShouldSearch = false;
}
// Check to see if we should ignore a class
for (int32 Index = 0; Index < ClassesToIgnore.Num(); Index++)
{
// Bail if we are on the ignore list
if ( Object->IsA(ClassesToIgnore[Index]) )
{
bShouldSearch = false;
break;
}
}
if ( bShouldSearch )
{
// Check to see if we should ignore it due to package
for ( int32 Index = 0; Index < PackagesToIgnore.Num(); Index++ )
{
// If this object belongs to this package, bail
if ( Object->IsInPackage(PackagesToIgnore[Index]) )
{
bShouldSearch = false;
break;
}
}
}
if ( bShouldSearch && !bIncludeDefaults && Object->IsTemplate() )
{
// if this object is an archetype and we don't want to see assets referenced by defaults, don't include this object
bShouldSearch = false;
}
return bShouldSearch;
}
/**
* Returns a list of all assets referenced by the specified UObject.
*/
void FFindReferencedAssets::BuildAssetList(UObject *Object, const TArray<UClass*>& IgnoreClasses, const TArray<UPackage*>& IgnorePackages, TSet<UObject*>& ReferencedAssets, bool bIncludeDefaultRefs, bool bOnlyDirectReferences)
{
TArray<FReferencedAssets> LocalReferencers;
// Create a new entry for this actor.
LocalReferencers.Emplace( Object );
for (FThreadSafeObjectIterator It; It; ++It)
{
// Skip the level, world, and any packages that should be ignored
if ( ShouldSearchForAssets(*It, IgnoreClasses, IgnorePackages, bIncludeDefaultRefs) )
{
It->Mark(OBJECTMARK_TagExp);
}
else
{
It->UnMark(OBJECTMARK_TagExp);
}
}
// Add to the list of referenced assets.
const int32 MaxRecursionDepth = bOnlyDirectReferences ? 1 : 0;
FFindAssetsArchive( Object, LocalReferencers.Last().AssetList, NULL, MaxRecursionDepth, /*bIncludeClasses=*/true, bIncludeDefaultRefs );
ReferencedAssets = LocalReferencers.Last().AssetList;
}
/**
* Functor that starts the serialization process
*
* @param Search the object to start searching
* @param IgnoreClasses the list of classes to skip
* @param IgnorePackages the list of packages to skip
*/
FFindAssetsArchive::FFindAssetsArchive(
UObject* Search,
TSet<UObject*>& OutAssetList,
ObjectReferenceGraph* ReferenceGraph/*=NULL*/,
int32 MaxRecursion/*=0*/,
bool bIncludeClasses/*=true*/,
bool bIncludeDefaults/*=false*/,
bool bReverseReferenceGraph/*=false*/ )
: StartObject(Search)
, AssetList(OutAssetList)
, CurrentReferenceGraph(ReferenceGraph)
, bIncludeScriptRefs(bIncludeClasses)
, bIncludeDefaultRefs(bIncludeDefaults)
, MaxRecursionDepth(MaxRecursion)
, CurrentDepth(0)
, bUseReverseReferenceGraph(bReverseReferenceGraph)
{
ArIsObjectReferenceCollector = true;
ArIsModifyingWeakAndStrongReferences = true; // While we are not modifying them, we want to follow weak references as well
ArIgnoreClassRef = !bIncludeScriptRefs;
CurrentObject = StartObject;
*this << StartObject;
}
/**
* Adds the object reference to the asset list if it supports thumbnails.
* Recursively searches through its references for more assets
*
* @param Obj the object to inspect
*/
FArchive& FFindAssetsArchive::operator<<(UObject*& Obj)
{
// Don't check null references or objects already visited
if (Obj != NULL &&
// if we wish to filter out assets referenced through script, we need to ignore
// all class objects, not just the UObject::Class reference
(!ArIgnoreClassRef || (Cast<UClass>(Obj) == NULL)))
{
bool bUnvisited = Obj->HasAnyMarks(OBJECTMARK_TagExp);
// Clear the search flag so we don't revisit objects
Obj->UnMark(OBJECTMARK_TagExp);
if ( Obj->IsA(UField::StaticClass()) )
{
// skip all of the other stuff because the serialization of UFields will quickly overflow
// our stack given the number of temporary variables we create in the below code
if (bUnvisited)
{
Obj->Serialize(*this);
}
}
else
{
bool bRecurse = true;
const bool bCDO = Obj->HasAnyFlags(RF_ClassDefaultObject);
const bool bIsContent = Obj->IsAsset();
const bool bIncludeAnyway = (Obj->GetOuter() == CurrentObject) && (Cast<UClass>(CurrentObject) == NULL);
const bool bShouldReportAsset = !bCDO && (bIsContent || bIncludeAnyway);
// remember which object we were serializing
UObject* PreviousObject = CurrentObject;
if ( bShouldReportAsset )
{
CurrentObject = Obj;
// Add this object to the list to display
AssetList.Add(CurrentObject);
if ( CurrentReferenceGraph != NULL )
{
UObject* Key = bUseReverseReferenceGraph? CurrentObject: PreviousObject;
UObject* Value = bUseReverseReferenceGraph? PreviousObject: CurrentObject;
auto* CurrentObjectAssets = GetAssetList(Key);
check(CurrentObjectAssets);
// add this object to the list of objects referenced by the object currently being serialized
CurrentObjectAssets->Add(Value);
if (bUnvisited)
{
HandleReferencedObject(CurrentObject);
}
}
}
else if ( Obj == StartObject )
{
if (bUnvisited)
{
HandleReferencedObject(Obj);
}
}
else if (Obj->GetOuter() && PreviousObject != Obj->GetOuter() && Obj->GetOuter()->HasAnyMarks(OBJECTMARK_TagExp))
{
Obj->Mark(OBJECTMARK_TagExp);
bRecurse = false;
}
if ( bRecurse && ( MaxRecursionDepth == 0 || CurrentDepth < MaxRecursionDepth ) )
{
CurrentDepth++;
CurrentObject = Obj;
// Now recursively search this object for more references
if (bUnvisited)
{
Obj->Serialize(*this);
}
CurrentDepth--;
}
// restore the previous object that was being serialized
CurrentObject = PreviousObject;
}
}
return *this;
}
/**
* Manually serializes the class and archetype for the specified object so that assets which are referenced
* through the object's class/archetype can be differentiated.
*/
void FFindAssetsArchive::HandleReferencedObject(UObject* Obj )
{
if ( CurrentReferenceGraph != NULL )
{
// here we allow recursion if the current depth is less-than-equal (as opposed to less-than) because the archetype and class are treated as transparent objects
// serialization of the class and object are controlled by the "show class refs" and "show default refs" buttons
if ( MaxRecursionDepth == 0 || CurrentDepth < MaxRecursionDepth )
{
// now change the current reference list to the one for this object
if ( bIncludeDefaultRefs == true )
{
UObject* ObjectArc = Obj->GetArchetype();
UObject* Key = bUseReverseReferenceGraph? ObjectArc: Obj;
UObject* Value = bUseReverseReferenceGraph? Obj: ObjectArc;
auto* ReferencedAssets = GetAssetList(Key);
// @see the comment for the bIncludeScriptRefs block
ReferencedAssets->Add(Value);
UObject* PreviousObject = CurrentObject;
CurrentObject = ObjectArc;
if ( ObjectArc->HasAnyMarks(OBJECTMARK_TagExp) )
{
// temporarily disable serialization of the class, as we need to specially handle that as well
bool bSkipClassSerialization = ArIgnoreClassRef;
ArIgnoreClassRef = true;
ObjectArc->UnMark(OBJECTMARK_TagExp);
ObjectArc->Serialize(*this);
ArIgnoreClassRef = bSkipClassSerialization;
}
CurrentObject = PreviousObject;
}
if ( bIncludeScriptRefs == true )
{
UClass* ObjectClass = Obj->GetClass();
UObject* Key = bUseReverseReferenceGraph? ObjectClass: Obj;
UObject* Value = bUseReverseReferenceGraph? Obj: ObjectClass;
auto* ReferencedAssets = GetAssetList(Key);
// we want to see assets referenced by this object's class, but classes don't have associated thumbnail rendering info
// so we'll need to serialize the class manually in order to get the object references encountered through the class to fal
// under the appropriate tree item
// serializing the class will result in serializing the class default object; but we need to do this manually (for the same reason
// that we do it for the class), so temporarily prevent the CDO from being serialized by this archive
ReferencedAssets->Add(Value);
UObject* PreviousObject = CurrentObject;
CurrentObject = ObjectClass;
if ( ObjectClass->HasAnyMarks(OBJECTMARK_TagExp) )
{
ObjectClass->UnMark(OBJECTMARK_TagExp);
ObjectClass->Serialize(*this);
}
CurrentObject = PreviousObject;
}
}
}
}
/**
* Retrieves the referenced assets list for the specified object.
*/
TSet<TObjectPtr<UObject>>* FFindAssetsArchive::GetAssetList( UObject* Referencer )
{
check(Referencer);
auto* ReferencedAssetList = CurrentReferenceGraph->Find(Referencer);
if ( ReferencedAssetList == NULL )
{
// add a new entry for the specified object
ReferencedAssetList = &CurrentReferenceGraph->Add(Referencer, TSet<TObjectPtr<UObject>>{});
}
return ReferencedAssetList;
}