xo-facet: + FacetRegistry for runtime poly facet finding
This commit is contained in:
parent
4555d55eef
commit
074edc97d9
2 changed files with 240 additions and 0 deletions
153
include/xo/facet/FacetRegistry.hpp
Normal file
153
include/xo/facet/FacetRegistry.hpp
Normal file
|
|
@ -0,0 +1,153 @@
|
|||
/** @file FacetRegistry.hpp
|
||||
*
|
||||
* @brief Runtime facet implementation lookup
|
||||
*
|
||||
* @author Roland Conybeare, Jan 2026
|
||||
**/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "facet_implementation.hpp"
|
||||
#include "typeseq.hpp"
|
||||
#include <unordered_map>
|
||||
#include <utility>
|
||||
|
||||
namespace xo {
|
||||
namespace facet {
|
||||
|
||||
/** @class FacetRegistry
|
||||
*
|
||||
* @brief Runtime registry for facet implementations
|
||||
*
|
||||
* Enables late-binding between facets. For example, given an
|
||||
* obj<AGCObject> with unknown concrete type at compile time,
|
||||
* look up the APrintable implementation at runtime.
|
||||
*
|
||||
* Example:
|
||||
* // Registration (at initialization time)
|
||||
* FacetRegistry::instance().register_impl<APrintable, DFloat>();
|
||||
* FacetRegistry::instance().register_impl<APrintable, DList>();
|
||||
*
|
||||
* // Lookup (at runtime)
|
||||
* typeseq repr_id = obj->_typeseq();
|
||||
* const APrintable* printable = runtime_impl_for<APrintable>(repr_id);
|
||||
* printable->print(obj.data(), out);
|
||||
**/
|
||||
class FacetRegistry {
|
||||
public:
|
||||
using key_type = std::pair<typeseq, typeseq>;
|
||||
|
||||
/** hash function for key_type **/
|
||||
struct KeyHash {
|
||||
std::size_t operator()(const key_type & k) const noexcept {
|
||||
// combine the two seqno values
|
||||
std::size_t h1 = std::hash<int32_t>{}(k.first.seqno());
|
||||
std::size_t h2 = std::hash<int32_t>{}(k.second.seqno());
|
||||
return h1 ^ (h2 << 1);
|
||||
}
|
||||
};
|
||||
|
||||
/** singleton instance **/
|
||||
static FacetRegistry & instance() {
|
||||
static FacetRegistry s_instance;
|
||||
return s_instance;
|
||||
}
|
||||
|
||||
/** Number of registered (facet, repr) pairs **/
|
||||
std::size_t size() const { return registry_.size(); }
|
||||
|
||||
/** Check if implementation is registered **/
|
||||
bool contains(typeseq facet_id,
|
||||
typeseq repr_id) const
|
||||
{
|
||||
return registry_.find(key_type(facet_id, repr_id)) != registry_.end();
|
||||
}
|
||||
|
||||
/** Type-safe registration
|
||||
*
|
||||
* Registers the compile-time FacetImplementation<AFacet, DRepr>
|
||||
* for runtime lookup.
|
||||
*
|
||||
* @tparam AFacet abstract facet type
|
||||
* @tparam DRepr data representation type
|
||||
**/
|
||||
template <typename AFacet, typename DRepr>
|
||||
void register_impl() {
|
||||
static FacetImplType<AFacet, DRepr> impl;
|
||||
|
||||
this->_register_impl(typeseq::id<AFacet>(),
|
||||
typeseq::id<DRepr>(),
|
||||
&impl);
|
||||
}
|
||||
|
||||
/** Type-safe lookup
|
||||
*
|
||||
* @tparam AFacet abstract facet type
|
||||
* @param repr_id typeseq for data representation
|
||||
* @return pointer to AFacet implementation, or nullptr
|
||||
**/
|
||||
template <typename AFacet>
|
||||
const AFacet * lookup(typeseq repr_id) const {
|
||||
return static_cast<const AFacet *>(this->_lookup(typeseq::id<AFacet>(), repr_id));
|
||||
}
|
||||
|
||||
private:
|
||||
/** Register a facet implementation (type-erased)
|
||||
*
|
||||
* @param facet_id typeseq for abstract facet (e.g., APrintable)
|
||||
* @param repr_id typeseq for data representation (e.g., DFloat)
|
||||
* @param impl pointer to stateless implementation instance
|
||||
**/
|
||||
void _register_impl(typeseq facet_id,
|
||||
typeseq repr_id,
|
||||
const void * impl)
|
||||
{
|
||||
registry_[key_type(facet_id, repr_id)] = impl;
|
||||
}
|
||||
|
||||
/** Lookup facet implementation (type-erased)
|
||||
*
|
||||
* @return pointer to implementation, or nullptr if not registered
|
||||
**/
|
||||
const void * _lookup(typeseq facet_id,
|
||||
typeseq repr_id) const
|
||||
{
|
||||
auto ix = registry_.find(key_type(facet_id, repr_id));
|
||||
|
||||
if (ix == registry_.end())
|
||||
return nullptr;
|
||||
else
|
||||
return ix->second;
|
||||
}
|
||||
|
||||
private:
|
||||
FacetRegistry() = default;
|
||||
|
||||
std::unordered_map<key_type, const void *, KeyHash> registry_;
|
||||
};
|
||||
|
||||
/** Convenience function for runtime lookup
|
||||
*
|
||||
* @tparam AFacet abstract facet type
|
||||
* @param repr_id typeseq for data representation
|
||||
* @return pointer to AFacet implementation, or nullptr
|
||||
**/
|
||||
template <typename AFacet>
|
||||
inline const AFacet * runtime_impl_for(typeseq repr_id) {
|
||||
return FacetRegistry::instance().lookup<AFacet>(repr_id);
|
||||
}
|
||||
|
||||
/** Convenience function for registration
|
||||
*
|
||||
* @tparam AFacet abstract facet type
|
||||
* @tparam DRepr data representation type
|
||||
**/
|
||||
template <typename AFacet, typename DRepr>
|
||||
inline void register_facet_impl() {
|
||||
FacetRegistry::instance().register_impl<AFacet, DRepr>();
|
||||
}
|
||||
|
||||
} /*namespace facet*/
|
||||
} /*namespace xo*/
|
||||
|
||||
/* end FacetRegistry.hpp */
|
||||
|
|
@ -5,6 +5,7 @@
|
|||
|
||||
#include "xo/facet/facet.hpp"
|
||||
#include "xo/facet/facet_implementation.hpp"
|
||||
#include "xo/facet/FacetRegistry.hpp"
|
||||
#include "xo/facet/OObject.hpp"
|
||||
#include "xo/facet/RRouter.hpp"
|
||||
#include "xo/facet/typeseq.hpp"
|
||||
|
|
@ -19,6 +20,9 @@ namespace xo {
|
|||
using xo::facet::valid_abstract_facet;
|
||||
using xo::facet::valid_facet_implementation;
|
||||
using xo::facet::FacetImplementation;
|
||||
using xo::facet::FacetRegistry;
|
||||
using xo::facet::runtime_impl_for;
|
||||
using xo::facet::register_facet_impl;
|
||||
using xo::facet::DVariantPlaceholder;
|
||||
using xo::facet::OObject;
|
||||
using xo::facet::valid_object_router;
|
||||
|
|
@ -402,6 +406,89 @@ namespace xo {
|
|||
|
||||
REQUIRE(z1o);
|
||||
}
|
||||
|
||||
TEST_CASE("registry-1", "[facet][registry]")
|
||||
{
|
||||
auto & registry = FacetRegistry::instance();
|
||||
|
||||
// register implementations
|
||||
register_facet_impl<AComplex, DRectCoords>();
|
||||
register_facet_impl<AComplex, DPolarCoords>();
|
||||
|
||||
REQUIRE(registry.contains(typeseq::id<AComplex>(),
|
||||
typeseq::id<DRectCoords>()));
|
||||
REQUIRE(registry.contains(typeseq::id<AComplex>(),
|
||||
typeseq::id<DPolarCoords>()));
|
||||
}
|
||||
|
||||
TEST_CASE("registry-lookup-1", "[facet][registry]")
|
||||
{
|
||||
// ensure registered
|
||||
register_facet_impl<AComplex, DRectCoords>();
|
||||
register_facet_impl<AComplex, DPolarCoords>();
|
||||
|
||||
// runtime lookup using typeseq
|
||||
typeseq rect_id = typeseq::id<DRectCoords>();
|
||||
typeseq polar_id = typeseq::id<DPolarCoords>();
|
||||
|
||||
const AComplex * rect_impl = runtime_impl_for<AComplex>(rect_id);
|
||||
const AComplex * polar_impl = runtime_impl_for<AComplex>(polar_id);
|
||||
|
||||
REQUIRE(rect_impl != nullptr);
|
||||
REQUIRE(polar_impl != nullptr);
|
||||
|
||||
// use implementations
|
||||
DRectCoords z1{1.0, 0.0};
|
||||
DPolarCoords z2{0.0, 1.0};
|
||||
|
||||
REQUIRE(rect_impl->xcoord(&z1) == 1.0);
|
||||
REQUIRE(rect_impl->ycoord(&z1) == 0.0);
|
||||
|
||||
REQUIRE(polar_impl->xcoord(&z2) == 1.0);
|
||||
REQUIRE(polar_impl->ycoord(&z2) == 0.0);
|
||||
}
|
||||
|
||||
TEST_CASE("registry-cross-facet", "[facet][registry]")
|
||||
{
|
||||
// simulate the DList::pretty() use case:
|
||||
// given obj<AComplex> with unknown repr type,
|
||||
// look up implementation at runtime
|
||||
|
||||
register_facet_impl<AComplex, DRectCoords>();
|
||||
register_facet_impl<AComplex, DPolarCoords>();
|
||||
|
||||
// create type-erased objects
|
||||
DRectCoords z1{1.0, 0.0};
|
||||
DPolarCoords z2{0.0, 2.0};
|
||||
|
||||
obj<AComplex, DRectCoords> obj1(&z1);
|
||||
obj<AComplex, DPolarCoords> obj2(&z2);
|
||||
|
||||
// simulate: only know typeseq at runtime
|
||||
typeseq repr1 = obj1._typeseq();
|
||||
typeseq repr2 = obj2._typeseq();
|
||||
|
||||
// lookup implementations
|
||||
const AComplex * impl1 = runtime_impl_for<AComplex>(repr1);
|
||||
const AComplex * impl2 = runtime_impl_for<AComplex>(repr2);
|
||||
|
||||
REQUIRE(impl1 != nullptr);
|
||||
REQUIRE(impl2 != nullptr);
|
||||
|
||||
// use via runtime-looked-up implementation
|
||||
REQUIRE(impl1->magnitude(obj1.data()) == 1.0);
|
||||
REQUIRE(impl2->magnitude(obj2.data()) == 2.0);
|
||||
}
|
||||
|
||||
TEST_CASE("registry-not-found", "[facet][registry]")
|
||||
{
|
||||
// lookup for unregistered type returns nullptr
|
||||
struct DUnknown {};
|
||||
|
||||
const AComplex * impl = runtime_impl_for<AComplex>(typeseq::id<DUnknown>());
|
||||
|
||||
REQUIRE(impl == nullptr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue