// Copyright Epic Games, Inc. All Rights Reserved. #include "IndexedCacheStorageManager.h" #include "IndexedCacheStorage.h" #include "Async/Async.h" #include "Async/Mutex.h" #include "Async/UniqueLock.h" #include "Experimental/Async/ConditionVariable.h" #include "HAL/FileManager.h" #include "HAL/IConsoleManager.h" #include "HAL/PlatformMisc.h" #include "Misc/ConfigCacheIni.h" #include "HAL/PlatformStackWalk.h" #include "Misc/ScopeExit.h" #include "Misc/ScopeRWLock.h" #include "String/LexFromString.h" #include "String/ParseTokens.h" DECLARE_LOG_CATEGORY_EXTERN(LogIndexedCacheStorageManager, Display, All); DEFINE_LOG_CATEGORY(LogIndexedCacheStorageManager); #define LOG_ICS(Verbosity, STRING, ...) UE_LOG(LogIndexedCacheStorageManager, Verbosity, TEXT("%s: ") STRING, ANSI_TO_TCHAR(__FUNCTION__), ##__VA_ARGS__ ) namespace Experimental { class FCacheStorageMetaData { public: enum class ECacheStorageState : uint8 { Deleted, Created, Mounted, Count }; const TCHAR* LexToString(ECacheStorageState Val) { static const TCHAR* Strings[] = { TEXT("ECacheStorageState::Deleted"), TEXT("ECacheStorageState::Created"), TEXT("ECacheStorageState::Mounted"), }; static_assert(int32(ECacheStorageState::Count) == UE_ARRAY_COUNT(Strings), ""); return Strings[int32(Val)]; } FCacheStorageMetaData(int32 InCacheIndex, const FString& InMountName, bool bCacheExists, uint64 InEarlyStartupSize) : MountName(InMountName) , CacheIndex(InCacheIndex) , EarlyStartupSize(InEarlyStartupSize) { CacheStorageState = bCacheExists ? ECacheStorageState::Created : ECacheStorageState::Deleted; } bool IsValid() const { return !MountName.IsEmpty(); } const FString& GetMountName() const { return MountName; } uint64 GetEarlyStartupSize() const { return EarlyStartupSize; } FString GetMountPath() const { FString MountedPath; IIndexedCacheStorage::Get().GetCacheStorageMountPath(MountedPath, MountName); return MountedPath; } void WaitForPredicateWhileLocked(const TFunction& Predicate) { while (!Predicate()) { ConditionVariable.Wait(Mutex); } } void NotifyWaitersWhileLocked() { ConditionVariable.NotifyAll(); } bool CreateCacheStorage(uint64 RequestNumberOfBytes) { UE::TUniqueLock Lock(Mutex); check(EarlyStartupSize == 0 || RequestNumberOfBytes == EarlyStartupSize); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("CreateCacheStorage request for %ull bytes at cache %d. Current state %s"), RequestNumberOfBytes, CacheIndex, LexToString(CacheStorageState)); WaitForPredicateWhileLocked([this]()->bool { return CacheStorageState != ECacheStorageState::Mounted; });; check(MountRefCount == 0); // Do not uncomment the code below: even if the cache is in 'Created' state, we might want to handle the case of a reallocation and // the lower level takes care of either reuse the one we asked for (same size), or destroy and create a new one //if (CacheStorageState == ECacheStorageState::Created) //{ // return true; //} UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("CreateCacheStorage creating cache %d with %ull bytes"), CacheIndex, RequestNumberOfBytes); bool bCacheExists = IIndexedCacheStorage::Get().CreateCacheStorage(RequestNumberOfBytes, CacheIndex); if (bCacheExists) { CacheStorageState = ECacheStorageState::Created; UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("CreateCacheStorage created cache %d at %s. Current state %s"), CacheIndex, *MountName, LexToString(CacheStorageState)); } else { CacheStorageState = ECacheStorageState::Deleted; UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("CreateCacheStorage failed to create cache %d at %s. Current state %s"), CacheIndex, *MountName, LexToString(CacheStorageState)); } NotifyWaitersWhileLocked(); return bCacheExists; } void DestroyCacheStorage() { UE::TUniqueLock Lock(Mutex); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("DestroyCacheStorage request at cache %d. Current state %s"), CacheIndex, LexToString(CacheStorageState)); WaitForPredicateWhileLocked([this]()->bool { return CacheStorageState != ECacheStorageState::Mounted; });; check(MountRefCount == 0); if (CacheStorageState == ECacheStorageState::Deleted) { UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("DestroyCacheStorage skipped deletion of cache %d at %s"), CacheIndex, *MountName); return; } UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("DestroyCacheStorage deleting cache %d"), CacheIndex); bool bCachedDestroyed = IIndexedCacheStorage::Get().DestroyCacheStorage(CacheIndex); if (bCachedDestroyed) { CacheStorageState = ECacheStorageState::Deleted; UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("DestroyCacheStorage deleted cache %d at %s. Current state %s"), CacheIndex, *MountName, LexToString(CacheStorageState)); } else { CacheStorageState = ECacheStorageState::Created; UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("DestroyCacheStorage failed to delete cache %d at %s. Current state %s"), CacheIndex, *MountName, LexToString(CacheStorageState)); } NotifyWaitersWhileLocked(); } uint64 GetCacheStorageInfo() { UE::TUniqueLock Lock(Mutex); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("GetCacheStorageInfo request at cache %d. Current state %s"), CacheIndex, LexToString(CacheStorageState)); uint64 CurrentDataSize = 0; uint64 CurrentJournalSize = 0; if (CacheStorageState == ECacheStorageState::Deleted) { UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("GetCacheStorageInfo cache %d is empty"), CacheIndex, CurrentDataSize); return CurrentDataSize; } UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("GetCacheStorageInfo cache %d"), CacheIndex); IIndexedCacheStorage::Get().GetCacheStorageInfo(CacheIndex, CurrentDataSize, CurrentJournalSize); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("GetCacheStorageInfo cache %d = data %llu bytes / journal %llu bytes"), CacheIndex, CurrentDataSize, CurrentJournalSize); NotifyWaitersWhileLocked(); return CurrentDataSize; } FString MountCacheStorage() { UE::TUniqueLock Lock(Mutex); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("MountCacheStorage request at cache %d. Current state %s"), CacheIndex, LexToString(CacheStorageState)); if (CacheStorageState == ECacheStorageState::Deleted) { check(MountRefCount == 0); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("MountCacheStorage skip mount for cache %d deleted"), CacheIndex); return FString(); } FString MountedPath = GetMountPath(); if (CacheStorageState == ECacheStorageState::Mounted) { check(MountRefCount > 0); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("MountCacheStorage skip mount for cache %d already mounted (%d)"), CacheIndex, MountRefCount); ++MountRefCount; return MountedPath; } check(MountRefCount == 0); check(CacheStorageState == ECacheStorageState::Created); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("MountCacheStorage mounting cache %d at %s"), CacheIndex, *MountedPath); bool bMounted = IIndexedCacheStorage::Get().MountCacheStorage(MountedPath, CacheIndex, MountName); check(MountRefCount == 0); if (bMounted) { CacheStorageState = ECacheStorageState::Mounted; MountRefCount = 1; UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("MountCacheStorage mounted cache %d at %s. Current state %s"), CacheIndex, *MountedPath, LexToString(CacheStorageState)); } else { CacheStorageState = ECacheStorageState::Created; UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("MountCacheStorage failed to mount cache %d at %s. Current state %s"), CacheIndex, *MountedPath, LexToString(CacheStorageState)); } NotifyWaitersWhileLocked(); return (bMounted) ? MountedPath : FString(); } void UnmountCacheStorage() { UE::TUniqueLock Lock(Mutex); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("UnmountCacheStorage request at cache %d. Current state %s"), CacheIndex, LexToString(CacheStorageState)); if (CacheStorageState == ECacheStorageState::Deleted || CacheStorageState == ECacheStorageState::Created) { check(MountRefCount == 0); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("UnmountCacheStorage skip unmount for cache %d already unmounted"), CacheIndex); return; } check(CacheStorageState == ECacheStorageState::Mounted); check(MountRefCount > 0); if (MountRefCount > 1) { --MountRefCount; UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("UnmountCacheStorage cache %d still mounted (%d)"), CacheIndex, MountRefCount); return; } FString MountedPath = GetMountPath(); UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("UnmountCacheStorage unmounting cache %d at %s"), CacheIndex, *MountedPath); IIndexedCacheStorage::Get().UnmountCacheStorage(*MountName); CacheStorageState = ECacheStorageState::Created; UE_LOG(LogIndexedCacheStorageManager, Display, TEXT("UnmountCacheStorage unmounted cache %d at %s. Current state %s"), CacheIndex, *MountedPath, LexToString(CacheStorageState)); check(MountRefCount == 1); MountRefCount = 0; NotifyWaitersWhileLocked(); } private: const FString MountName; const int32 CacheIndex; const uint64 EarlyStartupSize; int32 MountRefCount = 0; ECacheStorageState CacheStorageState = ECacheStorageState::Deleted; UE::FMutex Mutex; UE::FConditionVariable ConditionVariable; }; using FCacheStorageMetaDataArray = TArray >; struct FIndexedCacheStorageMetaData { FIndexedCacheStorageMetaData(FCacheStorageMetaDataArray&& InCacheStorageMetaDataArray) : IndexedCacheEntriesMetaData(MoveTemp(InCacheStorageMetaDataArray)) { } const FCacheStorageMetaDataArray IndexedCacheEntriesMetaData; }; static FIndexedCacheStorageManager* GIndexedCacheStorageManager = nullptr; FIndexedCacheStorageManager& FIndexedCacheStorageManager::Get() { static FIndexedCacheStorageManager Instance; UE_CALL_ONCE([&] { GIndexedCacheStorageManager = &Instance; } ); return Instance; } void FIndexedCacheStorageManager::EnsureEarlyStartupCache(const TArray& InCacheStorageAlreadyExists) { int32 EarlyStartupCacheIndex = GetStorageIndex(TEXT("EarlyStartupCache")); if (EarlyStartupCacheIndex == 0) { LOG_ICS(Display, TEXT("No Storage named 'EarlyStartupCache' has been set. Skipping")); return; } TArray CacheStorageAlreadyExists(InCacheStorageAlreadyExists); if (CacheStorageAlreadyExists[EarlyStartupCacheIndex]) { LOG_ICS(Error, TEXT("'EarlyStartupCache' entry already exists. Destroying it")); DestroyCacheStorage(EarlyStartupCacheIndex); CacheStorageAlreadyExists[EarlyStartupCacheIndex] = false; } TMap EarlyStartupCaches; { uint64 PersistentDownloadDirEarlyStartupSize = IIndexedCacheStorage::Get().GetPersistentDownloadDirEarlyStartupSize(); if (PersistentDownloadDirEarlyStartupSize > 0) { EarlyStartupCaches.Add(0, PersistentDownloadDirEarlyStartupSize); } for (int32 CacheIndex = 0; CacheIndex < IndexedCacheStorageMetaData->IndexedCacheEntriesMetaData.Num(); ++CacheIndex) { FCacheStorageMetaData* CacheStorageMetaData = IndexedCacheStorageMetaData->IndexedCacheEntriesMetaData[CacheIndex].Get(); if (CacheStorageMetaData == nullptr) { continue; } uint64 EarlyStartupSize = CacheStorageMetaData->GetEarlyStartupSize(); if (EarlyStartupSize > 0) { EarlyStartupCaches.Add(CacheIndex, EarlyStartupSize); } } } uint64 EarlyStartupCacheSize = 0; uint64 NumCacheStorageInGroup = 0; for (const TPair& EarlyStartupCache : EarlyStartupCaches) { uint64 CacheStorageSizeForCommonAlloc = 0; const int32 CacheStorageIndex = EarlyStartupCache.Key; const uint64 CacheStorageNewDataSize = EarlyStartupCache.Value; const uint64 CacheStorageNewJournalSize = IIndexedCacheStorage::Get().GetCacheStorageJournalSize(CacheStorageNewDataSize, CacheStorageIndex); if (CacheStorageAlreadyExists[CacheStorageIndex]) { uint64 CacheStorageCurrentDataSize = 0; uint64 CacheStorageCurrentJournalSize = 0; IIndexedCacheStorage::Get().GetCacheStorageInfo(CacheStorageIndex, CacheStorageCurrentDataSize, CacheStorageCurrentJournalSize); if (CacheStorageCurrentDataSize != CacheStorageNewDataSize || CacheStorageCurrentJournalSize != CacheStorageNewJournalSize) { LOG_ICS(Display, TEXT("Cache %d size mismatch: data: %llu vs %llu. journal: %llu vs %llu. Destroying cache"), CacheStorageIndex, CacheStorageCurrentDataSize, CacheStorageNewDataSize, CacheStorageCurrentJournalSize, CacheStorageNewJournalSize); DestroyCacheStorage(CacheStorageIndex); CacheStorageSizeForCommonAlloc = CacheStorageNewDataSize + CacheStorageNewJournalSize; } else { LOG_ICS(Display, TEXT("Cache %d up to date with %llu bytes. Skipping from grouped alloc"), CacheStorageIndex, CacheStorageCurrentDataSize); } } else if (CacheStorageNewDataSize > 0) { LOG_ICS(Display, TEXT("Cache %d does not exist, adding %llu bytes"), CacheStorageIndex, CacheStorageNewDataSize); CacheStorageSizeForCommonAlloc = CacheStorageNewDataSize + CacheStorageNewJournalSize; } if (CacheStorageSizeForCommonAlloc > 0) { EarlyStartupCacheSize += CacheStorageSizeForCommonAlloc; ++NumCacheStorageInGroup; } } if (EarlyStartupCacheSize == 0) { LOG_ICS(Display, TEXT("All caches are up-to-date, skipping reservation")); return; } if (NumCacheStorageInGroup <= 1) { LOG_ICS(Display, TEXT("Only one cache requested, skipping reservation")); return; } { // Make sure we alloc at least 2MB const uint64 MinGroupAllocSize = 2 << 20; EarlyStartupCacheSize = FMath::Max(MinGroupAllocSize, EarlyStartupCacheSize); // Remove alloc meta data because we want to allocate exactly EarlyStartupCacheSize uint64 EarlyStartupCacheSizeMetaData = IIndexedCacheStorage::Get().GetCacheStorageJournalSize(EarlyStartupCacheSize, EarlyStartupCacheIndex); // make sure we don't underflow EarlyStartupCacheSize -= FMath::Min(EarlyStartupCacheSizeMetaData, EarlyStartupCacheSize); // Make sure we alloc at least the metadata size EarlyStartupCacheSize = FMath::Max(EarlyStartupCacheSizeMetaData, EarlyStartupCacheSize); } LOG_ICS(Display, TEXT("Ensure %llu bytes through cache %d"), EarlyStartupCacheSize, EarlyStartupCacheIndex); bool bCacheCreated = true; while (true) { if (CreateCacheStorage(EarlyStartupCacheSize, EarlyStartupCacheIndex)) { DestroyCacheStorage(EarlyStartupCacheIndex); break; } } } //////////////////////////////////////////////////////////////////////////////// uint64 ParseSizeParam(FStringView Value) { Value = Value.TrimStartAndEnd(); uint64 Size = 0; LexFromString(Size, Value); if (Size > 0) { if (Value.EndsWith(TEXT("GB"))) return Size << 30; if (Value.EndsWith(TEXT("MB"))) return Size << 20; if (Value.EndsWith(TEXT("KB"))) return Size << 10; } return Size; } // Parse IniString expected to be in the form of OutIniName:[OutIniSection]:OutIniKey bool GetIniParameters(FString& OutIniName, FString& OutIniSection, FString& OutIniKey, const FString& IniString) { TArray ConfigStringArrayNameSize; UE::String::ParseTokens(IniString, TEXTVIEW(":"), [&](FStringView Element) { ConfigStringArrayNameSize.Add(Element); }); if (ConfigStringArrayNameSize.Num() != 3 || ConfigStringArrayNameSize[0].Len() == 0 || ConfigStringArrayNameSize[1].Len() == 0 || ConfigStringArrayNameSize[2].Len() == 0 ) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("Error while parsing %s: expecting OutIniName:[OutIniSection]:OutIniKey"), *IniString); return false; } FStringView IniSectionView = ConfigStringArrayNameSize[1]; if (IniSectionView[0] != TEXT('[') || IniSectionView[IniSectionView.Len()-1] != TEXT(']')) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("Error while parsing ini section %.*s"), IniSectionView.Len(), IniSectionView.GetData()); return false; } IniSectionView.RemovePrefix(1); IniSectionView.RemoveSuffix(1); OutIniName = ConfigStringArrayNameSize[0]; OutIniSection = IniSectionView; OutIniKey = ConfigStringArrayNameSize[2]; return true; } void FIndexedCacheStorageManager::Initialize() { uint32 NumIndexedCacheEntries = IIndexedCacheStorage::Get().GetNumIndexedCacheStorages(); if (NumIndexedCacheEntries == 0) { return; } FCacheStorageMetaDataArray IndexedCacheEntriesMetaData; IndexedCacheEntriesMetaData.SetNum(NumIndexedCacheEntries); TArray CacheStorageIndices; IIndexedCacheStorage::Get().EnumerateCacheStorages(CacheStorageIndices); TArray CacheStorageAlreadyExists; CacheStorageAlreadyExists.SetNumZeroed(NumIndexedCacheEntries); for (int32 CacheStorageIndex : CacheStorageIndices) { CacheStorageAlreadyExists[CacheStorageIndex] = true; } // Takes on the pattern // (Name="CacheStorageName",Index=CacheStorageIndex,Mount="CacheStorageMount") TArray StorageConfigs; GConfig->GetArray(TEXT("IndexedCacheStorage"), TEXT("Storage"), StorageConfigs, GEngineIni); uint32 RegisteredStorages = 0; for (const FString& Category : StorageConfigs) { FString TrimmedCategory = Category; TrimmedCategory.TrimStartAndEndInline(); if (TrimmedCategory.Left(1) == TEXT("(")) { TrimmedCategory.RightChopInline(1, EAllowShrinking::No); } if (TrimmedCategory.Right(1) == TEXT(")")) { TrimmedCategory.LeftChopInline(1, EAllowShrinking::No); } // Find all custom chunks and parse const TCHAR* PropertyName = TEXT("Name="); const TCHAR* PropertyIndex = TEXT("Index="); const TCHAR* PropertyMount = TEXT("Mount="); const TCHAR* PropertyEarlyStartup = TEXT("EarlyStartup="); FString StorageName; int32 StorageIndex; FString StorageMount; if (FParse::Value(*TrimmedCategory, PropertyName, StorageName) && FParse::Value(*TrimmedCategory, PropertyIndex, StorageIndex) && FParse::Value(*TrimmedCategory, PropertyMount, StorageMount)) { if (StorageName.Len() == 0) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("Found empty Name in [IndexedCacheStorage]:Storage")); continue; } if (StorageIndex <= 0 || StorageIndex >= (int32)NumIndexedCacheEntries) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("Found invalid Index in [IndexedCacheStorage]:Storage: %d, max allowed = %d"), StorageIndex, NumIndexedCacheEntries - 1); continue; } if (StorageMount.Len() == 0) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("Found empty Mount in [IndexedCacheStorage]:Storage")); continue; } StorageName.ReplaceInline(TEXT("\""), TEXT("")); StorageMount.ReplaceInline(TEXT("\""), TEXT("")); int32& ExistingStorageIndex = StorageNameToStorageIndex.FindOrAdd(StorageName, 0); if (ExistingStorageIndex > 0) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("Found an existing entry with name %s in [IndexedCacheStorage]:Storage"), *StorageName); continue; } ExistingStorageIndex = StorageIndex; uint64 EarlyStartupSize = 0; { FString EarlyStartupIniPath; if (FParse::Value(*TrimmedCategory, PropertyEarlyStartup, EarlyStartupIniPath)) { FString IniName; FString IniSection; FString IniKey; if (GetIniParameters(IniName, IniSection, IniKey, EarlyStartupIniPath)) { FString EarlyStartupSizeStr = GConfig->GetStr(*IniSection, *IniKey, *IniName); EarlyStartupSize = ParseSizeParam(EarlyStartupSizeStr); if (EarlyStartupSize == 0) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("%s%s specified, but no %s:%s exists in the %s ini file"), PropertyEarlyStartup, *EarlyStartupIniPath, *IniSection, *IniKey, *IniName); } } } } if (IndexedCacheEntriesMetaData[StorageIndex] == nullptr) { IndexedCacheEntriesMetaData[StorageIndex] = MakeUnique(StorageIndex, StorageMount, CacheStorageAlreadyExists[StorageIndex], EarlyStartupSize); ++RegisteredStorages; } else { const FString& ExistingName = IndexedCacheEntriesMetaData[StorageIndex]->GetMountName(); UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("Found an existing entry named %s with at cache %d for name %s in [IndexedCacheStorage]:Storage"), *ExistingName, StorageIndex, *StorageName); } } } bHasRegisteredEntries = (RegisteredStorages > 0); if (bHasRegisteredEntries) { IndexedCacheStorageMetaData = MakeUnique(MoveTemp(IndexedCacheEntriesMetaData)); EnsureEarlyStartupCache(CacheStorageAlreadyExists); } } bool FIndexedCacheStorageManager::SupportsIndexedCacheStorage() { return bHasRegisteredEntries && IIndexedCacheStorage::Get().SupportsIndexedCacheStorage(); } int32 FIndexedCacheStorageManager::GetStorageIndex(const FString& StorageName) { int32* StorageIndex = StorageNameToStorageIndex.Find(StorageName); return StorageIndex ? *StorageIndex : 0; } uint64 FIndexedCacheStorageManager::GetCacheEarlyStartupSize(int32 CacheIndex) { FCacheStorageMetaData* CacheStorageMetaData = GetCacheStorageMetaData(CacheIndex); if (CacheStorageMetaData == nullptr) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("GetCacheEarlyStartupSize skip invalid cache %d"), CacheIndex); return 0; } return CacheStorageMetaData->GetEarlyStartupSize(); } void FIndexedCacheStorageManager::EnumerateCacheStorages(TArray& OutCacheStorageNames) { OutCacheStorageNames.Reserve(StorageNameToStorageIndex.Num()); for (const TPair& StorageNameToStorageIndexIter : StorageNameToStorageIndex) { OutCacheStorageNames.Add(StorageNameToStorageIndexIter.Key); } } bool FIndexedCacheStorageManager::CreateCacheStorage(uint64 RequestNumberOfBytes, int32 CacheIndex) { FCacheStorageMetaData* CacheStorageMetaData = GetCacheStorageMetaData(CacheIndex); if (CacheStorageMetaData == nullptr) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("CreateCacheStorage skip invalid cache %d"), CacheIndex); return false; } return CacheStorageMetaData->CreateCacheStorage(RequestNumberOfBytes); } void FIndexedCacheStorageManager::DestroyCacheStorage(int32 CacheIndex) { FCacheStorageMetaData* CacheStorageMetaData = GetCacheStorageMetaData(CacheIndex); if (CacheStorageMetaData == nullptr) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("DestroyCacheStorage skip invalid cache %d"), CacheIndex); return; } CacheStorageMetaData->DestroyCacheStorage(); } uint64 FIndexedCacheStorageManager::GetCacheStorageCapacity(int32 CacheIndex) { FCacheStorageMetaData* CacheStorageMetaData = GetCacheStorageMetaData(CacheIndex); if (CacheStorageMetaData == nullptr) { UE_LOG(LogIndexedCacheStorageManager, Warning, TEXT("GetCacheStorageCapacity skip invalid cache %d"), CacheIndex); return 0; } return CacheStorageMetaData->GetCacheStorageInfo(); } FString FIndexedCacheStorageManager::MountCacheStorage(int32 CacheIndex) { FCacheStorageMetaData* CacheStorageMetaData = GetCacheStorageMetaData(CacheIndex); if (CacheStorageMetaData == nullptr) { UE_LOG(LogIndexedCacheStorageManager, Warning, TEXT("MountCacheStorage skip invalid cache %d"), CacheIndex); return FString(); } return CacheStorageMetaData->MountCacheStorage(); } void FIndexedCacheStorageManager::UnmountCacheStorage(int32 CacheIndex) { FCacheStorageMetaData* CacheStorageMetaData = GetCacheStorageMetaData(CacheIndex); if (CacheStorageMetaData == nullptr) { UE_LOG(LogIndexedCacheStorageManager, Warning, TEXT("UnmountCacheStorage skip invalid cache %d"), CacheIndex); return; } return CacheStorageMetaData->UnmountCacheStorage(); } FString FIndexedCacheStorageManager::GetMountName(int32 CacheIndex) { const FCacheStorageMetaData* CacheStorageMetaData = GetCacheStorageMetaData(CacheIndex); if (CacheStorageMetaData == nullptr) { UE_LOG(LogIndexedCacheStorageManager, Warning, TEXT("GetMountName skip invalid cache %d"), CacheIndex); return FString(); } return CacheStorageMetaData->GetMountName(); } FString FIndexedCacheStorageManager::GetMountPath(int32 CacheIndex) { const FCacheStorageMetaData* CacheStorageMetaData = GetCacheStorageMetaData(CacheIndex); if (CacheStorageMetaData == nullptr) { UE_LOG(LogIndexedCacheStorageManager, Warning, TEXT("GetMountPath skip invalid cache %d"), CacheIndex); return FString(); } return CacheStorageMetaData->GetMountPath(); } FCacheStorageMetaData* FIndexedCacheStorageManager::GetCacheStorageMetaData(int32 CacheIndex) { // We must skip index 0 since it's mapped to GamePersistentDownloadDir() bool bValid = CacheIndex > 0 && IndexedCacheStorageMetaData && CacheIndex < IndexedCacheStorageMetaData->IndexedCacheEntriesMetaData.Num(); if (!bValid) { return nullptr; } FCacheStorageMetaData* OutCacheStorageMetaData = IndexedCacheStorageMetaData->IndexedCacheEntriesMetaData[CacheIndex].Get(); if (OutCacheStorageMetaData && OutCacheStorageMetaData->IsValid()) { return OutCacheStorageMetaData; } return nullptr; } #if !UE_BUILD_SHIPPING namespace IndexedCacheStorageManager_Debug { static void CopyFiles(const FString& TargetDirectory); static void ListFiles(); static FAutoConsoleCommand GCopyFiles( TEXT("ics.CopyFiles"), TEXT("Copies all cache files to the supplied folder"), FConsoleCommandWithArgsDelegate::CreateLambda( [](const TArray& Args) { if (Args.IsEmpty()) { UE_LOG(LogIndexedCacheStorageManager, Error, TEXT("usage: ics.CopyFiles ")); return; } CopyFiles(Args[0]); })); static FAutoConsoleCommand GListFiles( TEXT("ics.ListFiles"), TEXT("Copies all cache files to the supplied folder"), FConsoleCommandWithArgsDelegate::CreateLambda( [](const TArray&) { ListFiles(); })); static void CollectCacheIndices(TArray& CacheIndices) { uint32 NumIndexedCacheEntries = IIndexedCacheStorage::Get().GetNumIndexedCacheStorages(); CacheIndices.Reserve(NumIndexedCacheEntries+1); if (FPaths::HasProjectPersistentDownloadDir()) { CacheIndices.Add(0); } for (uint32 CacheIndex = 1; CacheIndex < NumIndexedCacheEntries; ++CacheIndex) { uint64 CacheCapacity = FIndexedCacheStorageManager::Get().GetCacheStorageCapacity(CacheIndex); if (CacheCapacity == 0) { continue; } CacheIndices.Add(CacheIndex); } } static void ListFiles() { TArray CacheIndices; CollectCacheIndices(CacheIndices); UE_LOG(LogConsoleResponse, Display, TEXT("Listing cache files ...")); AsyncTask( ENamedThreads::AnyBackgroundThreadNormalTask, [CacheIndices]() mutable { for (int32 CacheIndex : CacheIndices) { FIndexedCacheStorageScopeMount IndexedCacheStorageScopeMount(CacheIndex); FString SourcePath = (CacheIndex == 0) ? FPaths::ProjectPersistentDownloadDir() : IndexedCacheStorageScopeMount.MountPath; if (SourcePath.IsEmpty()) { return; } IFileManager::Get().IterateDirectoryStatRecursively( *SourcePath, [](const TCHAR* FileOrDir, const FFileStatData& StatData) { if (StatData.bIsValid) { int64 FileSize = !StatData.bIsDirectory ? StatData.FileSize : 0; UE_LOG(LogConsoleResponse, Display, TEXT("%10llu %s%c"), FileSize, FileOrDir, (StatData.bIsDirectory ? TEXT('/') : TEXT('\0'))); } return true; } ); } UE_LOG(LogConsoleResponse, Display, TEXT("Listing cache files done")); } ); } static void CopyFiles(const FString& InTargetDirectory) { if (!IFileManager::Get().DirectoryExists(*InTargetDirectory)) { IFileManager::Get().MakeDirectory(*InTargetDirectory); } TArray CacheIndices; CollectCacheIndices(CacheIndices); FString TargetDirectory(InTargetDirectory); UE_LOG(LogConsoleResponse, Display, TEXT("Copy files to %s ..."), *TargetDirectory); AsyncTask( ENamedThreads::AnyBackgroundThreadNormalTask, [CacheIndices, TargetDirectory]() mutable { for (int32 CacheIndex : CacheIndices) { FIndexedCacheStorageScopeMount IndexedCacheStorageScopeMount(CacheIndex); FString SourcePath = (CacheIndex == 0) ? FPaths::ProjectPersistentDownloadDir() : IndexedCacheStorageScopeMount.MountPath; if (SourcePath.IsEmpty()) { return; } if (!SourcePath.EndsWith(TEXT("/"))) { SourcePath += TEXT("/"); } TArray SourceFiles; TArray DestFiles; FString TargetDirectoryCache = FString::Printf(TEXT("%s/cache_%02d"), *TargetDirectory, CacheIndex); IFileManager::Get().IterateDirectoryStatRecursively( *SourcePath, [SourcePath, TargetDirectoryCache, &SourceFiles, &DestFiles](const TCHAR* FileOrDir, const FFileStatData& StatData) { if (StatData.bIsValid) { FString RelativeSourceFile(FileOrDir); if (StatData.bIsDirectory && !RelativeSourceFile.EndsWith(TEXT("/"))) { RelativeSourceFile += TEXT("/"); } FPaths::MakePathRelativeTo(RelativeSourceFile, *SourcePath); FString DestFile = TargetDirectoryCache / RelativeSourceFile; FString DestDir = StatData.bIsDirectory ? DestFile : FPaths::GetPath(DestFile); if (StatData.bIsDirectory) { IFileManager::Get().MakeDirectory(*DestDir, true); } else { SourceFiles.Add(FileOrDir); DestFiles.Add(DestFile); } } return true; } ); for (int32 SourceFileIndex = 0; SourceFileIndex < SourceFiles.Num(); ++SourceFileIndex) { UE_LOG(LogConsoleResponse, Display, TEXT("[%d/%d] %s -> %s"), SourceFileIndex+1, SourceFiles.Num(), *SourceFiles[SourceFileIndex], *DestFiles[SourceFileIndex]); IFileManager::Get().Copy(*DestFiles[SourceFileIndex], *SourceFiles[SourceFileIndex]); } } UE_LOG(LogConsoleResponse, Display, TEXT("Copy files to %s done"), *TargetDirectory); } ); } } // namespace IndexedCacheStorageManager_Debug #endif // !UE_BUILD_SHIPPING }