269 lines
18 KiB
Markdown
269 lines
18 KiB
Markdown
# TP 2.0 API Semantics
|
||
|
||
本文记录 TP 2.0 当前公共 API 的 ownership、presence、响应式、retained surface、事务与失败合同。它描述已经实现的行为;目标背景与设计理由见 [retained runtime architecture](retained-runtime-target-architecture.md)。
|
||
|
||
完整的公共符号、参数形状、返回值和示例见 [API reference](API-REFERENCE.md)。
|
||
|
||
## 1. 产品边界
|
||
|
||
TP 负责最终 Emacs text property policy/contribution、signals/bindings、stable objects、marker-backed mounts、surface diff、transaction、rollback 和 Buffer publication。
|
||
|
||
TP 不实现 selector、stylesheet、specificity、origin/importance、CSS cascade layers、CSS-wide values、custom properties 或 Box/Flex/Grid。需要 CSS cascade 的调用者先通过独立 ECSS 获得 final declarations,再交给 TP。
|
||
|
||
## 2. 普通值、显式 nil 与 computed source
|
||
|
||
普通 Elisp value 始终是 literal,包括 function object、keymap command、`help-echo` callback 和 list。TP 不会因为一个值可调用就执行它。
|
||
|
||
需要求值的 declaration 必须显式使用:
|
||
|
||
```elisp
|
||
(tp-computed (lambda () ...))
|
||
```
|
||
|
||
`tp-resolve-value` 只执行这种 tagged source。compute result 只 normalize/project 一次,不会被隐式二次调用。compute error 直接终止 candidate transaction。
|
||
|
||
TP 严格区分:
|
||
|
||
- absent:property plist 中没有该 key;
|
||
- present nil:property key 存在,value 为 `nil`。
|
||
|
||
`tp-member` 和底层 contribution ledger 使用 presence-aware 语义。properties mount 的显式 nil contribution 可以覆盖 baseline,但 unmount 仍只撤销 TP 自己拥有的 contribution。
|
||
|
||
## 3. Property policy 与 direct declarations
|
||
|
||
`tp-define-property-policy` 为一个 canonical native property id 注册:
|
||
|
||
- normalizer;
|
||
- validator;
|
||
- equality;
|
||
- merge;
|
||
- projector。
|
||
|
||
注册是原子的:无效 options 或 function 会报 `tp-invalid-property-policy`,旧 definition 保持不变。
|
||
|
||
`tp-register-text-property` 为原生 Emacs property 建立默认 policy,并返回 policy record。`tp-text-property-id` 把原生 property 映射到 canonical `text/PROPERTY` id;`tp-text-declarations` 把普通 property plist 转换为 canonical declarations。
|
||
|
||
`tp-merge-declarations` 按输入顺序合并 direct declaration groups,并防御性复制 caller-owned value。它只做 TP contribution composition,不实现 CSS winner selection。
|
||
|
||
`tp-define-style`、`tp-style-declarations` 和 `tp-undefine-style` 管理 named direct declarations。registry getter 返回防御性副本。
|
||
|
||
## 4. Declaration recipes
|
||
|
||
`define-tp` 定义一个返回 native property plist 的 recipe;`define-tps` 定义一组有序 recipe elements。`tp-define-layer`、`define-tp-group` 与 `tp-define-group` 是同一静态 declaration workflow 的命名入口。
|
||
|
||
Recipe 可以是静态或参数化的,可以组合其他 recipes。展开结果经过同一 direct property policy/projector。Recipe application 不建立 live identity,不写 `tp-name`、`tp-layers` 或 `tp-meta`。
|
||
|
||
旧 `$variable` syntax 会报 `tp-invalid-layer-definition`。响应式值必须使用 `tp-computed` 加 signal/binding,不能建立第二套 watcher runtime。
|
||
|
||
Recipe/group definition 与 redefinition 是原子的:definition body、generated named elements 或 compiled style 任一步失败时,不留下半个新 definition,已有 definition 保持可用。
|
||
|
||
## 5. 一次性 façade
|
||
|
||
### 5.1 `tp-propertize`
|
||
|
||
```elisp
|
||
(tp-propertize STRING DECLARATIONS)
|
||
```
|
||
|
||
返回新的 propertized string,不修改输入 STRING,不创建 object、binding、anchor、mount 或 surface。
|
||
|
||
### 5.2 `tp-apply`
|
||
|
||
```elisp
|
||
(tp-apply BUFFER START END DECLARATIONS)
|
||
```
|
||
|
||
只修改 BUFFER 的 `[START, END)` 文本属性,不替换文字,成功返回 `(START . END)`。无效 buffer 不会退回 current buffer;无效 range 直接报错。
|
||
|
||
### 5.3 Direct operations
|
||
|
||
`tp-set`、`tp-reset`、`tp-add`、`tp-remove`、`tp-clear`、`tp-get`、`tp-at` 与 `tp-member` 保留既有 string/buffer 调用形状,但统一经过 direct property resolution。
|
||
|
||
- whole-string `tp-set`/`tp-reset`/`tp-add` 返回新 string;
|
||
- 带 range 的 string 操作按各函数 docstring 的 mutation contract 执行;
|
||
- buffer 坐标使用 Emacs 原生 1-based position;
|
||
- string 坐标使用 0-based index;
|
||
- direct buffer write 遵循 Emacs read-only、undo 和 modified semantics。
|
||
|
||
Search/match/regexp/navigation/query API 继续委托 Emacs 原生 text-property interval 语义,不创建 retained runtime。
|
||
|
||
## 6. Signals 与 bindings
|
||
|
||
`tp-signal-create` 返回 global 或 buffer-scoped signal。`tp-signal-read` 在当前 binding computation 中登记依赖;`tp-signal-peek` 只读值而不登记依赖;`tp-signal-set` 在 transaction 中设置 candidate value。
|
||
|
||
相等写入按 signal equality 返回 no-op,不 invalidates subscribers。buffer-scoped signal 随 buffer kill 自动 dispose;global signal 使用 `tp-signal-dispose` 显式释放。
|
||
|
||
`tp-bind` 的 identity 是 owner 加 caller-namespaced key。相同 owner/key 幂等复用 binding。binding 保存 last successful value、dynamic dependencies、dirty/revision state 和 lifecycle policy。
|
||
|
||
`tp-binding-read` 读取 memoized binding value并登记 binding-to-binding dependency。每次 recompute 成功后,本次实际读取集合替换旧 dependencies;条件分支因此自动断开旧 source。cycle 报 `tp-binding-cycle` 并包含 dependency path。
|
||
|
||
删除 owner 会释放 bindings、subscriptions 和下游 edges。`tp-reactive-counters` 只读报告 invalidated、recomputed、skipped、subscription-added 和 subscription-removed,用于结构性性能验收,不暴露内部 hash tables。
|
||
|
||
## 7. Surface plan 与 object identity
|
||
|
||
`tp-surface-plan-create` 的公共字段是:
|
||
|
||
| Field | Contract |
|
||
| --- | --- |
|
||
| `key` | sibling-local stable key;同一 parent 下不可重复 |
|
||
| `kind` | opaque comparable discriminator |
|
||
| `text` | optional plain/propertized string leaf |
|
||
| `props` | final direct Emacs properties |
|
||
| `children` | ordered child plans |
|
||
| `tags` | opaque indexed metadata,TP 不解释其业务含义 |
|
||
| `capability` | `content` 或 `properties` |
|
||
|
||
Plan 不允许 marker、buffer position、patch op、producer closure 或 binding closure。Constructor 防御性复制 caller 提供的 key、kind、text、props、children list 与 tags,使 caller 后续 mutation 不改变 committed plan。`tp-surface-result-create` 只携带 plan 与 opaque client state;TP 在 prepare transaction 内从该 plan 生成唯一的 authoritative rendered output,不接受第二份 caller-provided text 或 traversal records。Result 与 producer normalization 会在各自边界建立防御性 plan snapshot。Producer 如果为当前 candidate freshly allocate 了完整 plan tree,并且之后不再 mutation 或 expose,可使用 `tp-surface-plan-create-owned` 与 `tp-surface-result-create-owned` 将 plan 所有权转给 TP,跳过重复 snapshot;owned result 必须绑定当前 active prepare context,并且只允许被 prepare normalization 消费一次;普通 caller-owned plan 必须使用复制入口。
|
||
|
||
producer 在 prepare 阶段接收 context,并在产生 plan 前调用:
|
||
|
||
```elisp
|
||
(tp-object-ensure CONTEXT PARENT KEY KIND)
|
||
```
|
||
|
||
Object identity 只在所属 surface 中有效。显式 key 按 parent/key/kind reconcile;unkeyed object 按 position/kind reconcile。duplicate sibling key、stale parent、orphan object 或 cross-surface handle 在 prepare 中失败。
|
||
|
||
Candidate object 只有成功 publication 后才变为 live。失败 candidate handle 必须不可解析。`tp-object-resolve` 按 surface/key path 查询 live handle,不创建 identity。
|
||
|
||
无可见字符但需要保留的 logical object 使用 `tp-object-retain`。一个 logical object 可以通过 `tp-object-attach-fragment` 关联多个离散 plan fragments;attachment 保存在 prepare side state,不进入 plan。
|
||
|
||
当 producer 已经证明一个现有 logical subtree 在本次 candidate 中保持不变,可以使用 `tp-object-retain-subtree` 复用当前 live root 下已知的 object path。该 API 会把 subtree objects 标记为 touched/retained,并保留其 live bindings;它不创建缺失对象,也不替 producer 验证 candidate plan、输出范围或布局不变性。调用方仍必须提交完整 candidate plan,并在不满足证明时回退到普通 `tp-object-ensure` 路径。若完整 candidate 仍会重新提交这些对象的 output attachment,但已证明它们的 identity、lifecycle 和 output 不变,可使用 `tp-object-reuse-subtree`:对象保留在 candidate 中但不进入 touched/reconcile 集合。
|
||
|
||
Content range ownership 有两个入口:`tp-object-attach-content-ranges` 对每个 `:tags` 建立防御性快照;只有当 producer 为当前 candidate 新建了 tags、之后不会再修改或暴露它们时,才可使用 `tp-object-attach-content-ranges-owned` 转移这些值的所有权并跳过复制。转移入口不改变失败 candidate 的 rollback 语义;未成功 publication 的值会随 candidate 一起丢弃。普通 caller-owned tags 必须使用复制入口。
|
||
|
||
`tp-surface-report` 始终返回完整的防御性 report snapshot。只需要 transaction、operation、scope 或 object reconciliation 计数等提交标量的 integration caller 应使用 `tp-surface-report-summary`,它不暴露 nested report payload,也不为读取这些标量复制整份 report。
|
||
|
||
## 8. Surface lifecycle
|
||
|
||
### 8.1 Materialize
|
||
|
||
```elisp
|
||
(tp-surface-materialize-string PLAN-OR-PRODUCER)
|
||
```
|
||
|
||
以 ephemeral prepare context 生成 propertized string,不建立 live surface。函数返回前释放 candidate objects、bindings、subscriptions 和 anchors。
|
||
|
||
### 8.2 Mount/update
|
||
|
||
```elisp
|
||
(tp-surface-mount BUFFER PLAN-OR-PRODUCER OPTIONS)
|
||
(tp-surface-update SURFACE PLAN-OR-PRODUCER)
|
||
(tp-surface-update-scoped SURFACE OBJECTS PLAN-OR-PRODUCER OPTIONS)
|
||
```
|
||
|
||
Mount options 支持 `:capability`、`:start`、`:end`、`:inhibit-read-only`、`:client-state` 和 `:observers`。
|
||
|
||
`content` surface 拥有其 span 的 text 和 properties,可以插入、删除、替换或移动输出。`properties` surface 只能贡献声明的 properties,不能替换 host text。
|
||
|
||
`tp-surface-update-scoped` 仍接收完整 candidate。TP 从 object-to-mount index 得到授权范围,验证 candidate 没有改变范围外输出,再在同一 transaction 发布。默认 mismatch 报 `tp-scope-mismatch`;`(:on-mismatch root)` 显式允许 full-root fallback。默认返回 defensive report snapshot;调用方如果只通过 `tp-surface-report-summary` 读取标量提交指标,可传 `(:return-report nil)` 避免构造完整 report snapshot。
|
||
|
||
相等 candidate 不产生 publication,surface revision 和 buffer modified state 保持不变。
|
||
|
||
### 8.3 Unmount
|
||
|
||
`tp-surface-unmount` 释放 surface、objects、bindings、mounts、markers、subscriptions、indexes 和 opaque client state,并返回 generic report。content surface 删除自己拥有的 span;properties surface 只撤销仍由 TP 拥有的 contributions。
|
||
|
||
kill-buffer cleanup 以 buffer 死亡为权威结果,释放 runtime state,不尝试复活 buffer。
|
||
|
||
## 9. Range anchors 与 property conflicts
|
||
|
||
`tp-range-anchor-create` 接受 buffer、start/end、marker insertion policy 与 `stale`/`shorten`/`remove` boundary policy,返回 opaque handle。raw marker 和 position 不进入 surface plan。
|
||
|
||
producer 使用 `tp-object-attach-range` 把 object 绑定到 anchor。一个 properties surface 的重叠 mounts 通过 property policy 合成 contributions。
|
||
|
||
TP 为每个 interval 保存:
|
||
|
||
- host baseline presence/value;
|
||
- ordered TP contributions;
|
||
- last published presence/value。
|
||
|
||
如果当前 host value 与 TP last published value 不同,update 报 `tp-property-conflict`,不会覆盖外部值。`tp-range-rebase` 显式把当前 host state 接受为新 baseline。Unmount 只在当前值仍等于 last published value 时恢复 baseline,否则保留 host value并在 report 中列出 conflict。
|
||
|
||
## 10. `tp-watch`
|
||
|
||
```elisp
|
||
(tp-watch BUFFER START END COMPUTE)
|
||
```
|
||
|
||
`COMPUTE` 返回 native direct declarations。`tp-watch` 组合 range anchor、properties surface、stable object 和 binding,返回 underlying surface handle。COMPUTE 中读取的 signals/bindings进入正常 dependency graph;更新与 unmount 使用同一 conflict和rollback合同。
|
||
|
||
## 11. Transactions
|
||
|
||
`tp-with-transaction` 的顺序是:
|
||
|
||
1. 保存 candidate signal writes 并去重 dirty bindings;
|
||
2. 为所有实际受影响 surfaces 建立 prepare contexts;
|
||
3. 运行 binding graph 与 producers;
|
||
4. 校验 object、plan、capability、range、conflict 与 lifecycle;
|
||
5. 为所有 surfaces 准备 text/property operations 与 inverse journals;
|
||
6. 默认从 publication batch 的 exact entry bindings 按稳定 surface id publish;
|
||
7. 从 batch 绑定的同一 participant vector 按声明顺序 stage participant,再执行
|
||
declared precommit;
|
||
8. commit signal journal;
|
||
9. 在 candidate 绑定的 single final accept 内按顺序 apply bounded opaque markers,再 accept
|
||
change group;marker 只能使用 closed `tp-vector-slots/v1` fixed-write
|
||
primitive,不能注册 callback;partial apply 或 accept failure 先逆序
|
||
restore markers;
|
||
10. final accept 成功后固定写入 tagged success,再运行 contained
|
||
committed/observer work。
|
||
|
||
嵌套 transaction 加入最外层。一个 global signal 可以原子触达多个 buffers;任一 surface 失败时,已发布 surfaces 和 source/binding state 全部回滚。
|
||
|
||
`tp-transaction-participate-v2` 允许 client side state 在 surfaces 发布后、
|
||
source commit 前加入同一 rollback boundary。调用方通过 `:key`、`:stage` 和
|
||
`:rollback` 注册 structured participant;返回值仍是 key,内部 participant
|
||
identity、state 与 journal 不暴露。participant key 在一个 outer transaction
|
||
中必须唯一。它不是 observer;失败会回滚 transaction。Observer failure 只记录,
|
||
不回滚已提交结果。
|
||
|
||
publication batch、structured participant、final marker 与 tagged outcome 共享
|
||
现有 journal/change-group,不复制第二份 live state。publication batch 是唯一
|
||
live writer:surface 从 candidate entries 执行,participant 从 candidate
|
||
绑定的同一 identity vector 执行,final accept 从 candidate binding 执行;任何
|
||
binding/order 漂移都会 fail-fast 并回滚。TP 2.0 不再提供 alternate writer、
|
||
execution route 或 artifact-mode switch。`tp-with-transaction` 的返回值仍是 body result;
|
||
success/failure outcome 只走内部 side channel。zero-surface 与 output-equal
|
||
operation 不创建 publication batch。
|
||
|
||
ETAF uses this API with one opaque participant for its immutable generation and
|
||
Ebox client state. The participant is published only after candidate
|
||
preparation, and its paired rollback is still required when TP final accept
|
||
fails. This keeps ETAF generation visibility, Ebox publication, signals, and
|
||
client state on one commit boundary; ETAF's own flush guard is responsible for
|
||
detecting repeated input/version tuples and bounded non-convergence.
|
||
|
||
## 12. Diagnostics 与 reports
|
||
|
||
`tp-surface-report` 返回最近一次成功 publication 的防御性 report;equal/no-op update 不替换 report。字段包括 transaction/surface/revision、candidate source writes、binding counters、object reconcile counts、text/property operation counts、touched characters、scope/full-root 和 failure-related slots。
|
||
|
||
`tp-surface-inspect` 返回 surface id、buffer、capability、revision、object/mount count、opaque client state 与 report。`tp-surface-at-point` 从 side index 返回 mounted objects;它不扫描显示文本寻找 identity。`tp-object-mounts` 返回 defensive numeric range/tag snapshots,不暴露 live markers。
|
||
|
||
## 13. Error ownership
|
||
|
||
Surface publication 还会使用 `tp-invalid-content-range`、
|
||
`tp-producer-buffer-mutation`、`tp-publication-mismatch` 和
|
||
`tp-owned-result-error`;它们分别归属于 content range 校验、prepare
|
||
buffer guard、publication journal 和 owned producer result 合同。
|
||
|
||
主要错误类型:
|
||
|
||
- property/declaration:`tp-property-error`、`tp-invalid-property-policy`、`tp-invalid-declaration`、`tp-invalid-layer-definition`、`tp-unresolved-layer`;
|
||
- reactive:`tp-reactive-error`、`tp-invalid-signal-scope`、`tp-disposed-signal`、`tp-disposed-binding`、`tp-binding-cycle`;
|
||
- retained surface:`tp-surface-error`、`tp-invalid-surface-plan`、`tp-duplicate-object-key`、`tp-invalid-prepare-context`、`tp-stale-object`、`tp-cross-surface-object`、`tp-orphan-object`、`tp-capability-error`、`tp-stale-mount`、`tp-property-conflict`、`tp-dead-surface`、`tp-invalid-range-anchor`、`tp-invalid-content-range`、`tp-producer-buffer-mutation`、`tp-publication-mismatch`、`tp-owned-result-error`、`tp-scope-mismatch`。
|
||
|
||
内部 computation 不吞错或返回貌似合理的 fallback。只有用户入口和 batch test runner等外层边界负责把错误转换为展示信息。
|
||
|
||
## 14. 1.0 删除项
|
||
|
||
TP 1.0 删除了不能诚实映射到统一 retained runtime 的 0.3 managed behavior:
|
||
|
||
- `tp-render.el` 和 `tp-stack.el`;
|
||
- stack push/pop/move/hide/show/merge/flatten workflow;
|
||
- `tp-text` 双向内容替换;
|
||
- `$variable` declaration syntax;
|
||
- layer-to-buffer registry、buffer-list/identity scan refresh;
|
||
- managed attach/detach/diagnostics/transaction;
|
||
- 以 `tp-name`、`tp-layers`、`tp-meta` 作为字符上权威 runtime database 的机制。
|
||
|
||
TP 不提供 hidden compatibility engine,也不根据文本是否含旧 metadata 自动切换执行语义。静态 recipe、`tp-watch` 和 retained surface 分别承担复用声明、已有文本响应式属性与 retained content 的清晰职责。
|