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 {
|
} else {
|
||||||
o = parent._start
|
o = parent._start
|
||||||
}
|
}
|
||||||
|
// TODO: use something like DeleteSet here (a tree implementation would be best)
|
||||||
/**
|
/**
|
||||||
* @type {Set<AbstractItem>}
|
* @type {Set<AbstractItem>}
|
||||||
*/
|
*/
|
||||||
@ -391,6 +392,20 @@ export class AbstractItem extends AbstractStruct {
|
|||||||
throw new Error('unimplemented')
|
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.
|
* Mark this Item as deleted.
|
||||||
*
|
*
|
||||||
|
@ -13,7 +13,6 @@ import {
|
|||||||
changeItemRefOffset,
|
changeItemRefOffset,
|
||||||
GC,
|
GC,
|
||||||
splitItem,
|
splitItem,
|
||||||
compareIDs,
|
|
||||||
addToDeleteSet,
|
addToDeleteSet,
|
||||||
StructStore, Transaction, ID, AbstractType // eslint-disable-line
|
StructStore, Transaction, ID, AbstractType // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
@ -80,7 +79,7 @@ export class ItemDeleted extends AbstractItem {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
mergeWith (right) {
|
mergeWith (right) {
|
||||||
if (compareIDs(right.origin, this.lastId) && this.right === right && compareIDs(this.rightOrigin, right.rightOrigin)) {
|
if (super.mergeWith(right)) {
|
||||||
this._len += right._len
|
this._len += right._len
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import {
|
|||||||
getItemType,
|
getItemType,
|
||||||
splitItem,
|
splitItem,
|
||||||
changeItemRefOffset,
|
changeItemRefOffset,
|
||||||
compareIDs,
|
|
||||||
GC,
|
GC,
|
||||||
ItemDeleted,
|
ItemDeleted,
|
||||||
StructStore, Transaction, ID, AbstractType // eslint-disable-line
|
StructStore, Transaction, ID, AbstractType // eslint-disable-line
|
||||||
@ -75,7 +74,7 @@ export class ItemJSON extends AbstractItem {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
mergeWith (right) {
|
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)
|
this.content = this.content.concat(right.content)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,6 @@ import {
|
|||||||
getItemType,
|
getItemType,
|
||||||
splitItem,
|
splitItem,
|
||||||
changeItemRefOffset,
|
changeItemRefOffset,
|
||||||
compareIDs,
|
|
||||||
ItemDeleted,
|
ItemDeleted,
|
||||||
GC,
|
GC,
|
||||||
StructStore, Transaction, ID, AbstractType // eslint-disable-line
|
StructStore, Transaction, ID, AbstractType // eslint-disable-line
|
||||||
@ -76,7 +75,7 @@ export class ItemString extends AbstractItem {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
mergeWith (right) {
|
mergeWith (right) {
|
||||||
if (compareIDs(right.origin, this.lastId) && this.right === right && compareIDs(this.rightOrigin, right.rightOrigin)) {
|
if (super.mergeWith(right)) {
|
||||||
this.string += right.string
|
this.string += right.string
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
@ -36,12 +36,10 @@ const findNextPosition = (transaction, store, currentAttributes, left, right, co
|
|||||||
case ItemString:
|
case ItemString:
|
||||||
if (!right.deleted) {
|
if (!right.deleted) {
|
||||||
if (count < right.length) {
|
if (count < right.length) {
|
||||||
right = getItemCleanStart(store, createID(right.id.client, right.id.clock + count))
|
// split right
|
||||||
left = right.left
|
getItemCleanStart(store, createID(right.id.client, right.id.clock + count))
|
||||||
count = 0
|
|
||||||
} else {
|
|
||||||
count -= right.length
|
|
||||||
}
|
}
|
||||||
|
count -= right.length
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
case ItemFormat:
|
case ItemFormat:
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
|
|
||||||
import {
|
import {
|
||||||
getItemRange,
|
findIndexSS,
|
||||||
StructStore, Transaction, ID // eslint-disable-line
|
AbstractItem, StructStore, Transaction, ID // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as math from 'lib0/math.js'
|
import * as math from 'lib0/math.js'
|
||||||
@ -50,7 +50,7 @@ export class DeleteSet {
|
|||||||
*/
|
*/
|
||||||
export const findIndexDS = (dis, clock) => {
|
export const findIndexDS = (dis, clock) => {
|
||||||
let left = 0
|
let left = 0
|
||||||
let right = dis.length
|
let right = dis.length - 1
|
||||||
while (left <= right) {
|
while (left <= right) {
|
||||||
const midindex = math.floor((left + right) / 2)
|
const midindex = math.floor((left + right) / 2)
|
||||||
const mid = dis[midindex]
|
const mid = dis[midindex]
|
||||||
@ -59,11 +59,9 @@ export const findIndexDS = (dis, clock) => {
|
|||||||
if (clock < midclock + mid.len) {
|
if (clock < midclock + mid.len) {
|
||||||
return midindex
|
return midindex
|
||||||
}
|
}
|
||||||
left = midindex
|
left = midindex + 1
|
||||||
} else if (right !== midindex) {
|
|
||||||
right = midindex
|
|
||||||
} else {
|
} else {
|
||||||
break
|
right = midindex - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return null
|
return null
|
||||||
@ -165,18 +163,50 @@ export const writeDeleteSet = (encoder, ds) => {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {decoding.Decoder} decoder
|
* @param {decoding.Decoder} decoder
|
||||||
* @param {StructStore} ss
|
* @param {StructStore} store
|
||||||
* @param {Transaction} transaction
|
* @param {Transaction} transaction
|
||||||
*/
|
*/
|
||||||
export const readDeleteSet = (decoder, ss, transaction) => {
|
export const readDeleteSet = (decoder, store, transaction) => {
|
||||||
const numClients = decoding.readVarUint(decoder)
|
const numClients = decoding.readVarUint(decoder)
|
||||||
for (let i = 0; i < numClients; i++) {
|
for (let i = 0; i < numClients; i++) {
|
||||||
const client = decoding.readVarUint(decoder)
|
const client = decoding.readVarUint(decoder)
|
||||||
const len = decoding.readVarUint(decoder)
|
const numberOfDeletes = decoding.readVarUint(decoder)
|
||||||
for (let i = 0; i < len; i++) {
|
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 clock = decoding.readVarUint(decoder)
|
||||||
const len = 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) {
|
if (clock < midclock + mid.length) {
|
||||||
return midindex
|
return midindex
|
||||||
}
|
}
|
||||||
if (left === midindex) {
|
left = midindex + 1
|
||||||
throw error.unexpectedCase()
|
|
||||||
}
|
|
||||||
left = midindex
|
|
||||||
} else {
|
} else {
|
||||||
right = midindex
|
right = midindex - 1
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
throw error.unexpectedCase()
|
throw error.unexpectedCase()
|
||||||
@ -193,47 +190,6 @@ export const getItemCleanEnd = (store, id) => {
|
|||||||
return struct
|
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
|
* Replace `item` with `newitem` in store
|
||||||
* @param {StructStore} store
|
* @param {StructStore} store
|
||||||
|
@ -67,7 +67,7 @@ export class YEvent {
|
|||||||
* @return {boolean}
|
* @return {boolean}
|
||||||
*/
|
*/
|
||||||
adds (struct) {
|
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) => {
|
export const writeStructs = (encoder, store, _sm) => {
|
||||||
// we filter all valid _sm entries into sm
|
// we filter all valid _sm entries into sm
|
||||||
const sm = new Map()
|
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) => {
|
_sm.forEach((clock, client) => {
|
||||||
if (getState(store, client) > clock) {
|
if (getState(store, client) > clock) {
|
||||||
sm.set(client, clock)
|
sm.set(client, clock)
|
||||||
@ -87,7 +92,6 @@ export const writeStructs = (encoder, store, _sm) => {
|
|||||||
sm.set(client, 0)
|
sm.set(client, 0)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const encoderUserPosMap = map.create()
|
|
||||||
// write # states that were updated
|
// write # states that were updated
|
||||||
encoding.writeVarUint(encoder, sm.size)
|
encoding.writeVarUint(encoder, sm.size)
|
||||||
sm.forEach((clock, client) => {
|
sm.forEach((clock, client) => {
|
||||||
@ -95,11 +99,11 @@ export const writeStructs = (encoder, store, _sm) => {
|
|||||||
writeID(encoder, createID(client, clock))
|
writeID(encoder, createID(client, clock))
|
||||||
encoderUserPosMap.set(client, encoding.length(encoder))
|
encoderUserPosMap.set(client, encoding.length(encoder))
|
||||||
// write diff to pos where structs are written
|
// write diff to pos where structs are written
|
||||||
// We will fill out this value later *)
|
|
||||||
encoding.writeUint32(encoder, 0)
|
encoding.writeUint32(encoder, 0)
|
||||||
})
|
})
|
||||||
sm.forEach((clock, client) => {
|
sm.forEach((clock, client) => {
|
||||||
const decPos = encoderUserPosMap.get(client)
|
const decPos = encoderUserPosMap.get(client)
|
||||||
|
// fill out diff to pos where structs are written
|
||||||
encoding.setUint32(encoder, decPos, encoding.length(encoder) - decPos)
|
encoding.setUint32(encoder, decPos, encoding.length(encoder) - decPos)
|
||||||
/**
|
/**
|
||||||
* @type {Array<AbstractStruct>}
|
* @type {Array<AbstractStruct>}
|
||||||
@ -116,6 +120,8 @@ export const writeStructs = (encoder, store, _sm) => {
|
|||||||
structs[i].write(encoder, 0, 0)
|
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>>}
|
* @type {Map<number,Iterator<AbstractRef>>}
|
||||||
*/
|
*/
|
||||||
const structReaders = new Map()
|
const structReaders = new Map()
|
||||||
|
const endOfMessagePos = decoder.pos + decoding.readUint32(decoder)
|
||||||
const clientbeforeState = decoding.readVarUint(decoder)
|
const clientbeforeState = decoding.readVarUint(decoder)
|
||||||
/**
|
/**
|
||||||
|
* Stack of pending structs waiting for struct dependencies.
|
||||||
|
* Maximum length of stack is structReaders.size.
|
||||||
* @type {Array<AbstractRef>}
|
* @type {Array<AbstractRef>}
|
||||||
*/
|
*/
|
||||||
const stack = []
|
const stack = []
|
||||||
const localState = getStates(store)
|
const localState = getStates(store)
|
||||||
let lastStructReader = null
|
|
||||||
for (let i = 0; i < clientbeforeState; i++) {
|
for (let i = 0; i < clientbeforeState; i++) {
|
||||||
const nextID = readID(decoder)
|
const nextID = readID(decoder)
|
||||||
const decoderPos = decoder.pos + decoding.readUint32(decoder)
|
const decoderPos = decoder.pos + decoding.readUint32(decoder)
|
||||||
lastStructReader = decoding.clone(decoder, decoderPos)
|
const structReaderDecoder = decoding.clone(decoder, decoderPos)
|
||||||
const numberOfStructs = decoding.readVarUint(lastStructReader)
|
const numberOfStructs = decoding.readVarUint(structReaderDecoder)
|
||||||
structReaders.set(nextID.client, createStructReaderIterator(lastStructReader, numberOfStructs, nextID, localState.get(nextID.client) || 0))
|
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()) {
|
for (const it of structReaders.values()) {
|
||||||
// todo try for in of it
|
// todo try for in of it
|
||||||
for (let res = it.next(); !res.done; res = it.next()) {
|
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
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testRepeatGeneratingYarrayTests20 = tc => {
|
export const testRepeatGeneratingYarrayTests20 = tc => {
|
||||||
applyRandomTests(tc, arrayTransactions, 20)
|
applyRandomTests(tc, arrayTransactions, 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
Loading…
x
Reference in New Issue
Block a user