added eventhandler

This commit is contained in:
Kevin Jahns 2015-07-10 15:00:54 +02:00
parent 8e9e62b3d0
commit 8cc374cabb
6 changed files with 248 additions and 248 deletions

View File

@ -8,7 +8,7 @@
"no-underscore-dangle": 0, "no-underscore-dangle": 0,
"no-constant-condition": 0, "no-constant-condition": 0,
"no-empty": 0, "no-empty": 0,
"new-cap": [2, { "capIsNewExceptions": ["List"] }], "new-cap": [2, { "capIsNewExceptions": ["List", "Y"] }],
}, },
"parser": "babel-eslint", "parser": "babel-eslint",
"globals": { "globals": {
@ -27,6 +27,7 @@
"setInterval": true, "setInterval": true,
"Operation": true, "Operation": true,
"getRandom": true, "getRandom": true,
"RBTree": true "RBTree": true,
"compareIds": true
} }
} }

View File

@ -9,15 +9,15 @@ class AbstractTransaction { //eslint-disable-line no-unused-vars
if (t == null) { if (t == null) {
var op = yield* this.getOperation(id); var op = yield* this.getOperation(id);
if (op != null) { 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; this.store.initializedTypes[sid] = t;
} }
} }
return t; return t;
} }
createType (model) { *createType (model) {
var sid = JSON.stringify(model.id); 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; this.store.initializedTypes[sid] = t;
return t; return t;
} }

View File

