Adding to HB is now handled by Operation.execute. engine changed. Currently fixing errors and working on memory menagement for large HB tables

This commit is contained in:
DadaMonad
2014-12-16 11:21:57 +00:00
parent 7696864841
commit 2cf899418a
64 changed files with 3013 additions and 10106 deletions

View File

@@ -22,6 +22,7 @@ class Engine
else
throw new Error "You forgot to specify a parser for type #{json.type}. The message is #{JSON.stringify json}."
#
# Apply a set of operations. E.g. the operations you received from another users HB._encode().
# @note You must not use this method when you already have ops in your HB!
@@ -30,8 +31,6 @@ class Engine
ops = []
for o in ops_json
ops.push @parseOperation o
for o in ops
@HB.addOperation o
for o in ops
if not o.execute()
@unprocessed_ops.push o
@@ -64,8 +63,6 @@ class Engine
if @HB.getOperation(o)?
else if not o.execute()
@unprocessed_ops.push o
else
@HB.addOperation o
@tryUnprocessed()
#

View File

@@ -1,107 +0,0 @@
text_types_uninitialized = require "../Types/TextTypes"
HistoryBuffer = require "../HistoryBuffer"
Engine = require "../Engine"
adaptConnector = require "../ConnectorAdapter"
#
# Framework for Text Datastructures.
#
class TextFramework
#
# @param {String} user_id Uniqe user id that defines this peer.
# @param {Connector} Connector The connector defines how you connect to the other peers.
#
constructor: (user_id, @connector)->
@HB = new HistoryBuffer user_id
text_types = text_types_uninitialized @HB
@types = text_types.types
@engine = new Engine @HB, text_types.parser
adaptConnector @connector, @engine, @HB, text_types.execution_listener
beginning = @HB.addOperation new @types.Delimiter {creator: '_', op_number: '_beginning'} , undefined, undefined
end = @HB.addOperation new @types.Delimiter {creator: '_', op_number: '_end'} , beginning, undefined
beginning.next_cl = end
beginning.execute()
end.execute()
first_word = new @types.WordType {creator: '_', op_number: '_'}, beginning, end
@HB.addOperation(first_word).execute()
uid_r = { creator: '_', op_number: "RM" }
uid_beg = { creator: '_', op_number: "_RM_beginning" }
uid_end = { creator: '_', op_number: "_RM_end" }
beg = @HB.addOperation(new @types.Delimiter uid_beg, undefined, uid_end).execute()
end = @HB.addOperation(new @types.Delimiter uid_end, beg, undefined).execute()
@root_element = @HB.addOperation(new @types.ReplaceManager undefined, uid_r, beg, end).execute()
@root_element.replace first_word, { creator: '_', op_number: 'Replaceable'}
#
# @return WordType
#
getSharedObject: ()->
@root_element.val()
#
# Get the initialized connector.
#
getConnector: ()->
@connector
#
# @see HistoryBuffer
#
getHistoryBuffer: ()->
@HB
#
# Get the UserId from the HistoryBuffer object.
# In most cases this will be the same as the user_id value with which
# JsonFramework was initialized (Depending on the HistoryBuffer implementation).
#
getUserId: ()->
@HB.getUserId()
#
# @see JsonType.val
#
val: ()->
@getSharedObject().val()
#
# @see WordType.insertText
#
insertText: (pos, content)->
@getSharedObject().insertText pos, content
#
# @see WordType.deleteText
#
deleteText: (pos, length)->
@getSharedObject().deleteText pos, length
#
# @see WordType.bind
#
bind: (textarea)->
@getSharedObject().bind textarea
#
# @see WordType.replaceText
#
replaceText: (text)->
@getSharedObject().replaceText text
#
# @see Operation.on
#
on: ()->
@root_element.on arguments...
module.exports = TextFramework
if window?
if not window.Y?
window.Y = {}
window.Y.TextFramework = TextFramework

View File

