.. _reader-plugins-particle-page:

##########################################
Particle Reader Plugins
##########################################

The PtServer supports dynamic loading of reader plugins, allowing you to extend the server with custom
particle file format readers. A reader plugin acts as a provider of particle data to the server,
enabling in-house file formats to be served with the same ease of use and performance as the built-in
PTFX and VTP readers.

******************************
Particle Plugin Discovery
******************************

To use reader plugins with the PtServer, set the **CEW_PT_READER_PLUGIN_FOLDER** environment variable
to point to the folder containing your plugin shared libraries. The PtServer will scan this folder and
load all libraries matching the naming convention on startup.

Plugins must be named with the ``cpt_`` prefix (case-insensitive):

::

    cpt_{PLUGIN_NAME}.[dll|so|dylib]

Examples:

::

    cpt_MyCustomFormat.dll     (Windows)
    cpt_MyCustomFormat.so      (Linux)
    cpt_MyCustomFormat.dylib   (macOS)

The PtServer checks for duplicates and only loads files that match the required prefix and platform
extension. Subdirectories are not recursively scanned.

**********************************
Particle Reader Plugin API (C ABI)
**********************************

Reader plugins expose a single C entry point and register themselves with the framework via a callback.

**Required export:**

.. code-block:: c

    extern "C" CPT_PLUGIN_EXPORT CPT_PluginExitFunc initializeReaderPlugin(
        const CPT_FrameworkServices* frameworkServices);

The ``initializeReaderPlugin`` function is called once when the plugin is loaded. The plugin must:

1. Check API version compatibility (major version must match).
2. Optionally call ``setPluginInfoFunc`` to report plugin metadata (name, version, author).
3. Call ``registerReaderFunc`` to register one or more reader factories.
4. Return a cleanup function (or ``nullptr`` if no cleanup is needed).

**Framework services provided to the plugin:**

.. code-block:: c

    struct CPT_FrameworkServices {
        int                     pluginApiMajorVer;   // Currently 1
        int                     pluginApiMinorVer;   // Currently 0
        CPT_RegisterReaderFunc  registerReaderFunc;  // Register reader factories
        CPT_LogFunc             logFunc;             // Server-side logging
        CPT_SetPluginInfoFunc   setPluginInfoFunc;   // Report plugin metadata
    };

**Reader registration parameters:**

.. code-block:: c

    struct CPT_RegisterReaderParams {
        int                     pluginApiMajorVer;
        int                     pluginApiMinorVer;
        CPT_CreateReaderFunc    createReaderFunc;   // Factory: create a reader instance
        CPT_DestroyReaderFunc   destroyReaderFunc;  // Destroy a reader instance
        CPT_CanReadFunc         canReadFunc;        // Test if a file path is supported
    };

**Function signatures:**

.. code-block:: c

    typedef cee::pt::ParticleDatasetReader* (*CPT_CreateReaderFunc)();
    typedef void (*CPT_DestroyReaderFunc)(cee::pt::ParticleDatasetReader*);
    typedef bool (*CPT_CanReadFunc)(const char* filePath);

The ``canReadFunc`` is called to determine whether the plugin can handle a given file path (typically
by checking the file extension). The ``createReaderFunc`` returns a new instance of your reader that
implements the ``ParticleDatasetReader`` interface.

************************************
Implementing ParticleDatasetReader
************************************

Your plugin's reader must derive from ``cee::pt::ParticleDatasetReader`` and implement the following
virtual methods:

.. code-block:: cpp

    class ParticleDatasetReader {
    public:
        virtual bool open(const cee::Str& filePath, Error* error = nullptr) = 0;
        virtual bool setActiveScalarField(const cee::Str& fieldName) = 0;

    protected:
        virtual std::unique_ptr<FrameData> readFrame(
            size_t frameIndex, const cee::Str& activeScalar) = 0;
        virtual void resetDerivedState() {}
    };

- ``open()``: Open the dataset at the given path. Populate the dataset header (frame count, bounding
  box, particle count, scalar field names) and return ``true`` on success.
- ``setActiveScalarField()``: Switch the active scalar field for subsequent frame reads.
- ``readFrame()``: Read a single frame's particle data (positions, IDs, and optional scalar values)
  and return it as a ``FrameData`` object.
- ``resetDerivedState()``: Optional cleanup hook called when the reader is closed.

The base class provides thread-safe caching, so ``readFrame()`` is only called on cache misses. The
base class also handles ``close()``, ``clearActiveScalarField()``, and LRU cache eviction.

********************************
Particle Reader Plugin SDK
********************************

To create your own reader plugin, download |DesktopProductName| from https://developer.techsoft3d.com.
The particle reader plugin framework is found in the **CeeParticleModel** folder in the distribution,
which includes:

- ``ParticleReaderPluginApi.h``: the C ABI header your plugin must include.
- ``ParticleDatasetReader.h``: the base class your reader must implement.
- ``FrameData.h`` / ``DatasetHeader.h``: data structures for frame and header information.

The framework is provided as source code so you can compile with your preferred compiler and settings.

For the full desktop API reference, see the |DesktopProductName| documentation:
https://docs.techsoft3d.com/hoops/latest/envision-desktop/api-ref/components.html

************************************************
In-Process Custom Particle Readers (Desktop)
************************************************

In addition to the plugin mechanism (shared libraries loaded at runtime), the desktop
``ParticleModel`` API also supports injecting a custom reader directly via:

.. code-block:: cpp

    bool ParticleModel::open(std::unique_ptr<ParticleDatasetReader> reader, Error* error = nullptr);

This transfers ownership of your reader instance to the model. Use this approach when:

- You want to embed particle reading in your own application (no separate server).
- Your reader requires application-specific context (e.g. database connections) that cannot be
  passed through the plugin C ABI.
- You are prototyping a new format before packaging it as a plugin.

****************************
Version Compatibility
****************************

The plugin API uses semantic versioning:

- **Major version** (``CPT_API_MAJOR_VER``): Must match between plugin and host. A mismatch causes
  the plugin to be rejected at load time.
- **Minor version** (``CPT_API_MINOR_VER``): Additive changes only. A plugin compiled against an
  older minor version will still load on a newer host.

Current version: **1.0**.

**********************
Cross-References
**********************

- :doc:`/guide/server-particle-model`: PtServer setup and configuration.
- :doc:`/guide/data-provider-plugins`: equivalent plugin system for UgServer (Data Providers).
- :doc:`/guide/distribution`: distribution layout including ``server/PtServer``.
