#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 struct Type_Metadata { using type_metadata_tag = void; using attribute_types = Type_List; static_assert((Property_Attribute && ...)); static_assert(unique_single_value_categories()); std::tuple attributes; }; template concept Type_Metadata_Type = requires { typename Type::type_metadata_tag; typename Type::attribute_types; }; template constexpr auto type_metadata(Attributes&&... attributes) { return Type_Metadata...>{ {std::forward(attributes)...}}; } using No_Type_Metadata = Type_Metadata<>; inline constexpr No_Type_Metadata no_type_metadata{}; template class Object_Schema { [[no_unique_address]] Defaults object_defaults_; [[no_unique_address]] Type_Metadata_Value type_metadata_; Synchronization_Plan synchronization_plan_; std::tuple 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; 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, Type_Metadata_Value type_metadata_value, Synchronization_Plan synchronization_plan, std::tuple 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 using property_type = std::tuple_element_t>; const Defaults& object_defaults() const noexcept { return object_defaults_; } const Type_Metadata_Value& metadata() const noexcept { return type_metadata_; } template using type_attribute_type = find_attribute_in_list_t; template static constexpr std::size_t type_attribute_count = [](Type_List) { return count_attribute_category(); }(type_attribute_types{}); template static constexpr bool has_type_attribute = type_attribute_count != 0; template constexpr decltype(auto) type_attribute() const requires (type_attribute_count == 1) { using target = type_attribute_type; return std::get(type_metadata_.attributes); } template constexpr void for_each_type_attribute(Function&& function) const { std::apply([&](const auto&... attributes) { (function(attributes), ...); }, type_metadata_.attributes); } template constexpr void for_each_type_attribute(Function&& function) const { std::apply([&](const auto&... attributes) { ([&] { using type = std::remove_cvref_t; if constexpr (std::same_as, Category>) function(attributes); }(), ...); }, type_metadata_.attributes); } 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::type_metadata_type; typename Schema::type_attribute_types; 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 concept Schema_Property_Index = Property_Schema && Index < Schema::property_count; template struct Schema_Dependency_Index; template struct Schema_Dependency_Index> { static constexpr std::size_t value = schema_member_property_index_v; static_assert(value < Schema::property_count, "computed dependency member is not registered in this property schema"); static_assert(Schema::template property_type::readable, "computed dependency must be readable"); }; template struct Schema_Dependency_Index> { static constexpr std::size_t value = schema_property_index_v; static_assert(value < Schema::property_count, "computed dependency key is not registered in this property schema"); static_assert(Schema::template property_type::readable, "computed dependency must be readable"); }; template consteval bool unique_dependency_indices(const std::array& 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 struct Schema_Dependency_Indices; template struct Schema_Dependency_Indices> { static constexpr std::array value{Schema_Dependency_Index::value...}; static_assert(unique_dependency_indices(value), "computed property declares the same dependency more than once"); }; template consteval auto schema_property_dependency_indices() requires Schema_Property_Index { using dependency_spec = typename Schema::template property_type::dependency_spec; return Schema_Dependency_Indices::value; } template inline constexpr std::size_t schema_property_dependency_count_v = schema_property_dependency_indices().size(); template inline constexpr bool schema_property_depends_on_index_v = [] { constexpr auto dependencies = schema_property_dependency_indices(); for (auto index : dependencies) { if (index == Dependency_Index) { return true; } } return false; }(); template consteval auto schema_property_dependency_matrix() { std::array, Schema::property_count> matrix{}; [&](std::index_sequence) { ([&] { constexpr auto dependencies = schema_property_dependency_indices(); for (auto dependency : dependencies) { matrix[Index][dependency] = true; } }(), ...); }(std::make_index_sequence{}); return matrix; } template consteval bool schema_dependency_graph_acyclic() { constexpr auto dependencies = schema_property_dependency_matrix(); std::array dependency_counts{}; std::array 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 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_Sensitive_Attribute = Sensitive_Attribute; 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 concept Valid_Property_Schema = Property_Schema && schema_dependency_graph_acyclic(); template inline constexpr bool property_requires_synchronization_v = Schema::template property_type::writable || Schema::template property_type::synchronized_view_read; template inline constexpr bool property_has_derived_synchronization_v = Schema::template property_type::synchronized_view_read && !Schema::template property_type::writable; template inline constexpr bool property_owns_synchronization_domain_v = Schema::template property_type::writable || (Schema::template property_type::synchronized_view_read && !property_has_derived_synchronization_v); template concept Schema_Readable_Property_Index = Schema_Property_Index && Schema::template property_type::readable; template concept Schema_Writable_Property_Index = Schema_Property_Index && Schema::template property_type::writable; template concept Schema_Readable_Property_Member = Schema_Property_Member && Schema::template property_type>::readable; template concept Schema_Writable_Property_Member = Schema_Property_Member && Schema::template property_type>::writable; template concept Schema_Readable_Property_Key = Schema_Property_Key && Schema::template property_type>::readable; template concept Schema_Writable_Property_Key = Schema_Property_Key && Schema::template property_type>::writable; 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; constexpr auto owns_synchronization_domain = [](std::index_sequence) { return std::array{property_owns_synchronization_domain_v...}; }(std::make_index_sequence{}); constexpr auto derived_synchronization = [](std::index_sequence) { return std::array{property_has_derived_synchronization_v...}; }(std::make_index_sequence{}); constexpr auto dependencies = schema_property_dependency_matrix(); 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 (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 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> 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(); std::array 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 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_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 bool visit_schema_property(const Schema& schema, std::string_view key_value, Function&& function) { auto index = schema_property_index(key_value); if (!index) { return false; } return visit_schema_property_at(schema, *index, std::forward(function)); } template auto make_object_schema(Defaults&& object_defaults, Metadata&& metadata, Source&& synchronization_source, Properties&&... properties) requires Object_Defaults_Type> && Type_Metadata_Type> && Synchronization_Source_Type> { using schema_type = Object_Schema, std::decay_t, std::decay_t...>; constexpr bool valid_schema = Valid_Property_Schema; static_assert(valid_schema, "property dependency graph must be valid and acyclic"); auto plan = materialize_synchronization_plan(std::forward(synchronization_source)); schema_type schema{std::forward(object_defaults), std::forward(metadata), std::move(plan), std::tuple...>{ std::forward(properties)...}}; if constexpr (valid_schema) { static_cast(resolve_synchronization_plan(schema, schema.synchronization_plan())); } return schema; } template auto object_schema(Defaults&& object_defaults, Metadata&& metadata, Source&& synchronization_source, Properties&&... properties) requires Object_Defaults_Type> && Type_Metadata_Type> && Synchronization_Source_Type> && (Property_Descriptor_Type> && ...) { return make_object_schema(std::forward(object_defaults), std::forward(metadata), std::forward(synchronization_source), std::forward(properties)...); } template auto object_schema(Metadata&& metadata, Source&& synchronization_source, Properties&&... properties) requires Type_Metadata_Type> && Synchronization_Source_Type> && (Property_Descriptor_Type> && ...) { return make_object_schema(no_defaults, std::forward(metadata), std::forward(synchronization_source), std::forward(properties)...); } 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), no_type_metadata, 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, no_type_metadata, std::forward(synchronization_source), std::forward(properties)...); } template auto object_schema(Defaults&& object_defaults, Metadata&& metadata, Properties&&... properties) requires Object_Defaults_Type> && Type_Metadata_Type> && (Property_Descriptor_Type> && ...) { return make_object_schema(std::forward(object_defaults), std::forward(metadata), Synchronization_Plan{}, std::forward(properties)...); } template auto object_schema(Metadata&& metadata, Properties&&... properties) requires Type_Metadata_Type> && (Property_Descriptor_Type> && ...) { return make_object_schema(no_defaults, std::forward(metadata), Synchronization_Plan{}, 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), no_type_metadata, Synchronization_Plan{}, std::forward(properties)...); } template auto object_schema(Properties&&... properties) requires (Property_Descriptor_Type> && ...) { return make_object_schema(no_defaults, no_type_metadata, Synchronization_Plan{}, std::forward(properties)...); } template requires requires(Arguments&&... arguments) { object_schema(std::forward(arguments)...); } auto object(Arguments&&... arguments) { return object_schema(std::forward(arguments)...); } }