From aeabf657d4851a0c9a37a479abef94369770ca9f Mon Sep 17 00:00:00 2001 From: Jim Zhou Date: Mon, 3 Aug 2026 11:35:01 +0800 Subject: [PATCH 001/328] [0789] IR Spaghettification Refactor Phase 1 (#4147) --- devel/0789.md | 93 +++++++ moebius/Data/Convert/loro/linear_ir.cpp | 305 +++++++++++++++++++++++ moebius/Data/Convert/loro/linear_ir.hpp | 64 +++++ moebius/tests/Data/linear_ir_test.cpp | 312 ++++++++++++++++++++++++ 4 files changed, 774 insertions(+) create mode 100644 devel/0789.md create mode 100644 moebius/Data/Convert/loro/linear_ir.cpp create mode 100644 moebius/Data/Convert/loro/linear_ir.hpp create mode 100644 moebius/tests/Data/linear_ir_test.cpp diff --git a/devel/0789.md b/devel/0789.md new file mode 100644 index 0000000000..d4dcdf2e4a --- /dev/null +++ b/devel/0789.md @@ -0,0 +1,93 @@ +# [0789] IR Spaghettification Refactor +## Context(背景与动机) + +Mogan STEM 的实时协同(`LORO_ENABLED`)当前把 moebius 文档树 1:1 映射到 Loro 的**可移动树容器(LoroTree)**:每个树节点绑定一个 `mogan_tree_id`(= Loro `TreeID {peer, counter}`),见 [loro_shadow.hpp:33-39](moebius/Data/Convert/loro/loro_shadow.hpp:33)。结构操作(SPLIT/JOIN/INSERT_NODE/REMOVE_NODE)必然让 TreeID 消亡或新建,因为**在 LoroTree 模型里身份与树节点本身耦合**——这正是 ID 漂移、关联光标/选区/批注丢失的根因。 + +代码确认的 churn 点(全部在 [loro_shadow_mod.cpp](moebius/Data/Convert/loro/loro_shadow_mod.cpp)): +- `mirror_split`(L104)为后半段**新建**节点 y_id; +- `mirror_join`(L256)**删除** y_id; +- `mirror_insert_node`(L158)/`mirror_remove_node`(L194)新建/删除 wrapper; +- `mirror_mod` 兜底(L318)整块 delete + re-seed。 + +**目标**:把 body 的同步模型从「树容器」改为「单条线性 LoroText 标记流」。结构不再是 CRDT 直接同步对象,而是标记流的解释结果。SPLIT/JOIN/INSERT_NODE/REMOVE_NODE 变成标记 token 的插入/删除,**所有存活字符的 Loro op-id 保持不变**。 + +--- + +## 目标架构 + +``` +Mogan Tree (tree_rep*) + ↕ tree ↔ linear_ir (Phase 1–2,纯 C++,无 Loro 依赖) +Linear IR (array:OPEN/CLOSE/TEXT/BINARY/MARKER) + ↕ linear_ir ↔ markup 文本 (Phase 1,转义编解码) +单条 markup 字符串 + ↕ FFI(单 LoroText 容器) (Phase 3) +LoroDoc: LoroText "body" + LoroTree(仅 meta-section,不变) +``` + +身份模型变更(Phase 4): +- body 节点身份:`tree_rep*` → **Loro Cursor**(锚定在该节点 OPEN token 首字符的 op-id),存为 hex。Cursor 在并发编辑下自动位移,无需手维护数值偏移。 +- meta-section 身份:保持 `mogan_tree_id`(TreeID)不变。 + +前向镜像不变量:`mirror_mod` 在 **post-apply** buffer 上运行([edit_collab.cpp:96](src/Edit/Modify/edit_collab.cpp:96)),依赖 `raw_apply` 不改 `tree_rep*` 指针——新模型继续复用此不变量,用 id_map(tree_rep*) 定位 LoroText 中的 token 位置。 + +--- + +## 线性 IR 与标记设计 + +### linear_item 类型(Phase 1 新增) +```cpp +enum linear_item_kind { LI_OPEN, LI_CLOSE, LI_TEXT, LI_BINARY, LI_MARKER }; +enum linear_marker_kind { LM_SPLIT }; // MARKER 子类型 +struct linear_item { + linear_item_kind kind; + string label; // OPEN: 标签名或 "generic:"(atomic 为空) + string text; // TEXT: 原文;BINARY: base64 + linear_marker_kind marker; // MARKER 用 +}; +// array 即整篇 body 的线性 IR +``` + +### tree ↔ linear_ir(Phase 1–2,复用 [loro_ir.cpp](moebius/Data/Convert/loro/loro_ir.cpp) 的标签处理) +- `tree_to_linear_ir(t)`:先序遍历。复合/泛型节点 → `OPEN(label) …children… CLOSE`;原子 → `OPEN("") TEXT(content) CLOSE`(**统一包裹**,避免相邻原子串歧义,无需 SEP)。复用 `as_string(L(t))` / `moebius::make_tree_label` / `"generic:"` 前缀逻辑。 +- `linear_ir_to_tree(items)`:栈式解析 OPEN…CLOSE 重建树。 +- 纯函数、无 Loro 依赖,可直接 round-trip 单测(对标现有 `loro_ir_test.cpp`)。 + +### MARKER(SPLIT) 语义(核心) +`SPLIT` 标记出现在某节点内部时,表示「关闭当前最内层节点,并以**相同 label** 开一个新兄弟」。故: +``` +[OPEN(para), TEXT("hel"), MARKER(SPLIT), TEXT("lo"), CLOSE] + → (para "hel")(para "lo") +``` +- **SPLIT 操作** = 在切分偏移处插入 1 个 `MARKER(SPLIT)` token(后半段字符原地保留,op-id 不变)。 +- **JOIN 操作** = 删除该 `MARKER(SPLIT)` token(两段 TEXT 成为同一节点的两个原子子节点,字符 op-id 不变)。 +- **INSERT_NODE**(包裹)= 在内容前后插入 `OPEN(label)` / `CLOSE` 两个 token。 +- **REMOVE_NODE**(脱壳)= 删除对应的 `OPEN` / `CLOSE` token。 + +> 待 Phase 1–2 用 round-trip 锁定的语义点:JOIN 后两段 TEXT 是否需合并为单原子以匹配 moebius `raw_join` 的 `(para "hello")`。若 moebius 期望合并,则 `linear_ir_to_tree` 对「同一节点内相邻 TEXT」做归一化合并;以线性 IR 为唯一真相源,tree 解释与之对齐。 + +### markup 文本编码(Phase 1,进/出 LoroText) +用转义引入符 `\x01`(用户文本中出现的 `\x01`/`\x02` 双写转义,故用户文本永不裸含 `\x01`): +- `OPEN(label)` → `\x01 L