什么是 Operational Transformation (OT)?解析 Google Docs 等协同编辑工具的算法基石

各位同仁,各位对分布式系统与协同编辑充满热情的开发者们:

今天,我们将深入探讨一个在现代软件工程中至关重要的算法基石——操作转换(Operational Transformation,简称OT)。它不仅仅是一个抽象的理论,更是Google Docs、腾讯文档等无数实时协同编辑工具的心脏。想象一下,多人同时在同一文档上编辑,每个人都在自由地输入、删除、格式化,而最终所有人看到的文档内容却能奇迹般地保持一致,并且完美地反映了所有人的意图——这并非魔法,而是OT的精妙之处。

协同编辑的挑战与OT的诞生

在深入OT的细节之前,我们首先要理解它所解决的核心问题:并发性与一致性。

考虑一个简单的文本编辑器,如果只有一个用户,所有操作都是线性的,毫无疑问。但当多个用户同时编辑同一个文档时,情况就变得复杂了。

朴素的解决方案的局限性:

  1. “最后写入者获胜”(Last Write Wins): 这种策略最简单,但也是最粗暴的。如果用户A在位置0插入"Hello",用户B在位置0插入"World",那么最终文档将只保留其中一个操作的结果,另一个用户的编辑将被无情覆盖。这显然无法满足协同编辑的需求,因为用户的“意图”被完全忽略了。

  2. 锁定机制(Locking): 另一种常见方法是锁定。例如,当一个用户编辑某个段落时,其他用户无法编辑该段落。这能保证数据一致性,但极大地牺牲了并发性,用户体验非常差,与实时协同的目标背道而驰。

  3. 合并与冲突解决(Merge and Conflict Resolution): 像Git这样的版本控制系统,在用户提交各自的更改后,会进行合并。如果存在冲突,则需要人工介入解决。这种方式适用于异步、非实时的协作,但在毫秒级的实时协同场景中,让用户频繁手动解决冲突是不可接受的。

协同编辑的真正挑战在于,我们希望:

  • 实时性: 用户的操作应立即反映在文档中,并尽快同步给其他用户。
  • 收敛性(Convergence): 无论操作的到达顺序如何,所有用户最终看到的文档状态都应该是一致的。
  • 意图保存(Intention Preservation): 每个用户的操作都应该在最终文档中体现出其预期的效果,即使在并发操作下也应如此。

为了应对这些挑战,OT应运而生。它不是简单地覆盖或合并,而是一种动态调整操作的机制,确保当操作应用于一个已经被其他并发操作修改过的文档状态时,它仍然能够实现其原始意图。

核心概念:操作、状态与冲突

在OT的世界里,有几个核心概念是我们理解其工作原理的基础。

操作(Operation)

在OT中,文档的每一次修改都被抽象为一个“操作”。一个操作是文档从一个状态转换到另一个状态的原子性描述。对于文本编辑而言,最基本的操作通常包括:

  • 插入(Insert): 在特定位置插入一段文本。
  • 删除(Delete): 从特定位置删除一段文本。
  • 保留/保持(Retain): 跳过或保留特定数量的字符,通常用于表示操作不影响文档的某个部分,或者作为操作的一部分描述。

操作的结构示例:
一个操作通常包含位置信息和内容/长度信息。

| 字段 | 类型 | 描述 类型: RetainOperation | InsertOperation | DeleteOperation
|———-|———-|———————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————— ——————–|—————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————————-OT 全称 Operational Transformation,中文通常称为“操作转换”。在当今这个高度互联的世界,多人实时协同编辑已成为现代办公和在线协作的基础设施。从Google Docs、腾讯文档到飞书文档,甚至是我们日常使用的代码编辑器,背后都有OT或其变体在默默工作。作为一名编程专家,我将带大家深入剖解OT,揭示其如何成为实现实时协同编辑的算法基石。

1. 协同编辑的挑战与OT的诞生

想象一下,你和你的同事正在共同撰写一份重要报告。你负责引言部分,他负责结论。当你们同时修改文档时,如果没有一个智能的机制来协调这些修改,那么最终的文档很可能会变成一团糟:你的修改可能覆盖他的,或者他的修改让你的编辑失去上下文。这就是协同编辑面临的根本挑战:如何处理并发操作,并确保所有用户最终都能看到一个一致且准确反映所有人意图的文档状态。

