首次提交
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
#pragma once
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
namespace structive {
|
||||
template <class>
|
||||
struct Member_Pointer_Traits;
|
||||
template <class Object, class Value>
|
||||
struct Member_Pointer_Traits<Value Object::*> {
|
||||
using object_type = Object;
|
||||
using value_type = Value;
|
||||
};
|
||||
template <class>
|
||||
struct Member_Function_Traits;
|
||||
template <class Object, class Return, class... Args>
|
||||
struct Member_Function_Traits<Return (Object::*)(Args...)> {
|
||||
using object_type = Object;
|
||||
using return_type = Return;
|
||||
};
|
||||
template <class Object, class Return, class... Args>
|
||||
struct Member_Function_Traits<Return (Object::*)(Args...) const> {
|
||||
using object_type = Object;
|
||||
using return_type = Return;
|
||||
};
|
||||
template <class Object, class Return, class... Args>
|
||||
struct Member_Function_Traits<Return (Object::*)(Args...) noexcept> : Member_Function_Traits<Return (Object::*)(Args...)> {};
|
||||
template <class Object, class Return, class... Args>
|
||||
struct Member_Function_Traits<Return (Object::*)(Args...) const noexcept> : Member_Function_Traits<Return (Object::*)(Args...) const> {};
|
||||
template <auto Member>
|
||||
struct Member_Storage_Identity {};
|
||||
template <auto Getter>
|
||||
concept Trusted_Getter_Function = requires {
|
||||
typename Member_Function_Traits<decltype(Getter)>::object_type;
|
||||
} && requires(const typename Member_Function_Traits<decltype(Getter)>::object_type& object) {
|
||||
std::invoke(Getter, object);
|
||||
} && (!std::is_void_v<std::invoke_result_t<decltype(Getter), const typename Member_Function_Traits<decltype(Getter)>::object_type&>>) && (!std::is_rvalue_reference_v<std::invoke_result_t<decltype(Getter), const typename Member_Function_Traits<decltype(Getter)>::object_type&>>) && (!(std::is_lvalue_reference_v<std::invoke_result_t<decltype(Getter), const typename Member_Function_Traits<decltype(Getter)>::object_type&>> && !std::is_const_v<std::remove_reference_t<std::invoke_result_t<decltype(Getter), const typename Member_Function_Traits<decltype(Getter)>::object_type&>>>));
|
||||
template <auto Getter>
|
||||
using trusted_getter_object_t = typename Member_Function_Traits<decltype(Getter)>::object_type;
|
||||
template <auto Getter>
|
||||
using trusted_getter_value_t = std::remove_cvref_t<std::invoke_result_t<decltype(Getter), const trusted_getter_object_t<Getter>&>>;
|
||||
template <auto Setter, class Object, class Value>
|
||||
concept Trusted_Setter_Function_For = requires {
|
||||
typename Member_Function_Traits<decltype(Setter)>::object_type;
|
||||
} && std::same_as<typename Member_Function_Traits<decltype(Setter)>::object_type, Object> && requires(Object& object, Value value) {
|
||||
{ std::invoke(Setter, object, std::move(value)) } -> std::same_as<void>;
|
||||
};
|
||||
template <auto Member>
|
||||
struct Member_Accessor {
|
||||
using traits = Member_Pointer_Traits<decltype(Member)>;
|
||||
using object_type = typename traits::object_type;
|
||||
using value_type = typename traits::value_type;
|
||||
using storage_identity = Member_Storage_Identity<Member>;
|
||||
static constexpr bool readable = true;
|
||||
static constexpr bool writable = !std::is_const_v<value_type>;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
static constexpr bool trusted_object_access = false;
|
||||
static constexpr auto member = Member;
|
||||
constexpr const value_type& read(const object_type& object) const {
|
||||
return object.*Member;
|
||||
}
|
||||
constexpr value_type& read(object_type& object) const {
|
||||
return object.*Member;
|
||||
}
|
||||
template <class Value>
|
||||
constexpr void write(object_type& object, Value&& value) const requires writable && std::assignable_from<value_type&, Value> {
|
||||
object.*Member = std::forward<Value>(value);
|
||||
}
|
||||
};
|
||||
template <class Object, class Value, class Function>
|
||||
struct Synchronized_Computed_Accessor {
|
||||
using object_type = Object;
|
||||
using value_type = Value;
|
||||
using storage_identity = void;
|
||||
static constexpr bool readable = true;
|
||||
static constexpr bool writable = false;
|
||||
static constexpr bool synchronized_view_read = true;
|
||||
static constexpr bool trusted_object_access = false;
|
||||
[[no_unique_address]] Function function;
|
||||
template <class View>
|
||||
constexpr value_type read(const View& view) const requires std::invocable<const Function&, const View&> && std::constructible_from<value_type, std::invoke_result_t<const Function&, const View&>> {
|
||||
return value_type(std::invoke(function, view));
|
||||
}
|
||||
};
|
||||
template <auto Getter> requires Trusted_Getter_Function<Getter>
|
||||
struct Trusted_Computed_Accessor {
|
||||
using object_type = trusted_getter_object_t<Getter>;
|
||||
using value_type = trusted_getter_value_t<Getter>;
|
||||
using storage_identity = void;
|
||||
static constexpr bool readable = true;
|
||||
static constexpr bool writable = false;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
static constexpr bool trusted_object_access = true;
|
||||
constexpr decltype(auto) read(const object_type& object) const noexcept(std::is_nothrow_invocable_v<decltype(Getter), const object_type&>) {
|
||||
return std::invoke(Getter, object);
|
||||
}
|
||||
};
|
||||
template <auto Getter, auto Setter> requires Trusted_Getter_Function<Getter> && Trusted_Setter_Function_For<Setter, trusted_getter_object_t<Getter>, trusted_getter_value_t<Getter>>
|
||||
struct Trusted_Getter_Setter_Accessor {
|
||||
using object_type = trusted_getter_object_t<Getter>;
|
||||
using value_type = trusted_getter_value_t<Getter>;
|
||||
using storage_identity = void;
|
||||
static constexpr bool readable = true;
|
||||
static constexpr bool writable = true;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
static constexpr bool trusted_object_access = true;
|
||||
constexpr decltype(auto) read(const object_type& object) const noexcept(std::is_nothrow_invocable_v<decltype(Getter), const object_type&>) {
|
||||
return std::invoke(Getter, object);
|
||||
}
|
||||
template <class Value>
|
||||
constexpr void write(object_type& object, Value&& value) const requires std::invocable<decltype(Setter), object_type&, Value> && std::same_as<std::invoke_result_t<decltype(Setter), object_type&, Value>, void> {
|
||||
std::invoke(Setter, object, std::forward<Value>(value));
|
||||
}
|
||||
};
|
||||
template <class Accessor>
|
||||
concept Property_Accessor = requires {
|
||||
typename Accessor::object_type;
|
||||
typename Accessor::value_type;
|
||||
typename Accessor::storage_identity;
|
||||
{ Accessor::readable } -> std::convertible_to<bool>;
|
||||
{ Accessor::writable } -> std::convertible_to<bool>;
|
||||
{ Accessor::synchronized_view_read } -> std::convertible_to<bool>;
|
||||
{ Accessor::trusted_object_access } -> std::convertible_to<bool>;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
#pragma once
|
||||
#include "fixed_string.hpp"
|
||||
#include <cmath>
|
||||
#include <concepts>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
namespace structive {
|
||||
struct Key_Category {};
|
||||
struct Access_Category {};
|
||||
struct Persistence_Category {};
|
||||
struct Unit_Category {};
|
||||
struct Sensitive_Category {};
|
||||
enum class External_Access {
|
||||
none,
|
||||
read,
|
||||
write,
|
||||
read_write
|
||||
};
|
||||
enum class Persistence_Access {
|
||||
none,
|
||||
load,
|
||||
store,
|
||||
load_store
|
||||
};
|
||||
template <Fixed_String Value>
|
||||
struct Key_Attribute {
|
||||
using attribute_category = Key_Category;
|
||||
static constexpr bool single_valued = true;
|
||||
static constexpr bool inheritable = false;
|
||||
static constexpr auto value = Value;
|
||||
};
|
||||
template <External_Access Value>
|
||||
struct External_Access_Attribute {
|
||||
using attribute_category = Access_Category;
|
||||
static constexpr bool single_valued = true;
|
||||
static constexpr bool inheritable = true;
|
||||
static constexpr auto value = Value;
|
||||
};
|
||||
template <Persistence_Access Value>
|
||||
struct Persistence_Access_Attribute {
|
||||
using attribute_category = Persistence_Category;
|
||||
static constexpr bool single_valued = true;
|
||||
static constexpr bool inheritable = true;
|
||||
static constexpr auto value = Value;
|
||||
};
|
||||
template <Fixed_String Value>
|
||||
struct Unit_Attribute {
|
||||
using attribute_category = Unit_Category;
|
||||
static constexpr bool single_valued = true;
|
||||
static constexpr bool inheritable = false;
|
||||
static constexpr auto value = Value;
|
||||
};
|
||||
template <bool Value>
|
||||
struct Sensitive_Attribute {
|
||||
using attribute_category = Sensitive_Category;
|
||||
static constexpr bool single_valued = true;
|
||||
static constexpr bool inheritable = true;
|
||||
static constexpr auto value = Value;
|
||||
};
|
||||
template <auto Value>
|
||||
struct Min_Constraint {
|
||||
using property_constraint_tag = void;
|
||||
static constexpr bool inheritable = false;
|
||||
static constexpr auto code = Fixed_String{"min_value"};
|
||||
template <class T>
|
||||
constexpr bool validate(const T& candidate) const requires requires { candidate >= Value; } {
|
||||
return candidate >= Value;
|
||||
}
|
||||
};
|
||||
template <auto Value>
|
||||
struct Max_Constraint {
|
||||
using property_constraint_tag = void;
|
||||
static constexpr bool inheritable = false;
|
||||
static constexpr auto code = Fixed_String{"max_value"};
|
||||
template <class T>
|
||||
constexpr bool validate(const T& candidate) const requires requires { candidate <= Value; } {
|
||||
return candidate <= Value;
|
||||
}
|
||||
};
|
||||
struct Finite_Constraint {
|
||||
using property_constraint_tag = void;
|
||||
static constexpr bool inheritable = false;
|
||||
static constexpr auto code = Fixed_String{"finite"};
|
||||
template <std::floating_point T>
|
||||
bool validate(T candidate) const {
|
||||
return std::isfinite(candidate);
|
||||
}
|
||||
};
|
||||
template <Fixed_String Code, class Function>
|
||||
struct Custom_Constraint {
|
||||
using property_constraint_tag = void;
|
||||
static constexpr bool inheritable = false;
|
||||
static constexpr auto code = Code;
|
||||
[[no_unique_address]] Function function;
|
||||
template <class T>
|
||||
constexpr bool validate(const T& candidate) const requires std::predicate<const Function&, const T&> {
|
||||
return static_cast<bool>(function(candidate));
|
||||
}
|
||||
};
|
||||
template <Fixed_String Value>
|
||||
inline constexpr Key_Attribute<Value> key{};
|
||||
template <External_Access Value>
|
||||
inline constexpr External_Access_Attribute<Value> external_access{};
|
||||
template <Persistence_Access Value>
|
||||
inline constexpr Persistence_Access_Attribute<Value> persistence_access{};
|
||||
template <Fixed_String Value>
|
||||
inline constexpr Unit_Attribute<Value> unit{};
|
||||
template <bool Value = true>
|
||||
inline constexpr Sensitive_Attribute<Value> sensitive{};
|
||||
template <auto Value>
|
||||
inline constexpr Min_Constraint<Value> min_value{};
|
||||
template <auto Value>
|
||||
inline constexpr Max_Constraint<Value> max_value{};
|
||||
inline constexpr Finite_Constraint finite{};
|
||||
template <Fixed_String Code, class Function>
|
||||
constexpr auto constraint(Function&& function) {
|
||||
return Custom_Constraint<Code, std::decay_t<Function>>{std::forward<Function>(function)};
|
||||
}
|
||||
template <class Attribute>
|
||||
concept Property_Constraint = requires {
|
||||
typename Attribute::property_constraint_tag;
|
||||
{ Attribute::code.view() } -> std::convertible_to<std::string_view>;
|
||||
};
|
||||
template <class Attribute, class Value>
|
||||
concept Property_Constraint_For = Property_Constraint<Attribute> && requires(const Attribute& attribute, const Value& value) {
|
||||
{ attribute.validate(value) } -> std::convertible_to<bool>;
|
||||
};
|
||||
template <class Attribute>
|
||||
concept Inheritable_Attribute = requires {
|
||||
{ Attribute::inheritable } -> std::convertible_to<bool>;
|
||||
} && Attribute::inheritable;
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
#pragma once
|
||||
#include "accessor.hpp"
|
||||
#include "attributes.hpp"
|
||||
#include "meta.hpp"
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
namespace structive {
|
||||
template <class Value, class... Attributes>
|
||||
consteval bool constraints_compatible() {
|
||||
return (([] {
|
||||
if constexpr (Property_Constraint<Attributes>) {
|
||||
return Property_Constraint_For<Attributes, Value>;
|
||||
}
|
||||
return true;
|
||||
}()) && ...);
|
||||
}
|
||||
template <Property_Accessor Accessor, class... Attributes>
|
||||
struct Property_Descriptor {
|
||||
using property_descriptor_tag = void;
|
||||
using accessor_type = Accessor;
|
||||
using object_type = typename Accessor::object_type;
|
||||
using value_type = typename Accessor::value_type;
|
||||
using storage_identity = typename Accessor::storage_identity;
|
||||
using attribute_types = Type_List<Attributes...>;
|
||||
static constexpr bool readable = Accessor::readable;
|
||||
static constexpr bool writable = Accessor::writable;
|
||||
static constexpr bool synchronized_view_read = Accessor::synchronized_view_read;
|
||||
static constexpr bool trusted_object_access = Accessor::trusted_object_access;
|
||||
static_assert(unique_single_value_categories<Attributes...>());
|
||||
using key_type = find_attribute_in_list_t<Key_Category, attribute_types>;
|
||||
static_assert(!std::same_as<key_type, void>);
|
||||
static_assert(key_type::value.view().size() > 0);
|
||||
static_assert(constraints_compatible<value_type, Attributes...>());
|
||||
[[no_unique_address]] Accessor accessor{};
|
||||
std::tuple<Attributes...> attributes;
|
||||
template <class Category>
|
||||
using attribute_type = find_attribute_in_list_t<Category, attribute_types>;
|
||||
template <class Category>
|
||||
static constexpr bool has_attribute = !std::same_as<attribute_type<Category>, void>;
|
||||
template <class Category>
|
||||
constexpr decltype(auto) attribute() const requires has_attribute<Category> {
|
||||
using target = attribute_type<Category>;
|
||||
return std::get<target>(attributes);
|
||||
}
|
||||
constexpr std::string_view key() const noexcept {
|
||||
return key_type::value.view();
|
||||
}
|
||||
template <class Function>
|
||||
constexpr void for_each_attribute(Function&& function) const {
|
||||
std::apply([&](const auto&... values) {
|
||||
(function(values), ...);
|
||||
}, attributes);
|
||||
}
|
||||
template <class Function>
|
||||
constexpr void for_each_constraint(Function&& function) const {
|
||||
std::apply([&](const auto&... values) {
|
||||
([&] {
|
||||
using type = std::remove_cvref_t<decltype(values)>;
|
||||
if constexpr (Property_Constraint<type>) {
|
||||
function(values);
|
||||
}
|
||||
}(), ...);
|
||||
}, attributes);
|
||||
}
|
||||
};
|
||||
template <class Type>
|
||||
concept Property_Descriptor_Type = requires {
|
||||
typename Type::property_descriptor_tag;
|
||||
typename Type::object_type;
|
||||
typename Type::value_type;
|
||||
typename Type::accessor_type;
|
||||
typename Type::storage_identity;
|
||||
};
|
||||
template <auto Member, class... Attributes>
|
||||
constexpr auto property(Attributes&&... attributes) {
|
||||
using accessor = Member_Accessor<Member>;
|
||||
return Property_Descriptor<accessor, std::decay_t<Attributes>...>{{}, {std::forward<Attributes>(attributes)...}};
|
||||
}
|
||||
template <auto Member, class... Attributes>
|
||||
constexpr auto field(Attributes&&... attributes) {
|
||||
return property<Member>(std::forward<Attributes>(attributes)...);
|
||||
}
|
||||
template <class Object, class Value, class Function, class... Attributes>
|
||||
constexpr auto computed_property(Function&& function, Attributes&&... attributes) {
|
||||
using accessor = Synchronized_Computed_Accessor<Object, Value, std::decay_t<Function>>;
|
||||
return Property_Descriptor<accessor, std::decay_t<Attributes>...>{accessor{std::forward<Function>(function)}, {std::forward<Attributes>(attributes)...}};
|
||||
}
|
||||
template <auto Getter, class... Attributes> requires Trusted_Getter_Function<Getter>
|
||||
constexpr auto trusted_computed_property(Attributes&&... attributes) {
|
||||
using accessor = Trusted_Computed_Accessor<Getter>;
|
||||
return Property_Descriptor<accessor, std::decay_t<Attributes>...>{{}, {std::forward<Attributes>(attributes)...}};
|
||||
}
|
||||
template <auto Getter, auto Setter, class... Attributes> requires Trusted_Getter_Function<Getter> && Trusted_Setter_Function_For<Setter, trusted_getter_object_t<Getter>, trusted_getter_value_t<Getter>>
|
||||
constexpr auto trusted_accessor_property(Attributes&&... attributes) {
|
||||
using accessor = Trusted_Getter_Setter_Accessor<Getter, Setter>;
|
||||
return Property_Descriptor<accessor, std::decay_t<Attributes>...>{{}, {std::forward<Attributes>(attributes)...}};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
#pragma once
|
||||
#include <cstddef>
|
||||
#include <string_view>
|
||||
namespace structive {
|
||||
template <std::size_t N>
|
||||
struct Fixed_String {
|
||||
char value[N]{};
|
||||
consteval Fixed_String(const char (&text)[N]) {
|
||||
for (std::size_t i = 0; i < N; ++i) {
|
||||
value[i] = text[i];
|
||||
}
|
||||
}
|
||||
constexpr std::string_view view() const {
|
||||
return {value, N - 1};
|
||||
}
|
||||
constexpr auto operator<=>(const Fixed_String&) const = default;
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
#pragma once
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
namespace structive {
|
||||
template <class... Types>
|
||||
struct Type_List {};
|
||||
template <class Type, class List>
|
||||
struct Type_List_Contains;
|
||||
template <class Type, class... Types>
|
||||
struct Type_List_Contains<Type, Type_List<Types...>> : std::bool_constant<(std::same_as<Type, Types> || ...)> {};
|
||||
template <class Type, class List>
|
||||
inline constexpr bool type_list_contains_v = Type_List_Contains<Type, List>::value;
|
||||
template <class List, class Type>
|
||||
struct Type_List_Push_Unique;
|
||||
template <class... Types, class Type>
|
||||
struct Type_List_Push_Unique<Type_List<Types...>, Type> {
|
||||
using type = std::conditional_t<(std::same_as<Type, Types> || ...), Type_List<Types...>, Type_List<Types..., Type>>;
|
||||
};
|
||||
template <class Input, class Output = Type_List<>>
|
||||
struct Type_List_Unique;
|
||||
template <class Output>
|
||||
struct Type_List_Unique<Type_List<>, Output> {
|
||||
using type = Output;
|
||||
};
|
||||
template <class Head, class... Tail, class Output>
|
||||
struct Type_List_Unique<Type_List<Head, Tail...>, Output> {
|
||||
using next = typename Type_List_Push_Unique<Output, Head>::type;
|
||||
using type = typename Type_List_Unique<Type_List<Tail...>, next>::type;
|
||||
};
|
||||
template <class Type, class List>
|
||||
struct Type_List_Index;
|
||||
template <class Type, class... Tail>
|
||||
struct Type_List_Index<Type, Type_List<Type, Tail...>> : std::integral_constant<std::size_t, 0> {};
|
||||
template <class Type, class Head, class... Tail>
|
||||
struct Type_List_Index<Type, Type_List<Head, Tail...>> : std::integral_constant<std::size_t, 1 + Type_List_Index<Type, Type_List<Tail...>>::value> {};
|
||||
template <class Type, class List>
|
||||
inline constexpr std::size_t type_list_index_v = Type_List_Index<Type, List>::value;
|
||||
template <class Attribute, class = void>
|
||||
struct Attribute_Category_Of {
|
||||
using type = void;
|
||||
};
|
||||
template <class Attribute>
|
||||
struct Attribute_Category_Of<Attribute, std::void_t<typename Attribute::attribute_category>> {
|
||||
using type = typename Attribute::attribute_category;
|
||||
};
|
||||
template <class Attribute>
|
||||
using attribute_category_of_t = typename Attribute_Category_Of<Attribute>::type;
|
||||
template <class Category, class... Attributes>
|
||||
struct Find_Attribute_By_Category;
|
||||
template <class Category>
|
||||
struct Find_Attribute_By_Category<Category> {
|
||||
using type = void;
|
||||
};
|
||||
template <class Category, class Head, class... Tail>
|
||||
struct Find_Attribute_By_Category<Category, Head, Tail...> {
|
||||
using type = std::conditional_t<std::same_as<Category, attribute_category_of_t<Head>>, Head, typename Find_Attribute_By_Category<Category, Tail...>::type>;
|
||||
};
|
||||
template <class Category, class List>
|
||||
struct Find_Attribute_In_List;
|
||||
template <class Category, class... Attributes>
|
||||
struct Find_Attribute_In_List<Category, Type_List<Attributes...>> {
|
||||
using type = typename Find_Attribute_By_Category<Category, Attributes...>::type;
|
||||
};
|
||||
template <class Category, class List>
|
||||
using find_attribute_in_list_t = typename Find_Attribute_In_List<Category, List>::type;
|
||||
template <class Category, class... Attributes>
|
||||
consteval std::size_t count_attribute_category() {
|
||||
return (std::size_t{0} + ... + (std::same_as<attribute_category_of_t<Attributes>, Category> ? 1u : 0u));
|
||||
}
|
||||
template <class Attribute, class... Attributes>
|
||||
consteval bool unique_single_value_category_for() {
|
||||
if constexpr (requires { Attribute::single_valued; }) {
|
||||
if constexpr (Attribute::single_valued && !std::same_as<attribute_category_of_t<Attribute>, void>) {
|
||||
return count_attribute_category<attribute_category_of_t<Attribute>, Attributes...>() == 1;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
template <class... Attributes>
|
||||
consteval bool unique_single_value_categories() {
|
||||
return (unique_single_value_category_for<Attributes, Attributes...>() && ...);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
#pragma once
|
||||
#include "accessor.hpp"
|
||||
#include "attributes.hpp"
|
||||
#include "descriptor.hpp"
|
||||
#include "fixed_string.hpp"
|
||||
#include "meta.hpp"
|
||||
#include "schema.hpp"
|
||||
#include "synchronization.hpp"
|
||||
#include "type_descriptor.hpp"
|
||||
#include "property_object.hpp"
|
||||
#include "validation.hpp"
|
||||
@@ -0,0 +1,963 @@
|
||||
#pragma once
|
||||
#include "type_descriptor.hpp"
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <concepts>
|
||||
#include <cstddef>
|
||||
#include <functional>
|
||||
#include <initializer_list>
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <mutex>
|
||||
#include <shared_mutex>
|
||||
#include <span>
|
||||
#include <stdexcept>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <typeinfo>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace structive {
|
||||
struct Null_Shared_Mutex {
|
||||
void lock() {}
|
||||
void unlock() {}
|
||||
void lock_shared() {}
|
||||
void unlock_shared() {}
|
||||
};
|
||||
struct Shared_Mutex_Policy {
|
||||
using mutex_type = std::shared_mutex;
|
||||
};
|
||||
struct No_Lock_Policy {
|
||||
using mutex_type = Null_Shared_Mutex;
|
||||
};
|
||||
template <class Mutex>
|
||||
concept Shared_Lockable = requires(Mutex& mutex) {
|
||||
mutex.lock();
|
||||
mutex.unlock();
|
||||
mutex.lock_shared();
|
||||
mutex.unlock_shared();
|
||||
};
|
||||
template <class Policy>
|
||||
concept Synchronization_Policy = requires {
|
||||
typename Policy::mutex_type;
|
||||
} && Shared_Lockable<typename Policy::mutex_type>;
|
||||
enum class Managed_Access_Mode {
|
||||
internal,
|
||||
external,
|
||||
persistence
|
||||
};
|
||||
enum class Runtime_Access_Result {
|
||||
ok,
|
||||
unknown_property,
|
||||
not_readable,
|
||||
not_writable,
|
||||
type_mismatch
|
||||
};
|
||||
using Runtime_Read_Callback = void (*)(void*, std::size_t, std::string_view, const std::type_info&, const void*);
|
||||
class Property_Object_Base {
|
||||
struct Runtime_Interface {
|
||||
const std::type_info& (*object_type)() noexcept;
|
||||
std::size_t (*property_count)() noexcept;
|
||||
Runtime_Access_Result (*read)(const Property_Object_Base&, Managed_Access_Mode, std::string_view, void*, Runtime_Read_Callback);
|
||||
Runtime_Access_Result (*write)(Property_Object_Base&, Managed_Access_Mode, std::string_view, const std::type_info&, const void*);
|
||||
};
|
||||
const Runtime_Interface* runtime_interface_{};
|
||||
protected:
|
||||
explicit Property_Object_Base(const Runtime_Interface* runtime_interface) noexcept : runtime_interface_(runtime_interface) {}
|
||||
Property_Object_Base(const Property_Object_Base&) noexcept = default;
|
||||
Property_Object_Base(Property_Object_Base&&) noexcept = default;
|
||||
Property_Object_Base& operator=(const Property_Object_Base&) noexcept = default;
|
||||
Property_Object_Base& operator=(Property_Object_Base&&) noexcept = default;
|
||||
~Property_Object_Base() = default;
|
||||
template <class Object, Synchronization_Policy Policy>
|
||||
friend class Property_Object;
|
||||
public:
|
||||
const std::type_info& runtime_object_type() const noexcept {
|
||||
return runtime_interface_->object_type();
|
||||
}
|
||||
std::size_t runtime_property_count() const noexcept {
|
||||
return runtime_interface_->property_count();
|
||||
}
|
||||
Runtime_Access_Result runtime_read(Managed_Access_Mode mode, std::string_view key, void* context, Runtime_Read_Callback callback) const {
|
||||
return runtime_interface_->read(*this, mode, key, context, callback);
|
||||
}
|
||||
Runtime_Access_Result runtime_write(Managed_Access_Mode mode, std::string_view key, const std::type_info& value_type, const void* value) {
|
||||
return runtime_interface_->write(*this, mode, key, value_type, value);
|
||||
}
|
||||
};
|
||||
template <Managed_Access_Mode Mode, class Schema, std::size_t Index>
|
||||
inline constexpr bool property_read_allowed_v = Mode == Managed_Access_Mode::internal ? Schema::template property_type<Index>::readable : Mode == Managed_Access_Mode::external ? external_readable_v<Schema, Index> : persistence_storable_v<Schema, Index>;
|
||||
template <Managed_Access_Mode Mode, class Schema, std::size_t Index>
|
||||
inline constexpr bool property_write_allowed_v = Mode == Managed_Access_Mode::internal ? Schema::template property_type<Index>::writable : Mode == Managed_Access_Mode::external ? external_writable_v<Schema, Index> : persistence_loadable_v<Schema, Index>;
|
||||
template <Managed_Access_Mode Mode, class Schema, std::size_t Index>
|
||||
inline constexpr bool property_visible_v = property_read_allowed_v<Mode, Schema, Index> || property_write_allowed_v<Mode, Schema, Index>;
|
||||
struct Property_Synchronization {
|
||||
Synchronization_Plan plan;
|
||||
};
|
||||
inline Property_Synchronization property_synchronization(Synchronization_Plan plan) {
|
||||
return {std::move(plan)};
|
||||
}
|
||||
template <class Object, Synchronization_Source_Type Source>
|
||||
Property_Synchronization property_synchronization(Source&& source) requires Property_Described_Object<Object> {
|
||||
using Schema = type_descriptor_schema_t<Object>;
|
||||
return {materialize_synchronization_plan<Schema>(std::forward<Source>(source))};
|
||||
}
|
||||
struct Resolved_Synchronization_View {
|
||||
static constexpr std::size_t unsynchronized_slot = std::numeric_limits<std::size_t>::max();
|
||||
std::span<const std::size_t> lock_slots;
|
||||
std::size_t lock_count{};
|
||||
std::size_t slot(std::size_t index) const noexcept {
|
||||
return lock_slots[index];
|
||||
}
|
||||
bool uses_lock(std::size_t index) const noexcept {
|
||||
return slot(index) != unsynchronized_slot;
|
||||
}
|
||||
};
|
||||
template <class Derived, Synchronization_Policy Lock_Policy = Shared_Mutex_Policy>
|
||||
class Property_Object : public Property_Object_Base {
|
||||
public:
|
||||
using object_type = Derived;
|
||||
using mutex_type = typename Lock_Policy::mutex_type;
|
||||
private:
|
||||
struct Dynamic_Lock_Targets {
|
||||
std::vector<std::size_t> slots;
|
||||
std::vector<std::size_t> unsynchronized_properties;
|
||||
};
|
||||
template <std::size_t Capacity>
|
||||
struct Static_Lock_Targets {
|
||||
std::array<std::size_t, Capacity> slots{};
|
||||
std::size_t slot_count{};
|
||||
std::array<std::size_t, Capacity> unsynchronized_properties{};
|
||||
std::size_t unsynchronized_count{};
|
||||
};
|
||||
std::vector<std::size_t> lock_slots_;
|
||||
std::size_t lock_count_{};
|
||||
std::unique_ptr<mutex_type[]> locks_;
|
||||
template <class Schema>
|
||||
void initialize(const Schema& schema, const Synchronization_Plan& plan) {
|
||||
auto resolved = resolve_synchronization_plan(schema, plan);
|
||||
lock_slots_.assign(resolved.lock_slots.begin(), resolved.lock_slots.end());
|
||||
lock_count_ = resolved.lock_count;
|
||||
locks_ = lock_count_ ? std::make_unique<mutex_type[]>(lock_count_) : nullptr;
|
||||
}
|
||||
void copy_synchronization_from(const Property_Object& other) {
|
||||
lock_slots_ = other.lock_slots_;
|
||||
lock_count_ = other.lock_count_;
|
||||
locks_ = lock_count_ ? std::make_unique<mutex_type[]>(lock_count_) : nullptr;
|
||||
}
|
||||
std::size_t slot(std::size_t index) const noexcept {
|
||||
return lock_slots_[index];
|
||||
}
|
||||
template <std::size_t Capacity>
|
||||
static void sort_unique_prefix(std::array<std::size_t, Capacity>& values, std::size_t& count) {
|
||||
for (std::size_t index = 1; index < count; ++index) {
|
||||
auto value = values[index];
|
||||
auto position = index;
|
||||
while (position > 0 && value < values[position - 1]) {
|
||||
values[position] = values[position - 1];
|
||||
--position;
|
||||
}
|
||||
values[position] = value;
|
||||
}
|
||||
if (count == 0) {
|
||||
return;
|
||||
}
|
||||
std::size_t write = 1;
|
||||
for (std::size_t read = 1; read < count; ++read) {
|
||||
if (values[read] != values[write - 1]) {
|
||||
values[write++] = values[read];
|
||||
}
|
||||
}
|
||||
count = write;
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
static bool runtime_property_readable(std::size_t index) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static const auto table = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
||||
return std::array<bool, Schema::property_count>{property_read_allowed_v<Mode, Schema, Index>...};
|
||||
}(std::make_index_sequence<Schema::property_count>{});
|
||||
return table[index];
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
static bool runtime_property_visible(std::size_t index) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static const auto table = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
||||
return std::array<bool, Schema::property_count>{property_visible_v<Mode, Schema, Index>...};
|
||||
}(std::make_index_sequence<Schema::property_count>{});
|
||||
return table[index];
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
Dynamic_Lock_Targets collect_dynamic_lock_targets(std::span<const std::string_view> keys, bool shared_access) const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
Dynamic_Lock_Targets targets;
|
||||
targets.slots.reserve(keys.size());
|
||||
targets.unsynchronized_properties.reserve(keys.size());
|
||||
for (auto key : keys) {
|
||||
auto index = schema_property_index<Schema>(key);
|
||||
if (!index) {
|
||||
throw std::invalid_argument("Unknown property: " + std::string(key));
|
||||
}
|
||||
bool allowed = shared_access ? runtime_property_readable<Mode>(*index) : runtime_property_visible<Mode>(*index);
|
||||
if (!allowed) {
|
||||
throw std::invalid_argument("Property is not accessible through this view: " + std::string(key));
|
||||
}
|
||||
auto lock_slot = slot(*index);
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
targets.unsynchronized_properties.push_back(*index);
|
||||
} else {
|
||||
targets.slots.push_back(lock_slot);
|
||||
}
|
||||
}
|
||||
std::sort(targets.slots.begin(), targets.slots.end());
|
||||
targets.slots.erase(std::unique(targets.slots.begin(), targets.slots.end()), targets.slots.end());
|
||||
std::sort(targets.unsynchronized_properties.begin(), targets.unsynchronized_properties.end());
|
||||
targets.unsynchronized_properties.erase(std::unique(targets.unsynchronized_properties.begin(), targets.unsynchronized_properties.end()), targets.unsynchronized_properties.end());
|
||||
return targets;
|
||||
}
|
||||
template <Managed_Access_Mode Mode, bool Shared_Access, std::size_t... Index>
|
||||
Static_Lock_Targets<sizeof...(Index)> collect_static_lock_targets() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(((Shared_Access ? property_read_allowed_v<Mode, Schema, Index> : property_visible_v<Mode, Schema, Index>) && ...));
|
||||
Static_Lock_Targets<sizeof...(Index)> targets;
|
||||
auto collect = [&]<std::size_t Property_Index>() {
|
||||
auto lock_slot = slot(Property_Index);
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
targets.unsynchronized_properties[targets.unsynchronized_count++] = Property_Index;
|
||||
} else {
|
||||
targets.slots[targets.slot_count++] = lock_slot;
|
||||
}
|
||||
};
|
||||
(collect.template operator()<Index>(), ...);
|
||||
sort_unique_prefix(targets.slots, targets.slot_count);
|
||||
sort_unique_prefix(targets.unsynchronized_properties, targets.unsynchronized_count);
|
||||
return targets;
|
||||
}
|
||||
template <Managed_Access_Mode Mode, bool Shared_Access>
|
||||
auto collect_all_static_lock_targets() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
Static_Lock_Targets<Schema::property_count> targets;
|
||||
auto collect = [&]<std::size_t Index>() {
|
||||
constexpr bool allowed = Shared_Access ? property_read_allowed_v<Mode, Schema, Index> : property_write_allowed_v<Mode, Schema, Index>;
|
||||
if constexpr (allowed) {
|
||||
auto lock_slot = slot(Index);
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
targets.unsynchronized_properties[targets.unsynchronized_count++] = Index;
|
||||
} else {
|
||||
targets.slots[targets.slot_count++] = lock_slot;
|
||||
}
|
||||
}
|
||||
};
|
||||
[&]<std::size_t... Index>(std::index_sequence<Index...>) {
|
||||
(collect.template operator()<Index>(), ...);
|
||||
}(std::make_index_sequence<Schema::property_count>{});
|
||||
sort_unique_prefix(targets.slots, targets.slot_count);
|
||||
sort_unique_prefix(targets.unsynchronized_properties, targets.unsynchronized_count);
|
||||
return targets;
|
||||
}
|
||||
template <std::size_t Index, class View>
|
||||
decltype(auto) read_unlocked(const View& view) const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
const auto& descriptor = type_descriptor<Derived>().template property<Index>();
|
||||
using Accessor = typename Schema::template property_type<Index>::accessor_type;
|
||||
if constexpr (Accessor::synchronized_view_read) {
|
||||
return descriptor.accessor.read(view);
|
||||
} else {
|
||||
return descriptor.accessor.read(static_cast<const Derived&>(*this));
|
||||
}
|
||||
}
|
||||
template <std::size_t Index, class Value>
|
||||
void write_unlocked(Value&& value) {
|
||||
const auto& descriptor = type_descriptor<Derived>().template property<Index>();
|
||||
descriptor.accessor.write(static_cast<Derived&>(*this), std::forward<Value>(value));
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
class Single_Read_View {
|
||||
const Property_Object* owner_{};
|
||||
std::size_t property_index_{};
|
||||
std::size_t lock_slot_{};
|
||||
public:
|
||||
Single_Read_View(const Property_Object& owner, std::size_t property_index, std::size_t lock_slot) : owner_(&owner), property_index_(property_index), lock_slot_(lock_slot) {}
|
||||
template <auto Member>
|
||||
decltype(auto) get() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
if (owner_->slot(index) != lock_slot_ || (lock_slot_ == Resolved_Synchronization_View::unsynchronized_slot && index != property_index_)) {
|
||||
throw std::logic_error("Computed property reads outside its synchronization slot");
|
||||
}
|
||||
return owner_->template read_unlocked<index>(*this);
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
decltype(auto) get_key() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
if (owner_->slot(index) != lock_slot_ || (lock_slot_ == Resolved_Synchronization_View::unsynchronized_slot && index != property_index_)) {
|
||||
throw std::logic_error("Computed property reads outside its synchronization slot");
|
||||
}
|
||||
return owner_->template read_unlocked<index>(*this);
|
||||
}
|
||||
};
|
||||
template <Managed_Access_Mode Mode, std::size_t Index>
|
||||
auto read_one() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(property_read_allowed_v<Mode, Schema, Index>);
|
||||
using Value = typename Schema::template property_type<Index>::value_type;
|
||||
auto lock_slot = slot(Index);
|
||||
Single_Read_View<Mode> view{*this, Index, lock_slot};
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
return Value(read_unlocked<Index>(view));
|
||||
}
|
||||
std::shared_lock lock{locks_[lock_slot]};
|
||||
return Value(read_unlocked<Index>(view));
|
||||
}
|
||||
template <Managed_Access_Mode Mode, std::size_t Index, class Value>
|
||||
void write_one(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(property_write_allowed_v<Mode, Schema, Index>);
|
||||
auto lock_slot = slot(Index);
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
write_unlocked<Index>(std::forward<Value>(value));
|
||||
return;
|
||||
}
|
||||
std::unique_lock lock{locks_[lock_slot]};
|
||||
write_unlocked<Index>(std::forward<Value>(value));
|
||||
}
|
||||
public:
|
||||
template <Managed_Access_Mode Mode>
|
||||
class Read_Guard {
|
||||
const Property_Object* owner_{};
|
||||
std::vector<std::size_t> slots_;
|
||||
std::vector<std::size_t> unsynchronized_properties_;
|
||||
std::vector<std::shared_lock<mutex_type>> locks_;
|
||||
bool holds(std::size_t index) const {
|
||||
auto lock_slot = owner_->slot(index);
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
return std::binary_search(unsynchronized_properties_.begin(), unsynchronized_properties_.end(), index);
|
||||
}
|
||||
return std::binary_search(slots_.begin(), slots_.end(), lock_slot);
|
||||
}
|
||||
friend class Property_Object;
|
||||
Read_Guard(const Property_Object& owner, Dynamic_Lock_Targets targets) : owner_(&owner), slots_(std::move(targets.slots)), unsynchronized_properties_(std::move(targets.unsynchronized_properties)) {
|
||||
locks_.reserve(slots_.size());
|
||||
for (auto lock_slot : slots_) {
|
||||
locks_.emplace_back(owner_->locks_[lock_slot]);
|
||||
}
|
||||
}
|
||||
public:
|
||||
template <std::size_t Index>
|
||||
decltype(auto) get_index() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(Index < Schema::property_count);
|
||||
static_assert(property_read_allowed_v<Mode, Schema, Index>);
|
||||
if (!holds(Index)) {
|
||||
throw std::logic_error("Property is outside the held synchronization set");
|
||||
}
|
||||
return owner_->template read_unlocked<Index>(*this);
|
||||
}
|
||||
template <auto Member>
|
||||
decltype(auto) get() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return get_index<index>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
decltype(auto) get_key() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return get_index<index>();
|
||||
}
|
||||
};
|
||||
template <Managed_Access_Mode Mode>
|
||||
class Write_Guard {
|
||||
Property_Object* owner_{};
|
||||
std::vector<std::size_t> slots_;
|
||||
std::vector<std::size_t> unsynchronized_properties_;
|
||||
std::vector<std::unique_lock<mutex_type>> locks_;
|
||||
bool holds(std::size_t index) const {
|
||||
auto lock_slot = owner_->slot(index);
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
return std::binary_search(unsynchronized_properties_.begin(), unsynchronized_properties_.end(), index);
|
||||
}
|
||||
return std::binary_search(slots_.begin(), slots_.end(), lock_slot);
|
||||
}
|
||||
friend class Property_Object;
|
||||
Write_Guard(Property_Object& owner, Dynamic_Lock_Targets targets) : owner_(&owner), slots_(std::move(targets.slots)), unsynchronized_properties_(std::move(targets.unsynchronized_properties)) {
|
||||
locks_.reserve(slots_.size());
|
||||
for (auto lock_slot : slots_) {
|
||||
locks_.emplace_back(owner_->locks_[lock_slot]);
|
||||
}
|
||||
}
|
||||
public:
|
||||
template <std::size_t Index>
|
||||
decltype(auto) get_index() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(Index < Schema::property_count);
|
||||
static_assert(property_read_allowed_v<Mode, Schema, Index>);
|
||||
if (!holds(Index)) {
|
||||
throw std::logic_error("Property is outside the held synchronization set");
|
||||
}
|
||||
return owner_->template read_unlocked<Index>(*this);
|
||||
}
|
||||
template <auto Member>
|
||||
decltype(auto) get() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return get_index<index>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
decltype(auto) get_key() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return get_index<index>();
|
||||
}
|
||||
template <std::size_t Index, class Value>
|
||||
void set_index(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(Index < Schema::property_count);
|
||||
static_assert(property_write_allowed_v<Mode, Schema, Index>);
|
||||
if (!holds(Index)) {
|
||||
throw std::logic_error("Property is outside the held synchronization set");
|
||||
}
|
||||
owner_->template write_unlocked<Index>(std::forward<Value>(value));
|
||||
}
|
||||
template <auto Member, class Value>
|
||||
void set(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
set_index<index>(std::forward<Value>(value));
|
||||
}
|
||||
template <Fixed_String Key, class Value>
|
||||
void set_key(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
set_index<index>(std::forward<Value>(value));
|
||||
}
|
||||
};
|
||||
template <Managed_Access_Mode Mode, std::size_t Capacity>
|
||||
class Static_Read_Guard {
|
||||
const Property_Object* owner_{};
|
||||
Static_Lock_Targets<Capacity> targets_;
|
||||
std::array<std::shared_lock<mutex_type>, Capacity> locks_{};
|
||||
bool holds(std::size_t index) const {
|
||||
auto lock_slot = owner_->slot(index);
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
return std::binary_search(targets_.unsynchronized_properties.begin(), targets_.unsynchronized_properties.begin() + static_cast<std::ptrdiff_t>(targets_.unsynchronized_count), index);
|
||||
}
|
||||
return std::binary_search(targets_.slots.begin(), targets_.slots.begin() + static_cast<std::ptrdiff_t>(targets_.slot_count), lock_slot);
|
||||
}
|
||||
friend class Property_Object;
|
||||
Static_Read_Guard(const Property_Object& owner, Static_Lock_Targets<Capacity> targets) : owner_(&owner), targets_(std::move(targets)) {
|
||||
for (std::size_t index = 0; index < targets_.slot_count; ++index) {
|
||||
locks_[index] = std::shared_lock<mutex_type>{owner_->locks_[targets_.slots[index]]};
|
||||
}
|
||||
}
|
||||
public:
|
||||
template <std::size_t Index>
|
||||
decltype(auto) get_index() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(Index < Schema::property_count);
|
||||
static_assert(property_read_allowed_v<Mode, Schema, Index>);
|
||||
if (!holds(Index)) {
|
||||
throw std::logic_error("Property is outside the held synchronization set");
|
||||
}
|
||||
return owner_->template read_unlocked<Index>(*this);
|
||||
}
|
||||
template <auto Member>
|
||||
decltype(auto) get() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return get_index<index>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
decltype(auto) get_key() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return get_index<index>();
|
||||
}
|
||||
};
|
||||
template <Managed_Access_Mode Mode, std::size_t Capacity>
|
||||
class Static_Write_Guard {
|
||||
Property_Object* owner_{};
|
||||
Static_Lock_Targets<Capacity> targets_;
|
||||
std::array<std::unique_lock<mutex_type>, Capacity> locks_{};
|
||||
bool holds(std::size_t index) const {
|
||||
auto lock_slot = owner_->slot(index);
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
return std::binary_search(targets_.unsynchronized_properties.begin(), targets_.unsynchronized_properties.begin() + static_cast<std::ptrdiff_t>(targets_.unsynchronized_count), index);
|
||||
}
|
||||
return std::binary_search(targets_.slots.begin(), targets_.slots.begin() + static_cast<std::ptrdiff_t>(targets_.slot_count), lock_slot);
|
||||
}
|
||||
friend class Property_Object;
|
||||
Static_Write_Guard(Property_Object& owner, Static_Lock_Targets<Capacity> targets) : owner_(&owner), targets_(std::move(targets)) {
|
||||
for (std::size_t index = 0; index < targets_.slot_count; ++index) {
|
||||
locks_[index] = std::unique_lock<mutex_type>{owner_->locks_[targets_.slots[index]]};
|
||||
}
|
||||
}
|
||||
public:
|
||||
template <std::size_t Index>
|
||||
decltype(auto) get_index() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(Index < Schema::property_count);
|
||||
static_assert(property_read_allowed_v<Mode, Schema, Index>);
|
||||
if (!holds(Index)) {
|
||||
throw std::logic_error("Property is outside the held synchronization set");
|
||||
}
|
||||
return owner_->template read_unlocked<Index>(*this);
|
||||
}
|
||||
template <auto Member>
|
||||
decltype(auto) get() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return get_index<index>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
decltype(auto) get_key() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return get_index<index>();
|
||||
}
|
||||
template <std::size_t Index, class Value>
|
||||
void set_index(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert(Index < Schema::property_count);
|
||||
static_assert(property_write_allowed_v<Mode, Schema, Index>);
|
||||
if (!holds(Index)) {
|
||||
throw std::logic_error("Property is outside the held synchronization set");
|
||||
}
|
||||
owner_->template write_unlocked<Index>(std::forward<Value>(value));
|
||||
}
|
||||
template <auto Member, class Value>
|
||||
void set(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
set_index<index>(std::forward<Value>(value));
|
||||
}
|
||||
template <Fixed_String Key, class Value>
|
||||
void set_key(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
set_index<index>(std::forward<Value>(value));
|
||||
}
|
||||
};
|
||||
private:
|
||||
template <Managed_Access_Mode Mode, class Function>
|
||||
void for_each_readable_impl(Function&& function) const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
const auto& schema = type_descriptor<Derived>();
|
||||
schema.for_each_property([&](auto index, const auto& descriptor) {
|
||||
constexpr std::size_t property_index = decltype(index)::value;
|
||||
if constexpr (property_read_allowed_v<Mode, Schema, property_index>) {
|
||||
auto value = read_one<Mode, property_index>();
|
||||
std::invoke(function, index, descriptor, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
template <Managed_Access_Mode Mode, class Function>
|
||||
void for_each_readable_locked_impl(Function&& function) const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
auto targets = collect_all_static_lock_targets<Mode, true>();
|
||||
Static_Read_Guard<Mode, Schema::property_count> guard{*this, std::move(targets)};
|
||||
type_descriptor<Derived>().for_each_property([&](auto index, const auto& descriptor) {
|
||||
constexpr std::size_t property_index = decltype(index)::value;
|
||||
if constexpr (property_read_allowed_v<Mode, Schema, property_index>) {
|
||||
std::invoke(function, index, descriptor, guard.template get_index<property_index>());
|
||||
}
|
||||
});
|
||||
}
|
||||
template <Managed_Access_Mode Mode, class Function>
|
||||
void with_all_writable_locked_impl(Function&& function) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
auto targets = collect_all_static_lock_targets<Mode, false>();
|
||||
Static_Write_Guard<Mode, Schema::property_count> guard{*this, std::move(targets)};
|
||||
std::invoke(std::forward<Function>(function), guard);
|
||||
}
|
||||
static const std::type_info& runtime_object_type_impl() noexcept {
|
||||
return typeid(Derived);
|
||||
}
|
||||
static std::size_t runtime_property_count_impl() noexcept {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
return Schema::property_count;
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
Runtime_Access_Result runtime_read_mode(std::string_view key, void* context, Runtime_Read_Callback callback) const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
auto index = schema_property_index<Schema>(key);
|
||||
if (!index) {
|
||||
return Runtime_Access_Result::unknown_property;
|
||||
}
|
||||
if (!runtime_property_readable<Mode>(*index)) {
|
||||
return Runtime_Access_Result::not_readable;
|
||||
}
|
||||
Runtime_Access_Result result = Runtime_Access_Result::unknown_property;
|
||||
visit_schema_property(type_descriptor<Derived>(), key, [&](auto property_index_constant, const auto&) {
|
||||
constexpr std::size_t property_index = decltype(property_index_constant)::value;
|
||||
using Value = typename Schema::template property_type<property_index>::value_type;
|
||||
auto lock_slot = slot(property_index);
|
||||
Single_Read_View<Mode> view{*this, property_index, lock_slot};
|
||||
auto emit = [&] {
|
||||
if constexpr (std::is_reference_v<decltype(read_unlocked<property_index>(view))>) {
|
||||
auto&& value = read_unlocked<property_index>(view);
|
||||
callback(context, property_index, key, typeid(Value), std::addressof(value));
|
||||
} else {
|
||||
Value value = read_unlocked<property_index>(view);
|
||||
callback(context, property_index, key, typeid(Value), std::addressof(value));
|
||||
}
|
||||
};
|
||||
if (lock_slot == Resolved_Synchronization_View::unsynchronized_slot) {
|
||||
emit();
|
||||
} else {
|
||||
std::shared_lock lock{locks_[lock_slot]};
|
||||
emit();
|
||||
}
|
||||
result = Runtime_Access_Result::ok;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
static Runtime_Access_Result runtime_read_impl(const Property_Object_Base& base, Managed_Access_Mode mode, std::string_view key, void* context, Runtime_Read_Callback callback) {
|
||||
const auto& self = static_cast<const Property_Object&>(base);
|
||||
switch (mode) {
|
||||
case Managed_Access_Mode::internal:
|
||||
return self.template runtime_read_mode<Managed_Access_Mode::internal>(key, context, callback);
|
||||
case Managed_Access_Mode::external:
|
||||
return self.template runtime_read_mode<Managed_Access_Mode::external>(key, context, callback);
|
||||
case Managed_Access_Mode::persistence:
|
||||
return self.template runtime_read_mode<Managed_Access_Mode::persistence>(key, context, callback);
|
||||
}
|
||||
return Runtime_Access_Result::not_readable;
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
Runtime_Access_Result runtime_write_mode(std::string_view key, const std::type_info& value_type, const void* value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
auto index = schema_property_index<Schema>(key);
|
||||
if (!index) {
|
||||
return Runtime_Access_Result::unknown_property;
|
||||
}
|
||||
if (!runtime_property_visible<Mode>(*index)) {
|
||||
return Runtime_Access_Result::not_writable;
|
||||
}
|
||||
Runtime_Access_Result result = Runtime_Access_Result::unknown_property;
|
||||
visit_schema_property(type_descriptor<Derived>(), key, [&](auto property_index_constant, const auto&) {
|
||||
constexpr std::size_t property_index = decltype(property_index_constant)::value;
|
||||
using Property = typename Schema::template property_type<property_index>;
|
||||
using Accessor = typename Property::accessor_type;
|
||||
using Value = typename Property::value_type;
|
||||
if constexpr (!property_write_allowed_v<Mode, Schema, property_index> || !requires(const Accessor& accessor, Derived& object, const Value& candidate) { accessor.write(object, candidate); }) {
|
||||
result = Runtime_Access_Result::not_writable;
|
||||
} else if (value_type != typeid(Value)) {
|
||||
result = Runtime_Access_Result::type_mismatch;
|
||||
} else {
|
||||
write_one<Mode, property_index>(*static_cast<const Value*>(value));
|
||||
result = Runtime_Access_Result::ok;
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
static Runtime_Access_Result runtime_write_impl(Property_Object_Base& base, Managed_Access_Mode mode, std::string_view key, const std::type_info& value_type, const void* value) {
|
||||
auto& self = static_cast<Property_Object&>(base);
|
||||
switch (mode) {
|
||||
case Managed_Access_Mode::internal:
|
||||
return self.template runtime_write_mode<Managed_Access_Mode::internal>(key, value_type, value);
|
||||
case Managed_Access_Mode::external:
|
||||
return self.template runtime_write_mode<Managed_Access_Mode::external>(key, value_type, value);
|
||||
case Managed_Access_Mode::persistence:
|
||||
return self.template runtime_write_mode<Managed_Access_Mode::persistence>(key, value_type, value);
|
||||
}
|
||||
return Runtime_Access_Result::not_writable;
|
||||
}
|
||||
static const Property_Object_Base::Runtime_Interface* runtime_interface() noexcept {
|
||||
static const Property_Object_Base::Runtime_Interface value{
|
||||
&runtime_object_type_impl,
|
||||
&runtime_property_count_impl,
|
||||
&runtime_read_impl,
|
||||
&runtime_write_impl
|
||||
};
|
||||
return &value;
|
||||
}
|
||||
protected:
|
||||
Property_Object() : Property_Object_Base(runtime_interface()) {
|
||||
const auto& schema = type_descriptor<Derived>();
|
||||
initialize(schema, schema.synchronization_plan());
|
||||
}
|
||||
explicit Property_Object(Property_Synchronization synchronization) : Property_Object_Base(runtime_interface()) {
|
||||
initialize(type_descriptor<Derived>(), synchronization.plan);
|
||||
}
|
||||
Property_Object(const Property_Object& other) : Property_Object_Base(runtime_interface()) {
|
||||
copy_synchronization_from(other);
|
||||
}
|
||||
Property_Object(Property_Object&& other) : Property_Object_Base(runtime_interface()) {
|
||||
copy_synchronization_from(other);
|
||||
}
|
||||
Property_Object& operator=(const Property_Object&) noexcept {
|
||||
return *this;
|
||||
}
|
||||
Property_Object& operator=(Property_Object&&) noexcept {
|
||||
return *this;
|
||||
}
|
||||
~Property_Object() = default;
|
||||
public:
|
||||
const auto& schema() const noexcept {
|
||||
return type_descriptor<Derived>();
|
||||
}
|
||||
Resolved_Synchronization_View resolved_synchronization() const noexcept {
|
||||
return {lock_slots_, lock_count_};
|
||||
}
|
||||
Derived& unsafe_object() noexcept {
|
||||
return static_cast<Derived&>(*this);
|
||||
}
|
||||
const Derived& unsafe_object() const noexcept {
|
||||
return static_cast<const Derived&>(*this);
|
||||
}
|
||||
template <auto Member>
|
||||
auto read() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return read_one<Managed_Access_Mode::internal, index>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
auto read_key() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return read_one<Managed_Access_Mode::internal, index>();
|
||||
}
|
||||
template <auto Member, class Value>
|
||||
void write(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
write_one<Managed_Access_Mode::internal, index>(std::forward<Value>(value));
|
||||
}
|
||||
template <Fixed_String Key, class Value>
|
||||
void write_key(Value&& value) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
write_one<Managed_Access_Mode::internal, index>(std::forward<Value>(value));
|
||||
}
|
||||
template <auto Member>
|
||||
std::size_t lock_slot() const noexcept {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return slot(index);
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
Read_Guard<Mode> lock_shared(std::span<const std::string_view> keys) const {
|
||||
return Read_Guard<Mode>{*this, collect_dynamic_lock_targets<Mode>(keys, true)};
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
Read_Guard<Mode> lock_shared(std::initializer_list<std::string_view> keys) const {
|
||||
return lock_shared<Mode>(std::span<const std::string_view>{keys.begin(), keys.size()});
|
||||
}
|
||||
Read_Guard<Managed_Access_Mode::internal> lock_shared(std::span<const std::string_view> keys) const {
|
||||
return lock_shared<Managed_Access_Mode::internal>(keys);
|
||||
}
|
||||
Read_Guard<Managed_Access_Mode::internal> lock_shared(std::initializer_list<std::string_view> keys) const {
|
||||
return lock_shared<Managed_Access_Mode::internal>(keys);
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
Write_Guard<Mode> lock_unique(std::span<const std::string_view> keys) {
|
||||
return Write_Guard<Mode>{*this, collect_dynamic_lock_targets<Mode>(keys, false)};
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
Write_Guard<Mode> lock_unique(std::initializer_list<std::string_view> keys) {
|
||||
return lock_unique<Mode>(std::span<const std::string_view>{keys.begin(), keys.size()});
|
||||
}
|
||||
Write_Guard<Managed_Access_Mode::internal> lock_unique(std::span<const std::string_view> keys) {
|
||||
return lock_unique<Managed_Access_Mode::internal>(keys);
|
||||
}
|
||||
Write_Guard<Managed_Access_Mode::internal> lock_unique(std::initializer_list<std::string_view> keys) {
|
||||
return lock_unique<Managed_Access_Mode::internal>(keys);
|
||||
}
|
||||
template <Managed_Access_Mode Mode, auto... Members>
|
||||
auto lock_shared_mode() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert((Schema_Property_Member<Schema, Members> && ...));
|
||||
auto targets = collect_static_lock_targets<Mode, true, schema_member_property_index_v<Schema, Members>...>();
|
||||
return Static_Read_Guard<Mode, sizeof...(Members)>{*this, std::move(targets)};
|
||||
}
|
||||
template <auto... Members>
|
||||
auto lock_shared() const {
|
||||
return lock_shared_mode<Managed_Access_Mode::internal, Members...>();
|
||||
}
|
||||
template <Managed_Access_Mode Mode, auto... Members>
|
||||
auto lock_unique_mode() {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
static_assert((Schema_Property_Member<Schema, Members> && ...));
|
||||
auto targets = collect_static_lock_targets<Mode, false, schema_member_property_index_v<Schema, Members>...>();
|
||||
return Static_Write_Guard<Mode, sizeof...(Members)>{*this, std::move(targets)};
|
||||
}
|
||||
template <auto... Members>
|
||||
auto lock_unique() {
|
||||
return lock_unique_mode<Managed_Access_Mode::internal, Members...>();
|
||||
}
|
||||
template <class Function>
|
||||
void for_each_readable(Function&& function) const {
|
||||
for_each_readable_impl<Managed_Access_Mode::internal>(std::forward<Function>(function));
|
||||
}
|
||||
template <class Function>
|
||||
void for_each_readable_locked(Function&& function) const {
|
||||
for_each_readable_locked_impl<Managed_Access_Mode::internal>(std::forward<Function>(function));
|
||||
}
|
||||
template <class Function>
|
||||
void with_all_writable_locked(Function&& function) {
|
||||
with_all_writable_locked_impl<Managed_Access_Mode::internal>(std::forward<Function>(function));
|
||||
}
|
||||
template <Managed_Access_Mode Mode>
|
||||
class Capability_View {
|
||||
Property_Object* owner_{};
|
||||
public:
|
||||
explicit Capability_View(Property_Object& owner) : owner_(&owner) {}
|
||||
template <auto Member>
|
||||
auto read() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return owner_->template read_one<Mode, index>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
auto read_key() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return owner_->template read_one<Mode, index>();
|
||||
}
|
||||
template <auto Member, class Value>
|
||||
void write(Value&& value) requires (Mode != Managed_Access_Mode::persistence) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
owner_->template write_one<Mode, index>(std::forward<Value>(value));
|
||||
}
|
||||
template <Fixed_String Key, class Value>
|
||||
void write_key(Value&& value) requires (Mode != Managed_Access_Mode::persistence) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
owner_->template write_one<Mode, index>(std::forward<Value>(value));
|
||||
}
|
||||
template <auto Member, class Value>
|
||||
void load(Value&& value) requires (Mode == Managed_Access_Mode::persistence) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
owner_->template write_one<Mode, index>(std::forward<Value>(value));
|
||||
}
|
||||
template <Fixed_String Key, class Value>
|
||||
void load_key(Value&& value) requires (Mode == Managed_Access_Mode::persistence) {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
owner_->template write_one<Mode, index>(std::forward<Value>(value));
|
||||
}
|
||||
template <auto Member>
|
||||
auto store() const requires (Mode == Managed_Access_Mode::persistence) {
|
||||
return read<Member>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
auto store_key() const requires (Mode == Managed_Access_Mode::persistence) {
|
||||
return read_key<Key>();
|
||||
}
|
||||
template <auto... Members>
|
||||
auto lock_shared() const {
|
||||
return owner_->template lock_shared_mode<Mode, Members...>();
|
||||
}
|
||||
template <auto... Members>
|
||||
auto lock_unique() {
|
||||
return owner_->template lock_unique_mode<Mode, Members...>();
|
||||
}
|
||||
Read_Guard<Mode> lock_shared(std::span<const std::string_view> keys) const {
|
||||
return owner_->template lock_shared<Mode>(keys);
|
||||
}
|
||||
Write_Guard<Mode> lock_unique(std::span<const std::string_view> keys) {
|
||||
return owner_->template lock_unique<Mode>(keys);
|
||||
}
|
||||
template <class Function>
|
||||
void for_each_readable(Function&& function) const {
|
||||
owner_->template for_each_readable_impl<Mode>(std::forward<Function>(function));
|
||||
}
|
||||
template <class Function>
|
||||
void for_each_readable_locked(Function&& function) const {
|
||||
owner_->template for_each_readable_locked_impl<Mode>(std::forward<Function>(function));
|
||||
}
|
||||
template <class Function>
|
||||
void with_all_writable_locked(Function&& function) requires (Mode != Managed_Access_Mode::persistence) {
|
||||
owner_->template with_all_writable_locked_impl<Mode>(std::forward<Function>(function));
|
||||
}
|
||||
template <class Function>
|
||||
void with_all_loadable_locked(Function&& function) requires (Mode == Managed_Access_Mode::persistence) {
|
||||
owner_->template with_all_writable_locked_impl<Mode>(std::forward<Function>(function));
|
||||
}
|
||||
};
|
||||
template <Managed_Access_Mode Mode>
|
||||
class Const_Capability_View {
|
||||
const Property_Object* owner_{};
|
||||
public:
|
||||
explicit Const_Capability_View(const Property_Object& owner) : owner_(&owner) {}
|
||||
template <auto Member>
|
||||
auto read() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return owner_->template read_one<Mode, index>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
auto read_key() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
static_assert(index < Schema::property_count);
|
||||
return owner_->template read_one<Mode, index>();
|
||||
}
|
||||
template <auto Member>
|
||||
auto store() const requires (Mode == Managed_Access_Mode::persistence) {
|
||||
return read<Member>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
auto store_key() const requires (Mode == Managed_Access_Mode::persistence) {
|
||||
return read_key<Key>();
|
||||
}
|
||||
template <auto... Members>
|
||||
auto lock_shared() const {
|
||||
return owner_->template lock_shared_mode<Mode, Members...>();
|
||||
}
|
||||
Read_Guard<Mode> lock_shared(std::span<const std::string_view> keys) const {
|
||||
return owner_->template lock_shared<Mode>(keys);
|
||||
}
|
||||
template <class Function>
|
||||
void for_each_readable(Function&& function) const {
|
||||
owner_->template for_each_readable_impl<Mode>(std::forward<Function>(function));
|
||||
}
|
||||
template <class Function>
|
||||
void for_each_readable_locked(Function&& function) const {
|
||||
owner_->template for_each_readable_locked_impl<Mode>(std::forward<Function>(function));
|
||||
}
|
||||
};
|
||||
Capability_View<Managed_Access_Mode::external> external() {
|
||||
return Capability_View<Managed_Access_Mode::external>{*this};
|
||||
}
|
||||
Capability_View<Managed_Access_Mode::persistence> persistence() {
|
||||
return Capability_View<Managed_Access_Mode::persistence>{*this};
|
||||
}
|
||||
Const_Capability_View<Managed_Access_Mode::external> external() const {
|
||||
return Const_Capability_View<Managed_Access_Mode::external>{*this};
|
||||
}
|
||||
Const_Capability_View<Managed_Access_Mode::persistence> persistence() const {
|
||||
return Const_Capability_View<Managed_Access_Mode::persistence>{*this};
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,430 @@
|
||||
#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 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, 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_Access_Attribute = External_Access_Attribute<External_Access::none>;
|
||||
using Default_Persistence_Attribute = Persistence_Access_Attribute<Persistence_Access::none>;
|
||||
using Default_Sensitive_Attribute = Sensitive_Attribute<false>;
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr External_Access effective_external_access_v = effective_attribute_t<Schema, Index, Access_Category, Default_Access_Attribute>::value;
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr Persistence_Access effective_persistence_access_v = effective_attribute_t<Schema, Index, Persistence_Category, Default_Persistence_Attribute>::value;
|
||||
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, std::size_t Index>
|
||||
inline constexpr bool external_readable_v = effective_external_access_v<Schema, Index> == External_Access::read || effective_external_access_v<Schema, Index> == External_Access::read_write;
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr bool external_writable_v = effective_external_access_v<Schema, Index> == External_Access::write || effective_external_access_v<Schema, Index> == External_Access::read_write;
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr bool persistence_loadable_v = effective_persistence_access_v<Schema, Index> == Persistence_Access::load || effective_persistence_access_v<Schema, Index> == Persistence_Access::load_store;
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr bool persistence_storable_v = effective_persistence_access_v<Schema, Index> == Persistence_Access::store || effective_persistence_access_v<Schema, Index> == Persistence_Access::load_store;
|
||||
template <class Schema, std::size_t Index>
|
||||
consteval bool property_capability_semantics_valid() {
|
||||
using property_type = typename Schema::template property_type<Index>;
|
||||
if constexpr ((external_readable_v<Schema, Index> || persistence_storable_v<Schema, Index>) && !property_type::readable) {
|
||||
return false;
|
||||
}
|
||||
if constexpr ((external_writable_v<Schema, Index> || persistence_loadable_v<Schema, Index>) && !property_type::writable) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
template <class Schema, std::size_t... Index>
|
||||
consteval bool schema_capability_semantics_valid_impl(std::index_sequence<Index...>) {
|
||||
return (property_capability_semantics_valid<Schema, Index>() && ...);
|
||||
}
|
||||
template <class Schema>
|
||||
consteval bool schema_capability_semantics_valid() {
|
||||
return schema_capability_semantics_valid_impl<Schema>(std::make_index_sequence<Schema::property_count>{});
|
||||
}
|
||||
template <class Schema>
|
||||
concept Valid_Property_Schema = Property_Schema<Schema> && requires {
|
||||
requires schema_capability_semantics_valid<Schema>();
|
||||
};
|
||||
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;
|
||||
}
|
||||
}
|
||||
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(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<decltype(descriptor)>;
|
||||
if (declared_property_key<property_type>() == key_value) {
|
||||
std::invoke(function, index, descriptor);
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
});
|
||||
return found;
|
||||
}
|
||||
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)...);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,168 @@
|
||||
#pragma once
|
||||
#include <array>
|
||||
#include <cstddef>
|
||||
#include <concepts>
|
||||
#include <initializer_list>
|
||||
#include <limits>
|
||||
#include <span>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
namespace structive {
|
||||
enum class Synchronization_Default {
|
||||
independent,
|
||||
shared,
|
||||
unsynchronized
|
||||
};
|
||||
struct Sync_Default_Rule {
|
||||
Synchronization_Default value{Synchronization_Default::independent};
|
||||
};
|
||||
struct Sync_Property_Rule {
|
||||
enum class Mode {
|
||||
independent,
|
||||
unsynchronized
|
||||
};
|
||||
Mode mode{Mode::independent};
|
||||
std::string property;
|
||||
};
|
||||
struct Sync_Group_Rule {
|
||||
std::string name;
|
||||
std::vector<std::string> properties;
|
||||
};
|
||||
template <auto Member, Sync_Property_Rule::Mode Mode>
|
||||
struct Sync_Member_Rule {
|
||||
static constexpr auto member = Member;
|
||||
static constexpr auto mode = Mode;
|
||||
};
|
||||
template <auto... Members>
|
||||
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 <auto Member>
|
||||
constexpr auto sync_independent() {
|
||||
return Sync_Member_Rule<Member, Sync_Property_Rule::Mode::independent>{};
|
||||
}
|
||||
template <auto Member>
|
||||
constexpr auto sync_unsynchronized() {
|
||||
return Sync_Member_Rule<Member, Sync_Property_Rule::Mode::unsynchronized>{};
|
||||
}
|
||||
template <auto... Members>
|
||||
Sync_Member_Group_Rule<Members...> sync_group(std::string_view name) {
|
||||
return {std::string(name)};
|
||||
}
|
||||
template <class... Properties>
|
||||
Sync_Group_Rule sync_group(std::string_view name, Properties&&... properties) requires (std::convertible_to<Properties, std::string_view> && ...) {
|
||||
Sync_Group_Rule rule;
|
||||
rule.name = name;
|
||||
rule.properties.reserve(sizeof...(Properties));
|
||||
(rule.properties.emplace_back(std::string_view(std::forward<Properties>(properties))), ...);
|
||||
return rule;
|
||||
}
|
||||
class Synchronization_Plan {
|
||||
Synchronization_Default default_mode_{Synchronization_Default::independent};
|
||||
std::vector<Sync_Property_Rule> property_rules_;
|
||||
std::vector<Sync_Group_Rule> 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<const std::string_view> 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<std::string_view> properties) {
|
||||
return group(name, std::span<const std::string_view>{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 <class Type>
|
||||
concept Synchronization_Plan_Type = requires {
|
||||
typename Type::synchronization_plan_tag;
|
||||
};
|
||||
template <class Type>
|
||||
concept Runtime_Synchronization_Rule = std::same_as<std::remove_cvref_t<Type>, Sync_Default_Rule> || std::same_as<std::remove_cvref_t<Type>, Sync_Property_Rule> || std::same_as<std::remove_cvref_t<Type>, Sync_Group_Rule>;
|
||||
template <class... Rules>
|
||||
struct Synchronization_Spec {
|
||||
using synchronization_spec_tag = void;
|
||||
std::tuple<Rules...> rules;
|
||||
};
|
||||
template <class Type>
|
||||
concept Synchronization_Spec_Type = requires {
|
||||
typename Type::synchronization_spec_tag;
|
||||
};
|
||||
template <class Type>
|
||||
concept Synchronization_Source_Type = Synchronization_Plan_Type<Type> || Synchronization_Spec_Type<Type>;
|
||||
template <class... Rules>
|
||||
auto synchronization(Rules&&... rules) {
|
||||
if constexpr ((Runtime_Synchronization_Rule<Rules> && ...)) {
|
||||
Synchronization_Plan plan;
|
||||
(plan.apply(std::forward<Rules>(rules)), ...);
|
||||
return plan;
|
||||
} else {
|
||||
return Synchronization_Spec<std::decay_t<Rules>...>{{std::forward<Rules>(rules)...}};
|
||||
}
|
||||
}
|
||||
template <std::size_t Property_Count>
|
||||
struct Resolved_Synchronization_Plan {
|
||||
static constexpr std::size_t unsynchronized_slot = std::numeric_limits<std::size_t>::max();
|
||||
std::array<std::size_t, Property_Count> 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;
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
#pragma once
|
||||
#include "schema.hpp"
|
||||
#include <concepts>
|
||||
#include <type_traits>
|
||||
namespace structive {
|
||||
template <class Object>
|
||||
struct Type_Descriptor;
|
||||
template <class Object>
|
||||
using type_descriptor_schema_t = std::remove_cvref_t<decltype(Type_Descriptor<Object>::get())>;
|
||||
template <class Object>
|
||||
concept Property_Described_Object = requires {
|
||||
{ Type_Descriptor<Object>::get() };
|
||||
requires Valid_Property_Schema<type_descriptor_schema_t<Object>>;
|
||||
requires std::same_as<typename type_descriptor_schema_t<Object>::object_type, Object>;
|
||||
};
|
||||
template <Property_Described_Object Object>
|
||||
const type_descriptor_schema_t<Object>& type_descriptor() {
|
||||
static const auto value = Type_Descriptor<Object>::get();
|
||||
return value;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
#pragma once
|
||||
#include "schema.hpp"
|
||||
#include <optional>
|
||||
#include <string_view>
|
||||
#include <type_traits>
|
||||
namespace structive {
|
||||
struct Validation_Error {
|
||||
std::string_view property_key;
|
||||
std::string_view code;
|
||||
};
|
||||
template <Valid_Property_Schema Schema, std::size_t Index, class Value>
|
||||
std::optional<Validation_Error> validate_property_value(const Schema& schema, const Value& value) {
|
||||
std::optional<Validation_Error> error;
|
||||
schema.template property<Index>().for_each_constraint([&](const auto& constraint_value) {
|
||||
if (!error && !static_cast<bool>(constraint_value.validate(value))) {
|
||||
using constraint_type = std::remove_cvref_t<decltype(constraint_value)>;
|
||||
error = Validation_Error{declared_property_key<typename Schema::template property_type<Index>>(), constraint_type::code.view()};
|
||||
}
|
||||
});
|
||||
return error;
|
||||
}
|
||||
template <auto Member, Valid_Property_Schema Schema, class Value>
|
||||
std::optional<Validation_Error> validate_property_value(const Schema& schema, const Value& value) requires Schema_Property_Member<Schema, Member> {
|
||||
return validate_property_value<Schema, schema_member_property_index_v<Schema, Member>>(schema, value);
|
||||
}
|
||||
template <Fixed_String Key, Valid_Property_Schema Schema, class Value>
|
||||
std::optional<Validation_Error> validate_property_key_value(const Schema& schema, const Value& value) requires Schema_Property_Key<Schema, Key> {
|
||||
return validate_property_value<Schema, schema_property_index_v<Schema, Key>>(schema, value);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user