2014-12-07 19:09:38 -05:00
// Copyright 1998-2015 Epic Games, Inc. All Rights Reserved.
2014-08-14 03:37:01 -04:00
# include "HotReloadPrivatePCH.h"
2014-09-16 15:06:34 -04:00
# include "Runtime/Analytics/Analytics/Public/Interfaces/IAnalyticsProvider.h"
2014-08-14 03:37:01 -04:00
# include "ScopedTimers.h"
2014-09-05 13:31:22 -04:00
# include "DesktopPlatformModule.h"
2014-09-17 04:34:40 -04:00
# if WITH_ENGINE
# include "HotReloadClassReinstancer.h"
# include "EngineAnalytics.h"
# endif
2014-08-14 03:37:01 -04:00
DEFINE_LOG_CATEGORY ( LogHotReload ) ;
# define LOCTEXT_NAMESPACE "HotReload"
2014-12-11 06:03:58 -05:00
namespace EThreeStateBool
{
enum Type
{
False ,
True ,
Unknown
} ;
static bool ToBool ( EThreeStateBool : : Type Value )
{
switch ( Value )
{
case EThreeStateBool : : False :
return false ;
case EThreeStateBool : : True :
return true ;
default :
UE_LOG ( LogHotReload , Fatal , TEXT ( " Can't convert EThreeStateBool to bool value because it's Unknown " ) ) ;
break ;
}
return false ;
}
static EThreeStateBool : : Type FromBool ( bool Value )
{
return Value ? EThreeStateBool : : True : EThreeStateBool : : False ;
}
} ;
2014-08-14 03:37:01 -04:00
/**
* Module for HotReload support
*/
2014-09-05 12:46:22 -04:00
class FHotReloadModule : public IHotReloadModule , FSelfRegisteringExec
2014-08-14 03:37:01 -04:00
{
public :
2014-12-11 06:03:58 -05:00
2014-09-05 12:46:22 -04:00
FHotReloadModule ( )
{
ModuleCompileReadPipe = nullptr ;
bRequestCancelCompilation = false ;
2014-12-11 06:03:58 -05:00
bIsAnyGameModuleLoaded = EThreeStateBool : : Unknown ;
bDirectoryWatcherInitialized = false ;
2014-09-05 12:46:22 -04:00
}
2014-08-14 03:37:01 -04:00
/** IModuleInterface implementation */
virtual void StartupModule ( ) override ;
virtual void ShutdownModule ( ) override ;
2014-09-05 12:46:22 -04:00
/** FSelfRegisteringExec implementation */
virtual bool Exec ( UWorld * Inworld , const TCHAR * Cmd , FOutputDevice & Ar ) override ;
2014-08-14 03:37:01 -04:00
/** IHotReloadInterface implementation */
2014-09-05 12:46:22 -04:00
virtual void Tick ( ) override ;
virtual void SaveConfig ( ) override ;
2014-12-11 06:03:58 -05:00
virtual bool RecompileModule ( const FName InModuleName , const bool bReloadAfterRecompile , FOutputDevice & Ar , bool bFailIfGeneratedCodeChanges = true , bool bForceCodeProject = false ) override ;
2014-09-05 12:46:22 -04:00
virtual bool IsCurrentlyCompiling ( ) const override { return ModuleCompileProcessHandle . IsValid ( ) ; }
virtual void RequestStopCompilation ( ) override { bRequestCancelCompilation = true ; }
2014-08-14 03:37:01 -04:00
virtual void AddHotReloadFunctionRemap ( Native NewFunctionPointer , Native OldFunctionPointer ) override ;
2015-01-26 20:16:08 -05:00
virtual ECompilationResult : : Type RebindPackages ( TArray < UPackage * > Packages , TArray < FName > DependentModules , const bool bWaitForCompletion , FOutputDevice & Ar ) override ;
2014-12-11 06:03:58 -05:00
virtual ECompilationResult : : Type DoHotReloadFromEditor ( ) override ;
2014-09-05 12:46:22 -04:00
virtual FHotReloadEvent & OnHotReload ( ) override { return HotReloadEvent ; }
virtual FModuleCompilerStartedEvent & OnModuleCompilerStarted ( ) override { return ModuleCompilerStartedEvent ; }
virtual FModuleCompilerFinishedEvent & OnModuleCompilerFinished ( ) override { return ModuleCompilerFinishedEvent ; }
virtual FString GetModuleCompileMethod ( FName InModuleName ) override ;
2014-12-11 06:03:58 -05:00
virtual bool IsAnyGameModuleLoaded ( ) override ;
2014-08-14 03:37:01 -04:00
private :
2014-09-05 12:46:22 -04:00
/**
* Enumerates compilation methods for modules .
*/
enum class EModuleCompileMethod
{
Runtime ,
External ,
Unknown
} ;
/**
* Helper structure to hold on to module state while asynchronously recompiling DLLs
*/
struct FModuleToRecompile
{
/** Name of the module */
FString ModuleName ;
/** Desired module file name suffix, or empty string if not needed */
FString ModuleFileSuffix ;
/** The module file name to use after a compilation succeeds, or an empty string if not changing */
FString NewModuleFilename ;
} ;
/**
* Helper structure to store the compile time and method for a module
*/
struct FModuleCompilationData
{
/** Has a timestamp been set for the .dll file */
bool bHasFileTimeStamp ;
/** Last known timestamp for the .dll file */
FDateTime FileTimeStamp ;
/** Last known compilation method of the .dll file */
EModuleCompileMethod CompileMethod ;
FModuleCompilationData ( )
: bHasFileTimeStamp ( false )
, CompileMethod ( EModuleCompileMethod : : Unknown )
{ }
} ;
2014-08-14 03:37:01 -04:00
/**
* Adds a callback to directory watcher for the game binaries folder .
*/
void InitHotReloadWatcher ( ) ;
/**
* Removes a directory watcher callback
*/
void ShutdownHotReloadWatcher ( ) ;
/**
* Performs hot - reload from IDE ( when game DLLs change )
*/
void DoHotReloadFromIDE ( ) ;
/**
* Performs internal module recompilation
*/
ECompilationResult : : Type RebindPackagesInternal ( TArray < UPackage * > Packages , TArray < FName > DependentModules , const bool bWaitForCompletion , FOutputDevice & Ar ) ;
/**
* Does the actual hot - reload , unloads old modules , loads new ones
*/
2014-09-18 08:10:17 -04:00
ECompilationResult : : Type DoHotReloadInternal ( bool bRecompileFinished , ECompilationResult : : Type CompilationResult , TArray < UPackage * > Packages , TArray < FName > InDependentModules , FOutputDevice & HotReloadAr ) ;
2014-08-14 03:37:01 -04:00
/**
* Callback for async ompilation
*/
2014-09-18 08:10:17 -04:00
void DoHotReloadCallback ( bool bRecompileFinished , ECompilationResult : : Type CompilationResult , TArray < UPackage * > Packages , TArray < FName > InDependentModules , FOutputDevice & HotReloadAr ) ;
2014-08-14 03:37:01 -04:00
/**
2014-12-17 11:52:39 -05:00
* Gets all currently loaded game module names and optionally , the file names for those modules
2014-08-14 03:37:01 -04:00
*/
2014-12-17 11:52:39 -05:00
void GetGameModules ( TArray < FString > & OutGameModules , TArray < FString > * OutGameModuleFilePaths = nullptr ) ;
2014-08-14 03:37:01 -04:00
/**
* Gets packages to re - bind and dependent modules .
*/
void GetPackagesToRebindAndDependentModules ( const TArray < FString > & InGameModuleNames , TArray < UPackage * > & OutPackagesToRebind , TArray < FName > & OutDependentModules ) ;
2014-09-16 15:06:34 -04:00
# if WITH_ENGINE
2014-08-14 03:37:01 -04:00
/**
* Called from CoreUObject to re - instance hot - reloaded classes
*/
void ReinstanceClass ( UClass * OldClass , UClass * NewClass ) ;
2014-09-16 15:06:34 -04:00
# endif
2014-08-14 03:37:01 -04:00
/**
* Tick function for FTicker : checks for re - loaded modules and does hot - reload from IDE
*/
bool Tick ( float DeltaTime ) ;
/**
* Directory watcher callback
*/
void OnHotReloadBinariesChanged ( const TArray < struct FFileChangeData > & FileChanges ) ;
2014-08-20 13:58:31 -04:00
/**
* Broadcasts that a hot reload just finished .
*
* @ param bWasTriggeredAutomatically True if the hot reload was invoked automatically by the hot reload system after detecting a changed DLL
*/
void BroadcastHotReload ( bool bWasTriggeredAutomatically )
2014-08-14 03:37:01 -04:00
{
2014-08-20 13:58:31 -04:00
HotReloadEvent . Broadcast ( bWasTriggeredAutomatically ) ;
2014-08-14 03:37:01 -04:00
}
/**
* Sends analytics event about the re - load
2014-09-05 12:46:22 -04:00
*/
2014-08-14 03:37:01 -04:00
static void RecordAnalyticsEvent ( const TCHAR * ReloadFrom , ECompilationResult : : Type Result , double Duration , int32 PackageCount , int32 DependentModulesCount ) ;
2014-09-05 12:46:22 -04:00
/**
* Declares a type of delegates that is executed after a module recompile has finished .
*
* The first argument signals whether compilation has finished .
* The second argument shows whether compilation was successful or not .
*/
2014-09-18 08:10:17 -04:00
DECLARE_DELEGATE_TwoParams ( FRecompileModulesCallback , bool , ECompilationResult : : Type ) ;
2014-09-05 12:46:22 -04:00
/**
* Tries to recompile the specified modules in the background . When recompiling finishes , the specified callback
* delegate will be triggered , passing along a bool that tells you whether the compile action succeeded . This
* function never tries to unload modules or to reload the modules after they finish compiling . You should do
* that yourself in the recompile completion callback !
*
* @ param ModuleNames Names of the modules to recompile
* @ param RecompileModulesCallback Callback function to execute after compilation finishes ( whether successful or not . )
* @ param bWaitForCompletion True if the function should not return until recompilation attempt has finished and callbacks have fired
* @ param Ar Output device for logging compilation status
* @ return True if the recompile action was kicked off successfully . If this returns false , then the recompile callback will never fire . In the case where bWaitForCompletion = false , this will also return false if the compilation failed for any reason .
*/
bool RecompileModulesAsync ( const TArray < FName > ModuleNames , const FRecompileModulesCallback & InRecompileModulesCallback , const bool bWaitForCompletion , FOutputDevice & Ar ) ;
/** Called for successfully re-complied module */
void OnModuleCompileSucceeded ( FName ModuleName , const FString & NewModuleFilename ) ;
/**
* Tries to recompile the specified DLL using UBT . Does not interact with modules . This is a low level routine .
*
* @ param ModuleNames List of modules to recompile , including the module name and optional file suffix .
* @ param Ar Output device for logging compilation status .
2014-12-11 06:03:58 -05:00
* @ param bForceCodeProject Even if it ' s a non - code project , treat it as code - based project
2014-09-05 12:46:22 -04:00
*/
2014-12-11 06:03:58 -05:00
bool RecompileModuleDLLs ( const TArray < FModuleToRecompile > & ModuleNames , FOutputDevice & Ar , bool bFailIfGeneratedCodeChanges , bool bForceCodeProject ) ;
2014-09-05 12:46:22 -04:00
/** Returns arguments to pass to UnrealBuildTool when compiling modules */
static FString MakeUBTArgumentsForModuleCompiling ( ) ;
/**
* Starts compiling DLL files for one or more modules .
*
* @ param GameName The name of the game .
* @ param ModuleNames The list of modules to compile .
* @ param InRecompileModulesCallback Callback function to make when module recompiles .
* @ param Ar
* @ param bInFailIfGeneratedCodeChanges If true , fail the compilation if generated headers change .
* @ param InAdditionalCmdLineArgs Additional arguments to pass to UBT .
2014-12-11 06:03:58 -05:00
* @ param bForceCodeProject Compile as code - based project even if there ' s no game modules loaded
2014-09-05 12:46:22 -04:00
* @ return true if successful , false otherwise .
*/
bool StartCompilingModuleDLLs ( const FString & GameName , const TArray < FModuleToRecompile > & ModuleNames ,
const FRecompileModulesCallback & InRecompileModulesCallback , FOutputDevice & Ar , bool bInFailIfGeneratedCodeChanges ,
2014-12-11 06:03:58 -05:00
const FString & InAdditionalCmdLineArgs , bool bForceCodeProject ) ;
2014-09-05 12:46:22 -04:00
/** Launches UnrealBuildTool with the specified command line parameters */
bool InvokeUnrealBuildToolForCompile ( const FString & InCmdLineParams , FOutputDevice & Ar ) ;
/** Checks to see if a pending compilation action has completed and optionally waits for it to finish. If completed, fires any appropriate callbacks and reports status provided bFireEvents is true. */
2014-10-08 04:42:34 -04:00
void CheckForFinishedModuleDLLCompile ( const bool bWaitForCompletion , bool & bCompileStillInProgress , bool & bCompileSucceeded , FOutputDevice & Ar , bool bFireEvents = true ) ;
2014-09-05 12:46:22 -04:00
/** Called when the compile data for a module need to be update in memory and written to config */
void UpdateModuleCompileData ( FName ModuleName ) ;
/** Called when a new module is added to the manager to get the saved compile data from config */
static void ReadModuleCompilationInfoFromConfig ( FName ModuleName , FModuleCompilationData & CompileData ) ;
/** Saves the module's compile data to config */
static void WriteModuleCompilationInfoToConfig ( FName ModuleName , const FModuleCompilationData & CompileData ) ;
/** Access the module's file and read the timestamp from the file system. Returns true if the timestamp was read successfully. */
bool GetModuleFileTimeStamp ( FName ModuleName , FDateTime & OutFileTimeStamp ) const ;
2014-10-23 12:05:53 -04:00
/** Checks if the specified array of modules to recompile contains only game modules */
bool ContainsOnlyGameModules ( const TArray < FModuleToRecompile > & ModuleNames ) const ;
2014-12-11 06:03:58 -05:00
/** Callback registered with ModuleManager to know if any new modules have been loaded */
void ModulesChangedCallback ( FName ModuleName , EModuleChangeReason ReasonForChange ) ;
2014-08-14 03:37:01 -04:00
/** FTicker delegate (hot-reload from IDE) */
FTickerDelegate TickerDelegate ;
2015-01-08 09:29:27 -05:00
/** Handle to the registered TickerDelegate */
FDelegateHandle TickerDelegateHandle ;
2014-08-14 03:37:01 -04:00
/** Callback when game binaries folder changes */
IDirectoryWatcher : : FDirectoryChanged BinariesFolderChangedDelegate ;
2015-01-08 09:29:27 -05:00
/** Handle to the registered delegate above */
FDelegateHandle BinariesFolderChangedDelegateHandle ;
2014-08-14 03:37:01 -04:00
/** True if currently hot-reloading from editor (suppresses hot-reload from IDE) */
bool bIsHotReloadingFromEditor ;
struct FRecompiledModule
{
FString Name ;
FString NewFilename ;
FRecompiledModule ( ) { }
FRecompiledModule ( const FString & InName , const FString & InFilename )
: Name ( InName )
, NewFilename ( InFilename )
{ }
} ;
/** New module DLLs */
TArray < FRecompiledModule > NewModules ;
2014-10-22 13:33:53 -04:00
/** Moduels that have been recently recompiled from the editor **/
TSet < FString > ModulesRecentlyCompiledInTheEditor ;
2014-08-14 03:37:01 -04:00
/** Delegate broadcast when a module has been hot-reloaded */
FHotReloadEvent HotReloadEvent ;
2014-09-05 12:46:22 -04:00
/** Array of modules that we're currently recompiling */
TArray < FModuleToRecompile > ModulesBeingCompiled ;
/** Array of modules that we're going to recompile */
TArray < FModuleToRecompile > ModulesThatWereBeingRecompiled ;
/** Last known compilation data for each module */
TMap < FName , TSharedRef < FModuleCompilationData > > ModuleCompileData ;
/** Multicast delegate which will broadcast a notification when the compiler starts */
FModuleCompilerStartedEvent ModuleCompilerStartedEvent ;
/** Multicast delegate which will broadcast a notification when the compiler finishes */
FModuleCompilerFinishedEvent ModuleCompilerFinishedEvent ;
/** When compiling a module using an external application, stores the handle to the process that is running */
FProcHandle ModuleCompileProcessHandle ;
/** When compiling a module using an external application, this is the process read pipe handle */
void * ModuleCompileReadPipe ;
/** When compiling a module using an external application, this is the text that was read from the read pipe handle */
FString ModuleCompileReadPipeText ;
/** Callback to execute after an asynchronous recompile has completed (whether successful or not.) */
FRecompileModulesCallback RecompileModulesCallback ;
/** true if we should attempt to cancel the current async compilation */
bool bRequestCancelCompilation ;
2014-12-11 06:03:58 -05:00
/** Tracks the validity of the game module existance */
EThreeStateBool : : Type bIsAnyGameModuleLoaded ;
/** True if the directory watcher has been successfully initialized */
bool bDirectoryWatcherInitialized ;
2014-08-14 03:37:01 -04:00
} ;
2014-09-05 12:46:22 -04:00
namespace HotReloadDefs
{
const static FString CompilationInfoConfigSection ( " ModuleFileTracking " ) ;
// These strings should match the values of the enum EModuleCompileMethod in ModuleManager.h
// and should be handled in ReadModuleCompilationInfoFromConfig() & WriteModuleCompilationInfoToConfig() below
const static FString CompileMethodRuntime ( " Runtime " ) ;
const static FString CompileMethodExternal ( " External " ) ;
const static FString CompileMethodUnknown ( " Unknown " ) ;
// Add one minute epsilon to timestamp comparision
const static FTimespan TimeStampEpsilon ( 0 , 1 , 0 ) ;
}
2014-08-14 03:37:01 -04:00
IMPLEMENT_MODULE ( FHotReloadModule , HotReload ) ;
void FHotReloadModule : : StartupModule ( )
{
bIsHotReloadingFromEditor = false ;
2014-09-16 15:06:34 -04:00
# if WITH_ENGINE
2014-08-14 03:37:01 -04:00
// Register re-instancing delegate (Core)
FCoreUObjectDelegates : : ReplaceHotReloadClassDelegate . BindRaw ( this , & FHotReloadModule : : ReinstanceClass ) ;
2014-09-16 15:06:34 -04:00
# endif
2014-08-14 03:37:01 -04:00
// Register directory watcher delegate
InitHotReloadWatcher ( ) ;
// Register hot-reload from IDE ticker
TickerDelegate = FTickerDelegate : : CreateRaw ( this , & FHotReloadModule : : Tick ) ;
2015-01-08 09:29:27 -05:00
TickerDelegateHandle = FTicker : : GetCoreTicker ( ) . AddTicker ( TickerDelegate ) ;
2014-12-11 06:03:58 -05:00
FModuleManager : : Get ( ) . OnModulesChanged ( ) . AddRaw ( this , & FHotReloadModule : : ModulesChangedCallback ) ;
2014-08-14 03:37:01 -04:00
}
void FHotReloadModule : : ShutdownModule ( )
{
2015-01-08 09:29:27 -05:00
FTicker : : GetCoreTicker ( ) . RemoveTicker ( TickerDelegateHandle ) ;
2014-08-14 03:37:01 -04:00
ShutdownHotReloadWatcher ( ) ;
}
2014-09-05 12:46:22 -04:00
bool FHotReloadModule : : Exec ( UWorld * Inworld , const TCHAR * Cmd , FOutputDevice & Ar )
{
# if !UE_BUILD_SHIPPING
if ( FParse : : Command ( & Cmd , TEXT ( " Module " ) ) )
{
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
// Recompile <ModuleName>
if ( FParse : : Command ( & Cmd , TEXT ( " Recompile " ) ) )
{
const FString ModuleNameStr = FParse : : Token ( Cmd , 0 ) ;
if ( ! ModuleNameStr . IsEmpty ( ) )
{
const FName ModuleName ( * ModuleNameStr ) ;
const bool bReloadAfterRecompile = true ;
2014-12-11 06:03:58 -05:00
const bool bForceCodeProject = false ;
const bool bFailIfGeneratedCodeChanges = true ;
RecompileModule ( ModuleName , bReloadAfterRecompile , Ar , bFailIfGeneratedCodeChanges , bForceCodeProject ) ;
2014-09-05 12:46:22 -04:00
}
return true ;
}
2014-09-11 11:39:20 -04:00
# endif // WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
}
# endif // !UE_BUILD_SHIPPING
return false ;
}
void FHotReloadModule : : Tick ( )
{
// We never want to block on a pending compile when checking compilation status during Tick(). We're
// just checking so that we can fire callbacks if and when compilation has finished.
const bool bWaitForCompletion = false ;
// Ignored output variables
bool bCompileStillInProgress = false ;
bool bCompileSucceeded = false ;
FOutputDeviceNull NullOutput ;
CheckForFinishedModuleDLLCompile ( bWaitForCompletion , bCompileStillInProgress , bCompileSucceeded , NullOutput ) ;
}
void FHotReloadModule : : SaveConfig ( )
{
// Find all the modules
TArray < FModuleStatus > Modules ;
FModuleManager : : Get ( ) . QueryModules ( Modules ) ;
// Update the compile data for each one
for ( const FModuleStatus & Module : Modules )
{
UpdateModuleCompileData ( * Module . Name ) ;
}
}
FString FHotReloadModule : : GetModuleCompileMethod ( FName InModuleName )
{
if ( ! ModuleCompileData . Contains ( InModuleName ) )
{
UpdateModuleCompileData ( InModuleName ) ;
}
switch ( ModuleCompileData . FindChecked ( InModuleName ) . Get ( ) . CompileMethod )
{
case EModuleCompileMethod : : External :
return HotReloadDefs : : CompileMethodExternal ;
case EModuleCompileMethod : : Runtime :
return HotReloadDefs : : CompileMethodRuntime ;
default :
return HotReloadDefs : : CompileMethodUnknown ;
}
}
2014-12-11 06:03:58 -05:00
bool FHotReloadModule : : RecompileModule ( const FName InModuleName , const bool bReloadAfterRecompile , FOutputDevice & Ar , bool bFailIfGeneratedCodeChanges , bool bForceCodeProject )
2014-09-05 12:46:22 -04:00
{
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-10-22 13:33:53 -04:00
UE_LOG ( LogHotReload , Log , TEXT ( " Recompiling module %s... " ) , * InModuleName . ToString ( ) ) ;
// This is an internal request for hot-reload (not from IDE)
bIsHotReloadingFromEditor = true ;
// A list of modules that have been recompiled in the editor is going to prevent false
// hot-reload from IDE events as this call is blocking any potential callbacks coming from the filesystem
// and bIsHotReloadingFromEditor may not be enough to prevent those from being treated as actual hot-reload from IDE modules
ModulesRecentlyCompiledInTheEditor . Empty ( ) ;
2014-09-05 12:46:22 -04:00
2014-11-03 15:47:28 -05:00
2014-09-05 12:46:22 -04:00
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " CodeModuleName " ) , FText : : FromName ( InModuleName ) ) ;
const FText StatusUpdate = FText : : Format ( NSLOCTEXT ( " ModuleManager " , " Recompile_SlowTaskName " , " Compiling {CodeModuleName}... " ) , Args ) ;
2015-01-30 10:48:51 -05:00
FScopedSlowTask SlowTask ( 2 , StatusUpdate ) ;
2014-10-08 04:42:34 -04:00
SlowTask . MakeDialog ( ) ;
2014-09-05 12:46:22 -04:00
ModuleCompilerStartedEvent . Broadcast ( ) ;
// Update our set of known modules, in case we don't already know about this module
FModuleManager : : Get ( ) . AddModule ( InModuleName ) ;
// Only use rolling module names if the module was already loaded into memory. This allows us to try compiling
// the module without actually having to unload it first.
const bool bWasModuleLoaded = FModuleManager : : Get ( ) . IsModuleLoaded ( InModuleName ) ;
const bool bUseRollingModuleNames = bWasModuleLoaded ;
2015-01-30 10:48:51 -05:00
SlowTask . EnterProgressFrame ( ) ;
2014-09-05 12:46:22 -04:00
bool bWasSuccessful = true ;
if ( bUseRollingModuleNames )
{
// First, try to compile the module. If the module is already loaded, we won't unload it quite yet. Instead
// make sure that it compiles successfully.
// Find a unique file name for the module
FString UniqueSuffix ;
FString UniqueModuleFileName ;
FModuleManager : : Get ( ) . MakeUniqueModuleFilename ( InModuleName , UniqueSuffix , UniqueModuleFileName ) ;
TArray < FModuleToRecompile > ModulesToRecompile ;
FModuleToRecompile ModuleToRecompile ;
ModuleToRecompile . ModuleName = InModuleName . ToString ( ) ;
ModuleToRecompile . ModuleFileSuffix = UniqueSuffix ;
ModuleToRecompile . NewModuleFilename = UniqueModuleFileName ;
ModulesToRecompile . Add ( ModuleToRecompile ) ;
2014-10-22 13:33:53 -04:00
ModulesRecentlyCompiledInTheEditor . Add ( FPaths : : ConvertRelativePathToFull ( UniqueModuleFileName ) ) ;
2014-12-11 06:03:58 -05:00
bWasSuccessful = RecompileModuleDLLs ( ModulesToRecompile , Ar , bFailIfGeneratedCodeChanges , bForceCodeProject ) ;
2014-09-05 12:46:22 -04:00
}
2015-01-30 10:48:51 -05:00
SlowTask . EnterProgressFrame ( ) ;
2014-09-05 12:46:22 -04:00
if ( bWasSuccessful )
{
// Shutdown the module if it's already running
if ( bWasModuleLoaded )
{
Ar . Logf ( TEXT ( " Unloading module before compile. " ) ) ;
FModuleManager : : Get ( ) . UnloadOrAbandonModuleWithCallback ( InModuleName , Ar ) ;
}
if ( ! bUseRollingModuleNames )
{
// Try to recompile the DLL
TArray < FModuleToRecompile > ModulesToRecompile ;
FModuleToRecompile ModuleToRecompile ;
2014-12-11 06:03:58 -05:00
ModuleToRecompile . ModuleName = InModuleName . ToString ( ) ;
2014-10-22 13:33:53 -04:00
if ( FModuleManager : : Get ( ) . IsModuleLoaded ( InModuleName ) )
{
ModulesRecentlyCompiledInTheEditor . Add ( FPaths : : ConvertRelativePathToFull ( FModuleManager : : Get ( ) . GetModuleFilename ( InModuleName ) ) ) ;
}
2014-12-11 06:03:58 -05:00
else
{
ModuleToRecompile . NewModuleFilename = FModuleManager : : Get ( ) . GetGameBinariesDirectory ( ) / FModuleManager : : GetCleanModuleFilename ( InModuleName , true ) ;
ModulesRecentlyCompiledInTheEditor . Add ( FPaths : : ConvertRelativePathToFull ( ModuleToRecompile . NewModuleFilename ) ) ;
}
ModulesToRecompile . Add ( ModuleToRecompile ) ;
bWasSuccessful = RecompileModuleDLLs ( ModulesToRecompile , Ar , bFailIfGeneratedCodeChanges , bForceCodeProject ) ;
2014-09-05 12:46:22 -04:00
}
// Reload the module if it was loaded before we recompiled
2014-12-11 06:03:58 -05:00
if ( bWasSuccessful & & ( bWasModuleLoaded | | bForceCodeProject ) & & bReloadAfterRecompile )
2014-09-05 12:46:22 -04:00
{
Ar . Logf ( TEXT ( " Reloading module after successful compile. " ) ) ;
bWasSuccessful = FModuleManager : : Get ( ) . LoadModuleWithCallback ( InModuleName , Ar ) ;
}
}
2014-12-11 06:03:58 -05:00
if ( bForceCodeProject & & bWasSuccessful )
{
BroadcastHotReload ( false ) ;
}
bIsHotReloadingFromEditor = false ;
2014-10-22 13:33:53 -04:00
2014-09-05 12:46:22 -04:00
return bWasSuccessful ;
# else
return false ;
2014-09-11 11:39:20 -04:00
# endif // WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
}
2014-08-14 03:37:01 -04:00
/** Type hash for a UObject Function Pointer, maybe not a great choice, but it should be sufficient for the needs here. **/
inline uint32 GetTypeHash ( Native A )
{
return * ( uint32 * ) & A ;
}
/** Map from old function pointer to new function pointer for hot reload. */
static TMap < Native , Native > HotReloadFunctionRemap ;
/** Adds and entry for the UFunction native pointer remap table */
void FHotReloadModule : : AddHotReloadFunctionRemap ( Native NewFunctionPointer , Native OldFunctionPointer )
{
Native OtherNewFunction = HotReloadFunctionRemap . FindRef ( OldFunctionPointer ) ;
check ( ! OtherNewFunction | | OtherNewFunction = = NewFunctionPointer ) ;
check ( NewFunctionPointer ) ;
check ( OldFunctionPointer ) ;
HotReloadFunctionRemap . Add ( OldFunctionPointer , NewFunctionPointer ) ;
}
2014-12-11 06:03:58 -05:00
ECompilationResult : : Type FHotReloadModule : : DoHotReloadFromEditor ( )
2014-08-14 03:37:01 -04:00
{
2014-12-11 06:03:58 -05:00
// Get all game modules we want to compile
2014-08-14 03:37:01 -04:00
TArray < FString > GameModuleNames ;
2014-12-11 06:03:58 -05:00
GetGameModules ( GameModuleNames ) ;
// Don't wait -- we want compiling to happen asynchronously
const bool bWaitForCompletion = false ;
2014-08-14 03:37:01 -04:00
TArray < UPackage * > PackagesToRebind ;
TArray < FName > DependentModules ;
2014-12-11 06:03:58 -05:00
2014-08-14 03:37:01 -04:00
ECompilationResult : : Type Result = ECompilationResult : : Unsupported ;
2014-12-11 06:03:58 -05:00
2014-08-14 03:37:01 -04:00
// Analytics
double Duration = 0.0 ;
if ( GameModuleNames . Num ( ) > 0 )
{
FScopedDurationTimer Timer ( Duration ) ;
GetPackagesToRebindAndDependentModules ( GameModuleNames , PackagesToRebind , DependentModules ) ;
Result = RebindPackagesInternal ( PackagesToRebind , DependentModules , bWaitForCompletion , * GLog ) ;
}
RecordAnalyticsEvent ( TEXT ( " Editor " ) , Result , Duration , PackagesToRebind . Num ( ) , DependentModules . Num ( ) ) ;
2014-12-11 06:03:58 -05:00
return Result ;
2014-08-14 03:37:01 -04:00
}
2014-09-18 08:10:17 -04:00
void FHotReloadModule : : DoHotReloadCallback ( bool bRecompileFinished , ECompilationResult : : Type CompilationResult , TArray < UPackage * > Packages , TArray < FName > InDependentModules , FOutputDevice & HotReloadAr )
2014-08-14 03:37:01 -04:00
{
2014-09-18 08:10:17 -04:00
DoHotReloadInternal ( bRecompileFinished , CompilationResult , Packages , InDependentModules , HotReloadAr ) ;
2014-08-14 03:37:01 -04:00
}
2014-09-18 08:10:17 -04:00
ECompilationResult : : Type FHotReloadModule : : DoHotReloadInternal ( bool bRecompileFinished , ECompilationResult : : Type CompilationResult , TArray < UPackage * > Packages , TArray < FName > InDependentModules , FOutputDevice & HotReloadAr )
2014-08-14 03:37:01 -04:00
{
ECompilationResult : : Type Result = ECompilationResult : : Unsupported ;
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-18 08:10:17 -04:00
if ( CompilationResult = = ECompilationResult : : Succeeded )
2014-08-14 03:37:01 -04:00
{
FFeedbackContext & ErrorsFC = UClass : : GetDefaultPropertiesFeedbackContext ( ) ;
ErrorsFC . Errors . Empty ( ) ;
ErrorsFC . Warnings . Empty ( ) ;
// Rebind the hot reload DLL
TGuardValue < bool > GuardIsHotReload ( GIsHotReload , true ) ;
TGuardValue < bool > GuardIsInitialLoad ( GIsInitialLoad , true ) ;
HotReloadFunctionRemap . Empty ( ) ; // redundant
CollectGarbage ( GARBAGE_COLLECTION_KEEPFLAGS ) ; // we create a new CDO in the transient package...this needs to go away before we try again.
// Load the new modules up
bool bReloadSucceeded = false ;
for ( TArray < UPackage * > : : TConstIterator CurPackageIt ( Packages ) ; CurPackageIt ; + + CurPackageIt )
{
UPackage * Package = * CurPackageIt ;
FName ShortPackageName = FPackageName : : GetShortFName ( Package - > GetFName ( ) ) ;
// Abandon the old module. We can't unload it because various data structures may be living
// that have vtables pointing to code that would become invalidated.
FModuleManager : : Get ( ) . AbandonModule ( ShortPackageName ) ;
// Module should never be loaded at this point
check ( ! FModuleManager : : Get ( ) . IsModuleLoaded ( ShortPackageName ) ) ;
// Load the newly-recompiled module up (it will actually have a different DLL file name at this point.)
FModuleManager : : Get ( ) . LoadModule ( ShortPackageName ) ;
bReloadSucceeded = FModuleManager : : Get ( ) . IsModuleLoaded ( ShortPackageName ) ;
if ( ! bReloadSucceeded )
{
HotReloadAr . Logf ( ELogVerbosity : : Warning , TEXT ( " HotReload failed, reload failed %s. " ) , * Package - > GetName ( ) ) ;
Result = ECompilationResult : : OtherCompilationError ;
break ;
}
}
// Load dependent modules.
for ( int32 Nx = 0 ; Nx < InDependentModules . Num ( ) ; + + Nx )
{
const FName ModuleName = InDependentModules [ Nx ] ;
FModuleManager : : Get ( ) . UnloadOrAbandonModuleWithCallback ( ModuleName , HotReloadAr ) ;
const bool bLoaded = FModuleManager : : Get ( ) . LoadModuleWithCallback ( ModuleName , HotReloadAr ) ;
if ( ! bLoaded )
{
HotReloadAr . Logf ( ELogVerbosity : : Warning , TEXT ( " Unable to reload module %s " ) , * ModuleName . GetPlainNameString ( ) ) ;
}
}
if ( ErrorsFC . Errors . Num ( ) | | ErrorsFC . Warnings . Num ( ) )
{
TArray < FString > All ;
All = ErrorsFC . Errors ;
All + = ErrorsFC . Warnings ;
ErrorsFC . Errors . Empty ( ) ;
ErrorsFC . Warnings . Empty ( ) ;
FString AllInOne ;
for ( int32 Index = 0 ; Index < All . Num ( ) ; Index + + )
{
AllInOne + = All [ Index ] ;
AllInOne + = TEXT ( " \n " ) ;
}
HotReloadAr . Logf ( ELogVerbosity : : Warning , TEXT ( " Some classes could not be reloaded: \n %s " ) , * AllInOne ) ;
}
if ( bReloadSucceeded )
{
int32 Count = 0 ;
// Remap all native functions (and gather scriptstructs)
TArray < UScriptStruct * > ScriptStructs ;
for ( FRawObjectIterator It ; It ; + + It )
{
if ( UFunction * Function = Cast < UFunction > ( * It ) )
{
if ( Native NewFunction = HotReloadFunctionRemap . FindRef ( Function - > GetNativeFunc ( ) ) )
{
Count + + ;
Function - > SetNativeFunc ( NewFunction ) ;
}
}
if ( UScriptStruct * ScriptStruct = Cast < UScriptStruct > ( * It ) )
{
if ( Packages . ContainsByPredicate ( [ = ] ( UPackage * Package ) { return ScriptStruct - > IsIn ( Package ) ; } ) & & ScriptStruct - > GetCppStructOps ( ) )
{
ScriptStructs . Add ( ScriptStruct ) ;
}
}
}
// now let's set up the script structs...this relies on super behavior, so null them all, then set them all up. Internally this sets them up hierarchically.
for ( int32 ScriptIndex = 0 ; ScriptIndex < ScriptStructs . Num ( ) ; ScriptIndex + + )
{
ScriptStructs [ ScriptIndex ] - > ClearCppStructOps ( ) ;
}
for ( int32 ScriptIndex = 0 ; ScriptIndex < ScriptStructs . Num ( ) ; ScriptIndex + + )
{
ScriptStructs [ ScriptIndex ] - > PrepareCppStructOps ( ) ;
check ( ScriptStructs [ ScriptIndex ] - > GetCppStructOps ( ) ) ;
}
HotReloadAr . Logf ( ELogVerbosity : : Warning , TEXT ( " HotReload successful (%d functions remapped %d scriptstructs remapped) " ) , Count , ScriptStructs . Num ( ) ) ;
HotReloadFunctionRemap . Empty ( ) ;
Result = ECompilationResult : : Succeeded ;
}
2014-08-20 13:58:31 -04:00
const bool bWasTriggeredAutomatically = ! bIsHotReloadingFromEditor ;
BroadcastHotReload ( bWasTriggeredAutomatically ) ;
2014-08-14 03:37:01 -04:00
}
2014-09-18 08:10:17 -04:00
else if ( ECompilationResult : : Failed ( CompilationResult ) & & bRecompileFinished )
2014-08-14 03:37:01 -04:00
{
HotReloadAr . Logf ( ELogVerbosity : : Warning , TEXT ( " HotReload failed, recompile failed " ) ) ;
Result = ECompilationResult : : OtherCompilationError ;
}
# endif
bIsHotReloadingFromEditor = false ;
return Result ;
}
2015-01-26 20:16:08 -05:00
ECompilationResult : : Type FHotReloadModule : : RebindPackages ( TArray < UPackage * > InPackages , TArray < FName > DependentModules , const bool bWaitForCompletion , FOutputDevice & Ar )
2014-08-14 03:37:01 -04:00
{
ECompilationResult : : Type Result = ECompilationResult : : Unknown ;
double Duration = 0.0 ;
{
FScopedDurationTimer RebindTimer ( Duration ) ;
Result = RebindPackagesInternal ( InPackages , DependentModules , bWaitForCompletion , Ar ) ;
}
RecordAnalyticsEvent ( TEXT ( " Rebind " ) , Result , Duration , InPackages . Num ( ) , DependentModules . Num ( ) ) ;
2015-01-26 20:16:08 -05:00
return Result ;
2014-08-14 03:37:01 -04:00
}
ECompilationResult : : Type FHotReloadModule : : RebindPackagesInternal ( TArray < UPackage * > InPackages , TArray < FName > DependentModules , const bool bWaitForCompletion , FOutputDevice & Ar )
{
ECompilationResult : : Type Result = ECompilationResult : : Unsupported ;
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-08-14 03:37:01 -04:00
bool bCanRebind = InPackages . Num ( ) > 0 ;
// Verify that we're going to be able to rebind the specified packages
if ( bCanRebind )
{
for ( UPackage * Package : InPackages )
{
check ( Package ) ;
if ( Package - > GetOuter ( ) ! = NULL )
{
Ar . Logf ( ELogVerbosity : : Warning , TEXT ( " Could not rebind package for %s, package is either not bound yet or is not a DLL. " ) , * Package - > GetName ( ) ) ;
bCanRebind = false ;
break ;
}
}
}
// We can only proceed if a compile isn't already in progress
2014-09-05 12:46:22 -04:00
if ( IsCurrentlyCompiling ( ) )
2014-08-14 03:37:01 -04:00
{
Ar . Logf ( ELogVerbosity : : Warning , TEXT ( " Could not rebind package because a module compile is already in progress. " ) ) ;
bCanRebind = false ;
}
if ( bCanRebind )
{
bIsHotReloadingFromEditor = true ;
const double StartTime = FPlatformTime : : Seconds ( ) ;
TArray < FName > ModuleNames ;
for ( UPackage * Package : InPackages )
{
// Attempt to recompile this package's module
FName ShortPackageName = FPackageName : : GetShortFName ( Package - > GetFName ( ) ) ;
ModuleNames . Add ( ShortPackageName ) ;
}
// Add dependent modules.
ModuleNames . Append ( DependentModules ) ;
// Start compiling modules
2014-09-05 12:46:22 -04:00
const bool bCompileStarted = RecompileModulesAsync (
2014-08-14 03:37:01 -04:00
ModuleNames ,
2014-09-05 12:46:22 -04:00
FRecompileModulesCallback : : CreateRaw < FHotReloadModule , TArray < UPackage * > , TArray < FName > , FOutputDevice & > ( this , & FHotReloadModule : : DoHotReloadCallback , InPackages , DependentModules , Ar ) ,
2014-08-14 03:37:01 -04:00
bWaitForCompletion ,
Ar ) ;
if ( bCompileStarted )
{
if ( bWaitForCompletion )
{
Ar . Logf ( ELogVerbosity : : Warning , TEXT ( " HotReload operation took %4.1fs. " ) , float ( FPlatformTime : : Seconds ( ) - StartTime ) ) ;
2014-09-10 05:13:32 -04:00
bIsHotReloadingFromEditor = false ;
2014-08-14 03:37:01 -04:00
}
else
{
Ar . Logf ( ELogVerbosity : : Warning , TEXT ( " Starting HotReload took %4.1fs. " ) , float ( FPlatformTime : : Seconds ( ) - StartTime ) ) ;
}
Result = ECompilationResult : : Succeeded ;
}
else
{
Ar . Logf ( ELogVerbosity : : Warning , TEXT ( " RebindPackages failed because the compiler could not be started. " ) ) ;
Result = ECompilationResult : : OtherCompilationError ;
2014-09-10 05:13:32 -04:00
bIsHotReloadingFromEditor = false ;
2014-08-14 03:37:01 -04:00
}
}
else
# endif
{
Ar . Logf ( ELogVerbosity : : Warning , TEXT ( " RebindPackages not possible for specified packages (or application was compiled in monolithic mode.) " ) ) ;
}
return Result ;
}
2014-09-16 15:06:34 -04:00
# if WITH_ENGINE
2014-08-14 03:37:01 -04:00
void FHotReloadModule : : ReinstanceClass ( UClass * OldClass , UClass * NewClass )
2014-09-17 04:34:40 -04:00
{
FHotReloadClassReinstancer ReinstanceHelper ( NewClass , OldClass ) ;
if ( ReinstanceHelper . ClassNeedsReinstancing ( ) )
{
UE_LOG ( LogHotReload , Log , TEXT ( " Re-instancing %s after hot-reload. " ) , NewClass ? * NewClass - > GetName ( ) : * OldClass - > GetName ( ) ) ;
2014-12-11 06:03:58 -05:00
ReinstanceHelper . ReinstanceObjectsAndUpdateDefaults ( ) ;
2014-09-17 04:34:40 -04:00
}
2014-08-14 03:37:01 -04:00
}
2014-09-16 15:06:34 -04:00
# endif
2014-08-14 03:37:01 -04:00
2014-12-17 11:52:39 -05:00
void FHotReloadModule : : GetGameModules ( TArray < FString > & OutGameModules , TArray < FString > * OutGameModuleFilePaths )
2014-08-14 03:37:01 -04:00
{
// Ask the module manager for a list of currently-loaded gameplay modules
TArray < FModuleStatus > ModuleStatuses ;
FModuleManager : : Get ( ) . QueryModules ( ModuleStatuses ) ;
for ( TArray < FModuleStatus > : : TConstIterator ModuleStatusIt ( ModuleStatuses ) ; ModuleStatusIt ; + + ModuleStatusIt )
{
const FModuleStatus & ModuleStatus = * ModuleStatusIt ;
// We only care about game modules that are currently loaded
if ( ModuleStatus . bIsLoaded & & ModuleStatus . bIsGameModule )
{
2014-12-17 11:52:39 -05:00
OutGameModules . Add ( ModuleStatus . Name ) ;
if ( OutGameModuleFilePaths ! = nullptr )
{
OutGameModuleFilePaths - > Add ( ModuleStatus . FilePath ) ;
}
2014-08-14 03:37:01 -04:00
}
}
}
void FHotReloadModule : : OnHotReloadBinariesChanged ( const TArray < struct FFileChangeData > & FileChanges )
{
if ( bIsHotReloadingFromEditor )
{
// DO NOTHING, this case is handled by RebindPackages
return ;
}
TArray < FString > GameModuleNames ;
2014-12-17 11:52:39 -05:00
TArray < FString > GameModuleFilePaths ;
GetGameModules ( GameModuleNames , & GameModuleFilePaths ) ;
2014-08-14 03:37:01 -04:00
if ( GameModuleNames . Num ( ) > 0 )
{
// Check if any of the game DLLs has been added
for ( auto & Change : FileChanges )
{
2014-10-07 04:40:55 -04:00
# if PLATFORM_MAC
// On the Mac the Add event is for a temporary linker(?) file that gets immediately renamed
// to a dylib. In the future we may want to support modified event for all platforms anyway once
// shadow copying works with hot-reload.
if ( Change . Action = = FFileChangeData : : FCA_Modified )
# else
2014-08-14 03:37:01 -04:00
if ( Change . Action = = FFileChangeData : : FCA_Added )
2014-10-07 04:40:55 -04:00
# endif
2014-08-14 03:37:01 -04:00
{
const FString Filename = FPaths : : GetCleanFilename ( Change . Filename ) ;
if ( Filename . EndsWith ( FPlatformProcess : : GetModuleExtension ( ) ) )
{
2014-12-17 11:52:39 -05:00
for ( int32 GameModuleIndex = 0 ; GameModuleIndex < GameModuleNames . Num ( ) ; + + GameModuleIndex )
2014-08-14 03:37:01 -04:00
{
2014-12-17 11:52:39 -05:00
const FString & GameModuleName = GameModuleNames [ GameModuleIndex ] ;
const FString & GameModuleFilePath = GameModuleFilePaths [ GameModuleIndex ] ;
const FString GameModuleFileNameWithoutExtension = FPaths : : GetBaseFilename ( GameModuleFilePath ) ;
if ( Filename . StartsWith ( GameModuleFileNameWithoutExtension + TEXT ( " - " ) ) ) // Hot reload always adds a numbered suffix preceded by a hyphen, but otherwise the module name must match exactly!
2014-08-14 03:37:01 -04:00
{
2014-12-17 11:52:39 -05:00
if ( ! NewModules . ContainsByPredicate ( [ & ] ( const FRecompiledModule & Module ) { return Module . Name = = GameModuleName ; } ) & &
! ModulesRecentlyCompiledInTheEditor . Contains ( FPaths : : ConvertRelativePathToFull ( Change . Filename ) ) )
{
// Add to queue. We do not hot-reload here as there may potentially be other modules being compiled.
NewModules . Add ( FRecompiledModule ( GameModuleName , Change . Filename ) ) ;
UE_LOG ( LogHotReload , Log , TEXT ( " New module detected: %s " ) , * Filename ) ;
}
2014-08-14 03:37:01 -04:00
}
}
}
}
}
}
}
void FHotReloadModule : : InitHotReloadWatcher ( )
{
FDirectoryWatcherModule & DirectoryWatcherModule = FModuleManager : : Get ( ) . LoadModuleChecked < FDirectoryWatcherModule > ( TEXT ( " DirectoryWatcher " ) ) ;
IDirectoryWatcher * DirectoryWatcher = DirectoryWatcherModule . Get ( ) ;
if ( DirectoryWatcher )
{
// Watch the game binaries folder for new files
FString BinariesPath = FPaths : : ConvertRelativePathToFull ( FPaths : : GameDir ( ) / TEXT ( " Binaries " ) / FPlatformProcess : : GetBinariesSubdirectory ( ) ) ;
2014-12-11 06:03:58 -05:00
if ( FPaths : : DirectoryExists ( BinariesPath ) )
{
BinariesFolderChangedDelegate = IDirectoryWatcher : : FDirectoryChanged : : CreateRaw ( this , & FHotReloadModule : : OnHotReloadBinariesChanged ) ;
2015-01-08 09:29:27 -05:00
bDirectoryWatcherInitialized = DirectoryWatcher - > RegisterDirectoryChangedCallback_Handle ( BinariesPath , BinariesFolderChangedDelegate , BinariesFolderChangedDelegateHandle ) ;
2014-12-11 06:03:58 -05:00
}
2014-08-14 03:37:01 -04:00
}
}
void FHotReloadModule : : ShutdownHotReloadWatcher ( )
{
2014-08-22 07:48:36 -04:00
FDirectoryWatcherModule * DirectoryWatcherModule = FModuleManager : : GetModulePtr < FDirectoryWatcherModule > ( TEXT ( " DirectoryWatcher " ) ) ;
2014-08-21 10:24:05 -04:00
if ( DirectoryWatcherModule ! = nullptr )
2014-08-14 03:37:01 -04:00
{
2014-08-21 10:24:05 -04:00
IDirectoryWatcher * DirectoryWatcher = DirectoryWatcherModule - > Get ( ) ;
if ( DirectoryWatcher )
{
FString BinariesPath = FPaths : : ConvertRelativePathToFull ( FPaths : : GameDir ( ) / TEXT ( " Binaries " ) / FPlatformProcess : : GetBinariesSubdirectory ( ) ) ;
2015-01-08 09:29:27 -05:00
DirectoryWatcher - > UnregisterDirectoryChangedCallback_Handle ( BinariesPath , BinariesFolderChangedDelegateHandle ) ;
2014-08-21 10:24:05 -04:00
}
2014-08-14 03:37:01 -04:00
}
}
bool FHotReloadModule : : Tick ( float DeltaTime )
{
if ( NewModules . Num ( ) )
{
// We have new modules in the queue, but make sure UBT has finished compiling all of them
2014-09-18 15:12:13 -04:00
if ( ! FDesktopPlatformModule : : Get ( ) - > IsUnrealBuildToolRunning ( ) )
2014-08-14 03:37:01 -04:00
{
DoHotReloadFromIDE ( ) ;
NewModules . Empty ( ) ;
}
else
{
UE_LOG ( LogHotReload , Verbose , TEXT ( " Detected %d reloaded modules but UnrealBuildTool is still running " ) , NewModules . Num ( ) ) ;
}
}
return true ;
}
void FHotReloadModule : : GetPackagesToRebindAndDependentModules ( const TArray < FString > & InGameModuleNames , TArray < UPackage * > & OutPackagesToRebind , TArray < FName > & OutDependentModules )
{
for ( auto & GameModuleName : InGameModuleNames )
{
FString PackagePath ( FString ( TEXT ( " /Script/ " ) ) + GameModuleName ) ;
UPackage * Package = FindPackage ( NULL , * PackagePath ) ;
if ( Package ! = NULL )
{
OutPackagesToRebind . Add ( Package ) ;
}
else
{
OutDependentModules . Add ( * GameModuleName ) ;
}
}
}
void FHotReloadModule : : DoHotReloadFromIDE ( )
{
TArray < FString > GameModuleNames ;
TArray < UPackage * > PackagesToRebind ;
TArray < FName > DependentModules ;
double Duration = 0.0 ;
ECompilationResult : : Type Result = ECompilationResult : : Unsupported ;
GetGameModules ( GameModuleNames ) ;
if ( GameModuleNames . Num ( ) > 0 )
{
FScopedDurationTimer Timer ( Duration ) ;
UE_LOG ( LogHotReload , Log , TEXT ( " Starting Hot-Reload from IDE " ) ) ;
2014-10-08 04:42:34 -04:00
FScopedSlowTask SlowTask ( 100.f , LOCTEXT ( " CompilingGameCode " , " Compiling Game Code " ) ) ;
SlowTask . MakeDialog ( ) ;
2014-08-14 03:37:01 -04:00
// Update compile data before we start compiling
for ( auto & NewModule : NewModules )
{
2014-10-08 04:42:34 -04:00
// Move on 10% / num items
SlowTask . EnterProgressFrame ( 10.f / NewModules . Num ( ) ) ;
2014-09-05 12:46:22 -04:00
UpdateModuleCompileData ( * NewModule . Name ) ;
OnModuleCompileSucceeded ( * NewModule . Name , NewModule . NewFilename ) ;
2014-08-14 03:37:01 -04:00
}
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( 10 ) ;
2014-08-14 03:37:01 -04:00
GetPackagesToRebindAndDependentModules ( GameModuleNames , PackagesToRebind , DependentModules ) ;
2014-10-08 04:42:34 -04:00
SlowTask . EnterProgressFrame ( 80 ) ;
2014-08-14 03:37:01 -04:00
check ( PackagesToRebind . Num ( ) | | DependentModules . Num ( ) )
{
const bool bRecompileFinished = true ;
2014-09-18 08:10:17 -04:00
const ECompilationResult : : Type RecompileResult = ECompilationResult : : Succeeded ;
Result = DoHotReloadInternal ( bRecompileFinished , RecompileResult , PackagesToRebind , DependentModules , * GLog ) ;
2014-08-14 03:37:01 -04:00
}
}
RecordAnalyticsEvent ( TEXT ( " IDE " ) , Result , Duration , PackagesToRebind . Num ( ) , DependentModules . Num ( ) ) ;
}
void FHotReloadModule : : RecordAnalyticsEvent ( const TCHAR * ReloadFrom , ECompilationResult : : Type Result , double Duration , int32 PackageCount , int32 DependentModulesCount )
{
2014-09-16 15:06:34 -04:00
# if WITH_ENGINE
2014-08-14 03:37:01 -04:00
if ( FEngineAnalytics : : IsAvailable ( ) )
{
TArray < FAnalyticsEventAttribute > ReloadAttribs ;
ReloadAttribs . Add ( FAnalyticsEventAttribute ( TEXT ( " ReloadFrom " ) , ReloadFrom ) ) ;
ReloadAttribs . Add ( FAnalyticsEventAttribute ( TEXT ( " Result " ) , ECompilationResult : : ToString ( Result ) ) ) ;
ReloadAttribs . Add ( FAnalyticsEventAttribute ( TEXT ( " Duration " ) , FString : : Printf ( TEXT ( " %.4lf " ) , Duration ) ) ) ;
ReloadAttribs . Add ( FAnalyticsEventAttribute ( TEXT ( " Packages " ) , FString : : Printf ( TEXT ( " %d " ) , PackageCount ) ) ) ;
ReloadAttribs . Add ( FAnalyticsEventAttribute ( TEXT ( " DependentModules " ) , FString : : Printf ( TEXT ( " %d " ) , DependentModulesCount ) ) ) ;
FEngineAnalytics : : GetProvider ( ) . RecordEvent ( TEXT ( " Editor.Usage.HotReload " ) , ReloadAttribs ) ;
}
2014-09-16 15:06:34 -04:00
# endif
2014-08-14 03:37:01 -04:00
}
2014-09-05 12:46:22 -04:00
bool FHotReloadModule : : RecompileModulesAsync ( const TArray < FName > ModuleNames , const FRecompileModulesCallback & InRecompileModulesCallback , const bool bWaitForCompletion , FOutputDevice & Ar )
{
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
// NOTE: This method of recompiling always using a rolling file name scheme, since we never want to unload before
// we start recompiling, and we need the output DLL to be unlocked before we invoke the compiler
ModuleCompilerStartedEvent . Broadcast ( ) ;
TArray < FModuleToRecompile > ModulesToRecompile ;
for ( TArray < FName > : : TConstIterator CurModuleIt ( ModuleNames ) ; CurModuleIt ; + + CurModuleIt )
{
const FName CurModuleName = * CurModuleIt ;
// Update our set of known modules, in case we don't already know about this module
FModuleManager : : Get ( ) . AddModule ( CurModuleName ) ;
FString NewModuleFileNameOnSuccess = FModuleManager : : Get ( ) . GetModuleFilename ( CurModuleName ) ;
// Find a unique file name for the module
FString UniqueSuffix ;
FString UniqueModuleFileName ;
FModuleManager : : Get ( ) . MakeUniqueModuleFilename ( CurModuleName , UniqueSuffix , UniqueModuleFileName ) ;
// If the recompile succeeds, we'll update our cached file name to use the new unique file name
// that we setup for the module
NewModuleFileNameOnSuccess = UniqueModuleFileName ;
FModuleToRecompile ModuleToRecompile ;
ModuleToRecompile . ModuleName = CurModuleName . ToString ( ) ;
ModuleToRecompile . ModuleFileSuffix = UniqueSuffix ;
ModuleToRecompile . NewModuleFilename = UniqueModuleFileName ;
ModulesToRecompile . Add ( ModuleToRecompile ) ;
}
// Kick off compilation!
const FString AdditionalArguments = MakeUBTArgumentsForModuleCompiling ( ) ;
const bool bFailIfGeneratedCodeChanges = false ;
2014-12-11 06:03:58 -05:00
const bool bForceCodeProject = false ;
bool bWasSuccessful = StartCompilingModuleDLLs ( FApp : : GetGameName ( ) , ModulesToRecompile , InRecompileModulesCallback , Ar , bFailIfGeneratedCodeChanges , AdditionalArguments , bForceCodeProject ) ;
2014-09-05 12:46:22 -04:00
if ( bWasSuccessful )
{
// Go ahead and check for completion right away. This is really just so that we can handle the case
// where the user asked us to wait for the compile to finish before returning.
bool bCompileStillInProgress = false ;
bool bCompileSucceeded = false ;
FOutputDeviceNull NullOutput ;
CheckForFinishedModuleDLLCompile ( bWaitForCompletion , bCompileStillInProgress , bCompileSucceeded , NullOutput ) ;
if ( ! bCompileStillInProgress & & ! bCompileSucceeded )
{
bWasSuccessful = false ;
}
}
return bWasSuccessful ;
# else
return false ;
2014-09-11 11:39:20 -04:00
# endif // WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
}
void FHotReloadModule : : OnModuleCompileSucceeded ( FName ModuleName , const FString & NewModuleFilename )
{
// If the compile succeeded, update the module info entry with the new file name for this module
FModuleManager : : Get ( ) . SetModuleFilename ( ModuleName , NewModuleFilename ) ;
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
// UpdateModuleCompileData() should have been run before compiling so the
// data in ModuleInfo should be correct for the pre-compile dll file.
FModuleCompilationData & CompileData = ModuleCompileData . FindChecked ( ModuleName ) . Get ( ) ;
FDateTime FileTimeStamp ;
bool bGotFileTimeStamp = GetModuleFileTimeStamp ( ModuleName , FileTimeStamp ) ;
CompileData . bHasFileTimeStamp = bGotFileTimeStamp ;
CompileData . FileTimeStamp = FileTimeStamp ;
if ( CompileData . bHasFileTimeStamp )
{
CompileData . CompileMethod = EModuleCompileMethod : : Runtime ;
}
else
{
CompileData . CompileMethod = EModuleCompileMethod : : Unknown ;
}
WriteModuleCompilationInfoToConfig ( ModuleName , CompileData ) ;
# endif
}
2014-12-11 06:03:58 -05:00
bool FHotReloadModule : : RecompileModuleDLLs ( const TArray < FModuleToRecompile > & ModuleNames , FOutputDevice & Ar , bool bFailIfGeneratedCodeChanges , bool bForceCodeProject )
2014-09-05 12:46:22 -04:00
{
bool bCompileSucceeded = false ;
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
const FString AdditionalArguments = MakeUBTArgumentsForModuleCompiling ( ) ;
2014-12-11 06:03:58 -05:00
if ( StartCompilingModuleDLLs ( FApp : : GetGameName ( ) , ModuleNames , FRecompileModulesCallback ( ) , Ar , bFailIfGeneratedCodeChanges , AdditionalArguments , bForceCodeProject ) )
2014-09-05 12:46:22 -04:00
{
const bool bWaitForCompletion = true ; // Always wait
bool bCompileStillInProgress = false ;
CheckForFinishedModuleDLLCompile ( bWaitForCompletion , bCompileStillInProgress , bCompileSucceeded , Ar ) ;
}
# endif
return bCompileSucceeded ;
}
FString FHotReloadModule : : MakeUBTArgumentsForModuleCompiling ( )
{
FString AdditionalArguments ;
if ( FPaths : : IsProjectFilePathSet ( ) )
{
// We have to pass FULL paths to UBT
FString FullProjectPath = FPaths : : ConvertRelativePathToFull ( FPaths : : GetProjectFilePath ( ) ) ;
// @todo projectdirs: Currently non-Rocket projects that exist under the UE4 root are compiled by UBT with no .uproject file
// name passed in (see bIsProjectTarget in VCProject.cs), which causes intermediate libraries to be saved to the Engine
// intermediate folder instead of the project's intermediate folder. We're emulating this behavior here for module
// recompiling, so that compiled modules will be able to find their import libraries in the original folder they were compiled.
if ( FRocketSupport : : IsRocket ( ) | | ! FullProjectPath . StartsWith ( FPaths : : ConvertRelativePathToFull ( FPaths : : RootDir ( ) ) ) )
{
const FString ProjectFilenameWithQuotes = FString : : Printf ( TEXT ( " \" %s \" " ) , * FullProjectPath ) ;
AdditionalArguments + = FString : : Printf ( TEXT ( " %s " ) , * ProjectFilenameWithQuotes ) ;
}
if ( FRocketSupport : : IsRocket ( ) )
{
AdditionalArguments + = TEXT ( " -rocket " ) ;
}
}
return AdditionalArguments ;
}
bool FHotReloadModule : : StartCompilingModuleDLLs ( const FString & GameName , const TArray < FModuleToRecompile > & ModuleNames ,
const FRecompileModulesCallback & InRecompileModulesCallback , FOutputDevice & Ar , bool bInFailIfGeneratedCodeChanges ,
2014-12-11 06:03:58 -05:00
const FString & InAdditionalCmdLineArgs , bool bForceCodeProject )
2014-09-05 12:46:22 -04:00
{
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
// Keep track of what we're compiling
ModulesBeingCompiled = ModuleNames ;
ModulesThatWereBeingRecompiled = ModulesBeingCompiled ;
const TCHAR * BuildPlatformName = FPlatformMisc : : GetUBTPlatform ( ) ;
const TCHAR * BuildConfigurationName = FModuleManager : : GetUBTConfiguration ( ) ;
RecompileModulesCallback = InRecompileModulesCallback ;
// Pass a module file suffix to UBT if we have one
FString ModuleArg ;
for ( int32 CurModuleIndex = 0 ; CurModuleIndex < ModuleNames . Num ( ) ; + + CurModuleIndex )
{
if ( ! ModuleNames [ CurModuleIndex ] . ModuleFileSuffix . IsEmpty ( ) )
{
ModuleArg + = FString : : Printf ( TEXT ( " -ModuleWithSuffix %s %s " ) , * ModuleNames [ CurModuleIndex ] . ModuleName , * ModuleNames [ CurModuleIndex ] . ModuleFileSuffix ) ;
}
else
{
ModuleArg + = FString : : Printf ( TEXT ( " -Module %s " ) , * ModuleNames [ CurModuleIndex ] . ModuleName ) ;
}
Ar . Logf ( TEXT ( " Recompiling %s... " ) , * ModuleNames [ CurModuleIndex ] . ModuleName ) ;
// prepare the compile info in the FModuleInfo so that it can be compared after compiling
FName ModuleFName ( * ModuleNames [ CurModuleIndex ] . ModuleName ) ;
UpdateModuleCompileData ( ModuleFName ) ;
}
FString ExtraArg ;
# if UE_EDITOR
// NOTE: When recompiling from the editor, we're passed the game target name, not the editor target name, but we'll
// pass "-editorrecompile" to UBT which tells UBT to figure out the editor target to use for this game, since
// we can't possibly know what the target is called from within the engine code.
ExtraArg = TEXT ( " -editorrecompile " ) ;
# endif
if ( bInFailIfGeneratedCodeChanges )
{
// Additional argument to let UHT know that we can only compile the module if the generated code didn't change
ExtraArg + = TEXT ( " -FailIfGeneratedCodeChanges " ) ;
}
2014-10-23 12:05:53 -04:00
// If there's nothing to compile, don't bother linking the DLLs as the old ones are up-to-date
ExtraArg + = TEXT ( " -canskiplink " ) ;
2014-09-11 09:28:33 -04:00
2014-10-23 12:05:53 -04:00
// Shared PCH does no work with hot-reloading engine/editor modules as we don't scan all modules for them.
if ( ! ContainsOnlyGameModules ( ModuleNames ) )
{
ExtraArg + = TEXT ( " -nosharedpch " ) ;
}
2014-09-16 16:22:14 -04:00
FString TargetName = GameName ;
# if WITH_EDITOR
// If there are no game modules loaded, then it's not a code-based project and the target
2014-09-09 13:57:13 -04:00
// for UBT should be the editor.
2014-12-11 06:03:58 -05:00
if ( ! bForceCodeProject & & ! IsAnyGameModuleLoaded ( ) )
2014-09-09 13:57:13 -04:00
{
TargetName = TEXT ( " UE4Editor " ) ;
}
2014-09-16 16:22:14 -04:00
# endif
2014-09-05 12:46:22 -04:00
FString CmdLineParams = FString : : Printf ( TEXT ( " %s%s %s %s %s%s " ) ,
2014-09-09 13:57:13 -04:00
* TargetName , * ModuleArg ,
2014-09-05 12:46:22 -04:00
BuildPlatformName , BuildConfigurationName ,
* ExtraArg , * InAdditionalCmdLineArgs ) ;
const bool bInvocationSuccessful = InvokeUnrealBuildToolForCompile ( CmdLineParams , Ar ) ;
if ( ! bInvocationSuccessful )
{
// No longer compiling modules
ModulesBeingCompiled . Empty ( ) ;
ModuleCompilerFinishedEvent . Broadcast ( FString ( ) , ECompilationResult : : OtherCompilationError , false ) ;
// Fire task completion delegate
2014-09-18 08:10:17 -04:00
RecompileModulesCallback . ExecuteIfBound ( false , ECompilationResult : : OtherCompilationError ) ;
2014-09-05 12:46:22 -04:00
RecompileModulesCallback . Unbind ( ) ;
}
return bInvocationSuccessful ;
# else
return false ;
# endif
}
bool FHotReloadModule : : InvokeUnrealBuildToolForCompile ( const FString & InCmdLineParams , FOutputDevice & Ar )
{
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
// Make sure we're not already compiling something!
check ( ! IsCurrentlyCompiling ( ) ) ;
// Setup output redirection pipes, so that we can harvest compiler output and display it ourselves
void * PipeRead = NULL ;
void * PipeWrite = NULL ;
verify ( FPlatformProcess : : CreatePipe ( PipeRead , PipeWrite ) ) ;
ModuleCompileReadPipeText = TEXT ( " " ) ;
2014-09-05 13:31:22 -04:00
FProcHandle ProcHandle = FDesktopPlatformModule : : Get ( ) - > InvokeUnrealBuildToolAsync ( InCmdLineParams , Ar , PipeRead , PipeWrite ) ;
2014-09-05 12:46:22 -04:00
// We no longer need the Write pipe so close it.
// We DO need the Read pipe however...
FPlatformProcess : : ClosePipe ( 0 , PipeWrite ) ;
if ( ! ProcHandle . IsValid ( ) )
{
// We're done with the process handle now
ModuleCompileProcessHandle . Reset ( ) ;
ModuleCompileReadPipe = NULL ;
}
else
{
ModuleCompileProcessHandle = ProcHandle ;
ModuleCompileReadPipe = PipeRead ;
}
return ProcHandle . IsValid ( ) ;
# else
return false ;
2014-09-11 11:39:20 -04:00
# endif // WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
}
2014-10-08 04:42:34 -04:00
void FHotReloadModule : : CheckForFinishedModuleDLLCompile ( const bool bWaitForCompletion , bool & bCompileStillInProgress , bool & bCompileSucceeded , FOutputDevice & Ar , bool bFireEvents )
2014-09-05 12:46:22 -04:00
{
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
bCompileStillInProgress = false ;
ECompilationResult : : Type CompilationResult = ECompilationResult : : OtherCompilationError ;
// Is there a compilation in progress?
if ( IsCurrentlyCompiling ( ) )
{
bCompileStillInProgress = true ;
2014-10-08 04:42:34 -04:00
FText StatusUpdate ;
if ( ModulesBeingCompiled . Num ( ) > 0 )
2014-09-05 12:46:22 -04:00
{
2014-10-08 04:42:34 -04:00
FFormatNamedArguments Args ;
Args . Add ( TEXT ( " CodeModuleName " ) , FText : : FromString ( ModulesBeingCompiled [ 0 ] . ModuleName ) ) ;
StatusUpdate = FText : : Format ( NSLOCTEXT ( " FModuleManager " , " CompileSpecificModuleStatusMessage " , " {CodeModuleName}: Compiling modules... " ) , Args ) ;
2014-09-05 12:46:22 -04:00
}
2014-10-08 04:42:34 -04:00
else
{
StatusUpdate = NSLOCTEXT ( " FModuleManager " , " CompileStatusMessage " , " Compiling modules... " ) ;
}
FScopedSlowTask SlowTask ( 0 , StatusUpdate , GIsSlowTask ) ;
SlowTask . MakeDialog ( ) ;
2014-09-05 12:46:22 -04:00
// Check to see if the compile has finished yet
int32 ReturnCode = - 1 ;
while ( bCompileStillInProgress )
{
2014-09-18 08:10:17 -04:00
// Store the return code in a temp variable for now because it still gets overwritten
// when the process is running.
int32 ProcReturnCode = - 1 ;
if ( FPlatformProcess : : GetProcReturnCode ( ModuleCompileProcessHandle , & ProcReturnCode ) )
2014-09-05 12:46:22 -04:00
{
2014-09-18 08:10:17 -04:00
ReturnCode = ProcReturnCode ;
2014-09-05 12:46:22 -04:00
bCompileStillInProgress = false ;
}
if ( bRequestCancelCompilation )
{
FPlatformProcess : : TerminateProc ( ModuleCompileProcessHandle ) ;
bCompileStillInProgress = bRequestCancelCompilation = false ;
}
if ( bCompileStillInProgress )
{
ModuleCompileReadPipeText + = FPlatformProcess : : ReadPipe ( ModuleCompileReadPipe ) ;
if ( ! bWaitForCompletion )
{
// We haven't finished compiling, but we were asked to return immediately
break ;
}
2015-02-06 11:58:00 -05:00
SlowTask . EnterProgressFrame ( 0.0f ) ;
2014-09-05 12:46:22 -04:00
// Give up a small timeslice if we haven't finished recompiling yet
FPlatformProcess : : Sleep ( 0.01f ) ;
}
}
bRequestCancelCompilation = false ;
if ( ! bCompileStillInProgress )
{
// Compilation finished, now we need to grab all of the text from the output pipe
ModuleCompileReadPipeText + = FPlatformProcess : : ReadPipe ( ModuleCompileReadPipe ) ;
2014-09-18 08:10:17 -04:00
// This includes 'canceled' (-1) and 'up-to-date' (-2)
CompilationResult = ( ECompilationResult : : Type ) ReturnCode ;
2014-09-05 12:46:22 -04:00
// If compilation succeeded for all modules, go back to the modules and update their module file names
// in case we recompiled the modules to a new unique file name. This is needed so that when the module
2014-09-18 08:10:17 -04:00
// is reloaded after the recompile, we load the new DLL file name, not the old one.
// Note that we don't want to do anything in case the build was canceled or source code has not changed.
2014-09-05 12:46:22 -04:00
if ( CompilationResult = = ECompilationResult : : Succeeded )
{
for ( int32 CurModuleIndex = 0 ; CurModuleIndex < ModulesThatWereBeingRecompiled . Num ( ) ; + + CurModuleIndex )
{
const FModuleToRecompile & CurModule = ModulesThatWereBeingRecompiled [ CurModuleIndex ] ;
// Were we asked to assign a new file name for this module?
if ( ! CurModule . NewModuleFilename . IsEmpty ( ) )
{
// If the compile succeeded, update the module info entry with the new file name for this module
OnModuleCompileSucceeded ( FName ( * CurModule . ModuleName ) , CurModule . NewModuleFilename ) ;
}
}
}
2014-09-18 08:10:17 -04:00
ModulesThatWereBeingRecompiled . Empty ( ) ;
2014-09-05 12:46:22 -04:00
// We're done with the process handle now
ModuleCompileProcessHandle . Close ( ) ;
ModuleCompileProcessHandle . Reset ( ) ;
FPlatformProcess : : ClosePipe ( ModuleCompileReadPipe , 0 ) ;
Ar . Log ( * ModuleCompileReadPipeText ) ;
const FString FinalOutput = ModuleCompileReadPipeText ;
ModuleCompileReadPipe = NULL ;
ModuleCompileReadPipeText = TEXT ( " " ) ;
// No longer compiling modules
ModulesBeingCompiled . Empty ( ) ;
2014-09-18 08:10:17 -04:00
bCompileSucceeded = ! ECompilationResult : : Failed ( CompilationResult ) ;
2014-09-05 12:46:22 -04:00
if ( bFireEvents )
{
const bool bShowLogOnSuccess = false ;
ModuleCompilerFinishedEvent . Broadcast ( FinalOutput , CompilationResult , ! bCompileSucceeded | | bShowLogOnSuccess ) ;
// Fire task completion delegate
2014-09-18 08:10:17 -04:00
RecompileModulesCallback . ExecuteIfBound ( true , CompilationResult ) ;
2014-09-05 12:46:22 -04:00
RecompileModulesCallback . Unbind ( ) ;
}
}
else
{
Ar . Logf ( TEXT ( " Error: CheckForFinishedModuleDLLCompile: Compilation is still in progress " ) ) ;
}
}
else
{
Ar . Logf ( TEXT ( " Error: CheckForFinishedModuleDLLCompile: There is no compilation in progress right now " ) ) ;
}
2014-09-11 11:39:20 -04:00
# endif // WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
}
void FHotReloadModule : : UpdateModuleCompileData ( FName ModuleName )
{
// Find or create a compile data object for this module
TSharedRef < FModuleCompilationData > * CompileDataPtr = ModuleCompileData . Find ( ModuleName ) ;
if ( CompileDataPtr = = nullptr )
{
CompileDataPtr = & ModuleCompileData . Add ( ModuleName , TSharedRef < FModuleCompilationData > ( new FModuleCompilationData ( ) ) ) ;
}
// reset the compile data before updating it
FModuleCompilationData & CompileData = CompileDataPtr - > Get ( ) ;
CompileData . bHasFileTimeStamp = false ;
CompileData . FileTimeStamp = FDateTime ( 0 ) ;
CompileData . CompileMethod = EModuleCompileMethod : : Unknown ;
2014-09-11 11:39:20 -04:00
# if WITH_HOT_RELOAD
2014-09-05 12:46:22 -04:00
ReadModuleCompilationInfoFromConfig ( ModuleName , CompileData ) ;
FDateTime FileTimeStamp ;
bool bGotFileTimeStamp = GetModuleFileTimeStamp ( ModuleName , FileTimeStamp ) ;
if ( ! bGotFileTimeStamp )
{
// File missing? Reset the cached timestamp and method to defaults and save them.
CompileData . bHasFileTimeStamp = false ;
CompileData . FileTimeStamp = FDateTime ( 0 ) ;
CompileData . CompileMethod = EModuleCompileMethod : : Unknown ;
WriteModuleCompilationInfoToConfig ( ModuleName , CompileData ) ;
}
else
{
if ( CompileData . bHasFileTimeStamp )
{
if ( FileTimeStamp > CompileData . FileTimeStamp + HotReloadDefs : : TimeStampEpsilon )
{
// The file is newer than the cached timestamp
// The file must have been compiled externally
CompileData . FileTimeStamp = FileTimeStamp ;
CompileData . CompileMethod = EModuleCompileMethod : : External ;
WriteModuleCompilationInfoToConfig ( ModuleName , CompileData ) ;
}
}
else
{
// The cached timestamp and method are default value so this file has no history yet
// We can only set its timestamp and save
CompileData . bHasFileTimeStamp = true ;
CompileData . FileTimeStamp = FileTimeStamp ;
WriteModuleCompilationInfoToConfig ( ModuleName , CompileData ) ;
}
}
# endif
}
void FHotReloadModule : : ReadModuleCompilationInfoFromConfig ( FName ModuleName , FModuleCompilationData & CompileData )
{
FString DateTimeString ;
if ( GConfig - > GetString ( * HotReloadDefs : : CompilationInfoConfigSection , * FString : : Printf ( TEXT ( " %s.TimeStamp " ) , * ModuleName . ToString ( ) ) , DateTimeString , GEditorUserSettingsIni ) )
{
FDateTime TimeStamp ;
if ( ! DateTimeString . IsEmpty ( ) & & FDateTime : : Parse ( DateTimeString , TimeStamp ) )
{
CompileData . bHasFileTimeStamp = true ;
CompileData . FileTimeStamp = TimeStamp ;
FString CompileMethodString ;
if ( GConfig - > GetString ( * HotReloadDefs : : CompilationInfoConfigSection , * FString : : Printf ( TEXT ( " %s.LastCompileMethod " ) , * ModuleName . ToString ( ) ) , CompileMethodString , GEditorUserSettingsIni ) )
{
if ( CompileMethodString . Equals ( HotReloadDefs : : CompileMethodRuntime , ESearchCase : : IgnoreCase ) )
{
CompileData . CompileMethod = EModuleCompileMethod : : Runtime ;
}
else if ( CompileMethodString . Equals ( HotReloadDefs : : CompileMethodExternal , ESearchCase : : IgnoreCase ) )
{
CompileData . CompileMethod = EModuleCompileMethod : : External ;
}
}
}
}
}
void FHotReloadModule : : WriteModuleCompilationInfoToConfig ( FName ModuleName , const FModuleCompilationData & CompileData )
{
FString DateTimeString ;
if ( CompileData . bHasFileTimeStamp )
{
DateTimeString = CompileData . FileTimeStamp . ToString ( ) ;
}
GConfig - > SetString ( * HotReloadDefs : : CompilationInfoConfigSection , * FString : : Printf ( TEXT ( " %s.TimeStamp " ) , * ModuleName . ToString ( ) ) , * DateTimeString , GEditorUserSettingsIni ) ;
FString CompileMethodString = HotReloadDefs : : CompileMethodUnknown ;
if ( CompileData . CompileMethod = = EModuleCompileMethod : : Runtime )
{
CompileMethodString = HotReloadDefs : : CompileMethodRuntime ;
}
else if ( CompileData . CompileMethod = = EModuleCompileMethod : : External )
{
CompileMethodString = HotReloadDefs : : CompileMethodExternal ;
}
GConfig - > SetString ( * HotReloadDefs : : CompilationInfoConfigSection , * FString : : Printf ( TEXT ( " %s.LastCompileMethod " ) , * ModuleName . ToString ( ) ) , * CompileMethodString , GEditorUserSettingsIni ) ;
}
bool FHotReloadModule : : GetModuleFileTimeStamp ( FName ModuleName , FDateTime & OutFileTimeStamp ) const
{
FString Filename = FModuleManager : : Get ( ) . GetModuleFilename ( ModuleName ) ;
if ( IFileManager : : Get ( ) . FileSize ( * Filename ) > 0 )
{
OutFileTimeStamp = FDateTime ( IFileManager : : Get ( ) . GetTimeStamp ( * Filename ) ) ;
return true ;
}
return false ;
}
2014-12-11 06:03:58 -05:00
bool FHotReloadModule : : IsAnyGameModuleLoaded ( )
2014-09-09 13:57:13 -04:00
{
2014-12-11 06:03:58 -05:00
if ( bIsAnyGameModuleLoaded = = EThreeStateBool : : Unknown )
2014-09-09 13:57:13 -04:00
{
2014-12-11 06:03:58 -05:00
bool bGameModuleFound = false ;
// Ask the module manager for a list of currently-loaded gameplay modules
TArray < FModuleStatus > ModuleStatuses ;
FModuleManager : : Get ( ) . QueryModules ( ModuleStatuses ) ;
2014-09-09 13:57:13 -04:00
2014-12-11 06:03:58 -05:00
for ( auto ModuleStatusIt = ModuleStatuses . CreateConstIterator ( ) ; ModuleStatusIt ; + + ModuleStatusIt )
2014-09-09 13:57:13 -04:00
{
2014-12-11 06:03:58 -05:00
const FModuleStatus & ModuleStatus = * ModuleStatusIt ;
2014-09-09 13:57:13 -04:00
2014-12-11 06:03:58 -05:00
// We only care about game modules that are currently loaded
if ( ModuleStatus . bIsLoaded & & ModuleStatus . bIsGameModule )
{
// There is at least one loaded game module.
bGameModuleFound = true ;
break ;
}
}
bIsAnyGameModuleLoaded = EThreeStateBool : : FromBool ( bGameModuleFound ) ;
}
return EThreeStateBool : : ToBool ( bIsAnyGameModuleLoaded ) ;
2014-09-09 13:57:13 -04:00
}
2014-10-23 12:05:53 -04:00
bool FHotReloadModule : : ContainsOnlyGameModules ( const TArray < FModuleToRecompile > & ModulesToCompile ) const
{
const FString AbsoluteGameDir ( FPaths : : ConvertRelativePathToFull ( FPaths : : GameDir ( ) ) ) ;
bool bOnlyGameModules = true ;
for ( auto & ModuleToCompile : ModulesToCompile )
{
const FString FullModulePath ( FPaths : : ConvertRelativePathToFull ( ModuleToCompile . NewModuleFilename ) ) ;
if ( ! FullModulePath . StartsWith ( AbsoluteGameDir ) )
{
bOnlyGameModules = false ;
break ;
}
}
return bOnlyGameModules ;
}
2014-12-11 06:03:58 -05:00
void FHotReloadModule : : ModulesChangedCallback ( FName ModuleName , EModuleChangeReason ReasonForChange )
{
// Force update game modules state on the next call to IsAnyGameModuleLoaded
bIsAnyGameModuleLoaded = EThreeStateBool : : Unknown ;
// If the hot reload directory watcher hasn't been initialized yet (because the binaries directory did not exist) try to initialize it now
if ( ! bDirectoryWatcherInitialized )
{
InitHotReloadWatcher ( ) ;
}
}
2014-08-14 03:37:01 -04:00
# undef LOCTEXT_NAMESPACE