早期的协同编辑尝试往往采用“锁定”机制,即当一个用户编辑某个区域时,其他用户无法修改。这种方法虽然保证了数据的一致性,但极大地牺牲了并发性,用户体验极差,与“实时协同”的愿景背道而驰。

随着互联网技术的发展和对实时交互体验的追求,我们需要一种更精巧的解决方案。这种方案必须具备以下关键特性:

  1. 收敛性 (Convergence): 无论并发操作的顺序如何,所有参与者最终都必须能看到相同的文档状态。
  2. 意图保存 (Intention Preservation): 每个操作的语义和效果都应在最终文档中得到保留,即使在并发操作的影响下,操作执行的“意图”也不应被扭曲。
  3. 因果顺序保持 (Causality Preservation): 如果一个操作是基于另一个操作的结果而产生的,那么在最终文档中,这两个操作的相对顺序必须得到尊重。

正是在这样的背景下,操作转换 (OT) 理论在20世纪80年代末被提出,并在随后的几十年里不断发展和完善,成为解决实时协同编辑问题的最经典和广泛应用的算法。

OT的核心思想是:当一个操作在客户端生成并发送到服务器或另一个客户端时,如果在此期间文档状态发生了变化,那么这个操作在应用之前,需要根据这些中间的变化进行“转换”,以确保它能正确地应用于新的文档状态,并保持其原始意图。

2. OT的核心概念:操作、状态与转换函数

理解OT,首先要理解其基本构成要素。

2.1 文档与操作

在OT中,我们把文档看作是一个状态,而对文档的每一次修改都抽象为一个操作 (Operation)。对于纯文本,最基本的操作类型有三种:

  • 插入 (Insert): 在文档的某个位置插入一段文本。
  • 删除 (Delete): 从文档的某个位置删除指定长度的文本。
  • 保留 (Retain): 表示跳过文档的某个部分,不做任何修改。这在描述一个操作对文档影响的范围时非常有用,特别是在组合和转换操作时。

为了方便表示和处理,一个操作通常被描述为一个操作序列,每个元素是上述三种类型之一。例如,一个操作可能表示为 [retain(5), insert("World"), delete(2), retain(3)],意味着:跳过前5个字符,插入"World",删除2个字符,再跳过3个字符。

示例:一个简单的文本操作表示

假设文档内容是 "hello"。

  • 在位置0插入 "W":[insert("W"), retain(5)]
  • 删除位置2开始的3个字符 "llo":[retain(2), delete(3)]
  • 在位置5插入 "!":[retain(5), insert("!")]

我们可以用一个类来表示这些操作:

import json

class TextOperation:
    """
    表示一个对文本进行修改的操作。
    一个操作由一系列子操作组成,每个子操作可以是 'retain', 'insert', 'delete'。
    """
    def __init__(self, ops=None):
        self.ops = ops if ops is not None else []
        self._cursor = 0 # 用于辅助apply和compose操作时的光标位置

    def __repr__(self):
        return f"TextOperation({self.ops})"

    def insert(self, text):
        if not text:
            return self
        self.ops.append({'type': 'insert', 'value': text})
        self._cursor += len(text)
        return self

    def delete(self, length):
        if length <= 0:
            return self
        self.ops.append({'type': 'delete', 'length': length})
        return self

    def retain(self, length):
        if length <= 0:
            return self
        self.ops.append({'type': 'retain', 'length': length})
        self._cursor += length
        return self

    def apply(self, text):
        """将当前操作应用于给定的文本,返回新的文本。"""
        new_text = []
        text_cursor = 0
        for op in self.ops:
            op_type = op['type']
            if op_type == 'retain':
                length = op['length']
                new_text.append(text[text_cursor : text_cursor + length])
                text_cursor += length
            elif op_type == 'insert':
                new_text.append(op['value'])
            elif op_type == 'delete':
                length = op['length']
                text_cursor += length # 实际删除,所以跳过原文本
            else:
                raise ValueError(f"未知操作类型: {op_type}")
        # 如果操作没有完全覆盖原文本,则保留剩余部分
        new_text.append(text[text_cursor:])
        return "".join(new_text)

    # 简化版,用于演示。实际中需要更复杂的规范化
    def normalize(self):
        """规范化操作序列,合并相邻的同类型操作等。"""
        normalized_ops = []
        for op in self.ops:
            if op['type'] == 'insert' and normalized_ops and normalized_ops[-1]['type'] == 'insert':
                normalized_ops[-1]['value'] += op['value']
            elif op['type'] == 'delete' and normalized_ops and normalized_ops[-1]['type'] == 'delete':
                normalized_ops[-1]['length'] += op['length']
            elif op['type'] == 'retain' and normalized_ops and normalized_ops[-1]['type'] == 'retain':
                normalized_ops[-1]['length'] += op['length']
            elif (op['type'] == 'insert' and not op['value']) or 
                 (op['type'] == 'delete' and op['length'] == 0) or 
                 (op['type'] == 'retain' and op['length'] == 0):
                # 忽略空操作
                pass
            else:
                normalized_ops.append(op)
        self.ops = normalized_ops
        return self

    def _clone(self):
        """克隆当前操作,用于转换函数中生成新操作。"""
        return TextOperation(json.loads(json.dumps(self.ops))) # Deep copy

