fixed merging and adapted writeStructs to write end of message
This commit is contained in:
parent
c635963747
commit
90b3fa9dd9
@ -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.
|
||||
*
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
}
|
||||
}
|
||||
|
@ -310,7 +310,7 @@ const arrayTransactions = [
|
||||
* @param {t.TestCase} tc
|
||||
*/
|
||||
export const testRepeatGeneratingYarrayTests20 = tc => {
|
||||
applyRandomTests(tc, arrayTransactions, 20)
|
||||
applyRandomTests(tc, arrayTransactions, 2)
|
||||
}
|
||||
|
||||
/**
|
||||
|
Loading…
x
Reference in New Issue
Block a user