@@ -1,101 +0,0 @@
json_types_uninitialized = require "../Types/XmlTypes"
HistoryBuffer = require "../HistoryBuffer"
Engine = require "../Engine"
adaptConnector = require "../ConnectorAdapter"
#
# Framework for Xml-like data-structures.
# Known values that are supported:
#
class XmlFramework
#
# @param {String} user_id Unique id of the peer.
# @param {Connector} Connector the connector class.
#
constructor: (user_id, @connector)->
@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 = new @types.XmlType(undefined, undefined, undefined, undefined, document.createElement("shared"))
#@HB.addOperation(first_word).execute()
uid_beg = @HB.getReservedUniqueIdentifier()
uid_end = @HB.getReservedUniqueIdentifier()
beg = @HB.addOperation(new @types.Delimiter uid_beg, undefined, uid_end).execute()
end = @HB.addOperation(new @types.Delimiter uid_end, beg, undefined).execute()
@root_element = new @types.ReplaceManager undefined, @HB.getReservedUniqueIdentifier(), beg, end
@HB.addOperation(@root_element).execute()
#@root_element.replace first_word
#
# @return JsonType
#
getSharedObject: ()->
@root_element.val()
#
# Get the initialized connector.
#
getConnector: ()->
@connector
#
# @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
# JsonFramework was initialized (Depending on the HistoryBuffer implementation).
#
getUserId: ()->
@HB.getUserId()
#
# @see JsonType.toJson
#
toJson : ()->
@getSharedObject().toJson()
#
# @see JsonType.val
#
val : ()->
if (arguments.length is 0) or (typeof arguments[0] is "boolean")
@getSharedObject().val(arguments[0])
else if arguments.length is 1
newXml = new @types.XmlType(undefined, undefined, undefined, undefined, arguments[0])
@HB.addOperation(newXml).execute()
@root_element.replace newXml
newXml
else
throw new Error "can only parse 0, or 1 parameter!"
#
# @see Operation.on
#
on: ()->
@getSharedObject().on arguments...
module.exports = XmlFramework
if window?
if not window.Y?
window.Y = {}
window.Y.XmlFramework = XmlFramework

View File

@@ -26,7 +26,7 @@ class HistoryBuffer
emptyGarbage: ()=>
for o in @garbage
#if @getOperationCounter(o.creator) > o.op_number
#if @getOperationCounter(o.uid.creator) > o.uid.op_number
o.cleanup?()
@garbage = @trash
@@ -104,13 +104,13 @@ class HistoryBuffer
if o.next_cl? # applies for all ops but the most right delimiter!
# search for the next _known_ operation. (When state_vector is {} then this is the Delimiter)
o_next = o.next_cl
while o_next.next_cl? and unknown(o_next.creator, o_next.op_number)
while o_next.next_cl? and unknown(o_next.uid.creator, o_next.uid.op_number)
o_next = o_next.next_cl
o_json.next = o_next.getUid()
else if o.prev_cl? # most right delimiter only!
# same as the above with prev.
o_prev = o.prev_cl
while o_prev.prev_cl? and unknown(o_prev.creator, o_prev.op_number)
while o_prev.prev_cl? and unknown(o_prev.uid.creator, o_prev.uid.op_number)
o_prev = o_prev.prev_cl
o_json.prev = o_prev.getUid()
json.push o_json
@@ -130,6 +130,7 @@ class HistoryBuffer
uid =
'creator' : user_id
'op_number' : @operation_counter[user_id]
'doSync' : true
@operation_counter[user_id]++
uid
@@ -147,35 +148,35 @@ class HistoryBuffer
# other operations (it wont executed)
#
addOperation: (o)->
if not @buffer[o.creator]?
@buffer[o.creator] = {}
if @buffer[o.creator][o.op_number]?
if not @buffer[o.uid.creator]?
@buffer[o.uid.creator] = {}
if @buffer[o.uid.creator][o.uid.op_number]?
throw new Error "You must not overwrite operations!"
@buffer[o.creator][o.op_number] = o
@buffer[o.uid.creator][o.uid.op_number] = o
@number_of_operations_added_to_HB ?= 0 # TODO: Debug, remove this
@number_of_operations_added_to_HB++
o
removeOperation: (o)->
delete @buffer[o.creator]?[o.op_number]
delete @buffer[o.uid.creator]?[o.uid.op_number]
#
# Increment the operation_counter that defines the current state of the Engine.
#
addToCounter: (o)->
if not @operation_counter[o.creator]?
@operation_counter[o.creator] = 0
if typeof o.op_number is 'number' and o.creator isnt @getUserId()
if not @operation_counter[o.uid.creator]?
@operation_counter[o.uid.creator] = 0
if typeof o.uid.op_number is 'number' and o.uid.creator isnt @getUserId()
# TODO: fix this issue better.
# Operations should income in order
# Then you don't have to do this..
if o.op_number is @operation_counter[o.creator]
@operation_counter[o.creator]++
while @getOperation({creator:o.creator, op_number: @operation_counter[o.creator]})?
@operation_counter[o.creator]++
if o.uid.op_number is @operation_counter[o.uid.creator]
@operation_counter[o.uid.creator]++
while @getOperation({creator:o.uid.creator, op_number: @operation_counter[o.uid.creator]})?
@operation_counter[o.uid.creator]++
#if @operation_counter[o.creator] isnt (o.op_number + 1)
#console.log (@operation_counter[o.creator] - (o.op_number + 1))
#if @operation_counter[o.uid.creator] isnt (o.uid.op_number + 1)
#console.log (@operation_counter[o.uid.creator] - (o.uid.op_number + 1))
#console.log o
#throw new Error "You don't receive operations in the proper order. Try counting like this 0,1,2,3,4,.. ;)"

View File

@@ -19,22 +19,14 @@ module.exports = (HB)->
class Operation
#
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @see HistoryBuffer.getNextOperationIdentifier
# @param {Object} uid A unique identifier.
# If uid is undefined, a new uid will be created before at the end of the execution sequence
#
constructor: (uid)->
@is_deleted = false
@doSync = true
@garbage_collected = false
if not uid?
uid = HB.getNextOperationIdentifier()
if not uid.doSync?
uid.doSync = not isNaN(parseInt(uid.op_number))
{
'creator': @creator
'op_number' : @op_number
'doSync' : @doSync
} = uid
if uid?
@uid = uid
type: "Insert"
@@ -123,20 +115,28 @@ module.exports = (HB)->
# Computes a unique identifier (uid) that identifies this operation.
#
getUid: ()->
{ 'creator': @creator, 'op_number': @op_number , 'sync': @doSync}
@uid
dontSync: ()->
@doSync = false
#
# @private
# If not already done, set the uid
# Add this to the HB
# Notify the all the listeners.
#
execute: ()->
@is_executed = true
if not @uid?
# When this operation was created without a uid, then set it here.
# 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()
@
@
#
# @private
@@ -261,8 +261,6 @@ 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)
#
# @see HistoryBuffer.getNextOperationIdentifier
#
constructor: (uid, prev_cl, next_cl, origin)->
@saveOperation 'prev_cl', prev_cl
@saveOperation 'next_cl', next_cl
@@ -360,7 +358,7 @@ module.exports = (HB)->
# $o happened concurrently
if o.getDistanceToOrigin() is i
# case 1
if o.creator < @creator
if o.uid.creator < @uid.creator
@prev_cl = o
distance_to_origin = i + 1
else
@@ -465,8 +463,6 @@ 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)
#
# @see HistoryBuffer.getNextOperationIdentifier
#
constructor: (uid, prev_cl, next_cl, origin)->
@saveOperation 'prev_cl', prev_cl
@saveOperation 'next_cl', next_cl
@@ -504,10 +500,10 @@ module.exports = (HB)->
else if @prev_cl? and not @prev_cl.next_cl?
delete @prev_cl.unchecked.next_cl
@prev_cl.next_cl = @
else if @prev_cl? or @next_cl?
else if @prev_cl? or @next_cl? or true # TODO: are you sure? This can happen right?
super
else
throw new Error "Delimiter is unsufficient defined!"
#else
# throw new Error "Delimiter is unsufficient defined!"
#
# @private

View File

@@ -98,19 +98,6 @@ module.exports = (HB)->
#
class JsonType extends types.MapManager
#
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
# @param {Object} initial_value Create this operation with an initial value.
# @param {String|Boolean} Whether the initial_value should be created as mutable. (Optional - see setMutableDefault)
#
constructor: (uid, initial_value, mutable)->
super uid
if initial_value?
if typeof initial_value isnt "object"
throw new Error "The initial value of JsonTypes must be of type Object! (current type: #{typeof initial_value})"
for name,o of initial_value
@val name, o, mutable
#
# Identifies this class.
# Use it to check whether this is a json-type or something else.
@@ -158,7 +145,7 @@ module.exports = (HB)->
# this event is not created by Yatta.
that.val(event.name, event.object[event.name])
that.on 'change', (event_name, property_name, op)->
if this is that and op.creator isnt HB.getUserId()
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?
@@ -170,7 +157,7 @@ module.exports = (HB)->
type: 'update'
name: property_name
oldValue: oldVal
changed_by: op.creator
changed_by: op.uid.creator
else
notifier.performChange 'add', ()->
that.bound_json[property_name] = that.val(property_name)
@@ -180,7 +167,7 @@ module.exports = (HB)->
type: 'add'
name: property_name
oldValue: oldVal
changed_by: op.creator
changed_by: op.uid.creator
@bound_json
#
@@ -236,11 +223,13 @@ module.exports = (HB)->
#
val: (name, content, mutable)->
if typeof name is 'object'
# Special case. First argument is an object. Then the second arg is mutable.
# Special case. First argument is an object. Then the second arg is mutable.
# (I refer to var name and content here)
# Keep that in mind when reading the following..
json = new JsonType undefined, name, content
HB.addOperation(json).execute()
@replace_manager.replace json
jt = new JsonType()
@replace_manager.replace jt.execute()
for n,o of name
jt.val n, o, mutable
@
else if name? and arguments.length > 1
if mutable?
@@ -253,15 +242,16 @@ module.exports = (HB)->
if typeof content is 'function'
@ # Just do nothing
else if (not content?) or (((not mutable) or typeof content is 'number') and content.constructor isnt Object)
obj = HB.addOperation(new types.ImmutableObject undefined, content).execute()
super name, obj
super name, (new types.ImmutableObject undefined, content).execute()
else
if typeof content is 'string'
word = HB.addOperation(new types.WordType undefined).execute()
word = (new types.WordType undefined).execute()
word.insertText 0, content
super name, word
else if content.constructor is Object
json = HB.addOperation(new JsonType undefined, content, mutable).execute()
json = new JsonType().execute()
for n,o of content
json.val n, o, mutable
super name, json
else
throw new Error "You must not set #{typeof content}-types in collaborative Json-objects!"

View File