2.2 文档状态 (Document State)

文档状态就是特定时刻的文档内容。在协同编辑中,每个客户端和服务器都维护一个文档状态。但由于网络延迟,这些状态可能在短时间内不一致。OT的目标就是通过转换操作,使这些不一致的状态最终收敛到一致。

2.3 转换函数 (Transformation Function)

这是OT的核心所在。转换函数 transform(op1, op2) 接收两个并发操作 op1op2,这两个操作都基于同一个初始文档状态 S。它的任务是生成两个新的操作 op1'op2',使得:

  1. op1' 可以正确地应用于 S 经过 op2 后的状态 (S + op2)。
  2. op2' 可以正确地应用于 S 经过 op1 后的状态 (S + op1)。
  3. 最重要的是,收敛性条件:S + op1 + op2' 必须等于 S + op2 + op1' 这意味着无论先应用 op1 后应用 op2',还是先应用 op2 后应用 op1',最终的文档状态都应该相同。

这个过程可以用一个通勤图来表示:

      S
     / 
    /   
  op1   op2
  /       
 S+op1   S+op2
         /
   op2' op1'
       /
      /
  S+op1+op2' == S+op2+op1'

transform 函数的实现是OT算法最复杂的部分,它需要考虑各种操作类型组合(插入对插入、插入对删除、删除对插入、删除对删除)以及它们在文档中的相对位置。

3. OT的核心算法:深入转换函数的逻辑

让我们通过详细的例子和伪代码来理解 transform 函数如何处理不同类型的操作组合。

假设我们有两个操作 opAopB,它们都基于相同的初始文档状态 S。我们要计算 opA_prime (应用于 S + opB) 和 opB_prime (应用于 S + opA)。

为了实现 transform,我们通常会设计一个 transform_pair(op1_component, op2_component) 函数,处理操作序列中的一对原子组件。

转换规则的核心思想:

  • 位置调整: 如果一个操作在另一个操作之前插入了内容,那么后续操作的索引位置需要向后移动。如果删除了内容,则需要向前移动。
  • 内容冲突: 两个插入操作在同一位置插入,或者一个操作删除另一个操作插入的内容,需要特殊的处理来确保意图保存。

我们来定义一个 transform_pair 函数,它接收两个操作 op1op2 的当前子操作(例如 {'type': 'insert', 'value': 'A'}{'type': 'retain', 'length': 5}),并返回一对转换后的子操作。

3.1 transform 函数的实现细节

我们将构建一个 transform 方法,它会遍历 op1op2 的操作序列,逐对处理它们的子操作。

