rename AttributionManager=>IdMap

The "AttributionManager" will be an abstract class that maps data
(probably using IdMap(s))
This commit is contained in:
Kevin Jahns
2025-04-18 20:26:05 +02:00
parent 55238e0faf
commit 1d025ae73f
8 changed files with 72 additions and 72 deletions

View File

@@ -102,8 +102,8 @@ export {
IdSet,
equalIdSets,
createDeleteSetFromStructStore,
AttributionManager,
createAttributionManager
IdMap,
createIdMap
} from './internals.js'
const glo = /** @type {any} */ (typeof globalThis !== 'undefined'

View File

@@ -40,4 +40,4 @@ export * from './structs/ContentString.js'
export * from './structs/ContentType.js'
export * from './structs/Item.js'
export * from './structs/Skip.js'
export * from './utils/AttributionManager.js'
export * from './utils/IdMap.js'

View File

@@ -26,7 +26,7 @@ import {
updateMarkerChanges,
ContentType,
warnPrematureAccess,
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction, AttributionManager, // eslint-disable-line
ArraySearchMarker, UpdateDecoderV1, UpdateDecoderV2, UpdateEncoderV1, UpdateEncoderV2, ID, Doc, Item, Snapshot, Transaction, IdMap, // eslint-disable-line
snapshot
} from '../internals.js'
@@ -999,13 +999,13 @@ export class YText extends AbstractType {
* Note that deleted content that was not deleted in prevYdoc is rendered as an insertion with the
* attribution `{ isDeleted: true, .. }`.
*
* @param {AttributionManager} [attributionManager]
* @param {IdMap<any>} [idMap]
* @param {Doc} [prevYdoc]
* @return {import('../utils/Delta.js').Delta} The Delta representation of this type.
*
* @public
*/
getContent (attributionManager, prevYdoc) {
getContent (idMap, prevYdoc) {
this.doc ?? warnPrematureAccess()
const prevSnapshot = prevYdoc ? snapshot(prevYdoc) : null
const d = delta.create()

View File

@@ -12,21 +12,21 @@ import * as array from 'lib0/array'
* @param {T} attr
*
*/
const amAttrsHas = (attrs, attr) => attrs.find(a => a === attr)
const idmapAttrsHas = (attrs, attr) => attrs.find(a => a === attr)
/**
* @template T
* @param {Array<T>} a
* @param {Array<T>} b
*/
export const amAttrsEqual = (a, b) => a.length === b.length && a.every(v => amAttrsHas(b, v))
export const idmapAttrsEqual = (a, b) => a.length === b.length && a.every(v => idmapAttrsHas(b, v))
/**
* @template T
* @param {Array<T>} a
* @param {Array<T>} b
*/
const amAttrRangeJoin = (a, b) => a.concat(b.filter(attr => !amAttrsHas(a, attr)))
const idmapAttrRangeJoin = (a, b) => a.concat(b.filter(attr => !idmapAttrsHas(a, attr)))
/**
* @template Attrs
@@ -132,7 +132,7 @@ export class AttrRanges {
// merge range with nextRange
const largerRange = range.len > nextRange.len ? range : nextRange
const smallerLen = range.len < nextRange.len ? range.len : nextRange.len
ids[i] = new AttrRange(range.clock, smallerLen, amAttrRangeJoin(range.attrs, nextRange.attrs))
ids[i] = new AttrRange(range.clock, smallerLen, idmapAttrRangeJoin(range.attrs, nextRange.attrs))
if (range.len === nextRange.len) {
ids.splice(i + 1, 1)
} else {
@@ -152,7 +152,7 @@ export class AttrRanges {
for (i = 1, j = 1; i < ids.length; i++) {
const left = ids[j - 1]
const right = ids[i]
if (left.clock + left.len === right.clock && amAttrsEqual(left.attrs, right.attrs)) {
if (left.clock + left.len === right.clock && idmapAttrsEqual(left.attrs, right.attrs)) {
ids[j - 1] = new AttrRange(left.clock, left.len + right.len, left.attrs)
} else if (right.len !== 0) {
if (j < i) {
@@ -169,11 +169,11 @@ export class AttrRanges {
/**
* @template T
* @param {Array<AttributionManager<T>>} ams
* @return {AttributionManager<T>} A fresh IdSet
* @param {Array<IdMap<T>>} ams
* @return {IdMap<T>} A fresh IdSet
*/
export const mergeAttributionManagers = ams => {
const merged = createAttributionManager()
export const mergeIdMaps = ams => {
const merged = createIdMap()
for (let amsI = 0; amsI < ams.length; amsI++) {
ams[amsI].clients.forEach((rangesLeft, client) => {
if (!merged.clients.has(client)) {
@@ -196,7 +196,7 @@ export const mergeAttributionManagers = ams => {
/**
* @template Attrs
*/
export class AttributionManager {
export class IdMap {
constructor () {
/**
* @type {Map<number,AttrRanges<Attrs>>}
@@ -265,15 +265,15 @@ export class AttributionManager {
}
}
export const createAttributionManager = () => new AttributionManager()
export const createIdMap = () => new IdMap()
/**
* Remove all ranges from `exclude` from `ds`. The result is a fresh AttributionManager containing all ranges from `idSet` that are not
* Remove all ranges from `exclude` from `ds`. The result is a fresh IdMap containing all ranges from `idSet` that are not
* in `exclude`.
*
* @template {AttributionManager<any>} Set
* @template {IdMap<any>} Set
* @param {Set} set
* @param {IdSet | AttributionManager<any>} exclude
* @param {IdSet | IdMap<any>} exclude
* @return {Set}
*/
export const diffAttributionManager = _diffSet
export const diffIdMap = _diffSet

View File

@@ -4,7 +4,7 @@ import {
splitItem,
iterateStructs,
UpdateEncoderV2,
AttributionManager,
IdMap,
AttrRanges,
AbstractStruct, DSDecoderV1, DSEncoderV1, DSDecoderV2, DSEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line
} from '../internals.js'
@@ -217,16 +217,16 @@ export const insertIntoIdSet = (dest, src) => {
* Remove all ranges from `exclude` from `ds`. The result is a fresh IdSet containing all ranges from `idSet` that are not
* in `exclude`.
*
* @template {IdSet | AttributionManager<any>} Set
* @template {IdSet | IdMap<any>} Set
* @param {Set} set
* @param {IdSet | AttributionManager<any>} exclude
* @param {IdSet | IdMap<any>} exclude
* @return {Set}
*/
export const _diffSet = (set, exclude) => {
/**
* @type {Set}
*/
const res = /** @type {any } */ (set instanceof IdSet ? new IdSet() : new AttributionManager())
const res = /** @type {any } */ (set instanceof IdSet ? new IdSet() : new IdMap())
const Ranges = set instanceof IdSet ? IdRanges : AttrRanges
set.clients.forEach((_setRanges, client) => {
/**
@@ -288,7 +288,7 @@ export const _diffSet = (set, exclude) => {
*
* @template {IdSet} Set
* @param {Set} set
* @param {IdSet | AttributionManager<any>} exclude
* @param {IdSet | IdMap<any>} exclude
* @return {Set}
*/
export const diffIdSet = _diffSet

View File

@@ -1,13 +1,13 @@
import * as t from 'lib0/testing'
import * as am from '../src/utils/AttributionManager.js'
import { compareAttributionManagers, createAttributionManager, ID, createRandomIdSet, createRandomAttributionManager } from './testHelper.js'
import * as am from '../src/utils/IdMap.js'
import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap } from './testHelper.js'
/**
* @template T
* @param {Array<[number, number, number, Array<T>]>} ops
*/
const simpleConstructAttrs = ops => {
const attrs = createAttributionManager()
const attrs = createIdMap()
ops.forEach(op => {
attrs.add(op[0], op[1], op[2], op[3])
})
@@ -20,49 +20,49 @@ const simpleConstructAttrs = ops => {
export const testAmMerge = _tc => {
const attrs = [42]
t.group('filter out empty items (1))', () => {
compareAttributionManagers(
compareIdmaps(
simpleConstructAttrs([[0, 1, 0, attrs]]),
simpleConstructAttrs([])
)
})
t.group('filter out empty items (2))', () => {
compareAttributionManagers(
compareIdmaps(
simpleConstructAttrs([[0, 1, 0, attrs], [0, 2, 0, attrs]]),
simpleConstructAttrs([])
)
})
t.group('filter out empty items (3 - end))', () => {
compareAttributionManagers(
compareIdmaps(
simpleConstructAttrs([[0, 1, 1, attrs], [0, 2, 0, attrs]]),
simpleConstructAttrs([[0, 1, 1, attrs]])
)
})
t.group('filter out empty items (4 - middle))', () => {
compareAttributionManagers(
compareIdmaps(
simpleConstructAttrs([[0, 1, 1, attrs], [0, 2, 0, attrs], [0, 3, 1, attrs]]),
simpleConstructAttrs([[0, 1, 1, attrs], [0, 3, 1, attrs]])
)
})
t.group('filter out empty items (5 - beginning))', () => {
compareAttributionManagers(
compareIdmaps(
simpleConstructAttrs([[0, 1, 0, attrs], [0, 2, 1, attrs], [0, 3, 1, attrs]]),
simpleConstructAttrs([[0, 2, 1, attrs], [0, 3, 1, attrs]])
)
})
t.group('merge of overlapping id ranges', () => {
compareAttributionManagers(
compareIdmaps(
simpleConstructAttrs([[0, 1, 2, attrs], [0, 0, 2, attrs]]),
simpleConstructAttrs([[0, 0, 3, attrs]])
)
})
t.group('construct without hole', () => {
compareAttributionManagers(
compareIdmaps(
simpleConstructAttrs([[0, 1, 2, attrs], [0, 3, 1, attrs]]),
simpleConstructAttrs([[0, 1, 3, attrs]])
)
})
t.group('no merge of overlapping id ranges with different attributes', () => {
compareAttributionManagers(
compareIdmaps(
simpleConstructAttrs([[0, 1, 2, [1]], [0, 0, 2, [2]]]),
simpleConstructAttrs([[0, 0, 1, [2]], [0, 1, 1, [1, 2]], [0, 2, 1, [1]]])
)
@@ -72,20 +72,20 @@ export const testAmMerge = _tc => {
/**
* @param {t.TestCase} tc
*/
export const testRepeatMergingMultipleAttrManagers = tc => {
export const testRepeatMergingMultipleIdMaps = tc => {
const clients = 4
const clockRange = 5
/**
* @type {Array<am.AttributionManager<number>>}
* @type {Array<am.IdMap<number>>}
*/
const sets = []
for (let i = 0; i < 3; i++) {
sets.push(createRandomAttributionManager(tc.prng, clients, clockRange, [1, 2, 3]))
sets.push(createRandomIdMap(tc.prng, clients, clockRange, [1, 2, 3]))
}
const merged = am.mergeAttributionManagers(sets)
const mergedReverse = am.mergeAttributionManagers(sets.reverse())
compareAttributionManagers(merged, mergedReverse)
const composed = am.createAttributionManager()
const merged = am.mergeIdMaps(sets)
const mergedReverse = am.mergeIdMaps(sets.reverse())
compareIdmaps(merged, mergedReverse)
const composed = am.createIdMap()
for (let iclient = 0; iclient < clients; iclient++) {
for (let iclock = 0; iclock < clockRange + 42; iclock++) {
const mergedHas = merged.has(new ID(iclient, iclock))
@@ -99,7 +99,7 @@ export const testRepeatMergingMultipleAttrManagers = tc => {
}
}
}
compareAttributionManagers(merged, composed)
compareIdmaps(merged, composed)
}
/**
@@ -109,12 +109,12 @@ export const testRepeatRandomDiffing = tc => {
const clients = 4
const clockRange = 100
const attrs = [1, 2, 3]
const ds1 = createRandomAttributionManager(tc.prng, clients, clockRange, attrs)
const ds2 = createRandomAttributionManager(tc.prng, clients, clockRange, attrs)
const merged = am.mergeAttributionManagers([ds1, ds2])
const e1 = am.diffAttributionManager(ds1, ds2)
const e2 = am.diffAttributionManager(merged, ds2)
compareAttributionManagers(e1, e2)
const ds1 = createRandomIdMap(tc.prng, clients, clockRange, attrs)
const ds2 = createRandomIdMap(tc.prng, clients, clockRange, attrs)
const merged = am.mergeIdMaps([ds1, ds2])
const e1 = am.diffIdMap(ds1, ds2)
const e2 = am.diffIdMap(merged, ds2)
compareIdmaps(e1, e2)
}
/**
@@ -124,13 +124,13 @@ export const testRepeatRandomDiffing2 = tc => {
const clients = 4
const clockRange = 100
const attrs = [1, 2, 3]
const am1 = createRandomAttributionManager(tc.prng, clients, clockRange, attrs)
const am2 = createRandomAttributionManager(tc.prng, clients, clockRange, attrs)
const am1 = createRandomIdMap(tc.prng, clients, clockRange, attrs)
const am2 = createRandomIdMap(tc.prng, clients, clockRange, attrs)
const idsExclude = createRandomIdSet(tc.prng, clients, clockRange)
const merged = am.mergeAttributionManagers([am1, am2])
const mergedExcluded = am.diffAttributionManager(merged, idsExclude)
const e1 = am.diffAttributionManager(am1, idsExclude)
const e2 = am.diffAttributionManager(am2, idsExclude)
const excludedMerged = am.mergeAttributionManagers([e1, e2])
compareAttributionManagers(mergedExcluded, excludedMerged)
const merged = am.mergeIdMaps([am1, am2])
const mergedExcluded = am.diffIdMap(merged, idsExclude)
const e1 = am.diffIdMap(am1, idsExclude)
const e2 = am.diffIdMap(am2, idsExclude)
const excludedMerged = am.mergeIdMaps([e1, e2])
compareIdmaps(mergedExcluded, excludedMerged)
}

View File

@@ -13,7 +13,7 @@ import * as updates from './updates.tests.js'
import * as relativePositions from './relativePositions.tests.js'
import * as delta from './delta.tests.js'
import * as idset from './IdSet.tests.js'
import * as attributionManager from './AttributionManager.tests.js'
import * as idmap from './IdMap.tests.js'
import { runTests } from 'lib0/testing'
import { isBrowser, isNode } from 'lib0/environment'
@@ -24,7 +24,7 @@ if (isBrowser) {
}
const tests = {
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, delta, idset, attributionManager
doc, map, array, text, xml, encoding, undoredo, compatibility, snapshot, updates, relativePositions, delta, idset, idmap
}
const run = async () => {

View File

@@ -8,7 +8,7 @@ import * as map from 'lib0/map'
import * as Y from '../src/index.js'
import * as math from 'lib0/math'
import {
amAttrsEqual, createIdSet, createAttributionManager, addToIdSet
idmapAttrsEqual, createIdSet, createIdMap, addToIdSet
} from '../src/internals.js'
export * from '../src/index.js'
@@ -329,10 +329,10 @@ export const compareIdSets = (idSet1, idSet2) => {
/**
* @template T
* @param {Y.AttributionManager<T>} am1
* @param {Y.AttributionManager<T>} am2
* @param {Y.IdMap<T>} am1
* @param {Y.IdMap<T>} am2
*/
export const compareAttributionManagers = (am1, am2) => {
export const compareIdmaps = (am1, am2) => {
if (am1.clients.size !== am2.clients.size) return false
for (const [client, _items1] of am1.clients.entries()) {
const items1 = _items1.getIds()
@@ -340,8 +340,8 @@ export const compareAttributionManagers = (am1, am2) => {
t.assert(items2 !== undefined && items1.length === items2.length)
for (let i = 0; i < items1.length; i++) {
const di1 = items1[i]
const di2 = /** @type {Array<import('../src/utils/AttributionManager.js').AttrRange<T>>} */ (items2)[i]
t.assert(di1.clock === di2.clock && di1.len === di2.len && amAttrsEqual(di1.attrs, di2.attrs))
const di2 = /** @type {Array<import('../src/utils/IdMap.js').AttrRange<T>>} */ (items2)[i]
t.assert(di1.clock === di2.clock && di1.len === di2.len && idmapAttrsEqual(di1.attrs, di2.attrs))
}
}
return true
@@ -374,12 +374,12 @@ export const createRandomIdSet = (gen, clients, clockRange) => {
* @param {number} clients
* @param {number} clockRange (max clock - exclusive - by each client)
* @param {Array<T>} attrChoices (max clock - exclusive - by each client)
* @return {Y.AttributionManager<T>}
* @return {Y.IdMap<T>}
*/
export const createRandomAttributionManager = (gen, clients, clockRange, attrChoices) => {
export const createRandomIdMap = (gen, clients, clockRange, attrChoices) => {
const maxOpLen = 5
const numOfOps = math.ceil((clients * clockRange) / maxOpLen)
const attrMngr = createAttributionManager()
const idMap = createIdMap()
for (let i = 0; i < numOfOps; i++) {
const client = prng.uint32(gen, 0, clients - 1)
const clockStart = prng.uint32(gen, 0, clockRange)
@@ -392,9 +392,9 @@ export const createRandomAttributionManager = (gen, clients, clockRange, attrCho
attrs.push(a)
}
}
attrMngr.add(client, clockStart, len, attrs)
idMap.add(client, clockStart, len, attrs)
}
return attrMngr
return idMap
}
/**