implement solid move-range approach - tests not running
This commit is contained in:
parent
b2b7b8c280
commit
69b7f4bfb9
@ -33,6 +33,7 @@ const maxSearchMarker = 80
|
|||||||
* @param {AbstractType<any>} yarray
|
* @param {AbstractType<any>} yarray
|
||||||
* @param {number} index
|
* @param {number} index
|
||||||
* @param {function(ListIterator):T} f
|
* @param {function(ListIterator):T} f
|
||||||
|
* @return T
|
||||||
*/
|
*/
|
||||||
export const useSearchMarker = (tr, yarray, index, f) => {
|
export const useSearchMarker = (tr, yarray, index, f) => {
|
||||||
const searchMarker = yarray._searchMarker
|
const searchMarker = yarray._searchMarker
|
||||||
|
@ -11,7 +11,8 @@ import {
|
|||||||
ListIterator,
|
ListIterator,
|
||||||
useSearchMarker,
|
useSearchMarker,
|
||||||
createRelativePositionFromTypeIndex,
|
createRelativePositionFromTypeIndex,
|
||||||
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item // eslint-disable-line
|
UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, Doc, Transaction, Item, // eslint-disable-line
|
||||||
|
getMinimalListViewRanges
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -173,47 +174,23 @@ export class YArray extends AbstractType {
|
|||||||
* @param {number} assocEnd >= 0 if end should be associated with the right character.
|
* @param {number} assocEnd >= 0 if end should be associated with the right character.
|
||||||
*/
|
*/
|
||||||
moveRange (startIndex, endIndex, target, assocStart = 1, assocEnd = -1) {
|
moveRange (startIndex, endIndex, target, assocStart = 1, assocEnd = -1) {
|
||||||
if (startIndex <= target && target <= endIndex) {
|
if (
|
||||||
// It doesn't make sense to move a range into the same range (it's basically a no-op).
|
(startIndex <= target && target <= endIndex) || // It doesn't make sense to move a range into the same range (it's basically a no-op).
|
||||||
|
endIndex - startIndex < 0 // require length of >= 0
|
||||||
|
) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if (this.doc !== null) {
|
if (this.doc !== null) {
|
||||||
transact(this.doc, transaction => {
|
transact(this.doc, transaction => {
|
||||||
const start = createRelativePositionFromTypeIndex(this, startIndex, assocStart)
|
const ranges = useSearchMarker(transaction, this, startIndex, walker =>
|
||||||
const end = createRelativePositionFromTypeIndex(this, endIndex + 1, assocEnd)
|
getMinimalListViewRanges(transaction, walker, endIndex - startIndex + 1)
|
||||||
useSearchMarker(transaction, this, target, walker => {
|
|
||||||
walker.insertMove(transaction, [{ start, end }])
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
const content = /** @type {Array<any>} */ (this._prelimContent).splice(startIndex, endIndex - startIndex + 1)
|
|
||||||
;/** @type {Array<any>} */ (this._prelimContent).splice(target, 0, ...content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {number} start Inclusive move-start
|
|
||||||
* @param {number} end Inclusive move-end
|
|
||||||
* @param {number} target
|
|
||||||
* @param {number} assocStart >=0 if start should be associated with the right character. See relative-position assoc parameter.
|
|
||||||
* @param {number} assocEnd >= 0 if end should be associated with the right character.
|
|
||||||
*/
|
|
||||||
moveRangeNew (start, end, target, assocStart = 1, assocEnd = -1) {
|
|
||||||
if (start <= target && target <= end) {
|
|
||||||
// It doesn't make sense to move a range into the same range (it's basically a no-op).
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if (this.doc !== null) {
|
|
||||||
transact(this.doc, transaction => {
|
|
||||||
const ranges = useSearchMarker(transaction, this, target, walker =>
|
|
||||||
getMinimalListViewRanges(walker, start, end, assocStart, assocEnd)
|
|
||||||
)
|
)
|
||||||
useSearchMarker(transaction, this, target, walker => {
|
useSearchMarker(transaction, this, target, walker => {
|
||||||
walker.insertMove(transaction, ranges)
|
walker.insertMove(transaction, ranges)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
const content = /** @type {Array<any>} */ (this._prelimContent).splice(start, end - start + 1)
|
const content = /** @type {Array<any>} */ (this._prelimContent).splice(startIndex, endIndex - startIndex + 1)
|
||||||
;/** @type {Array<any>} */ (this._prelimContent).splice(target, 0, ...content)
|
;/** @type {Array<any>} */ (this._prelimContent).splice(target, 0, ...content)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,6 +12,7 @@ import {
|
|||||||
ContentDoc,
|
ContentDoc,
|
||||||
Doc,
|
Doc,
|
||||||
compareIDs,
|
compareIDs,
|
||||||
|
createRelativePosition,
|
||||||
RelativePosition, ID, AbstractContent, ContentMove, Transaction, Item, AbstractType // eslint-disable-line
|
RelativePosition, ID, AbstractContent, ContentMove, Transaction, Item, AbstractType // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@ -328,7 +329,7 @@ export class ListIterator {
|
|||||||
}
|
}
|
||||||
if (nextItem.right) {
|
if (nextItem.right) {
|
||||||
nextItem = nextItem.right
|
nextItem = nextItem.right
|
||||||
this.nextItem = nextItem
|
this.nextItem = nextItem // @todo move this after the while loop
|
||||||
} else {
|
} else {
|
||||||
this.reachedEnd = true
|
this.reachedEnd = true
|
||||||
}
|
}
|
||||||
@ -584,54 +585,107 @@ const concatArrayContent = (content, added) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
* Move-ranges must not cross each other.
|
||||||
|
*
|
||||||
|
* This function computes the minimal amount of ranges to move a range of content to
|
||||||
|
* a different place.
|
||||||
|
*
|
||||||
|
* Algorithm:
|
||||||
|
* * Store the current stack in $preStack and $preItem = walker.nextItem
|
||||||
|
* * Iterate forward $len items.
|
||||||
|
* * The current stack is stored is $afterStack and $
|
||||||
|
* * Delete the stack-items that both of them have in common
|
||||||
|
*
|
||||||
* @param {Transaction} tr
|
* @param {Transaction} tr
|
||||||
* @param {ListIterator} walker
|
* @param {ListIterator} walker
|
||||||
* @param {number} len
|
* @param {number} len
|
||||||
|
* @return {Array<{ start: RelativePosition, end: RelativePosition }>}
|
||||||
*/
|
*/
|
||||||
const getMinimalListViewRanges = (tr, walker, len) => {
|
export const getMinimalListViewRanges = (tr, walker, len) => {
|
||||||
walker.reduceMoveDepth(tr)
|
if (len === 0) return []
|
||||||
if (walker.index + len > walker.type._length) {
|
if (walker.index + len > walker.type._length) {
|
||||||
throw lengthExceeded
|
throw lengthExceeded
|
||||||
}
|
}
|
||||||
walker.index += len
|
// stepping outside the current move-range as much as possible
|
||||||
|
walker.reduceMoveDepth(tr)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* We store nextItem in a variable because this version cannot be null.
|
* @type {Array<{ start: RelativePosition, end: RelativePosition }>}
|
||||||
*/
|
*/
|
||||||
let nextItem = /** @type {Item} */ (walker.nextItem)
|
|
||||||
const ranges = []
|
const ranges = []
|
||||||
while (len > 0 && !walker.reachedEnd) {
|
// store relevant information for the beginning, before we iterate forward
|
||||||
while (nextItem.countable && !walker.reachedEnd && len > 0 && nextItem !== walker.currMoveEnd) {
|
/**
|
||||||
if (!nextItem.deleted && nextItem.moved === walker.currMove) {
|
* @type {Array<Item>}
|
||||||
const slicedContent = slice(nextItem.content, this.rel, len)
|
*/
|
||||||
len -= slicedContent.length
|
const preStack = walker.movedStack.map(si => si.move)
|
||||||
value = concat(value, slicedContent)
|
const preMove = walker.currMove
|
||||||
if (this.rel + slicedContent.length === nextItem.length) {
|
const preItem = /** @type {Item} */ (walker.nextItem)
|
||||||
this.rel = 0
|
const preRel = walker.rel
|
||||||
} else {
|
|
||||||
this.rel += slicedContent.length
|
walker.forward(tr, len)
|
||||||
continue // do not iterate to item.right
|
|
||||||
}
|
// store the same information for the end, after we iterate forward
|
||||||
}
|
/**
|
||||||
if (nextItem.right) {
|
* @type {Array<Item>}
|
||||||
nextItem = nextItem.right
|
*/
|
||||||
this.nextItem = nextItem
|
const afterStack = walker.movedStack.map(si => si.move)
|
||||||
} else {
|
const afterMove = walker.currMove
|
||||||
this.reachedEnd = true
|
const afterItem = /** @type {Item} */ (walker.nextItem)
|
||||||
}
|
const afterRel = walker.rel
|
||||||
}
|
|
||||||
if ((!this.reachedEnd || this.currMove !== null) && len > 0) {
|
let start = createRelativePosition(walker.type, createID(preItem.id.client, preItem.id.clock + preRel), 0)
|
||||||
// always set nextItem before any method call
|
let end = walker.reachedEnd
|
||||||
this.nextItem = nextItem
|
? createRelativePosition(
|
||||||
this.forward(tr, 0)
|
walker.type,
|
||||||
if (this.nextItem == null) {
|
createID(afterItem.id.client, afterItem.id.clock + afterItem.length - 1),
|
||||||
throw new Error('debug me') // @todo remove
|
-1
|
||||||
}
|
)
|
||||||
nextItem = this.nextItem
|
: createRelativePosition(
|
||||||
}
|
walker.type,
|
||||||
|
createID(afterItem.id.client, afterItem.id.clock + afterRel),
|
||||||
|
0
|
||||||
|
)
|
||||||
|
|
||||||
|
if (preMove) {
|
||||||
|
preStack.push(preMove)
|
||||||
}
|
}
|
||||||
this.nextItem = nextItem
|
if (afterMove) {
|
||||||
if (len < 0) {
|
afterStack.push(afterMove)
|
||||||
this.index -= len
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// remove common stack-items
|
||||||
|
while (preStack.length > 0 && preStack[0] === afterStack[0]) {
|
||||||
|
preStack.shift()
|
||||||
|
afterStack.shift()
|
||||||
|
}
|
||||||
|
|
||||||
|
// remove stack-items that are useless for our computation (that wouldn't produce meaningful ranges)
|
||||||
|
// @todo
|
||||||
|
|
||||||
|
while (preStack.length > 0) {
|
||||||
|
const move = /** @type {Item} */ (preStack.pop())
|
||||||
|
ranges.push({
|
||||||
|
start,
|
||||||
|
end: /** @type {ContentMove} */ (move.content).end
|
||||||
|
})
|
||||||
|
start = createRelativePosition(walker.type, createID(move.id.client, move.id.clock), -1)
|
||||||
|
}
|
||||||
|
|
||||||
|
const middleMove = { start, end }
|
||||||
|
ranges.push(middleMove)
|
||||||
|
|
||||||
|
while (afterStack.length > 0) {
|
||||||
|
const move = /** @type {Item} */ (afterStack.pop())
|
||||||
|
ranges.push({
|
||||||
|
start: /** @type {ContentMove} */ (move.content).start,
|
||||||
|
end
|
||||||
|
})
|
||||||
|
end = createRelativePosition(walker.type, createID(move.id.client, move.id.clock), 0)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update end of the center move operation
|
||||||
|
// Move ranges must be applied in order
|
||||||
|
middleMove.end = end
|
||||||
|
|
||||||
return ranges
|
return ranges
|
||||||
}
|
}
|
||||||
|
@ -563,7 +563,7 @@ const arrayTransactions = [
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
const pos = prng.int32(gen, 0, yarray.length - 1)
|
const pos = prng.int32(gen, 0, yarray.length - 1)
|
||||||
const len = 1 // prng.int32(gen, 1, math.min(3, yarray.length - pos))
|
const len = 1 // prng.int32(gen, 1, math.min(3, yarray.length - pos)) @todo!
|
||||||
const _newPosAdj = prng.int32(gen, 0, yarray.length - len)
|
const _newPosAdj = prng.int32(gen, 0, yarray.length - len)
|
||||||
// make sure that we don't insert in-between the moved range
|
// make sure that we don't insert in-between the moved range
|
||||||
const newPos = _newPosAdj + (_newPosAdj > yarray.length - len ? len : 0)
|
const newPos = _newPosAdj + (_newPosAdj > yarray.length - len ? len : 0)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user