diff --git a/examples/yjs-dist.esm b/examples/yjs-dist.esm
index 2399227a..61d3d836 100644
--- a/examples/yjs-dist.esm
+++ b/examples/yjs-dist.esm
@@ -1,7 +1,7 @@
import Y from '../src/y.js'
import yArray from '../../y-array/src/y-array.js'
-import yMap from '../../y-map/src/Map.js'
+import yMap from '../../y-map/src/y-map.js'
import yText from '../../y-text/src/Text.js'
import yXml from '../../y-xml/src/y-xml.js'
import yMemory from '../../y-memory/src/y-memory.js'
diff --git a/rollup.browser.js b/rollup.browser.js
index 6c3f1777..f43af28e 100644
--- a/rollup.browser.js
+++ b/rollup.browser.js
@@ -1,4 +1,3 @@
-import inject from 'rollup-plugin-inject'
import babel from 'rollup-plugin-babel'
import uglify from 'rollup-plugin-uglify'
import nodeResolve from 'rollup-plugin-node-resolve'
@@ -16,12 +15,7 @@ export default {
browser: true
}),
commonjs(),
- babel({
- runtimeHelpers: true
- }),
- inject({
- regeneratorRuntime: 'regenerator-runtime'
- }),
+ babel(),
uglify({
output: {
comments: function (node, comment) {
diff --git a/rollup.test.js b/rollup.test.js
index c74129e6..e0dbe22d 100644
--- a/rollup.test.js
+++ b/rollup.test.js
@@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
import multiEntry from 'rollup-plugin-multi-entry'
export default {
- entry: 'test/y-xml.tests.js',
+ entry: 'test/*.js',
moduleName: 'y-tests',
format: 'umd',
plugins: [
diff --git a/src/Connector.js b/src/Connector.js
index 46fc263f..38b08231 100644
--- a/src/Connector.js
+++ b/src/Connector.js
@@ -105,6 +105,7 @@ export default function extendConnector (Y/* :any */) {
}
}
}
+
userJoined (user, role, auth) {
if (role == null) {
throw new Error('You must specify the role of the joined user!')
@@ -133,6 +134,7 @@ export default function extendConnector (Y/* :any */) {
}
this._syncWithUser(user)
}
+
// Execute a function _when_ we are connected.
// If not connected, wait until connected
whenSynced (f) {
@@ -142,18 +144,20 @@ export default function extendConnector (Y/* :any */) {
this.whenSyncedListeners.push(f)
}
}
+
_syncWithUser (userid) {
if (this.role === 'slave') {
return // "The current sync has not finished or this is controlled by a master!"
}
sendSyncStep1(this, userid)
}
+
_fireIsSyncedListeners () {
this.y.db.whenTransactionsFinished().then(() => {
if (!this.isSynced) {
this.isSynced = true
// It is safer to remove this!
- // TODO: remove: yield * this.garbageCollectAfterSync()
+ // TODO: remove: this.garbageCollectAfterSync()
// call whensynced listeners
for (var f of this.whenSyncedListeners) {
f()
@@ -162,6 +166,7 @@ export default function extendConnector (Y/* :any */) {
}
})
}
+
send (uid, buffer) {
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - please don\'t use this method to send custom messages')
@@ -169,6 +174,7 @@ export default function extendConnector (Y/* :any */) {
this.log('%s: Send \'%y\' to %s', this.userId, buffer, uid)
this.logMessage('Message: %Y', buffer)
}
+
broadcast (buffer) {
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - please don\'t use this method to send custom messages')
@@ -176,6 +182,7 @@ export default function extendConnector (Y/* :any */) {
this.log('%s: Broadcast \'%y\'', this.userId, buffer)
this.logMessage('Message: %Y', buffer)
}
+
/*
Buffer operations, and broadcast them when ready.
*/
@@ -207,6 +214,7 @@ export default function extendConnector (Y/* :any */) {
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
}
}
+
/*
You received a raw message, and you know that it is intended for Yjs. Then call this function.
*/
@@ -224,14 +232,11 @@ export default function extendConnector (Y/* :any */) {
encoder.writeVarString(roomname)
let messageType = decoder.readVarString()
let senderConn = this.connections.get(sender)
-
this.log('%s: Receive \'%s\' from %s', this.userId, messageType, sender)
this.logMessage('Message: %Y', buffer)
-
if (senderConn == null && !skipAuth) {
throw new Error('Received message from unknown peer!')
}
-
if (messageType === 'sync step 1' || messageType === 'sync step 2') {
let auth = decoder.readVarUint()
if (senderConn.auth == null) {
@@ -284,97 +289,6 @@ export default function extendConnector (Y/* :any */) {
this._fireIsSyncedListeners()
}
}
-
- /*
- Currently, the HB encodes operations as JSON. For the moment I want to keep it
- that way. Maybe we support encoding in the HB as XML in the future, but for now I don't want
- too much overhead. Y is very likely to get changed a lot in the future
-
- Because we don't want to encode JSON as string (with character escaping, wich makes it pretty much unreadable)
- we encode the JSON as XML.
-
- When the HB support encoding as XML, the format should look pretty much like this.
-
- does not support primitive values as array elements
- expects an ltx (less than xml) object
- */
- parseMessageFromXml (m/* :any */) {
- function parseArray (node) {
- for (var n of node.children) {
- if (n.getAttribute('isArray') === 'true') {
- return parseArray(n)
- } else {
- return parseObject(n)
- }
- }
- }
- function parseObject (node/* :any */) {
- var json = {}
- for (var attrName in node.attrs) {
- var value = node.attrs[attrName]
- var int = parseInt(value, 10)
- if (isNaN(int) || ('' + int) !== value) {
- json[attrName] = value
- } else {
- json[attrName] = int
- }
- }
- for (var n/* :any */ in node.children) {
- var name = n.name
- if (n.getAttribute('isArray') === 'true') {
- json[name] = parseArray(n)
- } else {
- json[name] = parseObject(n)
- }
- }
- return json
- }
- parseObject(m)
- }
- /*
- encode message in xml
- we use string because Strophe only accepts an "xml-string"..
- So {a:4,b:{c:5}} will look like
-
-
-
- m - ltx element
- json - Object
- */
- encodeMessageToXml (msg, obj) {
- // attributes is optional
- function encodeObject (m, json) {
- for (var name in json) {
- var value = json[name]
- if (name == null) {
- // nop
- } else if (value.constructor === Object) {
- encodeObject(m.c(name), value)
- } else if (value.constructor === Array) {
- encodeArray(m.c(name), value)
- } else {
- m.setAttribute(name, value)
- }
- }
- }
- function encodeArray (m, array) {
- m.setAttribute('isArray', 'true')
- for (var e of array) {
- if (e.constructor === Object) {
- encodeObject(m.c('array-element'), e)
- } else {
- encodeArray(m.c('array-element'), e)
- }
- }
- }
- if (obj.constructor === Object) {
- encodeObject(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
- } else if (obj.constructor === Array) {
- encodeArray(msg.c('y', { xmlns: 'http://y.ninja/connector-stanza' }), obj)
- } else {
- throw new Error("I can't encode this json!")
- }
- }
}
Y.AbstractConnector = AbstractConnector
}
diff --git a/src/Database.js b/src/Database.js
index cb2f5601..8cac24f6 100644
--- a/src/Database.js
+++ b/src/Database.js
@@ -85,11 +85,11 @@ export default function extendDatabase (Y /* :any */) {
console.warn('gc should be empty when not synced!')
}
return new Promise((resolve) => {
- os.requestTransaction(function * () {
+ os.requestTransaction(function () {
if (os.y.connector != null && os.y.connector.isSynced) {
for (var i = 0; i < os.gc2.length; i++) {
var oid = os.gc2[i]
- yield * this.garbageCollectOperation(oid)
+ this.garbageCollectOperation(oid)
}
os.gc2 = os.gc1
os.gc1 = []
@@ -197,15 +197,15 @@ export default function extendDatabase (Y /* :any */) {
this.gc = false
this.gcTimeout = -1
return new Promise(function (resolve) {
- self.requestTransaction(function * () {
+ self.requestTransaction(function () {
var ungc /* :Array */ = self.gc1.concat(self.gc2)
self.gc1 = []
self.gc2 = []
for (var i = 0; i < ungc.length; i++) {
- var op = yield * this.getOperation(ungc[i])
+ var op = this.getOperation(ungc[i])
if (op != null) {
delete op.gc
- yield * this.setOperation(op)
+ this.setOperation(op)
}
}
resolve()
@@ -224,7 +224,7 @@ export default function extendDatabase (Y /* :any */) {
returns true iff op was added to GC
*/
- * addToGarbageCollector (op, left) {
+ addToGarbageCollector (op, left) {
if (
op.gc == null &&
op.deleted === true &&
@@ -235,12 +235,12 @@ export default function extendDatabase (Y /* :any */) {
if (left != null && left.deleted === true) {
gc = true
} else if (op.content != null && op.content.length > 1) {
- op = yield * this.getInsertionCleanStart([op.id[0], op.id[1] + 1])
+ op = this.getInsertionCleanStart([op.id[0], op.id[1] + 1])
gc = true
}
if (gc) {
op.gc = true
- yield * this.setOperation(op)
+ this.setOperation(op)
this.store.queueGarbageCollector(op.id)
return true
}
@@ -265,7 +265,7 @@ export default function extendDatabase (Y /* :any */) {
}
}
}
- * destroy () {
+ destroy () {
clearTimeout(this.gcInterval)
this.gcInterval = null
this.stopRepairCheck()
@@ -274,9 +274,9 @@ export default function extendDatabase (Y /* :any */) {
if (!this.userIdPromise.inProgress) {
this.userIdPromise.inProgress = true
var self = this
- self.requestTransaction(function * () {
+ self.requestTransaction(function () {
self.userId = userId
- var state = yield * this.getState(userId)
+ var state = this.getState(userId)
self.opClock = state.clock
self.userIdPromise.resolve(userId)
})
@@ -355,7 +355,7 @@ export default function extendDatabase (Y /* :any */) {
this.listenersByIdRequestPending = true
var store = this
- this.requestTransaction(function * () {
+ this.requestTransaction(function () {
var exeNow = store.listenersByIdExecuteNow
store.listenersByIdExecuteNow = []
@@ -366,7 +366,7 @@ export default function extendDatabase (Y /* :any */) {
for (let key = 0; key < exeNow.length; key++) {
let o = exeNow[key].op
- yield * store.tryExecute.call(this, o)
+ store.tryExecute.call(this, o)
}
for (var sid in ls) {
@@ -374,9 +374,9 @@ export default function extendDatabase (Y /* :any */) {
var id = JSON.parse(sid)
var op
if (typeof id[1] === 'string') {
- op = yield * this.getOperation(id)
+ op = this.getOperation(id)
} else {
- op = yield * this.getInsertion(id)
+ op = this.getInsertion(id)
}
if (op == null) {
store.listenersById[sid] = l
@@ -385,7 +385,7 @@ export default function extendDatabase (Y /* :any */) {
let listener = l[i]
let o = listener.op
if (--listener.missing === 0) {
- yield * store.tryExecute.call(this, o)
+ store.tryExecute.call(this, o)
}
}
}
@@ -402,15 +402,15 @@ export default function extendDatabase (Y /* :any */) {
addOperation: any;
whenOperationsExist: any;
*/
- * tryExecute (op) {
- this.store.addToDebug('yield * this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
+ tryExecute (op) {
+ this.store.addToDebug('this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
if (op.struct === 'Delete') {
- yield * Y.Struct.Delete.execute.call(this, op)
+ Y.Struct.Delete.execute.call(this, op)
// this is now called in Transaction.deleteOperation!
- // yield * this.store.operationAdded(this, op)
+ // this.store.operationAdded(this, op)
} else {
// check if this op was defined
- var defined = yield * this.getInsertion(op.id)
+ var defined = this.getInsertion(op.id)
while (defined != null && defined.content != null) {
// check if this op has a longer content in the case it is defined
if (defined.id[1] + defined.content.length < op.id[1] + op.content.length) {
@@ -419,23 +419,23 @@ export default function extendDatabase (Y /* :any */) {
op.id = [op.id[0], op.id[1] + overlapSize]
op.left = Y.utils.getLastId(defined)
op.origin = op.left
- defined = yield * this.getOperation(op.id) // getOperation suffices here
+ defined = this.getOperation(op.id) // getOperation suffices here
} else {
break
}
}
if (defined == null) {
var opid = op.id
- var isGarbageCollected = yield * this.isGarbageCollected(opid)
+ var isGarbageCollected = this.isGarbageCollected(opid)
if (!isGarbageCollected) {
// TODO: reduce number of get / put calls for op ..
- yield * Y.Struct[op.struct].execute.call(this, op)
- yield * this.addOperation(op)
- yield * this.store.operationAdded(this, op)
+ Y.Struct[op.struct].execute.call(this, op)
+ this.addOperation(op)
+ this.store.operationAdded(this, op)
// operationAdded can change op..
- op = yield * this.getOperation(opid)
+ op = this.getOperation(opid)
// if insertion, try to combine with left
- yield * this.tryCombineWithLeft(op)
+ this.tryCombineWithLeft(op)
}
}
}
@@ -452,15 +452,15 @@ export default function extendDatabase (Y /* :any */) {
* Always:
* * Call type
*/
- * operationAdded (transaction, op) {
+ operationAdded (transaction, op) {
if (op.struct === 'Delete') {
var type = this.initializedTypes[JSON.stringify(op.targetParent)]
if (type != null) {
- yield * type._changed(transaction, op)
+ type._changed(transaction, op)
}
} else {
// increase SS
- yield * transaction.updateState(op.id[0])
+ transaction.updateState(op.id[0])
var opLen = op.content != null ? op.content.length : 1
for (let i = 0; i < opLen; i++) {
// notify whenOperation listeners (by id)
@@ -480,9 +480,9 @@ export default function extendDatabase (Y /* :any */) {
// if parent is deleted, mark as gc'd and return
if (op.parent != null) {
- var parentIsDeleted = yield * transaction.isDeleted(op.parent)
+ var parentIsDeleted = transaction.isDeleted(op.parent)
if (parentIsDeleted) {
- yield * transaction.deleteList(op.id)
+ transaction.deleteList(op.id)
return
}
}
@@ -490,7 +490,7 @@ export default function extendDatabase (Y /* :any */) {
// notify parent, if it was instanciated as a custom type
if (t != null) {
let o = Y.utils.copyOperation(op)
- yield * t._changed(transaction, o)
+ t._changed(transaction, o)
}
if (!op.deleted) {
// Delete if DS says this is actually deleted
@@ -499,13 +499,13 @@ export default function extendDatabase (Y /* :any */) {
// TODO: !! console.log('TODO: change this before commiting')
for (let i = 0; i < len; i++) {
var id = [startId[0], startId[1] + i]
- var opIsDeleted = yield * transaction.isDeleted(id)
+ var opIsDeleted = transaction.isDeleted(id)
if (opIsDeleted) {
var delop = {
struct: 'Delete',
target: id
}
- yield * this.tryExecute.call(transaction, delop)
+ this.tryExecute.call(transaction, delop)
}
}
}
@@ -528,6 +528,7 @@ export default function extendDatabase (Y /* :any */) {
return Promise.resolve()
}
}
+
// Check if there is another transaction request.
// * the last transaction is always a flush :)
getNextRequest () {
@@ -542,8 +543,8 @@ export default function extendDatabase (Y /* :any */) {
return null
} else {
this.transactionIsFlushed = true
- return function * () {
- yield * this.flush()
+ return function () {
+ this.flush()
}
}
} else {
@@ -570,13 +571,13 @@ export default function extendDatabase (Y /* :any */) {
Init type. This is called when a remote operation is retrieved, and transformed to a type
TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
*/
- * initType (id, args) {
+ initType (id, args) {
var sid = JSON.stringify(id)
var t = this.store.initializedTypes[sid]
if (t == null) {
- var op/* :MapStruct | ListStruct */ = yield * this.getOperation(id)
+ var op/* :MapStruct | ListStruct */ = this.getOperation(id)
if (op != null) {
- t = yield * Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
+ t = Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
this.store.initializedTypes[sid] = t
}
}
@@ -591,11 +592,11 @@ export default function extendDatabase (Y /* :any */) {
var op = Y.Struct[structname].create(id, typedefinition[1])
op.type = typedefinition[0].name
- this.requestTransaction(function * () {
+ this.requestTransaction(function () {
if (op.id[0] === 0xFFFFFF) {
- yield * this.setOperation(op)
+ this.setOperation(op)
} else {
- yield * this.applyCreatedOperations([op])
+ this.applyCreatedOperations([op])
}
})
var t = Y[op.type].typeDefinition.createType(this, op, typedefinition[1])
diff --git a/src/Database.spec.js b/src/Database.spec.js
deleted file mode 100644
index 353b2880..00000000
--- a/src/Database.spec.js
+++ /dev/null
@@ -1,354 +0,0 @@
-/* global async, databases, describe, beforeEach, afterEach */
-/* eslint-env browser,jasmine,console */
-'use strict'
-
-var Y = require('./SpecHelper.js')
-
-for (let database of databases) {
- describe(`Database (${database})`, function () {
- var store
- describe('DeleteStore', function () {
- describe('Basic', function () {
- beforeEach(function () {
- store = new Y[database](null, {
- gcTimeout: -1,
- namespace: 'testing'
- })
- })
- afterEach(function (done) {
- store.requestTransaction(function * () {
- yield * this.store.destroy()
- done()
- })
- })
- it('Deleted operation is deleted', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.markDeleted(['u1', 10], 1)
- expect(yield * this.isDeleted(['u1', 10])).toBeTruthy()
- expect(yield * this.getDeleteSet()).toEqual({'u1': [[10, 1, false]]})
- done()
- })
- }))
- it('Deleted operation extends other deleted operation', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.markDeleted(['u1', 10], 1)
- yield * this.markDeleted(['u1', 11], 1)
- expect(yield * this.isDeleted(['u1', 10])).toBeTruthy()
- expect(yield * this.isDeleted(['u1', 11])).toBeTruthy()
- expect(yield * this.getDeleteSet()).toEqual({'u1': [[10, 2, false]]})
- done()
- })
- }))
- it('Deleted operation extends other deleted operation', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.markDeleted(['0', 3], 1)
- yield * this.markDeleted(['0', 4], 1)
- yield * this.markDeleted(['0', 2], 1)
- expect(yield * this.getDeleteSet()).toEqual({'0': [[2, 3, false]]})
- done()
- })
- }))
- it('Debug #1', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.markDeleted(['166', 0], 1)
- yield * this.markDeleted(['166', 2], 1)
- yield * this.markDeleted(['166', 0], 1)
- yield * this.markDeleted(['166', 2], 1)
- yield * this.markGarbageCollected(['166', 2], 1)
- yield * this.markDeleted(['166', 1], 1)
- yield * this.markDeleted(['166', 3], 1)
- yield * this.markGarbageCollected(['166', 3], 1)
- yield * this.markDeleted(['166', 0], 1)
- expect(yield * this.getDeleteSet()).toEqual({'166': [[0, 2, false], [2, 2, true]]})
- done()
- })
- }))
- it('Debug #2', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.markDeleted(['293', 0], 1)
- yield * this.markDeleted(['291', 2], 1)
- yield * this.markDeleted(['291', 2], 1)
- yield * this.markGarbageCollected(['293', 0], 1)
- yield * this.markDeleted(['293', 1], 1)
- yield * this.markGarbageCollected(['291', 2], 1)
- expect(yield * this.getDeleteSet()).toEqual({'291': [[2, 1, true]], '293': [[0, 1, true], [1, 1, false]]})
- done()
- })
- }))
- it('Debug #3', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.markDeleted(['581', 0], 1)
- yield * this.markDeleted(['581', 1], 1)
- yield * this.markDeleted(['580', 0], 1)
- yield * this.markDeleted(['580', 0], 1)
- yield * this.markGarbageCollected(['581', 0], 1)
- yield * this.markDeleted(['581', 2], 1)
- yield * this.markDeleted(['580', 1], 1)
- yield * this.markDeleted(['580', 2], 1)
- yield * this.markDeleted(['580', 1], 1)
- yield * this.markDeleted(['580', 2], 1)
- yield * this.markGarbageCollected(['581', 2], 1)
- yield * this.markGarbageCollected(['581', 1], 1)
- yield * this.markGarbageCollected(['580', 1], 1)
- expect(yield * this.getDeleteSet()).toEqual({'580': [[0, 1, false], [1, 1, true], [2, 1, false]], '581': [[0, 3, true]]})
- done()
- })
- }))
- it('Debug #4', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.markDeleted(['544', 0], 1)
- yield * this.markDeleted(['543', 2], 1)
- yield * this.markDeleted(['544', 0], 1)
- yield * this.markDeleted(['543', 2], 1)
- yield * this.markGarbageCollected(['544', 0], 1)
- yield * this.markDeleted(['545', 1], 1)
- yield * this.markDeleted(['543', 4], 1)
- yield * this.markDeleted(['543', 3], 1)
- yield * this.markDeleted(['544', 1], 1)
- yield * this.markDeleted(['544', 2], 1)
- yield * this.markDeleted(['544', 1], 1)
- yield * this.markDeleted(['544', 2], 1)
- yield * this.markGarbageCollected(['543', 2], 1)
- yield * this.markGarbageCollected(['543', 4], 1)
- yield * this.markGarbageCollected(['544', 2], 1)
- yield * this.markGarbageCollected(['543', 3], 1)
- expect(yield * this.getDeleteSet()).toEqual({'543': [[2, 3, true]], '544': [[0, 1, true], [1, 1, false], [2, 1, true]], '545': [[1, 1, false]]})
- done()
- })
- }))
- it('Debug #5', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
- expect(yield * this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 1, true], [1, 3, false]]})
- yield * this.applyDeleteSet({'16': [[1, 2, false]], '17': [[0, 4, true]]})
- expect(yield * this.getDeleteSet()).toEqual({'16': [[1, 2, false]], '17': [[0, 4, true]]})
- done()
- })
- }))
- it('Debug #6', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.applyDeleteSet({'40': [[0, 3, false]]})
- expect(yield * this.getDeleteSet()).toEqual({'40': [[0, 3, false]]})
- yield * this.applyDeleteSet({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
- expect(yield * this.getDeleteSet()).toEqual({'39': [[2, 2, false]], '40': [[0, 1, true], [1, 2, false]], '41': [[2, 1, false]]})
- done()
- })
- }))
- it('Debug #7', async(function * (done) {
- store.requestTransaction(function * () {
- yield * this.markDeleted(['9', 2], 1)
- yield * this.markDeleted(['11', 2], 1)
- yield * this.markDeleted(['11', 4], 1)
- yield * this.markDeleted(['11', 1], 1)
- yield * this.markDeleted(['9', 4], 1)
- yield * this.markDeleted(['10', 0], 1)
- yield * this.markGarbageCollected(['11', 2], 1)
- yield * this.markDeleted(['11', 2], 1)
- yield * this.markGarbageCollected(['11', 3], 1)
- yield * this.markDeleted(['11', 3], 1)
- yield * this.markDeleted(['11', 3], 1)
- yield * this.markDeleted(['9', 4], 1)
- yield * this.markDeleted(['10', 0], 1)
- yield * this.markGarbageCollected(['11', 1], 1)
- yield * this.markDeleted(['11', 1], 1)
- expect(yield * this.getDeleteSet()).toEqual({'9': [[2, 1, false], [4, 1, false]], '10': [[0, 1, false]], '11': [[1, 3, true], [4, 1, false]]})
- done()
- })
- }))
- })
- })
- describe('OperationStore', function () {
- describe('Basic Tests', function () {
- beforeEach(function () {
- store = new Y[database](null, {
- gcTimeout: -1,
- namespace: 'testing'
- })
- })
- afterEach(function (done) {
- store.requestTransaction(function * () {
- yield * this.store.destroy()
- done()
- })
- })
- it('debug #1', function (done) {
- store.requestTransaction(function * () {
- yield * this.os.put({id: [2]})
- yield * this.os.put({id: [0]})
- yield * this.os.delete([2])
- yield * this.os.put({id: [1]})
- expect(yield * this.os.find([0])).toBeTruthy()
- expect(yield * this.os.find([1])).toBeTruthy()
- expect(yield * this.os.find([2])).toBeFalsy()
- done()
- })
- })
- it('can add&retrieve 5 elements', function (done) {
- store.requestTransaction(function * () {
- 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')
- expect((yield * this.os.find([4])).val).toEqual('four')
- expect((yield * this.os.find([5])).val).toEqual('five')
- done()
- })
- })
- it('5 elements do not exist anymore after deleting them', function (done) {
- store.requestTransaction(function * () {
- 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])
- expect(yield * this.os.find([3])).not.toBeTruthy()
- yield * this.os.delete([2])
- expect(yield * this.os.find([2])).not.toBeTruthy()
- yield * this.os.delete([1])
- expect(yield * this.os.find([1])).not.toBeTruthy()
- yield * this.os.delete([5])
- expect(yield * this.os.find([5])).not.toBeTruthy()
- done()
- })
- })
- })
- var numberOfOSTests = 1000
- describe(`Random Tests - after adding&deleting (0.8/0.2) ${numberOfOSTests} times`, function () {
- var elements = []
- beforeAll(function (done) {
- store = new Y[database](null, {
- gcTimeout: -1,
- namespace: 'testing'
- })
- store.requestTransaction(function * () {
- for (var i = 0; i < numberOfOSTests; i++) {
- var r = Math.random()
- if (r < 0.8) {
- var obj = [Math.floor(Math.random() * numberOfOSTests * 10000)]
- if (!(yield * this.os.find(obj))) {
- elements.push(obj)
- yield * this.os.put({id: obj})
- }
- } else if (elements.length > 0) {
- var elemid = Math.floor(Math.random() * elements.length)
- var elem = elements[elemid]
- elements = elements.filter(function (e) {
- return !Y.utils.compareIds(e, elem)
- })
- yield * this.os.delete(elem)
- }
- }
- done()
- })
- })
- afterAll(function (done) {
- store.requestTransaction(function * () {
- yield * this.store.destroy()
- done()
- })
- })
- it('can find every object', function (done) {
- store.requestTransaction(function * () {
- for (var id of elements) {
- expect((yield * this.os.find(id)).id).toEqual(id)
- }
- done()
- })
- })
-
- it('can find every object with lower bound search', function (done) {
- store.requestTransaction(function * () {
- for (var id of elements) {
- var e = yield * this.os.findWithLowerBound(id)
- expect(e.id).toEqual(id)
- }
- done()
- })
- })
-
- it('iterating over a tree with lower bound yields the right amount of results', function (done) {
- var lowerBound = elements[Math.floor(Math.random() * elements.length)]
- var expectedResults = elements.filter(function (e, pos) {
- return (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) && elements.indexOf(e) === pos
- }).length
-
- var actualResults = 0
- store.requestTransaction(function * () {
- yield * this.os.iterate(this, lowerBound, null, function * (val) {
- expect(val).toBeDefined()
- actualResults++
- })
- expect(expectedResults).toEqual(actualResults)
- done()
- })
- })
-
- it('iterating over a tree without bounds yield the right amount of results', function (done) {
- var lowerBound = null
- var expectedResults = elements.filter(function (e, pos) {
- return elements.indexOf(e) === pos
- }).length
- var actualResults = 0
- store.requestTransaction(function * () {
- yield * this.os.iterate(this, lowerBound, null, function * (val) {
- expect(val).toBeDefined()
- actualResults++
- })
- expect(expectedResults).toEqual(actualResults)
- done()
- })
- })
-
- it('iterating over a tree with upper bound yields the right amount of results', function (done) {
- var upperBound = elements[Math.floor(Math.random() * elements.length)]
- var expectedResults = elements.filter(function (e, pos) {
- return (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) && elements.indexOf(e) === pos
- }).length
-
- var actualResults = 0
- store.requestTransaction(function * () {
- yield * this.os.iterate(this, null, upperBound, function * (val) {
- expect(val).toBeDefined()
- actualResults++
- })
- expect(expectedResults).toEqual(actualResults)
- done()
- })
- })
-
- it('iterating over a tree with upper and lower bounds yield the right amount of results', function (done) {
- var b1 = elements[Math.floor(Math.random() * elements.length)]
- var b2 = elements[Math.floor(Math.random() * elements.length)]
- var upperBound, lowerBound
- if (Y.utils.smaller(b1, b2)) {
- lowerBound = b1
- upperBound = b2
- } else {
- lowerBound = b2
- upperBound = b1
- }
- var expectedResults = elements.filter(function (e, pos) {
- return (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) &&
- (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) && elements.indexOf(e) === pos
- }).length
- var actualResults = 0
- store.requestTransaction(function * () {
- yield * this.os.iterate(this, lowerBound, upperBound, function * (val) {
- expect(val).toBeDefined()
- actualResults++
- })
- expect(expectedResults).toEqual(actualResults)
- done()
- })
- })
- })
- })
- })
-}
diff --git a/src/MessageHandler.js b/src/MessageHandler.js
index c62280eb..6a01c2e4 100644
--- a/src/MessageHandler.js
+++ b/src/MessageHandler.js
@@ -58,7 +58,7 @@ export function computeMessageUpdate (decoder, encoder, conn) {
}
export function sendSyncStep1 (conn, syncUser) {
- conn.y.db.requestTransaction(function * () {
+ conn.y.db.requestTransaction(function () {
let encoder = new BinaryEncoder()
encoder.writeVarString(conn.opts.room || '')
encoder.writeVarString('sync step 1')
@@ -66,7 +66,7 @@ export function sendSyncStep1 (conn, syncUser) {
encoder.writeVarUint(conn.protocolVersion)
let preferUntransformed = conn.preferUntransformed && this.os.length === 0 // TODO: length may not be defined
encoder.writeUint8(preferUntransformed ? 1 : 0)
- yield * this.writeStateSet(encoder)
+ this.writeStateSet(encoder)
conn.send(syncUser, encoder.createBuffer())
})
}
@@ -99,19 +99,19 @@ export function computeMessageSyncStep1 (decoder, encoder, conn, senderConn, sen
return conn.y.db.whenTransactionsFinished().then(() => {
// send sync step 2
- conn.y.db.requestTransaction(function * () {
+ conn.y.db.requestTransaction(function () {
encoder.writeVarString('sync step 2')
encoder.writeVarString(conn.authInfo || '')
if (preferUntransformed) {
encoder.writeUint8(1)
- yield * this.writeOperationsUntransformed(encoder)
+ this.writeOperationsUntransformed(encoder)
} else {
encoder.writeUint8(0)
- yield * this.writeOperations(encoder, decoder)
+ this.writeOperations(encoder, decoder)
}
- yield * this.writeDeleteSet(encoder)
+ this.writeDeleteSet(encoder)
conn.send(senderConn.uid, encoder.createBuffer())
senderConn.receivedSyncStep2 = true
})
@@ -174,17 +174,17 @@ export function computeMessageSyncStep2 (decoder, encoder, conn, senderConn, sen
let defer = senderConn.syncStep2
// apply operations first
- db.requestTransaction(function * () {
+ db.requestTransaction(function () {
let osUntransformed = decoder.readUint8()
if (osUntransformed === 1) {
- yield * this.applyOperationsUntransformed(decoder)
+ this.applyOperationsUntransformed(decoder)
} else {
this.store.applyOperations(decoder)
}
})
// then apply ds
- db.requestTransaction(function * () {
- yield * this.applyDeleteSet(decoder)
+ db.requestTransaction(function () {
+ this.applyDeleteSet(decoder)
})
return db.whenTransactionsFinished().then(() => {
conn._setSyncedWith(sender)
diff --git a/src/Persistence.js b/src/Persistence.js
index c5dbd681..7c961ed2 100644
--- a/src/Persistence.js
+++ b/src/Persistence.js
@@ -8,9 +8,11 @@ export default function extendPersistence (Y) {
this.saveOperationsBuffer = []
this.log = Y.debug('y:persistence')
}
+
saveToMessageQueue (binary) {
this.log('Room %s: Save message to message queue', this.y.options.connector.room)
}
+
saveOperations (ops) {
ops = ops.map(function (op) {
return Y.Struct[op.struct].encode(op)
@@ -39,5 +41,6 @@ export default function extendPersistence (Y) {
}
}
}
+
Y.AbstractPersistence = AbstractPersistence
}
diff --git a/src/RedBlackTree.js b/src/RedBlackTree.js
new file mode 100644
index 00000000..40483e99
--- /dev/null
+++ b/src/RedBlackTree.js
@@ -0,0 +1,506 @@
+
+export default function extendRBTree (Y) {
+ class N {
+ // A created node is always red!
+ constructor (val) {
+ this.val = val
+ this.color = true
+ this._left = null
+ this._right = null
+ this._parent = null
+ if (val.id === null) {
+ throw new Error('You must define id!')
+ }
+ }
+ isRed () { return this.color }
+ isBlack () { return !this.color }
+ redden () { this.color = true; return this }
+ blacken () { this.color = false; return this }
+ get grandparent () {
+ return this.parent.parent
+ }
+ get parent () {
+ return this._parent
+ }
+ get sibling () {
+ return (this === this.parent.left)
+ ? this.parent.right : this.parent.left
+ }
+ get left () {
+ return this._left
+ }
+ get right () {
+ return this._right
+ }
+ set left (n) {
+ if (n !== null) {
+ n._parent = this
+ }
+ this._left = n
+ }
+ set right (n) {
+ if (n !== null) {
+ n._parent = this
+ }
+ this._right = n
+ }
+ rotateLeft (tree) {
+ var parent = this.parent
+ var newParent = this.right
+ var newRight = this.right.left
+ newParent.left = this
+ this.right = newRight
+ if (parent === null) {
+ tree.root = newParent
+ newParent._parent = null
+ } else if (parent.left === this) {
+ parent.left = newParent
+ } else if (parent.right === this) {
+ parent.right = newParent
+ } else {
+ throw new Error('The elements are wrongly connected!')
+ }
+ }
+ next () {
+ if (this.right !== null) {
+ // search the most left node in the right tree
+ var o = this.right
+ while (o.left !== null) {
+ o = o.left
+ }
+ return o
+ } else {
+ var p = this
+ while (p.parent !== null && p !== p.parent.left) {
+ p = p.parent
+ }
+ return p.parent
+ }
+ }
+ prev () {
+ if (this.left !== null) {
+ // search the most right node in the left tree
+ var o = this.left
+ while (o.right !== null) {
+ o = o.right
+ }
+ return o
+ } else {
+ var p = this
+ while (p.parent !== null && p !== p.parent.right) {
+ p = p.parent
+ }
+ return p.parent
+ }
+ }
+ rotateRight (tree) {
+ var parent = this.parent
+ var newParent = this.left
+ var newLeft = this.left.right
+ newParent.right = this
+ this.left = newLeft
+ if (parent === null) {
+ tree.root = newParent
+ newParent._parent = null
+ } else if (parent.left === this) {
+ parent.left = newParent
+ } else if (parent.right === this) {
+ parent.right = newParent
+ } else {
+ throw new Error('The elements are wrongly connected!')
+ }
+ }
+ getUncle () {
+ // we can assume that grandparent exists when this is called!
+ if (this.parent === this.parent.parent.left) {
+ return this.parent.parent.right
+ } else {
+ return this.parent.parent.left
+ }
+ }
+ }
+
+ class RBTree {
+ constructor () {
+ this.root = null
+ this.length = 0
+ }
+ findNext (id) {
+ return this.findWithLowerBound([id[0], id[1] + 1])
+ }
+ findPrev (id) {
+ return this.findWithUpperBound([id[0], id[1] - 1])
+ }
+ findNodeWithLowerBound (from) {
+ if (from === void 0) {
+ throw new Error('You must define from!')
+ }
+ var o = this.root
+ if (o === null) {
+ return null
+ } else {
+ while (true) {
+ if ((from === null || Y.utils.smaller(from, o.val.id)) && o.left !== null) {
+ // o is included in the bound
+ // try to find an element that is closer to the bound
+ o = o.left
+ } else if (from !== null && Y.utils.smaller(o.val.id, from)) {
+ // o is not within the bound, maybe one of the right elements is..
+ if (o.right !== null) {
+ o = o.right
+ } else {
+ // there is no right element. Search for the next bigger element,
+ // this should be within the bounds
+ return o.next()
+ }
+ } else {
+ return o
+ }
+ }
+ }
+ }
+ findNodeWithUpperBound (to) {
+ if (to === void 0) {
+ throw new Error('You must define from!')
+ }
+ var o = this.root
+ if (o === null) {
+ return null
+ } else {
+ while (true) {
+ if ((to === null || Y.utils.smaller(o.val.id, to)) && o.right !== null) {
+ // o is included in the bound
+ // try to find an element that is closer to the bound
+ o = o.right
+ } else if (to !== null && Y.utils.smaller(to, o.val.id)) {
+ // o is not within the bound, maybe one of the left elements is..
+ if (o.left !== null) {
+ o = o.left
+ } else {
+ // there is no left element. Search for the prev smaller element,
+ // this should be within the bounds
+ return o.prev()
+ }
+ } else {
+ return o
+ }
+ }
+ }
+ }
+ findSmallestNode () {
+ var o = this.root
+ while (o != null && o.left != null) {
+ o = o.left
+ }
+ return o
+ }
+ findWithLowerBound (from) {
+ var n = this.findNodeWithLowerBound(from)
+ return n == null ? null : n.val
+ }
+ findWithUpperBound (to) {
+ var n = this.findNodeWithUpperBound(to)
+ return n == null ? null : n.val
+ }
+ iterate (t, from, to, f) {
+ var o
+ if (from === null) {
+ o = this.findSmallestNode()
+ } else {
+ o = this.findNodeWithLowerBound(from)
+ }
+ while (
+ o !== null &&
+ (
+ to === null || // eslint-disable-line no-unmodified-loop-condition
+ Y.utils.smaller(o.val.id, to) ||
+ Y.utils.compareIds(o.val.id, to)
+ )
+ ) {
+ f.call(t, o.val)
+ o = o.next()
+ }
+ return true
+ }
+ logTable (from, to, filter) {
+ if (filter == null) {
+ filter = function () {
+ return true
+ }
+ }
+ if (from == null) { from = null }
+ if (to == null) { to = null }
+ var os = []
+ this.iterate(this, from, to, function (o) {
+ if (filter(o)) {
+ var o_ = {}
+ for (var key in o) {
+ if (typeof o[key] === 'object') {
+ o_[key] = JSON.stringify(o[key])
+ } else {
+ o_[key] = o[key]
+ }
+ }
+ os.push(o_)
+ }
+ })
+ if (console.table != null) {
+ console.table(os)
+ }
+ }
+ find (id) {
+ var n
+ return (n = this.findNode(id)) ? n.val : null
+ }
+ findNode (id) {
+ if (id == null || id.constructor !== Array) {
+ throw new Error('Expect id to be an array!')
+ }
+ var o = this.root
+ if (o === null) {
+ return false
+ } else {
+ while (true) {
+ if (o === null) {
+ return false
+ }
+ if (Y.utils.smaller(id, o.val.id)) {
+ o = o.left
+ } else if (Y.utils.smaller(o.val.id, id)) {
+ o = o.right
+ } else {
+ return o
+ }
+ }
+ }
+ }
+ delete (id) {
+ if (id == null || id.constructor !== Array) {
+ throw new Error('id is expected to be an Array!')
+ }
+ var d = this.findNode(id)
+ if (d == null) {
+ // throw new Error('Element does not exist!')
+ return
+ }
+ this.length--
+ if (d.left !== null && d.right !== null) {
+ // switch d with the greates element in the left subtree.
+ // o should have at most one child.
+ var o = d.left
+ // find
+ while (o.right !== null) {
+ o = o.right
+ }
+ // switch
+ d.val = o.val
+ d = o
+ }
+ // d has at most one child
+ // let n be the node that replaces d
+ var isFakeChild
+ var child = d.left || d.right
+ if (child === null) {
+ isFakeChild = true
+ child = new N({id: 0})
+ child.blacken()
+ d.right = child
+ } else {
+ isFakeChild = false
+ }
+
+ if (d.parent === null) {
+ if (!isFakeChild) {
+ this.root = child
+ child.blacken()
+ child._parent = null
+ } else {
+ this.root = null
+ }
+ return
+ } else if (d.parent.left === d) {
+ d.parent.left = child
+ } else if (d.parent.right === d) {
+ d.parent.right = child
+ } else {
+ throw new Error('Impossible!')
+ }
+ if (d.isBlack()) {
+ if (child.isRed()) {
+ child.blacken()
+ } else {
+ this._fixDelete(child)
+ }
+ }
+ this.root.blacken()
+ if (isFakeChild) {
+ if (child.parent.left === child) {
+ child.parent.left = null
+ } else if (child.parent.right === child) {
+ child.parent.right = null
+ } else {
+ throw new Error('Impossible #3')
+ }
+ }
+ }
+ _fixDelete (n) {
+ function isBlack (node) {
+ return node !== null ? node.isBlack() : true
+ }
+ function isRed (node) {
+ return node !== null ? node.isRed() : false
+ }
+ if (n.parent === null) {
+ // this can only be called after the first iteration of fixDelete.
+ return
+ }
+ // d was already replaced by the child
+ // d is not the root
+ // d and child are black
+ var sibling = n.sibling
+ if (isRed(sibling)) {
+ // make sibling the grandfather
+ n.parent.redden()
+ sibling.blacken()
+ if (n === n.parent.left) {
+ n.parent.rotateLeft(this)
+ } else if (n === n.parent.right) {
+ n.parent.rotateRight(this)
+ } else {
+ throw new Error('Impossible #2')
+ }
+ sibling = n.sibling
+ }
+ // parent, sibling, and children of n are black
+ if (n.parent.isBlack() &&
+ sibling.isBlack() &&
+ isBlack(sibling.left) &&
+ isBlack(sibling.right)
+ ) {
+ sibling.redden()
+ this._fixDelete(n.parent)
+ } else if (n.parent.isRed() &&
+ sibling.isBlack() &&
+ isBlack(sibling.left) &&
+ isBlack(sibling.right)
+ ) {
+ sibling.redden()
+ n.parent.blacken()
+ } else {
+ if (n === n.parent.left &&
+ sibling.isBlack() &&
+ isRed(sibling.left) &&
+ isBlack(sibling.right)
+ ) {
+ sibling.redden()
+ sibling.left.blacken()
+ sibling.rotateRight(this)
+ sibling = n.sibling
+ } else if (n === n.parent.right &&
+ sibling.isBlack() &&
+ isRed(sibling.right) &&
+ isBlack(sibling.left)
+ ) {
+ sibling.redden()
+ sibling.right.blacken()
+ sibling.rotateLeft(this)
+ sibling = n.sibling
+ }
+ sibling.color = n.parent.color
+ n.parent.blacken()
+ if (n === n.parent.left) {
+ sibling.right.blacken()
+ n.parent.rotateLeft(this)
+ } else {
+ sibling.left.blacken()
+ n.parent.rotateRight(this)
+ }
+ }
+ }
+ 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!')
+ }
+ var node = new N(v)
+ if (this.root !== null) {
+ var p = this.root // p abbrev. parent
+ while (true) {
+ if (Y.utils.smaller(node.val.id, p.val.id)) {
+ if (p.left === null) {
+ p.left = node
+ break
+ } else {
+ p = p.left
+ }
+ } else if (Y.utils.smaller(p.val.id, node.val.id)) {
+ if (p.right === null) {
+ p.right = node
+ break
+ } else {
+ p = p.right
+ }
+ } else {
+ p.val = node.val
+ return p
+ }
+ }
+ this._fixInsert(node)
+ } else {
+ this.root = node
+ }
+ this.length++
+ this.root.blacken()
+ return node
+ }
+ _fixInsert (n) {
+ if (n.parent === null) {
+ n.blacken()
+ return
+ } else if (n.parent.isBlack()) {
+ return
+ }
+ var uncle = n.getUncle()
+ if (uncle !== null && uncle.isRed()) {
+ // Note: parent: red, uncle: red
+ n.parent.blacken()
+ uncle.blacken()
+ n.grandparent.redden()
+ this._fixInsert(n.grandparent)
+ } else {
+ // Note: parent: red, uncle: black or null
+ // Now we transform the tree in such a way that
+ // either of these holds:
+ // 1) grandparent.left.isRed
+ // and grandparent.left.left.isRed
+ // 2) grandparent.right.isRed
+ // and grandparent.right.right.isRed
+ if (n === n.parent.right && n.parent === n.grandparent.left) {
+ n.parent.rotateLeft(this)
+ // Since we rotated and want to use the previous
+ // cases, we need to set n in such a way that
+ // n.parent.isRed again
+ n = n.left
+ } else if (n === n.parent.left && n.parent === n.grandparent.right) {
+ n.parent.rotateRight(this)
+ // see above
+ n = n.right
+ }
+ // Case 1) or 2) hold from here on.
+ // Now traverse grandparent, make parent a black node
+ // on the highest level which holds two red nodes.
+ n.parent.blacken()
+ n.grandparent.redden()
+ if (n === n.parent.left) {
+ // Case 1
+ n.grandparent.rotateRight(this)
+ } else {
+ // Case 2
+ n.grandparent.rotateLeft(this)
+ }
+ }
+ }
+ flush () {}
+ }
+
+ Y.utils.RBTree = RBTree
+}
diff --git a/src/Struct.js b/src/Struct.js
index 8935a4d9..efda733a 100644
--- a/src/Struct.js
+++ b/src/Struct.js
@@ -73,8 +73,8 @@ export default function extendStruct (Y) {
requiredOps: function (op) {
return [] // [op.target]
},
- execute: function * (op) {
- return yield * this.deleteOperation(op.target, op.length || 1)
+ execute: function (op) {
+ return this.deleteOperation(op.target, op.length || 1)
}
}
@@ -216,18 +216,18 @@ export default function extendStruct (Y) {
}
return ids
},
- getDistanceToOrigin: function * (op) {
+ getDistanceToOrigin: function (op) {
if (op.left == null) {
return 0
} else {
var d = 0
- var o = yield * this.getInsertion(op.left)
+ var o = this.getInsertion(op.left)
while (!Y.utils.matchesId(o, op.origin)) {
d++
if (o.left == null) {
break
} else {
- o = yield * this.getInsertion(o.left)
+ o = this.getInsertion(o.left)
}
}
return d
@@ -248,7 +248,7 @@ export default function extendStruct (Y) {
# case 3: $origin > $o.origin
# $this insert_position is to the left of $o (forever!)
*/
- execute: function * (op) {
+ execute: function (op) {
var i // loop counter
// during this function some ops may get split into two pieces (e.g. with getInsertionCleanEnd)
@@ -258,17 +258,17 @@ export default function extendStruct (Y) {
if (op.origin != null) { // TODO: !== instead of !=
// we save in origin that op originates in it
// we need that later when we eventually garbage collect origin (see transaction)
- var origin = yield * this.getInsertionCleanEnd(op.origin)
+ var origin = this.getInsertionCleanEnd(op.origin)
if (origin.originOf == null) {
origin.originOf = []
}
origin.originOf.push(op.id)
- yield * this.setOperation(origin)
+ this.setOperation(origin)
if (origin.right != null) {
tryToRemergeLater.push(origin.right)
}
}
- var distanceToOrigin = i = yield * Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
+ var distanceToOrigin = i = Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
// now we begin to insert op in the list of insertions..
var o
@@ -277,29 +277,29 @@ export default function extendStruct (Y) {
// find o. o is the first conflicting operation
if (op.left != null) {
- o = yield * this.getInsertionCleanEnd(op.left)
+ o = this.getInsertionCleanEnd(op.left)
if (!Y.utils.compareIds(op.left, op.origin) && o.right != null) {
// only if not added previously
tryToRemergeLater.push(o.right)
}
- o = (o.right == null) ? null : yield * this.getOperation(o.right)
+ o = (o.right == null) ? null : this.getOperation(o.right)
} else { // left == null
- parent = yield * this.getOperation(op.parent)
+ parent = this.getOperation(op.parent)
let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
- start = startId == null ? null : yield * this.getOperation(startId)
+ start = startId == null ? null : this.getOperation(startId)
o = start
}
// make sure to split op.right if necessary (also add to tryCombineWithLeft)
if (op.right != null) {
tryToRemergeLater.push(op.right)
- yield * this.getInsertionCleanStart(op.right)
+ this.getInsertionCleanStart(op.right)
}
// handle conflicts
while (true) {
if (o != null && !Y.utils.compareIds(o.id, op.right)) {
- var oOriginDistance = yield * Struct.Insert.getDistanceToOrigin.call(this, o)
+ var oOriginDistance = Struct.Insert.getDistanceToOrigin.call(this, o)
if (oOriginDistance === i) {
// case 1
if (o.id[0] < op.id[0]) {
@@ -317,7 +317,7 @@ export default function extendStruct (Y) {
}
i++
if (o.right != null) {
- o = yield * this.getInsertion(o.right)
+ o = this.getInsertion(o.right)
} else {
o = null
}
@@ -330,17 +330,17 @@ export default function extendStruct (Y) {
var left = null
var right = null
if (parent == null) {
- parent = yield * this.getOperation(op.parent)
+ parent = this.getOperation(op.parent)
}
// reconnect left and set right of op
if (op.left != null) {
- left = yield * this.getInsertion(op.left)
+ left = this.getInsertion(op.left)
// link left
op.right = left.right
left.right = op.id
- yield * this.setOperation(left)
+ this.setOperation(left)
} else {
// set op.right from parent, if necessary
op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
@@ -348,33 +348,33 @@ export default function extendStruct (Y) {
// reconnect right
if (op.right != null) {
// TODO: wanna connect right too?
- right = yield * this.getOperation(op.right)
+ right = this.getOperation(op.right)
right.left = Y.utils.getLastId(op)
// if right exists, and it is supposed to be gc'd. Remove it from the gc
if (right.gc != null) {
if (right.content != null && right.content.length > 1) {
- right = yield * this.getInsertionCleanEnd(right.id)
+ right = this.getInsertionCleanEnd(right.id)
}
this.store.removeFromGarbageCollector(right)
}
- yield * this.setOperation(right)
+ this.setOperation(right)
}
// update parents .map/start/end properties
if (op.parentSub != null) {
if (left == null) {
parent.map[op.parentSub] = op.id
- yield * this.setOperation(parent)
+ this.setOperation(parent)
}
// is a child of a map struct.
// Then also make sure that only the most left element is not deleted
// We do not call the type in this case (this is what the third parameter is for)
if (op.right != null) {
- yield * this.deleteOperation(op.right, 1, true)
+ this.deleteOperation(op.right, 1, true)
}
if (op.left != null) {
- yield * this.deleteOperation(op.id, 1, true)
+ this.deleteOperation(op.id, 1, true)
}
} else {
if (right == null || left == null) {
@@ -384,14 +384,14 @@ export default function extendStruct (Y) {
if (left == null) {
parent.start = op.id
}
- yield * this.setOperation(parent)
+ this.setOperation(parent)
}
}
// try to merge original op.left and op.origin
for (i = 0; i < tryToRemergeLater.length; i++) {
- var m = yield * this.getOperation(tryToRemergeLater[i])
- yield * this.tryCombineWithLeft(m)
+ var m = this.getOperation(tryToRemergeLater[i])
+ this.tryCombineWithLeft(m)
}
}
}
@@ -451,16 +451,16 @@ export default function extendStruct (Y) {
*/
return []
},
- execute: function * (op) {
+ execute: function (op) {
op.start = null
op.end = null
},
- ref: function * (op, pos) {
+ ref: function (op, pos) {
if (op.start == null) {
return null
}
var res = null
- var o = yield * this.getOperation(op.start)
+ var o = this.getOperation(op.start)
while (true) {
if (!o.deleted) {
@@ -468,18 +468,18 @@ export default function extendStruct (Y) {
pos--
}
if (pos >= 0 && o.right != null) {
- o = yield * this.getOperation(o.right)
+ o = this.getOperation(o.right)
} else {
break
}
}
return res
},
- map: function * (o, f) {
+ map: function (o, f) {
o = o.start
var res = []
while (o != null) { // TODO: change to != (at least some convention)
- var operation = yield * this.getOperation(o)
+ var operation = this.getOperation(o)
if (!operation.deleted) {
res.push(f(operation))
}
@@ -532,23 +532,23 @@ export default function extendStruct (Y) {
requiredOps: function () {
return []
},
- execute: function * (op) {
+ execute: function (op) {
op.start = null
op.end = null
},
/*
Get a property by name
*/
- get: function * (op, name) {
+ get: function (op, name) {
var oid = op.map[name]
if (oid != null) {
- var res = yield * this.getOperation(oid)
+ var res = this.getOperation(oid)
if (res == null || res.deleted) {
return void 0
} else if (res.opContent == null) {
return res.content[0]
} else {
- return yield * this.getType(res.opContent)
+ return this.getType(res.opContent)
}
}
}
@@ -608,7 +608,7 @@ export default function extendStruct (Y) {
requiredOps: function () {
return []
},
- execute: function * () {},
+ execute: function () {},
ref: Struct.List.ref,
map: Struct.List.map,
/*
diff --git a/src/Transaction.js b/src/Transaction.js
index bdc43cac..69fc8fd6 100644
--- a/src/Transaction.js
+++ b/src/Transaction.js
@@ -86,11 +86,11 @@ export default function extendTransaction (Y) {
* does not check for Struct.*.requiredOps()
* also broadcasts it through the connector
*/
- * applyCreatedOperations (ops) {
+ applyCreatedOperations (ops) {
var send = []
for (var i = 0; i < ops.length; i++) {
var op = ops[i]
- yield * this.store.tryExecute.call(this, op)
+ this.store.tryExecute.call(this, op)
if (op.id == null || typeof op.id[1] !== 'string') {
send.push(Y.Struct[op.struct].encode(op))
}
@@ -104,17 +104,17 @@ export default function extendTransaction (Y) {
}
}
- * deleteList (start) {
+ deleteList (start) {
while (start != null) {
- start = yield * this.getOperation(start)
+ start = this.getOperation(start)
if (!start.gc) {
start.gc = true
start.deleted = true
- yield * this.setOperation(start)
+ this.setOperation(start)
var delLength = start.content != null ? start.content.length : 1
- yield * this.markDeleted(start.id, delLength)
+ this.markDeleted(start.id, delLength)
if (start.opContent != null) {
- yield * this.deleteOperation(start.opContent)
+ this.deleteOperation(start.opContent)
}
this.store.queueGarbageCollector(start.id)
}
@@ -125,14 +125,14 @@ export default function extendTransaction (Y) {
/*
Mark an operation as deleted, and add it to the GC, if possible.
*/
- * deleteOperation (targetId, length, preventCallType) /* :Generator */ {
+ deleteOperation (targetId, length, preventCallType) /* :Generator */ {
if (length == null) {
length = 1
}
- yield * this.markDeleted(targetId, length)
+ this.markDeleted(targetId, length)
while (length > 0) {
var callType = false
- var target = yield * this.os.findWithUpperBound([targetId[0], targetId[1] + length - 1])
+ var target = this.os.findWithUpperBound([targetId[0], targetId[1] + length - 1])
var targetLength = target != null && target.content != null ? target.content.length : 1
if (target == null || target.id[0] !== targetId[0] || target.id[1] + targetLength <= targetId[1]) {
// does not exist or is not in the range of the deletion
@@ -143,12 +143,12 @@ export default function extendTransaction (Y) {
if (!target.deleted) {
if (target.id[1] < targetId[1]) {
// starts to the left of the deletion range
- target = yield * this.getInsertionCleanStart(targetId)
+ target = this.getInsertionCleanStart(targetId)
targetLength = target.content.length // must have content property!
}
if (target.id[1] + targetLength > targetId[1] + length) {
// ends to the right of the deletion range
- target = yield * this.getInsertionCleanEnd([targetId[0], targetId[1] + length - 1])
+ target = this.getInsertionCleanEnd([targetId[0], targetId[1] + length - 1])
targetLength = target.content.length
}
}
@@ -163,35 +163,35 @@ export default function extendTransaction (Y) {
// delete containing lists
if (target.start != null) {
// TODO: don't do it like this .. -.-
- yield * this.deleteList(target.start)
- // yield * this.deleteList(target.id) -- do not gc itself because this may still get referenced
+ this.deleteList(target.start)
+ // this.deleteList(target.id) -- do not gc itself because this may still get referenced
}
if (target.map != null) {
for (var name in target.map) {
- yield * this.deleteList(target.map[name])
+ this.deleteList(target.map[name])
}
// TODO: here to.. (see above)
- // yield * this.deleteList(target.id) -- see above
+ // this.deleteList(target.id) -- see above
}
if (target.opContent != null) {
- yield * this.deleteOperation(target.opContent)
+ this.deleteOperation(target.opContent)
// target.opContent = null
}
if (target.requires != null) {
for (var i = 0; i < target.requires.length; i++) {
- yield * this.deleteOperation(target.requires[i])
+ this.deleteOperation(target.requires[i])
}
}
}
var left
if (target.left != null) {
- left = yield * this.getInsertion(target.left)
+ left = this.getInsertion(target.left)
} else {
left = null
}
// set here because it was deleted and/or gc'd
- yield * this.setOperation(target)
+ this.setOperation(target)
/*
Check if it is possible to add right to the gc.
@@ -200,12 +200,12 @@ export default function extendTransaction (Y) {
*/
var right
if (target.right != null) {
- right = yield * this.getOperation(target.right)
+ right = this.getOperation(target.right)
} else {
right = null
}
if (callType && !preventCallType) {
- yield * this.store.operationAdded(this, {
+ this.store.operationAdded(this, {
struct: 'Delete',
target: target.id,
length: targetLength,
@@ -213,9 +213,9 @@ export default function extendTransaction (Y) {
})
}
// need to gc in the end!
- yield * this.store.addToGarbageCollector.call(this, target, left)
+ this.store.addToGarbageCollector.call(this, target, left)
if (right != null) {
- yield * this.store.addToGarbageCollector.call(this, right, target)
+ this.store.addToGarbageCollector.call(this, right, target)
}
}
}
@@ -223,25 +223,25 @@ export default function extendTransaction (Y) {
/*
Mark an operation as deleted&gc'd
*/
- * markGarbageCollected (id, len) {
+ markGarbageCollected (id, len) {
// this.mem.push(["gc", id]);
- this.store.addToDebug('yield * this.markGarbageCollected(', id, ', ', len, ')')
- var n = yield * this.markDeleted(id, len)
+ this.store.addToDebug('this.markGarbageCollected(', id, ', ', len, ')')
+ var n = this.markDeleted(id, len)
if (n.id[1] < id[1] && !n.gc) {
// un-extend left
var newlen = n.len - (id[1] - n.id[1])
n.len -= newlen
- yield * this.ds.put(n)
+ this.ds.put(n)
n = {id: id, len: newlen, gc: false}
- yield * this.ds.put(n)
+ this.ds.put(n)
}
// get prev&next before adding a new operation
- var prev = yield * this.ds.findPrev(id)
- var next = yield * this.ds.findNext(id)
+ var prev = this.ds.findPrev(id)
+ var next = this.ds.findNext(id)
if (id[1] + len < n.id[1] + n.len && !n.gc) {
// un-extend right
- yield * this.ds.put({id: [id[0], id[1] + len], len: n.len - len, gc: false})
+ this.ds.put({id: [id[0], id[1] + len], len: n.len - len, gc: false})
n.len = len
}
// set gc'd
@@ -253,7 +253,7 @@ export default function extendTransaction (Y) {
Y.utils.compareIds([prev.id[0], prev.id[1] + prev.len], n.id)
) {
prev.len += n.len
- yield * this.ds.delete(n.id)
+ this.ds.delete(n.id)
n = prev
// ds.put n here?
}
@@ -264,22 +264,22 @@ export default function extendTransaction (Y) {
Y.utils.compareIds([n.id[0], n.id[1] + n.len], next.id)
) {
n.len += next.len
- yield * this.ds.delete(next.id)
+ this.ds.delete(next.id)
}
- yield * this.ds.put(n)
- yield * this.updateState(n.id[0])
+ this.ds.put(n)
+ this.updateState(n.id[0])
}
/*
Mark an operation as deleted.
returns the delete node
*/
- * markDeleted (id, length) {
+ markDeleted (id, length) {
if (length == null) {
length = 1
}
// this.mem.push(["del", id]);
- var n = yield * this.ds.findWithUpperBound(id)
+ var n = this.ds.findWithUpperBound(id)
if (n != null && n.id[0] === id[0]) {
if (n.id[1] <= id[1] && id[1] <= n.id[1] + n.len) {
// id is in n's range
@@ -293,7 +293,7 @@ export default function extendTransaction (Y) {
if (diff < length) {
// a partial deletion
n = {id: [id[0], id[1] + diff], len: length - diff, gc: false}
- yield * this.ds.put(n)
+ this.ds.put(n)
} else {
// already gc'd
throw new Error(
@@ -308,15 +308,15 @@ export default function extendTransaction (Y) {
} else {
// cannot extend left (there is no left!)
n = {id: id, len: length, gc: false}
- yield * this.ds.put(n) // TODO: you double-put !!
+ this.ds.put(n) // TODO: you double-put !!
}
} else {
// cannot extend left
n = {id: id, len: length, gc: false}
- yield * this.ds.put(n)
+ this.ds.put(n)
}
// can extend right?
- var next = yield * this.ds.findNext(n.id)
+ var next = this.ds.findNext(n.id)
if (
next != null &&
n.id[0] === next.id[0] &&
@@ -332,8 +332,8 @@ export default function extendTransaction (Y) {
// delete the missing range after next
diff = diff - next.len // missing range after next
if (diff > 0) {
- yield * this.ds.put(n) // unneccessary? TODO!
- yield * this.markDeleted([next.id[0], next.id[1] + next.len], diff)
+ this.ds.put(n) // unneccessary? TODO!
+ this.markDeleted([next.id[0], next.id[1] + next.len], diff)
}
}
break
@@ -342,8 +342,8 @@ export default function extendTransaction (Y) {
if (diff > next.len) {
// n is even longer than next
// get next.next, and try to extend it
- var _next = yield * this.ds.findNext(next.id)
- yield * this.ds.delete(next.id)
+ var _next = this.ds.findNext(next.id)
+ this.ds.delete(next.id)
if (_next == null || n.id[0] !== _next.id[0]) {
break
} else {
@@ -354,13 +354,13 @@ export default function extendTransaction (Y) {
} else {
// n just partially overlaps with next. extend n, delete next, and break this loop
n.len += next.len - diff
- yield * this.ds.delete(next.id)
+ this.ds.delete(next.id)
break
}
}
}
}
- yield * this.ds.put(n)
+ this.ds.put(n)
return n
}
/*
@@ -368,7 +368,7 @@ export default function extendTransaction (Y) {
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 () {
+ garbageCollectAfterSync () {
// debugger
if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
console.warn('gc should be empty after sync')
@@ -376,28 +376,28 @@ export default function extendTransaction (Y) {
if (!this.store.gc) {
return
}
- yield * this.os.iterate(this, null, null, function * (op) {
+ this.os.iterate(this, null, null, function (op) {
if (op.gc) {
delete op.gc
- yield * this.setOperation(op)
+ this.setOperation(op)
}
if (op.parent != null) {
- var parentDeleted = yield * this.isDeleted(op.parent)
+ var parentDeleted = this.isDeleted(op.parent)
if (parentDeleted) {
op.gc = true
if (!op.deleted) {
- yield * this.markDeleted(op.id, op.content != null ? op.content.length : 1)
+ this.markDeleted(op.id, op.content != null ? op.content.length : 1)
op.deleted = true
if (op.opContent != null) {
- yield * this.deleteOperation(op.opContent)
+ this.deleteOperation(op.opContent)
}
if (op.requires != null) {
for (var i = 0; i < op.requires.length; i++) {
- yield * this.deleteOperation(op.requires[i])
+ this.deleteOperation(op.requires[i])
}
}
}
- yield * this.setOperation(op)
+ this.setOperation(op)
this.store.gc1.push(op.id) // this is ok becaues its shortly before sync (otherwise use queueGarbageCollector!)
return
}
@@ -405,9 +405,9 @@ export default function extendTransaction (Y) {
if (op.deleted) {
var left = null
if (op.left != null) {
- left = yield * this.getInsertion(op.left)
+ left = this.getInsertion(op.left)
}
- yield * this.store.addToGarbageCollector.call(this, op, left)
+ this.store.addToGarbageCollector.call(this, op, left)
}
})
}
@@ -420,10 +420,10 @@ export default function extendTransaction (Y) {
* reset parent.end
* reset origins of all right ops
*/
- * garbageCollectOperation (id) {
- this.store.addToDebug('yield * this.garbageCollectOperation(', id, ')')
- var o = yield * this.getOperation(id)
- yield * this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
+ garbageCollectOperation (id) {
+ this.store.addToDebug('this.garbageCollectOperation(', id, ')')
+ var o = this.getOperation(id)
+ this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
// if op exists, then clean that mess up..
if (o != null) {
var deps = []
@@ -434,32 +434,32 @@ export default function extendTransaction (Y) {
deps = deps.concat(o.requires)
}
for (var i = 0; i < deps.length; i++) {
- var dep = yield * this.getOperation(deps[i])
+ var dep = this.getOperation(deps[i])
if (dep != null) {
if (!dep.deleted) {
- yield * this.deleteOperation(dep.id)
- dep = yield * this.getOperation(dep.id)
+ this.deleteOperation(dep.id)
+ dep = this.getOperation(dep.id)
}
dep.gc = true
- yield * this.setOperation(dep)
+ this.setOperation(dep)
this.store.queueGarbageCollector(dep.id)
} else {
- yield * this.markGarbageCollected(deps[i], 1)
+ this.markGarbageCollected(deps[i], 1)
}
}
// remove gc'd op from the left op, if it exists
if (o.left != null) {
- var left = yield * this.getInsertion(o.left)
+ var left = this.getInsertion(o.left)
left.right = o.right
- yield * this.setOperation(left)
+ this.setOperation(left)
}
// remove gc'd op from the right op, if it exists
// also reset origins of right ops
if (o.right != null) {
- var right = yield * this.getOperation(o.right)
+ var right = this.getOperation(o.right)
right.left = o.left
- yield * this.setOperation(right)
+ this.setOperation(right)
if (o.originOf != null && o.originOf.length > 0) {
// find new origin of right ops
@@ -478,7 +478,7 @@ export default function extendTransaction (Y) {
right.origin = neworigin
// search until you find origin pointer to the left of o
if (right.right != null) {
- var i = yield * this.getOperation(right.right)
+ var i = this.getOperation(right.right)
var ids = [o.id, o.right]
while (ids.some(function (id) {
return Y.utils.compareIds(id, i.origin)
@@ -486,14 +486,14 @@ export default function extendTransaction (Y) {
if (Y.utils.compareIds(i.origin, o.id)) {
// reset origin of i
i.origin = neworigin
- yield * this.setOperation(i)
+ this.setOperation(i)
}
// get next i
if (i.right == null) {
break
} else {
ids.push(i.id)
- i = yield * this.getOperation(i.right)
+ i = this.getOperation(i.right)
}
}
}
@@ -502,20 +502,20 @@ export default function extendTransaction (Y) {
// ** Now the new implementation starts **
// reset neworigin of all originOf[*]
for (var _i in o.originOf) {
- var originsIn = yield * this.getOperation(o.originOf[_i])
+ var originsIn = this.getOperation(o.originOf[_i])
if (originsIn != null) {
originsIn.origin = neworigin
- yield * this.setOperation(originsIn)
+ this.setOperation(originsIn)
}
}
if (neworigin != null) {
- var neworigin_ = yield * this.getInsertion(neworigin)
+ var neworigin_ = this.getInsertion(neworigin)
if (neworigin_.originOf == null) {
neworigin_.originOf = o.originOf
} else {
neworigin_.originOf = o.originOf.concat(neworigin_.originOf)
}
- yield * this.setOperation(neworigin_)
+ this.setOperation(neworigin_)
}
// we don't need to set right here, because
// right should be in o.originOf => it is set it the previous for loop
@@ -524,15 +524,15 @@ export default function extendTransaction (Y) {
// o may originate in another operation.
// Since o is deleted, we have to reset o.origin's `originOf` property
if (o.origin != null) {
- var origin = yield * this.getInsertion(o.origin)
+ var origin = this.getInsertion(o.origin)
origin.originOf = origin.originOf.filter(function (_id) {
return !Y.utils.compareIds(id, _id)
})
- yield * this.setOperation(origin)
+ this.setOperation(origin)
}
var parent
if (o.parent != null) {
- parent = yield * this.getOperation(o.parent)
+ parent = this.getOperation(o.parent)
}
// remove gc'd op from parent, if it exists
if (parent != null) {
@@ -559,38 +559,38 @@ export default function extendTransaction (Y) {
}
}
if (setParent) {
- yield * this.setOperation(parent)
+ this.setOperation(parent)
}
}
// finally remove it from the os
- yield * this.removeOperation(o.id)
+ this.removeOperation(o.id)
}
}
- * checkDeleteStoreForState (state) {
- var n = yield * this.ds.findWithUpperBound([state.user, state.clock])
+ checkDeleteStoreForState (state) {
+ var n = this.ds.findWithUpperBound([state.user, state.clock])
if (n != null && n.id[0] === state.user && n.gc) {
state.clock = Math.max(state.clock, n.id[1] + n.len)
}
}
- * updateState (user) {
- var state = yield * this.getState(user)
- yield * this.checkDeleteStoreForState(state)
- var o = yield * this.getInsertion([user, state.clock])
+ updateState (user) {
+ var state = this.getState(user)
+ this.checkDeleteStoreForState(state)
+ var o = this.getInsertion([user, state.clock])
var oLength = (o != null && o.content != null) ? o.content.length : 1
while (o != null && user === o.id[0] && o.id[1] <= state.clock && o.id[1] + oLength > state.clock) {
// either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
state.clock += oLength
- yield * this.checkDeleteStoreForState(state)
- o = yield * this.os.findNext(o.id)
+ this.checkDeleteStoreForState(state)
+ o = this.os.findNext(o.id)
oLength = (o != null && o.content != null) ? o.content.length : 1
}
- yield * this.setState(state)
+ this.setState(state)
}
/*
apply a delete set in order to get
the state of the supplied ds
*/
- * applyDeleteSet (decoder) {
+ applyDeleteSet (decoder) {
var deletions = []
let dsLength = decoder.readUint32()
@@ -606,7 +606,7 @@ export default function extendTransaction (Y) {
}
var pos = 0
var d = dv[pos]
- yield * this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) {
+ this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function (n) {
// cases:
// 1. d deletes something to the right of n
// => go to next n (break)
@@ -655,14 +655,14 @@ export default function extendTransaction (Y) {
for (var i = 0; i < deletions.length; i++) {
var del = deletions[i]
// always try to delete..
- yield * this.deleteOperation([del[0], del[1]], del[2])
+ this.deleteOperation([del[0], del[1]], del[2])
if (del[3]) {
// gc..
- yield * this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
+ this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
// remove operation..
var counter = del[1] + del[2]
while (counter >= del[1]) {
- var o = yield * this.os.findWithUpperBound([del[0], counter - 1])
+ var o = this.os.findWithUpperBound([del[0], counter - 1])
if (o == null) {
break
}
@@ -673,14 +673,14 @@ export default function extendTransaction (Y) {
}
if (o.id[1] + oLen > del[1] + del[2]) {
// overlaps right
- o = yield * this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
+ o = this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
}
if (o.id[1] < del[1]) {
// overlaps left
- o = yield * this.getInsertionCleanStart([del[0], del[1]])
+ o = this.getInsertionCleanStart([del[0], del[1]])
}
counter = o.id[1]
- yield * this.garbageCollectOperation(o.id)
+ this.garbageCollectOperation(o.id)
}
}
if (this.store.forwardAppliedOperations || this.store.y.persistence != null) {
@@ -695,16 +695,16 @@ export default function extendTransaction (Y) {
}
}
}
- * isGarbageCollected (id) {
- var n = yield * this.ds.findWithUpperBound(id)
+ isGarbageCollected (id) {
+ var n = this.ds.findWithUpperBound(id)
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc
}
/*
A DeleteSet (ds) describes all the deleted ops in the OS
*/
- * writeDeleteSet (encoder) {
+ writeDeleteSet (encoder) {
var ds = new Map()
- yield * this.ds.iterate(this, null, null, function * (n) {
+ this.ds.iterate(this, null, null, function (n) {
var user = n.id[0]
var counter = n.id[1]
var len = n.len
@@ -731,16 +731,16 @@ export default function extendTransaction (Y) {
}
}
}
- * isDeleted (id) {
- var n = yield * this.ds.findWithUpperBound(id)
+ isDeleted (id) {
+ var n = this.ds.findWithUpperBound(id)
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
}
- * setOperation (op) {
- yield * this.os.put(op)
+ setOperation (op) {
+ this.os.put(op)
return op
}
- * addOperation (op) {
- yield * this.os.put(op)
+ addOperation (op) {
+ this.os.put(op)
// case op is created by this user, op is already broadcasted in applyCreatedOperations
if (op.id[0] !== this.store.userId && typeof op.id[1] !== 'string') {
if (this.store.forwardAppliedOperations) {
@@ -753,7 +753,7 @@ export default function extendTransaction (Y) {
}
}
// if insertion, try to combine with left insertion (if both have content property)
- * tryCombineWithLeft (op) {
+ tryCombineWithLeft (op) {
if (
op != null &&
op.left != null &&
@@ -761,7 +761,7 @@ export default function extendTransaction (Y) {
op.left[0] === op.id[0] &&
Y.utils.compareIds(op.left, op.origin)
) {
- var left = yield * this.getInsertion(op.left)
+ var left = this.getInsertion(op.left)
if (left.content != null &&
left.id[1] + left.content.length === op.id[1] &&
left.originOf.length === 1 &&
@@ -776,13 +776,13 @@ export default function extendTransaction (Y) {
}
left.content = left.content.concat(op.content)
left.right = op.right
- yield * this.os.delete(op.id)
- yield * this.setOperation(left)
+ this.os.delete(op.id)
+ this.setOperation(left)
}
}
}
- * getInsertion (id) {
- var ins = yield * this.os.findWithUpperBound(id)
+ getInsertion (id) {
+ var ins = this.os.findWithUpperBound(id)
if (ins == null) {
return null
} else {
@@ -794,14 +794,14 @@ export default function extendTransaction (Y) {
}
}
}
- * getInsertionCleanStartEnd (id) {
- yield * this.getInsertionCleanStart(id)
- return yield * this.getInsertionCleanEnd(id)
+ getInsertionCleanStartEnd (id) {
+ this.getInsertionCleanStart(id)
+ return this.getInsertionCleanEnd(id)
}
// Return an insertion such that id is the first element of content
// This function manipulates an operation, if necessary
- * getInsertionCleanStart (id) {
- var ins = yield * this.getInsertion(id)
+ getInsertionCleanStart (id) {
+ var ins = this.getInsertion(id)
if (ins != null) {
if (ins.id[1] === id[1]) {
return ins
@@ -815,8 +815,8 @@ export default function extendTransaction (Y) {
left.right = ins.id
ins.left = leftLid
// debugger // check
- yield * this.setOperation(left)
- yield * this.setOperation(ins)
+ this.setOperation(left)
+ this.setOperation(ins)
if (left.gc) {
this.store.queueGarbageCollector(ins.id)
}
@@ -828,8 +828,8 @@ export default function extendTransaction (Y) {
}
// Return an insertion such that id is the last element of content
// This function manipulates an operation, if necessary
- * getInsertionCleanEnd (id) {
- var ins = yield * this.getInsertion(id)
+ getInsertionCleanEnd (id) {
+ var ins = this.getInsertion(id)
if (ins != null) {
if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) {
return ins
@@ -843,8 +843,8 @@ export default function extendTransaction (Y) {
ins.right = right.id
right.left = insLid
// debugger // check
- yield * this.setOperation(right)
- yield * this.setOperation(ins)
+ this.setOperation(right)
+ this.setOperation(ins)
if (ins.gc) {
this.store.queueGarbageCollector(right.id)
}
@@ -854,8 +854,8 @@ export default function extendTransaction (Y) {
return null
}
}
- * getOperation (id/* :any */)/* :Transaction */ {
- var o = yield * this.os.find(id)
+ getOperation (id/* :any */)/* :Transaction */ {
+ var o = this.os.find(id)
if (id[0] !== 0xFFFFFF || o != null) {
return o
} else { // type is string
@@ -870,7 +870,7 @@ export default function extendTransaction (Y) {
}
var op = Y.Struct[struct].create(id, args)
op.type = comp[1]
- yield * this.setOperation(op)
+ this.setOperation(op)
return op
} else {
throw new Error(
@@ -880,18 +880,18 @@ export default function extendTransaction (Y) {
}
}
}
- * removeOperation (id) {
- yield * this.os.delete(id)
+ removeOperation (id) {
+ this.os.delete(id)
}
- * setState (state) {
+ setState (state) {
var val = {
id: [state.user],
clock: state.clock
}
- yield * this.ss.put(val)
+ this.ss.put(val)
}
- * getState (user) {
- var n = yield * this.ss.find([user])
+ getState (user) {
+ var n = this.ss.find([user])
var clock = n == null ? null : n.clock
if (clock == null) {
clock = 0
@@ -901,9 +901,9 @@ export default function extendTransaction (Y) {
clock: clock
}
}
- * getStateVector () {
+ getStateVector () {
var stateVector = []
- yield * this.ss.iterate(this, null, null, function * (n) {
+ this.ss.iterate(this, null, null, function (n) {
stateVector.push({
user: n.id[0],
clock: n.clock
@@ -911,18 +911,18 @@ export default function extendTransaction (Y) {
})
return stateVector
}
- * getStateSet () {
+ getStateSet () {
var ss = {}
- yield * this.ss.iterate(this, null, null, function * (n) {
+ this.ss.iterate(this, null, null, function (n) {
ss[n.id[0]] = n.clock
})
return ss
}
- * writeStateSet (encoder) {
+ writeStateSet (encoder) {
let lenPosition = encoder.pos
let len = 0
encoder.writeUint32(0)
- yield * this.ss.iterate(this, null, null, function * (n) {
+ this.ss.iterate(this, null, null, function (n) {
encoder.writeVarUint(n.id[0])
encoder.writeVarUint(n.clock)
len++
@@ -976,14 +976,14 @@ export default function extendTransaction (Y) {
3. Found o = op.origin -> set op.left = op.origin, and send it to the user. start again from 1. (set op = o)
4. Found some o -> set o.right = op, o.left = o.origin, send it to the user, continue
*/
- * getOperations (startSS) {
+ getOperations (startSS) {
// TODO: use bounds here!
if (startSS == null) {
startSS = new Map()
}
var send = []
- var endSV = yield * this.getStateVector()
+ var endSV = this.getStateVector()
for (let endState of endSV) {
let user = endState.user
if (user === 0xFFFFFF) {
@@ -993,7 +993,7 @@ export default function extendTransaction (Y) {
if (startPos > 0) {
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
// find out if that is the case
- let firstMissing = yield * this.getInsertion([user, startPos])
+ let firstMissing = this.getInsertion([user, startPos])
if (firstMissing != null) {
// update startPos
startPos = firstMissing.id[1]
@@ -1007,7 +1007,7 @@ export default function extendTransaction (Y) {
if (user === 0xFFFFFF) {
continue
}
- yield * this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
+ this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function (op) {
op = Y.Struct[op.struct].encode(op)
if (op.struct !== 'Insert') {
send.push(op)
@@ -1038,7 +1038,7 @@ export default function extendTransaction (Y) {
*/
break
}
- o = yield * this.getInsertion(o.left)
+ o = this.getInsertion(o.left)
// we set another o, check if we can reduce $missingOrigins
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
missingOrigins.pop()
@@ -1076,7 +1076,7 @@ export default function extendTransaction (Y) {
return send.reverse()
}
- * writeOperations (encoder, decoder) {
+ writeOperations (encoder, decoder) {
let ss = new Map()
let ssLength = decoder.readUint32()
for (let i = 0; i < ssLength; i++) {
@@ -1084,7 +1084,7 @@ export default function extendTransaction (Y) {
let clock = decoder.readVarUint()
ss.set(user, clock)
}
- let ops = yield * this.getOperations(ss)
+ let ops = this.getOperations(ss)
encoder.writeUint32(ops.length)
for (let i = 0; i < ops.length; i++) {
let op = ops[i]
@@ -1092,17 +1092,17 @@ export default function extendTransaction (Y) {
}
}
- * toBinary () {
+ toBinary () {
let encoder = new BinaryEncoder()
- yield * this.writeOperationsUntransformed(encoder)
- yield * this.writeDeleteSet(encoder)
+ this.writeOperationsUntransformed(encoder)
+ this.writeDeleteSet(encoder)
return encoder.createBuffer()
}
- * fromBinary (buffer) {
+ fromBinary (buffer) {
let decoder = new BinaryDecoder(buffer)
- yield * this.applyOperationsUntransformed(decoder)
- yield * this.applyDeleteSet(decoder)
+ this.applyOperationsUntransformed(decoder)
+ this.applyDeleteSet(decoder)
}
/*
@@ -1110,43 +1110,43 @@ export default function extendTransaction (Y) {
* You can apply these operations using .applyOperationsUntransformed(ops)
*
*/
- * writeOperationsUntransformed (encoder) {
+ writeOperationsUntransformed (encoder) {
let lenPosition = encoder.pos
let len = 0
encoder.writeUint32(0) // placeholder
- yield * this.os.iterate(this, null, null, function * (op) {
+ this.os.iterate(this, null, null, function (op) {
if (op.id[0] !== 0xFFFFFF) {
len++
Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op))
}
})
encoder.setUint32(lenPosition, len)
- yield * this.writeStateSet(encoder)
+ this.writeStateSet(encoder)
}
- * applyOperationsUntransformed (decoder) {
+ applyOperationsUntransformed (decoder) {
let len = decoder.readUint32()
for (let i = 0; i < len; i++) {
let op = Y.Struct.binaryDecodeOperation(decoder)
- yield * this.os.put(op)
+ this.os.put(op)
}
- yield * this.os.iterate(this, null, null, function * (op) {
+ this.os.iterate(this, null, null, function (op) {
if (op.parent != null) {
if (op.struct === 'Insert') {
// update parents .map/start/end properties
if (op.parentSub != null && op.left == null) {
// op is child of Map
- let parent = yield * this.getOperation(op.parent)
+ let parent = this.getOperation(op.parent)
parent.map[op.parentSub] = op.id
- yield * this.setOperation(parent)
+ this.setOperation(parent)
} else if (op.right == null || op.left == null) {
- let parent = yield * this.getOperation(op.parent)
+ let parent = this.getOperation(op.parent)
if (op.right == null) {
parent.end = Y.utils.getLastId(op)
}
if (op.left == null) {
parent.start = op.id
}
- yield * this.setOperation(parent)
+ this.setOperation(parent)
}
}
}
@@ -1155,14 +1155,14 @@ export default function extendTransaction (Y) {
for (let i = 0; i < stateSetLength; i++) {
let user = decoder.readVarUint()
let clock = decoder.readVarUint()
- yield * this.ss.put({
+ this.ss.put({
id: [user],
clock: clock
})
}
}
/* this is what we used before.. use this as a reference..
- * makeOperationReady (startSS, op) {
+ makeOperationReady (startSS, op) {
op = Y.Struct[op.struct].encode(op)
op = Y.utils.copyObject(op) -- use copyoperation instead now!
var o = op
@@ -1172,7 +1172,7 @@ export default function extendTransaction (Y) {
// or the o that has no origin to the right of op
// (this is why we use the ids array)
while (o.right != null) {
- var right = yield * this.getOperation(o.right)
+ var right = this.getOperation(o.right)
if (o.right[1] < (startSS[o.right[0]] || 0) || !ids.some(function (id) {
return Y.utils.compareIds(id, right.origin)
})) {
@@ -1186,10 +1186,10 @@ export default function extendTransaction (Y) {
return op
}
*/
- * flush () {
- yield * this.os.flush()
- yield * this.ss.flush()
- yield * this.ds.flush()
+ flush () {
+ this.os.flush()
+ this.ss.flush()
+ this.ds.flush()
}
}
Y.Transaction = TransactionInterface
diff --git a/src/Utils.js b/src/Utils.js
index f5c0298c..03e841a3 100644
--- a/src/Utils.js
+++ b/src/Utils.js
@@ -315,7 +315,7 @@ export default function Utils (Y) {
this.awaiting++
ops.map(Y.utils.copyOperation).forEach(this.onevent)
}
- * awaitOps (transaction, f, args) {
+ awaitOps (transaction, f, args) {
function notSoSmartSort (array) {
// this function sorts insertions in a executable order
var result = []
@@ -339,7 +339,7 @@ export default function Utils (Y) {
}
var before = this.waiting.length
// somehow create new operations
- yield * f.apply(transaction, args)
+ f.apply(transaction, args)
// remove all appended ops / awaited ops
this.waiting.splice(before)
if (this.awaiting > 0) this.awaiting--
@@ -349,7 +349,7 @@ export default function Utils (Y) {
for (let i = 0; i < this.waiting.length; i++) {
var o = this.waiting[i]
if (o.struct === 'Insert') {
- var _o = yield * transaction.getInsertion(o.id)
+ var _o = transaction.getInsertion(o.id)
if (_o.parentSub != null && _o.left != null) {
// if o is an insertion of a map struc (parentSub is defined), then it shouldn't be necessary to compute left
this.waiting.splice(i, 1)
@@ -361,10 +361,10 @@ export default function Utils (Y) {
o.left = null
} else {
// find next undeleted op
- var left = yield * transaction.getInsertion(_o.left)
+ var left = transaction.getInsertion(_o.left)
while (left.deleted != null) {
if (left.left != null) {
- left = yield * transaction.getInsertion(left.left)
+ left = transaction.getInsertion(left.left)
} else {
left = null
break
@@ -690,7 +690,7 @@ export default function Utils (Y) {
this.writeBuffer = createEmptyOpsArray(5)
this.readBuffer = createEmptyOpsArray(10)
}
- * find (id, noSuperCall) {
+ find (id, noSuperCall) {
var i, r
for (i = this.readBuffer.length - 1; i >= 0; i--) {
r = this.readBuffer[i]
@@ -716,7 +716,7 @@ export default function Utils (Y) {
if (i < 0 && noSuperCall === undefined) {
// did not reach break in last loop
// read id and put it to the end of readBuffer
- o = yield * super.find(id)
+ o = super.find(id)
}
if (o != null) {
for (i = 0; i < this.readBuffer.length - 1; i++) {
@@ -726,7 +726,7 @@ export default function Utils (Y) {
}
return o
}
- * put (o) {
+ put (o) {
var id = o.id
var i, r // helper variables
for (i = this.writeBuffer.length - 1; i >= 0; i--) {
@@ -746,7 +746,7 @@ export default function Utils (Y) {
// write writeBuffer[0]
var write = this.writeBuffer[0]
if (write.id[0] !== null) {
- yield * super.put(write)
+ super.put(write)
}
// put o to the end of writeBuffer
for (i = 0; i < this.writeBuffer.length - 1; i++) {
@@ -766,7 +766,7 @@ export default function Utils (Y) {
}
this.readBuffer[this.readBuffer.length - 1] = o
}
- * delete (id) {
+ delete (id) {
var i, r
for (i = 0; i < this.readBuffer.length; i++) {
r = this.readBuffer[i]
@@ -776,44 +776,44 @@ export default function Utils (Y) {
}
}
}
- yield * this.flush()
- yield * super.delete(id)
+ this.flush()
+ super.delete(id)
}
- * findWithLowerBound (id) {
- var o = yield * this.find(id, true)
+ findWithLowerBound (id) {
+ var o = this.find(id, true)
if (o != null) {
return o
} else {
- yield * this.flush()
- return yield * super.findWithLowerBound.apply(this, arguments)
+ this.flush()
+ return super.findWithLowerBound.apply(this, arguments)
}
}
- * findWithUpperBound (id) {
- var o = yield * this.find(id, true)
+ findWithUpperBound (id) {
+ var o = this.find(id, true)
if (o != null) {
return o
} else {
- yield * this.flush()
- return yield * super.findWithUpperBound.apply(this, arguments)
+ this.flush()
+ return super.findWithUpperBound.apply(this, arguments)
}
}
- * findNext () {
- yield * this.flush()
- return yield * super.findNext.apply(this, arguments)
+ findNext () {
+ this.flush()
+ return super.findNext.apply(this, arguments)
}
- * findPrev () {
- yield * this.flush()
- return yield * super.findPrev.apply(this, arguments)
+ findPrev () {
+ this.flush()
+ return super.findPrev.apply(this, arguments)
}
- * iterate () {
- yield * this.flush()
- yield * super.iterate.apply(this, arguments)
+ iterate () {
+ this.flush()
+ super.iterate.apply(this, arguments)
}
- * flush () {
+ flush () {
for (var i = 0; i < this.writeBuffer.length; i++) {
var write = this.writeBuffer[i]
if (write.id[0] !== null) {
- yield * super.put(write)
+ super.put(write)
this.writeBuffer[i] = {
id: [null, null]
}
diff --git a/src/y-memory.js b/src/y-memory.js
new file mode 100644
index 00000000..67a593f2
--- /dev/null
+++ b/src/y-memory.js
@@ -0,0 +1,64 @@
+
+import extendRBTree from './RedBlackTree'
+
+export default function extend (Y) {
+ extendRBTree(Y)
+
+ class Transaction extends Y.Transaction {
+ constructor (store) {
+ super(store)
+ this.store = store
+ this.ss = store.ss
+ this.os = store.os
+ this.ds = store.ds
+ }
+ }
+ var Store = Y.utils.RBTree
+ var BufferedStore = Y.utils.createSmallLookupBuffer(Store)
+
+ class Database extends Y.AbstractDatabase {
+ constructor (y, opts) {
+ super(y, opts)
+ this.os = new BufferedStore()
+ this.ds = new Store()
+ this.ss = new BufferedStore()
+ }
+ logTable () {
+ var self = this
+ self.requestTransaction(function () {
+ console.log('User: ', this.store.y.connector.userId, "==============================") // eslint-disable-line
+ console.log("State Set (SS):", this.getStateSet()) // eslint-disable-line
+ console.log("Operation Store (OS):") // eslint-disable-line
+ this.os.logTable() // eslint-disable-line
+ console.log("Deletion Store (DS):") //eslint-disable-line
+ this.ds.logTable() // eslint-disable-line
+ if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
+ console.warn('GC1|2 not empty!', this.store.gc1, this.store.gc2)
+ }
+ if (JSON.stringify(this.store.listenersById) !== '{}') {
+ console.warn('listenersById not empty!')
+ }
+ if (JSON.stringify(this.store.listenersByIdExecuteNow) !== '[]') {
+ console.warn('listenersByIdExecuteNow not empty!')
+ }
+ if (this.store.transactionInProgress) {
+ console.warn('Transaction still in progress!')
+ }
+ }, true)
+ }
+ transact (makeGen) {
+ const t = new Transaction(this)
+ while (makeGen != null) {
+ makeGen.call(t)
+ makeGen = this.getNextRequest()
+ }
+ }
+ destroy () {
+ super.destroy()
+ delete this.os
+ delete this.ss
+ delete this.ds
+ }
+ }
+ Y.memory = Database
+}
diff --git a/src/y.js b/src/y.js
index ebc07591..07403e3a 100644
--- a/src/y.js
+++ b/src/y.js
@@ -4,6 +4,7 @@ import extendDatabase from './Database.js'
import extendTransaction from './Transaction.js'
import extendStruct from './Struct.js'
import extendUtils from './Utils.js'
+import extendMemory from './y-memory.js'
import debug from 'debug'
import { formatYjsMessage, formatYjsMessageType } from './MessageHandler.js'
@@ -13,6 +14,7 @@ extendDatabase(Y)
extendTransaction(Y)
extendStruct(Y)
extendUtils(Y)
+extendMemory(Y)
Y.debug = debug
debug.formatters.Y = formatYjsMessage
@@ -182,7 +184,7 @@ class YConfig extends Y.utils.NamedEventHandler {
var opts = this.options
var share = {}
this.share = share
- this.db.requestTransaction(function * requestTransaction () {
+ this.db.requestTransaction(function requestTransaction () {
// create shared object
for (var propertyname in opts.share) {
var typeConstructor = opts.share[propertyname].split('(')
@@ -195,7 +197,7 @@ class YConfig extends Y.utils.NamedEventHandler {
var typedef = type.typeDefinition
var id = [0xFFFFFF, typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeArgs]
let args = Y.utils.parseTypeDefinition(type, typeArgs)
- share[propertyname] = yield * this.store.initType.call(this, id, args)
+ share[propertyname] = this.store.initType.call(this, id, args)
}
})
if (this.persistence != null) {
@@ -250,8 +252,8 @@ class YConfig extends Y.utils.NamedEventHandler {
return this.db.whenTransactionsFinished().then(function () {
self.db.destroyTypes()
// make sure to wait for all transactions before destroying the db
- self.db.requestTransaction(function * () {
- yield * self.db.destroy()
+ self.db.requestTransaction(function () {
+ self.db.destroy()
})
return self.db.whenTransactionsFinished()
})
diff --git a/test/encode-decode.js b/test/encode-decode.js
index ce6d69b0..bace3a09 100644
--- a/test/encode-decode.js
+++ b/test/encode-decode.js
@@ -161,7 +161,9 @@ test('encode/decode List operations', async function binList (t) {
testEncoding(t, writeList, readList, {
struct: 'List',
id: [100, 33],
- type: 'Array'
+ type: 'Array',
+ start: null,
+ end: null
})
})
diff --git a/test/red-black-tree.js b/test/red-black-tree.js
new file mode 100644
index 00000000..14a19164
--- /dev/null
+++ b/test/red-black-tree.js
@@ -0,0 +1,217 @@
+import Y from '../src/y.js'
+import Chance from 'chance'
+import { test, proxyConsole } from 'cutest'
+
+proxyConsole()
+
+var numberOfRBTreeTests = 10000
+
+function checkRedNodesDoNotHaveBlackChildren (t, tree) {
+ let correct = true
+ function traverse (n) {
+ if (n == null) {
+ return
+ }
+ if (n.isRed()) {
+ if (n.left != null) {
+ correct = correct && !n.left.isRed()
+ }
+ if (n.right != null) {
+ correct = correct && !n.right.isRed()
+ }
+ }
+ traverse(n.left)
+ traverse(n.right)
+ }
+ traverse(tree.root)
+ t.assert(correct, 'Red nodes do not have black children')
+}
+
+function checkBlackHeightOfSubTreesAreEqual (t, tree) {
+ let correct = true
+ function traverse (n) {
+ if (n == null) {
+ return 0
+ }
+ var sub1 = traverse(n.left)
+ var sub2 = traverse(n.right)
+ if (sub1 !== sub2) {
+ correct = false
+ }
+ if (n.isRed()) {
+ return sub1
+ } else {
+ return sub1 + 1
+ }
+ }
+ traverse(tree.root)
+ t.assert(correct, 'Black-height of sub-trees are equal')
+}
+
+function checkRootNodeIsBlack (t, tree) {
+ t.assert(tree.root == null || tree.root.isBlack(), 'root node is black')
+}
+
+test('RedBlack Tree', async function redBlackTree (t) {
+ let memory = new Y.memory(null, { // eslint-disable-line
+ name: 'Memory',
+ gcTimeout: -1
+ })
+ let tree = memory.os
+ memory.requestTransaction(function () {
+ tree.put({id: [8433]})
+ tree.put({id: [12844]})
+ tree.put({id: [1795]})
+ tree.put({id: [30302]})
+ tree.put({id: [64287]})
+ tree.delete([8433])
+ tree.put({id: [28996]})
+ tree.delete([64287])
+ tree.put({id: [22721]})
+ })
+ await memory.whenTransactionsFinished()
+ checkRootNodeIsBlack(t, tree)
+ checkBlackHeightOfSubTreesAreEqual(t, tree)
+ checkRedNodesDoNotHaveBlackChildren(t, tree)
+})
+
+test(`random tests (${numberOfRBTreeTests})`, async function random (t) {
+ let chance = new Chance(t.getSeed() * 1000000000)
+ let memory = new Y.memory(null, { // eslint-disable-line
+ name: 'Memory',
+ gcTimeout: -1
+ })
+ let tree = memory.os
+ let elements = []
+ memory.requestTransaction(function () {
+ for (var i = 0; i < numberOfRBTreeTests; i++) {
+ if (chance.bool({likelihood: 80})) {
+ // 80% chance to insert an element
+ let obj = [chance.integer({min: 0, max: numberOfRBTreeTests})]
+ let nodeExists = tree.find(obj)
+ if (!nodeExists) {
+ if (elements.some(e => e[0] === obj[0])) {
+ t.assert(false, 'tree and elements contain different results')
+ }
+ elements.push(obj)
+ tree.put({id: obj})
+ }
+ } else if (elements.length > 0) {
+ // ~20% chance to delete an element
+ var elem = chance.pickone(elements)
+ elements = elements.filter(function (e) {
+ return !Y.utils.compareIds(e, elem)
+ })
+ tree.delete(elem)
+ }
+ }
+ })
+ await memory.whenTransactionsFinished()
+ checkRootNodeIsBlack(t, tree)
+ checkBlackHeightOfSubTreesAreEqual(t, tree)
+ checkRedNodesDoNotHaveBlackChildren(t, tree)
+ memory.requestTransaction(function () {
+ let allNodesExist = true
+ for (let id of elements) {
+ let node = tree.find(id)
+ if (!Y.utils.compareIds(node.id, id)) {
+ allNodesExist = false
+ }
+ }
+ t.assert(allNodesExist, 'All inserted nodes exist')
+ })
+ memory.requestTransaction(function () {
+ let findAllNodesWithLowerBoundSerach = true
+ for (let id of elements) {
+ let node = tree.findWithLowerBound(id)
+ if (!Y.utils.compareIds(node.id, id)) {
+ findAllNodesWithLowerBoundSerach = false
+ }
+ }
+ t.assert(
+ findAllNodesWithLowerBoundSerach,
+ 'Find every object with lower bound search'
+ )
+ })
+
+ memory.requestTransaction(function () {
+ let lowerBound = chance.pickone(elements)
+ let expectedResults = elements.filter((e, pos) =>
+ (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) &&
+ elements.indexOf(e) === pos
+ ).length
+ let actualResults = 0
+ tree.iterate(this, lowerBound, null, function (val) {
+ if (val == null) {
+ t.assert(false, 'val is undefined!')
+ }
+ actualResults++
+ })
+ t.assert(
+ expectedResults === actualResults,
+ 'Iterating over a tree with lower bound yields the right amount of results'
+ )
+ })
+
+ memory.requestTransaction(function () {
+ let expectedResults = elements.filter((e, pos) =>
+ elements.indexOf(e) === pos
+ ).length
+ let actualResults = 0
+ tree.iterate(this, null, null, function (val) {
+ if (val == null) {
+ t.assert(false, 'val is undefined!')
+ }
+ actualResults++
+ })
+ t.assert(
+ expectedResults === actualResults,
+ 'iterating over a tree without bounds yields the right amount of results'
+ )
+ })
+
+ memory.requestTransaction(function () {
+ let upperBound = chance.pickone(elements)
+ let expectedResults = elements.filter((e, pos) =>
+ (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) &&
+ elements.indexOf(e) === pos
+ ).length
+ let actualResults = 0
+ tree.iterate(this, null, upperBound, function (val) {
+ if (val == null) {
+ t.assert(false, 'val is undefined!')
+ }
+ actualResults++
+ })
+ t.assert(
+ expectedResults === actualResults,
+ 'iterating over a tree with upper bound yields the right amount of results'
+ )
+ })
+
+ memory.requestTransaction(function () {
+ let upperBound = chance.pickone(elements)
+ let lowerBound = chance.pickone(elements)
+ if (Y.utils.smaller(upperBound, lowerBound)) {
+ [lowerBound, upperBound] = [upperBound, lowerBound]
+ }
+ let expectedResults = elements.filter((e, pos) =>
+ (Y.utils.smaller(lowerBound, e) || Y.utils.compareIds(e, lowerBound)) &&
+ (Y.utils.smaller(e, upperBound) || Y.utils.compareIds(e, upperBound)) &&
+ elements.indexOf(e) === pos
+ ).length
+ let actualResults = 0
+ tree.iterate(this, lowerBound, upperBound, function (val) {
+ if (val == null) {
+ t.assert(false, 'val is undefined!')
+ }
+ actualResults++
+ })
+ t.assert(
+ expectedResults === actualResults,
+ 'iterating over a tree with upper bound yields the right amount of results'
+ )
+ })
+
+ await memory.whenTransactionsFinished()
+})
diff --git a/tests-lib/helper.js b/tests-lib/helper.js
index 9236cfa5..5585b1a8 100644
--- a/tests-lib/helper.js
+++ b/tests-lib/helper.js
@@ -4,7 +4,7 @@ import _Y from '../../yjs/src/y.js'
import yMemory from '../../y-memory/src/y-memory.js'
import yArray from '../../y-array/src/y-array.js'
import yText from '../../y-text/src/Text.js'
-import yMap from '../../y-map/src/Map.js'
+import yMap from '../../y-map/src/y-map.js'
import yXml from '../../y-xml/src/y-xml.js'
import yTest from './test-connector.js'
@@ -17,9 +17,9 @@ Y.extend(yMemory, yArray, yText, yMap, yTest, yXml)
export var database = { name: 'memory' }
export var connector = { name: 'test', url: 'http://localhost:1234' }
-function * getStateSet () {
+function getStateSet () {
var ss = {}
- yield * this.ss.iterate(this, null, null, function * (n) {
+ this.ss.iterate(this, null, null, function (n) {
var user = n.id[0]
var clock = n.clock
ss[user] = clock
@@ -27,9 +27,9 @@ function * getStateSet () {
return ss
}
-function * getDeleteSet () {
+function getDeleteSet () {
var ds = {}
- yield * this.ds.iterate(this, null, null, function * (n) {
+ this.ds.iterate(this, null, null, function (n) {
var user = n.id[0]
var counter = n.id[1]
var len = n.len
@@ -126,9 +126,9 @@ export async function compareUsers (t, users) {
let filterDeletedOps = users.every(u => u.db.gc === false)
var data = await Promise.all(users.map(async (u) => {
var data = {}
- u.db.requestTransaction(function * () {
+ u.db.requestTransaction(function () {
let ops = []
- yield * this.os.iterate(this, null, null, function * (op) {
+ this.os.iterate(this, null, null, function (op) {
ops.push(Y.Struct[op.struct].encode(op))
})
@@ -142,7 +142,7 @@ export async function compareUsers (t, users) {
as they might have been split up differently..
*/
if (filterDeletedOps) {
- let opIsDeleted = yield * this.isDeleted(op.id)
+ let opIsDeleted = this.isDeleted(op.id)
if (!opIsDeleted) {
data.os[JSON.stringify(op.id)] = op
}
@@ -150,8 +150,8 @@ export async function compareUsers (t, users) {
data.os[JSON.stringify(op.id)] = op
}
}
- data.ds = yield * getDeleteSet.apply(this)
- data.ss = yield * getStateSet.apply(this)
+ data.ds = getDeleteSet.apply(this)
+ data.ss = getStateSet.apply(this)
})
await u.db.whenTransactionsFinished()
return data