Operations are now Garbage Collected!
This commit is contained in:
@@ -23,13 +23,20 @@ module.exports = (HB)->
|
||||
# @see HistoryBuffer.getNextOperationIdentifier
|
||||
#
|
||||
constructor: (uid)->
|
||||
if not uid?
|
||||
@is_deleted = false
|
||||
@doSync = true
|
||||
@garbage_collected = false
|
||||
if uid?
|
||||
@doSync = not isNaN(parseInt(uid.op_number))
|
||||
else
|
||||
uid = HB.getNextOperationIdentifier()
|
||||
{
|
||||
'creator': @creator
|
||||
'op_number' : @op_number
|
||||
} = uid
|
||||
|
||||
type: "Insert"
|
||||
|
||||
#
|
||||
# Add an event listener. It depends on the operation which events are supported.
|
||||
# @param {String} event Name of the event.
|
||||
@@ -76,6 +83,21 @@ module.exports = (HB)->
|
||||
for f in @event_listeners[event]
|
||||
f.call op, event, args...
|
||||
|
||||
isDeleted: ()->
|
||||
@is_deleted
|
||||
|
||||
applyDelete: (garbagecollect = true)->
|
||||
if not @garbage_collected
|
||||
#console.log "applyDelete: #{@type}"
|
||||
@is_deleted = true
|
||||
if garbagecollect
|
||||
@garbage_collected = true
|
||||
HB.addToGarbageCollector @
|
||||
|
||||
cleanup: ()->
|
||||
#console.log "cleanup: #{@type}"
|
||||
HB.removeOperation @
|
||||
|
||||
#
|
||||
# Set the parent of this operation.
|
||||
#
|
||||
@@ -91,7 +113,10 @@ module.exports = (HB)->
|
||||
# Computes a unique identifier (uid) that identifies this operation.
|
||||
#
|
||||
getUid: ()->
|
||||
{ 'creator': @creator, 'op_number': @op_number }
|
||||
{ 'creator': @creator, 'op_number': @op_number , 'sync': @doSync}
|
||||
|
||||
dontSync: ()->
|
||||
@doSync = false
|
||||
|
||||
#
|
||||
# @private
|
||||
@@ -162,7 +187,7 @@ module.exports = (HB)->
|
||||
|
||||
#
|
||||
# @nodoc
|
||||
# A simple Delete-type operation that deletes an Insert-type operation.
|
||||
# A simple Delete-type operation that deletes an operation.
|
||||
#
|
||||
class Delete extends Operation
|
||||
|
||||
@@ -174,6 +199,8 @@ module.exports = (HB)->
|
||||
@saveOperation 'deletes', deletes
|
||||
super uid
|
||||
|
||||
type: "Delete"
|
||||
|
||||
#
|
||||
# @private
|
||||
# Convert all relevant information of this operation to the json-format.
|
||||
@@ -235,21 +262,46 @@ module.exports = (HB)->
|
||||
@saveOperation 'origin', prev_cl
|
||||
super uid
|
||||
|
||||
type: "Insert"
|
||||
|
||||
#
|
||||
# set content to null and other stuff
|
||||
# @private
|
||||
#
|
||||
applyDelete: (o)->
|
||||
@deleted_by ?= []
|
||||
@deleted_by.push o
|
||||
if @parent? and @deleted_by.length is 1
|
||||
if @parent? and not @isDeleted()
|
||||
# call iff wasn't deleted earlyer
|
||||
@parent.callEvent "delete", @
|
||||
if o?
|
||||
@deleted_by.push o
|
||||
garbagecollect = false
|
||||
if @prev_cl.isDeleted()
|
||||
garbagecollect = true
|
||||
super garbagecollect
|
||||
if @next_cl.isDeleted()
|
||||
# garbage collect next_cl
|
||||
@next_cl.applyDelete()
|
||||
|
||||
cleanup: ()->
|
||||
# TODO: Debugging
|
||||
if @prev_cl.isDeleted()
|
||||
# delete all ops that delete this insertion
|
||||
for d in @deleted_by
|
||||
d.cleanup()
|
||||
|
||||
# throw new Error "left is not deleted. inconsistency!, wrararar"
|
||||
# delete origin references to the right
|
||||
o = @next_cl
|
||||
while o.type isnt "Delimiter"
|
||||
if o.origin is @
|
||||
o.origin = @prev_cl
|
||||
o = o.next_cl
|
||||
# reconnect left/right
|
||||
@prev_cl.next_cl = @next_cl
|
||||
@next_cl.prev_cl = @prev_cl
|
||||
super
|
||||
|
||||
#
|
||||
# If isDeleted() is true this operation won't be maintained in the sl
|
||||
#
|
||||
isDeleted: ()->
|
||||
@deleted_by?.length > 0
|
||||
|
||||
#
|
||||
# @private
|
||||
@@ -262,45 +314,22 @@ module.exports = (HB)->
|
||||
if @origin is o
|
||||
break
|
||||
d++
|
||||
#TODO: delete this
|
||||
if @ is @prev_cl
|
||||
throw new Error "this should not happen ;) "
|
||||
o = o.prev_cl
|
||||
d
|
||||
|
||||
#
|
||||
# @private
|
||||
# Update the short list
|
||||
# TODO (Unused)
|
||||
update_sl: ()->
|
||||
o = @prev_cl
|
||||
update: (dest_cl,dest_sl)->
|
||||
while true
|
||||
if o.isDeleted()
|
||||
o = o[dest_cl]
|
||||
else
|
||||
@[dest_sl] = o
|
||||
|
||||
break
|
||||
update "prev_cl", "prev_sl"
|
||||
update "next_cl", "prev_sl"
|
||||
|
||||
|
||||
|
||||
#
|
||||
# @private
|
||||
# Include this operation in the associative lists.
|
||||
#
|
||||
execute: ()->
|
||||
if @is_executed?
|
||||
return @
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
if @prev_cl?.validateSavedOperations() and @next_cl?.validateSavedOperations() and @prev_cl.next_cl isnt @
|
||||
distance_to_origin = 0
|
||||
if @prev_cl?
|
||||
distance_to_origin = @getDistanceToOrigin() # most cases: 0
|
||||
o = @prev_cl.next_cl
|
||||
i = 0
|
||||
i = distance_to_origin # loop counter
|
||||
|
||||
# $this has to find a unique position between origin and the next known character
|
||||
# case 1: $origin equals $o.origin: the $creator parameter decides if left or right
|
||||
# let $OL= [o1,o2,o3,o4], whereby $this is to be inserted between o1 and o4
|
||||
@@ -314,10 +343,6 @@ module.exports = (HB)->
|
||||
# case 3: $origin > $o.origin
|
||||
# $this insert_position is to the left of $o (forever!)
|
||||
while true
|
||||
if not o?
|
||||
# TODO: Debugging
|
||||
console.log JSON.stringify @prev_cl.getUid()
|
||||
console.log JSON.stringify @next_cl.getUid()
|
||||
if o isnt @next_cl
|
||||
# $o happened concurrently
|
||||
if o.getDistanceToOrigin() is i
|
||||
@@ -346,6 +371,7 @@ module.exports = (HB)->
|
||||
@next_cl = @prev_cl.next_cl
|
||||
@prev_cl.next_cl = @
|
||||
@next_cl.prev_cl = @
|
||||
|
||||
parent = @prev_cl?.getParent()
|
||||
if parent?
|
||||
@setParent parent
|
||||
@@ -361,7 +387,7 @@ module.exports = (HB)->
|
||||
while true
|
||||
if prev instanceof Delimiter
|
||||
break
|
||||
if prev.isDeleted? and not prev.isDeleted()
|
||||
if not prev.isDeleted()
|
||||
position++
|
||||
prev = prev.prev_cl
|
||||
position
|
||||
@@ -370,7 +396,7 @@ module.exports = (HB)->
|
||||
# @nodoc
|
||||
# Defines an object that is cannot be changed. You can use this to set an immutable string, or a number.
|
||||
#
|
||||
class ImmutableObject extends Insert
|
||||
class ImmutableObject extends Operation
|
||||
|
||||
#
|
||||
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
|
||||
@@ -379,6 +405,8 @@ module.exports = (HB)->
|
||||
constructor: (uid, @content, prev, next, origin)->
|
||||
super uid, prev, next, origin
|
||||
|
||||
type: "ImmutableObject"
|
||||
|
||||
#
|
||||
# @return [String] The content of this operation.
|
||||
#
|
||||
@@ -398,8 +426,8 @@ module.exports = (HB)->
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
if @next_cl?
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
if @origin? # and @origin isnt @prev_cl
|
||||
json["origin"] = @origin().getUid()
|
||||
json
|
||||
|
||||
parser['ImmutableObject'] = (json)->
|
||||
@@ -432,11 +460,18 @@ module.exports = (HB)->
|
||||
@saveOperation 'origin', prev_cl
|
||||
super uid
|
||||
|
||||
#
|
||||
# If isDeleted() is true this operation won't be maintained in the sl
|
||||
#
|
||||
isDeleted: ()->
|
||||
false
|
||||
type: "Delimiter"
|
||||
|
||||
applyDelete: ()->
|
||||
super()
|
||||
o = @next_cl
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.next_cl
|
||||
undefined
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
#
|
||||
# @private
|
||||
|
||||
@@ -123,6 +123,11 @@ module.exports = (HB)->
|
||||
#
|
||||
type: "JsonType"
|
||||
|
||||
applyDelete: ()->
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
#
|
||||
# Transform this to a Json and loose all the sharing-abilities (the new object will be a deep clone)!
|
||||
# @return {Json}
|
||||
@@ -147,16 +152,18 @@ module.exports = (HB)->
|
||||
# @see WordType.setReplaceManager
|
||||
# Sets the parent of this JsonType object.
|
||||
#
|
||||
setReplaceManager: (rm)->
|
||||
@parent = rm.parent
|
||||
setReplaceManager: (replace_manager)->
|
||||
@replace_manager = replace_manager
|
||||
@on ['change','addProperty'], ()->
|
||||
rm.parent.forwardEvent this, arguments...
|
||||
if replace_manager.parent?
|
||||
replace_manager.parent.forwardEvent this, arguments...
|
||||
|
||||
#
|
||||
# Get the parent of this JsonType.
|
||||
# @return {JsonType}
|
||||
#
|
||||
getParent: ()->
|
||||
@parent
|
||||
@replace_manager.parent
|
||||
|
||||
#
|
||||
# Whether the default is 'mutable' (true) or 'immutable' (false)
|
||||
@@ -196,8 +203,9 @@ module.exports = (HB)->
|
||||
if typeof name is 'object'
|
||||
# Special case. First argument is an object. Then the second arg is mutable.
|
||||
# Keep that in mind when reading the following..
|
||||
for o_name,o of name
|
||||
@val(o_name,o,content)
|
||||
json = new JsonType undefined, name, content
|
||||
HB.addOperation(json).execute()
|
||||
@replace_manager.replace json
|
||||
@
|
||||
else if name? and (content? or content is null)
|
||||
if mutable?
|
||||
|
||||
@@ -18,6 +18,16 @@ module.exports = (HB)->
|
||||
@map = {}
|
||||
super uid
|
||||
|
||||
type: "MapManager"
|
||||
|
||||
applyDelete: ()->
|
||||
for name,p of @map
|
||||
p.applyDelete()
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
#
|
||||
# @see JsonTypes.val
|
||||
#
|
||||
@@ -60,6 +70,14 @@ module.exports = (HB)->
|
||||
@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
|
||||
@@ -81,6 +99,7 @@ module.exports = (HB)->
|
||||
end = HB.addOperation(new types.Delimiter uid_end, beg, undefined).execute()
|
||||
@map_manager.map[@name] = HB.addOperation(new ReplaceManager undefined, 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
|
||||
|
||||
@@ -107,7 +126,7 @@ module.exports = (HB)->
|
||||
# @nodoc
|
||||
# Manages a list of Insert-type operations.
|
||||
#
|
||||
class ListManager extends types.Insert
|
||||
class ListManager extends types.Operation
|
||||
|
||||
#
|
||||
# A ListManager maintains a non-empty list that has a beginning and an end (both Delimiters!)
|
||||
@@ -126,6 +145,8 @@ module.exports = (HB)->
|
||||
@end.execute()
|
||||
super uid, prev, next, origin
|
||||
|
||||
type: "ListManager"
|
||||
|
||||
#
|
||||
# @private
|
||||
# @see Operation.execute
|
||||
@@ -195,6 +216,22 @@ module.exports = (HB)->
|
||||
if initial_content?
|
||||
@replace initial_content
|
||||
|
||||
type: "ReplaceManager"
|
||||
|
||||
applyDelete: ()->
|
||||
o = @beginning
|
||||
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: ()->
|
||||
super()
|
||||
|
||||
#
|
||||
# Replace the existing word with a new word.
|
||||
#
|
||||
@@ -205,6 +242,7 @@ module.exports = (HB)->
|
||||
o = @getLastOperation()
|
||||
op = new Replaceable content, @, replaceable_uid, o, o.next_cl
|
||||
HB.addOperation(op).execute()
|
||||
undefined
|
||||
|
||||
#
|
||||
# Add change listeners for parent.
|
||||
@@ -222,7 +260,7 @@ module.exports = (HB)->
|
||||
@deleteListener 'addProperty', addPropertyListener
|
||||
@on 'insert', addPropertyListener
|
||||
super parent
|
||||
|
||||
|
||||
#
|
||||
# Get the value of this WordType
|
||||
# @return {String}
|
||||
@@ -247,8 +285,8 @@ module.exports = (HB)->
|
||||
if @prev_cl? and @next_cl?
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
if @origin? # and @origin isnt @prev_cl
|
||||
json["origin"] = @origin().getUid()
|
||||
json
|
||||
|
||||
parser["ReplaceManager"] = (json)->
|
||||
@@ -279,10 +317,12 @@ module.exports = (HB)->
|
||||
constructor: (content, parent, uid, prev, next, origin)->
|
||||
@saveOperation 'content', content
|
||||
@saveOperation 'parent', parent
|
||||
if not (prev? and next? and content?)
|
||||
throw new Error "You must define content, prev, and next for Replaceable-types!"
|
||||
if not (prev? and next?)
|
||||
throw new Error "You must define prev, and next for Replaceable-types!"
|
||||
super uid, prev, next, origin
|
||||
|
||||
type: "Replaceable"
|
||||
|
||||
#
|
||||
# Return the content that this operation holds.
|
||||
#
|
||||
@@ -295,6 +335,17 @@ module.exports = (HB)->
|
||||
replace: (content)->
|
||||
@parent.replace content
|
||||
|
||||
applyDelete: ()->
|
||||
if @content?
|
||||
@content.applyDelete()
|
||||
@content.dontSync()
|
||||
@beforeDelete = @content # TODO!!!!!!!!!!
|
||||
@content = null
|
||||
super
|
||||
|
||||
cleanup: ()->
|
||||
super
|
||||
|
||||
#
|
||||
# If possible set the replace manager in the content.
|
||||
# @see WordType.setReplaceManager
|
||||
@@ -303,8 +354,15 @@ module.exports = (HB)->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
@content.setReplaceManager?(@parent)
|
||||
super
|
||||
@content?.setReplaceManager?(@parent)
|
||||
ins_result = super()
|
||||
if ins_result
|
||||
if @next_cl.type is "Delimiter" and @prev_cl.type isnt "Delimiter"
|
||||
@prev_cl.applyDelete()
|
||||
else if @next_cl.type isnt "Delimiter"
|
||||
@applyDelete()
|
||||
|
||||
return ins_result
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
@@ -313,7 +371,7 @@ module.exports = (HB)->
|
||||
json =
|
||||
{
|
||||
'type': "Replaceable"
|
||||
'content': @content.getUid()
|
||||
'content': @content?.getUid()
|
||||
'ReplaceManager' : @parent.getUid()
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
|
||||
@@ -26,6 +26,9 @@ module.exports = (HB)->
|
||||
if not (prev? and next?)
|
||||
throw new Error "You must define prev, and next for TextInsert-types!"
|
||||
super uid, prev, next, origin
|
||||
|
||||
type: "TextInsert"
|
||||
|
||||
#
|
||||
# Retrieve the effective length of the $content of this operation.
|
||||
#
|
||||
@@ -35,13 +38,17 @@ module.exports = (HB)->
|
||||
else
|
||||
@content.length
|
||||
|
||||
applyDelete: ()->
|
||||
@content = null
|
||||
super
|
||||
|
||||
#
|
||||
# The result will be concatenated with the results from the other insert operations
|
||||
# in order to retrieve the content of the engine.
|
||||
# @see HistoryBuffer.toExecutedArray
|
||||
#
|
||||
val: (current_position)->
|
||||
if @isDeleted()
|
||||
if @isDeleted() or not @content?
|
||||
""
|
||||
else
|
||||
@content
|
||||
@@ -59,7 +66,7 @@ module.exports = (HB)->
|
||||
'prev': @prev_cl.getUid()
|
||||
'next': @next_cl.getUid()
|
||||
}
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
if @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
json
|
||||
|
||||
@@ -98,16 +105,32 @@ module.exports = (HB)->
|
||||
#
|
||||
type: "WordType"
|
||||
|
||||
applyDelete: ()->
|
||||
o = @beginning
|
||||
while o?
|
||||
o.applyDelete()
|
||||
o = o.next_cl
|
||||
super()
|
||||
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
#
|
||||
# Inserts a string into the word.
|
||||
#
|
||||
# @return {WordType} This WordType object.
|
||||
#
|
||||
insertText: (position, content)->
|
||||
o = @getOperationByPosition position
|
||||
# TODO: getOperationByPosition should return "(i-2)th" character
|
||||
ith = @getOperationByPosition position # the (i-1)th character. e.g. "abc" a is the 0th character
|
||||
left = ith.prev_cl # left is the non-deleted charather to the left of ith
|
||||
while left.isDeleted()
|
||||
left = left.prev_cl # find the first character to the left, that is not deleted. Case position is 0, its the Delimiter.
|
||||
right = left.next_cl
|
||||
for c in content
|
||||
op = new TextInsert c, undefined, o.prev_cl, o
|
||||
op = new TextInsert c, undefined, left, right
|
||||
HB.addOperation(op).execute()
|
||||
left = op
|
||||
@
|
||||
|
||||
#
|
||||
@@ -300,8 +323,8 @@ module.exports = (HB)->
|
||||
json['prev'] = @prev_cl.getUid()
|
||||
if @next_cl?
|
||||
json['next'] = @next_cl.getUid()
|
||||
if @origin? and @origin isnt @prev_cl
|
||||
json["origin"] = @origin.getUid()
|
||||
if @origin? # and @origin isnt @prev_cl
|
||||
json["origin"] = @origin().getUid()
|
||||
json
|
||||
|
||||
parser['WordType'] = (json)->
|
||||
|
||||
Reference in New Issue
Block a user