2019-03-27 11:33:31 -04:00
// Copyright 1998-2019 Epic Games, Inc. All Rights Reserved.
# include "LiveCodingModule.h"
# include "Modules/ModuleManager.h"
# include "Features/IModularFeatures.h"
# include "HAL/IConsoleManager.h"
# include "Misc/CoreDelegates.h"
# include "LiveCodingLog.h"
# include "External/LC_EntryPoint.h"
# include "Misc/App.h"
# include "Misc/Paths.h"
# include "Misc/ConfigCacheIni.h"
# include "LiveCodingSettings.h"
# include "ISettingsModule.h"
# include "ISettingsSection.h"
# include "Windows/WindowsHWrapper.h"
IMPLEMENT_MODULE ( FLiveCodingModule , LiveCoding )
# define LOCTEXT_NAMESPACE "LiveCodingModule"
bool GIsCompileActive = false ;
FString GLiveCodingConsolePath ;
FString GLiveCodingConsoleArguments ;
2019-05-28 10:22:20 -04:00
# if IS_MONOLITHIC
extern const TCHAR * GLiveCodingEngineDir ;
extern const TCHAR * GLiveCodingProject ;
# endif
2019-03-27 11:33:31 -04:00
FLiveCodingModule : : FLiveCodingModule ( )
: bEnabledLastTick ( false )
, bEnabledForSession ( false )
, bStarted ( false )
2019-05-28 10:22:20 -04:00
, bUpdateModulesInTick ( false )
2019-03-27 11:33:31 -04:00
, FullEnginePluginsDir ( FPaths : : ConvertRelativePathToFull ( FPaths : : EnginePluginsDir ( ) ) )
, FullProjectDir ( FPaths : : ConvertRelativePathToFull ( FPaths : : ProjectDir ( ) ) )
, FullProjectPluginsDir ( FPaths : : ConvertRelativePathToFull ( FPaths : : ProjectPluginsDir ( ) ) )
{
}
void FLiveCodingModule : : StartupModule ( )
{
Settings = GetMutableDefault < ULiveCodingSettings > ( ) ;
IConsoleManager & ConsoleManager = IConsoleManager : : Get ( ) ;
EnableCommand = ConsoleManager . RegisterConsoleCommand (
TEXT ( " LiveCoding " ) ,
TEXT ( " Enables live coding support " ) ,
FConsoleCommandDelegate : : CreateRaw ( this , & FLiveCodingModule : : EnableForSession , true ) ,
ECVF_Cheat
) ;
2019-05-28 10:22:20 -04:00
CompileCommand = ConsoleManager . RegisterConsoleCommand (
TEXT ( " LiveCoding.Compile " ) ,
TEXT ( " Initiates a live coding compile " ) ,
FConsoleCommandDelegate : : CreateRaw ( this , & FLiveCodingModule : : Compile ) ,
ECVF_Cheat
) ;
# if IS_MONOLITHIC
FString DefaultEngineDir = GLiveCodingEngineDir ;
# else
FString DefaultEngineDir = FPaths : : EngineDir ( ) ;
# endif
# if USE_DEBUG_LIVE_CODING_CONSOLE
static const TCHAR * DefaultConsolePath = TEXT ( " Binaries/Win64/LiveCodingConsole-Win64-Debug.exe " ) ;
# else
static const TCHAR * DefaultConsolePath = TEXT ( " Binaries/Win64/LiveCodingConsole.exe " ) ;
# endif
2019-03-27 11:33:31 -04:00
ConsolePathVariable = ConsoleManager . RegisterConsoleVariable (
TEXT ( " LiveCoding.ConsolePath " ) ,
2019-05-28 10:22:20 -04:00
FPaths : : ConvertRelativePathToFull ( DefaultEngineDir / DefaultConsolePath ) ,
2019-03-27 11:33:31 -04:00
TEXT ( " Path to the live coding console application " ) ,
ECVF_Cheat
) ;
2019-05-28 10:22:20 -04:00
# if IS_MONOLITHIC
FString SourceProject = ( GLiveCodingProject ! = nullptr ) ? GLiveCodingProject : TEXT ( " " ) ;
# else
FString SourceProject = FPaths : : IsProjectFilePathSet ( ) ? FPaths : : GetProjectFilePath ( ) : TEXT ( " " ) ;
# endif
SourceProjectVariable = ConsoleManager . RegisterConsoleVariable (
TEXT ( " LiveCoding.SourceProject " ) ,
FPaths : : ConvertRelativePathToFull ( SourceProject ) ,
TEXT ( " Path to the project that this target was built from " ) ,
ECVF_Cheat
) ;
2019-03-27 11:33:31 -04:00
EndFrameDelegateHandle = FCoreDelegates : : OnEndFrame . AddRaw ( this , & FLiveCodingModule : : Tick ) ;
ISettingsModule * SettingsModule = FModuleManager : : GetModulePtr < ISettingsModule > ( " Settings " ) ;
if ( SettingsModule ! = nullptr )
{
SettingsSection = SettingsModule - > RegisterSettings ( " Editor " , " General " , " Live Coding " ,
LOCTEXT ( " LiveCodingSettingsName " , " Live Coding " ) ,
LOCTEXT ( " LiveCodintSettingsDescription " , " Settings for recompiling C++ code while the engine is running. " ) ,
GetMutableDefault < ULiveCodingSettings > ( )
) ;
}
extern void Startup ( Windows : : HINSTANCE hInstance ) ;
Startup ( hInstance ) ;
2019-04-29 11:59:36 -04:00
if ( Settings - > bEnabled & & ! FApp : : IsUnattended ( ) )
2019-03-27 11:33:31 -04:00
{
if ( Settings - > Startup = = ELiveCodingStartupMode : : Automatic )
{
StartLiveCoding ( ) ;
ShowConsole ( ) ;
}
else if ( Settings - > Startup = = ELiveCodingStartupMode : : AutomaticButHidden )
{
GLiveCodingConsoleArguments = L " -Hidden " ;
StartLiveCoding ( ) ;
}
}
if ( FParse : : Param ( FCommandLine : : Get ( ) , TEXT ( " LiveCoding " ) ) )
{
StartLiveCoding ( ) ;
}
bEnabledLastTick = Settings - > bEnabled ;
}
void FLiveCodingModule : : ShutdownModule ( )
{
extern void Shutdown ( ) ;
Shutdown ( ) ;
FCoreDelegates : : OnEndFrame . Remove ( EndFrameDelegateHandle ) ;
IConsoleManager & ConsoleManager = IConsoleManager : : Get ( ) ;
2019-05-28 10:22:20 -04:00
ConsoleManager . UnregisterConsoleObject ( SourceProjectVariable ) ;
2019-03-27 11:33:31 -04:00
ConsoleManager . UnregisterConsoleObject ( ConsolePathVariable ) ;
2019-05-28 10:22:20 -04:00
ConsoleManager . UnregisterConsoleObject ( CompileCommand ) ;
2019-03-27 11:33:31 -04:00
ConsoleManager . UnregisterConsoleObject ( EnableCommand ) ;
}
void FLiveCodingModule : : EnableByDefault ( bool bEnable )
{
if ( Settings - > bEnabled ! = bEnable )
{
Settings - > bEnabled = bEnable ;
if ( SettingsSection . IsValid ( ) )
{
SettingsSection - > Save ( ) ;
}
}
EnableForSession ( bEnable ) ;
}
bool FLiveCodingModule : : IsEnabledByDefault ( ) const
{
return Settings - > bEnabled ;
}
void FLiveCodingModule : : EnableForSession ( bool bEnable )
{
if ( bEnable )
{
if ( ! bStarted )
{
StartLiveCoding ( ) ;
ShowConsole ( ) ;
}
}
else
{
if ( bStarted )
{
UE_LOG ( LogLiveCoding , Display , TEXT ( " Console will be hidden but remain running in the background. Restart to disable completely. " ) ) ;
LppSetActive ( false ) ;
LppSetVisible ( false ) ;
bEnabledForSession = false ;
}
}
}
bool FLiveCodingModule : : IsEnabledForSession ( ) const
{
return bEnabledForSession ;
}
bool FLiveCodingModule : : CanEnableForSession ( ) const
{
# if !IS_MONOLITHIC
FModuleManager & ModuleManager = FModuleManager : : Get ( ) ;
if ( ModuleManager . HasAnyOverridenModuleFilename ( ) )
{
return false ;
}
# endif
return true ;
}
bool FLiveCodingModule : : HasStarted ( ) const
{
return bStarted ;
}
void FLiveCodingModule : : ShowConsole ( )
{
if ( bStarted )
{
LppSetVisible ( true ) ;
LppSetActive ( true ) ;
LppShowConsole ( ) ;
}
}
void FLiveCodingModule : : Compile ( )
{
if ( ! GIsCompileActive )
{
EnableForSession ( true ) ;
if ( bStarted )
{
LppTriggerRecompile ( ) ;
GIsCompileActive = true ;
}
}
}
bool FLiveCodingModule : : IsCompiling ( ) const
{
return GIsCompileActive ;
}
void FLiveCodingModule : : Tick ( )
{
if ( Settings - > bEnabled ! = bEnabledLastTick & & Settings - > Startup ! = ELiveCodingStartupMode : : Manual )
{
EnableForSession ( Settings - > bEnabled ) ;
bEnabledLastTick = Settings - > bEnabled ;
}
2019-05-28 10:22:20 -04:00
if ( bUpdateModulesInTick )
{
UpdateModules ( ) ;
bUpdateModulesInTick = false ;
}
2019-03-27 11:33:31 -04:00
}
bool FLiveCodingModule : : StartLiveCoding ( )
{
if ( ! bStarted )
{
// Make sure there aren't any hot reload modules already active
if ( ! CanEnableForSession ( ) )
{
UE_LOG ( LogLiveCoding , Error , TEXT ( " Unable to start live coding session. Some modules have already been hot reloaded. " ) ) ;
return false ;
}
// Setup the console path
GLiveCodingConsolePath = ConsolePathVariable - > GetString ( ) ;
if ( ! FPaths : : FileExists ( GLiveCodingConsolePath ) )
{
UE_LOG ( LogLiveCoding , Error , TEXT ( " Unable to start live coding session. Missing executable '%s'. Use the LiveCoding.ConsolePath console variable to modify. " ) , * GLiveCodingConsolePath ) ;
return false ;
}
2019-05-28 10:22:20 -04:00
// Get the source project filename
FString SourceProject = SourceProjectVariable - > GetString ( ) ;
if ( SourceProject . Len ( ) > 0 & & ! FPaths : : FileExists ( SourceProject ) )
{
UE_LOG ( LogLiveCoding , Error , TEXT ( " Unable to start live coding session. Unable to find source project file '%s'. " ) , * SourceProject ) ;
return false ;
}
2019-03-27 11:33:31 -04:00
UE_LOG ( LogLiveCoding , Display , TEXT ( " Starting LiveCoding " ) ) ;
// Enable external build system
LppUseExternalBuildSystem ( ) ;
// Enable the server
FString ProcessGroup = FString : : Printf ( TEXT ( " UE4_%s_0x%08x " ) , FApp : : GetProjectName ( ) , GetTypeHash ( FPaths : : ProjectDir ( ) ) ) ;
LppRegisterProcessGroup ( TCHAR_TO_ANSI ( * ProcessGroup ) ) ;
// Build the command line
FString Arguments ;
Arguments + = FString : : Printf ( TEXT ( " %s " ) , FPlatformMisc : : GetUBTPlatform ( ) ) ;
Arguments + = FString : : Printf ( TEXT ( " %s " ) , EBuildConfigurations : : ToString ( FApp : : GetBuildConfiguration ( ) ) ) ;
Arguments + = FString : : Printf ( TEXT ( " -TargetType=%s " ) , FPlatformMisc : : GetUBTTarget ( ) ) ;
2019-05-28 10:22:20 -04:00
if ( SourceProject . Len ( ) > 0 )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
Arguments + = FString : : Printf ( TEXT ( " -Project= \" %s \" " ) , * FPaths : : ConvertRelativePathToFull ( SourceProject ) ) ;
2019-03-27 11:33:31 -04:00
}
LppSetBuildArguments ( * Arguments ) ;
2019-05-28 10:22:20 -04:00
// Configure all the current modules. For non-commandlets, schedule it to be done in the first Tick() so we can batch everything together.
if ( IsRunningCommandlet ( ) )
{
UpdateModules ( ) ;
}
else
{
bUpdateModulesInTick = true ;
}
2019-03-27 11:33:31 -04:00
// Register a delegate to listen for new modules loaded from this point onwards
ModulesChangedDelegateHandle = FModuleManager : : Get ( ) . OnModulesChanged ( ) . AddRaw ( this , & FLiveCodingModule : : OnModulesChanged ) ;
// Mark it as started
bStarted = true ;
bEnabledForSession = true ;
}
return true ;
}
void FLiveCodingModule : : UpdateModules ( )
{
2019-05-28 10:22:20 -04:00
if ( bEnabledForSession )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
# if IS_MONOLITHIC
wchar_t FullFilePath [ WINDOWS_MAX_PATH ] ;
verify ( GetModuleFileName ( hInstance , FullFilePath , ARRAY_COUNT ( FullFilePath ) ) ) ;
LppEnableModule ( FullFilePath ) ;
# else
TArray < FModuleStatus > ModuleStatuses ;
FModuleManager : : Get ( ) . QueryModules ( ModuleStatuses ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
TArray < FString > EnableModules ;
for ( const FModuleStatus & ModuleStatus : ModuleStatuses )
{
if ( ModuleStatus . bIsLoaded )
{
FName ModuleName ( * ModuleStatus . Name ) ;
if ( ! ConfiguredModules . Contains ( ModuleName ) )
{
FString FullFilePath = FPaths : : ConvertRelativePathToFull ( ModuleStatus . FilePath ) ;
if ( ShouldPreloadModule ( ModuleName , FullFilePath ) )
{
EnableModules . Add ( FullFilePath ) ;
}
else
{
LppEnableLazyLoadedModule ( * FullFilePath ) ;
}
ConfiguredModules . Add ( ModuleName ) ;
}
}
}
if ( EnableModules . Num ( ) > 0 )
{
TArray < const TCHAR * > EnableModuleFileNames ;
for ( const FString & EnableModule : EnableModules )
{
EnableModuleFileNames . Add ( * EnableModule ) ;
}
LppEnableModules ( EnableModuleFileNames . GetData ( ) , EnableModuleFileNames . Num ( ) ) ;
}
2019-03-27 11:33:31 -04:00
# endif
2019-05-28 10:22:20 -04:00
}
2019-03-27 11:33:31 -04:00
}
void FLiveCodingModule : : OnModulesChanged ( FName ModuleName , EModuleChangeReason Reason )
{
# if !IS_MONOLITHIC
if ( Reason = = EModuleChangeReason : : ModuleLoaded )
{
2019-05-28 10:22:20 -04:00
// Assume that Tick() won't be called if we're running a commandlet
if ( IsRunningCommandlet ( ) )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
UpdateModules ( ) ;
2019-03-27 11:33:31 -04:00
}
else
{
2019-05-28 10:22:20 -04:00
bUpdateModulesInTick = true ;
2019-03-27 11:33:31 -04:00
}
}
# endif
}
bool FLiveCodingModule : : ShouldPreloadModule ( const FName & Name , const FString & FullFilePath ) const
{
if ( Settings - > PreloadNamedModules . Contains ( Name ) )
{
return true ;
}
if ( FullFilePath . StartsWith ( FullProjectDir ) )
{
if ( Settings - > bPreloadProjectModules = = Settings - > bPreloadProjectPluginModules )
{
return Settings - > bPreloadProjectModules ;
}
if ( FullFilePath . StartsWith ( FullProjectPluginsDir ) )
{
return Settings - > bPreloadProjectPluginModules ;
}
else
{
return Settings - > bPreloadProjectModules ;
}
}
else
{
if ( FApp : : IsEngineInstalled ( ) )
{
return false ;
}
if ( Settings - > bPreloadEngineModules = = Settings - > bPreloadEnginePluginModules )
{
return Settings - > bPreloadEngineModules ;
}
if ( FullFilePath . StartsWith ( FullEnginePluginsDir ) )
{
return Settings - > bPreloadEnginePluginModules ;
}
else
{
return Settings - > bPreloadEngineModules ;
}
}
}
# undef LOCTEXT_NAMESPACE