From 8bb596c582f87f75aca03afa5e5740df8b17001f Mon Sep 17 00:00:00 2001 From: Tamino Bauknecht Date: Sun, 26 Apr 2026 19:09:27 +0200 Subject: [PATCH] first (non-working) draft --- include/ppplugin/plugin_manager.h | 181 +++++++++++++++++++++++------- src/CMakeLists.txt | 1 + src/plugin_manager.cpp | 99 ++++++++++++++++ 3 files changed, 238 insertions(+), 43 deletions(-) create mode 100644 src/plugin_manager.cpp diff --git a/include/ppplugin/plugin_manager.h b/include/ppplugin/plugin_manager.h index a330807..5d5dd4c 100644 --- a/include/ppplugin/plugin_manager.h +++ b/include/ppplugin/plugin_manager.h @@ -3,53 +3,94 @@ #include "ppplugin/c/plugin.h" #include "ppplugin/cpp/plugin.h" -#include "ppplugin/detail/template_helpers.h" #include "ppplugin/errors.h" #include "ppplugin/expected.h" #include "ppplugin/lua/plugin.h" #include "ppplugin/plugin.h" #include "ppplugin/python/plugin.h" +#include "ppplugin/shell/plugin.h" -#include - +#include +#include #include -#include +#include +#include +#include namespace ppplugin { -template -class GenericPluginManager { - static_assert(sizeof...(Plugins) > 0, - "Plugin manager requires at least one plugin type!"); +template +class PluginManagerTemplate { +public: + template + class ReloadGuard { + public: + using PluginType = P; + + public: + explicit ReloadGuard(std::unique_ptr* plugin) + : mutex_ { std::make_shared() } + , plugin_ { plugin } + { + } + + PluginType* operator->() + { + auto guard = lock(); + assert(plugin_); + return plugin_->get(); + } + const PluginType* operator->() const + { + auto guard = lock(); + assert(plugin_); + return plugin_->get(); + } + + [[nodiscard]] std::lock_guard lock() + { + assert(mutex_); + return std::lock_guard { *mutex_ }; + } + + private: + std::shared_ptr mutex_; + std::unique_ptr* plugin_; + }; + template + using LoadedPlugin = std::conditional_t, P>; public: - explicit GenericPluginManager(bool auto_reload = false) - : auto_reload_ { auto_reload } - { - } + explicit PluginManagerTemplate(bool auto_reload = false, std::chrono::milliseconds file_check_interval = defaultFileCheckInterval()); - virtual ~GenericPluginManager() = default; - GenericPluginManager(const GenericPluginManager&) = default; - GenericPluginManager(GenericPluginManager&&) noexcept = default; - GenericPluginManager& operator=(const GenericPluginManager&) = default; - GenericPluginManager& operator=(GenericPluginManager&&) noexcept = default; + virtual ~PluginManagerTemplate() = default; + PluginManagerTemplate(const PluginManagerTemplate&) = delete; + PluginManagerTemplate(PluginManagerTemplate&&) noexcept = delete; + PluginManagerTemplate& operator=(const PluginManagerTemplate&) = delete; + PluginManagerTemplate& operator=(PluginManagerTemplate&&) noexcept = delete; /** * Load Lua script from given path. + * + * @note this is identical to load */ - [[nodiscard]] Expected loadLuaPlugin( - const std::filesystem::path& plugin_library_path) - { - return LuaPlugin::load(plugin_library_path); - } + [[nodiscard]] Expected, LoadError> loadLuaPlugin( + const std::filesystem::path& plugin_path); /** * Load Python script from given path. + * + * @note this is identical to load */ - [[nodiscard]] Expected loadPythonPlugin( - const std::filesystem::path& plugin_library_path) - { - return PythonPlugin::load(plugin_library_path); - } + [[nodiscard]] Expected, LoadError> loadPythonPlugin( + const std::filesystem::path& plugin_path); + + /** + * Load POSIX shell script from given path. + * + * @note this is identical to load + */ + [[nodiscard]] Expected, LoadError> loadShellPlugin( + const std::filesystem::path& plugin_path); /** * Load a C++ plugin from given library path. @@ -59,36 +100,90 @@ class GenericPluginManager { * @attention The returned object has to be kept alive until all objects * created by the plugin are fully destroyed. * Failure to do so will result in a SEGFAULT. + * + * @note this is identical to load */ - [[nodiscard]] Expected loadCppPlugin( - const std::filesystem::path& plugin_library_path) - { - return CppPlugin::load(plugin_library_path); - } + [[nodiscard]] Expected, LoadError> loadCppPlugin( + const std::filesystem::path& plugin_path); + /** * Load a C plugin from given library path. * This plugin can call any C function or C++ functions that were marked * as 'extern "C"'. + * + * @note this is identical to load + */ + [[nodiscard]] Expected, LoadError> loadCPlugin( + const std::filesystem::path& plugin_path); + + /** + * Load and register plugin from provided path. + */ + template + [[nodiscard]] Expected, LoadError> load(const std::filesystem::path& plugin_path); + +private: + /** + * Reload all plugins associated with the given file path. + */ + void reloadPlugin(const std::filesystem::path& plugin_path); + + /** + * Continuously monitor filesystem for changes to any of the stored + * plugin files. */ - [[nodiscard]] Expected loadCPlugin( - const std::filesystem::path& plugin_library_path) - { - return CPlugin::load(plugin_library_path); - } + void monitorFiles(); private: - using PluginVariant = typename detail::templates::WrapParameterPack::Type; - // store for each plugin its shared library to avoid that the lib will be - // unloaded; this might cause segfaults when accessing the plugin - std::vector plugins_; + static constexpr std::chrono::milliseconds defaultFileCheckInterval() { return std::chrono::milliseconds { 500 }; } + +private: + /** + * Variant that may contain any plugin P as LoadedPlugin

