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:
DadaMonad
2015-01-16 13:36:15 +00:00
parent 6b46500325
commit b647b2af58
20 changed files with 543 additions and 1254 deletions

View File

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

View File

@@ -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"

View File

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

View File

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