Add a complete tree to find faster

This commit is contained in:
Corentin STG_CADIOU 2015-07-15 17:25:07 +02:00
parent 9252c9f78a
commit 6699623d10
12 changed files with 9886 additions and 4059 deletions

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. * Broadcast a message to all connected peers.
* @param message {Object} The message to broadcast. * @param message {Object} The message to broadcast.
# *
broadcast: (message)-> broadcast: (message)->
throw new Error "You must implement broadcast!" throw new Error "You must implement broadcast!"
# *
* Send a message to a peer, or set of peers * Send a message to a peer, or set of peers
# *
send: (peer_s, message)-> send: (peer_s, message)->
throw new Error "You must implement send!" throw new Error "You must implement send!"
*/ */

View File

@ -125,6 +125,7 @@ module.exports = function() {
this.beginning.execute(); this.beginning.execute();
this.end.execute(); this.end.execute();
this.shortTree = new RBTreeByIndex(); this.shortTree = new RBTreeByIndex();
this.completeTree = new RBTreeByIndex();
ListManager.__super__.constructor.call(this, custom_type, uid, content, content_operations); ListManager.__super__.constructor.call(this, custom_type, uid, content, content_operations);
} }
@ -186,13 +187,11 @@ module.exports = function() {
ListManager.prototype.getNextNonDeleted = function(start) { ListManager.prototype.getNextNonDeleted = function(start) {
var operation; var operation;
if (start.isDeleted()) { if (start.isDeleted() || (start.node == null)) {
operation = start.next_cl; operation = start.next_cl;
while (!(operation instanceof ops.Delimiter)) { while (!(operation instanceof ops.Delimiter)) {
if (operation.is_deleted) { if (operation.is_deleted) {
operation = operation.next_cl; operation = operation.next_cl;
} else if (operation instanceof ops.Delimiter) {
return false;
} else { } else {
break; break;
} }
@ -206,6 +205,26 @@ module.exports = function() {
return operation; 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() { ListManager.prototype.toArray = function() {
return this.shortTree.map(function(operation) { return this.shortTree.map(function(operation) {
return operation.val(); return operation.val();
@ -223,8 +242,7 @@ module.exports = function() {
}; };
ListManager.prototype.val = function(pos) { ListManager.prototype.val = function(pos) {
var ref; if (pos != null) {
if ((0 <= (ref = pos != null) && ref < this.shortTree.size)) {
return this.shortTree.find(pos).val(); return this.shortTree.find(pos).val();
} else { } else {
return this.toArray(); return this.toArray();
@ -232,8 +250,7 @@ module.exports = function() {
}; };
ListManager.prototype.ref = function(pos) { ListManager.prototype.ref = function(pos) {
var ref; if (pos != null) {
if ((0 <= (ref = pos != null) && ref < this.shortTree.size)) {
return this.shortTree.find(pos); return this.shortTree.find(pos);
} else { } else {
return this.shortTree.map(function(operation) { return this.shortTree.map(function(operation) {
@ -271,24 +288,25 @@ module.exports = function() {
if (left === this.beginning) { if (left === this.beginning) {
leftNode = null; leftNode = null;
rightNode = this.shortTree.findNode(0); rightNode = this.shortTree.findNode(0);
right = rightNode.data; right = rightNode ? rightNode.data : this.end;
} else { } else {
rightNode = left.node.nextNode(); rightNode = left.node.next();
leftNode = left.node; leftNode = left.node;
right = rightNode.data; right = rightNode ? rightNode.data : this.end;
} }
left = right.prev_cl; left = right.prev_cl;
if (contents instanceof ops.Operation) { if (contents instanceof ops.Operation) {
tmp = (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);
this.shortTree.insert_between(leftNode, rightNode, tmp); tmp.execute();
} else { } else {
for (j = 0, len = contents.length; j < len; j++) { for (j = 0, len = contents.length; j < len; j++) {
c = contents[j]; c = contents[j];
if ((c != null) && (c._name != null) && (c._getModel != null)) { if ((c != null) && (c._name != null) && (c._getModel != null)) {
c = c._getModel(this.custom_types, this.operations); 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);
leftNode = this.shortTree.insert_between(leftNode, rightNode, tmp); tmp.execute();
leftNode = tmp.node;
left = tmp; left = tmp;
} }
} }
@ -311,11 +329,8 @@ module.exports = function() {
break; break;
} }
deleteOperation = (new ops.Delete(null, void 0, operation)).execute(); deleteOperation = (new ops.Delete(null, void 0, operation)).execute();
operation.node.traverse_up(function(node, parent) { operation.node = null;
return parent.deleted_weight = (parent.deleted_weight || 0) + 1; operation = this.getNextNonDeleted(operation);
});
this.shortTree.remove_node(operation.node);
operation = getNextNonDeleted(operation);
} }
return this; return this;
}; };
@ -330,7 +345,13 @@ module.exports = function() {
}; };
ListManager.prototype.callOperationSpecificInsertEvents = function(operation) { ListManager.prototype.callOperationSpecificInsertEvents = function(operation) {
var getContentType; 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) { getContentType = function(content) {
if (content instanceof ops.Operation) { if (content instanceof ops.Operation) {
return content.getCustomType(); return content.getCustomType();
@ -342,7 +363,7 @@ module.exports = function() {
{ {
type: "insert", type: "insert",
reference: operation, reference: operation,
position: operation.getPosition(), position: operation.completeNode.position(),
object: this.getCustomType(), object: this.getCustomType(),
changedBy: operation.uid.creator, changedBy: operation.uid.creator,
value: getContentType(operation.val()) value: getContentType(operation.val())
@ -351,11 +372,15 @@ module.exports = function() {
}; };
ListManager.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) { ListManager.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) {
if (operation.node) {
this.shortTree.remove_node(operation.node);
operation.node = null;
}
return this.callEvent([ return this.callEvent([
{ {
type: "delete", type: "delete",
reference: operation, reference: operation,
position: operation.getPosition(), position: operation.completeNode.position(),
object: this.getCustomType(), object: this.getCustomType(),
length: 1, length: 1,
changedBy: del_op.uid.creator, changedBy: del_op.uid.creator,

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

@ -109,7 +109,12 @@ module.exports = ()->
@beginning.execute() @beginning.execute()
@end.execute() @end.execute()
# The short tree is storing the non deleted operations
@shortTree = new RBTreeByIndex() @shortTree = new RBTreeByIndex()
# The complete tree is storing all the operations
@completeTree = new RBTreeByIndex()
super custom_type, uid, content, content_operations super custom_type, uid, content, content_operations
type: "ListManager" type: "ListManager"
@ -160,21 +165,36 @@ module.exports = ()->
# get the next non-deleted operation # get the next non-deleted operation
getNextNonDeleted: (start)-> getNextNonDeleted: (start)->
if start.isDeleted() if start.isDeleted() or not start.node?
operation = start.next_cl operation = start.next_cl
while not ((operation instanceof ops.Delimiter)) while not ((operation instanceof ops.Delimiter))
if operation.is_deleted if operation.is_deleted
operation = operation.next_cl operation = operation.next_cl
else if operation instanceof ops.Delimiter else
return false break
else break
else else
operation = start.node.next().node operation = start.node.next.node
if not operation if not operation
return false return false
operation 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 # Transforms the list to an array
# Doesn't return left-right delimiter. # Doesn't return left-right delimiter.
toArray: ()-> toArray: ()->
@ -189,13 +209,13 @@ module.exports = ()->
init = fun(init, operation) init = fun(init, operation)
val: (pos)-> val: (pos)->
if 0 <= pos? < @shortTree.size if pos?
@shortTree.find(pos).val() @shortTree.find(pos).val()
else else
@toArray() @toArray()
ref: (pos)-> ref: (pos)->
if 0 <= pos? < @shortTree.size if pos?
@shortTree.find(pos) @shortTree.find(pos)
else else
@shortTree.map (operation) -> @shortTree.map (operation) ->
@ -229,24 +249,26 @@ module.exports = ()->
if left is @beginning if left is @beginning
leftNode = null leftNode = null
rightNode = @shortTree.findNode 0 rightNode = @shortTree.findNode 0
right = rightNode.data right = if rightNode then rightNode.data else @end
else else
rightNode = left.node.nextNode() # left.node should always exist (insert after a non-deleted element)
rightNode = left.node.next
leftNode = left.node leftNode = left.node
right = rightNode.data right = if rightNode then rightNode.data else @end
left = right.prev_cl left = right.prev_cl
# TODO: always expect an array as content. Then you can combine this with the other option (else) # TODO: always expect an array as content. Then you can combine this with the other option (else)
if contents instanceof ops.Operation if contents instanceof ops.Operation
tmp = (new ops.Insert null, content, null, undefined, undefined, left, right).execute() tmp = new ops.Insert null, content, null, undefined, undefined, left, right
@shortTree.insert_between leftNode, rightNode, tmp tmp.execute()
else else
for c in contents for c in contents
if c? and c._name? and c._getModel? if c? and c._name? and c._getModel?
c = c._getModel(@custom_types, @operations) 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
leftNode = @shortTree.insert_between leftNode, rightNode, tmp tmp.execute()
leftNode = tmp.node
left = tmp left = tmp
@ @
@ -268,44 +290,58 @@ module.exports = ()->
# #
# @return {ListManager Type} This String object # @return {ListManager Type} This String object
# #
deleteRef: (operation, length = 1) -> deleteRef: (operation, length = 1, dir = 'right') ->
nextOperation = (operation) =>
if dir == 'right' then @getNextNonDeleted operation else @getPrevNonDeleted operation
for i in [0...length] for i in [0...length]
if operation instanceof ops.Delimiter if operation instanceof ops.Delimiter
break break
deleteOperation = (new ops.Delete null, undefined, operation).execute() deleteOperation = (new ops.Delete null, undefined, operation).execute()
operation.node.traverse_up (node, parent) ->
parent.deleted_weight = (parent.deleted_weight or 0) + 1
@shortTree.remove_node operation.node
operation = getNextNonDeleted operation operation = nextOperation operation
@ @
delete: (position, length = 1)-> delete: (position, length = 1)->
operation = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character operation = @getOperationByPosition(position+length) # position 0 in this case is the deletion of the first character
@deleteRef operation, length @deleteRef operation, length, 'left'
callOperationSpecificInsertEvents: (operation)-> 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)
getContentType = (content)-> getContentType = (content)->
if content instanceof ops.Operation if content instanceof ops.Operation
content.getCustomType() content.getCustomType()
else else
content content
@callEvent [ @callEvent [
type: "insert" type: "insert"
reference: operation reference: operation
position: operation.getPosition() position: operation.node.position()
object: @getCustomType() object: @getCustomType()
changedBy: operation.uid.creator changedBy: operation.uid.creator
value: getContentType operation.val() value: getContentType operation.val()
] ]
callOperationSpecificDeleteEvents: (operation, del_op)-> callOperationSpecificDeleteEvents: (operation, del_op)->
if operation.node
position = operation.node.position()
@shortTree.remove_node operation.node
operation.node = null
@callEvent [ @callEvent [
type: "delete" type: "delete"
reference: operation reference: operation
position: operation.getPosition() position: position
object: @getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent) object: @getCustomType() # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
length: 1 length: 1
changedBy: del_op.uid.creator changedBy: del_op.uid.creator

View File

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

4
y.js

File diff suppressed because one or more lines are too long