2347 lines
77 KiB
C++
2347 lines
77 KiB
C++
// Copyright 2011-2020 Molecular Matters GmbH, all rights reserved.
|
|
|
|
#if LC_VERSION == 1
|
|
|
|
// BEGIN EPIC MOD
|
|
//#include PCH_INCLUDE
|
|
// END EPIC MOD
|
|
#include "LC_ServerCommandThread.h"
|
|
#include "LC_Commands.h"
|
|
// BEGIN EPIC MOD
|
|
//#include "LC_MainFrame.h"
|
|
// END EPIC MOD
|
|
#include "LC_Telemetry.h"
|
|
#include "LC_Symbols.h"
|
|
#include "LC_Filesystem.h"
|
|
#include "LC_Process.h"
|
|
#include "LC_Compiler.h"
|
|
#include "LC_StringUtil.h"
|
|
#include "LC_CommandMap.h"
|
|
#include "LC_FileAttributeCache.h"
|
|
// BEGIN EPIC MOD
|
|
//#include "LC_App.h"
|
|
// END EPIC MOD
|
|
#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"
|
|
// BEGIN EPIC MOD
|
|
//#include "LC_ExceptionHandlerDialog.h"
|
|
//#include "LC_Resource.h"
|
|
// END EPIC MOD
|
|
#include "LC_PrimitiveNames.h"
|
|
#include "LC_MemoryStream.h"
|
|
#include "LC_NamedSharedMemory.h"
|
|
#include "LC_VisualStudioAutomation.h"
|
|
#include "LC_Thread.h"
|
|
#include <mmsystem.h>
|
|
|
|
// BEGIN EPIC MOD
|
|
#include "LC_AppSettings.h"
|
|
#include "LC_Allocators.h"
|
|
#include "LC_DuplexPipeClient.h"
|
|
#include "LiveCodingServer.h"
|
|
#include "Containers/UnrealString.h"
|
|
// END EPIC MOD
|
|
|
|
// 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");
|
|
|
|
|
|
|
|
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, Filesystem::OpenMode::READ);
|
|
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()
|
|
// BEGIN EPIC MOD
|
|
, m_liveModuleToAdditionalLibraries()
|
|
// END EPIC MOD
|
|
, m_restartCS()
|
|
, m_restartJob(nullptr)
|
|
, m_restartedProcessCount(0u)
|
|
// BEGIN EPIC MOD
|
|
#if WITH_VISUALSTUDIO_DTE
|
|
// END EPIC MOD
|
|
, m_restartedProcessIdToDebugger()
|
|
// BEGIN EPIC MOD
|
|
#endif
|
|
// END EPIC MOD
|
|
{
|
|
// BEGIN EPIC MOD
|
|
#if WITH_VISUALSTUDIO_DTE
|
|
// END EPIC MOD
|
|
visualStudio::Startup();
|
|
// BEGIN EPIC MOD
|
|
#endif
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD
|
|
m_serverThread = Thread::CreateFromMemberFunction("Live coding server", 64u * 1024u, this, &ServerCommandThread::ServerThread);
|
|
m_compileThread = Thread::CreateFromMemberFunction("Live coding compilation", 64u * 1024u, this, &ServerCommandThread::CompileThread);
|
|
// END EPIC MOD
|
|
|
|
m_liveModules.reserve(256u);
|
|
m_liveProcesses.reserve(8u);
|
|
m_imageHeaderToLiveModule.reserve(256u);
|
|
|
|
m_commandThreads.reserve(8u);
|
|
}
|
|
|
|
|
|
ServerCommandThread::~ServerCommandThread(void)
|
|
{
|
|
// note that we deliberately do almost *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.
|
|
delete m_directoryCache;
|
|
|
|
// BEGIN EPIC MOD
|
|
#if WITH_VISUALSTUDIO_DTE
|
|
// END EPIC MOD
|
|
visualStudio::Shutdown();
|
|
// BEGIN EPIC MOD
|
|
#endif
|
|
// END EPIC MOD
|
|
}
|
|
|
|
|
|
void ServerCommandThread::RestartTargets(void)
|
|
{
|
|
// protect against concurrent compilation
|
|
m_restartCS.Enter();
|
|
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->SetBusy(true);
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->ChangeStatusBarText(L"Restarting target applications...");
|
|
|
|
LC_LOG_USER("---------- Restarting target applications ----------");
|
|
|
|
// prevent current Live++ instance from shutting down by associating it with a new job object to keep it alive
|
|
if (!m_restartJob)
|
|
{
|
|
m_restartJob = ::CreateJobObjectW(NULL, primitiveNames::JobGroup(m_processGroupName).c_str());
|
|
::AssignProcessToJobObject(m_restartJob, ::GetCurrentProcess());
|
|
}
|
|
|
|
// protect against m_liveProcesses being accessed when processes restart and register themselves with this Live++ instance
|
|
CriticalSection::ScopedLock lock(&m_actionCS);
|
|
|
|
// remove processes that were successfully restarted last time
|
|
for (auto processIt = m_liveProcesses.begin(); processIt != m_liveProcesses.end(); /* nothing */)
|
|
{
|
|
LiveProcess* liveProcess = *processIt;
|
|
if (liveProcess->WasSuccessfulRestart())
|
|
{
|
|
Process::Handle processHandle = liveProcess->GetProcessHandle();
|
|
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);
|
|
}
|
|
|
|
// BEGIN EPIC MOD
|
|
if (liveProcess->IsReinstancingFlowEnabled())
|
|
{
|
|
--m_reinstancingProcessCount;
|
|
}
|
|
|
|
if (liveProcess->IsDisableCompileFinishNotification())
|
|
{
|
|
--m_disableCompileFinishNotificationProcessCount;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
delete liveProcess;
|
|
|
|
processIt = m_liveProcesses.erase(processIt);
|
|
}
|
|
else
|
|
{
|
|
++processIt;
|
|
}
|
|
}
|
|
|
|
// try preparing all processes for a restart
|
|
const size_t count = m_liveProcesses.size();
|
|
for (size_t i = 0u; i < count; ++i)
|
|
{
|
|
LiveProcess* liveProcess = m_liveProcesses[i];
|
|
const bool success = liveProcess->PrepareForRestart();
|
|
if (success)
|
|
{
|
|
++m_restartedProcessCount;
|
|
|
|
// BEGIN EPIC MOD
|
|
#if WITH_VISUALSTUDIO_DTE
|
|
// END EPIC MOD
|
|
// check if a VS debugger is currently attached to the process about to restart
|
|
const Process::Id processId = liveProcess->GetProcessId();
|
|
EnvDTE::DebuggerPtr debugger = visualStudio::FindDebuggerAttachedToProcess(processId);
|
|
if (debugger)
|
|
{
|
|
m_restartedProcessIdToDebugger.emplace(+processId, debugger);
|
|
}
|
|
// BEGIN EPIC MOD
|
|
#endif
|
|
// END EPIC MOD
|
|
}
|
|
}
|
|
|
|
// exit all successfully prepared processes
|
|
for (size_t i = 0u; i < count; ++i)
|
|
{
|
|
LiveProcess* liveProcess = m_liveProcesses[i];
|
|
liveProcess->WaitForExitBeforeRestart();
|
|
}
|
|
|
|
// restart all successfully prepared processes
|
|
for (size_t i = 0u; i < count; ++i)
|
|
{
|
|
LiveProcess* liveProcess = m_liveProcesses[i];
|
|
// BEGIN EPIC MOD
|
|
liveProcess->Restart(m_restartJob);
|
|
// END EPIC MOD
|
|
}
|
|
|
|
// BEGIN EPIC MOD - Prevent orphaned console instances if processes fail to restart. Job object will be duplicated into child process.
|
|
if (m_restartJob != nullptr)
|
|
{
|
|
CloseHandle(m_restartJob);
|
|
m_restartJob = nullptr;
|
|
}
|
|
// END EPIC MOD
|
|
}
|
|
|
|
|
|
void ServerCommandThread::CompileChanges(void)
|
|
{
|
|
// protect against accepting this command while compilation is already in progress
|
|
CriticalSection::ScopedLock lock(&m_actionCS);
|
|
m_manualRecompileTriggered = true;
|
|
}
|
|
|
|
|
|
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()).GetString();
|
|
}
|
|
|
|
|
|
scheduler::Task<LiveModule*>* ServerCommandThread::LoadModule(Process::Id processId, void* moduleBase, const wchar_t* givenModulePath, scheduler::TaskBase* taskRoot)
|
|
{
|
|
// 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
|
|
const std::wstring& modulePath = Filesystem::NormalizePath(givenModulePath).GetString();
|
|
const executable::Header imageHeader = GetImageHeader(modulePath.c_str());
|
|
if (!executable::IsValidHeader(imageHeader))
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
LiveProcess* liveProcess = FindProcessById(processId);
|
|
LC_ASSERT(liveProcess, "Invalid process ID.");
|
|
|
|
if (liveProcess->TriedToLoadImage(imageHeader))
|
|
{
|
|
// tried loading this module into this process already
|
|
return nullptr;
|
|
}
|
|
|
|
// find any other process ID that tried to load this module already
|
|
{
|
|
const size_t count = m_liveProcesses.size();
|
|
for (size_t i = 0u; i < count; ++i)
|
|
{
|
|
LiveProcess* otherLiveProcess = m_liveProcesses[i];
|
|
if (otherLiveProcess->TriedToLoadImage(imageHeader))
|
|
{
|
|
// some *other* process loaded this module already
|
|
LC_LOG_USER("Registering module %S (PID: %d)", modulePath.c_str(), +processId);
|
|
|
|
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;
|
|
}
|
|
}
|
|
}
|
|
|
|
symbols::Provider* moduleProvider = symbols::OpenEXE(modulePath.c_str(), symbols::OpenOptions::ACCUMULATE_SIZE);
|
|
if (!moduleProvider)
|
|
{
|
|
return nullptr;
|
|
}
|
|
|
|
liveProcess->AddLoadedImage(imageHeader);
|
|
|
|
// accumulate module info
|
|
{
|
|
const Filesystem::PathAttributes attributes = Filesystem::GetAttributes(modulePath.c_str());
|
|
const uint64_t size = Filesystem::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;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::UnloadModule(Process::Id processId, const wchar_t* givenModulePath)
|
|
{
|
|
// 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
|
|
const std::wstring& modulePath = Filesystem::NormalizePath(givenModulePath).GetString();
|
|
const executable::Header imageHeader = GetImageHeader(modulePath.c_str());
|
|
if (!executable::IsValidHeader(imageHeader))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
LiveProcess* liveProcess = FindProcessById(processId);
|
|
LC_ASSERT(liveProcess, "Invalid process ID.");
|
|
|
|
if (!liveProcess->TriedToLoadImage(imageHeader))
|
|
{
|
|
// this module was never loaded
|
|
return false;
|
|
}
|
|
|
|
LC_LOG_USER("Unloading module %S", modulePath.c_str());
|
|
|
|
liveProcess->RemoveLoadedImage(imageHeader);
|
|
m_imageHeaderToLiveModule.erase(imageHeader);
|
|
|
|
for (auto it = m_liveModules.begin(); it != m_liveModules.end(); /* nothing */)
|
|
{
|
|
LiveModule* liveModule = *it;
|
|
if (std::equal_to<executable::Header>()(liveModule->GetImageHeader(), imageHeader))
|
|
{
|
|
liveModule->Unload();
|
|
delete liveModule;
|
|
|
|
it = m_liveModules.erase(it);
|
|
|
|
return true;
|
|
}
|
|
else
|
|
{
|
|
++it;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
if (uniquePaths.size() != 0u)
|
|
{
|
|
LC_SUCCESS_USER("Prewarmed compiler/linker environment cache (%.3fs, %zu executables)", scope.ReadSeconds(), uniquePaths.size());
|
|
}
|
|
}
|
|
|
|
|
|
Thread::ReturnValue ServerCommandThread::ServerThread(void)
|
|
{
|
|
// keep named shared memory alive so that restarted processes don't try spawning new Live++ instances
|
|
Process::NamedSharedMemory* sharedMemory = Process::CreateNamedSharedMemory(primitiveNames::StartupNamedSharedMemory(m_processGroupName).c_str(), 4096u);
|
|
Process::WriteNamedSharedMemory(sharedMemory, ::GetCurrentProcessId());
|
|
|
|
// 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
|
|
// BEGIN EPIC MOD
|
|
context->commandThread = Thread::CreateFromMemberFunction("Live coding client command communication", 64u * 1024u, this, &ServerCommandThread::CommandThread, &context->pipe, context->readyEvent);
|
|
context->exceptionCommandThread = Thread::CreateFromMemberFunction("Live coding client exception command communication", 64u * 1024u, this, &ServerCommandThread::ExceptionCommandThread, &context->exceptionPipe);
|
|
// END EPIC MOD
|
|
|
|
// register this connection
|
|
{
|
|
CriticalSection::ScopedLock lock(&m_connectionCS);
|
|
m_commandThreads.push_back(context);
|
|
}
|
|
}
|
|
|
|
Process::DestroyNamedSharedMemory(sharedMemory);
|
|
|
|
return Thread::ReturnValue(0u);
|
|
}
|
|
|
|
// BEGIN EPIC MOD
|
|
bool ServerCommandThread::HasReinstancingProcess()
|
|
{
|
|
return m_reinstancingProcessCount.load() != 0;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD
|
|
bool ServerCommandThread::ShowCompileFinishNotification()
|
|
{
|
|
return m_disableCompileFinishNotificationProcessCount.load() == 0;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// 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
|
|
bool ServerCommandThread::actions::FinishedLazyLoadingModules::Execute(const CommandType* command, const DuplexPipe* pipe, void*, const void*, size_t)
|
|
{
|
|
pipe->SendAck();
|
|
return false;
|
|
}
|
|
|
|
struct ClientProxyThread
|
|
{
|
|
struct ProxyEnableModulesFinishedAction
|
|
{
|
|
typedef commands::EnableModulesFinished CommandType;
|
|
|
|
static bool Execute(CommandType* command, const DuplexPipe* pipe, void*, const void*, size_t)
|
|
{
|
|
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::Current::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()
|
|
{
|
|
std::vector<commands::ModuleData> modules;
|
|
modules.resize(m_enableModules.size());
|
|
|
|
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());
|
|
}
|
|
|
|
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);
|
|
}
|
|
};
|
|
|
|
bool ServerCommandThread::EnableRequiredModules(const TArray<FString>& RequiredModules)
|
|
{
|
|
bool bEnabledModule = false;
|
|
for (LiveProcess* liveProcess : m_liveProcesses)
|
|
{
|
|
types::vector<std::wstring> LoadModuleFileNames;
|
|
for (const FString& RequiredModule : RequiredModules)
|
|
{
|
|
std::wstring ModuleFileName = Filesystem::NormalizePath(*RequiredModule).GetString();
|
|
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;
|
|
commandMap.RegisterAction<actions::EnableModules>();
|
|
commandMap.RegisterAction<actions::FinishedLazyLoadingModules>();
|
|
commandMap.HandleCommands(&ServerPipe, this);
|
|
|
|
for (const std::wstring& loadModuleFileName : LoadModuleFileNames)
|
|
{
|
|
liveProcess->SetLazyLoadedModuleAsLoaded(loadModuleFileName);
|
|
}
|
|
|
|
bEnabledModule = true;
|
|
}
|
|
}
|
|
return bEnabledModule;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD
|
|
void ServerCommandThread::CompileChanges(bool didAllProcessesMakeProgress, commands::PostCompileResult& postCompileResult)
|
|
// END EPIC MOD
|
|
{
|
|
// 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.AddUnique(liveProcess->GetBuildArguments());
|
|
}
|
|
|
|
GLiveCodingServer->GetStatusChangeDelegate().ExecuteIfBound(L"Compiling changes for live coding...");
|
|
|
|
// Keep retrying the compile until we've added all the required modules
|
|
FModuleToModuleFiles ModuleToModuleFiles;
|
|
ELiveCodingCompileReason CompileReason = ELiveCodingCompileReason::Initial;
|
|
for (;;)
|
|
{
|
|
// Build a list of modules which are enabled for live coding
|
|
TArray<FString> ValidModules;
|
|
for (LiveModule* liveModule : m_liveModules)
|
|
{
|
|
ValidModules.Add(liveModule->GetModuleName().c_str());
|
|
}
|
|
|
|
// Build a list of loaded modules which are not enabled
|
|
TSet<FString> LazyLoadModules;
|
|
for (LiveProcess* liveProcess : m_liveProcesses)
|
|
{
|
|
const std::unordered_map<std::wstring, LiveProcess::LazyLoadedModule>& lazyLoadedModules = liveProcess->GetLazyLoadedModules();
|
|
for (const std::unordered_map<std::wstring, LiveProcess::LazyLoadedModule>::value_type& kvp : lazyLoadedModules)
|
|
{
|
|
if (!kvp.second.m_loaded)
|
|
{
|
|
LazyLoadModules.Add(FString(kvp.first.c_str()));
|
|
}
|
|
}
|
|
}
|
|
|
|
// Execute the compile
|
|
TArray<FString> RequiredModules;
|
|
ELiveCodingCompileResult CompileResult = CompileDelegate.Execute(Targets, ValidModules, LazyLoadModules, RequiredModules, ModuleToModuleFiles, CompileReason);
|
|
if (CompileResult == ELiveCodingCompileResult::Success)
|
|
{
|
|
break;
|
|
}
|
|
else if (CompileResult == ELiveCodingCompileResult::Canceled)
|
|
{
|
|
postCompileResult = commands::PostCompileResult::Cancelled;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Error, L"Compilation canceled.");
|
|
return;
|
|
}
|
|
else if (CompileResult == ELiveCodingCompileResult::Failure)
|
|
{
|
|
postCompileResult = commands::PostCompileResult::Failure;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Error, L"Compilation error.");
|
|
return;
|
|
}
|
|
|
|
// Enable any lazy-loaded modules that we need
|
|
if (!RequiredModules.IsEmpty() && !EnableRequiredModules(RequiredModules))
|
|
{
|
|
postCompileResult = commands::PostCompileResult::Failure;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Error, L"Compilation error.");
|
|
return;
|
|
}
|
|
|
|
CompileReason = ELiveCodingCompileReason::Retry;
|
|
}
|
|
|
|
// Reset the unity file cache
|
|
symbols::ResetCachedUnityManifests();
|
|
|
|
// Build up a list of all the modified object files in each module
|
|
types::unordered_map<std::wstring, const LiveModule*> EnabledModulesByName;
|
|
for (const LiveModule* liveModule : m_liveModules)
|
|
{
|
|
EnabledModulesByName[liveModule->GetModuleName()] = liveModule;
|
|
}
|
|
|
|
for(const TPair<FString, FModuleFiles>& Pair : ModuleToModuleFiles)
|
|
{
|
|
const LiveModule* liveModule = nullptr;
|
|
std::wstring ModuleFileName = Filesystem::Devirtualize(Filesystem::NormalizePath(*Pair.Key).GetString());
|
|
types::unordered_map<std::wstring, const LiveModule*>::iterator ix = EnabledModulesByName.find(ModuleFileName);
|
|
if (ix == EnabledModulesByName.end())
|
|
{
|
|
// 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 = Filesystem::GetFilename(ModuleFileName.c_str()).GetString();
|
|
|
|
for (const LiveModule* testLiveModule : m_liveModules)
|
|
{
|
|
if (ModuleFileNameOnly == Filesystem::GetFilename(testLiveModule->GetModuleName().c_str()).GetString())
|
|
{
|
|
ModuleFileName = testLiveModule->GetModuleName();
|
|
liveModule = testLiveModule;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (liveModule == nullptr)
|
|
{
|
|
LC_WARNING_USER("The module '%S' has not been loaded by any process. Changes will be ignored.", ModuleFileName.c_str());
|
|
continue;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
liveModule = ix->second;
|
|
}
|
|
|
|
types::vector<symbols::ModifiedObjFile> ObjectFiles;
|
|
for (const FString& ObjectFile : Pair.Value.Objects)
|
|
{
|
|
std::wstring NormalizedObjectFile = Filesystem::NormalizePath(*ObjectFile).GetString();
|
|
|
|
if (!liveModule->IsModifiedSource(Filesystem::Devirtualize(NormalizedObjectFile).c_str()))
|
|
{
|
|
continue;
|
|
}
|
|
|
|
// If this file has a .lc.obj suffix, temporarily replace the original .obj file while generating the patch.
|
|
// It'd be nice to track this explicitly inside Live++ and just load the new file, but it requires a lot of changes and would make upgrades difficult.
|
|
static const TCHAR Suffix[] = TEXT(".lc.obj");
|
|
static const size_t SuffixLen = UE_ARRAY_COUNT(Suffix) - 1;
|
|
if (NormalizedObjectFile.length() >= SuffixLen && _wcsicmp(NormalizedObjectFile.c_str() + NormalizedObjectFile.length() - SuffixLen, Suffix) == 0)
|
|
{
|
|
// Get the original filename
|
|
std::wstring OriginalObjectFile(NormalizedObjectFile.c_str(), NormalizedObjectFile.c_str() + NormalizedObjectFile.length() - SuffixLen);
|
|
OriginalObjectFile += L".obj";
|
|
|
|
// Back up the original file, if it exists
|
|
Filesystem::PathAttributes OriginalFileAttributes = Filesystem::GetAttributes(OriginalObjectFile.c_str());
|
|
if (Filesystem::DoesExist(OriginalFileAttributes))
|
|
{
|
|
std::wstring OriginalObjectFileBackup = OriginalObjectFile + L".lctmp";
|
|
m_restoreFiles.push_back(std::make_pair(OriginalObjectFileBackup, OriginalObjectFile));
|
|
Filesystem::DeleteIfExists(OriginalObjectFileBackup.c_str());
|
|
Filesystem::Move(OriginalObjectFile.c_str(), OriginalObjectFileBackup.c_str());
|
|
}
|
|
|
|
// Move the new file into place
|
|
m_restoreFiles.push_back(std::make_pair(OriginalObjectFile, NormalizedObjectFile));
|
|
Filesystem::Move(NormalizedObjectFile.c_str(), OriginalObjectFile.c_str());
|
|
NormalizedObjectFile = OriginalObjectFile;
|
|
}
|
|
|
|
// Add the file to the list of modifications
|
|
symbols::ModifiedObjFile ModifiedObjFile;
|
|
ModifiedObjFile.objPath = NormalizedObjectFile;
|
|
ObjectFiles.push_back(std::move(ModifiedObjFile));
|
|
}
|
|
|
|
m_liveModuleToModifiedOrNewObjFiles.insert(std::make_pair(ModuleFileName, std::move(ObjectFiles)));
|
|
|
|
types::vector<std::wstring> AdditionalLibraries;
|
|
for (const FString& ObjectFile : Pair.Value.Libraries)
|
|
{
|
|
AdditionalLibraries.push_back(Filesystem::NormalizePath(*ObjectFile).GetString());
|
|
}
|
|
m_liveModuleToAdditionalLibraries.insert(std::make_pair(ModuleFileName, std::move(AdditionalLibraries)));
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// BEGIN EPIC MOD
|
|
// For Epic, don't even bother calling update if we have nothing that has been reported as modified/new.
|
|
// This prevents the automatic change detection code from running which has issues with unity file changes.
|
|
static const types::vector<std::wstring> emptyLibFiles;
|
|
for (size_t i = 0u; i < count; ++i)
|
|
{
|
|
LiveModule* liveModule = m_liveModules[i];
|
|
|
|
// 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() || objFilesIt->second.empty())
|
|
{
|
|
// no .objs for this module, ignore
|
|
continue;
|
|
}
|
|
|
|
const auto libFilesIt = m_liveModuleToAdditionalLibraries.find(liveModule->GetModuleName());
|
|
const types::vector<std::wstring>& libFiles = libFilesIt != m_liveModuleToAdditionalLibraries.end() ? libFilesIt->second : emptyLibFiles;
|
|
|
|
// build a patch with the given list of .objs for this module
|
|
const types::vector<symbols::ModifiedObjFile>& objFiles = objFilesIt->second;
|
|
LiveModule::ErrorType::Enum moduleUpdateError = liveModule->Update(&fileCache, m_directoryCache, updateType, objFiles, libFiles);
|
|
|
|
// 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;
|
|
}
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// 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)
|
|
{
|
|
// BEGIN EPIC MOD
|
|
GLiveCodingServer->GetBringToFrontDelegate().ExecuteIfBound();
|
|
// END EPIC MOD
|
|
}
|
|
|
|
// 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)
|
|
{
|
|
// BEGIN EPIC MOD
|
|
GLiveCodingServer->GetBringToFrontDelegate().ExecuteIfBound();
|
|
// END EPIC MOD
|
|
}
|
|
|
|
// 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
|
|
bool setFocus = false;
|
|
switch (updateError)
|
|
{
|
|
case LiveModule::ErrorType::NO_CHANGE:
|
|
postCompileResult = commands::PostCompileResult::NoChanges;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Success, L"No changes detected.");
|
|
setFocus = !ShowCompileFinishNotification();
|
|
break;
|
|
|
|
case LiveModule::ErrorType::COMPILE_ERROR:
|
|
postCompileResult = commands::PostCompileResult::Failure;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Error, L"Compilation error.");
|
|
break;
|
|
|
|
case LiveModule::ErrorType::LINK_ERROR:
|
|
postCompileResult = commands::PostCompileResult::Failure;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Error, L"Linker error.");
|
|
break;
|
|
|
|
case LiveModule::ErrorType::LOAD_PATCH_ERROR:
|
|
postCompileResult = commands::PostCompileResult::Failure;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Error, L"Could not load patch image.");
|
|
break;
|
|
|
|
case LiveModule::ErrorType::ACTIVATE_PATCH_ERROR:
|
|
postCompileResult = commands::PostCompileResult::Failure;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Error, L"Could not activate patch.");
|
|
break;
|
|
|
|
case LiveModule::ErrorType::SUCCESS:
|
|
postCompileResult = commands::PostCompileResult::Success;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Success, L"Patch creation successful.");
|
|
setFocus = true;
|
|
break;
|
|
|
|
default:
|
|
postCompileResult = commands::PostCompileResult::Success;
|
|
GLiveCodingServer->GetCompileFinishedDelegate().ExecuteIfBound(ELiveCodingResult::Success, L"Finished.");
|
|
break;
|
|
}
|
|
|
|
if (setFocus)
|
|
{
|
|
EnumWindows(FocusApplicationWindows, (LPARAM)&m_liveProcesses);
|
|
}
|
|
// END EPIC MOD
|
|
|
|
LC_LOG_USER("---------- Finished (%.3fs) ----------", scope.ReadSeconds());
|
|
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->ResetStatusBarText();
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->SetBusy(false);
|
|
}
|
|
|
|
// BEGIN EPIC MOD - Add the ability for pre and post compile notifications
|
|
void ServerCommandThread::CallPrecompileHooks(bool didAllProcessesMakeProgress)
|
|
{
|
|
// 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;
|
|
|
|
if (updateType == LiveModule::UpdateType::NO_CLIENT_COMMUNICATION)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const size_t count = m_liveProcesses.size();
|
|
for (size_t i = 0u; i < count; ++i)
|
|
{
|
|
LiveProcess* liveProcess = m_liveProcesses[i];
|
|
liveProcess->GetPipe()->SendCommandAndWaitForAck(commands::PreCompile{}, nullptr, 0u);
|
|
}
|
|
}
|
|
|
|
void ServerCommandThread::CallPostcompileHooks(bool didAllProcessesMakeProgress, commands::PostCompileResult postCompileResult)
|
|
{
|
|
// 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;
|
|
|
|
if (updateType == LiveModule::UpdateType::NO_CLIENT_COMMUNICATION)
|
|
{
|
|
return;
|
|
}
|
|
|
|
const size_t count = m_liveProcesses.size();
|
|
for (size_t i = 0u; i < count; ++i)
|
|
{
|
|
LiveProcess* liveProcess = m_liveProcesses[i];
|
|
liveProcess->GetPipe()->SendCommandAndWaitForAck(commands::PostCompile{ postCompileResult }, nullptr, 0u);
|
|
}
|
|
}
|
|
// END EPIC MOD
|
|
|
|
Thread::ReturnValue 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 (;;)
|
|
{
|
|
// protect against concurrent restarts
|
|
m_restartCS.Enter();
|
|
|
|
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 (isShortcutPressed || foundAnyModification || m_manualRecompileTriggered)
|
|
{
|
|
// clear the log if desired by the user
|
|
if (appSettings::g_clearLogOnRecompile->GetValue())
|
|
{
|
|
// BEGIN EPIC MOD
|
|
GLiveCodingServer->GetClearOutputDelegate().ExecuteIfBound();
|
|
// END EPIC MOD
|
|
}
|
|
|
|
if (foundAnyModification)
|
|
{
|
|
LC_SUCCESS_USER("Detected file modification, re-checking until timeout (%d ms)", changeNotificationTimeout);
|
|
changeNotification.CheckNext(changeNotificationTimeout);
|
|
}
|
|
else if (isShortcutPressed)
|
|
{
|
|
// BEGIN EPIC MOD
|
|
LC_SUCCESS_USER("Accepted Live coding shortcut");
|
|
// END EPIC MDO
|
|
}
|
|
else if (m_manualRecompileTriggered)
|
|
{
|
|
LC_SUCCESS_USER("Manual recompile triggered");
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
// BEGIN EPIC MOD
|
|
if (liveProcess->IsReinstancingFlowEnabled())
|
|
{
|
|
--m_reinstancingProcessCount;
|
|
}
|
|
|
|
if (liveProcess->IsDisableCompileFinishNotification())
|
|
{
|
|
--m_disableCompileFinishNotificationProcessCount;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
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)
|
|
{
|
|
// BEGIN EPIC MOD
|
|
LC_SUCCESS_USER("Possible debugger detected, can take a while to freeze threads");
|
|
LC_SUCCESS_USER("Do not continue the execution of the process until the threads have been thawed");
|
|
// END EPIC MOD
|
|
|
|
// not all processes made progress.
|
|
// this usually means that at least one of them is currently being debugged.
|
|
// let each process handle this.
|
|
const size_t processCount = m_liveProcesses.size();
|
|
for (size_t i = 0u; i < processCount; ++i)
|
|
{
|
|
LiveProcess* liveProcess = m_liveProcesses[i];
|
|
liveProcess->HandleDebuggingPreCompile();
|
|
}
|
|
|
|
// 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.
|
|
{
|
|
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();
|
|
|
|
// bring Live++ to front on shortcut trigger
|
|
if (appSettings::g_receiveFocusOnRecompile->GetValue() == appSettings::FocusOnRecompile::ON_SHORTCUT)
|
|
{
|
|
// BEGIN EPIC MOD
|
|
GLiveCodingServer->GetBringToFrontDelegate().ExecuteIfBound();
|
|
// END EPIC MOD
|
|
}
|
|
|
|
// BEGIN EPIC MOD - Add the ability for pre and post compile notifications
|
|
CallPrecompileHooks(didAllProcessesMakeProgress);
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD
|
|
commands::PostCompileResult postCompileResult = commands::PostCompileResult::Success;
|
|
CompileChanges(didAllProcessesMakeProgress, postCompileResult);
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD - Add the ability for pre and post compile notifications
|
|
CallPostcompileHooks(didAllProcessesMakeProgress, postCompileResult);
|
|
// END EPIC MOD
|
|
|
|
|
|
// BEGIN EPIC MOD - Non-destructive compile
|
|
for (std::vector<std::pair<std::wstring, std::wstring>>::reverse_iterator it = m_restoreFiles.rbegin(); it != m_restoreFiles.rend(); it++)
|
|
{
|
|
Filesystem::DeleteIfExists(it->second.c_str());
|
|
Filesystem::Move(it->first.c_str(), it->second.c_str());
|
|
}
|
|
m_restoreFiles.clear();
|
|
// END EPIC MOD
|
|
|
|
RemoveVirtualDrive();
|
|
|
|
if (!didAllProcessesMakeProgress)
|
|
{
|
|
// BEGIN EPIC MOD
|
|
LC_SUCCESS_USER("Possible debugger detected, can take a while to thaw threads");
|
|
// END EPIC MOD
|
|
|
|
const size_t processCount = m_liveProcesses.size();
|
|
for (size_t i = 0u; i < processCount; ++i)
|
|
{
|
|
LiveProcess* liveProcess = m_liveProcesses[i];
|
|
liveProcess->HandleDebuggingPostCompile();
|
|
}
|
|
|
|
// remove the lock on the exception handler dialog
|
|
m_exceptionCS.Leave();
|
|
|
|
// BEGIN EPIC MOD
|
|
LC_SUCCESS_USER("Thawing process has completed. You may now resume process execution");
|
|
// END EPIC MOD
|
|
}
|
|
|
|
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();
|
|
// BEGIN EPIC MOD
|
|
m_liveModuleToAdditionalLibraries.clear();
|
|
// END EPIC MOD
|
|
}
|
|
|
|
m_restartCS.Leave();
|
|
|
|
Thread::Current::SleepMilliSeconds(10u);
|
|
}
|
|
|
|
return Thread::ReturnValue(0u);
|
|
}
|
|
|
|
|
|
Thread::ReturnValue ServerCommandThread::CommandThread(DuplexPipeServer* pipe, Event* readyEvent)
|
|
{
|
|
// handle incoming commands
|
|
CommandMap commandMap;
|
|
commandMap.RegisterAction<actions::TriggerRecompile>();
|
|
commandMap.RegisterAction<actions::TriggerRestart>();
|
|
commandMap.RegisterAction<actions::LogMessage>();
|
|
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>();
|
|
// BEGIN EPIC MOD - Adding ShowConsole command
|
|
commandMap.RegisterAction<actions::ShowConsole>();
|
|
// END EPIC MOD
|
|
// BEGIN EPIC MOD - Adding SetVisible command
|
|
commandMap.RegisterAction<actions::SetVisible>();
|
|
// END EPIC MOD
|
|
// BEGIN EPIC MOD - Adding SetActive command
|
|
commandMap.RegisterAction<actions::SetActive>();
|
|
// END EPIC MOD
|
|
// BEGIN EPIC MOD - Adding SetBuildArguments command
|
|
commandMap.RegisterAction<actions::SetBuildArguments>();
|
|
// END EPIC MOD
|
|
// BEGIN EPIC MOD - Support for lazy-loading modules
|
|
commandMap.RegisterAction<actions::EnableLazyLoadedModule>();
|
|
// END EPIC MOD
|
|
// BEGIN EPIC MOD
|
|
commandMap.RegisterAction<actions::SetReinstancingFlow>();
|
|
commandMap.RegisterAction<actions::DisableCompileFinishNotification>();
|
|
// END EPIC MOD
|
|
// BEGIN EPIC MOD
|
|
commandMap.RegisterAction<actions::EnableModulesEx>();
|
|
// 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 Thread::ReturnValue(1u);
|
|
}
|
|
|
|
// wait until we're allowed to handle commands again
|
|
m_handleCommandsEvent.Wait();
|
|
|
|
// tell client that compilation has finished
|
|
pipe->SendCommandAndWaitForAck(commands::CompilationFinished {}, nullptr, 0u);
|
|
}
|
|
|
|
RemoveCommandThread(pipe);
|
|
return Thread::ReturnValue(0u);
|
|
}
|
|
|
|
|
|
Thread::ReturnValue ServerCommandThread::ExceptionCommandThread(DuplexPipeServer* exceptionPipe)
|
|
{
|
|
// handle incoming exception commands
|
|
CommandMap commandMap;
|
|
commandMap.RegisterAction<actions::HandleException>();
|
|
|
|
for (;;)
|
|
{
|
|
const bool success = commandMap.HandleCommands(exceptionPipe, this);
|
|
if ((!success) || (!exceptionPipe->IsValid()))
|
|
{
|
|
// pipe was closed or is broken, bail out
|
|
return Thread::ReturnValue(1u);
|
|
}
|
|
}
|
|
|
|
return Thread::ReturnValue(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(Process::Id 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;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::TriggerRecompile::Execute(const CommandType*, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
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;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::TriggerRestart::Execute(const CommandType*, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
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->RestartTargets();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::LogMessage::Execute(const CommandType*, const DuplexPipe* pipe, void*, const void* payload, size_t)
|
|
{
|
|
LC_LOG_USER("%S", static_cast<const wchar_t*>(payload));
|
|
|
|
pipe->SendAck();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::BuildPatch::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void* payload, size_t payloadSize)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
ServerCommandThread* commandThread = static_cast<ServerCommandThread*>(context);
|
|
|
|
// protect against accepting this command while compilation is already in progress
|
|
CriticalSection::ScopedLock lock(&commandThread->m_actionCS);
|
|
|
|
memoryStream::Reader payloadStream(payload, payloadSize);
|
|
for (unsigned int i = 0u; i < command->fileCount; ++i)
|
|
{
|
|
const commands::BuildPatch::PatchData patchData = payloadStream.Read<commands::BuildPatch::PatchData>();
|
|
const symbols::ModifiedObjFile modifiedObjFile = { patchData.objPath, patchData.amalgamatedObjPath };
|
|
|
|
commandThread->m_liveModuleToModifiedOrNewObjFiles[patchData.moduleName].push_back(modifiedObjFile);
|
|
}
|
|
|
|
commandThread->m_manualRecompileTriggered = true;
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::HandleException::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
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
|
|
pipe->SendCommandAndWaitForAck(commands::HandleExceptionFinished { nullptr, nullptr, nullptr, false }, nullptr, 0u);
|
|
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();
|
|
pipe->SendCommandAndWaitForAck(commands::HandleExceptionFinished { frameData.returnAddress, frameData.framePointer, frameData.stackPointer, true }, nullptr, 0u);
|
|
return true;
|
|
}
|
|
else if (result == IDC_EXCEPTION_HANDLER_IGNORE)
|
|
{
|
|
// tell the client that we ignored the exception
|
|
pipe->SendCommandAndWaitForAck(commands::HandleExceptionFinished { nullptr, nullptr, nullptr, false }, nullptr, 0u);
|
|
return true;
|
|
}
|
|
|
|
// signal client that we handled the exception and there's nothing left to do
|
|
pipe->SendCommandAndWaitForAck(commands::HandleExceptionFinished { nullptr, nullptr, nullptr, true }, nullptr, 0u);
|
|
#endif
|
|
// END EPIC MOD
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::ReadyForCompilation::Execute(const CommandType*, const DuplexPipe* pipe, void*, const void*, size_t)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
// don't continue execution
|
|
return false;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::DisconnectClient::Execute(const CommandType*, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
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
|
|
bool ServerCommandThread::actions::ShowConsole::Execute(const CommandType*, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
GLiveCodingServer->GetShowConsoleDelegate().ExecuteIfBound();
|
|
|
|
return true;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD - Adding SetVisible command
|
|
bool ServerCommandThread::actions::SetVisible::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
GLiveCodingServer->GetSetVisibleDelegate().ExecuteIfBound(command->visible);
|
|
|
|
return true;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD - Adding SetActive command
|
|
bool ServerCommandThread::actions::SetActive::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
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
|
|
bool ServerCommandThread::actions::SetBuildArguments::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
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
|
|
bool ServerCommandThread::actions::EnableLazyLoadedModule::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
ServerCommandThread* commandThread = static_cast<ServerCommandThread*>(context);
|
|
|
|
// protect against accepting this command while compilation is already in progress
|
|
CriticalSection::ScopedLock lock(&commandThread->m_actionCS);
|
|
|
|
// Check if this module is already enabled - it may have been lazy-loaded, then fully loaded, by a restarted process. If so, translate this into a call to EnableModules.
|
|
const std::wstring modulePath = Filesystem::NormalizePath(command->fileName).GetString();
|
|
for (LiveModule* module : commandThread->m_liveModules)
|
|
{
|
|
if(module->GetModuleName() == modulePath)
|
|
{
|
|
EnableModules::CommandType EnableCmd = { };
|
|
EnableCmd.moduleCount = 1;
|
|
EnableCmd.processId = command->processId;
|
|
EnableCmd.token = command->token;
|
|
|
|
commands::ModuleData Module;
|
|
Module.base = command->moduleBase;
|
|
wcscpy_s(Module.path, command->fileName);
|
|
|
|
return EnableModules::Execute(&EnableCmd, pipe, context, &Module, sizeof(Module));
|
|
}
|
|
}
|
|
|
|
// Acknowledge the command
|
|
pipe->SendAck();
|
|
|
|
// Register the module for lazy loading
|
|
for (LiveProcess* process : commandThread->m_liveProcesses)
|
|
{
|
|
if (process->GetProcessId() == command->processId)
|
|
{
|
|
process->AddLazyLoadedModule(modulePath, command->moduleBase);
|
|
LC_LOG_DEV("Registered module %S for lazy-loading", modulePath.c_str());
|
|
}
|
|
}
|
|
|
|
// Tell the client we're done
|
|
pipe->SendCommandAndWaitForAck(commands::EnableModulesFinished { command->token }, nullptr, 0u);
|
|
return true;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD
|
|
bool ServerCommandThread::actions::SetReinstancingFlow::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
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)
|
|
{
|
|
if (command->enable)
|
|
{
|
|
if (!process->IsReinstancingFlowEnabled())
|
|
{
|
|
++commandThread->m_reinstancingProcessCount;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (process->IsReinstancingFlowEnabled())
|
|
{
|
|
--commandThread->m_reinstancingProcessCount;
|
|
}
|
|
}
|
|
process->SetReinstancingFlow(command->enable);
|
|
}
|
|
}
|
|
|
|
pipe->SendAck();
|
|
|
|
return true;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// BEGIN EPIC MOD
|
|
bool ServerCommandThread::actions::DisableCompileFinishNotification::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void*, size_t)
|
|
{
|
|
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)
|
|
{
|
|
++commandThread->m_disableCompileFinishNotificationProcessCount;
|
|
process->DisableCompileFinishNotification();
|
|
}
|
|
}
|
|
|
|
pipe->SendAck();
|
|
|
|
return true;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
bool ServerCommandThread::actions::RegisterProcess::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void* payload, size_t)
|
|
{
|
|
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).GetString();
|
|
|
|
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())
|
|
{
|
|
// BEGIN EPIC MOD
|
|
std::wstring caption(L"Live coding - Registering process ");
|
|
// END EPIC MOD
|
|
caption += Filesystem::GetFilename(processPath.c_str()).GetString();
|
|
|
|
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)
|
|
{
|
|
const wchar_t* imagePath = pointer::Offset<const wchar_t*>(payload, 0u);
|
|
const wchar_t* commandLine = pointer::Offset<const wchar_t*>(imagePath, command->imagePathSize);
|
|
const wchar_t* workingDirectory = pointer::Offset<const wchar_t*>(commandLine, command->commandLineSize);
|
|
const void* environment = pointer::Offset<const wchar_t*>(workingDirectory, command->workingDirectorySize);
|
|
|
|
LiveProcess* liveProcess = new LiveProcess(processHandle, command->processId, command->threadId, command->jumpToSelf, pipe, imagePath, commandLine, workingDirectory, environment, command->environmentSize);
|
|
commandThread->m_liveProcesses.push_back(liveProcess);
|
|
// BEGIN EPIC MOD - No built-in UI
|
|
// commandThread->m_mainFrame->UpdateWindowTitle();
|
|
// END EPIC MOD
|
|
|
|
if (command->restartedProcessId == Process::Id(0u))
|
|
{
|
|
// this is a new process
|
|
LC_SUCCESS_USER("Registered process %S (PID: %d)", processPath.c_str(), +command->processId);
|
|
}
|
|
else
|
|
{
|
|
// this process was restarted
|
|
LC_SUCCESS_USER("Registered restarted process %S (PID: %d, previous PID: %d)", processPath.c_str(), +command->processId, command->restartedProcessId);
|
|
|
|
// BEGIN EPIC MOD
|
|
#if WITH_VISUALSTUDIO_DTE
|
|
// END EPIC MOD
|
|
// reattach the debugger in case the previous process had a debugger attached
|
|
{
|
|
auto it = commandThread->m_restartedProcessIdToDebugger.find(+command->restartedProcessId);
|
|
if (it != commandThread->m_restartedProcessIdToDebugger.end())
|
|
{
|
|
LC_LOG_USER("Reattaching debugger to PID %d", +command->processId);
|
|
|
|
const EnvDTE::DebuggerPtr& debugger = it->second;
|
|
const bool success = visualStudio::AttachToProcess(debugger, command->processId);
|
|
if (!success)
|
|
{
|
|
LC_ERROR_USER("Failed to reattach debugger to PID %d", +command->processId);
|
|
}
|
|
|
|
commandThread->m_restartedProcessIdToDebugger.erase(it);
|
|
}
|
|
}
|
|
// BEGIN EPIC MOD
|
|
#endif
|
|
// END EPIC MOD
|
|
|
|
--commandThread->m_restartedProcessCount;
|
|
if (commandThread->m_restartedProcessCount == 0u)
|
|
{
|
|
// finished restarting, remove the job that kept this instance alive
|
|
if (commandThread->m_restartJob)
|
|
{
|
|
::CloseHandle(commandThread->m_restartJob);
|
|
commandThread->m_restartJob = nullptr;
|
|
|
|
commandThread->m_restartCS.Leave();
|
|
|
|
LC_LOG_USER("---------- Restarting finished ----------");
|
|
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->ResetStatusBarText();
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->SetBusy(false);
|
|
}
|
|
// BEGIN EPIC MOD - Prevent orphaned console instances if processes fail to restart. Job object will be duplicated into child process.
|
|
else
|
|
{
|
|
commandThread->m_restartCS.Leave();
|
|
LC_LOG_USER("---------- Restarting finished ----------");
|
|
}
|
|
// END EPIC MOD
|
|
}
|
|
}
|
|
}
|
|
|
|
// tell client we are finished
|
|
pipe->SendCommandAndWaitForAck(commands::RegisterProcessFinished { registeredSuccessfully }, nullptr, 0u);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::EnableModules::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void* payload, size_t payloadSize)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->ChangeStatusBarText(L"Loading modules...");
|
|
|
|
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.
|
|
CriticalSection::ScopedLock lock(&commandThread->m_actionCS);
|
|
|
|
telemetry::Scope moduleLoadingScope("Module loading");
|
|
|
|
// set up virtual drives before loading anything, otherwise files won't be detected and therefore discarded
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
// wait for all tasks to finish
|
|
scheduler::RunTask(rootTask);
|
|
scheduler::WaitForTask(rootTask);
|
|
|
|
const size_t loadModuleTaskCount = loadModuleTasks.size();
|
|
commandThread->m_liveModules.reserve(loadModuleTaskCount);
|
|
|
|
size_t loadedTranslationUnits = 0u;
|
|
|
|
// update all live modules loaded by the tasks
|
|
for (size_t i = 0u; i < loadModuleTaskCount; ++i)
|
|
{
|
|
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();
|
|
}
|
|
|
|
// BEGIN EPIC MOD - Suppress output when lazy loading modules
|
|
if (+commandThread->m_compileThread == nullptr || Thread::Current::GetId() != Thread::GetId(commandThread->m_compileThread))
|
|
{
|
|
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());
|
|
}
|
|
}
|
|
}
|
|
// END EPIC MOD
|
|
|
|
// remove virtual drives once we're finished
|
|
RemoveVirtualDrive();
|
|
|
|
// tell server we are finished
|
|
pipe->SendCommandAndWaitForAck(commands::EnableModulesFinished { command->token }, nullptr, 0u);
|
|
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->ResetStatusBarText();
|
|
|
|
return true;
|
|
}
|
|
|
|
// BEGIN EPIC MOD
|
|
bool ServerCommandThread::actions::EnableModulesEx::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void* payload, size_t payloadSize)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
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.
|
|
CriticalSection::ScopedLock lock(&commandThread->m_actionCS);
|
|
|
|
telemetry::Scope moduleLoadingScope("Module loading ex");
|
|
|
|
// Locate the process
|
|
LiveProcess* liveProcess = nullptr;
|
|
for (LiveProcess* process : commandThread->m_liveProcesses)
|
|
{
|
|
if (process->GetProcessId() == command->processId)
|
|
{
|
|
liveProcess = process;
|
|
break;
|
|
}
|
|
}
|
|
|
|
// set up virtual drives before loading anything, otherwise files won't be detected and therefore discarded
|
|
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)
|
|
{
|
|
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);
|
|
}
|
|
}
|
|
|
|
const unsigned int lazyModuleCount = command->lazyLoadModuleCount;
|
|
unsigned int loadedLazyModules = 0;
|
|
for (unsigned int i = 0u; i < lazyModuleCount; ++i)
|
|
{
|
|
const commands::ModuleData moduleData = payloadStream.Read<commands::ModuleData>();
|
|
|
|
// Check if this module is already enabled - it may have been lazy-loaded, then fully loaded, by a restarted process. If so, translate this into a call to EnableModules.
|
|
bool isEnabled = false;
|
|
const std::wstring modulePath = Filesystem::NormalizePath(moduleData.path).GetString();
|
|
for (LiveModule* module : commandThread->m_liveModules)
|
|
{
|
|
if (module->GetModuleName() == modulePath)
|
|
{
|
|
scheduler::Task<LiveModule*>* task = commandThread->LoadModule(command->processId, moduleData.base, moduleData.path, rootTask);
|
|
if (task)
|
|
{
|
|
loadModuleTasks.push_back(task);
|
|
}
|
|
isEnabled = true;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!isEnabled && liveProcess != nullptr)
|
|
{
|
|
liveProcess->AddLazyLoadedModule(modulePath, static_cast<Windows::HMODULE>(moduleData.base));
|
|
LC_LOG_DEV("Registered module %S for lazy-loading", modulePath.c_str());
|
|
++loadedLazyModules;
|
|
}
|
|
}
|
|
|
|
const unsigned int reservedPagesCount = command->reservedPagesCount;
|
|
for (unsigned int i = 0u; i < reservedPagesCount; ++i)
|
|
{
|
|
const uintptr_t page = payloadStream.Read<uintptr_t>();
|
|
if (liveProcess != nullptr)
|
|
{
|
|
liveProcess->AddPage(reinterpret_cast<void*>(page));
|
|
}
|
|
}
|
|
|
|
// wait for all tasks to finish
|
|
scheduler::RunTask(rootTask);
|
|
scheduler::WaitForTask(rootTask);
|
|
|
|
const size_t loadModuleTaskCount = loadModuleTasks.size();
|
|
commandThread->m_liveModules.reserve(loadModuleTaskCount);
|
|
|
|
size_t loadedTranslationUnits = 0u;
|
|
|
|
// update all live modules loaded by the tasks
|
|
for (size_t i = 0u; i < loadModuleTaskCount; ++i)
|
|
{
|
|
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 (+commandThread->m_compileThread == nullptr || Thread::Current::GetId() != Thread::GetId(commandThread->m_compileThread))
|
|
{
|
|
if (loadModuleTaskCount > 0u)
|
|
{
|
|
LC_SUCCESS_USER("Loaded %zu module(s), %u lazy load module(s), and %u reserved page ranges (%.3fs, %zu translation units)",
|
|
loadModuleTaskCount, loadedLazyModules, reservedPagesCount, moduleLoadingScope.ReadSeconds(), loadedTranslationUnits);
|
|
}
|
|
|
|
// 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());
|
|
}
|
|
}
|
|
}
|
|
|
|
// remove virtual drives once we're finished
|
|
RemoveVirtualDrive();
|
|
|
|
// tell server we are finished
|
|
pipe->SendCommandAndWaitForAck(commands::EnableModulesFinished{ command->token }, nullptr, 0u);
|
|
|
|
return true;
|
|
}
|
|
// END EPIC MOD
|
|
|
|
bool ServerCommandThread::actions::DisableModules::Execute(const CommandType* command, const DuplexPipe* pipe, void* context, const void* payload, size_t payloadSize)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->ChangeStatusBarText(L"Unloading modules...");
|
|
|
|
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.
|
|
CriticalSection::ScopedLock lock(&commandThread->m_actionCS);
|
|
|
|
telemetry::Scope moduleUnloadingScope("Module unloading");
|
|
|
|
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;
|
|
}
|
|
}
|
|
|
|
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);
|
|
|
|
// EPIC REMOVED: g_theApp.GetMainFrame()->ResetStatusBarText();
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::ApplySettingBool::Execute(const CommandType* command, const DuplexPipe* pipe, void*, const void*, size_t)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
appSettings::ApplySettingBool(command->settingName, (command->settingValue == 0) ? false : true);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::ApplySettingInt::Execute(const CommandType* command, const DuplexPipe* pipe, void*, const void*, size_t)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
appSettings::ApplySettingInt(command->settingName, command->settingValue);
|
|
|
|
return true;
|
|
}
|
|
|
|
|
|
bool ServerCommandThread::actions::ApplySettingString::Execute(const CommandType* command, const DuplexPipe* pipe, void*, const void*, size_t)
|
|
{
|
|
pipe->SendAck();
|
|
|
|
appSettings::ApplySettingString(command->settingName, command->settingValue);
|
|
|
|
return true;
|
|
}
|
|
|
|
// BEGIN EPIC MODS
|
|
#pragma warning(pop)
|
|
// END EPIC MODS
|
|
|
|
|
|
#endif |