improve mem usage by conditional execution of the integration part (step throught the integration if there are conflicting items)
This commit is contained in:
parent
7fb63de8fc
commit
6b0154f046
@ -14,7 +14,13 @@ export class AbstractStruct {
|
|||||||
constructor (id, length) {
|
constructor (id, length) {
|
||||||
this.id = id
|
this.id = id
|
||||||
this.length = length
|
this.length = length
|
||||||
this.deleted = false
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get deleted () {
|
||||||
|
throw error.methodUnimplemented()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -68,7 +68,7 @@ export class ContentDeleted {
|
|||||||
*/
|
*/
|
||||||
integrate (transaction, item) {
|
integrate (transaction, item) {
|
||||||
addToDeleteSet(transaction.deleteSet, item.id, this.len)
|
addToDeleteSet(transaction.deleteSet, item.id, this.len)
|
||||||
item.deleted = true
|
item.markDeleted()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -13,13 +13,8 @@ export const structGCRefNumber = 0
|
|||||||
* @private
|
* @private
|
||||||
*/
|
*/
|
||||||
export class GC extends AbstractStruct {
|
export class GC extends AbstractStruct {
|
||||||
/**
|
get deleted () {
|
||||||
* @param {ID} id
|
return true
|
||||||
* @param {number} length
|
|
||||||
*/
|
|
||||||
constructor (id, length) {
|
|
||||||
super(id, length)
|
|
||||||
this.deleted = true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
delete () {}
|
delete () {}
|
||||||
|
@ -100,7 +100,7 @@ export const splitItem = (transaction, leftItem, diff) => {
|
|||||||
leftItem.content.splice(diff)
|
leftItem.content.splice(diff)
|
||||||
)
|
)
|
||||||
if (leftItem.deleted) {
|
if (leftItem.deleted) {
|
||||||
rightItem.deleted = true
|
rightItem.markDeleted()
|
||||||
}
|
}
|
||||||
if (leftItem.keep) {
|
if (leftItem.keep) {
|
||||||
rightItem.keep = true
|
rightItem.keep = true
|
||||||
@ -279,11 +279,6 @@ export class Item extends AbstractStruct {
|
|||||||
* @type {String | null}
|
* @type {String | null}
|
||||||
*/
|
*/
|
||||||
this.parentSub = parentSub
|
this.parentSub = parentSub
|
||||||
/**
|
|
||||||
* Whether this item was deleted or not.
|
|
||||||
* @type {Boolean}
|
|
||||||
*/
|
|
||||||
this.deleted = false
|
|
||||||
/**
|
/**
|
||||||
* If this type's effect is reundone this type refers to the type that undid
|
* If this type's effect is reundone this type refers to the type that undid
|
||||||
* this operation.
|
* this operation.
|
||||||
@ -294,14 +289,44 @@ export class Item extends AbstractStruct {
|
|||||||
* @type {AbstractContent}
|
* @type {AbstractContent}
|
||||||
*/
|
*/
|
||||||
this.content = content
|
this.content = content
|
||||||
/**
|
this.info = this.content.isCountable() ? binary.BIT2 : 0
|
||||||
* If true, do not garbage collect this Item.
|
|
||||||
*/
|
// this.keep = false
|
||||||
this.keep = false
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If true, do not garbage collect this Item.
|
||||||
|
*/
|
||||||
|
get keep () {
|
||||||
|
return (this.info & binary.BIT1) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
set keep (doKeep) {
|
||||||
|
if (this.keep !== doKeep) {
|
||||||
|
this.info ^= binary.BIT1
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
get countable () {
|
get countable () {
|
||||||
return this.content.isCountable()
|
return (this.info & binary.BIT2) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether this item was deleted or not.
|
||||||
|
* @type {Boolean}
|
||||||
|
*/
|
||||||
|
get deleted () {
|
||||||
|
return (this.info & binary.BIT3) > 0
|
||||||
|
}
|
||||||
|
|
||||||
|
set deleted (doDelete) {
|
||||||
|
if (this.deleted !== doDelete) {
|
||||||
|
this.info ^= binary.BIT3
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
markDeleted () {
|
||||||
|
this.info |= binary.BIT3
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -367,110 +392,108 @@ export class Item extends AbstractStruct {
|
|||||||
* @param {number} offset
|
* @param {number} offset
|
||||||
*/
|
*/
|
||||||
integrate (transaction, offset) {
|
integrate (transaction, offset) {
|
||||||
const store = transaction.doc.store
|
|
||||||
if (offset > 0) {
|
if (offset > 0) {
|
||||||
this.id.clock += offset
|
this.id.clock += offset
|
||||||
this.left = getItemCleanEnd(transaction, store, createID(this.id.client, this.id.clock - 1))
|
this.left = getItemCleanEnd(transaction, transaction.doc.store, createID(this.id.client, this.id.clock - 1))
|
||||||
this.origin = this.left.lastId
|
this.origin = this.left.lastId
|
||||||
this.content = this.content.splice(offset)
|
this.content = this.content.splice(offset)
|
||||||
this.length -= offset
|
this.length -= offset
|
||||||
}
|
}
|
||||||
const parentSub = this.parentSub
|
|
||||||
const length = this.length
|
|
||||||
const parent = /** @type {AbstractType<any>|null} */ (this.parent)
|
|
||||||
|
|
||||||
if (parent) {
|
if (this.parent) {
|
||||||
/**
|
if ((!this.left && (!this.right || this.right.left !== null)) || (this.left && this.left.right !== this.right)) {
|
||||||
* @type {Item|null}
|
/**
|
||||||
*/
|
* @type {Item|null}
|
||||||
let left = this.left
|
*/
|
||||||
|
let left = this.left
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @type {Item|null}
|
* @type {Item|null}
|
||||||
*/
|
*/
|
||||||
let o
|
let o
|
||||||
// set o to the first conflicting item
|
// set o to the first conflicting item
|
||||||
if (left !== null) {
|
if (left !== null) {
|
||||||
o = left.right
|
o = left.right
|
||||||
} else if (parentSub !== null) {
|
} else if (this.parentSub !== null) {
|
||||||
o = parent._map.get(parentSub) || null
|
o = /** @type {AbstractType<any>} */ (this.parent)._map.get(this.parentSub) || null
|
||||||
while (o !== null && o.left !== null) {
|
while (o !== null && o.left !== null) {
|
||||||
o = o.left
|
o = o.left
|
||||||
}
|
|
||||||
} else {
|
|
||||||
o = parent._start
|
|
||||||
}
|
|
||||||
// TODO: use something like DeleteSet here (a tree implementation would be best)
|
|
||||||
// @todo use global set definitions
|
|
||||||
/**
|
|
||||||
* @type {Set<Item>}
|
|
||||||
*/
|
|
||||||
const conflictingItems = new Set()
|
|
||||||
/**
|
|
||||||
* @type {Set<Item>}
|
|
||||||
*/
|
|
||||||
const itemsBeforeOrigin = new Set()
|
|
||||||
// Let c in conflictingItems, b in itemsBeforeOrigin
|
|
||||||
// ***{origin}bbbb{this}{c,b}{c,b}{o}***
|
|
||||||
// Note that conflictingItems is a subset of itemsBeforeOrigin
|
|
||||||
while (o !== null && o !== this.right) {
|
|
||||||
itemsBeforeOrigin.add(o)
|
|
||||||
conflictingItems.add(o)
|
|
||||||
if (compareIDs(this.origin, o.origin)) {
|
|
||||||
// case 1
|
|
||||||
if (o.id.client < this.id.client) {
|
|
||||||
left = o
|
|
||||||
conflictingItems.clear()
|
|
||||||
}
|
|
||||||
} else if (o.origin !== null && itemsBeforeOrigin.has(getItem(store, o.origin))) {
|
|
||||||
// case 2
|
|
||||||
if (o.origin === null || !conflictingItems.has(getItem(store, o.origin))) {
|
|
||||||
left = o
|
|
||||||
conflictingItems.clear()
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
break
|
o = /** @type {AbstractType<any>} */ (this.parent)._start
|
||||||
}
|
}
|
||||||
o = o.right
|
// TODO: use something like DeleteSet here (a tree implementation would be best)
|
||||||
|
// @todo use global set definitions
|
||||||
|
/**
|
||||||
|
* @type {Set<Item>}
|
||||||
|
*/
|
||||||
|
const conflictingItems = new Set()
|
||||||
|
/**
|
||||||
|
* @type {Set<Item>}
|
||||||
|
*/
|
||||||
|
const itemsBeforeOrigin = new Set()
|
||||||
|
// Let c in conflictingItems, b in itemsBeforeOrigin
|
||||||
|
// ***{origin}bbbb{this}{c,b}{c,b}{o}***
|
||||||
|
// Note that conflictingItems is a subset of itemsBeforeOrigin
|
||||||
|
while (o !== null && o !== this.right) {
|
||||||
|
itemsBeforeOrigin.add(o)
|
||||||
|
conflictingItems.add(o)
|
||||||
|
if (compareIDs(this.origin, o.origin)) {
|
||||||
|
// case 1
|
||||||
|
if (o.id.client < this.id.client) {
|
||||||
|
left = o
|
||||||
|
conflictingItems.clear()
|
||||||
|
}
|
||||||
|
} else if (o.origin !== null && itemsBeforeOrigin.has(getItem(transaction.doc.store, o.origin))) {
|
||||||
|
// case 2
|
||||||
|
if (o.origin === null || !conflictingItems.has(getItem(transaction.doc.store, o.origin))) {
|
||||||
|
left = o
|
||||||
|
conflictingItems.clear()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
o = o.right
|
||||||
|
}
|
||||||
|
this.left = left
|
||||||
}
|
}
|
||||||
this.left = left
|
|
||||||
// reconnect left/right + update parent map/start if necessary
|
// reconnect left/right + update parent map/start if necessary
|
||||||
if (left !== null) {
|
if (this.left !== null) {
|
||||||
const right = left.right
|
const right = this.left.right
|
||||||
this.right = right
|
this.right = right
|
||||||
left.right = this
|
this.left.right = this
|
||||||
} else {
|
} else {
|
||||||
let r
|
let r
|
||||||
if (parentSub !== null) {
|
if (this.parentSub !== null) {
|
||||||
r = parent._map.get(parentSub) || null
|
r = /** @type {AbstractType<any>} */ (this.parent)._map.get(this.parentSub) || null
|
||||||
while (r !== null && r.left !== null) {
|
while (r !== null && r.left !== null) {
|
||||||
r = r.left
|
r = r.left
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
r = parent._start
|
r = /** @type {AbstractType<any>} */ (this.parent)._start
|
||||||
parent._start = this
|
;/** @type {AbstractType<any>} */ (this.parent)._start = this
|
||||||
}
|
}
|
||||||
this.right = r
|
this.right = r
|
||||||
}
|
}
|
||||||
if (this.right !== null) {
|
if (this.right !== null) {
|
||||||
this.right.left = this
|
this.right.left = this
|
||||||
} else if (parentSub !== null) {
|
} else if (this.parentSub !== null) {
|
||||||
// set as current parent value if right === null and this is parentSub
|
// set as current parent value if right === null and this is parentSub
|
||||||
parent._map.set(parentSub, this)
|
/** @type {AbstractType<any>} */ (this.parent)._map.set(this.parentSub, this)
|
||||||
if (left !== null) {
|
if (this.left !== null) {
|
||||||
// this is the current attribute value of parent. delete right
|
// this is the current attribute value of parent. delete right
|
||||||
left.delete(transaction)
|
this.left.delete(transaction)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
// adjust length of parent
|
// adjust length of parent
|
||||||
if (parentSub === null && this.countable && !this.deleted) {
|
if (this.parentSub === null && this.countable && !this.deleted) {
|
||||||
parent._length += length
|
/** @type {AbstractType<any>} */ (this.parent)._length += this.length
|
||||||
}
|
}
|
||||||
addStruct(store, this)
|
addStruct(transaction.doc.store, this)
|
||||||
this.content.integrate(transaction, this)
|
this.content.integrate(transaction, this)
|
||||||
// add parent to transaction.changed
|
// add parent to transaction.changed
|
||||||
addChangedTypeToTransaction(transaction, parent, parentSub)
|
addChangedTypeToTransaction(transaction, /** @type {AbstractType<any>} */ (this.parent), this.parentSub)
|
||||||
if ((parent._item !== null && parent._item.deleted) || (this.right !== null && parentSub !== null)) {
|
if ((/** @type {AbstractType<any>} */ (this.parent)._item !== null && /** @type {AbstractType<any>} */ (this.parent)._item.deleted) || (this.right !== null && this.parentSub !== null)) {
|
||||||
// delete if parent is deleted or if this is not the current attribute value of parent
|
// delete if parent is deleted or if this is not the current attribute value of parent
|
||||||
this.delete(transaction)
|
this.delete(transaction)
|
||||||
}
|
}
|
||||||
@ -554,7 +577,7 @@ export class Item extends AbstractStruct {
|
|||||||
if (this.countable && this.parentSub === null) {
|
if (this.countable && this.parentSub === null) {
|
||||||
parent._length -= this.length
|
parent._length -= this.length
|
||||||
}
|
}
|
||||||
this.deleted = true
|
this.markDeleted()
|
||||||
addToDeleteSet(transaction.deleteSet, this.id, this.length)
|
addToDeleteSet(transaction.deleteSet, this.id, this.length)
|
||||||
maplib.setIfUndefined(transaction.changed, parent, set.create).add(this.parentSub)
|
maplib.setIfUndefined(transaction.changed, parent, set.create).add(this.parentSub)
|
||||||
this.content.delete(transaction)
|
this.content.delete(transaction)
|
||||||
|
@ -205,29 +205,38 @@ export const testFormattingRemovedInMidText = tc => {
|
|||||||
t.assert(Y.getTypeChildren(text0).length === 3)
|
t.assert(Y.getTypeChildren(text0).length === 3)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const tryGc = () => {
|
||||||
|
if (typeof global !== 'undefined' && global.gc) {
|
||||||
|
global.gc()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testLargeFragmentedDocument = tc => {
|
export const testLargeFragmentedDocument = tc => {
|
||||||
const itemsToInsert = 1000000
|
const itemsToInsert = 2000000
|
||||||
let update = /** @type {any} */ (null)
|
let update = /** @type {any} */ (null)
|
||||||
;(() => {
|
;(() => {
|
||||||
const doc1 = new Y.Doc()
|
const doc1 = new Y.Doc()
|
||||||
const text0 = doc1.getText('txt')
|
const text0 = doc1.getText('txt')
|
||||||
t.measureTime(`time to insert ${itemsToInsert}`, () => {
|
tryGc()
|
||||||
|
t.measureTime(`time to insert ${itemsToInsert} items`, () => {
|
||||||
doc1.transact(() => {
|
doc1.transact(() => {
|
||||||
for (let i = 0; i < itemsToInsert; i++) {
|
for (let i = 0; i < itemsToInsert; i++) {
|
||||||
text0.insert(0, '0')
|
text0.insert(0, '0')
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
t.measureTime('time to encode', () => {
|
tryGc()
|
||||||
|
t.measureTime('time to encode document', () => {
|
||||||
update = Y.encodeStateAsUpdate(doc1)
|
update = Y.encodeStateAsUpdate(doc1)
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
;(() => {
|
;(() => {
|
||||||
const doc2 = new Y.Doc()
|
const doc2 = new Y.Doc()
|
||||||
t.measureTime('time to apply', () => {
|
tryGc()
|
||||||
|
t.measureTime(`time to apply ${itemsToInsert} updates`, () => {
|
||||||
Y.applyUpdate(doc2, update)
|
Y.applyUpdate(doc2, update)
|
||||||
})
|
})
|
||||||
})()
|
})()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user