206 lines
15 KiB
Markdown
206 lines
15 KiB
Markdown
# TP Retained/Reactive Text Runtime 目标架构
|
||
|
||
英文版见 [TP Retained/Reactive Text Runtime Target Architecture](retained-runtime-target-architecture-en.md)。
|
||
|
||
公共入口索引见 [API-REFERENCE.md](API-REFERENCE.md);语义合同见
|
||
[API-SEMANTICS.md](API-SEMANTICS.md)。
|
||
|
||
状态:TP 1.0 已实现的架构合同。本文记录已经落地并由测试保护的目标边界;当前模块和公共行为事实分别以 [ARCHITECTURE.md](ARCHITECTURE.md) 与 [API-SEMANTICS.md](API-SEMANTICS.md) 为准。文件名保留 `target-architecture` 以维持既有链接稳定。
|
||
|
||
## 1. 产品定位
|
||
|
||
TP 1.0 是一个可独立安装、测试、发布和使用的 Emacs retained/reactive text runtime:它把声明式属性、响应式数据和稳定文本对象投影到 string 与 buffer,并拥有最终文本属性 diff、marker/index、事务和 buffer publication。
|
||
|
||
TP 不依赖 Ebox,也不包含 Box、Flex、Grid、padding、layout owner 或 Ebox region 等空间布局概念。Ebox、ETAF、dashboard、diagnostics、交互列表和普通 buffer 标注都可以通过同一 TP public API 使用它。
|
||
|
||
TP 也不是 CSS 引擎。完整 stylesheet、selector、specificity、origin/importance、CSS cascade layer、CSS-wide value 和 custom property 由独立 ECSS 包负责;ECSS 可以把最终 computed declarations 交给 TP 发布,但 TP 不 require、检测或命名 ECSS。
|
||
|
||
一句话边界是:
|
||
|
||
> TP 负责对象、依赖、位置、变化和提交;上层 consumer 负责这些对象的业务含义以及期望显示结果。
|
||
|
||
## 2. 设计不变量
|
||
|
||
- TP source、tests、examples 和 package metadata 不 require、检测或命名 Ebox。
|
||
- TP 不拥有 CSS stylesheet、selector matching、specificity 或 cascade winner;这些能力属于独立 ECSS 包。
|
||
- live runtime 只有一套 property、binding、surface、mount、diff 和 transaction core,不增加 embedded mode。
|
||
- TP 是唯一最终 buffer committer;producer 和 consumer 的 prepare 阶段只能产生候选值。
|
||
- 一个 live object 只有一个 TP identity;consumer 可以保存 opaque handle,但不能建立需要长期同步的平行 identity。
|
||
- signal update 从 source 直达 binding,再从 object 直达 marker-backed mount;正常热路径不扫描 `buffer-list` 或按 `tp-name`/`tp-layers` 搜索文本。
|
||
- `tp-name`、`tp-layers`、`tp-meta` 不是 live runtime 数据库;definition、provenance、identity、dependencies 和 mount metadata 都在 side state。
|
||
- capability 只有 `content` 与 `properties` 两种;它们共享同一计算和提交语义,只是写权限不同。
|
||
- 普通函数值是 literal;只有显式 `tp-computed` value source 会执行并收集依赖。
|
||
- signal source、bindings、plans、markers/indexes 和 opaque client state 在 transaction 中使用同一 revision,失败时共同回滚。
|
||
|
||
## 3. 独立数据流
|
||
|
||
```text
|
||
application state / theme / host edit
|
||
│
|
||
▼
|
||
signals + bindings
|
||
│ exact dependency graph
|
||
▼
|
||
TP prepare context
|
||
│ candidate objects/bindings/anchors
|
||
▼
|
||
ordinary or client producer
|
||
│ object → direct property contributions
|
||
│ → generic keyed surface plan
|
||
▼
|
||
reconcile + prepare diff
|
||
│ prepared text/property operations
|
||
▼
|
||
atomic multi-surface publication
|
||
│
|
||
▼
|
||
strings / Emacs buffers
|
||
```
|
||
|
||
prepare 只产生 candidate state。producer 返回普通 surface plan 和可选 opaque client state。TP 不解释 client state,但把它与 plan、dependencies、mounts 和 revision 一起原子晋升。
|
||
|
||
## 4. 属性 contribution、合成与 value source
|
||
|
||
### 4.1 Property policy 与 contribution composition
|
||
|
||
TP 为它最终写入的 Emacs text property 维护通用 property policy:presence、normalizer、validator、equality、merge 和 projector。policy 只回答多个 TP contribution 如何合成、值是否真的变化以及怎样形成最终 Emacs property;它不定义 selector、stylesheet 或 CSS winner。
|
||
|
||
每个 properties mount 只贡献它声明的 property。TP 按稳定的 mount/contribution 顺序逐属性合成 host baseline 与所有 TP contribution,严格区分显式 nil 和 absent,并保存足够的 side state 以便撤销单个 contribution、检测外部冲突和执行事务回滚。这个过程是文本属性 ownership/composition,不是 CSS cascade。
|
||
|
||
ECSS 是独立的纯样式计算包:输入 property schemas、subject tree/adapter 与 stylesheet,输出 computed declarations 和 provenance,不读写 buffer、不持有 marker,也不依赖 TP 或 Ebox。Ebox 或普通文本 UI 若需要完整 CSS 能力,先调用 ECSS,再把最终文本/属性计划交给 TP;不需要 CSS 的调用者直接使用 TP。
|
||
|
||
### 4.2 Literal 与 computed
|
||
|
||
普通 Elisp value 永远是 literal,包括 function object。`help-echo` function、keymap command 和 callback 不会被 TP 隐式调用。
|
||
|
||
需要求值的声明必须使用 `tp-computed` 包装 compute function。compute 在所属 object binding 的 prepare context 中运行;`tp-signal-read` 和 `tp-binding-read` 自动登记依赖。返回值随后按 property policy normalize/validate,并按 literal 处理,不隐式调用第二次。错误终止整个 candidate transaction;相等结果不产生 surface change。
|
||
|
||
上层 DSL 可以接受可求值 Elisp,但必须在 constructor/compiler 边界明确区分 quoted list、literal function 和 computed source,并把响应式计算编译到 `tp-computed`,不能建立第二套 watcher。
|
||
|
||
## 5. Stable object 与 prepare context
|
||
|
||
每个 object 的 identity 只在一个 surface 中有效,由 parent identity、sibling key 和 opaque kind 保留。显式 key 在同一 parent 下唯一;unkeyed object 只能按 position+kind 保留;handle 不能跨 surface 使用。
|
||
|
||
TP 在 mount/update prepare 开始时创建短生命周期 prepare context。producer 调用:
|
||
|
||
```elisp
|
||
(tp-object-ensure context parent key kind)
|
||
```
|
||
|
||
匹配的 live object 返回原 handle;新增 object 只存在于 candidate state。duplicate key、stale parent、orphan object 或 cross-surface handle 在 prepare 中失败。只有成功 publication 才把 candidate identity 变成 live identity;失败 handle 必须不可解析。
|
||
|
||
`tp-surface-materialize-string` 给 producer 提供相同语义的 ephemeral context。object/binding 只在这次纯计算中存在,string 返回后全部释放,不进入 live registry。
|
||
|
||
`tp-object-resolve` 只读地按 surface 与 key path 解析 live handle,用于公开选择/交互入口,不创建对象。
|
||
|
||
## 6. Binding 与真正的响应式依赖
|
||
|
||
definition 是可复用配方,binding 是安装在一个 object 上的具体计算。`tp-bind` 以 object handle 加 caller-namespaced binding key 幂等安装,至少保存 compute、last successful value、dependencies、dirty state、revision 和 lifecycle policy。
|
||
|
||
`tp-binding-read` 读取另一个 binding 的 memoized value并登记 binding→binding dependency。因此 signal、computed property、measurement 或普通 derived value 可以组成真实依赖图,而不是在变量变化后扫描 buffer。
|
||
|
||
重新计算前撤销旧依赖,成功后以本次实际读取集合替换。conditional branch 因而会自动断开不再使用的 source。transaction dirty queue 去重,同一 binding 每批最多重算一次;循环报告完整路径。
|
||
|
||
每次 prepare 记录 touched object/binding。没有可见字符但仍属于 candidate 的 logical object 必须显式 retain;一个 logical object 可以通过 prepare-only attachment 对应多个不连续 plan fragment,attachment 不进入 plan。删除 object 时同步清理 bindings、subscriptions、anchors 和 mounts;仍存在但本次未声明的 binding 默认删除,只有显式 lifecycle policy 才能保留。普通 binding 可返回任意 client value,只有 designated producer binding 返回 plan/subplan。
|
||
|
||
## 7. Surface plan
|
||
|
||
surface plan 是不可变语义的纯数据,节点字段为:
|
||
|
||
| Field | Contract |
|
||
| --- | --- |
|
||
| `key` | sibling-local stable key |
|
||
| `kind` | opaque comparable discriminator |
|
||
| `text` | optional plain/propertized string leaf |
|
||
| `props` | final direct Emacs text properties |
|
||
| `children` | ordered child plans |
|
||
| `tags` | opaque side metadata indexed but not interpreted by TP |
|
||
| `capability` | `content` or `properties` |
|
||
|
||
plan 不携带 marker、buffer position、patch op、producer closure、binding closure 或 client continuation。合法的 keymap/help-echo/callback function 可以作为最终 property value 保留,reconciler 永不调用它们。
|
||
|
||
public constructor 对 caller-owned plist/string 做 defensive copy 或提供 immutable semantics;commit 后修改原值不能改变已发布 surface。
|
||
|
||
## 8. Mount、range anchor 与属性所有权
|
||
|
||
### 8.1 Content capability
|
||
|
||
`content` mount 拥有其 span 的文本和属性,可以插入、删除、移动、替换文字以及修改属性。它适合 dashboard、menu、special UI buffer 和高级 renderer output。
|
||
|
||
### 8.2 Properties capability
|
||
|
||
`properties` mount 只能贡献声明的属性,不能修改 host text。`tp-range-anchor-create` 从 buffer、start/end 和 marker insertion policy 创建 opaque marker-backed anchor;producer 在 prepare 中调用 `tp-object-attach-range` 把 object 绑定到 anchor。plan 本身仍没有位置。
|
||
|
||
TP side state 为每个 anchor/property interval 保存 host baseline、各 TP contribution 和最后一次 published value。同一 surface 的重叠 contribution 通过 property policy 逐属性合成。
|
||
|
||
外部代码把同一 property 改成不同于 TP 最后发布值时,下一次 prepare 默认报 `tp-property-conflict`。TP 不覆盖外部值,也不恢复旧 snapshot;调用者显式 `tp-range-rebase` 或 unmount。unmount 只撤销 TP contribution:当前值仍等于 TP 最后发布值时恢复 baseline,否则保留外部值并在 report 中记录 conflict。
|
||
|
||
host text 编辑由 live markers 跟随。跨越或删除 anchor 边界时按显式 shorten/remove/stale policy 处理;TP 不扫描附近文本猜测新位置。
|
||
|
||
## 9. Reconcile 与 transaction
|
||
|
||
TP 按 parent/key reconcile object identity,并对 old/new plan 计算 child insert/remove/reorder、text diff 和 property-run diff。normal update 从 object→mount index 直接取得一个或多个范围;公开查询只返回数值 range/tag snapshot,不暴露 live marker。
|
||
|
||
transaction 顺序是:冻结 candidate signal writes;建立 prepare contexts;准备所有实际受影响 surfaces;验证 capability/conflict/lifecycle;生成 per-surface operations 与 inverse journals;按稳定 surface id publish;最后原子切换 source values、bindings、plans、indexes、client state 和 revisions;全部成功后才运行 observers。
|
||
|
||
一个 global signal 可以触达多个 surfaces。TP 必须先全部 prepare,再逐 surface publish;任一 compute、buffer write、marker/index step 或 transaction participant 失败时,回滚已经发布的 surfaces,并恢复 signal values、binding values/dependencies、dirty queues 和 revisions。observer failure 发生在成功 commit 后,只记录而不回滚。
|
||
|
||
若 publish 中 buffer 被 kill,kill-buffer teardown 是不可逆权威结果;其他 surface 与 source state 回滚,TP 不复活 killed buffer。
|
||
|
||
## 10. Public API 层级
|
||
|
||
### 10.1 简单的一次性属性
|
||
|
||
```elisp
|
||
(tp-propertize string declarations)
|
||
(tp-apply buffer start end declarations)
|
||
```
|
||
|
||
二者使用相同的 direct-declaration validation/projection 和 mutation primitives,不建立 identity、binding 或 live mount。现有 `tp-set`、`tp-reset`、`tp-add`、`tp-remove`、lookup/search APIs 继续复用这些底层语义。
|
||
|
||
### 10.2 响应式已有文本
|
||
|
||
```elisp
|
||
(tp-watch buffer start end compute)
|
||
```
|
||
|
||
`tp-watch` 创建 properties range anchor、surface object 和 binding,COMPUTE 返回 declarations,结果是可 update/unmount 的 opaque handle。普通用户不需要手工创建 object、binding 或 surface。
|
||
|
||
### 10.3 完整 retained UI
|
||
|
||
```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)
|
||
(tp-surface-unmount surface)
|
||
```
|
||
|
||
`tp-surface-update-scoped` 是一次事务内基于 retained object identity 的授权。TP 通过 mount index 解析对象,支持一个对象拥有多个离散 mount,验证完整 candidate 不会修改授权范围之外的输出,然后在同一回滚边界内发布 text、direct properties、mount、index、client state 和 revision。scope 不写入文本,也不会形成第二套响应式 runtime。默认 mismatch 直接报错;调用者可显式选择 full-root fallback。
|
||
|
||
完整 API 还包括 `tp-computed`、signals、prepare/object、bindings、range anchors、transactions、materialize-string、at-point/inspect/report。TP 不增加含义重叠的 `tp-mount`/`tp-update` aliases,也不公开 raw marker/patch/change-set、domain dirty kind 或 `:impact` APIs。
|
||
|
||
## 11. 独立验收场景
|
||
|
||
TP 1.0 在没有 Ebox load-path 时必须长期通过:
|
||
|
||
1. static string/buffer properties:nested face、keymap、help-echo function、explicit nil;
|
||
2. reactive status:`tp-watch` 与 retained content producer、conditional/binding dependency、batch;
|
||
3. retained dashboard:keyed add/remove/reorder、button、signal-driven theme declarations、rollback;
|
||
4. existing-text diagnostics:range anchor、host edit、overlap、external property conflict/rebase/unmount;
|
||
5. two-buffer producer:identity/state 隔离与 global-signal multi-surface atomicity;
|
||
6. performance:sparse update 与实际 dependency closure 成正比,equal value 零 buffer op,unmount 后无 marker/subscription/weak-reference 泄漏。
|
||
|
||
## 12. 兼容、版本与删除
|
||
|
||
TP 1.0 是主版本切换。能直接映射到统一 core 的静态 public API 保留;依赖 inline `tp-layers`/`tp-meta` database 和扫描式 renderer 才能工作的 managed behavior 必须明确删除或替换,不保留 hidden compatibility engine。
|
||
|
||
当前没有 legacy import API;调用者必须显式提供 plan 或 producer。TP 不会扫描历史 propertized text 来重建 surface identity。
|
||
|
||
当前实现已经删除 layer→buffer registry、scan-driven refresh hooks、旧 inline managed codec、重复 transaction 和旧 batch renderer。TP 1.0 package、tests、examples 和本文档均可在没有 Ebox repository 的环境中独立工作。
|
||
|
||
## 13. 已冻结并实现的合同
|
||
|
||
TP 1.0 的 executable contract tests 已确定并持续保护:prepare-context/object 时序;binding identity/lifecycle;literal/computed value;range anchor 和 property conflict;single/multi-surface rollback;三层便利 API;error taxonomy;report shape;explicit nil/absence;read-only、undo、narrowing、indirect-buffer 和 kill-buffer 行为。
|
||
|
||
任何实现若要求 Ebox/ECSS-specific branch、CSS selector/cascade winner、post-commit identity scan、plan 中的 raw position/closure、第二套 renderer 或无法安全撤销 properties contribution,应停止接入并重新评审 ownership model,而不是增加 adapter mode。
|