Files
UnrealEngineUWP/Engine/Source/Developer/Windows/LiveCoding/Private/External/LC_ClientUserCommandThread.cpp
ben marsh cf183af67c Integrating live coding feature (aka Live++) into UE4.
Allows fast iteration of C++ changes without restarting the application. To use, select the "Live Coding (Experimental)" mode from the drop down menu next to the editor's compile button, or type "LiveCoding" into the console for a monolithic build. Press Ctrl+Alt+F11 to find changes and compile.

Changes vs standalone Live++ version:

* UBT is used to execute builds. This allows standard UE4 adaptive unity mode, allows us to reuse object files when we do regular builds, supports using any build executor allowed by UBT (XGE, SNDBS, etc..).
* Adding new source files is supported.
* Custom visualizer for FNames is supported via a weakly linked symbol in a static library (Engine/Extras/NatvisHelpers).
* Settings are exposed in the editor's project settings dialog.
* Standalone application has been rewritten as a Slate app ("LiveCodingConsole"). There is an additional option to start the program as hidden, where it will not be visible until Ctrl+Alt+F11 is hit. Similarly, closing the window will hide it instead of closing the application.
* Does not require a standalone licensed version of Live++.

Known issues:

* Does not currently support class layout changes / object reinstancing

#rb none
[FYI] Marc.Audy, Stefan.Boberg, Nick.Penwarden
#jira

#ROBOMERGE-SOURCE: CL 5304722 in //UE4/Release-4.22/...
#ROBOMERGE-BOT: RELEASE (Release-4.22 -> Main)

[CL 5309051 by ben marsh in Main branch]
2019-03-05 18:49:25 -05:00

791 lines
20 KiB
C++

