2015-06-10 03:14:50 -04:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
# include "RuntimeAssetCachePrivatePCH.h"
# include "RuntimeAssetCacheAsyncWorker.h"
# include "RuntimeAssetCachePluginInterface.h"
# include "RuntimeAssetCacheBucket.h"
# include "RuntimeAssetCacheBackend.h"
# include "RuntimeAssetCacheEntryMetadata.h"
# include "RuntimeAssetCacheBucketScopeLock.h"
# include "ScopeExit.h"
# include "RuntimeAssetCacheModule.h"
2015-06-19 03:13:33 -04:00
# include "RuntimeAssetCacheBPHooks.h"
2015-06-10 03:14:50 -04:00
/** Stats */
DEFINE_STAT ( STAT_RAC_NumBuilds ) ;
DEFINE_STAT ( STAT_RAC_NumCacheHits ) ;
DEFINE_STAT ( STAT_RAC_NumFails ) ;
DEFINE_STAT ( STAT_RAC_NumGets ) ;
DEFINE_STAT ( STAT_RAC_NumPuts ) ;
2015-06-19 03:13:33 -04:00
FRuntimeAssetCacheAsyncWorker : : FRuntimeAssetCacheAsyncWorker ( IRuntimeAssetCacheBuilder * InCacheBuilder
, TMap < FName , FRuntimeAssetCacheBucket * > * InBuckets
, int32 InHandle
, const FOnRuntimeAssetCacheAsyncComplete & InCompletionCallback )
2015-06-10 03:14:50 -04:00
: CacheBuilder ( InCacheBuilder )
, Buckets ( InBuckets )
2015-06-19 03:13:33 -04:00
, CompletionCallback ( InCompletionCallback )
2015-06-19 14:17:08 -04:00
, Handle ( InHandle )
2015-06-19 03:13:33 -04:00
, bFiredCompletionDelegate ( false )
2015-06-10 03:14:50 -04:00
{ }
void FRuntimeAssetCacheAsyncWorker : : DoWork ( )
{
const TCHAR * Bucket = CacheBuilder - > GetTypeName ( ) ;
FName BucketName = FName ( Bucket ) ;
FString CacheKey = BuildCacheKey ( CacheBuilder ) ;
FName CacheKeyName = FName ( * CacheKey ) ;
FRuntimeAssetCacheBucket * CurrentBucket = ( * Buckets ) [ BucketName ] ;
2015-06-19 03:13:33 -04:00
int64 CachedDataSize = - 1 ;
2015-06-10 03:14:50 -04:00
INC_DWORD_STAT ( STAT_RAC_NumGets ) ;
FCacheEntryMetadata * Metadata = nullptr ;
{
DECLARE_SCOPE_CYCLE_COUNTER ( TEXT ( " RAC async get time " ) , STAT_RAC_AsyncGetTime , STATGROUP_RAC ) ;
Metadata = CurrentBucket - > GetMetadata ( CacheKey ) ;
}
if ( Metadata = = nullptr )
{
FRuntimeAssetCacheBucketScopeLock Guard ( * CurrentBucket ) ;
CurrentBucket - > AddMetadataEntry ( CacheKey , new FCacheEntryMetadata ( FDateTime : : MaxValue ( ) , 0 , 0 , CacheKeyName ) , false ) ;
}
else
{
while ( Metadata - > IsBuilding ( ) )
{
FPlatformProcess : : SleepNoStats ( 0.0f ) ;
}
2015-06-19 03:13:33 -04:00
Metadata = FRuntimeAssetCacheBackend : : Get ( ) . GetCachedData ( BucketName , * CacheKey , Data , CachedDataSize ) ;
2015-06-10 03:14:50 -04:00
}
ON_SCOPE_EXIT
{
/** Make sure completed work counter works properly regardless of where function is exited. */
GetRuntimeAssetCache ( ) . AddToAsyncCompletionCounter ( - 1 ) ;
} ;
/* Entry found. */
if ( Metadata
/* But was saved with older builder version. */
& & Metadata - > GetCachedAssetVersion ( ) < CacheBuilder - > GetAssetVersion ( ) )
{
/* Pretend entry wasn't found, so it gets rebuilt. */
FRuntimeAssetCacheBucketScopeLock Guard ( * CurrentBucket ) ;
FRuntimeAssetCacheBackend : : Get ( ) . RemoveCacheEntry ( BucketName , * CacheKey ) ;
CurrentBucket - > AddToCurrentSize ( - Metadata - > GetCachedAssetSize ( ) ) ;
CurrentBucket - > RemoveMetadataEntry ( CacheKey ) ;
delete Metadata ;
Metadata = nullptr ;
}
if ( Metadata )
{
2015-06-19 03:13:33 -04:00
check ( CacheBuilder - > GetSerializedDataSize ( ) ) ;
2015-06-10 03:14:50 -04:00
INC_DWORD_STAT ( STAT_RAC_NumCacheHits ) ;
FRuntimeAssetCacheBucketScopeLock Guard ( * CurrentBucket ) ;
2015-06-19 03:13:33 -04:00
Metadata - > SetCachedAssetSize ( CacheBuilder - > GetSerializedDataSize ( ) ) ;
2015-06-10 03:14:50 -04:00
Metadata - > SetLastAccessTime ( FDateTime : : Now ( ) ) ;
bEntryRetrieved = true ;
return ;
}
if ( ! CacheBuilder )
{
// Failed, cleanup data and return false.
INC_DWORD_STAT ( STAT_RAC_NumFails ) ;
2015-06-19 03:13:33 -04:00
Data = nullptr ;
2015-06-10 03:14:50 -04:00
bEntryRetrieved = false ;
CurrentBucket - > RemoveMetadataEntry ( CacheKey ) ;
return ;
}
{
INC_DWORD_STAT ( STAT_RAC_NumBuilds ) ;
DECLARE_SCOPE_CYCLE_COUNTER ( TEXT ( " RAC async build time " ) , STAT_RAC_AsyncBuildTime , STATGROUP_RAC )
2015-06-19 03:13:33 -04:00
Data = CacheBuilder - > Build ( ) ;
2015-06-10 03:14:50 -04:00
}
2015-06-19 03:13:33 -04:00
if ( ! Data )
2015-06-10 03:14:50 -04:00
{
// Failed, cleanup data and return false.
INC_DWORD_STAT ( STAT_RAC_NumFails ) ;
CurrentBucket - > RemoveMetadataEntry ( CacheKey ) ;
return ;
}
2015-06-19 03:13:33 -04:00
checkf ( CacheBuilder - > GetSerializedDataSize ( ) , TEXT ( " Size of asset to cache cannot be null. Asset cache key: %s " ) , * CacheKey ) ;
checkf ( CacheBuilder - > GetSerializedDataSize ( ) < CurrentBucket - > GetSize ( ) , TEXT ( " Cached asset is bigger than cache size. Increase cache size (%d) or reduce asset size(%d). Asset cache key: %s " ) , CurrentBucket - > GetSize ( ) , CacheBuilder - > GetSerializedDataSize ( ) , * CacheKey ) ;
2015-06-10 03:14:50 -04:00
FRuntimeAssetCacheBucketScopeLock Lock ( * CurrentBucket ) ;
// Do we need to make some space in cache?
2015-06-19 03:13:33 -04:00
int32 SizeOfSpaceToFree = CurrentBucket - > GetCurrentSize ( ) + CacheBuilder - > GetSerializedDataSize ( ) - CurrentBucket - > GetSize ( ) ;
2015-06-10 03:14:50 -04:00
if ( SizeOfSpaceToFree > 0 )
{
// Remove oldest entries from cache until we can fit upcoming entry.
FreeCacheSpace ( BucketName , SizeOfSpaceToFree ) ;
}
{
DECLARE_SCOPE_CYCLE_COUNTER ( TEXT ( " RAC async put time " ) , STAT_RAC_PutTime , STATGROUP_RAC )
FDateTime Now = FDateTime : : Now ( ) ;
2015-06-19 10:16:40 -04:00
Metadata = CurrentBucket - > GetMetadata ( * CacheKey ) ;
2015-06-10 03:14:50 -04:00
if ( Metadata )
{
Metadata - > SetLastAccessTime ( Now ) ;
if ( Metadata - > GetCachedAssetSize ( ) = = 0 )
{
2015-06-19 03:13:33 -04:00
CurrentBucket - > AddToCurrentSize ( CacheBuilder - > GetSerializedDataSize ( ) ) ;
2015-06-10 03:14:50 -04:00
}
2015-06-19 03:13:33 -04:00
Metadata - > SetCachedAssetSize ( CacheBuilder - > GetSerializedDataSize ( ) ) ;
2015-06-10 03:14:50 -04:00
Metadata - > SetCachedAssetVersion ( CacheBuilder - > GetAssetVersion ( ) ) ;
CurrentBucket - > AddMetadataEntry ( CacheKey , Metadata , false ) ;
}
else
{
2015-06-19 03:13:33 -04:00
Metadata = new FCacheEntryMetadata ( Now , CacheBuilder - > GetSerializedDataSize ( ) , CacheBuilder - > GetAssetVersion ( ) , CacheKeyName ) ;
2015-06-10 03:14:50 -04:00
CurrentBucket - > AddMetadataEntry ( CacheKey , Metadata , true ) ;
}
2015-06-19 03:13:33 -04:00
FRuntimeAssetCacheBackend : : Get ( ) . PutCachedData ( BucketName , * CacheKey , Data , CacheBuilder - > GetSerializedDataSize ( ) , Metadata ) ;
2015-06-10 03:14:50 -04:00
// Mark that building is finished AFTER putting data into
// cache to avoid duplicate builds of the same entry.
Metadata - > FinishBuilding ( ) ;
}
bEntryRetrieved = true ;
}
TStatId FRuntimeAssetCacheAsyncWorker : : GetStatId ( )
{
return TStatId ( ) ;
}
2015-06-19 03:13:33 -04:00
void FRuntimeAssetCacheAsyncWorker : : FireCompletionDelegate ( )
{
check ( IsInGameThread ( ) ) ;
if ( ! FiredCompletionDelegate ( ) )
{
bFiredCompletionDelegate = true ;
CompletionCallback . ExecuteIfBound ( Handle , FVoidPtrParam ( Data ) ) ;
}
}
2015-06-10 03:14:50 -04:00
FString FRuntimeAssetCacheAsyncWorker : : SanitizeCacheKey ( const TCHAR * CacheKey )
{
FString Output ;
FString Input ( CacheKey ) ;
int32 StartValid = 0 ;
int32 NumValid = 0 ;
for ( int32 i = 0 ; i < Input . Len ( ) ; i + + )
{
if ( FChar : : IsAlnum ( Input [ i ] ) | | FChar : : IsUnderscore ( Input [ i ] ) )
{
NumValid + + ;
}
else
{
// Copy the valid range so far
Output + = Input . Mid ( StartValid , NumValid ) ;
// Reset valid ranges
StartValid = i + 1 ;
NumValid = 0 ;
// Replace the invalid character with a special string
Output + = FString : : Printf ( TEXT ( " $%x " ) , uint32 ( Input [ i ] ) ) ;
}
}
// Just return the input if the entire string was valid
if ( StartValid = = 0 & & NumValid = = Input . Len ( ) )
{
return Input ;
}
else if ( NumValid > 0 )
{
// Copy the remaining valid part
Output + = Input . Mid ( StartValid , NumValid ) ;
}
return Output ;
}
FString FRuntimeAssetCacheAsyncWorker : : BuildCacheKey ( const TCHAR * VersionString , const TCHAR * PluginSpecificCacheKeySuffix )
{
FString UnsanitizedCacheKey = FString ( VersionString ) ;
UnsanitizedCacheKey . AppendChars ( PluginSpecificCacheKeySuffix , FCString : : Strlen ( PluginSpecificCacheKeySuffix ) ) ;
return SanitizeCacheKey ( * UnsanitizedCacheKey ) ;
}
2015-06-19 03:13:33 -04:00
FString FRuntimeAssetCacheAsyncWorker : : BuildCacheKey ( IRuntimeAssetCacheBuilder * CacheBuilder )
2015-06-10 03:14:50 -04:00
{
2015-06-11 07:47:00 -04:00
return BuildCacheKey ( CacheBuilder - > GetBuilderName ( ) , * CacheBuilder - > GetAssetUniqueName ( ) ) ;
2015-06-10 03:14:50 -04:00
}
void FRuntimeAssetCacheAsyncWorker : : FreeCacheSpace ( FName Bucket , int32 NumberOfBytesToFree )
{
int32 AccumulatedSize = 0 ;
FRuntimeAssetCacheBucket * CurrentBucket = ( * Buckets ) [ Bucket ] ;
while ( AccumulatedSize < = NumberOfBytesToFree )
{
FCacheEntryMetadata * OldestEntry = CurrentBucket - > GetOldestEntry ( ) ;
checkf ( ! OldestEntry - > IsBuilding ( ) , TEXT ( " Cache is trying to remove asset before it finished building. Increase cache size. Asset name: %s, cache size: %d " ) , * OldestEntry - > GetName ( ) . ToString ( ) , CurrentBucket - > GetSize ( ) ) ;
AccumulatedSize + = OldestEntry - > GetCachedAssetSize ( ) ;
FRuntimeAssetCacheBackend : : Get ( ) . RemoveCacheEntry ( Bucket , * OldestEntry - > GetName ( ) . ToString ( ) ) ;
( * Buckets ) [ Bucket ] - > AddToCurrentSize ( - OldestEntry - > GetCachedAssetSize ( ) ) ;
CurrentBucket - > RemoveMetadataEntry ( OldestEntry - > GetName ( ) . ToString ( ) ) ;
}
}
2015-06-10 09:37:39 -04:00
FArchive & operator < < ( FArchive & Ar , FCacheEntryMetadata & Metadata )
{
Ar < < Metadata . CachedAssetSize ;
Ar < < Metadata . CachedAssetVersion ;
FString String ;
if ( Ar . IsLoading ( ) )
{
Ar < < String ;
Metadata . Name = FName ( * String ) ;
}
else if ( Ar . IsSaving ( ) )
{
Metadata . Name . ToString ( String ) ;
Ar < < String ;
}
return Ar ;
}