class TextOperation:
    # ... (之前的代码) ...

    def transform(self, other_op):
        """
        根据另一个并发操作 other_op 转换当前操作 self。
        返回 (self_prime, other_op_prime)
        self_prime 是 self 转换后的操作,可以应用于 original_doc + other_op
        other_op_prime 是 other_op 转换后的操作,可以应用于 original_doc + self
        """
        self_prime = TextOperation()
        other_op_prime = TextOperation()

        idx1, idx2 = 0, 0
        ops1, ops2 = self.ops, other_op.ops

        while idx1 < len(ops1) or idx2 < len(ops2):
            op1_component = ops1[idx1] if idx1 < len(ops1) else None
            op2_component = ops2[idx2] if idx2 < len(ops2) else None

            # 情况1: op1 是插入操作
            if op1_component and op1_component['type'] == 'insert':
                self_prime.insert(op1_component['value'])
                idx1 += 1
                continue # op1的插入不影响op2的位置,但op2的插入会影响op1

            # 情况2: op2 是插入操作
            if op2_component and op2_component['type'] == 'insert':
                other_op_prime.insert(op2_component['value'])
                idx2 += 1
                continue # op2的插入不影响op1的位置,但op1的插入会影响op2

            # 如果op1或op2已经处理完,但另一个还有retain/delete,则需要处理剩余部分
            if not op1_component and op2_component:
                # op1 已经处理完,op2 还有 delete/retain
                # 这意味着 op2_component 应该应用于 op1 已经完成后的文档
                # 但由于 op1 没做任何操作,op2_component 保持不变
                # 这只有在文档末尾时才会发生,因为前面的 retain/delete 会匹配
                if op2_component['type'] == 'retain':
                    other_op_prime.retain(op2_component['length'])
                    idx2 += 1
                elif op2_component['type'] == 'delete':
                    other_op_prime.delete(op2_component['length'])
                    idx2 += 1
                continue

            if not op2_component and op1_component:
                # 同理,op2 处理完,op1 还有 delete/retain
                if op1_component['type'] == 'retain':
                    self_prime.retain(op1_component['length'])
                    idx1 += 1
                elif op1_component['type'] == 'delete':
                    self_prime.delete(op1_component['length'])
                    idx1 += 1
                continue

            # 以下情况,op1_component 和 op2_component 都是 retain 或 delete

            # 情况3: op1 和 op2 都是 retain
            if op1_component['type'] == 'retain' and op2_component['type'] == 'retain':
                length = min(op1_component['length'], op2_component['length'])
                self_prime.retain(length)
                other_op_prime.retain(length)

                op1_component['length'] -= length
                op2_component['length'] -= length

                if op1_component['length'] == 0: idx1 += 1
                if op2_component['length'] == 0: idx2 += 1
                continue

            # 情况4: op1 是 delete
            if op1_component['type'] == 'delete':
                if op2_component['type'] == 'delete': # op1 delete, op2 delete
                    # 共同删除的部分,两个操作都不能再次删除
                    length = min(op1_component['length'], op2_component['length'])
                    op1_component['length'] -= length
                    op2_component['length'] -= length

                    # 对于冲突的删除,我们通常选择一个操作“胜出”,或者都忽略
                    # 这里的处理是:两个操作都将该部分从原始文档中删除,
                    # 转换后,它们不再需要删除这部分(因为已经被另一个操作删除了)
                    # 所以 self_prime 和 other_op_prime 都不需要包含这部分的删除
                    # 只需要处理剩余的非冲突删除
                    if op1_component['length'] == 0: idx1 += 1
                    if op2_component['length'] == 0: idx2 += 1
                    continue
                else: # op1 delete, op2 retain
                    # op1 删除的区域,op2 保留。
                    # op1_prime: 仍然删除该区域
                    # op2_prime: 不再保留该区域 (因为它被op1删除了)
                    length = min(op1_component['length'], op2_component['length'])
                    self_prime.delete(length) # op1 删除这部分
                    # other_op_prime 不做任何操作,因为 op1 删除了它想保留的部分

                    op1_component['length'] -= length
                    op2_component['length'] -= length

                    if op1_component['length'] == 0: idx1 += 1
                    if op2_component['length'] == 0: idx2 += 1
                    continue

            # 情况5: op2 是 delete
            if op2_component['type'] == 'delete': # op2 delete, op1 retain
                # op2 删除的区域,op1 保留。
                # other_op_prime: 仍然删除该区域
                # self_prime: 不再保留该区域 (因为它被op2删除了)
                length = min(op1_component['length'], op2_component['length'])
                other_op_prime.delete(length) # op2 删除这部分
                # self_prime 不做任何操作,因为 op2 删除了它想保留的部分

                op1_component['length'] -= length
                op2_component['length'] -= length

                if op1_component['length'] == 0: idx1 += 1
                if op2_component['length'] == 0: idx2 += 1
                continue

        return self_prime.normalize(), other_op_prime.normalize()

# 将transform方法添加到TextOperation类中
TextOperation.transform = transform

