161 lines
5.2 KiB
C++
161 lines
5.2 KiB
C++
|
// Copyright Epic Games, Inc. All Rights Reserved.
|
||
|
|
||
|
#include "CoreMinimal.h"
|
||
|
#include "Misc/CoreMisc.h"
|
||
|
#include "Misc/Guid.h"
|
||
|
#include "Misc/CommandLine.h"
|
||
|
#include "UObject/UObjectIterator.h"
|
||
|
#include "HAL/FileManager.h"
|
||
|
#include "Misc/FileHelper.h"
|
||
|
#include "Misc/Paths.h"
|
||
|
#include "Engine/World.h"
|
||
|
#include "HAL/IConsoleManager.h"
|
||
|
|
||
|
#include "LyraLogChannels.h"
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
#if ALLOW_DEBUG_FILES
|
||
|
|
||
|
// Writes a collection of the specified name containing a list of items (returning the absolute file path to the collection)
|
||
|
// This is manual rather than relying on the collection manager so it can be used at runtime without depending on a developer module
|
||
|
FString WriteCollectionFile(const FString& CollectionName, const TArray<FString>& Items)
|
||
|
{
|
||
|
// If in the editor, create it in the directory that CST_Local would have used, otherwise write it to the profiling dir for later harvesting
|
||
|
const FString OutputDir = WITH_EDITOR ? (FPaths::ProjectSavedDir() / TEXT("Collections")) : (FPaths::ProfilingDir() / TEXT("AssetSnapshots"));
|
||
|
|
||
|
IFileManager::Get().MakeDirectory(*OutputDir, true);
|
||
|
|
||
|
const FString LogFilename = OutputDir / (CollectionName + TEXT(".collection"));
|
||
|
|
||
|
if (FArchive* OutputFile = IFileManager::Get().CreateDebugFileWriter(*LogFilename))
|
||
|
{
|
||
|
const FGuid CollectionGUID = FGuid::NewGuid();
|
||
|
|
||
|
OutputFile->Logf(TEXT("FileVersion:2"));
|
||
|
OutputFile->Logf(TEXT("Type:Static"));
|
||
|
OutputFile->Logf(TEXT("Guid:%s"), *CollectionGUID.ToString(EGuidFormats::DigitsWithHyphens));
|
||
|
OutputFile->Logf(TEXT(""));
|
||
|
|
||
|
for (const FString& Item : Items)
|
||
|
{
|
||
|
OutputFile->Logf(TEXT("%s"), *Item);
|
||
|
}
|
||
|
|
||
|
// Flush, close and delete.
|
||
|
delete OutputFile;
|
||
|
|
||
|
const FString AbsolutePath = IFileManager::Get().ConvertToAbsolutePathForExternalAppForRead(*LogFilename);
|
||
|
return AbsolutePath;
|
||
|
}
|
||
|
|
||
|
return FString();
|
||
|
}
|
||
|
|
||
|
FAutoConsoleCommandWithWorldAndArgs GObjListToCollectionCmd(
|
||
|
TEXT("Lyra.ObjListToCollection"),
|
||
|
TEXT("Spits out a collection that contains the current object list"),
|
||
|
FConsoleCommandWithWorldAndArgsDelegate::CreateStatic(
|
||
|
[](const TArray<FString>& Params, UWorld* World)
|
||
|
{
|
||
|
// Get the list of loaded assets
|
||
|
TArray<FString> AssetPaths;
|
||
|
for (TObjectIterator<UObject> It; It; ++It)
|
||
|
{
|
||
|
UObject* Obj = *It;
|
||
|
if (Obj->IsAsset())
|
||
|
{
|
||
|
AssetPaths.Add(Obj->GetPathName());
|
||
|
}
|
||
|
else if (UBlueprintGeneratedClass* Class = Cast<UBlueprintGeneratedClass>(Obj))
|
||
|
{
|
||
|
FString BlueprintName = Class->GetPathName();
|
||
|
BlueprintName.RemoveFromEnd(TEXT("_C"));
|
||
|
AssetPaths.Add(BlueprintName);
|
||
|
}
|
||
|
}
|
||
|
AssetPaths.Sort();
|
||
|
|
||
|
// Determine the filename
|
||
|
FString CollectionNameSuffix;
|
||
|
if (Params.Num() > 0)
|
||
|
{
|
||
|
CollectionNameSuffix = TEXT("_") + Params[0];
|
||
|
}
|
||
|
const FString CollectionName = FString::Printf(TEXT("_LoadedAssets_%s_%s%s"), *FDateTime::Now().ToString(TEXT("%H%M%S")), *GWorld->GetMapName(), *CollectionNameSuffix);
|
||
|
|
||
|
// Write the collection out
|
||
|
const FString CollectionFilePath = WriteCollectionFile(CollectionName, AssetPaths);
|
||
|
UE_LOG(LogLyra, Warning, TEXT("Wrote collection of loaded assets to %s"), *CollectionFilePath);
|
||
|
}));
|
||
|
|
||
|
#endif
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|
||
|
// This can be used in a command to compare assets to a parent class (BP or C++ default) to determine if any fields are actually 'fixed' and can be removed to save memory
|
||
|
void AnalyzeObjectListForDifferences(TArrayView<UObject*> ObjectList, UClass* CommonClass, const TSet<FName>& PropertiesToIgnore, bool bLogAllMatchedDefault=false)
|
||
|
{
|
||
|
check(CommonClass);
|
||
|
UObject* CommonClassCDO = CommonClass->GetDefaultObject();
|
||
|
|
||
|
UE_LOG(LogLyra, Log, TEXT(" Field\tDifferentToBase\tNumValues\tValues"));
|
||
|
|
||
|
for (TFieldIterator<FProperty> PropIt(CommonClass); PropIt; ++PropIt)
|
||
|
{
|
||
|
FProperty* Prop = *PropIt;
|
||
|
if (PropertiesToIgnore.Contains(Prop->GetFName()))
|
||
|
{
|
||
|
continue;
|
||
|
}
|
||
|
|
||
|
//@TODO: Handle fixed length arrays
|
||
|
ensure(Prop->ArrayDim <= 1);
|
||
|
|
||
|
FString DefaultValueStr;
|
||
|
Prop->ExportText_InContainer(0, /*out*/ DefaultValueStr, CommonClassCDO, CommonClassCDO, nullptr, 0);
|
||
|
|
||
|
bool bAnyMatchedDefaultValue = false;
|
||
|
bool bAllMatchedDefaultValue = true;
|
||
|
|
||
|
TSet<FString> ValuesObserved;
|
||
|
for (UObject* Object : ObjectList)
|
||
|
{
|
||
|
FString ValueStr;
|
||
|
if (Prop->ExportText_InContainer(0, /*out*/ ValueStr, Object, CommonClassCDO, nullptr, 0))
|
||
|
{
|
||
|
ValuesObserved.Add(ValueStr);
|
||
|
bAllMatchedDefaultValue = false;
|
||
|
}
|
||
|
else
|
||
|
{
|
||
|
bAnyMatchedDefaultValue = true;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (bAnyMatchedDefaultValue)
|
||
|
{
|
||
|
ValuesObserved.Add(DefaultValueStr);
|
||
|
}
|
||
|
|
||
|
if (!bAllMatchedDefaultValue)
|
||
|
{
|
||
|
const FString ValueList = FString::Join(ValuesObserved, TEXT(","));
|
||
|
|
||
|
UE_LOG(LogLyra, Log, TEXT(" %s::%s\t%s\t%d\t%s"),
|
||
|
*CommonClass->GetName(),
|
||
|
*Prop->GetName(),
|
||
|
(ValuesObserved.Num() == 1) ? TEXT("FixedDifferent") : TEXT("Varies"),
|
||
|
ValuesObserved.Num(),
|
||
|
*ValueList);
|
||
|
}
|
||
|
else if (bLogAllMatchedDefault)
|
||
|
{
|
||
|
UE_LOG(LogLyra, Log, TEXT(" %s::%s\t%s"), *CommonClass->GetName(), *Prop->GetName(), TEXT("Default"));
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
//////////////////////////////////////////////////////////////////////////
|
||
|
|