Merge a4c83482ca02bf9a54a1ae004d9309f50307762e into 4feaf6c6fb507432aefa1cbfedd602218add81ab

This commit is contained in:
Corentin Cadiou 2015-07-28 13:33:47 +00:00
commit f9fdc916ad
15 changed files with 24284 additions and 41062 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -137,13 +137,13 @@ module.exports = {
/*
* Broadcast a message to all connected peers.
* @param message {Object} The message to broadcast.
#
*
broadcast: (message)->
throw new Error "You must implement broadcast!"
#
*
* Send a message to a peer, or set of peers
#
*
send: (peer_s, message)->
throw new Error "You must implement send!"
*/

View File

@ -490,7 +490,7 @@ module.exports = function() {
};
Insert.prototype.execute = function() {
var base1, distance_to_origin, i, o;
var base1, distance_to_origin, i, o, oDistance;
if (!this.validateSavedOperations()) {
return false;
} else {
@ -520,15 +520,16 @@ module.exports = function() {
i = distance_to_origin;
while (true) {
if (o !== this.next_cl) {
if (o.getDistanceToOrigin() === i) {
oDistance = o.getDistanceToOrigin();
if (oDistance === i) {
if (o.uid.creator < this.uid.creator) {
this.prev_cl = o;
distance_to_origin = i + 1;
} else {
}
} else if (o.getDistanceToOrigin() < i) {
if (i - distance_to_origin <= o.getDistanceToOrigin()) {
} else if (oDistance < i) {
if (i - distance_to_origin <= oDistance) {
this.prev_cl = o;
distance_to_origin = i + 1;
} else {

View File

@ -1,9 +1,11 @@
var basic_ops_uninitialized,
var RBTReeByIndex, basic_ops_uninitialized,
extend = function(child, parent) { for (var key in parent) { if (hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
hasProp = {}.hasOwnProperty;
basic_ops_uninitialized = require("./Basic");
RBTReeByIndex = require('bintrees/lib/rbtree_by_index');
module.exports = function() {
var basic_ops, ops;
basic_ops = basic_ops_uninitialized();
@ -122,6 +124,8 @@ module.exports = function() {
this.beginning.next_cl = this.end;
this.beginning.execute();
this.end.execute();
this.shortTree = new RBTreeByIndex();
this.completeTree = new RBTreeByIndex();
ListManager.__super__.constructor.call(this, custom_type, uid, content, content_operations);
}
@ -181,114 +185,128 @@ module.exports = function() {
return this.beginning.next_cl;
};
ListManager.prototype.getNextNonDeleted = function(start) {
var operation;
if (start.isDeleted() || (start.node == null)) {
operation = start.next_cl;
while (!(operation instanceof ops.Delimiter)) {
if (operation.is_deleted) {
operation = operation.next_cl;
} else {
break;
}
}
} else {
operation = start.node.next.node;
if (!operation) {
return false;
}
}
return operation;
};
ListManager.prototype.getPrevNonDeleted = function(start) {
var operation;
if (start.isDeleted() || (start.node == null)) {
operation = start.prev_cl;
while (!(operation instanceof ops.Delimiter)) {
if (operation.is_deleted) {
operation = operation.prev_cl;
} else {
break;
}
}
} else {
operation = start.node.prev.node;
if (!operation) {
return false;
}
}
return operation;
};
ListManager.prototype.toArray = function() {
var o, result;
o = this.beginning.next_cl;
result = [];
while (o !== this.end) {
if (!o.is_deleted) {
result.push(o.val());
}
o = o.next_cl;
}
return result;
return this.shortTree.map(function(operation) {
return operation.val();
});
};
ListManager.prototype.map = function(f) {
var o, result;
o = this.beginning.next_cl;
result = [];
while (o !== this.end) {
if (!o.is_deleted) {
result.push(f(o));
}
o = o.next_cl;
}
return result;
ListManager.prototype.map = function(fun) {
return this.shortTree.map(fun);
};
ListManager.prototype.fold = function(init, f) {
var o;
o = this.beginning.next_cl;
while (o !== this.end) {
if (!o.is_deleted) {
init = f(init, o);
}
o = o.next_cl;
}
return init;
ListManager.prototype.fold = function(init, fun) {
return this.shortTree.map(function(operation) {
return init = fun(init, operation);
});
};
ListManager.prototype.val = function(pos) {
var o;
if (pos != null) {
o = this.getOperationByPosition(pos + 1);
if (!(o instanceof ops.Delimiter)) {
return o.val();
} else {
throw new Error("this position does not exist");
}
return this.shortTree.find(pos).val();
} else {
return this.toArray();
}
};
ListManager.prototype.ref = function(pos) {
var o;
if (pos != null) {
o = this.getOperationByPosition(pos + 1);
if (!(o instanceof ops.Delimiter)) {
return o;
} else {
return null;
}
return this.shortTree.find(pos);
} else {
throw new Error("you must specify a position parameter");
return this.shortTree.map(function(operation) {
return operation;
});
}
};
ListManager.prototype.getOperationByPosition = function(position) {
var o;
o = this.beginning;
while (true) {
if (o instanceof ops.Delimiter && (o.prev_cl != null)) {
o = o.prev_cl;
while (o.isDeleted() && (o.prev_cl != null)) {
o = o.prev_cl;
}
break;
}
if (position <= 0 && !o.isDeleted()) {
break;
}
o = o.next_cl;
if (!o.isDeleted()) {
position -= 1;
}
if (position === 0) {
return this.beginning;
} else if (position === this.shortTree.size + 1) {
return this.end;
} else {
return this.shortTree.find(position - 1);
}
return o;
};
ListManager.prototype.push = function(content) {
return this.insertAfter(this.end.prev_cl, [content]);
};
ListManager.prototype.insertAfterHelper = function(root, content) {
var right;
if (!root.right) {
root.bt.right = content;
return content.bt.parent = root;
} else {
return right = root.next_cl;
}
};
ListManager.prototype.insertAfter = function(left, contents) {
var c, j, len, right, tmp;
right = left.next_cl;
while (right.isDeleted()) {
right = right.next_cl;
var c, j, leftNode, len, right, rightNode, tmp;
if (left === this.beginning) {
leftNode = null;
rightNode = this.shortTree.findNode(0);
right = rightNode ? rightNode.data : this.end;
} else {
rightNode = left.node.next;
leftNode = left.node;
right = rightNode ? rightNode.data : this.end;
}
left = right.prev_cl;
if (contents instanceof ops.Operation) {
(new ops.Insert(null, content, null, void 0, void 0, left, right)).execute();
tmp = new ops.Insert(null, content, null, void 0, void 0, left, right);
tmp.execute();
} else {
for (j = 0, len = contents.length; j < len; j++) {
c = contents[j];
if ((c != null) && (c._name != null) && (c._getModel != null)) {
c = c._getModel(this.custom_types, this.operations);
}
tmp = (new ops.Insert(null, c, null, void 0, void 0, left, right)).execute();
tmp = new ops.Insert(null, c, null, void 0, void 0, left, right);
tmp.execute();
leftNode = tmp.node;
left = tmp;
}
}
@ -301,29 +319,50 @@ module.exports = function() {
return this.insertAfter(ith, contents);
};
ListManager.prototype["delete"] = function(position, length) {
var d, delete_ops, i, j, o, ref;
ListManager.prototype.deleteRef = function(operation, length, dir) {
var deleteOperation, i, j, nextOperation, ref;
if (length == null) {
length = 1;
}
o = this.getOperationByPosition(position + 1);
delete_ops = [];
if (dir == null) {
dir = 'right';
}
nextOperation = (function(_this) {
return function(operation) {
if (dir === 'right') {
return _this.getNextNonDeleted(operation);
} else {
return _this.getPrevNonDeleted(operation);
}
};
})(this);
for (i = j = 0, ref = length; 0 <= ref ? j < ref : j > ref; i = 0 <= ref ? ++j : --j) {
if (o instanceof ops.Delimiter) {
if (operation instanceof ops.Delimiter) {
break;
}
d = (new ops.Delete(null, void 0, o)).execute();
o = o.next_cl;
while ((!(o instanceof ops.Delimiter)) && o.isDeleted()) {
o = o.next_cl;
}
delete_ops.push(d._encode());
deleteOperation = (new ops.Delete(null, void 0, operation)).execute();
operation = nextOperation(operation);
}
return this;
};
ListManager.prototype.callOperationSpecificInsertEvents = function(op) {
var getContentType;
ListManager.prototype["delete"] = function(position, length) {
var operation;
if (length == null) {
length = 1;
}
operation = this.getOperationByPosition(position + length);
return this.deleteRef(operation, length, 'left');
};
ListManager.prototype.callOperationSpecificInsertEvents = function(operation) {
var getContentType, next, nextNode, prev, prevNode;
prev = (this.getPrevNonDeleted(operation)) || this.beginning;
prevNode = prev ? prev.node : null;
next = (this.getNextNonDeleted(operation)) || this.end;
nextNode = next ? next.node : null;
operation.node = operation.node || (this.shortTree.insert_between(prevNode, nextNode, operation));
operation.completeNode = operation.completeNode || (this.completeTree.insert_between(operation.prev_cl.completeNode, operation.next_cl.completeNode, operation));
getContentType = function(content) {
if (content instanceof ops.Operation) {
return content.getCustomType();
@ -334,25 +373,31 @@ module.exports = function() {
return this.callEvent([
{
type: "insert",
reference: op,
position: op.getPosition(),
reference: operation,
position: operation.node.position(),
object: this.getCustomType(),
changedBy: op.uid.creator,
value: getContentType(op.val())
changedBy: operation.uid.creator,
value: getContentType(operation.val())
}
]);
};
ListManager.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {
ListManager.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) {
var position;
if (operation.node) {
position = operation.node.position();
this.shortTree.remove_node(operation.node);
operation.node = null;
}
return this.callEvent([
{
type: "delete",
reference: op,
position: op.getPosition(),
reference: operation,
position: position,
object: this.getCustomType(),
length: 1,
changedBy: del_op.uid.creator,
oldValue: op.val()
oldValue: operation.val()
}
]);
};
@ -406,14 +451,14 @@ module.exports = function() {
}
};
Composition.prototype.callOperationSpecificInsertEvents = function(op) {
Composition.prototype.callOperationSpecificInsertEvents = function(operation) {
var o;
if (this.tmp_composition_ref != null) {
if (op.uid.creator === this.tmp_composition_ref.creator && op.uid.op_number === this.tmp_composition_ref.op_number) {
this.composition_ref = op;
if (operation.uid.creator === this.tmp_composition_ref.creator && operation.uid.op_number === this.tmp_composition_ref.op_number) {
this.composition_ref = operation;
delete this.tmp_composition_ref;
op = op.next_cl;
if (op === this.end) {
operation = operation.next_cl;
if (operation === this.end) {
return;
}
} else {
@ -421,7 +466,7 @@ module.exports = function() {
}
}
o = this.end.prev_cl;
while (o !== op) {
while (o !== operation) {
this.getCustomType()._unapply(o.undo_delta);
o = o.prev_cl;
}
@ -433,13 +478,13 @@ module.exports = function() {
return this.callEvent([
{
type: "update",
changedBy: op.uid.creator,
changedBy: operation.uid.creator,
newValue: this.val()
}
]);
};
Composition.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {};
Composition.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) {};
Composition.prototype.applyDelta = function(delta, operations) {
(new ops.Insert(null, delta, operations, this, null, this.end.prev_cl, this.end)).execute();
@ -507,40 +552,40 @@ module.exports = function() {
return void 0;
};
ReplaceManager.prototype.callOperationSpecificInsertEvents = function(op) {
ReplaceManager.prototype.callOperationSpecificInsertEvents = function(operation) {
var old_value;
if (op.next_cl.type === "Delimiter" && op.prev_cl.type !== "Delimiter") {
if (!op.is_deleted) {
old_value = op.prev_cl.val();
if (operation.next_cl.type === "Delimiter" && operation.prev_cl.type !== "Delimiter") {
if (!operation.is_deleted) {
old_value = operation.prev_cl.val();
this.callEventDecorator([
{
type: "update",
changedBy: op.uid.creator,
changedBy: operation.uid.creator,
oldValue: old_value
}
]);
}
op.prev_cl.applyDelete();
} else if (op.next_cl.type !== "Delimiter") {
op.applyDelete();
operation.prev_cl.applyDelete();
} else if (operation.next_cl.type !== "Delimiter") {
operation.applyDelete();
} else {
this.callEventDecorator([
{
type: "add",
changedBy: op.uid.creator
changedBy: operation.uid.creator
}
]);
}
return void 0;
};
ReplaceManager.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {
if (op.next_cl.type === "Delimiter") {
ReplaceManager.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) {
if (operation.next_cl.type === "Delimiter") {
return this.callEventDecorator([
{
type: "delete",
changedBy: del_op.uid.creator,
oldValue: op.val()
oldValue: operation.val()
}
]);
}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -521,17 +521,18 @@ module.exports = ()->
# $this insert_position is to the left of $o (forever!)
while true
if o isnt @next_cl
oDistance = o.getDistanceToOrigin()
# $o happened concurrently
if o.getDistanceToOrigin() is i
if oDistance is i
# case 1
if o.uid.creator < @uid.creator
@prev_cl = o
distance_to_origin = i + 1
else
# nop
else if o.getDistanceToOrigin() < i
else if oDistance < i
# case 2
if i - distance_to_origin <= o.getDistanceToOrigin()
if i - distance_to_origin <= oDistance
@prev_cl = o
distance_to_origin = i + 1
else

View File

@ -1,4 +1,5 @@
basic_ops_uninitialized = require "./Basic"
RBTReeByIndex = require 'bintrees/lib/rbtree_by_index'
module.exports = ()->
basic_ops = basic_ops_uninitialized()
@ -107,6 +108,13 @@ module.exports = ()->
@beginning.next_cl = @end
@beginning.execute()
@end.execute()
# The short tree is storing the non deleted operations
@shortTree = new RBTreeByIndex()
# The complete tree is storing all the operations
@completeTree = new RBTreeByIndex()
super custom_type, uid, content, content_operations
type: "ListManager"
@ -155,54 +163,63 @@ module.exports = ()->
getFirstOperation: ()->
@beginning.next_cl
# Transforms the the list to an array
# get the next non-deleted operation
getNextNonDeleted: (start)->
if start.isDeleted() or not start.node?
operation = start.next_cl
while not ((operation instanceof ops.Delimiter))
if operation.is_deleted
operation = operation.next_cl
else
break
else
operation = start.node.next.node
if not operation
return false
operation
getPrevNonDeleted: (start) ->
if start.isDeleted() or not start.node?
operation = start.prev_cl
while not ((operation instanceof ops.Delimiter))
if operation.is_deleted
operation = operation.prev_cl
else
break
else
operation = start.node.prev.node
if not operation
return false
operation
# Transforms the list to an array
# Doesn't return left-right delimiter.
toArray: ()->
o = @beginning.next_cl
result = []
while o isnt @end
if not o.is_deleted
result.push o.val()
o = o.next_cl
result
@shortTree.map (operation) ->
operation.val()
map: (f)->
o = @beginning.next_cl
result = []
while o isnt @end
if not o.is_deleted
result.push f(o)
o = o.next_cl
result
map: (fun)->
@shortTree.map fun
fold: (init, f)->
o = @beginning.next_cl
while o isnt @end
if not o.is_deleted
init = f(init, o)
o = o.next_cl
init
fold: (init, fun)->
@shortTree.map (operation) ->
init = fun(init, operation)
val: (pos)->
if pos?
o = @getOperationByPosition(pos+1)
if not (o instanceof ops.Delimiter)
o.val()
else
throw new Error "this position does not exist"
@shortTree.find(pos).val()
else
@toArray()
ref: (pos)->
if pos?
o = @getOperationByPosition(pos+1)
if not (o instanceof ops.Delimiter)
o
else
null
# throw new Error "this position does not exist"
@shortTree.find(pos)
else
throw new Error "you must specify a position parameter"
@shortTree.map (operation) ->
operation
#
# Retrieves the x-th not deleted element.
@ -210,42 +227,49 @@ module.exports = ()->
# the 0th character is the left Delimiter
#
getOperationByPosition: (position)->
o = @beginning
while true
# find the i-th op
if o instanceof ops.Delimiter and o.prev_cl?
# the user or you gave a position parameter that is to big
# for the current array. Therefore we reach a Delimiter.
# Then, we'll just return the last character.
o = o.prev_cl
while o.isDeleted() and o.prev_cl?
o = o.prev_cl
break
if position <= 0 and not o.isDeleted()
break
o = o.next_cl
if not o.isDeleted()
position -= 1
o
if position == 0
@beginning
else if position == @shortTree.size + 1
@end
else
@shortTree.find (position-1)
push: (content)->
@insertAfter @end.prev_cl, [content]
insertAfterHelper: (root, content)->
if !root.right
root.bt.right = content
content.bt.parent = root
else
right = root.next_cl
insertAfter: (left, contents)->
right = left.next_cl
while right.isDeleted()
right = right.next_cl # find the first character to the right, that is not deleted. In the case that position is 0, its the Delimiter.
if left is @beginning
leftNode = null
rightNode = @shortTree.findNode 0
right = if rightNode then rightNode.data else @end
else
# left.node should always exist (insert after a non-deleted element)
rightNode = left.node.next
leftNode = left.node
right = if rightNode then rightNode.data else @end
left = right.prev_cl
# TODO: always expect an array as content. Then you can combine this with the other option (else)
if contents instanceof ops.Operation
(new ops.Insert null, content, null, undefined, undefined, left, right).execute()
tmp = new ops.Insert null, content, null, undefined, undefined, left, right
tmp.execute()
else
for c in contents
if c? and c._name? and c._getModel?
c = c._getModel(@custom_types, @operations)
tmp = (new ops.Insert null, c, null, undefined, undefined, left, right).execute()
tmp = new ops.Insert null, c, null, undefined, undefined, left, right
tmp.execute()
leftNode = tmp.node
left = tmp
@
@ -266,45 +290,62 @@ module.exports = ()->
#
# @return {ListManager Type} This String object
#
delete: (position, length = 1)->
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
deleteRef: (operation, length = 1, dir = 'right') ->
nextOperation = (operation) =>
if dir == 'right' then @getNextNonDeleted operation else @getPrevNonDeleted operation
delete_ops = []
for i in [0...length]
if o instanceof ops.Delimiter
if operation instanceof ops.Delimiter
break
d = (new ops.Delete null, undefined, o).execute()
o = o.next_cl
while (not (o instanceof ops.Delimiter)) and o.isDeleted()
o = o.next_cl
delete_ops.push d._encode()
deleteOperation = (new ops.Delete null, undefined, operation).execute()
operation = nextOperation operation
@
delete: (position, length = 1)->
operation = @getOperationByPosition(position+length) # position 0 in this case is the deletion of the first character
@deleteRef operation, length, 'left'
callOperationSpecificInsertEvents: (operation)->
prev = (@getPrevNonDeleted operation) or @beginning
prevNode = if prev then prev.node else null
next = (@getNextNonDeleted operation) or @end
nextNode = if next then next.node else null
operation.node = operation.node or (@shortTree.insert_between prevNode, nextNode, operation)
operation.completeNode = operation.completeNode or (@completeTree.insert_between operation.prev_cl.completeNode, operation.next_cl.completeNode, operation)
callOperationSpecificInsertEvents: (op)->
getContentType = (content)->
if content instanceof ops.Operation
content.getCustomType()
else
content
@callEvent [
type: "insert"
reference: op
position: op.getPosition()
reference: operation
position: operation.node.position()
object: @getCustomType()
changedBy: op.uid.creator
value: getContentType op.val()
changedBy: operation.uid.creator
value: getContentType operation.val()
]
callOperationSpecificDeleteEvents: (op, del_op)->
callOperationSpecificDeleteEvents: (operation, del_op)->
if operation.node
position = operation.node.position()
@shortTree.remove_node operation.node
operation.node = null
@callEvent [
type: "delete"
reference: op
position: op.getPosition()
reference: operation
position: position
object: @getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
length: 1
changedBy: del_op.uid.creator
oldValue: op.val()
oldValue: operation.val()
]
ops.ListManager.parse = (json)->
@ -355,19 +396,19 @@ module.exports = ()->
#
# This is called, when the Insert-operation was successfully executed.
#
callOperationSpecificInsertEvents: (op)->
callOperationSpecificInsertEvents: (operation)->
if @tmp_composition_ref?
if op.uid.creator is @tmp_composition_ref.creator and op.uid.op_number is @tmp_composition_ref.op_number
@composition_ref = op
if operation.uid.creator is @tmp_composition_ref.creator and operation.uid.op_number is @tmp_composition_ref.op_number
@composition_ref = operation
delete @tmp_composition_ref
op = op.next_cl
if op is @end
operation = operation.next_cl
if operation is @end
return
else
return
o = @end.prev_cl
while o isnt op
while o isnt operation
@getCustomType()._unapply o.undo_delta
o = o.prev_cl
while o isnt @end
@ -377,11 +418,11 @@ module.exports = ()->
@callEvent [
type: "update"
changedBy: op.uid.creator
changedBy: operation.uid.creator
newValue: @val()
]
callOperationSpecificDeleteEvents: (op, del_op)->
callOperationSpecificDeleteEvents: (operation, del_op)->
return
#
@ -466,34 +507,34 @@ module.exports = ()->
# TODO: consider doing this in a more consistent manner. This could also be
# done with execute. But currently, there are no specital Insert-ops for ListManager.
#
callOperationSpecificInsertEvents: (op)->
if op.next_cl.type is "Delimiter" and op.prev_cl.type isnt "Delimiter"
callOperationSpecificInsertEvents: (operation)->
if operation.next_cl.type is "Delimiter" and operation.prev_cl.type isnt "Delimiter"
# this replaces another Replaceable
if not op.is_deleted # When this is received from the HB, this could already be deleted!
old_value = op.prev_cl.val()
if not operation.is_deleted # When this is received from the HB, this could already be deleted!
old_value = operation.prev_cl.val()
@callEventDecorator [
type: "update"
changedBy: op.uid.creator
changedBy: operation.uid.creator
oldValue: old_value
]
op.prev_cl.applyDelete()
else if op.next_cl.type isnt "Delimiter"
operation.prev_cl.applyDelete()
else if operation.next_cl.type isnt "Delimiter"
# This won't be recognized by the user, because another
# concurrent operation is set as the current value of the RM
op.applyDelete()
operation.applyDelete()
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
@callEventDecorator [
type: "add"
changedBy: op.uid.creator
changedBy: operation.uid.creator
]
undefined
callOperationSpecificDeleteEvents: (op, del_op)->
if op.next_cl.type is "Delimiter"
callOperationSpecificDeleteEvents: (operation, del_op)->
if operation.next_cl.type is "Delimiter"
@callEventDecorator [
type: "delete"
changedBy: del_op.uid.creator
oldValue: op.val()
oldValue: operation.val()
]
@ -528,6 +569,4 @@ module.exports = ()->
# throw new Error "Replace Manager doesn't contain anything."
o.val?() # ? - for the case that (currently) the RM does not contain anything (then o is a Delimiter)
basic_ops

View File

@ -27,6 +27,7 @@
},
"homepage": "https://dadamonad.github.io/yjs/",
"dependencies": {
"bintrees": "git://github.com/cphyc/js_bintrees#rbtree-by-index"
},
"devDependencies": {
"chai": "^2.2.0",

4
y.js

File diff suppressed because one or more lines are too long