in backend/new.js [1282:1357]
function applyOps(patches, changeState, docState) {
const [objActorNum, objCtr, keyActorNum, keyCtr, keyStr, idActorNum, idCtr, insert] = changeState.nextOp
const objActor = objActorNum === null ? null : docState.actorIds[objActorNum]
const keyActor = keyActorNum === null ? null : docState.actorIds[keyActorNum]
const ops = {
objActor, objCtr, keyActor, keyCtr, keyStr, idActor: docState.actorIds[idActorNum], idCtr, insert,
objId: objActor === null ? '_root' : `${objCtr}@${objActor}`
}
const {blockIndex, skipCount, visibleCount} = seekToOp(docState, ops)
const block = docState.blocks[blockIndex]
for (let col of block.columns) col.decoder.reset()
const resetFirstVisible = (skipCount === 0) || (block.firstVisibleActor === undefined) ||
(!insert && block.firstVisibleActor === keyActorNum && block.firstVisibleCtr === keyCtr)
const newBlock = {
columns: undefined,
bloom: new Uint8Array(block.bloom),
lastKey: copyObject(block.lastKey),
numVisible: copyObject(block.numVisible),
numOps: skipCount,
lastObjectActor: block.lastObjectActor,
lastObjectCtr: block.lastObjectCtr,
firstVisibleActor: resetFirstVisible ? undefined : block.firstVisibleActor,
firstVisibleCtr: resetFirstVisible ? undefined : block.firstVisibleCtr,
lastVisibleActor: undefined,
lastVisibleCtr: undefined
}
// Copy the operations up to the insertion position (the first skipCount operations)
const outCols = block.columns.map(col => ({columnId: col.columnId, encoder: encoderByColumnId(col.columnId)}))
copyColumns(outCols, block.columns, skipCount)
// Apply the operations from the change. This may cause blockIndex to move forwards if the
// property being updated straddles a block boundary.
const {blockIndex: lastBlockIndex, docOpsConsumed} =
mergeDocChangeOps(patches, newBlock, outCols, changeState, docState, visibleCount, blockIndex)
// Copy the remaining operations after the insertion position
const lastBlock = docState.blocks[lastBlockIndex]
let copyAfterMerge = -skipCount - docOpsConsumed
for (let i = blockIndex; i <= lastBlockIndex; i++) copyAfterMerge += docState.blocks[i].numOps
copyColumns(outCols, lastBlock.columns, copyAfterMerge)
newBlock.numOps += copyAfterMerge
for (let col of lastBlock.columns) {
if (!col.decoder.done) throw new RangeError(`excess ops in column ${col.columnId}`)
}
newBlock.columns = outCols.map(col => {
const decoder = decoderByColumnId(col.columnId, col.encoder.buffer)
return {columnId: col.columnId, decoder}
})
if (blockIndex === lastBlockIndex && newBlock.numOps <= MAX_BLOCK_SIZE) {
// The result is just one output block
if (copyAfterMerge > 0 && block.lastVisibleActor !== undefined && block.lastVisibleCtr !== undefined) {
// It's possible that none of the ops after the merge point are visible, in which case the
// lastVisible may not be strictly correct, because it may refer to an operation before the
// merge point rather than a list element inserted by the current change. However, this doesn't
// matter, because the only purpose for which we need it is to check whether one block ends with
// the same visible element as the next block starts with (to avoid double-counting its index);
// if the last list element of a block is invisible, the exact value of lastVisible doesn't
// matter since it will be different from the next block's firstVisible in any case.
newBlock.lastVisibleActor = block.lastVisibleActor
newBlock.lastVisibleCtr = block.lastVisibleCtr
}
docState.blocks[blockIndex] = newBlock
} else {
// Oversized output block must be split into smaller blocks
const newBlocks = splitBlock(newBlock, docState.actorIds)
docState.blocks.splice(blockIndex, lastBlockIndex - blockIndex + 1, ...newBlocks)
}
}