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

View File

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

View File

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

View File

@ -1,36 +1,117 @@
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;
} 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) {
var t = yield "transaction";
@ -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;
})();

View File

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

View File

@ -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();
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();
this.users[0].connector.flushAll();
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 (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];
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);
}
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&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(){
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(){
function compareMapValues(users){
var 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);
compareAllUsers(this.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);
}
});
}
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);
});
});
*/
});