Loro 富文本 CRDT 的介绍 [译]

Loro 富文本 CRDT 的介绍 [译]

💡 原文中文,约11100字,阅读约需27分钟。
📝

内容提要

本文介绍了Loro平台实现的富文本CRDT算法,满足无缝富文本协作的要求,可转换成富文本CRDT。在线演示基于Quill技术构建,提供实时协作和离线时的并发编辑功能。文章还介绍了REG算法、Peritext背景知识、列表CRDT和重放事件图。Loro的富文本CRDT算法使用样式锚点实现富文本编辑,采用B+树数据结构表示文本内容和样式。通过DiffCalculator工具计算差异,使用Fugue算法处理远程更新。经测试验证了算法的正确性和最终一致性。

🔎

延伸解读

样式锚点:Loro 富文本算法的核心机制

Loro 使用不可见的控制字符——样式锚点来标记样式的起始和结束。每个锚点包含样式操作 ID、键值对、Lamport 时间戳和扩展行为。确定字符样式时,找出所有覆盖该字符的锚点对,按样式键分组,选择时间戳最大的值,若相同则用对等 ID 决定。这种方法能准确处理样式重叠,且锚点对用户不可见,每个锚点实体长度为 1,但 Unicode 长度为 0。

与 Peritext 的兼容性及 REG 的挑战

Peritext 是首个富文本 CRDT 研究,能保留用户编辑意图,但原始版本无法直接与 REG 配合。因为 Peritext 用字符 OpID 定义样式范围,而 REG 不重现历史就无法识别这些 OpID;且 Peritext 不满足 REG 的“相同操作产生相同效果”前提。Loro 的新算法不依赖特定列表 CRDT,与 REG 兼容,并通过了 Peritext 的所有测试场景。

处理重叠样式:Quill Delta 的局限与解决方案

Quill Delta 格式无法表示同一键的多个值,如多人评论重叠。Loro 采用在样式键后添加唯一后缀(如 comment:alice)的方法,既简化了 CRDT 库代码,又支持多重评论重叠,同时使应用编码更友好。示例中,通过 configTextStyle 设置 comment 的 expand 为 none 并允许 overlap,即可实现重叠评论的 Delta 输出。

实现架构:B+树与最终一致性

Loro 内部使用 ContentTree 和 StyleRangeMap 两种基于 B+树的数据结构。ContentTree 存储文本内容和样式锚点,支持多种索引;StyleRangeMap 高效管理样式范围。两者分离以优化性能。通过 Fugue 算法合并远程更新,内部状态实现强最终一致性,确保相同更新产生相同富文本文档。测试包括 Peritext 标准测试和大量模糊测试,验证了正确性和可靠性。

❓

Q&A

Loro 的富文本 CRDT 算法有什么特别之处?

Loro 的富文本 CRDT 算法符合 Peritext 对无缝富文本协作的要求,能够基于任何列表 CRDT 算法之上,把它们转换成富文本 CRDT。它使用样式锚点和 B+ 树数据结构表示文本内容和样式,并通过 Fugue 算法处理远程更新,确保最终一致性。

为什么 Peritext 不能直接用在 REG 上?

Peritext 通过字符的 OpIDs 来定义样式范围,而基于 REG 的 CRDTs 在没有重现编辑历史的情况下无法准确识别这些 OpIDs 对应的位置。此外,REG 要求相同的操作产生相同的效果,但 Peritext 不满足这一前提,因为插入字符的样式取决于位置是否已被加粗以及墓碑中是否含有样式界限。

Loro 如何处理富文本中的样式冲突?

Loro 使用样式锚点来标记样式的开始和结束,每个锚点包含操作 ID、样式键值对、Lamport 时间戳和扩展行为。当多个样式锚点对同一字符生效时,选择具有最大 Lamport 时间戳的值;如果时间戳相同,则用对等 ID 决定。这种方法能准确处理样式重叠和冲突,保留用户意图。

Loro 的富文本算法如何保证最终一致性?

Loro 将样式锚点视为特殊元素,使用 List CRDT 方法解决并发冲突。内部状态由文本段落和样式锚点组成的元素列表构成,通过上游的 List CRDT(如 Fugue)实现强大的最终一致性。只要内部状态相同,最终生成的富文本文档就会一致,相同的更新操作会产生相同的结果。

在 Loro 中,如何在样式边界处插入文本?

插入文本时,根据样式的扩展行为决定插入位置:如果样式不应向后扩展,则在开始锚点之前插入;对于粗体等标记的结束,在样式锚点之前插入(扩展方式为“after”或“both”);对于链接等标记的结束,在样式锚点之后插入(扩展方式为“none”或“before”)。规则1优先于规则2和3,以避免意外产生新样式。

Loro 的富文本模块在 JavaScript 中如何配置和使用?

首先创建 Loro 文档并配置文本样式,例如:doc.configTextStyle({ bold: { expand: "after" }, comment: { expand: "none", overlap: true }, link: { expand: "none" } })。然后获取文本对象,插入文本并应用样式,如 text.insert(0, "Hello world!") 和 text.mark({ start: 0, end: 5 }, "bold", true)。最后可以通过 text.toDelta() 获取富文本的 Delta 格式表示。

🏷️

标签

➡️

继续阅读