From 3556dead7436d65cae824ac0476f03f5f4635a87 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sun, 18 Mar 2018 17:43:47 +0000 Subject: [PATCH] Add hot reloading support --- src/openrct2/scripting/Plugin.cpp | 86 ++++++++++++++++++++++++- src/openrct2/scripting/Plugin.h | 21 +++++- src/openrct2/scripting/ScriptEngine.cpp | 28 ++++++++ src/openrct2/scripting/ScriptEngine.h | 2 + 4 files changed, 132 insertions(+), 5 deletions(-) diff --git a/src/openrct2/scripting/Plugin.cpp b/src/openrct2/scripting/Plugin.cpp index 0da56f3005..8ab7954dd3 100644 --- a/src/openrct2/scripting/Plugin.cpp +++ b/src/openrct2/scripting/Plugin.cpp @@ -21,6 +21,11 @@ #include #include +#include +#include +#include +#include + 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 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; diff --git a/src/openrct2/scripting/Plugin.h b/src/openrct2/scripting/Plugin.h index 207eff783a..675a985308 100644 --- a/src/openrct2/scripting/Plugin.h +++ b/src/openrct2/scripting/Plugin.h @@ -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); diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index bed35d9ca2..3e940fe70f 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -11,6 +11,7 @@ #include "../core/FileScanner.h" #include "../core/Path.hpp" #include "../interface/InteractiveConsole.h" +#include "../platform/Platform2.h" #include "../PlatformEnvironment.h" #include #include @@ -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 ScriptEngine::Eval(const std::string &s) diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index c978703b29..2aaca3195a 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -36,6 +36,7 @@ namespace OpenRCT2::Scripting duk_context * _context{}; std::queue, std::string>> _evalQueue; std::vector _plugins; + uint32 _lastHotReloadCheckTick{}; public: ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env); @@ -49,5 +50,6 @@ namespace OpenRCT2::Scripting void Initialise(); void LoadPlugins(); void StartPlugins(); + void AutoReloadPlugins(); }; }