#pragma once #include #include #include #include #include #include #include #include #include #include #include #include namespace structive { /// Defines the default lock-domain topology for properties that actually require managed synchronization. /// Stored intrinsic read-only properties are filtered out later and never receive a lock slot, regardless of this default. enum class Synchronization_Default { independent, shared, unsynchronized }; /// Changes the default topology used before per-property and group overrides are applied. struct Sync_Default_Rule { Synchronization_Default value{Synchronization_Default::independent}; }; /// Runtime-key override for one property. The key is resolved against the schema when the plan is materialized. /// `independent` forces a dedicated domain; `unsynchronized` deliberately removes the property from real locking. struct Sync_Property_Rule { enum class Mode { independent, unsynchronized }; Mode mode{Mode::independent}; std::string property; }; /// Runtime-key declaration that places multiple properties in one consistency domain. /// Group membership affects only properties that require synchronization; intrinsic read-only stored properties remain lock-free. struct Sync_Group_Rule { std::string name; std::vector properties; }; /// Compile-time member-pointer form of a single-property synchronization override. template struct Sync_Member_Rule { static constexpr auto member = Member; static constexpr auto mode = Mode; }; /// Compile-time member-pointer form of a synchronization group. Member validity is checked against the target schema during materialization. template struct Sync_Member_Group_Rule { static_assert(sizeof...(Members) > 0); std::string name; }; inline constexpr Sync_Default_Rule sync_all_independent{Synchronization_Default::independent}; inline constexpr Sync_Default_Rule sync_all_shared{Synchronization_Default::shared}; inline constexpr Sync_Default_Rule sync_all_unsynchronized{Synchronization_Default::unsynchronized}; inline Sync_Property_Rule sync_independent(std::string_view property) { return {Sync_Property_Rule::Mode::independent, std::string(property)}; } inline Sync_Property_Rule sync_unsynchronized(std::string_view property) { return {Sync_Property_Rule::Mode::unsynchronized, std::string(property)}; } template constexpr auto sync_independent() { return Sync_Member_Rule{}; } template constexpr auto sync_unsynchronized() { return Sync_Member_Rule{}; } template Sync_Member_Group_Rule sync_group(std::string_view name) { return {std::string(name)}; } template Sync_Group_Rule sync_group(std::string_view name, Properties&&... properties) requires (std::convertible_to && ...) { Sync_Group_Rule rule; rule.name = name; rule.properties.reserve(sizeof...(Properties)); (rule.properties.emplace_back(std::string_view(std::forward(properties))), ...); return rule; } /// Runtime-materialized synchronization description. /// A plan describes topology, not mutex objects: resolution converts keys and groups into compact lock slots for one schema. /// Invalid references, duplicate property configuration, empty groups and duplicate group names are rejected while resolving the plan. /// The schema default plan is resolved once per object type; a `Property_Synchronization` override resolves a separate compact layout only for that instance. class Synchronization_Plan { Synchronization_Default default_mode_{Synchronization_Default::independent}; std::vector property_rules_; std::vector group_rules_; public: using synchronization_plan_tag = void; Synchronization_Plan() = default; explicit Synchronization_Plan(Synchronization_Default mode) : default_mode_(mode) {} Synchronization_Default default_mode() const noexcept { return default_mode_; } const auto& property_rules() const noexcept { return property_rules_; } const auto& group_rules() const noexcept { return group_rules_; } Synchronization_Plan& set_default(Synchronization_Default mode) { default_mode_ = mode; return *this; } Synchronization_Plan& independent(std::string_view property) { property_rules_.push_back(sync_independent(property)); return *this; } Synchronization_Plan& unsynchronized(std::string_view property) { property_rules_.push_back(sync_unsynchronized(property)); return *this; } Synchronization_Plan& group(std::string_view name, std::span properties) { Sync_Group_Rule rule; rule.name = name; rule.properties.reserve(properties.size()); for (auto property : properties) { rule.properties.emplace_back(property); } group_rules_.push_back(std::move(rule)); return *this; } Synchronization_Plan& group(std::string_view name, std::initializer_list properties) { return group(name, std::span{properties.begin(), properties.size()}); } Synchronization_Plan& apply(Sync_Default_Rule rule) { default_mode_ = rule.value; return *this; } Synchronization_Plan& apply(Sync_Property_Rule rule) { property_rules_.push_back(std::move(rule)); return *this; } Synchronization_Plan& apply(Sync_Group_Rule rule) { group_rules_.push_back(std::move(rule)); return *this; } }; template concept Synchronization_Plan_Type = requires { typename Type::synchronization_plan_tag; }; template concept Runtime_Synchronization_Rule = std::same_as, Sync_Default_Rule> || std::same_as, Sync_Property_Rule> || std::same_as, Sync_Group_Rule>; template struct Synchronization_Spec { using synchronization_spec_tag = void; std::tuple rules; }; template concept Synchronization_Spec_Type = requires { typename Type::synchronization_spec_tag; }; template concept Synchronization_Source_Type = Synchronization_Plan_Type || Synchronization_Spec_Type; /// Builds synchronization configuration without forcing every caller onto runtime strings. /// If every rule is already runtime-shaped, this returns `Synchronization_Plan` directly. If any rule carries compile-time member pointers, it returns a typed specification that is later materialized against the concrete schema. template auto synchronization(Rules&&... rules) { if constexpr ((Runtime_Synchronization_Rule && ...)) { Synchronization_Plan plan; (plan.apply(std::forward(rules)), ...); return plan; } else { return Synchronization_Spec...>{{std::forward(rules)...}}; } } /// Compact result after applying defaults, overrides, groups and intrinsic property capabilities. /// `lock_slots[i]` identifies the mutex domain for property `i`; `unsynchronized_slot` means no mutex exists for that property. /// `lock_count` is the number of actual logical mutex domains after read-only filtering and group deduplication. template struct Resolved_Synchronization_Plan { static constexpr std::size_t unsynchronized_slot = std::numeric_limits::max(); std::array lock_slots{}; std::size_t lock_count{}; constexpr std::size_t slot(std::size_t property_index) const noexcept { return lock_slots[property_index]; } constexpr bool uses_lock(std::size_t property_index) const noexcept { return slot(property_index) != unsynchronized_slot; } }; }