#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; enum class Managed_Access_Mode { internal, external, persistence }; enum class Runtime_Access_Result { ok, unknown_property, not_readable, not_writable, type_mismatch }; using Runtime_Read_Callback = void (*)(void*, std::size_t, std::string_view, const std::type_info&, const void*); 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&, Managed_Access_Mode, std::string_view, void*, Runtime_Read_Callback); Runtime_Access_Result (*write)(Property_Object_Base&, Managed_Access_Mode, 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: const std::type_info& runtime_object_type() const noexcept { return runtime_interface_->object_type(); } std::size_t runtime_property_count() const noexcept { return runtime_interface_->property_count(); } Runtime_Access_Result runtime_read(Managed_Access_Mode mode, std::string_view key, void* context, Runtime_Read_Callback callback) const { return runtime_interface_->read(*this, mode, key, context, callback); } Runtime_Access_Result runtime_write(Managed_Access_Mode mode, std::string_view key, const std::type_info& value_type, const void* value) { return runtime_interface_->write(*this, mode, key, value_type, value); } }; template inline constexpr bool property_read_allowed_v = Mode == Managed_Access_Mode::internal ? Schema::template property_type::readable : Mode == Managed_Access_Mode::external ? external_readable_v : persistence_storable_v; template inline constexpr bool property_write_allowed_v = Mode == Managed_Access_Mode::internal ? Schema::template property_type::writable : Mode == Managed_Access_Mode::external ? external_writable_v : persistence_loadable_v; template inline constexpr bool property_visible_v = property_read_allowed_v || property_write_allowed_v; 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))}; } 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: 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::vector lock_slots_; std::size_t lock_count_{}; std::unique_ptr locks_; template void initialize(const Schema& schema, const Synchronization_Plan& plan) { auto resolved = resolve_synchronization_plan(schema, plan); lock_slots_.assign(resolved.lock_slots.begin(), resolved.lock_slots.end()); lock_count_ = resolved.lock_count; locks_ = lock_count_ ? std::make_unique(lock_count_) : nullptr; } void copy_synchronization_from(const Property_Object& other) { lock_slots_ = other.lock_slots_; lock_count_ = other.lock_count_; locks_ = lock_count_ ? std::make_unique(lock_count_) : nullptr; } std::size_t slot(std::size_t index) const noexcept { return lock_slots_[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; } template 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{property_read_allowed_v...}; }(std::make_index_sequence{}); return table[index]; } template static bool runtime_property_visible(std::size_t index) { using Schema = type_descriptor_schema_t; static const auto table = [](std::index_sequence) { return std::array{property_visible_v...}; }(std::make_index_sequence{}); return table[index]; } template 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_visible(*index); if (!allowed) { throw std::invalid_argument("Property is not accessible through this view: " + 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 ? property_read_allowed_v : property_visible_v) && ...)); 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 ? property_read_allowed_v : property_write_allowed_v; 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 property_index_{}; std::size_t lock_slot_{}; public: Single_Read_View(const Property_Object& owner, std::size_t property_index, std::size_t lock_slot) : owner_(&owner), property_index_(property_index), lock_slot_(lock_slot) {} template decltype(auto) get() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); 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"); } return owner_->template read_unlocked(*this); } template decltype(auto) get_key() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); 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"); } return owner_->template read_unlocked(*this); } }; template auto read_one() const { using Schema = type_descriptor_schema_t; static_assert(property_read_allowed_v); using Value = typename Schema::template property_type::value_type; auto lock_slot = slot(Index); Single_Read_View view{*this, Index, lock_slot}; if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { return Value(read_unlocked(view)); } std::shared_lock lock{locks_[lock_slot]}; return Value(read_unlocked(view)); } template void write_one(Value&& value) { using Schema = type_descriptor_schema_t; static_assert(property_write_allowed_v); auto lock_slot = slot(Index); if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { write_unlocked(std::forward(value)); return; } std::unique_lock lock{locks_[lock_slot]}; write_unlocked(std::forward(value)); } public: template class Read_Guard { const Property_Object* owner_{}; std::vector slots_; std::vector unsynchronized_properties_; std::vector> 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; Read_Guard(const Property_Object& owner, Dynamic_Lock_Targets targets) : owner_(&owner), slots_(std::move(targets.slots)), unsynchronized_properties_(std::move(targets.unsynchronized_properties)) { locks_.reserve(slots_.size()); for (auto lock_slot : slots_) { locks_.emplace_back(owner_->locks_[lock_slot]); } } public: template decltype(auto) get_index() const { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(property_read_allowed_v); 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 { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); return get_index(); } template decltype(auto) get_key() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); return get_index(); } }; template class Write_Guard { Property_Object* owner_{}; std::vector slots_; std::vector unsynchronized_properties_; std::vector> 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; Write_Guard(Property_Object& owner, Dynamic_Lock_Targets targets) : owner_(&owner), slots_(std::move(targets.slots)), unsynchronized_properties_(std::move(targets.unsynchronized_properties)) { locks_.reserve(slots_.size()); for (auto lock_slot : slots_) { locks_.emplace_back(owner_->locks_[lock_slot]); } } public: template decltype(auto) get_index() const { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(property_read_allowed_v); 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 { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); return get_index(); } template decltype(auto) get_key() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); return get_index(); } template void set_index(Value&& value) { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(property_write_allowed_v); 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) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); set_index(std::forward(value)); } template void set_key(Value&& value) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); set_index(std::forward(value)); } }; template class Static_Read_Guard { const Property_Object* owner_{}; Static_Lock_Targets targets_; std::array, Capacity> 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)) { for (std::size_t index = 0; index < targets_.slot_count; ++index) { locks_[index] = std::shared_lock{owner_->locks_[targets_.slots[index]]}; } } public: template decltype(auto) get_index() const { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(property_read_allowed_v); 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 { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); return get_index(); } template decltype(auto) get_key() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); return get_index(); } }; template class Static_Write_Guard { Property_Object* owner_{}; Static_Lock_Targets targets_; std::array, Capacity> 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)) { for (std::size_t index = 0; index < targets_.slot_count; ++index) { locks_[index] = std::unique_lock{owner_->locks_[targets_.slots[index]]}; } } public: template decltype(auto) get_index() const { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(property_read_allowed_v); 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 { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); return get_index(); } template decltype(auto) get_key() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); return get_index(); } template void set_index(Value&& value) { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(property_write_allowed_v); 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) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); set_index(std::forward(value)); } template void set_key(Value&& value) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); set_index(std::forward(value)); } }; 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 (property_read_allowed_v) { 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 (property_read_allowed_v) { 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; } template Runtime_Access_Result runtime_read_mode(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(type_descriptor(), key, [&](auto property_index_constant, const auto&) { constexpr std::size_t property_index = decltype(property_index_constant)::value; using Value = typename Schema::template property_type::value_type; auto lock_slot = slot(property_index); Single_Read_View view{*this, property_index, 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 (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) { emit(); } else { std::shared_lock lock{locks_[lock_slot]}; emit(); } result = Runtime_Access_Result::ok; }); return result; } static Runtime_Access_Result runtime_read_impl(const Property_Object_Base& base, Managed_Access_Mode mode, std::string_view key, void* context, Runtime_Read_Callback callback) { const auto& self = static_cast(base); switch (mode) { case Managed_Access_Mode::internal: return self.template runtime_read_mode(key, context, callback); case Managed_Access_Mode::external: return self.template runtime_read_mode(key, context, callback); case Managed_Access_Mode::persistence: return self.template runtime_read_mode(key, context, callback); } return Runtime_Access_Result::not_readable; } template Runtime_Access_Result runtime_write_mode(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_visible(*index)) { return Runtime_Access_Result::not_writable; } Runtime_Access_Result result = Runtime_Access_Result::unknown_property; visit_schema_property(type_descriptor(), key, [&](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 Accessor = typename Property::accessor_type; using Value = typename Property::value_type; if constexpr (!property_write_allowed_v || !requires(const Accessor& accessor, Derived& object, const Value& candidate) { accessor.write(object, candidate); }) { result = Runtime_Access_Result::not_writable; } 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, Managed_Access_Mode mode, std::string_view key, const std::type_info& value_type, const void* value) { auto& self = static_cast(base); switch (mode) { case Managed_Access_Mode::internal: return self.template runtime_write_mode(key, value_type, value); case Managed_Access_Mode::external: return self.template runtime_write_mode(key, value_type, value); case Managed_Access_Mode::persistence: return self.template runtime_write_mode(key, value_type, value); } return Runtime_Access_Result::not_writable; } 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()) { const auto& schema = type_descriptor(); initialize(schema, schema.synchronization_plan()); } 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(); } Resolved_Synchronization_View resolved_synchronization() const noexcept { return {lock_slots_, lock_count_}; } Derived& unsafe_object() noexcept { return static_cast(*this); } const Derived& unsafe_object() const noexcept { return static_cast(*this); } template auto read() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); return read_one(); } template auto read_key() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); return read_one(); } template void write(Value&& value) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); write_one(std::forward(value)); } template void write_key(Value&& value) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); write_one(std::forward(value)); } 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); } template Read_Guard lock_shared(std::span keys) const { return Read_Guard{*this, collect_dynamic_lock_targets(keys, true)}; } template Read_Guard lock_shared(std::initializer_list keys) const { return lock_shared(std::span{keys.begin(), keys.size()}); } Read_Guard lock_shared(std::span keys) const { return lock_shared(keys); } Read_Guard lock_shared(std::initializer_list keys) const { return lock_shared(keys); } template Write_Guard lock_unique(std::span keys) { return Write_Guard{*this, collect_dynamic_lock_targets(keys, false)}; } template Write_Guard lock_unique(std::initializer_list keys) { return lock_unique(std::span{keys.begin(), keys.size()}); } Write_Guard lock_unique(std::span keys) { return lock_unique(keys); } Write_Guard lock_unique(std::initializer_list keys) { return lock_unique(keys); } template auto lock_shared_mode() 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_shared() const { return lock_shared_mode(); } template auto lock_unique_mode() { 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)}; } template auto lock_unique() { return lock_unique_mode(); } 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)); } template class Capability_View { Property_Object* owner_{}; public: explicit Capability_View(Property_Object& owner) : owner_(&owner) {} template auto read() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); return owner_->template read_one(); } template auto read_key() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); return owner_->template read_one(); } template void write(Value&& value) requires (Mode != Managed_Access_Mode::persistence) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); owner_->template write_one(std::forward(value)); } template void write_key(Value&& value) requires (Mode != Managed_Access_Mode::persistence) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); owner_->template write_one(std::forward(value)); } template void load(Value&& value) requires (Mode == Managed_Access_Mode::persistence) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); owner_->template write_one(std::forward(value)); } template void load_key(Value&& value) requires (Mode == Managed_Access_Mode::persistence) { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); owner_->template write_one(std::forward(value)); } template auto store() const requires (Mode == Managed_Access_Mode::persistence) { return read(); } template auto store_key() const requires (Mode == Managed_Access_Mode::persistence) { return read_key(); } template auto lock_shared() const { return owner_->template lock_shared_mode(); } template auto lock_unique() { return owner_->template lock_unique_mode(); } Read_Guard lock_shared(std::span keys) const { return owner_->template lock_shared(keys); } Write_Guard lock_unique(std::span keys) { return owner_->template lock_unique(keys); } template void for_each_readable(Function&& function) const { owner_->template for_each_readable_impl(std::forward(function)); } template void for_each_readable_locked(Function&& function) const { owner_->template for_each_readable_locked_impl(std::forward(function)); } template void with_all_writable_locked(Function&& function) requires (Mode != Managed_Access_Mode::persistence) { owner_->template with_all_writable_locked_impl(std::forward(function)); } template void with_all_loadable_locked(Function&& function) requires (Mode == Managed_Access_Mode::persistence) { owner_->template with_all_writable_locked_impl(std::forward(function)); } }; template class Const_Capability_View { const Property_Object* owner_{}; public: explicit Const_Capability_View(const Property_Object& owner) : owner_(&owner) {} template auto read() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_member_property_index_v; static_assert(index < Schema::property_count); return owner_->template read_one(); } template auto read_key() const { using Schema = type_descriptor_schema_t; constexpr auto index = schema_property_index_v; static_assert(index < Schema::property_count); return owner_->template read_one(); } template auto store() const requires (Mode == Managed_Access_Mode::persistence) { return read(); } template auto store_key() const requires (Mode == Managed_Access_Mode::persistence) { return read_key(); } template auto lock_shared() const { return owner_->template lock_shared_mode(); } Read_Guard lock_shared(std::span keys) const { return owner_->template lock_shared(keys); } template void for_each_readable(Function&& function) const { owner_->template for_each_readable_impl(std::forward(function)); } template void for_each_readable_locked(Function&& function) const { owner_->template for_each_readable_locked_impl(std::forward(function)); } }; Capability_View external() { return Capability_View{*this}; } Capability_View persistence() { return Capability_View{*this}; } Const_Capability_View external() const { return Const_Capability_View{*this}; } Const_Capability_View persistence() const { return Const_Capability_View{*this}; } }; }