// Copyright Epic Games, Inc. All Rights Reserved. #include "LyraEditor.h" #include "AssetRegistry/AssetRegistryModule.h" #include "CollectionManagerTypes.h" #include "ICollectionManager.h" #include "CollectionManagerModule.h" #include "HAL/IConsoleManager.h" #include "Algo/Transform.h" ////////////////////////////////////////////////////////////////////////// FAutoConsoleCommandWithWorldArgsAndOutputDevice GDiffCollectionReferenceSupport( TEXT("Lyra.DiffCollectionReferenceSupport"), TEXT("Usage:\n") TEXT(" Lyra.DiffCollectionReferenceSupport OldCollectionName NewCollectionName [Deduplicate]\n") TEXT("\n") TEXT("It will list the assets in Old that 'support' assets introduced in New (are referencers directly/indirectly)\n") TEXT("as well as any loose unsupported assets.\n") TEXT("\n") TEXT("The optional third argument controls whether or not multi-supported assets will be de-duplicated (true) or not (false)"), FConsoleCommandWithWorldArgsAndOutputDeviceDelegate::CreateStatic( [](const TArray& Params, UWorld* World, FOutputDevice& Ar) { IAssetRegistry& AssetRegistry = IAssetRegistry::GetChecked();; ICollectionManager& CollectionManager = FCollectionManagerModule::GetModule().Get(); if (Params.Num() < 2) { Ar.Log(TEXT("Expected two parameters")); return; } if (AssetRegistry.IsLoadingAssets()) { Ar.Log(TEXT("Asset registry is still scanning, try again later")); return; } const bool bExcludeSecondInstanceOfMultiSupported = (Params.Num() >= 3) ? Params[2].ToBool() : true; TArray OldPaths; if (!CollectionManager.GetAssetsInCollection(FName(*Params[0]), ECollectionShareType::CST_All, /*out*/ OldPaths)) { Ar.Log(FString::Printf(TEXT("Failed to find collection %s"), *Params[0])); return; } TArray NewPaths; if (!CollectionManager.GetAssetsInCollection(FName(*Params[1]), ECollectionShareType::CST_All, /*out*/ NewPaths)) { Ar.Log(FString::Printf(TEXT("Failed to find collection %s"), *Params[1])); return; } auto ToPackageName = [](FName ObjectName) { return FName(*FPackageName::ObjectPathToPackageName(ObjectName.ToString())); }; TSet OldPathSet; Algo::Transform(OldPaths, OldPathSet, ToPackageName); TSet NewPathSet; Algo::Transform(NewPaths, NewPathSet, ToPackageName); TSet IntroducedAssetSet = NewPathSet.Difference(OldPathSet); // Map from added asset to list of assets in old paths that supports it (if any) TMap> SupportMap; TSet VisitedAssets; // Map of count of newly added assets directly/indirectly supported by each supporter asset in the old paths TMap SupporterCountMap; TMap> SupporterToAddedMap; TFunction RecursivelyBuildSupport = [&](const FName IntroducedAssetPath) { // Someone else may have already processed me as part of their dependencies if (!VisitedAssets.Contains(IntroducedAssetPath)) { VisitedAssets.Add(IntroducedAssetPath); TArray Referencers; AssetRegistry.GetReferencers(IntroducedAssetPath, /*out*/ Referencers); for (const FName& Referencer : Referencers) { if (OldPathSet.Contains(Referencer)) { // Direct support SupportMap.FindOrAdd(IntroducedAssetPath).Add(Referencer); } else { // Indirect, need to process recursively RecursivelyBuildSupport(Referencer); // Can now use the supports it indicated to build into our own TSet& MySupports = SupportMap.FindOrAdd(IntroducedAssetPath); if (TSet* pRecuriveReferencers = SupportMap.Find(Referencer)) { MySupports.Append(*pRecuriveReferencers); } } } } }; // Find the supporters for (const FName& IntroducedAssetPath : IntroducedAssetSet) { RecursivelyBuildSupport(IntroducedAssetPath); } // Count the strongest supporters for (const auto& KVP : SupportMap) { const FName SupportedAsset = KVP.Key; for (const FName& Supporter : KVP.Value) { SupporterToAddedMap.FindOrAdd(Supporter).Add(SupportedAsset); } } TSet AlreadyPrintedOut; Ar.Log(TEXT("List of supporters, sorted by count of newly added assets being supported")); SupporterCountMap.ValueSort(TGreater()); for (const auto& KVP : SupporterToAddedMap) { const FName Supporter = KVP.Key; // Filter to added assets (and exclude ones already printed if we were asked to) TArray AddedAssetsBeingSupported(KVP.Value.Array()); AddedAssetsBeingSupported = AddedAssetsBeingSupported.FilterByPredicate([&](FName Test) { return IntroducedAssetSet.Contains(Test); }); int32 IncludingMultisupportCount = AddedAssetsBeingSupported.Num(); if (bExcludeSecondInstanceOfMultiSupported) { AddedAssetsBeingSupported = AddedAssetsBeingSupported.FilterByPredicate([&](FName Test) { return !AlreadyPrintedOut.Contains(Test); }); } Algo::Sort(AddedAssetsBeingSupported, [](const FName LHS, const FName RHS) { return LHS.LexicalLess(RHS); }); // Print out the list Ar.Log(FString::Printf(TEXT("%s supports %d new assets:"), *Supporter.ToString(), IncludingMultisupportCount)); for (const FName& AddedAsset : AddedAssetsBeingSupported) { const int32 AddedAssetSupportCount = SupportMap.FindChecked(AddedAsset).Num(); Ar.Log(FString::Printf(TEXT("\t%s%s"), *AddedAsset.ToString(), (AddedAssetSupportCount > 1) ? TEXT(" [multi-supported]") : TEXT(""))); AlreadyPrintedOut.Add(AddedAsset); } const int32 NumExcludedDueToMultiSupport = IncludingMultisupportCount - AddedAssetsBeingSupported.Num(); if (NumExcludedDueToMultiSupport > 0) { Ar.Log(FString::Printf(TEXT("\tAnd %d more that were also supported by a previous supporter"), NumExcludedDueToMultiSupport)); } } Ar.Log(TEXT("\n")); Ar.Log(TEXT("List of unsupported assets:")); for (const FName& AssetName : IntroducedAssetSet) { if (!SupportMap.Contains(AssetName) || (SupportMap.FindRef(AssetName).Num() == 0)) { Ar.Log(FString::Printf(TEXT("\t%s"), *AssetName.ToString())); //@TODO: Check to see if this is instead supported by a primary asset maybe? } } }));