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 browser: true
}), }),
commonjs(), commonjs(),
// babel(), babel(),
/*
uglify({ uglify({
mangle: { mangle: {
except: ['YMap', 'Y', 'YArray', 'YText', 'YXmlHook', 'YXmlFragment', 'YXmlElement', 'YXmlEvent', 'YXmlText', 'YEvent', 'YArrayEvent', 'YMapEvent', 'Type', 'Delete', 'ItemJSON', 'ItemString', 'Item'] 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: ` banner: `
/** /**

View File

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

View File

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

View File

@ -36,7 +36,6 @@ export default class GC {
n._length += next._length n._length += next._length
y.os.delete(next._id) y.os.delete(next._id)
} }
if (id.user !== RootFakeUserID) { if (id.user !== RootFakeUserID) {
if (y.connector !== null && (y.connector._forwardAppliedStructs || id.user === y.userID)) { if (y.connector !== null && (y.connector._forwardAppliedStructs || id.user === y.userID)) {
y.connector.broadcastStruct(this) y.connector.broadcastStruct(this)
@ -85,4 +84,11 @@ export default class GC {
_splitAt () { _splitAt () {
return this 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 { getStructReference } from '../Util/structReferences.js'
import ID from '../Util/ID/ID.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 Delete from './Delete.js'
import { transactionTypeChanged } from '../Transaction.js' import { transactionTypeChanged } from '../Transaction.js'
import GC from './GC.js' import GC from './GC.js'
@ -486,7 +486,12 @@ export default class Item {
const parentID = decoder.readID() const parentID = decoder.readID()
// parent does not change, so we don't have to search for it again // parent does not change, so we don't have to search for it again
if (this._parent === null) { 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) { if (parent === null) {
missing.push(parentID) missing.push(parentID)
} else { } else {

View File

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

View File

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

View File

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