testing observers, refactoring some Basic Types
This commit is contained in:
@@ -45,7 +45,7 @@ module.exports = (HB)->
|
||||
# @overload unobserve(event, f)
|
||||
# @param f {Function} The function that you want to delete
|
||||
unobserve: (f)->
|
||||
@event_listeners.filter (g)->
|
||||
@event_listeners = @event_listeners.filter (g)->
|
||||
f isnt g
|
||||
|
||||
#
|
||||
@@ -59,7 +59,7 @@ module.exports = (HB)->
|
||||
#
|
||||
# Fire an event.
|
||||
# TODO: Do something with timeouts. You don't want this to fire for every operation (e.g. insert).
|
||||
#
|
||||
# TODO: do you need callEvent+forwardEvent? Only one suffices probably
|
||||
callEvent: ()->
|
||||
@forwardEvent @, arguments...
|
||||
|
||||
@@ -215,8 +215,10 @@ module.exports = (HB)->
|
||||
#
|
||||
execute: ()->
|
||||
if @validateSavedOperations()
|
||||
@deletes.applyDelete @
|
||||
super
|
||||
res = super
|
||||
if res
|
||||
@deletes.applyDelete @
|
||||
res
|
||||
else
|
||||
false
|
||||
|
||||
@@ -275,7 +277,12 @@ module.exports = (HB)->
|
||||
garbagecollect = true
|
||||
super garbagecollect
|
||||
if callLater
|
||||
@parent.callEvent "delete", @, o
|
||||
@parent.callEvent [
|
||||
type: "insert"
|
||||
position: @getPosition()
|
||||
object: @parent # TODO: You can combine getPosition + getParent in a more efficient manner! (only left Delimiter will hold @parent)
|
||||
changed_by: o.uid.creator
|
||||
]
|
||||
if @next_cl?.isDeleted()
|
||||
# garbage collect next_cl
|
||||
@next_cl.applyDelete()
|
||||
@@ -317,8 +324,7 @@ module.exports = (HB)->
|
||||
#
|
||||
# @private
|
||||
# Include this operation in the associative lists.
|
||||
# @param fire_event {boolean} Whether to fire the insert-event.
|
||||
execute: (fire_event = true)->
|
||||
execute: ()->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
@@ -336,7 +342,8 @@ module.exports = (HB)->
|
||||
# therefore $this would be always to the right of o3
|
||||
# case 2: $origin < $o.origin
|
||||
# if current $this insert_position > $o origin: $this ins
|
||||
# else $insert_position will not change (maybe we encounter case 1 later, then this will be to the right of $o)
|
||||
# else $insert_position will not change
|
||||
# (maybe we encounter case 1 later, then this will be to the right of $o)
|
||||
# case 3: $origin > $o.origin
|
||||
# $this insert_position is to the left of $o (forever!)
|
||||
while true
|
||||
@@ -369,13 +376,20 @@ module.exports = (HB)->
|
||||
@prev_cl.next_cl = @
|
||||
@next_cl.prev_cl = @
|
||||
|
||||
parent = @prev_cl?.getParent()
|
||||
@setParent @prev_cl.getParent() # do Insertions always have a parent?
|
||||
super # notify the execution_listeners
|
||||
if parent? and fire_event
|
||||
@setParent parent
|
||||
@parent.callEvent "insert", @
|
||||
@callOperationSpecificEvents()
|
||||
@
|
||||
|
||||
callOperationSpecificEvents: ()->
|
||||
@parent?.callEvent [
|
||||
type: "insert"
|
||||
position: @getPosition()
|
||||
object: @parent
|
||||
changed_by: @uid.creator
|
||||
value: @content
|
||||
]
|
||||
|
||||
#
|
||||
# Compute the position of this operation.
|
||||
#
|
||||
|
||||
@@ -148,30 +148,31 @@ module.exports = (HB)->
|
||||
if not event.changed_by? and (event.type is "add" or event.type = "update")
|
||||
# this event is not created by Yatta.
|
||||
that.val(event.name, event.object[event.name])
|
||||
that.observe (event_name, property_name, op)->
|
||||
if this is that and op.uid.creator isnt HB.getUserId()
|
||||
notifier = Object.getNotifier(that.bound_json)
|
||||
oldVal = that.bound_json[property_name]
|
||||
if oldVal?
|
||||
notifier.performChange 'update', ()->
|
||||
that.bound_json[property_name] = that.val(property_name)
|
||||
, that.bound_json
|
||||
notifier.notify
|
||||
object: that.bound_json
|
||||
type: 'update'
|
||||
name: property_name
|
||||
oldValue: oldVal
|
||||
changed_by: op.uid.creator
|
||||
else
|
||||
notifier.performChange 'add', ()->
|
||||
that.bound_json[property_name] = that.val(property_name)
|
||||
, that.bound_json
|
||||
notifier.notify
|
||||
object: that.bound_json
|
||||
type: 'add'
|
||||
name: property_name
|
||||
oldValue: oldVal
|
||||
changed_by: op.uid.creator
|
||||
@observe (events)->
|
||||
for event in events
|
||||
if event.created_ isnt HB.getUserId()
|
||||
notifier = Object.getNotifier(that.bound_json)
|
||||
oldVal = that.bound_json[event.name]
|
||||
if oldVal?
|
||||
notifier.performChange 'update', ()->
|
||||
that.bound_json[event.name] = that.val(event.name)
|
||||
, that.bound_json
|
||||
notifier.notify
|
||||
object: that.bound_json
|
||||
type: 'update'
|
||||
name: event.name
|
||||
oldValue: oldVal
|
||||
changed_by: event.changed_by
|
||||
else
|
||||
notifier.performChange 'add', ()->
|
||||
that.bound_json[event.name] = that.val(event.name)
|
||||
, that.bound_json
|
||||
notifier.notify
|
||||
object: that.bound_json
|
||||
type: 'add'
|
||||
name: event.name
|
||||
oldValue: oldVal
|
||||
changed_by:event.changed_by
|
||||
@bound_json
|
||||
|
||||
#
|
||||
|
||||
@@ -110,7 +110,10 @@ module.exports = (HB)->
|
||||
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()
|
||||
@map_manager.map[@name] = new ReplaceManager uid_r, beg, end
|
||||
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()
|
||||
@@ -192,22 +195,27 @@ module.exports = (HB)->
|
||||
|
||||
#
|
||||
# Retrieves the x-th not deleted element.
|
||||
# e.g. "abc" : the 1th character is "a"
|
||||
# the 0th character is the left Delimiter
|
||||
#
|
||||
getOperationByPosition: (position)->
|
||||
o = @beginning.next_cl
|
||||
if (position > 0 or o.isDeleted()) and not (o instanceof types.Delimiter)
|
||||
while o.isDeleted() and not (o instanceof types.Delimiter)
|
||||
# find first non deleted op
|
||||
o = o.next_cl
|
||||
while true
|
||||
# find the i-th op
|
||||
if o instanceof types.Delimiter
|
||||
break
|
||||
if position <= 0 and not o.isDeleted()
|
||||
break
|
||||
o = o.next_cl
|
||||
if not o.isDeleted()
|
||||
position -= 1
|
||||
o = @beginning
|
||||
while true
|
||||
# find the i-th op
|
||||
if o instanceof types.Delimiter and o.prev_cl?
|
||||
# the user or you gave a position parameter that is to big
|
||||
# for the current array. Therefore we reach a Delimiter.
|
||||
# Then, we'll just return the last character.
|
||||
o = o.prev_cl
|
||||
while o.isDeleted() or not (o instanceof types.Delimiter)
|
||||
o = o.prev_cl
|
||||
break
|
||||
if position <= 0 and not o.isDeleted()
|
||||
break
|
||||
|
||||
o = o.next_cl
|
||||
if not o.isDeleted()
|
||||
position -= 1
|
||||
o
|
||||
|
||||
#
|
||||
@@ -220,12 +228,14 @@ module.exports = (HB)->
|
||||
#
|
||||
class ReplaceManager extends ListManager
|
||||
#
|
||||
# @param {Object} event_properties Decorates the event that is thrown by the RM
|
||||
# @param {Object} event_this The object on which the event shall be executed
|
||||
# @param {Operation} initial_content Initialize this with a Replaceable that holds the initial_content.
|
||||
# @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)->
|
||||
# super uid, beginning, end, prev, next, origin
|
||||
constructor: (@event_porperties, @event_this, uid, beginning, end, prev, next, origin)->
|
||||
super uid, beginning, end, prev, next, origin
|
||||
|
||||
type: "ReplaceManager"
|
||||
|
||||
@@ -243,6 +253,21 @@ module.exports = (HB)->
|
||||
cleanup: ()->
|
||||
super()
|
||||
|
||||
#
|
||||
# This doesn't throw the same events as the ListManager. Therefore, the
|
||||
# Replaceables also not throw the same events.
|
||||
# So, ReplaceManager and ListManager both implement
|
||||
# these functions that are called when an Insertion is executed (at the end).
|
||||
#
|
||||
#
|
||||
callEventDecorator: (events)->
|
||||
if not @isDeleted()
|
||||
for name,prop of @event_porperties
|
||||
for event in events
|
||||
event[name] = prop
|
||||
@event_this.callEvent events
|
||||
undefined
|
||||
|
||||
#
|
||||
# Replace the existing word with a new word.
|
||||
#
|
||||
@@ -278,7 +303,7 @@ 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
|
||||
if @origin? # TODO: do this everywhere: and @origin isnt @prev_cl
|
||||
json["origin"] = @origin().getUid()
|
||||
json
|
||||
|
||||
@@ -334,23 +359,30 @@ module.exports = (HB)->
|
||||
super
|
||||
|
||||
#
|
||||
# This is called, when the Insert-type was successfully executed.
|
||||
# TODO: consider doing this in a more consistent manner. This could also be
|
||||
# done with execute. But currently, there are no specital Insert-types for ListManager.
|
||||
#
|
||||
execute: ()->
|
||||
if not @validateSavedOperations()
|
||||
return false
|
||||
else
|
||||
# only fire 'insert-event' (which will result in addProperty and change events),
|
||||
# when content is added.
|
||||
# In case of Json, empty content means that this is not the last update,
|
||||
# since content is deleted when 'applyDelete' was exectuted.
|
||||
ins_result = super(@content?) # @content? whether to fire or not
|
||||
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
|
||||
callOperationSpecificEvents: ()->
|
||||
if @next_cl.type is "Delimiter" and @prev_cl.type isnt "Delimiter"
|
||||
# this replaces another Replaceable
|
||||
old_value = @prev_cl.content
|
||||
@prev_cl.applyDelete()
|
||||
@parent.callEventDecorator [
|
||||
type: "update"
|
||||
changed_by: @uid.creator
|
||||
oldValue: old_value
|
||||
]
|
||||
else if @next_cl.type isnt "Delimiter"
|
||||
# This will never be recognized by the user, because another
|
||||
# concurrent operation is set as the current value of the RM
|
||||
@applyDelete()
|
||||
else # prev _and_ next are Delimiters. This is the first created Replaceable in the RM
|
||||
@parent.callEventDecorator [
|
||||
type: "add"
|
||||
changed_by: @uid.creator
|
||||
]
|
||||
undefined
|
||||
|
||||
#
|
||||
# Encode this operation in such a way that it can be parsed by remote peers.
|
||||
|
||||
@@ -152,10 +152,10 @@ module.exports = (HB)->
|
||||
# @return {WordType} This WordType object.
|
||||
#
|
||||
insertText: (position, content)->
|
||||
# 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
|
||||
@insertAfter left, content
|
||||
ith = @getOperationByPosition position
|
||||
# the (i-1)th character. e.g. "abc" the 1th character is "a"
|
||||
# the 0th character is the left Delimiter
|
||||
@insertAfter ith, content
|
||||
|
||||
#
|
||||
# Deletes a part of the word.
|
||||
@@ -163,7 +163,7 @@ module.exports = (HB)->
|
||||
# @return {WordType} This WordType object
|
||||
#
|
||||
deleteText: (position, length)->
|
||||
o = @getOperationByPosition position
|
||||
o = @getOperationByPosition(position+1) # position 0 in this case is the deletion of the first character
|
||||
|
||||
delete_ops = []
|
||||
for i in [0...length]
|
||||
|
||||
123
lib/Yatta.coffee
123
lib/Yatta.coffee
@@ -4,107 +4,38 @@ HistoryBuffer = require "./HistoryBuffer"
|
||||
Engine = require "./Engine"
|
||||
adaptConnector = require "./ConnectorAdapter"
|
||||
|
||||
#
|
||||
# Framework for Json data-structures.
|
||||
# Known values that are supported:
|
||||
# * String
|
||||
# * Integer
|
||||
# * Array
|
||||
#
|
||||
class Yatta
|
||||
createYatta = (connector)->
|
||||
user_id = connector.id # TODO: change to getUniqueId()
|
||||
HB = new HistoryBuffer user_id
|
||||
type_manager = json_types_uninitialized HB
|
||||
types = type_manager.types
|
||||
|
||||
#
|
||||
# @param {String} user_id Unique id of the peer.
|
||||
# @param {Connector} Connector the connector class.
|
||||
# Framework for Json data-structures.
|
||||
# Known values that are supported:
|
||||
# * String
|
||||
# * Integer
|
||||
# * Array
|
||||
#
|
||||
constructor: (@connector)->
|
||||
user_id = @connector.id # TODO: change to getUniqueId()
|
||||
@HB = new HistoryBuffer user_id
|
||||
type_manager = json_types_uninitialized @HB
|
||||
@types = type_manager.types
|
||||
@engine = new Engine @HB, type_manager.parser
|
||||
@HB.engine = @engine # TODO: !! only for debugging
|
||||
adaptConnector @connector, @engine, @HB, type_manager.execution_listener
|
||||
#first_word =
|
||||
@root_element = new @types.JsonType(@HB.getReservedUniqueIdentifier()).execute()
|
||||
###
|
||||
uid_beg = @HB.getReservedUniqueIdentifier()
|
||||
uid_end = @HB.getReservedUniqueIdentifier()
|
||||
beg = (new @types.Delimiter uid_beg, undefined, uid_end).execute()
|
||||
end = (new @types.Delimiter uid_end, beg, undefined).execute()
|
||||
class Yatta extends types.JsonType
|
||||
|
||||
@root_element = (new @types.ReplaceManager @HB.getReservedUniqueIdentifier(), beg, end).execute()
|
||||
@root_element.replace first_word, @HB.getReservedUniqueIdentifier()
|
||||
###
|
||||
#
|
||||
# @param {String} user_id Unique id of the peer.
|
||||
# @param {Connector} Connector the connector class.
|
||||
#
|
||||
constructor: ()->
|
||||
@connector = connector
|
||||
@HB = HB
|
||||
@types = types
|
||||
@engine = new Engine @HB, type_manager.parser
|
||||
adaptConnector @connector, @engine, @HB, type_manager.execution_listener
|
||||
super
|
||||
|
||||
#
|
||||
# @return JsonType
|
||||
#
|
||||
getSharedObject: ()->
|
||||
@root_element
|
||||
getConnector: ()->
|
||||
@connector
|
||||
|
||||
#
|
||||
# Get the initialized connector.
|
||||
#
|
||||
getConnector: ()->
|
||||
@connector
|
||||
return new Yatta(HB.getReservedUniqueIdentifier()).execute()
|
||||
|
||||
#
|
||||
# @see HistoryBuffer
|
||||
#
|
||||
getHistoryBuffer: ()->
|
||||
@HB
|
||||
|
||||
#
|
||||
# @see JsonType.setMutableDefault
|
||||
#
|
||||
setMutableDefault: (mutable)->
|
||||
@getSharedObject().setMutableDefault(mutable)
|
||||
|
||||
#
|
||||
# Get the UserId from the HistoryBuffer object.
|
||||
# In most cases this will be the same as the user_id value with which
|
||||
# Yatta was initialized (Depending on the HistoryBuffer implementation).
|
||||
#
|
||||
getUserId: ()->
|
||||
@HB.getUserId()
|
||||
|
||||
#
|
||||
# @see JsonType.toJson
|
||||
#
|
||||
toJson : ()->
|
||||
@getSharedObject().toJson()
|
||||
|
||||
#
|
||||
# @see JsonType.val
|
||||
#
|
||||
val : ()->
|
||||
@getSharedObject().val arguments...
|
||||
|
||||
#
|
||||
# @see Operation.on
|
||||
#
|
||||
observe: ()->
|
||||
@getSharedObject().observe arguments...
|
||||
|
||||
#
|
||||
# @see Operation.deleteListener
|
||||
#
|
||||
unobserve: ()->
|
||||
@getSharedObject().unobserve arguments...
|
||||
|
||||
#
|
||||
# @see JsonType.value
|
||||
#
|
||||
Object.defineProperty Yatta.prototype, 'value',
|
||||
get : -> @getSharedObject().value
|
||||
set : (o)->
|
||||
if o.constructor is {}.constructor
|
||||
for o_name,o_obj of o
|
||||
@val(o_name, o_obj, 'immutable')
|
||||
else
|
||||
throw new Error "You must only set Object values!"
|
||||
|
||||
module.exports = Yatta
|
||||
module.exports = createYatta
|
||||
if window? and not window.Yatta?
|
||||
window.Yatta = Yatta
|
||||
window.Yatta = createYatta
|
||||
|
||||
Reference in New Issue
Block a user