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