Babble PDF version

Babble is a free-flow collaborative text-editor designed to exercise the Joyce framework. To get an idea how Joyce would perform in the real word we needed an application complex enough to exercise the whole framework but familiar enough to a general audience that the contributions of the framework are well highlighted; particularly fluid collaboration, selective undo/redo and automatic storage.

Usage

When 'opening' a file in Babble, Joyce will discover the collaborative group for the document and synchronise the changes made while the local state was offline:

Edits from collaborators can be "tagged": on mouse-over we can display context information about the edit:

If concurrent edits conflict, Babble will apply one edit and highlight it with an alarming red shading. If one of the conflicting edits is local, Babble always displays the local edit. It is a key principle of Joyce that the user should feel in control of his local state; that local edits should not be sacrificed for remote ones unless specifically instructed.

Selective undo/redo is triggered through the history editor. Actions are arranged in to editor according to edit point and dependencies. In the figure below the action in the secod column has a dependency to the action in the first. Selecting the first-column action also highlights the second-column actions and the modifications that both the actions made are highlighted in the content display. In this way the user can visulise the extent of a prospective undo.

Since this representation can be confusing in a large document we can also filter the history editor to the 'local history' - the history at the current caret position:

Representing Text Editing in Joyce
Applications built with Joyce are architected as a collection of actions that implement the application commands and constraints that represent the concurrency semantics of those commands. We need a set of actions and constraints that encapsulate text editing.

Text editors are usually built around a linear character buffer that is addressed using character coordinates from 0 (before the first character) to N (after the last character given N-1 characters in the buffer). Two operations modify this buffer: insert(p, c), that inserts character c at position p, and delete(p, n) that removes a range of n characters starting at position p. Shared text editors are usually built around the same structure but use operational transform to transform remote inserts and deletes such that their local effect is the same as their effect at their source. Essentially, the transforms translate the edit points of inserts and the edit point and spans of deletes from the remote state into the local one by transforming the incoming operation against the operations that have applied to the local state. This gives good performance in distributed, real-time editing but is complex to implement, especially if multi-synchrony and undo-redo are required, intrinsic qualities of Joyce applications.

Babble borrows the idea of translating edit points from OT but uses a more systematic approach that meets the requirements of the Joyce framework. Our representation of a text buffer is more complex than a simple character array but allows us to capture the dependencies between edits and allows us to show, hide, recombine and re-order editing operations as directed by Joyce (for example during selective undo/redo or collaboration).

The representation is in three parts:

  1. The content: a linear text buffer similar to the structure used by non-concurrent and OT editors. However, with the exception of snapshotting and undo (see below) characters are only ever inserted into the buffer, not removed.
  2. The mask: a collection of character position intervals that indicate text that has been deleted. Masked text is not displayed and therefore cannot be edited (the cursor cannot be placed in the masked content).
  3. The history: a hierarchical collection of character position intervals that records the operations that have been applied to the content in terms of the range of characters affected.
[PICTURE]

The actions defined by Babble are:

  1. Insert(p, s): insert the string s in the content at position p.
  2. Delete(p, a): insert a mask of length a in the mask structure.
To define constraints, we say that one Insert must follow another if the edit point of the second intersects the span of the first. A Delete must follow another Delete or Insert if the spans of the two actions intersect. These are communicated to Joyce using ordering constraints. In the buffer below there have been two inserts and two deletes and the appropriate constraints have been set. Note that ordering constraints are transitive in Joyce so there is no need to set a constraint between D2 to I1.

                  |-D2------------|
                   v           |
              |-I2--|          |
                 v             v
            |-I1--------| |-D1--|
         A B p s t u q r C D E F G
         
Action Transform
Replication and history management lead to edit operations being generated against different states at different nodes, but any node must be able to replay remote operations against its local state and the effect of that operation must be the same. As discussed, most concurrent editors use operational transforms to 'correct' operations issued against dissimilar remote states. However, the twin needs of concurrency and history management often conflict in OT editors and lead to complex, difficult to implement solutions.

Babble's replay mechanism is derived from OT but retains a systematic operational history and is far simpler to implement that OT. We say that that every Insert operation creates a scope over the content inserted: a 1D co-ordinate space from 0 to the number of characters inserted. Subsequent insertions within this scope are always recorded in this co-ordinate space regardless of any mutations to the original insertion. For example, say we have the initial scope:

          A B C D E F G H
         |---------------|
         0      +0       8
         

This is a scope of 8 characters across which there is a shift of 0. The shifts in a scope are used to record how the original scope has been mutated by subsequent insert operations. If we insert 'pqr' between B and C:

              I(2, 'pqr')
             |-----|
          A B p q r C D E F G H
         |---       -----------|
         0   |-----|     +3    11
                +0
         

