From 8cc374cabb3be1c1926e573400491803ba2591ce Mon Sep 17 00:00:00 2001 From: Kevin Jahns <kevin.jahns@rwth-aachen.de> Date: Fri, 10 Jul 2015 15:00:54 +0200 Subject: [PATCH] added eventhandler --- .eslintrc | 5 +- src/OperationStore.js | 6 +- src/Struct.js | 37 ++---- src/Types/Map.js | 144 ++++++++++++++++------ src/y.js | 23 ++-- src/y.spec.js | 273 +++++++++++++++++------------------------- 6 files changed, 244 insertions(+), 244 deletions(-) diff --git a/.eslintrc b/.eslintrc index 05eb0c8c..b0791eea 100644 --- a/.eslintrc +++ b/.eslintrc @@ -8,7 +8,7 @@ "no-underscore-dangle": 0, "no-constant-condition": 0, "no-empty": 0, - "new-cap": [2, { "capIsNewExceptions": ["List"] }], + "new-cap": [2, { "capIsNewExceptions": ["List", "Y"] }], }, "parser": "babel-eslint", "globals": { @@ -27,6 +27,7 @@ "setInterval": true, "Operation": true, "getRandom": true, - "RBTree": true + "RBTree": true, + "compareIds": true } } diff --git a/src/OperationStore.js b/src/OperationStore.js index 355e2beb..1190c7c1 100644 --- a/src/OperationStore.js +++ b/src/OperationStore.js @@ -9,15 +9,15 @@ class AbstractTransaction { //eslint-disable-line no-unused-vars if (t == null) { var op = yield* this.getOperation(id); if (op != null) { - t = new Y[op.type].Create(op.id); + t = yield* Y[op.type].create(this.store, op.id); this.store.initializedTypes[sid] = t; } } return t; } - createType (model) { + *createType (model) { var sid = JSON.stringify(model.id); - var t = new Y[model.type].Create(model.id); + var t = yield* Y[model.type].create(this.store, model); this.store.initializedTypes[sid] = t; return t; } diff --git a/src/Struct.js b/src/Struct.js index 04c2a58d..64c9e990 100644 --- a/src/Struct.js +++ b/src/Struct.js @@ -40,9 +40,8 @@ var Struct = { var user = this.store.y.connector.userId; var state = yield* this.getState(user); op.id = [user, state.clock]; - if ((yield* this.addOperation(op)) === false) { - throw new Error("This is highly unexpected :("); - } + yield* Struct[op.struct].execute.call(this, op); + this.store.y.connector.broadcast({ type: "update", ops: [Struct[op.struct].encode(op)] @@ -56,11 +55,7 @@ var Struct = { throw new Error("You must define a delete target!"); } op.struct = "Delete"; - yield* Struct.Operation.create.call(this, op); - - var target = yield* this.getOperation(op.target); - target.deleted = true; - yield* this.setOperation(target); + return yield* Struct.Operation.create.call(this, op); }, encode: function (op) { return op; @@ -86,7 +81,7 @@ var Struct = { parentSub: string (optional) } */ - create: function*( op: Op ) : Insert { + create: function* ( op: Op ) : Insert { if ( op.left === undefined || op.right === undefined || op.parent === undefined ) { @@ -94,7 +89,7 @@ var Struct = { } op.origin = op.left; op.struct = "Insert"; - return op; + return yield* Struct.Operation.create.call(this, op); }, encode: function(op){ /*var e = { @@ -253,11 +248,11 @@ var Struct = { } }, List: { - create: function( op : Op){ + create: function* ( op : Op){ op.start = null; op.end = null; op.struct = "List"; - return Struct.Operation.create(op); + return yield* Struct.Operation.create.call(this, op); }, encode: function(op){ return { @@ -358,7 +353,7 @@ var Struct = { // empty } */ - create: function*( op : Op ){ + create: function* ( op : Op ){ op.map = {}; op.struct = "Map"; return yield* Struct.Operation.create.call(this, op); @@ -393,22 +388,6 @@ var Struct = { ? res.content : yield* this.getType(res.opContent)); } }, - set: function* (op, name, value) { - var right = op.map[name] || null; - var insert = { - left: null, - right: right, - parent: op.id, - parentSub: name - }; - if ( value != null && value._model != null - && value._model.length === 2) { - insert.opContent = value._model; - } else { - insert.content = value; - } - yield* Struct.Insert.create.call(this, insert); - }, delete: function* (op, name) { var v = op.map[name] || null; if (v != null) { diff --git a/src/Types/Map.js b/src/Types/Map.js index 46dba845..47482692 100644 --- a/src/Types/Map.js +++ b/src/Types/Map.js @@ -1,35 +1,116 @@ +var GeneratorFunction = (function*(){}).constructor; + +class EventHandler { + constructor (onevent) { + this.waiting = []; + this.awaiting = 0; + this.onevent = onevent; + } + receivedOp (op) { + if (this.awaiting <= 0) { + this.onevent([op]); + } else { + this.waiting.push(copyObject(op)); + } + } + awaitAndPrematurelyCall (op) { + this.awaiting++; + this.onevent([op]); + } + awaitedLastOp () { + var op = this.waiting.pop(); + for (var i = this.waiting.length - 1; i >= 0; i--) { + var w = this.waiting[i]; + if (compareIds(op.left, w.id)) { + // include the effect of op in w + w.right = op.id; + // exclude the effect of w in op + op.left = w.left; + } else if (compareIds(op.right, w.id)) { + // similar.. + w.left = op.id; + op.right = w.right; + } + } + this.awaiting--; + if (this.awaiting <= 0) { + var events = this.waiting; + this.waiting = []; + this.onevent(events); + } + } +} + (function(){ class Map { - constructor (os, _model) { - this._model = _model; + constructor (os, model) { + this._model = model.id; this.os = os; - } - val () { - if (arguments.length === 1) { - if (this.opContents[arguments[0]] == null) { - return this.contents[arguments[0]]; - } else { - let def = Promise.defer(); - var oid = this.opContents[arguments[0]]; - this.os.requestTransaction(function*(){ - def.resolve(yield* this.getType(oid)); - }); - return def.promise; + this.map = model.map; + this.contents = {}; + this.opContents = {}; + this.eventHandler = new EventHandler( ops =>{ + for (var i in ops) { + var op = ops[i]; + if (op.left === null) { + if (op.opContent != null) { + this.opContents[op.parentSub] = op.opContent; + } else { + this.contents[op.parentSub] = op.content; + } + } } - } else if (arguments.length === 2) { - var key = arguments[0]; - var value = arguments[1]; + }); + } + get (key) { + // return property. + // if property does not exist, return null + // if property is a type, return a promise + if (this.opContents[key] == null) { + return this.contents[key]; + } else { let def = Promise.defer(); - var _model = this._model; + var oid = this.opContents[key]; this.os.requestTransaction(function*(){ - var model = yield* this.getOperation(_model); - def.resolve(yield* Y.Struct.Map.set.call(this, model, key, value)); + def.resolve(yield* this.getType(oid)); }); return def.promise; + } + } + set (key, value) { + // set property. + // if property is a type, return a promise + // if not, apply immediately on this type an call event + + var right = this.map[key] || null; + var insert = { + left: null, + right: right, + parent: this._model, + parentSub: key + }; + var def = Promise.defer(); + if ( value != null && value.constructor === GeneratorFunction) { + // construct a new type + this.os.requestTransaction(function*(){ + var type = yield* value.call(this); + insert.opContent = type._model; + yield* Struct.Insert.create.call(this, insert); + def.resolve(type); + }); } else { - throw new Error("Implement this case!"); + insert.content = value; + var eventHandler = this.eventHandler; + eventHandler.awaitAndPrematurelyCall(insert); + + this.os.requestTransaction(function*(){ + yield* Struct.Insert.create.call(this, insert); + eventHandler.awaitedLastOp(); + }); + def.resolve(value); } + return def.promise; } /* *delete (key) { @@ -38,24 +119,15 @@ yield* Y.Struct.Map.delete.call(t, model, key); }*/ _changed (op) { - if (op.left === null) { - if (op.opContent != null) { - this.opContents[op.parentSub] = op.opContent; - } else { - this.contents[op.parentSub] = op.opContent; - } - } + this.eventHandler.receivedOp(op); } } Y.Map = function* YMap(){ - var t = yield "transaction"; - if (this instanceof Y.AbstractOperationStore) { - var model = yield* Y.Struct.map.create.call(t, {type: "Map"}); - return t.createType(model); - } else { - throw new Error("Don't use `new` to create this type!"); - } + var model = yield* Y.Struct.Map.create.call(this, {type: "Map"}); + return yield* this.createType(model); + }; + Y.Map.create = function* YMapCreate(os, model){ + return new Map(os, model); }; - Y.Map.Create = Map; })(); diff --git a/src/y.js b/src/y.js index 0cbad5c0..00f5ac1e 100644 --- a/src/y.js +++ b/src/y.js @@ -1,11 +1,18 @@ /* @flow */ -const GeneratorFunction = (function*(){}).constructor; +function Y (opts) { + var def = Promise.defer(); + new YConfig(opts, function(config){ //eslint-disable-line + def.resolve(config); + }); + return def.promise; +} -class Y { //eslint-disable-line no-unused-vars - constructor (opts) { +class YConfig { //eslint-disable-line no-unused-vars + constructor (opts, callback) { this.db = new Y[opts.db.name](this, opts.db); this.connector = new Y[opts.connector.name](this, opts.connector); + var yconfig = this; this.db.requestTransaction(function*(){ // create initial Map type var model = { @@ -15,15 +22,11 @@ class Y { //eslint-disable-line no-unused-vars map: {} }; yield* this.addOperation(model); - this.createType(model); + var root = yield* this.createType(model); + this.store.y.root = root; + callback(yconfig); }); } - transact (generator) { - if (generator.constructor !== GeneratorFunction) { - throw new Error("y.transact requires a Generator function! E.g. function*(){/*..*/}"); - } - this.db.requestTransaction(generator); - } destroy () { this.connector.disconnect(); this.db.removeDatabase(); diff --git a/src/y.spec.js b/src/y.spec.js index 5623783a..ef2796e7 100644 --- a/src/y.spec.js +++ b/src/y.spec.js @@ -24,9 +24,9 @@ function getRandomNumber(n) { var numberOfYMapTests = 30; function applyRandomTransactions (users, transactions, numberOfTransactions) { - function* randomTransaction (root) { + function randomTransaction (root) { var f = getRandom(transactions); - yield* f(root); + f(root); } for(var i = 0; i < numberOfTransactions; i++) { var r = Math.random(); @@ -34,7 +34,7 @@ function applyRandomTransactions (users, transactions, numberOfTransactions) { // 10% chance to flush users[0].connector.flushOne(); } else { - getRandom(users).transact(randomTransaction); + randomTransaction(getRandom(users).root); } } } @@ -51,8 +51,8 @@ function compareAllUsers(users){ for (var uid = 0; uid + 1 < users.length; uid++) { var u1 = users[uid]; var u2 = users[uid + 1]; - u1.transact(t1); - u2.transact(t2); + u1.db.requestTransaction(t1); + u2.db.requestTransaction(t2); expect(s1).toEqual(s2); var db1 = []; var db2 = []; @@ -71,10 +71,17 @@ function compareAllUsers(users){ describe("Yjs", function(){ jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; - beforeEach(function(){ + beforeEach(function(done){ + if (this.users != null) { + for (var y of this.users) { + y.destroy(); + } + } this.users = []; + + var promises = []; for (var i = 0; i < 5; i++) { - this.users.push(new Y({ + promises.push(Y({ db: { name: "Memory" }, @@ -84,205 +91,142 @@ describe("Yjs", function(){ } })); } - }); - afterEach(function(){ - for (var y of this.users) { - y.destroy(); - } - this.users = []; + Promise.all(promises).then( users => { + this.users = users; + done(); + }); }); describe("Basic tests", function(){ - it("There is an initial Map type & it is created only once", function(){ - var y = this.users[0]; - var root1; - y.transact(function*(root){ - expect(root).not.toBeUndefined(); - root1 = root; - }); - y.transact(function*(root2){ - expect(root1).toBe(root2); - }); - }); - it("Custom Types are created only once", function(){ - var y = this.users[0]; - var l1; - y.transact(function*(root){ - var l = yield* Y.List(); - yield* root.val("list", l); - l1 = l; - }); - y.transact(function*(root){ - expect(l1).toBe(yield* root.val("list")); - }); - }); - it("Basic get&set of Map property (converge via sync)", function(){ - var y = this.users[0]; - y.transact(function*(root){ - yield* root.val("stuff", "stuffy"); - expect(yield* root.val("stuff")).toEqual("stuffy"); - }); + var y = this.users[0].root; + y.set("stuff", "stuffy"); + expect(y.get("stuff")).toEqual("stuffy"); - y.connector.flushAll(); + this.users[0].connector.flushAll(); - var transaction = function*(root){ - expect(yield* root.val("stuff")).toEqual("stuffy"); - }; for (var key in this.users) { - var u = this.users[key]; - u.transact(transaction); + var u = this.users[key].root; + expect(u.get("stuff")).toEqual("stuffy"); } + compareAllUsers(this.users); }); - it("Basic get&set of Map property (converge via update)", function(){ - var y = this.users[0]; - y.connector.flushAll(); - y.transact(function*(root){ - yield* root.val("stuff", "stuffy"); - expect(yield* root.val("stuff")).toEqual("stuffy"); + it("Map can set custom types (Map)", function(done){ + var y = this.users[0].root; + y.set("Map", Y.Map).then(function(map) { + map.set("one", 1); + return y.get("Map"); + }).then(function(map){ + expect(map.get("one")).toEqual(1); + done(); }); + }); + it("Basic get&set of Map property (converge via update)", function(done){ + var u = this.users[0]; + u.connector.flushAll(); + var y = u.root; + y.set("stuff", "stuffy"); + expect(y.get("stuff")).toEqual("stuffy"); - var transaction = function*(root){ - expect(yield* root.val("stuff")).toEqual("stuffy"); - }; - y.connector.flushAll(); - - for (var key in this.users) { - var u = this.users[key]; - u.transact(transaction); - } + u.connector.flushAll(); + setTimeout(() => { + for (var key in this.users) { + var r = this.users[key].root; + expect(r.get("stuff")).toEqual("stuffy"); + } + done(); + }, 50); }); - it("Basic get&set of Map property (handle conflict)", function(){ + it("Basic get&set of Map property (handle conflict)", function(done){ var y = this.users[0]; y.connector.flushAll(); - this.users[0].transact(function*(root){ - yield* root.val("stuff", "c0"); - }); - this.users[1].transact(function*(root){ - yield* root.val("stuff", "c1"); - }); + y.root.set("stuff", "c0"); + + this.users[1].root.set("stuff", "c1"); - var transaction = function*(root){ - expect(yield* root.val("stuff")).toEqual("c0"); - }; y.connector.flushAll(); - for (var key in this.users) { - var u = this.users[key]; - u.transact(transaction); - } + setTimeout( () => { + for (var key in this.users) { + var u = this.users[key]; + expect(u.root.get("stuff")).toEqual("c0"); + compareAllUsers(this.users); + } + done(); + }, 50); }); - it("Basic get&set of Map property (handle three conflicts)", function(){ + it("Basic get&set of Map property (handle three conflicts)", function(done){ var y = this.users[0]; + this.users[0].root.set("stuff", "c0"); + this.users[1].root.set("stuff", "c1"); + this.users[2].root.set("stuff", "c2"); + this.users[3].root.set("stuff", "c3"); y.connector.flushAll(); - this.users[0].transact(function*(root){ - yield* root.val("stuff", "c0"); - }); - this.users[1].transact(function*(root){ - yield* root.val("stuff", "c1"); - }); - this.users[2].transact(function*(root){ - yield* root.val("stuff", "c2"); - }); - this.users[3].transact(function*(root){ - yield* root.val("stuff", "c3"); - }); - y.connector.flushAll(); - var transaction = function*(root){ - expect(yield* root.val("stuff")).toEqual("c0"); - }; - - for (var key in this.users) { - var u = this.users[key]; - u.transact(transaction); - } - }); - }); - it("Basic get&set&delete with Map property", function(){ - var y = this.users[0]; - y.connector.flushAll(); - this.users[0].transact(function*(root){ - yield* root.val("stuff", "c0"); - }); - this.users[0].transact(function*(root){ - yield* root.val("stuff", "c1"); - }); - this.users[0].transact(function*(root){ - yield* root.delete("stuff"); - }); - - y.connector.flushAll(); - var transaction = function*(root){ - expect(yield* root.val("stuff")).toBeUndefined(); - }; - for (var key in this.users) { - var u = this.users[key]; - u.transact(transaction); - } - }); - - it("List type: can create, insert, and delete elements", function(){ - var y = this.users[0]; - y.transact(function*(root){ - var list = yield* Y.List(); - yield* root.val("list", list); - yield* list.insert(0, [1, 2, 3, 4]); - yield* list.delete(1); - expect(yield* root.val("list")).not.toBeUndefined(); + setTimeout( () => { + for (var key in this.users) { + var u = this.users[key]; + expect(u.root.get("stuff")).toEqual("c0"); + } + compareAllUsers(this.users); + done(); + }, 50); }); - y.connector.flushAll(); - function* transaction (root) { - var list = yield* root.val("list"); - expect(yield* list.val()).toEqual([1, 3, 4]); - } - for (var u of this.users) { - u.transact(transaction); - } }); describe("Map random tests", function(){ var randomMapTransactions = [ - function* set (map) { - yield* map.val("somekey", getRandomNumber()); + function set (map) { + map.set("somekey", getRandomNumber()); }, function* delete_ (map) { - yield* map.delete("somekey"); + map.delete("somekey"); } ]; - it(`succeed after ${numberOfYMapTests} actions with flush before transactions`, function(){ - this.users[0].connector.flushAll(); - applyRandomTransactions(this.users, randomMapTransactions, numberOfYMapTests); - compareAllUsers(this.users); + function compareMapValues(users){ var firstMap; - for (var u of this.users) { - u.transact(function*(root){//eslint-disable-line - var val = yield* root.val(); - if (firstMap == null) { - firstMap = val; - } else { - expect(val).toEqual(firstMap); - } - }); + for (var u of users) { + var val = u.root.get(); + if (firstMap == null) { + firstMap = val; + } else { + expect(val).toEqual(firstMap); + } } + } + it(`succeed after ${numberOfYMapTests} actions with flush before transactions`, function(done){ + this.users[0].connector.flushAll(); + applyRandomTransactions(this.users, randomMapTransactions, numberOfYMapTests); + setTimeout(()=>{ + compareAllUsers(this.users); + compareMapValues(this.users); + done(); + }, 500); }); - it(`succeed after ${numberOfYMapTests} actions without flush before transactions`, function(){ + it(`succeed after ${numberOfYMapTests} actions without flush before transactions`, function(done){ applyRandomTransactions(this.users, randomMapTransactions, numberOfYMapTests); - compareAllUsers(this.users); + setTimeout(()=>{ + compareAllUsers(this.users); + compareMapValues(this.users); + done(); + }, 500); }); }); + +/* + var numberOfYListTests = 100; describe("List random tests", function(){ var randomListTests = [function* insert (root) { - var list = yield* root.val("list"); + var list = yield* root.get("list"); yield* list.insert(Math.floor(Math.random() * 10), [getRandomNumber()]); }, function* delete_(root) { - var list = yield* root.val("list"); + var list = yield* root.get("list"); yield* list.delete(Math.floor(Math.random() * 10)); }]; beforeEach(function(){ this.users[0].transact(function*(root){ var list = yield* Y.List(); - yield* root.val("list", list); + yield* root.set("list", list); }); this.users[0].connector.flushAll(); }); @@ -292,11 +236,11 @@ describe("Yjs", function(){ compareAllUsers(this.users); var userList; this.users[0].transact(function*(root){ - var list = yield* root.val("list"); + var list = yield* root.get("list"); if (userList == null) { - userList = yield* list.val(); + userList = yield* list.get(); } else { - expect(userList).toEqual(yield* list.val()); + expect(userList).toEqual(yield* list.get()); expect(userList.length > 0).toBeTruthy(); } }); @@ -323,4 +267,5 @@ describe("Yjs", function(){ compareAllUsers(this.users); }); }); + */ }); -- GitLab