# Structive Property Core 完整指南 [English](CORE_GUIDE.md) ## 1. Include 与 CMake Target ```cpp #include ``` ```cmake target_link_libraries(my_target PRIVATE structive::property_core) ``` Property Core 使用 C++20。 ## 2. 定义 Managed Object ```cpp using namespace structive; struct Device : Property_Object { double temperature{25.0}; double pressure{101.3}; int serial_number{1001}; }; ``` `Property_Object` 增加 managed operation,字段本身仍是普通成员。 ## 3. 定义 Type Descriptor ```cpp template <> struct structive::Type_Descriptor { static auto get() { return object( synchronization(sync_all_independent), field<&Device::temperature>( key<"temperature">, unit<"C">, min_value<-50.0>, max_value<200.0> ), field<&Device::pressure>( key<"pressure">, unit<"kPa"> ), field<&Device::serial_number>( key<"serial_number">, read_only ) ); } }; ``` Descriptor 就是 `Device` 的结构定义。 ### 3.1 类型级 Metadata 扩展可以直接给被描述类型附加编译期 Attribute;它与 Property metadata、可继承的 Property defaults 相互独立: ```cpp struct Action_Category {}; struct Action { using attribute_category = Action_Category; static constexpr bool single_valued = false; static constexpr bool inheritable = false; std::string_view name; }; return object( type_metadata(Action{"reset"}, Action{"calibrate"}), field<&Device::temperature>(key<"temperature">) ); ``` 通过 `Schema::type_attribute_count`、`Schema::has_type_attribute`、 单值 category 的 `schema.type_attribute()`,以及 `schema.for_each_type_attribute(callback)` 查询。Core 只存储并校验通用 Attribute 协议,不解释扩展拥有的 category。类型 metadata 不是 Property default, 不会复制到各个 Property。 ## 4. Schema 静态保证 合法 Schema 保证: - 每个 Property 都有非空 key; - key 唯一; - member selector 属于对应 object type; - 同一 Property 上 single-valued Attribute category 不重复; - capability metadata 不会要求底层 Accessor 不支持的操作; - Constraint 与 Property value type 兼容。 ```cpp static_assert(Property_Described_Object); using Schema = type_descriptor_schema_t; static_assert(Valid_Property_Schema); ``` ## 5. 获取 Schema ```cpp const auto& schema = type_descriptor(); const auto& same_schema = device.schema(); ``` 按 index 或 member pointer 获取 Property: ```cpp const auto& first = schema.property<0>(); const auto& temperature = schema.property<&Device::temperature>(); ``` ## 6. Property Descriptor Descriptor 暴露编译期结构事实: ```cpp using Property = std::remove_cvref_t())>; static_assert(Property::readable); static_assert(Property::writable); static_assert(Property::runtime_copy_writable); using Value = Property::value_type; using Accessor = Property::accessor_type; using Dependencies = Property::dependency_spec; ``` 所有自定义 `Property_Accessor` 都必须显式声明 `using dependency_spec = ...`。无依赖时使用 `No_Property_Dependencies`,不再存在隐式 fallback。 `read_only` 字段: ```cpp using Serial = std::remove_cvref_t())>; static_assert(Serial::readable); static_assert(!Serial::writable); ``` 运行时可直接取得 key: ```cpp auto key_value = schema.property<&Device::temperature>().key(); ``` ## 7. Intrinsic Capability Core capability: ```cpp Property_Capability::none Property_Capability::read Property_Capability::write Property_Capability::read_write ``` 便捷 Attribute: ```cpp read_only write_only read_write inaccessible ``` Capability 属于 Property 自身,不是 authorization rule。 不声明 capability 时,Structive 根据 Accessor 自动推导。 普通非 const member 默认 read/write;getter-only computed property 默认 read-only。 Capability 可以收窄 Accessor,但不能创造 Accessor 本来不存在的操作。 ## 8. Core 不存在访问控制 API Property Core 不提供 domain access mode,也不区分: ```text internal external persistence ``` Consumer 自己决定 policy。例如 GUI 可以只展示部分 Property,即使这些 Property 在结构上都 readable。 Core 只给出 intrinsic `readable` / `writable`。 ## 9. Core Attribute ### 9.1 Key 每个 Property 必须有 key: ```cpp key<"temperature"> ``` Key 是 runtime lookup 与 adapter 使用的结构协议身份。 ### 9.2 Capability ```cpp read_only write_only read_write inaccessible ``` Capability Attribute 是 single-valued、non-inheritable。 ### 9.3 Unit ```cpp unit<"C"> unit<"kPa"> ``` Core 保存单位信息,但不负责换算。 ### 9.4 Sensitive ```cpp sensitive<> sensitive ``` `sensitive` 是可继承 metadata,不实现访问控制。Consumer 可以把它作为自己 policy 的一个输入。 ## 10. 自定义 Attribute 与 Defaults 任何遵守 Attribute protocol 的类型都可以挂在 Property 上。 ```cpp struct Group_Category {}; template struct Group_Attribute { using attribute_category = Group_Category; static constexpr bool single_valued = true; static constexpr bool inheritable = true; static constexpr auto value = Value; }; ``` Inheritable Attribute 可以放进 `defaults(...)`: ```cpp object( defaults(sensitive<>), field<&Device::temperature>(key<"temperature">) ) ``` Capability 故意不可继承,因为每个 Property 的固有操作集合必须独立成立。 ## 11. Constraint 与 Validation 内置 Constraint: ```cpp min_value<0> max_value<100> finite ``` 自定义 Constraint: ```cpp constraint<"even">([](int value) { return value % 2 == 0; }) ``` 显式验证: ```cpp auto result = validate_property_value<&Device::temperature>(device.schema(), candidate); if (result) { auto key_value = result->property_key; auto code = result->code; } ``` `write()` 不自动调用 Validation。 ## 12. Typed Managed Read/Write C++ 业务代码优先 member pointer: ```cpp auto temperature = device.read<&Device::temperature>(); device.write<&Device::temperature>(30.0); ``` 也支持编译期 key: ```cpp auto temperature = device.read<"temperature">(); device.write<"temperature">(30.0); ``` Typed API 由 intrinsic capability 约束。对 `read_only` 调用 write 时,函数在 overload resolution 阶段就不可用。 ```cpp template concept Can_Write_Serial = requires(Object& object) { object.template write<&Device::serial_number>(1); }; static_assert(!Can_Write_Serial); ``` ## 13. Read-Only Fast Path Stored read-only property 在编译期和同步解析阶段都会被裁掉。 ```cpp field<&Device::serial_number>(key<"serial_number">, read_only) ``` 最终: ```text serial_number -> unsynchronized_slot ``` `read<&Device::serial_number>()` 不查询 lock slot,也不构造 `shared_lock`。 即使默认是 `sync_all_shared` 也一样。 如果一个对象只有 stored read-only property,这些 Property 对 `resolved_synchronization().lock_count` 的贡献为 0。 ## 14. Synchronization Plan Synchronization 是独立的 topology 层,不是访问权限系统。它只回答一个问题:managed mutable state 并发访问时,哪些 Property 属于同一个一致性域。 Stored intrinsic read-only property 会在 lock slot materialization 之前被移除。因此即使默认规则是 `sync_all_shared`,也不会仅仅因为一个 stored `read_only` Property 而创建 mutex。 ### 14.1 默认 Topology | 默认规则 | 对真正需要同步的 Property 的含义 | | --- | --- | | `sync_all_independent` | 每个 Property 独立一个 lock domain | | `sync_all_shared` | 所有 Property 共用一个 lock domain | | `sync_all_unsynchronized` | 不创建真实 lock domain | ```cpp synchronization(sync_all_independent) ``` `sync_all_unsynchronized` 是显式放弃同步。Structive 仍提供 managed access,但并发读写造成的线程安全责任由调用方承担。 ### 14.2 编译期 Member Rule Schema 在 C++ 中已知时优先使用 member pointer: ```cpp synchronization( sync_all_shared, sync_independent<&Device::temperature>(), sync_unsynchronized<&Device::debug_counter>() ) ``` Specification 针对具体 Schema materialize 时会检查 Member 是否真的注册。 ### 14.3 Runtime Key Rule Adapter 或动态配置代码可以使用 key 构造 `Synchronization_Plan`: ```cpp Synchronization_Plan plan; plan.set_default(Synchronization_Default::independent); plan.unsynchronized("debug_counter"); ``` Key 只能在 resolve 时验证。未知 Property、同一 Property 重复配置、空 Group、重复 Group 名都会抛 `std::invalid_argument`。 ### 14.4 Group 表示一致性域 ```cpp synchronization( sync_all_independent, sync_group<&Device::min_speed, &Device::max_speed>("speed_range") ) ``` 同一 Group 中真正需要同步的 Property 会解析到同一个 lock slot。Stored read-only Property 即使被宽泛规则或 Group 包含,也仍然是 `unsynchronized_slot`,因为 managed path 下不存在 writer。 Group 应该表达真实业务 invariant 或 snapshot boundary,而不是单纯为了减少 mutex 数量。 ### 14.5 为什么 Synchronization API 有多种入口 这些入口对应的是“信息在什么时候已知”,不是重复抽象: - 类型级默认规则描述一个类型的正常 topology; - compile-time member rule 让普通 C++ 代码获得 Schema 编译期检查; - runtime-key rule 服务动态 Adapter; - per-instance override 服务确实需要特殊 topology 的个别对象; - Guard 表达一次临时的多 Property 一致性操作。 它们最终都落到同一个 resolved lock-slot topology。 ## 15. 每实例 Synchronization Override 对象可以显式覆盖类型默认策略: ```cpp Device device{ property_synchronization( synchronization(sync_all_shared) ) }; ``` Typed member rule: ```cpp Device device{ property_synchronization( synchronization( sync_all_independent, sync_group<&Device::temperature, &Device::pressure>("environment") ) ) }; ``` 默认 topology 每类型只 resolve 一次并共享。只有显式 override 的实例才保存紧凑 override layout。Copy/move construction 保留源对象的 override topology;assignment 保留目标对象已经选择的 topology,因为赋值修改的是对象状态,不应该偷偷改变这个实例的同步策略。 ## 16. 查看 Resolved Synchronization ```cpp auto view = device.resolved_synchronization(); auto count = view.lock_count; ``` 成员 slot: ```cpp auto slot = device.lock_slot<&Device::temperature>(); ``` 无锁 Property 使用: ```cpp Resolved_Synchronization_View::unsynchronized_slot ``` Stored read-only Property 自动得到这个值。`resolved_synchronization()` 和 `lock_slot()` 更适合诊断和框架检查;普通业务代码通常应该通过 `read`、`write`、`lock_shared`、`lock_unique` 表达意图,而不是依赖数字 slot。 ## 17. 静态多属性 Guard Read Guard: ```cpp auto guard = device.lock_shared<&Device::temperature, &Device::pressure>(); auto by_member = guard.get<&Device::temperature>(); auto by_key = guard.get<"pressure">(); ``` Write Guard: ```cpp auto guard = device.lock_unique<&Device::min_speed, &Device::max_speed>(); guard.set<&Device::min_speed>(20); guard.set<"maximum_speed">(120); ``` Member 和 compile-time key 统一使用同名 `get`/`set`。Capability 直接进入 overload constraint:read-only Property 不能创建 typed unique guard,`requires` 表达式中也不会看到它的 `set`。 Static Guard 会先解析 slot、去重,然后按稳定的数字 slot 顺序获取锁。调用方传入 Member 的顺序不会成为 mutex 获取顺序,因此两个调用方即使按相反 Member 顺序请求同一批 domain,也不会因为 API 参数顺序造成 lock-order inversion。 Stored read-only Property 可以出现在 shared guard 的逻辑 held set 中,但不会因此创建 mutex,仍保持 read-only zero-lock fast path。 ## 18. Dynamic-Key Guard ```cpp auto read_guard = device.lock_shared({"temperature", "pressure"}); auto write_guard = device.lock_unique({"temperature", "pressure"}); auto value = read_guard.get<"temperature">(); ``` Dynamic Guard 服务“目标 Property 集合只有运行期才知道”的场景,因此 typed guard 的编译期错误在这里变成运行时错误: - 未知 key 抛 `std::invalid_argument`; - `lock_shared` 遇到不可读 Property 时拒绝; - `lock_unique` 遇到不可写 Property 时拒绝; - 后续 `get`/`set` 请求不在 held set 中的 Property 时抛 `std::logic_error`。 Dynamic Guard 和 Static Guard 使用完全相同的 resolved topology 和稳定锁顺序。 这些行为由 `core/tests/synchronization_test.cpp` 单独覆盖,包括 independent/shared/unsynchronized 默认规则、Group、override、非法 Plan、runtime-key Guard、阻塞语义以及相反 Member 顺序获取锁。 ## 19. Traversal 只遍历 Schema: ```cpp schema.for_each_property([&](auto index, const auto& descriptor) { }); ``` 遍历 readable value: ```cpp device.for_each_readable([&](auto index, const auto& descriptor, const auto& value) { }); ``` 一次性锁定后遍历: ```cpp device.for_each_readable_locked([&](auto index, const auto& descriptor, const auto& value) { }); ``` Locked traversal 只获取真实存在的同步 slot,read-only stored property 不会引入锁。 获取全部 writable lock domain: ```cpp device.with_all_writable_locked([&](auto& guard) { }); ``` 它不会自动执行 validation 或 rollback。 ## 20. Computed Property Synchronized computed property 接收 read view: ```cpp computed_property(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">) ``` `depends_on<...>` 把 dependency 变成 Schema 的显式结构事实。也可以通过 `depends_on_keys<"a", "b">` 按 key 声明。`depends_on<>` 是合法的显式零依赖声明。Computed view 只能读取已声明的直接 dependency。 如果 writable dependency 需要同一快照,只需要让这些 writable dependency 共享 synchronization domain: ```cpp synchronization( sync_all_independent, sync_group("speed", "min_speed", "max_speed") ) ``` Computed Property 自身不要加入 synchronization rule。它的读取 slot 根据 dependency graph 自动推导;如果需要同步的 dependency 不在同一个 domain,Schema/实例 synchronization topology 会被拒绝。Read-only stored dependency 不需要 lock slot。 Schema 可通过 `schema_property_dependency_indices()` 读取某个 Property 的直接 dependency index;`schema_dependency_graph_acyclic()` 暴露 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 Trusted getter: ```cpp trusted_computed_property<&Device::value>(key<"value">) ``` Trusted getter/setter: ```cpp trusted_accessor_property<&Device::get_value, &Device::set_value>(key<"value">) ``` 这类入口直接访问对象,而不是通过 synchronized dependency view。只有调用方明确掌握成员函数同步语义时才应该使用。 ## 22. Runtime Type-Erased Access `Property_Object_Base` 是动态 Adapter 边界,面向 GUI inspector、serialization adapter、脚本绑定、RPC 层等“只有运行期才知道 Property key”的代码。普通 typed C++ 业务代码仍应优先使用 `read` / `write`。 ```cpp Property_Object_Base& erased = device; ``` 类型信息: ```cpp erased.runtime_object_type(); erased.runtime_property_count(); ``` ### 22.1 Runtime Read ```cpp auto result = erased.runtime_read("temperature", context, callback); ``` 成功时 callback 恰好调用一次,并收到 Schema index、key、精确 `type_info` 以及当前 value 的指针。这个 value pointer 是借用指针,只在 callback 执行期间有效;必须同步消费或复制,不能保存到 callback 之后继续使用。 对于需要同步的 writable Property,Structive 在 callback 执行期间仍然持有对应的 managed read lock,因此 callback 不应该重入同一 lock domain 的冲突写操作。Stored read-only Property 继续走 intrinsic zero-lock fast path,不会因为入口变成 runtime 就额外创建或获取 mutex。 ### 22.2 Runtime Write ```cpp auto result = erased.runtime_write("temperature", typeid(double), &value); ``` Runtime write 明确不做隐式转换。`typeid(double)` 必须与 Property 声明的 value type 完全一致,`value` 指针在调用期间必须指向这个精确类型的有效对象。这个边界从 `const` 输入复制;如果一个 intrinsically writable Accessor 无法接受 copy-input,它会暴露 `runtime_copy_writable == false`,runtime access 返回 `unsupported_runtime_write`,而 typed `write` 在 Accessor 支持时仍可正常接收 move-only 值。成功写入继续复用 typed `write` 的 managed synchronization 路径。 ### 22.3 Result Contract | Result | 含义 | | --- | --- | | `ok` | lookup 和访问完成 | | `unknown_property` | Schema 中不存在该 runtime key | | `not_readable` | Property 存在,但 intrinsic capability 不可读 | | `not_writable` | Property 存在,但 intrinsic capability 不可写 | | `unsupported_runtime_write` | Property intrinsically writable,但 Accessor 无法接收 runtime copy-input 边界 | | `type_mismatch` | runtime write 提供的类型和 Property value type 不一致 | 这里没有 external/persistence mode,也没有访问控制 policy。Adapter 自己决定是否对外暴露、是否调用 runtime read/write;Structive 只报告 Property 自身的 intrinsic capability。 ### 22.4 为什么 Runtime API 故意保持底层 动态边界使用 `type_info`、`void*` 和 callback,是因为调用方在编译期根本不知道值类型。Core 不强制引入统一 `variant`、堆分配 `any`、serialization 格式或者 conversion registry,因为这些方案都会把所有权和转换策略重新塞回 Core。 因此 Runtime API 是 Adapter API,而不是普通业务代码首选 API。上层 Extension 完全可以按自己的领域需求再包一层 value container,而不用改变 Structive Core。 这些契约由 `core/tests/runtime_api_test.cpp` 单独覆盖,包括全部 result code、callback metadata、精确类型写入、managed blocking 语义,以及 read-only zero-lock runtime fast path。 ## 23. Lock Policy 默认: ```cpp Property_Object ``` NoLock: ```cpp Property_Object ``` `No_Lock_Policy` 不保存真实 mutex array,并从 managed hot path 去除真实 lock object。 ## 24. Raw Object Access ```cpp Device& raw = device.unsafe_object(); ``` 或直接 public member: ```cpp device.temperature = 30.0; ``` 都绕过 Structive managed contract。 这是刻意设计。Structive 是协作式结构基础设施,不是强制封装。 ## 25. 推荐规则 1. 存储保持普通 C++ member。 2. 只注册真正属于结构模型的字段。 3. C++ 业务代码优先 member-pointer typed API。 4. 当 managed model 永远不应该写某个存储字段时,用 `read_only`。 5. 利用 stored read-only 的 zero-lock 优化。 6. 并发 managed code 中不要通过 raw path 修改 read-only 字段。 7. 跨字段 mutable consistency 使用 synchronization group。 8. Validation 保持显式。 9. Runtime access 只用于真正动态的 Adapter。 10. GUI/RPC/Persistence/Authorization 自己拥有 exposure 与 access policy。