diff --git a/src/Connector.js b/src/Connector.js index 0cd54dc3..1784712f 100644 --- a/src/Connector.js +++ b/src/Connector.js @@ -41,11 +41,11 @@ class AbstractConnector { this.broadcastedHB = false this.syncingClients = [] this.whenSyncedListeners = [] - this.y.db.stopGarbageCollector() + return this.y.db.stopGarbageCollector() } setUserId (userId) { this.userId = userId - this.y.db.setUserId(userId) + return this.y.db.setUserId(userId) } onUserEvent (f) { this.userEventListeners.push(f) @@ -132,7 +132,7 @@ class AbstractConnector { } this.whenSyncedListeners = [] this.y.db.requestTransaction(function *() { - yield* this.store.garbageCollectAfterSync() + yield* this.garbageCollectAfterSync() }) } } diff --git a/src/Connectors/Test.js b/src/Connectors/Test.js index f645e797..e02c2a4a 100644 --- a/src/Connectors/Test.js +++ b/src/Connectors/Test.js @@ -55,8 +55,9 @@ class Test extends Y.AbstractConnector { options.role = 'master' options.forwardToSyncingClients = false super(y, options) - this.setUserId((userIdCounter++) + '') - globalRoom.addUser(this) + this.setUserId((userIdCounter++) + '').then(() => { + globalRoom.addUser(this) + }) this.globalRoom = globalRoom this.syncingClientDuration = 0 } diff --git a/src/Database.js b/src/Database.js index c8e5fcd2..d63476d9 100644 --- a/src/Database.js +++ b/src/Database.js @@ -13,7 +13,7 @@ * destroy() - destroy the database */ -class AbstractOperationStore { +class AbstractDatabase { constructor (y, opts) { this.y = y // E.g. this.listenersById[id] : Array @@ -80,16 +80,6 @@ class AbstractOperationStore { }) }) } - * garbageCollectAfterSync () { - this.requestTransaction(function * () { - yield* this.os.iterate(this, null, null, function * (op) { - if (op.deleted && op.left != null) { - var left = yield this.os.find(op.left) - this.store.addToGarbageCollector(op, left) - } - }) - }) - } /* Try to add to GC. @@ -130,12 +120,18 @@ class AbstractOperationStore { this.gcInterval = null } setUserId (userId) { - this.userId = userId - this.opClock = 0 - if (this.whenUserIdSetListener != null) { - this.whenUserIdSetListener() - this.whenUserIdSetListener = null - } + var self = this + return new Promise(function (resolve) { + self.requestTransaction(function * () { + self.userId = userId + self.opClock = (yield* this.getState(userId)).clock + if (self.whenUserIdSetListener != null) { + self.whenUserIdSetListener() + self.whenUserIdSetListener = null + } + resolve() + }) + }) } whenUserIdSet (f) { if (this.userId != null) { @@ -280,4 +276,4 @@ class AbstractOperationStore { } } } -Y.AbstractOperationStore = AbstractOperationStore +Y.AbstractDatabase = AbstractDatabase diff --git a/src/Database.spec.js b/src/Database.spec.js index 5de2dc98..dd559e31 100644 --- a/src/Database.spec.js +++ b/src/Database.spec.js @@ -156,10 +156,10 @@ for (var database of databases) { }) it('debug #1', function (done) { store.requestTransaction(function * () { - yield this.os.set({id: [2]}) - yield this.os.set({id: [0]}) + yield this.os.put({id: [2]}) + yield this.os.put({id: [0]}) yield this.os.delete([2]) - yield this.os.set({id: [1]}) + yield this.os.put({id: [1]}) expect(yield this.os.find([0])).not.toBeNull() expect(yield this.os.find([1])).not.toBeNull() expect(yield this.os.find([2])).toBeNull() @@ -168,11 +168,11 @@ for (var database of databases) { }) it('can add&retrieve 5 elements', function (done) { store.requestTransaction(function * () { - yield this.os.set({val: 'four', id: [4]}) - yield this.os.set({val: 'one', id: [1]}) - yield this.os.set({val: 'three', id: [3]}) - yield this.os.set({val: 'two', id: [2]}) - yield this.os.set({val: 'five', id: [5]}) + yield this.os.put({val: 'four', id: [4]}) + yield this.os.put({val: 'one', id: [1]}) + yield this.os.put({val: 'three', id: [3]}) + yield this.os.put({val: 'two', id: [2]}) + yield this.os.put({val: 'five', id: [5]}) expect((yield this.os.find([1])).val).toEqual('one') expect((yield this.os.find([2])).val).toEqual('two') expect((yield this.os.find([3])).val).toEqual('three') @@ -183,11 +183,11 @@ for (var database of databases) { }) it('5 elements do not exist anymore after deleting them', function (done) { store.requestTransaction(function * () { - yield this.os.set({val: 'four', id: [4]}) - yield this.os.set({val: 'one', id: [1]}) - yield this.os.set({val: 'three', id: [3]}) - yield this.os.set({val: 'two', id: [2]}) - yield this.os.set({val: 'five', id: [5]}) + yield this.os.put({val: 'four', id: [4]}) + yield this.os.put({val: 'one', id: [1]}) + yield this.os.put({val: 'three', id: [3]}) + yield this.os.put({val: 'two', id: [2]}) + yield this.os.put({val: 'five', id: [5]}) yield this.os.delete([4]) expect(yield this.os.find([4])).not.toBeTruthy() yield this.os.delete([3]) @@ -216,7 +216,7 @@ for (var database of databases) { var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)] if (!(yield this.os.findNode(obj))) { elements.push(obj) - yield this.os.set({id: obj}) + yield this.os.put({id: obj}) } } else if (elements.length > 0) { var elemid = Math.floor(Math.random() * elements.length) diff --git a/src/Databases/IndexedDB.js b/src/Databases/IndexedDB.js index 89391d72..8981a767 100644 --- a/src/Databases/IndexedDB.js +++ b/src/Databases/IndexedDB.js @@ -3,9 +3,8 @@ 'use strict' Y.IndexedDB = (function () { - class Transaction extends Y.AbstractTransaction { + class Transaction { constructor (store) { - super(store) this.transaction = store.db.transaction(['OperationStore', 'StateVector'], 'readwrite') this.sv = this.transaction.objectStore('StateVector') this.os = this.transaction.objectStore('OperationStore') @@ -83,7 +82,7 @@ Y.IndexedDB = (function () { return ops } } - class OperationStore extends Y.AbstractOperationStore { + class OperationStore extends Y.AbstractDatabase { constructor (y, opts) { super(y, opts) if (opts == null) { diff --git a/src/Databases/IndexedDB.spec.js b/src/Databases/IndexedDB.spec.js index 6741894c..0b4e256c 100644 --- a/src/Databases/IndexedDB.spec.js +++ b/src/Databases/IndexedDB.spec.js @@ -8,101 +8,6 @@ if (typeof window !== 'undefined' && false) { ob = new Y.IndexedDB(null, {namespace: 'Test', gcTimeout: -1}) }) - it('can add and get operation', function (done) { - ob.requestTransaction(function *() { - var op = yield* this.setOperation({ - 'id': ['1', 0], - 'stuff': true - }) - expect(yield* this.getOperation(['1', 0])) - .toEqual(op) - done() - }) - }) - - it('can remove operation', function (done) { - ob.requestTransaction(function *() { - var op = yield* this.setOperation({ - 'id': ['1', 0], - 'stuff': true - }) - expect(yield* this.getOperation(['1', 0])) - .toEqual(op) - yield* this.removeOperation(['1', 0]) - expect(yield* this.getOperation(['1', 0])) - .toBeNull() - done() - }) - }) - - it('getOperation(op) returns undefined if op does not exist', function (done) { - ob.requestTransaction(function *() { - var op = yield* this.getOperation("plzDon'tBeThere") - expect(op).toBeNull() - done() - }) - }) - - it('yield throws if request is unknown', function (done) { - ob.requestTransaction(function *() { - try { - yield* this.setOperation() - } catch (e) { - expect(true).toEqual(true) - done() - return - } - expect('Expected an Error!').toEqual(true) - done() - }) - }) - - it('sets and gets stateVector', function (done) { - ob.requestTransaction(function *() { - var s1 = {user: '1', clock: 1} - var s2 = {user: '2', clock: 3} - yield* this.setState(s1) - yield* this.setState(s2) - var sv = yield* this.getStateVector() - expect(sv).toEqual([s1, s2]) - done() - }) - }) - - it('gets stateSet', function (done) { - ob.requestTransaction(function *() { - var s1 = {user: '1', clock: 1} - var s2 = {user: '2', clock: 3} - yield* this.setState(s1) - yield* this.setState(s2) - var sv = yield* this.getStateSet() - expect(sv).toEqual({ - '1': 1, - '2': 3 - }) - done() - }) - }) - - it('getOperations returns operations (no parameters)', function (done) { - ob.requestTransaction(function *() { - var s1 = {user: '1', clock: 55} - yield* this.setState(s1) - var op1 = yield* this.setOperation({ - 'id': ['1', 0], - 'stuff': true - }) - var op2 = yield* this.setOperation({ - 'id': ['1', 3], - 'stuff': true - }) - var ops = yield* this.getOperations() - expect(ops.length).toBeGreaterThan(1) - expect(ops[0]).toEqual(op1) - expect(ops[1]).toEqual(op2) - done() - }) - }) afterAll(function (done) { ob.requestTransaction(function *() { yield* ob.removeDatabase() diff --git a/src/Databases/Memory.js b/src/Databases/Memory.js index 2cb3d670..b15ba204 100644 --- a/src/Databases/Memory.js +++ b/src/Databases/Memory.js @@ -1,19 +1,8 @@ /* global Y */ 'use strict' -class DeleteStore extends Y.utils.RBTree { - constructor () { - super() - this.mem = [] - } -} - -Y.utils.DeleteStore = DeleteStore - Y.Memory = (function () { - class Transaction extends Y.AbstractTransaction { - } - class OperationStore extends Y.AbstractOperationStore { + class Database extends Y.AbstractDatabase { constructor (y, opts) { super(y, opts) this.os = new Y.utils.RBTree() @@ -42,7 +31,7 @@ Y.Memory = (function () { var transact = () => { var makeGen = _makeGen while (makeGen != null) { - var t = new Transaction(this) + var t = new Y.Transaction(this) var gen = makeGen.call(t) var res = gen.next() while (!res.done) { @@ -64,7 +53,9 @@ Y.Memory = (function () { * destroy () { super.destroy() delete this.os + delete this.ss + delete this.ds } } - return OperationStore + return Database })() diff --git a/src/Databases/RedBlackTree.js b/src/Databases/RedBlackTree.js index a4a7211e..9586eb77 100644 --- a/src/Databases/RedBlackTree.js +++ b/src/Databases/RedBlackTree.js @@ -388,7 +388,7 @@ class RBTree { } } } - set (v) { + put (v) { if (v == null || v.id == null || v.id.constructor !== Array) { throw new Error('v is expected to have an id property which is an Array!') } diff --git a/src/Databases/RedBlackTree.spec.js b/src/Databases/RedBlackTree.spec.js index c8c6a427..3e47d1fd 100644 --- a/src/Databases/RedBlackTree.spec.js +++ b/src/Databases/RedBlackTree.spec.js @@ -59,15 +59,15 @@ describe('RedBlack Tree', function () { }) describe('debug #2', function () { var tree = new Y.utils.RBTree() - tree.set({id: [8433]}) - tree.set({id: [12844]}) - tree.set({id: [1795]}) - tree.set({id: [30302]}) - tree.set({id: [64287]}) + tree.put({id: [8433]}) + tree.put({id: [12844]}) + tree.put({id: [1795]}) + tree.put({id: [30302]}) + tree.put({id: [64287]}) tree.delete([8433]) - tree.set({id: [28996]}) + tree.put({id: [28996]}) tree.delete([64287]) - tree.set({id: [22721]}) + tree.put({id: [22721]}) itRootNodeIsBlack(tree, []) itBlackHeightOfSubTreesAreEqual(tree, []) @@ -82,7 +82,7 @@ describe('RedBlack Tree', function () { var obj = [Math.floor(Math.random() * numberOfRBTreeTests * 10000)] if (!tree.findNode(obj)) { elements.push(obj) - tree.set({id: obj}) + tree.put({id: obj}) } } else if (elements.length > 0) { var elemid = Math.floor(Math.random() * elements.length) diff --git a/src/Transaction.js b/src/Transaction.js index ad3ce6c3..7b725ce9 100644 --- a/src/Transaction.js +++ b/src/Transaction.js @@ -74,7 +74,7 @@ - this is called only by `getOperations(startSS)`. It makes an operation applyable on a given SS. */ -class AbstractTransaction { +class Transaction { constructor (store) { this.store = store this.ss = store.ss @@ -206,14 +206,14 @@ class AbstractTransaction { // un-extend left var newlen = n.val.len - (id[1] - n.val.id[1]) n.val.len -= newlen - n = yield this.ds.set({id: id, len: newlen, gc: false}) + n = yield this.ds.put({id: id, len: newlen, gc: false}) } // get prev&next before adding a new operation var prev = n.prev() var next = n.next() if (id[1] < n.val.id[1] + n.val.len - 1) { // un-extend right - yield this.ds.set({id: [id[0], id[1] + 1], len: n.val.len - 1, gc: false}) + yield this.ds.put({id: [id[0], id[1] + 1], len: n.val.len - 1, gc: false}) n.val.len = 1 } // set gc'd @@ -256,11 +256,11 @@ class AbstractTransaction { n.val.len++ } else { // cannot extend left - n = yield this.ds.set({id: id, len: 1, gc: false}) + n = yield this.ds.put({id: id, len: 1, gc: false}) } } else { // cannot extend left - n = yield this.ds.set({id: id, len: 1, gc: false}) + n = yield this.ds.put({id: id, len: 1, gc: false}) } // can extend right? var next = n.next() @@ -276,6 +276,19 @@ class AbstractTransaction { return n } } + /* + Call this method when the client is connected&synced with the + other clients (e.g. master). This will query the database for + operations that can be gc'd and add them to the garbage collector. + */ + * garbageCollectAfterSync () { + yield* this.os.iterate(this, null, null, function * (op) { + if (op.deleted && op.left != null) { + var left = yield* this.getOperation(op.left) + this.store.addToGarbageCollector(op, left) + } + }) + } /* Really remove an op and all its effects. The complicated case here is the Insert operation: @@ -482,13 +495,11 @@ class AbstractTransaction { return n !== null && n.val.id[0] === id[0] && id[1] < n.val.id[1] + n.val.len } * setOperation (op) { - // TODO: you can remove this step! probs.. - var n = yield this.os.findNode(op.id) - n.val = op + yield this.os.put(op) return op } * addOperation (op) { - var n = yield this.os.set(op) + var n = yield this.os.put(op) return function () { if (n != null) { n = n.next() @@ -505,10 +516,16 @@ class AbstractTransaction { yield this.os.delete(id) } * setState (state) { - this.ss.set({ + var val = { id: [state.user], clock: state.clock - }) + } + // TODO: find a way to skip this step.. (after implementing some dbs..) + if (yield this.ss.find([state.user])) { + yield this.ss.put(val) + } else { + yield this.ss.put(val) + } } * getState (user) { var n @@ -625,4 +642,4 @@ class AbstractTransaction { return op } } -Y.AbstractTransaction = AbstractTransaction +Y.Transaction = Transaction