fix first y-array test
This commit is contained in:
parent
4eec8ecdd3
commit
1311c7a0d8
@ -3,7 +3,7 @@ import commonjs from 'rollup-plugin-commonjs'
|
||||
import multiEntry from 'rollup-plugin-multi-entry'
|
||||
|
||||
export default {
|
||||
entry: 'test/{encode-decode,red-black-tree}.js',
|
||||
entry: 'test/y-array.tests.js',
|
||||
moduleName: 'y-tests',
|
||||
format: 'umd',
|
||||
plugins: [
|
||||
|
@ -1,4 +1,6 @@
|
||||
import utf8 from 'utf-8'
|
||||
import ID from '../Util/ID.js'
|
||||
import { default as RootID, RootFakeUserID } from '../Util/RootID.js'
|
||||
|
||||
export default class BinaryDecoder {
|
||||
constructor (buffer) {
|
||||
@ -107,9 +109,11 @@ export default class BinaryDecoder {
|
||||
*/
|
||||
readID () {
|
||||
let user = this.readVarUint()
|
||||
if (user === 0xFFFFFF) {
|
||||
if (user === RootFakeUserID) {
|
||||
// read property name and type id
|
||||
return new RootID(this.readVarString(), this.readVarUint())
|
||||
const rid = new RootID(this.readVarString(), null)
|
||||
rid.type = this.readVarUint()
|
||||
return rid
|
||||
}
|
||||
return new ID(user, this.readVarUint())
|
||||
}
|
||||
|
@ -1,4 +1,5 @@
|
||||
import utf8 from 'utf-8'
|
||||
import { RootFakeUserID } from '../Util/RootID.js'
|
||||
|
||||
const bits7 = 0b1111111
|
||||
const bits8 = 0b11111111
|
||||
@ -68,13 +69,14 @@ export default class BinaryEncoder {
|
||||
}
|
||||
}
|
||||
|
||||
writeOpID (id) {
|
||||
let user = id[0]
|
||||
writeID (id) {
|
||||
const user = id.user
|
||||
this.writeVarUint(user)
|
||||
if (user !== 0xFFFFFF) {
|
||||
this.writeVarUint(id[1])
|
||||
if (user !== RootFakeUserID) {
|
||||
this.writeVarUint(id.clock)
|
||||
} else {
|
||||
this.writeVarString(id[1])
|
||||
this.writeVarString(id.name)
|
||||
this.writeVarUint(id.type)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -41,7 +41,6 @@ export default class AbstractConnector {
|
||||
|
||||
reconnect () {
|
||||
this.log('reconnecting..')
|
||||
return this.y.db.startGarbageCollector()
|
||||
}
|
||||
|
||||
disconnect () {
|
||||
@ -63,7 +62,7 @@ export default class AbstractConnector {
|
||||
|
||||
userLeft (user) {
|
||||
if (this.connections.has(user)) {
|
||||
this.log('%s: User left %s', this.userId, user)
|
||||
this.log('%s: User left %s', this.y.userID, user)
|
||||
this.connections.delete(user)
|
||||
// check if isSynced event can be sent now
|
||||
this._setSyncedWith(null)
|
||||
@ -83,7 +82,7 @@ export default class AbstractConnector {
|
||||
if (this.connections.has(user)) {
|
||||
throw new Error('This user already joined!')
|
||||
}
|
||||
this.log('%s: User joined %s', this.userId, user)
|
||||
this.log('%s: User joined %s', this.y.userID, user)
|
||||
this.connections.set(user, {
|
||||
uid: user,
|
||||
isSynced: false,
|
||||
@ -115,33 +114,32 @@ export default class AbstractConnector {
|
||||
}
|
||||
}
|
||||
|
||||
_syncWithUser (userid) {
|
||||
_syncWithUser (userID) {
|
||||
if (this.role === 'slave') {
|
||||
return // "The current sync has not finished or this is controlled by a master!"
|
||||
}
|
||||
sendSyncStep1(this, userid)
|
||||
sendSyncStep1(this, userID)
|
||||
}
|
||||
|
||||
_fireIsSyncedListeners () {
|
||||
new Promise().then(() => {
|
||||
setTimeout(() => {
|
||||
if (!this.isSynced) {
|
||||
this.isSynced = true
|
||||
// It is safer to remove this!
|
||||
// TODO: remove: this.garbageCollectAfterSync()
|
||||
// call whensynced listeners
|
||||
for (var f of this.whenSyncedListeners) {
|
||||
f()
|
||||
}
|
||||
this.whenSyncedListeners = []
|
||||
}
|
||||
})
|
||||
}, 0)
|
||||
}
|
||||
|
||||
send (uid, buffer) {
|
||||
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
||||
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - don\'t use this method to send custom messages')
|
||||
}
|
||||
this.log('%s: Send \'%y\' to %s', this.userId, buffer, uid)
|
||||
this.log('%s: Send \'%y\' to %s', this.y.userID, buffer, uid)
|
||||
this.logMessage('Message: %Y', buffer)
|
||||
}
|
||||
|
||||
@ -149,7 +147,7 @@ export default class AbstractConnector {
|
||||
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
||||
throw new Error('Expected Message to be an ArrayBuffer or Uint8Array - don\'t use this method to send custom messages')
|
||||
}
|
||||
this.log('%s: Broadcast \'%y\'', this.userId, buffer)
|
||||
this.log('%s: Broadcast \'%y\'', this.y.userID, buffer)
|
||||
this.logMessage('Message: %Y', buffer)
|
||||
}
|
||||
|
||||
@ -157,7 +155,11 @@ export default class AbstractConnector {
|
||||
Buffer operations, and broadcast them when ready.
|
||||
*/
|
||||
broadcastStruct (struct) {
|
||||
let firstContent = this.broadcastBuffer.length === 0
|
||||
const firstContent = this.broadcastBuffer.length === 0
|
||||
if (firstContent) {
|
||||
this.broadcastBuffer.writeVarString(this.y.room)
|
||||
this.broadcastBuffer.writeVarString('update')
|
||||
}
|
||||
struct._toBinary(this.broadcastBuffer)
|
||||
if (this.maxBufferLength > 0 && this.broadcastBuffer.length > this.maxBufferLength) {
|
||||
// it is necessary to send the buffer now
|
||||
@ -172,7 +174,7 @@ export default class AbstractConnector {
|
||||
// (or buffer exceeds maxBufferLength)
|
||||
setTimeout(() => {
|
||||
if (this.broadcastBuffer.length > 0) {
|
||||
this.broadcast(this.broadcastBuffer)
|
||||
this.broadcast(this.broadcastBuffer.createBuffer())
|
||||
this.broadcastBuffer = new BinaryEncoder()
|
||||
}
|
||||
})
|
||||
@ -201,7 +203,7 @@ export default class AbstractConnector {
|
||||
if (!(buffer instanceof ArrayBuffer || buffer instanceof Uint8Array)) {
|
||||
return Promise.reject(new Error('Expected Message to be an ArrayBuffer or Uint8Array!'))
|
||||
}
|
||||
if (sender === this.userId) {
|
||||
if (sender === this.y.userID) {
|
||||
return Promise.resolve()
|
||||
}
|
||||
let decoder = new BinaryDecoder(buffer)
|
||||
@ -210,7 +212,7 @@ export default class AbstractConnector {
|
||||
encoder.writeVarString(roomname)
|
||||
let messageType = decoder.readVarString()
|
||||
let senderConn = this.connections.get(sender)
|
||||
this.log('%s: Receive \'%s\' from %s', this.userId, messageType, sender)
|
||||
this.log('%s: Receive \'%s\' from %s', this.y.userID, messageType, sender)
|
||||
this.logMessage('Message: %Y', buffer)
|
||||
if (senderConn == null && !skipAuth) {
|
||||
throw new Error('Received message from unknown peer!')
|
||||
@ -247,7 +249,7 @@ export default class AbstractConnector {
|
||||
computeMessage (messageType, senderConn, decoder, encoder, sender, skipAuth) {
|
||||
if (messageType === 'sync step 1' && (senderConn.auth === 'write' || senderConn.auth === 'read')) {
|
||||
// cannot wait for sync step 1 to finish, because we may wait for sync step 2 in sync step 1 (->lock)
|
||||
readSyncStep1()(decoder, encoder, this.y, senderConn, sender)
|
||||
readSyncStep1(decoder, encoder, this.y, senderConn, sender)
|
||||
} else if (messageType === 'sync step 2' && senderConn.auth === 'write') {
|
||||
readSyncStep2(decoder, encoder, this.y, senderConn, sender)
|
||||
} else if (messageType === 'update' && (skipAuth || senderConn.auth === 'write')) {
|
||||
|
@ -1,4 +1,4 @@
|
||||
import deleteItemRange from 'deleteItemRange'
|
||||
import { deleteItemRange } from '../Struct/Delete.js'
|
||||
|
||||
export function stringifyDeleteSet (y, decoder, strBuilder) {
|
||||
let dsLength = decoder.readUint32()
|
||||
@ -48,7 +48,7 @@ export function writeDeleteSet (y, encoder) {
|
||||
if (currentUser !== null) { // happens on first iteration
|
||||
encoder.setUint32(lastLenPos, currentLength)
|
||||
}
|
||||
encoder.writeUint32(laterDSLenPus, numberOfUsers)
|
||||
encoder.setUint32(laterDSLenPus, numberOfUsers)
|
||||
}
|
||||
|
||||
export function readDeleteSet (y, decoder) {
|
||||
|
@ -45,7 +45,7 @@ export function integrateRemoteStructs (decoder, encoder, y) {
|
||||
let reference = decoder.readVarUint()
|
||||
let Constr = getStruct(reference)
|
||||
let struct = new Constr()
|
||||
let missing = struct._fromBinary(decoder)
|
||||
let missing = struct._fromBinary(y, decoder)
|
||||
if (missing.length === 0) {
|
||||
while (struct != null) {
|
||||
_integrateRemoteStructHelper(y, struct)
|
||||
|
@ -10,15 +10,14 @@ export function readStateSet (decoder) {
|
||||
return ss
|
||||
}
|
||||
|
||||
export function writeStateSet (encoder) {
|
||||
export function writeStateSet (y, encoder) {
|
||||
let lenPosition = encoder.pos
|
||||
let len = 0
|
||||
encoder.writeUint32(0)
|
||||
this.ss.iterate(null, null, function (n) {
|
||||
encoder.writeVarUint(n.id[0])
|
||||
encoder.writeVarUint(n.clock)
|
||||
for (let [user, clock] of y.ss.state) {
|
||||
encoder.writeVarUint(user)
|
||||
encoder.writeVarUint(clock)
|
||||
len++
|
||||
})
|
||||
}
|
||||
encoder.setUint32(lenPosition, len)
|
||||
return len === 0
|
||||
}
|
||||
|
@ -1,4 +1,7 @@
|
||||
import BinaryEncoder from '../Binary/Encoder.js'
|
||||
import { readStateSet, writeStateSet } from './stateSet.js'
|
||||
import { writeDeleteSet } from './deleteSet.js'
|
||||
import ID from '../Util/ID.js'
|
||||
|
||||
export function stringifySyncStep1 (decoder, strBuilder) {
|
||||
let auth = decoder.readVarString()
|
||||
@ -17,14 +20,22 @@ export function stringifySyncStep1 (decoder, strBuilder) {
|
||||
}
|
||||
}
|
||||
|
||||
export function sendSyncStep1 (y, syncUser) {
|
||||
export function sendSyncStep1 (connector, syncUser) {
|
||||
let encoder = new BinaryEncoder()
|
||||
encoder.writeVarString(y.room)
|
||||
encoder.writeVarString(connector.y.room)
|
||||
encoder.writeVarString('sync step 1')
|
||||
encoder.writeVarString(y.connector.authInfo || '')
|
||||
encoder.writeVarUint(y.connector.protocolVersion)
|
||||
y.ss.writeStateSet(encoder)
|
||||
y.connector.send(syncUser, encoder.createBuffer())
|
||||
encoder.writeVarString(connector.authInfo || '')
|
||||
encoder.writeVarUint(connector.protocolVersion)
|
||||
writeStateSet(connector.y, encoder)
|
||||
connector.send(syncUser, encoder.createBuffer())
|
||||
}
|
||||
|
||||
export default function writeStructs (encoder, decoder, y, ss) {
|
||||
for (let [user, clock] of ss) {
|
||||
y.os.iterate(new ID(user, clock), null, function (struct) {
|
||||
struct._toBinary(encoder)
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export function readSyncStep1 (decoder, encoder, y, senderConn, sender) {
|
||||
@ -32,22 +43,20 @@ export function readSyncStep1 (decoder, encoder, y, senderConn, sender) {
|
||||
// check protocol version
|
||||
if (protocolVersion !== y.connector.protocolVersion) {
|
||||
console.warn(
|
||||
`You tried to sync with a yjs instance that has a different protocol version
|
||||
`You tried to sync with a Yjs instance that has a different protocol version
|
||||
(You: ${protocolVersion}, Client: ${protocolVersion}).
|
||||
The sync was stopped. You need to upgrade your dependencies (especially Yjs & the Connector)!
|
||||
`)
|
||||
y.destroy()
|
||||
}
|
||||
|
||||
// send sync step 2
|
||||
// write sync step 2
|
||||
encoder.writeVarString('sync step 2')
|
||||
encoder.writeVarString(y.connector.authInfo || '')
|
||||
writeDeleteSet(encoder)
|
||||
// reads ss and writes os
|
||||
writeOperations(encoder, decoder)
|
||||
writeDeleteSet(y, encoder)
|
||||
const ss = readStateSet(decoder)
|
||||
writeStructs(encoder, decoder, y, ss)
|
||||
y.connector.send(senderConn.uid, encoder.createBuffer())
|
||||
senderConn.receivedSyncStep2 = true
|
||||
if (y.connector.role === 'slave') {
|
||||
sendSyncStep1(y, sender)
|
||||
sendSyncStep1(y.connector, sender)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
import { integrateRemoteStructs } from './integrateRemoteStructs.js'
|
||||
import { stringifyUpdate } from './update.js'
|
||||
import ID from '../Util/ID.js'
|
||||
import { readDeleteSet } from './deleteSet.js'
|
||||
|
||||
export function stringifySyncStep2 (decoder, strBuilder) {
|
||||
strBuilder.push(' - auth: ' + decoder.readVarString() + '\n')
|
||||
@ -22,27 +22,8 @@ export function stringifySyncStep2 (decoder, strBuilder) {
|
||||
}
|
||||
}
|
||||
|
||||
export function writeSyncStep2 () {
|
||||
// TODO
|
||||
}
|
||||
|
||||
export default function writeStructs (encoder, decoder, y, ss) {
|
||||
let lenPos = encoder.pos
|
||||
let len = 0
|
||||
encoder.writeUint32(0)
|
||||
for (let [user, clock] of ss) {
|
||||
y.os.iterate(new ID(user, clock), null, function (struct) {
|
||||
struct._toBinary(y, encoder)
|
||||
len++
|
||||
})
|
||||
}
|
||||
encoder.setUint32(lenPos, len)
|
||||
}
|
||||
|
||||
export function readSyncStep2 (decoder, encoder, y, senderConn, sender) {
|
||||
// apply operations first
|
||||
applyDeleteSet(decoder)
|
||||
readDeleteSet(y, decoder)
|
||||
integrateRemoteStructs(decoder, encoder, y)
|
||||
// then apply ds
|
||||
y.connector._setSyncedWith(sender)
|
||||
}
|
||||
|
@ -35,7 +35,6 @@ export default function extendPersistence (Y) {
|
||||
}
|
||||
if (this.saveOperationsBuffer.length === 0) {
|
||||
this.saveOperationsBuffer = ops
|
||||
this.y.db.whenTransactionsFinished().then(saveOperations)
|
||||
} else {
|
||||
this.saveOperationsBuffer = this.saveOperationsBuffer.concat(ops)
|
||||
}
|
||||
|
@ -17,6 +17,18 @@ export default class DeleteStore extends Tree {
|
||||
var n = this.ds.findWithUpperBound(id)
|
||||
return n != null && n.id[0] === id[0] && id[1] < n.id[1] + n.len
|
||||
}
|
||||
applyMissingDeletesOnStruct (struct) {
|
||||
const strID = struct._id
|
||||
// find most right delete
|
||||
let n = this.findWithUpperBound(new ID(strID.user, strID.clock + struct.length - 1))
|
||||
if (n === null || n.id.user !== strID.user || n.id.clock + n.length <= strID.clock) {
|
||||
// struct is not deleted
|
||||
return null
|
||||
}
|
||||
// TODO:
|
||||
// * iterate to the right and apply new Delete's
|
||||
throw new Error('Not implemented!')
|
||||
}
|
||||
/*
|
||||
* Mark an operation as deleted. returns the deleted node
|
||||
*/
|
||||
|
@ -3,83 +3,61 @@ import RootID from '../Util/ID.js'
|
||||
import { getStruct } from '../Util/structReferences.js'
|
||||
|
||||
export default class OperationStore extends Tree {
|
||||
constructor (y) {
|
||||
super()
|
||||
this.y = y
|
||||
}
|
||||
get (id) {
|
||||
let struct = this.find(id)
|
||||
if (struct === null && id instanceof RootID) {
|
||||
let Constr = getStruct(id.type)
|
||||
struct = new Constr()
|
||||
struct._id = id
|
||||
struct._parent = this.y
|
||||
this.put(struct)
|
||||
}
|
||||
return struct
|
||||
}
|
||||
// Use getItem for structs with _length > 1
|
||||
getItem (id) {
|
||||
var item = this.findWithUpperBound(id)
|
||||
if (item == null) {
|
||||
if (item === null) {
|
||||
return null
|
||||
}
|
||||
var len = item.content != null ? item.content.length : 1 // in case of opContent
|
||||
if (id[0] === item.id[0] && id[1] < item.id[1] + len) {
|
||||
const itemID = item._id
|
||||
if (id.user === itemID.user && id.clock < itemID.clock + item._length) {
|
||||
return item
|
||||
} else {
|
||||
return null
|
||||
}
|
||||
|
||||
}
|
||||
// Return an insertion such that id is the first element of content
|
||||
// This function manipulates an operation, if necessary
|
||||
getInsertionCleanStart (id) {
|
||||
var ins = this.getInsertion(id)
|
||||
if (ins != null) {
|
||||
if (ins.id[1] === id[1]) {
|
||||
return ins
|
||||
} else {
|
||||
var left = Y.utils.copyObject(ins)
|
||||
ins.content = left.content.splice(id[1] - ins.id[1])
|
||||
ins.id = id
|
||||
var leftLid = Y.utils.getLastId(left)
|
||||
ins.origin = leftLid
|
||||
left.originOf = [ins.id]
|
||||
left.right = ins.id
|
||||
ins.left = leftLid
|
||||
// debugger // check
|
||||
this.setOperation(left)
|
||||
this.setOperation(ins)
|
||||
if (left.gc) {
|
||||
this.store.queueGarbageCollector(ins.id)
|
||||
}
|
||||
return ins
|
||||
}
|
||||
// This function manipulates an item, if necessary
|
||||
getItemCleanStart (id) {
|
||||
var ins = this.getItem(id)
|
||||
if (ins === null || ins._length === 1) {
|
||||
return ins
|
||||
}
|
||||
const insID = ins._id
|
||||
if (insID.clock === id.clock) {
|
||||
return ins
|
||||
} else {
|
||||
return null
|
||||
return ins._splitAt(this.y, id.clock - insID.clock)
|
||||
}
|
||||
}
|
||||
// Return an insertion such that id is the last element of content
|
||||
// This function manipulates an operation, if necessary
|
||||
getInsertionCleanEnd (id) {
|
||||
var ins = this.getInsertion(id)
|
||||
if (ins != null) {
|
||||
if (ins.content == null || (ins.id[1] + ins.content.length - 1 === id[1])) {
|
||||
return ins
|
||||
} else {
|
||||
var right = Y.utils.copyObject(ins)
|
||||
right.content = ins.content.splice(id[1] - ins.id[1] + 1) // cut off remainder
|
||||
right.id = [id[0], id[1] + 1]
|
||||
var insLid = Y.utils.getLastId(ins)
|
||||
right.origin = insLid
|
||||
ins.originOf = [right.id]
|
||||
ins.right = right.id
|
||||
right.left = insLid
|
||||
// debugger // check
|
||||
this.setOperation(right)
|
||||
this.setOperation(ins)
|
||||
if (ins.gc) {
|
||||
this.store.queueGarbageCollector(right.id)
|
||||
}
|
||||
return ins
|
||||
}
|
||||
getItemCleanEnd (id) {
|
||||
var ins = this.getItem(id)
|
||||
if (ins === null || ins._length === 1) {
|
||||
return ins
|
||||
}
|
||||
const insID = ins._id
|
||||
if (insID.clock + ins._length - 1 === id.clock) {
|
||||
return ins
|
||||
} else {
|
||||
return null
|
||||
ins._splitAt(this.y, id.clock - insID.clock + 1)
|
||||
return ins
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,26 +1,44 @@
|
||||
import StructManager from '../Util/StructManager.js'
|
||||
import { getReference } from '../Util/structReferences.js'
|
||||
|
||||
export function deleteItemRange (y, user, clock, range) {
|
||||
let items = y.os.getItems(this._target, this._length)
|
||||
for (let i = items.length - 1; i >= 0; i--) {
|
||||
items[i]._delete(y, false)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete is not a real struct. It will not be saved in OS
|
||||
*/
|
||||
export default class Delete {
|
||||
constructor () {
|
||||
this._target = null
|
||||
this._targetID = null
|
||||
this._length = null
|
||||
}
|
||||
_fromBinary (y, decoder) {
|
||||
this._targetID = decoder.readOpID()
|
||||
this._targetID = decoder.readID()
|
||||
this._length = decoder.readVarUint()
|
||||
}
|
||||
_toBinary (y, encoder) {
|
||||
encoder.writeUint8(StructManager.getReference(this.constructor))
|
||||
encoder.writeOpID(this._targetID)
|
||||
_toBinary (encoder) {
|
||||
encoder.writeUint8(getReference(this.constructor))
|
||||
encoder.writeID(this._targetID)
|
||||
encoder.writeVarUint(this._length)
|
||||
}
|
||||
_integrate (y) {
|
||||
let items = y.os.getItems(this._target, this._length)
|
||||
for (let i = items.length - 1; i >= 0; i--) {
|
||||
items[i]._delete()
|
||||
/**
|
||||
* - If created remotely (a remote user deleted something),
|
||||
* this Delete is applied to all structs in id-range.
|
||||
* - If created lokally (e.g. when y-array deletes a range of elements),
|
||||
* this struct is broadcasted only (it is already executed)
|
||||
*/
|
||||
_integrate (y, locallyCreated = false) {
|
||||
if (!locallyCreated) {
|
||||
// from remote
|
||||
const id = this._targetID
|
||||
deleteItemRange(y, id.user, id.clock, this._length)
|
||||
} else {
|
||||
// from local
|
||||
y.connector.broadcastStruct(this)
|
||||
}
|
||||
// TODO: only broadcast if created by local user or if y.connector._forwardAppliedStructs..
|
||||
y.connector.broadcastStruct(this)
|
||||
if (y.persistence !== null) {
|
||||
y.persistence.saveOperations(this)
|
||||
}
|
||||
|
@ -1,3 +1,27 @@
|
||||
import { getReference } from '../Util/structReferences.js'
|
||||
import ID from '../Util/ID.js'
|
||||
import { RootFakeUserID } from '../Util/RootID.js'
|
||||
import Delete from './Delete.js'
|
||||
|
||||
/**
|
||||
* Helper utility to split an Item (see _splitAt)
|
||||
* - copy all properties from a to b
|
||||
* - connect a to b
|
||||
* - assigns the correct _id
|
||||
* - save b to os
|
||||
*/
|
||||
export function splitHelper (y, a, b, diff) {
|
||||
const aID = a._id
|
||||
b._id = new ID(aID.user, aID.clock + diff)
|
||||
b._origin = a
|
||||
b._left = a
|
||||
a._right = b
|
||||
a._right_origin = b
|
||||
b._parent = a._parent
|
||||
b._parentSub = a._parentSub
|
||||
b._deleted = a._deleted
|
||||
y.os.put(b)
|
||||
}
|
||||
|
||||
export default class Item {
|
||||
constructor () {
|
||||
@ -13,22 +37,30 @@ export default class Item {
|
||||
get _length () {
|
||||
return 1
|
||||
}
|
||||
_getDistanceToOrigin () {
|
||||
if (this.left == null) {
|
||||
return 0
|
||||
} else {
|
||||
var d = 0
|
||||
var o = this.left
|
||||
while (o !== null && !this.origin.equals(o.id)) {
|
||||
d++
|
||||
o = o.left
|
||||
}
|
||||
return d
|
||||
/**
|
||||
* Splits this struct so that another struct can be inserted in-between.
|
||||
* This must be overwritten if _length > 1
|
||||
* Returns right part after split
|
||||
* - diff === 0 => this
|
||||
* - diff === length => this._right
|
||||
* - otherwise => split _content and return right part of split
|
||||
* (see ItemJSON/ItemString for implementation)
|
||||
*/
|
||||
_splitAt (y, diff) {
|
||||
if (diff === 0) {
|
||||
return this
|
||||
}
|
||||
return this._right
|
||||
}
|
||||
_delete (y) {
|
||||
_delete (y, createDelete = true) {
|
||||
this._deleted = true
|
||||
y.ds.markDeleted(this._id, this._length)
|
||||
if (createDelete) {
|
||||
let del = new Delete()
|
||||
del._targetID = this._id
|
||||
del._length = this._length
|
||||
del._integrate(y, true)
|
||||
}
|
||||
}
|
||||
/*
|
||||
* - Integrate the struct so that other types/structs can see it
|
||||
@ -36,8 +68,12 @@ export default class Item {
|
||||
* - Check if this is struct deleted
|
||||
*/
|
||||
_integrate (y) {
|
||||
if (this._id === null) {
|
||||
const selfID = this._id
|
||||
if (selfID === null) {
|
||||
this._id = y.ss.getNextID(this._length)
|
||||
} else if (selfID.clock < y.ss.getState(selfID.user)) {
|
||||
// already applied..
|
||||
return []
|
||||
}
|
||||
/*
|
||||
# $this has to find a unique position between origin and the next known character
|
||||
@ -71,86 +107,145 @@ export default class Item {
|
||||
// Note that conflictingItems is a subset of itemsBeforeOrigin
|
||||
while (o !== null && o !== this._right) {
|
||||
itemsBeforeOrigin.add(o)
|
||||
if (this.origin === o.origin) {
|
||||
if (this._origin === o._origin) {
|
||||
// case 1
|
||||
if (o._id.user < this._id.user) {
|
||||
this.left = o
|
||||
this._left = o
|
||||
conflictingItems = new Set()
|
||||
}
|
||||
} else if (itemsBeforeOrigin.has(o)) {
|
||||
// case 2
|
||||
if (conflictingItems.has(o)) {
|
||||
this.left = o
|
||||
this._left = o
|
||||
conflictingItems = new Set()
|
||||
}
|
||||
} else {
|
||||
break
|
||||
}
|
||||
o = o.right
|
||||
o = o._right
|
||||
}
|
||||
y.os.set(this)
|
||||
y.ds.checkIfDeleted(this)
|
||||
if (y.connector._forwardAppliedStructs || this._id.user === y.userID) {
|
||||
y.connector.broadcastStruct(this)
|
||||
if (this._left === null) {
|
||||
if (this._parentSub !== null) {
|
||||
this._parent._map.set(this._parentSub, this)
|
||||
} else {
|
||||
this._parent._start = this
|
||||
}
|
||||
}
|
||||
if (y.persistence !== null) {
|
||||
y.persistence.saveOperations(this)
|
||||
y.os.put(this)
|
||||
if (this._id.user !== RootFakeUserID) {
|
||||
if (y.connector._forwardAppliedStructs || this._id.user === y.userID) {
|
||||
y.connector.broadcastStruct(this)
|
||||
}
|
||||
if (y.persistence !== null) {
|
||||
y.persistence.saveOperations(this)
|
||||
}
|
||||
y.ds.applyMissingDeletesOnStruct(this)
|
||||
}
|
||||
}
|
||||
_toBinary (y, encoder) {
|
||||
encoder.writeUint8(StructManager.getReference(this.constructor))
|
||||
encoder.writeOpID(this._id)
|
||||
encoder.writeOpID(this._parent._id)
|
||||
encoder.writeVarString(this.parentSub === null ? '' : JSON.stringify(this.parentSub))
|
||||
encoder.writeOpID(this._left === null ? null : this._left._id)
|
||||
encoder.writeOpID(this._right_origin === null ? null : this._right_origin._id)
|
||||
encoder.writeOpID(this._origin === null ? null : this._origin._id)
|
||||
_toBinary (encoder) {
|
||||
encoder.writeUint8(getReference(this.constructor))
|
||||
let info = 0
|
||||
if (this._origin !== null) {
|
||||
info += 0b1 // origin is defined
|
||||
}
|
||||
if (this._left !== this._origin) {
|
||||
info += 0b10 // do not copy origin to left
|
||||
}
|
||||
if (this._right_origin !== null) {
|
||||
info += 0b100
|
||||
}
|
||||
if (this._parentSub !== null) {
|
||||
info += 0b1000
|
||||
}
|
||||
encoder.writeUint8(info)
|
||||
encoder.writeID(this._id)
|
||||
if (info & 0b1) {
|
||||
encoder.writeID(this._origin._id)
|
||||
}
|
||||
if (info & 0b10) {
|
||||
encoder.writeID(this._left._id)
|
||||
}
|
||||
if (info & 0b100) {
|
||||
encoder.writeID(this._right_origin._id)
|
||||
}
|
||||
if (~info & 0b101) {
|
||||
// neither origin nor right is defined
|
||||
encoder.writeID(this._parent._id)
|
||||
}
|
||||
if (info & 0b1000) {
|
||||
encoder.writeVarString(JSON.stringify(this._parentSub))
|
||||
}
|
||||
}
|
||||
_fromBinary (y, decoder) {
|
||||
let missing = []
|
||||
this._id = decoder.readOpID()
|
||||
let parent = decoder.readOpID()
|
||||
let parentSub = decoder.readVarString()
|
||||
if (parentSub.length > 0) {
|
||||
this._parentSub = JSON.parse(parentSub)
|
||||
}
|
||||
let left = decoder.readOpID()
|
||||
let right = decoder.readOpId()
|
||||
let origin = decoder.readOpID()
|
||||
if (parent !== null && this._parent === null) {
|
||||
let _parent = y.os.get(parent)
|
||||
if (_parent === null) {
|
||||
missing.push(parent)
|
||||
} else {
|
||||
this._parent = _parent
|
||||
const info = decoder.readUint8()
|
||||
this._id = decoder.readID()
|
||||
// read origin
|
||||
if (info & 0b1) {
|
||||
// origin != null
|
||||
const originID = decoder.readID()
|
||||
if (this._origin === null) {
|
||||
const origin = y.os.getItemCleanEnd(originID)
|
||||
if (origin === null) {
|
||||
missing.push(originID)
|
||||
} else {
|
||||
this._origin = origin
|
||||
}
|
||||
}
|
||||
}
|
||||
if (origin !== null && this._origin === null) {
|
||||
let _origin = y.os.getCleanStart(origin)
|
||||
if (_origin === null) {
|
||||
missing.push(origin)
|
||||
} else {
|
||||
this._origin = _origin
|
||||
// read left
|
||||
if (info & 0b10) {
|
||||
// left !== origin
|
||||
const leftID = decoder.readID()
|
||||
if (this._left === null) {
|
||||
const left = y.os.getItemCleanEnd(leftID)
|
||||
if (left === null) {
|
||||
// use origin instead
|
||||
this._left = this._origin
|
||||
} else {
|
||||
this._left = left
|
||||
}
|
||||
}
|
||||
} else {
|
||||
this._left = this._origin
|
||||
}
|
||||
// read right
|
||||
if (info & 0b100) {
|
||||
// right != null
|
||||
const rightID = decoder.readID()
|
||||
if (this._right_origin === null) {
|
||||
const right = y.os.getCleanStart(rightID)
|
||||
if (right === null) {
|
||||
missing.push(right)
|
||||
} else {
|
||||
this._right = right
|
||||
this._right_origin = right
|
||||
}
|
||||
}
|
||||
}
|
||||
if (left !== null && this._left === null) {
|
||||
let _left = y.os.getCleanEnd(left)
|
||||
if (_left === null) {
|
||||
// use origin instead
|
||||
this._left = this._origin
|
||||
} else {
|
||||
this._left = _left
|
||||
// read parent
|
||||
if (~info & 0b101) {
|
||||
// neither origin nor right is defined
|
||||
const parentID = decoder.readID()
|
||||
if (this._parent === null) {
|
||||
const parent = y.os.get(parentID)
|
||||
if (parent === null) {
|
||||
missing.push(parent)
|
||||
} else {
|
||||
this._parent = parent
|
||||
}
|
||||
}
|
||||
} else if (this._parent === null) {
|
||||
if (this._origin !== null) {
|
||||
this._parent = this._origin._parent
|
||||
} else if (this._right_origin !== null) {
|
||||
this._parent = this._origin._parent
|
||||
}
|
||||
}
|
||||
if (right !== null && this._right_origin === null) {
|
||||
let _right = y.os.getCleanStart(right)
|
||||
if (_right === null) {
|
||||
missing.push(right)
|
||||
} else {
|
||||
this._right = _right
|
||||
this._right_origin = _right
|
||||
}
|
||||
if (info & 0b1000) {
|
||||
this._parentSub = decoder.readVarString()
|
||||
}
|
||||
return missing
|
||||
}
|
||||
_logString () {
|
||||
return `left: ${this._left}, origin: ${this._origin}, right: ${this._right}, parent: ${this._parent}, parentSub: ${this._parentSub}`
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Item from './Item.js'
|
||||
import { splitHelper, default as Item } from './Item.js'
|
||||
|
||||
export default class ItemJSON extends Item {
|
||||
constructor () {
|
||||
@ -17,8 +17,8 @@ export default class ItemJSON extends Item {
|
||||
}
|
||||
return missing
|
||||
}
|
||||
_toBinary (y, encoder) {
|
||||
super._toBinary(y, encoder)
|
||||
_toBinary (encoder) {
|
||||
super._toBinary(encoder)
|
||||
let len = this._content.length
|
||||
encoder.writeVarUint(len)
|
||||
for (let i = 0; i < len; i++) {
|
||||
@ -29,4 +29,15 @@ export default class ItemJSON extends Item {
|
||||
let s = super._logString()
|
||||
return 'ItemJSON: ' + s
|
||||
}
|
||||
_splitAt (y, diff) {
|
||||
if (diff === 0) {
|
||||
return this
|
||||
} else if (diff >= this._length) {
|
||||
return this._right
|
||||
}
|
||||
let item = new ItemJSON()
|
||||
item._content = this._content.splice(diff)
|
||||
splitHelper(y, this, item, diff)
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,4 @@
|
||||
import Item from './Item.js'
|
||||
import { splitHelper, default as Item } from './Item.js'
|
||||
|
||||
export default class ItemString extends Item {
|
||||
constructor () {
|
||||
@ -13,12 +13,24 @@ export default class ItemString extends Item {
|
||||
this._content = decoder.readVarString()
|
||||
return missing
|
||||
}
|
||||
_toBinary (y, encoder) {
|
||||
super._toBinary(y, encoder)
|
||||
_toBinary (encoder) {
|
||||
super._toBinary(encoder)
|
||||
encoder.writeVarString(this._content)
|
||||
}
|
||||
_logString () {
|
||||
let s = super._logString()
|
||||
return 'ItemString: ' + s
|
||||
}
|
||||
_splitAt (y, diff) {
|
||||
if (diff === 0) {
|
||||
return this
|
||||
} else if (diff >= this._length) {
|
||||
return this._right
|
||||
}
|
||||
let item = new ItemString()
|
||||
item._content = this._content.slice(diff)
|
||||
this._content = this._content.slice(0, diff)
|
||||
splitHelper(y, this, item, diff)
|
||||
return item
|
||||
}
|
||||
}
|
||||
|
@ -5,6 +5,11 @@ export default class Type extends Item {
|
||||
super()
|
||||
this._map = new Map()
|
||||
this._start = null
|
||||
this._y = null
|
||||
}
|
||||
_integrate (y) {
|
||||
super._integrate(y)
|
||||
this._y = y
|
||||
}
|
||||
_delete (y) {
|
||||
super._delete(y)
|
||||
|
@ -2,21 +2,50 @@ import Type from '../Struct/Type.js'
|
||||
import ItemJSON from '../Struct/ItemJSON.js'
|
||||
|
||||
export default class YArray extends Type {
|
||||
toJSON () {
|
||||
return this.map(c => {
|
||||
if (c instanceof Type) {
|
||||
if (c.toJSON !== null) {
|
||||
return c.toJSON()
|
||||
} else {
|
||||
return c.toString()
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
map (f) {
|
||||
const res = []
|
||||
this.forEach((c, i) => {
|
||||
res.push(f(c, i, this))
|
||||
})
|
||||
return res
|
||||
}
|
||||
forEach (f) {
|
||||
let pos = 0
|
||||
let n = this._start
|
||||
while (n !== null) {
|
||||
let content = n._getContent()
|
||||
for (let i = 0; i < content.length; i++) {
|
||||
pos++
|
||||
let c = content[i]
|
||||
if (!c._deleted) {
|
||||
if (!n._deleted) {
|
||||
const content = n._content
|
||||
const contentLen = content.length
|
||||
for (let i = 0; i < contentLen; i++) {
|
||||
pos++
|
||||
f(content[i], pos, this)
|
||||
}
|
||||
}
|
||||
n = n._right
|
||||
}
|
||||
}
|
||||
get length () {
|
||||
let length = 0
|
||||
let n = this._start
|
||||
while (n !== null) {
|
||||
if (!n._deleted) {
|
||||
length += n._length
|
||||
}
|
||||
n = n._next
|
||||
}
|
||||
return length
|
||||
}
|
||||
[Symbol.iterator] () {
|
||||
return {
|
||||
next: function () {
|
||||
@ -41,15 +70,37 @@ export default class YArray extends Type {
|
||||
_count: 0
|
||||
}
|
||||
}
|
||||
delete (pos, length = 1) {
|
||||
let item = this._start
|
||||
let count = 0
|
||||
while (item !== null && length > 0) {
|
||||
if (count < pos && pos < count + item._length) {
|
||||
const diffDel = pos - count
|
||||
item = item
|
||||
._splitAt(this._y, diffDel)
|
||||
._splitAt(this._y, length)
|
||||
length -= item._length
|
||||
item._delete(this._y)
|
||||
}
|
||||
if (!item._deleted) {
|
||||
count += item._length
|
||||
}
|
||||
item = item._right
|
||||
}
|
||||
if (length > 0) {
|
||||
throw new Error('Delete exceeds the range of the YArray')
|
||||
}
|
||||
}
|
||||
insert (pos, content) {
|
||||
let left = this._start
|
||||
let right
|
||||
let right = null
|
||||
let count = 0
|
||||
while (left !== null && !left._deleted) {
|
||||
if (pos < count + left._content.length) {
|
||||
[left, right] = left._splitAt(pos - count)
|
||||
while (left !== null) {
|
||||
if (count <= pos && pos < count + left._content.length) {
|
||||
right = left._splitAt(this.y, pos - count)
|
||||
break
|
||||
}
|
||||
count += left._length
|
||||
left = left.right
|
||||
}
|
||||
if (pos > count) {
|
||||
@ -79,6 +130,9 @@ export default class YArray extends Type {
|
||||
prevJsonIns._content.push(c)
|
||||
}
|
||||
}
|
||||
if (prevJsonIns !== null) {
|
||||
prevJsonIns._integrate(this._y)
|
||||
}
|
||||
}
|
||||
_logString () {
|
||||
let s = super._logString()
|
||||
|
@ -3,6 +3,25 @@ import Item from '../Struct/Item.js'
|
||||
import ItemJSON from '../Struct/ItemJSON.js'
|
||||
|
||||
export default class YMap extends Type {
|
||||
toJSON () {
|
||||
const map = {}
|
||||
for (let [key, item] of this._map) {
|
||||
if (!item._deleted) {
|
||||
let res
|
||||
if (item instanceof Type) {
|
||||
if (item.toJSON !== undefined) {
|
||||
res = item.toJSON()
|
||||
} else {
|
||||
res = item.toString()
|
||||
}
|
||||
} else {
|
||||
res = item._content[0]
|
||||
}
|
||||
map[key] = res
|
||||
}
|
||||
}
|
||||
return map
|
||||
}
|
||||
set (key, value) {
|
||||
let old = this._map.get(key)
|
||||
let v
|
||||
|
@ -1,4 +1,10 @@
|
||||
import YArray from './YArray.js'
|
||||
|
||||
export default class YXml extends YArray {
|
||||
setDomFilter () {
|
||||
// TODO
|
||||
}
|
||||
toString () {
|
||||
return '<nodeName></nodeName>'
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,4 @@
|
||||
|
||||
import { getReference } from './structReferences.js'
|
||||
|
||||
export default class ID {
|
||||
constructor (user, clock) {
|
||||
this.user = user
|
||||
|
@ -1,9 +1,12 @@
|
||||
import { getReference } from './structReferences.js'
|
||||
|
||||
export const RootFakeUserID = 0xFFFFFF
|
||||
|
||||
export default class RootID {
|
||||
constructor (name, typeConstructor) {
|
||||
this.user = -1
|
||||
this.user = RootFakeUserID
|
||||
this.name = name
|
||||
this.type = StructManager.getReference(typeConstructor)
|
||||
this.type = getReference(typeConstructor)
|
||||
}
|
||||
equals (id) {
|
||||
return id !== null && id.user === this.user && id.name === this.name && id.type === this.type
|
||||
|
@ -140,11 +140,11 @@ export default class Tree {
|
||||
return null
|
||||
} else {
|
||||
while (true) {
|
||||
if (from === null || (from.lessThan(o.val.id) && o.left !== null)) {
|
||||
if (from === null || (from.lessThan(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 && o.val.id.lessThan(from)) {
|
||||
} else if (from !== null && o.val._id.lessThan(from)) {
|
||||
// o is not within the bound, maybe one of the right elements is..
|
||||
if (o.right !== null) {
|
||||
o = o.right
|
||||
@ -168,11 +168,11 @@ export default class Tree {
|
||||
return null
|
||||
} else {
|
||||
while (true) {
|
||||
if ((to === null || o.val.id.lessThan(to)) && o.right !== null) {
|
||||
if ((to === null || o.val._id.lessThan(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 && to.lessThan(o.val.id)) {
|
||||
} else if (to !== null && to.lessThan(o.val._id)) {
|
||||
// o is not within the bound, maybe one of the left elements is..
|
||||
if (o.left !== null) {
|
||||
o = o.left
|
||||
@ -213,8 +213,8 @@ export default class Tree {
|
||||
o !== null &&
|
||||
(
|
||||
to === null || // eslint-disable-line no-unmodified-loop-condition
|
||||
o.val.id.lessThan(to) ||
|
||||
o.val.id.equals(to)
|
||||
o.val._id.lessThan(to) ||
|
||||
o.val._id.equals(to)
|
||||
)
|
||||
) {
|
||||
f(o.val)
|
||||
@ -238,9 +238,9 @@ export default class Tree {
|
||||
if (o === null) {
|
||||
return null
|
||||
}
|
||||
if (id.lessThan(o.val.id)) {
|
||||
if (id.lessThan(o.val._id)) {
|
||||
o = o.left
|
||||
} else if (o.val.id.lessThan(id)) {
|
||||
} else if (o.val._id.lessThan(id)) {
|
||||
o = o.right
|
||||
} else {
|
||||
return o
|
||||
@ -393,14 +393,14 @@ export default class Tree {
|
||||
if (this.root !== null) {
|
||||
var p = this.root // p abbrev. parent
|
||||
while (true) {
|
||||
if (node.val.id.lessThan(p.val.id)) {
|
||||
if (node.val._id.lessThan(p.val._id)) {
|
||||
if (p.left === null) {
|
||||
p.left = node
|
||||
break
|
||||
} else {
|
||||
p = p.left
|
||||
}
|
||||
} else if (p.val.id.lessThan(node.val.id)) {
|
||||
} else if (p.val._id.lessThan(node.val._id)) {
|
||||
if (p.right === null) {
|
||||
p.right = node
|
||||
break
|
||||
|
23
src/Y.js
23
src/Y.js
@ -3,6 +3,7 @@ import OperationStore from './Store/OperationStore.js'
|
||||
import StateStore from './Store/StateStore.js'
|
||||
import { generateUserID } from './Util/generateUserID.js'
|
||||
import RootID from './Util/RootID.js'
|
||||
import NamedEventHandler from './Util/NamedEventHandler.js'
|
||||
|
||||
import { messageToString, messageToRoomname } from './MessageHandler/messageToString.js'
|
||||
|
||||
@ -15,8 +16,10 @@ import YXml from './Type/YXml.js'
|
||||
|
||||
import debug from 'debug'
|
||||
|
||||
export default class Y {
|
||||
export default class Y extends NamedEventHandler {
|
||||
constructor (opts) {
|
||||
super()
|
||||
this._opts = opts
|
||||
this.userID = generateUserID()
|
||||
this.ds = new DeleteStore(this)
|
||||
this.os = new OperationStore(this)
|
||||
@ -30,10 +33,17 @@ export default class Y {
|
||||
}
|
||||
this.connected = true
|
||||
this._missingStructs = new Map()
|
||||
this._readyToIntegrate = new Map()
|
||||
this._readyToIntegrate = []
|
||||
}
|
||||
// fake _start for root properties (y.set('name', type))
|
||||
get _start () {
|
||||
return null
|
||||
}
|
||||
set _start (start) {
|
||||
return null
|
||||
}
|
||||
get room () {
|
||||
return this.connector.opts.room
|
||||
return this._opts.connector.room
|
||||
}
|
||||
get (name, TypeConstructor) {
|
||||
let id = new RootID(name, TypeConstructor)
|
||||
@ -41,6 +51,7 @@ export default class Y {
|
||||
if (type === null) {
|
||||
type = new TypeConstructor()
|
||||
type._id = id
|
||||
type._parent = this
|
||||
type._integrate(this)
|
||||
}
|
||||
return type
|
||||
@ -68,9 +79,6 @@ export default class Y {
|
||||
} else {
|
||||
this.connector.disconnect()
|
||||
}
|
||||
this.os.iterate(null, null, function (struct) {
|
||||
struct.destroy()
|
||||
})
|
||||
this.os = null
|
||||
this.ds = null
|
||||
this.ss = null
|
||||
@ -88,7 +96,8 @@ Y.extend = function extendYjs () {
|
||||
}
|
||||
}
|
||||
|
||||
Y.Connector = Connector
|
||||
// TODO: The following assignments should be moved to yjs-dist
|
||||
Y.AbstractConnector = Connector
|
||||
Y.Persisence = Persistence
|
||||
Y.Array = YArray
|
||||
Y.Map = YMap
|
||||
|
@ -55,15 +55,15 @@ function checkRootNodeIsBlack (t, tree) {
|
||||
|
||||
test('RedBlack Tree', async function redBlackTree (t) {
|
||||
let tree = new RedBlackTree()
|
||||
tree.put({id: new ID(8433, 0)})
|
||||
tree.put({id: new ID(12844, 0)})
|
||||
tree.put({id: new ID(1795, 0)})
|
||||
tree.put({id: new ID(30302, 0)})
|
||||
tree.put({id: new ID(64287)})
|
||||
tree.put({_id: new ID(8433, 0)})
|
||||
tree.put({_id: new ID(12844, 0)})
|
||||
tree.put({_id: new ID(1795, 0)})
|
||||
tree.put({_id: new ID(30302, 0)})
|
||||
tree.put({_id: new ID(64287)})
|
||||
tree.delete(new ID(8433, 0))
|
||||
tree.put({id: new ID(28996)})
|
||||
tree.put({_id: new ID(28996)})
|
||||
tree.delete(new ID(64287))
|
||||
tree.put({id: new ID(22721)})
|
||||
tree.put({_id: new ID(22721)})
|
||||
checkRootNodeIsBlack(t, tree)
|
||||
checkBlackHeightOfSubTreesAreEqual(t, tree)
|
||||
checkRedNodesDoNotHaveBlackChildren(t, tree)
|
||||
@ -83,7 +83,7 @@ test(`random tests (${numberOfRBTreeTests})`, async function random (t) {
|
||||
t.assert(false, 'tree and elements contain different results')
|
||||
}
|
||||
elements.push(obj)
|
||||
tree.put({id: obj})
|
||||
tree.put({_id: obj})
|
||||
}
|
||||
} else if (elements.length > 0) {
|
||||
// ~20% chance to delete an element
|
||||
@ -101,7 +101,7 @@ test(`random tests (${numberOfRBTreeTests})`, async function random (t) {
|
||||
let allNodesExist = true
|
||||
for (let id of elements) {
|
||||
let node = tree.find(id)
|
||||
if (!node.id.equals(id)) {
|
||||
if (!node._id.equals(id)) {
|
||||
allNodesExist = false
|
||||
}
|
||||
}
|
||||
@ -110,7 +110,7 @@ test(`random tests (${numberOfRBTreeTests})`, async function random (t) {
|
||||
let findAllNodesWithLowerBoundSerach = true
|
||||
for (let id of elements) {
|
||||
let node = tree.findWithLowerBound(id)
|
||||
if (!node.id.equals(id)) {
|
||||
if (!node._id.equals(id)) {
|
||||
findAllNodesWithLowerBoundSerach = false
|
||||
}
|
||||
}
|
||||
|
@ -11,11 +11,11 @@ test('basic spec', async function array0 (t) {
|
||||
|
||||
let throwInvalidPosition = false
|
||||
try {
|
||||
array0.delete(1, 0)
|
||||
array0.delete(1, 1)
|
||||
} catch (e) {
|
||||
throwInvalidPosition = true
|
||||
}
|
||||
t.assert(throwInvalidPosition, 'Throws when deleting zero elements with an invalid position')
|
||||
t.assert(throwInvalidPosition, 'Throws when deleting with an invalid position')
|
||||
|
||||
array0.insert(0, ['A'])
|
||||
array0.delete(1, 0)
|
||||
|
@ -87,7 +87,7 @@ export async function compareUsers (t, users) {
|
||||
|
||||
var userArrayValues = users.map(u => u.get('array', Y.Array).toJSON())
|
||||
var userMapValues = users.map(u => u.get('map', Y.Map).toJSON())
|
||||
var userXmlValues = users.map(u => u.get('xml', Y.Xml).getDom().toString())
|
||||
var userXmlValues = users.map(u => u.get('xml', Y.Xml).toString())
|
||||
|
||||
// disconnect all except user 0
|
||||
await Promise.all(users.slice(1).map(async u =>
|
||||
@ -107,28 +107,22 @@ export async function compareUsers (t, users) {
|
||||
u.connector.whenSynced(resolve)
|
||||
})
|
||||
))
|
||||
var data = users.forEach(u => {
|
||||
var data = users.map(u => {
|
||||
var data = {}
|
||||
let ops = []
|
||||
u.os.iterate(null, null, function (op) {
|
||||
if (!op._deleted) {
|
||||
ops.push({
|
||||
id: op._id,
|
||||
left: op._left,
|
||||
right: op._right,
|
||||
deleted: op._deleted
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
data.os = {}
|
||||
for (let i = 0; i < ops.length; i++) {
|
||||
let op = ops[i]
|
||||
op = Y.Struct[op.struct].encode(op)
|
||||
delete op.origin
|
||||
data.os[JSON.stringify(op.id)] = op
|
||||
}
|
||||
data.ds = getDeleteSet.apply(this)
|
||||
data.ss = getStateSet.apply(this)
|
||||
data.os = ops
|
||||
data.ds = getDeleteSet(u)
|
||||
data.ss = getStateSet(u)
|
||||
return data
|
||||
})
|
||||
for (var i = 0; i < data.length - 1; i++) {
|
||||
@ -141,7 +135,7 @@ export async function compareUsers (t, users) {
|
||||
t.compare(data[i].ss, data[i + 1].ss, 'ss')
|
||||
}, `Compare user${i} with user${i + 1}`)
|
||||
}
|
||||
users.map(u => u.close())
|
||||
users.map(u => u.destroy())
|
||||
}
|
||||
|
||||
export async function initArrays (t, opts) {
|
||||
@ -161,6 +155,7 @@ export async function initArrays (t, opts) {
|
||||
connector: connOpts
|
||||
})
|
||||
result.users.push(y)
|
||||
result['array' + i] = y.get('array', Y.Array)
|
||||
y.get('xml', Y.Xml).setDomFilter(function (d, attrs) {
|
||||
if (d.nodeName === 'HIDDEN') {
|
||||
return null
|
||||
@ -193,9 +188,6 @@ export async function flushAll (t, users) {
|
||||
// use flushAll method specified in Test Connector
|
||||
await users[0].connector.testRoom.flushAll(users)
|
||||
} else {
|
||||
// flush for any connector
|
||||
await Promise.all(users.map(u => { return u.db.whenTransactionsFinished() }))
|
||||
|
||||
var flushCounter = users[0].get('flushHelper', Y.Map).get('0') || 0
|
||||
flushCounter++
|
||||
await Promise.all(users.map(async (u, i) => {
|
||||
|
@ -8,24 +8,21 @@ export class TestRoom {
|
||||
constructor (roomname) {
|
||||
this.room = roomname
|
||||
this.users = new Map()
|
||||
this.nextUserId = 0
|
||||
}
|
||||
join (connector) {
|
||||
if (connector.userId == null) {
|
||||
connector.setUserId(this.nextUserId++)
|
||||
}
|
||||
this.users.forEach((user, uid) => {
|
||||
if (user.role === 'master' || connector.role === 'master') {
|
||||
this.users.get(uid).userJoined(connector.userId, connector.role)
|
||||
const userID = connector.y.userID
|
||||
this.users.set(userID, connector)
|
||||
for (let [uid, user] of this.users) {
|
||||
if (uid !== userID && (user.role === 'master' || connector.role === 'master')) {
|
||||
connector.userJoined(uid, this.users.get(uid).role)
|
||||
this.users.get(uid).userJoined(userID, connector.role)
|
||||
}
|
||||
})
|
||||
this.users.set(connector.userId, connector)
|
||||
}
|
||||
}
|
||||
leave (connector) {
|
||||
this.users.delete(connector.userId)
|
||||
this.users.delete(connector.y.userID)
|
||||
this.users.forEach(user => {
|
||||
user.userLeft(connector.userId)
|
||||
user.userLeft(connector.y.userID)
|
||||
})
|
||||
}
|
||||
send (sender, receiver, m) {
|
||||
@ -82,7 +79,7 @@ export default function extendTestConnector (Y) {
|
||||
return super.disconnect()
|
||||
}
|
||||
logBufferParsed () {
|
||||
console.log(' === Logging buffer of user ' + this.userId + ' === ')
|
||||
console.log(' === Logging buffer of user ' + this.y.userID + ' === ')
|
||||
for (let [user, conn] of this.connections) {
|
||||
console.log(` ${user}:`)
|
||||
for (let i = 0; i < conn.buffer.length; i++) {
|
||||
@ -96,11 +93,11 @@ export default function extendTestConnector (Y) {
|
||||
}
|
||||
send (uid, message) {
|
||||
super.send(uid, message)
|
||||
this.testRoom.send(this.userId, uid, message)
|
||||
this.testRoom.send(this.y.userID, uid, message)
|
||||
}
|
||||
broadcast (message) {
|
||||
super.broadcast(message)
|
||||
this.testRoom.broadcast(this.userId, message)
|
||||
this.testRoom.broadcast(this.y.userID, message)
|
||||
}
|
||||
async whenSynced (f) {
|
||||
var synced = false
|
||||
@ -119,7 +116,7 @@ export default function extendTestConnector (Y) {
|
||||
})
|
||||
}
|
||||
receiveMessage (sender, m) {
|
||||
if (this.userId !== sender && this.connections.has(sender)) {
|
||||
if (this.y.userID !== sender && this.connections.has(sender)) {
|
||||
var buffer = this.connections.get(sender).buffer
|
||||
if (buffer == null) {
|
||||
buffer = this.connections.get(sender).buffer = []
|
||||
@ -135,30 +132,30 @@ export default function extendTestConnector (Y) {
|
||||
}
|
||||
}
|
||||
async _flushAll (flushUsers) {
|
||||
if (flushUsers.some(u => u.connector.userId === this.userId)) {
|
||||
if (flushUsers.some(u => u.connector.y.userID === this.y.userID)) {
|
||||
// this one needs to sync with every other user
|
||||
flushUsers = Array.from(this.connections.keys()).map(uid => this.testRoom.users.get(uid).y)
|
||||
}
|
||||
var finished = []
|
||||
for (let i = 0; i < flushUsers.length; i++) {
|
||||
let userId = flushUsers[i].connector.userId
|
||||
if (userId !== this.userId && this.connections.has(userId)) {
|
||||
let buffer = this.connections.get(userId).buffer
|
||||
let userID = flushUsers[i].connector.y.userID
|
||||
if (userID !== this.y.userID && this.connections.has(userID)) {
|
||||
let buffer = this.connections.get(userID).buffer
|
||||
if (buffer != null) {
|
||||
var messages = buffer.splice(0)
|
||||
for (let j = 0; j < messages.length; j++) {
|
||||
let p = super.receiveMessage(userId, messages[j])
|
||||
let p = super.receiveMessage(userID, messages[j])
|
||||
finished.push(p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await Promise.all(finished)
|
||||
await this.y.db.whenTransactionsFinished()
|
||||
return finished.length > 0 ? 'flushing' : 'done'
|
||||
}
|
||||
}
|
||||
Y.extend('test', TestConnector)
|
||||
// TODO: this should be moved to a separate module (dont work on Y)
|
||||
Y.test = TestConnector
|
||||
}
|
||||
|
||||
if (typeof Y !== 'undefined') {
|
||||
|
Loading…
x
Reference in New Issue
Block a user