The original scope has been split and the shift after the split incremented by the number of characters inserted. If an insertion is made between E and F, Babble transforms the edit point of the insertion according to the start and shift of the intersected region:

                I(2, 'pqr')        I((8-0-3), 'stu') = I(5, 'stu')
               |--------|        |--------|
          A  B  p  q  r  C  D  E  s  t  u  F  G  H
         |-----          --------          -------|
         0     |--------|  +3    |--------|  +6   14
                  +0                 +0
         

The process is recursive; if an insert is made between q and r:

   	                 I( (4-2-0), 'wxy') = I(2, 'wxy')
   	               |-------|
                         v                 
               |------------------|        |--------|
          A  B  p  q  w  x  y   r  C  D  E  s  t  u  F  G  H
         |-----                     --------          -------|
         0  +0 |-----         -----|  +3    |--------|  +6   14
                 +0  |-------|  +3              +0
                         +0        
         
Replaying Actions
Using scopes we can map the edit point of remote actions into our state. Given two concurrent editors, 'J' and 'M', starting from the same document and making independent insertions:
                 J                 time              M
                                    ||
          A B C D E F G H           ||        A B C D E F G H
         |---------------|          ||       |---------------|
                +0                  ||               +0
                                    ||
         I(2, 'pqr')                ||         I(3, 'stu')
                                    ||
          A B p q r C D E F G H     ||       A B C s t u D E F G H
         |---       -----------|    ||      |-----       ---------|
          +0 |-----|     +3         ||        +0  |-----|    +3
               +0                   ||               +0
                                    \/         
         

Now if M's change propagates to J Babble detects that the intersected scope is not the same as the remote scope (via the follows constraint) and shifts the insertion by the width of the intersected scopes until the correct scope is reached:

                       I(3, stu)  (I(6, stu) after transform.)
             |-----| |-----|
          A B p q r C s t u D E F G H
         |---       -       ---------|
             |-----| |-----|
               =====>|
               Edit point is shifted until it
               reached the right scope.
         

If the spans of concurrent actions overlap those actions are deemed to be in conflict. Babble does not try to resolve conflicts - it does not know about the linguistic context the conflicting actions were made in. Babble will simply pick one action to apply to the state and highlight the content introduced or removed by that action with a red shading (above). If one of the conflicting actions is local Babble will always apply the local action. Following from the previous example:

                  J                       time                   M

      D(8, 3)                              || I(10, 'wxy')
      = D( (8-6-0), 3)                     || = I(10-6, 'wxy')
      = D(2, 3) mustfollow I(3, stu)       || = I(4, 'wxy')
                                           ||
                                           ||
                        D(2, 3)            ||  
                      |-------|            ||
                       v                   ||                      I(4, 'wxy')
          |-----| |-----|                  ||                    |-----|
       A B p q r C s t u D E F G H         || A B p q r C s t u D w x y D E F G H
      |---       -       ---------|        ||
          |-----| |-----|                  ||
                                      <=========
                                       propagate

                             I(4, 'wxy')
                          |-----|
                            X                                 
                        D(2, 3)            ||  
                      |-------|            ||
                       v                   ||
          |-----| |-----|                  || 
       A B p q r C s t u D E F G H         || 
      |---       -       ---------|        ||
          |-----| |-----|                  \/
         

After transform the incoming action conflicts with a local action. It will not be applied but the content inserted ("stu") will be highlighted.

Transforms in History Management
When Joyce recombines and re-orders the history, the scoping mechanism allows Babble to dynamically reflect the result in the content. As a trivial example, say we start with the document and scope:
              A B C D E F G H
             |---------------|
                   +0
         

Inserting between B & C...

                   I(2, pqr)
                 |-----|   
              A B p q r C D E F G H
             |---       -----------|
                 |-----|     +3
                    +0
         

Inserting between D & E...

                             I(4, stu) (after transform)
                 |-----|   |-----|
              A B p q r C D s t u E F G H
             |---       ---       -------|
                 |-----|   |-----|  +6
                    +0        +0
         

Undo the last insertion. The compensation action removes the content and scope...

                   I(2, pqr)
                 |-----|   
              A B p q r C D E F G H
             |---       -----------|
                 |-----|     +3
                    +0
         

Insert between C & D:

                           I(5, wxy)  (after transform)
                 |-----| |-----|
              A B p q r C w x y D E F G H
             |---       -       ---------|
                 |-----| |-----|    +6
                   +0       +0
         

Redo I(4, stu). The action is transformed against the current state & replayed in the appropriate place.

                                   I(4, stu) (replayed against current state)
                 |-----| |-----| |-----|
              A B p q r C w x y D s t u E F G H
             |---       -       -       -------|
                 |-----| |-----| |-----|   +9