From 6dd26d3b483cfa1c715b34eecda8767fff1c8936 Mon Sep 17 00:00:00 2001 From: Kevin Jahns Date: Tue, 9 Jun 2020 23:48:27 +0200 Subject: [PATCH] reduce number of variables and sanity checks :dizzy_face: --- src/structs/GC.js | 2 +- src/structs/Item.js | 76 ++++++++++++++-------------------------- src/utils/StructStore.js | 2 +- src/utils/encoding.js | 64 +++++++++++++++++++++------------ 4 files changed, 69 insertions(+), 75 deletions(-) diff --git a/src/structs/GC.js b/src/structs/GC.js index 45541ad2..8f8d1908 100644 --- a/src/structs/GC.js +++ b/src/structs/GC.js @@ -52,7 +52,7 @@ export class GC extends AbstractStruct { /** * @param {Transaction} transaction * @param {StructStore} store - * @return {null | ID} + * @return {null | number} */ getMissing (transaction, store) { return null diff --git a/src/structs/Item.js b/src/structs/Item.js index 27e0f0a3..bedbfeda 100644 --- a/src/structs/Item.js +++ b/src/structs/Item.js @@ -290,8 +290,6 @@ export class Item extends AbstractStruct { */ this.content = content this.info = this.content.isCountable() ? binary.BIT2 : 0 - - // this.keep = false } /** @@ -330,50 +328,34 @@ export class Item extends AbstractStruct { } /** - * Return missing ids, or define missing items and return null. + * Return the creator clientID of the missing op or define missing items and return null. * * @param {Transaction} transaction * @param {StructStore} store - * @return {null | ID} + * @return {null | number} */ getMissing (transaction, store) { - const origin = this.origin - const rightOrigin = this.rightOrigin - const parent = /** @type {ID} */ (this.parent) - - if (origin && origin.clock >= getState(store, origin.client)) { - return this.origin + if (this.origin && this.origin.client !== this.id.client && this.origin.clock >= getState(store, this.origin.client)) { + return this.origin.client } - if (rightOrigin && rightOrigin.clock >= getState(store, rightOrigin.client)) { - return this.rightOrigin + if (this.rightOrigin && this.rightOrigin.client !== this.id.client && this.rightOrigin.clock >= getState(store, this.rightOrigin.client)) { + return this.rightOrigin.client } - if (parent && parent.constructor === ID && parent.clock >= getState(store, parent.client)) { - return parent + if (this.parent && this.parent.constructor === ID && this.id.client !== this.parent.client && this.parent.clock >= getState(store, this.parent.client)) { + return this.parent.client } // We have all missing ids, now find the items - if (origin) { - this.left = getItemCleanEnd(transaction, store, origin) + if (this.origin) { + this.left = getItemCleanEnd(transaction, store, this.origin) this.origin = this.left.lastId } - if (rightOrigin) { - this.right = getItemCleanStart(transaction, rightOrigin) + if (this.rightOrigin) { + this.right = getItemCleanStart(transaction, this.rightOrigin) this.rightOrigin = this.right.id } - if (parent && parent.constructor === ID) { - if (parent.clock < getState(store, parent.client)) { - const parentItem = getItem(store, parent) - if (parentItem.constructor === GC) { - this.parent = null - } else { - this.parent = /** @type {ContentType} */ (parentItem.content).type - } - } else { - return parent - } - } - // only set item if this shouldn't be garbage collected + // only set parent if this shouldn't be garbage collected if (!this.parent) { if (this.left && this.left.constructor === Item) { this.parent = this.left.parent @@ -383,6 +365,13 @@ export class Item extends AbstractStruct { this.parent = this.right.parent this.parentSub = this.right.parentSub } + } else if (this.parent.constructor === ID) { + const parentItem = getItem(store, this.parent) + if (parentItem.constructor === GC) { + this.parent = null + } else { + this.parent = /** @type {ContentType} */ (parentItem.content).type + } } return null } @@ -786,24 +775,11 @@ export const readItem = (decoder, id, info, doc) => { * @type {string|null} */ const parentYKey = canCopyParentInfo && hasParentYKey ? decoding.readVarString(decoder) : null - /** - * The parent type. - * @type {ID | AbstractType | null} - */ - const parent = canCopyParentInfo && !hasParentYKey ? readID(decoder) : (parentYKey ? doc.get(parentYKey) : null) - /** - * If the parent refers to this item with some kind of key (e.g. YMap, the - * key is specified here. The key is then used to refer to the list in which - * to insert this item. If `parentSub = null` type._start is the list in - * which to insert to. Otherwise it is `parent._map`. - * @type {String | null} - */ - const parentSub = canCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoding.readVarString(decoder) : null - /** - * @type {AbstractContent} - */ - const content = readItemContent(decoder, info) - - return new Item(id, null, origin, null, rightOrigin, parent, parentSub, content) + return new Item( + id, null, origin, null, rightOrigin, + canCopyParentInfo && !hasParentYKey ? readID(decoder) : (parentYKey ? doc.get(parentYKey) : null), // parent + canCopyParentInfo && (info & binary.BIT6) === binary.BIT6 ? decoding.readVarString(decoder) : null, // parentSub + /** @type {AbstractContent} */ (readItemContent(decoder, info)) // item content + ) } diff --git a/src/utils/StructStore.js b/src/utils/StructStore.js index 01832a81..572ee17d 100644 --- a/src/utils/StructStore.js +++ b/src/utils/StructStore.js @@ -126,7 +126,7 @@ export const findIndexSS = (structs, clock) => { let right = structs.length - 1 let mid = structs[right] let midclock = mid.id.clock - if (mid.id.clock === clock) { + if (midclock === clock) { return right } // @todo does it even make sense to pivot the search? diff --git a/src/utils/encoding.js b/src/utils/encoding.js index e7d4aeba..ff9df452 100644 --- a/src/utils/encoding.js +++ b/src/utils/encoding.js @@ -100,20 +100,18 @@ export const readClientsStructRefs = (decoder, clientRefs, doc) => { const numOfStateUpdates = decoding.readVarUint(decoder) for (let i = 0; i < numOfStateUpdates; i++) { const numberOfStructs = decoding.readVarUint(decoder) - const nextID = readID(decoder) - const nextIdClient = nextID.client - let nextIdClock = nextID.clock /** * @type {Array} */ const refs = [] - clientRefs.set(nextIdClient, refs) + let { client, clock } = readID(decoder) + let info, struct + clientRefs.set(client, refs) for (let i = 0; i < numberOfStructs; i++) { - const info = decoding.readUint8(decoder) - const id = createID(nextIdClient, nextIdClock) - const struct = (binary.BITS5 & info) === 0 ? new GC(id, decoding.readVarUint(decoder)) : readItem(decoder, id, info, doc) + info = decoding.readUint8(decoder) + struct = (binary.BITS5 & info) === 0 ? new GC(createID(client, clock), decoding.readVarUint(decoder)) : readItem(decoder, createID(client, clock), info, doc) refs.push(struct) - nextIdClock += struct.length + clock += struct.length } } return clientRefs @@ -147,14 +145,21 @@ export const readClientsStructRefs = (decoder, clientRefs, doc) => { const resumeStructIntegration = (transaction, store) => { const stack = store.pendingStack const clientsStructRefs = store.pendingClientsStructRefs + // sort them so that we take the higher id first, in case of conflicts the lower id will probably not conflict with the id from the higher user. + const clientsStructRefsIds = Array.from(clientsStructRefs.keys()).sort((a, b) => a - b) + let curStructsTarget = /** @type {{i:number,refs:Array}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1])) // iterate over all struct readers until we are done - while (stack.length !== 0 || clientsStructRefs.size !== 0) { + while (stack.length !== 0 || clientsStructRefsIds.length > 0) { if (stack.length === 0) { // take any first struct from clientsStructRefs and put it on the stack - const [client, structRefs] = clientsStructRefs.entries().next().value - stack.push(structRefs.refs[structRefs.i++]) - if (structRefs.refs.length === structRefs.i) { - clientsStructRefs.delete(client) + if (curStructsTarget.i < curStructsTarget.refs.length) { + stack.push(curStructsTarget.refs[curStructsTarget.i++]) + } else { + clientsStructRefsIds.pop() + if (clientsStructRefsIds.length > 0) { + curStructsTarget = /** @type {{i:number,refs:Array}} */ (clientsStructRefs.get(clientsStructRefsIds[clientsStructRefsIds.length - 1])) + } + continue } } const ref = stack[stack.length - 1] @@ -163,12 +168,11 @@ const resumeStructIntegration = (transaction, store) => { const refClock = refID.clock const localClock = getState(store, client) const offset = refClock < localClock ? localClock - refClock : 0 - const missing = ref.getMissing(transaction, store) if (refClock + offset !== localClock) { // A previous message from this client is missing // check if there is a pending structRef with a smaller clock and switch them - const structRefs = clientsStructRefs.get(client) - if (structRefs !== undefined) { + const structRefs = clientsStructRefs.get(client) || { refs: [], i: 0 } + if (structRefs.refs.length !== structRefs.i) { const r = structRefs.refs[structRefs.i] if (r.id.clock < refClock) { // put ref with smaller clock on stack instead and continue @@ -183,18 +187,15 @@ const resumeStructIntegration = (transaction, store) => { // wait until missing struct is available return } - if (missing) { - const client = missing.client + const missing = ref.getMissing(transaction, store) + if (missing !== null) { // get the struct reader that has the missing struct - const structRefs = clientsStructRefs.get(client) - if (structRefs === undefined) { + const structRefs = clientsStructRefs.get(missing) || { refs: [], i: 0 } + if (structRefs.refs.length === structRefs.i) { // This update message causally depends on another update message. return } stack.push(structRefs.refs[structRefs.i++]) - if (structRefs.i === structRefs.refs.length) { - clientsStructRefs.delete(client) - } } else { if (offset < ref.length) { ref.integrate(transaction, offset) @@ -202,6 +203,7 @@ const resumeStructIntegration = (transaction, store) => { stack.pop() } } + store.pendingClientsStructRefs.clear() } /** @@ -253,6 +255,21 @@ const mergeReadStructsIntoPendingReads = (store, clientsStructsRefs) => { } } +/** + * @param {Map,i:number}>} pendingClientsStructRefs + */ +const cleanupPendingStructs = pendingClientsStructRefs => { + // cleanup pendingClientsStructs if not fully finished + for (const [client, refs] of pendingClientsStructRefs) { + if (refs.i === refs.refs.length) { + pendingClientsStructRefs.delete(client) + } else { + refs.refs.splice(0, refs.i) + refs.i = 0 + } + } +} + /** * Read the next Item in a Decoder and fill this Item with the read data. * @@ -270,6 +287,7 @@ export const readStructs = (decoder, transaction, store) => { readClientsStructRefs(decoder, clientsStructRefs, transaction.doc) mergeReadStructsIntoPendingReads(store, clientsStructRefs) resumeStructIntegration(transaction, store) + cleanupPendingStructs(store.pendingClientsStructRefs) tryResumePendingDeleteReaders(transaction, store) }