// Copyright Epic Games, Inc. All Rights Reserved. #include "MuCOE/CustomizableObjectInstanceBaker.h" #include "MuCO/CustomizableObject.h" #include "MuCO/CustomizableObjectInstance.h" #include "MuCO/CustomizableObjectInstancePrivate.h" #include "MuCOE/CustomizableObjectInstanceBakingUtils.h" void UCustomizableObjectInstanceBaker::BakeInstance(UCustomizableObjectInstance* InTargetInstance, const FBakingConfiguration& InBakingConfig, const TSharedPtr InOnBakerFinishedWork) { // Cache the configuration and the callback used to report the baking results Configuration = InBakingConfig; // Cache a delegate that we will later invoke once the baking operation has finished all the work (so we can delete this object safelly) if (InOnBakerFinishedWork) { OnWorkFinished = InOnBakerFinishedWork; } if (!InTargetInstance) { UE_LOG(LogMutable,Error,TEXT("No instance has been provided.")) FinishBakingOperation(false, nullptr); return; } // We clone the InTargetInstance to ensure that the baked it is not open in a COI editor. InstanceToBake = InTargetInstance->Clone(); if (Configuration.OutputPath.IsEmpty()) { UE_LOG(LogMutable,Error,TEXT("No target save path has been provided.")) FinishBakingOperation(false, nullptr); return; } // If no name is provided then compose one (allow not having a name) // Configuration.OutputFilesBaseName = !Configuration.OutputFilesBaseName.IsEmpty() ? Configuration.OutputFilesBaseName : InstanceToBake.GetName(); // Schedule update of the instance for baking UpdateInstance(); } void UCustomizableObjectInstanceBaker::UpdateInstance() { // Call the instance update async method FInstanceUpdateNativeDelegate UpdateDelegate; UpdateDelegate.AddUObject(this, &UCustomizableObjectInstanceBaker::OnInstanceUpdate); // Once the update finishes successfully the baking operation will engage ScheduleInstanceUpdateForBaking(*InstanceToBake, UpdateDelegate); } void UCustomizableObjectInstanceBaker::OnInstanceUpdate(const FUpdateContext& Result) const { const EUpdateResult InstanceUpdateResult = Result.UpdateResult; UE_LOG(LogMutable, Display, TEXT("Instance finished update with state : %s."), *UEnum::GetValueAsString(InstanceUpdateResult)); if (InstanceToBake->GetPrivate()->SkeletalMeshStatus == ESkeletalMeshStatus::Success) { Bake(); } else { // Report a failed baking operation. UE_LOG(LogMutable, Display, TEXT("Instance updating for baking failed. Aborting instance baking operation.")); FinishBakingOperation(false, nullptr); } } void UCustomizableObjectInstanceBaker::Bake() const { // Set this flag so we ensure that the systems that make use of it know that should not display interaction messages to the user const bool GIsRunningUnattendedScript_Cached = GIsRunningUnattendedScript; GIsRunningUnattendedScript = true; // Array with all the packages that did get saved. Packages that failed the saving procedure will not appear in this array. TMap SavedPackages; // Ensures no interaction from the user will be required (alongside GIsRunningUnattendedScript) constexpr bool bIsUnhandledExecution = true; const bool bBakeWasSuccessful = BakeCustomizableObjectInstance(*InstanceToBake, Configuration, bIsUnhandledExecution, SavedPackages); // Generate the array of structs to output based on the packages reported as saved TArray SavedPackagesData; { // Display a list with the paths of the assets saved UE_LOG(LogMutable, Log, TEXT("Saved assets (%d) :"), SavedPackages.Num()); SavedPackagesData.Reserve(SavedPackages.Num()); for (const TPair& SavedPackagePair : SavedPackages) { FBakedResourceData BakeData; BakeData.AssetPath = SavedPackagePair.Key->GetPathName(); BakeData.SaveType = SavedPackagePair.Value; // Sanity checks check(BakeData.SaveType != EPackageSaveResolutionType::None); check(!BakeData.AssetPath.IsEmpty()); // Log the path for the caller to know UE_LOG(LogMutable, Log, TEXT("\t%s --- %s"), *UEnum::GetValueAsString(BakeData.SaveType), *BakeData.AssetPath); SavedPackagesData.Push(BakeData); } } // Report that the baking operation has been completed UE_LOG(LogMutable, Display, TEXT("Finishing COI baking procedure.")); FinishBakingOperation(bBakeWasSuccessful, &SavedPackagesData); // Reset the GIsRunningUnattendedScript flag GIsRunningUnattendedScript = GIsRunningUnattendedScript_Cached; } void UCustomizableObjectInstanceBaker::FinishBakingOperation(const bool bBakeWasSuccessful, TArray* InSavedPackagesData) const { // Report that the baking operation has been completed if (Configuration.OnBakeOperationCompletedCallback.IsBound()) { FCustomizableObjectInstanceBakeOutput Output; Output.bWasBakeSuccessful = bBakeWasSuccessful; if (InSavedPackagesData && !InSavedPackagesData->IsEmpty()) { Output.SavedPackages = MoveTemp(*InSavedPackagesData); } Configuration.OnBakeOperationCompletedCallback.Execute(Output); } // Notify whatever caller that the baker completed it's work and therefore is safe to destroy/discard if (OnWorkFinished && OnWorkFinished->IsBound()) { OnWorkFinished->Execute(); } }