@ -40,9 +40,8 @@ var Struct = {
var user = this.store.y.connector.userId; var user = this.store.y.connector.userId;
var state = yield* this.getState(user); var state = yield* this.getState(user);
op.id = [user, state.clock]; op.id = [user, state.clock];
if ((yield* this.addOperation(op)) === false) { yield* Struct[op.struct].execute.call(this, op);
throw new Error("This is highly unexpected :(");
}
this.store.y.connector.broadcast({ this.store.y.connector.broadcast({
type: "update", type: "update",
ops: [Struct[op.struct].encode(op)] ops: [Struct[op.struct].encode(op)]
@ -56,11 +55,7 @@ var Struct = {
throw new Error("You must define a delete target!"); throw new Error("You must define a delete target!");
} }
op.struct = "Delete"; op.struct = "Delete";
yield* Struct.Operation.create.call(this, op); return yield* Struct.Operation.create.call(this, op);
var target = yield* this.getOperation(op.target);
target.deleted = true;
yield* this.setOperation(target);
}, },
encode: function (op) { encode: function (op) {
return op; return op;
@ -86,7 +81,7 @@ var Struct = {
parentSub: string (optional) parentSub: string (optional)
} }
*/ */
create: function*( op: Op ) : Insert { create: function* ( op: Op ) : Insert {
if ( op.left === undefined if ( op.left === undefined
|| op.right === undefined || op.right === undefined
|| op.parent === undefined ) { || op.parent === undefined ) {
@ -94,7 +89,7 @@ var Struct = {
} }
op.origin = op.left; op.origin = op.left;
op.struct = "Insert"; op.struct = "Insert";
return op; return yield* Struct.Operation.create.call(this, op);
}, },
encode: function(op){ encode: function(op){
/*var e = { /*var e = {
@ -253,11 +248,11 @@ var Struct = {
} }
}, },
List: { List: {
create: function( op : Op){ create: function* ( op : Op){
op.start = null; op.start = null;
op.end = null; op.end = null;
op.struct = "List"; op.struct = "List";
return Struct.Operation.create(op); return yield* Struct.Operation.create.call(this, op);
}, },
encode: function(op){ encode: function(op){
return { return {
@ -358,7 +353,7 @@ var Struct = {
// empty // empty
} }
*/ */
create: function*( op : Op ){ create: function* ( op : Op ){
op.map = {}; op.map = {};
op.struct = "Map"; op.struct = "Map";
return yield* Struct.Operation.create.call(this, op); return yield* Struct.Operation.create.call(this, op);
@ -393,22 +388,6 @@ var Struct = {
? res.content : yield* this.getType(res.opContent)); ? 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) { delete: function* (op, name) {
var v = op.map[name] || null; var v = op.map[name] || null;
if (v != null) { if (v != null) {

View File

@ -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(){ (function(){
class Map { class Map {
constructor (os, _model) { constructor (os, model) {
this._model = _model; this._model = model.id;
this.os = os; this.os = os;
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;
} }
val () { }
if (arguments.length === 1) { }
if (this.opContents[arguments[0]] == null) { });
return this.contents[arguments[0]]; }
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 { } else {
let def = Promise.defer(); let def = Promise.defer();
var oid = this.opContents[arguments[0]]; var oid = this.opContents[key];
this.os.requestTransaction(function*(){ this.os.requestTransaction(function*(){
def.resolve(yield* this.getType(oid)); def.resolve(yield* this.getType(oid));
}); });
return def.promise; return def.promise;
} }
} else if (arguments.length === 2) {
var key = arguments[0];
var value = arguments[1];
let def = Promise.defer();
var _model = this._model;
this.os.requestTransaction(function*(){
var model = yield* this.getOperation(_model);
def.resolve(yield* Y.Struct.Map.set.call(this, model, key, value));
});
return def.promise;
} else {
throw new Error("Implement this case!");
} }
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 {
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) { *delete (key) {
@ -38,24 +119,15 @@
yield* Y.Struct.Map.delete.call(t, model, key); yield* Y.Struct.Map.delete.call(t, model, key);
}*/ }*/
_changed (op) { _changed (op) {
if (op.left === null) { this.eventHandler.receivedOp(op);
if (op.opContent != null) {
this.opContents[op.parentSub] = op.opContent;
} else {
this.contents[op.parentSub] = op.opContent;
}
}
} }
} }
Y.Map = function* YMap(){ Y.Map = function* YMap(){
var t = yield "transaction"; var model = yield* Y.Struct.Map.create.call(this, {type: "Map"});
if (this instanceof Y.AbstractOperationStore) { return yield* this.createType(model);
var model = yield* Y.Struct.map.create.call(t, {type: "Map"}); };
return t.createType(model); Y.Map.create = function* YMapCreate(os, model){
} else { return new Map(os, model);
throw new Error("Don't use `new` to create this type!");
}
}; };
Y.Map.Create = Map;
})(); })();

View File

@ -1,11 +1,18 @@
/* @flow */ /* @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 class YConfig { //eslint-disable-line no-unused-vars
constructor (opts) { constructor (opts, callback) {
this.db = new Y[opts.db.name](this, opts.db); this.db = new Y[opts.db.name](this, opts.db);
this.connector = new Y[opts.connector.name](this, opts.connector); this.connector = new Y[opts.connector.name](this, opts.connector);
var yconfig = this;
this.db.requestTransaction(function*(){ this.db.requestTransaction(function*(){
// create initial Map type // create initial Map type
var model = { var model = {
@ -15,15 +22,11 @@ class Y { //eslint-disable-line no-unused-vars
map: {} map: {}
}; };
yield* this.addOperation(model); 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 () { destroy () {
this.connector.disconnect(); this.connector.disconnect();
this.db.removeDatabase(); this.db.removeDatabase();

View File

@ -24,9 +24,9 @@ function getRandomNumber(n) {
var numberOfYMapTests = 30; var numberOfYMapTests = 30;
function applyRandomTransactions (users, transactions, numberOfTransactions) { function applyRandomTransactions (users, transactions, numberOfTransactions) {
function* randomTransaction (root) { function randomTransaction (root) {
var f = getRandom(transactions); var f = getRandom(transactions);
yield* f(root); f(root);
} }
for(var i = 0; i < numberOfTransactions; i++) { for(var i = 0; i < numberOfTransactions; i++) {
var r = Math.random(); var r = Math.random();
@ -34,7 +34,7 @@ function applyRandomTransactions (users, transactions, numberOfTransactions) {
// 10% chance to flush // 10% chance to flush
users[0].connector.flushOne(); users[0].connector.flushOne();
} else { } 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++) { for (var uid = 0; uid + 1 < users.length; uid++) {
var u1 = users[uid]; var u1 = users[uid];
var u2 = users[uid + 1]; var u2 = users[uid + 1];
u1.transact(t1); u1.db.requestTransaction(t1);
u2.transact(t2); u2.db.requestTransaction(t2);
expect(s1).toEqual(s2); expect(s1).toEqual(s2);
var db1 = []; var db1 = [];
var db2 = []; var db2 = [];
@ -71,10 +71,17 @@ function compareAllUsers(users){
describe("Yjs", function(){ describe("Yjs", function(){
jasmine.DEFAULT_TIMEOUT_INTERVAL = 500; jasmine.DEFAULT_TIMEOUT_INTERVAL = 500;
beforeEach(function(){ beforeEach(function(done){
if (this.users != null) {
for (var y of this.users) {
y.destroy();
}
}
this.users = []; this.users = [];
var promises = [];
for (var i = 0; i < 5; i++) { for (var i = 0; i < 5; i++) {
this.users.push(new Y({ promises.push(Y({
db: { db: {
name: "Memory" name: "Memory"
}, },
@ -84,205 +91,142 @@ describe("Yjs", function(){
} }
})); }));
} }
Promise.all(promises).then( users => {
this.users = users;
done();
}); });
afterEach(function(){
for (var y of this.users) {
y.destroy();
}
this.users = [];
}); });
describe("Basic tests", function(){ 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(){ it("Basic get&set of Map property (converge via sync)", function(){
var y = this.users[0]; var y = this.users[0].root;
y.transact(function*(root){ y.set("stuff", "stuffy");
yield* root.val("stuff", "stuffy"); expect(y.get("stuff")).toEqual("stuffy");
expect(yield* root.val("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);
}
});
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");
});
var transaction = function*(root){
expect(yield* root.val("stuff")).toEqual("stuffy");
};
y.connector.flushAll();
for (var key in this.users) { for (var key in this.users) {
var u = this.users[key]; var u = this.users[key].root;
u.transact(transaction); expect(u.get("stuff")).toEqual("stuffy");
} }
compareAllUsers(this.users);
}); });
it("Basic get&set of Map property (handle conflict)", function(){ 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");
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(done){
var y = this.users[0]; var y = this.users[0];
y.connector.flushAll(); y.connector.flushAll();
this.users[0].transact(function*(root){ y.root.set("stuff", "c0");
yield* root.val("stuff", "c0");
}); this.users[1].root.set("stuff", "c1");
this.users[1].transact(function*(root){
yield* root.val("stuff", "c1");
});
var transaction = function*(root){
expect(yield* root.val("stuff")).toEqual("c0");
};
y.connector.flushAll(); y.connector.flushAll();
setTimeout( () => {
for (var key in this.users) { for (var key in this.users) {
var u = this.users[key]; var u = this.users[key];
u.transact(transaction); 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]; 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(); 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");
};
setTimeout( () => {
for (var key in this.users) { for (var key in this.users) {
var u = this.users[key]; var u = this.users[key];
u.transact(transaction); expect(u.root.get("stuff")).toEqual("c0");
} }
compareAllUsers(this.users);
done();
}, 50);
}); });
}); });
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();
});
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(){ describe("Map random tests", function(){
var randomMapTransactions = [ var randomMapTransactions = [
function* set (map) { function set (map) {
yield* map.val("somekey", getRandomNumber()); map.set("somekey", getRandomNumber());
}, },
function* delete_ (map) { function* delete_ (map) {
yield* map.delete("somekey"); map.delete("somekey");
} }
]; ];
it(`succeed after ${numberOfYMapTests} actions with flush before transactions`, function(){ function compareMapValues(users){
this.users[0].connector.flushAll();
applyRandomTransactions(this.users, randomMapTransactions, numberOfYMapTests);
compareAllUsers(this.users);
var firstMap; var firstMap;
for (var u of this.users) { for (var u of users) {
u.transact(function*(root){//eslint-disable-line var val = u.root.get();
var val = yield* root.val();
if (firstMap == null) { if (firstMap == null) {
firstMap = val; firstMap = val;
} else { } else {
expect(val).toEqual(firstMap); expect(val).toEqual(firstMap);
} }
});
} }
}); }
it(`succeed after ${numberOfYMapTests} actions without flush before transactions`, function(){ it(`succeed after ${numberOfYMapTests} actions with flush before transactions`, function(done){
this.users[0].connector.flushAll();
applyRandomTransactions(this.users, randomMapTransactions, numberOfYMapTests); applyRandomTransactions(this.users, randomMapTransactions, numberOfYMapTests);
setTimeout(()=>{
compareAllUsers(this.users); compareAllUsers(this.users);
compareMapValues(this.users);
done();
}, 500);
});
it(`succeed after ${numberOfYMapTests} actions without flush before transactions`, function(done){
applyRandomTransactions(this.users, randomMapTransactions, numberOfYMapTests);
setTimeout(()=>{
compareAllUsers(this.users);
compareMapValues(this.users);
done();
}, 500);
}); });
}); });
/*
var numberOfYListTests = 100; var numberOfYListTests = 100;
describe("List random tests", function(){ describe("List random tests", function(){
var randomListTests = [function* insert (root) { 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()]); yield* list.insert(Math.floor(Math.random() * 10), [getRandomNumber()]);
}, function* delete_(root) { }, function* delete_(root) {
var list = yield* root.val("list"); var list = yield* root.get("list");
yield* list.delete(Math.floor(Math.random() * 10)); yield* list.delete(Math.floor(Math.random() * 10));
}]; }];
beforeEach(function(){ beforeEach(function(){
this.users[0].transact(function*(root){ this.users[0].transact(function*(root){
var list = yield* Y.List(); var list = yield* Y.List();
yield* root.val("list", list); yield* root.set("list", list);
}); });
this.users[0].connector.flushAll(); this.users[0].connector.flushAll();
}); });
@ -292,11 +236,11 @@ describe("Yjs", function(){
compareAllUsers(this.users); compareAllUsers(this.users);
var userList; var userList;
this.users[0].transact(function*(root){ this.users[0].transact(function*(root){
var list = yield* root.val("list"); var list = yield* root.get("list");
if (userList == null) { if (userList == null) {
userList = yield* list.val(); userList = yield* list.get();
} else { } else {
expect(userList).toEqual(yield* list.val()); expect(userList).toEqual(yield* list.get());
expect(userList.length > 0).toBeTruthy(); expect(userList.length > 0).toBeTruthy();
} }
}); });
@ -323,4 +267,5 @@ describe("Yjs", function(){
compareAllUsers(this.users); compareAllUsers(this.users);
}); });
}); });
*/
}); });