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"
2015-07-13 22:52:30 -04:00
# include "Runtime/Engine/Classes/Engine/BlueprintGeneratedClass.h"
2015-07-21 10:33:15 -04:00
# include "KismetEditorUtilities.h"
2015-07-21 14:03:24 -04:00
# 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 ;
2015-02-09 06:44:30 -05:00
virtual ECompilationResult : : Type DoHotReloadFromEditor ( const bool bWaitForCompletion ) 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
2015-07-13 07:30:06 -04:00
/**
* Finds all references to old CDOs and replaces them with the new ones .
* Skipping UBlueprintGeneratedClass : : OverridenArchetypeForCDO as it ' s the
* only one needed .
*/
void ReplaceReferencesToReconstructedCDOs ( ) ;
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
2015-09-30 05:26:51 -04:00
void RegisterForReinstancing ( UClass * OldClass , UClass * NewClass ) ;
void ReinstanceClasses ( ) ;
2014-08-14 03:37:01 -04:00
/**
* Called from CoreUObject to re - instance hot - reloaded classes
*/
2015-09-30 05:26:51 -04:00
void ReinstanceClass ( UClass * OldClass , UClass * NewClass , const TMap < UClass * , UClass * > & OldToNewClassesMap ) ;
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 ) ;
2015-02-07 04:01:23 -05:00
/**
* Strips hot - reload suffix from module filename .
*/
static void StripModuleSuffixFromFilename ( FString & InOutModuleFilename , const FString & ModuleName ) ;
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 ;
2015-07-13 07:30:06 -04:00
/** Reconstructed CDOs map during hot-reload. */
TMap < UObject * , UObject * > ReconstructedCDOsMap ;
2015-11-18 16:20:49 -05:00
/** Keeps record of hot-reload session starting time. */
double HotReloadStartTime ;
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 ) ;
2015-10-30 17:41:13 -04:00
namespace
{
/**
* Gets editor runs directory .
*/
FString GetEditorRunsDir ( )
{
FString TempDir = FPaths : : EngineIntermediateDir ( ) ;
return FPaths : : Combine ( * TempDir , TEXT ( " EditorRuns " ) ) ;
}
/**
* Creates a file that informs UBT that the editor is currently running .
*/
void CreateFileThatIndicatesEditorRunIfNeeded ( )
{
# if WITH_EDITOR
IPlatformFile & FS = IPlatformFile : : GetPlatformPhysical ( ) ;
FString EditorRunsDir = GetEditorRunsDir ( ) ;
FString FileName = FPaths : : Combine ( * EditorRunsDir , * FString : : Printf ( TEXT ( " %d " ) , FPlatformProcess : : GetCurrentProcessId ( ) ) ) ;
if ( FS . FileExists ( * FileName ) )
{
if ( ! GIsEditor )
{
FS . DeleteFile ( * FileName ) ;
}
}
else
{
if ( GIsEditor )
{
if ( ! FS . CreateDirectory ( * EditorRunsDir ) )
{
return ;
}
delete FS . OpenWrite ( * FileName ) ; // Touch file.
}
}
# endif // WITH_EDITOR
}
/**
* Deletes file left by CreateFileThatIndicatesEditorRunIfNeeded function .
*/
void DeleteFileThatIndicatesEditorRunIfNeeded ( )
{
# if WITH_EDITOR
IPlatformFile & FS = IPlatformFile : : GetPlatformPhysical ( ) ;
FString EditorRunsDir = GetEditorRunsDir ( ) ;
FString FileName = FPaths : : Combine ( * EditorRunsDir , * FString : : Printf ( TEXT ( " %d " ) , FPlatformProcess : : GetCurrentProcessId ( ) ) ) ;
if ( FS . FileExists ( * FileName ) )
{
FS . DeleteFile ( * FileName ) ;
}
# endif // WITH_EDITOR
}
}
2014-08-14 03:37:01 -04:00
void FHotReloadModule : : StartupModule ( )
{
2015-10-30 17:41:13 -04:00
CreateFileThatIndicatesEditorRunIfNeeded ( ) ;
2014-08-14 03:37:01 -04:00
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)
2015-09-30 05:26:51 -04:00
FCoreUObjectDelegates : : RegisterClassForHotReloadReinstancingDelegate . BindRaw ( this , & FHotReloadModule : : RegisterForReinstancing ) ;
FCoreUObjectDelegates : : ReinstanceHotReloadedClassesDelegate . BindRaw ( this , & FHotReloadModule : : ReinstanceClasses ) ;
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 ( ) ;
2015-10-30 17:41:13 -04:00
DeleteFileThatIndicatesEditorRunIfNeeded ( ) ;
2014-08-14 03:37:01 -04:00
}
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
2015-02-09 06:44:30 -05:00
ModuleCompilerStartedEvent . Broadcast ( false ) ; // we never perform an async compile
2014-09-05 12:46:22 -04:00
// 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 ;
2015-07-21 10:33:15 -04:00
static TSet < UBlueprint * > HotReloadBPSetToRecompile ;
static TSet < UBlueprint * > HotReloadBPSetToRecompileBytecodeOnly ;
2014-08-14 03:37:01 -04:00
/** 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 ) ;
}
2015-02-09 06:44:30 -05:00
ECompilationResult : : Type FHotReloadModule : : DoHotReloadFromEditor ( const bool bWaitForCompletion )
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 ) ;
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
}
2015-05-15 14:07:44 -04:00
# if WITH_HOT_RELOAD
/**
* Gets duplicated CDO from the cache , renames it and returns .
*/
2015-09-30 05:26:51 -04:00
UObject * GetCachedCDODuplicate ( UObject * CDO , FName Name )
2015-05-15 14:07:44 -04:00
{
UObject * DupCDO = nullptr ;
2015-09-30 05:26:51 -04:00
UObject * * DupCDOPtr = GetDuplicatedCDOMap ( ) . Find ( CDO ) ;
2015-05-15 14:07:44 -04:00
if ( DupCDOPtr ! = nullptr )
{
DupCDO = * DupCDOPtr ;
DupCDO - > Rename ( * Name . ToString ( ) , GetTransientPackage ( ) ,
REN_DoNotDirty | REN_DontCreateRedirectors | REN_ForceNoResetLoaders | REN_NonTransactional | REN_SkipGeneratedClasses ) ;
}
return DupCDO ;
}
# endif // WITH_HOT_RELOAD
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
2015-06-09 16:41:53 -04:00
# if WITH_ENGINE
2015-05-15 14:07:44 -04:00
FBlueprintCompileReinstancer : : FCDODuplicatesProvider & CDODuplicatesProvider = FBlueprintCompileReinstancer : : GetCDODuplicatesProviderDelegate ( ) ;
CDODuplicatesProvider . BindStatic ( & GetCachedCDODuplicate ) ;
2015-06-09 16:41:53 -04:00
# endif
2015-05-15 14:07:44 -04:00
2014-09-18 08:10:17 -04:00
if ( CompilationResult = = ECompilationResult : : Succeeded )
2014-08-14 03:37:01 -04:00
{
2015-06-24 09:40:50 -04:00
FModuleManager : : Get ( ) . ResetModulePathsCache ( ) ;
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.
2015-05-07 03:23:51 -04:00
const bool bAbandonModule = true ;
FModuleManager : : Get ( ) . UnloadOrAbandonModuleWithCallback ( ShortPackageName , HotReloadAr , bAbandonModule ) ;
2014-08-14 03:37:01 -04:00
// 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 )
{
2015-11-18 16:20:49 -05:00
if ( UFunction * Function = Cast < UFunction > ( static_cast < UObject * > ( It - > Object ) ) )
2014-08-14 03:37:01 -04:00
{
if ( Native NewFunction = HotReloadFunctionRemap . FindRef ( Function - > GetNativeFunc ( ) ) )
{
Count + + ;
Function - > SetNativeFunc ( NewFunction ) ;
}
}
2015-11-18 16:20:49 -05:00
if ( UScriptStruct * ScriptStruct = Cast < UScriptStruct > ( static_cast < UObject * > ( It - > Object ) ) )
2014-08-14 03:37:01 -04:00
{
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 ( ) ;
2015-07-13 07:30:06 -04:00
ReplaceReferencesToReconstructedCDOs ( ) ;
2014-08-14 03:37:01 -04:00
Result = ECompilationResult : : Succeeded ;
}
2014-08-20 13:58:31 -04:00
const bool bWasTriggeredAutomatically = ! bIsHotReloadingFromEditor ;
BroadcastHotReload ( bWasTriggeredAutomatically ) ;
2015-11-18 16:20:49 -05:00
HotReloadAr . Logf ( ELogVerbosity : : Warning , TEXT ( " HotReload took %4.1fs. " ) , FPlatformTime : : Seconds ( ) - HotReloadStartTime ) ;
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 ;
}
2015-05-15 14:07:44 -04:00
2015-06-09 16:41:53 -04:00
# if WITH_ENGINE
2015-05-15 14:07:44 -04:00
CDODuplicatesProvider . Unbind ( ) ;
GetDuplicatedCDOMap ( ) . Empty ( ) ;
2015-06-09 16:41:53 -04:00
# endif
2014-08-14 03:37:01 -04:00
# endif
bIsHotReloadingFromEditor = false ;
return Result ;
}
2015-07-13 07:30:06 -04:00
void FHotReloadModule : : ReplaceReferencesToReconstructedCDOs ( )
{
if ( ReconstructedCDOsMap . Num ( ) = = 0 )
{
return ;
}
2015-11-18 16:20:49 -05:00
// Thread pool manager. We need new thread pool with increased
// amount of stack size. Standard GThreadPool was encountering
// stack overflow error during serialization.
static struct FReplaceReferencesThreadPool
{
FReplaceReferencesThreadPool ( )
{
Pool = FQueuedThreadPool : : Allocate ( ) ;
int32 NumThreadsInThreadPool = FPlatformMisc : : NumberOfWorkerThreadsToSpawn ( ) ;
verify ( Pool - > Create ( NumThreadsInThreadPool , 256 * 1024 ) ) ;
}
~ FReplaceReferencesThreadPool ( )
{
Pool - > Destroy ( ) ;
}
FQueuedThreadPool * GetPool ( ) { return Pool ; }
private :
FQueuedThreadPool * Pool ;
} ThreadPoolManager ;
2015-07-13 07:30:06 -04:00
TArray < UObject * > OldCDOs ;
ReconstructedCDOsMap . GetKeys ( OldCDOs ) ;
2015-11-18 16:20:49 -05:00
// Structure to store CDOs reference info.
struct FReferenceInfo
{
UObject * Referencer ;
UObject * Referencee ;
UProperty * ReferencingProperty ;
FReferenceInfo ( UObject * InReferencer , UObject * InReferencee , UProperty * InReferencingProperty )
: Referencer ( InReferencer ) , Referencee ( InReferencee ) , ReferencingProperty ( InReferencingProperty )
{ }
} ;
// Async task to enable multithreaded CDOs reference search.
class FFindRefTask : public FNonAbandonableTask
{
public :
FFindRefTask ( const TArray < UObject * > * InReferencees , int32 ReserveElements = 0 )
: Referencees ( * InReferencees )
{
ObjectsArray . Reserve ( ReserveElements ) ;
}
void DoWork ( )
{
for ( UObject * Object : ObjectsArray )
{
FFindReferencersArchive FindRefsArchive ( Object , Referencees ) ;
TMap < UObject * , int32 > ReferenceCounts ;
TMultiMap < UObject * , UProperty * > ReferencingProperties ;
FindRefsArchive . GetReferenceCounts ( ReferenceCounts , ReferencingProperties ) ;
for ( auto & ReferencingProperty : ReferencingProperties )
{
static const UProperty * PropertyToSkip =
# if WITH_ENGINE
UBlueprintGeneratedClass : : StaticClass ( ) - > FindPropertyByName ( GET_MEMBER_NAME_CHECKED ( UBlueprintGeneratedClass , OverridenArchetypeForCDO ) ) ;
# else
nullptr ;
# endif
if ( ! ReferencingProperty . Value - > IsA < UObjectProperty > ( ) | | ReferencingProperty . Value = = PropertyToSkip )
{
continue ;
}
References . Emplace ( Object , ReferencingProperty . Key , ReferencingProperty . Value ) ;
}
}
}
TArray < UObject * > & GetObjectsArray ( )
{
return ObjectsArray ;
}
FORCEINLINE TStatId GetStatId ( ) const
{
RETURN_QUICK_DECLARE_CYCLE_STAT ( FFindRefTask , STATGROUP_ThreadPoolAsyncTasks ) ;
}
const TArray < FReferenceInfo > & GetReferences ( ) const { return References ; }
private :
const TArray < UObject * > & Referencees ;
TArray < UObject * > ObjectsArray ;
TArray < FReferenceInfo > References ;
} ;
const int32 NumberOfThreads = FPlatformMisc : : NumberOfWorkerThreadsToSpawn ( ) ;
const int32 NumObjects = GUObjectArray . GetObjectArrayNum ( ) ;
const int32 ObjectsPerTask = FMath : : CeilToInt ( ( float ) NumObjects / NumberOfThreads ) ;
// Create tasks.
TArray < FAsyncTask < FFindRefTask > > Tasks ;
Tasks . Reserve ( NumberOfThreads ) ;
for ( int32 TaskId = 0 ; TaskId < NumberOfThreads ; + + TaskId )
{
Tasks . Emplace ( & OldCDOs , ObjectsPerTask ) ;
}
// Distribute objects uniformly between tasks.
int32 CurrentTaskId = 0 ;
2015-07-13 07:30:06 -04:00
for ( FObjectIterator ObjIter ; ObjIter ; + + ObjIter )
{
UObject * CurObject = * ObjIter ;
2015-11-18 16:20:49 -05:00
if ( CurObject - > IsPendingKill ( ) )
2015-07-13 07:30:06 -04:00
{
2015-11-18 16:20:49 -05:00
continue ;
}
2015-07-13 07:30:06 -04:00
2015-11-18 16:20:49 -05:00
Tasks [ CurrentTaskId ] . GetTask ( ) . GetObjectsArray ( ) . Add ( CurObject ) ;
CurrentTaskId = ( CurrentTaskId + 1 ) % NumberOfThreads ;
}
2015-07-13 07:30:06 -04:00
2015-11-18 16:20:49 -05:00
// Run async tasks in worker threads.
for ( int32 TaskId = 0 ; TaskId < NumberOfThreads ; + + TaskId )
{
Tasks [ TaskId ] . StartBackgroundTask ( ThreadPoolManager . GetPool ( ) ) ;
}
// Wait until tasks are finished and replace found references
// in main thread.
for ( int32 TaskId = 0 ; TaskId < NumberOfThreads ; + + TaskId )
{
Tasks [ TaskId ] . EnsureCompletion ( ) ;
const TArray < FReferenceInfo > & References = Tasks [ TaskId ] . GetTask ( ) . GetReferences ( ) ;
for ( const FReferenceInfo & Reference : References )
{
UObject * OldCDO = Reference . Referencee ;
UObjectProperty * Prop = ( UObjectProperty * ) Reference . ReferencingProperty ;
Prop - > SetObjectPropertyValue ( ( uint8 * ) Reference . Referencer + Prop - > GetOffset_ForInternal ( ) , ReconstructedCDOsMap [ OldCDO ] ) ;
2015-07-13 07:30:06 -04:00
}
}
ReconstructedCDOsMap . Empty ( ) ;
}
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 )
{
2015-06-24 09:40:50 -04:00
FModuleManager : : Get ( ) . ResetModulePathsCache ( ) ;
2014-08-14 03:37:01 -04:00
bIsHotReloadingFromEditor = true ;
2015-11-18 16:20:49 -05:00
HotReloadStartTime = FPlatformTime : : Seconds ( ) ;
2014-08-14 03:37:01 -04:00
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 )
{
2015-11-18 16:20:49 -05:00
Ar . Logf ( ELogVerbosity : : Warning , TEXT ( " HotReload operation took %4.1fs. " ) , float ( FPlatformTime : : Seconds ( ) - HotReloadStartTime ) ) ;
2014-09-10 05:13:32 -04:00
bIsHotReloadingFromEditor = false ;
2014-08-14 03:37:01 -04:00
}
else
{
2015-11-18 16:20:49 -05:00
Ar . Logf ( ELogVerbosity : : Warning , TEXT ( " Starting HotReload took %4.1fs. " ) , float ( FPlatformTime : : Seconds ( ) - HotReloadStartTime ) ) ;
2014-08-14 03:37:01 -04:00
}
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
2015-09-30 05:26:51 -04:00
namespace {
static TArray < TPair < UClass * , UClass * > > & GetClassesToReinstance ( )
{
static TArray < TPair < UClass * , UClass * > > Data ;
return Data ;
}
}
void FHotReloadModule : : RegisterForReinstancing ( UClass * OldClass , UClass * NewClass )
{
TPair < UClass * , UClass * > Pair ;
Pair . Key = OldClass ;
Pair . Value = NewClass ;
GetClassesToReinstance ( ) . Add ( MoveTemp ( Pair ) ) ;
}
void FHotReloadModule : : ReinstanceClasses ( )
{
TMap < UClass * , UClass * > OldToNewClassesMap ;
for ( TPair < UClass * , UClass * > & Pair : GetClassesToReinstance ( ) )
{
if ( Pair . Value ! = nullptr )
{
OldToNewClassesMap . Add ( Pair . Key , Pair . Value ) ;
}
}
for ( TPair < UClass * , UClass * > & Pair : GetClassesToReinstance ( ) )
{
ReinstanceClass ( Pair . Key , Pair . Value , OldToNewClassesMap ) ;
}
GetClassesToReinstance ( ) . Empty ( ) ;
}
void FHotReloadModule : : ReinstanceClass ( UClass * OldClass , UClass * NewClass , const TMap < UClass * , UClass * > & OldToNewClassesMap )
2014-09-17 04:34:40 -04:00
{
2015-09-30 05:26:51 -04:00
TSharedPtr < FHotReloadClassReinstancer > ReinstanceHelper = FHotReloadClassReinstancer : : Create ( NewClass , OldClass , OldToNewClassesMap , ReconstructedCDOsMap , HotReloadBPSetToRecompile , HotReloadBPSetToRecompileBytecodeOnly ) ;
2015-02-24 11:28:59 -05:00
if ( ReinstanceHelper - > ClassNeedsReinstancing ( ) )
2014-09-17 04:34:40 -04:00
{
UE_LOG ( LogHotReload , Log , TEXT ( " Re-instancing %s after hot-reload. " ) , NewClass ? * NewClass - > GetName ( ) : * OldClass - > GetName ( ) ) ;
2015-02-24 11:28:59 -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 ( ) ) )
{
2015-02-07 04:01:23 -05:00
for ( int32 GameModuleIndex = 0 ; GameModuleIndex < GameModuleNames . Num ( ) ; + + GameModuleIndex )
2014-08-14 03:37:01 -04:00
{
2015-02-07 04:01:23 -05:00
const FString & GameModuleName = GameModuleNames [ GameModuleIndex ] ;
const FString & GameModuleFilePath = GameModuleFilePaths [ GameModuleIndex ] ;
2014-12-17 11:52:39 -05:00
2015-02-07 04:01:23 -05:00
// Handle module files which have already been hot-reloaded.
FString GameModuleFileNameWithoutExtension = FPaths : : GetBaseFilename ( GameModuleFilePath ) ;
StripModuleSuffixFromFilename ( GameModuleFileNameWithoutExtension , GameModuleName ) ;
// Hot reload always adds a numbered suffix preceded by a hyphen, but otherwise the module name must match exactly!
if ( Filename . StartsWith ( GameModuleFileNameWithoutExtension + TEXT ( " - " ) ) )
2014-08-14 03:37:01 -04:00
{
2015-02-07 04:01:23 -05:00
if ( ! NewModules . ContainsByPredicate ( [ & ] ( const FRecompiledModule & Module ) { return Module . Name = = GameModuleName ; } ) & &
! ModulesRecentlyCompiledInTheEditor . Contains ( FPaths : : ConvertRelativePathToFull ( Change . Filename ) ) )
2014-12-17 11:52:39 -05:00
{
// 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
}
}
}
}
}
}
}
2015-02-07 04:01:23 -05:00
void FHotReloadModule : : StripModuleSuffixFromFilename ( FString & InOutModuleFilename , const FString & ModuleName )
{
// First hyphen is where the UE4Edtior prefix ends
int32 FirstHyphenIndex = INDEX_NONE ;
if ( InOutModuleFilename . FindChar ( ' - ' , FirstHyphenIndex ) )
{
// Second hyphen means we already have a hot-reloaded module or other than Development config module
int32 SecondHyphenIndex = FirstHyphenIndex ;
do
{
SecondHyphenIndex = InOutModuleFilename . Find ( TEXT ( " - " ) , ESearchCase : : IgnoreCase , ESearchDir : : FromStart , SecondHyphenIndex + 1 ) ;
if ( SecondHyphenIndex ! = INDEX_NONE )
{
// Make sure that the section between hyphens is the expected module name. This guards against cases where module name has a hyphen inside.
FString HotReloadedModuleName = InOutModuleFilename . Mid ( FirstHyphenIndex + 1 , SecondHyphenIndex - FirstHyphenIndex - 1 ) ;
if ( HotReloadedModuleName = = ModuleName )
{
InOutModuleFilename = InOutModuleFilename . Mid ( 0 , SecondHyphenIndex ) ;
SecondHyphenIndex = INDEX_NONE ;
}
}
} while ( SecondHyphenIndex ! = INDEX_NONE ) ;
}
}
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
2015-02-09 06:44:30 -05:00
ModuleCompilerStartedEvent . Broadcast ( ! bWaitForCompletion ) ; // we perform an async compile providing we're not waiting for completion
2014-09-05 12:46:22 -04:00
TArray < FModuleToRecompile > ModulesToRecompile ;
2015-05-19 18:19:46 -04:00
for ( FName CurModuleName : ModuleNames )
2014-09-05 12:46:22 -04:00
{
// Update our set of known modules, in case we don't already know about this module
FModuleManager : : Get ( ) . AddModule ( CurModuleName ) ;
// Find a unique file name for the module
FModuleToRecompile ModuleToRecompile ;
ModuleToRecompile . ModuleName = CurModuleName . ToString ( ) ;
2015-05-19 18:19:46 -04:00
FModuleManager : : Get ( ) . MakeUniqueModuleFilename ( CurModuleName , ModuleToRecompile . ModuleFileSuffix , ModuleToRecompile . NewModuleFilename ) ;
2014-09-05 12:46:22 -04:00
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.
2015-12-04 09:32:58 -05:00
if ( FApp : : IsEngineInstalled ( ) | | ! FullProjectPath . StartsWith ( FPaths : : ConvertRelativePathToFull ( FPaths : : RootDir ( ) ) ) )
2014-09-05 12:46:22 -04:00
{
const FString ProjectFilenameWithQuotes = FString : : Printf ( TEXT ( " \" %s \" " ) , * FullProjectPath ) ;
AdditionalArguments + = FString : : Printf ( TEXT ( " %s " ) , * ProjectFilenameWithQuotes ) ;
}
}
2015-12-04 09:32:58 -05:00
// Use new FastPDB option to cut down linking time
AdditionalArguments + = TEXT ( " -FastPDB " ) ;
2014-09-05 12:46:22 -04:00
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 ;
2015-05-19 18:19:46 -04:00
for ( const FModuleToRecompile & Module : ModuleNames )
2014-09-05 12:46:22 -04:00
{
2015-05-19 18:19:46 -04:00
if ( ! Module . ModuleFileSuffix . IsEmpty ( ) )
2014-09-05 12:46:22 -04:00
{
2015-05-19 18:19:46 -04:00
ModuleArg + = FString : : Printf ( TEXT ( " -ModuleWithSuffix %s %s " ) , * Module . ModuleName , * Module . ModuleFileSuffix ) ;
2014-09-05 12:46:22 -04:00
}
else
{
2015-05-19 18:19:46 -04:00
ModuleArg + = FString : : Printf ( TEXT ( " -Module %s " ) , * Module . ModuleName ) ;
2014-09-05 12:46:22 -04:00
}
2015-05-19 18:19:46 -04:00
Ar . Logf ( TEXT ( " Recompiling %s... " ) , * Module . ModuleName ) ;
2014-09-05 12:46:22 -04:00
// prepare the compile info in the FModuleInfo so that it can be compared after compiling
2015-05-19 18:19:46 -04:00
FName ModuleFName ( * Module . ModuleName ) ;
2014-09-05 12:46:22 -04:00
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
2015-03-12 00:06:15 -04:00
FPlatformProcess : : CloseProc ( ModuleCompileProcessHandle ) ;
2014-09-05 12:46:22 -04:00
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 ;
2015-04-20 10:12:55 -04:00
if ( GConfig - > GetString ( * HotReloadDefs : : CompilationInfoConfigSection , * FString : : Printf ( TEXT ( " %s.TimeStamp " ) , * ModuleName . ToString ( ) ) , DateTimeString , GEditorPerProjectIni ) )
2014-09-05 12:46:22 -04:00
{
FDateTime TimeStamp ;
if ( ! DateTimeString . IsEmpty ( ) & & FDateTime : : Parse ( DateTimeString , TimeStamp ) )
{
CompileData . bHasFileTimeStamp = true ;
CompileData . FileTimeStamp = TimeStamp ;
FString CompileMethodString ;
2015-04-20 10:12:55 -04:00
if ( GConfig - > GetString ( * HotReloadDefs : : CompilationInfoConfigSection , * FString : : Printf ( TEXT ( " %s.LastCompileMethod " ) , * ModuleName . ToString ( ) ) , CompileMethodString , GEditorPerProjectIni ) )
2014-09-05 12:46:22 -04:00
{
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 ( ) ;
}
2015-04-20 10:12:55 -04:00
GConfig - > SetString ( * HotReloadDefs : : CompilationInfoConfigSection , * FString : : Printf ( TEXT ( " %s.TimeStamp " ) , * ModuleName . ToString ( ) ) , * DateTimeString , GEditorPerProjectIni ) ;
2014-09-05 12:46:22 -04:00
FString CompileMethodString = HotReloadDefs : : CompileMethodUnknown ;
if ( CompileData . CompileMethod = = EModuleCompileMethod : : Runtime )
{
CompileMethodString = HotReloadDefs : : CompileMethodRuntime ;
}
else if ( CompileData . CompileMethod = = EModuleCompileMethod : : External )
{
CompileMethodString = HotReloadDefs : : CompileMethodExternal ;
}
2015-04-20 10:12:55 -04:00
GConfig - > SetString ( * HotReloadDefs : : CompilationInfoConfigSection , * FString : : Printf ( TEXT ( " %s.LastCompileMethod " ) , * ModuleName . ToString ( ) ) , * CompileMethodString , GEditorPerProjectIni ) ;
2014-09-05 12:46:22 -04:00
}
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