mirror of
https://github.com/yjs/yjs.git
synced 2025-12-16 11:47:46 +01:00
diffing of attribution manager state
This commit is contained in:
@@ -1,12 +1,3 @@
|
|||||||
const resolver = {
|
|
||||||
resolveId (importee) {
|
|
||||||
return
|
|
||||||
if (importee === 'yjs') {
|
|
||||||
return `${process.cwd()}/src/index.js`
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
export default [{
|
export default [{
|
||||||
// cjs output
|
// cjs output
|
||||||
input: {
|
input: {
|
||||||
@@ -20,9 +11,6 @@ export default [{
|
|||||||
entryFileNames: '[name].cjs',
|
entryFileNames: '[name].cjs',
|
||||||
sourcemap: true
|
sourcemap: true
|
||||||
},
|
},
|
||||||
plugins: [
|
|
||||||
resolver
|
|
||||||
],
|
|
||||||
external: id => /^(lib0|y-protocols)\//.test(id)
|
external: id => /^(lib0|y-protocols)\//.test(id)
|
||||||
}, {
|
}, {
|
||||||
// esm output
|
// esm output
|
||||||
@@ -37,8 +25,5 @@ export default [{
|
|||||||
entryFileNames: '[name].mjs',
|
entryFileNames: '[name].mjs',
|
||||||
sourcemap: true
|
sourcemap: true
|
||||||
},
|
},
|
||||||
plugins: [
|
|
||||||
resolver
|
|
||||||
],
|
|
||||||
external: id => /^(lib0|y-protocols)\//.test(id)
|
external: id => /^(lib0|y-protocols)\//.test(id)
|
||||||
}]
|
}]
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
import {
|
import {
|
||||||
|
_diffSet,
|
||||||
findIndexInIdRanges,
|
findIndexInIdRanges,
|
||||||
ID // @eslint-disable-line
|
IdSet, ID // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
import * as array from 'lib0/array'
|
import * as array from 'lib0/array'
|
||||||
@@ -50,12 +51,20 @@ export class AttrRange {
|
|||||||
*/
|
*/
|
||||||
this.attrs = attrs
|
this.attrs = attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} clock
|
||||||
|
* @param {number} len
|
||||||
|
*/
|
||||||
|
copyWith (clock, len) {
|
||||||
|
return new AttrRange(clock, len, this.attrs)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template Attrs
|
* @template Attrs
|
||||||
*/
|
*/
|
||||||
class AttrRanges {
|
export class AttrRanges {
|
||||||
/**
|
/**
|
||||||
* @param {Array<AttrRange<Attrs>>} ids
|
* @param {Array<AttrRange<Attrs>>} ids
|
||||||
*/
|
*/
|
||||||
@@ -104,7 +113,7 @@ class AttrRanges {
|
|||||||
*/
|
*/
|
||||||
for (let i = 0; i < ids.length - 1;) {
|
for (let i = 0; i < ids.length - 1;) {
|
||||||
const range = ids[i]
|
const range = ids[i]
|
||||||
const nextRange = ids[i+1]
|
const nextRange = ids[i + 1]
|
||||||
// find out how to split range. it must match with next range.
|
// find out how to split range. it must match with next range.
|
||||||
// 1) we have space. Split if necessary.
|
// 1) we have space. Split if necessary.
|
||||||
// 2) concat attributes in range to the next range. Split range and splice the remainder at
|
// 2) concat attributes in range to the next range. Split range and splice the remainder at
|
||||||
@@ -257,3 +266,14 @@ export class AttributionManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export const createAttributionManager = () => new AttributionManager()
|
export const createAttributionManager = () => new AttributionManager()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove all ranges from `exclude` from `ds`. The result is a fresh AttributionManager containing all ranges from `idSet` that are not
|
||||||
|
* in `exclude`.
|
||||||
|
*
|
||||||
|
* @template {AttributionManager<any>} Set
|
||||||
|
* @param {Set} set
|
||||||
|
* @param {IdSet | AttributionManager<any>} exclude
|
||||||
|
* @return {Set}
|
||||||
|
*/
|
||||||
|
export const diffAttributionManager = _diffSet
|
||||||
|
|||||||
@@ -4,6 +4,8 @@ import {
|
|||||||
splitItem,
|
splitItem,
|
||||||
iterateStructs,
|
iterateStructs,
|
||||||
UpdateEncoderV2,
|
UpdateEncoderV2,
|
||||||
|
AttributionManager,
|
||||||
|
AttrRanges,
|
||||||
AbstractStruct, DSDecoderV1, DSEncoderV1, DSDecoderV2, DSEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line
|
AbstractStruct, DSDecoderV1, DSEncoderV1, DSDecoderV2, DSEncoderV2, Item, GC, StructStore, Transaction, ID // eslint-disable-line
|
||||||
} from '../internals.js'
|
} from '../internals.js'
|
||||||
|
|
||||||
@@ -27,6 +29,14 @@ export class IdRange {
|
|||||||
*/
|
*/
|
||||||
this.len = len
|
this.len = len
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {number} clock
|
||||||
|
* @param {number} len
|
||||||
|
*/
|
||||||
|
copyWith (clock, len) {
|
||||||
|
return new IdRange(clock, len)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class IdRanges {
|
class IdRanges {
|
||||||
@@ -203,54 +213,58 @@ 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
|
* Remove all ranges from `exclude` from `ds`. The result is a fresh IdSet containing all ranges from `idSet` that are not
|
||||||
* in `exclude`.
|
* in `exclude`.
|
||||||
*
|
*
|
||||||
* @param {IdSet} idSet
|
* @template {IdSet | AttributionManager<any>} Set
|
||||||
* @param {IdSet} exclude
|
* @param {Set} set
|
||||||
* @return {IdSet}
|
* @param {IdSet | AttributionManager<any>} exclude
|
||||||
|
* @return {Set}
|
||||||
*/
|
*/
|
||||||
export const diffIdSets = (idSet, exclude) => {
|
export const _diffSet = (set, exclude) => {
|
||||||
const res = new IdSet()
|
/**
|
||||||
idSet.clients.forEach((_idRanges, client) => {
|
* @type {Set}
|
||||||
|
*/
|
||||||
|
const res = /** @type {any } */ (set instanceof IdSet ? new IdSet() : new AttributionManager())
|
||||||
|
const Ranges = set instanceof IdSet ? IdRanges : AttrRanges
|
||||||
|
set.clients.forEach((_setRanges, client) => {
|
||||||
/**
|
/**
|
||||||
* @type {Array<IdRange>}
|
* @type {Array<IdRange>}
|
||||||
*/
|
*/
|
||||||
let resRanges = []
|
let resRanges = []
|
||||||
const _excludedRanges = exclude.clients.get(client)
|
const _excludedRanges = exclude.clients.get(client)
|
||||||
const idRanges = _idRanges.getIds()
|
const setRanges = _setRanges.getIds()
|
||||||
if (_excludedRanges == null) {
|
if (_excludedRanges == null) {
|
||||||
resRanges = idRanges.slice()
|
resRanges = setRanges.slice()
|
||||||
} else {
|
} else {
|
||||||
const excludedRanges = _excludedRanges.getIds()
|
const excludedRanges = _excludedRanges.getIds()
|
||||||
let i = 0; let j = 0
|
let i = 0; let j = 0
|
||||||
let currRange = idRanges[0]
|
let currRange = setRanges[0]
|
||||||
while (i < idRanges.length && j < excludedRanges.length) {
|
while (i < setRanges.length && j < excludedRanges.length) {
|
||||||
const e = excludedRanges[j]
|
const e = excludedRanges[j]
|
||||||
if (currRange.clock + currRange.len <= e.clock) { // no overlapping, use next range item
|
if (currRange.clock + currRange.len <= e.clock) { // no overlapping, use next range item
|
||||||
if (currRange.len > 0) resRanges.push(currRange)
|
if (currRange.len > 0) resRanges.push(currRange)
|
||||||
currRange = idRanges[++i]
|
currRange = setRanges[++i]
|
||||||
} else if (e.clock + e.len <= currRange.clock) { // no overlapping, use next excluded item
|
} else if (e.clock + e.len <= currRange.clock) { // no overlapping, use next excluded item
|
||||||
j++
|
j++
|
||||||
} else if (e.clock <= currRange.clock) { // exclude laps into range (we already know that the ranges somehow collide)
|
} else if (e.clock <= currRange.clock) { // exclude laps into range (we already know that the ranges somehow collide)
|
||||||
const newClock = e.clock + e.len
|
const newClock = e.clock + e.len
|
||||||
const newLen = currRange.clock + currRange.len - newClock
|
const newLen = currRange.clock + currRange.len - newClock
|
||||||
if (newLen > 0) {
|
if (newLen > 0) {
|
||||||
currRange = new IdRange(newClock, newLen)
|
currRange = currRange.copyWith(newClock, newLen)
|
||||||
j++
|
j++
|
||||||
} else {
|
} else {
|
||||||
// this item is completely overwritten. len=0. We can jump to the next range
|
// this item is completely overwritten. len=0. We can jump to the next range
|
||||||
currRange = idRanges[++i]
|
currRange = setRanges[++i]
|
||||||
}
|
}
|
||||||
} else { // currRange.clock < e.clock -- range laps into exclude => adjust len
|
} else { // currRange.clock < e.clock -- range laps into exclude => adjust len
|
||||||
// beginning can't be empty, add it to the result
|
// beginning can't be empty, add it to the result
|
||||||
const nextLen = e.clock - currRange.clock
|
const nextLen = e.clock - currRange.clock
|
||||||
resRanges.push(new IdRange(currRange.clock, nextLen))
|
resRanges.push(currRange.copyWith(currRange.clock, nextLen))
|
||||||
// retain the remaining length after exclude in currRange
|
// retain the remaining length after exclude in currRange
|
||||||
currRange = new IdRange(currRange.clock + e.len + nextLen, math.max(currRange.len - e.len - nextLen, 0))
|
currRange = currRange.copyWith(currRange.clock + e.len + nextLen, math.max(currRange.len - e.len - nextLen, 0))
|
||||||
if (currRange.len === 0) currRange = idRanges[++i]
|
if (currRange.len === 0) currRange = setRanges[++i]
|
||||||
else j++
|
else j++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -258,15 +272,27 @@ export const diffIdSets = (idSet, exclude) => {
|
|||||||
resRanges.push(currRange)
|
resRanges.push(currRange)
|
||||||
}
|
}
|
||||||
i++
|
i++
|
||||||
while (i < idRanges.length) {
|
while (i < setRanges.length) {
|
||||||
resRanges.push(idRanges[i++])
|
resRanges.push(setRanges[i++])
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (resRanges.length > 0) res.clients.set(client, new IdRanges(resRanges))
|
// @ts-ignore
|
||||||
|
if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges)))
|
||||||
})
|
})
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 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} Set
|
||||||
|
* @param {Set} set
|
||||||
|
* @param {IdSet | AttributionManager<any>} exclude
|
||||||
|
* @return {Set}
|
||||||
|
*/
|
||||||
|
export const diffIdSet = _diffSet
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {IdSet} idSet
|
* @param {IdSet} idSet
|
||||||
* @param {number} client
|
* @param {number} client
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import * as t from 'lib0/testing'
|
import * as t from 'lib0/testing'
|
||||||
import * as am from '../src/utils/AttributionManager.js'
|
import * as am from '../src/utils/AttributionManager.js'
|
||||||
import * as prng from 'lib0/prng'
|
import { compareAttributionManagers, createAttributionManager, ID, createRandomIdSet, createRandomAttributionManager } from './testHelper.js'
|
||||||
import * as math from 'lib0/math'
|
|
||||||
import { compareAttributionManagers, createAttributionManager, ID } from './testHelper.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @template T
|
* @template T
|
||||||
@@ -16,35 +14,6 @@ const simpleConstructAttrs = ops => {
|
|||||||
return attrs
|
return attrs
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @template T
|
|
||||||
* @param {prng.PRNG} gen
|
|
||||||
* @param {number} clients
|
|
||||||
* @param {number} clockRange (max clock - exclusive - by each client)
|
|
||||||
* @param {Array<T>} attrChoices (max clock - exclusive - by each client)
|
|
||||||
* @return {am.AttributionManager<T>}
|
|
||||||
*/
|
|
||||||
const createRandomAttributionManager = (gen, clients, clockRange, attrChoices) => {
|
|
||||||
const maxOpLen = 5
|
|
||||||
const numOfOps = math.ceil((clients * clockRange) / maxOpLen)
|
|
||||||
const attrMngr = createAttributionManager()
|
|
||||||
for (let i = 0; i < numOfOps; i++) {
|
|
||||||
const client = prng.uint32(gen, 0, clients - 1)
|
|
||||||
const clockStart = prng.uint32(gen, 0, clockRange)
|
|
||||||
const len = prng.uint32(gen, 0, clockRange - clockStart)
|
|
||||||
const attrs = [prng.oneOf(gen, attrChoices)]
|
|
||||||
// maybe add another attr
|
|
||||||
if (prng.bool(gen)) {
|
|
||||||
const a = prng.oneOf(gen, attrChoices)
|
|
||||||
if (attrs.find((attr => attr === a)) == null) {
|
|
||||||
attrs.push(a)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
attrMngr.add(client, clockStart, len, attrs)
|
|
||||||
}
|
|
||||||
return attrMngr
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} _tc
|
* @param {t.TestCase} _tc
|
||||||
*/
|
*/
|
||||||
@@ -133,3 +102,35 @@ export const testRepeatMergingMultipleAttrManagers = tc => {
|
|||||||
compareAttributionManagers(merged, composed)
|
compareAttributionManagers(merged, composed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
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 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)
|
||||||
|
}
|
||||||
|
|||||||
@@ -1,8 +1,6 @@
|
|||||||
import * as t from 'lib0/testing'
|
import * as t from 'lib0/testing'
|
||||||
import * as d from '../src/utils/IdSet.js'
|
import * as d from '../src/utils/IdSet.js'
|
||||||
import * as prng from 'lib0/prng'
|
import { compareIdSets, createRandomIdSet, ID } from './testHelper.js'
|
||||||
import * as math from 'lib0/math'
|
|
||||||
import { compareIdSets, ID } from './testHelper.js'
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {Array<[number, number, number]>} ops
|
* @param {Array<[number, number, number]>} ops
|
||||||
@@ -69,7 +67,7 @@ export const testIdsetMerge = _tc => {
|
|||||||
export const testDiffing = _tc => {
|
export const testDiffing = _tc => {
|
||||||
t.group('simple case (1))', () => {
|
t.group('simple case (1))', () => {
|
||||||
compareIdSets(
|
compareIdSets(
|
||||||
d.diffIdSets(
|
d.diffIdSet(
|
||||||
simpleConstructIdSet([[0, 1, 1], [0, 3, 1]]),
|
simpleConstructIdSet([[0, 1, 1], [0, 3, 1]]),
|
||||||
simpleConstructIdSet([[0, 3, 1]])
|
simpleConstructIdSet([[0, 3, 1]])
|
||||||
),
|
),
|
||||||
@@ -78,7 +76,7 @@ export const testDiffing = _tc => {
|
|||||||
})
|
})
|
||||||
t.group('subset left', () => {
|
t.group('subset left', () => {
|
||||||
compareIdSets(
|
compareIdSets(
|
||||||
d.diffIdSets(
|
d.diffIdSet(
|
||||||
simpleConstructIdSet([[0, 1, 3]]),
|
simpleConstructIdSet([[0, 1, 3]]),
|
||||||
simpleConstructIdSet([[0, 1, 1]])
|
simpleConstructIdSet([[0, 1, 1]])
|
||||||
),
|
),
|
||||||
@@ -87,7 +85,7 @@ export const testDiffing = _tc => {
|
|||||||
})
|
})
|
||||||
t.group('subset right', () => {
|
t.group('subset right', () => {
|
||||||
compareIdSets(
|
compareIdSets(
|
||||||
d.diffIdSets(
|
d.diffIdSet(
|
||||||
simpleConstructIdSet([[0, 1, 3]]),
|
simpleConstructIdSet([[0, 1, 3]]),
|
||||||
simpleConstructIdSet([[0, 3, 1]])
|
simpleConstructIdSet([[0, 3, 1]])
|
||||||
),
|
),
|
||||||
@@ -96,7 +94,7 @@ export const testDiffing = _tc => {
|
|||||||
})
|
})
|
||||||
t.group('subset middle', () => {
|
t.group('subset middle', () => {
|
||||||
compareIdSets(
|
compareIdSets(
|
||||||
d.diffIdSets(
|
d.diffIdSet(
|
||||||
simpleConstructIdSet([[0, 1, 3]]),
|
simpleConstructIdSet([[0, 1, 3]]),
|
||||||
simpleConstructIdSet([[0, 2, 1]])
|
simpleConstructIdSet([[0, 2, 1]])
|
||||||
),
|
),
|
||||||
@@ -105,7 +103,7 @@ export const testDiffing = _tc => {
|
|||||||
})
|
})
|
||||||
t.group('overlapping left', () => {
|
t.group('overlapping left', () => {
|
||||||
compareIdSets(
|
compareIdSets(
|
||||||
d.diffIdSets(
|
d.diffIdSet(
|
||||||
simpleConstructIdSet([[0, 1, 3]]),
|
simpleConstructIdSet([[0, 1, 3]]),
|
||||||
simpleConstructIdSet([[0, 0, 2]])
|
simpleConstructIdSet([[0, 0, 2]])
|
||||||
),
|
),
|
||||||
@@ -114,7 +112,7 @@ export const testDiffing = _tc => {
|
|||||||
})
|
})
|
||||||
t.group('overlapping right', () => {
|
t.group('overlapping right', () => {
|
||||||
compareIdSets(
|
compareIdSets(
|
||||||
d.diffIdSets(
|
d.diffIdSet(
|
||||||
simpleConstructIdSet([[0, 1, 3]]),
|
simpleConstructIdSet([[0, 1, 3]]),
|
||||||
simpleConstructIdSet([[0, 3, 5]])
|
simpleConstructIdSet([[0, 3, 5]])
|
||||||
),
|
),
|
||||||
@@ -123,7 +121,7 @@ export const testDiffing = _tc => {
|
|||||||
})
|
})
|
||||||
t.group('overlapping completely', () => {
|
t.group('overlapping completely', () => {
|
||||||
compareIdSets(
|
compareIdSets(
|
||||||
d.diffIdSets(
|
d.diffIdSet(
|
||||||
simpleConstructIdSet([[0, 1, 3]]),
|
simpleConstructIdSet([[0, 1, 3]]),
|
||||||
simpleConstructIdSet([[0, 0, 5]])
|
simpleConstructIdSet([[0, 0, 5]])
|
||||||
),
|
),
|
||||||
@@ -132,7 +130,7 @@ export const testDiffing = _tc => {
|
|||||||
})
|
})
|
||||||
t.group('overlapping into new range', () => {
|
t.group('overlapping into new range', () => {
|
||||||
compareIdSets(
|
compareIdSets(
|
||||||
d.diffIdSets(
|
d.diffIdSet(
|
||||||
simpleConstructIdSet([[0, 1, 3], [0, 5, 2]]),
|
simpleConstructIdSet([[0, 1, 3], [0, 5, 2]]),
|
||||||
simpleConstructIdSet([[0, 0, 6]])
|
simpleConstructIdSet([[0, 0, 6]])
|
||||||
),
|
),
|
||||||
@@ -141,38 +139,17 @@ export const testDiffing = _tc => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @param {prng.PRNG} gen
|
|
||||||
* @param {number} clients
|
|
||||||
* @param {number} clockRange (max clock - exclusive - by each client)
|
|
||||||
*/
|
|
||||||
const createRandomDiffSet = (gen, clients, clockRange) => {
|
|
||||||
const maxOpLen = 5
|
|
||||||
const numOfOps = math.ceil((clients * clockRange) / maxOpLen)
|
|
||||||
const ds = d.createIdSet()
|
|
||||||
for (let i = 0; i < numOfOps; i++) {
|
|
||||||
const client = prng.uint32(gen, 0, clients - 1)
|
|
||||||
const clockStart = prng.uint32(gen, 0, clockRange)
|
|
||||||
const len = prng.uint32(gen, 0, clockRange - clockStart)
|
|
||||||
d.addToIdSet(ds, client, clockStart, len)
|
|
||||||
}
|
|
||||||
if (ds.clients.size === clients && clients > 1 && prng.bool(gen)) {
|
|
||||||
ds.clients.delete(prng.uint32(gen, 0, clients))
|
|
||||||
}
|
|
||||||
return ds
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param {t.TestCase} tc
|
* @param {t.TestCase} tc
|
||||||
*/
|
*/
|
||||||
export const testRepeatRandomDiffing = tc => {
|
export const testRepeatRandomDiffing = tc => {
|
||||||
const clients = 4
|
const clients = 4
|
||||||
const clockRange = 100
|
const clockRange = 100
|
||||||
const ds1 = createRandomDiffSet(tc.prng, clients, clockRange)
|
const ds1 = createRandomIdSet(tc.prng, clients, clockRange)
|
||||||
const ds2 = createRandomDiffSet(tc.prng, clients, clockRange)
|
const ds2 = createRandomIdSet(tc.prng, clients, clockRange)
|
||||||
const merged = d.mergeIdSets([ds1, ds2])
|
const merged = d.mergeIdSets([ds1, ds2])
|
||||||
const e1 = d.diffIdSets(ds1, ds2)
|
const e1 = d.diffIdSet(ds1, ds2)
|
||||||
const e2 = d.diffIdSets(merged, ds2)
|
const e2 = d.diffIdSet(merged, ds2)
|
||||||
compareIdSets(e1, e2)
|
compareIdSets(e1, e2)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -187,7 +164,7 @@ export const testRepeatMergingMultipleIdsets = tc => {
|
|||||||
*/
|
*/
|
||||||
const idss = []
|
const idss = []
|
||||||
for (let i = 0; i < 3; i++) {
|
for (let i = 0; i < 3; i++) {
|
||||||
idss.push(createRandomDiffSet(tc.prng, clients, clockRange))
|
idss.push(createRandomIdSet(tc.prng, clients, clockRange))
|
||||||
}
|
}
|
||||||
const merged = d.mergeIdSets(idss)
|
const merged = d.mergeIdSets(idss)
|
||||||
const mergedReverse = d.mergeIdSets(idss.reverse())
|
const mergedReverse = d.mergeIdSets(idss.reverse())
|
||||||
@@ -206,3 +183,19 @@ export const testRepeatMergingMultipleIdsets = tc => {
|
|||||||
compareIdSets(merged, composed)
|
compareIdSets(merged, composed)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {t.TestCase} tc
|
||||||
|
*/
|
||||||
|
export const testRepeatRandomDiffing2 = tc => {
|
||||||
|
const clients = 4
|
||||||
|
const clockRange = 100
|
||||||
|
const ids1 = createRandomIdSet(tc.prng, clients, clockRange)
|
||||||
|
const ids2 = createRandomIdSet(tc.prng, clients, clockRange)
|
||||||
|
const idsExclude = createRandomIdSet(tc.prng, clients, clockRange)
|
||||||
|
const merged = d.mergeIdSets([ids1, ids2])
|
||||||
|
const mergedExcluded = d.diffIdSet(merged, idsExclude)
|
||||||
|
const e1 = d.diffIdSet(ids1, idsExclude)
|
||||||
|
const e2 = d.diffIdSet(ids2, idsExclude)
|
||||||
|
const excludedMerged = d.mergeIdSets([e1, e2])
|
||||||
|
compareIdSets(mergedExcluded, excludedMerged)
|
||||||
|
}
|
||||||
|
|||||||
@@ -6,7 +6,10 @@ import * as syncProtocol from 'y-protocols/sync'
|
|||||||
import * as object from 'lib0/object'
|
import * as object from 'lib0/object'
|
||||||
import * as map from 'lib0/map'
|
import * as map from 'lib0/map'
|
||||||
import * as Y from '../src/index.js'
|
import * as Y from '../src/index.js'
|
||||||
import { amAttrsEqual } from '../src/internals.js'
|
import * as math from 'lib0/math'
|
||||||
|
import {
|
||||||
|
amAttrsEqual, createIdSet, createAttributionManager, addToIdSet
|
||||||
|
} from '../src/internals.js'
|
||||||
|
|
||||||
export * from '../src/index.js'
|
export * from '../src/index.js'
|
||||||
|
|
||||||
@@ -344,7 +347,55 @@ export const compareAttributionManagers = (am1, am2) => {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param {prng.PRNG} gen
|
||||||
|
* @param {number} clients
|
||||||
|
* @param {number} clockRange (max clock - exclusive - by each client)
|
||||||
|
*/
|
||||||
|
export const createRandomIdSet = (gen, clients, clockRange) => {
|
||||||
|
const maxOpLen = 5
|
||||||
|
const numOfOps = math.ceil((clients * clockRange) / maxOpLen)
|
||||||
|
const ds = createIdSet()
|
||||||
|
for (let i = 0; i < numOfOps; i++) {
|
||||||
|
const client = prng.uint32(gen, 0, clients - 1)
|
||||||
|
const clockStart = prng.uint32(gen, 0, clockRange)
|
||||||
|
const len = prng.uint32(gen, 0, clockRange - clockStart)
|
||||||
|
addToIdSet(ds, client, clockStart, len)
|
||||||
|
}
|
||||||
|
if (ds.clients.size === clients && clients > 1 && prng.bool(gen)) {
|
||||||
|
ds.clients.delete(prng.uint32(gen, 0, clients))
|
||||||
|
}
|
||||||
|
return ds
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @template T
|
||||||
|
* @param {prng.PRNG} gen
|
||||||
|
* @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>}
|
||||||
|
*/
|
||||||
|
export const createRandomAttributionManager = (gen, clients, clockRange, attrChoices) => {
|
||||||
|
const maxOpLen = 5
|
||||||
|
const numOfOps = math.ceil((clients * clockRange) / maxOpLen)
|
||||||
|
const attrMngr = createAttributionManager()
|
||||||
|
for (let i = 0; i < numOfOps; i++) {
|
||||||
|
const client = prng.uint32(gen, 0, clients - 1)
|
||||||
|
const clockStart = prng.uint32(gen, 0, clockRange)
|
||||||
|
const len = prng.uint32(gen, 0, clockRange - clockStart)
|
||||||
|
const attrs = [prng.oneOf(gen, attrChoices)]
|
||||||
|
// maybe add another attr
|
||||||
|
if (prng.bool(gen)) {
|
||||||
|
const a = prng.oneOf(gen, attrChoices)
|
||||||
|
if (attrs.find(attr => attr === a) == null) {
|
||||||
|
attrs.push(a)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
attrMngr.add(client, clockStart, len, attrs)
|
||||||
|
}
|
||||||
|
return attrMngr
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 1. reconnect and flush all
|
* 1. reconnect and flush all
|
||||||
|
|||||||
Reference in New Issue
Block a user