mirror of
https://github.com/yjs/yjs.git
synced 2026-01-09 07:48:01 -05:00
implement attribution class that is de-duplicated in IdMap
This commit is contained in:
@@ -103,7 +103,9 @@ export {
|
||||
equalIdSets,
|
||||
createDeleteSetFromStructStore,
|
||||
IdMap,
|
||||
createIdMap
|
||||
createIdMap,
|
||||
createAttribution,
|
||||
Attribution
|
||||
} from './internals.js'
|
||||
|
||||
const glo = /** @type {any} */ (typeof globalThis !== 'undefined'
|
||||
|
||||
@@ -5,6 +5,50 @@ import {
|
||||
} from '../internals.js'
|
||||
|
||||
import * as array from 'lib0/array'
|
||||
import * as map from 'lib0/map'
|
||||
import * as encoding from 'lib0/encoding'
|
||||
import * as buf from 'lib0/buffer'
|
||||
import * as rabin from 'lib0/hash/rabin'
|
||||
|
||||
/**
|
||||
* @template V
|
||||
*/
|
||||
export class Attribution {
|
||||
/**
|
||||
* @param {string} name
|
||||
* @param {V} val
|
||||
*/
|
||||
constructor (name, val) {
|
||||
this.name = name
|
||||
this.val = val
|
||||
}
|
||||
|
||||
hash () {
|
||||
const encoder = encoding.createEncoder()
|
||||
encoding.writeVarString(encoder, this.name)
|
||||
encoding.writeAny(encoder, /** @type {any} */ (this.val))
|
||||
return buf.toBase64(rabin.fingerprint(rabin.StandardIrreducible128, encoding.toUint8Array(encoder)))
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param {Attribution<any>} attr
|
||||
*/
|
||||
const _hashAttribution = attr => {
|
||||
const encoder = encoding.createEncoder()
|
||||
encoding.writeVarString(encoder, attr.name)
|
||||
encoding.writeAny(encoder, attr.val)
|
||||
return buf.toBase64(rabin.fingerprint(rabin.StandardIrreducible128, encoding.toUint8Array(encoder)))
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* @template V
|
||||
* @param {string} name
|
||||
* @param {V} val
|
||||
* @return {Attribution<V>}
|
||||
*/
|
||||
export const createAttribution = (name, val) => new Attribution(name, val)
|
||||
|
||||
/**
|
||||
* @template T
|
||||
@@ -35,7 +79,7 @@ export class AttrRange {
|
||||
/**
|
||||
* @param {number} clock
|
||||
* @param {number} len
|
||||
* @param {Array<Attrs>} attrs
|
||||
* @param {Array<Attribution<Attrs>>} attrs
|
||||
*/
|
||||
constructor (clock, len, attrs) {
|
||||
/**
|
||||
@@ -79,7 +123,7 @@ export class AttrRanges {
|
||||
/**
|
||||
* @param {number} clock
|
||||
* @param {number} length
|
||||
* @param {Array<Attrs>} attrs
|
||||
* @param {Array<Attribution<Attrs>>} attrs
|
||||
*/
|
||||
add (clock, length, attrs) {
|
||||
this.sorted = false
|
||||
@@ -168,11 +212,20 @@ export class AttrRanges {
|
||||
}
|
||||
|
||||
/**
|
||||
* Merge multiple idmaps. Ensures that there are no redundant attribution definitions (two
|
||||
* Attributions that describe the same thing).
|
||||
*
|
||||
* @template T
|
||||
* @param {Array<IdMap<T>>} ams
|
||||
* @return {IdMap<T>} A fresh IdSet
|
||||
*/
|
||||
export const mergeIdMaps = ams => {
|
||||
/**
|
||||
* Maps attribution to the attribution of the merged idmap.
|
||||
*
|
||||
* @type {Map<Attribution<any>,Attribution<any>>}
|
||||
*/
|
||||
const attrMapper = new Map()
|
||||
const merged = createIdMap()
|
||||
for (let amsI = 0; amsI < ams.length; amsI++) {
|
||||
ams[amsI].clients.forEach((rangesLeft, client) => {
|
||||
@@ -186,6 +239,14 @@ export const mergeIdMaps = ams => {
|
||||
array.appendTo(ids, nextIds.getIds())
|
||||
}
|
||||
}
|
||||
ids.forEach(id => {
|
||||
// @ts-ignore
|
||||
id.attrs = id.attrs.map(attr =>
|
||||
map.setIfUndefined(attrMapper, attr, () =>
|
||||
_ensureAttrs(merged, [attr])[0]
|
||||
)
|
||||
)
|
||||
})
|
||||
merged.clients.set(client, new AttrRanges(ids))
|
||||
}
|
||||
})
|
||||
@@ -202,6 +263,14 @@ export class IdMap {
|
||||
* @type {Map<number,AttrRanges<Attrs>>}
|
||||
*/
|
||||
this.clients = new Map()
|
||||
/**
|
||||
* @type {Map<string, Attribution<Attrs>>}
|
||||
*/
|
||||
this.attrsH = new Map()
|
||||
/**
|
||||
* @type {Set<Attribution<Attrs>>}
|
||||
*/
|
||||
this.attrs = new Set()
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -253,9 +322,10 @@ export class IdMap {
|
||||
* @param {number} client
|
||||
* @param {number} clock
|
||||
* @param {number} len
|
||||
* @param {Array<Attrs>} attrs
|
||||
* @param {Array<Attribution<Attrs>>} attrs
|
||||
*/
|
||||
add (client, clock, len, attrs) {
|
||||
attrs = _ensureAttrs(this, attrs)
|
||||
const ranges = this.clients.get(client)
|
||||
if (ranges == null) {
|
||||
this.clients.set(client, new AttrRanges([new AttrRange(clock, len, attrs)]))
|
||||
@@ -265,15 +335,27 @@ export class IdMap {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @template Attrs
|
||||
* @param {IdMap<Attrs>} idmap
|
||||
* @param {Array<Attribution<Attrs>>} attrs
|
||||
* @return {Array<Attribution<Attrs>>}
|
||||
*/
|
||||
const _ensureAttrs = (idmap, attrs) => attrs.map(attr =>
|
||||
idmap.attrs.has(attr) ? attr : map.setIfUndefined(idmap.attrsH, _hashAttribution(attr), () => {
|
||||
idmap.attrs.add(attr)
|
||||
return attr
|
||||
}))
|
||||
|
||||
export const createIdMap = () => new IdMap()
|
||||
|
||||
/**
|
||||
* Remove all ranges from `exclude` from `ds`. The result is a fresh IdMap containing all ranges from `idSet` that are not
|
||||
* in `exclude`.
|
||||
*
|
||||
* @template {IdMap<any>} Set
|
||||
* @param {Set} set
|
||||
* @template {IdMap<any>} ISet
|
||||
* @param {ISet} set
|
||||
* @param {IdSet | IdMap<any>} exclude
|
||||
* @return {Set}
|
||||
* @return {ISet}
|
||||
*/
|
||||
export const diffIdMap = _diffSet
|
||||
|
||||
@@ -24,6 +24,7 @@ export class StructStore {
|
||||
*/
|
||||
this.pendingDs = null
|
||||
}
|
||||
|
||||
get ds () {
|
||||
return createDeleteSetFromStructStore(this)
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ import {
|
||||
createID,
|
||||
cleanupYTextAfterTransaction,
|
||||
IdSet, UpdateEncoderV1, UpdateEncoderV2, GC, StructStore, AbstractType, AbstractStruct, YEvent, Doc, // eslint-disable-line
|
||||
insertIntoIdSet
|
||||
// insertIntoIdSet
|
||||
} from '../internals.js'
|
||||
|
||||
import * as error from 'lib0/error'
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import * as t from 'lib0/testing'
|
||||
import * as am from '../src/utils/IdMap.js'
|
||||
import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap } from './testHelper.js'
|
||||
import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap, createAttribution } from './testHelper.js'
|
||||
|
||||
/**
|
||||
* @template T
|
||||
@@ -9,7 +9,7 @@ import { compareIdmaps, createIdMap, ID, createRandomIdSet, createRandomIdMap }
|
||||
const simpleConstructAttrs = ops => {
|
||||
const attrs = createIdMap()
|
||||
ops.forEach(op => {
|
||||
attrs.add(op[0], op[1], op[2], op[3])
|
||||
attrs.add(op[0], op[1], op[2], op[3].map(v => createAttribution('', v)))
|
||||
})
|
||||
return attrs
|
||||
}
|
||||
|
||||
@@ -8,7 +8,7 @@ import * as map from 'lib0/map'
|
||||
import * as Y from '../src/index.js'
|
||||
import * as math from 'lib0/math'
|
||||
import {
|
||||
idmapAttrsEqual, createIdSet, createIdMap, addToIdSet
|
||||
createIdSet, createIdMap, addToIdSet
|
||||
} from '../src/internals.js'
|
||||
|
||||
export * from '../src/index.js'
|
||||
@@ -327,6 +327,28 @@ export const compareIdSets = (idSet1, idSet2) => {
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* only use for testing
|
||||
*
|
||||
* @template T
|
||||
* @param {Array<Y.Attribution<T>>} attrs
|
||||
* @param {Y.Attribution<T>} attr
|
||||
*
|
||||
*/
|
||||
const _idmapAttrsHas = (attrs, attr) => {
|
||||
const hash = attr.hash()
|
||||
return attrs.find(a => a.hash() === hash)
|
||||
}
|
||||
|
||||
/**
|
||||
* only use for testing
|
||||
*
|
||||
* @template T
|
||||
* @param {Array<Y.Attribution<T>>} a
|
||||
* @param {Array<Y.Attribution<T>>} b
|
||||
*/
|
||||
export const _idmapAttrsEqual = (a, b) => a.length === b.length && a.every(v => _idmapAttrsHas(b, v))
|
||||
|
||||
/**
|
||||
* @template T
|
||||
* @param {Y.IdMap<T>} am1
|
||||
@@ -341,7 +363,7 @@ export const compareIdmaps = (am1, am2) => {
|
||||
for (let i = 0; i < items1.length; i++) {
|
||||
const di1 = items1[i]
|
||||
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))
|
||||
t.assert(di1.clock === di2.clock && di1.len === di2.len && _idmapAttrsEqual(di1.attrs, di2.attrs))
|
||||
}
|
||||
}
|
||||
return true
|
||||
@@ -392,7 +414,7 @@ export const createRandomIdMap = (gen, clients, clockRange, attrChoices) => {
|
||||
attrs.push(a)
|
||||
}
|
||||
}
|
||||
idMap.add(client, clockStart, len, attrs)
|
||||
idMap.add(client, clockStart, len, attrs.map(v => Y.createAttribution('', v)))
|
||||
}
|
||||
return idMap
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user