// Copyright 2011-2019 Molecular Matters GmbH, all rights reserved.
#include "LC_ClientUserCommandThread.h"
#include "LC_CommandMap.h"
#include "LC_DuplexPipeClient.h"
#include "LC_ClientCommandActions.h"
#include "LC_Event.h"
#include "LC_Process.h"
#include "LC_CriticalSection.h"
#include "LC_StringUtil.h"
#include <deque>
namespace userCommands
{
struct Scope
{
enum Enum
{
NONE,
ENABLE_MODULES,
DISABLE_MODULES,
};
};
struct BaseCommand
{
explicit BaseCommand(Scope::Enum scope)
: m_scope(scope)
{
}
virtual ~BaseCommand(void) {}
virtual void Execute(DuplexPipe* pipe) = 0;
inline Scope::Enum GetScope(void) const
{
return m_scope;
}
private:
Scope::Enum m_scope;
};
struct EnableModuleCommand : public BaseCommand
{
EnableModuleCommand(void)
: BaseCommand(Scope::ENABLE_MODULES)
{
}
virtual ~EnableModuleCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::EnableModule serverCommand;
serverCommand.processId = process::GetId();
wcscpy_s(serverCommand.path, moduleName.c_str());
serverCommand.token = token;
pipe->SendCommandAndWaitForAck(serverCommand);
}
Event* token;
std::wstring moduleName;
};
struct EnableAllModulesCommand : public BaseCommand
{
EnableAllModulesCommand(void)
: BaseCommand(Scope::ENABLE_MODULES)
{
}
virtual ~EnableAllModulesCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::EnableAllModules serverCommand;
serverCommand.processId = process::GetId();
wcscpy_s(serverCommand.path, moduleName.c_str());
serverCommand.token = token;
pipe->SendCommandAndWaitForAck(serverCommand);
}
Event* token;
std::wstring moduleName;
};
struct DisableModuleCommand : public BaseCommand
{
DisableModuleCommand(void)
: BaseCommand(Scope::DISABLE_MODULES)
{
}
virtual ~DisableModuleCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::DisableModule serverCommand;
serverCommand.processId = process::GetId();
wcscpy_s(serverCommand.path, moduleName.c_str());
serverCommand.token = token;
pipe->SendCommandAndWaitForAck(serverCommand);
}
Event* token;
std::wstring moduleName;
};
struct DisableAllModulesCommand : public BaseCommand
{
DisableAllModulesCommand(void)
: BaseCommand(Scope::DISABLE_MODULES)
{
}
virtual ~DisableAllModulesCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::DisableAllModules serverCommand;
serverCommand.processId = process::GetId();
wcscpy_s(serverCommand.path, moduleName.c_str());
serverCommand.token = token;
pipe->SendCommandAndWaitForAck(serverCommand);
}
Event* token;
std::wstring moduleName;
};
struct TriggerRecompileCommand : public BaseCommand
{
TriggerRecompileCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~TriggerRecompileCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::TriggerRecompile serverCommand;
pipe->SendCommandAndWaitForAck(serverCommand);
}
};
// BEGIN EPIC MOD - Adding ShowConsole command
struct ShowConsoleCommand : public BaseCommand
{
ShowConsoleCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~ShowConsoleCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::ShowConsole serverCommand;
pipe->SendCommandAndWaitForAck(serverCommand);
}
};
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetVisible command
struct SetVisibleCommand : public BaseCommand
{
SetVisibleCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~SetVisibleCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::SetVisible serverCommand;
serverCommand.visible = visible;
pipe->SendCommandAndWaitForAck(serverCommand);
}
bool visible;
};
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetActive command
struct SetActiveCommand : public BaseCommand
{
SetActiveCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~SetActiveCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::SetActive serverCommand;
serverCommand.active = active;
pipe->SendCommandAndWaitForAck(serverCommand);
}
bool active;
};
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetActive command
struct SetBuildArgumentsCommand : public BaseCommand
{
SetBuildArgumentsCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~SetBuildArgumentsCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::SetBuildArguments serverCommand;
serverCommand.processId = process::GetId();
wcscpy_s(serverCommand.arguments, arguments.c_str());
pipe->SendCommandAndWaitForAck(serverCommand);
}
std::wstring arguments;
};
// END EPIC MOD
struct BuildPatchCommand : public BaseCommand
{
BuildPatchCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~BuildPatchCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::BuildPatch serverCommand;
serverCommand.count = count;
pipe->SendCommandAndWaitForAck(serverCommand);
for (unsigned int i = 0u; i < count; ++i)
{
commands::BuildPatchPacket packet;
wcscpy_s(packet.moduleName, moduleNames[i].c_str());
wcscpy_s(packet.objPath, objPaths[i].c_str());
pipe->SendCommandAndWaitForAck(packet);
}
}
unsigned int count;
std::vector<std::wstring> moduleNames;
std::vector<std::wstring> objPaths;
};
struct ApplySettingBoolCommand : public BaseCommand
{
ApplySettingBoolCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~ApplySettingBoolCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::ApplySettingBool serverCommand;
strcpy_s(serverCommand.settingName, settingName.c_str());
serverCommand.settingValue = value;
pipe->SendCommandAndWaitForAck(serverCommand);
}
std::string settingName;
int value;
};
struct ApplySettingIntCommand : public BaseCommand
{
ApplySettingIntCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~ApplySettingIntCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::ApplySettingInt serverCommand;
strcpy_s(serverCommand.settingName, settingName.c_str());
serverCommand.settingValue = value;
pipe->SendCommandAndWaitForAck(serverCommand);
}
std::string settingName;
int value;
};
struct ApplySettingStringCommand : public BaseCommand
{
ApplySettingStringCommand(void)
: BaseCommand(Scope::NONE)
{
}
virtual ~ApplySettingStringCommand(void) {}
virtual void Execute(DuplexPipe* pipe) override
{
commands::ApplySettingString serverCommand;
strcpy_s(serverCommand.settingName, settingName.c_str());
wcscpy_s(serverCommand.settingValue, value.c_str());
pipe->SendCommandAndWaitForAck(serverCommand);
}
std::string settingName;
std::wstring value;
};
}
namespace
{
// queue for working on commands received by user code
static std::deque<userCommands::BaseCommand*> g_userCommandQueue;
static CriticalSection g_userCommandQueueCS;
}
ClientUserCommandThread::ClientUserCommandThread(DuplexPipeClient* pipeClient, DuplexPipeClient* exceptionPipeClient)
: m_thread(INVALID_HANDLE_VALUE)
, m_processGroupName()
, m_pipe(pipeClient)
, m_exceptionPipe(exceptionPipeClient)
, m_itemInQueueEvent(new Event(nullptr, Event::Type::MANUAL_RESET))
{
}
ClientUserCommandThread::~ClientUserCommandThread(void)
{
delete m_itemInQueueEvent;
}
unsigned int ClientUserCommandThread::Start(const std::wstring& processGroupName, Event* waitForStartEvent, CriticalSection* pipeAccessCS)
{
m_processGroupName = processGroupName;
// spawn a thread that does the work
ThreadContext* context = new ThreadContext;
context->thisInstance = this;
context->waitForStartEvent = waitForStartEvent;
context->pipeAccessCS = pipeAccessCS;
m_thread = thread::Create(128u * 1024u, &ThreadProxy, context);
return thread::GetId(m_thread);
}
void ClientUserCommandThread::Join(void)
{
if (m_thread != INVALID_HANDLE_VALUE)
{
thread::Join(m_thread);
thread::Close(m_thread);
}
}
void* ClientUserCommandThread::EnableModule(const wchar_t* const nameOfExeOrDll)
{
userCommands::EnableModuleCommand* command = new userCommands::EnableModuleCommand;
command->token = new Event(nullptr, Event::Type::AUTO_RESET);
command->moduleName = nameOfExeOrDll;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
return command->token;
}
void* ClientUserCommandThread::EnableAllModules(const wchar_t* const nameOfExeOrDll)
{
userCommands::EnableAllModulesCommand* command = new userCommands::EnableAllModulesCommand;
command->token = new Event(nullptr, Event::Type::AUTO_RESET);
command->moduleName = nameOfExeOrDll;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
return command->token;
}
void* ClientUserCommandThread::DisableModule(const wchar_t* const nameOfExeOrDll)
{
userCommands::DisableModuleCommand* command = new userCommands::DisableModuleCommand;
command->token = new Event(nullptr, Event::Type::AUTO_RESET);
command->moduleName = nameOfExeOrDll;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
return command->token;
}
void* ClientUserCommandThread::DisableAllModules(const wchar_t* const nameOfExeOrDll)
{
userCommands::DisableAllModulesCommand* command = new userCommands::DisableAllModulesCommand;
command->token = new Event(nullptr, Event::Type::AUTO_RESET);
command->moduleName = nameOfExeOrDll;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
return command->token;
}
void ClientUserCommandThread::WaitForToken(void* token)
{
Event* event = static_cast<Event*>(token);
if (m_thread != INVALID_HANDLE_VALUE)
{
// thread was successfully initialized, wait until the command has been executed in the queue
event->Wait();
}
delete event;
}
void ClientUserCommandThread::TriggerRecompile(void)
{
userCommands::TriggerRecompileCommand* command = new userCommands::TriggerRecompileCommand;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
void ClientUserCommandThread::BuildPatch(const wchar_t* moduleNames[], const wchar_t* objPaths[], unsigned int count)
{
userCommands::BuildPatchCommand* command = new userCommands::BuildPatchCommand;
command->count = count;
command->moduleNames.reserve(count);
command->objPaths.reserve(count);
for (unsigned int i = 0u; i < count; ++i)
{
command->moduleNames.push_back(moduleNames[i]);
command->objPaths.push_back(objPaths[i]);
}
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
void ClientUserCommandThread::InstallExceptionHandler(void)
{
// BEGIN EPIC MOD - Using internal CrashReporter
// exceptionHandler::Register(this);
// END EPIC MOD
}
ClientUserCommandThread::ExceptionResult ClientUserCommandThread::HandleException(EXCEPTION_RECORD* exception, CONTEXT* context, unsigned int threadId)
{
commands::HandleException serverCommand;
serverCommand.processId = process::GetId();
serverCommand.threadId = threadId;
serverCommand.exception = *exception;
serverCommand.context = *context;
serverCommand.clientContextPtr = context;
m_exceptionPipe->SendCommandAndWaitForAck(serverCommand);
ExceptionResult result = {};
CommandMap commandMap;
commandMap.RegisterAction<actions::HandleExceptionFinished>();
commandMap.HandleCommands(m_exceptionPipe, &result);
return result;
}
void ClientUserCommandThread::End(void)
{
// signal to the thread that a new item is in the queue to make it break out of its main loop
m_itemInQueueEvent->Reset();
m_itemInQueueEvent->Signal();
}
// BEGIN EPIC MOD - Adding ShowConsole command
void ClientUserCommandThread::ShowConsole()
{
userCommands::ShowConsoleCommand* command = new userCommands::ShowConsoleCommand;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetVisible command
void ClientUserCommandThread::SetVisible(bool visible)
{
userCommands::SetVisibleCommand* command = new userCommands::SetVisibleCommand;
command->visible = visible;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetActive command
void ClientUserCommandThread::SetActive(bool active)
{
userCommands::SetActiveCommand* command = new userCommands::SetActiveCommand;
command->active = active;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
// END EPIC MOD
// BEGIN EPIC MOD - Adding SetBuildArguments command
void ClientUserCommandThread::SetBuildArguments(const wchar_t* arguments)
{
userCommands::SetBuildArgumentsCommand* command = new userCommands::SetBuildArgumentsCommand;
command->arguments = arguments;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
// END EPIC MOD
void ClientUserCommandThread::ApplySettingBool(const char* const settingName, int value)
{
userCommands::ApplySettingBoolCommand* command = new userCommands::ApplySettingBoolCommand;
command->settingName = settingName;
command->value = value;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
void ClientUserCommandThread::ApplySettingInt(const char* const settingName, int value)
{
userCommands::ApplySettingIntCommand* command = new userCommands::ApplySettingIntCommand;
command->settingName = settingName;
command->value = value;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
void ClientUserCommandThread::ApplySettingString(const char* const settingName, const wchar_t* const value)
{
userCommands::ApplySettingStringCommand* command = new userCommands::ApplySettingStringCommand;
command->settingName = settingName;
command->value = value;
{
CriticalSection::ScopedLock lock(&g_userCommandQueueCS);
g_userCommandQueue.push_front(command);
}
// signal to the thread that a new item is in the queue
m_itemInQueueEvent->Signal();
}
unsigned int __stdcall ClientUserCommandThread::ThreadProxy(void* context)
{
thread::SetName("Live coding user commands");
ThreadContext* realContext = static_cast<ThreadContext*>(context);
const unsigned int exitCode = realContext->thisInstance->ThreadFunction(realContext->waitForStartEvent, realContext->pipeAccessCS);
delete realContext;
return exitCode;
}
unsigned int ClientUserCommandThread::ThreadFunction(Event* waitForStartEvent, CriticalSection* pipeAccessCS)
{
// wait until we get the signal that the thread can start
waitForStartEvent->Wait();
CommandMap moduleCommandMap;
moduleCommandMap.RegisterAction<actions::GetModule>();
moduleCommandMap.RegisterAction<actions::EnableModuleFinished>();
moduleCommandMap.RegisterAction<actions::EnableAllModulesFinished>();
moduleCommandMap.RegisterAction<actions::DisableModuleFinished>();
moduleCommandMap.RegisterAction<actions::DisableAllModulesFinished>();
// those commands are needed when loading compiled patches into spawned executables
moduleCommandMap.RegisterAction<actions::LoadPatch>();
moduleCommandMap.RegisterAction<actions::UnloadPatch>();
moduleCommandMap.RegisterAction<actions::EnterSyncPoint>();
moduleCommandMap.RegisterAction<actions::LeaveSyncPoint>();
moduleCommandMap.RegisterAction<actions::CallEntryPoint>();
moduleCommandMap.RegisterAction<actions::CallHooks>();
for (;;)
{
// wait for event that signals that something is in the queue
m_itemInQueueEvent->Wait();
if (!m_pipe->IsValid())
{
// BEGIN EPIC MOD - Using internal CrashReporter
// // pipe was closed or is broken, bail out
// exceptionHandler::Unregister();
// END EPIC MOD - Using internal CrashReporter
return 1u;
}
// lock critical section for accessing the pipe.
// we need to make sure that other threads talking through the pipe don't use it at the same time.
CriticalSection::ScopedLock pipeLock(pipeAccessCS);
// lock critical section for accessing the queue.
// user code might be calling other exported functions in the mean time.
CriticalSection::ScopedLock queueLock(&g_userCommandQueueCS);
if (g_userCommandQueue.size() == 0u)
{
// BEGIN EPIC MOD - Using internal CrashReporter
// // no new item available, bail out
// exceptionHandler::Unregister();
// END EPIC MOD - Using internal CrashReporter
return 2u;
}
// separate commands into three groups: ones that need to be scoped for enabling modules, the ones that
// need to be scoped for disabling modules, and the others that don't need to be scoped at all
std::vector<userCommands::BaseCommand*> enabledScopedCommands;
enabledScopedCommands.reserve(g_userCommandQueue.size());
std::vector<userCommands::BaseCommand*> disableScopedCommands;
disableScopedCommands.reserve(g_userCommandQueue.size());
std::vector<userCommands::BaseCommand*> commands;
commands.reserve(g_userCommandQueue.size());
while (g_userCommandQueue.size() > 0u)
{
userCommands::BaseCommand* command = g_userCommandQueue.back();
g_userCommandQueue.pop_back();
if (command->GetScope() == userCommands::Scope::NONE)
{
commands.push_back(command);
}
else if (command->GetScope() == userCommands::Scope::ENABLE_MODULES)
{
enabledScopedCommands.push_back(command);
}
else if (command->GetScope() == userCommands::Scope::DISABLE_MODULES)
{
disableScopedCommands.push_back(command);
}
}
// send out scoped commands first
{
const size_t count = enabledScopedCommands.size();
if (count != 0u)
{
m_pipe->SendCommandAndWaitForAck(commands::EnableModuleBatchBegin {});
for (size_t i=0u; i < count; ++i)
{
userCommands::BaseCommand* command = enabledScopedCommands[i];
command->Execute(m_pipe);
moduleCommandMap.HandleCommands(m_pipe, nullptr);
delete command;
}
m_pipe->SendCommandAndWaitForAck(commands::EnableModuleBatchEnd {});
}
}
{
const size_t count = disableScopedCommands.size();
if (count != 0u)
{
m_pipe->SendCommandAndWaitForAck(commands::DisableModuleBatchBegin{});
for (size_t i = 0u; i < count; ++i)
{
userCommands::BaseCommand* command = disableScopedCommands[i];
command->Execute(m_pipe);
moduleCommandMap.HandleCommands(m_pipe, nullptr);
delete command;
}
m_pipe->SendCommandAndWaitForAck(commands::DisableModuleBatchEnd{});
}
}
// send out non-scoped commands second
{
const size_t count = commands.size();
for (size_t i = 0u; i < count; ++i)
{
userCommands::BaseCommand* command = commands[i];
command->Execute(m_pipe);
delete command;
}
}
m_itemInQueueEvent->Reset();
}
return 0u;
}