Operations are now Garbage Collected!

This commit is contained in:
Kevin Jahns 2014-09-17 16:10:41 +02:00
parent b03f477a3f
commit 68c17f1876
63 changed files with 2420 additions and 934 deletions

View File

@ -75,11 +75,8 @@ Yatta! is still in an early development phase. Don't expect that everything is w
But I would become really motivated if you gave me some feedback :) ([github](https://github.com/DadaMonad/Yatta/issues)).
### Current Issues
Currently, I don't perform Garbage Collection. Therefore, the space requirement will never decrease.
* Garbage Collection
* XML support
## Support
Please report any issues to the [Github issue page](https://github.com/DadaMonad/Yatta/issues)!

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

@ -27,6 +27,7 @@
if (!((user_list != null ? user_list.length : void 0) === 0)) {
this.engine.applyOps(user_list[0].getHistoryBuffer()._encode());
}
this.HB.setManualGarbageCollect();
this.unexecuted = {};
}

View File

@ -1 +1 @@
{"version":3,"sources":["Connectors/TestConnector.coffee"],"names":[],"mappings":"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","file":"Connectors/TestConnector.js","sourceRoot":"/source/","sourcesContent":["\n_ = require \"underscore\"\n\nmodule.exports = (user_list)->\n\n #\n # @nodoc\n # A trivial Connector that simulates network delay.\n #\n class TestConnector\n\n #\n # @param {Engine} engine The transformation engine\n # @param {HistoryBuffer} HB\n # @param {Array<Function>} execution_listener You must ensure that whenever an operation is executed, every function in this Array is called.\n # @param {Yatta} yatta The Yatta framework.\n #\n constructor: (@engine, @HB, @execution_listener)->\n send_ = (o)=>\n @send o\n @execution_listener.push send_\n\n @applied_operations = []\n appliedOperationsListener = (o)=>\n @applied_operations.push o\n @execution_listener.push appliedOperationsListener\n if not (user_list?.length is 0)\n @engine.applyOps user_list[0].getHistoryBuffer()._encode()\n\n @unexecuted = {}\n\n #\n # This engine applied operations in a specific order.\n # Get the ops in the right order.\n #\n getOpsInExecutionOrder: ()->\n @applied_operations\n\n #\n # This function is called whenever an operation was executed.\n # @param {Operation} o The operation that was executed.\n #\n send: (o)->\n if (o.uid.creator is @HB.getUserId()) and (typeof o.uid.op_number isnt \"string\")\n for user in user_list\n if user.getUserId() isnt @HB.getUserId()\n user.getConnector().receive(o)\n\n #\n # This function is called whenever an operation was received from another peer.\n # @param {Operation} o The operation that was received.\n #\n receive: (o)->\n @unexecuted[o.uid.creator] ?= []\n @unexecuted[o.uid.creator].push o\n\n #\n # Flush one operation from the line of a specific user.\n #\n flushOne: (user)->\n if @unexecuted[user]?.length > 0\n @engine.applyOp @unexecuted[user].shift()\n\n #\n # Flush one operation on a random line.\n #\n flushOneRandom: ()->\n @flushOne (_.random 0, (user_list.length-1))\n\n #\n # Flush all operations on every line.\n #\n flushAll: ()->\n for n,ops of @unexecuted\n @engine.applyOps ops\n @unexecuted = {}\n\n"]}
{"version":3,"sources":["Connectors/TestConnector.coffee"],"names":[],"mappings":"AACA;AAAA,MAAA,CAAA;;AAAA,EAAA,CAAA,GAAI,OAAA,CAAQ,YAAR,CAAJ,CAAA;;AAAA,EAEA,MAAM,CAAC,OAAP,GAAiB,SAAC,SAAD,GAAA;AAMf,QAAA,aAAA;WAAM;AAQS,MAAA,uBAAE,MAAF,EAAW,EAAX,EAAgB,kBAAhB,GAAA;AACX,YAAA,gCAAA;AAAA,QADY,IAAC,CAAA,SAAA,MACb,CAAA;AAAA,QADqB,IAAC,CAAA,KAAA,EACtB,CAAA;AAAA,QAD0B,IAAC,CAAA,qBAAA,kBAC3B,CAAA;AAAA,QAAA,KAAA,GAAQ,CAAA,SAAA,KAAA,GAAA;iBAAA,SAAC,CAAD,GAAA;mBACN,KAAC,CAAA,IAAD,CAAM,CAAN,EADM;UAAA,EAAA;QAAA,CAAA,CAAA,CAAA,IAAA,CAAR,CAAA;AAAA,QAEA,IAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,KAAzB,CAFA,CAAA;AAAA,QAIA,IAAC,CAAA,kBAAD,GAAsB,EAJtB,CAAA;AAAA,QAKA,yBAAA,GAA4B,CAAA,SAAA,KAAA,GAAA;iBAAA,SAAC,CAAD,GAAA;mBAC1B,KAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,CAAzB,EAD0B;UAAA,EAAA;QAAA,CAAA,CAAA,CAAA,IAAA,CAL5B,CAAA;AAAA,QAOA,IAAC,CAAA,kBAAkB,CAAC,IAApB,CAAyB,yBAAzB,CAPA,CAAA;AAQA,QAAA,IAAG,CAAA,sBAAK,SAAS,CAAE,gBAAX,KAAqB,CAAtB,CAAP;AACE,UAAA,IAAC,CAAA,MAAM,CAAC,QAAR,CAAiB,SAAU,CAAA,CAAA,CAAE,CAAC,gBAAb,CAAA,CAA+B,CAAC,OAAhC,CAAA,CAAjB,CAAA,CADF;SARA;AAAA,QAWA,IAAC,CAAA,EAAE,CAAC,uBAAJ,CAAA,CAXA,CAAA;AAAA,QAYA,IAAC,CAAA,UAAD,GAAc,EAZd,CADW;MAAA,CAAb;;AAAA,8BAmBA,sBAAA,GAAwB,SAAA,GAAA;eACtB,IAAC,CAAA,mBADqB;MAAA,CAnBxB,CAAA;;AAAA,8BA0BA,IAAA,GAAM,SAAC,CAAD,GAAA;AACJ,YAAA,wBAAA;AAAA,QAAA,IAAG,CAAC,CAAC,CAAC,GAAG,CAAC,OAAN,KAAiB,IAAC,CAAA,EAAE,CAAC,SAAJ,CAAA,CAAlB,CAAA,IAAuC,CAAC,MAAA,CAAA,CAAQ,CAAC,GAAG,CAAC,SAAb,KAA4B,QAA7B,CAA1C;AACE;eAAA,gDAAA;iCAAA;AACE,YAAA,IAAG,IAAI,CAAC,SAAL,CAAA,CAAA,KAAsB,IAAC,CAAA,EAAE,CAAC,SAAJ,CAAA,CAAzB;4BACE,IAAI,CAAC,YAAL,CAAA,CAAmB,CAAC,OAApB,CAA4B,CAA5B,GADF;aAAA,MAAA;oCAAA;aADF;AAAA;0BADF;SADI;MAAA,CA1BN,CAAA;;AAAA,8BAoCA,OAAA,GAAS,SAAC,CAAD,GAAA;AACP,YAAA,YAAA;;yBAA8B;SAA9B;eACA,IAAC,CAAA,UAAW,CAAA,CAAC,CAAC,GAAG,CAAC,OAAN,CAAc,CAAC,IAA3B,CAAgC,CAAhC,EAFO;MAAA,CApCT,CAAA;;AAAA,8BA2CA,QAAA,GAAU,SAAC,IAAD,GAAA;AACR,YAAA,IAAA;AAAA,QAAA,kDAAoB,CAAE,gBAAnB,GAA4B,CAA/B;iBACE,IAAC,CAAA,MAAM,CAAC,OAAR,CAAgB,IAAC,CAAA,UAAW,CAAA,IAAA,CAAK,CAAC,KAAlB,CAAA,CAAhB,EADF;SADQ;MAAA,CA3CV,CAAA;;AAAA,8BAkDA,cAAA,GAAgB,SAAA,GAAA;eACd,IAAC,CAAA,QAAD,CAAW,CAAC,CAAC,MAAF,CAAS,CAAT,EAAa,SAAS,CAAC,MAAV,GAAiB,CAA9B,CAAX,EADc;MAAA,CAlDhB,CAAA;;AAAA,8BAwDA,QAAA,GAAU,SAAA,GAAA;AACR,YAAA,YAAA;AAAA;AAAA,aAAA,SAAA;wBAAA;AACE,UAAA,IAAC,CAAA,MAAM,CAAC,QAAR,CAAiB,GAAjB,CAAA,CADF;AAAA,SAAA;eAEA,IAAC,CAAA,UAAD,GAAc,GAHN;MAAA,CAxDV,CAAA;;2BAAA;;SAda;EAAA,CAFjB,CAAA;AAAA","file":"Connectors/TestConnector.js","sourceRoot":"/source/","sourcesContent":["\n_ = require \"underscore\"\n\nmodule.exports = (user_list)->\n\n #\n # @nodoc\n # A trivial Connector that simulates network delay.\n #\n class TestConnector\n\n #\n # @param {Engine} engine The transformation engine\n # @param {HistoryBuffer} HB\n # @param {Array<Function>} execution_listener You must ensure that whenever an operation is executed, every function in this Array is called.\n # @param {Yatta} yatta The Yatta framework.\n #\n constructor: (@engine, @HB, @execution_listener)->\n send_ = (o)=>\n @send o\n @execution_listener.push send_\n\n @applied_operations = []\n appliedOperationsListener = (o)=>\n @applied_operations.push o\n @execution_listener.push appliedOperationsListener\n if not (user_list?.length is 0)\n @engine.applyOps user_list[0].getHistoryBuffer()._encode()\n\n @HB.setManualGarbageCollect()\n @unexecuted = {}\n\n #\n # This engine applied operations in a specific order.\n # Get the ops in the right order.\n #\n getOpsInExecutionOrder: ()->\n @applied_operations\n\n #\n # This function is called whenever an operation was executed.\n # @param {Operation} o The operation that was executed.\n #\n send: (o)->\n if (o.uid.creator is @HB.getUserId()) and (typeof o.uid.op_number isnt \"string\")\n for user in user_list\n if user.getUserId() isnt @HB.getUserId()\n user.getConnector().receive(o)\n\n #\n # This function is called whenever an operation was received from another peer.\n # @param {Operation} o The operation that was received.\n #\n receive: (o)->\n @unexecuted[o.uid.creator] ?= []\n @unexecuted[o.uid.creator].push o\n\n #\n # Flush one operation from the line of a specific user.\n #\n flushOne: (user)->\n if @unexecuted[user]?.length > 0\n @engine.applyOp @unexecuted[user].shift()\n\n #\n # Flush one operation on a random line.\n #\n flushOneRandom: ()->\n @flushOne (_.random 0, (user_list.length-1))\n\n #\n # Flush all operations on every line.\n #\n flushAll: ()->\n for n,ops of @unexecuted\n @engine.applyOps ops\n @unexecuted = {}\n\n"]}

View File

@ -9,19 +9,26 @@
JsonFramework = (function() {
function JsonFramework(user_id, Connector) {
var first_word, type_manager;
var beg, end, first_word, type_manager, uid_beg, uid_end;
this.HB = new HistoryBuffer(user_id);
type_manager = json_types_uninitialized(this.HB);
this.types = type_manager.types;
this.engine = new Engine(this.HB, type_manager.parser);
this.HB.engine = this.engine;
this.connector = new Connector(this.engine, this.HB, type_manager.execution_listener, this);
first_word = new this.types.JsonType(this.HB.getReservedUniqueIdentifier());
this.HB.addOperation(first_word).execute();
this.root_element = first_word;
uid_beg = this.HB.getReservedUniqueIdentifier();
uid_end = this.HB.getReservedUniqueIdentifier();
beg = this.HB.addOperation(new this.types.Delimiter(uid_beg, void 0, uid_end)).execute();
end = this.HB.addOperation(new this.types.Delimiter(uid_end, beg, void 0)).execute();
this.root_element = new this.types.ReplaceManager(void 0, this.HB.getReservedUniqueIdentifier(), beg, end);
this.HB.addOperation(this.root_element).execute();
this.root_element.replace(first_word, this.HB.getReservedUniqueIdentifier());
}
JsonFramework.prototype.getSharedObject = function() {
return this.root_element;
return this.root_element.val();
};
JsonFramework.prototype.getConnector = function() {
@ -33,7 +40,7 @@
};
JsonFramework.prototype.setMutableDefault = function(mutable) {
return this.root_element.setMutableDefault(mutable);
return this.getSharedObject().setMutableDefault(mutable);
};
JsonFramework.prototype.getUserId = function() {
@ -41,26 +48,26 @@
};
JsonFramework.prototype.toJson = function() {
return this.root_element.toJson();
return this.getSharedObject().toJson();
};
JsonFramework.prototype.val = function(name, content, mutable) {
return this.root_element.val(name, content, mutable);
return this.getSharedObject().val(name, content, mutable);
};
JsonFramework.prototype.on = function() {
var _ref;
return (_ref = this.root_element).on.apply(_ref, arguments);
return (_ref = this.getSharedObject()).on.apply(_ref, arguments);
};
JsonFramework.prototype.deleteListener = function() {
var _ref;
return (_ref = this.root_element).deleteListener.apply(_ref, arguments);
return (_ref = this.getSharedObject()).deleteListener.apply(_ref, arguments);
};
Object.defineProperty(JsonFramework.prototype, 'value', {
get: function() {
return this.root_element.value;
return this.getSharedObject().value;
},
set: function(o) {
var o_name, o_obj, _results;

File diff suppressed because one or more lines are too long

View File

@ -1,34 +1,96 @@
(function() {
var HistoryBuffer;
var HistoryBuffer,
__bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
HistoryBuffer = (function() {
function HistoryBuffer(user_id) {
this.user_id = user_id;
this.emptyGarbage = __bind(this.emptyGarbage, this);
this.operation_counter = {};
this.buffer = {};
this.change_listeners = [];
this.garbage = [];
this.trash = [];
this.performGarbageCollection = true;
this.garbageCollectTimeout = 1000;
this.reserved_identifier_counter = 0;
setTimeout(this.emptyGarbage, this.garbageCollectTimeout);
}
HistoryBuffer.prototype.emptyGarbage = function() {
var o, _i, _len, _ref;
_ref = this.garbage;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
o = _ref[_i];
if (typeof o.cleanup === "function") {
o.cleanup();
}
}
this.garbage = this.trash;
this.trash = [];
if (this.garbageCollectTimeout !== -1) {
this.garbageCollectTimeoutId = setTimeout(this.emptyGarbage, this.garbageCollectTimeout);
}
return void 0;
};
HistoryBuffer.prototype.getUserId = function() {
return this.user_id;
};
HistoryBuffer.prototype.addToGarbageCollector = function() {
var o, _i, _len, _results;
if (this.performGarbageCollection) {
_results = [];
for (_i = 0, _len = arguments.length; _i < _len; _i++) {
o = arguments[_i];
if (o != null) {
_results.push(this.garbage.push(o));
} else {
_results.push(void 0);
}
}
return _results;
}
};
HistoryBuffer.prototype.stopGarbageCollection = function() {
this.performGarbageCollection = false;
this.setManualGarbageCollect();
this.garbage = [];
return this.trash = [];
};
HistoryBuffer.prototype.setManualGarbageCollect = function() {
this.garbageCollectTimeout = -1;
clearTimeout(this.garbageCollectTimeoutId);
return this.garbageCollectTimeoutId = void 0;
};
HistoryBuffer.prototype.setGarbageCollectTimeout = function(garbageCollectTimeout) {
this.garbageCollectTimeout = garbageCollectTimeout;
};
HistoryBuffer.prototype.getReservedUniqueIdentifier = function() {
return {
creator: '_',
op_number: '_'
op_number: "_" + (this.reserved_identifier_counter++)
};
};
HistoryBuffer.prototype.getOperationCounter = function() {
HistoryBuffer.prototype.getOperationCounter = function(user_id) {
var ctn, res, user, _ref;
res = {};
_ref = this.operation_counter;
for (user in _ref) {
ctn = _ref[user];
res[user] = ctn;
if (user_id == null) {
res = {};
_ref = this.operation_counter;
for (user in _ref) {
ctn = _ref[user];
res[user] = ctn;
}
return res;
} else {
return this.operation_counter[user_id];
}
return res;
};
HistoryBuffer.prototype._encode = function(state_vector) {
@ -48,7 +110,7 @@
user = _ref[u_name];
for (o_number in user) {
o = user[o_number];
if ((!isNaN(parseInt(o_number))) && unknown(u_name, o_number)) {
if (o.doSync && unknown(u_name, o_number)) {
o_json = o._encode();
if (o.next_cl != null) {
o_next = o.next_cl;
@ -108,6 +170,11 @@
return o;
};
HistoryBuffer.prototype.removeOperation = function(o) {
var _ref;
return (_ref = this.buffer[o.creator]) != null ? delete _ref[o.op_number] : void 0;
};
HistoryBuffer.prototype.addToCounter = function(o) {
var _results;
if (this.operation_counter[o.creator] == null) {

File diff suppressed because one or more lines are too long

View File

@ -9,12 +9,18 @@
execution_listener = [];
Operation = (function() {
function Operation(uid) {
if (uid == null) {
this.is_deleted = false;
this.doSync = true;
if (uid != null) {
this.doSync = !isNaN(parseInt(uid.op_number));
} else {
uid = HB.getNextOperationIdentifier();
}
this.creator = uid['creator'], this.op_number = uid['op_number'];
}
Operation.prototype.type = "Insert";
Operation.prototype.on = function(events, f) {
var e, _base, _i, _len, _results;
if (this.event_listeners == null) {
@ -71,6 +77,26 @@
}
};
Operation.prototype.isDeleted = function() {
return this.is_deleted;
};
Operation.prototype.applyDelete = function(garbagecollect) {
if (garbagecollect == null) {
garbagecollect = true;
}
if (!this.isDeleted()) {
this.is_deleted = true;
if (garbagecollect) {
return HB.addToGarbageCollector(this);
}
}
};
Operation.prototype.cleanup = function() {
return HB.removeOperation(this);
};
Operation.prototype.setParent = function(parent) {
this.parent = parent;
};
@ -82,10 +108,15 @@
Operation.prototype.getUid = function() {
return {
'creator': this.creator,
'op_number': this.op_number
'op_number': this.op_number,
'sync': this.doSync
};
};
Operation.prototype.dontSync = function() {
return this.doSync = false;
};
Operation.prototype.execute = function() {
var l, _i, _len;
this.is_executed = true;
@ -140,6 +171,8 @@
Delete.__super__.constructor.call(this, uid);
}
Delete.prototype.type = "Delete";
Delete.prototype._encode = function() {
return {
'type': "Delete",
@ -179,19 +212,47 @@
Insert.__super__.constructor.call(this, uid);
}
Insert.prototype.type = "Insert";
Insert.prototype.applyDelete = function(o) {
var garbagecollect;
if (this.deleted_by == null) {
this.deleted_by = [];
}
this.deleted_by.push(o);
if ((this.parent != null) && this.deleted_by.length === 1) {
return this.parent.callEvent("delete", this);
if ((this.parent != null) && !this.isDeleted()) {
this.parent.callEvent("delete", this);
}
if (o != null) {
this.deleted_by.push(o);
}
garbagecollect = false;
if (this.prev_cl.isDeleted()) {
garbagecollect = true;
} else if (this.next_cl.isDeleted()) {
this.next_cl.applyDelete();
}
return Insert.__super__.applyDelete.call(this, garbagecollect);
};
Insert.prototype.isDeleted = function() {
var _ref;
return ((_ref = this.deleted_by) != null ? _ref.length : void 0) > 0;
Insert.prototype.cleanup = function() {
var d, o, _i, _len, _ref;
if (this.prev_cl.isDeleted()) {
_ref = this.deleted_by;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
d = _ref[_i];
d.cleanup();
}
o = this.next_cl;
while (o.type !== "Delimiter") {
if (o.origin === this) {
o.origin = this.prev_cl;
}
o = o.next_cl;
}
this.prev_cl.next_cl = this.next_cl;
this.next_cl.prev_cl = this.prev_cl;
return Insert.__super__.cleanup.apply(this, arguments);
}
};
Insert.prototype.getDistanceToOrigin = function() {
@ -203,53 +264,21 @@
break;
}
d++;
if (this === this.prev_cl) {
throw new Error("this should not happen ;) ");
}
o = o.prev_cl;
}
return d;
};
Insert.prototype.update_sl = function() {
var o;
o = this.prev_cl;
({
update: function(dest_cl, dest_sl) {
var _results;
_results = [];
while (true) {
if (o.isDeleted()) {
_results.push(o = o[dest_cl]);
} else {
this[dest_sl] = o;
break;
}
}
return _results;
}
});
update("prev_cl", "prev_sl");
return update("next_cl", "prev_sl");
};
Insert.prototype.execute = function() {
var distance_to_origin, i, o, parent, _ref, _ref1, _ref2;
if (this.is_executed != null) {
return this;
}
var distance_to_origin, i, o, parent, _ref;
if (!this.validateSavedOperations()) {
return false;
} else {
if (((_ref = this.prev_cl) != null ? _ref.validateSavedOperations() : void 0) && ((_ref1 = this.next_cl) != null ? _ref1.validateSavedOperations() : void 0) && this.prev_cl.next_cl !== this) {
distance_to_origin = 0;
if (this.prev_cl != null) {
distance_to_origin = this.getDistanceToOrigin();
o = this.prev_cl.next_cl;
i = 0;
i = distance_to_origin;
while (true) {
if (o == null) {
console.log(JSON.stringify(this.prev_cl.getUid()));
console.log(JSON.stringify(this.next_cl.getUid()));
}
if (o !== this.next_cl) {
if (o.getDistanceToOrigin() === i) {
if (o.creator < this.creator) {
@ -278,7 +307,7 @@
this.prev_cl.next_cl = this;
this.next_cl.prev_cl = this;
}
parent = (_ref2 = this.prev_cl) != null ? _ref2.getParent() : void 0;
parent = (_ref = this.prev_cl) != null ? _ref.getParent() : void 0;
if (parent != null) {
this.setParent(parent);
this.parent.callEvent("insert", this);
@ -295,7 +324,7 @@
if (prev instanceof Delimiter) {
break;
}
if ((prev.isDeleted != null) && !prev.isDeleted()) {
if (!prev.isDeleted()) {
position++;
}
prev = prev.prev_cl;
@ -314,6 +343,8 @@
ImmutableObject.__super__.constructor.call(this, uid, prev, next, origin);
}
ImmutableObject.prototype.type = "ImmutableObject";
ImmutableObject.prototype.val = function() {
return this.content;
};
@ -331,15 +362,15 @@
if (this.next_cl != null) {
json['next'] = this.next_cl.getUid();
}
if ((this.origin != null) && this.origin !== this.prev_cl) {
json["origin"] = this.origin.getUid();
if (this.origin != null) {
json["origin"] = this.origin().getUid();
}
return json;
};
return ImmutableObject;
})(Insert);
})(Operation);
parser['ImmutableObject'] = function(json) {
var content, next, origin, prev, uid;
uid = json['uid'], content = json['content'], prev = json['prev'], next = json['next'], origin = json['origin'];
@ -355,8 +386,21 @@
Delimiter.__super__.constructor.call(this, uid);
}
Delimiter.prototype.isDeleted = function() {
return false;
Delimiter.prototype.type = "Delimiter";
Delimiter.prototype.applyDelete = function() {
var o;
Delimiter.__super__.applyDelete.call(this);
o = this.next_cl;
while (o != null) {
o.applyDelete();
o = o.next_cl;
}
return void 0;
};
Delimiter.prototype.cleanup = function() {
return Delimiter.__super__.cleanup.call(this);
};
Delimiter.prototype.execute = function() {

File diff suppressed because one or more lines are too long

View File

@ -78,6 +78,14 @@
JsonType.prototype.type = "JsonType";
JsonType.prototype.applyDelete = function() {
return JsonType.__super__.applyDelete.call(this);
};
JsonType.prototype.cleanup = function() {
return JsonType.__super__.cleanup.call(this);
};
JsonType.prototype.toJson = function() {
var json, name, o, val;
val = this.val();
@ -100,16 +108,18 @@
return json;
};
JsonType.prototype.setReplaceManager = function(rm) {
this.parent = rm.parent;
JsonType.prototype.setReplaceManager = function(replace_manager) {
this.replace_manager = replace_manager;
return this.on(['change', 'addProperty'], function() {
var _ref;
return (_ref = rm.parent).forwardEvent.apply(_ref, [this].concat(__slice.call(arguments)));
if (replace_manager.parent != null) {
return (_ref = replace_manager.parent).forwardEvent.apply(_ref, [this].concat(__slice.call(arguments)));
}
});
};
JsonType.prototype.getParent = function() {
return this.parent;
return this.replace_manager.parent;
};
JsonType.prototype.mutable_default = true;
@ -126,12 +136,11 @@
};
JsonType.prototype.val = function(name, content, mutable) {
var json, o, o_name, obj, word;
var json, obj, word;
if (typeof name === 'object') {
for (o_name in name) {
o = name[o_name];
this.val(o_name, o, content);
}
json = new JsonType(void 0, name, content);
HB.addOperation(json).execute();
this.replace_manager.replace(json);
return this;
} else if ((name != null) && ((content != null) || content === null)) {
if (mutable != null) {

File diff suppressed because one or more lines are too long

View File

@ -18,6 +18,22 @@
MapManager.__super__.constructor.call(this, uid);
}
MapManager.prototype.type = "MapManager";
MapManager.prototype.applyDelete = function() {
var name, p, _ref;
_ref = this.map;
for (name in _ref) {
p = _ref[name];
p.applyDelete();
}
return MapManager.__super__.applyDelete.call(this);
};
MapManager.prototype.cleanup = function() {
return MapManager.__super__.cleanup.call(this);
};
MapManager.prototype.val = function(name, content) {
var o, obj, result, _ref, _ref1;
if (content != null) {
@ -60,8 +76,18 @@
AddName.__super__.constructor.call(this, uid);
}
AddName.prototype.type = "AddName";
AddName.prototype.applyDelete = function() {
return AddName.__super__.applyDelete.call(this);
};
AddName.prototype.cleanup = function() {
return AddName.__super__.cleanup.call(this);
};
AddName.prototype.execute = function() {
var beg, end, uid_beg, uid_end, uid_r;
var beg, end, uid_beg, uid_end, uid_r, _base;
if (!this.validateSavedOperations()) {
return false;
} else {
@ -76,6 +102,7 @@
end = HB.addOperation(new types.Delimiter(uid_end, beg, void 0)).execute();
this.map_manager.map[this.name] = HB.addOperation(new ReplaceManager(void 0, uid_r, beg, end));
this.map_manager.map[this.name].setParent(this.map_manager, this.name);
((_base = this.map_manager.map[this.name]).add_name_ops != null ? _base.add_name_ops : _base.add_name_ops = []).push(this);
this.map_manager.map[this.name].execute();
}
return AddName.__super__.execute.apply(this, arguments);
@ -116,6 +143,8 @@
ListManager.__super__.constructor.call(this, uid, prev, next, origin);
}
ListManager.prototype.type = "ListManager";
ListManager.prototype.execute = function() {
if (this.validateSavedOperations()) {
this.beginning.setParent(this);
@ -170,7 +199,7 @@
return ListManager;
})(types.Insert);
})(types.Operation);
ReplaceManager = (function(_super) {
__extends(ReplaceManager, _super);
@ -181,11 +210,35 @@
}
}
ReplaceManager.prototype.type = "ReplaceManager";
ReplaceManager.prototype.applyDelete = function() {
var o, _i, _len, _ref;
o = this.beginning;
while (o != null) {
o.applyDelete();
o = o.next_cl;
}
if (this.add_name_ops != null) {
_ref = this.add_name_ops;
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
o = _ref[_i];
o.applyDelete();
}
}
return ReplaceManager.__super__.applyDelete.call(this);
};
ReplaceManager.prototype.cleanup = function() {
return ReplaceManager.__super__.cleanup.call(this);
};
ReplaceManager.prototype.replace = function(content, replaceable_uid) {
var o, op;
o = this.getLastOperation();
op = new Replaceable(content, this, replaceable_uid, o, o.next_cl);
return HB.addOperation(op).execute();
HB.addOperation(op).execute();
return void 0;
};
ReplaceManager.prototype.setParent = function(parent, property_name) {
@ -232,8 +285,8 @@
json['prev'] = this.prev_cl.getUid();
json['next'] = this.next_cl.getUid();
}
if ((this.origin != null) && this.origin !== this.prev_cl) {
json["origin"] = this.origin.getUid();
if (this.origin != null) {
json["origin"] = this.origin().getUid();
}
return json;
};
@ -252,12 +305,14 @@
function Replaceable(content, parent, uid, prev, next, origin) {
this.saveOperation('content', content);
this.saveOperation('parent', parent);
if (!((prev != null) && (next != null) && (content != null))) {
throw new Error("You must define content, prev, and next for Replaceable-types!");
if (!((prev != null) && (next != null))) {
throw new Error("You must define prev, and next for Replaceable-types!");
}
Replaceable.__super__.constructor.call(this, uid, prev, next, origin);
}
Replaceable.prototype.type = "Replaceable";
Replaceable.prototype.val = function() {
return this.content;
};
@ -266,23 +321,47 @@
return this.parent.replace(content);
};
Replaceable.prototype.applyDelete = function() {
if (this.content != null) {
this.content.applyDelete();
this.content.dontSync();
}
this.beforeDelete = this.content;
this.content = null;
return Replaceable.__super__.applyDelete.apply(this, arguments);
};
Replaceable.prototype.cleanup = function() {
return Replaceable.__super__.cleanup.apply(this, arguments);
};
Replaceable.prototype.execute = function() {
var _base;
var ins_result, _ref;
if (!this.validateSavedOperations()) {
return false;
} else {
if (typeof (_base = this.content).setReplaceManager === "function") {
_base.setReplaceManager(this.parent);
if ((_ref = this.content) != null) {
if (typeof _ref.setReplaceManager === "function") {
_ref.setReplaceManager(this.parent);
}
}
return Replaceable.__super__.execute.apply(this, arguments);
ins_result = Replaceable.__super__.execute.call(this);
if (ins_result) {
if (this.next_cl.type === "Delimiter" && this.prev_cl.type !== "Delimiter") {
this.prev_cl.applyDelete();
} else if (this.next_cl.type !== "Delimiter") {
this.applyDelete();
}
}
return ins_result;
}
};
Replaceable.prototype._encode = function() {
var json;
var json, _ref;
json = {
'type': "Replaceable",
'content': this.content.getUid(),
'content': (_ref = this.content) != null ? _ref.getUid() : void 0,
'ReplaceManager': this.parent.getUid(),
'prev': this.prev_cl.getUid(),
'next': this.next_cl.getUid(),

File diff suppressed because one or more lines are too long

View File

@ -32,6 +32,8 @@
TextInsert.__super__.constructor.call(this, uid, prev, next, origin);
}
TextInsert.prototype.type = "TextInsert";
TextInsert.prototype.getLength = function() {
if (this.isDeleted()) {
return 0;
@ -40,8 +42,13 @@
}
};
TextInsert.prototype.applyDelete = function() {
this.content = null;
return TextInsert.__super__.applyDelete.apply(this, arguments);
};
TextInsert.prototype.val = function(current_position) {
if (this.isDeleted()) {
if (this.isDeleted() || (this.content == null)) {
return "";
} else {
return this.content;
@ -57,7 +64,7 @@
'prev': this.prev_cl.getUid(),
'next': this.next_cl.getUid()
};
if ((this.origin != null) && this.origin !== this.prev_cl) {
if (this.origin !== this.prev_cl) {
json["origin"] = this.origin.getUid();
}
return json;
@ -80,13 +87,33 @@
WordType.prototype.type = "WordType";
WordType.prototype.applyDelete = function() {
var o;
o = this.beginning;
while (o != null) {
o.applyDelete();
o = o.next_cl;
}
return WordType.__super__.applyDelete.call(this);
};
WordType.prototype.cleanup = function() {
return WordType.__super__.cleanup.call(this);
};
WordType.prototype.insertText = function(position, content) {
var c, o, op, _i, _len;
o = this.getOperationByPosition(position);
var c, ith, left, op, right, _i, _len;
ith = this.getOperationByPosition(position);
left = ith.prev_cl;
while (left.isDeleted()) {
left = left.prev_cl;
}
right = left.next_cl;
for (_i = 0, _len = content.length; _i < _len; _i++) {
c = content[_i];
op = new TextInsert(c, void 0, o.prev_cl, o);
op = new TextInsert(c, void 0, left, right);
HB.addOperation(op).execute();
left = op;
}
return this;
};
@ -278,8 +305,8 @@
if (this.next_cl != null) {
json['next'] = this.next_cl.getUid();
}
if ((this.origin != null) && this.origin !== this.prev_cl) {
json["origin"] = this.origin.getUid();
if (this.origin != null) {
json["origin"] = this.origin().getUid();
}
return json;
};

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

@ -248,7 +248,7 @@
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -302,7 +302,7 @@ data from the received intent.</p>
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -335,7 +335,7 @@ JsonFramework was initialized (Depending on the HistoryBuffer implementation).</
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -85,6 +85,28 @@ if (x.type === &quot;JsonType&quot;) {
</dl>
<h2>Instance Method Summary</h2>
<ul class='summary'>
<li>
<span class='signature'>
<a href='#applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#toJson-dynamic'>
@ -102,7 +124,7 @@ if (x.type === &quot;JsonType&quot;) {
<a href='#setReplaceManager-dynamic'>
#
(void)
<b>setReplaceManager</b><span>(rm)</span>
<b>setReplaceManager</b><span>(replace_manager)</span>
</a>
</span>
<span class='desc'>
@ -205,6 +227,24 @@ if (x.type === &quot;JsonType&quot;) {
</div>
<h2>Instance Method Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='toJson-dynamic'>
#
@ -229,7 +269,7 @@ if (x.type === &quot;JsonType&quot;) {
<p class='signature' id='setReplaceManager-dynamic'>
#
(void)
<b>setReplaceManager</b><span>(rm)</span>
<b>setReplaceManager</b><span>(replace_manager)</span>
<br>
</p>
<div class='docstring'>
@ -426,7 +466,7 @@ if (x.type === &quot;JsonType&quot;) {
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -139,7 +139,7 @@ console.log(w.newProperty == &quot;Awesome&quot;) # true!</code></pre>
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -222,7 +222,7 @@ on how to do that with urls.</p>
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -356,7 +356,7 @@ JsonFramework was initialized (Depending on the HistoryBuffer implementation).</
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -77,6 +77,28 @@ if (x.type === &quot;WordType&quot;) {
</dl>
<h2>Instance Method Summary</h2>
<ul class='summary'>
<li>
<span class='signature'>
<a href='#applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
</a>
</span>
<span class='desc'>
</span>
</li>
<li>
<span class='signature'>
<a href='#insertText-dynamic'>
@ -206,6 +228,24 @@ if (x.type === &quot;WordType&quot;) {
</div>
<h2>Instance Method Details</h2>
<div class='methods'>
<div class='method_details'>
<p class='signature' id='applyDelete-dynamic'>
#
(void)
<b>applyDelete</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='cleanup-dynamic'>
#
(void)
<b>cleanup</b><span>()</span>
<br>
</p>
</div>
<div class='method_details'>
<p class='signature' id='insertText-dynamic'>
#
@ -375,7 +415,7 @@ yatta.bind(textbox);</code></pre>
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -38,7 +38,7 @@
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -70,7 +70,7 @@ But I would become really motivated if you gave me some feedback :) (<a href="ht
<li>Garbage Collection</li>
<li>XML support</li>
</ul>
<h2 id="support">Support</h2><p>Please report any issues to the <a href="https://github.com/DadaMonad/Yatta/issues">Github issue page</a>!</p><h2 id="license">License</h2><p>Yatta! is licensed under the <a href="./LICENSE.txt">MIT License</a>.</p><a href="&#x6d;&#x61;&#x69;&#108;&#x74;&#111;&#58;&#107;&#x65;&#118;&#105;&#110;&#x2e;&#106;&#x61;&#104;&#x6e;&#115;&#x40;&#114;&#x77;&#116;&#104;&#x2d;&#x61;&#x61;&#x63;&#x68;&#x65;&#x6e;&#46;&#x64;&#x65;">&#107;&#x65;&#118;&#105;&#110;&#x2e;&#106;&#x61;&#104;&#x6e;&#115;&#x40;&#114;&#x77;&#116;&#104;&#x2d;&#x61;&#x61;&#x63;&#x68;&#x65;&#x6e;&#46;&#x64;&#x65;</a>
<h2 id="support">Support</h2><p>Please report any issues to the <a href="https://github.com/DadaMonad/Yatta/issues">Github issue page</a>!</p><h2 id="license">License</h2><p>Yatta! is licensed under the <a href="./LICENSE.txt">MIT License</a>.</p><a href="&#109;&#97;&#105;&#x6c;&#116;&#x6f;&#58;&#x6b;&#x65;&#118;&#105;&#110;&#x2e;&#x6a;&#97;&#104;&#x6e;&#115;&#64;&#x72;&#x77;&#116;&#104;&#45;&#x61;&#x61;&#99;&#x68;&#101;&#110;&#x2e;&#x64;&#x65;">&#x6b;&#x65;&#118;&#105;&#110;&#x2e;&#x6a;&#97;&#104;&#x6e;&#115;&#64;&#x72;&#x77;&#116;&#104;&#45;&#x61;&#x61;&#99;&#x68;&#101;&#110;&#x2e;&#x64;&#x65;</a>
@ -79,7 +79,7 @@ But I would become really motivated if you gave me some feedback :) (<a href="ht
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -100,7 +100,7 @@
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -158,7 +158,7 @@
</div>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -48,7 +48,7 @@
</dl>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -60,7 +60,7 @@
</dl>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -60,7 +60,7 @@
</dl>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -48,7 +48,7 @@
</dl>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -39,7 +39,7 @@
</table>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

View File

@ -37,7 +37,7 @@
</table>
</div>
<div id='footer'>
August 26, 14 03:04:10 by
September 17, 14 16:00:52 by
<a href='https://github.com/coffeedoc/codo' title='CoffeeScript API documentation generator'>
Codo
</a>

File diff suppressed because one or more lines are too long

View File

@ -29,14 +29,6 @@
<input type='text'>
</div>
<ul>
<li>
<a href='class/WordType.html#_encode-dynamic' target='main' title='_encode'>
#_encode
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/JsonType.html#_encode-dynamic' target='main' title='_encode'>
#_encode
@ -45,6 +37,14 @@
(JsonType)
</small>
</li>
<li>
<a href='class/WordType.html#_encode-dynamic' target='main' title='_encode'>
#_encode
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/PeerJsConnector.html#addConnection-dynamic' target='main' title='addConnection'>
#addConnection
@ -54,13 +54,21 @@
</small>
</li>
<li>
<a href='class/WordType.html#bind-dynamic' target='main' title='bind'>
#bind
<a href='class/WordType.html#applyDelete-dynamic' target='main' title='applyDelete'>
#applyDelete
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/JsonType.html#applyDelete-dynamic' target='main' title='applyDelete'>
#applyDelete
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/TextFramework.html#bind-dynamic' target='main' title='bind'>
#bind
@ -69,6 +77,30 @@
(TextFramework)
</small>
</li>
<li>
<a href='class/WordType.html#bind-dynamic' target='main' title='bind'>
#bind
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/WordType.html#cleanup-dynamic' target='main' title='cleanup'>
#cleanup
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/JsonType.html#cleanup-dynamic' target='main' title='cleanup'>
#cleanup
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/PeerJsConnector.html#connectToPeer-dynamic' target='main' title='connectToPeer'>
#connectToPeer
@ -85,30 +117,6 @@
(IwcConnector)
</small>
</li>
<li>
<a href='class/WordType.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/JsonTypeWrapper.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(JsonTypeWrapper)
</small>
</li>
<li>
<a href='class/TextFramework.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(TextFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#constructor-dynamic' target='main' title='constructor'>
#constructor
@ -117,6 +125,14 @@
(JsonFramework)
</small>
</li>
<li>
<a href='class/PeerJsConnector.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(PeerJsConnector)
</small>
</li>
<li>
<a href='class/JsonType.html#constructor-dynamic' target='main' title='constructor'>
#constructor
@ -126,11 +142,27 @@
</small>
</li>
<li>
<a href='class/PeerJsConnector.html#constructor-dynamic' target='main' title='constructor'>
<a href='class/JsonTypeWrapper.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(PeerJsConnector)
(JsonTypeWrapper)
</small>
</li>
<li>
<a href='class/WordType.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/TextFramework.html#constructor-dynamic' target='main' title='constructor'>
#constructor
</a>
<small>
(TextFramework)
</small>
</li>
<li>
@ -197,14 +229,6 @@
(TextFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#getHistoryBuffer-dynamic' target='main' title='getHistoryBuffer'>
#getHistoryBuffer
</a>
<small>
(JsonFramework)
</small>
</li>
<li>
<a href='class/TextFramework.html#getHistoryBuffer-dynamic' target='main' title='getHistoryBuffer'>
#getHistoryBuffer
@ -213,6 +237,14 @@
(TextFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#getHistoryBuffer-dynamic' target='main' title='getHistoryBuffer'>
#getHistoryBuffer
</a>
<small>
(JsonFramework)
</small>
</li>
<li>
<a href='class/JsonType.html#getParent-dynamic' target='main' title='getParent'>
#getParent
@ -221,14 +253,6 @@
(JsonType)
</small>
</li>
<li>
<a href='class/TextFramework.html#getSharedObject-dynamic' target='main' title='getSharedObject'>
#getSharedObject
</a>
<small>
(TextFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#getSharedObject-dynamic' target='main' title='getSharedObject'>
#getSharedObject
@ -238,11 +262,11 @@
</small>
</li>
<li>
<a href='class/JsonFramework.html#getUserId-dynamic' target='main' title='getUserId'>
#getUserId
<a href='class/TextFramework.html#getSharedObject-dynamic' target='main' title='getSharedObject'>
#getSharedObject
</a>
<small>
(JsonFramework)
(TextFramework)
</small>
</li>
<li>
@ -254,11 +278,11 @@
</small>
</li>
<li>
<a href='class/TextFramework.html#insertText-dynamic' target='main' title='insertText'>
#insertText
<a href='class/JsonFramework.html#getUserId-dynamic' target='main' title='getUserId'>
#getUserId
</a>
<small>
(TextFramework)
(JsonFramework)
</small>
</li>
<li>
@ -269,6 +293,14 @@
(WordType)
</small>
</li>
<li>
<a href='class/TextFramework.html#insertText-dynamic' target='main' title='insertText'>
#insertText
</a>
<small>
(TextFramework)
</small>
</li>
<li>
<a href='class/JsonFramework.html#on-dynamic' target='main' title='on'>
#on
@ -333,14 +365,6 @@
(IwcConnector)
</small>
</li>
<li>
<a href='class/JsonType.html#setMutableDefault-dynamic' target='main' title='setMutableDefault'>
#setMutableDefault
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/JsonFramework.html#setMutableDefault-dynamic' target='main' title='setMutableDefault'>
#setMutableDefault
@ -350,8 +374,8 @@
</small>
</li>
<li>
<a href='class/JsonType.html#setReplaceManager-dynamic' target='main' title='setReplaceManager'>
#setReplaceManager
<a href='class/JsonType.html#setMutableDefault-dynamic' target='main' title='setMutableDefault'>
#setMutableDefault
</a>
<small>
(JsonType)
@ -365,6 +389,14 @@
(WordType)
</small>
</li>
<li>
<a href='class/JsonType.html#setReplaceManager-dynamic' target='main' title='setReplaceManager'>
#setReplaceManager
</a>
<small>
(JsonType)
</small>
</li>
<li>
<a href='class/JsonType.html#toJson-dynamic' target='main' title='toJson'>
#toJson
@ -397,14 +429,6 @@
(JsonType)
</small>
</li>
<li>
<a href='class/WordType.html#val-dynamic' target='main' title='val'>
#val
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/JsonFramework.html#val-dynamic' target='main' title='val'>
#val
@ -413,6 +437,14 @@
(JsonFramework)
</small>
</li>
<li>
<a href='class/WordType.html#val-dynamic' target='main' title='val'>
#val
</a>
<small>
(WordType)
</small>
</li>
<li>
<a href='class/TextFramework.html#val-dynamic' target='main' title='val'>
#val

View File

@ -227,7 +227,7 @@ Nah.. this is only for the cool kids.
```js
console.log(yatta.value.list[2] === 3) // true
console.log(yatta.value.list[2] === 2) // true
yatta.value.list = [3,4,5]
console.log(yatta.val('list')[2] === 5) // true
yatta.value.object = {c : 4}

View File

@ -40,7 +40,7 @@ Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connecto
it will be instantly shared with all the other collaborators.
*/
yatta = new Y.JsonFramework(user_id, Connector);
/**
Next, you may want to connect to another peer. Therefore you have to receive his
user_id. If the other peer is connected to other peers, the PeerJsConnector
@ -181,7 +181,7 @@ Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connecto
### Experimental method
Nah.. this is only for the cool kids.
*/
console.log(yatta.value.list[2] === 3) // true
console.log(yatta.value.list[2] === 2) // true
yatta.value.list = [3,4,5]
console.log(yatta.val('list')[2] === 5) // true
yatta.value.object = {c : 4}

View File

@ -0,0 +1,48 @@
## PeerJs + JSON Example
Here, I will give a short overview on how to enable collaborative json with the
[PeerJs](http://peerjs.com/) Connector and the Json Framework. Open
[index.html](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/index.html) in your Browser and
use the console to explore Yatta!
[PeerJs](http://peerjs.com) is a Framework that enables you to connect to other peers. You just need the
user-id of the peer (browser/client). And then you can connect to it.
First you have to include the following libraries in your html file:
```
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/JsonFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
```
### Create Connector
The PeerJs Framework requires an API key, or you need to set up your own PeerJs server.
Get an API key from the [Website](http://peerjs.com/peerserver).
The first parameter of `createPeerJsConnector` is forwarded as the options object in PeerJs.
Therefore, you may also specify the server/port here, if you have set up your own server.
```js
var yatta, yattaHandler;
Y.createPeerJsConnector({key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
```
You can also specify your own user_id with peerjs.
But then you have to make sure that no other client associated to your API-key has the same user_id.
```
Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
```
### Yatta
yatta is the shared json object. If you change something on this object,
it will be instantly shared with all the other collaborators.
```js
yatta = new Y.JsonFramework(user_id, Connector);
yatta.val('w','w');
});
```

View File

@ -0,0 +1,21 @@
<!DOCTYPE html>
<html>
<head>
<meta charset=utf-8 />
<title>PeerJs Json Example</title>
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/JsonFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
</head>
<body>
<h1> PeerJs + Json Tutorial</h1>
<p> Collaborative Json editing with <a href="https://github.com/DadaMonad/Yatta/">Yatta</a>
and <a href="http://peerjs.com/">PeerJs</a> (WebRTC). </p>
<p> <a href="https://github.com/DadaMonad/Yatta/">Yatta</a> is a Framework for Real-Time collaboration on arbitrary data structures.
You can find the code for this example <a href="https://github.com/DadaMonad/Yatta/tree/master/examples/PeerJs-Json">here</a>.
</p>
</body>
</html>

45
examples/temp_ex/index.js Normal file
View File

@ -0,0 +1,45 @@
/**
## PeerJs + JSON Example
Here, I will give a short overview on how to enable collaborative json with the
[PeerJs](http://peerjs.com/) Connector and the Json Framework. Open
[index.html](http://dadamonad.github.io/Yatta/examples/PeerJs-Json/index.html) in your Browser and
use the console to explore Yatta!
[PeerJs](http://peerjs.com) is a Framework that enables you to connect to other peers. You just need the
user-id of the peer (browser/client). And then you can connect to it.
First you have to include the following libraries in your html file:
```
<script src="http://cdn.peerjs.com/0.3/peer.js"></script>
<script src="../../build/browser/Frameworks/JsonFramework.js"></script>
<script src="../../build/browser/Connectors/PeerJsConnector.js"></script>
<script src="./index.js"></script>
```
### Create Connector
The PeerJs Framework requires an API key, or you need to set up your own PeerJs server.
Get an API key from the [Website](http://peerjs.com/peerserver).
The first parameter of `createPeerJsConnector` is forwarded as the options object in PeerJs.
Therefore, you may also specify the server/port here, if you have set up your own server.
*/
var yatta, yattaHandler;
Y.createPeerJsConnector({key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
/**
You can also specify your own user_id with peerjs.
But then you have to make sure that no other client associated to your API-key has the same user_id.
```
Y.createPeerJsConnector("unique_id", {key: 'h7nlefbgavh1tt9'}, function(Connector, user_id){
```
*/
/**
### Yatta
yatta is the shared json object. If you change something on this object,
it will be instantly shared with all the other collaborators.
*/
yatta = new Y.JsonFramework(user_id, Connector);
yatta.val('w','w');
});

View File

@ -27,6 +27,7 @@ module.exports = (user_list)->
if not (user_list?.length is 0)
@engine.applyOps user_list[0].getHistoryBuffer()._encode()
@HB.setManualGarbageCollect()
@unexecuted = {}
#

View File

@ -21,16 +21,25 @@ class JsonFramework
type_manager = json_types_uninitialized @HB
@types = type_manager.types
@engine = new Engine @HB, type_manager.parser
@HB.engine = @engine # TODO: !! only for debugging
@connector = new Connector @engine, @HB, type_manager.execution_listener, @
first_word = new @types.JsonType @HB.getReservedUniqueIdentifier()
first_word = new @types.JsonType(@HB.getReservedUniqueIdentifier())
@HB.addOperation(first_word).execute()
@root_element = first_word
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, @HB.getReservedUniqueIdentifier()
#
# @return JsonType
#
getSharedObject: ()->
@root_element
@root_element.val()
#
# Get the initialized connector.
@ -48,7 +57,7 @@ class JsonFramework
# @see JsonType.setMutableDefault
#
setMutableDefault: (mutable)->
@root_element.setMutableDefault(mutable)
@getSharedObject().setMutableDefault(mutable)
#
# Get the UserId from the HistoryBuffer object.
@ -62,31 +71,31 @@ class JsonFramework
# @see JsonType.toJson
#
toJson : ()->
@root_element.toJson()
@getSharedObject().toJson()
#
# @see JsonType.val
#
val : (name, content, mutable)->
@root_element.val(name, content, mutable)
@getSharedObject().val(name, content, mutable)
#
# @see Operation.on
#
on: ()->
@root_element.on arguments...
@getSharedObject().on arguments...
#
# @see Operation.deleteListener
#
deleteListener: ()->
@root_element.deleteListener arguments...
@getSharedObject().deleteListener arguments...
#
# @see JsonType.value
#
Object.defineProperty JsonFramework.prototype, 'value',
get : -> @root_element.value
get : -> @getSharedObject().value
set : (o)->
if o.constructor is {}.constructor
for o_name,o_obj of o

View File

@ -7,6 +7,8 @@
#
class HistoryBuffer
#
# Creates an empty HB.
# @param {Object} user_id Creator of the HB.
@ -15,6 +17,23 @@ class HistoryBuffer
@operation_counter = {}
@buffer = {}
@change_listeners = []
@garbage = [] # Will be cleaned on next call of garbageCollector
@trash = [] # Is deleted. Wait until it is not used anymore.
@performGarbageCollection = true
@garbageCollectTimeout = 1000
@reserved_identifier_counter = 0
setTimeout @emptyGarbage, @garbageCollectTimeout
emptyGarbage: ()=>
for o in @garbage
#if @getOperationCounter(o.creator) > o.op_number
o.cleanup?()
@garbage = @trash
@trash = []
if @garbageCollectTimeout isnt -1
@garbageCollectTimeoutId = setTimeout @emptyGarbage, @garbageCollectTimeout
undefined
#
# Get the user id with wich the History Buffer was initialized.
@ -22,8 +41,26 @@ class HistoryBuffer
getUserId: ()->
@user_id
addToGarbageCollector: ()->
if @performGarbageCollection
for o in arguments
if o?
@garbage.push o
stopGarbageCollection: ()->
@performGarbageCollection = false
@setManualGarbageCollect()
@garbage = []
@trash = []
setManualGarbageCollect: ()->
@garbageCollectTimeout = -1
clearTimeout @garbageCollectTimeoutId
@garbageCollectTimeoutId = undefined
setGarbageCollectTimeout: (@garbageCollectTimeout)->
#
# There is only one reserved unique identifier (uid), so use it wisely.
# I propose to use it in your Framework, to create something like a root element.
# An operation with this identifier is not propagated to other clients.
# This is why everybode must create the same operation with this uid.
@ -31,17 +68,21 @@ class HistoryBuffer
getReservedUniqueIdentifier: ()->
{
creator : '_'
op_number : '_'
op_number : "_#{@reserved_identifier_counter++}"
}
#
# Get the operation counter that describes the current state of the document.
#
getOperationCounter: ()->
res = {}
for user,ctn of @operation_counter
res[user] = ctn
res
getOperationCounter: (user_id)->
if not user_id?
res = {}
for user,ctn of @operation_counter
res[user] = ctn
res
else
@operation_counter[user_id]
#
# Encode this operation in such a way that it can be parsed by remote peers.
@ -55,16 +96,16 @@ class HistoryBuffer
for u_name,user of @buffer
for o_number,o of user
if (not isNaN(parseInt(o_number))) and unknown(u_name, o_number)
if o.doSync and unknown(u_name, o_number)
# its necessary to send it, and not known in state_vector
o_json = o._encode()
if o.next_cl?
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)
o_next = o_next.next_cl
o_json.next = o_next.getUid()
else if o.prev_cl?
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)
@ -111,6 +152,9 @@ class HistoryBuffer
@buffer[o.creator][o.op_number] = o
o
removeOperation: (o)->
delete @buffer[o.creator]?[o.op_number]
#
# Increment the operation_counter that defines the current state of the Engine.
#

View File

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

View File

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

View File

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

View File

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

View File

@ -14,7 +14,9 @@ Test = require "./TestSuite"
class JsonTest extends Test
makeNewUser: (user, conn)->
new Y.JsonFramework user, conn
super new Y.JsonFramework user, conn
type: "JsonTest"
getRandomRoot: (user_num, root)->
root ?= @users[user_num].getSharedObject()
@ -69,6 +71,7 @@ describe "JsonFramework", ->
done()
it "can handle many engines, many operations, concurrently (random)", ->
console.log "" # TODO
@yTest.run()
it "has a change listener", ()->
@ -95,7 +98,7 @@ describe "JsonFramework", ->
it "has a JsonTypeWrapper", ->
y = this.yTest.getSomeUser().root_element
y = this.yTest.getSomeUser().getSharedObject()
y.val('x',"dtrn", 'immutable')
y.val('set',{x:"x"}, 'immutable')
w = y.value
@ -109,6 +112,7 @@ describe "JsonFramework", ->
y.value.x = {q:4}
expect(y.value.x.q).to.equal(4)
### TODO: Handle this test
it "handles double-late-join", ->
test = new JsonTest("double")
test.run()
@ -120,6 +124,11 @@ describe "JsonFramework", ->
u1.engine.applyOps ops2
u2.engine.applyOps ops1
expect(u2.value.name.val()).to.equal(u2.value.name.val())
###
it "has a working test suite", ->
@yTest.compareAll()
it "can handle creaton of complex json", ->
@yTest.getSomeUser().val('x', {'a':'b'})
@ -128,10 +137,10 @@ describe "JsonFramework", ->
@yTest.getSomeUser().val('c', {'a':'c'})
@yTest.getSomeUser().val('c', {'a':'b'})
@yTest.compareAll()
@yTest.getSomeUser().value.a.a.q.insertText(0,'AAA')
q = @yTest.getSomeUser().value.a.a.q
q.insertText(0,'A')
@yTest.compareAll()
expect(@yTest.getSomeUser().value.a.a.q.val()).to.equal("AAAdtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
expect(@yTest.getSomeUser().value.a.a.q.val()).to.equal("Adtrndtrtdrntdrnrtdnrtdnrtdnrtdnrdnrdt")
it "handles immutables and primitive data types", ->
@yTest.getSomeUser().val('string', "text", "immutable")
@ -144,18 +153,5 @@ describe "JsonFramework", ->
expect(@yTest.getSomeUser().val('object').val('q')).to.equal "rr"
expect(@yTest.getSomeUser().val('null') is null).to.be.ok
it "converges t1", ->
op0 = {"type":"Delimiter","uid":{"creator":0,"op_number":0},"next":{"creator":0,"op_number":1}}
op1 = {"type":"Delimiter","uid":{"creator":0,"op_number":1},"prev":{"creator":0,"op_number":0}}
op2 = {"type":"WordType","uid":{"creator":0,"op_number":2},"beginning":{"creator":0,"op_number":0},"end":{"creator":0,"op_number":1}}
op3 = {"type":"AddName","uid":{"creator":0,"op_number":3},"map_manager":{"creator":"_","op_number":"_"},"name":"name"}
op4 = {"type":"Replaceable","content":{"creator":0,"op_number":2},"ReplaceManager":{"creator":"_","op_number":"___RM_name"},"prev":{"creator":"_","op_number":"___RM_name_beginning"},"next":{"creator":"_","op_number":"___RM_name_end"},"uid":{"creator":0,"op_number":4}}
op5 = {"type":"TextInsert","content":"u","uid":{"creator":1,"op_number":2},"prev":{"creator":1,"op_number":0},"next":{"creator":1,"op_number":1}}
op6 = {"type":"TextInsert","content":"w","uid":{"creator":2,"op_number":0},"prev":{"creator":0,"op_number":0},"next":{"creator":0,"op_number":1}}
op7 = {"type":"TextInsert","content":"d","uid":{"creator":1,"op_number":0},"prev":{"creator":0,"op_number":0},"next":{"creator":2,"op_number":0}}
op8 = {"type":"TextInsert","content":"a","uid":{"creator":1,"op_number":1},"prev":{"creator":1,"op_number":0},"next":{"creator":2,"op_number":0}}
ops = [op0, op1, op2, op3, op4, op5, op6, op7, op8]
@test_user.engine.applyOps ops
expect(@test_user.val('name').val()).to.equal("duaw")

View File

@ -14,8 +14,8 @@ module.exports = class Test
constructor: (@name_suffix = "")->
@number_of_test_cases_multiplier = 1
@repeat_this = 1 * @number_of_test_cases_multiplier
@doSomething_amount = 200 * @number_of_test_cases_multiplier
@number_of_engines = 7 + @number_of_test_cases_multiplier - 1
@doSomething_amount = 800 * @number_of_test_cases_multiplier
@number_of_engines = 5 + @number_of_test_cases_multiplier - 1
@time = 0
@ops = 0
@ -29,12 +29,14 @@ module.exports = class Test
@users = []
@Connector = Connector_uninitialized @users
for i in [0...@number_of_engines]
@users.push @makeNewUser (i+@name_suffix), @Connector
u = @makeNewUser (i+@name_suffix), @Connector
@users.push u
@users[0].val('name',"i")
@flushAll()
makeNewUser: (user_id, Connector)->
throw new Error "overwrite me!"
makeNewUser: (user)->
user.HB.setManualGarbageCollect()
user
getSomeUser: ()->
i = _.random 0, (@users.length-1)
@ -43,6 +45,7 @@ module.exports = class Test
getRandomText: (chars, min_length = 0)->
chars ?= "abcdefghijklmnopqrstuvwxyz"
length = _.random min_length, 10
#length = 1
nextchar = chars[(_.random 0, (chars.length-1))]
text = ""
_(length).times ()-> text += nextchar
@ -74,11 +77,6 @@ module.exports = class Test
y.insertText pos, @getRandomText()
null
types: [types.WordType]
,
f : (y)=> # REPLACE TEXT
y.replaceText @getRandomText()
null
types: [types.WordType]
,
f : (y)-> # DELETE TEXT
if y.val().length > 0
@ -87,8 +85,12 @@ module.exports = class Test
ops1 = y.deleteText pos, length
undefined
types : [types.WordType]
,
f : (y)=> # REPLACE TEXT
y.replaceText @getRandomText()
null
types: [types.WordType]
]
getRandomRoot: (user_num)->
throw new Error "overwrite me!"
@ -116,8 +118,10 @@ module.exports = class Test
choice = _.random (choices.length-1)
choices[choice](user_num)
flushAll: ()->
if @users.length <= 1
flushAll: (final)->
# TODO:!!
final = false
if @users.length <= 1 or not final
for user,user_number in @users
user.getConnector().flushAll()
else
@ -129,7 +133,7 @@ module.exports = class Test
compareAll: (test_number)->
@flushAll()
@flushAll(true)
@time += (new Date()).getTime() - @time_now
@ -139,8 +143,8 @@ module.exports = class Test
@ops += number_of_created_operations*@users.length
ops_per_msek = Math.floor(@ops/@time)
if test_number? and @debug
console.log "#{test_number}/#{@repeat_this}: Every collaborator (#{@users.length}) applied #{number_of_created_operations} ops in a different order." + " Over all we consumed #{@ops} operations in #{@time/1000} seconds (#{ops_per_msek} ops/msek)."
if test_number? # and @debug
console.log "#{test_number}/#{@repeat_this}: #{number_of_created_operations} were created and applied on (#{@users.length}) users ops in a different order." + " Over all we consumed #{@ops} operations in #{@time/1000} seconds (#{ops_per_msek} ops/msek)."
for i in [0...(@users.length-1)]
if @debug
@ -179,7 +183,12 @@ module.exports = class Test
console.log ''
for times in [1..@repeat_this]
@time_now = (new Date).getTime()
for i in [1..@doSomething_amount]
for i in [1..Math.floor(@doSomething_amount/2)]
@doSomething()
@flushAll(false)
for u in @users
u.HB.emptyGarbage()
for i in [1..Math.floor(@doSomething_amount/2)]
@doSomething()
@compareAll(times)
@ -188,8 +197,13 @@ module.exports = class Test
@reinitialize()
testHBencoding: ()->
@users[@users.length] = @makeNewUser 'testuser', (Connector_uninitialized [])
# in case of JsonFramework, every user will create its JSON first! therefore, the testusers id must be small than all the others (see InsertType)
@users[@users.length] = @makeNewUser (-1), (Connector_uninitialized [])
@users[@users.length-1].engine.applyOps @users[0].HB._encode()
#if @getContent(@users.length-1) isnt @getContent(0)
# console.log "testHBencoding:"
# console.log "Unprocessed ops first: #{@users[0].engine.unprocessed_ops.length}"
# console.log "Unprocessed ops last: #{@users[@users.length-1].engine.unprocessed_ops.length}"
expect(@getContent(@users.length-1)).to.deep.equal(@getContent(0))

View File

@ -11,10 +11,12 @@ Y = require "../lib/index"
Connector_uninitialized = require "../lib/Connectors/TestConnector"
Test = require "./TestSuite"
class TextTest extends Test
type: "TextTest"
makeNewUser: (user, conn)->
new Y.TextFramework user, conn
super new Y.TextFramework user, conn
getRandomRoot: (user_num)->
@users[user_num].getSharedObject()
@ -22,7 +24,6 @@ class TextTest extends Test
getContent: (user_num)->
@users[user_num].val()
describe "TextFramework", ->
beforeEach (done)->
@timeout 50000
@ -32,5 +33,14 @@ describe "TextFramework", ->
@test_user = @yTest.makeNewUser 0, (Connector_uninitialized [])
done()
it "simple multi-char insert", ->
u = @yTest.getSomeUser()
u.insertText 0, "abc"
@yTest.compareAll()
expect(u.val()).to.equal("abc")
it "can handle many engines, many operations, concurrently (random)", ->
@yTest.run()