Add a complete tree to find faster
This commit is contained in:
parent
9252c9f78a
commit
6699623d10
File diff suppressed because one or more lines are too long
@ -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!"
|
||||||
*/
|
*/
|
||||||
|
@ -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
@ -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
|
||||||
|
@ -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",
|
||||||
|
Loading…
x
Reference in New Issue
Block a user