425 lines
22 KiB
C++
425 lines
22 KiB
C++
#pragma once
|
|
#include "descriptor.hpp"
|
|
#include "meta.hpp"
|
|
#include "synchronization.hpp"
|
|
#include <algorithm>
|
|
#include <array>
|
|
#include <concepts>
|
|
#include <cstddef>
|
|
#include <functional>
|
|
#include <optional>
|
|
#include <span>
|
|
#include <stdexcept>
|
|
#include <string>
|
|
#include <string_view>
|
|
#include <tuple>
|
|
#include <type_traits>
|
|
#include <utility>
|
|
#include <vector>
|
|
namespace structive {
|
|
template <class Property>
|
|
consteval std::string_view declared_property_key() {
|
|
using key_type = typename Property::template attribute_type<Key_Category>;
|
|
return key_type::value.view();
|
|
}
|
|
template <class... Properties>
|
|
consteval bool unique_property_keys() {
|
|
constexpr std::array<std::string_view, sizeof...(Properties)> keys{declared_property_key<Properties>()...};
|
|
for (std::size_t i = 0; i < keys.size(); ++i) {
|
|
for (std::size_t j = i + 1; j < keys.size(); ++j) {
|
|
if (keys[i] == keys[j]) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
template <class Left, class Right>
|
|
consteval bool distinct_storage_identity() {
|
|
using left = typename Left::storage_identity;
|
|
using right = typename Right::storage_identity;
|
|
if constexpr (std::same_as<left, void> || std::same_as<right, void>) {
|
|
return true;
|
|
}
|
|
return !std::same_as<left, right>;
|
|
}
|
|
template <class Tuple, std::size_t Left, std::size_t... Right>
|
|
consteval bool distinct_storage_row(std::index_sequence<Right...>) {
|
|
using left = std::tuple_element_t<Left, Tuple>;
|
|
return (distinct_storage_identity<left, std::tuple_element_t<Left + 1 + Right, Tuple>>() && ...);
|
|
}
|
|
template <class... Properties>
|
|
consteval bool unique_property_storage() {
|
|
using tuple = std::tuple<Properties...>;
|
|
if constexpr (sizeof...(Properties) < 2) {
|
|
return true;
|
|
} else {
|
|
return []<std::size_t... Left>(std::index_sequence<Left...>) {
|
|
return (distinct_storage_row<tuple, Left>(std::make_index_sequence<sizeof...(Properties) - Left - 1>{}) && ...);
|
|
}(std::make_index_sequence<sizeof...(Properties) - 1>{});
|
|
}
|
|
}
|
|
template <class... Attributes>
|
|
struct Object_Defaults {
|
|
using object_defaults_tag = void;
|
|
using attribute_types = Type_List<Attributes...>;
|
|
static_assert(unique_single_value_categories<Attributes...>());
|
|
static_assert((Inheritable_Attribute<Attributes> && ...));
|
|
std::tuple<Attributes...> attributes;
|
|
};
|
|
template <class Type>
|
|
concept Object_Defaults_Type = requires {
|
|
typename Type::object_defaults_tag;
|
|
typename Type::attribute_types;
|
|
};
|
|
template <class... Attributes>
|
|
constexpr auto defaults(Attributes&&... attributes) {
|
|
return Object_Defaults<std::decay_t<Attributes>...>{{std::forward<Attributes>(attributes)...}};
|
|
}
|
|
struct No_Defaults {
|
|
using object_defaults_tag = void;
|
|
using attribute_types = Type_List<>;
|
|
std::tuple<> attributes;
|
|
};
|
|
inline constexpr No_Defaults no_defaults{};
|
|
template <class Object, class Defaults, class... Properties>
|
|
class Object_Schema {
|
|
[[no_unique_address]] Defaults object_defaults_;
|
|
Synchronization_Plan synchronization_plan_;
|
|
std::tuple<Properties...> properties_;
|
|
public:
|
|
using property_schema_tag = void;
|
|
using object_type = Object;
|
|
using defaults_type = Defaults;
|
|
using property_types = Type_List<Properties...>;
|
|
static constexpr std::size_t property_count = sizeof...(Properties);
|
|
static_assert((std::same_as<typename Properties::object_type, Object> && ...));
|
|
static_assert(unique_property_keys<Properties...>());
|
|
static_assert(unique_property_storage<Properties...>());
|
|
Object_Schema(Defaults object_defaults, Synchronization_Plan synchronization_plan, std::tuple<Properties...> properties) : object_defaults_(std::move(object_defaults)), synchronization_plan_(std::move(synchronization_plan)), properties_(std::move(properties)) {}
|
|
Object_Schema(const Object_Schema&) = default;
|
|
Object_Schema(Object_Schema&&) noexcept = default;
|
|
Object_Schema& operator=(const Object_Schema&) = delete;
|
|
Object_Schema& operator=(Object_Schema&&) = delete;
|
|
template <std::size_t Index>
|
|
using property_type = std::tuple_element_t<Index, std::tuple<Properties...>>;
|
|
const Defaults& object_defaults() const noexcept {
|
|
return object_defaults_;
|
|
}
|
|
template <auto Selector>
|
|
constexpr const auto& property() const {
|
|
if constexpr (std::integral<decltype(Selector)>) {
|
|
static_assert(Selector >= 0);
|
|
constexpr std::size_t index = static_cast<std::size_t>(Selector);
|
|
static_assert(index < property_count);
|
|
return std::get<index>(properties_);
|
|
} else {
|
|
static_assert(std::is_member_object_pointer_v<decltype(Selector)>);
|
|
using member_traits = Member_Pointer_Traits<decltype(Selector)>;
|
|
static_assert(std::same_as<typename member_traits::object_type, Object>);
|
|
using identity = Member_Storage_Identity<Selector>;
|
|
constexpr auto matches = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
|
return std::array<bool, property_count>{std::same_as<typename std::tuple_element_t<Index, std::tuple<Properties...>>::storage_identity, identity>...};
|
|
}(std::make_index_sequence<property_count>{});
|
|
constexpr std::size_t index = [matches] {
|
|
for (std::size_t value = 0; value < matches.size(); ++value) {
|
|
if (matches[value]) {
|
|
return value;
|
|
}
|
|
}
|
|
return property_count;
|
|
}();
|
|
static_assert(index < property_count, "member is not registered in this property schema");
|
|
return std::get<index>(properties_);
|
|
}
|
|
}
|
|
template <class Function>
|
|
constexpr void for_each_property(Function&& function) const {
|
|
[&]<std::size_t... Index>(std::index_sequence<Index...>) {
|
|
(function(std::integral_constant<std::size_t, Index>{}, std::get<Index>(properties_)), ...);
|
|
}(std::make_index_sequence<property_count>{});
|
|
}
|
|
const Synchronization_Plan& synchronization_plan() const noexcept {
|
|
return synchronization_plan_;
|
|
}
|
|
};
|
|
template <class Schema>
|
|
concept Property_Schema = requires {
|
|
typename Schema::property_schema_tag;
|
|
typename Schema::object_type;
|
|
typename Schema::defaults_type;
|
|
typename Schema::property_types;
|
|
{ Schema::property_count } -> std::convertible_to<std::size_t>;
|
|
};
|
|
template <Property_Schema Schema>
|
|
consteval auto schema_property_keys() {
|
|
return []<std::size_t... Index>(std::index_sequence<Index...>) {
|
|
return std::array<std::string_view, Schema::property_count>{declared_property_key<typename Schema::template property_type<Index>>()...};
|
|
}(std::make_index_sequence<Schema::property_count>{});
|
|
}
|
|
template <Property_Schema Schema>
|
|
constexpr std::optional<std::size_t> schema_property_index(std::string_view key_value) {
|
|
constexpr auto keys = schema_property_keys<Schema>();
|
|
for (std::size_t index = 0; index < keys.size(); ++index) {
|
|
if (keys[index] == key_value) {
|
|
return index;
|
|
}
|
|
}
|
|
return std::nullopt;
|
|
}
|
|
template <Property_Schema Schema, Fixed_String Key>
|
|
consteval std::size_t schema_property_index() {
|
|
constexpr auto keys = schema_property_keys<Schema>();
|
|
for (std::size_t index = 0; index < keys.size(); ++index) {
|
|
if (keys[index] == Key.view()) {
|
|
return index;
|
|
}
|
|
}
|
|
return Schema::property_count;
|
|
}
|
|
template <class Schema, Fixed_String Key>
|
|
inline constexpr std::size_t schema_property_index_v = schema_property_index<Schema, Key>();
|
|
template <class Schema, Fixed_String Key>
|
|
concept Schema_Property_Key = Property_Schema<Schema> && schema_property_index_v<Schema, Key> < Schema::property_count;
|
|
template <Property_Schema Schema, auto Member>
|
|
consteval std::size_t schema_member_property_index() {
|
|
static_assert(std::is_member_object_pointer_v<decltype(Member)>);
|
|
using member_traits = Member_Pointer_Traits<decltype(Member)>;
|
|
static_assert(std::same_as<typename member_traits::object_type, typename Schema::object_type>);
|
|
using identity = Member_Storage_Identity<Member>;
|
|
constexpr auto matches = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
|
return std::array<bool, Schema::property_count>{std::same_as<typename Schema::template property_type<Index>::storage_identity, identity>...};
|
|
}(std::make_index_sequence<Schema::property_count>{});
|
|
for (std::size_t index = 0; index < matches.size(); ++index) {
|
|
if (matches[index]) {
|
|
return index;
|
|
}
|
|
}
|
|
return Schema::property_count;
|
|
}
|
|
template <class Schema, auto Member>
|
|
inline constexpr std::size_t schema_member_property_index_v = schema_member_property_index<Schema, Member>();
|
|
template <class Schema, auto Member>
|
|
concept Schema_Property_Member = Property_Schema<Schema> && std::is_member_object_pointer_v<decltype(Member)> && schema_member_property_index_v<Schema, Member> < Schema::property_count;
|
|
template <class Schema, std::size_t Index>
|
|
concept Schema_Property_Index = Property_Schema<Schema> && Index < Schema::property_count;
|
|
template <class Schema, std::size_t Index, class Category, class Fallback>
|
|
struct Effective_Attribute {
|
|
private:
|
|
using property_attribute = typename Schema::template property_type<Index>::template attribute_type<Category>;
|
|
using default_attribute = find_attribute_in_list_t<Category, typename Schema::defaults_type::attribute_types>;
|
|
public:
|
|
using type = std::conditional_t<!std::same_as<property_attribute, void>, property_attribute, std::conditional_t<!std::same_as<default_attribute, void>, default_attribute, Fallback>>;
|
|
};
|
|
template <class Schema, std::size_t Index, class Category, class Fallback>
|
|
using effective_attribute_t = typename Effective_Attribute<Schema, Index, Category, Fallback>::type;
|
|
using Default_Sensitive_Attribute = Sensitive_Attribute<false>;
|
|
template <class Schema, std::size_t Index>
|
|
inline constexpr bool effective_sensitive_v = effective_attribute_t<Schema, Index, Sensitive_Category, Default_Sensitive_Attribute>::value;
|
|
template <class Schema, std::size_t Index, class Category>
|
|
using property_declared_attribute_t = typename Schema::template property_type<Index>::template attribute_type<Category>;
|
|
template <class Schema, class Category>
|
|
using object_default_attribute_t = find_attribute_in_list_t<Category, typename Schema::defaults_type::attribute_types>;
|
|
template <class Schema, std::size_t Index, class Category>
|
|
inline constexpr bool has_declared_effective_attribute_v = !std::same_as<property_declared_attribute_t<Schema, Index, Category>, void> || !std::same_as<object_default_attribute_t<Schema, Category>, void>;
|
|
template <std::size_t Index, class Category, Property_Schema Schema>
|
|
constexpr decltype(auto) declared_effective_attribute(const Schema& schema) requires has_declared_effective_attribute_v<Schema, Index, Category> {
|
|
using property_attribute = property_declared_attribute_t<Schema, Index, Category>;
|
|
if constexpr (!std::same_as<property_attribute, void>) {
|
|
return schema.template property<Index>().template attribute<Category>();
|
|
} else {
|
|
using default_attribute = object_default_attribute_t<Schema, Category>;
|
|
return std::get<default_attribute>(schema.object_defaults().attributes);
|
|
}
|
|
}
|
|
template <class Schema>
|
|
concept Valid_Property_Schema = Property_Schema<Schema>;
|
|
template <class Schema, std::size_t Index>
|
|
inline constexpr bool property_requires_synchronization_v = Schema::template property_type<Index>::writable || Schema::template property_type<Index>::synchronized_view_read;
|
|
template <class Schema, std::size_t Index>
|
|
concept Schema_Readable_Property_Index = Schema_Property_Index<Schema, Index> && Schema::template property_type<Index>::readable;
|
|
template <class Schema, std::size_t Index>
|
|
concept Schema_Writable_Property_Index = Schema_Property_Index<Schema, Index> && Schema::template property_type<Index>::writable;
|
|
template <class Schema, auto Member>
|
|
concept Schema_Readable_Property_Member = Schema_Property_Member<Schema, Member> && Schema::template property_type<schema_member_property_index_v<Schema, Member>>::readable;
|
|
template <class Schema, auto Member>
|
|
concept Schema_Writable_Property_Member = Schema_Property_Member<Schema, Member> && Schema::template property_type<schema_member_property_index_v<Schema, Member>>::writable;
|
|
template <class Schema, Fixed_String Key>
|
|
concept Schema_Readable_Property_Key = Schema_Property_Key<Schema, Key> && Schema::template property_type<schema_property_index_v<Schema, Key>>::readable;
|
|
template <class Schema, Fixed_String Key>
|
|
concept Schema_Writable_Property_Key = Schema_Property_Key<Schema, Key> && Schema::template property_type<schema_property_index_v<Schema, Key>>::writable;
|
|
template <Valid_Property_Schema Schema>
|
|
Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_plan(const Schema&, const Synchronization_Plan& plan) {
|
|
using resolved_type = Resolved_Synchronization_Plan<Schema::property_count>;
|
|
constexpr auto unsynchronized_slot = resolved_type::unsynchronized_slot;
|
|
std::array<std::size_t, Schema::property_count> logical{};
|
|
std::array<bool, Schema::property_count> explicitly_configured{};
|
|
std::size_t next_token = Schema::property_count + 1;
|
|
if (plan.default_mode() == Synchronization_Default::independent) {
|
|
for (std::size_t index = 0; index < Schema::property_count; ++index) {
|
|
logical[index] = index;
|
|
}
|
|
} else if (plan.default_mode() == Synchronization_Default::shared) {
|
|
logical.fill(Schema::property_count);
|
|
} else {
|
|
logical.fill(unsynchronized_slot);
|
|
}
|
|
auto require_index = [](std::string_view key_value) {
|
|
auto index = schema_property_index<Schema>(key_value);
|
|
if (!index) {
|
|
throw std::invalid_argument("Synchronization plan references unknown property: " + std::string(key_value));
|
|
}
|
|
return *index;
|
|
};
|
|
auto mark = [&](std::size_t index, std::string_view key_value) {
|
|
if (explicitly_configured[index]) {
|
|
throw std::invalid_argument("Synchronization plan configures property more than once: " + std::string(key_value));
|
|
}
|
|
explicitly_configured[index] = true;
|
|
};
|
|
for (const auto& rule : plan.property_rules()) {
|
|
auto index = require_index(rule.property);
|
|
mark(index, rule.property);
|
|
logical[index] = rule.mode == Sync_Property_Rule::Mode::unsynchronized ? unsynchronized_slot : next_token++;
|
|
}
|
|
std::vector<std::string_view> group_names;
|
|
group_names.reserve(plan.group_rules().size());
|
|
for (const auto& rule : plan.group_rules()) {
|
|
if (rule.properties.empty()) {
|
|
throw std::invalid_argument("Synchronization group cannot be empty: " + rule.name);
|
|
}
|
|
if (std::find(group_names.begin(), group_names.end(), rule.name) != group_names.end()) {
|
|
throw std::invalid_argument("Synchronization group name is duplicated: " + rule.name);
|
|
}
|
|
group_names.push_back(rule.name);
|
|
auto token = next_token++;
|
|
for (const auto& key_value : rule.properties) {
|
|
auto index = require_index(key_value);
|
|
mark(index, key_value);
|
|
logical[index] = token;
|
|
}
|
|
}
|
|
constexpr auto requires_synchronization = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
|
return std::array<bool, Schema::property_count>{property_requires_synchronization_v<Schema, Index>...};
|
|
}(std::make_index_sequence<Schema::property_count>{});
|
|
for (std::size_t index = 0; index < Schema::property_count; ++index) {
|
|
if (!requires_synchronization[index]) {
|
|
logical[index] = unsynchronized_slot;
|
|
}
|
|
}
|
|
resolved_type resolved;
|
|
resolved.lock_slots.fill(unsynchronized_slot);
|
|
std::vector<std::pair<std::size_t, std::size_t>> token_slots;
|
|
token_slots.reserve(Schema::property_count);
|
|
for (std::size_t index = 0; index < Schema::property_count; ++index) {
|
|
if (logical[index] == unsynchronized_slot) {
|
|
continue;
|
|
}
|
|
auto it = std::find_if(token_slots.begin(), token_slots.end(), [&](const auto& item) {
|
|
return item.first == logical[index];
|
|
});
|
|
if (it == token_slots.end()) {
|
|
auto slot = resolved.lock_count++;
|
|
token_slots.emplace_back(logical[index], slot);
|
|
resolved.lock_slots[index] = slot;
|
|
} else {
|
|
resolved.lock_slots[index] = it->second;
|
|
}
|
|
}
|
|
return resolved;
|
|
}
|
|
template <Property_Schema Schema, auto Member>
|
|
consteval std::string_view schema_member_property_key() requires Schema_Property_Member<Schema, Member> {
|
|
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
|
return declared_property_key<typename Schema::template property_type<index>>();
|
|
}
|
|
template <class Schema>
|
|
void apply_synchronization_rule(Synchronization_Plan& plan, Sync_Default_Rule rule) {
|
|
plan.apply(rule);
|
|
}
|
|
template <class Schema>
|
|
void apply_synchronization_rule(Synchronization_Plan& plan, Sync_Property_Rule rule) {
|
|
plan.apply(std::move(rule));
|
|
}
|
|
template <class Schema>
|
|
void apply_synchronization_rule(Synchronization_Plan& plan, Sync_Group_Rule rule) {
|
|
plan.apply(std::move(rule));
|
|
}
|
|
template <class Schema, auto Member, Sync_Property_Rule::Mode Mode>
|
|
void apply_synchronization_rule(Synchronization_Plan& plan, Sync_Member_Rule<Member, Mode>) {
|
|
static_assert(Schema_Property_Member<Schema, Member>);
|
|
constexpr auto key_value = schema_member_property_key<Schema, Member>();
|
|
if constexpr (Mode == Sync_Property_Rule::Mode::independent) {
|
|
plan.independent(key_value);
|
|
} else {
|
|
plan.unsynchronized(key_value);
|
|
}
|
|
}
|
|
template <class Schema, auto... Members>
|
|
void apply_synchronization_rule(Synchronization_Plan& plan, const Sync_Member_Group_Rule<Members...>& rule) {
|
|
static_assert((Schema_Property_Member<Schema, Members> && ...));
|
|
constexpr std::array<std::string_view, sizeof...(Members)> keys{schema_member_property_key<Schema, Members>()...};
|
|
plan.group(rule.name, std::span<const std::string_view>{keys});
|
|
}
|
|
template <class Schema, Synchronization_Source_Type Source>
|
|
Synchronization_Plan materialize_synchronization_plan(Source&& source) {
|
|
if constexpr (Synchronization_Plan_Type<std::remove_cvref_t<Source>>) {
|
|
return std::forward<Source>(source);
|
|
} else {
|
|
Synchronization_Plan plan;
|
|
std::apply([&](auto&&... rules) {
|
|
(apply_synchronization_rule<Schema>(plan, std::forward<decltype(rules)>(rules)), ...);
|
|
}, std::forward<Source>(source).rules);
|
|
return plan;
|
|
}
|
|
}
|
|
template <Valid_Property_Schema Schema, class Function>
|
|
bool visit_schema_property_at(const Schema& schema, std::size_t index_value, Function&& function) {
|
|
bool found = false;
|
|
schema.for_each_property([&](auto index, const auto& descriptor) {
|
|
if (!found && decltype(index)::value == index_value) {
|
|
std::invoke(function, index, descriptor);
|
|
found = true;
|
|
}
|
|
});
|
|
return found;
|
|
}
|
|
template <Valid_Property_Schema Schema, class Function>
|
|
bool visit_schema_property(const Schema& schema, std::string_view key_value, Function&& function) {
|
|
auto index = schema_property_index<Schema>(key_value);
|
|
if (!index) {
|
|
return false;
|
|
}
|
|
return visit_schema_property_at(schema, *index, std::forward<Function>(function));
|
|
}
|
|
template <class Object, class Defaults, class Source, class... Properties>
|
|
auto make_object_schema(Defaults&& object_defaults, Source&& synchronization_source, Properties&&... properties) requires Synchronization_Source_Type<std::remove_cvref_t<Source>> {
|
|
using schema_type = Object_Schema<Object, std::decay_t<Defaults>, std::decay_t<Properties>...>;
|
|
static_assert(Valid_Property_Schema<schema_type>);
|
|
auto plan = materialize_synchronization_plan<schema_type>(std::forward<Source>(synchronization_source));
|
|
schema_type schema{std::forward<Defaults>(object_defaults), std::move(plan), std::tuple<std::decay_t<Properties>...>{std::forward<Properties>(properties)...}};
|
|
static_cast<void>(resolve_synchronization_plan(schema, schema.synchronization_plan()));
|
|
return schema;
|
|
}
|
|
template <class Object, class Defaults, class Source, class... Properties>
|
|
auto object_schema(Defaults&& object_defaults, Source&& synchronization_source, Properties&&... properties) requires Object_Defaults_Type<std::remove_cvref_t<Defaults>> && Synchronization_Source_Type<std::remove_cvref_t<Source>> && (Property_Descriptor_Type<std::remove_cvref_t<Properties>> && ...) {
|
|
return make_object_schema<Object>(std::forward<Defaults>(object_defaults), std::forward<Source>(synchronization_source), std::forward<Properties>(properties)...);
|
|
}
|
|
template <class Object, class Source, class... Properties>
|
|
auto object_schema(Source&& synchronization_source, Properties&&... properties) requires Synchronization_Source_Type<std::remove_cvref_t<Source>> && (Property_Descriptor_Type<std::remove_cvref_t<Properties>> && ...) {
|
|
return make_object_schema<Object>(no_defaults, std::forward<Source>(synchronization_source), std::forward<Properties>(properties)...);
|
|
}
|
|
template <class Object, class Defaults, class... Properties>
|
|
auto object_schema(Defaults&& object_defaults, Properties&&... properties) requires Object_Defaults_Type<std::remove_cvref_t<Defaults>> && (Property_Descriptor_Type<std::remove_cvref_t<Properties>> && ...) {
|
|
return make_object_schema<Object>(std::forward<Defaults>(object_defaults), Synchronization_Plan{}, std::forward<Properties>(properties)...);
|
|
}
|
|
template <class Object, class... Properties>
|
|
auto object_schema(Properties&&... properties) requires (Property_Descriptor_Type<std::remove_cvref_t<Properties>> && ...) {
|
|
return make_object_schema<Object>(no_defaults, Synchronization_Plan{}, std::forward<Properties>(properties)...);
|
|
}
|
|
template <class Object, class... Arguments>
|
|
auto object(Arguments&&... arguments) {
|
|
return object_schema<Object>(std::forward<Arguments>(arguments)...);
|
|
}
|
|
}
|