Skip to content

Commit

Permalink
Add/edit a lot of comments for the RGA.
Browse files Browse the repository at this point in the history
It is still sort of hopeless to try to understand RGA by reading the
source code (as the talk tries to explain).
  • Loading branch information
jorendorff committed Nov 28, 2016
1 parent be949d8 commit 8f0816b
Show file tree
Hide file tree
Showing 2 changed files with 44 additions and 11 deletions.
16 changes: 10 additions & 6 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,10 +11,10 @@ var server = require('http').Server(app);
// with adjustable artifical latency.
var io = require('slow.io')(server);

// The server only knows how to send a single page, index.html. (It's not
// *quite* that simple really. Attaching socket.io to the server, above, adds
// more functionality to the server. It can now serve 'socket.io/socket.io.js',
// the browser-side half of socket.io.)
// The server knows how to serve two files: index.html and lib/rga.js. (It's
// not *quite* that simple really. Attaching slow.io to the server, above, adds
// more functionality to the server. It can now serve a couple of scripts:
// '/socket.io/socket.io.js' and '/slow.io/slow.io.js'.)
app.get('/', function (req, res) {
res.sendFile(__dirname + "/index.html");
});
Expand All @@ -24,7 +24,9 @@ app.get('/lib/rga.js', function (req, res) {
});

// The document model is a "Replicated Global Array", implemented in a separate
// module.
// module. Each client has a replica of the document, represented by an RGA
// that lives in the browser. There's also a central replica `doc` on the
// server.
var RGA = require('./lib/rga.js');
var doc = new RGA(0);
var nextUserId = 1; // Used to generate a unique id for each user.
Expand All @@ -37,7 +39,9 @@ io.on('connection', function (socket) {
console.log("connection - assigning id " + userId);
socket.emit("welcome", {id: userId, history: doc.history()});

// Propagate ops in both directions.
// Propagate ops between the new client and `doc`. Since `doc` is also tied
// to all other clients, they form one network, and edits at one client will
// eventually reach all replicas.
RGA.tieToSocket(doc, socket);
});

Expand Down
39 changes: 34 additions & 5 deletions lib/rga.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,24 +8,54 @@

var MAX_REPLICA_ID_BITS = 16;

// An RGA is a replicated string.
function RGA(id, history, queue) {
// Each replica has an ID. These must be unique, mainly because the RGA
// assigns a unique "timestamp" to every edit (insert or delete of an element).
// Those edit-ids are used in conflict resolution, and the replica's .id field
// is used to make sure the timestamps are really unique.
if (typeof id !== "number" || (id | 0) !== id || id < 0 || id >= (1 << MAX_REPLICA_ID_BITS))
throw new TypeError("RGA constructor: first argument is not a valid id");
this._idForLogging = RGA._nextIdForLogging++;
this.id = id;
this.left = {next: undefined, timestamp: -1, chr: undefined, removed: false};

// For debug log readability, an even-more-unique ID is handy.
this._idForLogging = RGA._nextIdForLogging++;

// The string is stored as a linked list of characters, like a string of
// beads, only instead of beads they're called "nodes". The leftmost node
// does not have a character on it; it's just a placeholder. Since each node
// contains a strong reference (the `.next` field) to the next node, the only
// node we really need a reference to is `this.left`. From here, we can use
// the `.next` fields to walk the whole rest of the string.
this.left = {
next: undefined, // Each node has a reference to the next node (undefined if no next node).
timestamp: -1, // Unique timestamp when this node was created.
chr: undefined, // The character.
removed: false // True if anyone has deleted this character.
};

// We need to find nodes quickly by their timestamp, so keep a table.
this._index = new Map([[this.left.timestamp, this.left]]);

// This is for assigning a unique timestamp to each new character
// the user types in.
this._nextTimestamp = id;

// The rest of this stuff is necessary in order to forward change events
// around. When this RGA is tied to other RGAs, they'll grab the `.downstream`
// method and use it as a callback.
this._subscribers = [];
this._queue = queue || RGA._browserQueue;
this._onDestroy = [];

var self = this;
this.downstream = function (sender, op) {
self._downstream(sender, op);
};
this.downstream._id = id;

// OK! `this` is now complete and ready for data. If we're joining an
// existing server, some edits have already happened. So we begin by
// replaying the whole history so far (if any).
if (history !== undefined) {
for (var i = 0; i < history.length; i++)
this._downstream(this.downstream, history[i]);
Expand Down Expand Up @@ -308,7 +338,6 @@ RGA.diff = function diff(s0, s1) {

var patch = [];
compare(s0, s1, 0, patch);
//console.log("diff result:", patch);
return {ops: patch};
};

Expand Down Expand Up @@ -355,7 +384,7 @@ RGA.AceEditorRGA = function AceEditorRGA(id, editor, history, queue) {
this.downstream = function (source, op) {
self._customDownstream(source, op);
};
this.downstream._id = id;
this.downstream._id = id; // for debugging
};

RGA.AceEditorRGA.prototype = Object.create(RGA.prototype);
Expand Down

0 comments on commit 8f0816b

Please sign in to comment.