removed generators
This commit is contained in:
parent
6b5c02f1ce
commit
ccf6d86c98
@ -1,7 +1,7 @@
|
||||
|
||||
import Y from '../src/y.js'
|
||||
import yArray from '../../y-array/src/y-array.js'
|
||||
import yMap from '../../y-map/src/Map.js'
|
||||
import yMap from '../../y-map/src/y-map.js'
|
||||
import yText from '../../y-text/src/Text.js'
|
||||
import yXml from '../../y-xml/src/y-xml.js'
|
||||
import yMemory from '../../y-memory/src/y-memory.js'
|
||||
|
@ -1,4 +1,3 @@
|
||||
import inject from 'rollup-plugin-inject'
|
||||
import babel from 'rollup-plugin-babel'
|
||||
import uglify from 'rollup-plugin-uglify'
|
||||
import nodeResolve from 'rollup-plugin-node-resolve'
|
||||
@ -16,12 +15,7 @@ export default {
|
||||
browser: true
|
||||
}),
|
||||
commonjs(),
|
||||
babel({
|
||||
runtimeHelpers: true
|
||||
}),
|
||||
inject({
|
||||
regeneratorRuntime: 'regenerator-runtime'
|
||||
}),
|
||||
babel(),
|
||||
uglify({
|
||||
output: {
|
||||
comments: function (node, comment) {
|
||||
|
@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
||||
import multiEntry from 'rollup-plugin-multi-entry'
|
||||
|
||||
export default {
|
||||
entry: 'test/y-xml.tests.js',
|
||||
entry: 'test/*.js',
|
||||
moduleName: 'y-tests',
|
||||
format: 'umd',
|
||||
plugins: [
|
||||
|
104
src/Connector.js
104
src/Connector.js
@ -105,6 +105,7 @@ export default function extendConnector (Y/* :any */) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
userJoined (user, role, auth) {
|
||||
if (role == null) {
|
||||
throw new Error('You must specify the role of the joined user!')
|
||||
@ -133,6 +134,7 @@ export default function extendConnector (Y/* :any */) {
|
||||
}
|
||||
this._syncWithUser(user)
|
||||
}
|
||||
|
||||
// Execute a function _when_ we are connected.
|
||||
// If not connected, wait until connected
|
||||
whenSynced (f) {
|
||||
@ -142,18 +144,20 @@ export default function extendConnector (Y/* :any */) {
|
||||
this.whenSyncedListeners.push(f)
|
||||
}
|
||||
}
|
||||
|
||||
_syncWithUser (userid) {
|
||||
if (this.role === 'slave') {
|
||||
return // "The current sync has not finished or this is controlled by a master!"
|
||||
}
|
||||
sendSyncStep1(this, userid)
|
||||
}
|
||||
|
||||
_fireIsSyncedListeners () {
|
||||
this.y.db.whenTransactionsFinished().then(() => {
|
||||
if (!this.isSynced) {
|
||||
this.isSynced = true
|
||||
// It is safer to remove this!
|
||||
// TODO: remove: yield * this.garbageCollectAfterSync()
|
||||
// TODO: remove: this.garbageCollectAfterSync()
|
||||
// call whensynced listeners
|
||||
for (var f of this.whenSyncedListeners) {
|
||||
f()
|
||||
@ -162,6 +166,7 @@ export default function extendConnector (Y/* :any */) {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
send (uid, buffer) {
|
||||
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
||||
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - please don\'t use this method to send custom messages')
|
||||
@ -169,6 +174,7 @@ export default function extendConnector (Y/* :any */) {
|
||||
this.log('%s: Send \'%y\' to %s', this.userId, buffer, uid)
|
||||
this.logMessage('Message: %Y', buffer)
|
||||
}
|
||||
|
||||
broadcast (buffer) {
|
||||
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
||||
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - please don\'t use this method to send custom messages')
|
||||
@ -176,6 +182,7 @@ export default function extendConnector (Y/* :any */) {
|
||||
this.log('%s: Broadcast \'%y\'', this.userId, buffer)
|
||||
this.logMessage('Message: %Y', buffer)
|
||||
}
|
||||
|
||||
/*
|
||||
Buffer operations, and broadcast them when ready.
|
||||
*/
|
||||
@ -207,6 +214,7 @@ export default function extendConnector (Y/* :any */) {
|
||||
this.broadcastOpBuffer = this.broadcastOpBuffer.concat(ops)
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
You received a raw message, and you know that it is intended for Yjs. Then call this function.
|
||||
*/
|
||||
@ -224,14 +232,11 @@ export default function extendConnector (Y/* :any */) {
|
||||
encoder.writeVarString(roomname)
|
||||
let messageType = decoder.readVarString()
|
||||
let senderConn = this.connections.get(sender)
|
||||
|
||||
this.log('%s: Receive \'%s\' from %s', this.userId, messageType, sender)
|
||||
this.logMessage('Message: %Y', buffer)
|
||||
|
||||
if (senderConn == null && !skipAuth) {
|
||||
throw new Error('Received message from unknown peer!')
|
||||
}
|
||||
|
||||
if (messageType === 'sync step 1' || messageType === 'sync step 2') {
|
||||
let auth = decoder.readVarUint()
|
||||
if (senderConn.auth == null) {
|
||||
@ -284,97 +289,6 @@ export default function extendConnector (Y/* :any */) {
|
||||
this._fireIsSyncedListeners()
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
Currently, the HB encodes operations as JSON. For the moment I want to keep it
|
||||
that way. Maybe we support encoding in the HB as XML in the future, but for now I don't want
|
||||
too much overhead. Y is very likely to get changed a lot in the future
|
||||
|
||||
Because we don't want to encode JSON as string (with character escaping, wich makes it pretty much unreadable)
|
||||
we encode the JSON as XML.
|
||||
|
||||
When the HB support encoding as XML, the format should look pretty much like this.
|
||||
|
||||
does not support primitive values as array elements
|
||||
expects an ltx (less than xml) object
|
||||
*/
|
||||
parseMessageFromXml (m/* :any */) {
|
||||
function parseArray (node) {
|
||||
for (var n of node.children) {
|
||||
if (n.getAttribute('isArray') === 'true') {
|
||||
return parseArray(n)
|
||||
} else {
|
||||
return parseObject(n)
|
||||
}
|
||||
}
|
||||
}
|
||||
function parseObject (node/* :any */) {
|
||||
var json = {}
|
||||
for (var attrName in node.attrs) {
|
||||
var value = node.attrs[attrName]
|
||||
var int = parseInt(value, 10)
|
||||
if (isNaN(int) || ('' + int) !== value) {
|
||||
json[attrName] = value
|
||||
} else {
|
||||
json[attrName] = int
|
||||
}
|
||||
}
|
||||
for (var n/* :any */ in node.children) {
|
||||
var name = n.name
|
||||
if (n.getAttribute('isArray') === 'true') {
|
||||
json[name] = parseArray(n)
|
||||
} else {
|
||||
json[name] = parseObject(n)
|
||||
}
|
||||
}
|
||||
return json
|
||||
}
|
||||
parseObject(m)
|
||||
}
|
||||
/*
|
||||
encode message in xml
|
||||
we use string because Strophe only accepts an "xml-string"..
|
||||
So {a:4,b:{c:5}} will look like
|
||||
<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
|
||||
}
|
||||
|
@ -85,11 +85,11 @@ export default function extendDatabase (Y /* :any */) {
|
||||
console.warn('gc should be empty when not synced!')
|
||||
}
|
||||
return new Promise((resolve) => {
|
||||
os.requestTransaction(function * () {
|
||||
os.requestTransaction(function () {
|
||||
if (os.y.connector != null && os.y.connector.isSynced) {
|
||||
for (var i = 0; i < os.gc2.length; i++) {
|
||||
var oid = os.gc2[i]
|
||||
yield * this.garbageCollectOperation(oid)
|
||||
this.garbageCollectOperation(oid)
|
||||
}
|
||||
os.gc2 = os.gc1
|
||||
os.gc1 = []
|
||||
@ -197,15 +197,15 @@ export default function extendDatabase (Y /* :any */) {
|
||||
this.gc = false
|
||||
this.gcTimeout = -1
|
||||
return new Promise(function (resolve) {
|
||||
self.requestTransaction(function * () {
|
||||
self.requestTransaction(function () {
|
||||
var ungc /* :Array<Struct> */ = self.gc1.concat(self.gc2)
|
||||
self.gc1 = []
|
||||
self.gc2 = []
|
||||
for (var i = 0; i < ungc.length; i++) {
|
||||
var op = yield * this.getOperation(ungc[i])
|
||||
var op = this.getOperation(ungc[i])
|
||||
if (op != null) {
|
||||
delete op.gc
|
||||
yield * this.setOperation(op)
|
||||
this.setOperation(op)
|
||||
}
|
||||
}
|
||||
resolve()
|
||||
@ -224,7 +224,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
|
||||
returns true iff op was added to GC
|
||||
*/
|
||||
* addToGarbageCollector (op, left) {
|
||||
addToGarbageCollector (op, left) {
|
||||
if (
|
||||
op.gc == null &&
|
||||
op.deleted === true &&
|
||||
@ -235,12 +235,12 @@ export default function extendDatabase (Y /* :any */) {
|
||||
if (left != null && left.deleted === true) {
|
||||
gc = true
|
||||
} else if (op.content != null && op.content.length > 1) {
|
||||
op = yield * this.getInsertionCleanStart([op.id[0], op.id[1] + 1])
|
||||
op = this.getInsertionCleanStart([op.id[0], op.id[1] + 1])
|
||||
gc = true
|
||||
}
|
||||
if (gc) {
|
||||
op.gc = true
|
||||
yield * this.setOperation(op)
|
||||
this.setOperation(op)
|
||||
this.store.queueGarbageCollector(op.id)
|
||||
return true
|
||||
}
|
||||
@ -265,7 +265,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
}
|
||||
}
|
||||
}
|
||||
* destroy () {
|
||||
destroy () {
|
||||
clearTimeout(this.gcInterval)
|
||||
this.gcInterval = null
|
||||
this.stopRepairCheck()
|
||||
@ -274,9 +274,9 @@ export default function extendDatabase (Y /* :any */) {
|
||||
if (!this.userIdPromise.inProgress) {
|
||||
this.userIdPromise.inProgress = true
|
||||
var self = this
|
||||
self.requestTransaction(function * () {
|
||||
self.requestTransaction(function () {
|
||||
self.userId = userId
|
||||
var state = yield * this.getState(userId)
|
||||
var state = this.getState(userId)
|
||||
self.opClock = state.clock
|
||||
self.userIdPromise.resolve(userId)
|
||||
})
|
||||
@ -355,7 +355,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
this.listenersByIdRequestPending = true
|
||||
var store = this
|
||||
|
||||
this.requestTransaction(function * () {
|
||||
this.requestTransaction(function () {
|
||||
var exeNow = store.listenersByIdExecuteNow
|
||||
store.listenersByIdExecuteNow = []
|
||||
|
||||
@ -366,7 +366,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
|
||||
for (let key = 0; key < exeNow.length; key++) {
|
||||
let o = exeNow[key].op
|
||||
yield * store.tryExecute.call(this, o)
|
||||
store.tryExecute.call(this, o)
|
||||
}
|
||||
|
||||
for (var sid in ls) {
|
||||
@ -374,9 +374,9 @@ export default function extendDatabase (Y /* :any */) {
|
||||
var id = JSON.parse(sid)
|
||||
var op
|
||||
if (typeof id[1] === 'string') {
|
||||
op = yield * this.getOperation(id)
|
||||
op = this.getOperation(id)
|
||||
} else {
|
||||
op = yield * this.getInsertion(id)
|
||||
op = this.getInsertion(id)
|
||||
}
|
||||
if (op == null) {
|
||||
store.listenersById[sid] = l
|
||||
@ -385,7 +385,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
let listener = l[i]
|
||||
let o = listener.op
|
||||
if (--listener.missing === 0) {
|
||||
yield * store.tryExecute.call(this, o)
|
||||
store.tryExecute.call(this, o)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -402,15 +402,15 @@ export default function extendDatabase (Y /* :any */) {
|
||||
addOperation: any;
|
||||
whenOperationsExist: any;
|
||||
*/
|
||||
* tryExecute (op) {
|
||||
this.store.addToDebug('yield * this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
|
||||
tryExecute (op) {
|
||||
this.store.addToDebug('this.store.tryExecute.call(this, ', JSON.stringify(op), ')')
|
||||
if (op.struct === 'Delete') {
|
||||
yield * Y.Struct.Delete.execute.call(this, op)
|
||||
Y.Struct.Delete.execute.call(this, op)
|
||||
// this is now called in Transaction.deleteOperation!
|
||||
// yield * this.store.operationAdded(this, op)
|
||||
// this.store.operationAdded(this, op)
|
||||
} else {
|
||||
// check if this op was defined
|
||||
var defined = yield * this.getInsertion(op.id)
|
||||
var defined = this.getInsertion(op.id)
|
||||
while (defined != null && defined.content != null) {
|
||||
// check if this op has a longer content in the case it is defined
|
||||
if (defined.id[1] + defined.content.length < op.id[1] + op.content.length) {
|
||||
@ -419,23 +419,23 @@ export default function extendDatabase (Y /* :any */) {
|
||||
op.id = [op.id[0], op.id[1] + overlapSize]
|
||||
op.left = Y.utils.getLastId(defined)
|
||||
op.origin = op.left
|
||||
defined = yield * this.getOperation(op.id) // getOperation suffices here
|
||||
defined = this.getOperation(op.id) // getOperation suffices here
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
if (defined == null) {
|
||||
var opid = op.id
|
||||
var isGarbageCollected = yield * this.isGarbageCollected(opid)
|
||||
var isGarbageCollected = this.isGarbageCollected(opid)
|
||||
if (!isGarbageCollected) {
|
||||
// TODO: reduce number of get / put calls for op ..
|
||||
yield * Y.Struct[op.struct].execute.call(this, op)
|
||||
yield * this.addOperation(op)
|
||||
yield * this.store.operationAdded(this, op)
|
||||
Y.Struct[op.struct].execute.call(this, op)
|
||||
this.addOperation(op)
|
||||
this.store.operationAdded(this, op)
|
||||
// operationAdded can change op..
|
||||
op = yield * this.getOperation(opid)
|
||||
op = this.getOperation(opid)
|
||||
// if insertion, try to combine with left
|
||||
yield * this.tryCombineWithLeft(op)
|
||||
this.tryCombineWithLeft(op)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -452,15 +452,15 @@ export default function extendDatabase (Y /* :any */) {
|
||||
* Always:
|
||||
* * Call type
|
||||
*/
|
||||
* operationAdded (transaction, op) {
|
||||
operationAdded (transaction, op) {
|
||||
if (op.struct === 'Delete') {
|
||||
var type = this.initializedTypes[JSON.stringify(op.targetParent)]
|
||||
if (type != null) {
|
||||
yield * type._changed(transaction, op)
|
||||
type._changed(transaction, op)
|
||||
}
|
||||
} else {
|
||||
// increase SS
|
||||
yield * transaction.updateState(op.id[0])
|
||||
transaction.updateState(op.id[0])
|
||||
var opLen = op.content != null ? op.content.length : 1
|
||||
for (let i = 0; i < opLen; i++) {
|
||||
// notify whenOperation listeners (by id)
|
||||
@ -480,9 +480,9 @@ export default function extendDatabase (Y /* :any */) {
|
||||
|
||||
// if parent is deleted, mark as gc'd and return
|
||||
if (op.parent != null) {
|
||||
var parentIsDeleted = yield * transaction.isDeleted(op.parent)
|
||||
var parentIsDeleted = transaction.isDeleted(op.parent)
|
||||
if (parentIsDeleted) {
|
||||
yield * transaction.deleteList(op.id)
|
||||
transaction.deleteList(op.id)
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -490,7 +490,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
// notify parent, if it was instanciated as a custom type
|
||||
if (t != null) {
|
||||
let o = Y.utils.copyOperation(op)
|
||||
yield * t._changed(transaction, o)
|
||||
t._changed(transaction, o)
|
||||
}
|
||||
if (!op.deleted) {
|
||||
// Delete if DS says this is actually deleted
|
||||
@ -499,13 +499,13 @@ export default function extendDatabase (Y /* :any */) {
|
||||
// TODO: !! console.log('TODO: change this before commiting')
|
||||
for (let i = 0; i < len; i++) {
|
||||
var id = [startId[0], startId[1] + i]
|
||||
var opIsDeleted = yield * transaction.isDeleted(id)
|
||||
var opIsDeleted = transaction.isDeleted(id)
|
||||
if (opIsDeleted) {
|
||||
var delop = {
|
||||
struct: 'Delete',
|
||||
target: id
|
||||
}
|
||||
yield * this.tryExecute.call(transaction, delop)
|
||||
this.tryExecute.call(transaction, delop)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -528,6 +528,7 @@ export default function extendDatabase (Y /* :any */) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
}
|
||||
|
||||
// Check if there is another transaction request.
|
||||
// * the last transaction is always a flush :)
|
||||
getNextRequest () {
|
||||
@ -542,8 +543,8 @@ export default function extendDatabase (Y /* :any */) {
|
||||
return null
|
||||
} else {
|
||||
this.transactionIsFlushed = true
|
||||
return function * () {
|
||||
yield * this.flush()
|
||||
return function () {
|
||||
this.flush()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
@ -570,13 +571,13 @@ export default function extendDatabase (Y /* :any */) {
|
||||
Init type. This is called when a remote operation is retrieved, and transformed to a type
|
||||
TODO: delete type from store.initializedTypes[id] when corresponding id was deleted!
|
||||
*/
|
||||
* initType (id, args) {
|
||||
initType (id, args) {
|
||||
var sid = JSON.stringify(id)
|
||||
var t = this.store.initializedTypes[sid]
|
||||
if (t == null) {
|
||||
var op/* :MapStruct | ListStruct */ = yield * this.getOperation(id)
|
||||
var op/* :MapStruct | ListStruct */ = this.getOperation(id)
|
||||
if (op != null) {
|
||||
t = yield * Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
||||
t = Y[op.type].typeDefinition.initType.call(this, this.store, op, args)
|
||||
this.store.initializedTypes[sid] = t
|
||||
}
|
||||
}
|
||||
@ -591,11 +592,11 @@ export default function extendDatabase (Y /* :any */) {
|
||||
var op = Y.Struct[structname].create(id, typedefinition[1])
|
||||
op.type = typedefinition[0].name
|
||||
|
||||
this.requestTransaction(function * () {
|
||||
this.requestTransaction(function () {
|
||||
if (op.id[0] === 0xFFFFFF) {
|
||||
yield * this.setOperation(op)
|
||||
this.setOperation(op)
|
||||
} else {
|
||||
yield * this.applyCreatedOperations([op])
|
||||
this.applyCreatedOperations([op])
|
||||
}
|
||||
})
|
||||
var t = Y[op.type].typeDefinition.createType(this, op, typedefinition[1])
|
||||
|
@ -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()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}
|
@ -58,7 +58,7 @@ export function computeMessageUpdate (decoder, encoder, conn) {
|
||||
}
|
||||
|
||||
export function sendSyncStep1 (conn, syncUser) {
|
||||
conn.y.db.requestTransaction(function * () {
|
||||
conn.y.db.requestTransaction(function () {
|
||||
let encoder = new BinaryEncoder()
|
||||
encoder.writeVarString(conn.opts.room || '')
|
||||
encoder.writeVarString('sync step 1')
|
||||
@ -66,7 +66,7 @@ export function sendSyncStep1 (conn, syncUser) {
|
||||
encoder.writeVarUint(conn.protocolVersion)
|
||||
let preferUntransformed = conn.preferUntransformed && this.os.length === 0 // TODO: length may not be defined
|
||||
encoder.writeUint8(preferUntransformed ? 1 : 0)
|
||||
yield * this.writeStateSet(encoder)
|
||||
this.writeStateSet(encoder)
|
||||
conn.send(syncUser, encoder.createBuffer())
|
||||
})
|
||||
}
|
||||
@ -99,19 +99,19 @@ export function computeMessageSyncStep1 (decoder, encoder, conn, senderConn, sen
|
||||
|
||||
return conn.y.db.whenTransactionsFinished().then(() => {
|
||||
// send sync step 2
|
||||
conn.y.db.requestTransaction(function * () {
|
||||
conn.y.db.requestTransaction(function () {
|
||||
encoder.writeVarString('sync step 2')
|
||||
encoder.writeVarString(conn.authInfo || '')
|
||||
|
||||
if (preferUntransformed) {
|
||||
encoder.writeUint8(1)
|
||||
yield * this.writeOperationsUntransformed(encoder)
|
||||
this.writeOperationsUntransformed(encoder)
|
||||
} else {
|
||||
encoder.writeUint8(0)
|
||||
yield * this.writeOperations(encoder, decoder)
|
||||
this.writeOperations(encoder, decoder)
|
||||
}
|
||||
|
||||
yield * this.writeDeleteSet(encoder)
|
||||
this.writeDeleteSet(encoder)
|
||||
conn.send(senderConn.uid, encoder.createBuffer())
|
||||
senderConn.receivedSyncStep2 = true
|
||||
})
|
||||
@ -174,17 +174,17 @@ export function computeMessageSyncStep2 (decoder, encoder, conn, senderConn, sen
|
||||
let defer = senderConn.syncStep2
|
||||
|
||||
// apply operations first
|
||||
db.requestTransaction(function * () {
|
||||
db.requestTransaction(function () {
|
||||
let osUntransformed = decoder.readUint8()
|
||||
if (osUntransformed === 1) {
|
||||
yield * this.applyOperationsUntransformed(decoder)
|
||||
this.applyOperationsUntransformed(decoder)
|
||||
} else {
|
||||
this.store.applyOperations(decoder)
|
||||
}
|
||||
})
|
||||
// then apply ds
|
||||
db.requestTransaction(function * () {
|
||||
yield * this.applyDeleteSet(decoder)
|
||||
db.requestTransaction(function () {
|
||||
this.applyDeleteSet(decoder)
|
||||
})
|
||||
return db.whenTransactionsFinished().then(() => {
|
||||
conn._setSyncedWith(sender)
|
||||
|
@ -8,9 +8,11 @@ export default function extendPersistence (Y) {
|
||||
this.saveOperationsBuffer = []
|
||||
this.log = Y.debug('y:persistence')
|
||||
}
|
||||
|
||||
saveToMessageQueue (binary) {
|
||||
this.log('Room %s: Save message to message queue', this.y.options.connector.room)
|
||||
}
|
||||
|
||||
saveOperations (ops) {
|
||||
ops = ops.map(function (op) {
|
||||
return Y.Struct[op.struct].encode(op)
|
||||
@ -39,5 +41,6 @@ export default function extendPersistence (Y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Y.AbstractPersistence = AbstractPersistence
|
||||
}
|
||||
|
506
src/RedBlackTree.js
Normal file
506
src/RedBlackTree.js
Normal 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
|
||||
}
|
@ -73,8 +73,8 @@ export default function extendStruct (Y) {
|
||||
requiredOps: function (op) {
|
||||
return [] // [op.target]
|
||||
},
|
||||
execute: function * (op) {
|
||||
return yield * this.deleteOperation(op.target, op.length || 1)
|
||||
execute: function (op) {
|
||||
return this.deleteOperation(op.target, op.length || 1)
|
||||
}
|
||||
}
|
||||
|
||||
@ -216,18 +216,18 @@ export default function extendStruct (Y) {
|
||||
}
|
||||
return ids
|
||||
},
|
||||
getDistanceToOrigin: function * (op) {
|
||||
getDistanceToOrigin: function (op) {
|
||||
if (op.left == null) {
|
||||
return 0
|
||||
} else {
|
||||
var d = 0
|
||||
var o = yield * this.getInsertion(op.left)
|
||||
var o = this.getInsertion(op.left)
|
||||
while (!Y.utils.matchesId(o, op.origin)) {
|
||||
d++
|
||||
if (o.left == null) {
|
||||
break
|
||||
} else {
|
||||
o = yield * this.getInsertion(o.left)
|
||||
o = this.getInsertion(o.left)
|
||||
}
|
||||
}
|
||||
return d
|
||||
@ -248,7 +248,7 @@ export default function extendStruct (Y) {
|
||||
# case 3: $origin > $o.origin
|
||||
# $this insert_position is to the left of $o (forever!)
|
||||
*/
|
||||
execute: function * (op) {
|
||||
execute: function (op) {
|
||||
var i // loop counter
|
||||
|
||||
// during this function some ops may get split into two pieces (e.g. with getInsertionCleanEnd)
|
||||
@ -258,17 +258,17 @@ export default function extendStruct (Y) {
|
||||
if (op.origin != null) { // TODO: !== instead of !=
|
||||
// we save in origin that op originates in it
|
||||
// we need that later when we eventually garbage collect origin (see transaction)
|
||||
var origin = yield * this.getInsertionCleanEnd(op.origin)
|
||||
var origin = this.getInsertionCleanEnd(op.origin)
|
||||
if (origin.originOf == null) {
|
||||
origin.originOf = []
|
||||
}
|
||||
origin.originOf.push(op.id)
|
||||
yield * this.setOperation(origin)
|
||||
this.setOperation(origin)
|
||||
if (origin.right != null) {
|
||||
tryToRemergeLater.push(origin.right)
|
||||
}
|
||||
}
|
||||
var distanceToOrigin = i = yield * Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
|
||||
var distanceToOrigin = i = Struct.Insert.getDistanceToOrigin.call(this, op) // most cases: 0 (starts from 0)
|
||||
|
||||
// now we begin to insert op in the list of insertions..
|
||||
var o
|
||||
@ -277,29 +277,29 @@ export default function extendStruct (Y) {
|
||||
|
||||
// find o. o is the first conflicting operation
|
||||
if (op.left != null) {
|
||||
o = yield * this.getInsertionCleanEnd(op.left)
|
||||
o = this.getInsertionCleanEnd(op.left)
|
||||
if (!Y.utils.compareIds(op.left, op.origin) && o.right != null) {
|
||||
// only if not added previously
|
||||
tryToRemergeLater.push(o.right)
|
||||
}
|
||||
o = (o.right == null) ? null : yield * this.getOperation(o.right)
|
||||
o = (o.right == null) ? null : this.getOperation(o.right)
|
||||
} else { // left == null
|
||||
parent = yield * this.getOperation(op.parent)
|
||||
parent = this.getOperation(op.parent)
|
||||
let startId = op.parentSub ? parent.map[op.parentSub] : parent.start
|
||||
start = startId == null ? null : yield * this.getOperation(startId)
|
||||
start = startId == null ? null : this.getOperation(startId)
|
||||
o = start
|
||||
}
|
||||
|
||||
// make sure to split op.right if necessary (also add to tryCombineWithLeft)
|
||||
if (op.right != null) {
|
||||
tryToRemergeLater.push(op.right)
|
||||
yield * this.getInsertionCleanStart(op.right)
|
||||
this.getInsertionCleanStart(op.right)
|
||||
}
|
||||
|
||||
// handle conflicts
|
||||
while (true) {
|
||||
if (o != null && !Y.utils.compareIds(o.id, op.right)) {
|
||||
var oOriginDistance = yield * Struct.Insert.getDistanceToOrigin.call(this, o)
|
||||
var oOriginDistance = Struct.Insert.getDistanceToOrigin.call(this, o)
|
||||
if (oOriginDistance === i) {
|
||||
// case 1
|
||||
if (o.id[0] < op.id[0]) {
|
||||
@ -317,7 +317,7 @@ export default function extendStruct (Y) {
|
||||
}
|
||||
i++
|
||||
if (o.right != null) {
|
||||
o = yield * this.getInsertion(o.right)
|
||||
o = this.getInsertion(o.right)
|
||||
} else {
|
||||
o = null
|
||||
}
|
||||
@ -330,17 +330,17 @@ export default function extendStruct (Y) {
|
||||
var left = null
|
||||
var right = null
|
||||
if (parent == null) {
|
||||
parent = yield * this.getOperation(op.parent)
|
||||
parent = this.getOperation(op.parent)
|
||||
}
|
||||
|
||||
// reconnect left and set right of op
|
||||
if (op.left != null) {
|
||||
left = yield * this.getInsertion(op.left)
|
||||
left = this.getInsertion(op.left)
|
||||
// link left
|
||||
op.right = left.right
|
||||
left.right = op.id
|
||||
|
||||
yield * this.setOperation(left)
|
||||
this.setOperation(left)
|
||||
} else {
|
||||
// set op.right from parent, if necessary
|
||||
op.right = op.parentSub ? parent.map[op.parentSub] || null : parent.start
|
||||
@ -348,33 +348,33 @@ export default function extendStruct (Y) {
|
||||
// reconnect right
|
||||
if (op.right != null) {
|
||||
// TODO: wanna connect right too?
|
||||
right = yield * this.getOperation(op.right)
|
||||
right = this.getOperation(op.right)
|
||||
right.left = Y.utils.getLastId(op)
|
||||
|
||||
// if right exists, and it is supposed to be gc'd. Remove it from the gc
|
||||
if (right.gc != null) {
|
||||
if (right.content != null && right.content.length > 1) {
|
||||
right = yield * this.getInsertionCleanEnd(right.id)
|
||||
right = this.getInsertionCleanEnd(right.id)
|
||||
}
|
||||
this.store.removeFromGarbageCollector(right)
|
||||
}
|
||||
yield * this.setOperation(right)
|
||||
this.setOperation(right)
|
||||
}
|
||||
|
||||
// update parents .map/start/end properties
|
||||
if (op.parentSub != null) {
|
||||
if (left == null) {
|
||||
parent.map[op.parentSub] = op.id
|
||||
yield * this.setOperation(parent)
|
||||
this.setOperation(parent)
|
||||
}
|
||||
// is a child of a map struct.
|
||||
// Then also make sure that only the most left element is not deleted
|
||||
// We do not call the type in this case (this is what the third parameter is for)
|
||||
if (op.right != null) {
|
||||
yield * this.deleteOperation(op.right, 1, true)
|
||||
this.deleteOperation(op.right, 1, true)
|
||||
}
|
||||
if (op.left != null) {
|
||||
yield * this.deleteOperation(op.id, 1, true)
|
||||
this.deleteOperation(op.id, 1, true)
|
||||
}
|
||||
} else {
|
||||
if (right == null || left == null) {
|
||||
@ -384,14 +384,14 @@ export default function extendStruct (Y) {
|
||||
if (left == null) {
|
||||
parent.start = op.id
|
||||
}
|
||||
yield * this.setOperation(parent)
|
||||
this.setOperation(parent)
|
||||
}
|
||||
}
|
||||
|
||||
// try to merge original op.left and op.origin
|
||||
for (i = 0; i < tryToRemergeLater.length; i++) {
|
||||
var m = yield * this.getOperation(tryToRemergeLater[i])
|
||||
yield * this.tryCombineWithLeft(m)
|
||||
var m = this.getOperation(tryToRemergeLater[i])
|
||||
this.tryCombineWithLeft(m)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -451,16 +451,16 @@ export default function extendStruct (Y) {
|
||||
*/
|
||||
return []
|
||||
},
|
||||
execute: function * (op) {
|
||||
execute: function (op) {
|
||||
op.start = null
|
||||
op.end = null
|
||||
},
|
||||
ref: function * (op, pos) {
|
||||
ref: function (op, pos) {
|
||||
if (op.start == null) {
|
||||
return null
|
||||
}
|
||||
var res = null
|
||||
var o = yield * this.getOperation(op.start)
|
||||
var o = this.getOperation(op.start)
|
||||
|
||||
while (true) {
|
||||
if (!o.deleted) {
|
||||
@ -468,18 +468,18 @@ export default function extendStruct (Y) {
|
||||
pos--
|
||||
}
|
||||
if (pos >= 0 && o.right != null) {
|
||||
o = yield * this.getOperation(o.right)
|
||||
o = this.getOperation(o.right)
|
||||
} else {
|
||||
break
|
||||
}
|
||||
}
|
||||
return res
|
||||
},
|
||||
map: function * (o, f) {
|
||||
map: function (o, f) {
|
||||
o = o.start
|
||||
var res = []
|
||||
while (o != null) { // TODO: change to != (at least some convention)
|
||||
var operation = yield * this.getOperation(o)
|
||||
var operation = this.getOperation(o)
|
||||
if (!operation.deleted) {
|
||||
res.push(f(operation))
|
||||
}
|
||||
@ -532,23 +532,23 @@ export default function extendStruct (Y) {
|
||||
requiredOps: function () {
|
||||
return []
|
||||
},
|
||||
execute: function * (op) {
|
||||
execute: function (op) {
|
||||
op.start = null
|
||||
op.end = null
|
||||
},
|
||||
/*
|
||||
Get a property by name
|
||||
*/
|
||||
get: function * (op, name) {
|
||||
get: function (op, name) {
|
||||
var oid = op.map[name]
|
||||
if (oid != null) {
|
||||
var res = yield * this.getOperation(oid)
|
||||
var res = this.getOperation(oid)
|
||||
if (res == null || res.deleted) {
|
||||
return void 0
|
||||
} else if (res.opContent == null) {
|
||||
return res.content[0]
|
||||
} else {
|
||||
return yield * this.getType(res.opContent)
|
||||
return this.getType(res.opContent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -608,7 +608,7 @@ export default function extendStruct (Y) {
|
||||
requiredOps: function () {
|
||||
return []
|
||||
},
|
||||
execute: function * () {},
|
||||
execute: function () {},
|
||||
ref: Struct.List.ref,
|
||||
map: Struct.List.map,
|
||||
/*
|
||||
|
@ -86,11 +86,11 @@ export default function extendTransaction (Y) {
|
||||
* does not check for Struct.*.requiredOps()
|
||||
* also broadcasts it through the connector
|
||||
*/
|
||||
* applyCreatedOperations (ops) {
|
||||
applyCreatedOperations (ops) {
|
||||
var send = []
|
||||
for (var i = 0; i < ops.length; i++) {
|
||||
var op = ops[i]
|
||||
yield * this.store.tryExecute.call(this, op)
|
||||
this.store.tryExecute.call(this, op)
|
||||
if (op.id == null || typeof op.id[1] !== 'string') {
|
||||
send.push(Y.Struct[op.struct].encode(op))
|
||||
}
|
||||
@ -104,17 +104,17 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
|
||||
* deleteList (start) {
|
||||
deleteList (start) {
|
||||
while (start != null) {
|
||||
start = yield * this.getOperation(start)
|
||||
start = this.getOperation(start)
|
||||
if (!start.gc) {
|
||||
start.gc = true
|
||||
start.deleted = true
|
||||
yield * this.setOperation(start)
|
||||
this.setOperation(start)
|
||||
var delLength = start.content != null ? start.content.length : 1
|
||||
yield * this.markDeleted(start.id, delLength)
|
||||
this.markDeleted(start.id, delLength)
|
||||
if (start.opContent != null) {
|
||||
yield * this.deleteOperation(start.opContent)
|
||||
this.deleteOperation(start.opContent)
|
||||
}
|
||||
this.store.queueGarbageCollector(start.id)
|
||||
}
|
||||
@ -125,14 +125,14 @@ export default function extendTransaction (Y) {
|
||||
/*
|
||||
Mark an operation as deleted, and add it to the GC, if possible.
|
||||
*/
|
||||
* deleteOperation (targetId, length, preventCallType) /* :Generator<any, any, any> */ {
|
||||
deleteOperation (targetId, length, preventCallType) /* :Generator<any, any, any> */ {
|
||||
if (length == null) {
|
||||
length = 1
|
||||
}
|
||||
yield * this.markDeleted(targetId, length)
|
||||
this.markDeleted(targetId, length)
|
||||
while (length > 0) {
|
||||
var callType = false
|
||||
var target = yield * this.os.findWithUpperBound([targetId[0], targetId[1] + length - 1])
|
||||
var target = this.os.findWithUpperBound([targetId[0], targetId[1] + length - 1])
|
||||
var targetLength = target != null && target.content != null ? target.content.length : 1
|
||||
if (target == null || target.id[0] !== targetId[0] || target.id[1] + targetLength <= targetId[1]) {
|
||||
// does not exist or is not in the range of the deletion
|
||||
@ -143,12 +143,12 @@ export default function extendTransaction (Y) {
|
||||
if (!target.deleted) {
|
||||
if (target.id[1] < targetId[1]) {
|
||||
// starts to the left of the deletion range
|
||||
target = yield * this.getInsertionCleanStart(targetId)
|
||||
target = this.getInsertionCleanStart(targetId)
|
||||
targetLength = target.content.length // must have content property!
|
||||
}
|
||||
if (target.id[1] + targetLength > targetId[1] + length) {
|
||||
// ends to the right of the deletion range
|
||||
target = yield * this.getInsertionCleanEnd([targetId[0], targetId[1] + length - 1])
|
||||
target = this.getInsertionCleanEnd([targetId[0], targetId[1] + length - 1])
|
||||
targetLength = target.content.length
|
||||
}
|
||||
}
|
||||
@ -163,35 +163,35 @@ export default function extendTransaction (Y) {
|
||||
// delete containing lists
|
||||
if (target.start != null) {
|
||||
// TODO: don't do it like this .. -.-
|
||||
yield * this.deleteList(target.start)
|
||||
// yield * this.deleteList(target.id) -- do not gc itself because this may still get referenced
|
||||
this.deleteList(target.start)
|
||||
// this.deleteList(target.id) -- do not gc itself because this may still get referenced
|
||||
}
|
||||
if (target.map != null) {
|
||||
for (var name in target.map) {
|
||||
yield * this.deleteList(target.map[name])
|
||||
this.deleteList(target.map[name])
|
||||
}
|
||||
// TODO: here to.. (see above)
|
||||
// yield * this.deleteList(target.id) -- see above
|
||||
// this.deleteList(target.id) -- see above
|
||||
}
|
||||
if (target.opContent != null) {
|
||||
yield * this.deleteOperation(target.opContent)
|
||||
this.deleteOperation(target.opContent)
|
||||
// target.opContent = null
|
||||
}
|
||||
if (target.requires != null) {
|
||||
for (var i = 0; i < target.requires.length; i++) {
|
||||
yield * this.deleteOperation(target.requires[i])
|
||||
this.deleteOperation(target.requires[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
var left
|
||||
if (target.left != null) {
|
||||
left = yield * this.getInsertion(target.left)
|
||||
left = this.getInsertion(target.left)
|
||||
} else {
|
||||
left = null
|
||||
}
|
||||
|
||||
// set here because it was deleted and/or gc'd
|
||||
yield * this.setOperation(target)
|
||||
this.setOperation(target)
|
||||
|
||||
/*
|
||||
Check if it is possible to add right to the gc.
|
||||
@ -200,12 +200,12 @@ export default function extendTransaction (Y) {
|
||||
*/
|
||||
var right
|
||||
if (target.right != null) {
|
||||
right = yield * this.getOperation(target.right)
|
||||
right = this.getOperation(target.right)
|
||||
} else {
|
||||
right = null
|
||||
}
|
||||
if (callType && !preventCallType) {
|
||||
yield * this.store.operationAdded(this, {
|
||||
this.store.operationAdded(this, {
|
||||
struct: 'Delete',
|
||||
target: target.id,
|
||||
length: targetLength,
|
||||
@ -213,9 +213,9 @@ export default function extendTransaction (Y) {
|
||||
})
|
||||
}
|
||||
// need to gc in the end!
|
||||
yield * this.store.addToGarbageCollector.call(this, target, left)
|
||||
this.store.addToGarbageCollector.call(this, target, left)
|
||||
if (right != null) {
|
||||
yield * this.store.addToGarbageCollector.call(this, right, target)
|
||||
this.store.addToGarbageCollector.call(this, right, target)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -223,25 +223,25 @@ export default function extendTransaction (Y) {
|
||||
/*
|
||||
Mark an operation as deleted&gc'd
|
||||
*/
|
||||
* markGarbageCollected (id, len) {
|
||||
markGarbageCollected (id, len) {
|
||||
// this.mem.push(["gc", id]);
|
||||
this.store.addToDebug('yield * this.markGarbageCollected(', id, ', ', len, ')')
|
||||
var n = yield * this.markDeleted(id, len)
|
||||
this.store.addToDebug('this.markGarbageCollected(', id, ', ', len, ')')
|
||||
var n = this.markDeleted(id, len)
|
||||
if (n.id[1] < id[1] && !n.gc) {
|
||||
// un-extend left
|
||||
var newlen = n.len - (id[1] - n.id[1])
|
||||
n.len -= newlen
|
||||
yield * this.ds.put(n)
|
||||
this.ds.put(n)
|
||||
n = {id: id, len: newlen, gc: false}
|
||||
yield * this.ds.put(n)
|
||||
this.ds.put(n)
|
||||
}
|
||||
// get prev&next before adding a new operation
|
||||
var prev = yield * this.ds.findPrev(id)
|
||||
var next = yield * this.ds.findNext(id)
|
||||
var prev = this.ds.findPrev(id)
|
||||
var next = this.ds.findNext(id)
|
||||
|
||||
if (id[1] + len < n.id[1] + n.len && !n.gc) {
|
||||
// un-extend right
|
||||
yield * this.ds.put({id: [id[0], id[1] + len], len: n.len - len, gc: false})
|
||||
this.ds.put({id: [id[0], id[1] + len], len: n.len - len, gc: false})
|
||||
n.len = len
|
||||
}
|
||||
// set gc'd
|
||||
@ -253,7 +253,7 @@ export default function extendTransaction (Y) {
|
||||
Y.utils.compareIds([prev.id[0], prev.id[1] + prev.len], n.id)
|
||||
) {
|
||||
prev.len += n.len
|
||||
yield * this.ds.delete(n.id)
|
||||
this.ds.delete(n.id)
|
||||
n = prev
|
||||
// ds.put n here?
|
||||
}
|
||||
@ -264,22 +264,22 @@ export default function extendTransaction (Y) {
|
||||
Y.utils.compareIds([n.id[0], n.id[1] + n.len], next.id)
|
||||
) {
|
||||
n.len += next.len
|
||||
yield * this.ds.delete(next.id)
|
||||
this.ds.delete(next.id)
|
||||
}
|
||||
yield * this.ds.put(n)
|
||||
yield * this.updateState(n.id[0])
|
||||
this.ds.put(n)
|
||||
this.updateState(n.id[0])
|
||||
}
|
||||
/*
|
||||
Mark an operation as deleted.
|
||||
|
||||
returns the delete node
|
||||
*/
|
||||
* markDeleted (id, length) {
|
||||
markDeleted (id, length) {
|
||||
if (length == null) {
|
||||
length = 1
|
||||
}
|
||||
// this.mem.push(["del", id]);
|
||||
var n = yield * this.ds.findWithUpperBound(id)
|
||||
var n = this.ds.findWithUpperBound(id)
|
||||
if (n != null && n.id[0] === id[0]) {
|
||||
if (n.id[1] <= id[1] && id[1] <= n.id[1] + n.len) {
|
||||
// id is in n's range
|
||||
@ -293,7 +293,7 @@ export default function extendTransaction (Y) {
|
||||
if (diff < length) {
|
||||
// a partial deletion
|
||||
n = {id: [id[0], id[1] + diff], len: length - diff, gc: false}
|
||||
yield * this.ds.put(n)
|
||||
this.ds.put(n)
|
||||
} else {
|
||||
// already gc'd
|
||||
throw new Error(
|
||||
@ -308,15 +308,15 @@ export default function extendTransaction (Y) {
|
||||
} else {
|
||||
// cannot extend left (there is no left!)
|
||||
n = {id: id, len: length, gc: false}
|
||||
yield * this.ds.put(n) // TODO: you double-put !!
|
||||
this.ds.put(n) // TODO: you double-put !!
|
||||
}
|
||||
} else {
|
||||
// cannot extend left
|
||||
n = {id: id, len: length, gc: false}
|
||||
yield * this.ds.put(n)
|
||||
this.ds.put(n)
|
||||
}
|
||||
// can extend right?
|
||||
var next = yield * this.ds.findNext(n.id)
|
||||
var next = this.ds.findNext(n.id)
|
||||
if (
|
||||
next != null &&
|
||||
n.id[0] === next.id[0] &&
|
||||
@ -332,8 +332,8 @@ export default function extendTransaction (Y) {
|
||||
// delete the missing range after next
|
||||
diff = diff - next.len // missing range after next
|
||||
if (diff > 0) {
|
||||
yield * this.ds.put(n) // unneccessary? TODO!
|
||||
yield * this.markDeleted([next.id[0], next.id[1] + next.len], diff)
|
||||
this.ds.put(n) // unneccessary? TODO!
|
||||
this.markDeleted([next.id[0], next.id[1] + next.len], diff)
|
||||
}
|
||||
}
|
||||
break
|
||||
@ -342,8 +342,8 @@ export default function extendTransaction (Y) {
|
||||
if (diff > next.len) {
|
||||
// n is even longer than next
|
||||
// get next.next, and try to extend it
|
||||
var _next = yield * this.ds.findNext(next.id)
|
||||
yield * this.ds.delete(next.id)
|
||||
var _next = this.ds.findNext(next.id)
|
||||
this.ds.delete(next.id)
|
||||
if (_next == null || n.id[0] !== _next.id[0]) {
|
||||
break
|
||||
} else {
|
||||
@ -354,13 +354,13 @@ export default function extendTransaction (Y) {
|
||||
} else {
|
||||
// n just partially overlaps with next. extend n, delete next, and break this loop
|
||||
n.len += next.len - diff
|
||||
yield * this.ds.delete(next.id)
|
||||
this.ds.delete(next.id)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
yield * this.ds.put(n)
|
||||
this.ds.put(n)
|
||||
return n
|
||||
}
|
||||
/*
|
||||
@ -368,7 +368,7 @@ export default function extendTransaction (Y) {
|
||||
other clients (e.g. master). This will query the database for
|
||||
operations that can be gc'd and add them to the garbage collector.
|
||||
*/
|
||||
* garbageCollectAfterSync () {
|
||||
garbageCollectAfterSync () {
|
||||
// debugger
|
||||
if (this.store.gc1.length > 0 || this.store.gc2.length > 0) {
|
||||
console.warn('gc should be empty after sync')
|
||||
@ -376,28 +376,28 @@ export default function extendTransaction (Y) {
|
||||
if (!this.store.gc) {
|
||||
return
|
||||
}
|
||||
yield * this.os.iterate(this, null, null, function * (op) {
|
||||
this.os.iterate(this, null, null, function (op) {
|
||||
if (op.gc) {
|
||||
delete op.gc
|
||||
yield * this.setOperation(op)
|
||||
this.setOperation(op)
|
||||
}
|
||||
if (op.parent != null) {
|
||||
var parentDeleted = yield * this.isDeleted(op.parent)
|
||||
var parentDeleted = this.isDeleted(op.parent)
|
||||
if (parentDeleted) {
|
||||
op.gc = true
|
||||
if (!op.deleted) {
|
||||
yield * this.markDeleted(op.id, op.content != null ? op.content.length : 1)
|
||||
this.markDeleted(op.id, op.content != null ? op.content.length : 1)
|
||||
op.deleted = true
|
||||
if (op.opContent != null) {
|
||||
yield * this.deleteOperation(op.opContent)
|
||||
this.deleteOperation(op.opContent)
|
||||
}
|
||||
if (op.requires != null) {
|
||||
for (var i = 0; i < op.requires.length; i++) {
|
||||
yield * this.deleteOperation(op.requires[i])
|
||||
this.deleteOperation(op.requires[i])
|
||||
}
|
||||
}
|
||||
}
|
||||
yield * this.setOperation(op)
|
||||
this.setOperation(op)
|
||||
this.store.gc1.push(op.id) // this is ok becaues its shortly before sync (otherwise use queueGarbageCollector!)
|
||||
return
|
||||
}
|
||||
@ -405,9 +405,9 @@ export default function extendTransaction (Y) {
|
||||
if (op.deleted) {
|
||||
var left = null
|
||||
if (op.left != null) {
|
||||
left = yield * this.getInsertion(op.left)
|
||||
left = this.getInsertion(op.left)
|
||||
}
|
||||
yield * this.store.addToGarbageCollector.call(this, op, left)
|
||||
this.store.addToGarbageCollector.call(this, op, left)
|
||||
}
|
||||
})
|
||||
}
|
||||
@ -420,10 +420,10 @@ export default function extendTransaction (Y) {
|
||||
* reset parent.end
|
||||
* reset origins of all right ops
|
||||
*/
|
||||
* garbageCollectOperation (id) {
|
||||
this.store.addToDebug('yield * this.garbageCollectOperation(', id, ')')
|
||||
var o = yield * this.getOperation(id)
|
||||
yield * this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
|
||||
garbageCollectOperation (id) {
|
||||
this.store.addToDebug('this.garbageCollectOperation(', id, ')')
|
||||
var o = this.getOperation(id)
|
||||
this.markGarbageCollected(id, (o != null && o.content != null) ? o.content.length : 1) // always mark gc'd
|
||||
// if op exists, then clean that mess up..
|
||||
if (o != null) {
|
||||
var deps = []
|
||||
@ -434,32 +434,32 @@ export default function extendTransaction (Y) {
|
||||
deps = deps.concat(o.requires)
|
||||
}
|
||||
for (var i = 0; i < deps.length; i++) {
|
||||
var dep = yield * this.getOperation(deps[i])
|
||||
var dep = this.getOperation(deps[i])
|
||||
if (dep != null) {
|
||||
if (!dep.deleted) {
|
||||
yield * this.deleteOperation(dep.id)
|
||||
dep = yield * this.getOperation(dep.id)
|
||||
this.deleteOperation(dep.id)
|
||||
dep = this.getOperation(dep.id)
|
||||
}
|
||||
dep.gc = true
|
||||
yield * this.setOperation(dep)
|
||||
this.setOperation(dep)
|
||||
this.store.queueGarbageCollector(dep.id)
|
||||
} else {
|
||||
yield * this.markGarbageCollected(deps[i], 1)
|
||||
this.markGarbageCollected(deps[i], 1)
|
||||
}
|
||||
}
|
||||
|
||||
// remove gc'd op from the left op, if it exists
|
||||
if (o.left != null) {
|
||||
var left = yield * this.getInsertion(o.left)
|
||||
var left = this.getInsertion(o.left)
|
||||
left.right = o.right
|
||||
yield * this.setOperation(left)
|
||||
this.setOperation(left)
|
||||
}
|
||||
// remove gc'd op from the right op, if it exists
|
||||
// also reset origins of right ops
|
||||
if (o.right != null) {
|
||||
var right = yield * this.getOperation(o.right)
|
||||
var right = this.getOperation(o.right)
|
||||
right.left = o.left
|
||||
yield * this.setOperation(right)
|
||||
this.setOperation(right)
|
||||
|
||||
if (o.originOf != null && o.originOf.length > 0) {
|
||||
// find new origin of right ops
|
||||
@ -478,7 +478,7 @@ export default function extendTransaction (Y) {
|
||||
right.origin = neworigin
|
||||
// search until you find origin pointer to the left of o
|
||||
if (right.right != null) {
|
||||
var i = yield * this.getOperation(right.right)
|
||||
var i = this.getOperation(right.right)
|
||||
var ids = [o.id, o.right]
|
||||
while (ids.some(function (id) {
|
||||
return Y.utils.compareIds(id, i.origin)
|
||||
@ -486,14 +486,14 @@ export default function extendTransaction (Y) {
|
||||
if (Y.utils.compareIds(i.origin, o.id)) {
|
||||
// reset origin of i
|
||||
i.origin = neworigin
|
||||
yield * this.setOperation(i)
|
||||
this.setOperation(i)
|
||||
}
|
||||
// get next i
|
||||
if (i.right == null) {
|
||||
break
|
||||
} else {
|
||||
ids.push(i.id)
|
||||
i = yield * this.getOperation(i.right)
|
||||
i = this.getOperation(i.right)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -502,20 +502,20 @@ export default function extendTransaction (Y) {
|
||||
// ** Now the new implementation starts **
|
||||
// reset neworigin of all originOf[*]
|
||||
for (var _i in o.originOf) {
|
||||
var originsIn = yield * this.getOperation(o.originOf[_i])
|
||||
var originsIn = this.getOperation(o.originOf[_i])
|
||||
if (originsIn != null) {
|
||||
originsIn.origin = neworigin
|
||||
yield * this.setOperation(originsIn)
|
||||
this.setOperation(originsIn)
|
||||
}
|
||||
}
|
||||
if (neworigin != null) {
|
||||
var neworigin_ = yield * this.getInsertion(neworigin)
|
||||
var neworigin_ = this.getInsertion(neworigin)
|
||||
if (neworigin_.originOf == null) {
|
||||
neworigin_.originOf = o.originOf
|
||||
} else {
|
||||
neworigin_.originOf = o.originOf.concat(neworigin_.originOf)
|
||||
}
|
||||
yield * this.setOperation(neworigin_)
|
||||
this.setOperation(neworigin_)
|
||||
}
|
||||
// we don't need to set right here, because
|
||||
// right should be in o.originOf => it is set it the previous for loop
|
||||
@ -524,15 +524,15 @@ export default function extendTransaction (Y) {
|
||||
// o may originate in another operation.
|
||||
// Since o is deleted, we have to reset o.origin's `originOf` property
|
||||
if (o.origin != null) {
|
||||
var origin = yield * this.getInsertion(o.origin)
|
||||
var origin = this.getInsertion(o.origin)
|
||||
origin.originOf = origin.originOf.filter(function (_id) {
|
||||
return !Y.utils.compareIds(id, _id)
|
||||
})
|
||||
yield * this.setOperation(origin)
|
||||
this.setOperation(origin)
|
||||
}
|
||||
var parent
|
||||
if (o.parent != null) {
|
||||
parent = yield * this.getOperation(o.parent)
|
||||
parent = this.getOperation(o.parent)
|
||||
}
|
||||
// remove gc'd op from parent, if it exists
|
||||
if (parent != null) {
|
||||
@ -559,38 +559,38 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
if (setParent) {
|
||||
yield * this.setOperation(parent)
|
||||
this.setOperation(parent)
|
||||
}
|
||||
}
|
||||
// finally remove it from the os
|
||||
yield * this.removeOperation(o.id)
|
||||
this.removeOperation(o.id)
|
||||
}
|
||||
}
|
||||
* checkDeleteStoreForState (state) {
|
||||
var n = yield * this.ds.findWithUpperBound([state.user, state.clock])
|
||||
checkDeleteStoreForState (state) {
|
||||
var n = this.ds.findWithUpperBound([state.user, state.clock])
|
||||
if (n != null && n.id[0] === state.user && n.gc) {
|
||||
state.clock = Math.max(state.clock, n.id[1] + n.len)
|
||||
}
|
||||
}
|
||||
* updateState (user) {
|
||||
var state = yield * this.getState(user)
|
||||
yield * this.checkDeleteStoreForState(state)
|
||||
var o = yield * this.getInsertion([user, state.clock])
|
||||
updateState (user) {
|
||||
var state = this.getState(user)
|
||||
this.checkDeleteStoreForState(state)
|
||||
var o = this.getInsertion([user, state.clock])
|
||||
var oLength = (o != null && o.content != null) ? o.content.length : 1
|
||||
while (o != null && user === o.id[0] && o.id[1] <= state.clock && o.id[1] + oLength > state.clock) {
|
||||
// either its a new operation (1. case), or it is an operation that was deleted, but is not yet in the OS
|
||||
state.clock += oLength
|
||||
yield * this.checkDeleteStoreForState(state)
|
||||
o = yield * this.os.findNext(o.id)
|
||||
this.checkDeleteStoreForState(state)
|
||||
o = this.os.findNext(o.id)
|
||||
oLength = (o != null && o.content != null) ? o.content.length : 1
|
||||
}
|
||||
yield * this.setState(state)
|
||||
this.setState(state)
|
||||
}
|
||||
/*
|
||||
apply a delete set in order to get
|
||||
the state of the supplied ds
|
||||
*/
|
||||
* applyDeleteSet (decoder) {
|
||||
applyDeleteSet (decoder) {
|
||||
var deletions = []
|
||||
|
||||
let dsLength = decoder.readUint32()
|
||||
@ -606,7 +606,7 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
var pos = 0
|
||||
var d = dv[pos]
|
||||
yield * this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function * (n) {
|
||||
this.ds.iterate(this, [user, 0], [user, Number.MAX_VALUE], function (n) {
|
||||
// cases:
|
||||
// 1. d deletes something to the right of n
|
||||
// => go to next n (break)
|
||||
@ -655,14 +655,14 @@ export default function extendTransaction (Y) {
|
||||
for (var i = 0; i < deletions.length; i++) {
|
||||
var del = deletions[i]
|
||||
// always try to delete..
|
||||
yield * this.deleteOperation([del[0], del[1]], del[2])
|
||||
this.deleteOperation([del[0], del[1]], del[2])
|
||||
if (del[3]) {
|
||||
// gc..
|
||||
yield * this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
|
||||
this.markGarbageCollected([del[0], del[1]], del[2]) // always mark gc'd
|
||||
// remove operation..
|
||||
var counter = del[1] + del[2]
|
||||
while (counter >= del[1]) {
|
||||
var o = yield * this.os.findWithUpperBound([del[0], counter - 1])
|
||||
var o = this.os.findWithUpperBound([del[0], counter - 1])
|
||||
if (o == null) {
|
||||
break
|
||||
}
|
||||
@ -673,14 +673,14 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
if (o.id[1] + oLen > del[1] + del[2]) {
|
||||
// overlaps right
|
||||
o = yield * this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
|
||||
o = this.getInsertionCleanEnd([del[0], del[1] + del[2] - 1])
|
||||
}
|
||||
if (o.id[1] < del[1]) {
|
||||
// overlaps left
|
||||
o = yield * this.getInsertionCleanStart([del[0], del[1]])
|
||||
o = this.getInsertionCleanStart([del[0], del[1]])
|
||||
}
|
||||
counter = o.id[1]
|
||||
yield * this.garbageCollectOperation(o.id)
|
||||
this.garbageCollectOperation(o.id)
|
||||
}
|
||||
}
|
||||
if (this.store.forwardAppliedOperations || this.store.y.persistence != null) {
|
||||
@ -695,16 +695,16 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
* isGarbageCollected (id) {
|
||||
var n = yield * this.ds.findWithUpperBound(id)
|
||||
isGarbageCollected (id) {
|
||||
var n = this.ds.findWithUpperBound(id)
|
||||
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len && n.gc
|
||||
}
|
||||
/*
|
||||
A DeleteSet (ds) describes all the deleted ops in the OS
|
||||
*/
|
||||
* writeDeleteSet (encoder) {
|
||||
writeDeleteSet (encoder) {
|
||||
var ds = new Map()
|
||||
yield * this.ds.iterate(this, null, null, function * (n) {
|
||||
this.ds.iterate(this, null, null, function (n) {
|
||||
var user = n.id[0]
|
||||
var counter = n.id[1]
|
||||
var len = n.len
|
||||
@ -731,16 +731,16 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
* isDeleted (id) {
|
||||
var n = yield * this.ds.findWithUpperBound(id)
|
||||
isDeleted (id) {
|
||||
var n = this.ds.findWithUpperBound(id)
|
||||
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
|
||||
}
|
||||
* setOperation (op) {
|
||||
yield * this.os.put(op)
|
||||
setOperation (op) {
|
||||
this.os.put(op)
|
||||
return op
|
||||
}
|
||||
* addOperation (op) {
|
||||
yield * this.os.put(op)
|
||||
addOperation (op) {
|
||||
this.os.put(op)
|
||||
// case op is created by this user, op is already broadcasted in applyCreatedOperations
|
||||
if (op.id[0] !== this.store.userId && typeof op.id[1] !== 'string') {
|
||||
if (this.store.forwardAppliedOperations) {
|
||||
@ -753,7 +753,7 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
// if insertion, try to combine with left insertion (if both have content property)
|
||||
* tryCombineWithLeft (op) {
|
||||
tryCombineWithLeft (op) {
|
||||
if (
|
||||
op != null &&
|
||||
op.left != null &&
|
||||
@ -761,7 +761,7 @@ export default function extendTransaction (Y) {
|
||||
op.left[0] === op.id[0] &&
|
||||
Y.utils.compareIds(op.left, op.origin)
|
||||
) {
|
||||
var left = yield * this.getInsertion(op.left)
|
||||
var left = this.getInsertion(op.left)
|
||||
if (left.content != null &&
|
||||
left.id[1] + left.content.length === op.id[1] &&
|
||||
left.originOf.length === 1 &&
|
||||
@ -776,13 +776,13 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
left.content = left.content.concat(op.content)
|
||||
left.right = op.right
|
||||
yield * this.os.delete(op.id)
|
||||
yield * this.setOperation(left)
|
||||
this.os.delete(op.id)
|
||||
this.setOperation(left)
|
||||
}
|
||||
}
|
||||
}
|
||||
* getInsertion (id) {
|
||||
var ins = yield * this.os.findWithUpperBound(id)
|
||||
getInsertion (id) {
|
||||
var ins = this.os.findWithUpperBound(id)
|
||||
if (ins == null) {
|
||||
return null
|
||||
} else {
|
||||
@ -794,14 +794,14 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
* getInsertionCleanStartEnd (id) {
|
||||
yield * this.getInsertionCleanStart(id)
|
||||
return yield * this.getInsertionCleanEnd(id)
|
||||
getInsertionCleanStartEnd (id) {
|
||||
this.getInsertionCleanStart(id)
|
||||
return this.getInsertionCleanEnd(id)
|
||||
}
|
||||
// Return an insertion such that id is the first element of content
|
||||
// This function manipulates an operation, if necessary
|
||||
* getInsertionCleanStart (id) {
|
||||
var ins = yield * this.getInsertion(id)
|
||||
getInsertionCleanStart (id) {
|
||||
var ins = this.getInsertion(id)
|
||||
if (ins != null) {
|
||||
if (ins.id[1] === id[1]) {
|
||||
return ins
|
||||
@ -815,8 +815,8 @@ export default function extendTransaction (Y) {
|
||||
left.right = ins.id
|
||||
ins.left = leftLid
|
||||
// debugger // check
|
||||
yield * this.setOperation(left)
|
||||
yield * this.setOperation(ins)
|
||||
this.setOperation(left)
|
||||
this.setOperation(ins)
|
||||
if (left.gc) {
|
||||
this.store.queueGarbageCollector(ins.id)
|
||||
}
|
||||
@ -828,8 +828,8 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
// Return an insertion such that id is the last element of content
|
||||
// This function manipulates an operation, if necessary
|
||||
* getInsertionCleanEnd (id) {
|
||||
var ins = yield * this.getInsertion(id)
|
||||
getInsertionCleanEnd (id) {
|
||||
var ins = this.getInsertion(id)
|
||||
if (ins != null) {
|
||||
if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) {
|
||||
return ins
|
||||
@ -843,8 +843,8 @@ export default function extendTransaction (Y) {
|
||||
ins.right = right.id
|
||||
right.left = insLid
|
||||
// debugger // check
|
||||
yield * this.setOperation(right)
|
||||
yield * this.setOperation(ins)
|
||||
this.setOperation(right)
|
||||
this.setOperation(ins)
|
||||
if (ins.gc) {
|
||||
this.store.queueGarbageCollector(right.id)
|
||||
}
|
||||
@ -854,8 +854,8 @@ export default function extendTransaction (Y) {
|
||||
return null
|
||||
}
|
||||
}
|
||||
* getOperation (id/* :any */)/* :Transaction<any> */ {
|
||||
var o = yield * this.os.find(id)
|
||||
getOperation (id/* :any */)/* :Transaction<any> */ {
|
||||
var o = this.os.find(id)
|
||||
if (id[0] !== 0xFFFFFF || o != null) {
|
||||
return o
|
||||
} else { // type is string
|
||||
@ -870,7 +870,7 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
var op = Y.Struct[struct].create(id, args)
|
||||
op.type = comp[1]
|
||||
yield * this.setOperation(op)
|
||||
this.setOperation(op)
|
||||
return op
|
||||
} else {
|
||||
throw new Error(
|
||||
@ -880,18 +880,18 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
* removeOperation (id) {
|
||||
yield * this.os.delete(id)
|
||||
removeOperation (id) {
|
||||
this.os.delete(id)
|
||||
}
|
||||
* setState (state) {
|
||||
setState (state) {
|
||||
var val = {
|
||||
id: [state.user],
|
||||
clock: state.clock
|
||||
}
|
||||
yield * this.ss.put(val)
|
||||
this.ss.put(val)
|
||||
}
|
||||
* getState (user) {
|
||||
var n = yield * this.ss.find([user])
|
||||
getState (user) {
|
||||
var n = this.ss.find([user])
|
||||
var clock = n == null ? null : n.clock
|
||||
if (clock == null) {
|
||||
clock = 0
|
||||
@ -901,9 +901,9 @@ export default function extendTransaction (Y) {
|
||||
clock: clock
|
||||
}
|
||||
}
|
||||
* getStateVector () {
|
||||
getStateVector () {
|
||||
var stateVector = []
|
||||
yield * this.ss.iterate(this, null, null, function * (n) {
|
||||
this.ss.iterate(this, null, null, function (n) {
|
||||
stateVector.push({
|
||||
user: n.id[0],
|
||||
clock: n.clock
|
||||
@ -911,18 +911,18 @@ export default function extendTransaction (Y) {
|
||||
})
|
||||
return stateVector
|
||||
}
|
||||
* getStateSet () {
|
||||
getStateSet () {
|
||||
var ss = {}
|
||||
yield * this.ss.iterate(this, null, null, function * (n) {
|
||||
this.ss.iterate(this, null, null, function (n) {
|
||||
ss[n.id[0]] = n.clock
|
||||
})
|
||||
return ss
|
||||
}
|
||||
* writeStateSet (encoder) {
|
||||
writeStateSet (encoder) {
|
||||
let lenPosition = encoder.pos
|
||||
let len = 0
|
||||
encoder.writeUint32(0)
|
||||
yield * this.ss.iterate(this, null, null, function * (n) {
|
||||
this.ss.iterate(this, null, null, function (n) {
|
||||
encoder.writeVarUint(n.id[0])
|
||||
encoder.writeVarUint(n.clock)
|
||||
len++
|
||||
@ -976,14 +976,14 @@ export default function extendTransaction (Y) {
|
||||
3. Found o = op.origin -> set op.left = op.origin, and send it to the user. start again from 1. (set op = o)
|
||||
4. Found some o -> set o.right = op, o.left = o.origin, send it to the user, continue
|
||||
*/
|
||||
* getOperations (startSS) {
|
||||
getOperations (startSS) {
|
||||
// TODO: use bounds here!
|
||||
if (startSS == null) {
|
||||
startSS = new Map()
|
||||
}
|
||||
var send = []
|
||||
|
||||
var endSV = yield * this.getStateVector()
|
||||
var endSV = this.getStateVector()
|
||||
for (let endState of endSV) {
|
||||
let user = endState.user
|
||||
if (user === 0xFFFFFF) {
|
||||
@ -993,7 +993,7 @@ export default function extendTransaction (Y) {
|
||||
if (startPos > 0) {
|
||||
// There is a change that [user, startPos] is in a composed Insertion (with a smaller counter)
|
||||
// find out if that is the case
|
||||
let firstMissing = yield * this.getInsertion([user, startPos])
|
||||
let firstMissing = this.getInsertion([user, startPos])
|
||||
if (firstMissing != null) {
|
||||
// update startPos
|
||||
startPos = firstMissing.id[1]
|
||||
@ -1007,7 +1007,7 @@ export default function extendTransaction (Y) {
|
||||
if (user === 0xFFFFFF) {
|
||||
continue
|
||||
}
|
||||
yield * this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function * (op) {
|
||||
this.os.iterate(this, [user, startPos], [user, Number.MAX_VALUE], function (op) {
|
||||
op = Y.Struct[op.struct].encode(op)
|
||||
if (op.struct !== 'Insert') {
|
||||
send.push(op)
|
||||
@ -1038,7 +1038,7 @@ export default function extendTransaction (Y) {
|
||||
*/
|
||||
break
|
||||
}
|
||||
o = yield * this.getInsertion(o.left)
|
||||
o = this.getInsertion(o.left)
|
||||
// we set another o, check if we can reduce $missingOrigins
|
||||
while (missingOrigins.length > 0 && Y.utils.matchesId(o, missingOrigins[missingOrigins.length - 1].origin)) {
|
||||
missingOrigins.pop()
|
||||
@ -1076,7 +1076,7 @@ export default function extendTransaction (Y) {
|
||||
return send.reverse()
|
||||
}
|
||||
|
||||
* writeOperations (encoder, decoder) {
|
||||
writeOperations (encoder, decoder) {
|
||||
let ss = new Map()
|
||||
let ssLength = decoder.readUint32()
|
||||
for (let i = 0; i < ssLength; i++) {
|
||||
@ -1084,7 +1084,7 @@ export default function extendTransaction (Y) {
|
||||
let clock = decoder.readVarUint()
|
||||
ss.set(user, clock)
|
||||
}
|
||||
let ops = yield * this.getOperations(ss)
|
||||
let ops = this.getOperations(ss)
|
||||
encoder.writeUint32(ops.length)
|
||||
for (let i = 0; i < ops.length; i++) {
|
||||
let op = ops[i]
|
||||
@ -1092,17 +1092,17 @@ export default function extendTransaction (Y) {
|
||||
}
|
||||
}
|
||||
|
||||
* toBinary () {
|
||||
toBinary () {
|
||||
let encoder = new BinaryEncoder()
|
||||
yield * this.writeOperationsUntransformed(encoder)
|
||||
yield * this.writeDeleteSet(encoder)
|
||||
this.writeOperationsUntransformed(encoder)
|
||||
this.writeDeleteSet(encoder)
|
||||
return encoder.createBuffer()
|
||||
}
|
||||
|
||||
* fromBinary (buffer) {
|
||||
fromBinary (buffer) {
|
||||
let decoder = new BinaryDecoder(buffer)
|
||||
yield * this.applyOperationsUntransformed(decoder)
|
||||
yield * this.applyDeleteSet(decoder)
|
||||
this.applyOperationsUntransformed(decoder)
|
||||
this.applyDeleteSet(decoder)
|
||||
}
|
||||
|
||||
/*
|
||||
@ -1110,43 +1110,43 @@ export default function extendTransaction (Y) {
|
||||
* You can apply these operations using .applyOperationsUntransformed(ops)
|
||||
*
|
||||
*/
|
||||
* writeOperationsUntransformed (encoder) {
|
||||
writeOperationsUntransformed (encoder) {
|
||||
let lenPosition = encoder.pos
|
||||
let len = 0
|
||||
encoder.writeUint32(0) // placeholder
|
||||
yield * this.os.iterate(this, null, null, function * (op) {
|
||||
this.os.iterate(this, null, null, function (op) {
|
||||
if (op.id[0] !== 0xFFFFFF) {
|
||||
len++
|
||||
Y.Struct[op.struct].binaryEncode(encoder, Y.Struct[op.struct].encode(op))
|
||||
}
|
||||
})
|
||||
encoder.setUint32(lenPosition, len)
|
||||
yield * this.writeStateSet(encoder)
|
||||
this.writeStateSet(encoder)
|
||||
}
|
||||
* applyOperationsUntransformed (decoder) {
|
||||
applyOperationsUntransformed (decoder) {
|
||||
let len = decoder.readUint32()
|
||||
for (let i = 0; i < len; i++) {
|
||||
let op = Y.Struct.binaryDecodeOperation(decoder)
|
||||
yield * this.os.put(op)
|
||||
this.os.put(op)
|
||||
}
|
||||
yield * this.os.iterate(this, null, null, function * (op) {
|
||||
this.os.iterate(this, null, null, function (op) {
|
||||
if (op.parent != null) {
|
||||
if (op.struct === 'Insert') {
|
||||
// update parents .map/start/end properties
|
||||
if (op.parentSub != null && op.left == null) {
|
||||
// op is child of Map
|
||||
let parent = yield * this.getOperation(op.parent)
|
||||
let parent = this.getOperation(op.parent)
|
||||
parent.map[op.parentSub] = op.id
|
||||
yield * this.setOperation(parent)
|
||||
this.setOperation(parent)
|
||||
} else if (op.right == null || op.left == null) {
|
||||
let parent = yield * this.getOperation(op.parent)
|
||||
let parent = this.getOperation(op.parent)
|
||||
if (op.right == null) {
|
||||
parent.end = Y.utils.getLastId(op)
|
||||
}
|
||||
if (op.left == null) {
|
||||
parent.start = op.id
|
||||
}
|
||||
yield * this.setOperation(parent)
|
||||
this.setOperation(parent)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1155,14 +1155,14 @@ export default function extendTransaction (Y) {
|
||||
for (let i = 0; i < stateSetLength; i++) {
|
||||
let user = decoder.readVarUint()
|
||||
let clock = decoder.readVarUint()
|
||||
yield * this.ss.put({
|
||||
this.ss.put({
|
||||
id: [user],
|
||||
clock: clock
|
||||
})
|
||||
}
|
||||
}
|
||||
/* this is what we used before.. use this as a reference..
|
||||
* makeOperationReady (startSS, op) {
|
||||
makeOperationReady (startSS, op) {
|
||||
op = Y.Struct[op.struct].encode(op)
|
||||
op = Y.utils.copyObject(op) -- use copyoperation instead now!
|
||||
var o = op
|
||||
@ -1172,7 +1172,7 @@ export default function extendTransaction (Y) {
|
||||
// or the o that has no origin to the right of op
|
||||
// (this is why we use the ids array)
|
||||
while (o.right != null) {
|
||||
var right = yield * this.getOperation(o.right)
|
||||
var right = this.getOperation(o.right)
|
||||
if (o.right[1] < (startSS[o.right[0]] || 0) || !ids.some(function (id) {
|
||||
return Y.utils.compareIds(id, right.origin)
|
||||
})) {
|
||||
@ -1186,10 +1186,10 @@ export default function extendTransaction (Y) {
|
||||
return op
|
||||
}
|
||||
*/
|
||||
* flush () {
|
||||
yield * this.os.flush()
|
||||
yield * this.ss.flush()
|
||||
yield * this.ds.flush()
|
||||
flush () {
|
||||
this.os.flush()
|
||||
this.ss.flush()
|
||||
this.ds.flush()
|
||||
}
|
||||
}
|
||||
Y.Transaction = TransactionInterface
|
||||
|
62
src/Utils.js
62
src/Utils.js
@ -315,7 +315,7 @@ export default function Utils (Y) {
|
||||
this.awaiting++
|
||||
ops.map(Y.utils.copyOperation).forEach(this.onevent)
|
||||
}
|
||||
* awaitOps (transaction, f, args) {
|
||||
awaitOps (transaction, f, args) {
|
||||
function notSoSmartSort (array) {
|
||||
// this function sorts insertions in a executable order
|
||||
var result = []
|
||||
@ -339,7 +339,7 @@ export default function Utils (Y) {
|
||||
}
|
||||
var before = this.waiting.length
|
||||
// somehow create new operations
|
||||
yield * f.apply(transaction, args)
|
||||
f.apply(transaction, args)
|
||||
// remove all appended ops / awaited ops
|
||||
this.waiting.splice(before)
|
||||
if (this.awaiting > 0) this.awaiting--
|
||||
@ -349,7 +349,7 @@ export default function Utils (Y) {
|
||||
for (let i = 0; i < this.waiting.length; i++) {
|
||||
var o = this.waiting[i]
|
||||
if (o.struct === 'Insert') {
|
||||
var _o = yield * transaction.getInsertion(o.id)
|
||||
var _o = transaction.getInsertion(o.id)
|
||||
if (_o.parentSub != null && _o.left != null) {
|
||||
// if o is an insertion of a map struc (parentSub is defined), then it shouldn't be necessary to compute left
|
||||
this.waiting.splice(i, 1)
|
||||
@ -361,10 +361,10 @@ export default function Utils (Y) {
|
||||
o.left = null
|
||||
} else {
|
||||
// find next undeleted op
|
||||
var left = yield * transaction.getInsertion(_o.left)
|
||||
var left = transaction.getInsertion(_o.left)
|
||||
while (left.deleted != null) {
|
||||
if (left.left != null) {
|
||||
left = yield * transaction.getInsertion(left.left)
|
||||
left = transaction.getInsertion(left.left)
|
||||
} else {
|
||||
left = null
|
||||
break
|
||||
@ -690,7 +690,7 @@ export default function Utils (Y) {
|
||||
this.writeBuffer = createEmptyOpsArray(5)
|
||||
this.readBuffer = createEmptyOpsArray(10)
|
||||
}
|
||||
* find (id, noSuperCall) {
|
||||
find (id, noSuperCall) {
|
||||
var i, r
|
||||
for (i = this.readBuffer.length - 1; i >= 0; i--) {
|
||||
r = this.readBuffer[i]
|
||||
@ -716,7 +716,7 @@ export default function Utils (Y) {
|
||||
if (i < 0 && noSuperCall === undefined) {
|
||||
// did not reach break in last loop
|
||||
// read id and put it to the end of readBuffer
|
||||
o = yield * super.find(id)
|
||||
o = super.find(id)
|
||||
}
|
||||
if (o != null) {
|
||||
for (i = 0; i < this.readBuffer.length - 1; i++) {
|
||||
@ -726,7 +726,7 @@ export default function Utils (Y) {
|
||||
}
|
||||
return o
|
||||
}
|
||||
* put (o) {
|
||||
put (o) {
|
||||
var id = o.id
|
||||
var i, r // helper variables
|
||||
for (i = this.writeBuffer.length - 1; i >= 0; i--) {
|
||||
@ -746,7 +746,7 @@ export default function Utils (Y) {
|
||||
// write writeBuffer[0]
|
||||
var write = this.writeBuffer[0]
|
||||
if (write.id[0] !== null) {
|
||||
yield * super.put(write)
|
||||
super.put(write)
|
||||
}
|
||||
// put o to the end of writeBuffer
|
||||
for (i = 0; i < this.writeBuffer.length - 1; i++) {
|
||||
@ -766,7 +766,7 @@ export default function Utils (Y) {
|
||||
}
|
||||
this.readBuffer[this.readBuffer.length - 1] = o
|
||||
}
|
||||
* delete (id) {
|
||||
delete (id) {
|
||||
var i, r
|
||||
for (i = 0; i < this.readBuffer.length; i++) {
|
||||
r = this.readBuffer[i]
|
||||
@ -776,44 +776,44 @@ export default function Utils (Y) {
|
||||
}
|
||||
}
|
||||
}
|
||||
yield * this.flush()
|
||||
yield * super.delete(id)
|
||||
this.flush()
|
||||
super.delete(id)
|
||||
}
|
||||
* findWithLowerBound (id) {
|
||||
var o = yield * this.find(id, true)
|
||||
findWithLowerBound (id) {
|
||||
var o = this.find(id, true)
|
||||
if (o != null) {
|
||||
return o
|
||||
} else {
|
||||
yield * this.flush()
|
||||
return yield * super.findWithLowerBound.apply(this, arguments)
|
||||
this.flush()
|
||||
return super.findWithLowerBound.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
* findWithUpperBound (id) {
|
||||
var o = yield * this.find(id, true)
|
||||
findWithUpperBound (id) {
|
||||
var o = this.find(id, true)
|
||||
if (o != null) {
|
||||
return o
|
||||
} else {
|
||||
yield * this.flush()
|
||||
return yield * super.findWithUpperBound.apply(this, arguments)
|
||||
this.flush()
|
||||
return super.findWithUpperBound.apply(this, arguments)
|
||||
}
|
||||
}
|
||||
* findNext () {
|
||||
yield * this.flush()
|
||||
return yield * super.findNext.apply(this, arguments)
|
||||
findNext () {
|
||||
this.flush()
|
||||
return super.findNext.apply(this, arguments)
|
||||
}
|
||||
* findPrev () {
|
||||
yield * this.flush()
|
||||
return yield * super.findPrev.apply(this, arguments)
|
||||
findPrev () {
|
||||
this.flush()
|
||||
return super.findPrev.apply(this, arguments)
|
||||
}
|
||||
* iterate () {
|
||||
yield * this.flush()
|
||||
yield * super.iterate.apply(this, arguments)
|
||||
iterate () {
|
||||
this.flush()
|
||||
super.iterate.apply(this, arguments)
|
||||
}
|
||||
* flush () {
|
||||
flush () {
|
||||
for (var i = 0; i < this.writeBuffer.length; i++) {
|
||||
var write = this.writeBuffer[i]
|
||||
if (write.id[0] !== null) {
|
||||
yield * super.put(write)
|
||||
super.put(write)
|
||||
this.writeBuffer[i] = {
|
||||
id: [null, null]
|
||||
}
|
||||
|
64
src/y-memory.js
Normal file
64
src/y-memory.js
Normal 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
|
||||
}
|
10
src/y.js
10
src/y.js
@ -4,6 +4,7 @@ import extendDatabase from './Database.js'
|
||||
import extendTransaction from './Transaction.js'
|
||||
import extendStruct from './Struct.js'
|
||||
import extendUtils from './Utils.js'
|
||||
import extendMemory from './y-memory.js'
|
||||
import debug from 'debug'
|
||||
import { formatYjsMessage, formatYjsMessageType } from './MessageHandler.js'
|
||||
|
||||
@ -13,6 +14,7 @@ extendDatabase(Y)
|
||||
extendTransaction(Y)
|
||||
extendStruct(Y)
|
||||
extendUtils(Y)
|
||||
extendMemory(Y)
|
||||
|
||||
Y.debug = debug
|
||||
debug.formatters.Y = formatYjsMessage
|
||||
@ -182,7 +184,7 @@ class YConfig extends Y.utils.NamedEventHandler {
|
||||
var opts = this.options
|
||||
var share = {}
|
||||
this.share = share
|
||||
this.db.requestTransaction(function * requestTransaction () {
|
||||
this.db.requestTransaction(function requestTransaction () {
|
||||
// create shared object
|
||||
for (var propertyname in opts.share) {
|
||||
var typeConstructor = opts.share[propertyname].split('(')
|
||||
@ -195,7 +197,7 @@ class YConfig extends Y.utils.NamedEventHandler {
|
||||
var typedef = type.typeDefinition
|
||||
var id = [0xFFFFFF, typedef.struct + '_' + typeName + '_' + propertyname + '_' + typeArgs]
|
||||
let args = Y.utils.parseTypeDefinition(type, typeArgs)
|
||||
share[propertyname] = yield * this.store.initType.call(this, id, args)
|
||||
share[propertyname] = this.store.initType.call(this, id, args)
|
||||
}
|
||||
})
|
||||
if (this.persistence != null) {
|
||||
@ -250,8 +252,8 @@ class YConfig extends Y.utils.NamedEventHandler {
|
||||
return this.db.whenTransactionsFinished().then(function () {
|
||||
self.db.destroyTypes()
|
||||
// make sure to wait for all transactions before destroying the db
|
||||
self.db.requestTransaction(function * () {
|
||||
yield * self.db.destroy()
|
||||
self.db.requestTransaction(function () {
|
||||
self.db.destroy()
|
||||
})
|
||||
return self.db.whenTransactionsFinished()
|
||||
})
|
||||
|
@ -161,7 +161,9 @@ test('encode/decode List operations', async function binList (t) {
|
||||
testEncoding(t, writeList, readList, {
|
||||
struct: 'List',
|
||||
id: [100, 33],
|
||||
type: 'Array'
|
||||
type: 'Array',
|
||||
start: null,
|
||||
end: null
|
||||
})
|
||||
})
|
||||
|
||||
|
217
test/red-black-tree.js
Normal file
217
test/red-black-tree.js
Normal 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()
|
||||
})
|
@ -4,7 +4,7 @@ import _Y from '../../yjs/src/y.js'
|
||||
import yMemory from '../../y-memory/src/y-memory.js'
|
||||
import yArray from '../../y-array/src/y-array.js'
|
||||
import yText from '../../y-text/src/Text.js'
|
||||
import yMap from '../../y-map/src/Map.js'
|
||||
import yMap from '../../y-map/src/y-map.js'
|
||||
import yXml from '../../y-xml/src/y-xml.js'
|
||||
import yTest from './test-connector.js'
|
||||
|
||||
@ -17,9 +17,9 @@ Y.extend(yMemory, yArray, yText, yMap, yTest, yXml)
|
||||
export var database = { name: 'memory' }
|
||||
export var connector = { name: 'test', url: 'http://localhost:1234' }
|
||||
|
||||
function * getStateSet () {
|
||||
function getStateSet () {
|
||||
var ss = {}
|
||||
yield * this.ss.iterate(this, null, null, function * (n) {
|
||||
this.ss.iterate(this, null, null, function (n) {
|
||||
var user = n.id[0]
|
||||
var clock = n.clock
|
||||
ss[user] = clock
|
||||
@ -27,9 +27,9 @@ function * getStateSet () {
|
||||
return ss
|
||||
}
|
||||
|
||||
function * getDeleteSet () {
|
||||
function getDeleteSet () {
|
||||
var ds = {}
|
||||
yield * this.ds.iterate(this, null, null, function * (n) {
|
||||
this.ds.iterate(this, null, null, function (n) {
|
||||
var user = n.id[0]
|
||||
var counter = n.id[1]
|
||||
var len = n.len
|
||||
@ -126,9 +126,9 @@ export async function compareUsers (t, users) {
|
||||
let filterDeletedOps = users.every(u => u.db.gc === false)
|
||||
var data = await Promise.all(users.map(async (u) => {
|
||||
var data = {}
|
||||
u.db.requestTransaction(function * () {
|
||||
u.db.requestTransaction(function () {
|
||||
let ops = []
|
||||
yield * this.os.iterate(this, null, null, function * (op) {
|
||||
this.os.iterate(this, null, null, function (op) {
|
||||
ops.push(Y.Struct[op.struct].encode(op))
|
||||
})
|
||||
|
||||
@ -142,7 +142,7 @@ export async function compareUsers (t, users) {
|
||||
as they might have been split up differently..
|
||||
*/
|
||||
if (filterDeletedOps) {
|
||||
let opIsDeleted = yield * this.isDeleted(op.id)
|
||||
let opIsDeleted = this.isDeleted(op.id)
|
||||
if (!opIsDeleted) {
|
||||
data.os[JSON.stringify(op.id)] = op
|
||||
}
|
||||
@ -150,8 +150,8 @@ export async function compareUsers (t, users) {
|
||||
data.os[JSON.stringify(op.id)] = op
|
||||
}
|
||||
}
|
||||
data.ds = yield * getDeleteSet.apply(this)
|
||||
data.ss = yield * getStateSet.apply(this)
|
||||
data.ds = getDeleteSet.apply(this)
|
||||
data.ss = getStateSet.apply(this)
|
||||
})
|
||||
await u.db.whenTransactionsFinished()
|
||||
return data
|
||||
|
Loading…
x
Reference in New Issue
Block a user