与 mxGraphModel 变更的映射
变更事件:
START_EDIT→ 多次EXECUTED→END_EDIT/CHANGE。典型变更类:
mxChildChange→ 在父元素children中增删/重排mxCell或层次结构。mxGeometryChange→ 更新对应mxGeometry子元素的attrs(如 x/y/width/height、points)。mxTerminalChange→ 更新mxCell@source/@target(或相关引用属性)。mxStyleChange→ 更新mxCell@style。mxValueChange→ 更新mxCell@value(或text)。- 其他:
mxVisibleChange、mxCollapseChange、mxRootChange等按属性/结构映射。
事务对齐:将
model.beginUpdate()/endUpdate()封装为ydoc.transact(),把同一事务内的多次变更合并为单个 Yjs 更新广播,减少抖动。
方案 A vs 方案 B(优化对比)
方案 A:最小改造,保留 Drawio 补丁管线
- 思路:
- 继续用
DrawioFile/DrawioFileSync现有diffPages/patchPages补丁格式与逻辑。 - 用 Yjs 仅承载“补丁消息”的分发与持久化,例如
Y.Array<{ts, clientId, patch}>。
- 继续用
- 优点:
- 对现有代码入侵极小;快速上线;复用既有冲突兜底(LWW+checksum)。
- 享受 Yjs 网络层(y-websocket / y-webrtc)和离线重放能力。
- 局限:
- 补丁仍为文本/结构差异,合并粒度粗;并发编辑仍主要依赖补丁序列化顺序与 LWW。
- 与 Yjs 的 CRDT 优势未完全发挥,存在双重“补丁/CRDT”叠加复杂性。
- 适用:
- 需要快速验证与上线;对并发冲突质量要求一般;现有补丁生态必须保留。
- 思路:
方案 B:完全 CRDT 化,使用 yXMLElement
- 思路:
- 用
yXMLElement直接表示页面 XML(mxGraphModel→yXMLElement树)。 - 将
mx*Change事件映射为对yXMLElement的属性与children的原子更新; 远端变更通过 Yjs 观察器反向驱动mxGraphModel(构造并execute对应 change)。
- 用
- 优点:
- 字段级/节点级冲突自动合并;数组顺序冲突由 CRDT 解决。
- 离线/重连/晚加入自然一致;无需额外 LWW 兜底。
- 代价:
- 需要双向桥接(mxGraphModel ↔ yXMLElement)与不变式维护(如
maintainEdgeParent)。 - 初期集成复杂度高、测试面大(几何、终端、样式、分层等全覆盖)。
- 需要双向桥接(mxGraphModel ↔ yXMLElement)与不变式维护(如
- 适用:
- 并发编辑强一致与细粒度合并是核心诉求;可投入更多集成成本。
- 思路:
推荐迁移路径:
- A 方案落地(快速见效,验证网络与离线能力)。
- 分模块/字段渐进替换为 B(优先几何/样式/终端等高频字段)。
- 全量切换至 B,并淘汰旧补丁协议。
导入与回放(B 方案关键流程)
导入 XML → yXMLElement:
- 解析根节点为
yXMLElement{ tag:'mxGraphModel', attrs, children }。 mxCell作为children中的元素;其mxGeometry作为其children的一员; 属性如id/vertex/edge/style/source/target/value/visible放入attrs。
- 解析根节点为
本地变更 → Yjs:
- 监听
model.addListener(mxEvent.CHANGE, ...),遍历edit.changes:mxChildChange:在父节点的children: Y.Array执行insert/delete/move。mxGeometryChange:定位mxGeometry子元素,更新attrs(x/y/width/height/points)。mxTerminalChange:更新mxCell@source/target。mxStyleChange/mxValueChange:更新attrs.style / attrs.value或text。
- 外围用
ydoc.transact(() => { ... })批量提交。
- 监听
远端变更 → mxGraphModel:
- 观察
yXMLElement(对attrs/children/text的更改),将其翻译为相应mx*Change并execute:- 例如某
children插入了一个mxCell→ 构造mxChildChange插入。 - 某几何属性变化 → 构造
mxGeometryChange。
- 例如某
- 观察
不变式与引用:
- 通过
id管理引用关系(终端、父子、跨层);保持与mxGraphModel.cells一致。 - 对
maintainEdgeParent/ignoreRelativeEdgeParent等语义,优先通过execute(change)路径触发框架内置维护。
- 通过
持久化与网络
- Provider:
y-websocket或y-webrtc;离线y-indexeddb。 - 房间命名建议:
diagram:<fileId>:<pageId>(分页隔离)。 - 元数据:
Y.Map承载当前页、光标/选区(awareness)。
测试清单(B 方案)
- 顶点/边的新增、删除、复制、粘贴、分层移动。
- 边连接/断开、终端切换、多端同时编辑端点。
- 几何移动/缩放、旋转(若有)、多端冲突编辑。
- 样式编辑并发(颜色、连线样式、文本样式)。
- 大图性能:批量移动 1000+ 节点、撤销/重做与事务边界。
- 离线编辑 → 重连后正确合并;晚加入完整回放。
总结
- 采用统一
yXMLElement+children: Y.Array的抽象,可自然映射 Drawio XML,解耦补丁协议并获得字段级 CRDT 合并。 - 推荐以 A 方案快速上线,再分步迁移到 B 方案以获得最佳并发一致性与维护性。