From 074edc97d99a10d25a11bfa1d50e2458876bb2da Mon Sep 17 00:00:00 2001 From: Roland Conybeare Date: Thu, 8 Jan 2026 19:38:21 -0500 Subject: [PATCH] xo-facet: + FacetRegistry for runtime poly facet finding --- include/xo/facet/FacetRegistry.hpp | 153 +++++++++++++++++++++++++++++ utest/objectmodel.test.cpp | 87 ++++++++++++++++ 2 files changed, 240 insertions(+) create mode 100644 include/xo/facet/FacetRegistry.hpp diff --git a/include/xo/facet/FacetRegistry.hpp b/include/xo/facet/FacetRegistry.hpp new file mode 100644 index 0000000..862b14c --- /dev/null +++ b/include/xo/facet/FacetRegistry.hpp @@ -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 +#include + +namespace xo { + namespace facet { + + /** @class FacetRegistry + * + * @brief Runtime registry for facet implementations + * + * Enables late-binding between facets. For example, given an + * obj with unknown concrete type at compile time, + * look up the APrintable implementation at runtime. + * + * Example: + * // Registration (at initialization time) + * FacetRegistry::instance().register_impl(); + * FacetRegistry::instance().register_impl(); + * + * // Lookup (at runtime) + * typeseq repr_id = obj->_typeseq(); + * const APrintable* printable = runtime_impl_for(repr_id); + * printable->print(obj.data(), out); + **/ + class FacetRegistry { + public: + using key_type = std::pair; + + /** 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{}(k.first.seqno()); + std::size_t h2 = std::hash{}(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 + * for runtime lookup. + * + * @tparam AFacet abstract facet type + * @tparam DRepr data representation type + **/ + template + void register_impl() { + static FacetImplType impl; + + this->_register_impl(typeseq::id(), + typeseq::id(), + &impl); + } + + /** Type-safe lookup + * + * @tparam AFacet abstract facet type + * @param repr_id typeseq for data representation + * @return pointer to AFacet implementation, or nullptr + **/ + template + const AFacet * lookup(typeseq repr_id) const { + return static_cast(this->_lookup(typeseq::id(), 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 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 + inline const AFacet * runtime_impl_for(typeseq repr_id) { + return FacetRegistry::instance().lookup(repr_id); + } + + /** Convenience function for registration + * + * @tparam AFacet abstract facet type + * @tparam DRepr data representation type + **/ + template + inline void register_facet_impl() { + FacetRegistry::instance().register_impl(); + } + + } /*namespace facet*/ +} /*namespace xo*/ + +/* end FacetRegistry.hpp */ diff --git a/utest/objectmodel.test.cpp b/utest/objectmodel.test.cpp index 57a80c5..5566ff9 100644 --- a/utest/objectmodel.test.cpp +++ b/utest/objectmodel.test.cpp @@ -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(); + register_facet_impl(); + + REQUIRE(registry.contains(typeseq::id(), + typeseq::id())); + REQUIRE(registry.contains(typeseq::id(), + typeseq::id())); + } + + TEST_CASE("registry-lookup-1", "[facet][registry]") + { + // ensure registered + register_facet_impl(); + register_facet_impl(); + + // runtime lookup using typeseq + typeseq rect_id = typeseq::id(); + typeseq polar_id = typeseq::id(); + + const AComplex * rect_impl = runtime_impl_for(rect_id); + const AComplex * polar_impl = runtime_impl_for(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 with unknown repr type, + // look up implementation at runtime + + register_facet_impl(); + register_facet_impl(); + + // create type-erased objects + DRectCoords z1{1.0, 0.0}; + DPolarCoords z2{0.0, 2.0}; + + obj obj1(&z1); + obj obj2(&z2); + + // simulate: only know typeseq at runtime + typeseq repr1 = obj1._typeseq(); + typeseq repr2 = obj2._typeseq(); + + // lookup implementations + const AComplex * impl1 = runtime_impl_for(repr1); + const AComplex * impl2 = runtime_impl_for(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(typeseq::id()); + + REQUIRE(impl == nullptr); + } } }