计算属性自动检查

This commit is contained in:
2026-08-11 11:54:09 +08:00
parent 11407faf86
commit e91f134f16
14 changed files with 305 additions and 51 deletions
+3 -3
View File
@@ -180,14 +180,14 @@ This relies on the Structive managed contract. If another thread deliberately wr
A computed property is usually intrinsically read-only, but it may read writable dependencies.
```cpp
computed_property<Device, int>([](const auto& view) {
computed_property<Device, int>(depends_on<&Device::min_speed, &Device::max_speed>, [](const auto& view) {
return view.template get<&Device::max_speed>() - view.template get<&Device::min_speed>();
}, key<"speed_span">)
```
The computed value has no writable storage of its own. Its synchronized view protects the consistency domain of writable dependencies. Read-only stored dependencies can be read directly because they cannot change through the managed path.
The computed value has no writable storage of its own. Dependencies are explicit Schema facts, and the computed view can read only declared direct dependencies. Read-only stored dependencies can be read directly because they cannot change through the managed path.
Writable dependencies that must form one snapshot should be placed in the same synchronization group as the computed property.
Writable dependencies that must form one snapshot should be placed in the same synchronization group with each other. The computed property's read slot is derived from its dependency graph and must not be configured directly in synchronization rules.
## Managed access and raw access
+4 -3
View File
@@ -186,16 +186,17 @@ struct structive::Type_Descriptor<Device> {
Computed Property 通常自身不可写,但它可能依赖可写字段:
```cpp
computed_property<Device, int>([](const auto& view) {
computed_property<Device, int>(depends_on<&Device::min_speed, &Device::max_speed>, [](const auto& view) {
return view.template get<&Device::max_speed>() - view.template get<&Device::min_speed>();
}, key<"speed_span">)
```
Computed value 本身没有可写存储。它的 synchronized view 保护的是**可写依赖的一致性域**。
- dependency 是 Schema 的显式结构事实,computed view 只能读取已声明的直接依赖;
- read-only 存储依赖可以直接读取,不需要锁;
- writable 依赖如果需要同一快照,应与 computed property 放在同一个 synchronization group
- computed property 的同步语义不是“给只读值创建一个成员锁”,而是描述其依赖一致性边界
- writable 依赖如果需要同一快照,只需要彼此处于同一个 synchronization groupcomputed property 的读取 slot 会从 dependency 自动推导
- computed property 不再直接加入 synchronization rule,它的同步语义由 dependency graph 决定
## Managed Access 与 Raw Access
+43 -1
View File
@@ -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);
+121 -4
View File
@@ -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>
+2
View File
@@ -78,4 +78,6 @@ if(STRUCTIVE_BUILD_TESTS)
structive_expect_compile_failure(incompatible_constraint "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/incompatible_constraint.cpp")
structive_expect_compile_failure(foreign_sync_member "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/foreign_sync_member.cpp")
structive_expect_compile_failure(duplicate_attribute "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/duplicate_attribute.cpp")
structive_expect_compile_failure(missing_computed_dependency "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/missing_computed_dependency.cpp")
structive_expect_compile_failure(undeclared_computed_read "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/undeclared_computed_read.cpp")
endif()
@@ -0,0 +1,21 @@
#include <structive/property/property.hpp>
using namespace structive;
struct Device : Property_Object<Device> {
int value{};
int hidden{};
};
template <>
struct structive::Type_Descriptor<Device> {
static auto get() {
return object<Device>(
field<&Device::value>(key<"value">),
computed_property<Device, int>(depends_on<&Device::hidden>, [](const auto& view) {
return view.template get<&Device::hidden>();
}, key<"computed">)
);
}
};
int main() {
static_cast<void>(Type_Descriptor<Device>::get());
return 0;
}
@@ -0,0 +1,23 @@
#include <structive/property/property.hpp>
using namespace structive;
struct Device : Property_Object<Device> {
int left{};
int right{};
};
template <>
struct structive::Type_Descriptor<Device> {
static auto get() {
return object<Device>(
synchronization(sync_all_shared),
field<&Device::left>(key<"left">),
field<&Device::right>(key<"right">),
computed_property<Device, int>(depends_on<&Device::left>, [](const auto& view) {
return view.template get<&Device::right>();
}, key<"computed">)
);
}
};
int main() {
Device device;
return device.read<"computed">();
}
+36 -5
View File
@@ -47,6 +47,8 @@ struct structive::Type_Descriptor<Device> {
}
};
struct Computed_Device : Property_Object<Computed_Device> {
Computed_Device() = default;
explicit Computed_Device(Property_Synchronization synchronization) : Property_Object(std::move(synchronization)) {}
int min_speed{10};
int max_speed{100};
int fixed_offset{5};
@@ -55,13 +57,19 @@ template <>
struct structive::Type_Descriptor<Computed_Device> {
static auto get() {
return object<Computed_Device>(
synchronization(sync_all_independent, sync_group("speed", "min_speed", "max_speed", "speed_span")),
synchronization(sync_all_independent, sync_group("speed", "min_speed", "max_speed")),
field<&Computed_Device::min_speed>(key<"min_speed">),
field<&Computed_Device::max_speed>(key<"max_speed">),
field<&Computed_Device::fixed_offset>(key<"fixed_offset">, read_only),
computed_property<Computed_Device, int>([](const auto& view) {
computed_property<Computed_Device, int>(depends_on<&Computed_Device::min_speed, &Computed_Device::max_speed, &Computed_Device::fixed_offset>, [](const auto& view) {
return view.template get<"max_speed">() - view.template get<"min_speed">() + view.template get<"fixed_offset">();
}, key<"speed_span">)
}, key<"speed_span">),
computed_property<Computed_Device, int>(depends_on_keys<"speed_span">, [](const auto& view) {
return view.template get<"speed_span">() * 2;
}, key<"double_span">),
computed_property<Computed_Device, int>(depends_on<&Computed_Device::fixed_offset>, [](const auto& view) {
return view.template get<&Computed_Device::fixed_offset>() * 2;
}, key<"fixed_twice">)
);
}
};
@@ -84,10 +92,10 @@ template <>
struct structive::Type_Descriptor<Lockless_Device> {
static auto get() {
return object<Lockless_Device>(
synchronization(sync_all_independent, sync_group("sum", "left", "right", "sum")),
synchronization(sync_all_independent, sync_group("sum", "left", "right")),
field<&Lockless_Device::left>(key<"left">),
field<&Lockless_Device::right>(key<"right">),
computed_property<Lockless_Device, int>([](const auto& view) {
computed_property<Lockless_Device, int>(depends_on_keys<"left", "right">, [](const auto& view) {
return view.template get<&Lockless_Device::left>() + view.template get<&Lockless_Device::right>();
}, key<"sum">)
);
@@ -265,11 +273,34 @@ int main() {
++value_visits;
});
REQUIRE(value_visits == 5);
using Computed_Schema = type_descriptor_schema_t<Computed_Device>;
constexpr auto speed_span_index = schema_property_index_v<Computed_Schema, "speed_span">;
constexpr auto speed_span_dependencies = schema_property_dependency_indices<Computed_Schema, speed_span_index>();
static_assert(speed_span_dependencies.size() == 3);
static_assert(speed_span_dependencies[0] == schema_member_property_index_v<Computed_Schema, &Computed_Device::min_speed>);
static_assert(speed_span_dependencies[1] == schema_member_property_index_v<Computed_Schema, &Computed_Device::max_speed>);
static_assert(speed_span_dependencies[2] == schema_member_property_index_v<Computed_Schema, &Computed_Device::fixed_offset>);
Computed_Device computed;
REQUIRE(computed.lock_slot<&Computed_Device::fixed_offset>() == Resolved_Synchronization_View::unsynchronized_slot);
constexpr auto double_span_index = schema_property_index_v<Computed_Schema, "double_span">;
constexpr auto fixed_twice_index = schema_property_index_v<Computed_Schema, "fixed_twice">;
REQUIRE(computed.resolved_synchronization().slot(speed_span_index) == computed.lock_slot<&Computed_Device::min_speed>());
REQUIRE(computed.resolved_synchronization().slot(speed_span_index) == computed.lock_slot<&Computed_Device::max_speed>());
REQUIRE(computed.resolved_synchronization().slot(double_span_index) == computed.resolved_synchronization().slot(speed_span_index));
REQUIRE(computed.resolved_synchronization().slot(fixed_twice_index) == Resolved_Synchronization_View::unsynchronized_slot);
REQUIRE(computed.read<"speed_span">() == 95);
REQUIRE(computed.read<"double_span">() == 190);
REQUIRE(computed.read<"fixed_twice">() == 10);
computed.write<&Computed_Device::min_speed>(20);
REQUIRE(computed.read<"speed_span">() == 85);
REQUIRE(computed.read<"double_span">() == 170);
bool rejected_independent_dependencies = false;
try {
Computed_Device invalid{property_synchronization<Computed_Device>(synchronization(sync_all_independent))};
} catch (const std::invalid_argument&) {
rejected_independent_dependencies = true;
}
REQUIRE(rejected_independent_dependencies);
Lockless_Device lockless;
REQUIRE(lockless.read<&Lockless_Device::left>() == 1);
lockless.write<&Lockless_Device::right>(4);
+8 -4
View File
@@ -508,21 +508,25 @@ This acquires all writable managed lock domains; it does not perform validation
A synchronized computed property receives a read view:
```cpp
computed_property<Device, int>([](const auto& view) {
computed_property<Device, int>(depends_on<&Device::min_speed, &Device::max_speed>, [](const auto& view) {
return view.template get<&Device::max_speed>() - view.template get<&Device::min_speed>();
}, key<"speed_span">)
```
Writable dependencies that must form one snapshot should share the computed synchronization domain:
`depends_on<...>` makes dependencies explicit Schema facts. `depends_on_keys<"a", "b">` is available when key-based declaration is more appropriate. A computed view can read only its declared direct dependencies.
Writable dependencies that must form one snapshot should share a synchronization domain with each other:
```cpp
synchronization(
sync_all_independent,
sync_group("speed", "min_speed", "max_speed", "speed_span")
sync_group("speed", "min_speed", "max_speed")
)
```
A read-only stored dependency may be read through the computed view without joining a lock slot, because it has no managed writer.
Do not put the computed property itself in a synchronization rule. Its read slot is derived from the dependency graph; a schema or per-instance topology is rejected when synchronized dependencies do not resolve to one domain. Read-only stored dependencies need no lock slot.
`schema_property_dependency_indices<Schema, Index>()` exposes the direct dependency indices for schema consumers.
## 21. Trusted accessor properties
+8 -4
View File
@@ -500,21 +500,25 @@ device.with_all_writable_locked([&](auto& guard) {
Synchronized computed property 接收 read view
```cpp
computed_property<Device, int>([](const auto& view) {
computed_property<Device, int>(depends_on<&Device::min_speed, &Device::max_speed>, [](const auto& view) {
return view.template get<&Device::max_speed>() - view.template get<&Device::min_speed>();
}, key<"speed_span">)
```
如果 writable dependency 需要同一快照,应共享 computed synchronization domain
`depends_on<...>` 把 dependency 变成 Schema 的显式结构事实。也可以通过 `depends_on_keys<"a", "b">` 按 key 声明。Computed view 只能读取已声明的直接 dependency。
如果 writable dependency 需要同一快照,只需要让这些 writable dependency 共享 synchronization domain
```cpp
synchronization(
sync_all_independent,
sync_group("speed", "min_speed", "max_speed", "speed_span")
sync_group("speed", "min_speed", "max_speed")
)
```
Read-only stored dependency 可以直接通过 computed view 读取,不需要加入 lock slot,因为它没有 managed writer
Computed Property 自身不要加入 synchronization rule。它的读取 slot 根据 dependency graph 自动推导;如果需要同步的 dependency 不在同一个 domainSchema/实例 synchronization topology 会被拒绝。Read-only stored dependency 不需要 lock slot。
Schema 可通过 `schema_property_dependency_indices<Schema, Index>()` 读取某个 Property 的直接 dependency index。
## 21. Trusted Accessor Property
+2 -2
View File
@@ -213,9 +213,9 @@ A computed property may itself be read-only while depending on writable fields.
The computed property does not contain writable storage. However, its read may require a stable snapshot of mutable dependencies.
`Synchronized_Computed_Accessor` therefore uses a synchronized read view. Writable dependencies must share the computed consistency domain when the computed expression needs an atomic snapshot.
Dependencies must be explicit Schema facts. The `Synchronized_Computed_Accessor` read view can access only declared direct dependencies, and the computed property's read slot is derived from the dependency graph instead of requiring its key to be repeated in a synchronization group.
Read-only stored dependencies are safe to read directly through the synchronized view because they have no managed writer.
Writable dependencies that require one atomic snapshot must share one synchronization domain with each other. Read-only stored dependencies are safe to read directly through the synchronized view because they have no managed writer.
### Rule
+2 -2
View File
@@ -211,9 +211,9 @@ Computed Property 自身可以只读,但它可能依赖可写字段。
Computed value 本身没有可写存储,但读取它时可能需要 mutable dependency 的一致快照。
`Synchronized_Computed_Accessor` 因此使用 synchronized read view。需要原子一致性的 writable dependency 应和 computed property 处于同一个同步域
Dependency 必须显式进入 Schema。`Synchronized_Computed_Accessor` 的 read view 只能读取声明过的直接 dependencycomputed property 的读取 slot 从 dependency graph 推导,而不是由调用方再次把 computed key 写进 synchronization group
Read-only stored dependency 没有 managed writer,所以可以直接读取而无需锁。
需要原子一致性的 writable dependency 应彼此处于同一个同步域。Read-only stored dependency 没有 managed writer,所以可以直接读取而无需锁。
### 原则