#pragma once #include "descriptor.hpp" #include "meta.hpp" #include "synchronization.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace structive { template consteval std::string_view declared_property_key() { using key_type = typename Property::template attribute_type; return key_type::value.view(); } template consteval bool unique_property_keys() { constexpr std::array keys{declared_property_key()...}; 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 consteval bool distinct_storage_identity() { using left = typename Left::storage_identity; using right = typename Right::storage_identity; if constexpr (std::same_as || std::same_as) { return true; } return !std::same_as; } template consteval bool distinct_storage_row(std::index_sequence) { using left = std::tuple_element_t; return (distinct_storage_identity>() && ...); } template consteval bool unique_property_storage() { using tuple = std::tuple; if constexpr (sizeof...(Properties) < 2) { return true; } else { return [](std::index_sequence) { return (distinct_storage_row(std::make_index_sequence{}) && ...); }(std::make_index_sequence{}); } } template struct Object_Defaults { using object_defaults_tag = void; using attribute_types = Type_List; static_assert(unique_single_value_categories()); static_assert((Inheritable_Attribute && ...)); std::tuple attributes; }; template concept Object_Defaults_Type = requires { typename Type::object_defaults_tag; typename Type::attribute_types; }; template constexpr auto defaults(Attributes&&... attributes) { return Object_Defaults...>{{std::forward(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_Schema { [[no_unique_address]] Defaults object_defaults_; Synchronization_Plan synchronization_plan_; std::tuple properties_; public: using property_schema_tag = void; using object_type = Object; using defaults_type = Defaults; using property_types = Type_List; static constexpr std::size_t property_count = sizeof...(Properties); static_assert((std::same_as && ...)); static_assert(unique_property_keys()); static_assert(unique_property_storage()); Object_Schema(Defaults object_defaults, Synchronization_Plan synchronization_plan, std::tuple 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 using property_type = std::tuple_element_t>; const Defaults& object_defaults() const noexcept { return object_defaults_; } template constexpr const auto& property() const { if constexpr (std::integral) { static_assert(Selector >= 0); constexpr std::size_t index = static_cast(Selector); static_assert(index < property_count); return std::get(properties_); } else { static_assert(std::is_member_object_pointer_v); using member_traits = Member_Pointer_Traits; static_assert(std::same_as); using identity = Member_Storage_Identity; constexpr auto matches = [](std::index_sequence) { return std::array{std::same_as>::storage_identity, identity>...}; }(std::make_index_sequence{}); 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(properties_); } } template constexpr void for_each_property(Function&& function) const { [&](std::index_sequence) { (function(std::integral_constant{}, std::get(properties_)), ...); }(std::make_index_sequence{}); } const Synchronization_Plan& synchronization_plan() const noexcept { return synchronization_plan_; } }; template 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; }; template consteval auto schema_property_keys() { return [](std::index_sequence) { return std::array{declared_property_key>()...}; }(std::make_index_sequence{}); } template constexpr std::optional schema_property_index(std::string_view key_value) { constexpr auto keys = schema_property_keys(); for (std::size_t index = 0; index < keys.size(); ++index) { if (keys[index] == key_value) { return index; } } return std::nullopt; } template consteval std::size_t schema_property_index() { constexpr auto keys = schema_property_keys(); for (std::size_t index = 0; index < keys.size(); ++index) { if (keys[index] == Key.view()) { return index; } } return Schema::property_count; } template inline constexpr std::size_t schema_property_index_v = schema_property_index(); template concept Schema_Property_Key = Property_Schema && schema_property_index_v < Schema::property_count; template consteval std::size_t schema_member_property_index() { static_assert(std::is_member_object_pointer_v); using member_traits = Member_Pointer_Traits; static_assert(std::same_as); using identity = Member_Storage_Identity; constexpr auto matches = [](std::index_sequence) { return std::array{std::same_as::storage_identity, identity>...}; }(std::make_index_sequence{}); for (std::size_t index = 0; index < matches.size(); ++index) { if (matches[index]) { return index; } } return Schema::property_count; } template inline constexpr std::size_t schema_member_property_index_v = schema_member_property_index(); template concept Schema_Property_Member = Property_Schema && std::is_member_object_pointer_v && schema_member_property_index_v < Schema::property_count; template struct Effective_Attribute { private: using property_attribute = typename Schema::template property_type::template attribute_type; using default_attribute = find_attribute_in_list_t; public: using type = std::conditional_t, property_attribute, std::conditional_t, default_attribute, Fallback>>; }; template using effective_attribute_t = typename Effective_Attribute::type; using Default_Access_Attribute = External_Access_Attribute; using Default_Persistence_Attribute = Persistence_Access_Attribute; using Default_Sensitive_Attribute = Sensitive_Attribute; template inline constexpr External_Access effective_external_access_v = effective_attribute_t::value; template inline constexpr Persistence_Access effective_persistence_access_v = effective_attribute_t::value; template inline constexpr bool effective_sensitive_v = effective_attribute_t::value; template using property_declared_attribute_t = typename Schema::template property_type::template attribute_type; template using object_default_attribute_t = find_attribute_in_list_t; template inline constexpr bool has_declared_effective_attribute_v = !std::same_as, void> || !std::same_as, void>; template constexpr decltype(auto) declared_effective_attribute(const Schema& schema) requires has_declared_effective_attribute_v { using property_attribute = property_declared_attribute_t; if constexpr (!std::same_as) { return schema.template property().template attribute(); } else { using default_attribute = object_default_attribute_t; return std::get(schema.object_defaults().attributes); } } template inline constexpr bool external_readable_v = effective_external_access_v == External_Access::read || effective_external_access_v == External_Access::read_write; template inline constexpr bool external_writable_v = effective_external_access_v == External_Access::write || effective_external_access_v == External_Access::read_write; template inline constexpr bool persistence_loadable_v = effective_persistence_access_v == Persistence_Access::load || effective_persistence_access_v == Persistence_Access::load_store; template inline constexpr bool persistence_storable_v = effective_persistence_access_v == Persistence_Access::store || effective_persistence_access_v == Persistence_Access::load_store; template consteval bool property_capability_semantics_valid() { using property_type = typename Schema::template property_type; if constexpr ((external_readable_v || persistence_storable_v) && !property_type::readable) { return false; } if constexpr ((external_writable_v || persistence_loadable_v) && !property_type::writable) { return false; } return true; } template consteval bool schema_capability_semantics_valid_impl(std::index_sequence) { return (property_capability_semantics_valid() && ...); } template consteval bool schema_capability_semantics_valid() { return schema_capability_semantics_valid_impl(std::make_index_sequence{}); } template concept Valid_Property_Schema = Property_Schema && requires { requires schema_capability_semantics_valid(); }; template Resolved_Synchronization_Plan resolve_synchronization_plan(const Schema&, const Synchronization_Plan& plan) { using resolved_type = Resolved_Synchronization_Plan; constexpr auto unsynchronized_slot = resolved_type::unsynchronized_slot; std::array logical{}; std::array 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(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 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; } } resolved_type resolved; resolved.lock_slots.fill(unsynchronized_slot); std::vector> 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 consteval std::string_view schema_member_property_key() requires Schema_Property_Member { constexpr auto index = schema_member_property_index_v; return declared_property_key>(); } template void apply_synchronization_rule(Synchronization_Plan& plan, Sync_Default_Rule rule) { plan.apply(rule); } template void apply_synchronization_rule(Synchronization_Plan& plan, Sync_Property_Rule rule) { plan.apply(std::move(rule)); } template void apply_synchronization_rule(Synchronization_Plan& plan, Sync_Group_Rule rule) { plan.apply(std::move(rule)); } template void apply_synchronization_rule(Synchronization_Plan& plan, Sync_Member_Rule) { static_assert(Schema_Property_Member); constexpr auto key_value = schema_member_property_key(); if constexpr (Mode == Sync_Property_Rule::Mode::independent) { plan.independent(key_value); } else { plan.unsynchronized(key_value); } } template void apply_synchronization_rule(Synchronization_Plan& plan, const Sync_Member_Group_Rule& rule) { static_assert((Schema_Property_Member && ...)); constexpr std::array keys{schema_member_property_key()...}; plan.group(rule.name, std::span{keys}); } template Synchronization_Plan materialize_synchronization_plan(Source&& source) { if constexpr (Synchronization_Plan_Type>) { return std::forward(source); } else { Synchronization_Plan plan; std::apply([&](auto&&... rules) { (apply_synchronization_rule(plan, std::forward(rules)), ...); }, std::forward(source).rules); return plan; } } template bool visit_schema_property(const Schema& schema, std::string_view key_value, Function&& function) { bool found = false; schema.for_each_property([&](auto index, const auto& descriptor) { if (!found) { using property_type = std::remove_cvref_t; if (declared_property_key() == key_value) { std::invoke(function, index, descriptor); found = true; } } }); return found; } template auto make_object_schema(Defaults&& object_defaults, Source&& synchronization_source, Properties&&... properties) requires Synchronization_Source_Type> { using schema_type = Object_Schema, std::decay_t...>; static_assert(Valid_Property_Schema); auto plan = materialize_synchronization_plan(std::forward(synchronization_source)); schema_type schema{std::forward(object_defaults), std::move(plan), std::tuple...>{std::forward(properties)...}}; static_cast(resolve_synchronization_plan(schema, schema.synchronization_plan())); return schema; } template auto object_schema(Defaults&& object_defaults, Source&& synchronization_source, Properties&&... properties) requires Object_Defaults_Type> && Synchronization_Source_Type> && (Property_Descriptor_Type> && ...) { return make_object_schema(std::forward(object_defaults), std::forward(synchronization_source), std::forward(properties)...); } template auto object_schema(Source&& synchronization_source, Properties&&... properties) requires Synchronization_Source_Type> && (Property_Descriptor_Type> && ...) { return make_object_schema(no_defaults, std::forward(synchronization_source), std::forward(properties)...); } template auto object_schema(Defaults&& object_defaults, Properties&&... properties) requires Object_Defaults_Type> && (Property_Descriptor_Type> && ...) { return make_object_schema(std::forward(object_defaults), Synchronization_Plan{}, std::forward(properties)...); } template auto object_schema(Properties&&... properties) requires (Property_Descriptor_Type> && ...) { return make_object_schema(no_defaults, Synchronization_Plan{}, std::forward(properties)...); } template auto object(Arguments&&... arguments) { return object_schema(std::forward(arguments)...); } }