// Copyright Epic Games, Inc. All Rights Reserved. #include "MetasoundAssetSubsystem.h" #include "Algo/Sort.h" #include "Algo/Transform.h" #include "AssetRegistry/AssetRegistryModule.h" #include "Async/Async.h" #include "Engine/AssetManager.h" #include "Metasound.h" #include "MetasoundAssetBase.h" #include "MetasoundBuilderSubsystem.h" #include "MetasoundFrontendDocument.h" #include "MetasoundFrontendRegistries.h" #include "MetasoundFrontendSearchEngine.h" #include "MetasoundFrontendTransform.h" #include "MetasoundSettings.h" #include "MetasoundSource.h" #include "MetasoundTrace.h" #include "MetasoundUObjectRegistry.h" #include "Misc/CoreDelegates.h" #include "UObject/NoExportTypes.h" #include UE_INLINE_GENERATED_CPP_BY_NAME(MetasoundAssetSubsystem) namespace Metasound { namespace AssetSubsystemPrivate { bool GetAssetClassInfo(const FAssetData& InAssetData, Frontend::FNodeClassInfo& OutInfo) { using namespace Metasound; using namespace Metasound::Frontend; bool bSuccess = true; OutInfo.Type = EMetasoundFrontendClassType::External; OutInfo.AssetPath = InAssetData.AssetClassPath; FString AssetClassID; bSuccess &= InAssetData.GetTagValue(AssetTags::AssetClassID, AssetClassID); OutInfo.AssetClassID = FGuid(AssetClassID); OutInfo.ClassName = FMetasoundFrontendClassName(FName(), *AssetClassID, FName()); #if WITH_EDITORONLY_DATA InAssetData.GetTagValue(AssetTags::IsPreset, OutInfo.bIsPreset); #endif // WITH_EDITORONLY_DATA int32 RegistryVersionMajor = 0; bSuccess &= InAssetData.GetTagValue(AssetTags::RegistryVersionMajor, RegistryVersionMajor); OutInfo.Version.Major = RegistryVersionMajor; int32 RegistryVersionMinor = 0; bSuccess &= InAssetData.GetTagValue(AssetTags::RegistryVersionMinor, RegistryVersionMinor); OutInfo.Version.Minor = RegistryVersionMinor; #if WITH_EDITORONLY_DATA auto ParseTypesString = [&](const FName AssetTag, TSet& OutTypes) { FString TypesString; if (InAssetData.GetTagValue(AssetTag, TypesString)) { TArray DataTypeStrings; TypesString.ParseIntoArray(DataTypeStrings, *AssetTags::ArrayDelim); Algo::Transform(DataTypeStrings, OutTypes, [](const FString& DataType) { return *DataType; }); return true; } return false; }; // These values are optional and not necessary to return successfully as MetaSounds // don't require inputs or outputs for asset tags to be valid (ex. a new MetaSound, // non-source asset has no inputs or outputs) OutInfo.InputTypes.Reset(); ParseTypesString(AssetTags::RegistryInputTypes, OutInfo.InputTypes); OutInfo.OutputTypes.Reset(); ParseTypesString(AssetTags::RegistryOutputTypes, OutInfo.OutputTypes); #endif // WITH_EDITORONLY_DATA return bSuccess; } // Remove the Map entry only if the key and value are equal. // // This protects against scenarios where a metasound is renamed or moved // and the new entry was being erroneously removed from the PathMap bool RemoveIfExactMatch(TMap& InMap, const Frontend::FNodeRegistryKey& InKeyToRemove, const FSoftObjectPath& InPathToRemove) { if (const FSoftObjectPath* Path = InMap.Find(InKeyToRemove)) { if (*Path == InPathToRemove) { InMap.Remove(InKeyToRemove); return true; } else { UE_LOG(LogMetaSound, VeryVerbose, TEXT("Object paths do not match. Skipping removal of %s:%s from the asset subsystem."), *InKeyToRemove.ToString(), *InPathToRemove.ToString()); } } return false; } } } void UMetaSoundAssetSubsystem::Initialize(FSubsystemCollectionBase& InCollection) { using namespace Metasound::Frontend; IMetaSoundAssetManager::Set(*this); FCoreDelegates::OnPostEngineInit.AddUObject(this, &UMetaSoundAssetSubsystem::PostEngineInit); } void UMetaSoundAssetSubsystem::PostEngineInit() { check(UAssetManager::IsInitialized()); UAssetManager& AssetManager = UAssetManager::Get(); AssetManager.CallOrRegister_OnCompletedInitialScan(FSimpleMulticastDelegate::FDelegate::CreateUObject(this, &UMetaSoundAssetSubsystem::PostInitAssetScan)); RebuildDenyListCache(AssetManager); } void UMetaSoundAssetSubsystem::PostInitAssetScan() { METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(UMetaSoundAssetSubsystem::PostInitAssetScan); const UMetaSoundSettings* Settings = GetDefault(); if (ensureAlways(Settings)) { SearchAndIterateDirectoryAssets(Settings->DirectoriesToRegister, [this](const FAssetData& AssetData) { AddOrUpdateAsset(AssetData); }); } bIsInitialAssetScanComplete = true; } #if WITH_EDITORONLY_DATA void UMetaSoundAssetSubsystem::AddAssetReferences(FMetasoundAssetBase& InAssetBase) { using namespace Metasound; using namespace Metasound::Frontend; const FMetasoundFrontendDocument& Document = InAssetBase.GetDocumentChecked(); const FNodeRegistryKey AssetClassKey = FNodeRegistryKey(Document.RootGraph); if (!ContainsKey(AssetClassKey)) { AddOrUpdateAsset(*InAssetBase.GetOwningAsset()); UE_LOG(LogMetaSound, Verbose, TEXT("Adding asset '%s' to MetaSoundAsset registry."), *InAssetBase.GetOwningAssetName()); } bool bAddFromReferencedAssets = false; const TSet& ReferencedAssetClassKeys = InAssetBase.GetReferencedAssetClassKeys(); for (const FString& KeyString : ReferencedAssetClassKeys) { FNodeRegistryKey RegistryKey; const bool bIsKey = FNodeRegistryKey::Parse(KeyString, RegistryKey); if (!bIsKey || !ContainsKey(RegistryKey)) { UE_LOG(LogMetaSound, Verbose, TEXT("Missing referenced class '%s' asset entry."), *KeyString); bAddFromReferencedAssets = true; } } // All keys are loaded if (!bAddFromReferencedAssets) { return; } UE_LOG(LogMetaSound, Verbose, TEXT("Attempting preemptive reference load...")); TArray ReferencedAssets = InAssetBase.GetReferencedAssets(); for (FMetasoundAssetBase* Asset : ReferencedAssets) { if (Asset) { const FMetasoundFrontendDocument& RefDocument = Asset->GetDocumentChecked(); const FNodeRegistryKey ClassKey = FNodeRegistryKey(RefDocument.RootGraph); if (!ContainsKey(ClassKey)) { UE_LOG(LogMetaSound, Verbose, TEXT("Preemptive load of class '%s' due to early " "registration request (asset scan likely not complete)."), *ClassKey.ToString()); UObject* MetaSoundObject = Asset->GetOwningAsset(); if (ensureAlways(MetaSoundObject)) { AddOrUpdateAsset(*MetaSoundObject); } } } else { UE_LOG(LogMetaSound, Warning, TEXT("Null referenced dependent asset in %s. Resaving asset in editor may fix the issue"), *InAssetBase.GetOwningAssetName()); } } } #endif Metasound::Frontend::FNodeRegistryKey UMetaSoundAssetSubsystem::AddOrUpdateAsset(const UObject& InObject) { using namespace Metasound; using namespace Metasound::AssetSubsystemPrivate; using namespace Metasound::Frontend; METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(UMetaSoundAssetSubsystem::AddOrUpdateAsset); const FMetasoundAssetBase* MetaSoundAsset = Metasound::IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(&InObject); check(MetaSoundAsset); const FMetasoundFrontendDocument& Document = MetaSoundAsset->GetDocumentChecked(); const FNodeRegistryKey RegistryKey = FNodeRegistryKey(Document.RootGraph); if (RegistryKey.IsValid()) { PathMap.FindOrAdd(RegistryKey) = InObject.GetPathName(); } return RegistryKey; } Metasound::Frontend::FNodeRegistryKey UMetaSoundAssetSubsystem::AddOrUpdateAsset(const FAssetData& InAssetData) { using namespace Metasound; using namespace Metasound::Frontend; METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(UMetaSoundAssetSubsystem::AddOrUpdateAsset); FNodeClassInfo ClassInfo; bool bClassInfoFound = AssetSubsystemPrivate::GetAssetClassInfo(InAssetData, ClassInfo); if (!bClassInfoFound) { UObject* Object = nullptr; FSoftObjectPath Path = InAssetData.ToSoftObjectPath(); if (!FPackageName::GetPackageMountPoint(InAssetData.GetObjectPathString()).IsNone()) { if (InAssetData.IsAssetLoaded()) { Object = Path.ResolveObject(); UE_LOG(LogMetaSound, Verbose, TEXT("Adding loaded asset '%s' to MetaSoundAsset registry."), *Object->GetName()); } else { Object = Path.TryLoad(); UE_LOG(LogMetaSound, Verbose, TEXT("Loaded asset '%s' and adding to MetaSoundAsset registry."), *Object->GetName()); } } if (Object) { return AddOrUpdateAsset(*Object); } } if (ClassInfo.AssetClassID.IsValid()) { const FNodeRegistryKey RegistryKey = FNodeRegistryKey(ClassInfo); if (RegistryKey.IsValid()) { PathMap.FindOrAdd(RegistryKey) = InAssetData.GetSoftObjectPath(); } return RegistryKey; } // Invalid ClassID means the node could not be registered. // Let caller report or ensure as necessary. return FNodeRegistryKey::GetInvalid(); } bool UMetaSoundAssetSubsystem::CanAutoUpdate(const FMetasoundFrontendClassName& InClassName) const { const UMetaSoundSettings* Settings = GetDefault(); if (!Settings->bAutoUpdateEnabled) { return false; } return !AutoUpdateDenyListCache.Contains(InClassName.GetFullName()); } bool UMetaSoundAssetSubsystem::ContainsKey(const Metasound::Frontend::FNodeRegistryKey& InRegistryKey) const { return PathMap.Contains(InRegistryKey); } void UMetaSoundAssetSubsystem::RebuildDenyListCache(const UAssetManager& InAssetManager) { using namespace Metasound::Frontend; const UMetaSoundSettings* Settings = GetDefault(); if (Settings->DenyListCacheChangeID == AutoUpdateDenyListChangeID) { return; } AutoUpdateDenyListCache.Reset(); for (const FMetasoundFrontendClassName& ClassName : Settings->AutoUpdateDenylist) { AutoUpdateDenyListCache.Add(ClassName.GetFullName()); } check(UAssetManager::IsInitialized()); UAssetManager& AssetManager = UAssetManager::Get(); for (const FDefaultMetaSoundAssetAutoUpdateSettings& UpdateSettings : Settings->AutoUpdateAssetDenylist) { FAssetData AssetData; if (AssetManager.GetAssetDataForPath(UpdateSettings.MetaSound, AssetData)) { FString AssetClassID; if (AssetData.GetTagValue(AssetTags::AssetClassID, AssetClassID)) { const FMetasoundFrontendClassName ClassName = { FName(), *AssetClassID, FName() }; AutoUpdateDenyListCache.Add(ClassName.GetFullName()); } } } AutoUpdateDenyListChangeID = Settings->DenyListCacheChangeID; } #if WITH_EDITOR TSet UMetaSoundAssetSubsystem::GetReferencedAssetClasses(const FMetasoundAssetBase& InAssetBase) const { METASOUND_TRACE_CPUPROFILER_EVENT_SCOPE(UMetaSoundAssetSubsystem::GetReferencedAssetClasses); using namespace Metasound::Frontend; TSet OutAssetInfos; const FMetasoundFrontendDocument& Document = InAssetBase.GetDocumentChecked(); for (const FMetasoundFrontendClass& Class : Document.Dependencies) { const FNodeRegistryKey Key = FNodeRegistryKey(Class.Metadata); if (const FSoftObjectPath* ObjectPath = PathMap.Find(Key)) { OutAssetInfos.Add(FAssetInfo{Key, *ObjectPath}); } else { const FMetasoundFrontendRegistryContainer* Registry = FMetasoundFrontendRegistryContainer::Get(); check(Registry); const bool bIsRegistered = Registry->IsNodeRegistered(Key); bool bReportFail = false; if (bIsRegistered) { if (!Registry->IsNodeNative(Key)) { bReportFail = true; } } else { // Don't report failure if a matching class with a matching major version and higher minor version exists (it will be autoupdated) FMetasoundFrontendClass FrontendClass; const bool bDidFindClassWithName = ISearchEngine::Get().FindClassWithHighestVersion(Key.ClassName.ToNodeClassName(), FrontendClass); if (!(bDidFindClassWithName && Key.Version.Major == FrontendClass.Metadata.GetVersion().Major && Key.Version.Minor < FrontendClass.Metadata.GetVersion().Minor)) { bReportFail = true; } } if (bReportFail) { if (bIsInitialAssetScanComplete) { UE_LOG(LogMetaSound, Warning, TEXT("MetaSound Node Class with registry key '%s' not registered when gathering referenced asset classes from '%s': Retrieving all asset classes may not be comprehensive."), *Key.ToString(), *InAssetBase.GetOwningAssetName()); } else { UE_LOG(LogMetaSound, Warning, TEXT("Attempt to get registered dependent asset with key '%s' from MetaSound asset '%s' before asset scan has completed: Asset class cannot be provided"), *Key.ToString(), *InAssetBase.GetOwningAssetName()); } } } } return MoveTemp(OutAssetInfos); } #endif void UMetaSoundAssetSubsystem::RescanAutoUpdateDenyList() { check(UAssetManager::IsInitialized()); RebuildDenyListCache(UAssetManager::Get()); } FMetasoundAssetBase* UMetaSoundAssetSubsystem::TryLoadAssetFromKey(const Metasound::Frontend::FNodeRegistryKey& RegistryKey) const { if (const FSoftObjectPath* ObjectPath = FindObjectPathFromKey(RegistryKey)) { return TryLoadAsset(*ObjectPath); } return nullptr; } bool UMetaSoundAssetSubsystem::TryLoadReferencedAssets(const FMetasoundAssetBase& InAssetBase, TArray& OutReferencedAssets) const { using namespace Metasound::Frontend; bool bSucceeded = true; OutReferencedAssets.Reset(); TArray ReferencedAssets; const TSet& AssetClassKeys = InAssetBase.GetReferencedAssetClassKeys(); for (const FString& KeyString : AssetClassKeys) { FNodeRegistryKey Key; FNodeRegistryKey::Parse(KeyString, Key); if (FMetasoundAssetBase* MetaSound = TryLoadAssetFromKey(Key)) { OutReferencedAssets.Add(MetaSound); } else { UE_LOG(LogMetaSound, Error, TEXT("Failed to find referenced MetaSound asset with key '%s'"), *KeyString); bSucceeded = false; } } return bSucceeded; } void UMetaSoundAssetSubsystem::RequestAsyncLoadReferencedAssets(FMetasoundAssetBase& InAssetBase) { const TSet& AsyncReferences = InAssetBase.GetAsyncReferencedAssetClassPaths(); if (AsyncReferences.Num() > 0) { if (UObject* OwningAsset = InAssetBase.GetOwningAsset()) { TArray PathsToLoad = AsyncReferences.Array(); // Protect against duplicate calls to async load assets. if (FMetaSoundAsyncAssetDependencies* ExistingAsyncLoad = FindLoadingDependencies(OwningAsset)) { if (ExistingAsyncLoad->Dependencies == PathsToLoad) { // early out since these are already actively being loaded. return; } } int32 AsyncLoadID = AsyncLoadIDCounter++; auto AssetsLoadedDelegate = [this, AsyncLoadID]() { this->OnAssetsLoaded(AsyncLoadID); }; // Store async loading data for use when async load is complete. FMetaSoundAsyncAssetDependencies& AsyncDependencies = LoadingDependencies.AddDefaulted_GetRef(); AsyncDependencies.LoadID = AsyncLoadID; AsyncDependencies.MetaSound = OwningAsset; AsyncDependencies.Dependencies = PathsToLoad; AsyncDependencies.StreamableHandle = StreamableManager.RequestAsyncLoad(PathsToLoad, AssetsLoadedDelegate); } else { UE_LOG(LogMetaSound, Error, TEXT("Cannot load async asset as FMetasoundAssetBase null owning UObject"), *InAssetBase.GetOwningAssetName()); } } } void UMetaSoundAssetSubsystem::WaitUntilAsyncLoadReferencedAssetsComplete(FMetasoundAssetBase& InAssetBase) { TSet TransitiveReferences; TArray TransitiveReferencesQueue; TransitiveReferences.Add(&InAssetBase); TransitiveReferencesQueue.Add(&InAssetBase); while (!TransitiveReferencesQueue.IsEmpty()) { FMetasoundAssetBase* Reference = TransitiveReferencesQueue.Pop(); UObject* OwningAsset = Reference->GetOwningAsset(); if (!OwningAsset) { continue; } while (FMetaSoundAsyncAssetDependencies* LoadingDependency = FindLoadingDependencies(OwningAsset)) { // Grab shared ptr to handle as LoadingDependencies may be deleted and have it's shared pointer removed. TSharedPtr StreamableHandle = LoadingDependency->StreamableHandle; if (StreamableHandle.IsValid()) { UE_LOG(LogMetaSound, Verbose, TEXT("Waiting on async load (id: %d) from asset %s"), LoadingDependency->LoadID, *InAssetBase.GetOwningAssetName()); EAsyncPackageState::Type LoadState = StreamableHandle->WaitUntilComplete(); if (EAsyncPackageState::Complete != LoadState) { UE_LOG(LogMetaSound, Error, TEXT("Failed to complete loading of async dependent assets from parent asset %s"), *InAssetBase.GetOwningAssetName()); RemoveLoadingDependencies(LoadingDependency->LoadID); } else { // This will remove the loading dependencies from internal storage OnAssetsLoaded(LoadingDependency->LoadID); } // This will prevent OnAssetsLoaded from being called via the streamables // internal delegate complete callback. StreamableHandle->CancelHandle(); } } for (FMetasoundAssetBase* NextReference : Reference->GetReferencedAssets()) { bool bAlreadyInSet; TransitiveReferences.Add(NextReference, &bAlreadyInSet); if (!bAlreadyInSet) { TransitiveReferencesQueue.Add(NextReference); } } } } FMetaSoundAsyncAssetDependencies* UMetaSoundAssetSubsystem::FindLoadingDependencies(const UObject* InParentAsset) { auto IsEqualMetaSoundUObject = [InParentAsset](const FMetaSoundAsyncAssetDependencies& InDependencies) -> bool { return (InDependencies.MetaSound == InParentAsset); }; return LoadingDependencies.FindByPredicate(IsEqualMetaSoundUObject); } FMetaSoundAsyncAssetDependencies* UMetaSoundAssetSubsystem::FindLoadingDependencies(int32 InLoadID) { auto IsEqualID = [InLoadID](const FMetaSoundAsyncAssetDependencies& InDependencies) -> bool { return (InDependencies.LoadID == InLoadID); }; return LoadingDependencies.FindByPredicate(IsEqualID); } void UMetaSoundAssetSubsystem::RemoveLoadingDependencies(int32 InLoadID) { auto IsEqualID = [InLoadID](const FMetaSoundAsyncAssetDependencies& InDependencies) -> bool { return (InDependencies.LoadID == InLoadID); }; LoadingDependencies.RemoveAllSwap(IsEqualID); } void UMetaSoundAssetSubsystem::OnAssetsLoaded(int32 InLoadID) { FMetaSoundAsyncAssetDependencies* LoadedDependencies = FindLoadingDependencies(InLoadID); if (ensureMsgf(LoadedDependencies, TEXT("Call to async asset load complete with invalid IDs %d"), InLoadID)) { if (LoadedDependencies->StreamableHandle.IsValid()) { if (LoadedDependencies->MetaSound) { Metasound::IMetasoundUObjectRegistry& UObjectRegistry = Metasound::IMetasoundUObjectRegistry::Get(); FMetasoundAssetBase* ParentAssetBase = UObjectRegistry.GetObjectAsAssetBase(LoadedDependencies->MetaSound); if (ensureMsgf(ParentAssetBase, TEXT("UClass of Parent MetaSound asset %s is not registered in metasound UObject Registery"), *LoadedDependencies->MetaSound->GetPathName())) { // Get all async loaded assets TArray LoadedAssets; LoadedDependencies->StreamableHandle->GetLoadedAssets(LoadedAssets); // Cast UObjects to FMetaSoundAssetBase TArray LoadedAssetBases; for (UObject* AssetDependency : LoadedAssets) { if (AssetDependency) { FMetasoundAssetBase* AssetDependencyBase = UObjectRegistry.GetObjectAsAssetBase(AssetDependency); if (ensure(AssetDependencyBase)) { LoadedAssetBases.Add(AssetDependencyBase); } } } // Update parent asset with loaded assets. ParentAssetBase->OnAsyncReferencedAssetsLoaded(LoadedAssetBases); } } } // Remove from active array of loading dependencies. RemoveLoadingDependencies(InLoadID); } } const FSoftObjectPath* UMetaSoundAssetSubsystem::FindObjectPathFromKey(const Metasound::Frontend::FNodeRegistryKey& InRegistryKey) const { return PathMap.Find(InRegistryKey); } FMetasoundAssetBase* UMetaSoundAssetSubsystem::TryLoadAsset(const FSoftObjectPath& InObjectPath) const { return Metasound::IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(InObjectPath.TryLoad()); } void UMetaSoundAssetSubsystem::RemoveAsset(const UObject& InObject) { using namespace Metasound; using namespace Metasound::Frontend; if (const FMetasoundAssetBase* MetaSoundAsset = Metasound::IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(&InObject)) { const FSoftObjectPath ObjectPath(&InObject); const FMetasoundFrontendDocument& Document = MetaSoundAsset->GetDocumentChecked(); FNodeRegistryKey RegistryKey = FNodeRegistryKey(Document.RootGraph); if (AssetSubsystemPrivate::RemoveIfExactMatch(PathMap, RegistryKey, ObjectPath)) { UMetaSoundBuilderSubsystem::GetChecked().DetachBuilderFromAsset(Document.RootGraph.Metadata.GetClassName()); } } } void UMetaSoundAssetSubsystem::RemoveAsset(const FAssetData& InAssetData) { using namespace Metasound; using namespace Metasound::Frontend; FNodeClassInfo ClassInfo; if (ensureAlways(AssetSubsystemPrivate::GetAssetClassInfo(InAssetData, ClassInfo))) { const FNodeRegistryKey RegistryKey(ClassInfo); const FSoftObjectPath ObjectPath = InAssetData.GetSoftObjectPath(); if (AssetSubsystemPrivate::RemoveIfExactMatch(PathMap, RegistryKey, ObjectPath)) { UMetaSoundBuilderSubsystem::GetChecked().DetachBuilderFromAsset(ClassInfo.ClassName); } } } void UMetaSoundAssetSubsystem::RenameAsset(const FAssetData& InAssetData, bool bInReregisterWithFrontend) { auto PerformRename = [this, &InAssetData]() { RemoveAsset(InAssetData); ResetAssetClassDisplayName(InAssetData); AddOrUpdateAsset(InAssetData); }; FMetasoundAssetBase* MetaSoundAsset = Metasound::IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(InAssetData.GetAsset()); check(MetaSoundAsset); MetaSoundAsset->UnregisterGraphWithFrontend(); PerformRename(); if (bInReregisterWithFrontend) { MetaSoundAsset->RegisterGraphWithFrontend(); } } void UMetaSoundAssetSubsystem::ResetAssetClassDisplayName(const FAssetData& InAssetData) { UObject* Object = nullptr; FSoftObjectPath Path = InAssetData.GetSoftObjectPath(); if (InAssetData.IsAssetLoaded()) { Object = Path.ResolveObject(); } else { Object = Path.TryLoad(); } FMetasoundAssetBase* MetaSoundAsset = Metasound::IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(Object); check(MetaSoundAsset); FMetasoundFrontendGraphClass& Class = MetaSoundAsset->GetDocumentChecked().RootGraph; #if WITH_EDITOR Class.Metadata.SetDisplayName(FText()); #endif // WITH_EDITOR } void UMetaSoundAssetSubsystem::SearchAndIterateDirectoryAssets(const TArray& InDirectories, TFunctionRef InFunction) { if (InDirectories.IsEmpty()) { return; } UAssetManager& AssetManager = UAssetManager::Get(); FAssetManagerSearchRules Rules; for (const FDirectoryPath& Path : InDirectories) { Rules.AssetScanPaths.Add(*Path.Path); } Metasound::IMetasoundUObjectRegistry::Get().IterateRegisteredUClasses([&](UClass& RegisteredClass) { Rules.AssetBaseClass = &RegisteredClass; TArray MetaSoundAssets; AssetManager.SearchAssetRegistryPaths(MetaSoundAssets, Rules); for (const FAssetData& AssetData : MetaSoundAssets) { InFunction(AssetData); } }); } void UMetaSoundAssetSubsystem::RegisterAssetClassesInDirectories(const TArray& InDirectories) { TArray Directories; Algo::Transform(InDirectories, Directories, [](const FMetaSoundAssetDirectory& AssetDir) { return AssetDir.Directory; }); SearchAndIterateDirectoryAssets(Directories, [this](const FAssetData& AssetData) { AddOrUpdateAsset(AssetData); FMetasoundAssetBase* MetaSoundAsset = Metasound::IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(AssetData.GetAsset()); check(MetaSoundAsset); Metasound::Frontend::FMetaSoundAssetRegistrationOptions RegOptions; if (const UMetaSoundSettings* Settings = GetDefault()) { RegOptions.bAutoUpdateLogWarningOnDroppedConnection = Settings->bAutoUpdateLogWarningOnDroppedConnection; } MetaSoundAsset->RegisterGraphWithFrontend(RegOptions); }); } void UMetaSoundAssetSubsystem::UnregisterAssetClassesInDirectories(const TArray& InDirectories) { TArray Directories; Algo::Transform(InDirectories, Directories, [](const FMetaSoundAssetDirectory& AssetDir) { return AssetDir.Directory; }); SearchAndIterateDirectoryAssets(Directories, [this](const FAssetData& AssetData) { using namespace Metasound; using namespace Metasound::Frontend; if (AssetData.IsAssetLoaded()) { FMetasoundAssetBase* MetaSoundAsset = Metasound::IMetasoundUObjectRegistry::Get().GetObjectAsAssetBase(AssetData.GetAsset()); check(MetaSoundAsset); MetaSoundAsset->UnregisterGraphWithFrontend(); RemoveAsset(AssetData); } else { FNodeClassInfo AssetClassInfo; if (ensureAlways(AssetSubsystemPrivate::GetAssetClassInfo(AssetData, AssetClassInfo))) { const FNodeRegistryKey RegistryKey = FNodeRegistryKey(AssetClassInfo); const bool bIsRegistered = FMetasoundFrontendRegistryContainer::Get()->IsNodeRegistered(RegistryKey); if (bIsRegistered) { FMetasoundFrontendRegistryContainer::Get()->UnregisterNode(RegistryKey); AssetSubsystemPrivate::RemoveIfExactMatch(PathMap, RegistryKey, AssetData.GetSoftObjectPath()); } } } }); }