Files
UnrealEngine/Engine/Plugins/Mutable/Source/CustomizableObjectEditor/Private/MuCOE/CustomizableObjectCompileRunnable.cpp
Brandyn / Techy fcc1b09210 init
2026-04-04 15:40:51 -05:00

778 lines
26 KiB
C++

// Copyright Epic Games, Inc. All Rights Reserved.
#include "MuCOE/CustomizableObjectCompileRunnable.h"
#include "CustomizableObjectCompiler.h"
#include "MuCOE/GenerateMutableSource/GenerateMutableSource.h"
#include "HAL/FileManager.h"
#include "MuCO/UnrealMutableModelDiskStreamer.h"
#include "MuCO/UnrealToMutableTextureConversionUtils.h"
#include "MuCO/CustomizableObject.h"
#include "MuCO/CustomizableObjectSystem.h"
#include "MuCO/CustomizableObjectSystemPrivate.h"
#include "MuCOE/CompileRequest.h"
#include "MuR/Model.h"
#include "MuT/Compiler.h"
#include "MuT/ErrorLog.h"
#include "MuT/UnrealPixelFormatOverride.h"
#include "Serialization/MemoryWriter.h"
#include "Async/Async.h"
#include "Containers/Ticker.h"
#include "Serialization/ObjectAndNameAsStringProxyArchive.h"
#include "Trace/Trace.inl"
#include "UObject/StrongObjectPtr.h"
#include "UObject/SoftObjectPtr.h"
#include "UObject/ICookInfo.h"
#include "Engine/AssetUserData.h"
#include "Engine/SkeletalMesh.h"
#include "Animation/AnimInstance.h"
#include "DerivedDataCacheInterface.h"
#include "DerivedDataCache.h"
#include "DerivedDataRequestOwner.h"
#include "Interfaces/ITargetPlatform.h"
#define LOCTEXT_NAMESPACE "CustomizableObjectEditor"
#define UE_MUTABLE_CORE_REGION TEXT("Mutable Core")
TAutoConsoleVariable<bool> CVarMutableCompilerDiskCache(
TEXT("mutable.ForceCompilerDiskCache"),
false,
TEXT("Force the use of disk cache to reduce memory usage when compiling CustomizableObjects both in editor and cook commandlets."),
ECVF_Default);
TAutoConsoleVariable<bool> CVarMutableCompilerFastCompression(
TEXT("mutable.ForceFastTextureCompression"),
false,
TEXT("Force the use of lower quality but faster compression during cook."),
ECVF_Default);
FCustomizableObjectCompileRunnable::FCustomizableObjectCompileRunnable(UE::Mutable::Private::Ptr<UE::Mutable::Private::Node> Root, const TSharedRef<FCustomizableObjectCompiler>& InCompiler)
: MutableRoot(Root)
, WeakCompiler(InCompiler)
, bThreadCompleted(false)
{
PrepareUnrealCompression();
}
TSharedPtr<UE::Mutable::Private::FImage> FCustomizableObjectCompileRunnable::LoadImageResourceReferenced(int32 ID)
{
MUTABLE_CPUPROFILER_SCOPE(LoadResourceReferenced);
TSharedPtr<UE::Mutable::Private::FImage> Image;
if (!ReferencedTextures.IsValidIndex(ID))
{
// The id is not valid for this CO
check(false);
return Image;
}
// Find the texture id
FMutableSourceTextureData& TextureData = ReferencedTextures[ID];
// In the editor the src data can be directly accessed
Image = MakeShared<UE::Mutable::Private::FImage>();
int32 MipmapsToSkip = 0;
EUnrealToMutableConversionError Error = ConvertTextureUnrealSourceToMutable(Image.Get(), TextureData, MipmapsToSkip);
if (Error != EUnrealToMutableConversionError::Success)
{
// This could happen in the editor, because some source textures may have changed while there was a background compilation.
// We just show a warning and move on. This cannot happen during cooks, so it is fine.
UE_LOG(LogMutable, Warning, TEXT("Failed to load some source texture data for texture ID [%d]. Some textures may be corrupted."), ID);
}
return Image;
}
uint32 FCustomizableObjectCompileRunnable::Run()
{
TRACE_BEGIN_REGION(UE_MUTABLE_CORE_REGION);
UE_LOG(LogMutable, Verbose, TEXT("PROFILE: [ %16.8f ] FCustomizableObjectCompileRunnable::Run start."), FPlatformTime::Seconds());
uint32 Result = 1;
ErrorMsg = FString();
// Translate CO compile options into UE::Mutable::Private::CompilerOptions
UE::Mutable::Private::Ptr<UE::Mutable::Private::CompilerOptions> CompilerOptions = new UE::Mutable::Private::CompilerOptions();
bool bUseDiskCache = Options.bUseDiskCompilation;
if (CVarMutableCompilerDiskCache->GetBool())
{
bUseDiskCache = true;
}
CompilerOptions->SetUseDiskCache(bUseDiskCache);
if (Options.OptimizationLevel > UE_MUTABLE_MAX_OPTIMIZATION)
{
UE_LOG(LogMutable, Log, TEXT("Mutable compile optimization level out of range. Clamping to maximum."));
Options.OptimizationLevel = UE_MUTABLE_MAX_OPTIMIZATION;
}
switch (Options.OptimizationLevel)
{
case 0:
CompilerOptions->SetOptimisationEnabled(false);
CompilerOptions->SetConstReductionEnabled(false);
CompilerOptions->SetOptimisationMaxIteration(1);
break;
case 1:
CompilerOptions->SetOptimisationEnabled(false);
CompilerOptions->SetConstReductionEnabled(true);
CompilerOptions->SetOptimisationMaxIteration(1);
break;
case 2:
CompilerOptions->SetOptimisationEnabled(true);
CompilerOptions->SetConstReductionEnabled(true);
CompilerOptions->SetOptimisationMaxIteration(0);
break;
default:
CompilerOptions->SetOptimisationEnabled(true);
CompilerOptions->SetConstReductionEnabled(true);
CompilerOptions->SetOptimisationMaxIteration(0);
break;
}
// Texture compression override, if necessary
bool bUseHighQualityCompression = (Options.TextureCompression == ECustomizableObjectTextureCompression::HighQuality);
if (CVarMutableCompilerFastCompression->GetBool())
{
bUseHighQualityCompression = false;
}
if (bUseHighQualityCompression)
{
CompilerOptions->SetImagePixelFormatOverride( UnrealPixelFormatFunc );
}
CompilerOptions->SetReferencedResourceCallback(
[this](int32 ID, TSharedPtr<TSharedPtr<UE::Mutable::Private::FImage>> ResolvedImage, bool bRunImmediatlyIfPossible)
{
UE::Tasks::FTask LaunchTask = UE::Tasks::Launch(TEXT("LoadImageReferenceTasks"),
[ID,ResolvedImage,this]()
{
TSharedPtr<UE::Mutable::Private::FImage> Result = LoadImageResourceReferenced(ID);
*ResolvedImage = Result;
},
LowLevelTasks::ETaskPriority::BackgroundLow
);
return LaunchTask;
},
[this](int32 ID, const FString& MorphNameRef, TSharedPtr<TSharedPtr<UE::Mutable::Private::FMesh>> ResolvedMesh, bool bRunImmediatlyIfPossible) -> UE::Tasks::FTask
{
MUTABLE_CPUPROFILER_SCOPE(LoadMeshReferenceTasks);
ResolvedMesh->Reset();
if (!ReferencedMeshes.IsValidIndex(ID))
{
// The id is not valid for this CO
check(false);
return UE::Tasks::MakeCompletedTask<void>();
}
UE::Tasks::FTaskEvent CompletionEvent(UE_SOURCE_LOCATION);
// Find the mesh conversion data
FMutableSourceMeshData& MeshData = ReferencedMeshes[ID];
FString MorphName = MorphNameRef;
// It would be great to be able to do this conversion in a worker thread, but the engine doesn't support it yet.
auto ConvertMeshFunc = [MeshData, MorphName, ResolvedMesh, CompletionEvent, WeakCompiler = WeakCompiler, bRunImmediatlyIfPossible]() mutable
{
MUTABLE_CPUPROFILER_SCOPE(LoadMeshFunc);
check(IsInGameThread());
// If we are shutting down, we are not allowed to try to load anything.
if (IsEngineExitRequested())
{
*ResolvedMesh = MakeShared<UE::Mutable::Private::FMesh>();
CompletionEvent.Trigger();
return;
}
TSharedPtr<FCustomizableObjectCompiler> Compiler = WeakCompiler.Pin();
if (!Compiler)
{
*ResolvedMesh = MakeShared<UE::Mutable::Private::FMesh>();
CompletionEvent.Trigger();
return;
}
TSharedRef<FMeshConversionContext> MeshConversionContext = MakeShared<FMeshConversionContext>();
MeshConversionContext->Source = MeshData;
MeshConversionContext->MorphName = MorphName;
MeshConversionContext->CompilationContext = Compiler->CompilationContext;
// Convert Synchronously
if (bRunImmediatlyIfPossible)
{
MeshConversionContext->ReferencedObjects.Emplace(TStrongObjectPtr<UObject>(UE::Mutable::Private::LoadObject(MeshData.Mesh)));
MeshConversionContext->ReferencedObjects.Emplace(TStrongObjectPtr<UObject>(UE::Mutable::Private::LoadObject(MeshData.TableReferenceSkeletalMesh)));
MeshConversionContext->ReferencedObjects.Emplace(TStrongObjectPtr<UObject>(UE::Mutable::Private::LoadClass(MeshData.AnimInstance)));
UE::Tasks::FTask ConvertTask = ConvertSkeletalMeshToMutable(MeshConversionContext);
ConvertTask.Wait();
*ResolvedMesh = MeshConversionContext->Result;
CompletionEvent.Trigger();
return;
}
auto ConvertSkeletalMesh = [ResolvedMesh, MeshConversionContext, CompletionEvent]()
{
UE::Tasks::FTask ConvertTask = ConvertSkeletalMeshToMutable(MeshConversionContext);
UE::Tasks::FTask LaunchTask = UE::Tasks::Launch(TEXT("ConvertSkeletalMeshToMutable Callback"),
[ResolvedMesh, MeshConversionContext, CompletionEvent]() mutable
{
*ResolvedMesh = MeshConversionContext->Result;
CompletionEvent.Trigger();
},
ConvertTask,
UE::Tasks::ETaskPriority::Inherit);
};
// Find assets to stream
TArray<FSoftObjectPath> AssetsToStream;
auto AddReferencedStreamableRenderAsset = [MeshConversionContext, &AssetsToStream](TSoftObjectPtr<UStreamableRenderAsset>& SoftObject)
{
if (UObject* Object = SoftObject.Get())
{
MeshConversionContext->ReferencedObjects.Add(TStrongObjectPtr<UObject>(Object));
}
else if (!SoftObject.IsNull())
{
AssetsToStream.Add(SoftObject.ToSoftObjectPath());
}
};
AddReferencedStreamableRenderAsset(MeshData.Mesh);
AddReferencedStreamableRenderAsset(MeshData.TableReferenceSkeletalMesh);
if (UObject* Object = MeshData.AnimInstance.Get())
{
MeshConversionContext->ReferencedObjects.Add(TStrongObjectPtr<UObject>(Object));
}
else if (!MeshData.AnimInstance.IsNull())
{
AssetsToStream.Add(MeshData.AnimInstance.ToSoftObjectPath());
}
if (AssetsToStream.IsEmpty())
{
ConvertSkeletalMesh();
}
else
{
UCustomizableObjectSystem* System = UCustomizableObjectSystem::GetInstanceChecked();
const TSharedRef<FMutableStreamableManager>& Streamable = System->GetPrivate()->StreamableManager;
auto OnAssetLoadCallback = [MeshConversionContext, ConvertSkeletalMesh]()
{
MeshConversionContext->ReferencedObjects.Emplace(TStrongObjectPtr<UObject>(MeshConversionContext->Source.Mesh.Get()));
MeshConversionContext->ReferencedObjects.Emplace(TStrongObjectPtr<UObject>(MeshConversionContext->Source.TableReferenceSkeletalMesh.Get()));
MeshConversionContext->ReferencedObjects.Emplace(TStrongObjectPtr<UObject>(MeshConversionContext->Source.AnimInstance.Get()));
ConvertSkeletalMesh();
};
if (IsRunningCookCommandlet())
{
Streamable->RequestSyncLoad(AssetsToStream);
OnAssetLoadCallback();
}
else
{
Streamable->RequestAsyncLoad(AssetsToStream, OnAssetLoadCallback, true);
}
}
};
if (IsInGameThread())
{
ConvertMeshFunc();
}
else
{
check(!bRunImmediatlyIfPossible);
if (TSharedPtr<FCustomizableObjectCompiler> Compiler = WeakCompiler.Pin())
{
Compiler->AddGameThreadCompileTask(MoveTemp(ConvertMeshFunc));
}
else
{
*ResolvedMesh = MakeShared<UE::Mutable::Private::FMesh>();
CompletionEvent.Trigger();
}
}
return CompletionEvent;
}
);
const int32 MinResidentMips = UTexture::GetStaticMinTextureResidentMipCount();
CompilerOptions->SetDataPackingStrategy( MinResidentMips, Options.EmbeddedDataBytesLimit, Options.PackagedDataBytesLimit );
// We always compile for progressive image generation.
CompilerOptions->SetEnableProgressiveImages(true);
CompilerOptions->SetImageTiling(Options.ImageTiling);
// On server builds we don't want the images to be generted.
if (Options.TargetPlatform && Options.TargetPlatform->IsServerOnly())
{
CompilerOptions->SetDisableImageGeneration(true);
}
TFunction<void()> WaitCallback = [WeakCompiler=WeakCompiler]()
{
if (IsInGameThread())
{
TSharedPtr<FCustomizableObjectCompiler> Compiler = WeakCompiler.Pin();
if (Compiler)
{
Compiler->Tick();
}
}
};
UE::Mutable::Private::Ptr<UE::Mutable::Private::Compiler> Compiler = new UE::Mutable::Private::Compiler(CompilerOptions, WaitCallback);
UE_LOG(LogMutable, Verbose, TEXT("PROFILE: [ %16.8f ] FCustomizableObjectCompileRunnable Compile start."), FPlatformTime::Seconds());
Model = Compiler->Compile(MutableRoot);
// Dump all the log messages from the compiler
TSharedPtr<const UE::Mutable::Private::FErrorLog> pLog = Compiler->GetLog();
for (int32 i = 0; i < pLog->GetMessageCount(); ++i)
{
const FString& Message = pLog->GetMessageText(i);
const UE::Mutable::Private::ErrorLogMessageType MessageType = pLog->GetMessageType(i);
const UE::Mutable::Private::ErrorLogMessageAttachedDataView MessageAttachedData = pLog->GetMessageAttachedData(i);
if (MessageType == UE::Mutable::Private::ELMT_WARNING || MessageType == UE::Mutable::Private::ELMT_ERROR)
{
const EMessageSeverity::Type Severity = MessageType == UE::Mutable::Private::ELMT_WARNING ? EMessageSeverity::Warning : EMessageSeverity::Error;
const ELoggerSpamBin SpamBin = [&] {
switch (pLog->GetMessageSpamBin(i)) {
case UE::Mutable::Private::ErrorLogMessageSpamBin::ELMSB_UNKNOWN_TAG:
return ELoggerSpamBin::TagsNotFound;
case UE::Mutable::Private::ErrorLogMessageSpamBin::ELMSB_ALL:
default:
return ELoggerSpamBin::ShowAll;
}
}();
if (MessageAttachedData.UnassignedUVs && MessageAttachedData.UnassignedUVsSize > 0)
{
TSharedPtr<FErrorAttachedData> ErrorAttachedData = MakeShared<FErrorAttachedData>();
ErrorAttachedData->UnassignedUVs.Reset();
ErrorAttachedData->UnassignedUVs.Append(MessageAttachedData.UnassignedUVs, MessageAttachedData.UnassignedUVsSize);
const UObject* Context = static_cast<const UObject*>(pLog->GetMessageContext(i));
ArrayErrors.Add(FError(Severity, FText::AsCultureInvariant(Message), ErrorAttachedData, Context, SpamBin));
}
else
{
// TODO: Review, and probably propagate the UObject type into the runtime.
const UObject* Context = static_cast<const UObject*>(pLog->GetMessageContext(i));
const UObject* Context2 = static_cast<const UObject*>(pLog->GetMessageContext2(i));
ArrayErrors.Add(FError(Severity, FText::AsCultureInvariant(Message), Context, Context2, SpamBin));
}
}
}
Compiler = nullptr;
bThreadCompleted = true;
UE_LOG(LogMutable, Verbose, TEXT("PROFILE: [ %16.8f ] FCustomizableObjectCompileRunnable::Run end."), FPlatformTime::Seconds());
CompilerOptions->LogStats();
TRACE_END_REGION(UE_MUTABLE_CORE_REGION);
return Result;
}
bool FCustomizableObjectCompileRunnable::IsCompleted() const
{
return bThreadCompleted;
}
const TArray<FCustomizableObjectCompileRunnable::FError>& FCustomizableObjectCompileRunnable::GetArrayErrors() const
{
return ArrayErrors;
}
FCustomizableObjectSaveDDRunnable::FCustomizableObjectSaveDDRunnable(const TSharedPtr<FCompilationRequest>& InRequest,
TSharedPtr<UE::Mutable::Private::FMutableCachedPlatformData>& InPlatformData)
{
MUTABLE_CPUPROFILER_SCOPE(FCustomizableObjectSaveDDRunnable::FCustomizableObjectSaveDDRunnable);
PlatformData = InPlatformData;
Options = InRequest->Options;
DDCKey = InRequest->GetDerivedDataCacheKey();
DefaultDDCPolicy = InRequest->GetDerivedDataCachePolicy();
UCustomizableObject* CustomizableObject = InRequest->GetCustomizableObject();
CustomizableObjectName = GetNameSafe(CustomizableObject);
CustomizableObjectId = GenerateDataDistributionIdentifier(*CustomizableObject);
CustomizableObjectHeader.InternalVersion = GetECustomizableObjectVersionEnumHash();
CustomizableObjectHeader.VersionId = CustomizableObject->GetPrivate()->GetVersionId();
// Cache ModelResources
{
FMemoryWriter64 MemoryWriter(ModelResourcesData);
FObjectAndNameAsStringProxyArchive ObjectWriter(MemoryWriter, true);
PlatformData->ModelResources->Serialize(ObjectWriter);
}
if (!Options.bIsCooking)
{
// We will be saving all compilation data in two separate files, write CO Data
FullFileName = CustomizableObject->GetPrivate()->GetCompiledDataFileName(Options.TargetPlatform);
PlatformData->ModelStreamableBulkData->FullFilePath = FullFileName;
}
}
uint32 FCustomizableObjectSaveDDRunnable::Run()
{
MUTABLE_CPUPROFILER_SCOPE(FCustomizableObjectSaveDDRunnable::Run)
if (PlatformData->Model)
{
CachePlatformData();
bool bStoredSuccessfully = false;
// TODO UE-222775: Allow using DDC in editor builds, not just for cooking.
if (Options.bStoreCompiledDataInDDC && !DDCKey.Hash.IsZero())
{
StoreCachedPlatformDataInDDC(bStoredSuccessfully);
}
if (!Options.bIsCooking && !bStoredSuccessfully)
{
StoreCachedPlatformDataToDisk();
}
}
bThreadCompleted = true;
return 1;
}
bool FCustomizableObjectSaveDDRunnable::IsCompleted() const
{
return bThreadCompleted;
}
const ITargetPlatform* FCustomizableObjectSaveDDRunnable::GetTargetPlatform() const
{
return Options.TargetPlatform;
}
void FCustomizableObjectSaveDDRunnable::CachePlatformData()
{
MUTABLE_CPUPROFILER_SCOPE(CachePlatformData);
if (!PlatformData->Model || !PlatformData->ModelStreamableBulkData)
{
check(false);
return;
}
// Cache ModelStreamables
{
// Generate list of files and update streamable blocks ids and offsets
if (Options.bUseBulkData)
{
UE::Mutable::Private::GenerateBulkDataFilesListWithFileLimit(CustomizableObjectId, PlatformData->Model, *PlatformData->ModelStreamableBulkData.Get(), MAX_uint8,
Options.bIsCooking, false, *Options.TargetPlatform, PlatformData->BulkDataFiles);
}
else
{
const uint64 PackageDataBytesLimit = Options.bIsCooking ? Options.PackagedDataBytesLimit : MAX_uint64;
UE::Mutable::Private::GenerateBulkDataFilesListWithSizeLimit(PlatformData->Model, *PlatformData->ModelStreamableBulkData.Get(), Options.TargetPlatform, PackageDataBytesLimit, PlatformData->BulkDataFiles);
}
}
// Cache Model and Model Roms
{
FMemoryWriter64 ModelMemoryWriter(ModelData);
FUnrealMutableModelBulkWriterCook Streamer(&ModelMemoryWriter, &PlatformData->ModelStreamableData);
// Serialize UE::Mutable::Private::FModel and streamable resources
constexpr bool bDropData = true;
UE::Mutable::Private::FModel::Serialise(PlatformData->Model.Get(), Streamer, bDropData);
}
}
void FCustomizableObjectSaveDDRunnable::StoreCachedPlatformDataInDDC(bool& bStoredSuccessfully)
{
MUTABLE_CPUPROFILER_SCOPE(StoreCachedPlatformDataInDDC);
using namespace UE::DerivedData;
check(PlatformData->Model.Get() != nullptr);
check(DDCKey.Hash.IsZero() == false);
UE::FSharedString SharedName = UE::FSharedString(CustomizableObjectName);
bStoredSuccessfully = true;
// DDC record
FCacheRecordBuilder RecordBuilder(DDCKey);
// ModelStreamable will be modified for the DDC record. Modify a copy
FModelStreamableBulkData ModelStreamablesDDC = *PlatformData->ModelStreamableBulkData.Get();
// Store streamable resources as FValues.
{
MUTABLE_CPUPROFILER_SCOPE(SerializeBulkDataForDDC);
// Generate list of files and update streamable blocks ids and offsets
const int32 MaxDDCFiles = Options.bIsCooking ? MAX_uint8 : 1 << 13;
UE::Mutable::Private::GenerateBulkDataFilesListWithFileLimit(CustomizableObjectId, PlatformData->Model, ModelStreamablesDDC, MaxDDCFiles,
Options.bIsCooking, !Options.bIsCooking, *Options.TargetPlatform, BulkDataFilesDDC);
if (!Options.bIsCooking)
{
TArray<FCachePutValueRequest> PutValueRequests;
PutValueRequests.Reserve(BulkDataFilesDDC.Num());
const auto WriteBulkDataDDC = [&PutValueRequests, &ModelStreamablesDDC, SharedName, DefaultDDCPolicy = DefaultDDCPolicy, DDCKey = DDCKey]
(UE::Mutable::Private::FFile& File, TArray64<uint8>& FileBulkData, uint32 FileIndex)
{
FCachePutValueRequest& ValueRequest = PutValueRequests.AddDefaulted_GetRef();
ValueRequest.Name = SharedName;
ValueRequest.Policy = DefaultDDCPolicy;
ValueRequest.Value = FValue::Compress(FSharedBuffer::MakeView(FileBulkData.GetData(), FileBulkData.Num()));
ValueRequest.Key = DDCKey;
ValueRequest.Key.Hash = ValueRequest.Value.GetRawHash();
ModelStreamablesDDC.DDCValues.Add(ValueRequest.Value.GetRawHash());
};
constexpr bool bDropData = false;
UE::Mutable::Private::SerializeBulkDataFiles(*PlatformData.Get(), BulkDataFilesDDC, WriteBulkDataDDC, bDropData);
FRequestOwner RequestOwner(UE::DerivedData::EPriority::Blocking);
GetCache().PutValue(PutValueRequests, RequestOwner,
[&bStoredSuccessfully](FCachePutValueResponse&& Response)
{
if (Response.Status != EStatus::Ok)
{
bStoredSuccessfully = false;
}
});
RequestOwner.Wait();
}
else
{
const auto WriteBulkDataDDC = [&RecordBuilder](UE::Mutable::Private::FFile& File, TArray64<uint8>& FileBulkData, uint32 FileIndex)
{
const FValueId ValueId = GetDerivedDataValueIdForResource(File.DataType, FileIndex, File.ResourceType, File.Flags);
const FValue Value = FValue::Compress(FSharedBuffer::MakeView(FileBulkData.GetData(), FileBulkData.Num()));
RecordBuilder.AddValue(ValueId, Value);
};
constexpr bool bDropData = false;
UE::Mutable::Private::SerializeBulkDataFiles(*PlatformData.Get(), BulkDataFilesDDC, WriteBulkDataDDC, bDropData);
}
}
// Store streamable resources info as FValues
{
MUTABLE_CPUPROFILER_SCOPE(SerializeModelStreamables);
TArray64<uint8> ModelStreamablesBytesDDC;
FMemoryWriter64 MemoryWriterDDC(ModelStreamablesBytesDDC);
MemoryWriterDDC << ModelStreamablesDDC;
const FValue ModelStreamablesValue = FValue::Compress(FSharedBuffer::MakeView(ModelStreamablesBytesDDC.GetData(), ModelStreamablesBytesDDC.Num()));
RecordBuilder.AddValue(UE::Mutable::Private::GetDerivedDataModelStreamableBulkDataId(), ModelStreamablesValue);
}
// Store BulkData Files as a FValue to reconstruct the data later on
{
MUTABLE_CPUPROFILER_SCOPE(SerializeBulkDataFilesForDDC);
TArray<uint8> BulkDataFilesBytes;
FMemoryWriter MemoryWriter(BulkDataFilesBytes);
MemoryWriter << BulkDataFilesDDC;
const FValue BulkDataFilesValue = FValue::Compress(FSharedBuffer::MakeView(BulkDataFilesBytes.GetData(), BulkDataFilesBytes.Num()));
RecordBuilder.AddValue(UE::Mutable::Private::GetDerivedDataBulkDataFilesId(), BulkDataFilesValue);
}
// Store ModelResources bytes as a FValue
{
MUTABLE_CPUPROFILER_SCOPE(SerializeModelResourcesForDDC);
const FValue ModelResourcesValue = FValue::Compress(FSharedBuffer::MakeView(ModelResourcesData.GetData(), ModelResourcesData.Num()));
RecordBuilder.AddValue(UE::Mutable::Private::GetDerivedDataModelResourcesId(), ModelResourcesValue);
}
// Store Model bytes as a FValue
{
MUTABLE_CPUPROFILER_SCOPE(SerializeModelForDDC);
const FValue ModelValue = FValue::Compress(FSharedBuffer::MakeView(ModelData.GetData(), ModelData.Num()));
RecordBuilder.AddValue(UE::Mutable::Private::GetDerivedDataModelId(), ModelValue);
}
// Push record to the DDC
{
MUTABLE_CPUPROFILER_SCOPE(PushRecordToDDC);
if (bStoredSuccessfully)
{
FRequestOwner RequestOwner(UE::DerivedData::EPriority::Blocking);
const FCachePutRequest PutRequest = { UE::FSharedString(CustomizableObjectName), RecordBuilder.Build(), DefaultDDCPolicy };
GetCache().Put(MakeArrayView(&PutRequest, 1), RequestOwner,
[&bStoredSuccessfully](FCachePutResponse&& Response)
{
if (Response.Status != EStatus::Ok)
{
bStoredSuccessfully = false;
}
});
RequestOwner.Wait();
}
if (bStoredSuccessfully)
{
if (!Options.bIsCooking)
{
*PlatformData->ModelStreamableBulkData.Get() = ModelStreamablesDDC;
}
PlatformData->ModelStreamableBulkData->bIsStoredInDDC = true;
PlatformData->ModelStreamableBulkData->DDCKey = DDCKey;
PlatformData->ModelStreamableBulkData->DDCDefaultPolicy = DefaultDDCPolicy;
}
}
}
void FCustomizableObjectSaveDDRunnable::StoreCachedPlatformDataToDisk()
{
MUTABLE_CPUPROFILER_SCOPE(StoreCachedPlatformDataToDisk);
check(PlatformData->Model.Get() != nullptr);
check(!Options.bIsCooking);
// Create folder...
IFileManager& FileManager = IFileManager::Get();
FileManager.MakeDirectory(*GetCompiledDataFolderPath(), true);
// Delete files and create new memory writers for each streamable data type.
bool bSuccess = true;
TArray<TUniquePtr<FArchive>> MemoryWriters;
const int32 NumDataTypes = static_cast<int32>(UE::Mutable::Private::EStreamableDataType::DataTypeCount);
for (int32 DataType = 0; DataType < NumDataTypes; ++DataType)
{
const FString FilePath = FullFileName + GetDataTypeExtension(static_cast<UE::Mutable::Private::EStreamableDataType>(DataType));
if (FileManager.FileExists(*FilePath)
&& !FileManager.Delete(*FilePath, true, false, true))
{
UE_LOG(LogMutable, Error, TEXT("Failed to delete file for data type [%d]."), DataType);
bSuccess = false;
break;
}
if (FArchive* MemoryWriter = FileManager.CreateFileWriter(*FilePath))
{
*MemoryWriter << CustomizableObjectHeader;
MemoryWriters.Emplace(MemoryWriter);
}
}
if (bSuccess)
{
// Serialize Streamable resources
const auto WriteBulkDataToDisk = [&MemoryWriters](UE::Mutable::Private::FFile& File, TArray64<uint8>& FileBulkData, uint32 FileIndex)
{
const int32 DataTypeIndex = static_cast<int32>(File.DataType);
if (ensure(MemoryWriters.IsValidIndex(DataTypeIndex)))
{
MemoryWriters[DataTypeIndex]->Serialize(FileBulkData.GetData(), FileBulkData.Num() * sizeof(uint8));
}
};
// Serialize streamable resources into a single file and fix offsets
constexpr bool bDropData = true;
UE::Mutable::Private::SerializeBulkDataFiles(*PlatformData.Get(), PlatformData->BulkDataFiles, WriteBulkDataToDisk, bDropData);
// Serialize Model and ModelResources. Store after SerializeBulkDataFiles fixes the HashToStreamableFiles offsets.
if (ensure(MemoryWriters.IsValidIndex(0)))
{
MemoryWriters[0]->Serialize(ModelResourcesData.GetData(), ModelResourcesData.Num() * sizeof(uint8));
// ModelMemoryWriter (Writer to disk) doesn't handle FNames properly. Serialize them ModelStreamables in two steps.
TArray64<uint8> ModelStreamablesBytes;
FMemoryWriter64 ModelStreamablesMemoryWriter(ModelStreamablesBytes);
ModelStreamablesMemoryWriter << *PlatformData->ModelStreamableBulkData.Get();
MemoryWriters[0]->Serialize(ModelStreamablesBytes.GetData(), ModelStreamablesBytes.Num() * sizeof(uint8));
MemoryWriters[0]->Serialize(ModelData.GetData(), ModelData.Num() * sizeof(uint8));
}
// Write to disk and close the files
for (TUniquePtr<FArchive>& MemoryWriter : MemoryWriters)
{
if (MemoryWriter)
{
MemoryWriter->Flush();
if (!MemoryWriter->Close())
{
UE_LOG(LogMutable, Error, TEXT("Failed to write file to disk. File [%s]."), *MemoryWriter->GetArchiveName());
bSuccess = false;
break;
}
}
}
}
if (!bSuccess)
{
// Delete model to invalidate compilation.
PlatformData->Model.Reset();
}
}
#undef LOCTEXT_NAMESPACE