removed generators

This commit is contained in:
Kevin Jahns 2017-09-06 20:10:38 +02:00
parent 6b5c02f1ce
commit ccf6d86c98
17 changed files with 1118 additions and 769 deletions

View File

@ -1,7 +1,7 @@
import Y from '../src/y.js' import Y from '../src/y.js'
import yArray from '../../y-array/src/y-array.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 yText from '../../y-text/src/Text.js'
import yXml from '../../y-xml/src/y-xml.js' import yXml from '../../y-xml/src/y-xml.js'
import yMemory from '../../y-memory/src/y-memory.js' import yMemory from '../../y-memory/src/y-memory.js'

View File

@ -1,4 +1,3 @@
import inject from 'rollup-plugin-inject'
import babel from 'rollup-plugin-babel' import babel from 'rollup-plugin-babel'
import uglify from 'rollup-plugin-uglify' import uglify from 'rollup-plugin-uglify'
import nodeResolve from 'rollup-plugin-node-resolve' import nodeResolve from 'rollup-plugin-node-resolve'
@ -16,12 +15,7 @@ export default {
browser: true browser: true
}), }),
commonjs(), commonjs(),
babel({ babel(),
runtimeHelpers: true
}),
inject({
regeneratorRuntime: 'regenerator-runtime'
}),
uglify({ uglify({
output: { output: {
comments: function (node, comment) { comments: function (node, comment) {

View File

@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
import multiEntry from 'rollup-plugin-multi-entry' import multiEntry from 'rollup-plugin-multi-entry'
export default { export default {
entry: 'test/y-xml.tests.js', entry: 'test/*.js',
moduleName: 'y-tests', moduleName: 'y-tests',
format: 'umd', format: 'umd',
plugins: [ plugins: [

View File

@ -105,6 +105,7 @@ export default function extendConnector (Y/* :any */) {
} }
} }
} }
userJoined (user, role, auth) { userJoined (user, role, auth) {
if (role == null) { if (role == null) {
throw new Error('You must specify the role of the joined user!') 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) this._syncWithUser(user)
} }
// Execute a function _when_ we are connected. // Execute a function _when_ we are connected.
// If not connected, wait until connected // If not connected, wait until connected
whenSynced (f) { whenSynced (f) {
@ -142,18 +144,20 @@ export default function extendConnector (Y/* :any */) {
this.whenSyncedListeners.push(f) this.whenSyncedListeners.push(f)
} }
} }
_syncWithUser (userid) { _syncWithUser (userid) {
if (this.role === 'slave') { if (this.role === 'slave') {
return // "The current sync has not finished or this is controlled by a master!" return // "The current sync has not finished or this is controlled by a master!"
} }
sendSyncStep1(this, userid) sendSyncStep1(this, userid)
} }
_fireIsSyncedListeners () { _fireIsSyncedListeners () {
this.y.db.whenTransactionsFinished().then(() => { this.y.db.whenTransactionsFinished().then(() => {
if (!this.isSynced) { if (!this.isSynced) {
this.isSynced = true this.isSynced = true
// It is safer to remove this! // It is safer to remove this!
// TODO: remove: yield * this.garbageCollectAfterSync() // TODO: remove: this.garbageCollectAfterSync()
// call whensynced listeners // call whensynced listeners
for (var f of this.whenSyncedListeners) { for (var f of this.whenSyncedListeners) {
f() f()
@ -162,6 +166,7 @@ export default function extendConnector (Y/* :any */) {
} }
}) })
} }
send (uid, buffer) { send (uid, buffer) {
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) { 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') 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.log('%s: Send \'%y\' to %s', this.userId, buffer, uid)
this.logMessage('Message: %Y', buffer) this.logMessage('Message: %Y', buffer)
} }
broadcast (buffer) { broadcast (buffer) {
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) { 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') 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.log('%s: Broadcast \'%y\'', this.userId, buffer)
this.logMessage('Message: %Y', buffer) this.logMessage('Message: %Y', buffer)
} }
/* /*
Buffer operations, and broadcast them when ready. Buffer operations, and broadcast them when ready.
*/ */
@ -207,6 +214,7 @@ export default function extendConnector (Y/* :any */) {
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops) this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
} }
} }
/* /*
You received a raw message, and you know that it is intended for Yjs. Then call this function. 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) encoder.writeVarString(roomname)
let messageType = decoder.readVarString() let messageType = decoder.readVarString()
let senderConn = this.connections.get(sender) let senderConn = this.connections.get(sender)
this.log('%s: Receive \'%s\' from %s', this.userId, messageType, sender) this.log('%s: Receive \'%s\' from %s', this.userId, messageType, sender)
this.logMessage('Message: %Y', buffer) this.logMessage('Message: %Y', buffer)
if (senderConn == null && !skipAuth) { if (senderConn == null && !skipAuth) {
throw new Error('Received message from unknown peer!') throw new Error('Received message from unknown peer!')
} }
if (messageType === 'sync step 1' || messageType === 'sync step 2') { if (messageType === 'sync step 1' || messageType === 'sync step 2') {
let auth = decoder.readVarUint() let auth = decoder.readVarUint()
if (senderConn.auth == null) { if (senderConn.auth == null) {
@ -284,97 +289,6 @@ export default function extendConnector (Y/* :any */) {
this._fireIsSyncedListeners() 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
<y a="4">
<b c="5"></b>
</y>
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 Y.AbstractConnector = AbstractConnector
} }

View File

@ -85,11 +85,11 @@ export default function extendDatabase (Y /* :any */) {
console.warn('gc should be empty when not synced!') console.warn('gc should be empty when not synced!')
} }
return new Promise((resolve) => { return new Promise((resolve) => {
os.requestTransaction(function * () { os.requestTransaction(function () {
if (os.y.connector != null && os.y.connector.isSynced) { if (os.y.connector != null && os.y.connector.isSynced) {
for (var i = 0; i < os.gc2.length; i++) { for (var i = 0; i < os.gc2.length; i++) {
var oid = os.gc2[i] var oid = os.gc2[i]
yield * this.garbageCollectOperation(oid) this.garbageCollectOperation(oid)
} }
os.gc2 = os.gc1 os.gc2 = os.gc1
os.gc1 = [] os.gc1 = []
@ -197,15 +197,15 @@ export default function extendDatabase (Y /* :any */) {
this.gc = false this.gc = false
this.gcTimeout = -1 this.gcTimeout = -1
return new Promise(function (resolve) { return new Promise(function (resolve) {
self.requestTransaction(function * () { self.requestTransaction(function () {
var ungc /* :Array<Struct> */ = self.gc1.concat(self.gc2) var ungc /* :Array<Struct> */ = self.gc1.concat(self.gc2)
self.gc1 = [] self.gc1 = []
self.gc2 = [] self.gc2 = []
for (var i = 0; i < ungc.length; i++) { for (var i = 0; i < ungc.length; i++) {
var op = yield * this.getOperation(ungc[i]) var op = this.getOperation(ungc[i])
if (op != null) { if (op != null) {
delete op.gc delete op.gc
yield * this.setOperation(op) this.setOperation(op)
} }
} }
resolve() resolve()
@ -224,7 +224,7 @@ export default function extendDatabase (Y /* :any */) {
returns true iff op was added to GC returns true iff op was added to GC
*/ */
* addToGarbageCollector (op, left) { addToGarbageCollector (op, left) {
if ( if (
op.gc == null && op.gc == null &&
op.deleted === true && op.deleted === true &&
@ -235,12 +235,12 @@ export default function extendDatabase (Y /* :any */) {
if (left != null && left.deleted === true) { if (left != null && left.deleted === true) {
gc = true gc = true
} else if (op.content != null && op.content.length > 1) { } 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 gc = true
} }
if (gc) { if (gc) {
op.gc = true op.gc = true
yield * this.setOperation(op) this.setOperation(op)
this.store.queueGarbageCollector(op.id) this.store.queueGarbageCollector(op.id)
return true return true
} }
@ -265,7 +265,7 @@ export default function extendDatabase (Y /* :any */) {
} }
} }
} }
* destroy () { destroy () {
clearTimeout(this.gcInterval) clearTimeout(this.gcInterval)
this.gcInterval = null this.gcInterval = null
this.stopRepairCheck() this.stopRepairCheck()
@ -274,9 +274,9 @@ export default function extendDatabase (Y /* :any */) {
if (!this.userIdPromise.inProgress) { if (!this.userIdPromise.inProgress) {
this.userIdPromise.inProgress = true this.userIdPromise.inProgress = true
var self = this var self = this
self.requestTransaction(function * () { self.requestTransaction(function () {
self.userId = userId self.userId = userId
var state = yield * this.getState(userId) var state = this.getState(userId)
self.opClock = state.clock self.opClock = state.clock
self.userIdPromise.resolve(userId) self.userIdPromise.resolve(userId)
}) })
@ -355,7 +355,7 @@ export default function extendDatabase (Y /* :any */) {
this.listenersByIdRequestPending = true this.listenersByIdRequestPending = true
var store = this var store = this
this.requestTransaction(function * () { this.requestTransaction(function () {
var exeNow = store.listenersByIdExecuteNow var exeNow = store.listenersByIdExecuteNow
store.listenersByIdExecuteNow = [] store.listenersByIdExecuteNow = []
@ -366,7 +366,7 @@ export default function extendDatabase (Y /* :any */) {
for (let key = 0; key < exeNow.length; key++) { for (let key = 0; key < exeNow.length; key++) {
let o = exeNow[key].op let o = exeNow[key].op
yield * store.tryExecute.call(this, o) store.tryExecute.call(this, o)
} }
for (var sid in ls) { for (var sid in ls) {
@ -374,9 +374,9 @@ export default function extendDatabase (Y /* :any */) {
var id = JSON.parse(sid) var id = JSON.parse(sid)
var op var op
if (typeof id[1] === 'string') { if (typeof id[1] === 'string') {
op = yield * this.getOperation(id) op = this.getOperation(id)
} else { } else {
op = yield * this.getInsertion(id) op = this.getInsertion(id)
} }
if (op == null) { if (op == null) {
store.listenersById[sid] = l store.listenersById[sid] = l
@ -385,7 +385,7 @@ export default function extendDatabase (Y /* :any */) {
let listener = l[i] let listener = l[i]
let o = listener.op let o = listener.op
if (--listener.missing === 0) { 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; addOperation: any;
whenOperationsExist: any; whenOperationsExist: any;
*/ */
* tryExecute (op) { tryExecute (op) {
this.store.addToDebug('yield * this.store.tryExecute.call(this, ', JSON.stringify(op), ')') this.store.addToDebug('this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
if (op.struct === 'Delete') { 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! // this is now called in Transaction.deleteOperation!
// yield * this.store.operationAdded(this, op) // this.store.operationAdded(this, op)
} else { } else {
// check if this op was defined // 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) { while (defined != null && defined.content != null) {
// check if this op has a longer content in the case it is defined // 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) { 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.id = [op.id[0], op.id[1] + overlapSize]
op.left = Y.utils.getLastId(defined) op.left = Y.utils.getLastId(defined)
op.origin = op.left op.origin = op.left
defined = yield * this.getOperation(op.id) // getOperation suffices here defined = this.getOperation(op.id) // getOperation suffices here
} else { } else {
break break
} }
} }
if (defined == null) { if (defined == null) {
var opid = op.id var opid = op.id
var isGarbageCollected = yield * this.isGarbageCollected(opid) var isGarbageCollected = this.isGarbageCollected(opid)
if (!isGarbageCollected) { if (!isGarbageCollected) {
// TODO: reduce number of get / put calls for op .. // TODO: reduce number of get / put calls for op ..
yield * Y.Struct[op.struct].execute.call(this, op) Y.Struct[op.struct].execute.call(this, op)
yield * this.addOperation(op) this.addOperation(op)
yield * this.store.operationAdded(this, op) this.store.operationAdded(this, op)
// operationAdded can change op.. // operationAdded can change op..
op = yield * this.getOperation(opid) op = this.getOperation(opid)
// if insertion, try to combine with left // 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: * Always:
* * Call type * * Call type
*/ */
* operationAdded (transaction, op) { operationAdded (transaction, op) {
if (op.struct === 'Delete') { if (op.struct === 'Delete') {
var type = this.initializedTypes[JSON.stringify(op.targetParent)] var type = this.initializedTypes[JSON.stringify(op.targetParent)]
if (type != null) { if (type != null) {
yield * type._changed(transaction, op) type._changed(transaction, op)
} }
} else { } else {
// increase SS // increase SS
yield * transaction.updateState(op.id[0]) transaction.updateState(op.id[0])
var opLen = op.content != null ? op.content.length : 1 var opLen = op.content != null ? op.content.length : 1
for (let i = 0; i < opLen; i++) { for (let i = 0; i < opLen; i++) {
// notify whenOperation listeners (by id) // 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 parent is deleted, mark as gc'd and return
if (op.parent != null) { if (op.parent != null) {
var parentIsDeleted = yield * transaction.isDeleted(op.parent) var parentIsDeleted = transaction.isDeleted(op.parent)
if (parentIsDeleted) { if (parentIsDeleted) {
yield * transaction.deleteList(op.id) transaction.deleteList(op.id)
return return
} }
} }
@ -490,7 +490,7 @@ export default function extendDatabase (Y /* :any */) {
// notify parent, if it was instanciated as a custom type // notify parent, if it was instanciated as a custom type
if (t != null) { if (t != null) {
let o = Y.utils.copyOperation(op) let o = Y.utils.copyOperation(op)
yield * t._changed(transaction, o) t._changed(transaction, o)
} }
if (!op.deleted) { if (!op.deleted) {
// Delete if DS says this is actually 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') // TODO: !! console.log('TODO: change this before commiting')
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
var id = [startId[0], startId[1] + i] var id = [startId[0], startId[1] + i]
var opIsDeleted = yield * transaction.isDeleted(id) var opIsDeleted = transaction.isDeleted(id)
if (opIsDeleted) { if (opIsDeleted) {
var delop = { var delop = {
struct: 'Delete', struct: 'Delete',
target: id 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() return Promise.resolve()
} }
} }
// Check if there is another transaction request. // Check if there is another transaction request.
// * the last transaction is always a flush :) // * the last transaction is always a flush :)
getNextRequest () { getNextRequest () {
@ -542,8 +543,8 @@ export default function extendDatabase (Y /* :any */) {
return null return null
} else { } else {
this.transactionIsFlushed = true this.transactionIsFlushed = true
return function * () { return function () {
yield * this.flush() this.flush()
} }
} }
} else { } 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 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! TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
*/ */
* initType (id, args) { initType (id, args) {
var sid = JSON.stringify(id) var sid = JSON.stringify(id)
var t = this.store.initializedTypes[sid] var t = this.store.initializedTypes[sid]
if (t == null) { if (t == null) {
var op/* :MapStruct | ListStruct */ = yield * this.getOperation(id) var op/* :MapStruct | ListStruct */ = this.getOperation(id)
if (op != null) { 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 this.store.initializedTypes[sid] = t
} }
} }
@ -591,11 +592,11 @@ export default function extendDatabase (Y /* :any */) {
var op = Y.Struct[structname].create(id, typedefinition[1]) var op = Y.Struct[structname].create(id, typedefinition[1])
op.type = typedefinition[0].name op.type = typedefinition[0].name
this.requestTransaction(function * () { this.requestTransaction(function () {
if (op.id[0] === 0xFFFFFF) { if (op.id[0] === 0xFFFFFF) {
yield * this.setOperation(op) this.setOperation(op)
} else { } else {
yield * this.applyCreatedOperations([op]) this.applyCreatedOperations([op])
} }
}) })
var t = Y[op.type].typeDefinition.createType(this, op, typedefinition[1]) var t = Y[op.type].typeDefinition.createType(this, op, typedefinition[1])

View File

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

View File

@ -58,7 +58,7 @@ export function computeMessageUpdate (decoder, encoder, conn) {
} }
export function sendSyncStep1 (conn, syncUser) { export function sendSyncStep1 (conn, syncUser) {
conn.y.db.requestTransaction(function * () { conn.y.db.requestTransaction(function () {
let encoder = new BinaryEncoder() let encoder = new BinaryEncoder()
encoder.writeVarString(conn.opts.room || '') encoder.writeVarString(conn.opts.room || '')
encoder.writeVarString('sync step 1') encoder.writeVarString('sync step 1')
@ -66,7 +66,7 @@ export function sendSyncStep1 (conn, syncUser) {
encoder.writeVarUint(conn.protocolVersion) encoder.writeVarUint(conn.protocolVersion)
let preferUntransformed = conn.preferUntransformed && this.os.length === 0 // TODO: length may not be defined let preferUntransformed = conn.preferUntransformed && this.os.length === 0 // TODO: length may not be defined
encoder.writeUint8(preferUntransformed ? 1 : 0) encoder.writeUint8(preferUntransformed ? 1 : 0)
yield * this.writeStateSet(encoder) this.writeStateSet(encoder)
conn.send(syncUser, encoder.createBuffer()) conn.send(syncUser, encoder.createBuffer())
}) })
} }
@ -99,19 +99,19 @@ export function computeMessageSyncStep1 (decoder, encoder, conn, senderConn, sen
return conn.y.db.whenTransactionsFinished().then(() => { return conn.y.db.whenTransactionsFinished().then(() => {
// send sync step 2 // send sync step 2
conn.y.db.requestTransaction(function * () { conn.y.db.requestTransaction(function () {
encoder.writeVarString('sync step 2') encoder.writeVarString('sync step 2')
encoder.writeVarString(conn.authInfo || '') encoder.writeVarString(conn.authInfo || '')
if (preferUntransformed) { if (preferUntransformed) {
encoder.writeUint8(1) encoder.writeUint8(1)
yield * this.writeOperationsUntransformed(encoder) this.writeOperationsUntransformed(encoder)
} else { } else {
encoder.writeUint8(0) 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()) conn.send(senderConn.uid, encoder.createBuffer())
senderConn.receivedSyncStep2 = true senderConn.receivedSyncStep2 = true
}) })
@ -174,17 +174,17 @@ export function computeMessageSyncStep2 (decoder, encoder, conn, senderConn, sen
let defer = senderConn.syncStep2 let defer = senderConn.syncStep2
// apply operations first // apply operations first
db.requestTransaction(function * () { db.requestTransaction(function () {
let osUntransformed = decoder.readUint8() let osUntransformed = decoder.readUint8()
if (osUntransformed === 1) { if (osUntransformed === 1) {
yield * this.applyOperationsUntransformed(decoder) this.applyOperationsUntransformed(decoder)
} else { } else {
this.store.applyOperations(decoder) this.store.applyOperations(decoder)
} }
}) })
// then apply ds // then apply ds
db.requestTransaction(function * () { db.requestTransaction(function () {
yield * this.applyDeleteSet(decoder) this.applyDeleteSet(decoder)
}) })
return db.whenTransactionsFinished().then(() => { return db.whenTransactionsFinished().then(() => {
conn._setSyncedWith(sender) conn._setSyncedWith(sender)

View File

@ -8,9 +8,11 @@ export default function extendPersistence (Y) {
this.saveOperationsBuffer = [] this.saveOperationsBuffer = []
this.log = Y.debug('y:persistence') this.log = Y.debug('y:persistence')
} }
saveToMessageQueue (binary) { saveToMessageQueue (binary) {
this.log('Room %s: Save message to message queue', this.y.options.connector.room) this.log('Room %s: Save message to message queue', this.y.options.connector.room)
} }
saveOperations (ops) { saveOperations (ops) {
ops = ops.map(function (op) { ops = ops.map(function (op) {
return Y.Struct[op.struct].encode(op) return Y.Struct[op.struct].encode(op)
@ -39,5 +41,6 @@ export default function extendPersistence (Y) {
} }
} }
} }
Y.AbstractPersistence = AbstractPersistence Y.AbstractPersistence = AbstractPersistence
} }

506
src/RedBlackTree.js Normal file
View File

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

View File

@ -73,8 +73,8 @@ export default function extendStruct (Y) {
requiredOps: function (op) { requiredOps: function (op) {
return [] // [op.target] return [] // [op.target]
}, },
execute: function * (op) { execute: function (op) {
return yield * this.deleteOperation(op.target, op.length || 1) return this.deleteOperation(op.target, op.length || 1)
} }
} }
@ -216,18 +216,18 @@ export default function extendStruct (Y) {
} }
return ids return ids
}, },
getDistanceToOrigin: function * (op) { getDistanceToOrigin: function (op) {
if (op.left == null) { if (op.left == null) {
return 0 return 0
} else { } else {
var d = 0 var d = 0
var o = yield * this.getInsertion(op.left) var o = this.getInsertion(op.left)
while (!Y.utils.matchesId(o, op.origin)) { while (!Y.utils.matchesId(o, op.origin)) {
d++ d++
if (o.left == null) { if (o.left == null) {
break break
} else { } else {
o = yield * this.getInsertion(o.left) o = this.getInsertion(o.left)
} }
} }
return d return d
@ -248,7 +248,7 @@ export default function extendStruct (Y) {
# case 3: $origin > $o.origin # case 3: $origin > $o.origin
# $this insert_position is to the left of $o (forever!) # $this insert_position is to the left of $o (forever!)
*/ */
execute: function * (op) { execute: function (op) {
var i // loop counter var i // loop counter
// during this function some ops may get split into two pieces (e.g. with getInsertionCleanEnd) // 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 != if (op.origin != null) { // TODO: !== instead of !=
// we save in origin that op originates in it // we save in origin that op originates in it
// we need that later when we eventually garbage collect origin (see transaction) // 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) { if (origin.originOf == null) {
origin.originOf = [] origin.originOf = []
} }
origin.originOf.push(op.id) origin.originOf.push(op.id)
yield * this.setOperation(origin) this.setOperation(origin)
if (origin.right != null) { if (origin.right != null) {
tryToRemergeLater.push(origin.right) 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.. // now we begin to insert op in the list of insertions..
var o var o
@ -277,29 +277,29 @@ export default function extendStruct (Y) {
// find o. o is the first conflicting operation // find o. o is the first conflicting operation
if (op.left != null) { 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) { if (!Y.utils.compareIds(op.left, op.origin) && o.right != null) {
// only if not added previously // only if not added previously
tryToRemergeLater.push(o.right) 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 } else { // left == null
parent = yield * this.getOperation(op.parent) parent = this.getOperation(op.parent)
let startId = op.parentSub ? parent.map[op.parentSub] : parent.start 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 o = start
} }
// make sure to split op.right if necessary (also add to tryCombineWithLeft) // make sure to split op.right if necessary (also add to tryCombineWithLeft)
if (op.right != null) { if (op.right != null) {
tryToRemergeLater.push(op.right) tryToRemergeLater.push(op.right)
yield * this.getInsertionCleanStart(op.right) this.getInsertionCleanStart(op.right)
} }
// handle conflicts // handle conflicts
while (true) { while (true) {
if (o != null && !Y.utils.compareIds(o.id, op.right)) { 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) { if (oOriginDistance === i) {
// case 1 // case 1
if (o.id[0] < op.id[0]) { if (o.id[0] < op.id[0]) {
@ -317,7 +317,7 @@ export default function extendStruct (Y) {
} }
i++ i++
if (o.right != null) { if (o.right != null) {
o = yield * this.getInsertion(o.right) o = this.getInsertion(o.right)
} else { } else {
o = null o = null
} }
@ -330,17 +330,17 @@ export default function extendStruct (Y) {
var left = null var left = null
var right = null var right = null
if (parent == null) { if (parent == null) {
parent = yield * this.getOperation(op.parent) parent = this.getOperation(op.parent)
} }
// reconnect left and set right of op // reconnect left and set right of op
if (op.left != null) { if (op.left != null) {
left = yield * this.getInsertion(op.left) left = this.getInsertion(op.left)
// link left // link left
op.right = left.right op.right = left.right
left.right = op.id left.right = op.id
yield * this.setOperation(left) this.setOperation(left)
} else { } else {
// set op.right from parent, if necessary // set op.right from parent, if necessary
op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
@ -348,33 +348,33 @@ export default function extendStruct (Y) {
// reconnect right // reconnect right
if (op.right != null) { if (op.right != null) {
// TODO: wanna connect right too? // TODO: wanna connect right too?
right = yield * this.getOperation(op.right) right = this.getOperation(op.right)
right.left = Y.utils.getLastId(op) right.left = Y.utils.getLastId(op)
// if right exists, and it is supposed to be gc'd. Remove it from the gc // if right exists, and it is supposed to be gc'd. Remove it from the gc
if (right.gc != null) { if (right.gc != null) {
if (right.content != null && right.content.length > 1) { if (right.content != null && right.content.length > 1) {
right = yield * this.getInsertionCleanEnd(right.id) right = this.getInsertionCleanEnd(right.id)
} }
this.store.removeFromGarbageCollector(right) this.store.removeFromGarbageCollector(right)
} }
yield * this.setOperation(right) this.setOperation(right)
} }
// update parents .map/start/end properties // update parents .map/start/end properties
if (op.parentSub != null) { if (op.parentSub != null) {
if (left == null) { if (left == null) {
parent.map[op.parentSub] = op.id parent.map[op.parentSub] = op.id
yield * this.setOperation(parent) this.setOperation(parent)
} }
// is a child of a map struct. // is a child of a map struct.
// Then also make sure that only the most left element is not deleted // 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) // We do not call the type in this case (this is what the third parameter is for)
if (op.right != null) { if (op.right != null) {
yield * this.deleteOperation(op.right, 1, true) this.deleteOperation(op.right, 1, true)
} }
if (op.left != null) { if (op.left != null) {
yield * this.deleteOperation(op.id, 1, true) this.deleteOperation(op.id, 1, true)
} }
} else { } else {
if (right == null || left == null) { if (right == null || left == null) {
@ -384,14 +384,14 @@ export default function extendStruct (Y) {
if (left == null) { if (left == null) {
parent.start = op.id parent.start = op.id
} }
yield * this.setOperation(parent) this.setOperation(parent)
} }
} }
// try to merge original op.left and op.origin // try to merge original op.left and op.origin
for (i = 0; i < tryToRemergeLater.length; i++) { for (i = 0; i < tryToRemergeLater.length; i++) {
var m = yield * this.getOperation(tryToRemergeLater[i]) var m = this.getOperation(tryToRemergeLater[i])
yield * this.tryCombineWithLeft(m) this.tryCombineWithLeft(m)
} }
} }
} }
@ -451,16 +451,16 @@ export default function extendStruct (Y) {
*/ */
return [] return []
}, },
execute: function * (op) { execute: function (op) {
op.start = null op.start = null
op.end = null op.end = null
}, },
ref: function * (op, pos) { ref: function (op, pos) {
if (op.start == null) { if (op.start == null) {
return null return null
} }
var res = null var res = null
var o = yield * this.getOperation(op.start) var o = this.getOperation(op.start)
while (true) { while (true) {
if (!o.deleted) { if (!o.deleted) {
@ -468,18 +468,18 @@ export default function extendStruct (Y) {
pos-- pos--
} }
if (pos >= 0 && o.right != null) { if (pos >= 0 && o.right != null) {
o = yield * this.getOperation(o.right) o = this.getOperation(o.right)
} else { } else {
break break
} }
} }
return res return res
}, },
map: function * (o, f) { map: function (o, f) {
o = o.start o = o.start
var res = [] var res = []
while (o != null) { // TODO: change to != (at least some convention) while (o != null) { // TODO: change to != (at least some convention)
var operation = yield * this.getOperation(o) var operation = this.getOperation(o)
if (!operation.deleted) { if (!operation.deleted) {
res.push(f(operation)) res.push(f(operation))
} }
@ -532,23 +532,23 @@ export default function extendStruct (Y) {
requiredOps: function () { requiredOps: function () {
return [] return []
}, },
execute: function * (op) { execute: function (op) {
op.start = null op.start = null
op.end = null op.end = null
}, },
/* /*
Get a property by name Get a property by name
*/ */
get: function * (op, name) { get: function (op, name) {
var oid = op.map[name] var oid = op.map[name]
if (oid != null) { if (oid != null) {
var res = yield * this.getOperation(oid) var res = this.getOperation(oid)
if (res == null || res.deleted) { if (res == null || res.deleted) {
return void 0 return void 0
} else if (res.opContent == null) { } else if (res.opContent == null) {
return res.content[0] return res.content[0]
} else { } else {
return yield * this.getType(res.opContent) return this.getType(res.opContent)
} }
} }
} }
@ -608,7 +608,7 @@ export default function extendStruct (Y) {
requiredOps: function () { requiredOps: function () {
return [] return []
}, },
execute: function * () {}, execute: function () {},
ref: Struct.List.ref, ref: Struct.List.ref,
map: Struct.List.map, map: Struct.List.map,
/* /*

View File

@ -86,11 +86,11 @@ export default function extendTransaction (Y) {
* does not check for Struct.*.requiredOps() * does not check for Struct.*.requiredOps()
* also broadcasts it through the connector * also broadcasts it through the connector
*/ */
* applyCreatedOperations (ops) { applyCreatedOperations (ops) {
var send = [] var send = []
for (var i = 0; i < ops.length; i++) { for (var i = 0; i < ops.length; i++) {
var op = ops[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') { if (op.id == null || typeof op.id[1] !== 'string') {
send.push(Y.Struct[op.struct].encode(op)) send.push(Y.Struct[op.struct].encode(op))
} }
@ -104,17 +104,17 @@ export default function extendTransaction (Y) {
} }
} }
* deleteList (start) { deleteList (start) {
while (start != null) { while (start != null) {
start = yield * this.getOperation(start) start = this.getOperation(start)
if (!start.gc) { if (!start.gc) {
start.gc = true start.gc = true
start.deleted = true start.deleted = true
yield * this.setOperation(start) this.setOperation(start)
var delLength = start.content != null ? start.content.length : 1 var delLength = start.content != null ? start.content.length : 1
yield * this.markDeleted(start.id, delLength) this.markDeleted(start.id, delLength)
if (start.opContent != null) { if (start.opContent != null) {
yield * this.deleteOperation(start.opContent) this.deleteOperation(start.opContent)
} }
this.store.queueGarbageCollector(start.id) 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. Mark an operation as deleted, and add it to the GC, if possible.
*/ */
* deleteOperation (targetId, length, preventCallType) /* :Generator<any, any, any> */ { deleteOperation (targetId, length, preventCallType) /* :Generator<any, any, any> */ {
if (length == null) { if (length == null) {
length = 1 length = 1
} }
yield * this.markDeleted(targetId, length) this.markDeleted(targetId, length)
while (length > 0) { while (length > 0) {
var callType = false 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 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]) { 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 // 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.deleted) {
if (target.id[1] < targetId[1]) { if (target.id[1] < targetId[1]) {
// starts to the left of the deletion range // 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! targetLength = target.content.length // must have content property!
} }
if (target.id[1] + targetLength > targetId[1] + length) { if (target.id[1] + targetLength > targetId[1] + length) {
// ends to the right of the deletion range // 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 targetLength = target.content.length
} }
} }
@ -163,35 +163,35 @@ export default function extendTransaction (Y) {
// delete containing lists // delete containing lists
if (target.start != null) { if (target.start != null) {
// TODO: don't do it like this .. -.- // TODO: don't do it like this .. -.-
yield * this.deleteList(target.start) this.deleteList(target.start)
// yield * this.deleteList(target.id) -- do not gc itself because this may still get referenced // this.deleteList(target.id) -- do not gc itself because this may still get referenced
} }
if (target.map != null) { if (target.map != null) {
for (var name in target.map) { for (var name in target.map) {
yield * this.deleteList(target.map[name]) this.deleteList(target.map[name])
} }
// TODO: here to.. (see above) // TODO: here to.. (see above)
// yield * this.deleteList(target.id) -- see above // this.deleteList(target.id) -- see above
} }
if (target.opContent != null) { if (target.opContent != null) {
yield * this.deleteOperation(target.opContent) this.deleteOperation(target.opContent)
// target.opContent = null // target.opContent = null
} }
if (target.requires != null) { if (target.requires != null) {
for (var i = 0; i < target.requires.length; i++) { for (var i = 0; i < target.requires.length; i++) {
yield * this.deleteOperation(target.requires[i]) this.deleteOperation(target.requires[i])
} }
} }
} }
var left var left
if (target.left != null) { if (target.left != null) {
left = yield * this.getInsertion(target.left) left = this.getInsertion(target.left)
} else { } else {
left = null left = null
} }
// set here because it was deleted and/or gc'd // 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. Check if it is possible to add right to the gc.
@ -200,12 +200,12 @@ export default function extendTransaction (Y) {
*/ */
var right var right
if (target.right != null) { if (target.right != null) {
right = yield * this.getOperation(target.right) right = this.getOperation(target.right)
} else { } else {
right = null right = null
} }
if (callType && !preventCallType) { if (callType && !preventCallType) {
yield * this.store.operationAdded(this, { this.store.operationAdded(this, {
struct: 'Delete', struct: 'Delete',
target: target.id, target: target.id,
length: targetLength, length: targetLength,
@ -213,9 +213,9 @@ export default function extendTransaction (Y) {
}) })
} }
// need to gc in the end! // need to gc in the end!
yield * this.store.addToGarbageCollector.call(this, target, left) this.store.addToGarbageCollector.call(this, target, left)
if (right != null) { 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 Mark an operation as deleted&gc'd
*/ */
* markGarbageCollected (id, len) { markGarbageCollected (id, len) {
// this.mem.push(["gc", id]); // this.mem.push(["gc", id]);
this.store.addToDebug('yield * this.markGarbageCollected(', id, ', ', len, ')') this.store.addToDebug('this.markGarbageCollected(', id, ', ', len, ')')
var n = yield * this.markDeleted(id, len) var n = this.markDeleted(id, len)
if (n.id[1] < id[1] && !n.gc) { if (n.id[1] < id[1] && !n.gc) {
// un-extend left // un-extend left
var newlen = n.len - (id[1] - n.id[1]) var newlen = n.len - (id[1] - n.id[1])
n.len -= newlen n.len -= newlen
yield * this.ds.put(n) this.ds.put(n)
n = {id: id, len: newlen, gc: false} n = {id: id, len: newlen, gc: false}
yield * this.ds.put(n) this.ds.put(n)
} }
// get prev&next before adding a new operation // get prev&next before adding a new operation
var prev = yield * this.ds.findPrev(id) var prev = this.ds.findPrev(id)
var next = yield * this.ds.findNext(id) var next = this.ds.findNext(id)
if (id[1] + len < n.id[1] + n.len && !n.gc) { if (id[1] + len < n.id[1] + n.len && !n.gc) {
// un-extend right // 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 n.len = len
} }
// set gc'd // 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) Y.utils.compareIds([prev.id[0], prev.id[1] + prev.len], n.id)
) { ) {
prev.len += n.len prev.len += n.len
yield * this.ds.delete(n.id) this.ds.delete(n.id)
n = prev n = prev
// ds.put n here? // 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) Y.utils.compareIds([n.id[0], n.id[1] + n.len], next.id)
) { ) {
n.len += next.len n.len += next.len
yield * this.ds.delete(next.id) this.ds.delete(next.id)
} }
yield * this.ds.put(n) this.ds.put(n)
yield * this.updateState(n.id[0]) this.updateState(n.id[0])
} }
/* /*
Mark an operation as deleted. Mark an operation as deleted.
returns the delete node returns the delete node
*/ */
* markDeleted (id, length) { markDeleted (id, length) {
if (length == null) { if (length == null) {
length = 1 length = 1
} }
// this.mem.push(["del", id]); // 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 != null && n.id[0] === id[0]) {
if (n.id[1] <= id[1] && id[1] <= n.id[1] + n.len) { if (n.id[1] <= id[1] && id[1] <= n.id[1] + n.len) {
// id is in n's range // id is in n's range
@ -293,7 +293,7 @@ export default function extendTransaction (Y) {
if (diff < length) { if (diff < length) {
// a partial deletion // a partial deletion
n = {id: [id[0], id[1] + diff], len: length - diff, gc: false} n = {id: [id[0], id[1] + diff], len: length - diff, gc: false}
yield * this.ds.put(n) this.ds.put(n)
} else { } else {
// already gc'd // already gc'd
throw new Error( throw new Error(
@ -308,15 +308,15 @@ export default function extendTransaction (Y) {
} else { } else {
// cannot extend left (there is no left!) // cannot extend left (there is no left!)
n = {id: id, len: length, gc: false} 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 { } else {
// cannot extend left // cannot extend left
n = {id: id, len: length, gc: false} n = {id: id, len: length, gc: false}
yield * this.ds.put(n) this.ds.put(n)
} }
// can extend right? // can extend right?
var next = yield * this.ds.findNext(n.id) var next = this.ds.findNext(n.id)
if ( if (
next != null && next != null &&
n.id[0] === next.id[0] && n.id[0] === next.id[0] &&
@ -332,8 +332,8 @@ export default function extendTransaction (Y) {
// delete the missing range after next // delete the missing range after next
diff = diff - next.len // missing range after next diff = diff - next.len // missing range after next
if (diff > 0) { if (diff > 0) {
yield * this.ds.put(n) // unneccessary? TODO! this.ds.put(n) // unneccessary? TODO!
yield * this.markDeleted([next.id[0], next.id[1] + next.len], diff) this.markDeleted([next.id[0], next.id[1] + next.len], diff)
} }
} }
break break
@ -342,8 +342,8 @@ export default function extendTransaction (Y) {
if (diff > next.len) { if (diff > next.len) {
// n is even longer than next // n is even longer than next
// get next.next, and try to extend it // get next.next, and try to extend it
var _next = yield * this.ds.findNext(next.id) var _next = this.ds.findNext(next.id)
yield * this.ds.delete(next.id) this.ds.delete(next.id)
if (_next == null || n.id[0] !== _next.id[0]) { if (_next == null || n.id[0] !== _next.id[0]) {
break break
} else { } else {
@ -354,13 +354,13 @@ export default function extendTransaction (Y) {
} else { } else {
// n just partially overlaps with next. extend n, delete next, and break this loop // n just partially overlaps with next. extend n, delete next, and break this loop
n.len += next.len - diff n.len += next.len - diff
yield * this.ds.delete(next.id) this.ds.delete(next.id)
break break
} }
} }
} }
} }
yield * this.ds.put(n) this.ds.put(n)
return n return n
} }
/* /*
@ -368,7 +368,7 @@ export default function extendTransaction (Y) {
other clients (e.g. master). This will query the database for other clients (e.g. master). This will query the database for
operations that can be gc'd and add them to the garbage collector. operations that can be gc'd and add them to the garbage collector.
*/ */
* garbageCollectAfterSync () { garbageCollectAfterSync () {
// debugger // debugger
if (this.store.gc1.length > 0 || this.store.gc2.length > 0) { if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
console.warn('gc should be empty after sync') console.warn('gc should be empty after sync')
@ -376,28 +376,28 @@ export default function extendTransaction (Y) {
if (!this.store.gc) { if (!this.store.gc) {
return return
} }
yield * this.os.iterate(this, null, null, function * (op) { this.os.iterate(this, null, null, function (op) {
if (op.gc) { if (op.gc) {
delete op.gc delete op.gc
yield * this.setOperation(op) this.setOperation(op)
} }
if (op.parent != null) { if (op.parent != null) {
var parentDeleted = yield * this.isDeleted(op.parent) var parentDeleted = this.isDeleted(op.parent)
if (parentDeleted) { if (parentDeleted) {
op.gc = true op.gc = true
if (!op.deleted) { 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 op.deleted = true
if (op.opContent != null) { if (op.opContent != null) {
yield * this.deleteOperation(op.opContent) this.deleteOperation(op.opContent)
} }
if (op.requires != null) { if (op.requires != null) {
for (var i = 0; i < op.requires.length; i++) { 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!) this.store.gc1.push(op.id) // this is ok becaues its shortly before sync (otherwise use queueGarbageCollector!)
return return
} }
@ -405,9 +405,9 @@ export default function extendTransaction (Y) {
if (op.deleted) { if (op.deleted) {
var left = null var left = null
if (op.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 parent.end
* reset origins of all right ops * reset origins of all right ops
*/ */
* garbageCollectOperation (id) { garbageCollectOperation (id) {
this.store.addToDebug('yield * this.garbageCollectOperation(', id, ')') this.store.addToDebug('this.garbageCollectOperation(', id, ')')
var o = yield * this.getOperation(id) var o = this.getOperation(id)
yield * this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd 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 op exists, then clean that mess up..
if (o != null) { if (o != null) {
var deps = [] var deps = []
@ -434,32 +434,32 @@ export default function extendTransaction (Y) {
deps = deps.concat(o.requires) deps = deps.concat(o.requires)
} }
for (var i = 0; i < deps.length; i++) { 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 != null) {
if (!dep.deleted) { if (!dep.deleted) {
yield * this.deleteOperation(dep.id) this.deleteOperation(dep.id)
dep = yield * this.getOperation(dep.id) dep = this.getOperation(dep.id)
} }
dep.gc = true dep.gc = true
yield * this.setOperation(dep) this.setOperation(dep)
this.store.queueGarbageCollector(dep.id) this.store.queueGarbageCollector(dep.id)
} else { } else {
yield * this.markGarbageCollected(deps[i], 1) this.markGarbageCollected(deps[i], 1)
} }
} }
// remove gc'd op from the left op, if it exists // remove gc'd op from the left op, if it exists
if (o.left != null) { if (o.left != null) {
var left = yield * this.getInsertion(o.left) var left = this.getInsertion(o.left)
left.right = o.right left.right = o.right
yield * this.setOperation(left) this.setOperation(left)
} }
// remove gc'd op from the right op, if it exists // remove gc'd op from the right op, if it exists
// also reset origins of right ops // also reset origins of right ops
if (o.right != null) { if (o.right != null) {
var right = yield * this.getOperation(o.right) var right = this.getOperation(o.right)
right.left = o.left right.left = o.left
yield * this.setOperation(right) this.setOperation(right)
if (o.originOf != null && o.originOf.length > 0) { if (o.originOf != null && o.originOf.length > 0) {
// find new origin of right ops // find new origin of right ops
@ -478,7 +478,7 @@ export default function extendTransaction (Y) {
right.origin = neworigin right.origin = neworigin
// search until you find origin pointer to the left of o // search until you find origin pointer to the left of o
if (right.right != null) { if (right.right != null) {
var i = yield * this.getOperation(right.right) var i = this.getOperation(right.right)
var ids = [o.id, o.right] var ids = [o.id, o.right]
while (ids.some(function (id) { while (ids.some(function (id) {
return Y.utils.compareIds(id, i.origin) return Y.utils.compareIds(id, i.origin)
@ -486,14 +486,14 @@ export default function extendTransaction (Y) {
if (Y.utils.compareIds(i.origin, o.id)) { if (Y.utils.compareIds(i.origin, o.id)) {
// reset origin of i // reset origin of i
i.origin = neworigin i.origin = neworigin
yield * this.setOperation(i) this.setOperation(i)
} }
// get next i // get next i
if (i.right == null) { if (i.right == null) {
break break
} else { } else {
ids.push(i.id) 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 ** // ** Now the new implementation starts **
// reset neworigin of all originOf[*] // reset neworigin of all originOf[*]
for (var _i in o.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) { if (originsIn != null) {
originsIn.origin = neworigin originsIn.origin = neworigin
yield * this.setOperation(originsIn) this.setOperation(originsIn)
} }
} }
if (neworigin != null) { if (neworigin != null) {
var neworigin_ = yield * this.getInsertion(neworigin) var neworigin_ = this.getInsertion(neworigin)
if (neworigin_.originOf == null) { if (neworigin_.originOf == null) {
neworigin_.originOf = o.originOf neworigin_.originOf = o.originOf
} else { } else {
neworigin_.originOf = o.originOf.concat(neworigin_.originOf) neworigin_.originOf = o.originOf.concat(neworigin_.originOf)
} }
yield * this.setOperation(neworigin_) this.setOperation(neworigin_)
} }
// we don't need to set right here, because // we don't need to set right here, because
// right should be in o.originOf => it is set it the previous for loop // 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. // o may originate in another operation.
// Since o is deleted, we have to reset o.origin's `originOf` property // Since o is deleted, we have to reset o.origin's `originOf` property
if (o.origin != null) { if (o.origin != null) {
var origin = yield * this.getInsertion(o.origin) var origin = this.getInsertion(o.origin)
origin.originOf = origin.originOf.filter(function (_id) { origin.originOf = origin.originOf.filter(function (_id) {
return !Y.utils.compareIds(id, _id) return !Y.utils.compareIds(id, _id)
}) })
yield * this.setOperation(origin) this.setOperation(origin)
} }
var parent var parent
if (o.parent != null) { if (o.parent != null) {
parent = yield * this.getOperation(o.parent) parent = this.getOperation(o.parent)
} }
// remove gc'd op from parent, if it exists // remove gc'd op from parent, if it exists
if (parent != null) { if (parent != null) {
@ -559,38 +559,38 @@ export default function extendTransaction (Y) {
} }
} }
if (setParent) { if (setParent) {
yield * this.setOperation(parent) this.setOperation(parent)
} }
} }
// finally remove it from the os // finally remove it from the os
yield * this.removeOperation(o.id) this.removeOperation(o.id)
} }
} }
* checkDeleteStoreForState (state) { checkDeleteStoreForState (state) {
var n = yield * this.ds.findWithUpperBound([state.user, state.clock]) var n = this.ds.findWithUpperBound([state.user, state.clock])
if (n != null && n.id[0] === state.user && n.gc) { if (n != null && n.id[0] === state.user && n.gc) {
state.clock = Math.max(state.clock, n.id[1] + n.len) state.clock = Math.max(state.clock, n.id[1] + n.len)
} }
} }
* updateState (user) { updateState (user) {
var state = yield * this.getState(user) var state = this.getState(user)
yield * this.checkDeleteStoreForState(state) this.checkDeleteStoreForState(state)
var o = yield * this.getInsertion([user, state.clock]) var o = this.getInsertion([user, state.clock])
var oLength = (o != null && o.content != null) ? o.content.length : 1 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) { 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 // 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 state.clock += oLength
yield * this.checkDeleteStoreForState(state) this.checkDeleteStoreForState(state)
o = yield * this.os.findNext(o.id) o = this.os.findNext(o.id)
oLength = (o != null && o.content != null) ? o.content.length : 1 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 apply a delete set in order to get
the state of the supplied ds the state of the supplied ds
*/ */
* applyDeleteSet (decoder) { applyDeleteSet (decoder) {
var deletions = [] var deletions = []
let dsLength = decoder.readUint32() let dsLength = decoder.readUint32()
@ -606,7 +606,7 @@ export default function extendTransaction (Y) {
} }
var pos = 0 var pos = 0
var d = dv[pos] 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: // cases:
// 1. d deletes something to the right of n // 1. d deletes something to the right of n
// => go to next n (break) // => go to next n (break)
@ -655,14 +655,14 @@ export default function extendTransaction (Y) {
for (var i = 0; i < deletions.length; i++) { for (var i = 0; i < deletions.length; i++) {
var del = deletions[i] var del = deletions[i]
// always try to delete.. // always try to delete..
yield * this.deleteOperation([del[0], del[1]], del[2]) this.deleteOperation([del[0], del[1]], del[2])
if (del[3]) { if (del[3]) {
// gc.. // 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.. // remove operation..
var counter = del[1] + del[2] var counter = del[1] + del[2]
while (counter >= del[1]) { 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) { if (o == null) {
break break
} }
@ -673,14 +673,14 @@ export default function extendTransaction (Y) {
} }
if (o.id[1] + oLen > del[1] + del[2]) { if (o.id[1] + oLen > del[1] + del[2]) {
// overlaps right // 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]) { if (o.id[1] < del[1]) {
// overlaps left // overlaps left
o = yield * this.getInsertionCleanStart([del[0], del[1]]) o = this.getInsertionCleanStart([del[0], del[1]])
} }
counter = o.id[1] counter = o.id[1]
yield * this.garbageCollectOperation(o.id) this.garbageCollectOperation(o.id)
} }
} }
if (this.store.forwardAppliedOperations || this.store.y.persistence != null) { if (this.store.forwardAppliedOperations || this.store.y.persistence != null) {
@ -695,16 +695,16 @@ export default function extendTransaction (Y) {
} }
} }
} }
* isGarbageCollected (id) { isGarbageCollected (id) {
var n = yield * this.ds.findWithUpperBound(id) var n = this.ds.findWithUpperBound(id)
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc 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 A DeleteSet (ds) describes all the deleted ops in the OS
*/ */
* writeDeleteSet (encoder) { writeDeleteSet (encoder) {
var ds = new Map() 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 user = n.id[0]
var counter = n.id[1] var counter = n.id[1]
var len = n.len var len = n.len
@ -731,16 +731,16 @@ export default function extendTransaction (Y) {
} }
} }
} }
* isDeleted (id) { isDeleted (id) {
var n = yield * this.ds.findWithUpperBound(id) var n = this.ds.findWithUpperBound(id)
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
} }
* setOperation (op) { setOperation (op) {
yield * this.os.put(op) this.os.put(op)
return op return op
} }
* addOperation (op) { addOperation (op) {
yield * this.os.put(op) this.os.put(op)
// case op is created by this user, op is already broadcasted in applyCreatedOperations // 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 (op.id[0] !== this.store.userId && typeof op.id[1] !== 'string') {
if (this.store.forwardAppliedOperations) { 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) // if insertion, try to combine with left insertion (if both have content property)
* tryCombineWithLeft (op) { tryCombineWithLeft (op) {
if ( if (
op != null && op != null &&
op.left != null && op.left != null &&
@ -761,7 +761,7 @@ export default function extendTransaction (Y) {
op.left[0] === op.id[0] && op.left[0] === op.id[0] &&
Y.utils.compareIds(op.left, op.origin) Y.utils.compareIds(op.left, op.origin)
) { ) {
var left = yield * this.getInsertion(op.left) var left = this.getInsertion(op.left)
if (left.content != null && if (left.content != null &&
left.id[1] + left.content.length === op.id[1] && left.id[1] + left.content.length === op.id[1] &&
left.originOf.length === 1 && left.originOf.length === 1 &&
@ -776,13 +776,13 @@ export default function extendTransaction (Y) {
} }
left.content = left.content.concat(op.content) left.content = left.content.concat(op.content)
left.right = op.right left.right = op.right
yield * this.os.delete(op.id) this.os.delete(op.id)
yield * this.setOperation(left) this.setOperation(left)
} }
} }
} }
* getInsertion (id) { getInsertion (id) {
var ins = yield * this.os.findWithUpperBound(id) var ins = this.os.findWithUpperBound(id)
if (ins == null) { if (ins == null) {
return null return null
} else { } else {
@ -794,14 +794,14 @@ export default function extendTransaction (Y) {
} }
} }
} }
* getInsertionCleanStartEnd (id) { getInsertionCleanStartEnd (id) {
yield * this.getInsertionCleanStart(id) this.getInsertionCleanStart(id)
return yield * this.getInsertionCleanEnd(id) return this.getInsertionCleanEnd(id)
} }
// Return an insertion such that id is the first element of content // Return an insertion such that id is the first element of content
// This function manipulates an operation, if necessary // This function manipulates an operation, if necessary
* getInsertionCleanStart (id) { getInsertionCleanStart (id) {
var ins = yield * this.getInsertion(id) var ins = this.getInsertion(id)
if (ins != null) { if (ins != null) {
if (ins.id[1] === id[1]) { if (ins.id[1] === id[1]) {
return ins return ins
@ -815,8 +815,8 @@ export default function extendTransaction (Y) {
left.right = ins.id left.right = ins.id
ins.left = leftLid ins.left = leftLid
// debugger // check // debugger // check
yield * this.setOperation(left) this.setOperation(left)
yield * this.setOperation(ins) this.setOperation(ins)
if (left.gc) { if (left.gc) {
this.store.queueGarbageCollector(ins.id) 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 // Return an insertion such that id is the last element of content
// This function manipulates an operation, if necessary // This function manipulates an operation, if necessary
* getInsertionCleanEnd (id) { getInsertionCleanEnd (id) {
var ins = yield * this.getInsertion(id) var ins = this.getInsertion(id)
if (ins != null) { if (ins != null) {
if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) { if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) {
return ins return ins
@ -843,8 +843,8 @@ export default function extendTransaction (Y) {
ins.right = right.id ins.right = right.id
right.left = insLid right.left = insLid
// debugger // check // debugger // check
yield * this.setOperation(right) this.setOperation(right)
yield * this.setOperation(ins) this.setOperation(ins)
if (ins.gc) { if (ins.gc) {
this.store.queueGarbageCollector(right.id) this.store.queueGarbageCollector(right.id)
} }
@ -854,8 +854,8 @@ export default function extendTransaction (Y) {
return null return null
} }
} }
* getOperation (id/* :any */)/* :Transaction<any> */ { getOperation (id/* :any */)/* :Transaction<any> */ {
var o = yield * this.os.find(id) var o = this.os.find(id)
if (id[0] !== 0xFFFFFF || o != null) { if (id[0] !== 0xFFFFFF || o != null) {
return o return o
} else { // type is string } else { // type is string
@ -870,7 +870,7 @@ export default function extendTransaction (Y) {
} }
var op = Y.Struct[struct].create(id, args) var op = Y.Struct[struct].create(id, args)
op.type = comp[1] op.type = comp[1]
yield * this.setOperation(op) this.setOperation(op)
return op return op
} else { } else {
throw new Error( throw new Error(
@ -880,18 +880,18 @@ export default function extendTransaction (Y) {
} }
} }
} }
* removeOperation (id) { removeOperation (id) {
yield * this.os.delete(id) this.os.delete(id)
} }
* setState (state) { setState (state) {
var val = { var val = {
id: [state.user], id: [state.user],
clock: state.clock clock: state.clock
} }
yield * this.ss.put(val) this.ss.put(val)
} }
* getState (user) { getState (user) {
var n = yield * this.ss.find([user]) var n = this.ss.find([user])
var clock = n == null ? null : n.clock var clock = n == null ? null : n.clock
if (clock == null) { if (clock == null) {
clock = 0 clock = 0
@ -901,9 +901,9 @@ export default function extendTransaction (Y) {
clock: clock clock: clock
} }
} }
* getStateVector () { getStateVector () {
var stateVector = [] var stateVector = []
yield * this.ss.iterate(this, null, null, function * (n) { this.ss.iterate(this, null, null, function (n) {
stateVector.push({ stateVector.push({
user: n.id[0], user: n.id[0],
clock: n.clock clock: n.clock
@ -911,18 +911,18 @@ export default function extendTransaction (Y) {
}) })
return stateVector return stateVector
} }
* getStateSet () { getStateSet () {
var ss = {} 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 ss[n.id[0]] = n.clock
}) })
return ss return ss
} }
* writeStateSet (encoder) { writeStateSet (encoder) {
let lenPosition = encoder.pos let lenPosition = encoder.pos
let len = 0 let len = 0
encoder.writeUint32(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.id[0])
encoder.writeVarUint(n.clock) encoder.writeVarUint(n.clock)
len++ 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) 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 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! // TODO: use bounds here!
if (startSS == null) { if (startSS == null) {
startSS = new Map() startSS = new Map()
} }
var send = [] var send = []
var endSV = yield * this.getStateVector() var endSV = this.getStateVector()
for (let endState of endSV) { for (let endState of endSV) {
let user = endState.user let user = endState.user
if (user === 0xFFFFFF) { if (user === 0xFFFFFF) {
@ -993,7 +993,7 @@ export default function extendTransaction (Y) {
if (startPos > 0) { if (startPos > 0) {
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter) // There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
// find out if that is the case // find out if that is the case
let firstMissing = yield * this.getInsertion([user, startPos]) let firstMissing = this.getInsertion([user, startPos])
if (firstMissing != null) { if (firstMissing != null) {
// update startPos // update startPos
startPos = firstMissing.id[1] startPos = firstMissing.id[1]
@ -1007,7 +1007,7 @@ export default function extendTransaction (Y) {
if (user === 0xFFFFFF) { if (user === 0xFFFFFF) {
continue 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) op = Y.Struct[op.struct].encode(op)
if (op.struct !== 'Insert') { if (op.struct !== 'Insert') {
send.push(op) send.push(op)
@ -1038,7 +1038,7 @@ export default function extendTransaction (Y) {
*/ */
break break
} }
o = yield * this.getInsertion(o.left) o = this.getInsertion(o.left)
// we set another o, check if we can reduce $missingOrigins // we set another o, check if we can reduce $missingOrigins
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) { while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
missingOrigins.pop() missingOrigins.pop()
@ -1076,7 +1076,7 @@ export default function extendTransaction (Y) {
return send.reverse() return send.reverse()
} }
* writeOperations (encoder, decoder) { writeOperations (encoder, decoder) {
let ss = new Map() let ss = new Map()
let ssLength = decoder.readUint32() let ssLength = decoder.readUint32()
for (let i = 0; i < ssLength; i++) { for (let i = 0; i < ssLength; i++) {
@ -1084,7 +1084,7 @@ export default function extendTransaction (Y) {
let clock = decoder.readVarUint() let clock = decoder.readVarUint()
ss.set(user, clock) ss.set(user, clock)
} }
let ops = yield * this.getOperations(ss) let ops = this.getOperations(ss)
encoder.writeUint32(ops.length) encoder.writeUint32(ops.length)
for (let i = 0; i < ops.length; i++) { for (let i = 0; i < ops.length; i++) {
let op = ops[i] let op = ops[i]
@ -1092,17 +1092,17 @@ export default function extendTransaction (Y) {
} }
} }
* toBinary () { toBinary () {
let encoder = new BinaryEncoder() let encoder = new BinaryEncoder()
yield * this.writeOperationsUntransformed(encoder) this.writeOperationsUntransformed(encoder)
yield * this.writeDeleteSet(encoder) this.writeDeleteSet(encoder)
return encoder.createBuffer() return encoder.createBuffer()
} }
* fromBinary (buffer) { fromBinary (buffer) {
let decoder = new BinaryDecoder(buffer) let decoder = new BinaryDecoder(buffer)
yield * this.applyOperationsUntransformed(decoder) this.applyOperationsUntransformed(decoder)
yield * this.applyDeleteSet(decoder) this.applyDeleteSet(decoder)
} }
/* /*
@ -1110,43 +1110,43 @@ export default function extendTransaction (Y) {
* You can apply these operations using .applyOperationsUntransformed(ops) * You can apply these operations using .applyOperationsUntransformed(ops)
* *
*/ */
* writeOperationsUntransformed (encoder) { writeOperationsUntransformed (encoder) {
let lenPosition = encoder.pos let lenPosition = encoder.pos
let len = 0 let len = 0
encoder.writeUint32(0) // placeholder 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) { if (op.id[0] !== 0xFFFFFF) {
len++ len++
Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op)) Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op))
} }
}) })
encoder.setUint32(lenPosition, len) encoder.setUint32(lenPosition, len)
yield * this.writeStateSet(encoder) this.writeStateSet(encoder)
} }
* applyOperationsUntransformed (decoder) { applyOperationsUntransformed (decoder) {
let len = decoder.readUint32() let len = decoder.readUint32()
for (let i = 0; i < len; i++) { for (let i = 0; i < len; i++) {
let op = Y.Struct.binaryDecodeOperation(decoder) 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.parent != null) {
if (op.struct === 'Insert') { if (op.struct === 'Insert') {
// update parents .map/start/end properties // update parents .map/start/end properties
if (op.parentSub != null && op.left == null) { if (op.parentSub != null && op.left == null) {
// op is child of Map // op is child of Map
let parent = yield * this.getOperation(op.parent) let parent = this.getOperation(op.parent)
parent.map[op.parentSub] = op.id parent.map[op.parentSub] = op.id
yield * this.setOperation(parent) this.setOperation(parent)
} else if (op.right == null || op.left == null) { } 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) { if (op.right == null) {
parent.end = Y.utils.getLastId(op) parent.end = Y.utils.getLastId(op)
} }
if (op.left == null) { if (op.left == null) {
parent.start = op.id 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++) { for (let i = 0; i < stateSetLength; i++) {
let user = decoder.readVarUint() let user = decoder.readVarUint()
let clock = decoder.readVarUint() let clock = decoder.readVarUint()
yield * this.ss.put({ this.ss.put({
id: [user], id: [user],
clock: clock clock: clock
}) })
} }
} }
/* this is what we used before.. use this as a reference.. /* 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.Struct[op.struct].encode(op)
op = Y.utils.copyObject(op) -- use copyoperation instead now! op = Y.utils.copyObject(op) -- use copyoperation instead now!
var o = op var o = op
@ -1172,7 +1172,7 @@ export default function extendTransaction (Y) {
// or the o that has no origin to the right of op // or the o that has no origin to the right of op
// (this is why we use the ids array) // (this is why we use the ids array)
while (o.right != null) { 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) { if (o.right[1] < (startSS[o.right[0]] || 0) || !ids.some(function (id) {
return Y.utils.compareIds(id, right.origin) return Y.utils.compareIds(id, right.origin)
})) { })) {
@ -1186,10 +1186,10 @@ export default function extendTransaction (Y) {
return op return op
} }
*/ */
* flush () { flush () {
yield * this.os.flush() this.os.flush()
yield * this.ss.flush() this.ss.flush()
yield * this.ds.flush() this.ds.flush()
} }
} }
Y.Transaction = TransactionInterface Y.Transaction = TransactionInterface

View File

@ -315,7 +315,7 @@ export default function Utils (Y) {
this.awaiting++ this.awaiting++
ops.map(Y.utils.copyOperation).forEach(this.onevent) ops.map(Y.utils.copyOperation).forEach(this.onevent)
} }
* awaitOps (transaction, f, args) { awaitOps (transaction, f, args) {
function notSoSmartSort (array) { function notSoSmartSort (array) {
// this function sorts insertions in a executable order // this function sorts insertions in a executable order
var result = [] var result = []
@ -339,7 +339,7 @@ export default function Utils (Y) {
} }
var before = this.waiting.length var before = this.waiting.length
// somehow create new operations // somehow create new operations
yield * f.apply(transaction, args) f.apply(transaction, args)
// remove all appended ops / awaited ops // remove all appended ops / awaited ops
this.waiting.splice(before) this.waiting.splice(before)
if (this.awaiting > 0) this.awaiting-- if (this.awaiting > 0) this.awaiting--
@ -349,7 +349,7 @@ export default function Utils (Y) {
for (let i = 0; i < this.waiting.length; i++) { for (let i = 0; i < this.waiting.length; i++) {
var o = this.waiting[i] var o = this.waiting[i]
if (o.struct === 'Insert') { 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.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 // 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) this.waiting.splice(i, 1)
@ -361,10 +361,10 @@ export default function Utils (Y) {
o.left = null o.left = null
} else { } else {
// find next undeleted op // find next undeleted op
var left = yield * transaction.getInsertion(_o.left) var left = transaction.getInsertion(_o.left)
while (left.deleted != null) { while (left.deleted != null) {
if (left.left != null) { if (left.left != null) {
left = yield * transaction.getInsertion(left.left) left = transaction.getInsertion(left.left)
} else { } else {
left = null left = null
break break
@ -690,7 +690,7 @@ export default function Utils (Y) {
this.writeBuffer = createEmptyOpsArray(5) this.writeBuffer = createEmptyOpsArray(5)
this.readBuffer = createEmptyOpsArray(10) this.readBuffer = createEmptyOpsArray(10)
} }
* find (id, noSuperCall) { find (id, noSuperCall) {
var i, r var i, r
for (i = this.readBuffer.length - 1; i >= 0; i--) { for (i = this.readBuffer.length - 1; i >= 0; i--) {
r = this.readBuffer[i] r = this.readBuffer[i]
@ -716,7 +716,7 @@ export default function Utils (Y) {
if (i < 0 && noSuperCall === undefined) { if (i < 0 && noSuperCall === undefined) {
// did not reach break in last loop // did not reach break in last loop
// read id and put it to the end of readBuffer // read id and put it to the end of readBuffer
o = yield * super.find(id) o = super.find(id)
} }
if (o != null) { if (o != null) {
for (i = 0; i < this.readBuffer.length - 1; i++) { for (i = 0; i < this.readBuffer.length - 1; i++) {
@ -726,7 +726,7 @@ export default function Utils (Y) {
} }
return o return o
} }
* put (o) { put (o) {
var id = o.id var id = o.id
var i, r // helper variables var i, r // helper variables
for (i = this.writeBuffer.length - 1; i >= 0; i--) { for (i = this.writeBuffer.length - 1; i >= 0; i--) {
@ -746,7 +746,7 @@ export default function Utils (Y) {
// write writeBuffer[0] // write writeBuffer[0]
var write = this.writeBuffer[0] var write = this.writeBuffer[0]
if (write.id[0] !== null) { if (write.id[0] !== null) {
yield * super.put(write) super.put(write)
} }
// put o to the end of writeBuffer // put o to the end of writeBuffer
for (i = 0; i < this.writeBuffer.length - 1; i++) { 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 this.readBuffer[this.readBuffer.length - 1] = o
} }
* delete (id) { delete (id) {
var i, r var i, r
for (i = 0; i < this.readBuffer.length; i++) { for (i = 0; i < this.readBuffer.length; i++) {
r = this.readBuffer[i] r = this.readBuffer[i]
@ -776,44 +776,44 @@ export default function Utils (Y) {
} }
} }
} }
yield * this.flush() this.flush()
yield * super.delete(id) super.delete(id)
} }
* findWithLowerBound (id) { findWithLowerBound (id) {
var o = yield * this.find(id, true) var o = this.find(id, true)
if (o != null) { if (o != null) {
return o return o
} else { } else {
yield * this.flush() this.flush()
return yield * super.findWithLowerBound.apply(this, arguments) return super.findWithLowerBound.apply(this, arguments)
} }
} }
* findWithUpperBound (id) { findWithUpperBound (id) {
var o = yield * this.find(id, true) var o = this.find(id, true)
if (o != null) { if (o != null) {
return o return o
} else { } else {
yield * this.flush() this.flush()
return yield * super.findWithUpperBound.apply(this, arguments) return super.findWithUpperBound.apply(this, arguments)
} }
} }
* findNext () { findNext () {
yield * this.flush() this.flush()
return yield * super.findNext.apply(this, arguments) return super.findNext.apply(this, arguments)
} }
* findPrev () { findPrev () {
yield * this.flush() this.flush()
return yield * super.findPrev.apply(this, arguments) return super.findPrev.apply(this, arguments)
} }
* iterate () { iterate () {
yield * this.flush() this.flush()
yield * super.iterate.apply(this, arguments) super.iterate.apply(this, arguments)
} }
* flush () { flush () {
for (var i = 0; i < this.writeBuffer.length; i++) { for (var i = 0; i < this.writeBuffer.length; i++) {
var write = this.writeBuffer[i] var write = this.writeBuffer[i]
if (write.id[0] !== null) { if (write.id[0] !== null) {
yield * super.put(write) super.put(write)
this.writeBuffer[i] = { this.writeBuffer[i] = {
id: [null, null] id: [null, null]
} }

64
src/y-memory.js Normal file
View File

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

View File

@ -4,6 +4,7 @@ import extendDatabase from './Database.js'
import extendTransaction from './Transaction.js' import extendTransaction from './Transaction.js'
import extendStruct from './Struct.js' import extendStruct from './Struct.js'
import extendUtils from './Utils.js' import extendUtils from './Utils.js'
import extendMemory from './y-memory.js'
import debug from 'debug' import debug from 'debug'
import { formatYjsMessage, formatYjsMessageType } from './MessageHandler.js' import { formatYjsMessage, formatYjsMessageType } from './MessageHandler.js'
@ -13,6 +14,7 @@ extendDatabase(Y)
extendTransaction(Y) extendTransaction(Y)
extendStruct(Y) extendStruct(Y)
extendUtils(Y) extendUtils(Y)
extendMemory(Y)
Y.debug = debug Y.debug = debug
debug.formatters.Y = formatYjsMessage debug.formatters.Y = formatYjsMessage
@ -182,7 +184,7 @@ class YConfig extends Y.utils.NamedEventHandler {
var opts = this.options var opts = this.options
var share = {} var share = {}
this.share = share this.share = share
this.db.requestTransaction(function * requestTransaction () { this.db.requestTransaction(function requestTransaction () {
// create shared object // create shared object
for (var propertyname in opts.share) { for (var propertyname in opts.share) {
var typeConstructor = opts.share[propertyname].split('(') var typeConstructor = opts.share[propertyname].split('(')
@ -195,7 +197,7 @@ class YConfig extends Y.utils.NamedEventHandler {
var typedef = type.typeDefinition var typedef = type.typeDefinition
var id = [0xFFFFFF, typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeArgs] var id = [0xFFFFFF, typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeArgs]
let args = Y.utils.parseTypeDefinition(type, 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) { if (this.persistence != null) {
@ -250,8 +252,8 @@ class YConfig extends Y.utils.NamedEventHandler {
return this.db.whenTransactionsFinished().then(function () { return this.db.whenTransactionsFinished().then(function () {
self.db.destroyTypes() self.db.destroyTypes()
// make sure to wait for all transactions before destroying the db // make sure to wait for all transactions before destroying the db
self.db.requestTransaction(function * () { self.db.requestTransaction(function () {
yield * self.db.destroy() self.db.destroy()
}) })
return self.db.whenTransactionsFinished() return self.db.whenTransactionsFinished()
}) })

View File

@ -161,7 +161,9 @@ test('encode/decode List operations', async function binList (t) {
testEncoding(t, writeList, readList, { testEncoding(t, writeList, readList, {
struct: 'List', struct: 'List',
id: [100, 33], id: [100, 33],
type: 'Array' type: 'Array',
start: null,
end: null
}) })
}) })

217
test/red-black-tree.js Normal file
View File

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

View File

@ -4,7 +4,7 @@ import _Y from '../../yjs/src/y.js'
import yMemory from '../../y-memory/src/y-memory.js' import yMemory from '../../y-memory/src/y-memory.js'
import yArray from '../../y-array/src/y-array.js' import yArray from '../../y-array/src/y-array.js'
import yText from '../../y-text/src/Text.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 yXml from '../../y-xml/src/y-xml.js'
import yTest from './test-connector.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 database = { name: 'memory' }
export var connector = { name: 'test', url: 'http://localhost:1234' } export var connector = { name: 'test', url: 'http://localhost:1234' }
function * getStateSet () { function getStateSet () {
var ss = {} 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 user = n.id[0]
var clock = n.clock var clock = n.clock
ss[user] = clock ss[user] = clock
@ -27,9 +27,9 @@ function * getStateSet () {
return ss return ss
} }
function * getDeleteSet () { function getDeleteSet () {
var ds = {} 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 user = n.id[0]
var counter = n.id[1] var counter = n.id[1]
var len = n.len var len = n.len
@ -126,9 +126,9 @@ export async function compareUsers (t, users) {
let filterDeletedOps = users.every(u => u.db.gc === false) let filterDeletedOps = users.every(u => u.db.gc === false)
var data = await Promise.all(users.map(async (u) => { var data = await Promise.all(users.map(async (u) => {
var data = {} var data = {}
u.db.requestTransaction(function * () { u.db.requestTransaction(function () {
let ops = [] 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)) 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.. as they might have been split up differently..
*/ */
if (filterDeletedOps) { if (filterDeletedOps) {
let opIsDeleted = yield * this.isDeleted(op.id) let opIsDeleted = this.isDeleted(op.id)
if (!opIsDeleted) { if (!opIsDeleted) {
data.os[JSON.stringify(op.id)] = op 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.os[JSON.stringify(op.id)] = op
} }
} }
data.ds = yield * getDeleteSet.apply(this) data.ds = getDeleteSet.apply(this)
data.ss = yield * getStateSet.apply(this) data.ss = getStateSet.apply(this)
}) })
await u.db.whenTransactionsFinished() await u.db.whenTransactionsFinished()
return data return data