feat(solver): implement SolverRegistry with plugin loading (#293)
Phase 1b of the pluggable solver system. Converts KCSolve from a header-only INTERFACE target to a SHARED library and implements the SolverRegistry with dynamic plugin discovery. Changes: - Add KCSolveGlobal.h export macro header (KCSolveExport) - Move SolverRegistry method bodies from header to SolverRegistry.cpp - Implement scan() with dlopen/LoadLibrary plugin loading - Add scan_default_paths() for KCSOLVE_PLUGIN_PATH + system paths - Plugin entry points: kcsolve_api_version() + kcsolve_create() - API version checking (major version compatibility) - Convert CMakeLists.txt from INTERFACE to SHARED library - Link FreeCADBase (PRIVATE) for Console logging - Link dl on POSIX for dynamic loading - Fix -Wmissing-field-initializers warnings in IKCSolver.h defaults The registry discovers plugins by scanning directories for shared libraries that export the kcsolve C entry points. Plugins are validated for API version compatibility before registration. Manual registration via register_solver() remains available for built-in solvers (e.g. OndselAdapter in Phase 1c).
This commit is contained in:
@@ -32,6 +32,7 @@
|
||||
#include <vector>
|
||||
|
||||
#include "IKCSolver.h"
|
||||
#include "KCSolveGlobal.h"
|
||||
|
||||
namespace KCSolve
|
||||
{
|
||||
@@ -39,6 +40,9 @@ namespace KCSolve
|
||||
/// Factory function that creates a solver instance.
|
||||
using CreateSolverFn = std::function<std::unique_ptr<IKCSolver>()>;
|
||||
|
||||
/// Current KCSolve API major version. Plugins must match this to load.
|
||||
constexpr int API_VERSION_MAJOR = 1;
|
||||
|
||||
/// Singleton registry for pluggable solver backends.
|
||||
///
|
||||
/// Solver plugins register themselves at module load time via
|
||||
@@ -55,108 +59,64 @@ using CreateSolverFn = std::function<std::unique_ptr<IKCSolver>()>;
|
||||
/// auto solver = KCSolve::SolverRegistry::instance().get(); // default
|
||||
/// auto solver = KCSolve::SolverRegistry::instance().get("ondsel");
|
||||
|
||||
class SolverRegistry
|
||||
class KCSolveExport SolverRegistry
|
||||
{
|
||||
public:
|
||||
/// Access the singleton instance.
|
||||
static SolverRegistry& instance()
|
||||
{
|
||||
static SolverRegistry reg;
|
||||
return reg;
|
||||
}
|
||||
static SolverRegistry& instance();
|
||||
|
||||
~SolverRegistry();
|
||||
|
||||
/// Register a solver backend.
|
||||
/// @param name Unique solver name (e.g. "ondsel").
|
||||
/// @param factory Factory function that creates solver instances.
|
||||
/// @return true if registration succeeded, false if name taken.
|
||||
bool register_solver(const std::string& name, CreateSolverFn factory)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
auto [it, inserted] = factories_.emplace(name, std::move(factory));
|
||||
if (inserted && default_name_.empty()) {
|
||||
default_name_ = name; // first registered becomes default
|
||||
}
|
||||
return inserted;
|
||||
}
|
||||
bool register_solver(const std::string& name, CreateSolverFn factory);
|
||||
|
||||
/// Create an instance of the named solver.
|
||||
/// @param name Solver name. If empty, uses the default solver.
|
||||
/// @return Solver instance, or nullptr if not found.
|
||||
std::unique_ptr<IKCSolver> get(const std::string& name = {}) const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
const std::string& key = name.empty() ? default_name_ : name;
|
||||
if (key.empty()) {
|
||||
return nullptr;
|
||||
}
|
||||
auto it = factories_.find(key);
|
||||
if (it == factories_.end()) {
|
||||
return nullptr;
|
||||
}
|
||||
return it->second();
|
||||
}
|
||||
std::unique_ptr<IKCSolver> get(const std::string& name = {}) const;
|
||||
|
||||
/// Return the names of all registered solvers.
|
||||
std::vector<std::string> available() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
std::vector<std::string> names;
|
||||
names.reserve(factories_.size());
|
||||
for (const auto& [name, _] : factories_) {
|
||||
names.push_back(name);
|
||||
}
|
||||
return names;
|
||||
}
|
||||
std::vector<std::string> available() const;
|
||||
|
||||
/// Query which BaseJointKind values a named solver supports.
|
||||
/// Creates a temporary instance to call supported_joints().
|
||||
std::vector<BaseJointKind> joints_for(const std::string& name) const
|
||||
{
|
||||
auto solver = get(name);
|
||||
if (!solver) {
|
||||
return {};
|
||||
}
|
||||
return solver->supported_joints();
|
||||
}
|
||||
std::vector<BaseJointKind> joints_for(const std::string& name) const;
|
||||
|
||||
/// Set the default solver name.
|
||||
/// @return true if the name is registered, false otherwise.
|
||||
bool set_default(const std::string& name)
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
if (factories_.find(name) == factories_.end()) {
|
||||
return false;
|
||||
}
|
||||
default_name_ = name;
|
||||
return true;
|
||||
}
|
||||
bool set_default(const std::string& name);
|
||||
|
||||
/// Get the default solver name.
|
||||
std::string get_default() const
|
||||
{
|
||||
std::lock_guard<std::mutex> lock(mutex_);
|
||||
return default_name_;
|
||||
}
|
||||
std::string get_default() const;
|
||||
|
||||
/// Scan a directory for solver plugins (Phase 1b).
|
||||
/// Currently a no-op placeholder. Will dlopen/LoadLibrary shared
|
||||
/// objects that export kcsolve_create() / kcsolve_api_version().
|
||||
void scan(const std::string& /*directory*/)
|
||||
{
|
||||
}
|
||||
/// Scan a directory for solver plugin shared libraries.
|
||||
/// Each plugin must export kcsolve_api_version() and kcsolve_create().
|
||||
/// Non-existent or empty directories are handled gracefully.
|
||||
void scan(const std::string& directory);
|
||||
|
||||
/// Scan all default plugin discovery paths:
|
||||
/// 1. KCSOLVE_PLUGIN_PATH env var (colon-separated, semicolon on Windows)
|
||||
/// 2. <install_prefix>/lib/kcsolve/
|
||||
void scan_default_paths();
|
||||
|
||||
private:
|
||||
SolverRegistry() = default;
|
||||
~SolverRegistry() = default;
|
||||
SolverRegistry();
|
||||
|
||||
SolverRegistry(const SolverRegistry&) = delete;
|
||||
SolverRegistry& operator=(const SolverRegistry&) = delete;
|
||||
SolverRegistry(SolverRegistry&&) = delete;
|
||||
SolverRegistry& operator=(SolverRegistry&&) = delete;
|
||||
|
||||
/// Close a single plugin handle (platform-specific).
|
||||
static void close_handle(void* handle);
|
||||
|
||||
mutable std::mutex mutex_;
|
||||
std::unordered_map<std::string, CreateSolverFn> factories_;
|
||||
std::string default_name_;
|
||||
std::vector<void*> handles_; // loaded plugin library handles
|
||||
};
|
||||
|
||||
} // namespace KCSolve
|
||||
|
||||
Reference in New Issue
Block a user