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