Code View

plugin / plugin-2.2.0.0 / src / plugin / PluginCatalog.hh
// SPDX-License-Identifier: MIT
#pragma once

#include <plugin/PluginCreator.hh>

#include <any>
#include <map>
#include <memory>
#include <mutex>
#include <shared_mutex>
#include <stdexcept>
#include <string>
#include <vector>

namespace plugin
{
  /**
   * @brief Thread-safe catalog of plugins derived from the same base class.
   *
   * Supports both parameterless and config-based plugin creation.
   *
   * @tparam Base The base class of the plugin hierarchy
   */
  template< class Base >
  class PLUGIN_EXPORT PluginCatalog
  {
    public:
      using key_type       = std::string;
      using mapped_type    = Creator< Base >;
      using container_type = std::map< key_type, mapped_type, std::less< key_type > >;

      PluginCatalog( ) = default;

      PluginCatalog( PluginCatalog const& other )
      {
        std::shared_lock rdLock( other.mutex_ );
        entries_ = other.entries_;
      }

      PluginCatalog( PluginCatalog&& other ) noexcept
      {
        std::unique_lock wrLock( other.mutex_ );
        entries_ = std::move( other.entries_ );
      }

      ~PluginCatalog( ) = default;

      PluginCatalog& operator=( PluginCatalog const& other )
      {
        if( this != &other )
        {
          std::unique_lock wrThis( mutex_, std::defer_lock );
          std::shared_lock rdOther( other.mutex_, std::defer_lock );
          std::lock( wrThis, rdOther );
          entries_ = other.entries_;
        }
        return *this;
      }

      PluginCatalog& operator=( PluginCatalog&& other ) noexcept
      {
        if( this != &other )
        {
          std::scoped_lock lk( mutex_, other.mutex_ );
          entries_ = std::move( other.entries_ );
        }
        return *this;
      }

      // ----- Mutating operations (exclusive lock) -----

      void insert( key_type const& key, mapped_type const& creator )
      {
        std::unique_lock lk( mutex_ );
        entries_[ key ] = creator;
      }

      void erase( key_type const& key )
      {
        std::unique_lock lk( mutex_ );
        entries_.erase( key );
      }

      // ----- Read / create operations (shared lock) -----

      /**
       * @brief Create a plugin with the default (parameterless) constructor.
       */
      std::unique_ptr< Base > create( key_type const& key )
      {
        std::shared_lock lk( mutex_ );
        auto it = entries_.find( key );
        if( it == entries_.end( ) )
          return nullptr;
        return std::unique_ptr< Base >( it->second( ) );
      }

      /**
       * @brief Create a plugin with a type-erased config argument.
       *
       * @throws std::runtime_error  if the plugin has no config factory
       * @throws std::bad_any_cast   if Config type doesn't match registration
       */
      std::unique_ptr< Base > create( key_type const& key, std::any const& config )
      {
        std::shared_lock lk( mutex_ );
        auto it = entries_.find( key );
        if( it == entries_.end( ) )
          return nullptr;
        return std::unique_ptr< Base >( it->second( config ) );
      }

      bool contains( key_type const& key ) const
      {
        std::shared_lock lk( mutex_ );
        return entries_.find( key ) != entries_.end( );
      }

      bool empty( ) const
      {
        std::shared_lock lk( mutex_ );
        return entries_.empty( );
      }

      std::size_t size( ) const
      {
        std::shared_lock lk( mutex_ );
        return entries_.size( );
      }

      std::vector< std::string > names( bool includeAliases = false ) const
      {
        std::shared_lock lk( mutex_ );

        std::vector< std::string > result;
        result.reserve( entries_.size( ) );

        for( auto const& [ name, creator ] : entries_ )
        {
          if( includeAliases || ! creator.isAlias( ) )
            result.push_back( name );
        }

        return result;
      }

    private:
      container_type entries_;
      mutable std::shared_mutex mutex_;
  };
}  // namespace plugin

#define CREATECATALOG( BASE )                                       \
  template<>                                                        \
  plugin::PluginCatalog< BASE >& plugin::Plugin< BASE >::catalog( ) \
  {                                                                 \
    static plugin::PluginCatalog< BASE > internalCatalog;           \
    return internalCatalog;                                         \
  }