682 lines
35 KiB
C++
682 lines
35 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... Attributes>
|
|
struct Type_Metadata {
|
|
using type_metadata_tag = void;
|
|
using attribute_types = Type_List<Attributes...>;
|
|
static_assert((Property_Attribute<Attributes> && ...));
|
|
static_assert(unique_single_value_categories<Attributes...>());
|
|
std::tuple<Attributes...> attributes;
|
|
};
|
|
template <class Type>
|
|
concept Type_Metadata_Type = requires {
|
|
typename Type::type_metadata_tag;
|
|
typename Type::attribute_types;
|
|
};
|
|
template <class... Attributes>
|
|
constexpr auto type_metadata(Attributes&&... attributes) {
|
|
return Type_Metadata<std::decay_t<Attributes>...>{
|
|
{std::forward<Attributes>(attributes)...}};
|
|
}
|
|
using No_Type_Metadata = Type_Metadata<>;
|
|
inline constexpr No_Type_Metadata no_type_metadata{};
|
|
|
|
template <class Object, class Defaults, class Type_Metadata_Value, class... Properties>
|
|
class Object_Schema {
|
|
[[no_unique_address]] Defaults object_defaults_;
|
|
[[no_unique_address]] Type_Metadata_Value type_metadata_;
|
|
Synchronization_Plan synchronization_plan_;
|
|
std::tuple<Properties...> properties_;
|
|
public:
|
|
using property_schema_tag = void;
|
|
using object_type = Object;
|
|
using defaults_type = Defaults;
|
|
using type_metadata_type = Type_Metadata_Value;
|
|
using type_attribute_types = typename Type_Metadata_Value::attribute_types;
|
|
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, Type_Metadata_Value type_metadata_value,
|
|
Synchronization_Plan synchronization_plan,
|
|
std::tuple<Properties...> properties)
|
|
: object_defaults_(std::move(object_defaults)),
|
|
type_metadata_(std::move(type_metadata_value)),
|
|
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_;
|
|
}
|
|
const Type_Metadata_Value& metadata() const noexcept {
|
|
return type_metadata_;
|
|
}
|
|
template <class Category>
|
|
using type_attribute_type = find_attribute_in_list_t<Category, type_attribute_types>;
|
|
template <class Category>
|
|
static constexpr std::size_t type_attribute_count =
|
|
[]<class... Attributes>(Type_List<Attributes...>) {
|
|
return count_attribute_category<Category, Attributes...>();
|
|
}(type_attribute_types{});
|
|
template <class Category>
|
|
static constexpr bool has_type_attribute = type_attribute_count<Category> != 0;
|
|
template <class Category>
|
|
constexpr decltype(auto) type_attribute() const
|
|
requires (type_attribute_count<Category> == 1) {
|
|
using target = type_attribute_type<Category>;
|
|
return std::get<target>(type_metadata_.attributes);
|
|
}
|
|
template <class Function>
|
|
constexpr void for_each_type_attribute(Function&& function) const {
|
|
std::apply([&](const auto&... attributes) {
|
|
(function(attributes), ...);
|
|
}, type_metadata_.attributes);
|
|
}
|
|
template <class Category, class Function>
|
|
constexpr void for_each_type_attribute(Function&& function) const {
|
|
std::apply([&](const auto&... attributes) {
|
|
([&] {
|
|
using type = std::remove_cvref_t<decltype(attributes)>;
|
|
if constexpr (std::same_as<attribute_category_of_t<type>, Category>)
|
|
function(attributes);
|
|
}(), ...);
|
|
}, type_metadata_.attributes);
|
|
}
|
|
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::type_metadata_type;
|
|
typename Schema::type_attribute_types;
|
|
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, class Dependency>
|
|
struct Schema_Dependency_Index;
|
|
template <Property_Schema Schema, auto Member>
|
|
struct Schema_Dependency_Index<Schema, Member_Property_Dependency<Member>> {
|
|
static constexpr std::size_t value = schema_member_property_index_v<Schema, Member>;
|
|
static_assert(value < Schema::property_count, "computed dependency member is not registered in this property schema");
|
|
static_assert(Schema::template property_type<value>::readable, "computed dependency must be readable");
|
|
};
|
|
template <Property_Schema Schema, Fixed_String Key>
|
|
struct Schema_Dependency_Index<Schema, Key_Property_Dependency<Key>> {
|
|
static constexpr std::size_t value = schema_property_index_v<Schema, Key>;
|
|
static_assert(value < Schema::property_count, "computed dependency key is not registered in this property schema");
|
|
static_assert(Schema::template property_type<value>::readable, "computed dependency must be readable");
|
|
};
|
|
template <std::size_t Size>
|
|
consteval bool unique_dependency_indices(const std::array<std::size_t, Size>& indices) {
|
|
for (std::size_t left = 0; left < Size; ++left) {
|
|
for (std::size_t right = left + 1; right < Size; ++right) {
|
|
if (indices[left] == indices[right]) {
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
return true;
|
|
}
|
|
template <Property_Schema Schema, class Dependencies>
|
|
struct Schema_Dependency_Indices;
|
|
template <Property_Schema Schema, class... Dependencies>
|
|
struct Schema_Dependency_Indices<Schema, Property_Dependencies<Dependencies...>> {
|
|
static constexpr std::array<std::size_t, sizeof...(Dependencies)> value{Schema_Dependency_Index<Schema, Dependencies>::value...};
|
|
static_assert(unique_dependency_indices(value), "computed property declares the same dependency more than once");
|
|
};
|
|
template <Property_Schema Schema, std::size_t Index>
|
|
consteval auto schema_property_dependency_indices() requires Schema_Property_Index<Schema, Index> {
|
|
using dependency_spec = typename Schema::template property_type<Index>::dependency_spec;
|
|
return Schema_Dependency_Indices<Schema, dependency_spec>::value;
|
|
}
|
|
template <class Schema, std::size_t Index>
|
|
inline constexpr std::size_t schema_property_dependency_count_v = schema_property_dependency_indices<Schema, Index>().size();
|
|
template <class Schema, std::size_t Property_Index, std::size_t Dependency_Index>
|
|
inline constexpr bool schema_property_depends_on_index_v = [] {
|
|
constexpr auto dependencies = schema_property_dependency_indices<Schema, Property_Index>();
|
|
for (auto index : dependencies) {
|
|
if (index == Dependency_Index) {
|
|
return true;
|
|
}
|
|
}
|
|
return false;
|
|
}();
|
|
template <Property_Schema Schema>
|
|
consteval auto schema_property_dependency_matrix() {
|
|
std::array<std::array<bool, Schema::property_count>, Schema::property_count> matrix{};
|
|
[&]<std::size_t... Index>(std::index_sequence<Index...>) {
|
|
([&] {
|
|
constexpr auto dependencies = schema_property_dependency_indices<Schema, Index>();
|
|
for (auto dependency : dependencies) {
|
|
matrix[Index][dependency] = true;
|
|
}
|
|
}(), ...);
|
|
}(std::make_index_sequence<Schema::property_count>{});
|
|
return matrix;
|
|
}
|
|
template <Property_Schema Schema>
|
|
consteval bool schema_dependency_graph_acyclic() {
|
|
constexpr auto dependencies = schema_property_dependency_matrix<Schema>();
|
|
std::array<std::size_t, Schema::property_count> dependency_counts{};
|
|
std::array<std::size_t, Schema::property_count> ready{};
|
|
std::size_t ready_begin = 0;
|
|
std::size_t ready_end = 0;
|
|
for (std::size_t property = 0; property < Schema::property_count; ++property) {
|
|
for (std::size_t dependency = 0; dependency < Schema::property_count; ++dependency) {
|
|
dependency_counts[property] += dependencies[property][dependency] ? 1u : 0u;
|
|
}
|
|
if (dependency_counts[property] == 0) {
|
|
ready[ready_end++] = property;
|
|
}
|
|
}
|
|
std::size_t resolved_count = 0;
|
|
while (ready_begin != ready_end) {
|
|
auto resolved = ready[ready_begin++];
|
|
++resolved_count;
|
|
for (std::size_t property = 0; property < Schema::property_count; ++property) {
|
|
if (!dependencies[property][resolved]) {
|
|
continue;
|
|
}
|
|
--dependency_counts[property];
|
|
if (dependency_counts[property] == 0) {
|
|
ready[ready_end++] = property;
|
|
}
|
|
}
|
|
}
|
|
return resolved_count == 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> && schema_dependency_graph_acyclic<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>
|
|
inline constexpr bool property_has_derived_synchronization_v = Schema::template property_type<Index>::synchronized_view_read && !Schema::template property_type<Index>::writable;
|
|
template <class Schema, std::size_t Index>
|
|
inline constexpr bool property_owns_synchronization_domain_v = Schema::template property_type<Index>::writable || (Schema::template property_type<Index>::synchronized_view_read && !property_has_derived_synchronization_v<Schema, Index>);
|
|
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;
|
|
constexpr auto owns_synchronization_domain = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
|
return std::array<bool, Schema::property_count>{property_owns_synchronization_domain_v<Schema, Index>...};
|
|
}(std::make_index_sequence<Schema::property_count>{});
|
|
constexpr auto derived_synchronization = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
|
return std::array<bool, Schema::property_count>{property_has_derived_synchronization_v<Schema, Index>...};
|
|
}(std::make_index_sequence<Schema::property_count>{});
|
|
constexpr auto dependencies = schema_property_dependency_matrix<Schema>();
|
|
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 (derived_synchronization[index]) {
|
|
throw std::invalid_argument("Synchronization of computed property is derived from dependencies: " + std::string(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;
|
|
}
|
|
}
|
|
for (std::size_t index = 0; index < Schema::property_count; ++index) {
|
|
if (!owns_synchronization_domain[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;
|
|
}
|
|
}
|
|
if constexpr (Schema::property_count > 0) {
|
|
constexpr auto keys = schema_property_keys<Schema>();
|
|
std::array<bool, Schema::property_count> dependency_resolved{};
|
|
auto resolve_derived_slot = [&](auto&& self, std::size_t index) -> std::size_t {
|
|
if (!derived_synchronization[index] || dependency_resolved[index]) {
|
|
return resolved.lock_slots[index];
|
|
}
|
|
bool has_dependency_domain = false;
|
|
std::size_t dependency_slot = unsynchronized_slot;
|
|
for (std::size_t dependency = 0; dependency < Schema::property_count; ++dependency) {
|
|
if (!dependencies[index][dependency] || (!owns_synchronization_domain[dependency] && !derived_synchronization[dependency])) {
|
|
continue;
|
|
}
|
|
auto current_slot = derived_synchronization[dependency] ? self(self, dependency) : resolved.lock_slots[dependency];
|
|
if (!has_dependency_domain) {
|
|
dependency_slot = current_slot;
|
|
has_dependency_domain = true;
|
|
} else if (dependency_slot != current_slot) {
|
|
throw std::invalid_argument("Computed property dependencies do not share one synchronization domain: " + std::string(keys[index]));
|
|
}
|
|
}
|
|
resolved.lock_slots[index] = has_dependency_domain ? dependency_slot : unsynchronized_slot;
|
|
dependency_resolved[index] = true;
|
|
return resolved.lock_slots[index];
|
|
};
|
|
for (std::size_t index = 0; index < Schema::property_count; ++index) {
|
|
if (derived_synchronization[index]) {
|
|
resolve_derived_slot(resolve_derived_slot, index);
|
|
}
|
|
}
|
|
}
|
|
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 Metadata, class Source, class... Properties>
|
|
auto make_object_schema(Defaults&& object_defaults, Metadata&& metadata,
|
|
Source&& synchronization_source, Properties&&... properties)
|
|
requires Object_Defaults_Type<std::remove_cvref_t<Defaults>> &&
|
|
Type_Metadata_Type<std::remove_cvref_t<Metadata>> &&
|
|
Synchronization_Source_Type<std::remove_cvref_t<Source>> {
|
|
using schema_type = Object_Schema<Object, std::decay_t<Defaults>,
|
|
std::decay_t<Metadata>, std::decay_t<Properties>...>;
|
|
constexpr bool valid_schema = Valid_Property_Schema<schema_type>;
|
|
static_assert(valid_schema, "property dependency graph must be valid and acyclic");
|
|
auto plan = materialize_synchronization_plan<schema_type>(std::forward<Source>(synchronization_source));
|
|
schema_type schema{std::forward<Defaults>(object_defaults),
|
|
std::forward<Metadata>(metadata), std::move(plan),
|
|
std::tuple<std::decay_t<Properties>...>{
|
|
std::forward<Properties>(properties)...}};
|
|
if constexpr (valid_schema) {
|
|
static_cast<void>(resolve_synchronization_plan(schema, schema.synchronization_plan()));
|
|
}
|
|
return schema;
|
|
}
|
|
template <class Object, class Defaults, class Metadata, class Source, class... Properties>
|
|
auto object_schema(Defaults&& object_defaults, Metadata&& metadata,
|
|
Source&& synchronization_source, Properties&&... properties)
|
|
requires Object_Defaults_Type<std::remove_cvref_t<Defaults>> &&
|
|
Type_Metadata_Type<std::remove_cvref_t<Metadata>> &&
|
|
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<Metadata>(metadata),
|
|
std::forward<Source>(synchronization_source),
|
|
std::forward<Properties>(properties)...);
|
|
}
|
|
template <class Object, class Metadata, class Source, class... Properties>
|
|
auto object_schema(Metadata&& metadata, Source&& synchronization_source,
|
|
Properties&&... properties)
|
|
requires Type_Metadata_Type<std::remove_cvref_t<Metadata>> &&
|
|
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<Metadata>(metadata),
|
|
std::forward<Source>(synchronization_source),
|
|
std::forward<Properties>(properties)...);
|
|
}
|
|
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), no_type_metadata, 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, no_type_metadata, std::forward<Source>(synchronization_source), std::forward<Properties>(properties)...);
|
|
}
|
|
template <class Object, class Defaults, class Metadata, class... Properties>
|
|
auto object_schema(Defaults&& object_defaults, Metadata&& metadata,
|
|
Properties&&... properties)
|
|
requires Object_Defaults_Type<std::remove_cvref_t<Defaults>> &&
|
|
Type_Metadata_Type<std::remove_cvref_t<Metadata>> &&
|
|
(Property_Descriptor_Type<std::remove_cvref_t<Properties>> && ...) {
|
|
return make_object_schema<Object>(std::forward<Defaults>(object_defaults),
|
|
std::forward<Metadata>(metadata),
|
|
Synchronization_Plan{},
|
|
std::forward<Properties>(properties)...);
|
|
}
|
|
template <class Object, class Metadata, class... Properties>
|
|
auto object_schema(Metadata&& metadata, Properties&&... properties)
|
|
requires Type_Metadata_Type<std::remove_cvref_t<Metadata>> &&
|
|
(Property_Descriptor_Type<std::remove_cvref_t<Properties>> && ...) {
|
|
return make_object_schema<Object>(no_defaults, std::forward<Metadata>(metadata),
|
|
Synchronization_Plan{},
|
|
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), no_type_metadata, 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, no_type_metadata, Synchronization_Plan{}, std::forward<Properties>(properties)...);
|
|
}
|
|
template <class Object, class... Arguments>
|
|
requires requires(Arguments&&... arguments) {
|
|
object_schema<Object>(std::forward<Arguments>(arguments)...);
|
|
}
|
|
auto object(Arguments&&... arguments) {
|
|
return object_schema<Object>(std::forward<Arguments>(arguments)...);
|
|
}
|
|
}
|