Architecture Overview --------------------- ``rgpot`` follows a *metatensor*-style layered architecture: a Rust core defines the fundamental types and RPC infrastructure, exposed via a C ABI (generated by ``cbindgen``), with hand-written C++ RAII wrappers on top. Existing C++ potentials remain callable through an FFI callback mechanism. Layer Diagram ~~~~~~~~~~~~~ :: +---------------------------------------------+ | C++ Consumer Layer (RAII wrappers) | | include/rgpot/*.hpp | +---------------------------------------------+ | C ABI (generated by cbindgen) | | rgpot-core/include/rgpot.h | +---------------------------------------------+ | Rust Core | | rgpot-core/src/ | | - Types (ForceInput, ForceOut) | | - Potential trait + callback dispatch | | - Cap'n Proto RPC client/server | | - Error handling (status codes + TLS msg) | +---------------------------------------------+ | FFI callbacks +---------------------------------------------+ | Existing C++ Potentials (LJ, CuH2) | | CppCore/rgpot/LennardJones/, CuH2/ | +---------------------------------------------+ Layers in Detail ~~~~~~~~~~~~~~~~ Rust Core (``rgpot-core/``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^ The Rust crate is the single source of truth for the public API. It defines: ``rgpot_force_input_t`` and ``rgpot_force_out_t`` ``#[repr(C)]`` structs mirroring the legacy C++ ``ForceInput`` / ``ForceOut`` from ``ForceStructs.hpp``. These are the canonical data exchange types across the FFI boundary. ``rgpot_status_t`` An enum of status codes (``RGPOT_SUCCESS``, ``RGPOT_INVALID_PARAMETER``, ``RGPOT_INTERNAL_ERROR``, ``RGPOT_RPC_ERROR``, ``RGPOT_BUFFER_SIZE_ERROR``). Every ``extern "C"`` function returns one of these. ``rgpot_potential_t`` An opaque handle wrapping a C function pointer callback plus a ``void* user_data``. This is how C++ potentials are registered into the Rust core without the core knowing the concrete type. ``rgpot_last_error()`` Thread-local last error message. On any non-success return, the caller can retrieve a human-readable string describing the failure. ``catch_unwind`` wrapper Every ``extern "C"`` function boundary catches Rust panics and converts them to ``RGPOT_INTERNAL_ERROR`` plus an error message. This prevents undefined behaviour from unwinding across the FFI boundary. Feature Gates ::::::::::::: The crate uses Cargo feature flags to keep the core minimal: ``rpc`` Enables Cap'n Proto RPC client and server. Adds dependencies on ``capnp``, ``capnp-rpc``, and ``tokio``. The RPC module reuses the existing ``Potentials.capnp`` schema shared with the C++ server. ``cache`` Reserved for future caching integration. ``gen-header`` Enables C header generation via ``cbindgen``. Used only during development; run ``pixi r gen-header`` to regenerate ``include/rgpot.h``. Cap'n Proto Schema Sharing :::::::::::::::::::::::::: The Cap'n Proto schema (``Potentials.capnp``) is bundled inside the Rust crate at ``rgpot-core/schema/Potentials.capnp``. This is the canonical copy used when building from ``crates.io``. In the monorepo, ``build.rs`` also checks ``CppCore/rgpot/rpc/Potentials.capnp`` as a fallback. The Cap'n Proto wire format is stable across language implementations, so Rust clients and C++ servers (or vice versa) are fully interoperable. C ABI (``rgpot-core/include/rgpot.h``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ The C header is auto-generated by ``cbindgen`` from the Rust source. Run ``pixi r gen-header`` (which invokes ``cargo build --features gen-header``) to regenerate it. This header is the contract between the Rust core and all C/C++ consumers. It contains: - Type definitions for all ``#[repr(C)]`` structs and enums. - Forward declarations for opaque handles (``rgpot_potential_t``, ``rgpot_rpc_client_t``). - Function declarations for the entire public API. - Doxygen-style documentation extracted from Rust doc comments. - ``#ifdef RGPOT_HAS_RPC`` guards around RPC-specific declarations. This header is never edited by hand; any changes to the public API are made in the Rust source and regenerated. C++ RAII Wrappers (``include/rgpot/``) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ Hand-written C++ headers provide idiomatic wrappers around the C API: ``errors.hpp`` Maps ``rgpot_status_t`` codes to ``rgpot::Error`` exceptions via ``details::check_status()``. Internally calls ``rgpot_last_error()`` to populate the exception message. ``types.hpp`` Provides ``rgpot::InputSpec`` (a lightweight view over borrowed input data) and ``rgpot::CalcResult`` (owns the force buffer and stores energy/variance). These are distinct from the legacy ``rgpot::ForceInput`` / ``rgpot::ForceOut`` types in ``ForceStructs.hpp`` to avoid name collisions. ``potential.hpp`` The ``rgpot::PotentialHandle`` class is a move-only RAII wrapper around ``rgpot_potential_t*``. Key methods: ``calculate(const InputSpec&)`` Invokes the potential and returns a ``CalcResult``. Throws ``rgpot::Error`` on failure. ``from_impl(impl)`` Template factory that wraps any C++ potential object with a compatible ``forceImpl`` method. Generates a type-safe trampoline function automatically. ``from_callback(fn, user_data, free_fn)`` Low-level factory taking an explicit C function pointer. ``rpc_client.hpp`` RAII wrapper around ``rgpot_rpc_client_t*``. Connects to a remote server on construction and provides ``calculate()`` with the same ``InputSpec`` / ``CalcResult`` interface. Guarded by ``#ifdef RGPOT_HAS_RPC``. ``rgpot.hpp`` Aggregate header that includes everything. Existing C++ Potentials ^^^^^^^^^^^^^^^^^^^^^^^ The ``CppCore/rgpot/`` directory contains the original potential implementations (``LJPot``, ``CuH2Pot``) using the CRTP-based ``Potential`` template. These remain fully functional and unmodified. To use them through the new API, pass them to ``PotentialHandle::from_impl<>()``, which generates a type-safe trampoline that converts between the C ABI structs and the legacy ``ForceInput`` / ``ForceOut`` types automatically. Error Handling Conventions ~~~~~~~~~~~~~~~~~~~~~~~~~~ The project uses a two-tier error strategy: 1. **Across the FFI boundary** \:\: Status codes (``rgpot_status_t``) plus a thread-local error message (``rgpot_last_error()``). No exceptions cross FFI. Every Rust ``extern "C"`` function wraps its body in ``catch_unwind`` to convert panics to ``RGPOT_INTERNAL_ERROR``. 2. **Within C++** \:\: The RAII wrappers call ``details::check_status()`` which maps non-success codes to ``rgpot::Error`` exceptions. C++ consumers work with exceptions as usual. 3. **Within Rust** \:\: Standard ``Result`` types internally, converted to status codes at the ``extern "C"`` boundary. Adding a New Potential ~~~~~~~~~~~~~~~~~~~~~~ To register a new C++ potential with the Rust core: 1. Implement the potential class inheriting from ``Potential`` as usual. The class must provide ``void forceImpl(const ForceInput&, ForceOut*) const``. 2. Use the RAII wrapper to create a handle: .. code:: cpp #include "rgpot/potential.hpp" #include "my_pot/MyPot.hpp" rgpot::MyPot my_pot; auto handle = rgpot::PotentialHandle::from_impl(my_pot); auto result = handle.calculate(input_spec); double energy = result.energy(); 1. Alternatively, write a standalone ``extern "C"`` trampoline and call ``rgpot_potential_new()`` directly from C: .. code:: c rgpot_status_t my_trampoline(void *ud, const rgpot_force_input_t *in, rgpot_force_out_t *out) { /* ... forward to your implementation ... */ return RGPOT_SUCCESS; } rgpot_potential_t *pot = rgpot_potential_new(my_trampoline, my_data, NULL); Build System Integration ~~~~~~~~~~~~~~~~~~~~~~~~ Meson ^^^^^ The Rust crate is built via a ``custom_target`` in ``meson.build``, gated behind the ``with_rust_core`` option: .. code:: bash meson setup bbdir -Dwith_rust_core=true meson compile -C bbdir Meson invokes ``cargo build`` and links the resulting ``librgpot_core.a`` into the project. The feature flags (``rpc``, ``cache``) are forwarded from the corresponding Meson options. Standalone Rust ^^^^^^^^^^^^^^^ The crate can also be built and tested independently: .. code:: bash cd rgpot-core cargo build cargo test cargo build --features rpc # with RPC support Directory Reference ~~~~~~~~~~~~~~~~~~~ .. table:: +----------------------------------------+--------------------------------------------------------------+ | Path | Purpose | +========================================+==============================================================+ | ``rgpot-core/Cargo.toml`` | Rust crate configuration. | +----------------------------------------+--------------------------------------------------------------+ | ``rgpot-core/build.rs`` | capnp compilation (+ cbindgen when ``gen-header`` enabled). | +----------------------------------------+--------------------------------------------------------------+ | ``rgpot-core/cbindgen.toml`` | cbindgen configuration. | +----------------------------------------+--------------------------------------------------------------+ | ``rgpot-core/src/`` | Rust source (types, status, potential, c\ :sub:`api`\, rpc). | +----------------------------------------+--------------------------------------------------------------+ | ``rgpot-core/include/rgpot.h`` | Auto-generated C header. | +----------------------------------------+--------------------------------------------------------------+ | ``include/rgpot/`` | Hand-written C++ RAII wrapper headers. | +----------------------------------------+--------------------------------------------------------------+ | ``CppCore/rgpot/`` | Original C++ potentials and helpers. | +----------------------------------------+--------------------------------------------------------------+ | ``rgpot-core/schema/Potentials.capnp`` | Bundled Cap'n Proto schema (canonical for crates.io). | +----------------------------------------+--------------------------------------------------------------+ | ``CppCore/rgpot/rpc/Potentials.capnp`` | Shared Cap'n Proto schema (monorepo fallback). | +----------------------------------------+--------------------------------------------------------------+