2019-03-27 11:33:31 -04:00
// Copyright 2011-2019 Molecular Matters GmbH, all rights reserved.
# include "LC_ServerCommandThread.h"
# include "LC_Commands.h"
# include "LC_Telemetry.h"
# include "LC_Symbols.h"
# include "LC_FileUtil.h"
# include "LC_Process.h"
# include "LC_Compiler.h"
# include "LC_StringUtil.h"
# include "LC_CommandMap.h"
# include "LC_FileAttributeCache.h"
# include "LiveCodingServer.h"
# include "LC_Shortcut.h"
# include "LC_Key.h"
# include "LC_ChangeNotification.h"
# include "LC_DirectoryCache.h"
# include "LC_VirtualDrive.h"
# include "LC_LiveModule.h"
# include "LC_LiveProcess.h"
# include "LC_CodeCave.h"
# include "LC_PrimitiveNames.h"
# include "LC_AppSettings.h"
# include "LC_Allocators.h"
# include "LC_DuplexPipeClient.h"
2019-05-28 10:22:20 -04:00
# include "LC_MemoryStream.h"
2019-03-27 11:33:31 -04:00
# include <mmsystem.h>
// unreachable code
# pragma warning (disable : 4702)
// BEGIN EPIC MODS
# pragma warning(push)
# pragma warning(disable:6031) // warning C6031: Return value ignored: 'CoInitialize'.
// END EPIC MODS
namespace
{
static telemetry : : Accumulator g_loadedModuleSize ( " Module size " ) ;
struct InitializeCOM
{
InitializeCOM ( void )
{
2019-04-29 11:59:36 -04:00
const HRESULT success = : : CoInitialize ( NULL ) ;
if ( success ! = S_OK )
{
LC_LOG_DEV ( " Could not initialize COM. Error: %d " , success ) ;
}
2019-03-27 11:33:31 -04:00
}
~ InitializeCOM ( void )
{
: : CoUninitialize ( ) ;
}
} ;
static void AddVirtualDrive ( void )
{
const std : : wstring virtualDriveLetter = appSettings : : g_virtualDriveLetter - > GetValue ( ) ;
const std : : wstring virtualDrivePath = appSettings : : g_virtualDrivePath - > GetValue ( ) ;
if ( ( virtualDriveLetter . size ( ) ! = 0 ) & & ( virtualDrivePath . size ( ) ! = 0 ) )
{
virtualDrive : : Add ( virtualDriveLetter . c_str ( ) , virtualDrivePath . c_str ( ) ) ;
}
}
static void RemoveVirtualDrive ( void )
{
const std : : wstring virtualDriveLetter = appSettings : : g_virtualDriveLetter - > GetValue ( ) ;
const std : : wstring virtualDrivePath = appSettings : : g_virtualDrivePath - > GetValue ( ) ;
if ( ( virtualDriveLetter . size ( ) ! = 0 ) & & ( virtualDrivePath . size ( ) ! = 0 ) )
{
virtualDrive : : Remove ( virtualDriveLetter . c_str ( ) , virtualDrivePath . c_str ( ) ) ;
}
}
static executable : : Header GetImageHeader ( const wchar_t * path )
{
executable : : Image * image = executable : : OpenImage ( path , file : : OpenMode : : READ_ONLY ) ;
if ( ! image )
{
return executable : : Header { } ;
}
const executable : : Header & imageHeader = executable : : GetHeader ( image ) ;
executable : : CloseImage ( image ) ;
return imageHeader ;
}
}
ServerCommandThread : : ServerCommandThread ( MainFrame * mainFrame , const wchar_t * const processGroupName , RunMode : : Enum runMode )
: m_processGroupName ( processGroupName )
, m_runMode ( runMode )
, m_mainFrame ( mainFrame )
, m_serverThread ( )
, m_compileThread ( )
, m_liveModules ( )
, m_liveProcesses ( )
, m_imageHeaderToLiveModule ( )
, m_actionCS ( )
, m_exceptionCS ( )
, m_inExceptionHandlerEvent ( nullptr , Event : : Type : : MANUAL_RESET )
, m_handleCommandsEvent ( nullptr , Event : : Type : : MANUAL_RESET )
, m_directoryCache ( new DirectoryCache ( 2048u ) )
, m_connectionCS ( )
, m_commandThreads ( )
, m_manualRecompileTriggered ( false )
, m_liveModuleToModifiedOrNewObjFiles ( )
{
2019-05-28 10:22:20 -04:00
m_serverThread = thread : : Create ( " Live coding server " , 64u * 1024u , & ServerCommandThread : : ServerThread , this ) ;
m_compileThread = thread : : Create ( " Live coding compilation " , 64u * 1024u , & ServerCommandThread : : CompileThread , this ) ;
2019-03-27 11:33:31 -04:00
m_liveModules . reserve ( 256u ) ;
m_liveProcesses . reserve ( 8u ) ;
m_imageHeaderToLiveModule . reserve ( 256u ) ;
m_commandThreads . reserve ( 8u ) ;
}
ServerCommandThread : : ~ ServerCommandThread ( void )
{
// note that we deliberately do *nothing* here.
// this is only called when Live++ is being torn down anyway, so we leave cleanup to the OS.
// otherwise we could run into races when trying to terminate the thread that might currently be doing
// some intensive work.
2019-04-29 11:59:36 -04:00
delete m_directoryCache ;
2019-03-27 11:33:31 -04:00
}
std : : wstring ServerCommandThread : : GetProcessImagePath ( void ) const
{
// there must be at least one registered process.
// in case the EXE was erroneously started directly, no process will be registered.
// handle this case gracefully.
if ( m_liveProcesses . size ( ) = = 0u )
{
return L " Unknown " ;
}
return process : : GetImagePath ( m_liveProcesses [ 0 ] - > GetProcessHandle ( ) ) ;
}
2019-05-28 10:22:20 -04:00
scheduler : : Task < LiveModule * > * ServerCommandThread : : LoadModule ( unsigned int processId , void * moduleBase , const wchar_t * givenModulePath , scheduler : : TaskBase * taskRoot )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
// note that the path we get from the client might not be normalized, depending on how the executable was launched.
// it is crucial to normalize the path again, otherwise we could load already loaded modules into the same
// Live++ instance, which would wreak havoc
2019-03-27 11:33:31 -04:00
const std : : wstring & modulePath = file : : NormalizePath ( givenModulePath ) ;
const executable : : Header imageHeader = GetImageHeader ( modulePath . c_str ( ) ) ;
if ( ! executable : : IsValidHeader ( imageHeader ) )
{
2019-05-28 10:22:20 -04:00
return nullptr ;
2019-03-27 11:33:31 -04:00
}
LiveProcess * liveProcess = FindProcessById ( processId ) ;
LC_ASSERT ( liveProcess , " Invalid process ID. " ) ;
if ( liveProcess - > TriedToLoadImage ( imageHeader ) )
{
// tried loading this module into this process already
2019-05-28 10:22:20 -04:00
return nullptr ;
2019-03-27 11:33:31 -04:00
}
2019-05-28 10:22:20 -04:00
// find any other process ID that tried to load this module already
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
const size_t count = m_liveProcesses . size ( ) ;
2019-03-27 11:33:31 -04:00
for ( size_t i = 0u ; i < count ; + + i )
{
2019-05-28 10:22:20 -04:00
LiveProcess * otherLiveProcess = m_liveProcesses [ i ] ;
if ( otherLiveProcess - > TriedToLoadImage ( imageHeader ) )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
// some *other* process loaded this module already
LC_LOG_USER ( " Registering module %S (PID: %d) " , modulePath . c_str ( ) , processId ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
LiveModule * liveModule = m_imageHeaderToLiveModule [ imageHeader ] ;
if ( liveModule )
{
liveModule - > RegisterProcess ( liveProcess , moduleBase , modulePath ) ;
liveModule - > DisableControlFlowGuard ( liveProcess , moduleBase ) ;
const bool installedPatchesSuccessfully = liveModule - > InstallCompiledPatches ( liveProcess , moduleBase ) ;
if ( ! installedPatchesSuccessfully )
{
LC_ERROR_USER ( " Compiled patches could not be installed (PID: %d) " , processId ) ;
liveModule - > UnregisterProcess ( liveProcess ) ;
}
liveProcess - > AddLoadedImage ( imageHeader ) ;
}
return nullptr ;
}
2019-03-27 11:33:31 -04:00
}
}
2019-05-28 10:22:20 -04:00
symbols : : Provider * moduleProvider = symbols : : OpenEXE ( modulePath . c_str ( ) , symbols : : OpenOptions : : ACCUMULATE_SIZE ) ;
if ( ! moduleProvider )
{
return nullptr ;
}
2019-03-27 11:33:31 -04:00
liveProcess - > AddLoadedImage ( imageHeader ) ;
2019-05-28 10:22:20 -04:00
// accumulate module info
{
const file : : Attributes attributes = file : : GetAttributes ( modulePath . c_str ( ) ) ;
const uint64_t size = file : : GetSize ( attributes ) ;
g_loadedModuleSize . Accumulate ( size ) ;
g_loadedModuleSize . Print ( ) ;
g_loadedModuleSize . ResetCurrent ( ) ;
LC_LOG_USER ( " Loading module %S (%.3f MB) " , modulePath . c_str ( ) , size / 1048576.0f ) ;
}
// create a task to load the module of this batch concurrently
LiveModule * liveModule = new LiveModule ( modulePath . c_str ( ) , imageHeader , m_runMode ) ;
m_imageHeaderToLiveModule . emplace ( imageHeader , liveModule ) ;
auto task = scheduler : : CreateTask ( taskRoot , [ liveModule , liveProcess , modulePath , moduleBase , moduleProvider ] ( )
{
telemetry : : Scope scope ( " Loading module " ) ;
symbols : : DiaCompilandDB * moduleDiaCompilandDb = symbols : : GatherDiaCompilands ( moduleProvider ) ;
liveModule - > Load ( moduleProvider , moduleDiaCompilandDb ) ;
liveModule - > RegisterProcess ( liveProcess , moduleBase , modulePath ) ;
liveModule - > DisableControlFlowGuard ( liveProcess , moduleBase ) ;
symbols : : DestroyDiaCompilandDB ( moduleDiaCompilandDb ) ;
symbols : : Close ( moduleProvider ) ;
return liveModule ;
} ) ;
scheduler : : RunTask ( task ) ;
return task ;
2019-03-27 11:33:31 -04:00
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : UnloadModule ( unsigned int processId , const wchar_t * givenModulePath )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
// note that the path we get from the client might not be normalized, depending on how the executable was launched.
// it is crucial to normalize the path again, otherwise we could load already loaded modules into the same
// Live++ instance, which would wreak havoc
2019-03-27 11:33:31 -04:00
const std : : wstring & modulePath = file : : NormalizePath ( givenModulePath ) ;
const executable : : Header imageHeader = GetImageHeader ( modulePath . c_str ( ) ) ;
if ( ! executable : : IsValidHeader ( imageHeader ) )
{
2019-05-28 10:22:20 -04:00
return false ;
2019-03-27 11:33:31 -04:00
}
LiveProcess * liveProcess = FindProcessById ( processId ) ;
LC_ASSERT ( liveProcess , " Invalid process ID. " ) ;
if ( ! liveProcess - > TriedToLoadImage ( imageHeader ) )
{
// this module was never loaded
2019-05-28 10:22:20 -04:00
return false ;
2019-03-27 11:33:31 -04:00
}
2019-05-28 10:22:20 -04:00
LC_LOG_USER ( " Unloading module %S " , modulePath . c_str ( ) ) ;
2019-03-27 11:33:31 -04:00
liveProcess - > RemoveLoadedImage ( imageHeader ) ;
2019-05-28 10:22:20 -04:00
m_imageHeaderToLiveModule . erase ( imageHeader ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
for ( auto it = m_liveModules . begin ( ) ; it ! = m_liveModules . end ( ) ; /* nothing */ )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
LiveModule * liveModule = * it ;
if ( std : : equal_to < executable : : Header > ( ) ( liveModule - > GetImageHeader ( ) , imageHeader ) )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
liveModule - > Unload ( ) ;
delete liveModule ;
it = m_liveModules . erase ( it ) ;
return true ;
2019-03-27 11:33:31 -04:00
}
2019-05-28 10:22:20 -04:00
else
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
+ + it ;
2019-03-27 11:33:31 -04:00
}
}
2019-05-28 10:22:20 -04:00
return false ;
2019-03-27 11:33:31 -04:00
}
void ServerCommandThread : : PrewarmCompilerEnvironmentCache ( void )
{
// EPIC REMOVED: g_theApp.GetMainFrame()->ChangeStatusBarText(L"Prewarming compiler/linker environment cache...");
telemetry : : Scope scope ( " Prewarming compiler/linker environment cache " ) ;
// fetch unique compiler and linker paths from all modules
types : : StringSet uniquePaths ;
// compiler and linker paths can be overridden, so we need to make sure that we pre-warm the
// cache for all compilers and linkers involved, depending on the UI settings.
// there are 3 options:
// - the path is not overridden: fetch only the paths from the compilands
// - the paths are overridden, but only used as fallback: fetch the paths from the compilands
// as well as the overridden ones. we might need both, depending on which file we compile
// - the paths are overridden, and always used: fetch only the overridden paths, we're only using those
// fetch all compiler paths involved.
// the compiler is only used in default mode, NOT when using an external build system.
const bool useCompilerEnvironment = appSettings : : g_useCompilerEnvironment - > GetValue ( ) ;
if ( useCompilerEnvironment & & ( m_runMode = = RunMode : : DEFAULT ) )
{
const std : : wstring overriddenPath = appSettings : : GetCompilerPath ( ) ;
const bool useOverriddenPathAsFallback = appSettings : : g_useCompilerOverrideAsFallback - > GetValue ( ) ;
// always prewarm for overridden compiler path if it is available
const bool prewarmOverridenPath = ( overriddenPath . length ( ) ! = 0u ) ;
const bool prewarmCompilandCompilerPath = prewarmOverridenPath
? useOverriddenPathAsFallback // overridden path is set. only prewarm compiland compiler paths if the override is only used as fallback
: true ; // no override is set, always prewarm
if ( prewarmCompilandCompilerPath )
{
const size_t count = m_liveModules . size ( ) ;
for ( size_t i = 0u ; i < count ; + + i )
{
const LiveModule * liveModule = m_liveModules [ i ] ;
const symbols : : CompilandDB * compilandDB = liveModule - > GetCompilandDatabase ( ) ;
for ( auto it = compilandDB - > compilands . begin ( ) ; it ! = compilandDB - > compilands . end ( ) ; + + it )
{
const symbols : : Compiland * compiland = it - > second ;
LC_ASSERT ( compiland - > compilerPath . c_str ( ) , " Invalid compiler path. " ) ;
if ( compiland - > compilerPath . GetLength ( ) ! = 0u )
{
uniquePaths . insert ( compiland - > compilerPath ) ;
}
else
{
LC_WARNING_USER ( " Not prewarming environment cache for empty compiler in module %S " , liveModule - > GetModuleName ( ) . c_str ( ) ) ;
}
}
}
}
if ( prewarmOverridenPath )
{
uniquePaths . insert ( string : : ToUtf8String ( overriddenPath ) ) ;
}
}
// fetch all linker paths involved
const bool useLinkerEnvironment = appSettings : : g_useLinkerEnvironment - > GetValue ( ) ;
if ( useLinkerEnvironment )
{
const std : : wstring overriddenPath = appSettings : : GetLinkerPath ( ) ;
const bool useOverriddenPathAsFallback = appSettings : : g_useLinkerOverrideAsFallback - > GetValue ( ) ;
// always prewarm for overridden linker path if it is available
const bool prewarmOverridenPath = ( overriddenPath . length ( ) ! = 0u ) ;
const bool prewarmLinkerPath = prewarmOverridenPath
? useOverriddenPathAsFallback // overridden path is set. only prewarm linker paths if the override is only used as fallback
: true ; // no override is set, always prewarm
if ( prewarmLinkerPath )
{
const size_t count = m_liveModules . size ( ) ;
for ( size_t i = 0u ; i < count ; + + i )
{
const LiveModule * liveModule = m_liveModules [ i ] ;
const symbols : : LinkerDB * linkerDB = liveModule - > GetLinkerDatabase ( ) ;
if ( linkerDB - > linkerPath . GetLength ( ) ! = 0u )
{
uniquePaths . insert ( linkerDB - > linkerPath ) ;
}
else
{
LC_WARNING_USER ( " Not prewarming environment cache for empty linker in module %S " , liveModule - > GetModuleName ( ) . c_str ( ) ) ;
}
}
}
if ( prewarmOverridenPath )
{
uniquePaths . insert ( string : : ToUtf8String ( overriddenPath ) ) ;
}
}
// grab environment blocks for all unique compilers/linkers concurrently
auto taskRoot = scheduler : : CreateEmptyTask ( ) ;
types : : vector < scheduler : : TaskBase * > tasks ;
tasks . reserve ( uniquePaths . size ( ) ) ;
for ( auto it = uniquePaths . begin ( ) ; it ! = uniquePaths . end ( ) ; + + it )
{
auto task = scheduler : : CreateTask ( taskRoot , [ it ] ( )
{
const ImmutableString & path = * it ;
compiler : : UpdateEnvironmentCache ( string : : ToWideString ( path ) . c_str ( ) ) ;
return true ;
} ) ;
scheduler : : RunTask ( task ) ;
tasks . emplace_back ( task ) ;
}
// wait for all tasks to end
scheduler : : RunTask ( taskRoot ) ;
scheduler : : WaitForTask ( taskRoot ) ;
// destroy all tasks
scheduler : : DestroyTasks ( tasks ) ;
scheduler : : DestroyTask ( taskRoot ) ;
2019-05-28 10:22:20 -04:00
if ( uniquePaths . size ( ) ! = 0u )
{
LC_SUCCESS_USER ( " Prewarmed compiler/linker environment cache (%.3fs, %zu executables) " , scope . ReadSeconds ( ) , uniquePaths . size ( ) ) ;
}
2019-03-27 11:33:31 -04:00
}
unsigned int ServerCommandThread : : ServerThread ( void )
{
InitializeCOM initCOM ;
// inter process event for telling client that server is ready
Event serverReadyEvent ( primitiveNames : : ServerReadyEvent ( m_processGroupName ) . c_str ( ) , Event : : Type : : AUTO_RESET ) ;
// run separate pipe servers for all incoming connections
for ( ; ; )
{
CommandThreadContext * context = new CommandThreadContext ;
context - > pipe . Create ( primitiveNames : : Pipe ( m_processGroupName ) . c_str ( ) ) ;
context - > exceptionPipe . Create ( primitiveNames : : ExceptionPipe ( m_processGroupName ) . c_str ( ) ) ;
context - > readyEvent = new Event ( nullptr , Event : : Type : : AUTO_RESET ) ;
// tell other processes that a new server is ready
serverReadyEvent . Signal ( ) ;
// wait until any client connects, blocking
context - > pipe . WaitForClient ( ) ;
context - > exceptionPipe . WaitForClient ( ) ;
// a new client has connected, open a new thread for communication
2019-05-28 10:22:20 -04:00
context - > commandThread = thread : : Create ( " Live coding client command communication " , 64u * 1024u , & ServerCommandThread : : CommandThread , this , & context - > pipe , context - > readyEvent ) ;
context - > exceptionCommandThread = thread : : Create ( " Live coding client exception command communication " , 64u * 1024u , & ServerCommandThread : : ExceptionCommandThread , this , & context - > exceptionPipe ) ;
2019-03-27 11:33:31 -04:00
// register this connection
{
CriticalSection : : ScopedLock lock ( & m_connectionCS ) ;
m_commandThreads . push_back ( context ) ;
}
}
return 0u ;
}
// BEGIN EPIC MOD - Focus application windows on patch complete
BOOL CALLBACK FocusApplicationWindows ( HWND WindowHandle , LPARAM Lparam )
{
DWORD WindowProcessId ;
GetWindowThreadProcessId ( WindowHandle , & WindowProcessId ) ;
const types : : vector < LiveProcess * > & Processes = * ( const types : : vector < LiveProcess * > * ) Lparam ;
for ( LiveProcess * Process : Processes )
{
if ( Process - > GetProcessId ( ) = = WindowProcessId & & IsWindowVisible ( WindowHandle ) )
{
SetForegroundWindow ( WindowHandle ) ;
}
}
return Windows : : TRUE ;
}
// END EPIC MOD
// BEGIN EPIC MOD - Support for lazy-loading modules
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : FinishedLazyLoadingModules : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
return false ;
}
struct ClientProxyThread
{
2019-05-28 10:22:20 -04:00
struct ProxyEnableModulesFinishedAction
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
typedef commands : : EnableModulesFinished CommandType ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
static bool Execute ( CommandType * command , const DuplexPipe * pipe , void * , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
return false ;
}
} ;
LiveProcess * m_process ;
DuplexPipeClient * m_pipe ;
std : : vector < std : : wstring > m_enableModules ;
thread : : Handle m_threadHandle ;
ClientProxyThread ( LiveProcess * process , DuplexPipeClient * pipe , const std : : vector < std : : wstring > enableModules )
: m_process ( process )
, m_pipe ( pipe )
, m_enableModules ( enableModules )
{
m_threadHandle = thread : : Create ( 64u * 1024u , & StaticEntryPoint , this ) ;
thread : : SetName ( " Live coding client proxy " ) ;
}
~ ClientProxyThread ( )
{
thread : : Join ( m_threadHandle ) ;
thread : : Close ( m_threadHandle ) ;
}
static unsigned int __stdcall StaticEntryPoint ( void * context )
{
static_cast < ClientProxyThread * > ( context ) - > EntryPoint ( ) ;
return 0 ;
}
void EntryPoint ( )
{
2019-05-28 10:22:20 -04:00
std : : vector < commands : : ModuleData > modules ;
modules . resize ( m_enableModules . size ( ) ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
for ( size_t Idx = 0 ; Idx < m_enableModules . size ( ) ; Idx + + )
{
commands : : ModuleData & module = modules [ Idx ] ;
module . base = m_process - > GetLazyLoadedModuleBase ( m_enableModules [ Idx ] . c_str ( ) ) ;
wcscpy_s ( module . path , m_enableModules [ Idx ] . c_str ( ) ) ;
2019-03-27 11:33:31 -04:00
}
2019-05-28 10:22:20 -04:00
commands : : EnableModules enableModulesCommand ;
enableModulesCommand . processId = m_process - > GetProcessId ( ) ;
enableModulesCommand . moduleCount = m_enableModules . size ( ) ;
enableModulesCommand . token = nullptr ;
m_pipe - > SendCommandAndWaitForAck ( enableModulesCommand , modules . data ( ) , modules . size ( ) * sizeof ( commands : : ModuleData ) ) ;
CommandMap commandMap ;
commandMap . RegisterAction < ProxyEnableModulesFinishedAction > ( ) ;
commandMap . HandleCommands ( m_pipe , m_process ) ;
m_pipe - > SendCommandAndWaitForAck ( commands : : FinishedLazyLoadingModules ( ) , nullptr , 0 ) ;
2019-03-27 11:33:31 -04:00
}
} ;
// END EPIC MOD
void ServerCommandThread : : CompileChanges ( bool didAllProcessesMakeProgress )
{
// recompile files, if any
// EPIC REMOVED: g_theApp.GetMainFrame()->SetBusy(true);
// EPIC REMOVED: g_theApp.GetMainFrame()->ChangeStatusBarText(L"Creating patch...");
telemetry : : Scope scope ( " Creating patch " ) ;
// EPIC REMOVED: g_theApp.GetMainFrame()->OnCompilationStart();
LC_LOG_USER ( " ---------- Creating patch ---------- " ) ;
// BEGIN EPIC MOD - Hook for the compiler
GLiveCodingServer - > GetCompileStartedDelegate ( ) . ExecuteIfBound ( ) ;
const ILiveCodingServer : : FCompileDelegate & CompileDelegate = GLiveCodingServer - > GetCompileDelegate ( ) ;
if ( CompileDelegate . IsBound ( ) )
{
// Get the list of arguments for building each target, and use the delegate to pass them to UBT
TArray < FString > Targets ;
for ( LiveProcess * liveProcess : m_liveProcesses )
{
Targets . Add ( liveProcess - > GetBuildArguments ( ) ) ;
}
GLiveCodingServer - > GetStatusChangeDelegate ( ) . ExecuteIfBound ( L " Compiling changes for live coding... " ) ;
TMap < FString , TArray < FString > > ModuleToObjectFiles ;
2019-05-15 14:19:47 -04:00
if ( ! CompileDelegate . Execute ( Targets , ModuleToObjectFiles ) )
2019-03-27 11:33:31 -04:00
{
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Error , L " Compilation error. " ) ;
return ;
}
// Enable any lazy-loaded modules that we need
for ( LiveProcess * liveProcess : m_liveProcesses )
{
types : : vector < std : : wstring > LoadModuleFileNames ;
for ( const TPair < FString , TArray < FString > > & Pair : ModuleToObjectFiles )
{
std : : wstring ModuleFileName = file : : NormalizePath ( * Pair . Key ) ;
if ( liveProcess - > IsPendingLazyLoadedModule ( ModuleFileName ) )
{
LoadModuleFileNames . push_back ( ModuleFileName ) ;
}
}
if ( LoadModuleFileNames . size ( ) > 0 )
{
const std : : wstring PipeName = primitiveNames : : Pipe ( m_processGroupName + L " _ClientProxy " ) ;
DuplexPipeServer ServerPipe ;
ServerPipe . Create ( PipeName . c_str ( ) ) ;
DuplexPipeClient ClientPipe ;
ClientPipe . Connect ( PipeName . c_str ( ) ) ;
ClientProxyThread ClientThread ( liveProcess , & ClientPipe , LoadModuleFileNames ) ;
CommandMap commandMap ;
2019-05-28 10:22:20 -04:00
commandMap . RegisterAction < actions : : EnableModules > ( ) ;
commandMap . RegisterAction < actions : : FinishedLazyLoadingModules > ( ) ;
2019-03-27 11:33:31 -04:00
commandMap . HandleCommands ( & ServerPipe , this ) ;
for ( const std : : wstring & loadModuleFileName : LoadModuleFileNames )
{
liveProcess - > SetLazyLoadedModuleAsLoaded ( loadModuleFileName ) ;
}
}
}
// Build up a list of all the modified object files in each module
types : : unordered_set < std : : wstring > ValidModuleFileNames ;
for ( const LiveModule * liveModule : m_liveModules )
{
ValidModuleFileNames . insert ( liveModule - > GetModuleName ( ) ) ;
}
for ( const TPair < FString , TArray < FString > > & Pair : ModuleToObjectFiles )
{
std : : wstring ModuleFileName = file : : NormalizePath ( * Pair . Key ) ;
if ( ValidModuleFileNames . find ( ModuleFileName ) = = ValidModuleFileNames . end ( ) )
{
2019-05-28 10:22:20 -04:00
// We couldn't find this exact module filename, but this could be a staged executable. See if we can just match the name.
std : : wstring ModuleFileNameOnly = file : : GetFilename ( ModuleFileName ) ;
bool bFoundNameMatch = false ;
for ( const LiveModule * liveModule : m_liveModules )
{
if ( ModuleFileNameOnly = = file : : GetFilename ( liveModule - > GetModuleName ( ) ) )
{
ModuleFileName = liveModule - > GetModuleName ( ) ;
bFoundNameMatch = true ;
break ;
}
}
if ( ! bFoundNameMatch )
{
LC_ERROR_USER ( " Live coding is not enabled for %S. " , ModuleFileName . c_str ( ) ) ;
LC_ERROR_USER ( " Configure the list of enabled modules from the Live Coding section of the editor preferences window. " ) ;
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Error , * FString : : Printf ( TEXT ( " Live coding not enabled for %s " ) , ModuleFileName . c_str ( ) ) ) ;
return ;
}
2019-03-27 11:33:31 -04:00
}
2019-04-29 11:59:36 -04:00
types : : vector < LiveModule : : ModifiedObjFile > ObjectFiles ;
2019-03-27 11:33:31 -04:00
for ( const FString & ObjectFile : Pair . Value )
{
2019-04-29 11:59:36 -04:00
LiveModule : : ModifiedObjFile ModifiedObjFile ;
ModifiedObjFile . objPath = file : : NormalizePath ( * ObjectFile ) ;
ObjectFiles . push_back ( std : : move ( ModifiedObjFile ) ) ;
2019-03-27 11:33:31 -04:00
}
m_liveModuleToModifiedOrNewObjFiles . insert ( std : : make_pair ( ModuleFileName , std : : move ( ObjectFiles ) ) ) ;
}
}
GLiveCodingServer - > GetStatusChangeDelegate ( ) . ExecuteIfBound ( L " Creating patch... " ) ;
// END EPIC MOD
// recompile files, if any
const size_t count = m_liveModules . size ( ) ;
if ( count = = 0u )
{
LC_LOG_USER ( " No live modules enabled " ) ;
}
LiveModule : : ErrorType : : Enum updateError = LiveModule : : ErrorType : : NO_CHANGE ;
// check directory notifications first to prune file changes based on directories
m_directoryCache - > PrimeNotifications ( ) ;
FileAttributeCache fileCache ;
// when all processes made progress, none of them is being held in the debugger which means it is safe to
// communicate with the client, call hooks, use synchronization points, etc.
// however, when a process was held in the debugger and now spins inside the code cave, we are not allowed
// to call any of these functions, because that might lead to a deadlock.
// similarly, if we're currently handling an exception, calling any of the client-provided functions could be fatal.
const bool inExceptionHandler = m_inExceptionHandlerEvent . WaitTimeout ( 0u ) ;
const LiveModule : : UpdateType : : Enum updateType = ( didAllProcessesMakeProgress & & ! inExceptionHandler )
? LiveModule : : UpdateType : : DEFAULT
: LiveModule : : UpdateType : : NO_CLIENT_COMMUNICATION ;
// has the user given us at least one modified or new .obj file for at least one of the modules?
const bool hasAtLeastOneOptionalObj = ( m_liveModuleToModifiedOrNewObjFiles . size ( ) ! = 0u ) ;
for ( size_t i = 0u ; i < count ; + + i )
{
LiveModule * liveModule = m_liveModules [ i ] ;
LiveModule : : ErrorType : : Enum moduleUpdateError = LiveModule : : ErrorType : : SUCCESS ;
if ( hasAtLeastOneOptionalObj )
{
// try to find the list of modified or new .objs for this module
const auto objFilesIt = m_liveModuleToModifiedOrNewObjFiles . find ( liveModule - > GetModuleName ( ) ) ;
if ( objFilesIt = = m_liveModuleToModifiedOrNewObjFiles . end ( ) )
{
// no .objs for this module, ignore
continue ;
}
else
{
// build a patch with the given list of .objs for this module
2019-04-29 11:59:36 -04:00
const types : : vector < LiveModule : : ModifiedObjFile > & objFiles = objFilesIt - > second ;
2019-03-27 11:33:31 -04:00
moduleUpdateError = liveModule - > Update ( & fileCache , m_directoryCache , updateType , objFiles ) ;
}
}
else
{
// no optional .objs were given, update all live modules regularly
2019-04-29 11:59:36 -04:00
types : : vector < LiveModule : : ModifiedObjFile > emptyObjs ;
2019-03-27 11:33:31 -04:00
moduleUpdateError = liveModule - > Update ( & fileCache , m_directoryCache , updateType , emptyObjs ) ;
}
// only accept new error conditions for this module if there haven't been any updates until now.
// this ensures that error conditions are kept and can be shown when updating several modules at once.
if ( updateError = = LiveModule : : ErrorType : : NO_CHANGE )
{
updateError = moduleUpdateError ;
}
}
// restart directory notifications for next compilation
m_directoryCache - > RestartNotifications ( ) ;
//EPIC REMOVED: g_theApp.GetMainFrame()->OnCompilationEnd();
if ( updateError = = LiveModule : : ErrorType : : SUCCESS )
{
// bring Live++ to front on success
if ( appSettings : : g_receiveFocusOnRecompile - > GetValue ( ) = = appSettings : : FocusOnRecompile : : ON_SUCCESS )
{
GLiveCodingServer - > GetBringToFrontDelegate ( ) . ExecuteIfBound ( ) ;
}
// play sound on success
const std : : wstring soundOnSuccess = appSettings : : g_playSoundOnSuccess - > GetValue ( ) ;
if ( soundOnSuccess . size ( ) ! = 0u )
{
// first finish any sound that might still be playing, then play the real sound
: : PlaySoundW ( NULL , NULL , 0u ) ;
: : PlaySoundW ( soundOnSuccess . c_str ( ) , NULL , SND_FILENAME | SND_ASYNC | SND_NODEFAULT ) ;
}
}
if ( ( updateError = = LiveModule : : ErrorType : : COMPILE_ERROR ) | |
( updateError = = LiveModule : : ErrorType : : LINK_ERROR ) | |
( updateError = = LiveModule : : ErrorType : : LOAD_PATCH_ERROR ) | |
( updateError = = LiveModule : : ErrorType : : ACTIVATE_PATCH_ERROR ) )
{
// bring Live++ to front on failure
if ( appSettings : : g_receiveFocusOnRecompile - > GetValue ( ) = = appSettings : : FocusOnRecompile : : ON_ERROR )
{
GLiveCodingServer - > GetBringToFrontDelegate ( ) . ExecuteIfBound ( ) ;
}
// play sound on error
const std : : wstring soundOnError = appSettings : : g_playSoundOnError - > GetValue ( ) ;
if ( soundOnError . size ( ) ! = 0u )
{
// first finish any sound that might still be playing, then play the real sound
: : PlaySoundW ( NULL , NULL , 0u ) ;
: : PlaySoundW ( soundOnError . c_str ( ) , NULL , SND_FILENAME | SND_ASYNC | SND_NODEFAULT ) ;
}
}
// BEGIN EPIC MOD - Custom hooks for finishing compile
switch ( updateError )
{
case LiveModule : : ErrorType : : NO_CHANGE :
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Success , L " No changes detected. " ) ;
break ;
case LiveModule : : ErrorType : : COMPILE_ERROR :
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Error , L " Compilation error. " ) ;
break ;
case LiveModule : : ErrorType : : LINK_ERROR :
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Error , L " Linker error. " ) ;
break ;
case LiveModule : : ErrorType : : LOAD_PATCH_ERROR :
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Error , L " Could not load patch image. " ) ;
break ;
case LiveModule : : ErrorType : : ACTIVATE_PATCH_ERROR :
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Error , L " Could not activate patch. " ) ;
break ;
case LiveModule : : ErrorType : : SUCCESS :
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Success , L " Patch creation successful. " ) ;
EnumWindows ( FocusApplicationWindows , ( LPARAM ) & m_liveProcesses ) ;
break ;
default :
GLiveCodingServer - > GetCompileFinishedDelegate ( ) . ExecuteIfBound ( ELiveCodingResult : : Success , L " Finished. " ) ;
break ;
}
// END EPIC MOD
LC_LOG_USER ( " ---------- Finished (%.3fs) ---------- " , scope . ReadSeconds ( ) ) ;
// EPIC REMOVED: g_theApp.GetMainFrame()->ResetStatusBarText();
// EPIC REMOVED: g_theApp.GetMainFrame()->SetBusy(false);
}
unsigned int ServerCommandThread : : CompileThread ( void )
{
input : : Key keyControl ( VK_CONTROL ) ;
input : : Key keyAlt ( VK_MENU ) ;
input : : Key keyShift ( VK_SHIFT ) ;
input : : Key keyShortcut ( VK_F11 ) ;
Event compilationEvent ( primitiveNames : : CompilationEvent ( m_processGroupName ) . c_str ( ) , Event : : Type : : MANUAL_RESET ) ;
ChangeNotification changeNotification ;
if ( appSettings : : g_continuousCompilationEnabled - > GetValue ( ) )
{
changeNotification . Create ( appSettings : : g_continuousCompilationPath - > GetValue ( ) ) ;
}
for ( ; ; )
{
const int shortcutValue = appSettings : : g_compileShortcut - > GetValue ( ) ;
keyShortcut . AssignCode ( shortcut : : GetVirtualKeyCode ( shortcutValue ) ) ;
keyControl . Clear ( ) ;
keyAlt . Clear ( ) ;
keyShift . Clear ( ) ;
keyShortcut . Clear ( ) ;
keyControl . Update ( ) ;
keyAlt . Update ( ) ;
keyShift . Update ( ) ;
keyShortcut . Update ( ) ;
// BEGIN EPIC MOD - Adding SetActive command
if ( ! m_active )
{
keyShortcut . Clear ( ) ;
}
// END EPIC MOD
const bool control = shortcut : : ContainsControl ( shortcutValue ) ? keyControl . IsPressed ( ) : ! keyControl . IsPressed ( ) ;
const bool alt = shortcut : : ContainsAlt ( shortcutValue ) ? keyAlt . IsPressed ( ) : ! keyAlt . IsPressed ( ) ;
const bool shift = shortcut : : ContainsShift ( shortcutValue ) ? keyShift . IsPressed ( ) : ! keyShift . IsPressed ( ) ;
const bool isShortcutPressed = ( control & & alt & & shift & & keyShortcut . WentDown ( ) ) ;
// did anything change in the watched directory?
const unsigned int changeNotificationTimeout = static_cast < unsigned int > ( appSettings : : g_continuousCompilationTimeout - > GetValue ( ) ) ;
const bool foundAnyModification = changeNotification . CheckOnce ( ) ;
if ( foundAnyModification )
{
// clear the log if desired by the user
if ( appSettings : : g_clearLogOnRecompile - > GetValue ( ) )
{
GLiveCodingServer - > GetClearOutputDelegate ( ) . ExecuteIfBound ( ) ;
}
LC_SUCCESS_USER ( " Detected file modification, re-checking until timeout (%d ms) " , changeNotificationTimeout ) ;
changeNotification . CheckNext ( changeNotificationTimeout ) ;
}
if ( isShortcutPressed | | foundAnyModification | | m_manualRecompileTriggered )
{
// forbid command thread to handle commands through the pipe
m_handleCommandsEvent . Reset ( ) ;
// tell clients that we're about to compile.
// clients will send a command to say that they're ready. this command will let the command thread
// rest until we signal the event again.
compilationEvent . Signal ( ) ;
// remove inactive/disconnected processes
{
for ( auto processIt = m_liveProcesses . begin ( ) ; processIt ! = m_liveProcesses . end ( ) ; /* nothing */ )
{
LiveProcess * liveProcess = * processIt ;
process : : Handle processHandle = liveProcess - > GetProcessHandle ( ) ;
if ( ! process : : IsActive ( processHandle ) )
{
LC_WARNING_USER ( " Process %d is no longer valid, disconnecting " , liveProcess - > GetProcessId ( ) ) ;
process : : Close ( processHandle ) ;
// tell live modules to remove this process
const size_t moduleCount = m_liveModules . size ( ) ;
for ( size_t j = 0u ; j < moduleCount ; + + j )
{
LiveModule * liveModule = m_liveModules [ j ] ;
liveModule - > UnregisterProcess ( liveProcess ) ;
}
delete liveProcess ;
processIt = m_liveProcesses . erase ( processIt ) ;
}
else
{
// update process heart beats to know whether it made some progress
liveProcess - > ReadHeartBeatDelta ( m_processGroupName . c_str ( ) ) ;
+ + processIt ;
}
}
}
bool didAllProcessesMakeProgress = true ;
{
const size_t processCount = m_liveProcesses . size ( ) ;
for ( size_t i = 0u ; i < processCount ; + + i )
{
LiveProcess * liveProcess = m_liveProcesses [ i ] ;
didAllProcessesMakeProgress & = liveProcess - > MadeProgress ( ) ;
}
}
if ( ! didAllProcessesMakeProgress )
{
// install a code cave for all processes.
// this ensures that if a process is currently being held in the debugger, the process will
// not make progress in terms of new instructions being executed after continuing it in the debugger.
const size_t processCount = m_liveProcesses . size ( ) ;
for ( size_t i = 0u ; i < processCount ; + + i )
{
LiveProcess * liveProcess = m_liveProcesses [ i ] ;
liveProcess - > InstallCodeCave ( ) ;
}
// don't allow the exception handler dialog to be shown when continuing in the debugger with F5
m_exceptionCS . Enter ( ) ;
}
// wait until all command threads/clients are ready to go. we might not be getting commands
// from a client because it is being held in the debugger.
{
if ( didAllProcessesMakeProgress )
{
LC_SUCCESS_USER ( " Waiting for client(s) " ) ;
}
else
{
LC_SUCCESS_USER ( " Waiting for client(s), hit 'Continue' (F5) if being held in the debugger " ) ;
}
CriticalSection : : ScopedLock lock ( & m_connectionCS ) ;
const size_t count = m_commandThreads . size ( ) ;
for ( size_t i = 0u ; i < count ; + + i )
{
CommandThreadContext * threadContext = m_commandThreads [ i ] ;
threadContext - > readyEvent - > Wait ( ) ;
}
}
// do not let other processes register new modules during compilation
CriticalSection : : ScopedLock actionLock ( & m_actionCS ) ;
// setup the same virtual drive we had when loading the project
AddVirtualDrive ( ) ;
if ( isShortcutPressed | | m_manualRecompileTriggered )
{
// clear the log if desired by the user
if ( appSettings : : g_clearLogOnRecompile - > GetValue ( ) )
{
GLiveCodingServer - > GetClearOutputDelegate ( ) . ExecuteIfBound ( ) ;
}
if ( isShortcutPressed )
{
LC_SUCCESS_USER ( " Accepted live coding shortcut " ) ;
}
else if ( m_manualRecompileTriggered )
{
LC_SUCCESS_USER ( " Manual recompile triggered " ) ;
}
}
// bring Live++ to front on shortcut trigger
if ( appSettings : : g_receiveFocusOnRecompile - > GetValue ( ) = = appSettings : : FocusOnRecompile : : ON_SHORTCUT )
{
GLiveCodingServer - > GetBringToFrontDelegate ( ) . ExecuteIfBound ( ) ;
}
CompileChanges ( didAllProcessesMakeProgress ) ;
RemoveVirtualDrive ( ) ;
if ( ! didAllProcessesMakeProgress )
{
// remove all code caves
const size_t processCount = m_liveProcesses . size ( ) ;
for ( size_t i = 0u ; i < processCount ; + + i )
{
LiveProcess * liveProcess = m_liveProcesses [ i ] ;
liveProcess - > UninstallCodeCave ( ) ;
}
// remove the lock on the exception handler dialog
m_exceptionCS . Leave ( ) ;
}
compilationEvent . Reset ( ) ;
m_handleCommandsEvent . Signal ( ) ;
// clear change notifications that might have happened while compiling
changeNotification . Check ( 0u ) ;
// clear API recompiles
m_manualRecompileTriggered = false ;
m_liveModuleToModifiedOrNewObjFiles . clear ( ) ;
}
else
{
// nothing to do for now, go to sleep a bit
thread : : Sleep ( 10u ) ;
}
}
return 0u ;
}
unsigned int ServerCommandThread : : CommandThread ( DuplexPipeServer * pipe , Event * readyEvent )
{
// handle incoming commands
CommandMap commandMap ;
2019-05-28 10:22:20 -04:00
commandMap . RegisterAction < actions : : TriggerRecompile > ( ) ;
commandMap . RegisterAction < actions : : BuildPatch > ( ) ;
commandMap . RegisterAction < actions : : ReadyForCompilation > ( ) ;
commandMap . RegisterAction < actions : : DisconnectClient > ( ) ;
commandMap . RegisterAction < actions : : RegisterProcess > ( ) ;
commandMap . RegisterAction < actions : : EnableModules > ( ) ;
commandMap . RegisterAction < actions : : DisableModules > ( ) ;
commandMap . RegisterAction < actions : : ApplySettingBool > ( ) ;
commandMap . RegisterAction < actions : : ApplySettingInt > ( ) ;
commandMap . RegisterAction < actions : : ApplySettingString > ( ) ;
2019-03-27 11:33:31 -04:00
// BEGIN EPIC MOD - Adding ShowConsole command
2019-05-28 10:22:20 -04:00
commandMap . RegisterAction < actions : : ShowConsole > ( ) ;
2019-03-27 11:33:31 -04:00
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetVisible command
2019-05-28 10:22:20 -04:00
commandMap . RegisterAction < actions : : SetVisible > ( ) ;
2019-03-27 11:33:31 -04:00
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetActive command
2019-05-28 10:22:20 -04:00
commandMap . RegisterAction < actions : : SetActive > ( ) ;
2019-03-27 11:33:31 -04:00
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetBuildArguments command
2019-05-28 10:22:20 -04:00
commandMap . RegisterAction < actions : : SetBuildArguments > ( ) ;
2019-03-27 11:33:31 -04:00
// END EPIC MOD
// BEGIN EPIC MOD - Support for lazy-loading modules
2019-05-28 10:22:20 -04:00
commandMap . RegisterAction < actions : : EnableLazyLoadedModule > ( ) ;
2019-03-27 11:33:31 -04:00
// END EPIC MOD
for ( ; ; )
{
const bool success = commandMap . HandleCommands ( pipe , this ) ;
// we must have received a ReadyForCompilation command to get here, or the pipe is broken.
// in any case, let the main server thread responsible for compilation know that this client is ready.
// this is needed to always let the compilation thread advance, even when a client might have disconnected.
readyEvent - > Signal ( ) ;
if ( ( ! success ) | | ( ! pipe - > IsValid ( ) ) )
{
// pipe was closed or is broken, bail out.
// remove ourselves from the array of threads first.
RemoveCommandThread ( pipe ) ;
return 1u ;
}
// wait until we're allowed to handle commands again
m_handleCommandsEvent . Wait ( ) ;
// tell client that compilation has finished
2019-05-28 10:22:20 -04:00
pipe - > SendCommandAndWaitForAck ( commands : : CompilationFinished { } , nullptr , 0u ) ;
2019-03-27 11:33:31 -04:00
}
RemoveCommandThread ( pipe ) ;
return 0u ;
}
unsigned int ServerCommandThread : : ExceptionCommandThread ( DuplexPipeServer * exceptionPipe )
{
// handle incoming exception commands
CommandMap commandMap ;
2019-05-28 10:22:20 -04:00
commandMap . RegisterAction < actions : : HandleException > ( ) ;
2019-03-27 11:33:31 -04:00
for ( ; ; )
{
const bool success = commandMap . HandleCommands ( exceptionPipe , this ) ;
if ( ( ! success ) | | ( ! exceptionPipe - > IsValid ( ) ) )
{
// pipe was closed or is broken, bail out
return 1u ;
}
}
return 0u ;
}
void ServerCommandThread : : RemoveCommandThread ( const DuplexPipe * pipe )
{
CriticalSection : : ScopedLock lock ( & m_connectionCS ) ;
const size_t count = m_commandThreads . size ( ) ;
for ( size_t i = 0u ; i < count ; + + i )
{
CommandThreadContext * threadContext = m_commandThreads [ i ] ;
if ( & threadContext - > pipe = = pipe )
{
// don't bother cleaning up the context, just remove it
auto it = m_commandThreads . begin ( ) ;
std : : advance ( it , i ) ;
m_commandThreads . erase ( it ) ;
return ;
}
}
}
LiveProcess * ServerCommandThread : : FindProcessById ( unsigned int processId )
{
const size_t count = m_liveProcesses . size ( ) ;
for ( size_t i = 0u ; i < count ; + + i )
{
LiveProcess * process = m_liveProcesses [ i ] ;
if ( process - > GetProcessId ( ) = = processId )
{
return process ;
}
}
return nullptr ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : TriggerRecompile : : Execute ( const CommandType * , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against accepting this command while compilation is already in progress
CriticalSection : : ScopedLock lock ( & commandThread - > m_actionCS ) ;
pipe - > SendAck ( ) ;
commandThread - > m_manualRecompileTriggered = true ;
return true ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : BuildPatch : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * payload , size_t payloadSize )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
pipe - > SendAck ( ) ;
2019-03-27 11:33:31 -04:00
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against accepting this command while compilation is already in progress
CriticalSection : : ScopedLock lock ( & commandThread - > m_actionCS ) ;
2019-05-28 10:22:20 -04:00
memoryStream : : Reader payloadStream ( payload , payloadSize ) ;
for ( unsigned int i = 0u ; i < command - > fileCount ; + + i )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
const commands : : BuildPatch : : PatchData patchData = payloadStream . Read < commands : : BuildPatch : : PatchData > ( ) ;
const LiveModule : : ModifiedObjFile modifiedObjFile = { patchData . objPath , patchData . amalgamatedObjPath } ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
commandThread - > m_liveModuleToModifiedOrNewObjFiles [ patchData . moduleName ] . push_back ( modifiedObjFile ) ;
2019-03-27 11:33:31 -04:00
}
commandThread - > m_manualRecompileTriggered = true ;
return true ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : HandleException : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
// BEGIN EPIC MOD - Using internal CrashReporter
#if 0
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against several processes showing a dialog at the same time.
// protect against showing the exception handler dialog while compilation is already in progress.
CriticalSection : : ScopedLock lock ( & commandThread - > m_exceptionCS ) ;
LiveProcess * liveProcess = commandThread - > FindProcessById ( command - > processId ) ;
if ( ! liveProcess )
{
// signal client we did not handle the exception
2019-05-28 10:22:20 -04:00
pipe - > SendCommandAndWaitForAck ( commands : : HandleExceptionFinished { nullptr , nullptr , nullptr , false } , nullptr , 0u ) ;
2019-03-27 11:33:31 -04:00
return true ;
}
// let the compile thread know that we're currently handling an exception.
// this is needed to ensure that no hooks or synchronization points are called during compilation.
commandThread - > m_inExceptionHandlerEvent . Signal ( ) ;
// hold all processes in place
const size_t processCount = commandThread - > m_liveProcesses . size ( ) ;
for ( size_t i = 0u ; i < processCount ; + + i )
{
commandThread - > m_liveProcesses [ i ] - > InstallCodeCave ( ) ;
}
ExceptionHandlerDialog dialog ( commandThread - > m_processGroupName , liveProcess , command - > threadId , command - > exception , command - > context , command - > clientContextPtr ) ;
const INT_PTR result = dialog . DoModal ( ) ;
// release processes from the cave
for ( size_t i = 0u ; i < processCount ; + + i )
{
commandThread - > m_liveProcesses [ i ] - > UninstallCodeCave ( ) ;
}
// remove our signal saying that we're handling an exception
commandThread - > m_inExceptionHandlerEvent . Reset ( ) ;
if ( result = = IDC_EXCEPTION_HANDLER_LEAVE )
{
// tell the client that it needs to unwind its stack and continue at the return address
const ExceptionHandlerDialog : : ParentFrameData & frameData = dialog . GetParentFrameData ( ) ;
2019-05-28 10:22:20 -04:00
pipe - > SendCommandAndWaitForAck ( commands : : HandleExceptionFinished { frameData . returnAddress , frameData . framePointer , frameData . stackPointer , true } , nullptr , 0u ) ;
2019-03-27 11:33:31 -04:00
return true ;
}
else if ( result = = IDC_EXCEPTION_HANDLER_IGNORE )
{
// tell the client that we ignored the exception
2019-05-28 10:22:20 -04:00
pipe - > SendCommandAndWaitForAck ( commands : : HandleExceptionFinished { nullptr , nullptr , nullptr , false } , nullptr , 0u ) ;
2019-03-27 11:33:31 -04:00
return true ;
}
// signal client that we handled the exception and there's nothing left to do
2019-05-28 10:22:20 -04:00
pipe - > SendCommandAndWaitForAck ( commands : : HandleExceptionFinished { nullptr , nullptr , nullptr , true } , nullptr , 0u ) ;
2019-03-27 11:33:31 -04:00
# endif
// END EPIC MOD
return true ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : ReadyForCompilation : : Execute ( const CommandType * , const DuplexPipe * pipe , void * , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
// don't continue execution
return false ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : DisconnectClient : : Execute ( const CommandType * , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
ServerCommandThread * instance = static_cast < ServerCommandThread * > ( context ) ;
// unregister this connection
{
instance - > RemoveCommandThread ( pipe ) ;
CriticalSection : : ScopedLock lock ( & instance - > m_connectionCS ) ;
if ( instance - > m_commandThreads . size ( ) = = 0u )
{
// BEGIN EPIC MOD - No built-in UI
// // this was the last client to disconnect, remove the system tray
// g_theApp.GetMainFrame()->GetSystemTray()->Destroy();
// END EPIC MOD
}
}
pipe - > SendAck ( ) ;
return true ;
}
// BEGIN EPIC MOD - Adding ShowConsole command
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : ShowConsole : : Execute ( const CommandType * , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
GLiveCodingServer - > GetShowConsoleDelegate ( ) . ExecuteIfBound ( ) ;
return true ;
}
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetVisible command
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : SetVisible : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
GLiveCodingServer - > GetSetVisibleDelegate ( ) . ExecuteIfBound ( command - > visible ) ;
return true ;
}
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetActive command
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : SetActive : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against accepting this command while compilation is already in progress
CriticalSection : : ScopedLock lock ( & commandThread - > m_actionCS ) ;
pipe - > SendAck ( ) ;
commandThread - > m_active = command - > active ;
return true ;
}
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetBuildArguments command
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : SetBuildArguments : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against accepting this command while compilation is already in progress
CriticalSection : : ScopedLock lock ( & commandThread - > m_actionCS ) ;
for ( LiveProcess * process : commandThread - > m_liveProcesses )
{
if ( process - > GetProcessId ( ) = = command - > processId )
{
process - > SetBuildArguments ( command - > arguments ) ;
}
}
pipe - > SendAck ( ) ;
return true ;
}
// END EPIC MOD
// BEGIN EPIC MOD - Support for lazy-loading modules
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : EnableLazyLoadedModule : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against accepting this command while compilation is already in progress
CriticalSection : : ScopedLock lock ( & commandThread - > m_actionCS ) ;
for ( LiveProcess * process : commandThread - > m_liveProcesses )
{
if ( process - > GetProcessId ( ) = = command - > processId )
{
const std : : wstring modulePath = file : : NormalizePath ( command - > fileName ) ;
process - > AddLazyLoadedModule ( modulePath , command - > moduleBase ) ;
LC_LOG_DEV ( " Registered module %S for lazy-loading " , modulePath . c_str ( ) ) ;
}
}
pipe - > SendAck ( ) ;
return true ;
}
// END EPIC MOD
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : RegisterProcess : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against several client DLLs calling into this action at the same time
CriticalSection : : ScopedLock lock ( & commandThread - > m_actionCS ) ;
process : : Handle processHandle = process : : Open ( command - > processId ) ;
// check if any live module in this process group has patches installed already
{
const std : : wstring & processPath = process : : GetImagePath ( processHandle ) ;
bool registeredSuccessfully = true ;
if ( ! appSettings : : g_installCompiledPatchesMultiProcess - > GetValue ( ) )
{
// we are not allowed to install any compiled patches when a new executable is spawned
bool processGroupHasPatches = false ;
const size_t count = commandThread - > m_liveModules . size ( ) ;
for ( size_t i = 0u ; i < count ; + + i )
{
LiveModule * liveModule = commandThread - > m_liveModules [ i ] ;
if ( liveModule - > HasInstalledPatches ( ) )
{
std : : wstring caption ( L " Live coding - Registering process " ) ;
caption + = file : : GetFilename ( processPath ) ;
processGroupHasPatches = true ;
// BEGIN EPIC MOD - Using non-modal error dialog
GLiveCodingServer - > GetLogOutputDelegate ( ) . ExecuteIfBound ( ELiveCodingLogVerbosity : : Failure , L " This process cannot be added to the existing process group, because at least one module already has installed patches. Live coding is disabled for this process. " ) ;
// END EPIC MD
break ;
}
}
registeredSuccessfully = ! processGroupHasPatches ;
}
if ( registeredSuccessfully )
{
2019-05-28 10:22:20 -04:00
LiveProcess * liveProcess = new LiveProcess ( processHandle , command - > processId , command - > threadId , command - > jumpToSelf , pipe ) ;
2019-03-27 11:33:31 -04:00
commandThread - > m_liveProcesses . push_back ( liveProcess ) ;
// BEGIN EPIC MOD - No built-in UI
// commandThread->m_mainFrame->UpdateWindowTitle();
// END EPIC MOD
LC_SUCCESS_USER ( " Registered process %S (PID: %d) " , processPath . c_str ( ) , command - > processId ) ;
}
// tell client we are finished
2019-05-28 10:22:20 -04:00
pipe - > SendCommandAndWaitForAck ( commands : : RegisterProcessFinished { registeredSuccessfully } , nullptr , 0u ) ;
2019-03-27 11:33:31 -04:00
}
return true ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : EnableModules : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * payload , size_t payloadSize )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
pipe - > SendAck ( ) ;
// EPIC REMOVED: g_theApp.GetMainFrame()->ChangeStatusBarText(L"Loading modules...");
2019-03-27 11:33:31 -04:00
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against several client DLLs calling into this action at the same time.
// this ensures that all modules are loaded serialized per process.
2019-05-28 10:22:20 -04:00
CriticalSection : : ScopedLock lock ( & commandThread - > m_actionCS ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
telemetry : : Scope moduleLoadingScope ( " Module loading " ) ;
2019-03-27 11:33:31 -04:00
// set up virtual drives before loading anything, otherwise files won't be detected and therefore discarded
2019-05-28 10:22:20 -04:00
AddVirtualDrive ( ) ;
scheduler : : TaskBase * rootTask = scheduler : : CreateEmptyTask ( ) ;
types : : vector < scheduler : : Task < LiveModule * > * > loadModuleTasks ;
const unsigned int moduleCount = command - > moduleCount ;
loadModuleTasks . reserve ( moduleCount ) ;
memoryStream : : Reader payloadStream ( payload , payloadSize ) ;
for ( unsigned int i = 0u ; i < moduleCount ; + + i )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
const commands : : ModuleData moduleData = payloadStream . Read < commands : : ModuleData > ( ) ;
scheduler : : Task < LiveModule * > * task = commandThread - > LoadModule ( command - > processId , moduleData . base , moduleData . path , rootTask ) ;
// the module could have failed to load
if ( task )
{
loadModuleTasks . push_back ( task ) ;
}
2019-03-27 11:33:31 -04:00
}
2019-05-28 10:22:20 -04:00
// wait for all tasks to finish
scheduler : : RunTask ( rootTask ) ;
scheduler : : WaitForTask ( rootTask ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
const size_t loadModuleTaskCount = loadModuleTasks . size ( ) ;
commandThread - > m_liveModules . reserve ( loadModuleTaskCount ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
size_t loadedTranslationUnits = 0u ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
// update all live modules loaded by the tasks
for ( size_t i = 0u ; i < loadModuleTaskCount ; + + i )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
scheduler : : Task < LiveModule * > * task = loadModuleTasks [ i ] ;
LiveModule * liveModule = task - > GetResult ( ) ;
commandThread - > m_liveModules . push_back ( liveModule ) ;
// update directory cache for this live module
liveModule - > UpdateDirectoryCache ( commandThread - > m_directoryCache ) ;
// update the number of loaded translation units
loadedTranslationUnits + = liveModule - > GetCompilandDatabase ( ) - > compilands . size ( ) ;
}
scheduler : : DestroyTasks ( loadModuleTasks ) ;
scheduler : : DestroyTask ( rootTask ) ;
// dump memory statistics
{
LC_LOG_INDENT_TELEMETRY ;
g_symbolAllocator . PrintStats ( ) ;
g_immutableStringAllocator . PrintStats ( ) ;
g_contributionAllocator . PrintStats ( ) ;
g_compilandAllocator . PrintStats ( ) ;
g_dependencyAllocator . PrintStats ( ) ;
}
if ( loadModuleTaskCount > 0u )
{
LC_SUCCESS_USER ( " Loaded %zu module(s) (%.3fs, %zu translation units) " , loadModuleTaskCount , moduleLoadingScope . ReadSeconds ( ) , loadedTranslationUnits ) ;
}
// EPIC REMOVED commandThread->PrewarmCompilerEnvironmentCache();
// tell user we are ready, but only once to not clutter the log
{
static bool showedOnce = false ;
if ( ! showedOnce )
{
showedOnce = true ;
const int shortcut = appSettings : : g_compileShortcut - > GetValue ( ) ;
const std : : wstring & shortcutText = shortcut : : ConvertShortcutToText ( shortcut ) ;
LC_SUCCESS_USER ( " Live coding ready - Save changes and press %S to re-compile code " , shortcutText . c_str ( ) ) ;
}
2019-03-27 11:33:31 -04:00
}
// remove virtual drives once we're finished
2019-05-28 10:22:20 -04:00
RemoveVirtualDrive ( ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
// tell server we are finished
pipe - > SendCommandAndWaitForAck ( commands : : EnableModulesFinished { command - > token } , nullptr , 0u ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
// EPIC REMOVED: g_theApp.GetMainFrame()->ResetStatusBarText();
2019-03-27 11:33:31 -04:00
return true ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : DisableModules : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * context , const void * payload , size_t payloadSize )
2019-03-27 11:33:31 -04:00
{
2019-05-28 10:22:20 -04:00
pipe - > SendAck ( ) ;
// EPIC REMOVED: g_theApp.GetMainFrame()->ChangeStatusBarText(L"Unloading modules...");
2019-03-27 11:33:31 -04:00
ServerCommandThread * commandThread = static_cast < ServerCommandThread * > ( context ) ;
// protect against several client DLLs calling into this action at the same time.
2019-05-28 10:22:20 -04:00
// this ensures that all modules are loaded serialized per process.
CriticalSection : : ScopedLock lock ( & commandThread - > m_actionCS ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
telemetry : : Scope moduleUnloadingScope ( " Module unloading " ) ;
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
unsigned int unloadedModules = 0u ;
const unsigned int moduleCount = command - > moduleCount ;
memoryStream : : Reader payloadStream ( payload , payloadSize ) ;
for ( unsigned int i = 0u ; i < moduleCount ; + + i )
{
const commands : : ModuleData moduleData = payloadStream . Read < commands : : ModuleData > ( ) ;
const bool success = commandThread - > UnloadModule ( command - > processId , moduleData . path ) ;
if ( success )
{
+ + unloadedModules ;
}
}
2019-03-27 11:33:31 -04:00
2019-05-28 10:22:20 -04:00
if ( unloadedModules > 0u )
{
LC_SUCCESS_USER ( " Unloaded %u module(s) (%.3fs) " , unloadedModules , moduleUnloadingScope . ReadSeconds ( ) ) ;
}
// tell server we are finished
pipe - > SendCommandAndWaitForAck ( commands : : DisableModulesFinished { command - > token } , nullptr , 0u ) ;
2019-03-27 11:33:31 -04:00
// EPIC REMOVED: g_theApp.GetMainFrame()->ResetStatusBarText();
return true ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : ApplySettingBool : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
appSettings : : ApplySettingBool ( command - > settingName , ( command - > settingValue = = 0 ) ? false : true ) ;
return true ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : ApplySettingInt : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
appSettings : : ApplySettingInt ( command - > settingName , command - > settingValue ) ;
return true ;
}
2019-05-28 10:22:20 -04:00
bool ServerCommandThread : : actions : : ApplySettingString : : Execute ( const CommandType * command , const DuplexPipe * pipe , void * , const void * , size_t )
2019-03-27 11:33:31 -04:00
{
pipe - > SendAck ( ) ;
appSettings : : ApplySettingString ( command - > settingName , command - > settingValue ) ;
return true ;
}
// BEGIN EPIC MODS
# pragma warning(pop)
// END EPIC MODS