transform 逻辑的进一步解释:

  • insert vs insert:

    • opA: insert("A") at pos X
    • opB: insert("B") at pos X
    • 如果 opA 先应用,文档变成 ...A...opB 必须转换为在 A 之后插入 B,即 insert("B") at pos X+len("A")
    • 如果 opB 先应用,文档变成 ...B...opA 必须转换为在 B 之后插入 A,即 insert("A") at pos X+len("B")
    • 为了实现收敛,需要定义一个优先级规则。例如,根据客户端ID或时间戳决定哪个插入先发生。在上面的简化代码中,它会直接将 insert 添加到 self_primeother_op_prime。更健壮的OT会根据优先级在同一位置插入时调整位置。通常的规则是,如果两个插入在同一个位置,具有更高优先级(例如,更早的操作,或更小的客户端ID)的插入会先发生,另一个的插入位置会后移。
    • 在我的示例代码中,insert 操作是立即添加到 _prime 操作中,并且不消耗任何 retaindelete 的长度。这意味着插入操作会“挤开”后续的操作。这个逻辑是正确的:一个插入操作会影响后续所有操作的索引。
  • insert vs delete:

    • opA: insert("X") at pos P
    • opB: delete(L) at pos P'
    • 如果 P < P'opA 的插入会使得 opB 的删除位置后移 len("X")
    • 如果 P > P': opB 的删除会使得 opA 的插入位置前移 L
    • 如果 PP' 正在删除的范围内:opA 插入的内容会被 opB 删除。opA' 需要调整位置,opB' 需要调整删除长度。更复杂的OT实现会根据意图保存原则,决定是保留插入还是保留删除。 比如,如果插入发生在删除的内部,那么插入的内容不应该被删除。我的简化代码中,插入操作在处理时总是先完成,因此它不会被其他删除操作“吃掉”,这是符合某些OT模型的意图保存的。
  • insert vs retain:

    • opA: insert("X") at pos P
    • opB: retain(L) at pos P'
    • 如果 P < P'opA 的插入会使得 opB 的保留位置后移 len("X")opBretain 长度不变,但 opA 需要在 retain 之前插入。
    • 如果 P > P'opBretain 会使得 opA 的插入位置前移 LopAretain 之后插入。
    • 在我的代码中,insert 操作优先处理,它不消耗 retain 的长度,但会直接加入到 _prime 操作中,这隐式地调整了后续操作的上下文。
  • delete vs delete:

    • opA: delete(L1) at pos P
    • opB: delete(L2) at pos P'
    • 如果两个删除区域不重叠,则它们只是简单地调整彼此的位置。
    • 如果两个删除区域重叠:重叠部分不能被删除两次。通常,转换后的操作会只删除未被另一个操作删除的部分。例如,如果 opA 删除 [P, P+L1)opB 删除 [P', P'+L2),且 [P, P+L1) 完全包含 [P', P'+L2),那么 opA' 仍然删除 [P, P+L1),而 opB' 变成一个空操作,因为它要删除的部分已经被 opA 删除了。反之亦然。如果只是部分重叠,则需要计算非重叠部分。
    • 我的代码中,delete vs delete 的处理逻辑是:共同删除的部分,两个 _prime 操作都不再需要删除。各自未被另一个删除的部分,则继续删除。这是确保收敛性的关键。
  • delete vs retain:

    • opA: delete(L) at pos P
    • opB: retain(L') at pos P'
    • 如果 opA 删除了 opB 想要保留的部分,那么 opB' 就不再需要保留这部分,它应该跳过这些被删除的字符。而 opA' 仍然需要执行删除。
    • 我的代码中,op1 delete, op2 retain 意味着 op1 会删除 min(L, L') 长度的字符,self_prime 会包含这个删除。而 op2_prime 不需要 retain 这部分,因为它们已经被 op1 删除了。 反之亦然。

一个具体的 transform 例子:

初始文档 S = "abcdef"

  • opA: 在位置2插入 "X" -> S + opA = "abXcdef"
    • opA = [retain(2), insert("X"), retain(4)]
  • opB: 删除位置3的字符 ‘d’ -> S + opB = "abcef"
    • opB = [retain(3), delete(1), retain(2)]

现在我们来计算 transform(opA, opB) 得到 (opA_prime, opB_prime)

  1. opA 的第一个组件是 retain(2)opB 的第一个组件是 retain(3)

    • min(2, 3) = 2
    • opA_prime.retain(2)
    • opB_prime.retain(2)
    • opA 剩余 insert("X"), retain(4) (已处理 retain(2))
    • opB 剩余 retain(1), delete(1), retain(2) (已处理 retain(2))
  2. opA 当前组件是 insert("X")

    • self_prime.insert("X")
    • opA 剩余 retain(4)
    • opB 剩余 retain(1), delete(1), retain(2)
    • 注意:插入操作会“挤开”后续操作的位置,但不会消耗 other_opretaindelete 长度。
  3. opA 当前组件是 retain(4)opB 当前组件是 retain(1)

    • min(4, 1) = 1
    • self_prime.retain(1)
    • other_op_prime.retain(1)
    • opA 剩余 retain(3)
    • opB 剩余 delete(1), retain(2)
  4. opA 当前组件是 retain(3)opB 当前组件是 delete(1)

    • opBdeleteopAretain
    • min(3, 1) = 1
    • other_op_prime.delete(1) (因为 opB 删除了这个字符)
    • self_prime 不做任何 retain (因为 opA 想要保留的这个字符被 opB 删除了)
    • opA 剩余 retain(2)
    • opB 剩余 retain(2)
  5. opA 当前组件是 retain(2)opB 当前组件是 retain(2)

    • min(2, 2) = 2
    • self_prime.retain(2)
    • other_op_prime.retain(2)
    • opAopB 都处理完毕。

最终结果:

  • opA_prime = [retain(2), insert("X"), retain(1), retain(2)] -> 规范化后 [retain(2), insert("X"), retain(3)]
  • opB_prime = [retain(2), retain(1), delete(1), retain(2)] -> 规范化后 [retain(3), delete(1), retain(2)]

让我们验证收敛性:

  • S + opA = "abXcdef"

  • S + opB = "abcef"

  • S + opA + opB_prime: "abcdef" -> "abXcdef" (应用 opA) -> "abXce f" (应用 opB_primeretain(3)跳过"abX",delete(1)删除"c",retain(2)跳过"ef")

    • 结果:"abXef"
  • S + opB + opA_prime: "abcdef" -> "abcef" (应用 opB) -> "abXcef" (应用 opA_primeretain(2)跳过"ab",insert("X")插入"X",retain(3)跳过"cef")

    • 结果:"abXcef"

结果不一致!这说明我的 transform 简化实现,在 insertdelete 交叉时,没有正确处理优先级。opA_primeretain(3) 是在 opA 原始插入之后,而 opB_primedelete(1) 是在 opB 原始删除之后。

问题出在哪里?
opA 插入 "X" 时,它改变了文档长度,使得 opB 的删除位置需要调整。
opB 删除字符 ‘d’ 时,它改变了文档长度,使得 opA 的操作位置也需要调整。

我们必须严格遵循 transform 的定义:op1' 作用于 S + op2op2' 作用于 S + op1。这意味着 op1' 在被生成时,需要假设 op2 已经发生,并根据 op2 对文档状态的改变来调整自身。反之亦然。

重新审视 insert vs retaindelete 的逻辑:

transform 函数中,关键在于如何正确地调整 retaindeletelength,以及插入操作的相对位置。

class TextOperation:
    # ... (前面的代码省略,只保留transform方法的更严谨实现) ...

    def transform(self, other_op):
        """
        根据另一个并发操作 other_op 转换当前操作 self。
        返回 (self_prime, other_op_prime)
        self_prime 是 self 转换后的操作,可以应用于 original_doc + other_op
        other_op_prime 是 other_op 转换后的操作,可以应用于 original_doc + self
        """
        self_prime = TextOperation()
        other_op_prime = TextOperation()

        idx1, idx2 = 0, 0
        ops1, ops2 = self.ops, other_op.ops

        # 辅助函数,用于获取下一个非空操作组件
        def get_next_op(ops, idx):
            while idx < len(ops):
                op = ops[idx]
                if op['type'] == 'insert' and not op['value']:
                    idx += 1
                    continue
                if op['type'] == 'delete' and op['length'] == 0:
                    idx += 1
                    continue
                if op['type'] == 'retain' and op['length'] == 0:
                    idx += 1
                    continue
                return op, idx
            return None, idx

        op1_component, idx1 = get_next_op(ops1, idx1)
        op2_component, idx2 = get_next_op(ops2, idx2)

        while op1_component or op2_component:
            # Case 1: op1 is an insert
            if op1_component and op1_component['type'] == 'insert':
                # op1 的插入操作对 op2 的位置没有影响,因为 op1 是基于原始文档的
                # 但 op1' 必须在 op2 已经发生后的文档上插入
                # 如果 op2 在相同位置插入,通常需要一个优先级规则 (例如,客户端ID小的先)
                # 这里简化处理:op1' 总是插入其内容
                self_prime.insert(op1_component['value'])
                idx1 += 1
                op1_component, idx1 = get_next_op(ops1, idx1)
                continue

            # Case 2: op2 is an insert
            if op2_component and op2_component['type'] == 'insert':
                # op2 的插入操作对 op1 的位置没有影响
                # 但 op2' 必须在 op1 已经发生后的文档上插入
                # 为了保持意图,op2' 总是插入其内容
                other_op_prime.insert(op2_component['value'])
                idx2 += 1
                op2_component, idx2 = get_next_op(ops2, idx2)
                continue

            # Case 3: Both are retain (or one/both are delete, but length needs to match)
            # Both op1_component and op2_component must exist and be retain/delete now
            if not op1_component or not op2_component:
                # 理论上,如果 ops1 或 ops2 还有 retain/delete,而另一个已经处理完
                # 这应该在循环开头处理,或者通过长度匹配来处理
                # 如果走到这里,说明其中一个组件是 None,但前面的 insert 检查没通过
                # 实际上应该不会发生,除非文档长度不匹配
                raise Exception("Transform logic error: one op exhausted prematurely with retain/delete")

            if op1_component['type'] == 'retain' and op2_component['type'] == 'retain':
                length = min(op1_component['length'], op2_component['length'])
                self_prime.retain(length)
                other_op_prime.retain(length)

                op1_component['length'] -= length
                op2_component['length'] -= length

                if op1_component['length'] == 0:
                    idx1 += 1
                    op1_component, idx1 = get_next_op(ops1, idx1)
                if op2_component['length'] == 0:
                    idx2 += 1
                    op2_component, idx2 = get_next_op(ops2, idx2)
                continue

            # Case 4: op1 is delete, op2 is retain
            if op1_component['type'] == 'delete' and op2_component['type'] == 'retain':
                length = min(op1_component['length'], op2_component['length'])
                self_prime.delete(length) # op1 删除这部分
                # other_op_prime 不保留这部分,因为 op1 删除了它
                op1_component['length'] -= length
                op2_component['length'] -= length

                if op1_component['length'] == 0:
                    idx1 += 1
                    op1_component, idx1 = get_next_op(ops1, idx1)
                if op2_component['length'] == 0:
                    idx2 += 1
                    op2_component, idx2 = get_next_op(ops2, idx2)
                continue

            # Case 5: op2 is delete, op1 is retain
            if op2_component['type'] == 'delete' and op1_component['type'] == 'retain':
                length = min(op1_component['length'], op2_component['length'])
                other_op_prime.delete(length) # op2 删除这部分
                # self_prime 不保留这部分,因为 op2 删除了它
                op1_component['length'] -= length
                op2_component['length'] -= length

                if op1_component['length'] == 0:
                    idx1 += 1
                    op1_component, idx1 = get_next_op(ops1, idx1)
                if op2_component['length'] == 0:
                    idx2 += 1
                    op2_component, idx2 = get_next_op(ops2, idx2)
                continue

            # Case 6: Both are delete
            if op1_component['type'] == 'delete' and op2_component['type'] == 'delete':
                length = min(op1_component['length'], op2_component['length'])
                # 如果 op1 删除了,op2' 就不需要再删除这部分。反之亦然。
                # 所以两个转换后的操作都不包含这部分删除。
                op1_component['length'] -= length
                op2_component['length'] -= length

                if op1_component['length'] == 0:
                    idx1 += 1
                    op1_component, idx1 = get_next_op(ops1, idx1)
                if op2_component['length'] == 0:
                    idx2 += 1
                    op2_component, idx2 = get_next_op(ops2, idx2)
                continue

            # Fallback for unexpected cases
            raise Exception(f"Unexpected operation types during transform: op1={op1_component}, op2={op2_component}")

        return self_prime.normalize(), other_op_prime.normalize()

# 重新将transform方法添加到TextOperation类中
TextOperation.transform = transform

再次验证 transform 例子:

初始文档 S = "abcdef"

  • opA: 在位置2插入 "X" -> S + opA = "abXcdef"
    • opA = [retain(2), insert("X"), retain(4)]
  • opB: 删除位置3的字符 ‘d’ -> S + opB = "abcef"
    • opB = [retain(3), delete(1), retain(2)]

计算 transform(opA, opB)

  1. opA_comp = retain(2), opB_comp = retain(3)

    • length = min(2,3) = 2
    • self_prime.retain(2)
    • other_op_prime.retain(2)
    • opA_comp 剩余 retain(0), idx1 进1,opA_comp 变为 insert("X")
    • opB_comp 剩余 retain(1)
  2. opA_comp = insert("X")

    • self_prime.insert("X")
    • idx1 进1,opA_comp 变为 retain(4)
  3. opA_comp = retain(4), opB_comp = retain(1)

    • length = min(4,1) = 1
    • self_prime.retain(1)
    • other_op_prime.retain(1)
    • opA_comp 剩余 retain(3)
    • opB_comp 剩余 retain(0), idx2 进1,opB_comp 变为 delete(1)
  4. opA_comp = retain(3), opB_comp = delete(1)

    • length = min(3,1) = 1
    • other_op_prime.delete(1) (opB 删除,opA 只是保留,所以opA’不再保留这部分)
    • opA_comp 剩余 retain(2)
    • opB_comp 剩余 delete(0), idx2 进1,opB_comp 变为 retain(2)
  5. opA_comp = retain(2), opB_comp = retain(2)

    • length = min(2,2) = 2
    • self_prime.retain(2)
    • other_op_prime.retain(2)
    • opA_comp 剩余 retain(0), idx1 进1,opA_comp 变为 None
    • opB_comp 剩余 retain(0), idx2 进1,opB_comp 变为 None

所有组件处理完毕。

  • opA_prime = [retain(2), insert("X"), retain(1), retain(2)] -> 规范化后 [retain(2), insert("X"), retain(3)]
  • opB_prime = [retain(2), retain(1), delete(1), retain(2)] -> 规范化后 [retain(3), delete(1), retain(2)]

这与之前的计算结果一致。现在我们重新验证收敛性:

  • S = "abcdef"

  • S + opA + opB_prime:

    1. S + opA: "abcdef" -> "abXcdef"
    2. 应用 opB_prime ([retain(3), delete(1), retain(2)]) 到 "abXcdef":
      • retain(3): 跳过 "abX"
      • delete(1): 删除 "c" (原文档中的 ‘c’ 在 ‘X’ 之后,位置3)
      • retain(2): 跳过 "ef"
      • 最终结果: "abXef"
  • S + opB + opA_prime:

    1. S + opB: "abcdef" -> "abcef"
    2. 应用 opA_prime ([retain(2), insert("X"), retain(3)]) 到 "abcef":
      • retain(2): 跳过 "ab"
      • insert("X"): 插入 "X"
      • retain(3): 跳过 "cef"
      • 最终结果: "abXcef"

仍然不一致! 结果是 "abXef""abXcef"。问题在于 opB 删除的 ‘d’ 的位置。
S = "abcdef" 中,’d’ 在位置3。
opA 在位置2插入 ‘X’,导致 ‘d’ 移动到位置4。
opB_prime ([retain(3), delete(1), retain(2)]) 应该删除 S + opA 中的 ‘d’,即位置4的字符。
然而,我的 transform 函数中的 delete(1) 只是删除了一个字符,它本身没有进行位置调整。retain(3) 跳过了3个字符 (a, b, X),然后 delete(1) 删除了 ‘c’。这与 opB 的意图(删除原始文档中的 ‘d’)不符。

核心错误点:transform 函数没有正确处理“意图”的转换。
一个操作的 retaindelete 都是相对于其所基于的文档状态而言的。当文档状态被另一个并发操作修改后,这些 retaindelete 的“位置”和“长度”都需要相应调整。

正确的 transform 逻辑需要更精细的状态跟踪和对 retain / delete 长度的调整。

更严谨的 transform 逻辑 (基于 ShareJSot.js 的思路):

OT的 transform 函数通常需要两个辅助函数:transform_lefttransform_right,或者一个统一的 transform 函数,但内部逻辑更细致。一个常见的实现会逐个比较两个操作中的原子操作,并根据它们的类型和位置关系,调整各自的长度和生成新的操作。

让我们简化操作表示,只用列表的头部来代表当前处理的组件:


class TextOperation:
    # ... (之前的 apply, normalize 等方法) ...

    @staticmethod
    def _transform_cursor(op_list, cursor_val, op_type_key, op_len_key):
        """
        辅助函数,用于处理操作列表的游标。
        从 op_list 头部消耗 cursor_val,并返回消耗后的值。
        """
        consumed = 0
        while cursor_val > 0 and op_list:
            op = op_list[0]
            if op['type'] == op_type_key: # e.g., 'retain' or 'delete'
                length = op[op_len_key]
                to_consume = min(cursor_val, length)
                op[op_len_key] -= to_consume
                cursor_val -= to_consume
                consumed += to_consume
                if op[op_len_key] == 0:
                    op_list.pop(0)
            else: # Insert operations don't consume cursor for retain/delete
                break #

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注