refs, complex saveOperation' and validateSavedOperations`

This commit is contained in:
Kevin Jahns 2015-04-17 20:11:05 +02:00
parent f44f463e9d
commit b02662c36e
14 changed files with 4396 additions and 1008 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -154,32 +154,67 @@ module.exports = function() {
return json; return json;
}; };
Operation.prototype.saveOperation = function(name, op) { Operation.prototype.saveOperation = function(name, op, base) {
var base1, dest, j, last_path, len, path, paths;
if (base == null) {
base = "this";
}
if (op == null) { if (op == null) {
} else if ((op.execute != null) || !((op.op_number != null) && (op.creator != null))) { } else if ((op.execute != null) || !((op.op_number != null) && (op.creator != null))) {
return this[name] = op; if (base === "this") {
return this[name] = op;
} else {
dest = this[base];
paths = name.split("/");
last_path = paths.pop();
for (j = 0, len = paths.length; j < len; j++) {
path = paths[j];
dest = dest[path];
}
return dest[last_path] = op;
}
} else { } else {
if (this.unchecked == null) { if (this.unchecked == null) {
this.unchecked = {}; this.unchecked = {};
} }
return this.unchecked[name] = op; if ((base1 = this.unchecked)[base] == null) {
base1[base] = {};
}
return this.unchecked[base][name] = op;
} }
}; };
Operation.prototype.validateSavedOperations = function() { Operation.prototype.validateSavedOperations = function() {
var name, op, op_uid, ref, success, uninstantiated; var base, base_name, dest, j, last_path, len, name, op, op_uid, path, paths, ref, success, uninstantiated;
uninstantiated = {}; uninstantiated = {};
success = this; success = this;
ref = this.unchecked; ref = this.unchecked;
for (name in ref) { for (base_name in ref) {
op_uid = ref[name]; base = ref[base_name];
op = this.HB.getOperation(op_uid); for (name in base) {
if (op) { op_uid = base[name];
this[name] = op; op = this.HB.getOperation(op_uid);
} else { if (op) {
uninstantiated[name] = op_uid; if (base_name === "this") {
success = false; this[name] = op;
} else {
dest = this[base_name];
paths = name.split("/");
last_path = paths.pop();
for (j = 0, len = paths.length; j < len; j++) {
path = paths[j];
dest = dest[path];
}
dest[last_path] = op;
}
} else {
if (uninstantiated[base_name] == null) {
uninstantiated[base_name] = {};
}
uninstantiated[base_name][name] = op_uid;
success = false;
}
} }
} }
delete this.unchecked; delete this.unchecked;
@ -253,7 +288,8 @@ module.exports = function() {
ops.Insert = (function(superClass) { ops.Insert = (function(superClass) {
extend(Insert, superClass); extend(Insert, superClass);
function Insert(custom_type, content, parent, uid, prev_cl, next_cl, origin) { function Insert(custom_type, content, content_operations, parent, uid, prev_cl, next_cl, origin) {
var name, op;
if (content === void 0) { if (content === void 0) {
} else if ((content != null) && (content.creator != null)) { } else if ((content != null) && (content.creator != null)) {
@ -261,6 +297,13 @@ module.exports = function() {
} else { } else {
this.content = content; this.content = content;
} }
if (content_operations != null) {
this.content_operations = {};
for (name in content_operations) {
op = content_operations[name];
this.saveOperation(name, op, 'content_operations');
}
}
this.saveOperation('parent', parent); this.saveOperation('parent', parent);
this.saveOperation('prev_cl', prev_cl); this.saveOperation('prev_cl', prev_cl);
this.saveOperation('next_cl', next_cl); this.saveOperation('next_cl', next_cl);
@ -275,8 +318,28 @@ module.exports = function() {
Insert.prototype.type = "Insert"; Insert.prototype.type = "Insert";
Insert.prototype.val = function() { Insert.prototype.val = function() {
if ((this.content != null) && (this.content.getCustomType != null)) { var content, n, ref, ref1, v;
return this.content.getCustomType(); if (this.content != null) {
if (this.content.getCustomType != null) {
return this.content.getCustomType();
} else if (this.content.constructor === Object) {
content = {};
ref = this.content;
for (n in ref) {
v = ref[n];
content[n] = v;
}
if (this.content_operations != null) {
ref1 = this.content_operations;
for (n in ref1) {
v = ref1[n];
content[n] = v;
}
}
return content;
} else {
return this.content;
}
} else { } else {
return this.content; return this.content;
} }
@ -380,14 +443,14 @@ module.exports = function() {
}; };
Insert.prototype.execute = function() { Insert.prototype.execute = function() {
var base, distance_to_origin, i, o; var base1, distance_to_origin, i, o;
if (!this.validateSavedOperations()) { if (!this.validateSavedOperations()) {
return false; return false;
} else { } else {
if (this.content instanceof ops.Operation) { if (this.content instanceof ops.Operation) {
this.content.insert_parent = this; this.content.insert_parent = this;
if ((base = this.content).referenced_by == null) { if ((base1 = this.content).referenced_by == null) {
base.referenced_by = 0; base1.referenced_by = 0;
} }
this.content.referenced_by++; this.content.referenced_by++;
} }
@ -461,7 +524,7 @@ module.exports = function() {
}; };
Insert.prototype._encode = function(json) { Insert.prototype._encode = function(json) {
var ref; var n, o, operations, ref, ref1;
if (json == null) { if (json == null) {
json = {}; json = {};
} }
@ -474,9 +537,18 @@ module.exports = function() {
} }
json.parent = this.parent.getUid(); json.parent = this.parent.getUid();
if (((ref = this.content) != null ? ref.getUid : void 0) != null) { if (((ref = this.content) != null ? ref.getUid : void 0) != null) {
json['content'] = this.content.getUid(); json.content = this.content.getUid();
} else { } else {
json['content'] = JSON.stringify(this.content); json.content = JSON.stringify(this.content);
}
if (this.content_operations != null) {
operations = {};
ref1 = this.content_operations;
for (n in ref1) {
o = ref1[n];
operations[n] = o.getUid();
}
json.content_operations = operations;
} }
return Insert.__super__._encode.call(this, json); return Insert.__super__._encode.call(this, json);
}; };
@ -485,12 +557,12 @@ module.exports = function() {
})(ops.Operation); })(ops.Operation);
ops.Insert.parse = function(json) { ops.Insert.parse = function(json) {
var content, next, origin, parent, prev, uid; var content, content_operations, next, origin, parent, prev, uid;
content = json['content'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], parent = json['parent']; content = json['content'], content_operations = json['content_operations'], uid = json['uid'], prev = json['prev'], next = json['next'], origin = json['origin'], parent = json['parent'];
if (typeof content === "string") { if (typeof content === "string") {
content = JSON.parse(content); content = JSON.parse(content);
} }
return new this(null, content, parent, uid, prev, next, origin); return new this(null, content, content_operations, parent, uid, prev, next, origin);
}; };
ops.Delimiter = (function(superClass) { ops.Delimiter = (function(superClass) {
extend(Delimiter, superClass); extend(Delimiter, superClass);

View File

@ -281,14 +281,14 @@ module.exports = function() {
} }
left = right.prev_cl; left = right.prev_cl;
if (contents instanceof ops.Operation) { if (contents instanceof ops.Operation) {
(new ops.Insert(null, content, void 0, void 0, left, right)).execute(); (new ops.Insert(null, content, null, void 0, void 0, left, right)).execute();
} else { } else {
for (j = 0, len = contents.length; j < len; j++) { for (j = 0, len = contents.length; j < len; j++) {
c = contents[j]; c = contents[j];
if ((c != null) && (c._name != null) && (c._getModel != null)) { if ((c != null) && (c._name != null) && (c._getModel != null)) {
c = c._getModel(this.custom_types, this.operations); c = c._getModel(this.custom_types, this.operations);
} }
tmp = (new ops.Insert(null, c, void 0, void 0, left, right)).execute(); tmp = (new ops.Insert(null, c, null, void 0, void 0, left, right)).execute();
left = tmp; left = tmp;
} }
} }
@ -337,7 +337,7 @@ module.exports = function() {
position: op.getPosition(), position: op.getPosition(),
object: this.getCustomType(), object: this.getCustomType(),
changedBy: op.uid.creator, changedBy: op.uid.creator,
value: getContentType(op.content) value: getContentType(op.val())
} }
]); ]);
}; };
@ -366,17 +366,38 @@ module.exports = function() {
ops.Composition = (function(superClass) { ops.Composition = (function(superClass) {
extend(Composition, superClass); extend(Composition, superClass);
function Composition(custom_type, composition_value, uid, tmp_composition_ref) { function Composition(custom_type, _composition_value, composition_value_operations, uid, tmp_composition_ref) {
var n, o;
this._composition_value = _composition_value;
console.log("delete this ...");
this.constructed_with = [custom_type, this._composition_value, composition_value_operations, uid, tmp_composition_ref];
Composition.__super__.constructor.call(this, custom_type, uid); Composition.__super__.constructor.call(this, custom_type, uid);
if (tmp_composition_ref != null) { if (tmp_composition_ref != null) {
this.tmp_composition_ref = tmp_composition_ref; this.tmp_composition_ref = tmp_composition_ref;
} else { } else {
this.composition_ref = this.end.prev_cl; this.composition_ref = this.end.prev_cl;
} }
if (composition_value_operations != null) {
this.composition_value_operations = {};
for (n in composition_value_operations) {
o = composition_value_operations[n];
this.saveOperation(n, o, '_composition_value');
}
}
} }
Composition.prototype.type = "Composition"; Composition.prototype.type = "Composition";
Composition.prototype.execute = function() {
if (this.validateSavedOperations()) {
this.getCustomType()._setCompositionValue(this._composition_value);
delete this._composition_value;
return Composition.__super__.execute.apply(this, arguments);
} else {
return false;
}
};
Composition.prototype.callOperationSpecificInsertEvents = function(op) { Composition.prototype.callOperationSpecificInsertEvents = function(op) {
var o; var o;
if (this.tmp_composition_ref != null) { if (this.tmp_composition_ref != null) {
@ -394,7 +415,7 @@ module.exports = function() {
return; return;
} }
if (this.composition_ref.next_cl === op) { if (this.composition_ref.next_cl === op) {
op.undo_delta = this.getCustomType()._apply(op.content); op.undo_delta = this.getCustomType()._apply(op.val());
} else { } else {
o = this.end.prev_cl; o = this.end.prev_cl;
while (o !== op) { while (o !== op) {
@ -402,7 +423,7 @@ module.exports = function() {
o = o.prev_cl; o = o.prev_cl;
} }
while (o !== this.end) { while (o !== this.end) {
o.undo_delta = this.getCustomType()._apply(o.content); o.undo_delta = this.getCustomType()._apply(o.val());
o = o.next_cl; o = o.next_cl;
} }
} }
@ -418,16 +439,26 @@ module.exports = function() {
Composition.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {}; Composition.prototype.callOperationSpecificDeleteEvents = function(op, del_op) {};
Composition.prototype.applyDelta = function(delta) { Composition.prototype.applyDelta = function(delta, operations) {
(new ops.Insert(null, delta, this, null, this.end.prev_cl, this.end)).execute(); (new ops.Insert(null, delta, operations, this, null, this.end.prev_cl, this.end)).execute();
return void 0; return void 0;
}; };
Composition.prototype._encode = function(json) { Composition.prototype._encode = function(json) {
var custom, n, o, ref;
if (json == null) { if (json == null) {
json = {}; json = {};
} }
json.composition_value = JSON.stringify(this.getCustomType()._getCompositionValue()); custom = this.getCustomType()._getCompositionValue();
json.composition_value = custom.composition_value;
if (custom.composition_value_operations != null) {
json.composition_value_operations = {};
ref = custom.composition_value_operations;
for (n in ref) {
o = ref[n];
json.composition_value_operations[n] = o.getUid();
}
}
if (this.composition_ref != null) { if (this.composition_ref != null) {
json.composition_ref = this.composition_ref.getUid(); json.composition_ref = this.composition_ref.getUid();
} else { } else {
@ -440,9 +471,9 @@ module.exports = function() {
})(ops.ListManager); })(ops.ListManager);
ops.Composition.parse = function(json) { ops.Composition.parse = function(json) {
var composition_ref, composition_value, custom_type, uid; var composition_ref, composition_value, composition_value_operations, custom_type, uid;
uid = json['uid'], custom_type = json['custom_type'], composition_value = json['composition_value'], composition_ref = json['composition_ref']; uid = json['uid'], custom_type = json['custom_type'], composition_value = json['composition_value'], composition_value_operations = json['composition_value_operations'], composition_ref = json['composition_ref'];
return new this(custom_type, JSON.parse(composition_value), uid, composition_ref); return new this(custom_type, composition_value, composition_value_operations, uid, composition_ref);
}; };
ops.ReplaceManager = (function(superClass) { ops.ReplaceManager = (function(superClass) {
extend(ReplaceManager, superClass); extend(ReplaceManager, superClass);
@ -516,7 +547,7 @@ module.exports = function() {
ReplaceManager.prototype.replace = function(content, replaceable_uid) { ReplaceManager.prototype.replace = function(content, replaceable_uid) {
var o, relp; var o, relp;
o = this.getLastOperation(); o = this.getLastOperation();
relp = (new ops.Insert(null, content, this, replaceable_uid, o, o.next_cl)).execute(); relp = (new ops.Insert(null, content, null, this, replaceable_uid, o, o.next_cl)).execute();
return void 0; return void 0;
}; };

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@ -182,7 +182,7 @@ module.exports = ()->
# @param {String} name The name of the operation. After calling this function op is accessible via this[name]. # @param {String} name The name of the operation. After calling this function op is accessible via this[name].
# @param {Operation} op An Operation object # @param {Operation} op An Operation object
# #
saveOperation: (name, op)-> saveOperation: (name, op, base = "this")->
# #
# Every instance of $Operation must have an $execute function. # Every instance of $Operation must have an $execute function.
@ -194,11 +194,20 @@ module.exports = ()->
else if op.execute? or not (op.op_number? and op.creator?) else if op.execute? or not (op.op_number? and op.creator?)
# is instantiated, or op is string. Currently "Delimiter" is saved as string # is instantiated, or op is string. Currently "Delimiter" is saved as string
# (in combination with @parent you can retrieve the delimiter..) # (in combination with @parent you can retrieve the delimiter..)
@[name] = op if base is "this"
@[name] = op
else
dest = @[base]
paths = name.split("/")
last_path = paths.pop()
for path in paths
dest = dest[path]
dest[last_path] = op
else else
# not initialized. Do it when calling $validateSavedOperations() # not initialized. Do it when calling $validateSavedOperations()
@unchecked ?= {} @unchecked ?= {}
@unchecked[name] = op @unchecked[base] ?= {}
@unchecked[base][name] = op
# #
# @private # @private
@ -210,13 +219,23 @@ module.exports = ()->
validateSavedOperations: ()-> validateSavedOperations: ()->
uninstantiated = {} uninstantiated = {}
success = @ success = @
for name, op_uid of @unchecked for base_name, base of @unchecked
op = @HB.getOperation op_uid for name, op_uid of base
if op op = @HB.getOperation op_uid
@[name] = op if op
else if base_name is "this"
uninstantiated[name] = op_uid @[name] = op
success = false else
dest = @[base_name]
paths = name.split("/")
last_path = paths.pop()
for path in paths
dest = dest[path]
dest[last_path] = op
else
uninstantiated[base_name] ?= {}
uninstantiated[base_name][name] = op_uid
success = false
delete @unchecked delete @unchecked
if not success if not success
@unchecked = uninstantiated @unchecked = uninstantiated
@ -305,7 +324,7 @@ module.exports = ()->
# @param {Operation} prev_cl The predecessor of this operation in the complete-list (cl) # @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) # @param {Operation} next_cl The successor of this operation in the complete-list (cl)
# #
constructor: (custom_type, content, parent, uid, prev_cl, next_cl, origin)-> constructor: (custom_type, content, content_operations, parent, uid, prev_cl, next_cl, origin)->
# see encode to see, why we are doing it this way # see encode to see, why we are doing it this way
if content is undefined if content is undefined
# nop # nop
@ -313,6 +332,10 @@ module.exports = ()->
@saveOperation 'content', content @saveOperation 'content', content
else else
@content = content @content = content
if content_operations?
@content_operations = {}
for name, op of content_operations
@saveOperation name, op, 'content_operations'
@saveOperation 'parent', parent @saveOperation 'parent', parent
@saveOperation 'prev_cl', prev_cl @saveOperation 'prev_cl', prev_cl
@saveOperation 'next_cl', next_cl @saveOperation 'next_cl', next_cl
@ -325,8 +348,19 @@ module.exports = ()->
type: "Insert" type: "Insert"
val: ()-> val: ()->
if @content? and @content.getCustomType? if @content?
@content.getCustomType() if @content.getCustomType?
@content.getCustomType()
else if @content.constructor is Object
content = {}
for n,v of @content
content[n] = v
if @content_operations?
for n,v of @content_operations
content[n] = v
content
else
@content
else else
@content @content
@ -518,14 +552,20 @@ module.exports = ()->
json.parent = @parent.getUid() json.parent = @parent.getUid()
if @content?.getUid? if @content?.getUid?
json['content'] = @content.getUid() json.content = @content.getUid()
else else
json['content'] = JSON.stringify @content json.content = JSON.stringify @content
if @content_operations?
operations = {}
for n,o of @content_operations
operations[n] = o.getUid()
json.content_operations = operations
super json super json
ops.Insert.parse = (json)-> ops.Insert.parse = (json)->
{ {
'content' : content 'content' : content
'content_operations' : content_operations
'uid' : uid 'uid' : uid
'prev': prev 'prev': prev
'next': next 'next': next
@ -534,7 +574,7 @@ module.exports = ()->
} = json } = json
if typeof content is "string" if typeof content is "string"
content = JSON.parse(content) content = JSON.parse(content)
new this null, content, parent, uid, prev, next, origin new this null, content, content_operations, parent, uid, prev, next, origin
# #
# @nodoc # @nodoc

View File

@ -238,12 +238,12 @@ module.exports = ()->
# TODO: always expect an array as content. Then you can combine this with the other option (else) # TODO: always expect an array as content. Then you can combine this with the other option (else)
if contents instanceof ops.Operation if contents instanceof ops.Operation
(new ops.Insert null, content, undefined, undefined, left, right).execute() (new ops.Insert null, content, null, undefined, undefined, left, right).execute()
else else
for c in contents for c in contents
if c? and c._name? and c._getModel? if c? and c._name? and c._getModel?
c = c._getModel(@custom_types, @operations) c = c._getModel(@custom_types, @operations)
tmp = (new ops.Insert null, c, undefined, undefined, left, right).execute() tmp = (new ops.Insert null, c, null, undefined, undefined, left, right).execute()
left = tmp left = tmp
@ @
@ -290,7 +290,7 @@ module.exports = ()->
position: op.getPosition() position: op.getPosition()
object: @getCustomType() object: @getCustomType()
changedBy: op.uid.creator changedBy: op.uid.creator
value: getContentType op.content value: getContentType op.val()
] ]
callOperationSpecificDeleteEvents: (op, del_op)-> callOperationSpecificDeleteEvents: (op, del_op)->
@ -316,31 +316,60 @@ module.exports = ()->
class ops.Composition extends ops.ListManager class ops.Composition extends ops.ListManager
constructor: (custom_type, @composition_value, uid, composition_ref)-> constructor: (custom_type, @_composition_value, composition_value_operations, uid, tmp_composition_ref)->
# we can't use @seveOperation 'composition_ref', tmp_composition_ref here,
# because then there is a "loop" (insertion refers to parant, refers to insertion..)
# This is why we have to check in @callOperationSpecificInsertEvents until we find it
console.log("delete this ...")
this.constructed_with = [custom_type, @_composition_value, composition_value_operations, uid, tmp_composition_ref] # debug!
super custom_type, uid super custom_type, uid
if composition_ref if tmp_composition_ref?
@saveOperation 'composition_ref', composition_ref @tmp_composition_ref = tmp_composition_ref
else else
@composition_ref = @beginning @composition_ref = @end.prev_cl
if composition_value_operations?
@composition_value_operations = {}
for n,o of composition_value_operations
@saveOperation n, o, '_composition_value'
type: "Composition" type: "Composition"
val: ()-> #
@composition_value # @private
# @see Operation.execute
#
execute: ()->
if @validateSavedOperations()
@getCustomType()._setCompositionValue @_composition_value
delete @_composition_value
super
else
false
# #
# This is called, when the Insert-operation was successfully executed. # This is called, when the Insert-operation was successfully executed.
# #
callOperationSpecificInsertEvents: (op)-> callOperationSpecificInsertEvents: (op)->
if @tmp_composition_ref?
if op.uid.creator is @tmp_composition_ref.creator and op.uid.op_number is @tmp_composition_ref.op_number
@composition_ref = op
delete @tmp_composition_ref
o = op.next_cl
while o.next_cl?
if not o.isDeleted()
@callOperationSpecificInsertEvents o
o = o.next_cl
return
if @composition_ref.next_cl is op if @composition_ref.next_cl is op
op.undo_delta = @getCustomType()._apply op.content op.undo_delta = @getCustomType()._apply op.val()
else else
o = @end.prev_cl o = @end.prev_cl
while o isnt op while o isnt op
@getCustomType()._unapply o.undo_delta @getCustomType()._unapply o.undo_delta
o = o.prev_cl o = o.prev_cl
while o isnt @end while o isnt @end
o.undo_delta = @getCustomType()._apply o.content o.undo_delta = @getCustomType()._apply o.val()
o = o.next_cl o = o.next_cl
@composition_ref = @end.prev_cl @composition_ref = @end.prev_cl
@ -361,16 +390,24 @@ module.exports = ()->
# #
# @param delta The delta that is applied to the composition_value # @param delta The delta that is applied to the composition_value
# #
applyDelta: (delta)-> applyDelta: (delta, operations)->
(new ops.Insert null, delta, @, null, @end.prev_cl, @end).execute() (new ops.Insert null, delta, operations, @, null, @end.prev_cl, @end).execute()
undefined undefined
# #
# Encode this operation in such a way that it can be parsed by remote peers. # Encode this operation in such a way that it can be parsed by remote peers.
# #
_encode: (json = {})-> _encode: (json = {})->
json.composition_value = JSON.stringify @composition_value custom = @getCustomType()._getCompositionValue()
json.composition_ref = @composition_ref.getUid() json.composition_value = custom.composition_value
if custom.composition_value_operations?
json.composition_value_operations = {}
for n,o of custom.composition_value_operations
json.composition_value_operations[n] = o.getUid()
if @composition_ref?
json.composition_ref = @composition_ref.getUid()
else
json.composition_ref = @tmp_composition_ref
super json super json
ops.Composition.parse = (json)-> ops.Composition.parse = (json)->
@ -378,9 +415,10 @@ module.exports = ()->
'uid' : uid 'uid' : uid
'custom_type': custom_type 'custom_type': custom_type
'composition_value' : composition_value 'composition_value' : composition_value
'composition_value_operations' : composition_value_operations
'composition_ref' : composition_ref 'composition_ref' : composition_ref
} = json } = json
new this(custom_type, JSON.parse(composition_value), uid, composition_ref) new this(custom_type, composition_value, composition_value_operations, uid, composition_ref)
# #
@ -465,7 +503,7 @@ module.exports = ()->
# #
replace: (content, replaceable_uid)-> replace: (content, replaceable_uid)->
o = @getLastOperation() o = @getLastOperation()
relp = (new ops.Insert null, content, @, replaceable_uid, o, o.next_cl).execute() relp = (new ops.Insert null, content, null, @, replaceable_uid, o, o.next_cl).execute()
# TODO: delete repl (for debugging) # TODO: delete repl (for debugging)
undefined undefined

File diff suppressed because one or more lines are too long

3
y.js

File diff suppressed because one or more lines are too long