From 1fd33dd86af01a3ed9561f5f7f697ed7e49efa34 Mon Sep 17 00:00:00 2001 From: Ted John Date: Sat, 31 Mar 2018 00:08:59 +0100 Subject: [PATCH] Use new FileWatcher class --- src/openrct2/core/FileWatcher.cpp | 122 +++++++++++++++++++----- src/openrct2/core/FileWatcher.h | 26 ++++- src/openrct2/scripting/Plugin.h | 2 + src/openrct2/scripting/ScriptEngine.cpp | 43 ++++++--- src/openrct2/scripting/ScriptEngine.h | 8 ++ 5 files changed, 164 insertions(+), 37 deletions(-) diff --git a/src/openrct2/core/FileWatcher.cpp b/src/openrct2/core/FileWatcher.cpp index 7429ea8c97..a9b7d853a7 100644 --- a/src/openrct2/core/FileWatcher.cpp +++ b/src/openrct2/core/FileWatcher.cpp @@ -1,5 +1,6 @@ #include #include +#include #ifdef _WIN32 #define WIN32_LEAN_AND_MEAN @@ -14,6 +15,65 @@ #include "../core/String.hpp" #include "FileWatcher.h" +namespace fs = std::experimental::filesystem; + +#ifndef _WIN32 +FileWatcher::FileDescriptor::~FileDescriptor() +{ + Close(); +} + +void FileWatcher::FileDescriptor::Initialise() +{ + int 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); + + Fd = fd; + log_verbose("FileWatcher: inotify_init succeeded"); + } + else + { + log_verbose("FileWatcher: inotify_init failed"); + throw std::runtime_error("inotify_init failed"); + } +} + +void FileWatcher::FileDescriptor::Close() +{ + if (Fd != -1) + { + close(Fd); + Fd = -1; + } +} + +FileWatcher::WatchDescriptor::WatchDescriptor(int fd, const std::string& path) + : Fd(fd), + Wd(inotify_add_watch(fd, path.c_str(), IN_CLOSE_WRITE)), + Path(path) +{ + if (Wd >= 0) + { + log_verbose("FileWatcher: inotify watch added for %s", path.c_str()); + } + else + { + log_verbose("FileWatcher: inotify_add_watch failed for %s", path.c_str()); + throw std::runtime_error("inotify_add_watch failed for '" + path + "'"); + } +} + +FileWatcher::WatchDescriptor::~WatchDescriptor() +{ + inotify_rm_watch(Fd, Wd); + log_verbose("FileWatcher: inotify watch removed"); +} +#endif + FileWatcher::FileWatcher(const std::string &directoryPath) { #ifdef _WIN32 @@ -23,19 +83,13 @@ FileWatcher::FileWatcher(const std::string &directoryPath) throw std::runtime_error("Unable to open directory '" + directoryPath + "'"); } #else - auto fd = inotify_init(); - if (fd >= 0) + _fileDesc.Initialise(); + _watchDescs.emplace_back(_fileDesc.Fd, directoryPath); + for (auto& p : fs::recursive_directory_iterator(directoryPath)) { - auto wd = inotify_add_watch(fd, directoryPath.c_str(), IN_CLOSE_WRITE); - if (wd >= 0) + if (p.status().type() == fs::file_type::directory) { - _fileDesc = fd; - _watchDesc = wd; - } - else - { - close(fd); - throw std::runtime_error("Unable to watch directory '" + directoryPath + "'"); + _watchDescs.emplace_back(_fileDesc.Fd, p.path().string()); } } #endif @@ -48,8 +102,8 @@ FileWatcher::~FileWatcher() CancelIoEx(_directoryHandle, nullptr); CloseHandle(_directoryHandle); #else - inotify_rm_watch(_fileDesc, _watchDesc); - close(_fileDesc); + _finished = true; + _fileDesc.Close(); #endif _watchThread.join(); } @@ -79,24 +133,46 @@ void FileWatcher::WatchDirectory() } } #else + log_verbose("FileWatcher: reading event data..."); std::array eventData; - auto length = read(_fileDesc, eventData.data(), eventData.size()); - if (length >= 0) + while (!_finished) { - auto onFileChanged = OnFileChanged; - if (onFileChanged) + int length = read(_fileDesc.Fd, eventData.data(), eventData.size()); + if (length >= 0) { - int offset = 0; - while (offset < length) + log_verbose("FileWatcher: inotify event data received"); + auto onFileChanged = OnFileChanged; + if (onFileChanged) { - auto e = (inotify_event*)(eventData.data() + offset); - if ((e->mask & IN_CLOSE_WRITE) && !(e->mask & IN_ISDIR)) + int offset = 0; + while (offset < length) { - onFileChanged(e->name); + auto e = (inotify_event*)(eventData.data() + offset); + if ((e->mask & IN_CLOSE_WRITE) && !(e->mask & IN_ISDIR)) + { + log_verbose("FileWatcher: inotify event received for %s", e->name); + + // Find watch descriptor + int wd = e->wd; + auto findResult = std::find_if(_watchDescs.begin(), _watchDescs.end(), + [wd](const WatchDescriptor& watchDesc) + { + return wd == watchDesc.Wd; + }); + if (findResult != _watchDescs.end()) + { + auto directory = findResult->Path; + auto path = fs::path(directory) / fs::path(e->name); + onFileChanged(path); + } + } + offset += sizeof(inotify_event) + e->len; } - offset += sizeof(inotify_event) + e->len; } } + + // Sleep for 1/2 second + usleep(500000); } #endif } diff --git a/src/openrct2/core/FileWatcher.h b/src/openrct2/core/FileWatcher.h index 9f923e9e15..0c1dc23750 100644 --- a/src/openrct2/core/FileWatcher.h +++ b/src/openrct2/core/FileWatcher.h @@ -3,6 +3,7 @@ #include #include #include +#include #ifdef _WIN32 typedef void * HANDLE; @@ -18,8 +19,27 @@ private: #ifdef _WIN32 HANDLE _directoryHandle{}; #else - int _fileDesc{}; - int _watchDesc{}; + struct FileDescriptor + { + int Fd = -1; + + ~FileDescriptor(); + void Initialise(); + void Close(); + }; + + struct WatchDescriptor + { + int const Fd; + int const Wd; + std::string const Path; + + WatchDescriptor(int fd, const std::string& path); + ~WatchDescriptor(); + }; + + FileDescriptor _fileDesc; + std::vector _watchDescs; #endif public: @@ -29,5 +49,7 @@ public: ~FileWatcher(); private: + bool _finished{}; + void WatchDirectory(); }; diff --git a/src/openrct2/scripting/Plugin.h b/src/openrct2/scripting/Plugin.h index d14d125c3a..21daf557cb 100644 --- a/src/openrct2/scripting/Plugin.h +++ b/src/openrct2/scripting/Plugin.h @@ -47,6 +47,8 @@ namespace OpenRCT2::Scripting bool _hotReloadEnabled{}; public: + std::string GetPath() const { return _path; }; + Plugin() { } Plugin(duk_context * context, const std::string &path); Plugin(const Plugin&) = delete; diff --git a/src/openrct2/scripting/ScriptEngine.cpp b/src/openrct2/scripting/ScriptEngine.cpp index 82ba255bf3..a0f4e7cbdc 100644 --- a/src/openrct2/scripting/ScriptEngine.cpp +++ b/src/openrct2/scripting/ScriptEngine.cpp @@ -86,7 +86,6 @@ void ScriptEngine::LoadPlugins() auto plugin = std::make_shared(_context, path); ScriptExecutionInfo::PluginScope scope(_execInfo, plugin); plugin->Load(); - plugin->EnableHotReload(); _plugins.push_back(std::move(plugin)); } catch (const std::exception &e) @@ -94,27 +93,47 @@ void ScriptEngine::LoadPlugins() _console.WriteLineError(e.what()); } } + + // Enable hot reloading + _pluginFileWatcher = std::make_unique(base); + _pluginFileWatcher->OnFileChanged = + [this](const std::string &path) + { + std::lock_guard guard(_changedPluginFilesMutex); + _changedPluginFiles.push_back(path); + }; } void ScriptEngine::AutoReloadPlugins() { - for (auto& plugin : _plugins) + if (_changedPluginFiles.size() > 0) { - if (plugin->ShouldHotReload()) + std::lock_guard guard(_changedPluginFilesMutex); + for (auto& path : _changedPluginFiles) { - try + auto findResult = std::find_if(_plugins.begin(), _plugins.end(), + [&path](const std::shared_ptr& plugin) + { + return path == plugin->GetPath(); + }); + if (findResult != _plugins.end()) { - _hookEngine.UnsubscribeAll(plugin); + auto& plugin = *findResult; + try + { + _hookEngine.UnsubscribeAll(plugin); - ScriptExecutionInfo::PluginScope scope(_execInfo, plugin); - plugin->Load(); - plugin->Start(); - } - catch (const std::exception &e) - { - _console.WriteLineError(e.what()); + ScriptExecutionInfo::PluginScope scope(_execInfo, plugin); + plugin->Load(); + plugin->Start(); + } + catch (const std::exception &e) + { + _console.WriteLineError(e.what()); + } } } + _changedPluginFiles.clear(); } } diff --git a/src/openrct2/scripting/ScriptEngine.h b/src/openrct2/scripting/ScriptEngine.h index 5a11d519e5..5b808ba356 100644 --- a/src/openrct2/scripting/ScriptEngine.h +++ b/src/openrct2/scripting/ScriptEngine.h @@ -10,16 +10,20 @@ #pragma once #include "../common.h" +#include "../core/FileWatcher.h" #include "HookEngine.h" #include "Plugin.h" #include #include +#include #include #include +#include struct duk_hthread; typedef struct duk_hthread duk_context; +class FileWatcher; class InteractiveConsole; namespace OpenRCT2 @@ -88,6 +92,10 @@ namespace OpenRCT2::Scripting HookEngine _hookEngine; ScriptExecutionInfo _execInfo; + std::unique_ptr _pluginFileWatcher; + std::vector _changedPluginFiles; + std::mutex _changedPluginFilesMutex; + public: ScriptEngine(InteractiveConsole& console, IPlatformEnvironment& env); ScriptEngine(ScriptEngine&) = delete;