fixed merging and adapted writeStructs to write end of message

This commit is contained in:
Kevin Jahns 2019-04-07 12:47:04 +02:00
parent c635963747
commit 90b3fa9dd9
10 changed files with 85 additions and 81 deletions

View File

@ -168,6 +168,7 @@ export class AbstractItem extends AbstractStruct {
} else {
o = parent._start
}
// TODO: use something like DeleteSet here (a tree implementation would be best)
/**
* @type {Set<AbstractItem>}
*/
@ -391,6 +392,20 @@ export class AbstractItem extends AbstractStruct {
throw new Error('unimplemented')
}
/**
* @param {AbstractItem} right
* @return {boolean}
*/
mergeWith (right) {
if (compareIDs(right.origin, this.lastId) && this.right === right && compareIDs(this.rightOrigin, right.rightOrigin)) {
this.right = right.right
if (right.right !== null) {
right.right = this
}
return true
}
return false
}
/**
* Mark this Item as deleted.
*

View File

@ -13,7 +13,6 @@ import {
changeItemRefOffset,
GC,
splitItem,
compareIDs,
addToDeleteSet,
StructStore, Transaction, ID, AbstractType // eslint-disable-line
} from '../internals.js'
@ -80,7 +79,7 @@ export class ItemDeleted extends AbstractItem {
* @return {boolean}
*/
mergeWith (right) {
if (compareIDs(right.origin, this.lastId) && this.right === right && compareIDs(this.rightOrigin, right.rightOrigin)) {
if (super.mergeWith(right)) {
this._len += right._len
return true
}

View File

@ -10,7 +10,6 @@ import {
getItemType,
splitItem,
changeItemRefOffset,
compareIDs,
GC,
ItemDeleted,
StructStore, Transaction, ID, AbstractType // eslint-disable-line
@ -75,7 +74,7 @@ export class ItemJSON extends AbstractItem {
* @return {boolean}
*/
mergeWith (right) {
if (compareIDs(right.origin, this.lastId) && this.right === right && compareIDs(this.rightOrigin, right.rightOrigin)) {
if (super.mergeWith(right)) {
this.content = this.content.concat(right.content)
return true
}

View File

@ -9,7 +9,6 @@ import {
getItemType,
splitItem,
changeItemRefOffset,
compareIDs,
ItemDeleted,
GC,
StructStore, Transaction, ID, AbstractType // eslint-disable-line
@ -76,7 +75,7 @@ export class ItemString extends AbstractItem {
* @return {boolean}
*/
mergeWith (right) {
if (compareIDs(right.origin, this.lastId) && this.right === right && compareIDs(this.rightOrigin, right.rightOrigin)) {
if (super.mergeWith(right)) {
this.string += right.string
return true
}

View File

@ -36,12 +36,10 @@ const findNextPosition = (transaction, store, currentAttributes, left, right, co
case ItemString:
if (!right.deleted) {
if (count < right.length) {
right = getItemCleanStart(store, createID(right.id.client, right.id.clock + count))
left = right.left
count = 0
} else {
count -= right.length
// split right
getItemCleanStart(store, createID(right.id.client, right.id.clock + count))
}
count -= right.length
}
break
case ItemFormat:

View File

@ -1,7 +1,7 @@
import {
getItemRange,
StructStore, Transaction, ID // eslint-disable-line
findIndexSS,
AbstractItem, StructStore, Transaction, ID // eslint-disable-line
} from '../internals.js'
import * as math from 'lib0/math.js'
@ -50,7 +50,7 @@ export class DeleteSet {
*/
export const findIndexDS = (dis, clock) => {
let left = 0
let right = dis.length
let right = dis.length - 1
while (left <= right) {
const midindex = math.floor((left + right) / 2)
const mid = dis[midindex]
@ -59,11 +59,9 @@ export const findIndexDS = (dis, clock) => {
if (clock < midclock + mid.len) {
return midindex
}
left = midindex
} else if (right !== midindex) {
right = midindex
left = midindex + 1
} else {
break
right = midindex - 1
}
}
return null
@ -165,18 +163,50 @@ export const writeDeleteSet = (encoder, ds) => {
/**
* @param {decoding.Decoder} decoder
* @param {StructStore} ss
* @param {StructStore} store
* @param {Transaction} transaction
*/
export const readDeleteSet = (decoder, ss, transaction) => {
export const readDeleteSet = (decoder, store, transaction) => {
const numClients = decoding.readVarUint(decoder)
for (let i = 0; i < numClients; i++) {
const client = decoding.readVarUint(decoder)
const len = decoding.readVarUint(decoder)
for (let i = 0; i < len; i++) {
const numberOfDeletes = decoding.readVarUint(decoder)
const structs = store.clients.get(client) || []
const lastStruct = structs[structs.length - 1]
const state = lastStruct.id.clock + lastStruct.length
for (let i = 0; i < numberOfDeletes; i++) {
const clock = decoding.readVarUint(decoder)
const len = decoding.readVarUint(decoder)
getItemRange(ss, client, clock, len).forEach(struct => struct.delete(transaction))
if (clock < state) {
let index = findIndexSS(structs, clock)
/**
* We can ignore the case of GC and Delete structs, because we are going to skip them
* @type {AbstractItem}
*/
// @ts-ignore
let struct = structs[index++]
if (!struct.deleted) {
if (struct.id.clock < clock) {
struct = struct.splitAt(store, clock - struct.id.clock)
structs.splice(index, 0, struct)
}
struct.delete(transaction)
}
while (index < structs.length) {
// @ts-ignore
struct = structs[index++]
if (struct.id.clock < clock + len) {
if (!struct.deleted) {
if (clock + len < struct.id.clock + struct.length) {
structs.splice(index, 0, struct.splitAt(store, clock + len - struct.id.clock))
}
struct.delete(transaction)
}
} else {
break
}
}
}
}
}
}

View File

@ -97,12 +97,9 @@ export const findIndexSS = (structs, clock) => {
if (clock < midclock + mid.length) {
return midindex
}
if (left === midindex) {
throw error.unexpectedCase()
}
left = midindex
left = midindex + 1
} else {
right = midindex
right = midindex - 1
}
}
throw error.unexpectedCase()
@ -193,47 +190,6 @@ export const getItemCleanEnd = (store, id) => {
return struct
}
/**
* Expects that id is actually in store. This function throws or is an infinite loop otherwise.
* @param {StructStore} store
* @param {number} client
* @param {number} clock
* @param {number} len
* @return {Array<AbstractItem>}
*
* @private
*/
export const getItemRange = (store, client, clock, len) => {
/**
* @type {Array<AbstractItem>}
*/
// @ts-ignore
const structs = store.clients.get(client)
let index = findIndexSS(structs, clock)
let struct = structs[index]
let range = []
if (struct.id.clock <= clock) {
if (struct.id.clock < clock) {
struct = struct.splitAt(store, clock - struct.id.clock)
structs.splice(index + 1, 0, struct)
}
range.push(struct)
}
index++
while (index < structs.length) {
struct = structs[index++]
if (struct.id.clock < clock + len) {
range.push(struct)
} else {
break
}
}
if (struct.id.clock < clock + len && struct.id.clock + struct.length > clock + len) {
structs.splice(index + 1, 0, struct.splitAt(store, clock + len - struct.id.clock))
}
return range
}
/**
* Replace `item` with `newitem` in store
* @param {StructStore} store

View File

@ -67,7 +67,7 @@ export class YEvent {
* @return {boolean}
*/
adds (struct) {
return struct.id.clock > (this.transaction.beforeState.get(struct.id.client) || 0)
return struct.id.clock >= (this.transaction.beforeState.get(struct.id.client) || 0)
}
}

View File

@ -77,6 +77,11 @@ export const writeStructsFromTransaction = (encoder, transaction) => writeStruct
export const writeStructs = (encoder, store, _sm) => {
// we filter all valid _sm entries into sm
const sm = new Map()
const encoderUserPosMap = map.create()
const startMessagePos = encoding.length(encoder)
// write diff to pos of end of this message
// we use it in readStructs to jump ahead to the end of the message
encoding.writeUint32(encoder, 0)
_sm.forEach((clock, client) => {
if (getState(store, client) > clock) {
sm.set(client, clock)
@ -87,7 +92,6 @@ export const writeStructs = (encoder, store, _sm) => {
sm.set(client, 0)
}
})
const encoderUserPosMap = map.create()
// write # states that were updated
encoding.writeVarUint(encoder, sm.size)
sm.forEach((clock, client) => {
@ -95,11 +99,11 @@ export const writeStructs = (encoder, store, _sm) => {
writeID(encoder, createID(client, clock))
encoderUserPosMap.set(client, encoding.length(encoder))
// write diff to pos where structs are written
// We will fill out this value later *)
encoding.writeUint32(encoder, 0)
})
sm.forEach((clock, client) => {
const decPos = encoderUserPosMap.get(client)
// fill out diff to pos where structs are written
encoding.setUint32(encoder, decPos, encoding.length(encoder) - decPos)
/**
* @type {Array<AbstractStruct>}
@ -116,6 +120,8 @@ export const writeStructs = (encoder, store, _sm) => {
structs[i].write(encoder, 0, 0)
}
})
// fill out diff to pos of end of message
encoding.setUint32(encoder, startMessagePos, encoding.length(encoder) - startMessagePos)
}
/**
@ -134,20 +140,26 @@ export const readStructs = (decoder, transaction, store) => {
* @type {Map<number,Iterator<AbstractRef>>}
*/
const structReaders = new Map()
const endOfMessagePos = decoder.pos + decoding.readUint32(decoder)
const clientbeforeState = decoding.readVarUint(decoder)
/**
* Stack of pending structs waiting for struct dependencies.
* Maximum length of stack is structReaders.size.
* @type {Array<AbstractRef>}
*/
const stack = []
const localState = getStates(store)
let lastStructReader = null
for (let i = 0; i < clientbeforeState; i++) {
const nextID = readID(decoder)
const decoderPos = decoder.pos + decoding.readUint32(decoder)
lastStructReader = decoding.clone(decoder, decoderPos)
const numberOfStructs = decoding.readVarUint(lastStructReader)
structReaders.set(nextID.client, createStructReaderIterator(lastStructReader, numberOfStructs, nextID, localState.get(nextID.client) || 0))
const structReaderDecoder = decoding.clone(decoder, decoderPos)
const numberOfStructs = decoding.readVarUint(structReaderDecoder)
structReaders.set(nextID.client, createStructReaderIterator(structReaderDecoder, numberOfStructs, nextID, localState.get(nextID.client) || 0))
}
// Decoder is still stuck at creating struct readers.
// Jump ahead to end of message so that reading can continue.
// We will use the created struct readers for the remaining part of this workflow.
decoder.pos = endOfMessagePos
for (const it of structReaders.values()) {
// todo try for in of it
for (let res = it.next(); !res.done; res = it.next()) {
@ -173,8 +185,4 @@ export const readStructs = (decoder, transaction, store) => {
}
}
}
// if we read some structs, this points to the end of the transaction
if (lastStructReader !== null) {
decoder.pos = lastStructReader.pos
}
}

View File

@ -310,7 +310,7 @@ const arrayTransactions = [
* @param {t.TestCase} tc
*/
export const testRepeatGeneratingYarrayTests20 = tc => {
applyRandomTests(tc, arrayTransactions, 20)
applyRandomTests(tc, arrayTransactions, 2)
}
/**