. + */ + using LoadedAnyPlugin = detail::templates::WrapParameterPack; + /** + * Collection of all loaded plugins. + * Store a copy of each loaded plugin; this is used for auto-reloading and + * as a safeguard for C and C++ plugins to keep a reference to their shared libraries + * to avoid that the library will be unloaded; this might cause segfaults, e.g. + * because the destructors cannot be resolved anymore. + * + * @note any access to this member must be synchronized using mutex_ + */ + std::multimap> plugins_; + /** + * Keep copy of LoadedPlugin to access its mutex. + */ + std::unordered_map loaded_plugins_; /** * Auto-reload plugins on change on disk. */ - bool auto_reload_; // TODO: implement me :) + bool auto_reload_; + /** + * Thread to continuously observe the stored paths for changes. + */ + std::thread file_watch_thread_; + /** + * Interval of polling to detect file changes. + */ + std::chrono::milliseconds file_check_interval_; + /** + * Mutex protecting all members that are accessed by multiple threads. + */ + std::mutex mutex_; + std::condition_variable file_check_wait_; + /* + * Flag indicating if the monitoring of the filesystem should be stopped. + * + * @note any access to this member must be synchronized using mutex_ + */ + bool stop_file_checking_ { false }; }; -using PluginManager = GenericPluginManager; +using PluginManager = PluginManagerTemplate; +using PluginReloadManager = PluginManagerTemplate; } // namespace ppplugin #endif // PPPLUGIN_PLUGIN_MANAGER_H diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index 6713cbf..df70f66 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -1,6 +1,7 @@ set(LIBRARY_SOURCES "${CMAKE_SOURCE_DIR}/include/ppplugin.cpp" "boost_dll_loader.cpp" + "plugin_manager.cpp" "c/plugin.cpp" "cpp/plugin.cpp" "lua/plugin.cpp" diff --git a/src/plugin_manager.cpp b/src/plugin_manager.cpp new file mode 100644 index 0000000..6533166 --- /dev/null +++ b/src/plugin_manager.cpp @@ -0,0 +1,99 @@ +#include "ppplugin/plugin_manager.h" + +namespace ppplugin { +// convenience helper to simplify definition of member functions +template +using LoadedPlugin = typename PluginManagerTemplate::template LoadedPlugin

; + +template class PluginManagerTemplate; +template class PluginManagerTemplate; + +template +PluginManagerTemplate::PluginManagerTemplate(bool auto_reload, std::chrono::milliseconds file_check_interval) + : auto_reload_ { auto_reload } + , file_check_interval_ { file_check_interval } +{ +} + +template +Expected, LoadError> PluginManagerTemplate::loadLuaPlugin( + const std::filesystem::path& plugin_path) +{ + return load(plugin_path); +} + +template +Expected, LoadError> PluginManagerTemplate::loadPythonPlugin( + const std::filesystem::path& plugin_path) +{ + return load(plugin_path); +} + +template +Expected, LoadError> PluginManagerTemplate::loadShellPlugin( + const std::filesystem::path& plugin_path) +{ + return load(plugin_path); +} + +template +Expected, LoadError> PluginManagerTemplate::loadCppPlugin( + const std::filesystem::path& plugin_path) +{ + return load(plugin_path); +} + +template +Expected, LoadError> PluginManagerTemplate::loadCPlugin( + const std::filesystem::path& plugin_path) +{ + return load(plugin_path); +} + +template +template +Expected, LoadError> PluginManagerTemplate::load(const std::filesystem::path& plugin_path) +{ + return P::load(plugin_path).andThen([&](auto&& new_plugin) { + auto plugin = std::make_unique

(std::forward(new_plugin)); + auto it = plugins_.emplace(plugin_path, std::move(plugin)); + + LoadedPlugin

loaded_plugin { &it->second }; + loaded_plugins_.emplace(it->second.get(), loaded_plugin); + return loaded_plugin; + }); +} + +template +void PluginManagerTemplate::reloadPlugin(const std::filesystem::path& plugin_path) +{ + if constexpr (enablePluginReload) { + auto [begin, end] = plugins_.equal_range(plugin_path); + for (auto it = begin; it != end; ++it) { + auto loaded_plugin = loaded_plugins_.find(it->second.get()); + if (loaded_plugin == loaded_plugins_.end()) { + return; + } + static_assert(std::is_same_vsecond), int>); + std::visit([&](auto&& loaded_plugin) { + auto lock = loaded_plugin->second.lock(); + if (auto reloaded_plugin = detail::templates::RemoveCvrefTsecond)>::PluginType::load(plugin_path)) { + it->second = reloaded_plugin.value(); + } + }, + loaded_plugin->second); + } + } +} + +template +void PluginManagerTemplate::monitorFiles() +{ + while (!stop_file_checking_) { + std::unique_lock lock { mutex_ }; + file_check_wait_.wait_for(lock, file_check_interval_, [&]() { + return stop_file_checking_; + }); + } +} +} // namespace ppplugin