Files
Structive/core/include/structive/property/property_object.hpp
T
2026-08-07 17:50:04 +08:00

892 lines
42 KiB
C++

#pragma once
#include "type_descriptor.hpp"
#include <algorithm>
#include <array>
#include <concepts>
#include <cstddef>
#include <functional>
#include <initializer_list>
#include <memory>
#include <limits>
#include <mutex>
#include <shared_mutex>
#include <span>
#include <stdexcept>
#include <string>
#include <string_view>
#include <typeinfo>
#include <type_traits>
#include <utility>
#include <vector>
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 <class Mutex>
concept Shared_Lockable = requires(Mutex& mutex) {
mutex.lock();
mutex.unlock();
mutex.lock_shared();
mutex.unlock_shared();
};
template <class Policy>
concept Synchronization_Policy = requires {
typename Policy::mutex_type;
} && Shared_Lockable<typename Policy::mutex_type>;
namespace detail {
template <class Mutex, bool Stores_Mutexes = !std::same_as<Mutex, Null_Shared_Mutex>>
class Mutex_Storage;
template <class Mutex>
class Mutex_Storage<Mutex, true> {
std::unique_ptr<Mutex[]> mutexes_;
public:
Mutex_Storage() = default;
explicit Mutex_Storage(std::size_t count) : mutexes_(count ? std::make_unique<Mutex[]>(count) : nullptr) {}
void reset(std::size_t count) {
mutexes_ = count ? std::make_unique<Mutex[]>(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>
class Mutex_Storage<Mutex, false> {
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 <class Object, Synchronization_Policy Policy>
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 <class Object, Synchronization_Source_Type Source>
Property_Synchronization property_synchronization(Source&& source) requires Property_Described_Object<Object> {
using Schema = type_descriptor_schema_t<Object>;
return {materialize_synchronization_plan<Schema>(std::forward<Source>(source))};
}
struct Resolved_Synchronization_View {
static constexpr std::size_t unsynchronized_slot = std::numeric_limits<std::size_t>::max();
std::span<const std::size_t> 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 Derived, Synchronization_Policy Lock_Policy = Shared_Mutex_Policy>
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<mutex_type, Null_Shared_Mutex>;
struct Empty_Guard_Locks {};
struct Dynamic_Lock_Targets {
std::vector<std::size_t> slots;
std::vector<std::size_t> unsynchronized_properties;
};
template <std::size_t Capacity>
struct Static_Lock_Targets {
std::array<std::size_t, Capacity> slots{};
std::size_t slot_count{};
std::array<std::size_t, Capacity> unsynchronized_properties{};
std::size_t unsynchronized_count{};
};
std::unique_ptr<std::size_t[]> custom_lock_layout_;
[[no_unique_address]] detail::Mutex_Storage<mutex_type> mutex_storage_;
static const auto& default_resolved_synchronization() {
static const auto resolved = resolve_synchronization_plan(type_descriptor<Derived>(), type_descriptor<Derived>().synchronization_plan());
return resolved;
}
template <class Schema>
void initialize(const Schema& schema, const Synchronization_Plan& plan) {
auto resolved = resolve_synchronization_plan(schema, plan);
auto layout = std::make_unique<std::size_t[]>(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<Derived>;
if (other.custom_lock_layout_) {
auto layout = std::make_unique<std::size_t[]>(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 <std::size_t Capacity>
static void sort_unique_prefix(std::array<std::size_t, Capacity>& 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<Derived>;
static const auto table = []<std::size_t... Index>(std::index_sequence<Index...>) {
return std::array<bool, Schema::property_count>{Schema::template property_type<Index>::readable...};
}(std::make_index_sequence<Schema::property_count>{});
return table[index];
}
static bool runtime_property_writable(std::size_t index) {
using Schema = type_descriptor_schema_t<Derived>;
static const auto table = []<std::size_t... Index>(std::index_sequence<Index...>) {
return std::array<bool, Schema::property_count>{Schema::template property_type<Index>::writable...};
}(std::make_index_sequence<Schema::property_count>{});
return table[index];
}
Dynamic_Lock_Targets collect_dynamic_lock_targets(std::span<const std::string_view> keys, bool shared_access) const {
using Schema = type_descriptor_schema_t<Derived>;
Dynamic_Lock_Targets targets;
targets.slots.reserve(keys.size());
targets.unsynchronized_properties.reserve(keys.size());
for (auto key : keys) {
auto index = schema_property_index<Schema>(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 <bool Shared_Access, std::size_t... Index>
Static_Lock_Targets<sizeof...(Index)> collect_static_lock_targets() const {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(((Shared_Access ? Schema::template property_type<Index>::readable : Schema::template property_type<Index>::writable) && ...));
Static_Lock_Targets<sizeof...(Index)> targets;
auto collect = [&]<std::size_t Property_Index>() {
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()<Index>(), ...);
sort_unique_prefix(targets.slots, targets.slot_count);
sort_unique_prefix(targets.unsynchronized_properties, targets.unsynchronized_count);
return targets;
}
template <bool Shared_Access>
auto collect_all_static_lock_targets() const {
using Schema = type_descriptor_schema_t<Derived>;
Static_Lock_Targets<Schema::property_count> targets;
auto collect = [&]<std::size_t Index>() {
constexpr bool allowed = Shared_Access ? Schema::template property_type<Index>::readable : Schema::template property_type<Index>::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::size_t... Index>(std::index_sequence<Index...>) {
(collect.template operator()<Index>(), ...);
}(std::make_index_sequence<Schema::property_count>{});
sort_unique_prefix(targets.slots, targets.slot_count);
sort_unique_prefix(targets.unsynchronized_properties, targets.unsynchronized_count);
return targets;
}
template <std::size_t Index, class View>
decltype(auto) read_unlocked(const View& view) const {
using Schema = type_descriptor_schema_t<Derived>;
const auto& descriptor = type_descriptor<Derived>().template property<Index>();
using Accessor = typename Schema::template property_type<Index>::accessor_type;
if constexpr (Accessor::synchronized_view_read) {
return descriptor.accessor.read(view);
} else {
return descriptor.accessor.read(static_cast<const Derived&>(*this));
}
}
template <std::size_t Index, class Value>
void write_unlocked(Value&& value) {
const auto& descriptor = type_descriptor<Derived>().template property<Index>();
descriptor.accessor.write(static_cast<Derived&>(*this), std::forward<Value>(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 <auto Member>
decltype(auto) get() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
using Property = typename Schema::template property_type<index>;
static_assert(Property::readable);
if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) {
return owner_->template read_unlocked<index>(*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<index>(*this);
}
template <Fixed_String Key>
decltype(auto) get_key() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
using Property = typename Schema::template property_type<index>;
static_assert(Property::readable);
if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) {
return owner_->template read_unlocked<index>(*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<index>(*this);
}
};
template <std::size_t Index>
auto read_one() const {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(Schema::template property_type<Index>::readable);
using Property = typename Schema::template property_type<Index>;
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<Index>(*this));
}
auto lock_slot = slot(Index);
Single_Read_View view{*this, Index, lock_slot};
if constexpr (!uses_real_mutexes) {
return Value(read_unlocked<Index>(view));
}
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
return Value(read_unlocked<Index>(view));
}
std::shared_lock lock{mutex(lock_slot)};
return Value(read_unlocked<Index>(view));
}
template <std::size_t Index, class Value>
void write_one(Value&& value) {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(Schema::template property_type<Index>::writable);
if constexpr (!uses_real_mutexes) {
write_unlocked<Index>(std::forward<Value>(value));
return;
}
auto lock_slot = slot(Index);
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
write_unlocked<Index>(std::forward<Value>(value));
return;
}
std::unique_lock lock{mutex(lock_slot)};
write_unlocked<Index>(std::forward<Value>(value));
}
private:
class Basic_Read_Guard {
const Property_Object* owner_{};
std::vector<std::size_t> slots_;
std::vector<std::size_t> unsynchronized_properties_;
[[no_unique_address]] std::conditional_t<uses_real_mutexes, std::vector<std::shared_lock<mutex_type>>, 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 <std::size_t Index>
decltype(auto) get_index() const {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(Index < Schema::property_count);
static_assert(Schema::template property_type<Index>::readable);
if (!holds(Index)) {
throw std::logic_error("Property is outside the held synchronization set");
}
return owner_->template read_unlocked<Index>(*this);
}
template <auto Member>
decltype(auto) get() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
return get_index<index>();
}
template <Fixed_String Key>
decltype(auto) get_key() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
return get_index<index>();
}
};
class Basic_Write_Guard {
Property_Object* owner_{};
std::vector<std::size_t> slots_;
std::vector<std::size_t> unsynchronized_properties_;
[[no_unique_address]] std::conditional_t<uses_real_mutexes, std::vector<std::unique_lock<mutex_type>>, 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 <std::size_t Index>
decltype(auto) get_index() const {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(Index < Schema::property_count);
static_assert(Schema::template property_type<Index>::readable);
if (!holds(Index)) {
throw std::logic_error("Property is outside the held synchronization set");
}
return owner_->template read_unlocked<Index>(*this);
}
template <auto Member>
decltype(auto) get() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
return get_index<index>();
}
template <Fixed_String Key>
decltype(auto) get_key() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
return get_index<index>();
}
template <std::size_t Index, class Value>
void set_index(Value&& value) {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(Index < Schema::property_count);
static_assert(Schema::template property_type<Index>::writable);
if (!holds(Index)) {
throw std::logic_error("Property is outside the held synchronization set");
}
owner_->template write_unlocked<Index>(std::forward<Value>(value));
}
template <auto Member, class Value>
void set(Value&& value) {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
set_index<index>(std::forward<Value>(value));
}
template <Fixed_String Key, class Value>
void set_key(Value&& value) {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
set_index<index>(std::forward<Value>(value));
}
};
template <std::size_t Capacity>
class Static_Read_Guard {
const Property_Object* owner_{};
Static_Lock_Targets<Capacity> targets_;
[[no_unique_address]] std::conditional_t<uses_real_mutexes, std::array<std::shared_lock<mutex_type>, 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<std::ptrdiff_t>(targets_.unsynchronized_count), index);
}
return std::binary_search(targets_.slots.begin(), targets_.slots.begin() + static_cast<std::ptrdiff_t>(targets_.slot_count), lock_slot);
}
friend class Property_Object;
Static_Read_Guard(const Property_Object& owner, Static_Lock_Targets<Capacity> 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<mutex_type>{owner_->mutex(targets_.slots[index])};
}
}
}
public:
template <std::size_t Index>
decltype(auto) get_index() const {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(Index < Schema::property_count);
static_assert(Schema::template property_type<Index>::readable);
if (!holds(Index)) {
throw std::logic_error("Property is outside the held synchronization set");
}
return owner_->template read_unlocked<Index>(*this);
}
template <auto Member>
decltype(auto) get() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
return get_index<index>();
}
template <Fixed_String Key>
decltype(auto) get_key() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
return get_index<index>();
}
};
template <std::size_t Capacity>
class Static_Write_Guard {
Property_Object* owner_{};
Static_Lock_Targets<Capacity> targets_;
[[no_unique_address]] std::conditional_t<uses_real_mutexes, std::array<std::unique_lock<mutex_type>, 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<std::ptrdiff_t>(targets_.unsynchronized_count), index);
}
return std::binary_search(targets_.slots.begin(), targets_.slots.begin() + static_cast<std::ptrdiff_t>(targets_.slot_count), lock_slot);
}
friend class Property_Object;
Static_Write_Guard(Property_Object& owner, Static_Lock_Targets<Capacity> 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<mutex_type>{owner_->mutex(targets_.slots[index])};
}
}
}
public:
template <std::size_t Index>
decltype(auto) get_index() const {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(Index < Schema::property_count);
static_assert(Schema::template property_type<Index>::readable);
if (!holds(Index)) {
throw std::logic_error("Property is outside the held synchronization set");
}
return owner_->template read_unlocked<Index>(*this);
}
template <auto Member>
decltype(auto) get() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
return get_index<index>();
}
template <Fixed_String Key>
decltype(auto) get_key() const {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
return get_index<index>();
}
template <std::size_t Index, class Value>
void set_index(Value&& value) {
using Schema = type_descriptor_schema_t<Derived>;
static_assert(Index < Schema::property_count);
static_assert(Schema::template property_type<Index>::writable);
if (!holds(Index)) {
throw std::logic_error("Property is outside the held synchronization set");
}
owner_->template write_unlocked<Index>(std::forward<Value>(value));
}
template <auto Member, class Value>
void set(Value&& value) {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
set_index<index>(std::forward<Value>(value));
}
template <Fixed_String Key, class Value>
void set_key(Value&& value) {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
set_index<index>(std::forward<Value>(value));
}
};
public:
using Read_Guard = Basic_Read_Guard;
using Write_Guard = Basic_Write_Guard;
private:
template <class Function>
void for_each_readable_impl(Function&& function) const {
using Schema = type_descriptor_schema_t<Derived>;
const auto& schema = type_descriptor<Derived>();
schema.for_each_property([&](auto index, const auto& descriptor) {
constexpr std::size_t property_index = decltype(index)::value;
if constexpr (Schema::template property_type<property_index>::readable) {
auto value = read_one<property_index>();
std::invoke(function, index, descriptor, value);
}
});
}
template <class Function>
void for_each_readable_locked_impl(Function&& function) const {
using Schema = type_descriptor_schema_t<Derived>;
auto targets = collect_all_static_lock_targets<true>();
Static_Read_Guard<Schema::property_count> guard{*this, std::move(targets)};
type_descriptor<Derived>().for_each_property([&](auto index, const auto& descriptor) {
constexpr std::size_t property_index = decltype(index)::value;
if constexpr (Schema::template property_type<property_index>::readable) {
std::invoke(function, index, descriptor, guard.template get_index<property_index>());
}
});
}
template <class Function>
void with_all_writable_locked_impl(Function&& function) {
using Schema = type_descriptor_schema_t<Derived>;
auto targets = collect_all_static_lock_targets<false>();
Static_Write_Guard<Schema::property_count> guard{*this, std::move(targets)};
std::invoke(std::forward<Function>(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<Derived>;
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<Derived>;
auto index = schema_property_index<Schema>(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<Derived>(), 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<property_index>;
using Value = typename Property::value_type;
if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) {
if constexpr (std::is_reference_v<decltype(read_unlocked<property_index>(*this))>) {
auto&& value = read_unlocked<property_index>(*this);
callback(context, property_index, key, typeid(Value), std::addressof(value));
} else {
Value value = read_unlocked<property_index>(*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<decltype(read_unlocked<property_index>(view))>) {
auto&& value = read_unlocked<property_index>(view);
callback(context, property_index, key, typeid(Value), std::addressof(value));
} else {
Value value = read_unlocked<property_index>(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<const Property_Object&>(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<Derived>;
auto index = schema_property_index<Schema>(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<Derived>(), 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<property_index>;
using Accessor = typename Property::accessor_type;
using Value = typename Property::value_type;
if constexpr (!Schema::template property_type<property_index>::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<property_index>(*static_cast<const Value*>(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<Property_Object&>(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<Derived>(), 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<Derived>();
}
Resolved_Synchronization_View resolved_synchronization() const noexcept {
using Schema = type_descriptor_schema_t<Derived>;
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<Derived&>(*this);
}
const Derived& unsafe_object() const noexcept {
return static_cast<const Derived&>(*this);
}
template <auto Member>
auto read() const requires Schema_Readable_Property_Member<type_descriptor_schema_t<Derived>, Member> {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
return read_one<index>();
}
template <Fixed_String Key>
auto read_key() const requires Schema_Readable_Property_Key<type_descriptor_schema_t<Derived>, Key> {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
return read_one<index>();
}
template <auto Member, class Value>
void write(Value&& value) requires Schema_Writable_Property_Member<type_descriptor_schema_t<Derived>, Member> {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
write_one<index>(std::forward<Value>(value));
}
template <Fixed_String Key, class Value>
void write_key(Value&& value) requires Schema_Writable_Property_Key<type_descriptor_schema_t<Derived>, Key> {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_property_index_v<Schema, Key>;
static_assert(index < Schema::property_count);
write_one<index>(std::forward<Value>(value));
}
template <auto Member>
std::size_t lock_slot() const noexcept {
using Schema = type_descriptor_schema_t<Derived>;
constexpr auto index = schema_member_property_index_v<Schema, Member>;
static_assert(index < Schema::property_count);
return slot(index);
}
private:
Basic_Read_Guard lock_shared_impl(std::span<const std::string_view> keys) const {
return Basic_Read_Guard{*this, collect_dynamic_lock_targets(keys, true)};
}
Basic_Read_Guard lock_shared_impl(std::initializer_list<std::string_view> keys) const {
return lock_shared_impl(std::span<const std::string_view>{keys.begin(), keys.size()});
}
Basic_Write_Guard lock_unique_impl(std::span<const std::string_view> keys) {
return Basic_Write_Guard{*this, collect_dynamic_lock_targets(keys, false)};
}
Basic_Write_Guard lock_unique_impl(std::initializer_list<std::string_view> keys) {
return lock_unique_impl(std::span<const std::string_view>{keys.begin(), keys.size()});
}
template <auto... Members>
auto lock_shared_impl() const {
using Schema = type_descriptor_schema_t<Derived>;
static_assert((Schema_Property_Member<Schema, Members> && ...));
auto targets = collect_static_lock_targets<true, schema_member_property_index_v<Schema, Members>...>();
return Static_Read_Guard<sizeof...(Members)>{*this, std::move(targets)};
}
template <auto... Members>
auto lock_unique_impl() {
using Schema = type_descriptor_schema_t<Derived>;
static_assert((Schema_Property_Member<Schema, Members> && ...));
auto targets = collect_static_lock_targets<false, schema_member_property_index_v<Schema, Members>...>();
return Static_Write_Guard<sizeof...(Members)>{*this, std::move(targets)};
}
public:
Read_Guard lock_shared(std::span<const std::string_view> keys) const {
return lock_shared_impl(keys);
}
Read_Guard lock_shared(std::initializer_list<std::string_view> keys) const {
return lock_shared_impl(keys);
}
Write_Guard lock_unique(std::span<const std::string_view> keys) {
return lock_unique_impl(keys);
}
Write_Guard lock_unique(std::initializer_list<std::string_view> keys) {
return lock_unique_impl(keys);
}
template <auto... Members>
auto lock_shared() const requires (Schema_Readable_Property_Member<type_descriptor_schema_t<Derived>, Members> && ...) {
return lock_shared_impl<Members...>();
}
template <auto... Members>
auto lock_unique() requires (Schema_Writable_Property_Member<type_descriptor_schema_t<Derived>, Members> && ...) {
return lock_unique_impl<Members...>();
}
template <class Function>
void for_each_readable(Function&& function) const {
for_each_readable_impl(std::forward<Function>(function));
}
template <class Function>
void for_each_readable_locked(Function&& function) const {
for_each_readable_locked_impl(std::forward<Function>(function));
}
template <class Function>
void with_all_writable_locked(Function&& function) {
with_all_writable_locked_impl(std::forward<Function>(function));
}
};
}