// Copyright Epic Games, Inc. All Rights Reserved. #pragma once #include "Metasound.h" #include "MetasoundAssetManager.h" #include "MetasoundDocumentBuilderRegistry.h" #include "MetasoundFrontendDocumentIdGenerator.h" #include "MetasoundFrontendRegistryKey.h" #include "MetasoundGlobals.h" #include "MetasoundUObjectRegistry.h" #include "Misc/App.h" #include "Serialization/Archive.h" #if WITH_EDITORONLY_DATA #include "Algo/Transform.h" #include "MetasoundFrontendRegistryContainer.h" #include "UObject/GarbageCollection.h" #include "UObject/ObjectMacros.h" #include "UObject/StrongObjectPtrTemplates.h" #endif // WITH_EDITORONLY_DATA namespace Metasound::Engine { /** MetaSound Engine Asset helper provides routines for UObject based MetaSound assets. * Any UObject deriving from FMetaSoundAssetBase should use these helper functions * in their UObject overrides. */ struct FAssetHelper { static bool SerializationRequiresDeterminism(bool bIsCooking) { return bIsCooking || IsRunningCookCommandlet(); } #if WITH_EDITOR static void PreDuplicate(TScriptInterface MetaSound, FObjectDuplicationParameters& DupParams) { FDocumentBuilderRegistry::GetChecked().SetEventLogVerbosity(FDocumentBuilderRegistry::ELogEvent::DuplicateEntries, ELogVerbosity::NoLogging); } static void PostDuplicate(TScriptInterface MetaSound, EDuplicateMode::Type InDuplicateMode, FGuid& OutAssetClassID) { using namespace Engine; using namespace Frontend; if (InDuplicateMode == EDuplicateMode::Normal) { UObject* MetaSoundObject = MetaSound.GetObject(); check(MetaSoundObject); FDocumentBuilderRegistry& BuilderRegistry = FDocumentBuilderRegistry::GetChecked(); UMetaSoundBuilderBase& DuplicateBuilder = BuilderRegistry.FindOrBeginBuilding(*MetaSoundObject); FMetaSoundFrontendDocumentBuilder& DocBuilder = DuplicateBuilder.GetBuilder(); const FMetasoundFrontendClassName DuplicateName = DocBuilder.GetConstDocumentChecked().RootGraph.Metadata.GetClassName(); const FMetasoundFrontendClassName NewName = DocBuilder.GenerateNewClassName(); ensureAlwaysMsgf(IMetaSoundAssetManager::GetChecked().TryGetAssetIDFromClassName(NewName, OutAssetClassID), TEXT("Failed to retrieve newly duplicated MetaSoundClassName AssetID")); constexpr bool bForceUnregisterNodeClass = true; BuilderRegistry.FinishBuilding(DuplicateName, MetaSound->GetAssetPathChecked(), bForceUnregisterNodeClass); BuilderRegistry.SetEventLogVerbosity(FDocumentBuilderRegistry::ELogEvent::DuplicateEntries, ELogVerbosity::All); } } template static void PostEditUndo(TMetaSoundObject& InMetaSound) { InMetaSound.GetModifyContext().SetForceRefreshViews(); const FMetasoundFrontendClassName& ClassName = InMetaSound.GetConstDocument().RootGraph.Metadata.GetClassName(); Frontend::IDocumentBuilderRegistry::GetChecked().ReloadBuilder(ClassName); if (UMetasoundEditorGraphBase* Graph = Cast(InMetaSound.GetGraph())) { Graph->RegisterGraphWithFrontend(); } } template static void SetReferencedAssetClasses(TMetaSoundObject& InMetaSound, TSet&& InAssetClasses) { using namespace Frontend; InMetaSound.ReferencedAssetClassKeys.Reset(); InMetaSound.ReferencedAssetClassObjects.Reset(); for (const IMetaSoundAssetManager::FAssetInfo& AssetClass : InAssetClasses) { InMetaSound.ReferencedAssetClassKeys.Add(AssetClass.RegistryKey.ToString()); if (UObject* Object = AssetClass.AssetPath.TryLoad()) { InMetaSound.ReferencedAssetClassObjects.Add(Object); } else { UE_LOG(LogMetaSound, Error, TEXT("Failed to load referenced asset %s from asset %s"), *AssetClass.AssetPath.ToString(), *InMetaSound.GetPathName()); } } } #endif // WITH_EDITOR template static FTopLevelAssetPath GetAssetPathChecked(TMetaSoundObject& InMetaSound) { FTopLevelAssetPath Path; ensureAlwaysMsgf(Path.TrySetPath(&InMetaSound), TEXT("Failed to set TopLevelAssetPath from MetaSound '%s'. MetaSound must be highest level object in package."), *InMetaSound.GetPathName()); ensureAlwaysMsgf(Path.IsValid(), TEXT("Failed to set TopLevelAssetPath from MetaSound '%s'. This may be caused by calling this function when the asset is being destroyed."), *InMetaSound.GetPathName()); return Path; } template static TArray GetReferencedAssets(TMetaSoundObject& InMetaSound) { TArray ReferencedAssets; IMetasoundUObjectRegistry& UObjectRegistry = IMetasoundUObjectRegistry::Get(); for (TObjectPtr& Object : InMetaSound.ReferencedAssetClassObjects) { if (FMetasoundAssetBase* Asset = UObjectRegistry.GetObjectAsAssetBase(Object)) { ReferencedAssets.Add(Asset); } else { UE_LOG(LogMetaSound, Error, TEXT("Referenced asset \"%s\", referenced from \"%s\", is not convertible to FMetasoundAssetBase"), *Object->GetPathName(), *InMetaSound.GetPathName()); } } return ReferencedAssets; } static void PreSaveAsset(FMetasoundAssetBase& InMetaSound, FObjectPreSaveContext InSaveContext) { #if WITH_EDITORONLY_DATA using namespace Frontend; if (IMetaSoundAssetManager* AssetManager = IMetaSoundAssetManager::Get()) { AssetManager->WaitUntilAsyncLoadReferencedAssetsComplete(InMetaSound); } const bool bIsCooking = InSaveContext.IsCooking(); const bool bCanEverExecute = Metasound::CanEverExecuteGraph(bIsCooking); if (!bCanEverExecute) { const bool bIsDeterministic = SerializationRequiresDeterminism(bIsCooking); FDocumentIDGenerator::FScopeDeterminism DeterminismScope(bIsDeterministic); InMetaSound.UpdateAndRegisterForSerialization(); } else if (FApp::CanEverRenderAudio()) { if (UMetasoundEditorGraphBase* MetaSoundGraph = Cast(InMetaSound.GetGraph())) { // Uses graph flavor of register with frontend to update editor systems/asset editors in case editor is enabled. MetaSoundGraph->RegisterGraphWithFrontend(); InMetaSound.GetModifyContext().SetForceRefreshViews(); } } else { UE_LOG(LogMetaSound, Warning, TEXT("PreSaveAsset for MetaSound: (%s) is doing nothing because InSaveContext.IsCooking, IsRunningCommandlet, and FApp::CanEverRenderAudio were all false") , *InMetaSound.GetOwningAssetName()); } #endif // WITH_EDITORONLY_DATA } template static void SerializeToArchive(TMetaSoundObject& InMetaSound, FArchive& InArchive) { #if WITH_EDITORONLY_DATA using namespace Frontend; bool bVersionedAsset = false; if (InArchive.IsLoading()) { TStrongObjectPtr Builder; { FGCScopeGuard ScopeGuard; Builder.Reset(&FDocumentBuilderRegistry::GetChecked().FindOrBeginBuilding(InMetaSound)); } { const bool bIsCooking = InArchive.IsCooking(); const bool bIsDeterministic = SerializationRequiresDeterminism(bIsCooking); FDocumentIDGenerator::FScopeDeterminism DeterminismScope(bIsDeterministic); check(Builder.IsValid()); bVersionedAsset = InMetaSound.VersionAsset(Builder->GetBuilder()); } Builder->ClearInternalFlags(EInternalObjectFlags::Async); } if (bVersionedAsset) { InMetaSound.SetVersionedOnLoad(); } #endif // WITH_EDITORONLY_DATA } template static void PostLoad(TMetaSoundObject& InMetaSound) { using namespace Frontend; // Do not call asset manager on CDO objects which may be loaded before asset // manager is set. const bool bIsCDO = InMetaSound.HasAnyFlags(RF_ClassDefaultObject); if (!bIsCDO) { if (InMetaSound.GetAsyncReferencedAssetClassPaths().Num() > 0) { IMetaSoundAssetManager::GetChecked().RequestAsyncLoadReferencedAssets(InMetaSound); } } } template static void OnAsyncReferencedAssetsLoaded(TMetaSoundObject& InMetaSound, const TArray& InAsyncReferences) { for (FMetasoundAssetBase* AssetBase : InAsyncReferences) { if (AssetBase) { if (UObject* OwningAsset = AssetBase->GetOwningAsset()) { InMetaSound.ReferencedAssetClassObjects.Add(OwningAsset); InMetaSound.ReferenceAssetClassCache.Remove(FSoftObjectPath(OwningAsset)); } } } } #if WITH_EDITORONLY_DATA template static void SetMetaSoundRegistryAssetClassInfo(TMetaSoundObject& InMetaSound, const Frontend::FNodeClassInfo& InClassInfo) { using namespace Frontend; check(AssetTags::AssetClassID == GET_MEMBER_NAME_CHECKED(TMetaSoundObject, AssetClassID)); check(AssetTags::IsPreset == GET_MEMBER_NAME_CHECKED(TMetaSoundObject, bIsPreset)); check(AssetTags::RegistryInputTypes == GET_MEMBER_NAME_CHECKED(TMetaSoundObject, RegistryInputTypes)); check(AssetTags::RegistryOutputTypes == GET_MEMBER_NAME_CHECKED(TMetaSoundObject, RegistryOutputTypes)); check(AssetTags::RegistryVersionMajor == GET_MEMBER_NAME_CHECKED(TMetaSoundObject, RegistryVersionMajor)); check(AssetTags::RegistryVersionMinor == GET_MEMBER_NAME_CHECKED(TMetaSoundObject, RegistryVersionMinor)); bool bMarkDirty = InMetaSound.AssetClassID != InClassInfo.AssetClassID; bMarkDirty |= InMetaSound.RegistryVersionMajor != InClassInfo.Version.Major; bMarkDirty |= InMetaSound.RegistryVersionMinor != InClassInfo.Version.Minor; bMarkDirty |= InMetaSound.bIsPreset != InClassInfo.bIsPreset; InMetaSound.AssetClassID = InClassInfo.AssetClassID; InMetaSound.RegistryVersionMajor = InClassInfo.Version.Major; InMetaSound.RegistryVersionMinor = InClassInfo.Version.Minor; InMetaSound.bIsPreset = InClassInfo.bIsPreset; { TArray InputTypes; Algo::Transform(InClassInfo.InputTypes, InputTypes, [](const FName& Name) { return Name.ToString(); }); const FString TypeString = FString::Join(InputTypes, *AssetTags::ArrayDelim); bMarkDirty |= InMetaSound.RegistryInputTypes != TypeString; InMetaSound.RegistryInputTypes = TypeString; } { TArray OutputTypes; Algo::Transform(InClassInfo.OutputTypes, OutputTypes, [](const FName& Name) { return Name.ToString(); }); const FString TypeString = FString::Join(OutputTypes, *AssetTags::ArrayDelim); bMarkDirty |= InMetaSound.RegistryOutputTypes != TypeString; InMetaSound.RegistryOutputTypes = TypeString; } } #endif // WITH_EDITORONLY_DATA }; } // namespace Metasound::Engine