There are now "Pseudo operations" that are not sent, and get be queried by a special parameter with the HB.getOperation. This will reduce the number of operations that are sent and is necessary for the Array implementation, that I plan to implement in the near future
This commit is contained in:
@@ -152,10 +152,17 @@ class HistoryBuffer
|
||||
#
|
||||
# Retrieve an operation from a unique id.
|
||||
#
|
||||
# when uid has a "sub" property, the value of it will be applied
|
||||
# on the operations retrieveSub method (which must! be defined)
|
||||
#
|
||||
getOperation: (uid)->
|
||||
if uid.uid?
|
||||
uid = uid.uid
|
||||
@buffer[uid.creator]?[uid.op_number]
|
||||
o = @buffer[uid.creator]?[uid.op_number]
|
||||
if uid.sub? and o?
|
||||
o.retrieveSub uid.sub
|
||||
else
|
||||
o
|
||||
|
||||
#
|
||||
# Add an operation to the HB. Note that this will not link it against
|
||||
|
||||
@@ -31,6 +31,9 @@ module.exports = (HB)->
|
||||
|
||||
type: "Operation"
|
||||
|
||||
retrieveSub: ()->
|
||||
throw new Error "sub properties are not enable on this operation type!"
|
||||
|
||||
#
|
||||
# Add an event listener. It depends on the operation which events are supported.
|
||||
# @param {Function} f f is executed in case the event fires.
|
||||
@@ -101,7 +104,16 @@ module.exports = (HB)->
|
||||
# Computes a unique identifier (uid) that identifies this operation.
|
||||
#
|
||||
getUid: ()->
|
||||
@uid
|
||||
if not @uid.noOperation?
|
||||
@uid
|
||||
else
|
||||
@uid.alt # could be (safely) undefined
|
||||
|
||||
cloneUid: ()->
|
||||
uid = {}
|
||||
for n,v of @getUid()
|
||||
uid[n] = v
|
||||
uid
|
||||
|
||||
dontSync: ()->
|
||||
@uid.doSync = false
|
||||
@@ -119,9 +131,10 @@ module.exports = (HB)->
|
||||
# There is only one other place, where this can be done - before an Insertion
|
||||
# is executed (because we need the creator_id)
|
||||
@uid = HB.getNextOperationIdentifier()
|
||||
HB.addOperation @
|
||||
for l in execution_listener
|
||||
l @_encode()
|
||||
if not @uid.noOperation?
|
||||
HB.addOperation @
|
||||
for l in execution_listener
|
||||
l @_encode()
|
||||
@
|
||||
|
||||
#
|
||||
@@ -180,7 +193,6 @@ module.exports = (HB)->
|
||||
success
|
||||
|
||||
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# A simple Delete-type operation that deletes an operation.
|
||||
@@ -249,7 +261,8 @@ module.exports = (HB)->
|
||||
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
|
||||
# @param {Operation} next_cl The successor of this operation in the complete-list (cl)
|
||||
#
|
||||
constructor: (uid, prev_cl, next_cl, origin)->
|
||||
constructor: (uid, prev_cl, next_cl, origin, parent)->
|
||||
@saveOperation 'parent', parent
|
||||
@saveOperation 'prev_cl', prev_cl
|
||||
@saveOperation 'next_cl', next_cl
|
||||
if origin?
|
||||
@@ -283,7 +296,6 @@ module.exports = (HB)->
|
||||
@prev_cl.applyDelete()
|
||||
|
||||
cleanup: ()->
|
||||
# TODO: Debugging
|
||||
if @next_cl.isDeleted()
|
||||
# delete all ops that delete this insertion
|
||||
for d in @deleted_by
|
||||
@@ -300,8 +312,9 @@ module.exports = (HB)->
|
||||
@prev_cl.next_cl = @next_cl
|
||||
@next_cl.prev_cl = @prev_cl
|
||||
super
|
||||
else if @next_cl? and @prev_cl?
|
||||
throw new Error "This insertion was not supposed to be deleted!"
|
||||
# else
|
||||
# Someone inserted something in the meantime.
|
||||
# Remember: this can only be garbage collected when next_cl is deleted
|
||||
|
||||
#
|
||||
# @private
|
||||
@@ -324,6 +337,13 @@ module.exports = (HB)->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
if @parent?
|
||||
if not @prev_cl?
|
||||
@prev_cl = @parent.beginning
|
||||
if not @origin?
|
||||
@origin = @parent.beginning
|
||||
if not @next_cl?
|
||||
@next_cl = @parent.end
|
||||
if @prev_cl?
|
||||
distance_to_origin = @getDistanceToOrigin() # most cases: 0
|
||||
o = @prev_cl.next_cl
|
||||
@@ -419,8 +439,8 @@ module.exports = (HB)->
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Object} content
|
||||
#
|
||||
constructor: (uid, @content, prev, next, origin)->
|
||||
super uid, prev, next, origin
|
||||
constructor: (uid, @content)->
|
||||
super uid
|
||||
|
||||
type: "ImmutableObject"
|
||||
|
||||
@@ -439,23 +459,14 @@ module.exports = (HB)->
|
||||
'uid' : @getUid()
|
||||
'content' : @content
|
||||
}
|
||||
if @prev_cl?
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
if @next_cl?
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? # and @origin isnt @prev_cl
|
||||
json["origin"] = @origin().getUid()
|
||||
json
|
||||
|
||||
parser['ImmutableObject'] = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'content' : content
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
} = json
|
||||
new ImmutableObject uid, content, prev, next, origin
|
||||
new ImmutableObject uid, content
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
@@ -469,11 +480,11 @@ module.exports = (HB)->
|
||||
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl)
|
||||
# @param {Operation} next_cl The successor of this operation in the complete-list (cl)
|
||||
#
|
||||
constructor: (uid, prev_cl, next_cl, origin)->
|
||||
constructor: (prev_cl, next_cl, origin)->
|
||||
@saveOperation 'prev_cl', prev_cl
|
||||
@saveOperation 'next_cl', next_cl
|
||||
@saveOperation 'origin', prev_cl
|
||||
super uid
|
||||
super {noOperation: true}
|
||||
|
||||
type: "Delimiter"
|
||||
|
||||
|
||||
@@ -33,9 +33,7 @@ module.exports = (HB)->
|
||||
#
|
||||
val: (name, content)->
|
||||
if content?
|
||||
if not @map[name]?
|
||||
(new AddName undefined, @, name).execute()
|
||||
@map[name].replace content
|
||||
@retrieveSub(name).replace content
|
||||
@
|
||||
else if name?
|
||||
prop = @map[name]
|
||||
@@ -60,85 +58,22 @@ module.exports = (HB)->
|
||||
delete: (name)->
|
||||
@map[name]?.deleteContent()
|
||||
@
|
||||
#
|
||||
# @nodoc
|
||||
# When a new property in a map manager is created, then the uids of the inserted Operations
|
||||
# must be unique (think about concurrent operations). Therefore only an AddName operation is allowed to
|
||||
# add a property in a MapManager. If two AddName operations on the same MapManager name happen concurrently
|
||||
# only one will AddName operation will be executed.
|
||||
#
|
||||
class AddName extends types.Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Object} map_manager Uid or reference to the MapManager.
|
||||
# @param {String} name Name of the property that will be added.
|
||||
#
|
||||
constructor: (uid, map_manager, @name)->
|
||||
@saveOperation 'map_manager', map_manager
|
||||
super uid
|
||||
|
||||
type: "AddName"
|
||||
|
||||
applyDelete: ()->
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
#
|
||||
# If map_manager doesn't have the property name, then add it.
|
||||
# The ReplaceManager that is being written on the property is unique
|
||||
# in such a way that if AddName is executed (from another peer) it will
|
||||
# always have the same result (ReplaceManager, and its beginning and end are the same)
|
||||
#
|
||||
execute: ()->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
# helper for cloning an object
|
||||
clone = (o)->
|
||||
p = {}
|
||||
for name,value of o
|
||||
p[name] = value
|
||||
p
|
||||
uid_r = clone(@map_manager.getUid())
|
||||
uid_r.doSync = false
|
||||
uid_r.op_number = "_#{uid_r.op_number}_RM_#{@name}"
|
||||
if not HB.getOperation(uid_r)?
|
||||
uid_beg = clone(uid_r)
|
||||
uid_beg.op_number = "#{uid_r.op_number}_beginning"
|
||||
uid_end = clone(uid_r)
|
||||
uid_end.op_number = "#{uid_r.op_number}_end"
|
||||
beg = (new types.Delimiter uid_beg, undefined, uid_end).execute()
|
||||
end = (new types.Delimiter uid_end, beg, undefined).execute()
|
||||
event_properties =
|
||||
name: @name
|
||||
event_this = @map_manager
|
||||
@map_manager.map[@name] = new ReplaceManager event_properties, event_this, uid_r, beg, end
|
||||
@map_manager.map[@name].setParent @map_manager, @name
|
||||
(@map_manager.map[@name].add_name_ops ?= []).push @
|
||||
@map_manager.map[@name].execute()
|
||||
super
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
#
|
||||
_encode: ()->
|
||||
{
|
||||
'type' : "AddName"
|
||||
'uid' : @getUid()
|
||||
'map_manager' : @map_manager.getUid()
|
||||
'name' : @name
|
||||
}
|
||||
|
||||
parser['AddName'] = (json)->
|
||||
{
|
||||
'map_manager' : map_manager
|
||||
'uid' : uid
|
||||
'name' : name
|
||||
} = json
|
||||
new AddName uid, map_manager, name
|
||||
retrieveSub: (property_name)->
|
||||
if not @map[property_name]?
|
||||
event_properties =
|
||||
name: property_name
|
||||
event_this = @
|
||||
map_uid = @cloneUid()
|
||||
map_uid.sub = property_name
|
||||
rm_uid =
|
||||
noOperation: true
|
||||
alt: map_uid
|
||||
rm = new ReplaceManager event_properties, event_this, rm_uid # this operation shall not be saved in the HB
|
||||
@map[property_name] = rm
|
||||
rm.setParent @, property_name
|
||||
rm.execute()
|
||||
@map[property_name]
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
@@ -151,17 +86,13 @@ module.exports = (HB)->
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Delimiter} beginning Reference or Object.
|
||||
# @param {Delimiter} end Reference or Object.
|
||||
constructor: (uid, beginning, end, prev, next, origin)->
|
||||
if beginning? and end?
|
||||
@saveOperation 'beginning', beginning
|
||||
@saveOperation 'end', end
|
||||
else
|
||||
@beginning = new types.Delimiter undefined, undefined, undefined
|
||||
@end = new types.Delimiter undefined, @beginning, undefined
|
||||
@beginning.next_cl = @end
|
||||
@beginning.execute()
|
||||
@end.execute()
|
||||
super uid, prev, next, origin
|
||||
constructor: (uid)->
|
||||
@beginning = new types.Delimiter undefined, undefined
|
||||
@end = new types.Delimiter @beginning, undefined
|
||||
@beginning.next_cl = @end
|
||||
@beginning.execute()
|
||||
@end.execute()
|
||||
super uid
|
||||
|
||||
type: "ListManager"
|
||||
|
||||
@@ -236,10 +167,10 @@ module.exports = (HB)->
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
# @param {Delimiter} beginning Reference or Object.
|
||||
# @param {Delimiter} end Reference or Object.
|
||||
constructor: (@event_properties, @event_this, uid, beginning, end, prev, next, origin)->
|
||||
constructor: (@event_properties, @event_this, uid, beginning, end)->
|
||||
if not @event_properties['object']?
|
||||
@event_properties['object'] = @event_this
|
||||
super uid, beginning, end, prev, next, origin
|
||||
super uid, beginning, end
|
||||
|
||||
type: "ReplaceManager"
|
||||
|
||||
@@ -248,10 +179,6 @@ module.exports = (HB)->
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.next_cl
|
||||
# if this was created by an AddName operation, delete it too
|
||||
if @add_name_ops?
|
||||
for o in @add_name_ops
|
||||
o.applyDelete()
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
@@ -312,25 +239,8 @@ module.exports = (HB)->
|
||||
'beginning' : @beginning.getUid()
|
||||
'end' : @end.getUid()
|
||||
}
|
||||
if @prev_cl? and @next_cl?
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? # TODO: do this everywhere: and @origin isnt @prev_cl
|
||||
json["origin"] = @origin().getUid()
|
||||
json
|
||||
|
||||
parser["ReplaceManager"] = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
'beginning' : beginning
|
||||
'end' : end
|
||||
} = json
|
||||
new ReplaceManager uid, beginning, end, prev, next, origin
|
||||
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# The ReplaceManager manages Replaceables.
|
||||
@@ -346,9 +256,7 @@ module.exports = (HB)->
|
||||
constructor: (content, parent, uid, prev, next, origin, is_deleted)->
|
||||
@saveOperation 'content', content
|
||||
@saveOperation 'parent', parent
|
||||
if not (prev? and next?)
|
||||
throw new Error "You must define prev, and next for Replaceable-types!"
|
||||
super uid, prev, next, origin
|
||||
super uid, prev, next, origin # Parent is already saved by Replaceable
|
||||
@is_deleted = is_deleted
|
||||
|
||||
type: "Replaceable"
|
||||
@@ -415,20 +323,19 @@ module.exports = (HB)->
|
||||
{
|
||||
'type': "Replaceable"
|
||||
'content': @content?.getUid()
|
||||
'replace_manager' : @parent.getUid()
|
||||
'parent' : @parent.getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
'origin' : @origin.getUid()
|
||||
'uid' : @getUid()
|
||||
'is_deleted': @is_deleted
|
||||
}
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
json
|
||||
|
||||
parser["Replaceable"] = (json)->
|
||||
{
|
||||
'content' : content
|
||||
'replace_manager' : parent
|
||||
'parent' : parent
|
||||
'uid' : uid
|
||||
'prev': prev
|
||||
'next': next
|
||||
|
||||
@@ -22,14 +22,12 @@ module.exports = (HB)->
|
||||
# @param {String} content The content of this Insert-type Operation. Usually you restrict the length of content to size 1
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (content, uid, prev, next, origin)->
|
||||
constructor: (content, uid, prev, next, origin, parent)->
|
||||
if content?.uid?.creator
|
||||
@saveOperation 'content', content
|
||||
else
|
||||
@content = content
|
||||
if not (prev? and next?)
|
||||
throw new Error "You must define prev, and next for TextInsert-types!"
|
||||
super uid, prev, next, origin
|
||||
super uid, prev, next, origin, parent
|
||||
|
||||
type: "TextInsert"
|
||||
|
||||
@@ -78,13 +76,14 @@ module.exports = (HB)->
|
||||
'uid' : @getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
'origin': @origin.getUid()
|
||||
'parent': @parent.getUid()
|
||||
}
|
||||
|
||||
if @content?.getUid?
|
||||
json['content'] = @content.getUid()
|
||||
else
|
||||
json['content'] = @content
|
||||
if @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
json
|
||||
|
||||
parser["TextInsert"] = (json)->
|
||||
@@ -94,8 +93,9 @@ module.exports = (HB)->
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
'parent' : parent
|
||||
} = json
|
||||
new TextInsert content, uid, prev, next, origin
|
||||
new TextInsert content, uid, prev, next, origin, parent
|
||||
|
||||
#
|
||||
# Handles a WordType-like data structures with support for insertText/deleteText at a word-position.
|
||||
@@ -107,9 +107,9 @@ module.exports = (HB)->
|
||||
# @private
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
#
|
||||
constructor: (uid, beginning, end, prev, next, origin)->
|
||||
constructor: (uid)->
|
||||
@textfields = []
|
||||
super uid, beginning, end, prev, next, origin
|
||||
super uid
|
||||
|
||||
#
|
||||
# Identifies this class.
|
||||
@@ -331,27 +331,14 @@ module.exports = (HB)->
|
||||
json = {
|
||||
'type': "WordType"
|
||||
'uid' : @getUid()
|
||||
'beginning' : @beginning.getUid()
|
||||
'end' : @end.getUid()
|
||||
}
|
||||
if @prev_cl?
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
if @next_cl?
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? # and @origin isnt @prev_cl
|
||||
json["origin"] = @origin().getUid()
|
||||
json
|
||||
|
||||
parser['WordType'] = (json)->
|
||||
{
|
||||
'uid' : uid
|
||||
'beginning' : beginning
|
||||
'end' : end
|
||||
'prev': prev
|
||||
'next': next
|
||||
'origin' : origin
|
||||
} = json
|
||||
new WordType uid, beginning, end, prev, next, origin
|
||||
new WordType uid
|
||||
|
||||
types['TextInsert'] = TextInsert
|
||||
types['TextDelete'] = TextDelete
|
||||
|
||||
Reference in New Issue
Block a user