You've already forked UnrealEngineUWP
mirror of
https://github.com/izzy2lost/UnrealEngineUWP.git
synced 2026-03-26 18:15:20 -07:00
- Calls to OnPluginUnload (one GC call for all plugins) - Calls to FCollectionManager::HandleObjectDeleted (one UpdateCaches call at the end) #rb Dave.Belanger, Francis.Hurteau [FYI] Rex.Hill #rnx [CL 36757086 by nick verigakis in 5.5 branch]
2568 lines
87 KiB
C++
2568 lines
87 KiB
C++
// Copyright Epic Games, Inc. All Rights Reserved.
|
|
|
|
#include "CollectionManager.h"
|
|
|
|
#include "Algo/Sort.h"
|
|
#include "Algo/Unique.h"
|
|
#include "HAL/FileManager.h"
|
|
#include "Misc/Paths.h"
|
|
#include "Containers/Ticker.h"
|
|
#include "CollectionManagerLog.h"
|
|
#include "CollectionManagerModule.h"
|
|
#include "FileCache.h"
|
|
#include "Misc/FileHelper.h"
|
|
#include "Misc/ScopeRWLock.h"
|
|
#include "Async/ParallelFor.h"
|
|
#include "Misc/CommandLine.h"
|
|
#include "SourceControlPreferences.h"
|
|
#include "Tasks/Task.h"
|
|
|
|
#define LOCTEXT_NAMESPACE "CollectionManager"
|
|
|
|
// Base class for lock hierarchy. When used as a function parameter it means the called must hold at least a read lock
|
|
class FCollectionLock
|
|
{
|
|
protected:
|
|
UE_NODISCARD_CTOR explicit FCollectionLock(FRWLock& InLockObject, bool InWriteLock)
|
|
: LockObject(InLockObject)
|
|
, bWriteLock(InWriteLock)
|
|
{
|
|
if (InWriteLock)
|
|
{
|
|
LockObject.WriteLock();
|
|
}
|
|
else
|
|
{
|
|
LockObject.ReadLock();
|
|
}
|
|
}
|
|
|
|
// Promoted the lock from read to write, possibly being interrupted by another writer in between
|
|
void PromoteInterruptible()
|
|
{
|
|
if(!bWriteLock)
|
|
{
|
|
LockObject.ReadUnlock();
|
|
LockObject.WriteLock();
|
|
bWriteLock = true;
|
|
}
|
|
}
|
|
|
|
~FCollectionLock()
|
|
{
|
|
if(bWriteLock)
|
|
{
|
|
LockObject.WriteUnlock();
|
|
}
|
|
else
|
|
{
|
|
LockObject.ReadUnlock();
|
|
}
|
|
}
|
|
|
|
// Used for assertions to confirm that the correct kind of lock has been taken
|
|
bool IsWriteLock()
|
|
{
|
|
return bWriteLock;
|
|
}
|
|
|
|
private:
|
|
UE_NONCOPYABLE(FCollectionLock);
|
|
FRWLock& LockObject;
|
|
bool bWriteLock = false;
|
|
};
|
|
|
|
// Scoped lock type used to hold lock and to tag methods which should at least hold a read lock
|
|
class FCollectionLock_Read : public FCollectionLock
|
|
{
|
|
public:
|
|
UE_NODISCARD_CTOR explicit FCollectionLock_Read(FRWLock& InLockObject)
|
|
: FCollectionLock(InLockObject, false)
|
|
{
|
|
}
|
|
};
|
|
|
|
// A lock on the collection manager which begins in a read only state and can be promoted into a write lock with potential interruption in between
|
|
class FCollectionLock_RW : public FCollectionLock
|
|
{
|
|
public:
|
|
UE_NODISCARD_CTOR explicit FCollectionLock_RW(FRWLock& InLockObject, bool bWrite = false)
|
|
: FCollectionLock(InLockObject, bWrite)
|
|
{
|
|
}
|
|
|
|
// Promoted the lock from read to write, possibly being interrupted by another writer in between
|
|
using FCollectionLock::PromoteInterruptible;
|
|
// Used for assertions to confirm that the correct kind of lock has been taken
|
|
using FCollectionLock::IsWriteLock;
|
|
};
|
|
|
|
// Write lock on the collection manager
|
|
class FCollectionLock_Write : public FCollectionLock_RW
|
|
{
|
|
public:
|
|
UE_NODISCARD_CTOR explicit FCollectionLock_Write(FRWLock& InLockObject)
|
|
: FCollectionLock_RW(InLockObject, true)
|
|
{
|
|
}
|
|
};
|
|
|
|
/** Wraps up the lazy caching of the collection manager */
|
|
class FCollectionManagerCache
|
|
{
|
|
public:
|
|
FCollectionManagerCache(TMap<FCollectionNameType, TSharedRef<FCollection>>* InAvailableCollections);
|
|
|
|
/**
|
|
* Dirty the parts of the cache that need to change when a collection is added to our collection manager.
|
|
* The collection manager must be locked.
|
|
*/
|
|
void HandleCollectionAdded(FCollectionLock_Write&);
|
|
|
|
/**
|
|
* Dirty the parts of the cache that need to change when a collection is removed from our collection manager
|
|
* The collection manager must be locked.
|
|
*/
|
|
void HandleCollectionRemoved(FCollectionLock_Write&);
|
|
|
|
/**
|
|
* Dirty the parts of the cache that need to change when a collection is modified
|
|
* The collcetion manager must be lockedl
|
|
*/
|
|
void HandleCollectionChanged(FCollectionLock_Write&);
|
|
|
|
/**
|
|
* Update the given dirty parts of the cache based on which parts will be accessed while the given lock is held.
|
|
* A read/write lock will be promoted to a write lock if the cache must be updated.
|
|
* A write lock may also be passed as it extends the read/write lock.
|
|
* The calling thread may be interrupted by another write operation during the promotion operation.
|
|
* Therefore, caches should be updated as early as possible in order to prevent invalidation of state.
|
|
*
|
|
* This function is used rather than updating the caches in the Get* functions to prevent issues with pre-emption
|
|
* on the lock upgrade deep into a method.
|
|
*/
|
|
void UpdateCaches(FCollectionLock_RW& InGuard, ECollectionCacheFlags Flags);
|
|
|
|
/**
|
|
* Access the CachedCollectionNamesFromGuids map, asserting that it is up-to-date.
|
|
* The collection manager must be read-locked.
|
|
*/
|
|
const TMap<FGuid, FCollectionNameType>& GetCachedCollectionNamesFromGuids(FCollectionLock&) const;
|
|
|
|
/**
|
|
* Access the CachedObjects map, asserting that it is up-to-date.
|
|
* The collection manager must be read-locked.
|
|
*/
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& GetCachedObjects(FCollectionLock&) const;
|
|
|
|
/**
|
|
* Access the CachedHierarchy map, asserting that it is up-to-date.
|
|
* The collection manager must be read-locked.
|
|
*/
|
|
const TMap<FGuid, TArray<FGuid>>& GetCachedHierarchy(FCollectionLock&) const;
|
|
|
|
/**
|
|
* Access the CachedColors array, asserting that it is up-to-date
|
|
* The collection manager must be read-locked.
|
|
*/
|
|
const TArray<FLinearColor>& GetCachedColors(FCollectionLock&) const;
|
|
|
|
enum class ERecursiveWorkerFlowControl : uint8
|
|
{
|
|
Stop,
|
|
Continue,
|
|
};
|
|
|
|
typedef TFunctionRef<ERecursiveWorkerFlowControl(const FCollectionNameType&, ECollectionRecursionFlags::Flag)> FRecursiveWorkerFunc;
|
|
|
|
/**
|
|
* Perform a recursive operation on the given collection and optionally its parents and children.
|
|
* The collection manager must be read-locked and UpdateCaches must be called for names and hierarchy.
|
|
*/
|
|
void RecursionHelper_DoWork(FCollectionLock&, const FCollectionNameType& InCollectionKey, const ECollectionRecursionFlags::Flags InRecursionMode, FRecursiveWorkerFunc InWorkerFunc) const;
|
|
|
|
private:
|
|
/** Reference to the collections that are currently available in our owner collection manager */
|
|
TMap<FCollectionNameType, TSharedRef<FCollection>>* AvailableCollections;
|
|
|
|
/** A map of collection GUIDs to their associated collection names */
|
|
TMap<FGuid, FCollectionNameType> CachedCollectionNamesFromGuids_Internal;
|
|
|
|
/** A map of object paths to their associated collection info - only objects that are in collections will appear in here */
|
|
TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>> CachedObjects_Internal;
|
|
|
|
/** A map of parent collection GUIDs to their child collection GUIDs - only collections that have children will appear in here */
|
|
TMap<FGuid, TArray<FGuid>> CachedHierarchy_Internal;
|
|
|
|
/** An array of all unique colors currently used by collections */
|
|
TArray<FLinearColor> CachedColors_Internal;
|
|
|
|
/** Which parts of the cache are dirty */
|
|
ECollectionCacheFlags DirtyFlags = ECollectionCacheFlags::All;
|
|
|
|
ERecursiveWorkerFlowControl RecursionHelper_DoWorkOnParents(FCollectionLock&, const FCollectionNameType& InCollectionKey, FRecursiveWorkerFunc InWorkerFunc) const;
|
|
ERecursiveWorkerFlowControl RecursionHelper_DoWorkOnChildren(FCollectionLock&, const FCollectionNameType& InCollectionKey, FRecursiveWorkerFunc InWorkerFunc) const;
|
|
};
|
|
|
|
FCollectionManagerCache::FCollectionManagerCache(TMap<FCollectionNameType, TSharedRef<FCollection>>* InAvailableCollections)
|
|
: AvailableCollections(InAvailableCollections)
|
|
{
|
|
}
|
|
|
|
void FCollectionManagerCache::HandleCollectionAdded(FCollectionLock_Write&)
|
|
{
|
|
DirtyFlags |= ECollectionCacheFlags::Names;
|
|
}
|
|
|
|
void FCollectionManagerCache::HandleCollectionRemoved(FCollectionLock_Write&)
|
|
{
|
|
DirtyFlags |= ECollectionCacheFlags::All;
|
|
}
|
|
|
|
void FCollectionManagerCache::HandleCollectionChanged(FCollectionLock_Write&)
|
|
{
|
|
DirtyFlags |= ECollectionCacheFlags::Objects | ECollectionCacheFlags::Hierarchy | ECollectionCacheFlags::Colors;
|
|
}
|
|
|
|
void FCollectionManagerCache::UpdateCaches(FCollectionLock_RW& InGuard, ECollectionCacheFlags ToUpdate)
|
|
{
|
|
// Updating objects or hierarchy requires name mapping
|
|
if (EnumHasAnyFlags(ToUpdate, ECollectionCacheFlags::Hierarchy | ECollectionCacheFlags::Objects))
|
|
{
|
|
ToUpdate |= ECollectionCacheFlags::Names;
|
|
}
|
|
|
|
// Updating objects requires hierarchy
|
|
if (EnumHasAnyFlags(ToUpdate, ECollectionCacheFlags::Objects))
|
|
{
|
|
ToUpdate |= ECollectionCacheFlags::Hierarchy;
|
|
}
|
|
|
|
if (EnumHasAnyFlags(DirtyFlags, ToUpdate))
|
|
{
|
|
InGuard.PromoteInterruptible();
|
|
}
|
|
|
|
if (!EnumHasAnyFlags(DirtyFlags, ToUpdate))
|
|
{
|
|
// Caches we care about were updated while we switched locks
|
|
return;
|
|
}
|
|
|
|
// Limit updates to what's dirty
|
|
ToUpdate = ToUpdate & DirtyFlags;
|
|
const double CacheStartTime = FPlatformTime::Seconds();
|
|
|
|
if (EnumHasAllFlags(ToUpdate, ECollectionCacheFlags::Names))
|
|
{
|
|
CachedCollectionNamesFromGuids_Internal.Reset();
|
|
EnumRemoveFlags(DirtyFlags, ECollectionCacheFlags::Names);
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : *AvailableCollections)
|
|
{
|
|
const FCollectionNameType& CollectionKey = AvailableCollection.Key;
|
|
const TSharedRef<FCollection>& Collection = AvailableCollection.Value;
|
|
|
|
CachedCollectionNamesFromGuids_Internal.Add(Collection->GetCollectionGuid(), CollectionKey);
|
|
}
|
|
}
|
|
|
|
if (EnumHasAllFlags(ToUpdate, ECollectionCacheFlags::Hierarchy))
|
|
{
|
|
CachedHierarchy_Internal.Reset();
|
|
EnumRemoveFlags(DirtyFlags, ECollectionCacheFlags::Hierarchy);
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = GetCachedCollectionNamesFromGuids(InGuard);
|
|
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : *AvailableCollections)
|
|
{
|
|
const FCollectionNameType& CollectionKey = AvailableCollection.Key;
|
|
const TSharedRef<FCollection>& Collection = AvailableCollection.Value;
|
|
|
|
// Make sure this is a known parent GUID before adding it to the map
|
|
const FGuid& ParentCollectionGuid = Collection->GetParentCollectionGuid();
|
|
if (CachedCollectionNamesFromGuids.Contains(ParentCollectionGuid))
|
|
{
|
|
TArray<FGuid>& CollectionChildren = CachedHierarchy_Internal.FindOrAdd(ParentCollectionGuid);
|
|
CollectionChildren.AddUnique(Collection->GetCollectionGuid());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (EnumHasAllFlags(ToUpdate, ECollectionCacheFlags::Objects))
|
|
{
|
|
CachedObjects_Internal.Reset();
|
|
EnumRemoveFlags(DirtyFlags, ECollectionCacheFlags::Objects);
|
|
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : *AvailableCollections)
|
|
{
|
|
const FCollectionNameType& CollectionKey = AvailableCollection.Key;
|
|
const TSharedRef<FCollection>& Collection = AvailableCollection.Value;
|
|
const TSet<FSoftObjectPath>& ObjectsInCollection = Collection->GetObjectSet();
|
|
|
|
if (ObjectsInCollection.Num() == 0)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
auto RebuildCachedObjectsWorker = [CachedObjects_Internal=&CachedObjects_Internal, &ObjectsInCollection]
|
|
(const FCollectionNameType& InCollectionKey, ECollectionRecursionFlags::Flag InReason) -> ERecursiveWorkerFlowControl
|
|
{
|
|
// The worker reason will tell us why this collection is being processed (eg, because it is a parent of the collection we told it to DoWork on),
|
|
// however, the reason this object exists in that parent collection is because a child collection contains it, and this is the reason we need
|
|
// to put into the FObjectCollectionInfo, since that's what we'll test against later when we do the "do my children contain this object"? test
|
|
// That's why we flip the reason logic here...
|
|
ECollectionRecursionFlags::Flag ReasonObjectInCollection = InReason;
|
|
switch (InReason)
|
|
{
|
|
case ECollectionRecursionFlags::Parents:
|
|
ReasonObjectInCollection = ECollectionRecursionFlags::Children;
|
|
break;
|
|
case ECollectionRecursionFlags::Children:
|
|
ReasonObjectInCollection = ECollectionRecursionFlags::Parents;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
for (const FSoftObjectPath& ObjectPath : ObjectsInCollection)
|
|
{
|
|
TArray<FObjectCollectionInfo>& ObjectCollectionInfos = CachedObjects_Internal->FindOrAdd(ObjectPath);
|
|
FObjectCollectionInfo* ObjectInfoPtr = ObjectCollectionInfos.FindByPredicate([InCollectionKey](FObjectCollectionInfo& InCollectionInfo) { return InCollectionInfo.CollectionKey == InCollectionKey; });
|
|
if (ObjectInfoPtr)
|
|
{
|
|
ObjectInfoPtr->Reason |= ReasonObjectInCollection;
|
|
}
|
|
else
|
|
{
|
|
ObjectCollectionInfos.Add(FObjectCollectionInfo(InCollectionKey, ReasonObjectInCollection));
|
|
}
|
|
}
|
|
return ERecursiveWorkerFlowControl::Continue;
|
|
};
|
|
|
|
// Recursively process all collections so that they know they contain these objects (and why!)
|
|
RecursionHelper_DoWork(InGuard, CollectionKey, ECollectionRecursionFlags::All, RebuildCachedObjectsWorker);
|
|
}
|
|
|
|
}
|
|
|
|
if (EnumHasAllFlags(ToUpdate, ECollectionCacheFlags::Colors))
|
|
{
|
|
CachedColors_Internal.Reset();
|
|
EnumRemoveFlags(DirtyFlags, ECollectionCacheFlags::Colors);
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : *AvailableCollections)
|
|
{
|
|
const TSharedRef<FCollection>& Collection = AvailableCollection.Value;
|
|
|
|
if (const TOptional<FLinearColor> CollectionColor = Collection->GetCollectionColor())
|
|
{
|
|
CachedColors_Internal.Add(CollectionColor.GetValue());
|
|
}
|
|
// Deduplicate
|
|
Algo::SortBy(CachedColors_Internal, [](FLinearColor Color) { return GetTypeHash(Color); });
|
|
CachedColors_Internal.SetNum(Algo::Unique(CachedColors_Internal));
|
|
}
|
|
}
|
|
|
|
UE_LOG(LogCollectionManager, Verbose, TEXT("Rebuilt caches for %d collections in in %0.6f seconds"), AvailableCollections->Num(), FPlatformTime::Seconds() - CacheStartTime);
|
|
}
|
|
|
|
void FCollectionManagerCache::RecursionHelper_DoWork(FCollectionLock& Guard,
|
|
const FCollectionNameType& InCollectionKey,
|
|
const ECollectionRecursionFlags::Flags InRecursionMode,
|
|
FRecursiveWorkerFunc InWorkerFunc) const
|
|
{
|
|
checkf(!EnumHasAnyFlags(DirtyFlags, ECollectionCacheFlags::RecursionWorker), TEXT("Collection cache must be updated with RecursionWorker flags before recursing through hierarchy."));
|
|
|
|
if ((InRecursionMode & ECollectionRecursionFlags::Self) && InWorkerFunc(InCollectionKey, ECollectionRecursionFlags::Self) == ERecursiveWorkerFlowControl::Stop)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((InRecursionMode & ECollectionRecursionFlags::Parents) && RecursionHelper_DoWorkOnParents(Guard, InCollectionKey, InWorkerFunc) == ERecursiveWorkerFlowControl::Stop)
|
|
{
|
|
return;
|
|
}
|
|
|
|
if ((InRecursionMode & ECollectionRecursionFlags::Children) && RecursionHelper_DoWorkOnChildren(Guard, InCollectionKey, InWorkerFunc) == ERecursiveWorkerFlowControl::Stop)
|
|
{
|
|
return;
|
|
}
|
|
}
|
|
|
|
FCollectionManagerCache::ERecursiveWorkerFlowControl FCollectionManagerCache::RecursionHelper_DoWorkOnParents(
|
|
FCollectionLock& Guard, const FCollectionNameType& InCollectionKey, FRecursiveWorkerFunc InWorkerFunc) const
|
|
{
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections->Find(InCollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = GetCachedCollectionNamesFromGuids(Guard);
|
|
|
|
const FCollectionNameType* const ParentCollectionKeyPtr = CachedCollectionNamesFromGuids.Find((*CollectionRefPtr)->GetParentCollectionGuid());
|
|
if (ParentCollectionKeyPtr)
|
|
{
|
|
if (InWorkerFunc(*ParentCollectionKeyPtr, ECollectionRecursionFlags::Parents) == ERecursiveWorkerFlowControl::Stop || RecursionHelper_DoWorkOnParents(Guard, *ParentCollectionKeyPtr, InWorkerFunc) == ERecursiveWorkerFlowControl::Stop)
|
|
{
|
|
return ERecursiveWorkerFlowControl::Stop;
|
|
}
|
|
}
|
|
}
|
|
|
|
return ERecursiveWorkerFlowControl::Continue;
|
|
}
|
|
|
|
FCollectionManagerCache::ERecursiveWorkerFlowControl FCollectionManagerCache::RecursionHelper_DoWorkOnChildren(
|
|
FCollectionLock& Guard, const FCollectionNameType& InCollectionKey, FRecursiveWorkerFunc InWorkerFunc) const
|
|
{
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections->Find(InCollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
const TMap<FGuid, TArray<FGuid>>& CachedHierarchy = GetCachedHierarchy(Guard);
|
|
|
|
const TArray<FGuid>* const ChildCollectionGuids = CachedHierarchy.Find((*CollectionRefPtr)->GetCollectionGuid());
|
|
if (ChildCollectionGuids)
|
|
{
|
|
for (const FGuid& ChildCollectionGuid : *ChildCollectionGuids)
|
|
{
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = GetCachedCollectionNamesFromGuids(Guard);
|
|
|
|
const FCollectionNameType* const ChildCollectionKeyPtr = CachedCollectionNamesFromGuids.Find(ChildCollectionGuid);
|
|
if (ChildCollectionKeyPtr)
|
|
{
|
|
if (InWorkerFunc(*ChildCollectionKeyPtr, ECollectionRecursionFlags::Children) == ERecursiveWorkerFlowControl::Stop || RecursionHelper_DoWorkOnChildren(Guard, *ChildCollectionKeyPtr, InWorkerFunc) == ERecursiveWorkerFlowControl::Stop)
|
|
{
|
|
return ERecursiveWorkerFlowControl::Stop;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return ERecursiveWorkerFlowControl::Continue;
|
|
}
|
|
|
|
const TMap<FGuid, FCollectionNameType>& FCollectionManagerCache::GetCachedCollectionNamesFromGuids(FCollectionLock&) const
|
|
{
|
|
checkf(!EnumHasAnyFlags(DirtyFlags, ECollectionCacheFlags::Names), TEXT("Accessed guid->name map without updating cache"));
|
|
return CachedCollectionNamesFromGuids_Internal;
|
|
}
|
|
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& FCollectionManagerCache::GetCachedObjects(FCollectionLock&) const
|
|
{
|
|
checkf(!EnumHasAnyFlags(DirtyFlags, ECollectionCacheFlags::Objects), TEXT("Accessd object->collection map without updating cache"));
|
|
return CachedObjects_Internal;
|
|
}
|
|
|
|
const TMap<FGuid, TArray<FGuid>>& FCollectionManagerCache::GetCachedHierarchy(FCollectionLock&) const
|
|
{
|
|
checkf(!EnumHasAnyFlags(DirtyFlags, ECollectionCacheFlags::Hierarchy), TEXT("Accessed collection hierarchy map without updating cache"));
|
|
return CachedHierarchy_Internal;
|
|
}
|
|
|
|
const TArray<FLinearColor>& FCollectionManagerCache::GetCachedColors(FCollectionLock&) const
|
|
{
|
|
checkf(!EnumHasAnyFlags(DirtyFlags, ECollectionCacheFlags::Colors), TEXT("Accessed collection colors without updating cache"));
|
|
return CachedColors_Internal;
|
|
}
|
|
|
|
FStringView FCollectionManager::CollectionExtension = TEXTVIEW("collection");
|
|
|
|
FCollectionManager::FCollectionManager()
|
|
: CollectionCache(MakePimpl<FCollectionManagerCache>(&AvailableCollections))
|
|
{
|
|
CollectionFolders[ECollectionShareType::CST_Local] = FPaths::ProjectSavedDir() / TEXT("Collections");
|
|
CollectionFolders[ECollectionShareType::CST_Private] = FPaths::GameUserDeveloperDir() / TEXT("Collections");
|
|
CollectionFolders[ECollectionShareType::CST_Shared] = FPaths::ProjectContentDir() / TEXT("Collections");
|
|
|
|
bNoFixupRedirectors = FParse::Param(FCommandLine::Get(), TEXT("NoFixupRedirectorsInCollections"));
|
|
|
|
LoadCollections();
|
|
|
|
// Perform initial caching of collection information ready for user to interact with anything
|
|
UE::Tasks::Launch(UE_SOURCE_LOCATION, [this](){
|
|
FCollectionLock_Write Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::All);
|
|
});
|
|
|
|
// Watch for changes that may happen outside of the collection manager
|
|
for (int32 CacheIdx = 0; CacheIdx < ECollectionShareType::CST_All; ++CacheIdx)
|
|
{
|
|
const FString& CollectionFolder = CollectionFolders[CacheIdx];
|
|
|
|
if (CollectionFolder.IsEmpty())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// Make sure the folder we want to watch exists on disk
|
|
if (!IFileManager::Get().MakeDirectory(*CollectionFolder, true))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
DirectoryWatcher::FFileCacheConfig FileCacheConfig(FPaths::ConvertRelativePathToFull(CollectionFolder), FString());
|
|
FileCacheConfig.DetectMoves(false);
|
|
FileCacheConfig.RequireFileHashes(false);
|
|
|
|
CollectionFileCaches[CacheIdx] = MakeShareable(new DirectoryWatcher::FFileCache(FileCacheConfig));
|
|
}
|
|
|
|
TickFileCacheDelegateHandle = FTSTicker::GetCoreTicker().AddTicker(FTickerDelegate::CreateRaw(this, &FCollectionManager::TickFileCache), 1.0f);
|
|
}
|
|
|
|
FCollectionManager::~FCollectionManager()
|
|
{
|
|
FWriteScopeLock Guard(Lock);
|
|
FTSTicker::GetCoreTicker().RemoveTicker(TickFileCacheDelegateHandle);
|
|
}
|
|
|
|
|
|
bool FCollectionManager::HasCollections() const
|
|
{
|
|
FCollectionLock_Read Guard(Lock);
|
|
return AvailableCollections.Num() > 0;
|
|
}
|
|
|
|
void FCollectionManager::GetCollections(TArray<FCollectionNameType>& OutCollections) const
|
|
{
|
|
FCollectionLock_Read Guard(Lock);
|
|
OutCollections.Reserve(AvailableCollections.Num());
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : AvailableCollections)
|
|
{
|
|
const FCollectionNameType& CollectionKey = AvailableCollection.Key;
|
|
OutCollections.Add(CollectionKey);
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::GetCollections(FName CollectionName, TArray<FCollectionNameType>& OutCollections) const
|
|
{
|
|
FCollectionLock_Read Guard(Lock);
|
|
for (int32 CacheIdx = 0; CacheIdx < ECollectionShareType::CST_All; ++CacheIdx)
|
|
{
|
|
if (AvailableCollections.Contains(FCollectionNameType(CollectionName, ECollectionShareType::Type(CacheIdx))))
|
|
{
|
|
OutCollections.Add(FCollectionNameType(CollectionName, ECollectionShareType::Type(CacheIdx)));
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::GetCollectionNames(ECollectionShareType::Type ShareType, TArray<FName>& CollectionNames) const
|
|
{
|
|
FCollectionLock_Read Guard(Lock);
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : AvailableCollections)
|
|
{
|
|
const FCollectionNameType& CollectionKey = AvailableCollection.Key;
|
|
if (ShareType == ECollectionShareType::CST_All || ShareType == CollectionKey.Type)
|
|
{
|
|
CollectionNames.AddUnique(CollectionKey.Name);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::GetRootCollections(TArray<FCollectionNameType>& OutCollections) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Names);
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = CollectionCache->GetCachedCollectionNamesFromGuids(Guard);
|
|
|
|
OutCollections.Reserve(AvailableCollections.Num());
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : AvailableCollections)
|
|
{
|
|
const FCollectionNameType& CollectionKey = AvailableCollection.Key;
|
|
const TSharedRef<FCollection>& Collection = AvailableCollection.Value;
|
|
|
|
// A root collection either has no parent GUID, or a parent GUID that cannot currently be found - the check below handles both
|
|
if (!CachedCollectionNamesFromGuids.Contains(Collection->GetParentCollectionGuid()))
|
|
{
|
|
OutCollections.Add(CollectionKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::GetRootCollectionNames(ECollectionShareType::Type ShareType, TArray<FName>& CollectionNames) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Names);
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = CollectionCache->GetCachedCollectionNamesFromGuids(Guard);
|
|
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : AvailableCollections)
|
|
{
|
|
const FCollectionNameType& CollectionKey = AvailableCollection.Key;
|
|
const TSharedRef<FCollection>& Collection = AvailableCollection.Value;
|
|
|
|
if (ShareType == ECollectionShareType::CST_All || ShareType == CollectionKey.Type)
|
|
{
|
|
// A root collection either has no parent GUID, or a parent GUID that cannot currently be found - the check below handles both
|
|
if (!CachedCollectionNamesFromGuids.Contains(Collection->GetParentCollectionGuid()))
|
|
{
|
|
CollectionNames.AddUnique(CollectionKey.Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::GetChildCollections(FName CollectionName, ECollectionShareType::Type ShareType, TArray<FCollectionNameType>& OutCollections) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Names | ECollectionCacheFlags::Hierarchy);
|
|
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = CollectionCache->GetCachedCollectionNamesFromGuids(Guard);
|
|
const TMap<FGuid, TArray<FGuid>>& CachedHierarchy = CollectionCache->GetCachedHierarchy(Guard);
|
|
|
|
const int32 Start = ShareType == ECollectionShareType::CST_All ? 0 : int32(ShareType);
|
|
const int32 End = ShareType == ECollectionShareType::CST_All ? int32(ECollectionShareType::CST_All) : int32(ShareType) + 1;
|
|
|
|
for (int32 CacheIdx = Start; CacheIdx < End; ++CacheIdx)
|
|
{
|
|
FCollectionNameType CollectionKey{ CollectionName, ECollectionShareType::Type(CacheIdx) };
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const TArray<FGuid>* ChildCollectionGuids = CachedHierarchy.Find((*CollectionRefPtr)->GetCollectionGuid());
|
|
if (!ChildCollectionGuids)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (const FGuid& ChildCollectionGuid : *ChildCollectionGuids)
|
|
{
|
|
const FCollectionNameType* const ChildCollectionKeyPtr = CachedCollectionNamesFromGuids.Find(ChildCollectionGuid);
|
|
if (ChildCollectionKeyPtr)
|
|
{
|
|
OutCollections.Add(*ChildCollectionKeyPtr);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
void FCollectionManager::GetChildCollectionNames(FName CollectionName, ECollectionShareType::Type ShareType, ECollectionShareType::Type ChildShareType, TArray<FName>& CollectionNames) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Names | ECollectionCacheFlags::Hierarchy);
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = CollectionCache->GetCachedCollectionNamesFromGuids(Guard);
|
|
const TMap<FGuid, TArray<FGuid>>& CachedHierarchy = CollectionCache->GetCachedHierarchy(Guard);
|
|
|
|
const int32 Start = ShareType == ECollectionShareType::CST_All ? 0 : int32(ShareType);
|
|
const int32 End = ShareType == ECollectionShareType::CST_All ? int32(ECollectionShareType::CST_All) : int32(ShareType) + 1;
|
|
|
|
for (int32 CacheIdx = Start; CacheIdx < End; ++CacheIdx)
|
|
{
|
|
FCollectionNameType CollectionKey{ CollectionName, ECollectionShareType::Type(CacheIdx) };
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const TArray<FGuid>* ChildCollectionGuids = CachedHierarchy.Find((*CollectionRefPtr)->GetCollectionGuid());
|
|
if (!ChildCollectionGuids)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
for (const FGuid& ChildCollectionGuid : *ChildCollectionGuids)
|
|
{
|
|
const FCollectionNameType* const ChildCollectionKeyPtr = CachedCollectionNamesFromGuids.Find(ChildCollectionGuid);
|
|
if (ChildCollectionKeyPtr && (ChildShareType == ECollectionShareType::CST_All || ChildShareType == ChildCollectionKeyPtr->Type))
|
|
{
|
|
CollectionNames.AddUnique(ChildCollectionKeyPtr->Name);
|
|
}
|
|
}
|
|
};
|
|
}
|
|
|
|
TOptional<FCollectionNameType> FCollectionManager::GetParentCollection(FName CollectionName, ECollectionShareType::Type ShareType) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(FCollectionNameType(CollectionName, ShareType));
|
|
if (!CollectionRefPtr)
|
|
{
|
|
return TOptional<FCollectionNameType>();
|
|
}
|
|
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Names);
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = CollectionCache->GetCachedCollectionNamesFromGuids(Guard);
|
|
const FCollectionNameType* const ParentCollectionKeyPtr = CachedCollectionNamesFromGuids.Find((*CollectionRefPtr)->GetParentCollectionGuid());
|
|
if (ParentCollectionKeyPtr)
|
|
{
|
|
return *ParentCollectionKeyPtr;
|
|
}
|
|
return TOptional<FCollectionNameType>();
|
|
}
|
|
|
|
bool FCollectionManager::CollectionExists(FName CollectionName, ECollectionShareType::Type ShareType) const
|
|
{
|
|
FCollectionLock_Read Guard(Lock);
|
|
return CollectionExists_Locked(Guard, CollectionName, ShareType);
|
|
}
|
|
|
|
bool FCollectionManager::CollectionExists_Locked(FCollectionLock&, FName CollectionName, ECollectionShareType::Type ShareType) const
|
|
{
|
|
if (ShareType == ECollectionShareType::CST_All)
|
|
{
|
|
// Asked to check all share types...
|
|
for (int32 CacheIdx = 0; CacheIdx < ECollectionShareType::CST_All; ++CacheIdx)
|
|
{
|
|
if (AvailableCollections.Contains(FCollectionNameType(CollectionName, ECollectionShareType::Type(CacheIdx))))
|
|
{
|
|
// Collection exists in at least one cache
|
|
return true;
|
|
}
|
|
}
|
|
|
|
// Collection not found in any cache
|
|
return false;
|
|
}
|
|
else
|
|
{
|
|
return AvailableCollections.Contains(FCollectionNameType(CollectionName, ShareType));
|
|
}
|
|
}
|
|
|
|
bool FCollectionManager::GetAssetsInCollection(FName CollectionName, ECollectionShareType::Type ShareType, TArray<FSoftObjectPath>& AssetsPaths, ECollectionRecursionFlags::Flags RecursionMode) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::RecursionWorker);
|
|
bool bFoundAssets = false;
|
|
|
|
auto GetAssetsInCollectionWorker = [AvailableCollections=&AvailableCollections, &AssetsPaths, &bFoundAssets](const FCollectionNameType& InCollectionKey, ECollectionRecursionFlags::Flag InReason) -> FCollectionManagerCache::ERecursiveWorkerFlowControl
|
|
{
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections->Find(InCollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
(*CollectionRefPtr)->GetAssetsInCollection(AssetsPaths);
|
|
bFoundAssets = true;
|
|
}
|
|
return FCollectionManagerCache::ERecursiveWorkerFlowControl::Continue;
|
|
};
|
|
|
|
if (ShareType == ECollectionShareType::CST_All)
|
|
{
|
|
// Asked for all share types, find assets in the specified collection name in any cache
|
|
for (int32 CacheIdx = 0; CacheIdx < ECollectionShareType::CST_All; ++CacheIdx)
|
|
{
|
|
CollectionCache->RecursionHelper_DoWork(Guard, FCollectionNameType(CollectionName, ECollectionShareType::Type(CacheIdx)), RecursionMode, GetAssetsInCollectionWorker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CollectionCache->RecursionHelper_DoWork(Guard, FCollectionNameType(CollectionName, ShareType), RecursionMode, GetAssetsInCollectionWorker);
|
|
}
|
|
|
|
return bFoundAssets;
|
|
}
|
|
|
|
bool FCollectionManager::GetClassesInCollection(FName CollectionName, ECollectionShareType::Type ShareType, TArray<FTopLevelAssetPath>& ClassPaths, ECollectionRecursionFlags::Flags RecursionMode) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::RecursionWorker);
|
|
bool bFoundClasses = false;
|
|
|
|
auto GetClassesInCollectionWorker = [AvailableCollections=&AvailableCollections, &ClassPaths, &bFoundClasses](const FCollectionNameType& InCollectionKey, ECollectionRecursionFlags::Flag InReason) -> FCollectionManagerCache::ERecursiveWorkerFlowControl
|
|
{
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections->Find(InCollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
(*CollectionRefPtr)->GetClassesInCollection(ClassPaths);
|
|
bFoundClasses = true;
|
|
}
|
|
return FCollectionManagerCache::ERecursiveWorkerFlowControl::Continue;
|
|
};
|
|
|
|
if (ShareType == ECollectionShareType::CST_All)
|
|
{
|
|
// Asked for all share types, find classes in the specified collection name in any cache
|
|
for (int32 CacheIdx = 0; CacheIdx < ECollectionShareType::CST_All; ++CacheIdx)
|
|
{
|
|
CollectionCache->RecursionHelper_DoWork(Guard, FCollectionNameType(CollectionName, ECollectionShareType::Type(CacheIdx)), RecursionMode, GetClassesInCollectionWorker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CollectionCache->RecursionHelper_DoWork(Guard, FCollectionNameType(CollectionName, ShareType), RecursionMode, GetClassesInCollectionWorker);
|
|
}
|
|
|
|
return bFoundClasses;
|
|
}
|
|
|
|
bool FCollectionManager::GetObjectsInCollection(FName CollectionName, ECollectionShareType::Type ShareType, TArray<FSoftObjectPath>& ObjectPaths, ECollectionRecursionFlags::Flags RecursionMode) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::RecursionWorker);
|
|
bool bFoundObjects = false;
|
|
|
|
auto GetObjectsInCollectionWorker = [AvailableCollections=&AvailableCollections, &ObjectPaths, &bFoundObjects](const FCollectionNameType& InCollectionKey, ECollectionRecursionFlags::Flag InReason) -> FCollectionManagerCache::ERecursiveWorkerFlowControl
|
|
{
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections->Find(InCollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
(*CollectionRefPtr)->GetObjectsInCollection(ObjectPaths);
|
|
bFoundObjects = true;
|
|
}
|
|
return FCollectionManagerCache::ERecursiveWorkerFlowControl::Continue;
|
|
};
|
|
|
|
if (ShareType == ECollectionShareType::CST_All)
|
|
{
|
|
// Asked for all share types, find classes in the specified collection name in any cache
|
|
for (int32 CacheIdx = 0; CacheIdx < ECollectionShareType::CST_All; ++CacheIdx)
|
|
{
|
|
CollectionCache->RecursionHelper_DoWork(Guard, FCollectionNameType(CollectionName, ECollectionShareType::Type(CacheIdx)), RecursionMode, GetObjectsInCollectionWorker);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
CollectionCache->RecursionHelper_DoWork(Guard, FCollectionNameType(CollectionName, ShareType), RecursionMode, GetObjectsInCollectionWorker);
|
|
}
|
|
|
|
return bFoundObjects;
|
|
}
|
|
|
|
void FCollectionManager::GetCollectionsContainingObject(const FSoftObjectPath& ObjectPath, ECollectionShareType::Type ShareType, TArray<FName>& OutCollectionNames, ECollectionRecursionFlags::Flags RecursionMode) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Objects);
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& CachedObjects = CollectionCache->GetCachedObjects(Guard);
|
|
|
|
const TArray<FObjectCollectionInfo>* ObjectCollectionInfosPtr = CachedObjects.Find(ObjectPath);
|
|
if (ObjectCollectionInfosPtr)
|
|
{
|
|
for (const FObjectCollectionInfo& ObjectCollectionInfo : *ObjectCollectionInfosPtr)
|
|
{
|
|
if ((ShareType == ECollectionShareType::CST_All || ShareType == ObjectCollectionInfo.CollectionKey.Type) && (RecursionMode & ObjectCollectionInfo.Reason) != 0)
|
|
{
|
|
OutCollectionNames.Add(ObjectCollectionInfo.CollectionKey.Name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::GetCollectionsContainingObject(const FSoftObjectPath& ObjectPath, TArray<FCollectionNameType>& OutCollections, ECollectionRecursionFlags::Flags RecursionMode) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Objects);
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& CachedObjects = CollectionCache->GetCachedObjects(Guard);
|
|
|
|
const TArray<FObjectCollectionInfo>* ObjectCollectionInfosPtr = CachedObjects.Find(ObjectPath);
|
|
if (ObjectCollectionInfosPtr)
|
|
{
|
|
OutCollections.Reserve(OutCollections.Num() + ObjectCollectionInfosPtr->Num());
|
|
for (const FObjectCollectionInfo& ObjectCollectionInfo : *ObjectCollectionInfosPtr)
|
|
{
|
|
if ((RecursionMode & ObjectCollectionInfo.Reason) != 0)
|
|
{
|
|
OutCollections.Add(ObjectCollectionInfo.CollectionKey);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::GetCollectionsContainingObjects(const TArray<FSoftObjectPath>& ObjectPaths, TMap<FCollectionNameType, TArray<FSoftObjectPath>>& OutCollectionsAndMatchedObjects, ECollectionRecursionFlags::Flags RecursionMode) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Objects);
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& CachedObjects = CollectionCache->GetCachedObjects(Guard);
|
|
|
|
for (const FSoftObjectPath& ObjectPath : ObjectPaths)
|
|
{
|
|
const TArray<FObjectCollectionInfo>* ObjectCollectionInfosPtr = CachedObjects.Find(ObjectPath);
|
|
if (ObjectCollectionInfosPtr)
|
|
{
|
|
for (const FObjectCollectionInfo& ObjectCollectionInfo : *ObjectCollectionInfosPtr)
|
|
{
|
|
if ((RecursionMode & ObjectCollectionInfo.Reason) != 0)
|
|
{
|
|
TArray<FSoftObjectPath>& MatchedObjects = OutCollectionsAndMatchedObjects.FindOrAdd(ObjectCollectionInfo.CollectionKey);
|
|
MatchedObjects.Add(ObjectPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
FString FCollectionManager::GetCollectionsStringForObject(const FSoftObjectPath& ObjectPath, ECollectionShareType::Type ShareType, ECollectionRecursionFlags::Flags RecursionMode, bool bFullPaths) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Objects | ECollectionCacheFlags::RecursionWorker);
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& CachedObjects = CollectionCache->GetCachedObjects(Guard);
|
|
|
|
const TArray<FObjectCollectionInfo>* ObjectCollectionInfosPtr = CachedObjects.Find(ObjectPath);
|
|
if (ObjectCollectionInfosPtr)
|
|
{
|
|
TArray<FString> CollectionNameStrings;
|
|
TArray<FString> CollectionPathStrings;
|
|
|
|
auto GetCollectionsStringForObjectWorker = [&CollectionPathStrings](const FCollectionNameType& InCollectionKey, ECollectionRecursionFlags::Flag InReason) -> FCollectionManagerCache::ERecursiveWorkerFlowControl
|
|
{
|
|
CollectionPathStrings.Insert(InCollectionKey.Name.ToString(), 0);
|
|
return FCollectionManagerCache::ERecursiveWorkerFlowControl::Continue;
|
|
};
|
|
|
|
for (const FObjectCollectionInfo& ObjectCollectionInfo : *ObjectCollectionInfosPtr)
|
|
{
|
|
if ((ShareType == ECollectionShareType::CST_All || ShareType == ObjectCollectionInfo.CollectionKey.Type) && (RecursionMode & ObjectCollectionInfo.Reason) != 0)
|
|
{
|
|
if (bFullPaths)
|
|
{
|
|
CollectionPathStrings.Reset();
|
|
CollectionCache->RecursionHelper_DoWork(Guard, ObjectCollectionInfo.CollectionKey, ECollectionRecursionFlags::SelfAndParents, GetCollectionsStringForObjectWorker);
|
|
CollectionNameStrings.Add(FString::Join(CollectionPathStrings, TEXT("/")));
|
|
}
|
|
else
|
|
{
|
|
CollectionNameStrings.Add(ObjectCollectionInfo.CollectionKey.Name.ToString());
|
|
}
|
|
}
|
|
}
|
|
|
|
if (CollectionNameStrings.Num() > 0)
|
|
{
|
|
CollectionNameStrings.Sort();
|
|
return FString::Join(CollectionNameStrings, TEXT(", "));
|
|
}
|
|
}
|
|
|
|
return FString();
|
|
}
|
|
|
|
void FCollectionManager::CreateUniqueCollectionName(const FName& BaseName, ECollectionShareType::Type ShareType, FName& OutCollectionName) const
|
|
{
|
|
FCollectionLock_Read Guard(Lock);
|
|
int32 IntSuffix = 1;
|
|
bool CollectionAlreadyExists = false;
|
|
do
|
|
{
|
|
if (IntSuffix <= 1)
|
|
{
|
|
OutCollectionName = BaseName;
|
|
}
|
|
else
|
|
{
|
|
OutCollectionName = *FString::Printf(TEXT("%s%d"), *BaseName.ToString(), IntSuffix);
|
|
}
|
|
|
|
CollectionAlreadyExists = CollectionExists_Locked(Guard, OutCollectionName, ShareType);
|
|
++IntSuffix;
|
|
}
|
|
while (CollectionAlreadyExists);
|
|
}
|
|
|
|
bool FCollectionManager::IsValidCollectionName(const FString& CollectionName, ECollectionShareType::Type ShareType, FText* OutError) const
|
|
{
|
|
// Make sure we are not creating an FName that is too large
|
|
if (CollectionName.Len() >= NAME_SIZE)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = FText::Format(LOCTEXT("Error_CollectionNameTooLong","This collection name is too long ({0} characters), the maximum is {1}. Please choose a shorter name. Collection name: {2}"),
|
|
FText::AsNumber(CollectionName.Len()), FText::AsNumber(NAME_SIZE), FText::FromString(CollectionName));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FName CollectionNameFinal = *CollectionName;
|
|
|
|
// Make sure the we actually have a new name set
|
|
if (CollectionNameFinal.IsNone())
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_CollectionNameEmptyOrNone", "This collection name cannot be empty or 'None'.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Make sure the new name only contains valid characters
|
|
if (!CollectionNameFinal.IsValidXName(INVALID_OBJECTNAME_CHARACTERS INVALID_LONGPACKAGE_CHARACTERS, OutError))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
// Make sure we're not duplicating an existing collection name
|
|
// NB: Ok to call public function here because we don't need acquire a lock for the previous checks
|
|
if (CollectionExists(CollectionNameFinal, ShareType))
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = FText::Format(LOCTEXT("Error_CollectionAlreadyExists", "A collection already exists with the name '{0}'."), FText::FromName(CollectionNameFinal));
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::CreateCollection(FName CollectionName, ECollectionShareType::Type ShareType, ECollectionStorageMode::Type StorageMode, FText* OutError)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if (!IsValidCollectionName(CollectionName.ToString(), ShareType, OutError))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
|
|
// Try to add the collection
|
|
const bool bUseSCC = ShouldUseSCC(ShareType);
|
|
const FString CollectionFilename = GetCollectionFilename(CollectionName, ShareType);
|
|
|
|
// Validate collection name as file name
|
|
FText UnusedError;
|
|
bool bFilenameValid = FFileHelper::IsFilenameValidForSaving(CollectionName.ToString(), OutError ? *OutError : UnusedError);
|
|
if (!bFilenameValid)
|
|
{
|
|
return false;
|
|
}
|
|
|
|
TSharedRef<FCollection> NewCollection = MakeShareable(new FCollection(CollectionFilename, bUseSCC, StorageMode));
|
|
if (!AddCollection(Guard, NewCollection, ShareType))
|
|
{
|
|
// Failed to add the collection, it already exists
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_AlreadyExists", "The collection already exists.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
constexpr bool bForceCommitToRevisionControl = true;
|
|
if (!InternalSaveCollection(Guard, NewCollection, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
// Collection failed to save, remove it from the cache
|
|
RemoveCollection(Guard, NewCollection, ShareType);
|
|
return false;
|
|
}
|
|
|
|
CollectionFileCaches[ShareType]->IgnoreNewFile(NewCollection->GetSourceFilename());
|
|
}
|
|
|
|
// Collection saved!
|
|
// Broadcast events outside of lock
|
|
CollectionCreatedEvent.Broadcast(FCollectionNameType(CollectionName, ShareType));
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::RenameCollection(FName CurrentCollectionName, ECollectionShareType::Type CurrentShareType, FName NewCollectionName, ECollectionShareType::Type NewShareType, FText* OutError)
|
|
{
|
|
if (!ensure(CurrentShareType < ECollectionShareType::CST_All) || !ensure(NewShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType OriginalCollectionKey(CurrentCollectionName, CurrentShareType);
|
|
const FCollectionNameType NewCollectionKey(NewCollectionName, NewShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
|
|
TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(OriginalCollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// The collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Add the new collection
|
|
TSharedPtr<FCollection> NewCollection;
|
|
{
|
|
const bool bUseSCC = ShouldUseSCC(NewShareType);
|
|
const FString NewCollectionFilename = GetCollectionFilename(NewCollectionName, NewShareType);
|
|
|
|
// Create an exact copy of the collection using its new path - this will preserve its GUID and avoid losing hierarchy data
|
|
NewCollection = (*CollectionRefPtr)->Clone(NewCollectionFilename, bUseSCC, ECollectionCloneMode::Exact);
|
|
if (!AddCollection(Guard, NewCollection.ToSharedRef(), NewShareType))
|
|
{
|
|
// Failed to add the collection, it already exists
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_AlreadyExists", "The collection already exists.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
bool bForceCommitToRevisionControl = true;
|
|
if (!InternalSaveCollection(Guard, NewCollection.ToSharedRef(), OutError, bForceCommitToRevisionControl))
|
|
{
|
|
// Collection failed to save, remove it from the cache
|
|
RemoveCollection(Guard, NewCollection.ToSharedRef(), NewShareType);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Remove the old collection
|
|
{
|
|
FText UnusedError;
|
|
if ((*CollectionRefPtr)->DeleteSourceFile(OutError ? *OutError : UnusedError))
|
|
{
|
|
CollectionFileCaches[CurrentShareType]->IgnoreDeletedFile((*CollectionRefPtr)->GetSourceFilename());
|
|
|
|
RemoveCollection(Guard, *CollectionRefPtr, CurrentShareType);
|
|
}
|
|
else
|
|
{
|
|
// Failed to remove the old collection, so remove the collection we created.
|
|
NewCollection->DeleteSourceFile(OutError ? *OutError : UnusedError);
|
|
RemoveCollection(Guard, NewCollection.ToSharedRef(), NewShareType);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
CollectionFileCaches[NewShareType]->IgnoreNewFile(NewCollection->GetSourceFilename());
|
|
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
|
|
// Success, broadcast events outside of lock
|
|
CollectionRenamedEvent.Broadcast(OriginalCollectionKey, NewCollectionKey);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::ReparentCollection(FName CollectionName, ECollectionShareType::Type ShareType, FName ParentCollectionName, ECollectionShareType::Type ParentShareType, FText* OutError)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All) || (!ParentCollectionName.IsNone() && !ensure(ParentShareType < ECollectionShareType::CST_All)))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
TOptional<FCollectionNameType> OldParentCollectionKey;
|
|
TOptional<FCollectionNameType> NewParentCollectionKey;
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::RecursionWorker);
|
|
|
|
TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// The collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FGuid OldParentGuid = (*CollectionRefPtr)->GetParentCollectionGuid();
|
|
FGuid NewParentGuid;
|
|
|
|
if (!ParentCollectionName.IsNone())
|
|
{
|
|
// Find and set the new parent GUID
|
|
NewParentCollectionKey = FCollectionNameType(ParentCollectionName, ParentShareType);
|
|
TSharedRef<FCollection>* const ParentCollectionRefPtr = AvailableCollections.Find(NewParentCollectionKey.GetValue());
|
|
if (!ParentCollectionRefPtr)
|
|
{
|
|
// The collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
// Does the parent collection need saving in order to have a stable GUID?
|
|
if ((*ParentCollectionRefPtr)->GetCollectionVersion() < ECollectionVersion::AddedCollectionGuid)
|
|
{
|
|
bool bForceCommitToRevisionControl = false;
|
|
// Try and re-save the parent collection now
|
|
if (InternalSaveCollection(Guard, *ParentCollectionRefPtr, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
CollectionFileCaches[ParentShareType]->IgnoreFileModification((*ParentCollectionRefPtr)->GetSourceFilename());
|
|
}
|
|
else
|
|
{
|
|
return false;
|
|
}
|
|
}
|
|
|
|
if (!IsValidParentCollection_Locked(Guard, CollectionName, ShareType, ParentCollectionName, ParentShareType, OutError))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
NewParentGuid = (*ParentCollectionRefPtr)->GetCollectionGuid();
|
|
}
|
|
|
|
// Anything changed?
|
|
if (OldParentGuid == NewParentGuid)
|
|
{
|
|
return true;
|
|
}
|
|
|
|
(*CollectionRefPtr)->SetParentCollectionGuid(NewParentGuid);
|
|
|
|
// Try and save with the new parent GUID
|
|
bool bForceCommitToRevisionControl = false;
|
|
if (InternalSaveCollection(Guard, *CollectionRefPtr, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
CollectionFileCaches[ShareType]->IgnoreFileModification((*CollectionRefPtr)->GetSourceFilename());
|
|
}
|
|
else
|
|
{
|
|
// Failed to save... rollback the collection to use its old parent GUID
|
|
(*CollectionRefPtr)->SetParentCollectionGuid(OldParentGuid);
|
|
return false;
|
|
}
|
|
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Names);
|
|
|
|
// Find the old parent so we can notify about the change
|
|
{
|
|
const TMap<FGuid, FCollectionNameType>& CachedCollectionNamesFromGuids = CollectionCache->GetCachedCollectionNamesFromGuids(Guard);
|
|
|
|
const FCollectionNameType* const OldParentCollectionKeyPtr = CachedCollectionNamesFromGuids.Find(OldParentGuid);
|
|
if (OldParentCollectionKeyPtr)
|
|
{
|
|
OldParentCollectionKey = *OldParentCollectionKeyPtr;
|
|
}
|
|
}
|
|
}
|
|
|
|
// Success, broadcast event outside of lock
|
|
CollectionReparentedEvent.Broadcast(CollectionKey, OldParentCollectionKey, NewParentCollectionKey);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::DestroyCollection(FName CollectionName, ECollectionShareType::Type ShareType, FText* OutError)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
|
|
TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// The collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FText UnusedError;
|
|
if ((*CollectionRefPtr)->DeleteSourceFile(OutError ? *OutError : UnusedError))
|
|
{
|
|
CollectionFileCaches[ShareType]->IgnoreDeletedFile((*CollectionRefPtr)->GetSourceFilename());
|
|
|
|
RemoveCollection(Guard, *CollectionRefPtr, ShareType);
|
|
}
|
|
else
|
|
{
|
|
// Failed to delete the source file
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Broadcast event outside of lock
|
|
CollectionDestroyedEvent.Broadcast(CollectionKey);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::AddToCollection(FName CollectionName, ECollectionShareType::Type ShareType, const FSoftObjectPath& ObjectPath, FText* OutError)
|
|
{
|
|
return AddToCollection(CollectionName, ShareType, MakeArrayView(&ObjectPath, 1));
|
|
}
|
|
|
|
bool FCollectionManager::AddToCollection(FName CollectionName, ECollectionShareType::Type ShareType, TConstArrayView<FSoftObjectPath> ObjectPaths, int32* OutNumAdded, FText* OutError)
|
|
{
|
|
if (OutNumAdded)
|
|
{
|
|
*OutNumAdded = 0;
|
|
}
|
|
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// Collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ((*CollectionRefPtr)->GetStorageMode() != ECollectionStorageMode::Static)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_AddNeedsStaticCollection", "Objects can only be added to static collections.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
int32 NumAdded = 0;
|
|
for (const FSoftObjectPath& ObjectPath : ObjectPaths)
|
|
{
|
|
if ((*CollectionRefPtr)->AddObjectToCollection(ObjectPath))
|
|
{
|
|
NumAdded++;
|
|
}
|
|
}
|
|
|
|
if (NumAdded > 0)
|
|
{
|
|
constexpr bool bForceCommitToRevisionControl = false;
|
|
if (InternalSaveCollection(Guard, *CollectionRefPtr, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
CollectionFileCaches[ShareType]->IgnoreFileModification((*CollectionRefPtr)->GetSourceFilename());
|
|
|
|
// Added and saved
|
|
if (OutNumAdded)
|
|
{
|
|
*OutNumAdded = NumAdded;
|
|
}
|
|
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
// Fall out of scope to return
|
|
}
|
|
else
|
|
{
|
|
// Added but not saved, revert the add
|
|
for (const FSoftObjectPath& ObjectPath : ObjectPaths)
|
|
{
|
|
(*CollectionRefPtr)->RemoveObjectFromCollection(ObjectPath);
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
// Failed to add, all of the objects were already in the collection
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_AlreadyInCollection", "All of the assets were already in the collection.");
|
|
}
|
|
return false;
|
|
}
|
|
}
|
|
|
|
// Broadast event out of lock
|
|
AssetsAddedToCollectionDelegate.Broadcast(CollectionKey, ObjectPaths);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::RemoveFromCollection(FName CollectionName, ECollectionShareType::Type ShareType, const FSoftObjectPath& ObjectPath, FText* OutError)
|
|
{
|
|
return RemoveFromCollection(CollectionName, ShareType, MakeArrayView(&ObjectPath, 1), nullptr, OutError);
|
|
}
|
|
|
|
bool FCollectionManager::RemoveFromCollection(FName CollectionName, ECollectionShareType::Type ShareType, TConstArrayView<FSoftObjectPath> ObjectPaths, int32* OutNumRemoved, FText* OutError)
|
|
{
|
|
if (OutNumRemoved)
|
|
{
|
|
*OutNumRemoved = 0;
|
|
}
|
|
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// Collection not found
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ((*CollectionRefPtr)->GetStorageMode() != ECollectionStorageMode::Static)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_RemoveNeedsStaticCollection", "Objects can only be removed from static collections.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
TArray<FSoftObjectPath> RemovedAssets;
|
|
for (const FSoftObjectPath& ObjectPath : ObjectPaths)
|
|
{
|
|
if ((*CollectionRefPtr)->RemoveObjectFromCollection(ObjectPath))
|
|
{
|
|
RemovedAssets.Add(ObjectPath);
|
|
}
|
|
}
|
|
|
|
if (RemovedAssets.Num() == 0)
|
|
{
|
|
// Failed to remove, none of the objects were in the collection
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_NotInCollection", "None of the assets were in the collection.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
constexpr bool bForceCommitToRevisionControl = false;
|
|
if (!InternalSaveCollection(Guard, *CollectionRefPtr, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
// Removed but not saved, revert the remove
|
|
for (const FSoftObjectPath& RemovedAssetName : RemovedAssets)
|
|
{
|
|
(*CollectionRefPtr)->AddObjectToCollection(RemovedAssetName);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
CollectionFileCaches[ShareType]->IgnoreFileModification((*CollectionRefPtr)->GetSourceFilename());
|
|
|
|
// Removed and saved
|
|
if (OutNumRemoved)
|
|
{
|
|
*OutNumRemoved = RemovedAssets.Num();
|
|
}
|
|
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
|
|
// Broadcast event out of lock
|
|
AssetsRemovedFromCollectionDelegate.Broadcast(CollectionKey, ObjectPaths);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::SetDynamicQueryText(FName CollectionName, ECollectionShareType::Type ShareType, const FString& InQueryText, FText* OutError)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// Collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ((*CollectionRefPtr)->GetStorageMode() != ECollectionStorageMode::Dynamic)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_SetNeedsDynamicCollection", "Search queries can only be set on dynamic collections.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
(*CollectionRefPtr)->SetDynamicQueryText(InQueryText);
|
|
|
|
constexpr bool bForceCommitToRevisionControl = true;
|
|
if (!InternalSaveCollection(Guard, *CollectionRefPtr, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
return false;
|
|
}
|
|
CollectionFileCaches[ShareType]->IgnoreFileModification((*CollectionRefPtr)->GetSourceFilename());
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
|
|
// Broadcast event outside of lock
|
|
CollectionUpdatedEvent.Broadcast(CollectionKey);
|
|
return true;
|
|
|
|
}
|
|
|
|
bool FCollectionManager::GetDynamicQueryText(FName CollectionName, ECollectionShareType::Type ShareType, FString& OutQueryText, FText* OutError) const
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FCollectionLock_Read Guard(Lock);
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// Collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ((*CollectionRefPtr)->GetStorageMode() != ECollectionStorageMode::Dynamic)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_GetNeedsDynamicCollection", "Search queries can only be got from dynamic collections.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
OutQueryText = (*CollectionRefPtr)->GetDynamicQueryText();
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::TestDynamicQuery(FName CollectionName, ECollectionShareType::Type ShareType, const ITextFilterExpressionContext& InContext, bool& OutResult, FText* OutError) const
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FCollectionLock_Read Guard(Lock);
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// Collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ((*CollectionRefPtr)->GetStorageMode() != ECollectionStorageMode::Dynamic)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_TestNeedsDynamicCollection", "Search queries can only be tested on dynamic collections.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
(*CollectionRefPtr)->PrepareDynamicQuery();
|
|
OutResult = (*CollectionRefPtr)->TestDynamicQuery(InContext);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::EmptyCollection(FName CollectionName, ECollectionShareType::Type ShareType, FText* OutError)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
// Collection doesn't exist
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
if ((*CollectionRefPtr)->IsEmpty())
|
|
{
|
|
// Already empty - nothing to do
|
|
return true;
|
|
}
|
|
|
|
(*CollectionRefPtr)->Empty();
|
|
|
|
constexpr bool bForceCommitToRevisionControl = true;
|
|
if (InternalSaveCollection(Guard, *CollectionRefPtr, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
return false;
|
|
}
|
|
CollectionFileCaches[ShareType]->IgnoreFileModification((*CollectionRefPtr)->GetSourceFilename());
|
|
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
|
|
// Broadcast event outside of lock
|
|
CollectionUpdatedEvent.Broadcast(CollectionKey);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::SaveCollection(FName CollectionName, ECollectionShareType::Type ShareType, FText* OutError)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FCollectionStatusInfo StatusInfo = (*CollectionRefPtr)->GetStatusInfo();
|
|
|
|
const bool bNeedsSave = StatusInfo.bIsDirty || (StatusInfo.SCCState.IsValid() && StatusInfo.SCCState->IsModified());
|
|
if (!bNeedsSave)
|
|
{
|
|
// No changes - nothing to save
|
|
return true;
|
|
}
|
|
|
|
constexpr bool bForceCommitToRevisionControl = true;
|
|
if (!InternalSaveCollection(Guard, *CollectionRefPtr, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CollectionFileCaches[ShareType]->IgnoreFileModification((*CollectionRefPtr)->GetSourceFilename());
|
|
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
|
|
// Broadcast event out of lock
|
|
CollectionUpdatedEvent.Broadcast(CollectionKey);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::UpdateCollection(FName CollectionName, ECollectionShareType::Type ShareType, FText* OutError)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
FText UnusedError;
|
|
if (!(*CollectionRefPtr)->Update(OutError ? *OutError : UnusedError))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CollectionFileCaches[ShareType]->IgnoreFileModification((*CollectionRefPtr)->GetSourceFilename());
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
|
|
// Broadcast event outside of lock
|
|
CollectionUpdatedEvent.Broadcast(CollectionKey);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::GetCollectionStatusInfo(FName CollectionName, ECollectionShareType::Type ShareType, FCollectionStatusInfo& OutStatusInfo, FText* OutError) const
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FCollectionManager::GetCollectionStatusInfo);
|
|
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FCollectionLock_Read Guard(Lock);
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
OutStatusInfo = (*CollectionRefPtr)->GetStatusInfo();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FCollectionManager::HasCollectionColors(TArray<FLinearColor>* OutColors) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Colors);
|
|
const TArray<FLinearColor>& CollectionColors = CollectionCache->GetCachedColors(Guard);
|
|
if (OutColors)
|
|
{
|
|
*OutColors = CollectionColors;
|
|
}
|
|
return CollectionColors.Num() > 0;
|
|
}
|
|
|
|
bool FCollectionManager::GetCollectionColor(FName CollectionName, ECollectionShareType::Type ShareType, TOptional<FLinearColor>& OutColor, FText* OutError) const
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FCollectionLock_Read Guard(Lock);
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
OutColor = (*CollectionRefPtr)->GetCollectionColor();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FCollectionManager::SetCollectionColor(FName CollectionName, ECollectionShareType::Type ShareType, const TOptional<FLinearColor>& NewColor, FText* OutError)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (!CollectionRefPtr)
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
return false;
|
|
}
|
|
|
|
(*CollectionRefPtr)->SetCollectionColor(NewColor);
|
|
|
|
constexpr bool bForceCommitToRevisionControl = false;
|
|
if (!InternalSaveCollection(Guard, *CollectionRefPtr, OutError, bForceCommitToRevisionControl))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
CollectionFileCaches[ShareType]->IgnoreFileModification((*CollectionRefPtr)->GetSourceFilename());
|
|
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
|
|
// Broadcast event outside of lock
|
|
CollectionUpdatedEvent.Broadcast(CollectionKey);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::GetCollectionStorageMode(FName CollectionName, ECollectionShareType::Type ShareType, ECollectionStorageMode::Type& OutStorageMode, FText* OutError) const
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FCollectionLock_Read Guard(Lock);
|
|
const FCollectionNameType CollectionKey(CollectionName, ShareType);
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
OutStorageMode = (*CollectionRefPtr)->GetStorageMode();
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_DoesntExist", "The collection doesn't exist.");
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
bool FCollectionManager::IsObjectInCollection(const FSoftObjectPath& ObjectPath, FName CollectionName, ECollectionShareType::Type ShareType,ECollectionRecursionFlags::Flags RecursionMode, FText* OutError) const
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::RecursionWorker);
|
|
bool bFoundObject = false;
|
|
|
|
auto IsObjectInCollectionWorker = [AvailableCollections=&AvailableCollections, ObjectPath, &bFoundObject](const FCollectionNameType& InCollectionKey, ECollectionRecursionFlags::Flag InReason) -> FCollectionManagerCache::ERecursiveWorkerFlowControl
|
|
{
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections->Find(InCollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
bFoundObject = (*CollectionRefPtr)->IsObjectInCollection(ObjectPath);
|
|
}
|
|
return (bFoundObject) ? FCollectionManagerCache::ERecursiveWorkerFlowControl::Stop : FCollectionManagerCache::ERecursiveWorkerFlowControl::Continue;
|
|
};
|
|
|
|
CollectionCache->RecursionHelper_DoWork(Guard, FCollectionNameType(CollectionName, ShareType), RecursionMode, IsObjectInCollectionWorker);
|
|
|
|
return bFoundObject;
|
|
}
|
|
|
|
bool FCollectionManager::IsValidParentCollection(FName CollectionName, ECollectionShareType::Type ShareType, FName ParentCollectionName,ECollectionShareType::Type ParentShareType, FText* OutError) const
|
|
{
|
|
FCollectionLock_RW Guard(Lock);
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::RecursionWorker);
|
|
return IsValidParentCollection_Locked(Guard, CollectionName, ShareType, ParentCollectionName, ParentShareType, OutError);
|
|
}
|
|
|
|
bool FCollectionManager::IsValidParentCollection_Locked(FCollectionLock& InGuard, FName CollectionName, ECollectionShareType::Type ShareType, FName ParentCollectionName, ECollectionShareType::Type ParentShareType, FText* OutError) const
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All) || (!ParentCollectionName.IsNone() && !ensure(ParentShareType < ECollectionShareType::CST_All)))
|
|
{
|
|
// Bad share type
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("Error_Internal", "There was an internal error.");
|
|
}
|
|
return true;
|
|
}
|
|
|
|
if (ParentCollectionName.IsNone())
|
|
{
|
|
// Clearing the parent is always valid
|
|
return true;
|
|
}
|
|
|
|
bool bValidParent = true;
|
|
auto IsValidParentCollectionWorker = [OutError, CollectionName, ShareType, &bValidParent, AvailableCollections=&AvailableCollections]
|
|
(const FCollectionNameType& InCollectionKey, ECollectionRecursionFlags::Flag InReason) -> FCollectionManagerCache::ERecursiveWorkerFlowControl
|
|
{
|
|
const bool bMatchesCollectionBeingReparented = (CollectionName == InCollectionKey.Name && ShareType == InCollectionKey.Type);
|
|
if (bMatchesCollectionBeingReparented)
|
|
{
|
|
bValidParent = false;
|
|
if (OutError)
|
|
{
|
|
*OutError = (InReason == ECollectionRecursionFlags::Self)
|
|
? LOCTEXT("InvalidParent_CannotParentToSelf", "A collection cannot be parented to itself")
|
|
: LOCTEXT("InvalidParent_CannotParentToChildren", "A collection cannot be parented to its children");
|
|
}
|
|
return FCollectionManagerCache::ERecursiveWorkerFlowControl::Stop;
|
|
}
|
|
|
|
const bool bIsValidChildType = ECollectionShareType::IsValidChildType(InCollectionKey.Type, ShareType);
|
|
if (!bIsValidChildType)
|
|
{
|
|
bValidParent = false;
|
|
if (OutError)
|
|
{
|
|
*OutError = FText::Format(LOCTEXT("InvalidParent_InvalidChildType", "A {0} collection cannot contain a {1} collection"), ECollectionShareType::ToText(InCollectionKey.Type), ECollectionShareType::ToText(ShareType));
|
|
}
|
|
return FCollectionManagerCache::ERecursiveWorkerFlowControl::Stop;
|
|
}
|
|
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections->Find(InCollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
const ECollectionStorageMode::Type StorageMode = (*CollectionRefPtr)->GetStorageMode();
|
|
if (StorageMode == ECollectionStorageMode::Dynamic)
|
|
{
|
|
bValidParent = false;
|
|
if (OutError)
|
|
{
|
|
*OutError = LOCTEXT("InvalidParent_InvalidParentStorageType", "A dynamic collection cannot contain child collections");
|
|
}
|
|
return FCollectionManagerCache::ERecursiveWorkerFlowControl::Stop;
|
|
}
|
|
}
|
|
|
|
return FCollectionManagerCache::ERecursiveWorkerFlowControl::Continue;
|
|
};
|
|
|
|
CollectionCache->RecursionHelper_DoWork(InGuard, FCollectionNameType(ParentCollectionName, ParentShareType), ECollectionRecursionFlags::SelfAndParents, IsValidParentCollectionWorker);
|
|
|
|
return bValidParent;
|
|
}
|
|
|
|
void FCollectionManager::HandleFixupRedirectors(ICollectionRedirectorFollower& InRedirectorFollower)
|
|
{
|
|
if (bNoFixupRedirectors)
|
|
{
|
|
return;
|
|
}
|
|
|
|
TArray<FCollectionNameType> UpdatedCollections;
|
|
TArray<FSoftObjectPath> AddedObjects;
|
|
TArray<FSoftObjectPath> RemovedObjects;
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Objects);
|
|
|
|
const double LoadStartTime = FPlatformTime::Seconds();
|
|
|
|
TArray<TPair<FSoftObjectPath, FSoftObjectPath>> ObjectsToRename;
|
|
|
|
// Build up the list of redirected object into rename pairs
|
|
{
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& CachedObjects = CollectionCache->GetCachedObjects(Guard);
|
|
for (const TPair<FSoftObjectPath, TArray<FObjectCollectionInfo>>& CachedObjectInfo : CachedObjects)
|
|
{
|
|
FSoftObjectPath NewObjectPath;
|
|
if (InRedirectorFollower.FixupObject(CachedObjectInfo.Key, NewObjectPath))
|
|
{
|
|
ObjectsToRename.Emplace(CachedObjectInfo.Key, NewObjectPath);
|
|
}
|
|
}
|
|
}
|
|
|
|
AddedObjects.Reserve(ObjectsToRename.Num());
|
|
RemovedObjects.Reserve(ObjectsToRename.Num());
|
|
|
|
// Handle the rename for each redirected object
|
|
for (const TPair<FSoftObjectPath, FSoftObjectPath>& ObjectToRename : ObjectsToRename)
|
|
{
|
|
AddedObjects.Add(ObjectToRename.Value);
|
|
RemovedObjects.Add(ObjectToRename.Key);
|
|
|
|
ReplaceObjectInCollections(Guard, ObjectToRename.Key, ObjectToRename.Value, UpdatedCollections);
|
|
}
|
|
|
|
UE_LOG(LogCollectionManager, Log, TEXT( "Fixed up redirectors for %d collections in %0.6f seconds (updated %d objects)" ), AvailableCollections.Num(), FPlatformTime::Seconds() - LoadStartTime, ObjectsToRename.Num());
|
|
|
|
for (const TPair<FSoftObjectPath, FSoftObjectPath>& ObjectToRename : ObjectsToRename)
|
|
{
|
|
UE_LOG(LogCollectionManager, Verbose, TEXT( "\tRedirected '%s' to '%s'" ), *ObjectToRename.Key.ToString(), *ObjectToRename.Value.ToString());
|
|
}
|
|
if (UpdatedCollections.Num() > 0)
|
|
{
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
}
|
|
|
|
// Notify every collection that changed, outside of the lock
|
|
for (const FCollectionNameType& UpdatedCollection : UpdatedCollections)
|
|
{
|
|
AssetsRemovedFromCollectionDelegate.Broadcast(UpdatedCollection, RemovedObjects);
|
|
AssetsAddedToCollectionDelegate.Broadcast(UpdatedCollection, AddedObjects);
|
|
}
|
|
}
|
|
|
|
bool FCollectionManager::HandleRedirectorsDeleted(TConstArrayView<FSoftObjectPath> ObjectPaths, FText* OutError)
|
|
{
|
|
bool bSavedAllCollections = true;
|
|
TArray<FCollectionNameType> UpdatedCollections;
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
TSet<FCollectionNameType> CollectionsToSave;
|
|
FTextBuilder ErrorBuilder;
|
|
|
|
for (const FSoftObjectPath& ObjectPath : ObjectPaths)
|
|
{
|
|
// We don't have a cache for on-disk objects, so we have to do this the slower way and query each collection in turn
|
|
for (const TPair<FCollectionNameType, TSharedRef<FCollection>>& AvailableCollection : AvailableCollections)
|
|
{
|
|
const FCollectionNameType& CollectionKey = AvailableCollection.Key;
|
|
const TSharedRef<FCollection>& Collection = AvailableCollection.Value;
|
|
|
|
if (Collection->IsRedirectorInCollection(ObjectPath))
|
|
{
|
|
CollectionsToSave.Add(CollectionKey);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const FCollectionNameType& CollectionKey : CollectionsToSave)
|
|
{
|
|
if (TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(CollectionKey))
|
|
{
|
|
const TSharedRef<FCollection>& Collection = *CollectionRefPtr;
|
|
|
|
FText SaveError;
|
|
constexpr bool bForceCommitToRevisionControl = false;
|
|
if (InternalSaveCollection(Guard, Collection, &SaveError, bForceCommitToRevisionControl))
|
|
{
|
|
CollectionFileCaches[CollectionKey.Type]->IgnoreFileModification(Collection->GetSourceFilename());
|
|
|
|
UpdatedCollections.Add(CollectionKey);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCollectionManager, Error, TEXT("Error saving collection on redirector deletion: %s"), *SaveError.ToString());
|
|
ErrorBuilder.AppendLine(SaveError);
|
|
bSavedAllCollections = false;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (OutError)
|
|
{
|
|
*OutError = ErrorBuilder.ToText();
|
|
}
|
|
}
|
|
|
|
// Notify every collection that changed, outside of the lock
|
|
for (const FCollectionNameType& UpdatedCollection : UpdatedCollections)
|
|
{
|
|
AssetsRemovedFromCollectionDelegate.Broadcast(UpdatedCollection, ObjectPaths);
|
|
}
|
|
|
|
return bSavedAllCollections;
|
|
}
|
|
|
|
bool FCollectionManager::HandleRedirectorDeleted(const FSoftObjectPath& ObjectPath, FText* Error)
|
|
{
|
|
return HandleRedirectorsDeleted(MakeArrayView(&ObjectPath, 1), Error);
|
|
}
|
|
|
|
void FCollectionManager::HandleObjectRenamed(const FSoftObjectPath& OldObjectPath, const FSoftObjectPath& NewObjectPath)
|
|
{
|
|
TArray<FCollectionNameType> UpdatedCollections;
|
|
TArray<FSoftObjectPath> AddedObjects;
|
|
TArray<FSoftObjectPath> RemovedObjects;
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
|
|
ReplaceObjectInCollections(Guard, OldObjectPath, NewObjectPath, UpdatedCollections);
|
|
|
|
AddedObjects.Add(NewObjectPath);
|
|
RemovedObjects.Add(OldObjectPath);
|
|
|
|
if (UpdatedCollections.Num() > 0)
|
|
{
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
}
|
|
|
|
// Notify every collection that changed, outside the lock
|
|
for (const FCollectionNameType& UpdatedCollection : UpdatedCollections)
|
|
{
|
|
AssetsRemovedFromCollectionDelegate.Broadcast(UpdatedCollection, RemovedObjects);
|
|
AssetsAddedToCollectionDelegate.Broadcast(UpdatedCollection, AddedObjects);
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::HandleObjectsDeleted(TConstArrayView<FSoftObjectPath> ObjectPaths)
|
|
{
|
|
TArray<FCollectionNameType> UpdatedCollections;
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
|
|
if (SuppressObjectDeletionRefCount.load() > 0)
|
|
{
|
|
DeferredDeletedObjects.Append(ObjectPaths);
|
|
return;
|
|
}
|
|
|
|
CollectionCache->UpdateCaches(Guard, ECollectionCacheFlags::Objects);
|
|
|
|
for (const FSoftObjectPath& ObjectPath : ObjectPaths)
|
|
{
|
|
RemoveObjectFromCollections(Guard, ObjectPath, UpdatedCollections);
|
|
}
|
|
|
|
if (UpdatedCollections.Num() > 0)
|
|
{
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
}
|
|
|
|
// Notify every collection that changed, outside the lock
|
|
for (const FCollectionNameType& UpdatedCollection : UpdatedCollections)
|
|
{
|
|
AssetsRemovedFromCollectionDelegate.Broadcast(UpdatedCollection, ObjectPaths);
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::HandleObjectDeleted(const FSoftObjectPath& ObjectPath)
|
|
{
|
|
HandleObjectsDeleted(MakeArrayView(&ObjectPath, 1));
|
|
}
|
|
|
|
void FCollectionManager::SuppressObjectDeletionHandling()
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
SuppressObjectDeletionRefCount.fetch_add(1);
|
|
}
|
|
|
|
void FCollectionManager::ResumeObjectDeletionHandling()
|
|
{
|
|
check(IsInGameThread());
|
|
|
|
int32 PrevRefCount = SuppressObjectDeletionRefCount.fetch_sub(1);
|
|
ensure(PrevRefCount >= 1);
|
|
|
|
if (PrevRefCount == 1)
|
|
{
|
|
if (!DeferredDeletedObjects.IsEmpty())
|
|
{
|
|
HandleObjectsDeleted(DeferredDeletedObjects);
|
|
DeferredDeletedObjects.Empty();
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FCollectionManager::TickFileCache(float InDeltaTime)
|
|
{
|
|
QUICK_SCOPE_CYCLE_COUNTER(STAT_FCollectionManager_TickFileCache);
|
|
|
|
enum class ECollectionFileAction : uint8
|
|
{
|
|
None,
|
|
AddCollection,
|
|
MergeCollection,
|
|
RemoveCollection,
|
|
};
|
|
|
|
// Cached events to fire when we release the lock
|
|
TArray<TTuple<ECollectionFileAction, FCollectionNameType>> Events;
|
|
{
|
|
// Acquire write lock immediately so we don't need to deal with state change during promotion
|
|
FCollectionLock_Write Guard(Lock);
|
|
|
|
// Process changes that have happened outside of the collection manager
|
|
for (int32 CacheIdx = 0; CacheIdx < ECollectionShareType::CST_All; ++CacheIdx)
|
|
{
|
|
const ECollectionShareType::Type ShareType = ECollectionShareType::Type(CacheIdx);
|
|
|
|
TSharedPtr<DirectoryWatcher::FFileCache>& FileCache = CollectionFileCaches[CacheIdx];
|
|
if (!FileCache.IsValid())
|
|
{
|
|
continue;
|
|
}
|
|
|
|
FileCache->Tick();
|
|
|
|
const TArray<DirectoryWatcher::FUpdateCacheTransaction> FileCacheChanges = FileCache->GetOutstandingChanges();
|
|
for (const DirectoryWatcher::FUpdateCacheTransaction& FileCacheChange : FileCacheChanges)
|
|
{
|
|
const FString CollectionFilename = FileCacheChange.Filename.Get();
|
|
if (FPaths::GetExtension(CollectionFilename) != CollectionExtension)
|
|
{
|
|
continue;
|
|
}
|
|
|
|
const FName CollectionName = *FPaths::GetBaseFilename(CollectionFilename);
|
|
|
|
ECollectionFileAction CollectionFileAction = ECollectionFileAction::None;
|
|
switch (FileCacheChange.Action)
|
|
{
|
|
case DirectoryWatcher::EFileAction::Added:
|
|
case DirectoryWatcher::EFileAction::Modified:
|
|
// File was added or modified, but does this collection already exist?
|
|
CollectionFileAction = (AvailableCollections.Contains(FCollectionNameType(CollectionName, ShareType)))
|
|
? ECollectionFileAction::MergeCollection
|
|
: ECollectionFileAction::AddCollection;
|
|
break;
|
|
|
|
case DirectoryWatcher::EFileAction::Removed:
|
|
// File was removed, but does this collection actually exist?
|
|
CollectionFileAction = (AvailableCollections.Contains(FCollectionNameType(CollectionName, ShareType)))
|
|
? ECollectionFileAction::RemoveCollection
|
|
: ECollectionFileAction::None;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
switch (CollectionFileAction)
|
|
{
|
|
case ECollectionFileAction::AddCollection:
|
|
{
|
|
const bool bUseSCC = ShouldUseSCC(ShareType);
|
|
|
|
FText LoadErrorText;
|
|
TSharedRef<FCollection> NewCollection = MakeShareable(new FCollection(GetCollectionFilename(CollectionName, ShareType), bUseSCC, ECollectionStorageMode::Static));
|
|
if (NewCollection->Load(LoadErrorText))
|
|
{
|
|
if (AddCollection(Guard, NewCollection, ShareType))
|
|
{
|
|
Events.Emplace(CollectionFileAction, FCollectionNameType(CollectionName, ShareType));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCollectionManager, Warning, TEXT("%s"), *LoadErrorText.ToString());
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ECollectionFileAction::MergeCollection:
|
|
{
|
|
TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(FCollectionNameType(CollectionName, ShareType));
|
|
check(CollectionRefPtr); // We tested AvailableCollections.Contains(...) above, so this shouldn't fail
|
|
|
|
FText LoadErrorText;
|
|
FCollection TempCollection(GetCollectionFilename(CollectionName, ShareType), /*bUseSCC*/false, ECollectionStorageMode::Static);
|
|
if (TempCollection.Load(LoadErrorText))
|
|
{
|
|
if ((*CollectionRefPtr)->Merge(TempCollection))
|
|
{
|
|
Events.Emplace(CollectionFileAction, FCollectionNameType(CollectionName, ShareType));
|
|
}
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCollectionManager, Warning, TEXT("%s"), *LoadErrorText.ToString());
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ECollectionFileAction::RemoveCollection:
|
|
{
|
|
TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(FCollectionNameType(CollectionName, ShareType));
|
|
check(CollectionRefPtr); // We tested AvailableCollections.Contains(...) above, so this shouldn't fail
|
|
|
|
RemoveCollection(Guard, *CollectionRefPtr, ShareType);
|
|
Events.Emplace(CollectionFileAction, FCollectionNameType(CollectionName, ShareType));
|
|
}
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (Events.Num() > 0)
|
|
{
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
}
|
|
}
|
|
|
|
// Broadcast events outside the lock
|
|
for (const TTuple<ECollectionFileAction, FCollectionNameType>& Event : Events)
|
|
{
|
|
switch(Event.Key)
|
|
{
|
|
case ECollectionFileAction::AddCollection:
|
|
CollectionCreatedEvent.Broadcast(Event.Value);
|
|
break;
|
|
case ECollectionFileAction::MergeCollection:
|
|
CollectionUpdatedEvent.Broadcast(Event.Value);
|
|
break;
|
|
case ECollectionFileAction::RemoveCollection:
|
|
CollectionDestroyedEvent.Broadcast(Event.Value);
|
|
break;
|
|
}
|
|
}
|
|
|
|
return true; // Tick again
|
|
}
|
|
|
|
void FCollectionManager::LoadCollections()
|
|
{
|
|
TRACE_CPUPROFILER_EVENT_SCOPE(FCollectionManager::LoadCollections)
|
|
|
|
const double LoadStartTime = FPlatformTime::Seconds();
|
|
const int32 PrevNumCollections = AvailableCollections.Num();
|
|
LLM_SCOPE_BYNAME(TEXT("CollectionManager"));
|
|
|
|
// This function should only be called during construction, don't acquire a lock here, acquire it for each individual add operation
|
|
ParallelFor(
|
|
TEXT("LoadCollections.PF"),
|
|
ECollectionShareType::CST_All,1,
|
|
[this](int32 CacheIdx)
|
|
{
|
|
const ECollectionShareType::Type ShareType = ECollectionShareType::Type(CacheIdx);
|
|
const bool bUseSCC = ShouldUseSCC(ShareType);
|
|
const FString& CollectionFolder = CollectionFolders[CacheIdx];
|
|
const TStringBuilder<256> WildCard{InPlace, CollectionFolder, TEXTVIEW("/*."), CollectionExtension};
|
|
|
|
TArray<FString> Filenames;
|
|
IFileManager::Get().FindFiles(Filenames, *WildCard, true, false);
|
|
|
|
ParallelFor(
|
|
TEXT("LoadCollections.PF"),
|
|
Filenames.Num(),1,
|
|
[this, &Filenames, &CollectionFolder, bUseSCC, ShareType](int32 FilenameIdx)
|
|
{
|
|
const FString& BaseFilename = Filenames[FilenameIdx];
|
|
const FString Filename = CollectionFolder / BaseFilename;
|
|
|
|
FText LoadErrorText;
|
|
TSharedRef<FCollection> NewCollection = MakeShareable(new FCollection(Filename, bUseSCC, ECollectionStorageMode::Static));
|
|
if (NewCollection->Load(LoadErrorText))
|
|
{
|
|
FCollectionLock_Write Guard(Lock);
|
|
AddCollection(Guard, NewCollection, ShareType);
|
|
}
|
|
else
|
|
{
|
|
UE_LOG(LogCollectionManager, Warning, TEXT("%s"), *LoadErrorText.ToString());
|
|
}
|
|
},
|
|
EParallelForFlags::Unbalanced
|
|
);
|
|
},
|
|
EParallelForFlags::Unbalanced
|
|
);
|
|
|
|
// AddCollection is assumed to be adding an empty collection, so also notify that collection cache that the collection has "changed" since loaded collections may not always be empty
|
|
FCollectionLock_Write Guard(Lock);
|
|
CollectionCache->HandleCollectionChanged(Guard);
|
|
|
|
UE_LOG(LogCollectionManager, Log, TEXT( "Loaded %d collections in %0.6f seconds" ), AvailableCollections.Num() - PrevNumCollections, FPlatformTime::Seconds() - LoadStartTime);
|
|
}
|
|
|
|
bool FCollectionManager::ShouldUseSCC(ECollectionShareType::Type ShareType) const
|
|
{
|
|
return ShareType != ECollectionShareType::CST_Local && ShareType != ECollectionShareType::CST_System;
|
|
}
|
|
|
|
FString FCollectionManager::GetCollectionFilename(const FName& InCollectionName, const ECollectionShareType::Type InCollectionShareType) const
|
|
{
|
|
FString CollectionFilename = CollectionFolders[InCollectionShareType] / InCollectionName.ToString() + TEXT(".") + CollectionExtension;
|
|
FPaths::NormalizeFilename(CollectionFilename);
|
|
return CollectionFilename;
|
|
}
|
|
|
|
bool FCollectionManager::AddCollection(FCollectionLock_Write& Guard, const TSharedRef<FCollection>& CollectionRef, ECollectionShareType::Type ShareType)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionRef->GetCollectionName(), ShareType);
|
|
if (AvailableCollections.Contains(CollectionKey))
|
|
{
|
|
UE_LOG(LogCollectionManager, Warning, TEXT("Failed to add collection '%s' because it already exists."), *CollectionRef->GetCollectionName().ToString());
|
|
return false;
|
|
}
|
|
|
|
AvailableCollections.Add(CollectionKey, CollectionRef);
|
|
CollectionCache->HandleCollectionAdded(Guard);
|
|
return true;
|
|
}
|
|
|
|
bool FCollectionManager::RemoveCollection(FCollectionLock_Write& Guard, const TSharedRef<FCollection>& CollectionRef, ECollectionShareType::Type ShareType)
|
|
{
|
|
if (!ensure(ShareType < ECollectionShareType::CST_All))
|
|
{
|
|
// Bad share type
|
|
return false;
|
|
}
|
|
|
|
const FCollectionNameType CollectionKey(CollectionRef->GetCollectionName(), ShareType);
|
|
if (AvailableCollections.Remove(CollectionKey) > 0)
|
|
{
|
|
CollectionCache->HandleCollectionRemoved(Guard);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
void FCollectionManager::RemoveObjectFromCollections(FCollectionLock_Write& Guard, const FSoftObjectPath& ObjectPath, TArray<FCollectionNameType>& OutUpdatedCollections)
|
|
{
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& CachedObjects = CollectionCache->GetCachedObjects(Guard);
|
|
|
|
const TArray<FObjectCollectionInfo>* ObjectCollectionInfosPtr = CachedObjects.Find(ObjectPath);
|
|
if (!ObjectCollectionInfosPtr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Remove this object reference from all collections that use it
|
|
for (const FObjectCollectionInfo& ObjectCollectionInfo : *ObjectCollectionInfosPtr)
|
|
{
|
|
if ((ObjectCollectionInfo.Reason & ECollectionRecursionFlags::Self) != 0)
|
|
{
|
|
// The object is contained directly within this collection (rather than coming from a parent or child collection), so remove the object reference
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(ObjectCollectionInfo.CollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
OutUpdatedCollections.AddUnique(ObjectCollectionInfo.CollectionKey);
|
|
|
|
(*CollectionRefPtr)->RemoveObjectFromCollection(ObjectPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void FCollectionManager::ReplaceObjectInCollections(FCollectionLock_Write& InGuard, const FSoftObjectPath& OldObjectPath, const FSoftObjectPath& NewObjectPath, TArray<FCollectionNameType>& OutUpdatedCollections)
|
|
{
|
|
CollectionCache->UpdateCaches(InGuard, ECollectionCacheFlags::Objects);
|
|
const TMap<FSoftObjectPath, TArray<FObjectCollectionInfo>>& CachedObjects = CollectionCache->GetCachedObjects(InGuard);
|
|
|
|
const TArray<FObjectCollectionInfo>* OldObjectCollectionInfosPtr = CachedObjects.Find(OldObjectPath);
|
|
if (!OldObjectCollectionInfosPtr)
|
|
{
|
|
return;
|
|
}
|
|
|
|
// Replace this object reference in all collections that use it
|
|
for (const FObjectCollectionInfo& OldObjectCollectionInfo : *OldObjectCollectionInfosPtr)
|
|
{
|
|
if ((OldObjectCollectionInfo.Reason & ECollectionRecursionFlags::Self) != 0)
|
|
{
|
|
// The old object is contained directly within this collection (rather than coming from a parent or child collection), so update the object reference
|
|
const TSharedRef<FCollection>* const CollectionRefPtr = AvailableCollections.Find(OldObjectCollectionInfo.CollectionKey);
|
|
if (CollectionRefPtr)
|
|
{
|
|
OutUpdatedCollections.AddUnique(OldObjectCollectionInfo.CollectionKey);
|
|
|
|
(*CollectionRefPtr)->RemoveObjectFromCollection(OldObjectPath);
|
|
(*CollectionRefPtr)->AddObjectToCollection(NewObjectPath);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
bool FCollectionManager::InternalSaveCollection(FCollectionLock_Write& InGuard, const TSharedRef<FCollection>& CollectionRef, FText* OutError, bool bForceCommitToRevisionControl)
|
|
{
|
|
TArray<FText> AdditionalChangelistText;
|
|
|
|
// Give game specific editors a chance to add lines - do this under the lock because we don't expect re-entrancy
|
|
AddToCollectionCheckinDescriptionEvent.Broadcast(CollectionRef->GetCollectionName(), AdditionalChangelistText);
|
|
|
|
// Give settings a chance to add lines
|
|
TArray<FString> SettingsLines;
|
|
|
|
const USourceControlPreferences* Settings = GetDefault<USourceControlPreferences>();
|
|
if (const FString* SpecificMatch = Settings->SpecificCollectionChangelistTags.Find(CollectionRef->GetCollectionName()))
|
|
{
|
|
// Parse input buffer into an array of lines
|
|
SpecificMatch->ParseIntoArrayLines(SettingsLines, /*bCullEmpty=*/ false);
|
|
}
|
|
SettingsLines.Append(Settings->CollectionChangelistTags);
|
|
|
|
for (const FString& OneSettingLine : SettingsLines)
|
|
{
|
|
AdditionalChangelistText.Add(FText::FromString(*OneSettingLine));
|
|
}
|
|
|
|
// Save the collection
|
|
FText UnusedError;
|
|
return CollectionRef->Save(AdditionalChangelistText, OutError ? *OutError : UnusedError, bForceCommitToRevisionControl);
|
|
}
|
|
|
|
#undef LOCTEXT_NAMESPACE
|