Files
UnrealEngine/Engine/Plugins/Mutable/Source/MutableValidation/Private/MuV/COIBakingTestCommandlet.cpp
2025-05-18 13:04:45 +08:00

218 lines
7.9 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuV/COIBakingTestCommandlet.h"
#include "CustomizableObjectCompilationUtility.h"
#include "ScopedLogSection.h"
#include "ValidationUtils.h"
#include "AssetRegistry/AssetData.h"
#include "HAL/FileManager.h"
#include "MuCO/CustomizableObject.h"
#include "MuCO/CustomizableObjectInstance.h"
#include "MuCO/CustomizableObjectSystem.h"
#include "MuCOE/CustomizableObjectInstanceBakingUtils.h"
#include "HAL/FileManager.h"
#include "Interfaces/ITargetPlatformManagerModule.h"
#include "MuCO/CustomizableObjectPrivate.h"
#include "MuCO/CustomizableObjectSystemPrivate.h"
#include "MuCO/LoadUtils.h"
#include "MuCOE/CustomizableObjectBenchmarkingUtils.h"
/** Flag useful to know if we are currently updating an instance or not */
bool bIsInstanceBeingUpdated = false;
/** Did the instance update finish with a successful status? */
bool bWasInstanceUpdateSuccessful = false;
void OnInstanceUpdate(const FUpdateContext& Result)
{
const EUpdateResult InstanceUpdateResult = Result.UpdateResult;
UE_LOG(LogMutable,Display,TEXT("Instance finished update with state : %s."), *UEnum::GetValueAsString(InstanceUpdateResult));
bWasInstanceUpdateSuccessful = UCustomizableObjectSystem::IsUpdateResultValid(InstanceUpdateResult);
// Clear update flag so we can exit the update while loop
bIsInstanceBeingUpdated = false;
}
int32 UCOIBakingTestCommandlet::Main(const FString& Params)
{
// Ensure we have set the mutable system to the benchmarking mode and that we are reporting benchmarking data
FLogBenchmarkUtil::SetBenchmarkReportingStateOverride(true);
UCustomizableObjectSystemPrivate::SetUsageOfBenchmarkingSettings(true);
// Ensure we do not show any OK dialog since we are not an user that can interact with them
GIsRunningUnattendedScript = true;
// Look for the COI to be baked and load it
{
FString CustomizableObjectInstanceAssetPath = "";
if (!FParse::Value(*Params, TEXT("CustomizableObjectInstance="), CustomizableObjectInstanceAssetPath))
{
UE_LOG(LogMutable,Error,TEXT("Failed to parse Customizable Object Instance package name from provided argument : %s"),*Params)
return 1;
}
// Load the resource
UObject* FoundObject = MutablePrivate::LoadObject(FSoftObjectPath(CustomizableObjectInstanceAssetPath));
if (!FoundObject)
{
UE_LOG(LogMutable,Error,TEXT("Failed to retrieve UObject from path %s"),*CustomizableObjectInstanceAssetPath);
return 1;
}
// Get the CustomizableObjectInstance.
TargetInstance = Cast<UCustomizableObjectInstance>(FoundObject);
if (!TargetInstance)
{
UE_LOG(LogMutable,Error,TEXT("Failed to cast found UObject to UCustomizableObjectInstance."));
return 1;
}
UE_LOG(LogMutable,Display,TEXT("Successfully loaded %s Customizable Object Instance!"), *TargetInstance->GetName());
}
// Perform a blocking search to ensure all assets used by mutable are reachable using the AssetRegistry
PrepareAssetRegistry();
// Make sure there is nothing else that the engine needs to do before starting our test
Wait(60);
LogGlobalSettings();
// Compile it's CO (using current config)
UCustomizableObject* InstanceCustomizableObject = TargetInstance->GetCustomizableObject();
if (!InstanceCustomizableObject)
{
UE_LOG(LogMutable,Error,TEXT("The instance %s does not have a CO to compile : Exiting commandlet."), *TargetInstance->GetName());
return 1;
}
// Set the target platform to be using for the compilation. Must not be a nullptr
FCompilationOptions CompilationOptions = InstanceCustomizableObject->GetPrivate()->GetCompileOptions();
ITargetPlatformManagerModule& TPM = GetTargetPlatformManagerRef();
CompilationOptions.TargetPlatform = TPM.GetRunningTargetPlatform();
CompilationOptions.bUseDiskCompilation = false;
TSharedRef<FCustomizableObjectCompilationUtility> CompilationUtility = MakeShared<FCustomizableObjectCompilationUtility>();
if(!CompilationUtility->CompileCustomizableObject(*InstanceCustomizableObject, true, &CompilationOptions ))
{
UE_LOG(LogMutable,Error,TEXT("Failed to compile the target CO. Exiting commandlet."));
return 1;
}
// Update the instance
{
const FScopedLogSection UpdateSection (EMutableLogSection::Update);
// If this fail something is very wrong
check(TargetInstance);
// Instance update delegate
FInstanceUpdateNativeDelegate InstanceUpdateDelegate;
InstanceUpdateDelegate.AddStatic(&OnInstanceUpdate);
bIsInstanceBeingUpdated = true;
UE_LOG(LogMutable, Display, TEXT("Scheduling instance update."));
ScheduleInstanceUpdateForBaking(*TargetInstance, InstanceUpdateDelegate);
// Now tick the engine so the instance gets updated while running in the commandlet context
while (bIsInstanceBeingUpdated)
{
// Tick the engine
CommandletHelpers::TickEngine();
// Stop if exit was requested
if (IsEngineExitRequested())
{
break;
}
}
// Check the end status of the instance update
if (!bWasInstanceUpdateSuccessful)
{
UE_LOG(LogMutable, Error, TEXT("Failed to successfully update the target COI. Exiting commandlet."));
return 1;
}
}
// Bake the instance
bool bWasBakingSuccessful = false;
{
const FScopedLogSection InstanceBakeSection (EMutableLogSection::Bake);
const FString BakedResourcesFileName = "MuBakedInstances";
// If this fail something is very wrong
check(TargetInstance);
const FString InstanceName = TargetInstance.GetName();
// Create the actual directory in the filesystem of the host machine
const FString GlobalBakingDirectory = FPaths::ConvertRelativePathToFull(FPaths::Combine( FPaths::ProjectContentDir(), BakedResourcesFileName, InstanceName));
// This will only happen if we did make a partial run before and therefore the directory was not cleansed.
// Delete it then but notify the user since this may mean that we have duplicated COIs
if (FPaths::DirectoryExists(GlobalBakingDirectory))
{
UE_LOG(LogMutable,Warning,TEXT("The directory with path \" %s \" does already exist. This may be produced by an incompelte execution of a previous test. Clearing it out before continuing..."), *GlobalBakingDirectory);
if (!IFileManager::Get().DeleteDirectory(*GlobalBakingDirectory,false,true))
{
UE_LOG(LogMutable,Error,TEXT("Failed to delete the baking directory at path \" %s \"."), *GlobalBakingDirectory);
return 1;
}
}
// Compute the local path to the generated directory where to save the baked data
const FString LocalBakingDirectory = FPaths::Combine("/","Game", BakedResourcesFileName, InstanceName);
// Create a new directory where to save the bake itself
if (IFileManager::Get().MakeDirectory(*GlobalBakingDirectory, true))
{
UE_LOG(LogMutable,Display,TEXT("Starting Instance Baking operation."));
{
FBakingConfiguration Configuration;
Configuration.OutputFilesBaseName = FString::Printf(TEXT("%s_Bake"), *TargetInstance->GetName());
Configuration.OutputPath = LocalBakingDirectory;
Configuration.bExportAllResourcesOnBake = true;
Configuration.bGenerateConstantMaterialInstancesOnBake = true;
TMap<UPackage*, EPackageSaveResolutionType> SavedPackages;
bWasBakingSuccessful = BakeCustomizableObjectInstance(
*TargetInstance,
Configuration,
true,
SavedPackages);
}
// Delete the target directory where we did save the baked instance
if (!IFileManager::Get().DeleteDirectory(*GlobalBakingDirectory,true,true))
{
UE_LOG(LogMutable,Error,TEXT("Failed to deleta baking directory at path \" %s \"."), *GlobalBakingDirectory);
return 1;
}
}
else
{
UE_LOG(LogMutable,Error,TEXT("Failed to create baking directory at path \" %s \"."), *GlobalBakingDirectory);
return 1;
}
}
if (bWasBakingSuccessful)
{
UE_LOG(LogMutable, Display, TEXT("Instance Baking operation has been completed succesfully."));
return 0;
}
else
{
UE_LOG(LogMutable, Display, TEXT("Instance Baking operation has been completed with errors."));
return 1;
}
}