Cumulo Editor 是如何实现实时协同编程的

最近折腾了一下 Cirru Editor 的新版本: Cumulo Editor,
相关代码和演示能够在这些地址找到:
https://github.com/Cirru/cumu...
https://github.com/mvc-works/...
http://weibo.com/1651843872/F...
https://twitter.com/jiyinyiyo...git

协同编程是什么

网上能找到关于协同编辑的说明:
https://en.wikipedia.org/wiki...github

A collaborative editor is a form of collaborative software application that allows several people to edit a computer file using different computers, a practice called collaborative editing.算法

There are two types of collaborative editing: real-time and non-real-time. In real-time collaborative editing (RTCE), users can edit the same file simultaneously, whereas in Non-real-time collaborative editing, the users do not edit the same file at the same time (similar to revision control systems). Collaborative real-time editors generally permit both the above modes of editing in any given instance.编程

一个有名的例子是 Google Docs, 它支持多人同时编辑.
你能够看到多我的的光标同时显示在网页上, 在各自的位置编辑文档.
这是一种很高效的合做写文档的方式, 能够很是快速基于对方的结果迭代.segmentfault

要作到协同编程, 咱们就要对代码提供类似的功能, 容许多我的编辑.
首先咱们须要有服务器做为操做顺序的协调者, 用来保存和同步的状态,
接着须要有 Diff 算法, 使得经过网络传输的信息是最精简的,
而后还要有 Patch 算法, 而且在 Patch 先后光标的行为应当一致.
每一个环节到要打磨到必定的程度, 不然很容易影响到正常编写代码的过程.
实际上基于文本的代码同步会遇到不少问题, 设置对编辑器原有功能也会形成影响.数组

Cirru 的语法树的设计

Cirru 里采用了同样的思路, 代码的核心形态并非文本, 而是语法树,
并且完成了 Stack Editor 这样一个能比较快速地编辑代码的方案,
Stack Editor 基于 Clojure Macros 能够从语法树生成 Clojure 代码,
虽然不能生成所有的 Clojure 语法, 好比特殊符号, 好比 Macro,
可是能够覆盖很大一部分网页开发须要的功能, 从而提供完整方案.
因为 Stack Editor 是基于网页来作代码的编辑, 也就避开了文本编辑器的惯用方案.服务器

Cirru 的语法树看起来是 ["def" "a" ["+" "1" "2"]] 这样一个数组的形态,
这个形态和 S 表达式对应, 也就是 Lisp 社区普遍接受的表达式方案.
对于这样一个语法树, 经过肯定操做的类型, 以及操做的位置, 就能进行操做,
并且用户能够有个明确的坐标, 做为编辑时表达和肯定编辑位置的方案.
基于这样的结构, 能够获得很精简的语法树, 也便于在网页上渲染.微信

除了表达式能够被 Cirru 记法替代, 整个源码文件结构均可以被改写,
好比文件抽象为命名空间, 用一个 HashMap 存储,
而后每一个文件分红 ns defs proc 三部分, 其中 proc 表示脚本代码,
因此一个项目的源码看上去就像是这样:网络

{:package "app"
 :files {"app.main" {:ns ["ns" "app.main"]
                     :defs {"main!" ["defn" "main!" []]}
                     :proc []}
         "app.lib" {:ns ["ns" "app.lib"]
                    :defs {}
                    :proc []}}}

通过这样的思惟转化, 协同编程的问题就被从新理解了,
再也不问怎样同步文本, 而是说, 这棵树怎样进行修改的同步?
树的同步是个相对简单的问题, 由于操做树的一个分支, 一般不会影响到其余分支,
咱们只要定义好节点, 定义好操做, 而后这些操做有肯定的顺序, 就能够了.
以后经过服务器讲操做同步到多个不一样的客户端, 就能造成同步的假想.数据结构

针对 Cumulo Diff 的优化

