A simple scripting language in C++
Ferenc Szontágh
2025-04-18 c91e935c62b8e254b9daadf37b915c983518bff4
src/Modules/ModuleManager.hpp
@@ -2,14 +2,25 @@
#ifndef MODULES_MODULEMANAGER_HPP
#define MODULES_MODULEMANAGER_HPP
#include <memory>
#include <vector>
#include "BaseModule.hpp"
#include <filesystem>
#include <functional>
#include <memory>
#include <stdexcept>
#include <string>
#include <unordered_map>
#include <vector>
#include "BaseModule.hpp"
#include "Symbols/Value.hpp"
#ifndef _WIN32
#    include <dlfcn.h>
#else
#    include <windows.h>
#endif
namespace Modules {
using CallbackFunction = std::function<Symbols::Value(const std::vector<Symbols::Value> &)>;
/**
 * @brief Manager for registering and invoking modules.
@@ -19,7 +30,7 @@
    /**
     * @brief Get singleton instance of ModuleManager.
     */
    static ModuleManager &instance() {
    static ModuleManager & instance() {
        static ModuleManager mgr;
        return mgr;
    }
@@ -28,55 +39,138 @@
     * @brief Add a module to the manager.
     * @param module Unique pointer to a BaseModule.
     */
    void addModule(std::unique_ptr<BaseModule> module) {
        modules_.push_back(std::move(module));
    }
    void addModule(std::unique_ptr<BaseModule> module) { modules_.push_back(std::move(module)); }
    /**
     * @brief Invoke all registered modules to register their symbols.
     */
    void registerAll() {
        for (const auto &module : modules_) {
        for (const auto & module : modules_) {
            module->registerModule();
        }
    }
  private:
    ModuleManager() = default;
    std::vector<std::unique_ptr<BaseModule>> modules_;
    std::vector<std::unique_ptr<BaseModule>>                                                            modules_;
    // Built-in function callbacks: name -> function
    std::unordered_map<std::string,
        std::function<Symbols::Value(const std::vector<Symbols::Value>&)>> callbacks_;
    std::unordered_map<std::string, std::function<Symbols::Value(const std::vector<Symbols::Value> &)>> callbacks_;
    // Plugin handles for dynamically loaded modules
    std::vector<void *>                                                                                 pluginHandles_;
  public:
    /**
     * @brief Register a built-in function callback.
     * @param name Name of the function.
     * @param cb Callable taking argument values and returning a Value.
     */
    void registerFunction(const std::string &name,
                          std::function<Symbols::Value(const std::vector<Symbols::Value>&)> cb) {
    void registerFunction(const std::string &                                                name,
                          std::function<Symbols::Value(const std::vector<Symbols::Value> &)> cb) {
        callbacks_[name] = std::move(cb);
    }
    /**
     * @brief Check if a built-in function is registered.
     */
    bool hasFunction(const std::string &name) const {
        return callbacks_.find(name) != callbacks_.end();
    }
    bool hasFunction(const std::string & name) const { return callbacks_.find(name) != callbacks_.end(); }
    /**
     * @brief Call a built-in function callback.
     */
    Symbols::Value callFunction(const std::string &name,
                                const std::vector<Symbols::Value> &args) const {
    Symbols::Value callFunction(const std::string & name, const std::vector<Symbols::Value> & args) const {
        auto it = callbacks_.find(name);
        if (it == callbacks_.end()) {
            throw std::runtime_error("Built-in function callback not found: " + name);
        }
        return it->second(args);
    }
    /**
     * @brief Load all plugin modules from specified directory.
     * @param directory Path to directory containing plugin shared libraries.
     */
    void loadPlugins(const std::string & directory) {
        namespace fs = std::filesystem;
        if (!fs::exists(directory) || !fs::is_directory(directory)) {
            return;
        }
        // Recursively search for plugin shared libraries
        for (const auto & entry : fs::recursive_directory_iterator(directory)) {
            if (!entry.is_regular_file()) {
                continue;
            }
#ifdef _WIN32
            if (entry.path().extension() == ".dll") {
#else
            if (entry.path().extension() == ".so") {
#endif
                loadPlugin(entry.path().string());
            }
        }
    }
    /**
     * @brief Load a single plugin module from shared library.
     * @param path Filesystem path to the shared library.
     */
    void loadPlugin(const std::string & path) {
#ifndef _WIN32
        void * handle = dlopen(path.c_str(), RTLD_NOW);
        if (!handle) {
            throw std::runtime_error("Failed to load module: " + path + ": " + dlerror());
        }
#else
        HMODULE handle = LoadLibraryA(path.c_str());
        if (!handle) {
            throw std::runtime_error("Failed to load module: " + path);
        }
#endif
        pluginHandles_.push_back(handle);
#ifndef _WIN32
        dlerror();  // clear any existing error
        using PluginInitFunc    = void (*)();
        auto         initFunc   = reinterpret_cast<PluginInitFunc>(dlsym(handle, "plugin_init"));
        const char * dlsymError = dlerror();
        if (dlsymError) {
            dlclose(handle);
            pluginHandles_.pop_back();
            throw std::runtime_error("Cannot find symbol 'plugin_init' in " + path + ": " + dlsymError);
        }
        initFunc();
#else
        using PluginInitFunc = void(__cdecl *)();
        auto initFunc        = reinterpret_cast<PluginInitFunc>(GetProcAddress(handle, "plugin_init"));
        if (!initFunc) {
            FreeLibrary(handle);
            pluginHandles_.pop_back();
            throw std::runtime_error("Cannot find symbol 'plugin_init' in " + path);
        }
        initFunc();
#endif
    }
    /**
     * @brief Destructor unloads modules and plugin libraries in safe order.
     * Modules (and their callback functions) are destroyed before the libraries are unloaded,
     * ensuring that destructors (in plugin code) run while the libraries are still mapped.
     */
    ~ModuleManager() {
        // Destroy module instances (may call code in plugin libraries)
        modules_.clear();
        // Clear callback functions (may refer to plugin code)
        callbacks_.clear();
        // Unload all dynamically loaded plugin libraries
        for (auto handle : pluginHandles_) {
#ifndef _WIN32
            dlclose(handle);
#else
            FreeLibrary((HMODULE) handle);
#endif
        }
        // Clear handles
        pluginHandles_.clear();
    }
};
} // namespace Modules
#endif // MODULES_MODULEMANAGER_HPP
}  // namespace Modules
#endif  // MODULES_MODULEMANAGER_HPP