fix gc splice issues happening in y-quill

This commit is contained in:
Kevin Jahns
2025-06-06 19:08:31 +02:00
parent a76d6e1c0e
commit 8ef8ffc250
13 changed files with 60 additions and 58 deletions

View File

@@ -76,9 +76,9 @@ export class ContentAny {
*/
delete (transaction) {}
/**
* @param {StructStore} store
* @param {Transaction} _tr
*/
gc (store) {}
gc (_tr) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} offset

View File

@@ -66,9 +66,9 @@ export class ContentBinary {
*/
delete (transaction) {}
/**
* @param {StructStore} store
* @param {Transaction} _tr
*/
gc (store) {}
gc (_tr) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} offset

View File

@@ -68,13 +68,13 @@ export class ContentDeleted {
}
/**
* @param {Transaction} transaction
* @param {Transaction} _transaction
*/
delete (transaction) {}
delete (_transaction) {}
/**
* @param {StructStore} store
* @param {Transaction} _tr
*/
gc (store) {}
gc (_tr) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} offset

View File

@@ -110,9 +110,9 @@ export class ContentDoc {
}
/**
* @param {StructStore} store
* @param {Transaction} _tr
*/
gc (store) { }
gc (_tr) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder

View File

@@ -69,9 +69,9 @@ export class ContentEmbed {
*/
delete (transaction) {}
/**
* @param {StructStore} store
* @param {Transaction} _tr
*/
gc (store) {}
gc (_tr) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} offset

View File

@@ -77,9 +77,9 @@ export class ContentFormat {
*/
delete (_transaction) {}
/**
* @param {StructStore} _store
* @param {Transaction} _tr
*/
gc (_store) {}
gc (_tr) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} _offset

View File

@@ -73,9 +73,9 @@ export class ContentJSON {
*/
delete (transaction) {}
/**
* @param {StructStore} store
* @param {Transaction} _tr
*/
gc (store) {}
gc (_tr) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} offset

View File

@@ -84,9 +84,9 @@ export class ContentString {
*/
delete (transaction) {}
/**
* @param {StructStore} store
* @param {Transaction} _tr
*/
gc (store) {}
gc (_tr) {}
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} offset

View File