Cumulo 是我基于 React 设计一个经过 tree diff/patch 来跨客户端同步信息的方案,
React 有一份 Virtual DOM, 而后能够经过 Diff 分发而后 Patch 到不一样的客户端,
而 Cumulo 则是在服务端生成 Diff, 让客户端精确地同步须要的数据,
这个特性也就适合运用在 Cirru 语法树的同步上面, 服务端维护好树, 客户端同步.
这里不深刻讲 Cumulo, 能够看我以前的内容了解 Cumulo:
https://segmentfault.com/t/cu...
https://github.com/Cumulo/cum...

这里有个 tree diff 的效率问题, 由于数组的 Diff 其实比较低效,
由于数组比较难探测子节点之间如何匹配, 须要一个 key 来辅助,
好比 React 当中就借助一个 key 来判断子节点, 从而提升效率.
另外一个问题是数组受到位置影响比较明显, 好比前面插入元素, 后面的坐标就改变了.
而 HashMap 相对来讲是更稳定的实现, 只在祖先路径更改时坐标会改变.

因而我设计了一个新的数据结构, 原先 Cirru 表达式的格式是:

["def" "f" ["x" "y"]]

在新的格式当中存储成这样,

{:type :expr
 :data {"T" {:type :leaf, :text "def"}
        "j" {:type :leaf, :text "f"}
        "r" {:type :expr
             :data {"T" {:type :leaf, :text "x"}
                    "j" {:type :leaf, :text "y"}}}}}

为了方便理解, 能够认为是这样一个结构:

{"T" "def"
 "j" "f"
 "r" {"T" "x",
      "j" "y"}}

能够看到表达式的子节点变成了用 HashMap 存储, 增长了 key,
这个 key 的字典序是和表达式当中的子节点位置对应的,
在 Cirru 中咱们会遇到任意位置可能插入新的节点, 就须要生成新的 key,
并且这个新的 key 要知足正确的字典序, 好比在两个中间插入, 或者头尾插入.
关于这个 key 的算法, 我实现了一个简单的类库来达成, 能够了解:
http://clojure-china.org/t/bi...

总之转化为 HashMap 以后, 树的 Diff 就快多了, 并且冲突也会变少,
因而一会儿就绕过了不少问题, 协同编辑也就在必定范围内可行了.

Cumulo Editor 当前和将来

Cumulo Editor 总体基于 Stack Editor 的功能作了从新实现,
主打的是协同编辑, 同时也支持其余一些简单的编码辅助功能,
好比基于语法树跳转到定义, 快速在表达式之间跳转等等.
没有实现 Stack Editor 当中查找使用位置的功能, 之后也许会加上.

关于仍是关于协同编辑部分, 目前没有作深度的测试,
好比我把服务器部署在阿里云香港, 这个延时彻底是在可接受范围内的.
表达式的操做会等待服务器响应, 符号的修改是本地实时的,
至少目前测试看来, 编辑方面的流畅度没有明显的问题.
可能担忧的仍是人数和代码里增长以后, 编辑器是够延时增长引起问题.

另外一个问题是协同编辑会遇到抢占文件保存的问题,
也就是一个问保存文件, 致使另外一我的代码没完成就保存, 从而出错.
我认为这一点对实际开发的影响会不小, 甚至须要增长功能来规范.
固然如今也很难说, 我一我的比较搞不出那么多幺蛾子来,
因此若是有人感兴趣想一块儿测试, 微信上加我, 而且注明 "Cirru",
我会试着积累一下经验, 看下这方面究竟会有多大的问题.
因为整个方案基于 ClojureScript, 因此须要先有一些了解再开始尝试.

这东西首先很突破常规, 但比较难说是否派的上用场,毕竟协同编辑带啦冲突的话, 可能我的的开发效率降低也未可知,可是对于未知呢, 老是不能轻易下定论的, 花时间去探索一下总能够吧 :)

相关文章
相关标签/搜索