api更新
This commit is contained in:
+92
-48
@@ -270,8 +270,8 @@ device.write<&Device::temperature>(30.0);
|
||||
Compile-time key access is also available:
|
||||
|
||||
```cpp
|
||||
auto temperature = device.read_key<"temperature">();
|
||||
device.write_key<"temperature">(30.0);
|
||||
auto temperature = device.read<"temperature">();
|
||||
device.write<"temperature">(30.0);
|
||||
```
|
||||
|
||||
The typed interfaces are constrained by intrinsic capability. A write to `read_only` does not participate in overload resolution.
|
||||
@@ -308,44 +308,51 @@ If an object has only stored read-only properties, those properties contribute z
|
||||
|
||||
## 14. Synchronization plans
|
||||
|
||||
### 14.1 Independent
|
||||
Synchronization is intentionally a topology layer rather than a property permission system. It answers only one question: when managed mutable state is accessed concurrently, which properties share a consistency domain?
|
||||
|
||||
Stored intrinsic read-only properties are removed before lock slots are materialized. A broad rule such as `sync_all_shared` therefore never creates a mutex merely for a stored `read_only` property.
|
||||
|
||||
### 14.1 Default topologies
|
||||
|
||||
| Default | Meaning for properties that require synchronization |
|
||||
| --- | --- |
|
||||
| `sync_all_independent` | each property receives its own lock domain |
|
||||
| `sync_all_shared` | all properties share one lock domain |
|
||||
| `sync_all_unsynchronized` | no real lock domain is created |
|
||||
|
||||
```cpp
|
||||
synchronization(sync_all_independent)
|
||||
```
|
||||
|
||||
Each property that actually requires synchronization gets its own lock domain.
|
||||
`sync_all_unsynchronized` is an explicit opt-out. Structive still provides managed access, but the caller owns the thread-safety consequences of concurrent reads and writes.
|
||||
|
||||
### 14.2 Shared
|
||||
### 14.2 Compile-time member rules
|
||||
|
||||
```cpp
|
||||
synchronization(sync_all_shared)
|
||||
```
|
||||
|
||||
All properties that require synchronization share one lock domain.
|
||||
|
||||
Read-only stored properties are excluded before lock slots are materialized.
|
||||
|
||||
### 14.3 Unsynchronized
|
||||
|
||||
```cpp
|
||||
synchronization(sync_all_unsynchronized)
|
||||
```
|
||||
|
||||
Managed access performs no real locking even for writable properties.
|
||||
|
||||
### 14.4 Per-property override
|
||||
Use member pointers when the schema is known in C++ code:
|
||||
|
||||
```cpp
|
||||
synchronization(
|
||||
sync_all_independent,
|
||||
sync_unsynchronized<&Device::temperature>()
|
||||
sync_all_shared,
|
||||
sync_independent<&Device::temperature>(),
|
||||
sync_unsynchronized<&Device::debug_counter>()
|
||||
)
|
||||
```
|
||||
|
||||
Dynamic-key rule forms are also available.
|
||||
Member validity is checked while the synchronization specification is materialized for the schema.
|
||||
|
||||
### 14.5 Groups
|
||||
### 14.3 Runtime-key rules
|
||||
|
||||
Adapters or configuration code may build a `Synchronization_Plan` from keys:
|
||||
|
||||
```cpp
|
||||
Synchronization_Plan plan;
|
||||
plan.set_default(Synchronization_Default::independent);
|
||||
plan.unsynchronized("debug_counter");
|
||||
```
|
||||
|
||||
Key validity cannot be known until the plan is resolved. Unknown properties, duplicate property configuration, empty groups and duplicate group names are rejected with `std::invalid_argument`.
|
||||
|
||||
### 14.4 Groups are consistency domains
|
||||
|
||||
```cpp
|
||||
synchronization(
|
||||
@@ -354,7 +361,21 @@ synchronization(
|
||||
)
|
||||
```
|
||||
|
||||
Members in one group resolve to the same lock slot if they require synchronization.
|
||||
Members in one group resolve to the same lock slot if they actually require synchronization. A read-only stored member may appear in a broad rule or group, but it still resolves to `unsynchronized_slot` because there is no managed writer to protect.
|
||||
|
||||
A group should express a real invariant or snapshot boundary. It should not be used merely to reduce the mutex count.
|
||||
|
||||
### 14.5 Why the synchronization API has several forms
|
||||
|
||||
The forms represent different information availability, not duplicate concepts:
|
||||
|
||||
- type-level defaults describe the normal topology once per object type;
|
||||
- compile-time member rules give typed C++ code compile-time schema checking;
|
||||
- runtime-key rules support adapters that discover property names dynamically;
|
||||
- per-instance overrides support objects whose synchronization topology genuinely differs from the type default;
|
||||
- guards express a temporary multi-property consistency operation.
|
||||
|
||||
The common semantic model is always the same resolved lock-slot topology.
|
||||
|
||||
## 15. Per-instance synchronization override
|
||||
|
||||
@@ -381,7 +402,7 @@ Device device{
|
||||
};
|
||||
```
|
||||
|
||||
The default topology is shared per type. Only an object with an explicit override stores its compact override layout.
|
||||
The default topology is resolved once and shared per type. Only an object with an explicit override stores a compact override layout. Copy and move construction preserve an object's override topology; assignment preserves the destination object's existing topology because assignment changes object state, not the synchronization policy chosen for that instance.
|
||||
|
||||
## 16. Inspect resolved synchronization
|
||||
|
||||
@@ -402,7 +423,7 @@ No-lock properties use:
|
||||
Resolved_Synchronization_View::unsynchronized_slot
|
||||
```
|
||||
|
||||
For a read-only stored property this is automatic.
|
||||
For a stored read-only property this is automatic. `resolved_synchronization()` and `lock_slot()` are primarily diagnostics and framework-level inspection APIs; normal business code should usually express its intent through `read`, `write`, `lock_shared` and `lock_unique` instead of reasoning about numeric slots.
|
||||
|
||||
## 17. Static multi-property guards
|
||||
|
||||
@@ -410,7 +431,8 @@ Read guard:
|
||||
|
||||
```cpp
|
||||
auto guard = device.lock_shared<&Device::temperature, &Device::pressure>();
|
||||
auto temperature = guard.get<&Device::temperature>();
|
||||
auto by_member = guard.get<&Device::temperature>();
|
||||
auto by_key = guard.get<"pressure">();
|
||||
```
|
||||
|
||||
Write guard:
|
||||
@@ -418,23 +440,33 @@ Write guard:
|
||||
```cpp
|
||||
auto guard = device.lock_unique<&Device::min_speed, &Device::max_speed>();
|
||||
guard.set<&Device::min_speed>(20);
|
||||
guard.set<&Device::max_speed>(120);
|
||||
guard.set<"maximum_speed">(120);
|
||||
```
|
||||
|
||||
Static guards deduplicate slots and acquire them in stable slot order.
|
||||
Member and compile-time-key access intentionally use the same `get`/`set` names. Capability constraints are part of the overload itself: a typed unique guard cannot be requested for a read-only property, and `set` does not exist for a non-writable property in a `requires` expression.
|
||||
|
||||
A typed unique guard requires all selected properties to be intrinsically writable.
|
||||
Static guards resolve member slots, deduplicate repeated domains and acquire locks in stable numeric slot order. Request order therefore does not become mutex acquisition order, avoiding lock-order inversion when two callers request the same domains in different member order.
|
||||
|
||||
A read-only stored property may participate in a shared guard's logical held set without introducing a mutex. This allows a guard API to read it consistently with its intrinsic contract while preserving the read-only zero-lock fast path.
|
||||
|
||||
## 18. Dynamic-key guards
|
||||
|
||||
```cpp
|
||||
auto read_guard = device.lock_shared({"temperature", "pressure"});
|
||||
auto write_guard = device.lock_unique({"temperature", "pressure"});
|
||||
auto value = read_guard.get<"temperature">();
|
||||
```
|
||||
|
||||
Unknown keys throw `std::invalid_argument`.
|
||||
Dynamic guards exist for callers whose selected property set is known only at runtime. Because the keys are dynamic, errors that typed guards reject through constraints become runtime errors:
|
||||
|
||||
A dynamic unique guard rejects a read-only property because the key is only known at runtime.
|
||||
- an unknown key throws `std::invalid_argument`;
|
||||
- `lock_shared` rejects a non-readable property;
|
||||
- `lock_unique` rejects a non-writable property;
|
||||
- a later `get`/`set` throws `std::logic_error` if the requested property is outside the guard's held set.
|
||||
|
||||
Dynamic and static guards use the same resolved lock topology and the same stable lock ordering.
|
||||
|
||||
The behavior above is covered directly by `core/tests/synchronization_test.cpp`, including independent/shared/unsynchronized defaults, groups, overrides, invalid plans, runtime-key guards, blocking behavior and reversed member-order acquisition.
|
||||
|
||||
## 19. Traversal
|
||||
|
||||
@@ -509,7 +541,7 @@ These forms access the object directly rather than using synchronized dependency
|
||||
|
||||
## 22. Runtime type-erased access
|
||||
|
||||
Use `Property_Object_Base` for dynamic adapters:
|
||||
`Property_Object_Base` is the dynamic adapter boundary. It is intended for GUI inspectors, serialization adapters, scripting bridges, RPC layers and other code that learns a property key only at runtime. Normal typed C++ code should prefer `read` and `write`.
|
||||
|
||||
```cpp
|
||||
Property_Object_Base& erased = device;
|
||||
@@ -522,31 +554,43 @@ erased.runtime_object_type();
|
||||
erased.runtime_property_count();
|
||||
```
|
||||
|
||||
Read:
|
||||
### 22.1 Runtime read
|
||||
|
||||
```cpp
|
||||
auto result = erased.runtime_read("temperature", context, callback);
|
||||
```
|
||||
|
||||
Write:
|
||||
On success the callback is invoked exactly once with the schema index, key, exact `type_info` and a pointer to the current value. The value pointer is borrowed and is valid only during the callback; copy or consume it synchronously and never retain it.
|
||||
|
||||
For a synchronized writable property, Structive keeps the corresponding managed read lock held while the callback executes. The callback should therefore not re-enter a conflicting managed write on the same lock domain. A stored read-only property follows the intrinsic zero-lock fast path and does not acquire a mutex merely because the access is dynamic.
|
||||
|
||||
### 22.2 Runtime write
|
||||
|
||||
```cpp
|
||||
auto result = erased.runtime_write("temperature", typeid(double), &value);
|
||||
```
|
||||
|
||||
Possible results:
|
||||
Runtime write intentionally performs no implicit conversion. `typeid(double)` must exactly match the property's declared value type, and the pointer must address a live value of that exact type for the duration of the call. A successful runtime write uses the same managed write path and synchronization semantics as typed `write`.
|
||||
|
||||
```text
|
||||
ok
|
||||
unknown_property
|
||||
not_readable
|
||||
not_writable
|
||||
type_mismatch
|
||||
```
|
||||
### 22.3 Result contract
|
||||
|
||||
Runtime access has no external/persistence mode. An adapter owns its own policy and chooses whether it calls the intrinsic read/write operation.
|
||||
| Result | Meaning |
|
||||
| --- | --- |
|
||||
| `ok` | lookup and access completed |
|
||||
| `unknown_property` | no schema property has that runtime key |
|
||||
| `not_readable` | the property exists but its intrinsic capability is not readable |
|
||||
| `not_writable` | the property exists but its intrinsic capability is not writable |
|
||||
| `type_mismatch` | runtime write supplied a type different from the declared property value type |
|
||||
|
||||
Runtime read of a stored read-only property follows the same no-lock fast path as typed read.
|
||||
There is no external/persistence access mode and no access-control policy in this API. An adapter decides whether it wants to expose or call runtime read/write; Structive reports only the property's intrinsic capability.
|
||||
|
||||
### 22.4 Why this API is deliberately low-level
|
||||
|
||||
The runtime boundary uses `type_info`, `void*` and a callback because the value type is unknown to the caller at compile time. Core does not impose a universal `variant`, heap-owned `any`, serialization format or conversion registry, because any of those would add ownership and conversion policy that belongs to a higher-level adapter.
|
||||
|
||||
This is therefore an adapter API rather than the preferred business-code API. A higher-level extension may wrap it in domain-specific value containers without changing Structive Core.
|
||||
|
||||
The contract is covered directly by `core/tests/runtime_api_test.cpp`, including every result code, callback metadata, exact-type writes, managed blocking behavior and the read-only zero-lock runtime fast path.
|
||||
|
||||
## 23. Lock policies
|
||||
|
||||
|
||||
+92
-48
@@ -264,8 +264,8 @@ device.write<&Device::temperature>(30.0);
|
||||
也支持编译期 key:
|
||||
|
||||
```cpp
|
||||
auto temperature = device.read_key<"temperature">();
|
||||
device.write_key<"temperature">(30.0);
|
||||
auto temperature = device.read<"temperature">();
|
||||
device.write<"temperature">(30.0);
|
||||
```
|
||||
|
||||
Typed API 由 intrinsic capability 约束。对 `read_only` 调用 write 时,函数在 overload resolution 阶段就不可用。
|
||||
@@ -300,44 +300,51 @@ serial_number -> unsynchronized_slot
|
||||
|
||||
## 14. Synchronization Plan
|
||||
|
||||
### 14.1 Independent
|
||||
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)
|
||||
```
|
||||
|
||||
每个真正需要同步的 Property 各自一个 lock domain。
|
||||
`sync_all_unsynchronized` 是显式放弃同步。Structive 仍提供 managed access,但并发读写造成的线程安全责任由调用方承担。
|
||||
|
||||
### 14.2 Shared
|
||||
### 14.2 编译期 Member Rule
|
||||
|
||||
```cpp
|
||||
synchronization(sync_all_shared)
|
||||
```
|
||||
|
||||
所有真正需要同步的 Property 共用一个 lock domain。
|
||||
|
||||
Read-only stored property 在 slot materialization 前就被排除。
|
||||
|
||||
### 14.3 Unsynchronized
|
||||
|
||||
```cpp
|
||||
synchronization(sync_all_unsynchronized)
|
||||
```
|
||||
|
||||
即使 writable property 也不执行真实锁。
|
||||
|
||||
### 14.4 单 Property Override
|
||||
Schema 在 C++ 中已知时优先使用 member pointer:
|
||||
|
||||
```cpp
|
||||
synchronization(
|
||||
sync_all_independent,
|
||||
sync_unsynchronized<&Device::temperature>()
|
||||
sync_all_shared,
|
||||
sync_independent<&Device::temperature>(),
|
||||
sync_unsynchronized<&Device::debug_counter>()
|
||||
)
|
||||
```
|
||||
|
||||
也支持动态 key 规则。
|
||||
Specification 针对具体 Schema materialize 时会检查 Member 是否真的注册。
|
||||
|
||||
### 14.5 Group
|
||||
### 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(
|
||||
@@ -346,7 +353,21 @@ synchronization(
|
||||
)
|
||||
```
|
||||
|
||||
同组、且真正需要同步的 Property 解析到同一个 lock slot。
|
||||
同一 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
|
||||
|
||||
@@ -373,7 +394,7 @@ Device device{
|
||||
};
|
||||
```
|
||||
|
||||
默认 topology 每类型共享。只有显式 override 的实例才保存紧凑 override layout。
|
||||
默认 topology 每类型只 resolve 一次并共享。只有显式 override 的实例才保存紧凑 override layout。Copy/move construction 保留源对象的 override topology;assignment 保留目标对象已经选择的 topology,因为赋值修改的是对象状态,不应该偷偷改变这个实例的同步策略。
|
||||
|
||||
## 16. 查看 Resolved Synchronization
|
||||
|
||||
@@ -394,7 +415,7 @@ auto slot = device.lock_slot<&Device::temperature>();
|
||||
Resolved_Synchronization_View::unsynchronized_slot
|
||||
```
|
||||
|
||||
Stored read-only property 自动得到这个值。
|
||||
Stored read-only Property 自动得到这个值。`resolved_synchronization()` 和 `lock_slot()` 更适合诊断和框架检查;普通业务代码通常应该通过 `read`、`write`、`lock_shared`、`lock_unique` 表达意图,而不是依赖数字 slot。
|
||||
|
||||
## 17. 静态多属性 Guard
|
||||
|
||||
@@ -402,7 +423,8 @@ Read Guard:
|
||||
|
||||
```cpp
|
||||
auto guard = device.lock_shared<&Device::temperature, &Device::pressure>();
|
||||
auto temperature = guard.get<&Device::temperature>();
|
||||
auto by_member = guard.get<&Device::temperature>();
|
||||
auto by_key = guard.get<"pressure">();
|
||||
```
|
||||
|
||||
Write Guard:
|
||||
@@ -410,23 +432,33 @@ Write Guard:
|
||||
```cpp
|
||||
auto guard = device.lock_unique<&Device::min_speed, &Device::max_speed>();
|
||||
guard.set<&Device::min_speed>(20);
|
||||
guard.set<&Device::max_speed>(120);
|
||||
guard.set<"maximum_speed">(120);
|
||||
```
|
||||
|
||||
Static Guard 会对 slot 去重,并使用稳定 slot 顺序获取锁。
|
||||
Member 和 compile-time key 统一使用同名 `get`/`set`。Capability 直接进入 overload constraint:read-only Property 不能创建 typed unique guard,`requires` 表达式中也不会看到它的 `set`。
|
||||
|
||||
Typed unique guard 要求所有目标 Property intrinsically writable。
|
||||
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">();
|
||||
```
|
||||
|
||||
未知 key 抛 `std::invalid_argument`。
|
||||
Dynamic Guard 服务“目标 Property 集合只有运行期才知道”的场景,因此 typed guard 的编译期错误在这里变成运行时错误:
|
||||
|
||||
Dynamic unique guard 如果遇到 read-only property,也会因为 key 只能运行期确定而运行时拒绝。
|
||||
- 未知 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
|
||||
|
||||
@@ -501,7 +533,7 @@ trusted_accessor_property<&Device::get_value, &Device::set_value>(key<"value">)
|
||||
|
||||
## 22. Runtime Type-Erased Access
|
||||
|
||||
动态 Adapter 使用:
|
||||
`Property_Object_Base` 是动态 Adapter 边界,面向 GUI inspector、serialization adapter、脚本绑定、RPC 层等“只有运行期才知道 Property key”的代码。普通 typed C++ 业务代码仍应优先使用 `read` / `write`。
|
||||
|
||||
```cpp
|
||||
Property_Object_Base& erased = device;
|
||||
@@ -514,31 +546,43 @@ 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` 指针在调用期间必须指向这个精确类型的有效对象。成功写入复用 typed `write` 的同一 managed write 和 synchronization 语义。
|
||||
|
||||
```text
|
||||
ok
|
||||
unknown_property
|
||||
not_readable
|
||||
not_writable
|
||||
type_mismatch
|
||||
```
|
||||
### 22.3 Result Contract
|
||||
|
||||
Runtime access 没有 external/persistence mode。Adapter 自己拥有 policy,然后决定是否调用 intrinsic runtime read/write。
|
||||
| Result | 含义 |
|
||||
| --- | --- |
|
||||
| `ok` | lookup 和访问完成 |
|
||||
| `unknown_property` | Schema 中不存在该 runtime key |
|
||||
| `not_readable` | Property 存在,但 intrinsic capability 不可读 |
|
||||
| `not_writable` | Property 存在,但 intrinsic capability 不可写 |
|
||||
| `type_mismatch` | runtime write 提供的类型和 Property value type 不一致 |
|
||||
|
||||
Stored read-only property 的 runtime read 同样走 no-lock fast path。
|
||||
这里没有 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
|
||||
|
||||
|
||||
Reference in New Issue
Block a user