@@ -34,7 +34,13 @@ module.exports = (HB)->
val: (name, content)->
if content?
if not @map[name]?
HB.addOperation(new AddName undefined, @, name).execute()
(new AddName undefined, @, name).execute()
## TODO: del this
if @map[name] == null
qqq = @
x = new AddName undefined, @, name
x.execute()
## endtodo
@map[name].replace content
@
else if name?
@@ -88,16 +94,23 @@ module.exports = (HB)->
if not @validateSavedOperations()
return false
else
uid_r = @map_manager.getUid()
# 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 = @map_manager.getUid()
uid_beg = clone(uid_r)
uid_beg.op_number = "_#{uid_beg.op_number}_RM_#{@name}_beginning"
uid_end = @map_manager.getUid()
uid_end = clone(uid_r)
uid_end.op_number = "_#{uid_end.op_number}_RM_#{@name}_end"
beg = HB.addOperation(new types.Delimiter uid_beg, undefined, uid_end).execute()
end = HB.addOperation(new types.Delimiter uid_end, beg, undefined).execute()
@map_manager.map[@name] = HB.addOperation(new ReplaceManager undefined, uid_r, beg, 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 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()
@@ -138,8 +151,8 @@ module.exports = (HB)->
@saveOperation 'beginning', beginning
@saveOperation 'end', end
else
@beginning = HB.addOperation new types.Delimiter undefined, undefined, undefined
@end = HB.addOperation new types.Delimiter undefined, @beginning, undefined
@beginning = new types.Delimiter undefined, undefined, undefined
@end = new types.Delimiter undefined, @beginning, undefined
@beginning.next_cl = @end
@beginning.execute()
@end.execute()
@@ -240,8 +253,7 @@ module.exports = (HB)->
#
replace: (content, replaceable_uid)->
o = @getLastOperation()
op = new Replaceable content, @, replaceable_uid, o, o.next_cl
HB.addOperation(op).execute()
(new Replaceable content, @, replaceable_uid, o, o.next_cl).execute()
undefined
#

View File

@@ -23,7 +23,7 @@ module.exports = (HB)->
# @param {Object} uid A unique identifier. If uid is undefined, a new uid will be created.
#
constructor: (content, uid, prev, next, origin)->
if content?.creator?
if content?.uid?.creator
@saveOperation 'content', content
else
@content = content
@@ -140,13 +140,10 @@ module.exports = (HB)->
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
if content.type?
op = new TextInsert content, undefined, left, right
HB.addOperation(op).execute()
(new TextInsert content, undefined, left, right).execute()
else
for c in content
op = new TextInsert c, undefined, left, right
HB.addOperation(op).execute()
left = op
left = (new TextInsert c, undefined, left, right).execute()
@
#
# Inserts a string into the word.
@@ -171,7 +168,7 @@ module.exports = (HB)->
for i in [0...length]
if o instanceof types.Delimiter
break
d = HB.addOperation(new TextDelete undefined, o).execute()
d = (new TextDelete undefined, o).execute()
o = o.next_cl
while not (o instanceof types.Delimiter) and o.isDeleted()
o = o.next_cl
@@ -188,7 +185,7 @@ module.exports = (HB)->
# Can only be used if the ReplaceManager was set!
# @see WordType.setReplaceManager
if @replace_manager?
word = HB.addOperation(new WordType undefined).execute()
word = (new WordType undefined).execute()
word.insertText 0, text
@replace_manager.replace(word)
word

View File

@@ -1,8 +1,8 @@
json_types_uninitialized = require "../Types/JsonTypes"
HistoryBuffer = require "../HistoryBuffer"
Engine = require "../Engine"
adaptConnector = require "../ConnectorAdapter"
json_types_uninitialized = require "./Types/JsonTypes"
HistoryBuffer = require "./HistoryBuffer"
Engine = require "./Engine"
adaptConnector = require "./ConnectorAdapter"
#
@@ -12,29 +12,28 @@ adaptConnector = require "../ConnectorAdapter"
# * Integer
# * Array
#
class JsonFramework
class Yatta
#
# @param {String} user_id Unique id of the peer.
# @param {Connector} Connector the connector class.
#
constructor: (user_id, @connector)->
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 = new @types.JsonType(@HB.getReservedUniqueIdentifier())
@HB.addOperation(first_word).execute()
first_word = new @types.JsonType(@HB.getReservedUniqueIdentifier()).execute()
uid_beg = @HB.getReservedUniqueIdentifier()
uid_end = @HB.getReservedUniqueIdentifier()
beg = @HB.addOperation(new @types.Delimiter uid_beg, undefined, uid_end).execute()
end = @HB.addOperation(new @types.Delimiter uid_end, beg, undefined).execute()
beg = (new @types.Delimiter uid_beg, undefined, uid_end).execute()
end = (new @types.Delimiter uid_end, beg, undefined).execute()
@root_element = new @types.ReplaceManager undefined, @HB.getReservedUniqueIdentifier(), beg, end
@HB.addOperation(@root_element).execute()
@root_element = (new @types.ReplaceManager undefined, @HB.getReservedUniqueIdentifier(), beg, end).execute()
@root_element.replace first_word, @HB.getReservedUniqueIdentifier()
#
@@ -64,7 +63,7 @@ class JsonFramework
#
# Get the UserId from the HistoryBuffer object.
# In most cases this will be the same as the user_id value with which
# JsonFramework was initialized (Depending on the HistoryBuffer implementation).
# Yatta was initialized (Depending on the HistoryBuffer implementation).
#
getUserId: ()->
@HB.getUserId()
@@ -96,7 +95,7 @@ class JsonFramework
#
# @see JsonType.value
#
Object.defineProperty JsonFramework.prototype, 'value',
Object.defineProperty Yatta.prototype, 'value',
get : -> @getSharedObject().value
set : (o)->
if o.constructor is {}.constructor
@@ -105,8 +104,6 @@ class JsonFramework
else
throw new Error "You must only set Object values!"
module.exports = JsonFramework
if window?
if not window.Y?
window.Y = {}
window.Y.JsonFramework = JsonFramework
module.exports = Yatta
if window? and not window.Yatta?
window.Yatta = Yatta

View File

@@ -1,8 +0,0 @@
exports['JsonFramework'] =
require './Frameworks/JsonFramework'
exports['TextFramework'] =
require './Frameworks/TextFramework'
exports['XmlFramework'] =
require './Frameworks/XmlFramework'