2016-01-07 08:17:16 -05:00
// Copyright 1998-2016 Epic Games, Inc. All Rights Reserved.
2015-08-10 08:14:45 -04:00
# include "Core.h"
# include "DerivedDataBackendInterface.h"
# include "DDCCleanup.h"
# include "DDCStatsHelper.h"
# define MAX_BACKEND_KEY_LENGTH (120)
# define MAX_BACKEND_NUMBERED_SUBFOLDER_LENGTH (9)
# if PLATFORM_LINUX // PATH_MAX on Linux is 4096 (getconf PATH_MAX /, also see limits.h), so this value can be larger (note that it is still arbitrary).
// This should not affect sharing the cache between platforms as the absolute paths will be different anyway.
# define MAX_CACHE_DIR_LEN (3119)
# else
# define MAX_CACHE_DIR_LEN (119)
# endif // PLATFORM_LINUX
# define MAX_CACHE_EXTENTION_LEN (4)
/**
* Cache server that uses the OS filesystem
* The entire API should be callable from any thread ( except the singleton can be assumed to be called at least once before concurrent access ) .
* */
class FFileSystemDerivedDataBackend : public FDerivedDataBackendInterface
{
public :
/**
* Constructor that should only be called once by the singleton , grabs the cache path from the ini
* @ param InCacheDirectory directory to store the cache in
* @ param bForceReadOnly if true , do not attempt to write to this cache
*/
FFileSystemDerivedDataBackend ( const TCHAR * InCacheDirectory , bool bForceReadOnly , bool bTouchFiles , bool bPurgeTransientData , bool bDeleteOldFiles , int32 InDaysToDeleteUnusedFiles , int32 InMaxNumFoldersToCheck , int32 InMaxContinuousFileChecks )
: CachePath ( InCacheDirectory )
, bReadOnly ( bForceReadOnly )
, bFailed ( true )
, bTouch ( bTouchFiles )
, bPurgeTransient ( bPurgeTransientData )
, DaysToDeleteUnusedFiles ( InDaysToDeleteUnusedFiles )
{
// If we find a platform that has more stingent limits, this needs to be rethought.
static_assert ( MAX_BACKEND_KEY_LENGTH + MAX_CACHE_DIR_LEN + MAX_BACKEND_NUMBERED_SUBFOLDER_LENGTH + MAX_CACHE_EXTENTION_LEN < PLATFORM_MAX_FILEPATH_LENGTH ,
" Not enough room left for cache keys in max path. " ) ;
const double SlowInitDuration = 10.0 ;
double AccessDuration = 0.0 ;
check ( CachePath . Len ( ) ) ;
FPaths : : NormalizeFilename ( CachePath ) ;
const FString AbsoluteCachePath = IFileManager : : Get ( ) . ConvertToAbsolutePathForExternalAppForRead ( * CachePath ) ;
if ( AbsoluteCachePath . Len ( ) > MAX_CACHE_DIR_LEN )
{
const FText ErrorMessage = FText : : Format ( NSLOCTEXT ( " DerivedDataCache " , " PathTooLong " , " Cache path {0} is longer than {1} characters...please adjust [DerivedDataBackendGraph] paths to be shorter (this leaves more room for cache keys). " ) , FText : : FromString ( AbsoluteCachePath ) , FText : : AsNumber ( MAX_CACHE_DIR_LEN ) ) ;
FMessageDialog : : Open ( EAppMsgType : : Ok , ErrorMessage ) ;
UE_LOG ( LogDerivedDataCache , Fatal , TEXT ( " %s " ) , * ErrorMessage . ToString ( ) ) ;
}
if ( ! bReadOnly )
{
double TestStart = FPlatformTime : : Seconds ( ) ;
FString TempFilename = CachePath / FGuid : : NewGuid ( ) . ToString ( ) + " .tmp " ;
FFileHelper : : SaveStringToFile ( FString ( " TEST " ) , * TempFilename ) ;
int32 TestFileSize = IFileManager : : Get ( ) . FileSize ( * TempFilename ) ;
if ( TestFileSize < 4 )
{
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " Fail to write to %s, derived data cache to this directory will be read only. " ) , * CachePath ) ;
}
else
{
bFailed = false ;
}
if ( TestFileSize > = 0 )
{
IFileManager : : Get ( ) . Delete ( * TempFilename , false , false , true ) ;
}
AccessDuration = FPlatformTime : : Seconds ( ) - TestStart ;
}
if ( bFailed )
{
double StartTime = FPlatformTime : : Seconds ( ) ;
TArray < FString > FilesAndDirectories ;
IFileManager : : Get ( ) . FindFiles ( FilesAndDirectories , * ( CachePath / TEXT ( " *.* " ) ) , true , true ) ;
AccessDuration = FPlatformTime : : Seconds ( ) - StartTime ;
if ( FilesAndDirectories . Num ( ) > 0 )
{
bReadOnly = true ;
bFailed = false ;
}
}
if ( FString ( FCommandLine : : Get ( ) ) . Contains ( TEXT ( " DerivedDataCache " ) ) )
{
bTouch = true ; // we always touch files when running the DDC commandlet
}
// The command line (-ddctouch) enables touch on all filesystem backends if specified.
bTouch = bTouch | | FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " DDCTOUCH " ) ) ;
if ( bReadOnly )
{
bTouch = false ; // we won't touch read only paths
}
if ( bTouch )
{
UE_LOG ( LogDerivedDataCache , Display , TEXT ( " Files in %s will be touched. " ) , * CachePath ) ;
}
if ( ! bFailed & & AccessDuration > SlowInitDuration )
{
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " %s access is very slow (initialization took %.2lf seconds), consider disabling it. " ) , * CachePath , AccessDuration ) ;
}
if ( ! bReadOnly & & ! bFailed & & bDeleteOldFiles & & ! FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " NODDCCLEANUP " ) ) & & FDDCCleanup : : Get ( ) )
{
FDDCCleanup : : Get ( ) - > AddFilesystem ( CachePath , InDaysToDeleteUnusedFiles , InMaxNumFoldersToCheck , InMaxContinuousFileChecks ) ;
}
}
/** return true if the cache is usable **/
bool IsUsable ( )
{
return ! bFailed ;
}
/** return true if this cache is writable **/
virtual bool IsWritable ( ) override
{
return ! bReadOnly ;
}
/**
* Synchronous test for the existence of a cache item
*
* @ param CacheKey Alphanumeric + underscore key of this cache item
* @ return true if the data probably will be found , this can ' t be guaranteed because of concurrency in the backends , corruption , etc
*/
virtual bool CachedDataProbablyExists ( const TCHAR * CacheKey ) override
{
2016-01-19 09:54:25 -05:00
# if ENABLE_DDC_STATS
static FName NAME_CachedDataProbablyExists ( TEXT ( " CachedDataProbablyExists " ) ) ;
FDDCScopeStatHelper Stat ( CacheKey , NAME_CachedDataProbablyExists ) ;
static FName NAME_FileDDCPath ( TEXT ( " FileDDCPath " ) ) ;
Stat . AddTag ( NAME_FileDDCPath , CachePath ) ;
# endif
2015-08-10 08:14:45 -04:00
check ( ! bFailed ) ;
FString Filename = BuildFilename ( CacheKey ) ;
FDateTime TimeStamp = IFileManager : : Get ( ) . GetTimeStamp ( * Filename ) ;
bool bExists = TimeStamp > FDateTime : : MinValue ( ) ;
if ( bExists )
{
// Update file timestamp to prevent it from being deleted by DDC Cleanup.
if ( bTouch | |
( ! bReadOnly & & ( FDateTime : : UtcNow ( ) - TimeStamp ) . GetDays ( ) > ( DaysToDeleteUnusedFiles / 4 ) ) )
{
IFileManager : : Get ( ) . SetTimeStamp ( * Filename , FDateTime : : UtcNow ( ) ) ;
}
}
return bExists ;
}
/**
* Synchronous retrieve of a cache item
*
* @ param CacheKey Alphanumeric + underscore key of this cache item
* @ param OutData Buffer to receive the results , if any were found
* @ return true if any data was found , and in this case OutData is non - empty
*/
2015-12-10 16:56:55 -05:00
virtual bool GetCachedData ( const TCHAR * CacheKey , TArray < uint8 > & Data ) override
2015-08-10 08:14:45 -04:00
{
2016-01-19 09:54:25 -05:00
# if ENABLE_DDC_STATS
static FName NAME_GetCachedData ( TEXT ( " GetCachedData " ) ) ;
FDDCScopeStatHelper Stat ( CacheKey , NAME_GetCachedData ) ;
static FName NAME_FileDDCPath ( TEXT ( " FileDDCPath " ) ) ;
static FName NAME_Retrieved ( TEXT ( " Retrieved " ) ) ;
Stat . AddTag ( NAME_FileDDCPath , CachePath ) ;
# endif
2015-08-10 08:14:45 -04:00
check ( ! bFailed ) ;
FString Filename = BuildFilename ( CacheKey ) ;
double StartTime = FPlatformTime : : Seconds ( ) ;
if ( FFileHelper : : LoadFileToArray ( Data , * Filename , FILEREAD_Silent ) )
{
double ReadDuration = FPlatformTime : : Seconds ( ) - StartTime ;
double ReadSpeed = ReadDuration > 5.0 ? ( Data . Num ( ) / ReadDuration ) / ( 1024.0 * 1024.0 ) : 100.0 ;
// Slower than 0.5MB/s?
Copying //UE4/Orion-Staging to //UE4/Main (Origin: //Orion/Dev-General @2826496)
#lockdown Nick.Penwarden
==========================
MAJOR FEATURES + CHANGES
==========================
Change 2826201 on 2016/01/13 by Zabir.Hoque
Add more verbose logging to try to understand #OR-11297
#lockdown Andrew.Grant
#CodeReview Marcus.Wassmer
#RB none
#TESTS compiled Win64 debug editor, ran agora_p
Change 2826170 on 2016/01/13 by Marcus.Wassmer
Flush unloaded resource properly in LoadMap
#codereview Gil.Gribb
#rb none
#test cycling game. memory is freed properly now.
#lockdown Andrew.Grant
Change 2826135 on 2016/01/12 by Michael.Noland
Orion: Improve login screen on PC to reduce the potential impact of framerate on data center ping calculation
- Disabled async streaming for the duration of the QOS ping measurement to avoid hitches
- Added a circular throbber in the top left corner of the login screen indicating that something is async streaming (as a diagnostic tool for users affected by the datacenter ping, can be removed in the future)
- Added logging of the current average frame time when the datacenter ping is finalized
- Added a 'Pick Ideal Settings' button to the login screen (note: on the actual screen, not the login widget, so it will not appear on PS4)
#jira OR-12453
#rb paul.moore
#tests Ran a QOS server and client and verified that the new logging is occurring, tried out the new benchmark button, etc...
Merging CL# 2826128 using //Orion/Main_to_//Orion/Dev-General
Change 2826131 on 2016/01/12 by Michael.Noland
#UE4 - added print out of MS/FPS during Qos ping evaluation
#rb michael.noland
#tests loaded up through login screen to see output
Merging CL# 2825678 using //Orion/Main_to_//Orion/Dev-General
Change 2826128 on 2016/01/12 by Michael.Noland
Orion: Improve login screen on PC to reduce the potential impact of framerate on data center ping calculation
- Disabled async streaming for the duration of the QOS ping measurement to avoid hitches
- Added a circular throbber in the top left corner of the login screen indicating that something is async streaming (as a diagnostic tool for users affected by the datacenter ping, can be removed in the future)
- Added logging of the current average frame time when the datacenter ping is finalized
- Added a 'Pick Ideal Settings' button to the login screen (note: on the actual screen, not the login widget, so it will not appear on PS4)
#jira OR-12453
#rb paul.moore
#tests Ran a QOS server and client and verified that the new logging is occurring, tried out the new benchmark button, etc...
Merging CL# 2826116 using //Orion/Release-Next->//Orion/Main
Change 2826116 on 2016/01/12 by Michael.Noland
Orion: Improve login screen on PC to reduce the potential impact of framerate on data center ping calculation
- Disabled async streaming for the duration of the QOS ping measurement to avoid hitches
- Added a circular throbber in the top left corner of the login screen indicating that something is async streaming (as a diagnostic tool for users affected by the datacenter ping, can be removed in the future)
- Added logging of the current average frame time when the datacenter ping is finalized
- Added a 'Pick Ideal Settings' button to the login screen (note: on the actual screen, not the login widget, so it will not appear on PS4)
#jira OR-12453
#rb paul.moore
#tests Ran a QOS server and client and verified that the new logging is occurring, tried out the new benchmark button, etc...
#lockdown andrew.grant
#codereview josh.markiewicz
Change 2825772 on 2016/01/12 by Dmitry.Rekman
Linux signal handling improvements.
- Switch crash handlers to use "crash malloc" (preallocated memory) on crash.
- Remove unnecessary memory allocations from graceful termination handler.
#rb none
#tests Run the Linux server and crashed it a few times.
#codereview David.Vossel, Michael.Trepka
Change 2825768 on 2016/01/12 by Josh.Markiewicz
#UE4 - added print out of MS/FPS during Qos ping evaluation
#rb michael.noland
#tests loaded up through login screen to see output
Change 2825703 on 2016/01/12 by Brian.Karis
Switched on new motion blur. Set temporal AA sharpness to 1.
#rb none
#TESTS editor
Change 2825689 on 2016/01/12 by Lina.Halper
Fix for get animation notify crash
https://jira.ol.epicgames.net/browse/OR-12248
https://jira.ol.epicgames.net/browse/OR-12348
- Also fixed the crash in preview of persona due to blend sample cache contains previous animation data
- Also fixed blend space player to reinitialize cache data
- The main issue is marker doesn't clamp the length, causing notifies ensure to trigger.
#rb : Laurent.Delayen
#tests: 10 Sparrows bot match for long time
#code review: Martin.Wilson
#lockdown: Andrew.Grant
Change 2825680 on 2016/01/12 by Martin.Mittring
fixed all cases with r.Tonemapper.ScreenPercentage, ScreenPercentage, Fringe, Vignette, ViewRect, flickering with transluceny (View members have been modified while other thread was reading)
#rb:Olaf.Piesche, David.Hill
#test: PC, many cases
Change 2825579 on 2016/01/12 by Chris.Bunner
Force shadow shape bone indices on the required update list.
#rb Lina.Halper, Rolando.Caloca
#tests Editor
#codereview Daniel.Wright
#jira OR-12339
Change 2825443 on 2016/01/12 by Martin.Mittring
2016-01-14 08:11:47 -05:00
UE_CLOG ( ReadSpeed < 0.5 , LogDerivedDataCache , Warning , TEXT ( " %s is very slow (%.2lfMB/s) when accessing %s, consider disabling it. " ) , * CachePath , ReadSpeed , * Filename ) ;
2015-08-10 08:14:45 -04:00
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " FileSystemDerivedDataBackend: Cache hit on %s " ) , * Filename ) ;
2016-01-19 09:54:25 -05:00
# if ENABLE_DDC_STATS
Stat . AddTag ( NAME_Retrieved , true ) ;
# endif
2015-08-10 08:14:45 -04:00
return true ;
}
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " FileSystemDerivedDataBackend: Cache miss on %s " ) , * Filename ) ;
Data . Empty ( ) ;
2016-01-19 09:54:25 -05:00
# if ENABLE_DDC_STATS
Stat . AddTag ( NAME_Retrieved , false ) ;
# endif
2015-08-10 08:14:45 -04:00
return false ;
}
/**
* Asynchronous , fire - and - forget placement of a cache item
*
* @ param CacheKey Alphanumeric + underscore key of this cache item
* @ param OutData Buffer containing the data to cache , can be destroyed after the call returns , immediately
* @ param bPutEvenIfExists If true , then do not attempt skip the put even if CachedDataProbablyExists returns true
*/
2015-12-10 16:56:55 -05:00
virtual void PutCachedData ( const TCHAR * CacheKey , TArray < uint8 > & Data , bool bPutEvenIfExists ) override
2015-08-10 08:14:45 -04:00
{
2016-01-19 09:54:25 -05:00
# if ENABLE_DDC_STATS
static FName NAME_PutCachedData ( TEXT ( " PutCachedData " ) ) ;
FDDCScopeStatHelper Stat ( CacheKey , NAME_PutCachedData ) ;
static FName NAME_FileDDCPath ( TEXT ( " FileDDCPath " ) ) ;
Stat . AddTag ( NAME_FileDDCPath , CachePath ) ;
# endif
2015-08-10 08:14:45 -04:00
check ( ! bFailed ) ;
if ( ! bReadOnly )
{
if ( bPutEvenIfExists | | ! CachedDataProbablyExists ( CacheKey ) )
{
check ( Data . Num ( ) ) ;
FString Filename = BuildFilename ( CacheKey ) ;
FString TempFilename ( TEXT ( " temp. " ) ) ;
TempFilename + = FGuid : : NewGuid ( ) . ToString ( ) ;
TempFilename = FPaths : : GetPath ( Filename ) / TempFilename ;
bool bResult ;
{
bResult = FFileHelper : : SaveArrayToFile ( Data , * TempFilename ) ;
}
if ( bResult )
{
if ( IFileManager : : Get ( ) . FileSize ( * TempFilename ) = = Data . Num ( ) )
{
bool DoMove = ! CachedDataProbablyExists ( CacheKey ) ;
if ( bPutEvenIfExists & & ! DoMove )
{
DoMove = true ;
RemoveCachedData ( CacheKey , /*bTransient=*/ false ) ;
}
if ( DoMove )
{
if ( ! IFileManager : : Get ( ) . Move ( * Filename , * TempFilename , true , true , false , true ) )
{
UE_LOG ( LogDerivedDataCache , Log , TEXT ( " FFileSystemDerivedDataBackend: Move collision, attempt at redundant update, OK %s. " ) , * Filename ) ;
}
else
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " FFileSystemDerivedDataBackend: Successful cache put to %s " ) , * Filename ) ;
}
}
}
else
{
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " FFileSystemDerivedDataBackend: Temp file is short %s! " ) , * TempFilename ) ;
}
}
else
{
UE_LOG ( LogDerivedDataCache , Warning , TEXT ( " FFileSystemDerivedDataBackend: Could not write temp file %s! " ) , * TempFilename ) ;
}
// if everything worked, this is not necessary, but we will make every effort to avoid leaving junk in the cache
if ( FPaths : : FileExists ( TempFilename ) )
{
IFileManager : : Get ( ) . Delete ( * TempFilename , false , false , true ) ;
}
}
}
}
void RemoveCachedData ( const TCHAR * CacheKey , bool bTransient ) override
{
check ( ! bFailed ) ;
if ( ! bReadOnly & & ( ! bTransient | | bPurgeTransient ) )
{
FString Filename = BuildFilename ( CacheKey ) ;
if ( bTransient )
{
UE_LOG ( LogDerivedDataCache , Verbose , TEXT ( " Deleting transient cached data. Key=%s Filename=%s " ) , CacheKey , * Filename ) ;
}
IFileManager : : Get ( ) . Delete ( * Filename , false , false , true ) ;
}
}
private :
/**
* Threadsafe method to compute the filename from the cachekey , currently just adds a path and an extension .
*
* @ param CacheKey Alphanumeric + underscore key of this cache item
* @ return filename built from the cache key
*/
FString BuildFilename ( const TCHAR * CacheKey )
{
FString Key = FString ( CacheKey ) . ToUpper ( ) ;
for ( int32 i = 0 ; i < Key . Len ( ) ; i + + )
{
check ( FChar : : IsAlnum ( Key [ i ] ) | | FChar : : IsUnderscore ( Key [ i ] ) | | Key [ i ] = = L ' $ ' ) ;
}
uint32 Hash = FCrc : : StrCrc_DEPRECATED ( * Key ) ;
// this creates a tree of 1000 directories
FString HashPath = FString : : Printf ( TEXT ( " %1d/%1d/%1d/ " ) , ( Hash / 100 ) % 10 , ( Hash / 10 ) % 10 , Hash % 10 ) ;
return CachePath / HashPath / Key + TEXT ( " .udd " ) ;
}
/** Base path we are storing the cache files in. **/
FString CachePath ;
/** If true, do not attempt to write to this cache **/
bool bReadOnly ;
/** If true, we failed to write to this directory and it did not contain anything so we should not be used **/
bool bFailed ;
/** If true, CachedDataProbablyExists will update the file timestamps. */
bool bTouch ;
/** If true, allow transient data to be removed from the cache. */
bool bPurgeTransient ;
/** Age of file when it should be deleted from DDC cache. */
int32 DaysToDeleteUnusedFiles ;
} ;
FDerivedDataBackendInterface * CreateFileSystemDerivedDataBackend ( const TCHAR * CacheDirectory , bool bForceReadOnly /*= false*/ , bool bTouchFiles /*= false*/ , bool bPurgeTransient /*= false*/ , bool bDeleteOldFiles /*= false*/ , int32 InDaysToDeleteUnusedFiles /*= 60*/ , int32 InMaxNumFoldersToCheck /*= -1*/ , int32 InMaxContinuousFileChecks /*= -1*/ )
{
FFileSystemDerivedDataBackend * FileDDB = new FFileSystemDerivedDataBackend ( CacheDirectory , bForceReadOnly , bTouchFiles , bPurgeTransient , bDeleteOldFiles , InDaysToDeleteUnusedFiles , InMaxNumFoldersToCheck , InMaxContinuousFileChecks ) ;
if ( ! FileDDB - > IsUsable ( ) )
{
delete FileDDB ;
FileDDB = NULL ;
}
return FileDDB ;
}