Files
Structive/core/include/structive/property/schema.hpp
T
2026-08-08 21:21:23 +08:00

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)...);
}
}