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.
* @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

@ -125,6 +125,7 @@ module.exports = function() {
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);
}
@ -186,13 +187,11 @@ module.exports = function() {
ListManager.prototype.getNextNonDeleted = function(start) {
var operation;
if (start.isDeleted()) {
if (start.isDeleted() || (start.node == null)) {
operation = start.next_cl;
while (!(operation instanceof ops.Delimiter)) {
if (operation.is_deleted) {
operation = operation.next_cl;
} else if (operation instanceof ops.Delimiter) {
return false;
} else {
break;
}
@ -206,6 +205,26 @@ module.exports = function() {
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() {
return this.shortTree.map(function(operation) {
return operation.val();
@ -223,8 +242,7 @@ module.exports = function() {
};
ListManager.prototype.val = function(pos) {
var ref;
if ((0 <= (ref = pos != null) && ref < this.shortTree.size)) {
if (pos != null) {
return this.shortTree.find(pos).val();
} else {
return this.toArray();
@ -232,8 +250,7 @@ module.exports = function() {
};
ListManager.prototype.ref = function(pos) {
var ref;
if ((0 <= (ref = pos != null) && ref < this.shortTree.size)) {
if (pos != null) {
return this.shortTree.find(pos);
} else {
return this.shortTree.map(function(operation) {
@ -271,24 +288,25 @@ module.exports = function() {
if (left === this.beginning) {
leftNode = null;
rightNode = this.shortTree.findNode(0);
right = rightNode.data;
right = rightNode ? rightNode.data : this.end;
} else {
rightNode = left.node.nextNode();
rightNode = left.node.next();
leftNode = left.node;
right = rightNode.data;
right = rightNode ? rightNode.data : this.end;
}
left = right.prev_cl;
if (contents instanceof ops.Operation) {
tmp = (new ops.Insert(null, content, null, void 0, void 0, left, right)).execute();
this.shortTree.insert_between(leftNode, rightNode, tmp);
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();
leftNode = this.shortTree.insert_between(leftNode, rightNode, tmp);
tmp = new ops.Insert(null, c, null, void 0, void 0, left, right);
tmp.execute();
leftNode = tmp.node;
left = tmp;
}
}
@ -311,11 +329,8 @@ module.exports = function() {
break;
}
deleteOperation = (new ops.Delete(null, void 0, operation)).execute();
operation.node.traverse_up(function(node, parent) {
return parent.deleted_weight = (parent.deleted_weight || 0) + 1;
});
this.shortTree.remove_node(operation.node);
operation = getNextNonDeleted(operation);
operation.node = null;
operation = this.getNextNonDeleted(operation);
}
return this;
};
@ -330,7 +345,13 @@ module.exports = function() {
};
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) {
if (content instanceof ops.Operation) {
return content.getCustomType();
@ -342,7 +363,7 @@ module.exports = function() {
{
type: "insert",
reference: operation,
position: operation.getPosition(),
position: operation.completeNode.position(),
object: this.getCustomType(),
changedBy: operation.uid.creator,
value: getContentType(operation.val())
@ -351,11 +372,15 @@ module.exports = function() {
};
ListManager.prototype.callOperationSpecificDeleteEvents = function(operation, del_op) {
if (operation.node) {
this.shortTree.remove_node(operation.node);
operation.node = null;
}
return this.callEvent([
{
type: "delete",
reference: operation,
position: operation.getPosition(),
position: operation.completeNode.position(),
object: this.getCustomType(),
length: 1,
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()
@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"
@ -160,21 +165,36 @@ module.exports = ()->
# get the next non-deleted operation
getNextNonDeleted: (start)->
if start.isDeleted()
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 if operation instanceof ops.Delimiter
return false
else break
else
break
else
operation = start.node.next().node
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: ()->
@ -189,13 +209,13 @@ module.exports = ()->
init = fun(init, operation)
val: (pos)->
if 0 <= pos? < @shortTree.size
if pos?
@shortTree.find(pos).val()
else
@toArray()
ref: (pos)->
if 0 <= pos? < @shortTree.size
if pos?
@shortTree.find(pos)
else
@shortTree.map (operation) ->
@ -229,24 +249,26 @@ module.exports = ()->
if left is @beginning
leftNode = null
rightNode = @shortTree.findNode 0
right = rightNode.data
right = if rightNode then rightNode.data else @end
else
rightNode = left.node.nextNode()
# left.node should always exist (insert after a non-deleted element)
rightNode = left.node.next
leftNode = left.node
right = rightNode.data
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
tmp = (new ops.Insert null, content, null, undefined, undefined, left, right).execute()
@shortTree.insert_between leftNode, rightNode, tmp
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()
leftNode = @shortTree.insert_between leftNode, rightNode, tmp
tmp = new ops.Insert null, c, null, undefined, undefined, left, right
tmp.execute()
leftNode = tmp.node
left = tmp
@
@ -268,44 +290,58 @@ module.exports = ()->
#
# @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]
if operation instanceof ops.Delimiter
break
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)->
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)->
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)->
if content instanceof ops.Operation
content.getCustomType()
else
content
@callEvent [
type: "insert"
reference: operation
position: operation.getPosition()
position: operation.node.position()
object: @getCustomType()
changedBy: operation.uid.creator
value: getContentType operation.val()
]
callOperationSpecificDeleteEvents: (operation, del_op)->
if operation.node
position = operation.node.position()
@shortTree.remove_node operation.node
operation.node = null
@callEvent [
type: "delete"
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)
length: 1
changedBy: del_op.uid.creator

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