Add hot reloading support

This commit is contained in:
Ted John 2018-03-18 17:43:47 +00:00
parent de527b3ff7
commit 3556dead74
4 changed files with 132 additions and 5 deletions

View File

@ -21,6 +21,11 @@
#include <fstream>
#include <memory>
#include <fcntl.h>
#include <sys/inotify.h>
#include <sys/types.h>
#include <unistd.h>
using namespace OpenRCT2::Scripting;
Plugin::Plugin(duk_context * context, const std::string &path)
@ -29,11 +34,23 @@ Plugin::Plugin(duk_context * context, const std::string &path)
{
}
Plugin::Plugin(const Plugin&& src)
Plugin::Plugin(Plugin&& src)
: _context(src._context),
_path(src._path),
_metadata(src._metadata)
_metadata(src._metadata),
_hotReloadData(src._hotReloadData),
_hotReloadEnabled(src._hotReloadEnabled)
{
src._context = nullptr;
src._path = std::string();
src._metadata = PluginMetadata();
src._hotReloadData = HotReloadData();
src._hotReloadEnabled = false;
}
Plugin::~Plugin()
{
DisableHotReload();
}
void Plugin::Load()
@ -70,6 +87,11 @@ void Plugin::Load()
void Plugin::Start()
{
const auto& mainFunc = _metadata.Main;
if (mainFunc.context() == nullptr)
{
throw std::runtime_error("No main function specified.");
}
mainFunc.push();
auto result = duk_pcall(_context, 0);
if (result != DUK_ERR_NONE)
@ -81,6 +103,66 @@ void Plugin::Start()
duk_pop(_context);
}
void Plugin::Update()
{
}
void Plugin::EnableHotReload()
{
auto fd = inotify_init();
if (fd >= 0)
{
// Mark file as non-blocking
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
auto wd = inotify_add_watch(fd, _path.c_str(), IN_CLOSE_WRITE);
if (wd >= 0)
{
_hotReloadData.FileDesc = fd;
_hotReloadData.WatchDesc = wd;
_hotReloadEnabled = true;
}
else
{
close(fd);
}
}
}
bool Plugin::ShouldHotReload()
{
if (_hotReloadEnabled)
{
std::vector<char> eventData;
eventData.resize(1024);
auto length = read(_hotReloadData.FileDesc, eventData.data(), eventData.size());
int offset = 0;
while (offset < length)
{
auto e = (inotify_event*)&eventData[offset];
if ((e->mask & IN_CLOSE_WRITE) && !(e->mask & IN_ISDIR))
{
return true;
}
offset += sizeof(inotify_event) + e->len;
}
}
return false;
}
void Plugin::DisableHotReload()
{
if (_hotReloadEnabled)
{
inotify_rm_watch(_hotReloadData.FileDesc, _hotReloadData.WatchDesc);
close(_hotReloadData.FileDesc);
_hotReloadData = HotReloadData();
_hotReloadEnabled = false;
}
}
PluginMetadata Plugin::GetMetadata(const DukValue& dukMetadata)
{
PluginMetadata metadata;

View File

@ -34,17 +34,32 @@ namespace OpenRCT2::Scripting
class Plugin
{
private:
duk_context * const _context;
std::string const _path;
struct HotReloadData
{
int FileDesc{};
int WatchDesc{};
};
duk_context * _context;
std::string _path;
PluginMetadata _metadata;
HotReloadData _hotReloadData;
bool _hotReloadEnabled{};
public:
Plugin() { }
Plugin(duk_context * context, const std::string &path);
Plugin(const Plugin&) = delete;
Plugin(const Plugin&&);
Plugin(Plugin&&);
~Plugin();
void Load();
void Start();
void Update();
void EnableHotReload();
bool ShouldHotReload();
void DisableHotReload();
private:
static PluginMetadata GetMetadata(const DukValue& dukMetadata);

View File

@ -11,6 +11,7 @@
#include "../core/FileScanner.h"
#include "../core/Path.hpp"
#include "../interface/InteractiveConsole.h"
#include "../platform/Platform2.h"
#include "../PlatformEnvironment.h"
#include <dukglue/dukglue.h>
#include <duktape.h>
@ -67,6 +68,7 @@ void ScriptEngine::LoadPlugins()
{
Plugin p(_context, path);
p.Load();
p.EnableHotReload();
_plugins.push_back(std::move(p));
}
catch (const std::exception &e)
@ -76,6 +78,25 @@ void ScriptEngine::LoadPlugins()
}
}
void ScriptEngine::AutoReloadPlugins()
{
for (auto& plugin : _plugins)
{
if (plugin.ShouldHotReload())
{
try
{
plugin.Load();
plugin.Start();
}
catch (const std::exception &e)
{
_console.WriteLineError(e.what());
}
}
}
}
void ScriptEngine::StartPlugins()
{
for (auto& plugin : _plugins)
@ -111,6 +132,13 @@ void ScriptEngine::Update()
// Signal the promise so caller can continue
promise.set_value();
}
auto tick = Platform::GetTicks();
if (tick - _lastHotReloadCheckTick > 1000)
{
AutoReloadPlugins();
_lastHotReloadCheckTick = tick;
}
}
std::future<void> ScriptEngine::Eval(const std::string &s)

View File

@ -36,6 +36,7 @@ namespace OpenRCT2::Scripting
duk_context * _context{};
std::queue<std::tuple<std::promise<void>, std::string>> _evalQueue;
std::vector<Plugin> _plugins;
uint32 _lastHotReloadCheckTick{};
public:
ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env);
@ -49,5 +50,6 @@ namespace OpenRCT2::Scripting
void Initialise();
void LoadPlugins();
void StartPlugins();
void AutoReloadPlugins();
};
}