// 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; \
}