@@ -128,18 +128,18 @@ export class ContentType {
}
/**
* @param {StructStore} store
* @param {Transaction} tr
*/
gc (store) {
gc (tr) {
let item = this.type._start
while (item !== null) {
item.gc(store, true)
item.gc(tr, true)
item = item.right
}
this.type._start = null
this.type._map.forEach(/** @param {Item | null} item */ (item) => {
while (item !== null) {
item.gc(store, true)
item.gc(tr, true)
item = item.left
}
})
@@ -148,9 +148,9 @@ export class ContentType {
/**
* @param {UpdateEncoderV1 | UpdateEncoderV2} encoder
* @param {number} offset
* @param {number} _offset
*/
write (encoder, offset) {
write (encoder, _offset) {
this.type._write(encoder)
}

View File

@@ -3,8 +3,7 @@ import {
addStruct,
addStructToIdSet,
addToIdSet,
UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction, // eslint-disable-line
createID
UpdateEncoderV1, UpdateEncoderV2, StructStore, Transaction // eslint-disable-line
} from '../internals.js'
export const structGCRefNumber = 0
@@ -55,20 +54,23 @@ export class GC extends AbstractStruct {
}
/**
* @param {Transaction} transaction
* @param {StructStore} store
* @param {Transaction} _transaction
* @param {StructStore} _store
* @return {null | number}
*/
getMissing (transaction, store) {
getMissing (_transaction, _store) {
return null
}
/**
* @param {number} diff
* gc structs can't be spliced.
*
* If this feature is required in the future, then need to try to merge this struct after
* transaction.
*
* @param {number} _diff
*/
splice (diff) {
const other = new GC(createID(this.id.client, this.id.clock + diff), this.length - diff)
this.length = diff
return other
splice (_diff) {
return this
}
}

View File

@@ -634,16 +634,16 @@ export class Item extends AbstractStruct {
}
/**
* @param {StructStore} store
* @param {Transaction} tr
* @param {boolean} parentGCd
*/
gc (store, parentGCd) {
gc (tr, parentGCd) {
if (!this.deleted) {
throw error.unexpectedCase()
}
this.content.gc(store)
this.content.gc(tr)
if (parentGCd) {
replaceStruct(store, this, new GC(this.id, this.length))
replaceStruct(tr, this, new GC(this.id, this.length))
} else {
this.content = new ContentDeleted(this.length)
}
@@ -799,9 +799,9 @@ export class AbstractContent {
}
/**
* @param {StructStore} _store
* @param {Transaction} _transaction
*/
gc (_store) {
gc (_transaction) {
throw error.methodUnimplemented()
}

View File

@@ -198,7 +198,7 @@ export const getItem = /** @type {function(StructStore,ID):Item} */ (find)
export const findIndexCleanStart = (transaction, structs, clock) => {
const index = findIndexSS(structs, clock)
const struct = structs[index]
if (struct.id.clock < clock) {
if (struct.id.clock < clock && struct.constructor !== GC) {
structs.splice(index + 1, 0, struct instanceof Item ? splitItem(transaction, struct, clock - struct.id.clock) : struct.splice(clock - struct.id.clock))
return index + 1
}
@@ -247,16 +247,17 @@ export const getItemCleanEnd = (transaction, store, id) => {
/**
* Replace `item` with `newitem` in store
* @param {StructStore} store
* @param {Transaction} tr
* @param {GC|Item} struct
* @param {GC|Item} newStruct
*
* @private
* @function
*/
export const replaceStruct = (store, struct, newStruct) => {
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(struct.id.client))
export const replaceStruct = (tr, struct, newStruct) => {
const structs = /** @type {Array<GC|Item>} */ (tr.doc.store.clients.get(struct.id.client))
structs[findIndexSS(structs, struct.id.clock)] = newStruct
tr._mergeStructs.push(newStruct)
}
/**

View File

@@ -10,8 +10,7 @@ import {
generateNewClientId,
createID,
cleanupYTextAfterTransaction,
IdSet, UpdateEncoderV1, UpdateEncoderV2, GC, StructStore, AbstractType, AbstractStruct, YEvent, Doc,
diffIdSet, // eslint-disable-line
IdSet, UpdateEncoderV1, UpdateEncoderV2, GC, StructStore, AbstractType, AbstractStruct, YEvent, Doc
// insertIntoIdSet
} from '../internals.js'
@@ -239,14 +238,14 @@ const tryToMergeWithLefts = (structs, pos) => {
}
/**
* @param {Transaction} tr
* @param {IdSet} ds
* @param {StructStore} store
* @param {function(Item):boolean} gcFilter
*/
const tryGcDeleteSet = (ds, store, gcFilter) => {
const tryGcDeleteSet = (tr, ds, gcFilter) => {
for (const [client, _deleteItems] of ds.clients.entries()) {
const deleteItems = _deleteItems.getIds()
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(client))
const structs = /** @type {Array<GC|Item>} */ (tr.doc.store.clients.get(client))
for (let di = deleteItems.length - 1; di >= 0; di--) {
const deleteItem = deleteItems[di]
const endDeleteItemClock = deleteItem.clock + deleteItem.len
@@ -260,7 +259,7 @@ const tryGcDeleteSet = (ds, store, gcFilter) => {
break
}
if (struct instanceof Item && struct.deleted && !struct.keep && gcFilter(struct)) {
struct.gc(store, false)
struct.gc(tr, false)
}
}
}
@@ -271,7 +270,7 @@ const tryGcDeleteSet = (ds, store, gcFilter) => {
* @param {IdSet} ds
* @param {StructStore} store
*/
const tryMergeDeleteSet = (ds, store) => {
const tryMerge = (ds, store) => {
// try to merge deleted / gc'd items
// merge from right to left for better efficiency and so we don't miss any merge targets
ds.clients.forEach((_deleteItems, client) => {
@@ -293,13 +292,13 @@ const tryMergeDeleteSet = (ds, store) => {
}
/**
* @param {IdSet} ds
* @param {StructStore} store
* @param {Transaction} tr
* @param {IdSet} idset
* @param {function(Item):boolean} gcFilter
*/
export const tryGc = (ds, store, gcFilter) => {
tryGcDeleteSet(ds, store, gcFilter)
tryMergeDeleteSet(ds, store)
export const tryGc = (tr, idset, gcFilter) => {
tryGcDeleteSet(tr, idset, gcFilter)
tryMerge(idset, tr.doc.store)
}
/**
@@ -367,9 +366,9 @@ const cleanupTransactions = (transactionCleanups, i) => {
// Replace deleted items with ItemDeleted / GC.
// This is where content is actually remove from the Yjs Doc.
if (doc.gc) {
tryGcDeleteSet(ds, store, doc.gcFilter)
tryGcDeleteSet(transaction, ds, doc.gcFilter)
}
tryMergeDeleteSet(ds, store)
tryMerge(ds, store)
// on all affected store.clients props, try to merge
transaction.insertSet.clients.forEach((ids, client) => {