testing observers, refactoring some Basic Types

This commit is contained in:
DadaMonad
2014-12-22 17:05:15 +00:00
parent 21f7350c4d
commit cacfb54d5e
48 changed files with 3300 additions and 2368 deletions

View File

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

View File

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

View File

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

View File

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

View File

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