2020-04-28 13:05:49 -04:00
// Copyright Epic Games, Inc. All Rights Reserved.
# include "InstallBundleCache.h"
# include "InstallBundleManagerPrivatePCH.h"
2020-08-11 01:36:57 -04:00
# define INSTALLBUNDLE_CACHE_CHECK_INVARIANTS (DO_CHECK && 0)
2021-01-21 16:22:06 -04:00
# define INSTALLBUNDLE_CACHE_DUMP_INFO (0)
2020-04-28 13:05:49 -04:00
FInstallBundleCache : : ~ FInstallBundleCache ( )
{
}
void FInstallBundleCache : : Init ( FInstallBundleCacheInitInfo InitInfo )
{
CacheName = InitInfo . CacheName ;
TotalSize = InitInfo . Size ;
}
2020-08-11 01:36:57 -04:00
void FInstallBundleCache : : AddOrUpdateBundle ( EInstallBundleSourceType Source , const FInstallBundleCacheBundleInfo & AddInfo )
2020-04-28 13:05:49 -04:00
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_AddOrUpdateBundle ) ;
2020-08-11 01:36:57 -04:00
FPerSourceBundleCacheInfo & Info = PerSourceCacheInfo . FindOrAdd ( AddInfo . BundleName ) . FindOrAdd ( Source ) ;
Info . FullInstallSize = AddInfo . FullInstallSize ;
Info . CurrentInstallSize = AddInfo . CurrentInstallSize ;
2020-09-01 14:07:48 -04:00
Info . TimeStamp = AddInfo . TimeStamp ;
2021-12-10 18:32:36 -05:00
Info . AgeScalar = FMath : : Clamp ( AddInfo . AgeScalar , 0.1 , 1.0 ) ;
2020-08-11 01:36:57 -04:00
UpdateCacheInfoFromSourceInfo ( AddInfo . BundleName ) ;
2020-04-28 13:05:49 -04:00
CheckInvariants ( ) ;
}
2020-08-11 01:36:57 -04:00
void FInstallBundleCache : : RemoveBundle ( EInstallBundleSourceType Source , FName BundleName )
2020-04-28 13:05:49 -04:00
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_RemoveBundle ) ;
2020-08-11 01:36:57 -04:00
TMap < EInstallBundleSourceType , FPerSourceBundleCacheInfo > * SourcesMap = PerSourceCacheInfo . Find ( BundleName ) ;
if ( SourcesMap )
{
SourcesMap - > Remove ( Source ) ;
UpdateCacheInfoFromSourceInfo ( BundleName ) ;
CheckInvariants ( ) ;
}
2020-04-28 13:05:49 -04:00
}
2021-04-08 14:32:07 -04:00
TOptional < FInstallBundleCacheBundleInfo > FInstallBundleCache : : GetBundleInfo ( EInstallBundleSourceType Source , FName BundleName ) const
2020-04-28 13:05:49 -04:00
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_GetBundleInfo ) ;
2020-06-23 18:40:00 -04:00
TOptional < FInstallBundleCacheBundleInfo > Ret ;
2020-08-11 01:36:57 -04:00
2021-04-08 14:32:07 -04:00
const TMap < EInstallBundleSourceType , FPerSourceBundleCacheInfo > * SourcesMap = PerSourceCacheInfo . Find ( BundleName ) ;
2020-08-11 01:36:57 -04:00
if ( SourcesMap )
2020-06-23 18:40:00 -04:00
{
2021-04-08 14:32:07 -04:00
const FPerSourceBundleCacheInfo * SourceInfo = SourcesMap - > Find ( Source ) ;
2020-08-11 01:36:57 -04:00
if ( SourceInfo )
{
FInstallBundleCacheBundleInfo & OutInfo = Ret . Emplace ( ) ;
OutInfo . BundleName = BundleName ;
OutInfo . FullInstallSize = SourceInfo - > FullInstallSize ;
OutInfo . CurrentInstallSize = SourceInfo - > CurrentInstallSize ;
2021-01-21 16:22:06 -04:00
OutInfo . TimeStamp = SourceInfo - > TimeStamp ;
2021-12-10 18:32:36 -05:00
OutInfo . AgeScalar = SourceInfo - > AgeScalar ;
2020-08-11 01:36:57 -04:00
}
2020-06-23 18:40:00 -04:00
}
return Ret ;
2020-04-28 13:05:49 -04:00
}
uint64 FInstallBundleCache : : GetSize ( ) const
{
return TotalSize ;
}
2021-01-21 16:22:06 -04:00
uint64 FInstallBundleCache : : GetUsedSize ( ) const
2020-04-28 13:05:49 -04:00
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_GetUsedSize ) ;
2020-04-28 13:05:49 -04:00
uint64 UsedSize = 0 ;
for ( const TPair < FName , FBundleCacheInfo > & Pair : CacheInfo )
{
UsedSize + = Pair . Value . GetSize ( ) ;
}
2021-01-21 16:22:06 -04:00
return UsedSize ;
}
uint64 FInstallBundleCache : : GetFreeSpaceInternal ( uint64 UsedSize ) const
{
2020-04-28 13:05:49 -04:00
if ( UsedSize > TotalSize )
return 0 ;
return TotalSize - UsedSize ;
}
2021-01-21 16:22:06 -04:00
uint64 FInstallBundleCache : : GetFreeSpace ( ) const
{
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_GetFreeSpace ) ;
uint64 UsedSize = GetUsedSize ( ) ;
return GetFreeSpaceInternal ( UsedSize ) ;
}
2020-04-28 13:05:49 -04:00
FInstallBundleCacheReserveResult FInstallBundleCache : : Reserve ( FName BundleName )
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_Reserve ) ;
2020-04-28 13:05:49 -04:00
FInstallBundleCacheReserveResult Result ;
FBundleCacheInfo * BundleInfo = CacheInfo . Find ( BundleName ) ;
if ( BundleInfo = = nullptr )
{
Result . Result = EInstallBundleCacheReserveResult : : Success ;
return Result ;
}
if ( BundleInfo - > State = = ECacheState : : PendingEvict )
{
2020-06-23 18:40:00 -04:00
Result . Result = EInstallBundleCacheReserveResult : : Fail_PendingEvict ;
2020-04-28 13:05:49 -04:00
return Result ;
}
if ( BundleInfo - > State = = ECacheState : : Reserved )
{
Result . Result = EInstallBundleCacheReserveResult : : Success ;
return Result ;
}
if ( BundleInfo - > FullInstallSize < = BundleInfo - > CurrentInstallSize )
{
BundleInfo - > State = ECacheState : : Reserved ;
Result . Result = EInstallBundleCacheReserveResult : : Success ;
return Result ;
}
2020-06-23 18:40:00 -04:00
const uint64 SizeNeeded = BundleInfo - > FullInstallSize - BundleInfo - > CurrentInstallSize ;
2021-01-21 16:22:06 -04:00
const uint64 UsedSize = GetUsedSize ( ) ;
if ( TotalSize > = UsedSize + SizeNeeded )
2020-04-28 13:05:49 -04:00
{
BundleInfo - > State = ECacheState : : Reserved ;
Result . Result = EInstallBundleCacheReserveResult : : Success ;
2021-01-21 16:22:06 -04:00
2020-04-28 13:05:49 -04:00
return Result ;
}
2020-06-23 18:40:00 -04:00
Result . Result = EInstallBundleCacheReserveResult : : Fail_NeedsEvict ;
2020-04-28 13:05:49 -04:00
2021-01-21 16:22:06 -04:00
// TODO: Bundles that have BundleSize > 0 or are PendingEvict should be
// sorted to the beginning. We should be able to stop iterating sooner in that case.
2021-12-10 18:32:36 -05:00
CacheInfo . ValueSort ( [ Now = FDateTime : : UtcNow ( ) ] ( const FBundleCacheInfo & A , const FBundleCacheInfo & B )
2020-09-01 14:07:48 -04:00
{
2021-04-08 14:32:07 -04:00
if ( A . IsHintRequested ( ) = = B . IsHintRequested ( ) )
2020-09-01 14:07:48 -04:00
{
2021-12-10 18:32:36 -05:00
FTimespan AgeA = ( Now > A . TimeStamp ) ? Now - A . TimeStamp : FTimespan ( 0 ) ;
FTimespan AgeB = ( Now > B . TimeStamp ) ? Now - B . TimeStamp : FTimespan ( 0 ) ;
return AgeA * A . AgeScalar > AgeB * B . AgeScalar ;
2020-09-01 14:07:48 -04:00
}
2021-04-08 14:32:07 -04:00
return ! A . IsHintRequested ( ) & & B . IsHintRequested ( ) ;
2020-09-01 14:07:48 -04:00
} ) ;
2020-04-28 13:05:49 -04:00
2021-01-21 16:22:06 -04:00
uint64 CanFreeSpace = 0 ;
2020-04-28 13:05:49 -04:00
for ( const TPair < FName , FBundleCacheInfo > & Pair : CacheInfo )
{
if ( Pair . Key = = BundleName )
continue ;
if ( Pair . Value . State = = ECacheState : : Reserved )
continue ;
uint64 BundleSize = Pair . Value . GetSize ( ) ;
if ( BundleSize > 0 )
{
2021-01-21 16:22:06 -04:00
check ( UsedSize > = CanFreeSpace ) ;
if ( TotalSize < UsedSize - CanFreeSpace + SizeNeeded )
2020-06-23 18:40:00 -04:00
{
CanFreeSpace + = BundleSize ;
2021-01-21 16:22:06 -04:00
TArray < EInstallBundleSourceType > & SourcesToEvictFrom = Result . BundlesToEvict . Add ( Pair . Key ) ;
PerSourceCacheInfo . FindChecked ( Pair . Key ) . GenerateKeyArray ( SourcesToEvictFrom ) ;
2020-06-23 18:40:00 -04:00
}
}
else if ( Pair . Value . State = = ECacheState : : PendingEvict )
{
// Bundle manager must wait for all previous pending evictions to complete
// to ensure that there is actually enough free space in the cache
// before installing a bundle
2021-01-21 16:22:06 -04:00
TArray < EInstallBundleSourceType > & SourcesToEvictFrom = Result . BundlesToEvict . Add ( Pair . Key ) ;
PerSourceCacheInfo . FindChecked ( Pair . Key ) . GenerateKeyArray ( SourcesToEvictFrom ) ;
2020-04-28 13:05:49 -04:00
}
}
2021-01-21 16:22:06 -04:00
check ( UsedSize > = CanFreeSpace ) ;
if ( TotalSize < UsedSize - CanFreeSpace + SizeNeeded )
2020-04-28 13:05:49 -04:00
{
2020-06-23 18:40:00 -04:00
Result . Result = EInstallBundleCacheReserveResult : : Fail_CacheFull ;
2020-04-28 13:05:49 -04:00
}
else
{
check ( Result . BundlesToEvict . Num ( ) > 0 ) ;
}
2021-01-21 16:22:06 -04:00
# if INSTALLBUNDLE_CACHE_DUMP_INFO
GetStats ( true ) ;
# endif // INSTALLBUNDLE_CACHE_DUMP_INFO
2020-04-28 13:05:49 -04:00
return Result ;
}
2021-04-08 14:32:07 -04:00
FInstallBundleCacheFlushResult FInstallBundleCache : : Flush ( EInstallBundleSourceType * Source /*= nullptr*/ )
{
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_Flush ) ;
FInstallBundleCacheFlushResult Result ;
for ( const TPair < FName , FBundleCacheInfo > & Pair : CacheInfo )
{
if ( Pair . Value . State = = ECacheState : : Reserved )
continue ;
if ( Pair . Value . CurrentInstallSize = = 0 )
continue ;
if ( Source )
{
if ( PerSourceCacheInfo . FindChecked ( Pair . Key ) . Contains ( * Source ) )
{
TArray < EInstallBundleSourceType > & SourcesToEvictFrom = Result . BundlesToEvict . Add ( Pair . Key ) ;
SourcesToEvictFrom . Add ( * Source ) ;
}
}
else
{
TArray < EInstallBundleSourceType > & SourcesToEvictFrom = Result . BundlesToEvict . Add ( Pair . Key ) ;
PerSourceCacheInfo . FindChecked ( Pair . Key ) . GenerateKeyArray ( SourcesToEvictFrom ) ;
}
}
# if INSTALLBUNDLE_CACHE_DUMP_INFO
GetStats ( true ) ;
# endif // INSTALLBUNDLE_CACHE_DUMP_INFO
return Result ;
}
2020-04-28 13:05:49 -04:00
bool FInstallBundleCache : : Release ( FName BundleName )
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_Release ) ;
2020-04-28 13:05:49 -04:00
FBundleCacheInfo * BundleInfo = CacheInfo . Find ( BundleName ) ;
if ( BundleInfo = = nullptr )
{
return true ;
}
if ( BundleInfo - > State = = ECacheState : : Released )
{
return true ;
}
2021-04-08 14:32:07 -04:00
if ( BundleInfo - > State = = ECacheState : : Reserved )
2020-04-28 13:05:49 -04:00
{
BundleInfo - > State = ECacheState : : Released ;
return true ;
}
return false ;
}
bool FInstallBundleCache : : SetPendingEvict ( FName BundleName )
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_SetPendingEvict ) ;
2020-04-28 13:05:49 -04:00
FBundleCacheInfo * BundleInfo = CacheInfo . Find ( BundleName ) ;
if ( BundleInfo = = nullptr )
{
return true ;
}
if ( BundleInfo - > State = = ECacheState : : PendingEvict )
{
return true ;
}
if ( BundleInfo - > State = = ECacheState : : Released )
{
BundleInfo - > State = ECacheState : : PendingEvict ;
return true ;
}
return false ;
}
2021-04-08 14:32:07 -04:00
bool FInstallBundleCache : : ClearPendingEvict ( FName BundleName )
{
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_ClearPendingEvict ) ;
FBundleCacheInfo * BundleInfo = CacheInfo . Find ( BundleName ) ;
if ( BundleInfo = = nullptr )
{
return true ;
}
if ( BundleInfo - > State = = ECacheState : : Released )
{
return true ;
}
if ( BundleInfo - > State = = ECacheState : : PendingEvict )
{
BundleInfo - > State = ECacheState : : Released ;
return true ;
}
return false ;
}
2020-04-28 13:05:49 -04:00
void FInstallBundleCache : : HintRequested ( FName BundleName , bool bRequested )
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_HintRequested ) ;
2020-04-28 13:05:49 -04:00
FBundleCacheInfo * BundleInfo = CacheInfo . Find ( BundleName ) ;
if ( BundleInfo )
{
2021-04-08 14:32:07 -04:00
if ( bRequested )
{
BundleInfo - > HintReqeustedCount + = 1 ;
}
else
{
BundleInfo - > HintReqeustedCount - = 1 ;
check ( BundleInfo - > HintReqeustedCount > = 0 ) ;
}
2020-04-28 13:05:49 -04:00
}
}
void FInstallBundleCache : : CheckInvariants ( ) const
{
# if INSTALLBUNDLE_CACHE_CHECK_INVARIANTS
2020-08-11 01:36:57 -04:00
check ( PerSourceCacheInfo . Num ( ) = = CacheInfo . Num ( ) ) ;
for ( const TPair < FName , FBundleCacheInfo > & CachePair : CacheInfo )
{
const TMap < EInstallBundleSourceType , FPerSourceBundleCacheInfo > * SourcesMap = PerSourceCacheInfo . Find ( CachePair . Key ) ;
check ( SourcesMap ) ;
uint64 FullInstallSize = 0 ;
uint64 CurrentInstallSize = 0 ;
for ( const TPair < EInstallBundleSourceType , FPerSourceBundleCacheInfo > & Pair : * SourcesMap )
{
FullInstallSize + = Pair . Value . FullInstallSize ;
CurrentInstallSize + = Pair . Value . CurrentInstallSize ;
}
check ( CachePair . Value . FullInstallSize = = FullInstallSize ) ;
check ( CachePair . Value . CurrentInstallSize = = CurrentInstallSize ) ;
}
2020-04-28 13:05:49 -04:00
# endif // INSTALLBUNDLE_CACHE_CHECK_INVARIANTS
}
2020-08-11 01:36:57 -04:00
2021-01-21 16:22:06 -04:00
FInstallBundleCacheStats FInstallBundleCache : : GetStats ( bool bDumpToLog /*= false*/ ) const
{
FInstallBundleCacheStats Stats ;
Stats . CacheName = CacheName ;
Stats . MaxSize = TotalSize ;
if ( bDumpToLog )
{
UE_LOG ( LogInstallBundleManager , Display , TEXT ( " \n " ) ) ;
UE_LOG ( LogInstallBundleManager , Display , TEXT ( " *Install Bundle Cache Stats %s " ) , * CacheName . ToString ( ) ) ;
}
for ( const TPair < FName , FBundleCacheInfo > & CachePair : CacheInfo )
{
const FBundleCacheInfo & Info = CachePair . Value ;
Stats . UsedSize + = Info . GetSize ( ) ;
if ( Info . State = = ECacheState : : Reserved )
{
Stats . ReservedSize + = Info . CurrentInstallSize ;
}
if ( bDumpToLog & & ( Info . CurrentInstallSize > 0 | | Info . State ! = ECacheState : : Released ) )
{
UE_LOG ( LogInstallBundleManager , Verbose , TEXT ( " * \t bundle %s " ) , * CachePair . Key . ToString ( ) ) ;
UE_LOG ( LogInstallBundleManager , Verbose , TEXT ( " * \t \t full size: % " UINT64_FMT ) , Info . FullInstallSize ) ;
UE_LOG ( LogInstallBundleManager , Verbose , TEXT ( " * \t \t current size: % " UINT64_FMT ) , Info . CurrentInstallSize ) ;
UE_LOG ( LogInstallBundleManager , Verbose , TEXT ( " * \t \t reserved: %s " ) , ( Info . State = = ECacheState : : Reserved ) ? TEXT ( " true " ) : TEXT ( " false " ) ) ;
UE_LOG ( LogInstallBundleManager , Verbose , TEXT ( " * \t \t timestamp: %s " ) , * Info . TimeStamp . ToString ( ) ) ;
}
}
Stats . FreeSize = GetFreeSpaceInternal ( Stats . UsedSize ) ;
if ( bDumpToLog )
{
UE_LOG ( LogInstallBundleManager , Display , TEXT ( " * \t size: % " UINT64_FMT ) , Stats . MaxSize ) ;
UE_LOG ( LogInstallBundleManager , Display , TEXT ( " * \t used: % " UINT64_FMT ) , Stats . UsedSize ) ;
UE_LOG ( LogInstallBundleManager , Display , TEXT ( " * \t reserved: % " UINT64_FMT ) , Stats . ReservedSize ) ;
UE_LOG ( LogInstallBundleManager , Display , TEXT ( " * \t free: % " UINT64_FMT ) , Stats . FreeSize ) ;
UE_LOG ( LogInstallBundleManager , Display , TEXT ( " \n " ) ) ;
}
return Stats ;
}
2020-08-11 01:36:57 -04:00
void FInstallBundleCache : : UpdateCacheInfoFromSourceInfo ( FName BundleName )
{
2021-01-21 16:22:06 -04:00
CSV_SCOPED_TIMING_STAT ( InstallBundleManager , FInstallBundleCache_UpdateCacheInfoFromSourceInfo ) ;
2020-08-11 01:36:57 -04:00
TMap < EInstallBundleSourceType , FPerSourceBundleCacheInfo > * SourcesMap = PerSourceCacheInfo . Find ( BundleName ) ;
if ( SourcesMap = = nullptr )
{
CacheInfo . Remove ( BundleName ) ;
return ;
}
if ( SourcesMap - > Num ( ) = = 0 )
{
PerSourceCacheInfo . Remove ( BundleName ) ;
CacheInfo . Remove ( BundleName ) ;
return ;
}
2020-09-01 14:07:48 -04:00
FDateTime TimeStamp = FDateTime : : MinValue ( ) ;
2021-12-10 18:32:36 -05:00
double AgeScalar = 1.0 ;
2020-08-11 01:36:57 -04:00
uint64 FullInstallSize = 0 ;
uint64 CurrentInstallSize = 0 ;
for ( const TPair < EInstallBundleSourceType , FPerSourceBundleCacheInfo > & Pair : * SourcesMap )
{
FullInstallSize + = Pair . Value . FullInstallSize ;
CurrentInstallSize + = Pair . Value . CurrentInstallSize ;
2021-12-10 18:32:36 -05:00
if ( Pair . Value . CurrentInstallSize > 0 )
2020-09-01 14:07:48 -04:00
{
2021-12-10 18:32:36 -05:00
if ( Pair . Value . TimeStamp > TimeStamp )
{
TimeStamp = Pair . Value . TimeStamp ;
}
if ( Pair . Value . AgeScalar < AgeScalar )
{
AgeScalar = Pair . Value . AgeScalar ;
}
2020-09-01 14:07:48 -04:00
}
2020-08-11 01:36:57 -04:00
}
FBundleCacheInfo & BundleCacheInfo = CacheInfo . FindOrAdd ( BundleName ) ;
checkf ( BundleCacheInfo . FullInstallSize = = FullInstallSize | | BundleCacheInfo . State ! = ECacheState : : Reserved , TEXT ( " Bundle %s: FullInstallSize should not be updated while a bundle is Reserved! " ) , * BundleName . ToString ( ) ) ;
BundleCacheInfo . FullInstallSize = FullInstallSize ;
BundleCacheInfo . CurrentInstallSize = CurrentInstallSize ;
2020-09-01 14:07:48 -04:00
BundleCacheInfo . TimeStamp = TimeStamp ;
2021-12-10 18:32:36 -05:00
BundleCacheInfo . AgeScalar = AgeScalar ;
2020-08-11 01:36:57 -04:00
}