Files
create/src/Mod/Assembly/Solver/SolverRegistry.h
forbes 76b91c6597 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).
2026-02-19 16:07:37 -06:00

125 lines
5.0 KiB
C++

// SPDX-License-Identifier: LGPL-2.1-or-later
/****************************************************************************
* *
* Copyright (c) 2025 Kindred Systems <development@kindred-systems.com> *
* *
* This file is part of FreeCAD. *
* *
* FreeCAD is free software: you can redistribute it and/or modify it *
* under the terms of the GNU Lesser General Public License as *
* published by the Free Software Foundation, either version 2.1 of the *
* License, or (at your option) any later version. *
* *
* FreeCAD is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of *
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU *
* Lesser General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with FreeCAD. If not, see *
* <https://www.gnu.org/licenses/>. *
* *
***************************************************************************/
#ifndef KCSOLVE_SOLVERREGISTRY_H
#define KCSOLVE_SOLVERREGISTRY_H
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
#include "IKCSolver.h"
#include "KCSolveGlobal.h"
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
/// register_solver(). The Assembly module retrieves solvers via get().
///
/// Thread safety: all public methods are internally synchronized.
///
/// Usage:
/// // Registration (at module init):
/// KCSolve::SolverRegistry::instance().register_solver(
/// "ondsel", []() { return std::make_unique<OndselAdapter>(); });
///
/// // Retrieval:
/// auto solver = KCSolve::SolverRegistry::instance().get(); // default
/// auto solver = KCSolve::SolverRegistry::instance().get("ondsel");
class KCSolveExport SolverRegistry
{
public:
/// Access the singleton instance.
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);
/// 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;
/// Return the names of all registered solvers.
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;
/// Set the default solver name.
/// @return true if the name is registered, false otherwise.
bool set_default(const std::string& name);
/// Get the default solver name.
std::string get_default() const;
/// 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();
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
#endif // KCSOLVE_SOLVERREGISTRY_H