correctly handle gc with UndoManager and un-merge when syncing

This commit is contained in:
Kevin Jahns 2018-04-23 13:25:30 +02:00
parent ef6eb08335
commit 94933a704d
10 changed files with 38 additions and 15 deletions

View File

@ -19,8 +19,7 @@ export default {
browser: true
}),
commonjs(),
// babel(),
/*
babel(),
uglify({
mangle: {
except: ['YMap', 'Y', 'YArray', 'YText', 'YXmlHook', 'YXmlFragment', 'YXmlElement', 'YXmlEvent', 'YXmlText', 'YEvent', 'YArrayEvent', 'YMapEvent', 'Type', 'Delete', 'ItemJSON', 'ItemString', 'Item']
@ -36,7 +35,6 @@ export default {
}
}
})
*/
],
banner: `
/**

View File

@ -92,7 +92,7 @@ export function readDeleteSet (y, decoder) {
// delete maximum the len of d
// else delete as much as possible
diff = Math.min(n._id.clock - d[0], d[1])
// deleteItemRange(y, user, d[0], diff)
// deleteItemRange(y, user, d[0], diff, true)
deletions.push([user, d[0], diff])
} else {
// 3)
@ -100,7 +100,7 @@ export function readDeleteSet (y, decoder) {
if (d[2] && !n.gc) {
// d marks as gc'd but n does not
// then delete either way
// deleteItemRange(y, user, d[0], Math.min(diff, d[1]))
// deleteItemRange(y, user, d[0], Math.min(diff, d[1]), true)
deletions.push([user, d[0], Math.min(diff, d[1])])
}
}
@ -117,12 +117,12 @@ export function readDeleteSet (y, decoder) {
// Adapt the Tree implementation to support delete while iterating
for (let i = deletions.length - 1; i >= 0; i--) {
const del = deletions[i]
deleteItemRange(y, del[0], del[1], del[2])
deleteItemRange(y, del[0], del[1], del[2], true)
}
// for the rest.. just apply it
for (; pos < dv.length; pos++) {
d = dv[pos]
deleteItemRange(y, user, d[0], d[1])
deleteItemRange(y, user, d[0], d[1], true)
// deletions.push([user, d[0], d[1], d[2]])
}
}

View File

@ -42,7 +42,15 @@ export function writeStructs (y, encoder, ss) {
for (let user of y.ss.state.keys()) {
let clock = ss.get(user) || 0
if (user !== RootFakeUserID) {
y.os.iterate(new ID(user, clock), new ID(user, Number.MAX_VALUE), function (struct) {
const minBound = new ID(user, clock)
const overlappingLeft = y.os.findPrev(minBound)
const rightID = overlappingLeft === null ? null : overlappingLeft._id
if (rightID !== null && rightID.user === user && rightID.clock + overlappingLeft._length > clock) {
const struct = overlappingLeft._clonePartial(clock - rightID.clock)
struct._toBinary(encoder)
len++
}
y.os.iterate(minBound, new ID(user, Number.MAX_VALUE), function (struct) {
struct._toBinary(encoder)
len++
})

View File

@ -1,3 +1,4 @@
import Tree from '../Util/Tree.js'
import ID from '../Util/ID/ID.js'

View File

@ -7,7 +7,7 @@ import { logID } from '../MessageHandler/messageToString.js'
* Delete all items in an ID-range
* TODO: implement getItemCleanStartNode for better performance (only one lookup)
*/
export function deleteItemRange (y, user, clock, range) {
export function deleteItemRange (y, user, clock, range, gcChildren) {
const createDelete = y.connector !== null && y.connector._forwardAppliedStructs
let item = y.os.getItemCleanStart(new ID(user, clock))
if (item !== null) {
@ -24,7 +24,7 @@ export function deleteItemRange (y, user, clock, range) {
const nodeVal = node.val
if (!nodeVal._deleted) {
nodeVal._splitAt(y, range)
nodeVal._delete(y, createDelete, true)
nodeVal._delete(y, createDelete, gcChildren)
}
const nodeLen = nodeVal._length
range -= nodeLen
@ -100,7 +100,7 @@ export default class Delete {
if (!locallyCreated) {
// from remote
const id = this._targetID
deleteItemRange(y, id.user, id.clock, this._length)
deleteItemRange(y, id.user, id.clock, this._length, false)
} else if (y.connector !== null) {
// from local
y.connector.broadcastStruct(this)

View File

@ -36,7 +36,6 @@ export default class GC {
n._length += next._length
y.os.delete(next._id)
}
if (id.user !== RootFakeUserID) {
if (y.connector !== null && (y.connector._forwardAppliedStructs || id.user === y.userID)) {
y.connector.broadcastStruct(this)
@ -85,4 +84,11 @@ export default class GC {
_splitAt () {
return this
}
_clonePartial (diff) {
const gc = new GC()
gc._id = new ID(this._id.user, this._id.clock + diff)
gc._length = this._length - diff
return gc
}
}

View File

@ -1,6 +1,6 @@
import { getStructReference } from '../Util/structReferences.js'
import ID from '../Util/ID/ID.js'
import { RootFakeUserID } from '../Util/ID/RootID.js'
import { default as RootID, RootFakeUserID } from '../Util/ID/RootID.js'
import Delete from './Delete.js'
import { transactionTypeChanged } from '../Transaction.js'
import GC from './GC.js'
@ -486,7 +486,12 @@ export default class Item {
const parentID = decoder.readID()
// parent does not change, so we don't have to search for it again
if (this._parent === null) {
const parent = y.os.get(parentID)
let parent
if (parentID.constructor === RootID) {
parent = y.os.get(parentID)
} else {
parent = y.os.getItem(parentID)
}
if (parent === null) {
missing.push(parentID)
} else {

View File

@ -214,7 +214,10 @@ export default class Type extends Item {
* @param {boolean} createDelete Whether to propagate a message that this
* Type was deleted.
*/
_delete (y, createDelete, gcChildren = true) {
_delete (y, createDelete, gcChildren) {
if (gcChildren === undefined) {
gcChildren = y._hasUndoManager === false
}
super._delete(y, createDelete, gcChildren)
y._transaction.changedTypes.delete(this)
// delete map types

View File

@ -75,6 +75,7 @@ export default class UndoManager {
this._lastTransactionWasUndo = false
const y = scope._y
this.y = y
y._hasUndoManager = true
y.on('afterTransaction', (y, transaction, remote) => {
if (!remote && transaction.changedParentTypes.has(scope)) {
let reverseOperation = new ReverseOperation(y, transaction)

View File

@ -79,6 +79,7 @@ export default class Y extends NamedEventHandler {
}
// for compatibility with isParentOf
this._parent = null
this._hasUndoManager = false
}
_setContentReady () {
if (!this._contentReady) {