Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
181 changes: 138 additions & 43 deletions include/ppplugin/plugin_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -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 <boost/dll.hpp>

#include <chrono>
#include <condition_variable>
#include <filesystem>
#include <vector>
#include <map>
#include <mutex>
#include <thread>

namespace ppplugin {
template <typename... Plugins>
class GenericPluginManager {
static_assert(sizeof...(Plugins) > 0,
"Plugin manager requires at least one plugin type!");
template <bool enablePluginReload>
class PluginManagerTemplate {
public:
template <typename P>
class ReloadGuard {
public:
using PluginType = P;

public:
explicit ReloadGuard(std::unique_ptr<PluginType>* plugin)
: mutex_ { std::make_shared<std::mutex>() }
, 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<std::mutex> lock()
{
assert(mutex_);
return std::lock_guard<std::mutex> { *mutex_ };
}

private:
std::shared_ptr<std::mutex> mutex_;
std::unique_ptr<PluginType>* plugin_;
};
template <typename P>
using LoadedPlugin = std::conditional_t<enablePluginReload, ReloadGuard<P>, 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<LuaPlugin>
*/
[[nodiscard]] Expected<LuaPlugin, LoadError> loadLuaPlugin(
const std::filesystem::path& plugin_library_path)
{
return LuaPlugin::load(plugin_library_path);
}
[[nodiscard]] Expected<LoadedPlugin<LuaPlugin>, LoadError> loadLuaPlugin(
const std::filesystem::path& plugin_path);

/**
* Load Python script from given path.
*
* @note this is identical to load<PythonPlugin>
*/
[[nodiscard]] Expected<PythonPlugin, LoadError> loadPythonPlugin(
const std::filesystem::path& plugin_library_path)
{
return PythonPlugin::load(plugin_library_path);
}
[[nodiscard]] Expected<LoadedPlugin<PythonPlugin>, LoadError> loadPythonPlugin(
const std::filesystem::path& plugin_path);

/**
* Load POSIX shell script from given path.
*
* @note this is identical to load<ShellPlugin>
*/
[[nodiscard]] Expected<LoadedPlugin<ShellPlugin>, LoadError> loadShellPlugin(
const std::filesystem::path& plugin_path);

/**
* Load a C++ plugin from given library path.
Expand All @@ -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<CppPlugin>
*/
[[nodiscard]] Expected<CppPlugin, LoadError> loadCppPlugin(
const std::filesystem::path& plugin_library_path)
{
return CppPlugin::load(plugin_library_path);
}
[[nodiscard]] Expected<LoadedPlugin<CppPlugin>, 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<CPlugin>
*/
[[nodiscard]] Expected<LoadedPlugin<CPlugin>, LoadError> loadCPlugin(
const std::filesystem::path& plugin_path);

/**
* Load and register plugin from provided path.
*/
template <typename P>
[[nodiscard]] Expected<LoadedPlugin<P>, 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<CPlugin, LoadError> loadCPlugin(
const std::filesystem::path& plugin_library_path)
{
return CPlugin::load(plugin_library_path);
}
void monitorFiles();

private:
using PluginVariant = typename detail::templates::WrapParameterPack<std::variant, std::shared_ptr, Plugins...>::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<PluginVariant> plugins_;
static constexpr std::chrono::milliseconds defaultFileCheckInterval() { return std::chrono::milliseconds { 500 }; }

private:
/**
* Variant that may contain any plugin P as LoadedPlugin<P>.
*/
using LoadedAnyPlugin = detail::templates::WrapParameterPack<std::variant, LoadedPlugin, CPlugin, CppPlugin, LuaPlugin, PythonPlugin, ShellPlugin, NoopPlugin>;
/**
* 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<std::filesystem::path, std::unique_ptr<Plugin>> plugins_;
/**
* Keep copy of LoadedPlugin to access its mutex.
*/
std::unordered_map<Plugin*, LoadedAnyPlugin> 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<Plugin>;
using PluginManager = PluginManagerTemplate<false>;
using PluginReloadManager = PluginManagerTemplate<true>;
} // namespace ppplugin

#endif // PPPLUGIN_PLUGIN_MANAGER_H
1 change: 1 addition & 0 deletions src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -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"
Expand Down
99 changes: 99 additions & 0 deletions src/plugin_manager.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
#include "ppplugin/plugin_manager.h"

namespace ppplugin {
// convenience helper to simplify definition of member functions
template <bool enablePluginReload, typename P>
using LoadedPlugin = typename PluginManagerTemplate<enablePluginReload>::template LoadedPlugin<P>;

template class PluginManagerTemplate<true>;
template class PluginManagerTemplate<false>;

template <bool enablePluginReload>
PluginManagerTemplate<enablePluginReload>::PluginManagerTemplate(bool auto_reload, std::chrono::milliseconds file_check_interval)
: auto_reload_ { auto_reload }
, file_check_interval_ { file_check_interval }
{
}

template <bool enablePluginReload>
Expected<LoadedPlugin<enablePluginReload, LuaPlugin>, LoadError> PluginManagerTemplate<enablePluginReload>::loadLuaPlugin(
const std::filesystem::path& plugin_path)
{
return load<LuaPlugin>(plugin_path);
}

template <bool enablePluginReload>
Expected<LoadedPlugin<enablePluginReload, PythonPlugin>, LoadError> PluginManagerTemplate<enablePluginReload>::loadPythonPlugin(
const std::filesystem::path& plugin_path)
{
return load<PythonPlugin>(plugin_path);
}

template <bool enablePluginReload>
Expected<LoadedPlugin<enablePluginReload, ShellPlugin>, LoadError> PluginManagerTemplate<enablePluginReload>::loadShellPlugin(
const std::filesystem::path& plugin_path)
{
return load<ShellPlugin>(plugin_path);
}

template <bool enablePluginReload>
Expected<LoadedPlugin<enablePluginReload, CppPlugin>, LoadError> PluginManagerTemplate<enablePluginReload>::loadCppPlugin(
const std::filesystem::path& plugin_path)
{
return load<CppPlugin>(plugin_path);
}

template <bool enablePluginReload>
Expected<LoadedPlugin<enablePluginReload, CPlugin>, LoadError> PluginManagerTemplate<enablePluginReload>::loadCPlugin(
const std::filesystem::path& plugin_path)
{
return load<CPlugin>(plugin_path);
}

template <bool enablePluginReload>
template <typename P>
Expected<LoadedPlugin<enablePluginReload, P>, LoadError> PluginManagerTemplate<enablePluginReload>::load(const std::filesystem::path& plugin_path)
{
return P::load(plugin_path).andThen([&](auto&& new_plugin) {
auto plugin = std::make_unique<P>(std::forward<decltype(new_plugin)>(new_plugin));
auto it = plugins_.emplace(plugin_path, std::move(plugin));

LoadedPlugin<P> loaded_plugin { &it->second };
loaded_plugins_.emplace(it->second.get(), loaded_plugin);
return loaded_plugin;
});
}

template <bool enablePluginReload>
void PluginManagerTemplate<enablePluginReload>::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_v<decltype(loaded_plugin->second), int>);
std::visit([&](auto&& loaded_plugin) {
auto lock = loaded_plugin->second.lock();
if (auto reloaded_plugin = detail::templates::RemoveCvrefT<decltype(loaded_plugin->second)>::PluginType::load(plugin_path)) {
it->second = reloaded_plugin.value();
}
},
loaded_plugin->second);
}
}
}

template <bool enablePluginReload>
void PluginManagerTemplate<enablePluginReload>::monitorFiles()
{
while (!stop_file_checking_) {
std::unique_lock<std::mutex> lock { mutex_ };
file_check_wait_.wait_for(lock, file_check_interval_, [&]() {
return stop_file_checking_;
});
}
}
} // namespace ppplugin
Loading