diff --git a/include/simfil/model/model.h b/include/simfil/model/model.h index 0e02c93..a4aeeae 100644 --- a/include/simfil/model/model.h +++ b/include/simfil/model/model.h @@ -9,7 +9,10 @@ #include #include +#include #include +#include +#include #include #include @@ -19,6 +22,38 @@ namespace simfil { +namespace res +{ +// Tag type for ADL-based resolve hooks implemented by model libraries. +template +struct tag {}; +} + +namespace detail +{ +template +concept HasModelType = requires { typename T::ModelType; }; + +template +using ModelTypeOf = typename T::ModelType; +} + +/** + * ADL customization point for typed node resolution. + * Libraries define resolveInternal(tag, model, node) in their namespace. + */ +template +model_ptr resolveInternal(res::tag, ModelType const&, ModelNode const&) = delete; + +class ModelPool; + +// Built-in resolve hooks for core node types. Declared here so ADL sees them +// across translation units without relying on friend injection. +template<> +model_ptr resolveInternal(res::tag, ModelPool const&, ModelNode const&); +template<> +model_ptr resolveInternal(res::tag, ModelPool const&, ModelNode const&); + /** * Basic node model which only resolves trivial node types. */ @@ -58,6 +93,60 @@ class Model : public std::enable_shared_from_this */ virtual tl::expected resolve(ModelNode const& n, ResolveFn const& cb) const; + /** + * Resolve a node to a specific ModelNode subtype using ADL hooks. + * This provides a clean cast API without exposing model internals. + */ + template + model_ptr resolve(ModelNodeAddress const& address) const + { + if constexpr (std::is_same_v) { + return ModelNode::Ptr::make(shared_from_this(), address); + } + return resolve(*ModelNode::Ptr::make(shared_from_this(), address)); + } + + template + model_ptr resolve(ModelNodeAddress const& address, ScalarValueType data) const + { + if constexpr (std::is_same_v) { + return ModelNode::Ptr::make(shared_from_this(), address, std::move(data)); + } + return resolve(*ModelNode::Ptr::make(shared_from_this(), address, std::move(data))); + } + + template + model_ptr resolve(ModelNode::Ptr const& node) const + { + return resolve(*node); + } + + template + model_ptr resolve(ModelNode const& node) const + { + if constexpr (std::is_same_v) { + return model_ptr(node); + } + else { + if constexpr (!detail::HasModelType) { + static_assert(detail::HasModelType, "Target must provide a ModelType alias."); + return {}; + } + else { + using ModelType = detail::ModelTypeOf; +#if !defined(NDEBUG) + // In debug builds, validate the model type to catch misuse early. + auto typedModel = dynamic_cast(this); + assert(typedModel && "resolve called on incompatible model type."); + return resolveInternal(res::tag{}, *typedModel, node); +#else + // In release builds, avoid RTTI overhead on this hot path. + return resolveInternal(res::tag{}, *static_cast(this), node); +#endif + } + } + } + /** Add a small scalar value and get its model node view */ ModelNode::Ptr newSmallValue(bool value); ModelNode::Ptr newSmallValue(int16_t value); @@ -88,6 +177,8 @@ class ModelPool : public Model template friend struct BaseArray; public: + // Keep Model::resolve overloads visible alongside the virtual resolve override. + using Model::resolve; /** * The pool consists of multiple ModelNode columns, * each for a different data type. Each column @@ -156,12 +247,6 @@ class ModelPool : public Model ModelNode::Ptr newValue(std::string_view const& value); ModelNode::Ptr newValue(StringId handle); - /** Node-type-specific resolve-functions */ - [[nodiscard]] - model_ptr resolveObject(ModelNode::Ptr const& n) const; - [[nodiscard]] - model_ptr resolveArray(ModelNode::Ptr const& n) const; - /** Access the field name storage */ [[nodiscard]] std::shared_ptr strings() const; diff --git a/include/simfil/model/nodes.h b/include/simfil/model/nodes.h index 8992e51..4440567 100644 --- a/include/simfil/model/nodes.h +++ b/include/simfil/model/nodes.h @@ -67,18 +67,18 @@ using ScalarValueType = std::variant< namespace detail { - // Passkey for ModelNode construction: ModelNode types take this in their constructors so only - // model_ptr (and ModelPool via a shared key instance) can default/in-place construct them. - // This avoids per-node friendship and keeps IDEs happy across library boundaries. - struct mp_key - { - mp_key() = delete; - private: - explicit mp_key(int) {} - template friend struct ::simfil::model_ptr; - friend class ::simfil::Model; - friend class ::simfil::ModelPool; - }; +// Passkey for ModelNode construction: ModelNode types take this in their constructors so only +// model_ptr (and ModelPool via a shared key instance) can default/in-place construct them. +// This avoids per-node friendship and keeps IDEs happy across library boundaries. +struct mp_key +{ + mp_key() = delete; +private: + explicit mp_key(int) {} + template friend struct ::simfil::model_ptr; + friend class ::simfil::Model; + friend class ::simfil::ModelPool; +}; } /** @@ -206,6 +206,25 @@ struct ModelNodeAddress } }; +namespace detail +{ +// Shared storage entry for object fields across all BaseObject instantiations. +// Keeps the underlying ArrayArena type identical regardless of ModelType. +struct ObjectField +{ + ObjectField() = default; + ObjectField(StringId name, ModelNodeAddress a) : name_(name), node_(a) {} + StringId name_ = StringPool::Empty; + ModelNodeAddress node_; + + template + void serialize(S& s) { + s.value2b(name_); + s.object(node_); + } +}; +} + /** Semantic view onto a particular node in a ModelPool. */ struct ModelNode { @@ -413,13 +432,15 @@ struct ValueNode final : public ModelNodeBase * a reference to a Model-derived pool type. * @tparam ModelType Model-derived type. */ -template +template struct MandatoryDerivedModelNodeBase : public ModelNodeBase { - inline ModelType& model() const {return *modelPtr();} // NOLINT + using ModelType = ModelTypeT; + + inline ModelTypeT& model() const {return *modelPtr();} // NOLINT protected: - template + template inline ModelType_* modelPtr() const { return static_cast(const_cast(model_.get())); } @@ -570,24 +591,9 @@ struct BaseObject : public MandatoryDerivedModelNodeBase protected: /** - * Object field - a name and a tree node address. - * These are stored in the ModelPools Field array arena. + * Object fields are stored in the model's shared object-field arena. */ - struct Field - { - Field() = default; - Field(StringId name, ModelNodeAddress a) : name_(name), node_(a) {} - StringId name_ = StringPool::Empty; - ModelNodeAddress node_; - - template - void serialize(S& s) { - s.value2b(name_); - s.object(node_); - } - }; - - using Storage = ArrayArena; + using Storage = ArrayArena; using ModelNode::model_; using MandatoryDerivedModelNodeBase::model; @@ -637,6 +643,8 @@ template(*this)); diff --git a/src/model/model.cpp b/src/model/model.cpp index 4e23392..0e8a5df 100644 --- a/src/model/model.cpp +++ b/src/model/model.cpp @@ -394,17 +394,21 @@ ModelNode::Ptr ModelPool::newValue(StringId handle) { ModelNodeAddress{PooledString, static_cast(handle)}); } -model_ptr ModelPool::resolveObject(const ModelNode::Ptr& n) const { - if (n->addr_.column() != Objects) +// Core ADL resolve hooks for base Object/Array nodes. +template<> +model_ptr resolveInternal(res::tag, ModelPool const& model, ModelNode const& node) +{ + if (node.addr().column() != ModelPool::Objects) raise("Cannot cast this node to an object."); - return model_ptr::make(shared_from_this(), n->addr_); + return model_ptr::make(model.shared_from_this(), node.addr()); } -model_ptr ModelPool::resolveArray(ModelNode::Ptr const& n) const +template<> +model_ptr resolveInternal(res::tag, ModelPool const& model, ModelNode const& node) { - if (n->addr_.column() != Arrays) + if (node.addr().column() != ModelPool::Arrays) raise("Cannot cast this node to an array."); - return model_ptr::make(shared_from_this(), n->addr_); + return model_ptr::make(model.shared_from_this(), node.addr()); } std::shared_ptr ModelPool::strings() const