mirror of
https://github.com/OpenRCT2/OpenRCT2
synced 2025-12-23 15:52:55 +01:00
Add hot reloading support
This commit is contained in:
@@ -21,6 +21,11 @@
|
|||||||
#include <fstream>
|
#include <fstream>
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
|
#include <fcntl.h>
|
||||||
|
#include <sys/inotify.h>
|
||||||
|
#include <sys/types.h>
|
||||||
|
#include <unistd.h>
|
||||||
|
|
||||||
using namespace OpenRCT2::Scripting;
|
using namespace OpenRCT2::Scripting;
|
||||||
|
|
||||||
Plugin::Plugin(duk_context * context, const std::string &path)
|
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),
|
: _context(src._context),
|
||||||
_path(src._path),
|
_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()
|
void Plugin::Load()
|
||||||
@@ -70,6 +87,11 @@ void Plugin::Load()
|
|||||||
void Plugin::Start()
|
void Plugin::Start()
|
||||||
{
|
{
|
||||||
const auto& mainFunc = _metadata.Main;
|
const auto& mainFunc = _metadata.Main;
|
||||||
|
if (mainFunc.context() == nullptr)
|
||||||
|
{
|
||||||
|
throw std::runtime_error("No main function specified.");
|
||||||
|
}
|
||||||
|
|
||||||
mainFunc.push();
|
mainFunc.push();
|
||||||
auto result = duk_pcall(_context, 0);
|
auto result = duk_pcall(_context, 0);
|
||||||
if (result != DUK_ERR_NONE)
|
if (result != DUK_ERR_NONE)
|
||||||
@@ -81,6 +103,66 @@ void Plugin::Start()
|
|||||||
duk_pop(_context);
|
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 Plugin::GetMetadata(const DukValue& dukMetadata)
|
||||||
{
|
{
|
||||||
PluginMetadata metadata;
|
PluginMetadata metadata;
|
||||||
|
|||||||
@@ -34,17 +34,32 @@ namespace OpenRCT2::Scripting
|
|||||||
class Plugin
|
class Plugin
|
||||||
{
|
{
|
||||||
private:
|
private:
|
||||||
duk_context * const _context;
|
struct HotReloadData
|
||||||
std::string const _path;
|
{
|
||||||
|
int FileDesc{};
|
||||||
|
int WatchDesc{};
|
||||||
|
};
|
||||||
|
|
||||||
|
duk_context * _context;
|
||||||
|
std::string _path;
|
||||||
PluginMetadata _metadata;
|
PluginMetadata _metadata;
|
||||||
|
HotReloadData _hotReloadData;
|
||||||
|
bool _hotReloadEnabled{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
|
Plugin() { }
|
||||||
Plugin(duk_context * context, const std::string &path);
|
Plugin(duk_context * context, const std::string &path);
|
||||||
Plugin(const Plugin&) = delete;
|
Plugin(const Plugin&) = delete;
|
||||||
Plugin(const Plugin&&);
|
Plugin(Plugin&&);
|
||||||
|
~Plugin();
|
||||||
|
|
||||||
void Load();
|
void Load();
|
||||||
void Start();
|
void Start();
|
||||||
|
void Update();
|
||||||
|
|
||||||
|
void EnableHotReload();
|
||||||
|
bool ShouldHotReload();
|
||||||
|
void DisableHotReload();
|
||||||
|
|
||||||
private:
|
private:
|
||||||
static PluginMetadata GetMetadata(const DukValue& dukMetadata);
|
static PluginMetadata GetMetadata(const DukValue& dukMetadata);
|
||||||
|
|||||||
@@ -11,6 +11,7 @@
|
|||||||
#include "../core/FileScanner.h"
|
#include "../core/FileScanner.h"
|
||||||
#include "../core/Path.hpp"
|
#include "../core/Path.hpp"
|
||||||
#include "../interface/InteractiveConsole.h"
|
#include "../interface/InteractiveConsole.h"
|
||||||
|
#include "../platform/Platform2.h"
|
||||||
#include "../PlatformEnvironment.h"
|
#include "../PlatformEnvironment.h"
|
||||||
#include <dukglue/dukglue.h>
|
#include <dukglue/dukglue.h>
|
||||||
#include <duktape.h>
|
#include <duktape.h>
|
||||||
@@ -67,6 +68,7 @@ void ScriptEngine::LoadPlugins()
|
|||||||
{
|
{
|
||||||
Plugin p(_context, path);
|
Plugin p(_context, path);
|
||||||
p.Load();
|
p.Load();
|
||||||
|
p.EnableHotReload();
|
||||||
_plugins.push_back(std::move(p));
|
_plugins.push_back(std::move(p));
|
||||||
}
|
}
|
||||||
catch (const std::exception &e)
|
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()
|
void ScriptEngine::StartPlugins()
|
||||||
{
|
{
|
||||||
for (auto& plugin : _plugins)
|
for (auto& plugin : _plugins)
|
||||||
@@ -111,6 +132,13 @@ void ScriptEngine::Update()
|
|||||||
// Signal the promise so caller can continue
|
// Signal the promise so caller can continue
|
||||||
promise.set_value();
|
promise.set_value();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
auto tick = Platform::GetTicks();
|
||||||
|
if (tick - _lastHotReloadCheckTick > 1000)
|
||||||
|
{
|
||||||
|
AutoReloadPlugins();
|
||||||
|
_lastHotReloadCheckTick = tick;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
std::future<void> ScriptEngine::Eval(const std::string &s)
|
std::future<void> ScriptEngine::Eval(const std::string &s)
|
||||||
|
|||||||
@@ -36,6 +36,7 @@ namespace OpenRCT2::Scripting
|
|||||||
duk_context * _context{};
|
duk_context * _context{};
|
||||||
std::queue<std::tuple<std::promise<void>, std::string>> _evalQueue;
|
std::queue<std::tuple<std::promise<void>, std::string>> _evalQueue;
|
||||||
std::vector<Plugin> _plugins;
|
std::vector<Plugin> _plugins;
|
||||||
|
uint32 _lastHotReloadCheckTick{};
|
||||||
|
|
||||||
public:
|
public:
|
||||||
ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env);
|
ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env);
|
||||||
@@ -49,5 +50,6 @@ namespace OpenRCT2::Scripting
|
|||||||
void Initialise();
|
void Initialise();
|
||||||
void LoadPlugins();
|
void LoadPlugins();
|
||||||
void StartPlugins();
|
void StartPlugins();
|
||||||
|
void AutoReloadPlugins();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user