#pragma once #include "type_descriptor.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace structive { struct Null_Shared_Mutex { void lock() {} void unlock() {} void lock_shared() {} void unlock_shared() {} }; struct Shared_Mutex_Policy { using mutex_type = std::shared_mutex; }; struct No_Lock_Policy { using mutex_type = Null_Shared_Mutex; }; template concept Shared_Lockable = requires(Mutex& mutex) { mutex.lock(); mutex.unlock(); mutex.lock_shared(); mutex.unlock_shared(); }; template concept Synchronization_Policy = requires { typename Policy::mutex_type; } && Shared_Lockable; namespace detail { template > class Mutex_Storage; template class Mutex_Storage { std::unique_ptr mutexes_; public: Mutex_Storage() = default; explicit Mutex_Storage(std::size_t count) : mutexes_(count ? std::make_unique(count) : nullptr) {} void reset(std::size_t count) { mutexes_ = count ? std::make_unique(count) : nullptr; } Mutex& get(std::size_t index) noexcept { return mutexes_[index]; } Mutex& get(std::size_t index) const noexcept { return mutexes_[index]; } }; template class Mutex_Storage { static Mutex& mutex() noexcept { static Mutex value; return value; } public: Mutex_Storage() = default; explicit Mutex_Storage(std::size_t) {} void reset(std::size_t) {} Mutex& get(std::size_t) noexcept { return mutex(); } Mutex& get(std::size_t) const noexcept { return mutex(); } }; } /// Result of type-erased runtime property access. Runtime lookup never performs implicit type conversion. enum class Runtime_Access_Result { ok, unknown_property, not_readable, not_writable, unsupported_runtime_write, type_mismatch }; /// Callback used by `runtime_read`. /// The value pointer is valid only for the duration of the callback and must be consumed or copied synchronously. For synchronized writable state, the corresponding managed read lock remains held while the callback executes, so the callback must not re-enter a conflicting write on the same lock domain. using Runtime_Read_Callback = void (*)(void*, std::size_t, std::string_view, const std::type_info&, const void*); /// Type-erased boundary intended for adapters that discover a property key only at runtime. /// Normal C++ business code should prefer `Property_Object::read/write`, which preserve compile-time type and capability checking. class Property_Object_Base { struct Runtime_Interface { const std::type_info& (*object_type)() noexcept; std::size_t (*property_count)() noexcept; Runtime_Access_Result (*read)(const Property_Object_Base&, std::string_view, void*, Runtime_Read_Callback); Runtime_Access_Result (*write)(Property_Object_Base&, std::string_view, const std::type_info&, const void*); }; const Runtime_Interface* runtime_interface_{}; protected: explicit Property_Object_Base(const Runtime_Interface* runtime_interface) noexcept : runtime_interface_(runtime_interface) {} Property_Object_Base(const Property_Object_Base&) noexcept = default; Property_Object_Base(Property_Object_Base&&) noexcept = default; Property_Object_Base& operator=(const Property_Object_Base&) noexcept = default; Property_Object_Base& operator=(Property_Object_Base&&) noexcept = default; ~Property_Object_Base() = default; template friend class Property_Object; public: /// Returns the concrete managed object type represented by this erased base. const std::type_info& runtime_object_type() const noexcept { return runtime_interface_->object_type(); } /// Returns the number of properties in the concrete object's schema. std::size_t runtime_property_count() const noexcept { return runtime_interface_->property_count(); } /// Looks up `key` at runtime and invokes `callback` exactly once on success. /// The operation follows the same intrinsic readable capability and synchronization rules as typed `read`; stored read-only properties therefore keep their zero-lock fast path. `callback` is a required non-null function pointer. Runtime_Access_Result runtime_read(std::string_view key, void* context, Runtime_Read_Callback callback) const { return runtime_interface_->read(*this, key, context, callback); } /// Looks up `key` at runtime and performs a copy-input managed write. /// `value_type` must exactly match the property's declared value type and `value` must point to a live object of that exact type for the duration of the call. No numeric, string or user-defined conversion is attempted. Intrinsically writable properties that require move-only input report `unsupported_runtime_write`; typed `write` remains available for them. Runtime_Access_Result runtime_write(std::string_view key, const std::type_info& value_type, const void* value) { return runtime_interface_->write(*this, key, value_type, value); } }; /// Explicit per-instance synchronization override. The contained plan changes only lock topology; it does not change property capability or validation. struct Property_Synchronization { Synchronization_Plan plan; }; inline Property_Synchronization property_synchronization(Synchronization_Plan plan) { return {std::move(plan)}; } template Property_Synchronization property_synchronization(Source&& source) requires Property_Described_Object { using Schema = type_descriptor_schema_t; return {materialize_synchronization_plan(std::forward(source))}; } /// Non-owning inspection view of the effective synchronization topology for one object instance. /// `unsynchronized_slot` means that managed access for the property owns no mutex domain. For stored read-only properties this is automatic. struct Resolved_Synchronization_View { static constexpr std::size_t unsynchronized_slot = std::numeric_limits::max(); std::span lock_slots; std::size_t lock_count{}; std::size_t slot(std::size_t index) const noexcept { return lock_slots[index]; } bool uses_lock(std::size_t index) const noexcept { return slot(index) != unsynchronized_slot; } }; template class Property_Object : public Property_Object_Base { public: using object_type = Derived; using mutex_type = typename Lock_Policy::mutex_type; private: static constexpr bool uses_real_mutexes = !std::same_as; struct Empty_Guard_Locks {}; struct Dynamic_Lock_Targets { std::vector slots; std::vector unsynchronized_properties; }; template struct Static_Lock_Targets { std::array slots{}; std::size_t slot_count{}; std::array unsynchronized_properties{}; std::size_t unsynchronized_count{}; }; std::unique_ptr custom_lock_layout_; [[no_unique_address]] detail::Mutex_Storage mutex_storage_; static const auto& default_resolved_synchronization() { static const auto resolved = resolve_synchronization_plan(type_descriptor(), type_descriptor().synchronization_plan()); return resolved; } template void initialize(const Schema& schema, const Synchronization_Plan& plan) { auto resolved = resolve_synchronization_plan(schema, plan); auto layout = std::make_unique(Schema::property_count + 1); layout[0] = resolved.lock_count; std::copy(resolved.lock_slots.begin(), resolved.lock_slots.end(), layout.get() + 1); custom_lock_layout_ = std::move(layout); mutex_storage_.reset(resolved.lock_count); } void copy_synchronization_from(const Property_Object& other) { using Schema = type_descriptor_schema_t; if (other.custom_lock_layout_) { auto layout = std::make_unique(Schema::property_count + 1); std::copy_n(other.custom_lock_layout_.get(), Schema::property_count + 1, layout.get()); custom_lock_layout_ = std::move(layout); } else { custom_lock_layout_.reset(); } mutex_storage_.reset(lock_count()); } std::size_t lock_count() const noexcept { return custom_lock_layout_ ? custom_lock_layout_[0] : default_resolved_synchronization().lock_count; } std::size_t slot(std::size_t index) const noexcept { return custom_lock_layout_ ? custom_lock_layout_[index + 1] : default_resolved_synchronization().slot(index); } mutex_type& mutex(std::size_t index) noexcept { return mutex_storage_.get(index); } mutex_type& mutex(std::size_t index) const noexcept { return mutex_storage_.get(index); } template static void sort_unique_prefix(std::array& values, std::size_t& count) { for (std::size_t index = 1; index < count; ++index) { auto value = values[index]; auto position = index; while (position > 0 && value < values[position - 1]) { values[position] = values[position - 1]; --position; } values[position] = value; } if (count == 0) { return; } std::size_t write = 1; for (std::size_t read = 1; read < count; ++read) { if (values[read] != values[write - 1]) { values[write++] = values[read]; } } count = write; } static bool runtime_property_readable(std::size_t index) { using Schema = type_descriptor_schema_t; static const auto table = [](std::index_sequence) { return std::array{Schema::template property_type::readable...}; }(std::make_index_sequence{}); return table[index]; } static bool runtime_property_writable(std::size_t index) { using Schema = type_descriptor_schema_t; static const auto table = [](std::index_sequence) { return std::array{Schema::template property_type::writable...}; }(std::make_index_sequence{}); return table[index]; } Dynamic_Lock_Targets collect_dynamic_lock_targets(std::span keys, bool shared_access) const { using Schema = type_descriptor_schema_t; Dynamic_Lock_Targets targets; targets.slots.reserve(keys.size()); targets.unsynchronized_properties.reserve(keys.size()); for (auto key : keys) { auto index = schema_property_index(key); if (!index) { throw std::invalid_argument("Unknown property: " + std::string(key)); } bool allowed = shared_access ? runtime_property_readable(*index) : runtime_property_writable(*index); if (!allowed) { throw std::invalid_argument(std::string(shared_access ? "Property is not readable: " : "Property is not writable: ") + std::string(key)); } auto lock_slot = slot(*index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { targets.unsynchronized_properties.push_back(*index); } else { targets.slots.push_back(lock_slot); } } std::sort(targets.slots.begin(), targets.slots.end()); targets.slots.erase(std::unique(targets.slots.begin(), targets.slots.end()), targets.slots.end()); std::sort(targets.unsynchronized_properties.begin(), targets.unsynchronized_properties.end()); targets.unsynchronized_properties.erase(std::unique(targets.unsynchronized_properties.begin(), targets.unsynchronized_properties.end()), targets.unsynchronized_properties.end()); return targets; } template Static_Lock_Targets collect_static_lock_targets() const { using Schema = type_descriptor_schema_t; static_assert(((Shared_Access ? Schema::template property_type::readable : Schema::template property_type::writable) && ...)); Static_Lock_Targets targets; auto collect = [&]() { auto lock_slot = slot(Property_Index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { targets.unsynchronized_properties[targets.unsynchronized_count++] = Property_Index; } else { targets.slots[targets.slot_count++] = lock_slot; } }; (collect.template operator()(), ...); sort_unique_prefix(targets.slots, targets.slot_count); sort_unique_prefix(targets.unsynchronized_properties, targets.unsynchronized_count); return targets; } template auto collect_all_static_lock_targets() const { using Schema = type_descriptor_schema_t; Static_Lock_Targets targets; auto collect = [&]() { constexpr bool allowed = Shared_Access ? Schema::template property_type::readable : Schema::template property_type::writable; if constexpr (allowed) { auto lock_slot = slot(Index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { targets.unsynchronized_properties[targets.unsynchronized_count++] = Index; } else { targets.slots[targets.slot_count++] = lock_slot; } } }; [&](std::index_sequence) { (collect.template operator()(), ...); }(std::make_index_sequence{}); sort_unique_prefix(targets.slots, targets.slot_count); sort_unique_prefix(targets.unsynchronized_properties, targets.unsynchronized_count); return targets; } template decltype(auto) read_unlocked(const View& view) const { using Schema = type_descriptor_schema_t; const auto& descriptor = type_descriptor().template property(); using Accessor = typename Schema::template property_type::accessor_type; if constexpr (Accessor::synchronized_view_read) { return descriptor.accessor.read(view); } else { return descriptor.accessor.read(static_cast(*this)); } } template void write_unlocked(Value&& value) { const auto& descriptor = type_descriptor().template property(); descriptor.accessor.write(static_cast(*this), std::forward(value)); } template class Single_Read_View { const Property_Object* owner_{}; std::size_t lock_slot_{}; template decltype(auto) get_index() const { using Schema = type_descriptor_schema_t; using Owner_Property = typename Schema::template property_type; using Property = typename Schema::template property_type; if constexpr (Owner_Property::declares_dependencies) { static_assert(schema_property_depends_on_index_v, "computed property reads an undeclared dependency"); } else if constexpr (Property::writable || Property::accessor_type::synchronized_view_read) { if (owner_->slot(Index) != lock_slot_ || (lock_slot_ == Resolved_Synchronization_View::unsynchronized_slot && Index != Property_Index)) { throw std::logic_error("Computed property reads outside its synchronization slot"); } } if constexpr (Property::accessor_type::synchronized_view_read) { Single_Read_View nested{*owner_, lock_slot_}; return owner_->template read_unlocked(nested); } else { return owner_->template read_unlocked(*this); } } public: Single_Read_View(const Property_Object& owner, std::size_t lock_slot) : owner_(&owner), lock_slot_(lock_slot) {} template decltype(auto) get() const requires Schema_Readable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; return get_index(); } template decltype(auto) get() const requires Schema_Readable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; return get_index(); } }; template auto read_one() const { using Schema = type_descriptor_schema_t; static_assert(Schema::template property_type::readable); using Property = typename Schema::template property_type; using Value = typename Property::value_type; if constexpr ((!Property::writable && !Property::accessor_type::synchronized_view_read) || (!uses_real_mutexes && !Property::accessor_type::synchronized_view_read)) { return Value(read_unlocked(*this)); } auto lock_slot = slot(Index); Single_Read_View view{*this, lock_slot}; if constexpr (!uses_real_mutexes) { return Value(read_unlocked(view)); } if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { return Value(read_unlocked(view)); } std::shared_lock lock{mutex(lock_slot)}; return Value(read_unlocked(view)); } template void write_one(Value&& value) { using Schema = type_descriptor_schema_t; static_assert(Schema::template property_type::writable); if constexpr (!uses_real_mutexes) { write_unlocked(std::forward(value)); return; } auto lock_slot = slot(Index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { write_unlocked(std::forward(value)); return; } std::unique_lock lock{mutex(lock_slot)}; write_unlocked(std::forward(value)); } private: class Basic_Read_Guard { const Property_Object* owner_{}; std::vector slots_; std::vector unsynchronized_properties_; [[no_unique_address]] std::conditional_t>, Empty_Guard_Locks> locks_; bool holds(std::size_t index) const { auto lock_slot = owner_->slot(index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { return std::binary_search(unsynchronized_properties_.begin(), unsynchronized_properties_.end(), index); } return std::binary_search(slots_.begin(), slots_.end(), lock_slot); } friend class Property_Object; Basic_Read_Guard(const Property_Object& owner, Dynamic_Lock_Targets targets) : owner_(&owner), slots_(std::move(targets.slots)), unsynchronized_properties_(std::move(targets.unsynchronized_properties)) { if constexpr (uses_real_mutexes) { locks_.reserve(slots_.size()); for (auto lock_slot : slots_) { locks_.emplace_back(owner_->mutex(lock_slot)); } } } public: template decltype(auto) get_index() const requires Schema_Readable_Property_Index, Index> { if (!holds(Index)) { throw std::logic_error("Property is outside the held synchronization set"); } return owner_->template read_unlocked(*this); } template decltype(auto) get() const requires Schema_Readable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; return get_index(); } template decltype(auto) get() const requires Schema_Readable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; return get_index(); } }; class Basic_Write_Guard { Property_Object* owner_{}; std::vector slots_; std::vector unsynchronized_properties_; [[no_unique_address]] std::conditional_t>, Empty_Guard_Locks> locks_; bool holds(std::size_t index) const { auto lock_slot = owner_->slot(index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { return std::binary_search(unsynchronized_properties_.begin(), unsynchronized_properties_.end(), index); } return std::binary_search(slots_.begin(), slots_.end(), lock_slot); } friend class Property_Object; Basic_Write_Guard(Property_Object& owner, Dynamic_Lock_Targets targets) : owner_(&owner), slots_(std::move(targets.slots)), unsynchronized_properties_(std::move(targets.unsynchronized_properties)) { if constexpr (uses_real_mutexes) { locks_.reserve(slots_.size()); for (auto lock_slot : slots_) { locks_.emplace_back(owner_->mutex(lock_slot)); } } } public: template decltype(auto) get_index() const requires Schema_Readable_Property_Index, Index> { if (!holds(Index)) { throw std::logic_error("Property is outside the held synchronization set"); } return owner_->template read_unlocked(*this); } template decltype(auto) get() const requires Schema_Readable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; return get_index(); } template decltype(auto) get() const requires Schema_Readable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; return get_index(); } template void set_index(Value&& value) requires Schema_Writable_Property_Index, Index> { if (!holds(Index)) { throw std::logic_error("Property is outside the held synchronization set"); } owner_->template write_unlocked(std::forward(value)); } template void set(Value&& value) requires Schema_Writable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; set_index(std::forward(value)); } template void set(Value&& value) requires Schema_Writable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; set_index(std::forward(value)); } }; template class Static_Read_Guard { const Property_Object* owner_{}; Static_Lock_Targets targets_; [[no_unique_address]] std::conditional_t, Capacity>, Empty_Guard_Locks> locks_{}; bool holds(std::size_t index) const { auto lock_slot = owner_->slot(index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { return std::binary_search(targets_.unsynchronized_properties.begin(), targets_.unsynchronized_properties.begin() + static_cast(targets_.unsynchronized_count), index); } return std::binary_search(targets_.slots.begin(), targets_.slots.begin() + static_cast(targets_.slot_count), lock_slot); } friend class Property_Object; Static_Read_Guard(const Property_Object& owner, Static_Lock_Targets targets) : owner_(&owner), targets_(std::move(targets)) { if constexpr (uses_real_mutexes) { for (std::size_t index = 0; index < targets_.slot_count; ++index) { locks_[index] = std::shared_lock{owner_->mutex(targets_.slots[index])}; } } } public: template decltype(auto) get_index() const requires Schema_Readable_Property_Index, Index> { if (!holds(Index)) { throw std::logic_error("Property is outside the held synchronization set"); } return owner_->template read_unlocked(*this); } template decltype(auto) get() const requires Schema_Readable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; return get_index(); } template decltype(auto) get() const requires Schema_Readable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; return get_index(); } }; template class Static_Write_Guard { Property_Object* owner_{}; Static_Lock_Targets targets_; [[no_unique_address]] std::conditional_t, Capacity>, Empty_Guard_Locks> locks_{}; bool holds(std::size_t index) const { auto lock_slot = owner_->slot(index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { return std::binary_search(targets_.unsynchronized_properties.begin(), targets_.unsynchronized_properties.begin() + static_cast(targets_.unsynchronized_count), index); } return std::binary_search(targets_.slots.begin(), targets_.slots.begin() + static_cast(targets_.slot_count), lock_slot); } friend class Property_Object; Static_Write_Guard(Property_Object& owner, Static_Lock_Targets targets) : owner_(&owner), targets_(std::move(targets)) { if constexpr (uses_real_mutexes) { for (std::size_t index = 0; index < targets_.slot_count; ++index) { locks_[index] = std::unique_lock{owner_->mutex(targets_.slots[index])}; } } } public: template decltype(auto) get_index() const requires Schema_Readable_Property_Index, Index> { if (!holds(Index)) { throw std::logic_error("Property is outside the held synchronization set"); } return owner_->template read_unlocked(*this); } template decltype(auto) get() const requires Schema_Readable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; return get_index(); } template decltype(auto) get() const requires Schema_Readable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; return get_index(); } template void set_index(Value&& value) requires Schema_Writable_Property_Index, Index> { if (!holds(Index)) { throw std::logic_error("Property is outside the held synchronization set"); } owner_->template write_unlocked(std::forward(value)); } template void set(Value&& value) requires Schema_Writable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; set_index(std::forward(value)); } template void set(Value&& value) requires Schema_Writable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; set_index(std::forward(value)); } }; public: using Read_Guard = Basic_Read_Guard; using Write_Guard = Basic_Write_Guard; private: template void for_each_readable_impl(Function&& function) const { using Schema = type_descriptor_schema_t; const auto& schema = type_descriptor(); schema.for_each_property([&](auto index, const auto& descriptor) { constexpr std::size_t property_index = decltype(index)::value; if constexpr (Schema::template property_type::readable) { auto value = read_one(); std::invoke(function, index, descriptor, value); } }); } template void for_each_readable_locked_impl(Function&& function) const { using Schema = type_descriptor_schema_t; auto targets = collect_all_static_lock_targets(); Static_Read_Guard guard{*this, std::move(targets)}; type_descriptor().for_each_property([&](auto index, const auto& descriptor) { constexpr std::size_t property_index = decltype(index)::value; if constexpr (Schema::template property_type::readable) { std::invoke(function, index, descriptor, guard.template get_index()); } }); } template void with_all_writable_locked_impl(Function&& function) { using Schema = type_descriptor_schema_t; auto targets = collect_all_static_lock_targets(); Static_Write_Guard guard{*this, std::move(targets)}; std::invoke(std::forward(function), guard); } static const std::type_info& runtime_object_type_impl() noexcept { return typeid(Derived); } static std::size_t runtime_property_count_impl() noexcept { using Schema = type_descriptor_schema_t; return Schema::property_count; } Runtime_Access_Result runtime_read_impl_local(std::string_view key, void* context, Runtime_Read_Callback callback) const { using Schema = type_descriptor_schema_t; auto index = schema_property_index(key); if (!index) { return Runtime_Access_Result::unknown_property; } if (!runtime_property_readable(*index)) { return Runtime_Access_Result::not_readable; } Runtime_Access_Result result = Runtime_Access_Result::unknown_property; visit_schema_property_at(type_descriptor(), *index, [&](auto property_index_constant, const auto&) { constexpr std::size_t property_index = decltype(property_index_constant)::value; using Property = typename Schema::template property_type; using Value = typename Property::value_type; if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) { if constexpr (std::is_reference_v(*this))>) { auto&& value = read_unlocked(*this); callback(context, property_index, key, typeid(Value), std::addressof(value)); } else { Value value = read_unlocked(*this); callback(context, property_index, key, typeid(Value), std::addressof(value)); } } else { auto lock_slot = slot(property_index); Single_Read_View view{*this, lock_slot}; auto emit = [&] { if constexpr (std::is_reference_v(view))>) { auto&& value = read_unlocked(view); callback(context, property_index, key, typeid(Value), std::addressof(value)); } else { Value value = read_unlocked(view); callback(context, property_index, key, typeid(Value), std::addressof(value)); } }; if constexpr (!uses_real_mutexes) { emit(); } else if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { emit(); } else { std::shared_lock lock{mutex(lock_slot)}; emit(); } } result = Runtime_Access_Result::ok; }); return result; } static Runtime_Access_Result runtime_read_impl(const Property_Object_Base& base, std::string_view key, void* context, Runtime_Read_Callback callback) { const auto& self = static_cast(base); return self.runtime_read_impl_local(key, context, callback); } Runtime_Access_Result runtime_write_impl_local(std::string_view key, const std::type_info& value_type, const void* value) { using Schema = type_descriptor_schema_t; auto index = schema_property_index(key); if (!index) { return Runtime_Access_Result::unknown_property; } if (!runtime_property_writable(*index)) { return Runtime_Access_Result::not_writable; } Runtime_Access_Result result = Runtime_Access_Result::unknown_property; visit_schema_property_at(type_descriptor(), *index, [&](auto property_index_constant, const auto&) { constexpr std::size_t property_index = decltype(property_index_constant)::value; using Property = typename Schema::template property_type; using Value = typename Property::value_type; if constexpr (!Property::runtime_copy_writable) { result = Runtime_Access_Result::unsupported_runtime_write; } else if (value_type != typeid(Value)) { result = Runtime_Access_Result::type_mismatch; } else { write_one(*static_cast(value)); result = Runtime_Access_Result::ok; } }); return result; } static Runtime_Access_Result runtime_write_impl(Property_Object_Base& base, std::string_view key, const std::type_info& value_type, const void* value) { auto& self = static_cast(base); return self.runtime_write_impl_local(key, value_type, value); } static const Property_Object_Base::Runtime_Interface* runtime_interface() noexcept { static const Property_Object_Base::Runtime_Interface value{ &runtime_object_type_impl, &runtime_property_count_impl, &runtime_read_impl, &runtime_write_impl }; return &value; } protected: Property_Object() : Property_Object_Base(runtime_interface()), mutex_storage_(default_resolved_synchronization().lock_count) {} explicit Property_Object(Property_Synchronization synchronization) : Property_Object_Base(runtime_interface()) { initialize(type_descriptor(), synchronization.plan); } Property_Object(const Property_Object& other) : Property_Object_Base(runtime_interface()) { copy_synchronization_from(other); } Property_Object(Property_Object&& other) : Property_Object_Base(runtime_interface()) { copy_synchronization_from(other); } Property_Object& operator=(const Property_Object&) noexcept { return *this; } Property_Object& operator=(Property_Object&&) noexcept { return *this; } ~Property_Object() = default; public: const auto& schema() const noexcept { return type_descriptor(); } /// Returns the effective lock topology after intrinsic capability filtering and any per-instance override. Resolved_Synchronization_View resolved_synchronization() const noexcept { using Schema = type_descriptor_schema_t; if (custom_lock_layout_) { return {{custom_lock_layout_.get() + 1, Schema::property_count}, custom_lock_layout_[0]}; } const auto& resolved = default_resolved_synchronization(); return {resolved.lock_slots, resolved.lock_count}; } Derived& unsafe_object() noexcept { return static_cast(*this); } const Derived& unsafe_object() const noexcept { return static_cast(*this); } template auto read() const requires Schema_Readable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; return read_one(); } template auto read() const requires Schema_Readable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; return read_one(); } template void write(Value&& value) requires Schema_Writable_Property_Member, Member> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; write_one(std::forward(value)); } template void write(Value&& value) requires Schema_Writable_Property_Key, Key> { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; write_one(std::forward(value)); } /// Returns the resolved lock slot for a registered member. `unsynchronized_slot` means no mutex is created for that property. template std::size_t lock_slot() const noexcept { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); return slot(index); } private: Basic_Read_Guard lock_shared_impl(std::span keys) const { return Basic_Read_Guard{*this, collect_dynamic_lock_targets(keys, true)}; } Basic_Read_Guard lock_shared_impl(std::initializer_list keys) const { return lock_shared_impl(std::span{keys.begin(), keys.size()}); } Basic_Write_Guard lock_unique_impl(std::span keys) { return Basic_Write_Guard{*this, collect_dynamic_lock_targets(keys, false)}; } Basic_Write_Guard lock_unique_impl(std::initializer_list keys) { return lock_unique_impl(std::span{keys.begin(), keys.size()}); } template auto lock_shared_impl() const { using Schema = type_descriptor_schema_t; static_assert((Schema_Property_Member && ...)); auto targets = collect_static_lock_targets...>(); return Static_Read_Guard{*this, std::move(targets)}; } template auto lock_unique_impl() { using Schema = type_descriptor_schema_t; static_assert((Schema_Property_Member && ...)); auto targets = collect_static_lock_targets...>(); return Static_Write_Guard{*this, std::move(targets)}; } public: /// Runtime-key shared guard. Keys are validated at runtime; unknown or non-readable properties throw `std::invalid_argument`. Lock domains are deduplicated and acquired in stable slot order. Read_Guard lock_shared(std::span keys) const { return lock_shared_impl(keys); } Read_Guard lock_shared(std::initializer_list keys) const { return lock_shared_impl(keys); } /// Runtime-key unique guard. Keys are validated at runtime; unknown or non-writable properties throw `std::invalid_argument`. Lock domains are deduplicated and acquired in stable slot order. Write_Guard lock_unique(std::span keys) { return lock_unique_impl(keys); } Write_Guard lock_unique(std::initializer_list keys) { return lock_unique_impl(keys); } /// Compile-time shared guard for registered readable members. Invalid or non-readable selections are removed by constraints before the function can be called. template auto lock_shared() const requires (Schema_Readable_Property_Member, Members> && ...) { return lock_shared_impl(); } /// Compile-time unique guard for registered writable members. The implementation sorts and deduplicates resolved slots so callers may request members in any order without introducing lock-order inversion. template auto lock_unique() requires (Schema_Writable_Property_Member, Members> && ...) { return lock_unique_impl(); } template void for_each_readable(Function&& function) const { for_each_readable_impl(std::forward(function)); } template void for_each_readable_locked(Function&& function) const { for_each_readable_locked_impl(std::forward(function)); } template void with_all_writable_locked(Function&& function) { with_all_writable_locked_impl(std::forward(function)); } }; }