计算属性自动检查
This commit is contained in:
@@ -1,9 +1,47 @@
|
||||
#pragma once
|
||||
#include "fixed_string.hpp"
|
||||
#include <concepts>
|
||||
#include <functional>
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
namespace structive {
|
||||
template <auto Member>
|
||||
struct Member_Property_Dependency {
|
||||
static constexpr auto member = Member;
|
||||
};
|
||||
template <Fixed_String Key>
|
||||
struct Key_Property_Dependency {
|
||||
static constexpr auto key = Key;
|
||||
};
|
||||
template <class... Dependencies>
|
||||
struct Property_Dependencies {
|
||||
using property_dependencies_tag = void;
|
||||
static constexpr std::size_t count = sizeof...(Dependencies);
|
||||
};
|
||||
template <class Type>
|
||||
concept Property_Dependencies_Type = requires {
|
||||
typename Type::property_dependencies_tag;
|
||||
{ Type::count } -> std::convertible_to<std::size_t>;
|
||||
};
|
||||
template <auto... Members>
|
||||
inline constexpr Property_Dependencies<Member_Property_Dependency<Members>...> depends_on{};
|
||||
template <Fixed_String... Keys>
|
||||
inline constexpr Property_Dependencies<Key_Property_Dependency<Keys>...> depends_on_keys{};
|
||||
using No_Property_Dependencies = Property_Dependencies<>;
|
||||
template <class Accessor, class = void>
|
||||
struct Accessor_Dependency_Model {
|
||||
using type = No_Property_Dependencies;
|
||||
static constexpr bool declared = false;
|
||||
};
|
||||
template <class Accessor>
|
||||
struct Accessor_Dependency_Model<Accessor, std::void_t<typename Accessor::dependency_spec>> {
|
||||
using type = typename Accessor::dependency_spec;
|
||||
static constexpr bool declared = true;
|
||||
};
|
||||
template <class Accessor>
|
||||
using accessor_dependency_spec_t = typename Accessor_Dependency_Model<Accessor>::type;
|
||||
template <class Accessor>
|
||||
inline constexpr bool accessor_declares_dependencies_v = Accessor_Dependency_Model<Accessor>::declared;
|
||||
template <class>
|
||||
struct Member_Pointer_Traits;
|
||||
template <class Object, class Value>
|
||||
@@ -55,6 +93,7 @@ struct Member_Accessor {
|
||||
static constexpr bool writable = !std::is_const_v<value_type>;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
static constexpr bool trusted_object_access = false;
|
||||
using dependency_spec = No_Property_Dependencies;
|
||||
static constexpr auto member = Member;
|
||||
constexpr const value_type& read(const object_type& object) const {
|
||||
return object.*Member;
|
||||
@@ -67,7 +106,7 @@ struct Member_Accessor {
|
||||
object.*Member = std::forward<Value>(value);
|
||||
}
|
||||
};
|
||||
template <class Object, class Value, class Function>
|
||||
template <class Object, class Value, class Function, Property_Dependencies_Type Dependencies>
|
||||
struct Synchronized_Computed_Accessor {
|
||||
using object_type = Object;
|
||||
using value_type = Value;
|
||||
@@ -76,6 +115,7 @@ struct Synchronized_Computed_Accessor {
|
||||
static constexpr bool writable = false;
|
||||
static constexpr bool synchronized_view_read = true;
|
||||
static constexpr bool trusted_object_access = false;
|
||||
using dependency_spec = Dependencies;
|
||||
[[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&>> {
|
||||
@@ -91,6 +131,7 @@ struct Trusted_Computed_Accessor {
|
||||
static constexpr bool writable = false;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
static constexpr bool trusted_object_access = true;
|
||||
using dependency_spec = No_Property_Dependencies;
|
||||
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);
|
||||
}
|
||||
@@ -104,6 +145,7 @@ struct Trusted_Getter_Setter_Accessor {
|
||||
static constexpr bool writable = true;
|
||||
static constexpr bool synchronized_view_read = false;
|
||||
static constexpr bool trusted_object_access = true;
|
||||
using dependency_spec = No_Property_Dependencies;
|
||||
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);
|
||||
}
|
||||
|
||||
@@ -24,6 +24,7 @@ struct Property_Descriptor {
|
||||
using object_type = typename Accessor::object_type;
|
||||
using value_type = typename Accessor::value_type;
|
||||
using storage_identity = typename Accessor::storage_identity;
|
||||
using dependency_spec = accessor_dependency_spec_t<Accessor>;
|
||||
using attribute_types = Type_List<Attributes...>;
|
||||
using capability_type = find_attribute_in_list_t<Capability_Category, attribute_types>;
|
||||
static constexpr Property_Capability intrinsic_capability = [] {
|
||||
@@ -43,6 +44,8 @@ struct Property_Descriptor {
|
||||
static constexpr bool writable = Accessor::writable && (intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write);
|
||||
static constexpr bool synchronized_view_read = Accessor::synchronized_view_read;
|
||||
static constexpr bool trusted_object_access = Accessor::trusted_object_access;
|
||||
static constexpr bool declares_dependencies = accessor_declares_dependencies_v<Accessor>;
|
||||
static constexpr std::size_t dependency_count = dependency_spec::count;
|
||||
static constexpr bool runtime_copy_writable = writable && requires(const Accessor& accessor, object_type& object, const value_type& candidate) { accessor.write(object, candidate); };
|
||||
static_assert(unique_single_value_categories<Attributes...>());
|
||||
static_assert(!((intrinsic_capability == Property_Capability::read || intrinsic_capability == Property_Capability::read_write) && !Accessor::readable), "property capability requests read from a non-readable accessor");
|
||||
@@ -113,9 +116,10 @@ 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>>;
|
||||
template <class Object, class Value, Property_Dependencies_Type Dependencies, class Function, class... Attributes>
|
||||
constexpr auto computed_property(Dependencies, Function&& function, Attributes&&... attributes) {
|
||||
static_assert(Dependencies::count > 0, "computed property must declare at least one dependency");
|
||||
using accessor = Synchronized_Computed_Accessor<Object, Value, std::decay_t<Function>, Dependencies>;
|
||||
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>
|
||||
|
||||
@@ -331,37 +331,42 @@ private:
|
||||
const auto& descriptor = type_descriptor<Derived>().template property<Index>();
|
||||
descriptor.accessor.write(static_cast<Derived&>(*this), std::forward<Value>(value));
|
||||
}
|
||||
template <std::size_t Property_Index>
|
||||
class Single_Read_View {
|
||||
const Property_Object* owner_{};
|
||||
std::size_t property_index_{};
|
||||
std::size_t lock_slot_{};
|
||||
template <std::size_t Index>
|
||||
decltype(auto) get_index() const {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
using Owner_Property = typename Schema::template property_type<Property_Index>;
|
||||
using Property = typename Schema::template property_type<Index>;
|
||||
if constexpr (Owner_Property::declares_dependencies) {
|
||||
static_assert(schema_property_depends_on_index_v<Schema, Property_Index, Index>, "computed property reads an undeclared dependency");
|
||||
} else if constexpr (Property::writable || Property::accessor_type::synchronized_view_read) {
|
||||
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");
|
||||
}
|
||||
}
|
||||
if constexpr (Property::accessor_type::synchronized_view_read) {
|
||||
Single_Read_View<Index> nested{*owner_, lock_slot_};
|
||||
return owner_->template read_unlocked<Index>(nested);
|
||||
} else {
|
||||
return owner_->template read_unlocked<Index>(*this);
|
||||
}
|
||||
}
|
||||
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) {}
|
||||
Single_Read_View(const Property_Object& owner, std::size_t lock_slot) : owner_(&owner), lock_slot_(lock_slot) {}
|
||||
template <auto Member>
|
||||
decltype(auto) get() const requires Schema_Readable_Property_Member<type_descriptor_schema_t<Derived>, Member> {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_member_property_index_v<Schema, Member>;
|
||||
using Property = typename Schema::template property_type<index>;
|
||||
if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) {
|
||||
return owner_->template read_unlocked<index>(*this);
|
||||
}
|
||||
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);
|
||||
return get_index<index>();
|
||||
}
|
||||
template <Fixed_String Key>
|
||||
decltype(auto) get() const requires Schema_Readable_Property_Key<type_descriptor_schema_t<Derived>, Key> {
|
||||
using Schema = type_descriptor_schema_t<Derived>;
|
||||
constexpr auto index = schema_property_index_v<Schema, Key>;
|
||||
using Property = typename Schema::template property_type<index>;
|
||||
if constexpr (!Property::writable && !Property::accessor_type::synchronized_view_read) {
|
||||
return owner_->template read_unlocked<index>(*this);
|
||||
}
|
||||
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);
|
||||
return get_index<index>();
|
||||
}
|
||||
};
|
||||
template <std::size_t Index>
|
||||
@@ -374,7 +379,7 @@ private:
|
||||
return Value(read_unlocked<Index>(*this));
|
||||
}
|
||||
auto lock_slot = slot(Index);
|
||||
Single_Read_View view{*this, Index, lock_slot};
|
||||
Single_Read_View<Index> view{*this, lock_slot};
|
||||
if constexpr (!uses_real_mutexes) {
|
||||
return Value(read_unlocked<Index>(view));
|
||||
}
|
||||
@@ -671,7 +676,7 @@ private:
|
||||
}
|
||||
} else {
|
||||
auto lock_slot = slot(property_index);
|
||||
Single_Read_View view{*this, property_index, lock_slot};
|
||||
Single_Read_View<property_index> view{*this, lock_slot};
|
||||
auto emit = [&] {
|
||||
if constexpr (std::is_reference_v<decltype(read_unlocked<property_index>(view))>) {
|
||||
auto&& value = read_unlocked<property_index>(view);
|
||||
|
||||
@@ -203,6 +203,74 @@ template <class Schema, auto Member>
|
||||
concept Schema_Property_Member = Property_Schema<Schema> && std::is_member_object_pointer_v<decltype(Member)> && schema_member_property_index_v<Schema, Member> < Schema::property_count;
|
||||
template <class Schema, std::size_t Index>
|
||||
concept Schema_Property_Index = Property_Schema<Schema> && Index < Schema::property_count;
|
||||
template <class Schema, class Dependency>
|
||||
struct Schema_Dependency_Index;
|
||||
template <Property_Schema Schema, auto Member>
|
||||
struct Schema_Dependency_Index<Schema, Member_Property_Dependency<Member>> {
|
||||
static constexpr std::size_t value = schema_member_property_index_v<Schema, Member>;
|
||||
static_assert(value < Schema::property_count, "computed dependency member is not registered in this property schema");
|
||||
static_assert(Schema::template property_type<value>::readable, "computed dependency must be readable");
|
||||
};
|
||||
template <Property_Schema Schema, Fixed_String Key>
|
||||
struct Schema_Dependency_Index<Schema, Key_Property_Dependency<Key>> {
|
||||
static constexpr std::size_t value = schema_property_index_v<Schema, Key>;
|
||||
static_assert(value < Schema::property_count, "computed dependency key is not registered in this property schema");
|
||||
static_assert(Schema::template property_type<value>::readable, "computed dependency must be readable");
|
||||
};
|
||||
template <std::size_t Size>
|
||||
consteval bool unique_dependency_indices(const std::array<std::size_t, Size>& indices) {
|
||||
for (std::size_t left = 0; left < Size; ++left) {
|
||||
for (std::size_t right = left + 1; right < Size; ++right) {
|
||||
if (indices[left] == indices[right]) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
template <Property_Schema Schema, class Dependencies>
|
||||
struct Schema_Dependency_Indices;
|
||||
template <Property_Schema Schema, class... Dependencies>
|
||||
struct Schema_Dependency_Indices<Schema, Property_Dependencies<Dependencies...>> {
|
||||
static constexpr std::array<std::size_t, sizeof...(Dependencies)> value{Schema_Dependency_Index<Schema, Dependencies>::value...};
|
||||
static_assert(unique_dependency_indices(value), "computed property declares the same dependency more than once");
|
||||
};
|
||||
template <Property_Schema Schema, std::size_t Index>
|
||||
consteval auto schema_property_dependency_indices() requires Schema_Property_Index<Schema, Index> {
|
||||
using dependency_spec = typename Schema::template property_type<Index>::dependency_spec;
|
||||
constexpr auto indices = Schema_Dependency_Indices<Schema, dependency_spec>::value;
|
||||
for (auto dependency : indices) {
|
||||
if (dependency == Index) {
|
||||
throw "computed property cannot depend on itself";
|
||||
}
|
||||
}
|
||||
return indices;
|
||||
}
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr std::size_t schema_property_dependency_count_v = schema_property_dependency_indices<Schema, Index>().size();
|
||||
template <class Schema, std::size_t Property_Index, std::size_t Dependency_Index>
|
||||
inline constexpr bool schema_property_depends_on_index_v = [] {
|
||||
constexpr auto dependencies = schema_property_dependency_indices<Schema, Property_Index>();
|
||||
for (auto index : dependencies) {
|
||||
if (index == Dependency_Index) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}();
|
||||
template <Property_Schema Schema>
|
||||
consteval auto schema_property_dependency_matrix() {
|
||||
std::array<std::array<bool, Schema::property_count>, Schema::property_count> matrix{};
|
||||
[&]<std::size_t... Index>(std::index_sequence<Index...>) {
|
||||
([&] {
|
||||
constexpr auto dependencies = schema_property_dependency_indices<Schema, Index>();
|
||||
for (auto dependency : dependencies) {
|
||||
matrix[Index][dependency] = true;
|
||||
}
|
||||
}(), ...);
|
||||
}(std::make_index_sequence<Schema::property_count>{});
|
||||
return matrix;
|
||||
}
|
||||
template <class Schema, std::size_t Index, class Category, class Fallback>
|
||||
struct Effective_Attribute {
|
||||
private:
|
||||
@@ -237,6 +305,10 @@ concept Valid_Property_Schema = Property_Schema<Schema>;
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr bool property_requires_synchronization_v = Schema::template property_type<Index>::writable || Schema::template property_type<Index>::synchronized_view_read;
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr bool property_has_derived_synchronization_v = Schema::template property_type<Index>::synchronized_view_read && !Schema::template property_type<Index>::writable && Schema::template property_type<Index>::declares_dependencies;
|
||||
template <class Schema, std::size_t Index>
|
||||
inline constexpr bool property_owns_synchronization_domain_v = Schema::template property_type<Index>::writable || (Schema::template property_type<Index>::synchronized_view_read && !property_has_derived_synchronization_v<Schema, Index>);
|
||||
template <class Schema, std::size_t Index>
|
||||
concept Schema_Readable_Property_Index = Schema_Property_Index<Schema, Index> && Schema::template property_type<Index>::readable;
|
||||
template <class Schema, std::size_t Index>
|
||||
concept Schema_Writable_Property_Index = Schema_Property_Index<Schema, Index> && Schema::template property_type<Index>::writable;
|
||||
@@ -252,6 +324,14 @@ template <Valid_Property_Schema Schema>
|
||||
Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_plan(const Schema&, const Synchronization_Plan& plan) {
|
||||
using resolved_type = Resolved_Synchronization_Plan<Schema::property_count>;
|
||||
constexpr auto unsynchronized_slot = resolved_type::unsynchronized_slot;
|
||||
constexpr auto owns_synchronization_domain = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
||||
return std::array<bool, Schema::property_count>{property_owns_synchronization_domain_v<Schema, Index>...};
|
||||
}(std::make_index_sequence<Schema::property_count>{});
|
||||
constexpr auto derived_synchronization = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
||||
return std::array<bool, Schema::property_count>{property_has_derived_synchronization_v<Schema, Index>...};
|
||||
}(std::make_index_sequence<Schema::property_count>{});
|
||||
constexpr auto dependencies = schema_property_dependency_matrix<Schema>();
|
||||
constexpr auto keys = schema_property_keys<Schema>();
|
||||
std::array<std::size_t, Schema::property_count> logical{};
|
||||
std::array<bool, Schema::property_count> explicitly_configured{};
|
||||
std::size_t next_token = Schema::property_count + 1;
|
||||
@@ -272,6 +352,9 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
|
||||
return *index;
|
||||
};
|
||||
auto mark = [&](std::size_t index, std::string_view key_value) {
|
||||
if (derived_synchronization[index]) {
|
||||
throw std::invalid_argument("Synchronization of computed property is derived from dependencies: " + std::string(key_value));
|
||||
}
|
||||
if (explicitly_configured[index]) {
|
||||
throw std::invalid_argument("Synchronization plan configures property more than once: " + std::string(key_value));
|
||||
}
|
||||
@@ -299,11 +382,8 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
|
||||
logical[index] = token;
|
||||
}
|
||||
}
|
||||
constexpr auto requires_synchronization = []<std::size_t... Index>(std::index_sequence<Index...>) {
|
||||
return std::array<bool, Schema::property_count>{property_requires_synchronization_v<Schema, Index>...};
|
||||
}(std::make_index_sequence<Schema::property_count>{});
|
||||
for (std::size_t index = 0; index < Schema::property_count; ++index) {
|
||||
if (!requires_synchronization[index]) {
|
||||
if (!owns_synchronization_domain[index]) {
|
||||
logical[index] = unsynchronized_slot;
|
||||
}
|
||||
}
|
||||
@@ -326,6 +406,43 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
|
||||
resolved.lock_slots[index] = it->second;
|
||||
}
|
||||
}
|
||||
if constexpr (Schema::property_count > 0) {
|
||||
std::array<unsigned char, Schema::property_count> dependency_state{};
|
||||
auto resolve_derived_slot = [&](auto&& self, std::size_t index) -> std::size_t {
|
||||
if (!derived_synchronization[index]) {
|
||||
return resolved.lock_slots[index];
|
||||
}
|
||||
if (dependency_state[index] == 2) {
|
||||
return resolved.lock_slots[index];
|
||||
}
|
||||
if (dependency_state[index] == 1) {
|
||||
throw std::invalid_argument("Computed property dependency cycle: " + std::string(keys[index]));
|
||||
}
|
||||
dependency_state[index] = 1;
|
||||
bool has_dependency_domain = false;
|
||||
std::size_t dependency_slot = unsynchronized_slot;
|
||||
for (std::size_t dependency = 0; dependency < Schema::property_count; ++dependency) {
|
||||
if (!dependencies[index][dependency] || (!owns_synchronization_domain[dependency] && !derived_synchronization[dependency])) {
|
||||
continue;
|
||||
}
|
||||
auto current_slot = derived_synchronization[dependency] ? self(self, dependency) : resolved.lock_slots[dependency];
|
||||
if (!has_dependency_domain) {
|
||||
dependency_slot = current_slot;
|
||||
has_dependency_domain = true;
|
||||
} else if (dependency_slot != current_slot) {
|
||||
throw std::invalid_argument("Computed property dependencies do not share one synchronization domain: " + std::string(keys[index]));
|
||||
}
|
||||
}
|
||||
resolved.lock_slots[index] = has_dependency_domain ? dependency_slot : unsynchronized_slot;
|
||||
dependency_state[index] = 2;
|
||||
return resolved.lock_slots[index];
|
||||
};
|
||||
for (std::size_t index = 0; index < Schema::property_count; ++index) {
|
||||
if (derived_synchronization[index]) {
|
||||
resolve_derived_slot(resolve_derived_slot, index);
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolved;
|
||||
}
|
||||
template <Property_Schema Schema, auto Member>
|
||||
|
||||
Reference in New Issue
Block a user