架构优化

This commit is contained in:
2026-08-11 12:40:28 +08:00
parent e91f134f16
commit 8d4714f691
17 changed files with 210 additions and 107 deletions
+4 -4
View File
@@ -187,7 +187,7 @@ computed_property<Device, int>(depends_on<&Device::min_speed, &Device::max_speed
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. 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 with each other. The computed property's read slot is derived from its dependency graph and must not be configured directly in synchronization rules. 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. Dependency cycles are rejected when the Schema is formed. `depends_on<>` is a valid explicit zero-dependency declaration and produces an unsynchronized computed read.
## Managed access and raw access ## Managed access and raw access
@@ -212,9 +212,9 @@ The default synchronization topology is resolved once per type. Instances do not
`No_Lock_Policy` removes real mutex storage entirely. `No_Lock_Policy` removes real mutex storage entirely.
## Unified Attribute model ## Unified property metadata model
Core and extensions use one Attribute protocol. A property descriptor has one metadata store containing Attributes and Constraints. Core and extensions use one Attribute protocol for descriptive metadata, while Constraints keep their validation protocol.
```cpp ```cpp
struct Label_Category {}; struct Label_Category {};
@@ -236,7 +236,7 @@ field<&Device::temperature>(
) )
``` ```
Core stores extension metadata but does not interpret extension-owned categories. Core stores extension metadata but does not interpret extension-owned categories. `for_each_metadata()` traverses all property metadata, `for_each_attribute()` traverses only Attributes, and `for_each_constraint()` traverses only Constraints.
## Validation is explicit ## Validation is explicit
+6 -4
View File
@@ -196,7 +196,9 @@ Computed value 本身没有可写存储。它的 synchronized view 保护的是*
- dependency 是 Schema 的显式结构事实,computed view 只能读取已声明的直接依赖; - dependency 是 Schema 的显式结构事实,computed view 只能读取已声明的直接依赖;
- read-only 存储依赖可以直接读取,不需要锁; - read-only 存储依赖可以直接读取,不需要锁;
- writable 依赖如果需要同一快照,只需要彼此处于同一个 synchronization groupcomputed property 的读取 slot 会从 dependency 自动推导; - writable 依赖如果需要同一快照,只需要彼此处于同一个 synchronization groupcomputed property 的读取 slot 会从 dependency 自动推导;
- computed property 不再直接加入 synchronization rule,它的同步语义由 dependency graph 决定 - computed property 不再直接加入 synchronization rule,它的同步语义由 dependency graph 决定
- dependency graph 必须是 DAGSchema 形成时会在编译期拒绝 cycle;
- `depends_on<>` 是合法的显式零依赖声明,此类 computed read 固定为 unsynchronized。
## Managed Access 与 Raw Access ## Managed Access 与 Raw Access
@@ -219,9 +221,9 @@ Managed path 使用 Schema 描述的 intrinsic capability 和 synchronization。
`No_Lock_Policy` 完全不保存真实 mutex。 `No_Lock_Policy` 完全不保存真实 mutex。
## 统一 Attribute 模型 ## 统一 Property Metadata 模型
Core 和 Extension 使用同一套 Attribute 协议: Property Descriptor 只有一份 metadata storage,其中可以同时保存 Attribute 和 Constraint。Core 与 Extension 的描述性 metadata 共用 Attribute 协议,Constraint 保持自己的 validation 协议:
```cpp ```cpp
struct Label_Category {}; struct Label_Category {};
@@ -234,7 +236,7 @@ struct Label_Attribute {
}; };
``` ```
Extension metadata 可以直接挂在 Property 上: Extension metadata 可以直接挂在 Property 上`for_each_metadata()` 遍历全部 metadata`for_each_attribute()` 只遍历 Attribute`for_each_constraint()` 只遍历 Constraint
```cpp ```cpp
field<&Device::temperature>( field<&Device::temperature>(
+2 -14
View File
@@ -28,20 +28,6 @@ inline constexpr Property_Dependencies<Member_Property_Dependency<Members>...> d
template <Fixed_String... Keys> template <Fixed_String... Keys>
inline constexpr Property_Dependencies<Key_Property_Dependency<Keys>...> depends_on_keys{}; inline constexpr Property_Dependencies<Key_Property_Dependency<Keys>...> depends_on_keys{};
using No_Property_Dependencies = Property_Dependencies<>; 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> template <class>
struct Member_Pointer_Traits; struct Member_Pointer_Traits;
template <class Object, class Value> template <class Object, class Value>
@@ -159,6 +145,8 @@ concept Property_Accessor = requires {
typename Accessor::object_type; typename Accessor::object_type;
typename Accessor::value_type; typename Accessor::value_type;
typename Accessor::storage_identity; typename Accessor::storage_identity;
typename Accessor::dependency_spec;
requires Property_Dependencies_Type<typename Accessor::dependency_spec>;
{ Accessor::readable } -> std::convertible_to<bool>; { Accessor::readable } -> std::convertible_to<bool>;
{ Accessor::writable } -> std::convertible_to<bool>; { Accessor::writable } -> std::convertible_to<bool>;
{ Accessor::synchronized_view_read } -> std::convertible_to<bool>; { Accessor::synchronized_view_read } -> std::convertible_to<bool>;
@@ -106,10 +106,18 @@ constexpr auto constraint(Function&& function) {
return Custom_Constraint<Code, std::decay_t<Function>>{std::forward<Function>(function)}; return Custom_Constraint<Code, std::decay_t<Function>>{std::forward<Function>(function)};
} }
template <class Attribute> template <class Attribute>
concept Property_Attribute = requires {
typename Attribute::attribute_category;
{ Attribute::single_valued } -> std::convertible_to<bool>;
{ Attribute::inheritable } -> std::convertible_to<bool>;
};
template <class Attribute>
concept Property_Constraint = requires { concept Property_Constraint = requires {
typename Attribute::property_constraint_tag; typename Attribute::property_constraint_tag;
{ Attribute::code.view() } -> std::convertible_to<std::string_view>; { Attribute::code.view() } -> std::convertible_to<std::string_view>;
}; };
template <class Metadata>
concept Property_Metadata = Property_Attribute<Metadata> || Property_Constraint<Metadata>;
template <class Attribute, class Value> template <class Attribute, class Value>
concept Property_Constraint_For = Property_Constraint<Attribute> && requires(const Attribute& attribute, const Value& value) { concept Property_Constraint_For = Property_Constraint<Attribute> && requires(const Attribute& attribute, const Value& value) {
{ attribute.validate(value) } -> std::convertible_to<bool>; { attribute.validate(value) } -> std::convertible_to<bool>;
+46 -36
View File
@@ -8,25 +8,25 @@
#include <type_traits> #include <type_traits>
#include <utility> #include <utility>
namespace structive { namespace structive {
template <class Value, class... Attributes> template <class Value, class... Metadata>
consteval bool constraints_compatible() { consteval bool constraints_compatible() {
return (([] { return (([] {
if constexpr (Property_Constraint<Attributes>) { if constexpr (Property_Constraint<Metadata>) {
return Property_Constraint_For<Attributes, Value>; return Property_Constraint_For<Metadata, Value>;
} }
return true; return true;
}()) && ...); }()) && ...);
} }
template <Property_Accessor Accessor, class... Attributes> template <Property_Accessor Accessor, class... Metadata>
struct Property_Descriptor { struct Property_Descriptor {
using property_descriptor_tag = void; using property_descriptor_tag = void;
using accessor_type = Accessor; using accessor_type = Accessor;
using object_type = typename Accessor::object_type; using object_type = typename Accessor::object_type;
using value_type = typename Accessor::value_type; using value_type = typename Accessor::value_type;
using storage_identity = typename Accessor::storage_identity; using storage_identity = typename Accessor::storage_identity;
using dependency_spec = accessor_dependency_spec_t<Accessor>; using dependency_spec = typename Accessor::dependency_spec;
using attribute_types = Type_List<Attributes...>; using metadata_types = Type_List<Metadata...>;
using capability_type = find_attribute_in_list_t<Capability_Category, attribute_types>; using capability_type = find_attribute_in_list_t<Capability_Category, metadata_types>;
static constexpr Property_Capability intrinsic_capability = [] { static constexpr Property_Capability intrinsic_capability = [] {
if constexpr (!std::same_as<capability_type, void>) { if constexpr (!std::same_as<capability_type, void>) {
return capability_type::value; return capability_type::value;
@@ -44,48 +44,59 @@ struct Property_Descriptor {
static constexpr bool writable = Accessor::writable && (intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write); 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 synchronized_view_read = Accessor::synchronized_view_read;
static constexpr bool trusted_object_access = Accessor::trusted_object_access; 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 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 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((Property_Metadata<Metadata> && ...), "property metadata must be an Attribute or Constraint");
static_assert(unique_single_value_categories<Metadata...>());
static_assert(!((intrinsic_capability == Property_Capability::read || intrinsic_capability == Property_Capability::read_write) && !Accessor::readable), "property capability requests read from a non-readable accessor"); static_assert(!((intrinsic_capability == Property_Capability::read || intrinsic_capability == Property_Capability::read_write) && !Accessor::readable), "property capability requests read from a non-readable accessor");
static_assert(!((intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write) && !Accessor::writable), "property capability requests write from a non-writable accessor"); static_assert(!((intrinsic_capability == Property_Capability::write || intrinsic_capability == Property_Capability::read_write) && !Accessor::writable), "property capability requests write from a non-writable accessor");
using key_type = find_attribute_in_list_t<Key_Category, attribute_types>; using key_type = find_attribute_in_list_t<Key_Category, metadata_types>;
static_assert(!std::same_as<key_type, void>); static_assert(!std::same_as<key_type, void>);
static_assert(key_type::value.view().size() > 0); static_assert(key_type::value.view().size() > 0);
static_assert(constraints_compatible<value_type, Attributes...>()); static_assert(constraints_compatible<value_type, Metadata...>());
[[no_unique_address]] Accessor accessor{}; [[no_unique_address]] Accessor accessor{};
std::tuple<Attributes...> attributes; std::tuple<Metadata...> metadata;
template <class Category> template <class Category>
using attribute_type = find_attribute_in_list_t<Category, attribute_types>; using attribute_type = find_attribute_in_list_t<Category, metadata_types>;
template <class Category> template <class Category>
static constexpr std::size_t attribute_count = count_attribute_category<Category, Attributes...>(); static constexpr std::size_t attribute_count = count_attribute_category<Category, Metadata...>();
template <class Category> template <class Category>
static constexpr bool has_attribute = attribute_count<Category> != 0; static constexpr bool has_attribute = attribute_count<Category> != 0;
template <class Category> template <class Category>
constexpr decltype(auto) attribute() const requires (attribute_count<Category> == 1) { constexpr decltype(auto) attribute() const requires (attribute_count<Category> == 1) {
using target = attribute_type<Category>; using target = attribute_type<Category>;
return std::get<target>(attributes); return std::get<target>(metadata);
} }
constexpr std::string_view key() const noexcept { constexpr std::string_view key() const noexcept {
return key_type::value.view(); return key_type::value.view();
} }
template <class Function> template <class Function>
constexpr void for_each_attribute(Function&& function) const { constexpr void for_each_metadata(Function&& function) const {
std::apply([&](const auto&... values) { std::apply([&](const auto&... values) {
(function(values), ...); (function(values), ...);
}, attributes); }, metadata);
}
template <class Function>
constexpr void for_each_attribute(Function&& function) const {
std::apply([&](const auto&... values) {
([&] {
using type = std::remove_cvref_t<decltype(values)>;
if constexpr (Property_Attribute<type>) {
function(values);
}
}(), ...);
}, metadata);
} }
template <class Category, class Function> template <class Category, class Function>
constexpr void for_each_attribute(Function&& function) const { constexpr void for_each_attribute(Function&& function) const {
std::apply([&](const auto&... values) { std::apply([&](const auto&... values) {
([&] { ([&] {
using type = std::remove_cvref_t<decltype(values)>; using type = std::remove_cvref_t<decltype(values)>;
if constexpr (std::same_as<attribute_category_of_t<type>, Category>) { if constexpr (Property_Attribute<type> && std::same_as<attribute_category_of_t<type>, Category>) {
function(values); function(values);
} }
}(), ...); }(), ...);
}, attributes); }, metadata);
} }
template <class Function> template <class Function>
constexpr void for_each_constraint(Function&& function) const { constexpr void for_each_constraint(Function&& function) const {
@@ -96,7 +107,7 @@ struct Property_Descriptor {
function(values); function(values);
} }
}(), ...); }(), ...);
}, attributes); }, metadata);
} }
}; };
template <class Type> template <class Type>
@@ -107,29 +118,28 @@ concept Property_Descriptor_Type = requires {
typename Type::accessor_type; typename Type::accessor_type;
typename Type::storage_identity; typename Type::storage_identity;
}; };
template <auto Member, class... Attributes> template <auto Member, class... Metadata>
constexpr auto property(Attributes&&... attributes) { constexpr auto property(Metadata&&... metadata) {
using accessor = Member_Accessor<Member>; using accessor = Member_Accessor<Member>;
return Property_Descriptor<accessor, std::decay_t<Attributes>...>{{}, {std::forward<Attributes>(attributes)...}}; return Property_Descriptor<accessor, std::decay_t<Metadata>...>{{}, {std::forward<Metadata>(metadata)...}};
} }
template <auto Member, class... Attributes> template <auto Member, class... Metadata>
constexpr auto field(Attributes&&... attributes) { constexpr auto field(Metadata&&... metadata) {
return property<Member>(std::forward<Attributes>(attributes)...); return property<Member>(std::forward<Metadata>(metadata)...);
} }
template <class Object, class Value, Property_Dependencies_Type Dependencies, class Function, class... Attributes> template <class Object, class Value, Property_Dependencies_Type Dependencies, class Function, class... Metadata>
constexpr auto computed_property(Dependencies, Function&& function, Attributes&&... attributes) { constexpr auto computed_property(Dependencies, Function&& function, Metadata&&... metadata) {
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>; 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)...}}; return Property_Descriptor<accessor, std::decay_t<Metadata>...>{accessor{std::forward<Function>(function)}, {std::forward<Metadata>(metadata)...}};
} }
template <auto Getter, class... Attributes> requires Trusted_Getter_Function<Getter> template <auto Getter, class... Metadata> requires Trusted_Getter_Function<Getter>
constexpr auto trusted_computed_property(Attributes&&... attributes) { constexpr auto trusted_computed_property(Metadata&&... metadata) {
using accessor = Trusted_Computed_Accessor<Getter>; using accessor = Trusted_Computed_Accessor<Getter>;
return Property_Descriptor<accessor, std::decay_t<Attributes>...>{{}, {std::forward<Attributes>(attributes)...}}; return Property_Descriptor<accessor, std::decay_t<Metadata>...>{{}, {std::forward<Metadata>(metadata)...}};
} }
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>> template <auto Getter, auto Setter, class... Metadata> 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) { constexpr auto trusted_accessor_property(Metadata&&... metadata) {
using accessor = Trusted_Getter_Setter_Accessor<Getter, Setter>; using accessor = Trusted_Getter_Setter_Accessor<Getter, Setter>;
return Property_Descriptor<accessor, std::decay_t<Attributes>...>{{}, {std::forward<Attributes>(attributes)...}}; return Property_Descriptor<accessor, std::decay_t<Metadata>...>{{}, {std::forward<Metadata>(metadata)...}};
} }
} }
+2 -2
View File
@@ -55,7 +55,7 @@ struct Find_Attribute_By_Category<Category> {
}; };
template <class Category, class Head, class... Tail> template <class Category, class Head, class... Tail>
struct Find_Attribute_By_Category<Category, Head, 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>; using type = std::conditional_t<!std::same_as<attribute_category_of_t<Head>, void> && std::same_as<Category, attribute_category_of_t<Head>>, Head, typename Find_Attribute_By_Category<Category, Tail...>::type>;
}; };
template <class Category, class List> template <class Category, class List>
struct Find_Attribute_In_List; struct Find_Attribute_In_List;
@@ -67,7 +67,7 @@ template <class Category, class List>
using find_attribute_in_list_t = typename Find_Attribute_In_List<Category, List>::type; using find_attribute_in_list_t = typename Find_Attribute_In_List<Category, List>::type;
template <class Category, class... Attributes> template <class Category, class... Attributes>
consteval std::size_t count_attribute_category() { consteval std::size_t count_attribute_category() {
return (std::size_t{0} + ... + (std::same_as<attribute_category_of_t<Attributes>, Category> ? 1u : 0u)); return (std::size_t{0} + ... + (!std::same_as<attribute_category_of_t<Attributes>, void> && std::same_as<attribute_category_of_t<Attributes>, Category> ? 1u : 0u));
} }
template <class Attribute, class... Attributes> template <class Attribute, class... Attributes>
consteval bool unique_single_value_category_for() { consteval bool unique_single_value_category_for() {
@@ -338,15 +338,8 @@ private:
template <std::size_t Index> template <std::size_t Index>
decltype(auto) get_index() const { decltype(auto) get_index() const {
using Schema = type_descriptor_schema_t<Derived>; 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>; 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");
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) { if constexpr (Property::accessor_type::synchronized_view_read) {
Single_Read_View<Index> nested{*owner_, lock_slot_}; Single_Read_View<Index> nested{*owner_, lock_slot_};
return owner_->template read_unlocked<Index>(nested); return owner_->template read_unlocked<Index>(nested);
+43 -22
View File
@@ -238,13 +238,7 @@ struct Schema_Dependency_Indices<Schema, Property_Dependencies<Dependencies...>>
template <Property_Schema Schema, std::size_t Index> template <Property_Schema Schema, std::size_t Index>
consteval auto schema_property_dependency_indices() requires Schema_Property_Index<Schema, Index> { consteval auto schema_property_dependency_indices() requires Schema_Property_Index<Schema, Index> {
using dependency_spec = typename Schema::template property_type<Index>::dependency_spec; using dependency_spec = typename Schema::template property_type<Index>::dependency_spec;
constexpr auto indices = Schema_Dependency_Indices<Schema, dependency_spec>::value; return 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> template <class Schema, std::size_t Index>
inline constexpr std::size_t schema_property_dependency_count_v = schema_property_dependency_indices<Schema, Index>().size(); inline constexpr std::size_t schema_property_dependency_count_v = schema_property_dependency_indices<Schema, Index>().size();
@@ -271,6 +265,37 @@ consteval auto schema_property_dependency_matrix() {
}(std::make_index_sequence<Schema::property_count>{}); }(std::make_index_sequence<Schema::property_count>{});
return matrix; return matrix;
} }
template <Property_Schema Schema>
consteval bool schema_dependency_graph_acyclic() {
constexpr auto dependencies = schema_property_dependency_matrix<Schema>();
std::array<std::size_t, Schema::property_count> dependency_counts{};
std::array<std::size_t, Schema::property_count> ready{};
std::size_t ready_begin = 0;
std::size_t ready_end = 0;
for (std::size_t property = 0; property < Schema::property_count; ++property) {
for (std::size_t dependency = 0; dependency < Schema::property_count; ++dependency) {
dependency_counts[property] += dependencies[property][dependency] ? 1u : 0u;
}
if (dependency_counts[property] == 0) {
ready[ready_end++] = property;
}
}
std::size_t resolved_count = 0;
while (ready_begin != ready_end) {
auto resolved = ready[ready_begin++];
++resolved_count;
for (std::size_t property = 0; property < Schema::property_count; ++property) {
if (!dependencies[property][resolved]) {
continue;
}
--dependency_counts[property];
if (dependency_counts[property] == 0) {
ready[ready_end++] = property;
}
}
}
return resolved_count == Schema::property_count;
}
template <class Schema, std::size_t Index, class Category, class Fallback> template <class Schema, std::size_t Index, class Category, class Fallback>
struct Effective_Attribute { struct Effective_Attribute {
private: private:
@@ -301,11 +326,11 @@ constexpr decltype(auto) declared_effective_attribute(const Schema& schema) requ
} }
} }
template <class Schema> template <class Schema>
concept Valid_Property_Schema = Property_Schema<Schema>; concept Valid_Property_Schema = Property_Schema<Schema> && schema_dependency_graph_acyclic<Schema>();
template <class Schema, std::size_t Index> 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; 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> 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; inline constexpr bool property_has_derived_synchronization_v = Schema::template property_type<Index>::synchronized_view_read && !Schema::template property_type<Index>::writable;
template <class Schema, std::size_t Index> 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>); 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> template <class Schema, std::size_t Index>
@@ -331,7 +356,6 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
return std::array<bool, Schema::property_count>{property_has_derived_synchronization_v<Schema, Index>...}; return std::array<bool, Schema::property_count>{property_has_derived_synchronization_v<Schema, Index>...};
}(std::make_index_sequence<Schema::property_count>{}); }(std::make_index_sequence<Schema::property_count>{});
constexpr auto dependencies = schema_property_dependency_matrix<Schema>(); 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<std::size_t, Schema::property_count> logical{};
std::array<bool, Schema::property_count> explicitly_configured{}; std::array<bool, Schema::property_count> explicitly_configured{};
std::size_t next_token = Schema::property_count + 1; std::size_t next_token = Schema::property_count + 1;
@@ -407,18 +431,12 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
} }
} }
if constexpr (Schema::property_count > 0) { if constexpr (Schema::property_count > 0) {
std::array<unsigned char, Schema::property_count> dependency_state{}; constexpr auto keys = schema_property_keys<Schema>();
std::array<bool, Schema::property_count> dependency_resolved{};
auto resolve_derived_slot = [&](auto&& self, std::size_t index) -> std::size_t { auto resolve_derived_slot = [&](auto&& self, std::size_t index) -> std::size_t {
if (!derived_synchronization[index]) { if (!derived_synchronization[index] || dependency_resolved[index]) {
return resolved.lock_slots[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; bool has_dependency_domain = false;
std::size_t dependency_slot = unsynchronized_slot; std::size_t dependency_slot = unsynchronized_slot;
for (std::size_t dependency = 0; dependency < Schema::property_count; ++dependency) { for (std::size_t dependency = 0; dependency < Schema::property_count; ++dependency) {
@@ -434,7 +452,7 @@ Resolved_Synchronization_Plan<Schema::property_count> resolve_synchronization_pl
} }
} }
resolved.lock_slots[index] = has_dependency_domain ? dependency_slot : unsynchronized_slot; resolved.lock_slots[index] = has_dependency_domain ? dependency_slot : unsynchronized_slot;
dependency_state[index] = 2; dependency_resolved[index] = true;
return resolved.lock_slots[index]; return resolved.lock_slots[index];
}; };
for (std::size_t index = 0; index < Schema::property_count; ++index) { for (std::size_t index = 0; index < Schema::property_count; ++index) {
@@ -512,10 +530,13 @@ bool visit_schema_property(const Schema& schema, std::string_view key_value, Fun
template <class Object, class Defaults, class Source, class... Properties> 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>> { 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>...>; using schema_type = Object_Schema<Object, std::decay_t<Defaults>, std::decay_t<Properties>...>;
static_assert(Valid_Property_Schema<schema_type>); constexpr bool valid_schema = Valid_Property_Schema<schema_type>;
static_assert(valid_schema, "property dependency graph must be valid and acyclic");
auto plan = materialize_synchronization_plan<schema_type>(std::forward<Source>(synchronization_source)); 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)...}}; 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())); if constexpr (valid_schema) {
static_cast<void>(resolve_synchronization_plan(schema, schema.synchronization_plan()));
}
return schema; return schema;
} }
template <class Object, class Defaults, class Source, class... Properties> template <class Object, class Defaults, class Source, class... Properties>
+3
View File
@@ -80,4 +80,7 @@ if(STRUCTIVE_BUILD_TESTS)
structive_expect_compile_failure(duplicate_attribute "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/duplicate_attribute.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(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") structive_expect_compile_failure(undeclared_computed_read "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/undeclared_computed_read.cpp")
structive_expect_compile_failure(dependency_cycle "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/dependency_cycle.cpp")
structive_expect_compile_failure(missing_accessor_dependency_spec "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/missing_accessor_dependency_spec.cpp")
structive_expect_compile_failure(invalid_property_metadata "${CMAKE_CURRENT_LIST_DIR}/tests/compile_fail/invalid_property_metadata.cpp")
endif() endif()
@@ -0,0 +1,14 @@
#include <structive/property/property.hpp>
using namespace structive;
struct Device {};
int main() {
auto schema = object<Device>(
computed_property<Device, int>(depends_on_keys<"right">, [](const auto& view) {
return view.template get<"right">();
}, key<"left">),
computed_property<Device, int>(depends_on_keys<"left">, [](const auto& view) {
return view.template get<"left">();
}, key<"right">)
);
static_cast<void>(schema);
}
@@ -0,0 +1,9 @@
#include <structive/property/property.hpp>
struct Device {
int value{};
};
struct Invalid_Metadata {};
int main() {
auto schema = structive::object<Device>(structive::field<&Device::value>(structive::key<"value">, Invalid_Metadata{}));
static_cast<void>(schema);
}
@@ -0,0 +1,20 @@
#include <structive/property/property.hpp>
struct Device {
int value{};
};
struct Accessor {
using object_type = Device;
using value_type = int;
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 = false;
int read(const Device& device) const {
return device.value;
}
};
int main() {
structive::Property_Descriptor<Accessor, structive::Key_Attribute<"value">> descriptor;
static_cast<void>(descriptor);
}
+24 -1
View File
@@ -69,7 +69,10 @@ struct structive::Type_Descriptor<Computed_Device> {
}, key<"double_span">), }, key<"double_span">),
computed_property<Computed_Device, int>(depends_on<&Computed_Device::fixed_offset>, [](const auto& view) { computed_property<Computed_Device, int>(depends_on<&Computed_Device::fixed_offset>, [](const auto& view) {
return view.template get<&Computed_Device::fixed_offset>() * 2; return view.template get<&Computed_Device::fixed_offset>() * 2;
}, key<"fixed_twice">) }, key<"fixed_twice">),
computed_property<Computed_Device, int>(depends_on<>, [](const auto&) {
return 42;
}, key<"constant">)
); );
} }
}; };
@@ -211,6 +214,21 @@ int main() {
multi_sum += std::remove_cvref_t<decltype(attribute)>::value; multi_sum += std::remove_cvref_t<decltype(attribute)>::value;
}); });
REQUIRE(multi_sum == 8); REQUIRE(multi_sum == 8);
std::size_t metadata_count = 0;
std::size_t attribute_count = 0;
std::size_t constraint_count = 0;
schema.template property<&Device::temperature>().for_each_metadata([&](const auto&) {
++metadata_count;
});
schema.template property<&Device::temperature>().for_each_attribute([&](const auto&) {
++attribute_count;
});
schema.template property<&Device::temperature>().for_each_constraint([&](const auto&) {
++constraint_count;
});
REQUIRE(metadata_count == 7);
REQUIRE(attribute_count == 5);
REQUIRE(constraint_count == 2);
const auto& empty_schema = type_descriptor<Empty_Device>(); const auto& empty_schema = type_descriptor<Empty_Device>();
static_assert(type_descriptor_schema_t<Empty_Device>::property_count == 0); static_assert(type_descriptor_schema_t<Empty_Device>::property_count == 0);
REQUIRE(empty_schema.synchronization_plan().property_rules().empty()); REQUIRE(empty_schema.synchronization_plan().property_rules().empty());
@@ -284,13 +302,18 @@ int main() {
REQUIRE(computed.lock_slot<&Computed_Device::fixed_offset>() == Resolved_Synchronization_View::unsynchronized_slot); 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 double_span_index = schema_property_index_v<Computed_Schema, "double_span">;
constexpr auto fixed_twice_index = schema_property_index_v<Computed_Schema, "fixed_twice">; constexpr auto fixed_twice_index = schema_property_index_v<Computed_Schema, "fixed_twice">;
constexpr auto constant_index = schema_property_index_v<Computed_Schema, "constant">;
static_assert(schema_dependency_graph_acyclic<Computed_Schema>());
static_assert(schema_property_dependency_count_v<Computed_Schema, constant_index> == 0);
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::min_speed>());
REQUIRE(computed.resolved_synchronization().slot(speed_span_index) == computed.lock_slot<&Computed_Device::max_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(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.resolved_synchronization().slot(fixed_twice_index) == Resolved_Synchronization_View::unsynchronized_slot);
REQUIRE(computed.resolved_synchronization().slot(constant_index) == Resolved_Synchronization_View::unsynchronized_slot);
REQUIRE(computed.read<"speed_span">() == 95); REQUIRE(computed.read<"speed_span">() == 95);
REQUIRE(computed.read<"double_span">() == 190); REQUIRE(computed.read<"double_span">() == 190);
REQUIRE(computed.read<"fixed_twice">() == 10); REQUIRE(computed.read<"fixed_twice">() == 10);
REQUIRE(computed.read<"constant">() == 42);
computed.write<&Computed_Device::min_speed>(20); computed.write<&Computed_Device::min_speed>(20);
REQUIRE(computed.read<"speed_span">() == 85); REQUIRE(computed.read<"speed_span">() == 85);
REQUIRE(computed.read<"double_span">() == 170); REQUIRE(computed.read<"double_span">() == 170);
+7 -2
View File
@@ -100,8 +100,11 @@ static_assert(Property::writable);
static_assert(Property::runtime_copy_writable); static_assert(Property::runtime_copy_writable);
using Value = Property::value_type; using Value = Property::value_type;
using Accessor = Property::accessor_type; using Accessor = Property::accessor_type;
using Dependencies = Property::dependency_spec;
``` ```
Every custom `Property_Accessor` must declare `using dependency_spec = ...`. Use `No_Property_Dependencies` when it has no dependencies; there is no implicit fallback.
A `read_only` field reports: A `read_only` field reports:
```cpp ```cpp
@@ -513,7 +516,7 @@ computed_property<Device, int>(depends_on<&Device::min_speed, &Device::max_speed
}, key<"speed_span">) }, key<"speed_span">)
``` ```
`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. `depends_on<...>` makes dependencies explicit Schema facts. `depends_on_keys<"a", "b">` is available when key-based declaration is more appropriate. `depends_on<>` is a valid explicit zero-dependency declaration. 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: Writable dependencies that must form one snapshot should share a synchronization domain with each other:
@@ -526,7 +529,9 @@ synchronization(
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. 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. `schema_property_dependency_indices<Schema, Index>()` exposes the direct dependency indices for schema consumers. `schema_dependency_graph_acyclic<Schema>()` exposes the DAG invariant; `object_schema(...)` rejects cyclic dependency graphs at compile time.
A descriptor stores Attributes and Constraints in one metadata tuple. `for_each_metadata()` traverses both kinds, `for_each_attribute()` traverses only Attributes, and `for_each_constraint()` traverses only Constraints.
## 21. Trusted accessor properties ## 21. Trusted accessor properties
+7 -2
View File
@@ -98,8 +98,11 @@ static_assert(Property::writable);
static_assert(Property::runtime_copy_writable); static_assert(Property::runtime_copy_writable);
using Value = Property::value_type; using Value = Property::value_type;
using Accessor = Property::accessor_type; using Accessor = Property::accessor_type;
using Dependencies = Property::dependency_spec;
``` ```
所有自定义 `Property_Accessor` 都必须显式声明 `using dependency_spec = ...`。无依赖时使用 `No_Property_Dependencies`,不再存在隐式 fallback。
`read_only` 字段: `read_only` 字段:
```cpp ```cpp
@@ -505,7 +508,7 @@ computed_property<Device, int>(depends_on<&Device::min_speed, &Device::max_speed
}, key<"speed_span">) }, key<"speed_span">)
``` ```
`depends_on<...>` 把 dependency 变成 Schema 的显式结构事实。也可以通过 `depends_on_keys<"a", "b">` 按 key 声明。Computed view 只能读取已声明的直接 dependency。 `depends_on<...>` 把 dependency 变成 Schema 的显式结构事实。也可以通过 `depends_on_keys<"a", "b">` 按 key 声明。`depends_on<>` 是合法的显式零依赖声明。Computed view 只能读取已声明的直接 dependency。
如果 writable dependency 需要同一快照,只需要让这些 writable dependency 共享 synchronization domain 如果 writable dependency 需要同一快照,只需要让这些 writable dependency 共享 synchronization domain
@@ -518,7 +521,9 @@ synchronization(
Computed Property 自身不要加入 synchronization rule。它的读取 slot 根据 dependency graph 自动推导;如果需要同步的 dependency 不在同一个 domainSchema/实例 synchronization topology 会被拒绝。Read-only stored dependency 不需要 lock slot。 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。 Schema 可通过 `schema_property_dependency_indices<Schema, Index>()` 读取某个 Property 的直接 dependency index`schema_dependency_graph_acyclic<Schema>()` 暴露 DAG invariant`object_schema(...)` 会在编译期拒绝 dependency cycle
Descriptor 使用一份 metadata tuple 同时保存 Attribute 与 Constraint。`for_each_metadata()` 遍历两者,`for_each_attribute()` 只遍历 Attribute`for_each_constraint()` 只遍历 Constraint。
## 21. Trusted Accessor Property ## 21. Trusted Accessor Property
+7 -6
View File
@@ -46,6 +46,7 @@ Structive has two architectural layers.
- intrinsic readable/writable capability; - intrinsic readable/writable capability;
- Attributes; - Attributes;
- Constraints; - Constraints;
- dependency graph;
- default synchronization description. - default synchronization description.
This information belongs to the type. This information belongs to the type.
@@ -213,7 +214,7 @@ 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. The computed property does not contain writable storage. However, its read may require a stable snapshot of mutable dependencies.
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. Dependencies must be explicit Schema facts. Every Property Accessor declares a `dependency_spec`; no-dependency accessors use `No_Property_Dependencies` explicitly. 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. The dependency graph is a DAG invariant and cycles are rejected at compile time when the Schema is formed.
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. 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.
@@ -221,17 +222,17 @@ Writable dependencies that require one atomic snapshot must share one synchroniz
Do not give a read-only stored field a mutex. A computed read uses synchronization only for mutable dependencies that require consistency. Do not give a read-only stored field a mutex. A computed read uses synchronization only for mutable dependencies that require consistency.
## 12. One Attribute protocol ## 12. One property metadata store
Core and extensions share one Attribute mechanism. A Property Descriptor has one metadata store. Its entries are either Attributes or Constraints.
An Attribute owns a category and may define whether it is single-valued and inheritable. Core and extensions share one Attribute mechanism for descriptive metadata. An Attribute owns a category and declares whether it is single-valued and inheritable. Constraints use the validation protocol but live in the same descriptor metadata store.
Core must not create parallel metadata systems for UI, serialization, diagnostics or domain-specific features. Core must not create parallel metadata storage systems for UI, serialization, diagnostics or domain-specific features.
### Rule ### Rule
New metadata domains should extend the Attribute protocol instead of adding a second metadata framework. New descriptive metadata domains should extend the Attribute protocol; new validation rules should extend the Constraint protocol. Both remain entries in the same Property metadata store.
## 13. Category ownership must be clear ## 13. Category ownership must be clear
+7 -6
View File
@@ -46,6 +46,7 @@ Structive 有两层架构。
- intrinsic readable/writable - intrinsic readable/writable
- Attribute - Attribute
- Constraint - Constraint
- dependency graph
- 默认 synchronization 描述。 - 默认 synchronization 描述。
这些属于类型。 这些属于类型。
@@ -211,7 +212,7 @@ Computed Property 自身可以只读,但它可能依赖可写字段。
Computed value 本身没有可写存储,但读取它时可能需要 mutable dependency 的一致快照。 Computed value 本身没有可写存储,但读取它时可能需要 mutable dependency 的一致快照。
Dependency 必须显式进入 Schema。`Synchronized_Computed_Accessor` 的 read view 只能读取声明过的直接 dependencycomputed property 的读取 slot 从 dependency graph 推导,而不是由调用方再次把 computed key 写进 synchronization group。 Dependency 必须显式进入 Schema。每个 Property Accessor 都必须声明 `dependency_spec`,无依赖 Accessor 显式使用 `No_Property_Dependencies``Synchronized_Computed_Accessor` 的 read view 只能读取声明过的直接 dependencycomputed property 的读取 slot 从 dependency graph 推导,而不是由调用方再次把 computed key 写进 synchronization group。Dependency graph 是 DAG invariantSchema 形成时在编译期拒绝 cycle。
需要原子一致性的 writable dependency 应彼此处于同一个同步域。Read-only stored dependency 没有 managed writer,所以可以直接读取而无需锁。 需要原子一致性的 writable dependency 应彼此处于同一个同步域。Read-only stored dependency 没有 managed writer,所以可以直接读取而无需锁。
@@ -219,17 +220,17 @@ Dependency 必须显式进入 Schema。`Synchronized_Computed_Accessor` 的 read
不要给 read-only stored field 创建 mutex。Computed read 的同步只服务于需要一致性的 mutable dependency。 不要给 read-only stored field 创建 mutex。Computed read 的同步只服务于需要一致性的 mutable dependency。
## 12. 只有一套 Attribute 协议 ## 12. 只有一份 Property Metadata Storage
Core 与 Extension 共用同一套 Attribute mechanism Property Descriptor 只有一份 metadata storage,其中的条目只能是 Attribute 或 Constraint
Attribute 拥有 category,并可以声明 single-valued 与 inheritable 语义。 Core 与 Extension 的描述性 metadata 共用 Attribute mechanism。Attribute 拥有 category,并声明 single-valued 与 inheritable 语义Constraint 使用 validation protocol,但与 Attribute 保存在同一份 descriptor metadata storage 中
UI、serialization、诊断等新领域不应该另起第二套 metadata system UI、serialization、诊断等新领域不应该另起第二套 metadata storage
### 原则 ### 原则
新的 metadata domain 应扩展 Attribute protocol而不是创造另一套框架 新的描述性 metadata 扩展 Attribute protocol新的校验规则扩展 Constraint protocol;两者都作为同一份 Property metadata 的条目存在
## 13. Category 必须有明确所有者 ## 13. Category 必须有明确所有者