// 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& 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& Params, UWorld* World) { // Get the list of loaded assets TArray AssetPaths; for (TObjectIterator It; It; ++It) { UObject* Obj = *It; if (Obj->IsAsset()) { AssetPaths.Add(Obj->GetPathName()); } else if (UBlueprintGeneratedClass* Class = Cast(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 ObjectList, UClass* CommonClass, const TSet& PropertiesToIgnore, bool bLogAllMatchedDefault=false) { check(CommonClass); UObject* CommonClassCDO = CommonClass->GetDefaultObject(); UE_LOG(LogLyra, Log, TEXT(" Field\tDifferentToBase\tNumValues\tValues")); for (TFieldIterator 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 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")); } } } //////////////////////////////////////////////////////////////////////////