#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(); } }; } 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&, 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: 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(std::string_view key, void* context, Runtime_Read_Callback callback) const { return runtime_interface_->read(*this, key, context, callback); } 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); } }; 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: 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)); } 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); using Property = typename Schema::template property_type; static_assert(Property::readable); if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) { return owner_->template read_unlocked(*this); } 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); using Property = typename Schema::template property_type; static_assert(Property::readable); if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) { return owner_->template read_unlocked(*this); } 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(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, Index, 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 { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(Schema::template property_type::readable); 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(); } }; 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 { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(Schema::template property_type::readable); 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(Schema::template property_type::writable); 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_; [[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 { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(Schema::template property_type::readable); 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_; [[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 { using Schema = type_descriptor_schema_t; static_assert(Index < Schema::property_count); static_assert(Schema::template property_type::readable); 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(Schema::template property_type::writable); 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)); } }; 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(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 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, 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 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(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 (!Schema::template property_type::writable || !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, 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(); } 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; static_assert(index < Schema::property_count); return read_one(); } template auto read_key() const requires Schema_Readable_Property_Key, Key> { 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) requires Schema_Writable_Property_Member, Member> { 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) requires Schema_Writable_Property_Key, Key> { 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); } 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: 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); } 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); } template auto lock_shared() const requires (Schema_Readable_Property_Member, Members> && ...) { return lock_shared_impl(); } 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)); } }; }