@y/y 14.0.0-rc.2 → 14.0.0-rc.21
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +36 -12
- package/dist/src/index.d.ts +24 -1
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/structs/AbstractStruct.d.ts +14 -17
- package/dist/src/structs/AbstractStruct.d.ts.map +1 -1
- package/dist/src/structs/GC.d.ts +9 -14
- package/dist/src/structs/GC.d.ts.map +1 -1
- package/dist/src/structs/Item.d.ts +569 -31
- package/dist/src/structs/Item.d.ts.map +1 -1
- package/dist/src/structs/Skip.d.ts +9 -11
- package/dist/src/structs/Skip.d.ts.map +1 -1
- package/dist/src/utils/BlockSet.d.ts +8 -7
- package/dist/src/utils/BlockSet.d.ts.map +1 -1
- package/dist/src/utils/Doc.d.ts +4 -9
- package/dist/src/utils/Doc.d.ts.map +1 -1
- package/dist/src/utils/ID.d.ts +0 -1
- package/dist/src/utils/ID.d.ts.map +1 -1
- package/dist/src/utils/RelativePosition.d.ts +2 -5
- package/dist/src/utils/RelativePosition.d.ts.map +1 -1
- package/dist/src/utils/Renderer.d.ts +144 -0
- package/dist/src/utils/Renderer.d.ts.map +1 -0
- package/dist/src/utils/Snapshot.d.ts +7 -11
- package/dist/src/utils/Snapshot.d.ts.map +1 -1
- package/dist/src/utils/StructStore.d.ts +45 -23
- package/dist/src/utils/StructStore.d.ts.map +1 -1
- package/dist/src/utils/Transaction.d.ts +11 -21
- package/dist/src/utils/Transaction.d.ts.map +1 -1
- package/dist/src/utils/UndoManager.d.ts +13 -14
- package/dist/src/utils/UndoManager.d.ts.map +1 -1
- package/dist/src/utils/UpdateDecoder.d.ts +1 -1
- package/dist/src/utils/UpdateDecoder.d.ts.map +1 -1
- package/dist/src/utils/UpdateEncoder.d.ts +0 -1
- package/dist/src/utils/UpdateEncoder.d.ts.map +1 -1
- package/dist/src/utils/YEvent.d.ts +22 -26
- package/dist/src/utils/YEvent.d.ts.map +1 -1
- package/dist/src/utils/content-helper.d.ts +2 -0
- package/dist/src/utils/content-helper.d.ts.map +1 -0
- package/dist/src/utils/delta-helpers.d.ts +2 -3
- package/dist/src/utils/delta-helpers.d.ts.map +1 -1
- package/dist/src/utils/encoding-helpers.d.ts +6 -0
- package/dist/src/utils/encoding-helpers.d.ts.map +1 -0
- package/dist/src/utils/encoding.d.ts +16 -18
- package/dist/src/utils/encoding.d.ts.map +1 -1
- package/dist/src/utils/ids.d.ts +331 -0
- package/dist/src/utils/ids.d.ts.map +1 -0
- package/dist/src/utils/isParentOf.d.ts +1 -2
- package/dist/src/utils/isParentOf.d.ts.map +1 -1
- package/dist/src/utils/logging.d.ts +0 -1
- package/dist/src/utils/logging.d.ts.map +1 -1
- package/dist/src/utils/meta.d.ts +15 -64
- package/dist/src/utils/meta.d.ts.map +1 -1
- package/dist/src/utils/renderer-helpers.d.ts +99 -0
- package/dist/src/utils/renderer-helpers.d.ts.map +1 -0
- package/dist/src/utils/schemas.d.ts +3 -0
- package/dist/src/utils/schemas.d.ts.map +1 -0
- package/dist/src/utils/transaction-helpers.d.ts +18 -0
- package/dist/src/utils/transaction-helpers.d.ts.map +1 -0
- package/dist/src/utils/updates.d.ts +19 -25
- package/dist/src/utils/updates.d.ts.map +1 -1
- package/dist/src/ytype.d.ts +144 -41
- package/dist/src/ytype.d.ts.map +1 -1
- package/global.d.ts +53 -0
- package/package.json +12 -16
- package/src/index.js +32 -124
- package/src/structs/AbstractStruct.js +21 -16
- package/src/structs/GC.js +15 -20
- package/src/structs/Item.js +992 -318
- package/src/structs/Skip.js +16 -19
- package/src/utils/BlockSet.js +20 -20
- package/src/utils/Doc.js +18 -29
- package/src/utils/ID.js +0 -2
- package/src/utils/RelativePosition.js +15 -25
- package/src/utils/{AttributionManager.js → Renderer.js} +42 -197
- package/src/utils/Snapshot.js +15 -37
- package/src/utils/StructStore.js +89 -227
- package/src/utils/Transaction.js +89 -321
- package/src/utils/UndoManager.js +157 -19
- package/src/utils/UpdateDecoder.js +2 -3
- package/src/utils/UpdateEncoder.js +0 -4
- package/src/utils/YEvent.js +20 -26
- package/src/utils/content-helper.js +0 -0
- package/src/utils/delta-helpers.js +21 -42
- package/src/utils/encoding-helpers.js +110 -0
- package/src/utils/encoding.js +197 -122
- package/src/utils/ids.js +1527 -0
- package/src/utils/isParentOf.js +2 -4
- package/src/utils/logging.js +0 -4
- package/src/utils/meta.js +57 -46
- package/src/utils/renderer-helpers.js +110 -0
- package/src/utils/schemas.js +3 -0
- package/src/utils/transaction-helpers.js +413 -0
- package/src/utils/updates.js +24 -146
- package/src/ytype.js +626 -255
- package/tests/testHelper.js +10 -12
- package/dist/src/internals.d.ts +0 -36
- package/dist/src/internals.d.ts.map +0 -1
- package/dist/src/structs/ContentAny.d.ts +0 -67
- package/dist/src/structs/ContentAny.d.ts.map +0 -1
- package/dist/src/structs/ContentBinary.d.ts +0 -64
- package/dist/src/structs/ContentBinary.d.ts.map +0 -1
- package/dist/src/structs/ContentDeleted.d.ts +0 -64
- package/dist/src/structs/ContentDeleted.d.ts.map +0 -1
- package/dist/src/structs/ContentDoc.d.ts +0 -72
- package/dist/src/structs/ContentDoc.d.ts.map +0 -1
- package/dist/src/structs/ContentEmbed.d.ts +0 -67
- package/dist/src/structs/ContentEmbed.d.ts.map +0 -1
- package/dist/src/structs/ContentFormat.d.ts +0 -69
- package/dist/src/structs/ContentFormat.d.ts.map +0 -1
- package/dist/src/structs/ContentJSON.d.ts +0 -70
- package/dist/src/structs/ContentJSON.d.ts.map +0 -1
- package/dist/src/structs/ContentString.d.ts +0 -70
- package/dist/src/structs/ContentString.d.ts.map +0 -1
- package/dist/src/structs/ContentType.d.ts +0 -77
- package/dist/src/structs/ContentType.d.ts.map +0 -1
- package/dist/src/utils/AttributionManager.d.ts +0 -238
- package/dist/src/utils/AttributionManager.d.ts.map +0 -1
- package/dist/src/utils/IdMap.d.ts +0 -164
- package/dist/src/utils/IdMap.d.ts.map +0 -1
- package/dist/src/utils/IdSet.d.ts +0 -163
- package/dist/src/utils/IdSet.d.ts.map +0 -1
- package/dist/tests/IdMap.tests.d.ts +0 -9
- package/dist/tests/IdMap.tests.d.ts.map +0 -1
- package/dist/tests/IdSet.tests.d.ts +0 -9
- package/dist/tests/IdSet.tests.d.ts.map +0 -1
- package/dist/tests/attribution.tests.d.ts +0 -9
- package/dist/tests/attribution.tests.d.ts.map +0 -1
- package/dist/tests/compatibility.tests.d.ts +0 -5
- package/dist/tests/compatibility.tests.d.ts.map +0 -1
- package/dist/tests/delta.tests.d.ts +0 -7
- package/dist/tests/delta.tests.d.ts.map +0 -1
- package/dist/tests/doc.tests.d.ts +0 -13
- package/dist/tests/doc.tests.d.ts.map +0 -1
- package/dist/tests/encoding.tests.d.ts +0 -5
- package/dist/tests/encoding.tests.d.ts.map +0 -1
- package/dist/tests/index.d.ts +0 -2
- package/dist/tests/index.d.ts.map +0 -1
- package/dist/tests/relativePositions.tests.d.ts +0 -11
- package/dist/tests/relativePositions.tests.d.ts.map +0 -1
- package/dist/tests/snapshot.tests.d.ts +0 -14
- package/dist/tests/snapshot.tests.d.ts.map +0 -1
- package/dist/tests/testHelper.d.ts +0 -171
- package/dist/tests/testHelper.d.ts.map +0 -1
- package/dist/tests/undo-redo.tests.d.ts +0 -27
- package/dist/tests/undo-redo.tests.d.ts.map +0 -1
- package/dist/tests/updates.tests.d.ts +0 -26
- package/dist/tests/updates.tests.d.ts.map +0 -1
- package/dist/tests/y-array.tests.d.ts +0 -43
- package/dist/tests/y-array.tests.d.ts.map +0 -1
- package/dist/tests/y-map.tests.d.ts +0 -42
- package/dist/tests/y-map.tests.d.ts.map +0 -1
- package/dist/tests/y-text.tests.d.ts +0 -49
- package/dist/tests/y-text.tests.d.ts.map +0 -1
- package/dist/tests/y-xml.tests.d.ts +0 -14
- package/dist/tests/y-xml.tests.d.ts.map +0 -1
- package/src/internals.js +0 -35
- package/src/structs/ContentAny.js +0 -115
- package/src/structs/ContentBinary.js +0 -93
- package/src/structs/ContentDeleted.js +0 -101
- package/src/structs/ContentDoc.js +0 -141
- package/src/structs/ContentEmbed.js +0 -98
- package/src/structs/ContentFormat.js +0 -105
- package/src/structs/ContentJSON.js +0 -119
- package/src/structs/ContentString.js +0 -113
- package/src/structs/ContentType.js +0 -152
- package/src/utils/IdMap.js +0 -673
- package/src/utils/IdSet.js +0 -825
package/src/utils/ids.js
ADDED
|
@@ -0,0 +1,1527 @@
|
|
|
1
|
+
import * as math from 'lib0/math'
|
|
2
|
+
import * as traits from 'lib0/traits'
|
|
3
|
+
import * as encoding from 'lib0/encoding'
|
|
4
|
+
import * as decoding from 'lib0/decoding'
|
|
5
|
+
import * as buf from 'lib0/buffer'
|
|
6
|
+
import * as rabin from 'lib0/hash/rabin'
|
|
7
|
+
import * as array from 'lib0/array'
|
|
8
|
+
import * as map from 'lib0/map'
|
|
9
|
+
|
|
10
|
+
import { iterateStructs, findIndexSS, iterateStructsWithoutSplits, tryGc } from './transaction-helpers.js'
|
|
11
|
+
import { UpdateEncoderV2, IdSetEncoderV2 } from './UpdateEncoder.js'
|
|
12
|
+
import { IdSetDecoderV2 } from './UpdateDecoder.js'
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* @typedef {{ inserts: IdSet, deletes: IdSet }} ContentIds
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* @typedef {{ inserts: IdMap<any>, deletes: IdMap<any> }} ContentMap
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
export class IdRange {
|
|
23
|
+
/**
|
|
24
|
+
* @param {number} clock
|
|
25
|
+
* @param {number} len
|
|
26
|
+
*/
|
|
27
|
+
constructor (clock, len) {
|
|
28
|
+
/**
|
|
29
|
+
* @type {number}
|
|
30
|
+
*/
|
|
31
|
+
this.clock = clock
|
|
32
|
+
/**
|
|
33
|
+
* @type {number}
|
|
34
|
+
*/
|
|
35
|
+
this.len = len
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* @param {number} clock
|
|
40
|
+
* @param {number} len
|
|
41
|
+
*/
|
|
42
|
+
copyWith (clock, len) {
|
|
43
|
+
return new IdRange(clock, len)
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Helper method making this compatible with IdMap.
|
|
48
|
+
*
|
|
49
|
+
* @return {Array<ContentAttribute<any>>}
|
|
50
|
+
*/
|
|
51
|
+
get attrs () {
|
|
52
|
+
return []
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export class MaybeIdRange {
|
|
57
|
+
/**
|
|
58
|
+
* @param {number} clock
|
|
59
|
+
* @param {number} len
|
|
60
|
+
* @param {boolean} exists
|
|
61
|
+
*/
|
|
62
|
+
constructor (clock, len, exists) {
|
|
63
|
+
/**
|
|
64
|
+
* @type {number}
|
|
65
|
+
*/
|
|
66
|
+
this.clock = clock
|
|
67
|
+
/**
|
|
68
|
+
* @type {number}
|
|
69
|
+
*/
|
|
70
|
+
this.len = len
|
|
71
|
+
/**
|
|
72
|
+
* @type {boolean}
|
|
73
|
+
*/
|
|
74
|
+
this.exists = exists
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* @param {number} clock
|
|
80
|
+
* @param {number} len
|
|
81
|
+
* @param {boolean} exists
|
|
82
|
+
* @return {MaybeIdRange}
|
|
83
|
+
*/
|
|
84
|
+
export const createMaybeIdRange = (clock, len, exists) => new MaybeIdRange(clock, len, exists)
|
|
85
|
+
|
|
86
|
+
export class IdRanges {
|
|
87
|
+
/**
|
|
88
|
+
* @param {Array<IdRange>} ids
|
|
89
|
+
*/
|
|
90
|
+
constructor (ids) {
|
|
91
|
+
this.sorted = false
|
|
92
|
+
/**
|
|
93
|
+
* A typical use-case for IdSet is to append data. We heavily optimize this case by allowing the
|
|
94
|
+
* last item to be mutated if it isn't used currently.
|
|
95
|
+
* This flag is true if the last item was exposed to the outside.
|
|
96
|
+
*/
|
|
97
|
+
this._lastIsUsed = false
|
|
98
|
+
/**
|
|
99
|
+
* @private
|
|
100
|
+
*/
|
|
101
|
+
this._ids = ids
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
copy () {
|
|
105
|
+
const cpy = new IdRanges(this._ids.slice())
|
|
106
|
+
cpy.sorted = this.sorted
|
|
107
|
+
this._lastIsUsed = true
|
|
108
|
+
return cpy
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
/**
|
|
112
|
+
* @param {number} clock
|
|
113
|
+
* @param {number} length
|
|
114
|
+
*/
|
|
115
|
+
add (clock, length) {
|
|
116
|
+
const last = this._ids[this._ids.length - 1]
|
|
117
|
+
if (last != null && last.clock + last.len === clock) {
|
|
118
|
+
if (this._lastIsUsed) {
|
|
119
|
+
this._ids[this._ids.length - 1] = new IdRange(last.clock, last.len + length)
|
|
120
|
+
this._lastIsUsed = false
|
|
121
|
+
} else {
|
|
122
|
+
this._ids[this._ids.length - 1].len += length
|
|
123
|
+
}
|
|
124
|
+
} else {
|
|
125
|
+
this.sorted = false
|
|
126
|
+
this._ids.push(new IdRange(clock, length))
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Return the list of immutable id ranges, sorted and merged.
|
|
132
|
+
*/
|
|
133
|
+
getIds () {
|
|
134
|
+
const ids = this._ids
|
|
135
|
+
this._lastIsUsed = true
|
|
136
|
+
if (!this.sorted) {
|
|
137
|
+
this.sorted = true
|
|
138
|
+
ids.sort((a, b) => a.clock - b.clock)
|
|
139
|
+
// merge items without filtering or splicing the array
|
|
140
|
+
// i is the current pointer
|
|
141
|
+
// j refers to the current insert position for the pointed item
|
|
142
|
+
// try to merge dels[i] into dels[j-1] or set dels[j]=dels[i]
|
|
143
|
+
let i, j
|
|
144
|
+
for (i = 1, j = 1; i < ids.length; i++) {
|
|
145
|
+
const left = ids[j - 1]
|
|
146
|
+
const right = ids[i]
|
|
147
|
+
if (left.clock + left.len >= right.clock) {
|
|
148
|
+
const r = right.clock + right.len - left.clock
|
|
149
|
+
if (left.len < r) {
|
|
150
|
+
ids[j - 1] = new IdRange(left.clock, r)
|
|
151
|
+
}
|
|
152
|
+
} else if (left.len === 0) {
|
|
153
|
+
ids[j - 1] = right
|
|
154
|
+
} else {
|
|
155
|
+
if (j < i) {
|
|
156
|
+
ids[j] = right
|
|
157
|
+
}
|
|
158
|
+
j++
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
ids.length = ids[j - 1].len === 0 ? j - 1 : j
|
|
162
|
+
}
|
|
163
|
+
return ids
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* @implements {traits.EqualityTrait}
|
|
169
|
+
*/
|
|
170
|
+
export class IdSet {
|
|
171
|
+
constructor () {
|
|
172
|
+
/**
|
|
173
|
+
* @type {Map<number,IdRanges>}
|
|
174
|
+
*/
|
|
175
|
+
this.clients = new Map()
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
isEmpty () {
|
|
179
|
+
return this.clients.size === 0
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
/**
|
|
183
|
+
* @param {(idrange:IdRange, client:number) => void} f
|
|
184
|
+
*/
|
|
185
|
+
forEach (f) {
|
|
186
|
+
this.clients.forEach((ranges, client) => {
|
|
187
|
+
ranges.getIds().forEach((range) => {
|
|
188
|
+
f(range, client)
|
|
189
|
+
})
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/**
|
|
194
|
+
* @param {ID} id
|
|
195
|
+
* @return {boolean}
|
|
196
|
+
*/
|
|
197
|
+
hasId (id) {
|
|
198
|
+
return this.has(id.client, id.clock)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
/**
|
|
202
|
+
* @param {number} client
|
|
203
|
+
* @param {number} clock
|
|
204
|
+
*/
|
|
205
|
+
has (client, clock) {
|
|
206
|
+
const dr = this.clients.get(client)
|
|
207
|
+
if (dr) {
|
|
208
|
+
return findIndexInIdRanges(dr.getIds(), clock) !== null
|
|
209
|
+
}
|
|
210
|
+
return false
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
/**
|
|
214
|
+
* Return slices of ids that exist in this idset.
|
|
215
|
+
*
|
|
216
|
+
* @param {number} client
|
|
217
|
+
* @param {number} clock
|
|
218
|
+
* @param {number} len
|
|
219
|
+
* @return {Array<MaybeIdRange>}
|
|
220
|
+
*/
|
|
221
|
+
slice (client, clock, len) {
|
|
222
|
+
const dr = this.clients.get(client)
|
|
223
|
+
/**
|
|
224
|
+
* @type {Array<MaybeIdRange>}
|
|
225
|
+
*/
|
|
226
|
+
const res = []
|
|
227
|
+
if (dr) {
|
|
228
|
+
/**
|
|
229
|
+
* @type {Array<IdRange>}
|
|
230
|
+
*/
|
|
231
|
+
const ranges = dr.getIds()
|
|
232
|
+
let index = findRangeStartInIdRanges(ranges, clock)
|
|
233
|
+
if (index !== null) {
|
|
234
|
+
let prev = null
|
|
235
|
+
while (index < ranges.length) {
|
|
236
|
+
let r = ranges[index]
|
|
237
|
+
if (r.clock < clock) {
|
|
238
|
+
r = new IdRange(clock, r.len - (clock - r.clock))
|
|
239
|
+
}
|
|
240
|
+
if (r.clock + r.len > clock + len) {
|
|
241
|
+
r = new IdRange(r.clock, clock + len - r.clock)
|
|
242
|
+
}
|
|
243
|
+
if (r.len <= 0) break
|
|
244
|
+
const prevEnd = prev != null ? prev.clock + prev.len : clock
|
|
245
|
+
if (prevEnd < r.clock) {
|
|
246
|
+
res.push(createMaybeIdRange(prevEnd, r.clock - prevEnd, false))
|
|
247
|
+
}
|
|
248
|
+
prev = r
|
|
249
|
+
res.push(createMaybeIdRange(r.clock, r.len, true))
|
|
250
|
+
index++
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
if (res.length > 0) {
|
|
255
|
+
const last = res[res.length - 1]
|
|
256
|
+
const end = last.clock + last.len
|
|
257
|
+
if (end < clock + len) {
|
|
258
|
+
res.push(createMaybeIdRange(end, clock + len - end, false))
|
|
259
|
+
}
|
|
260
|
+
} else {
|
|
261
|
+
res.push(createMaybeIdRange(clock, len, false))
|
|
262
|
+
}
|
|
263
|
+
return res
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
/**
|
|
267
|
+
* @param {number} client
|
|
268
|
+
* @param {number} clock
|
|
269
|
+
* @param {number} len
|
|
270
|
+
*/
|
|
271
|
+
add (client, clock, len) {
|
|
272
|
+
if (len === 0) return
|
|
273
|
+
const idRanges = this.clients.get(client)
|
|
274
|
+
if (idRanges) {
|
|
275
|
+
idRanges.add(clock, len)
|
|
276
|
+
} else {
|
|
277
|
+
this.clients.set(client, new IdRanges([new IdRange(clock, len)]))
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* @param {number} client
|
|
283
|
+
* @param {number} clock
|
|
284
|
+
* @param {number} len
|
|
285
|
+
*/
|
|
286
|
+
delete (client, clock, len) {
|
|
287
|
+
_deleteRangeFromIdSet(this, client, clock, len)
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* @param {any} other
|
|
292
|
+
*/
|
|
293
|
+
[traits.EqualityTraitSymbol] (other) {
|
|
294
|
+
return equalIdSets(this, other)
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
/**
|
|
299
|
+
* @param {IdSet} ds1
|
|
300
|
+
* @param {IdSet} ds2
|
|
301
|
+
*/
|
|
302
|
+
export const equalIdSets = (ds1, ds2) => {
|
|
303
|
+
if (ds1.clients.size !== ds2.clients.size) return false
|
|
304
|
+
for (const [client, _deleteItems1] of ds1.clients.entries()) {
|
|
305
|
+
const deleteItems1 = _deleteItems1.getIds()
|
|
306
|
+
const deleteItems2 = ds2.clients.get(client)?.getIds()
|
|
307
|
+
if (deleteItems2 === undefined || deleteItems1.length !== deleteItems2.length) return false
|
|
308
|
+
for (let i = 0; i < deleteItems1.length; i++) {
|
|
309
|
+
const di1 = deleteItems1[i]
|
|
310
|
+
const di2 = deleteItems2[i]
|
|
311
|
+
if (di1.clock !== di2.clock || di1.len !== di2.len) {
|
|
312
|
+
return false
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return true
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
/**
|
|
320
|
+
* @param {IdSet | IdMap<any>} set
|
|
321
|
+
* @param {number} client
|
|
322
|
+
* @param {number} clock
|
|
323
|
+
* @param {number} len
|
|
324
|
+
*/
|
|
325
|
+
export const _deleteRangeFromIdSet = (set, client, clock, len) => {
|
|
326
|
+
const dr = set.clients.get(client)
|
|
327
|
+
if (dr && len > 0) {
|
|
328
|
+
const ids = dr.getIds()
|
|
329
|
+
let index = findRangeStartInIdRanges(ids, clock)
|
|
330
|
+
if (index != null) {
|
|
331
|
+
for (let r = ids[index]; index < ids.length && r.clock < clock + len; r = ids[++index]) {
|
|
332
|
+
if (r.clock < clock) {
|
|
333
|
+
ids[index] = r.copyWith(r.clock, clock - r.clock)
|
|
334
|
+
if (clock + len < r.clock + r.len) {
|
|
335
|
+
ids.splice(index + 1, 0, r.copyWith(clock + len, r.clock + r.len - clock - len))
|
|
336
|
+
}
|
|
337
|
+
} else if (clock + len < r.clock + r.len) {
|
|
338
|
+
// need to retain end
|
|
339
|
+
ids[index] = r.copyWith(clock + len, r.clock + r.len - clock - len)
|
|
340
|
+
} else if (ids.length === 1) {
|
|
341
|
+
set.clients.delete(client)
|
|
342
|
+
return
|
|
343
|
+
} else {
|
|
344
|
+
ids.splice(index--, 1)
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
/**
|
|
352
|
+
* Iterate over all structs that are mentioned by the IdSet.
|
|
353
|
+
*
|
|
354
|
+
* @param {Transaction} transaction
|
|
355
|
+
* @param {IdSet} ds
|
|
356
|
+
* @param {function(GC|Item):void} f
|
|
357
|
+
*
|
|
358
|
+
* @function
|
|
359
|
+
*/
|
|
360
|
+
export const iterateStructsByIdSet = (transaction, ds, f) =>
|
|
361
|
+
ds.clients.forEach((idRanges, clientid) => {
|
|
362
|
+
const ranges = idRanges.getIds()
|
|
363
|
+
const structs = /** @type {Array<GC|Item>} */ (transaction.doc.store.clients.get(clientid))
|
|
364
|
+
if (structs != null) {
|
|
365
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
366
|
+
const del = ranges[i]
|
|
367
|
+
iterateStructs(transaction, structs, del.clock, del.len, f)
|
|
368
|
+
}
|
|
369
|
+
}
|
|
370
|
+
})
|
|
371
|
+
|
|
372
|
+
/**
|
|
373
|
+
* Garbage-collect the deleted content referenced by `ids` on `doc`.
|
|
374
|
+
*
|
|
375
|
+
* This is useful to retroactively reclaim memory on a document created with `gc: false`
|
|
376
|
+
* (e.g. once a snapshot, or an UndoManager StackItem, that referenced this content is no
|
|
377
|
+
* longer needed) - without enabling gc for the whole document. Ids that reference live
|
|
378
|
+
* (non-deleted) content, already-collected structs, items flagged with `keep`, or items
|
|
379
|
+
* rejected by `gcFilter` are skipped.
|
|
380
|
+
*
|
|
381
|
+
* @param {Doc} doc
|
|
382
|
+
* @param {IdSet} ids
|
|
383
|
+
* @param {function(Item):boolean} [gcFilter]
|
|
384
|
+
*/
|
|
385
|
+
export const gcIdSet = (doc, ids, gcFilter = doc.gcFilter) =>
|
|
386
|
+
doc.transact(tr => {
|
|
387
|
+
// Split structs exactly at the IdSet boundaries so that only the referenced content is
|
|
388
|
+
// collected - an arbitrary IdSet need not align with struct boundaries.
|
|
389
|
+
iterateStructsByIdSet(tr, ids, () => {})
|
|
390
|
+
tryGc(tr, ids, gcFilter)
|
|
391
|
+
})
|
|
392
|
+
|
|
393
|
+
/**
|
|
394
|
+
* Iterate over all structs that are mentioned by the IdSet, without spliting the items.
|
|
395
|
+
*
|
|
396
|
+
* @param {StructStore} store
|
|
397
|
+
* @param {IdSet} ds
|
|
398
|
+
* @param {(struct: GC|Item|Skip, offset:number, len:number)=>void} f
|
|
399
|
+
*/
|
|
400
|
+
export const iterateStructsByIdSetWithoutSplits = (store, ds, f) =>
|
|
401
|
+
ds.clients.forEach((idRanges, clientid) => {
|
|
402
|
+
const ranges = idRanges.getIds()
|
|
403
|
+
const structs = /** @type {Array<GC|Item>} */ (store.clients.get(clientid))
|
|
404
|
+
if (structs != null) {
|
|
405
|
+
const lastStruct = structs[structs.length - 1]
|
|
406
|
+
const nextClock = lastStruct.id.clock + lastStruct.length
|
|
407
|
+
for (let i = 0; i < ranges.length; i++) {
|
|
408
|
+
const del = ranges[i]
|
|
409
|
+
if (del.clock < nextClock) {
|
|
410
|
+
iterateStructsWithoutSplits(structs, del.clock, del.len, f)
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
}
|
|
414
|
+
})
|
|
415
|
+
|
|
416
|
+
/**
|
|
417
|
+
* @param {Array<IdRange>} dis
|
|
418
|
+
* @param {number} clock
|
|
419
|
+
* @return {number|null}
|
|
420
|
+
*
|
|
421
|
+
* @private
|
|
422
|
+
* @function
|
|
423
|
+
*/
|
|
424
|
+
export const findIndexInIdRanges = (dis, clock) => {
|
|
425
|
+
let left = 0
|
|
426
|
+
let right = dis.length - 1
|
|
427
|
+
while (left <= right) {
|
|
428
|
+
const midindex = math.floor((left + right) / 2)
|
|
429
|
+
const mid = dis[midindex]
|
|
430
|
+
const midclock = mid.clock
|
|
431
|
+
if (midclock <= clock) {
|
|
432
|
+
if (clock < midclock + mid.len) {
|
|
433
|
+
return midindex
|
|
434
|
+
}
|
|
435
|
+
left = midindex + 1
|
|
436
|
+
} else {
|
|
437
|
+
right = midindex - 1
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
return null
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
/**
|
|
444
|
+
* Find the first range that contains clock or comes after clock.
|
|
445
|
+
*
|
|
446
|
+
* @param {Array<IdRange>} dis
|
|
447
|
+
* @param {number} clock
|
|
448
|
+
* @return {number|null}
|
|
449
|
+
*
|
|
450
|
+
* @private
|
|
451
|
+
* @function
|
|
452
|
+
*/
|
|
453
|
+
export const findRangeStartInIdRanges = (dis, clock) => {
|
|
454
|
+
let left = 0
|
|
455
|
+
let right = dis.length - 1
|
|
456
|
+
while (left <= right) {
|
|
457
|
+
const midindex = math.floor((left + right) / 2)
|
|
458
|
+
const mid = dis[midindex]
|
|
459
|
+
const midclock = mid.clock
|
|
460
|
+
if (midclock <= clock) {
|
|
461
|
+
if (clock < midclock + mid.len) {
|
|
462
|
+
return midindex
|
|
463
|
+
}
|
|
464
|
+
left = midindex + 1
|
|
465
|
+
} else {
|
|
466
|
+
right = midindex - 1
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
return left < dis.length ? left : null
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
/**
|
|
473
|
+
* @param {Array<IdSet>} idSets
|
|
474
|
+
* @return {IdSet} A fresh IdSet
|
|
475
|
+
*/
|
|
476
|
+
export const mergeIdSets = idSets => {
|
|
477
|
+
const merged = new IdSet()
|
|
478
|
+
for (let dssI = 0; dssI < idSets.length; dssI++) {
|
|
479
|
+
idSets[dssI].clients.forEach((rangesLeft, client) => {
|
|
480
|
+
if (!merged.clients.has(client)) {
|
|
481
|
+
// Write all missing keys from current ds and all following.
|
|
482
|
+
// If merged already contains `client` current ds has already been added.
|
|
483
|
+
const ids = rangesLeft.getIds().slice()
|
|
484
|
+
for (let i = dssI + 1; i < idSets.length; i++) {
|
|
485
|
+
const nextIds = idSets[i].clients.get(client)
|
|
486
|
+
if (nextIds) {
|
|
487
|
+
array.appendTo(ids, nextIds.getIds())
|
|
488
|
+
}
|
|
489
|
+
}
|
|
490
|
+
merged.clients.set(client, new IdRanges(ids))
|
|
491
|
+
}
|
|
492
|
+
})
|
|
493
|
+
}
|
|
494
|
+
return merged
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
/**
|
|
498
|
+
* @template {IdSet | IdMap<any>} S
|
|
499
|
+
* @param {S} dest
|
|
500
|
+
* @param {S} src
|
|
501
|
+
*/
|
|
502
|
+
const _insertIntoIdSet = (dest, src) => {
|
|
503
|
+
src.clients.forEach((srcRanges, client) => {
|
|
504
|
+
const targetRanges = dest.clients.get(client)
|
|
505
|
+
if (targetRanges) {
|
|
506
|
+
array.appendTo(targetRanges.getIds(), srcRanges.getIds())
|
|
507
|
+
targetRanges.sorted = false
|
|
508
|
+
} else {
|
|
509
|
+
// order first
|
|
510
|
+
srcRanges.getIds()
|
|
511
|
+
dest.clients.set(client, /** @type {any} */ (srcRanges.copy()))
|
|
512
|
+
}
|
|
513
|
+
})
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
/**
|
|
517
|
+
* @param {IdSet} dest
|
|
518
|
+
* @param {IdSet} src
|
|
519
|
+
*/
|
|
520
|
+
export const insertIntoIdSet = _insertIntoIdSet
|
|
521
|
+
|
|
522
|
+
/**
|
|
523
|
+
* @param {IdMap<any>} dest
|
|
524
|
+
* @param {IdMap<any>|IdSet} src
|
|
525
|
+
*/
|
|
526
|
+
export const insertIntoIdMap = _insertIntoIdSet
|
|
527
|
+
|
|
528
|
+
/**
|
|
529
|
+
* @todo rename to excludeIdSet | excludeIdMap
|
|
530
|
+
*
|
|
531
|
+
* Remove all ranges from `exclude` from `ds`. The result is a fresh IdSet containing all ranges from `idSet` that are not
|
|
532
|
+
* in `exclude`.
|
|
533
|
+
*
|
|
534
|
+
* @template {IdSet | IdMap<any>} Set
|
|
535
|
+
* @param {Set} set
|
|
536
|
+
* @param {IdSet | IdMap<any>} exclude
|
|
537
|
+
* @return {Set}
|
|
538
|
+
*/
|
|
539
|
+
export const _diffSet = (set, exclude) => {
|
|
540
|
+
/**
|
|
541
|
+
* @type {Set}
|
|
542
|
+
*/
|
|
543
|
+
const res = /** @type {any } */ (set instanceof IdSet ? new IdSet() : new IdMap())
|
|
544
|
+
const Ranges = set instanceof IdSet ? IdRanges : AttrRanges
|
|
545
|
+
set.clients.forEach((_setRanges, client) => {
|
|
546
|
+
/**
|
|
547
|
+
* @type {Array<IdRange>}
|
|
548
|
+
*/
|
|
549
|
+
let resRanges = []
|
|
550
|
+
const _excludedRanges = exclude.clients.get(client)
|
|
551
|
+
const setRanges = _setRanges.getIds()
|
|
552
|
+
if (_excludedRanges == null) {
|
|
553
|
+
resRanges = setRanges.slice()
|
|
554
|
+
} else {
|
|
555
|
+
const excludedRanges = _excludedRanges.getIds()
|
|
556
|
+
let i = 0; let j = 0
|
|
557
|
+
let currRange = setRanges[0]
|
|
558
|
+
while (i < setRanges.length && j < excludedRanges.length) {
|
|
559
|
+
const e = excludedRanges[j]
|
|
560
|
+
if (currRange.clock + currRange.len <= e.clock) { // no overlapping, use next range item
|
|
561
|
+
if (currRange.len > 0) resRanges.push(currRange)
|
|
562
|
+
currRange = setRanges[++i]
|
|
563
|
+
} else if (e.clock + e.len <= currRange.clock) { // no overlapping, use next excluded item
|
|
564
|
+
j++
|
|
565
|
+
} else if (e.clock <= currRange.clock) { // exclude laps into range (we already know that the ranges somehow collide)
|
|
566
|
+
const newClock = e.clock + e.len
|
|
567
|
+
const newLen = currRange.clock + currRange.len - newClock
|
|
568
|
+
if (newLen > 0) {
|
|
569
|
+
currRange = currRange.copyWith(newClock, newLen)
|
|
570
|
+
j++
|
|
571
|
+
} else {
|
|
572
|
+
// this item is completely overwritten. len=0. We can jump to the next range
|
|
573
|
+
currRange = setRanges[++i]
|
|
574
|
+
}
|
|
575
|
+
} else { // currRange.clock < e.clock -- range laps into exclude => adjust len
|
|
576
|
+
// beginning can't be empty, add it to the result
|
|
577
|
+
const nextLen = e.clock - currRange.clock
|
|
578
|
+
resRanges.push(currRange.copyWith(currRange.clock, nextLen))
|
|
579
|
+
// retain the remaining length after exclude in currRange
|
|
580
|
+
currRange = currRange.copyWith(currRange.clock + e.len + nextLen, math.max(currRange.len - e.len - nextLen, 0))
|
|
581
|
+
if (currRange.len === 0) currRange = setRanges[++i]
|
|
582
|
+
else j++
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
if (currRange != null) {
|
|
586
|
+
resRanges.push(currRange)
|
|
587
|
+
}
|
|
588
|
+
i++
|
|
589
|
+
while (i < setRanges.length) {
|
|
590
|
+
resRanges.push(setRanges[i++])
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
// @ts-ignore
|
|
594
|
+
if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges)))
|
|
595
|
+
})
|
|
596
|
+
return res
|
|
597
|
+
}
|
|
598
|
+
|
|
599
|
+
/**
|
|
600
|
+
* Remove all ranges from `exclude` from `idSet`. The result is a fresh IdSet containing all ranges from `idSet` that are not
|
|
601
|
+
* in `exclude`.
|
|
602
|
+
*
|
|
603
|
+
* @type {(idSet: IdSet, exclude: IdSet|IdMap<any>) => IdSet}
|
|
604
|
+
*/
|
|
605
|
+
export const diffIdSet = _diffSet
|
|
606
|
+
|
|
607
|
+
/**
|
|
608
|
+
* @template {IdSet | IdMap<any>} SetA
|
|
609
|
+
* @template {IdSet | IdMap<any>} SetB
|
|
610
|
+
* @param {SetA} setA
|
|
611
|
+
* @param {SetB} setB
|
|
612
|
+
* @return {SetA extends IdMap<infer A> ? (SetB extends IdMap<infer B> ? IdMap<A | B> : IdMap<A>) : IdSet}
|
|
613
|
+
*/
|
|
614
|
+
export const _intersectSets = (setA, setB) => {
|
|
615
|
+
/**
|
|
616
|
+
* @type {IdMap<any> | IdSet}
|
|
617
|
+
*/
|
|
618
|
+
const res = /** @type {any } */ (setA instanceof IdSet ? new IdSet() : new IdMap())
|
|
619
|
+
const Ranges = setA instanceof IdSet ? IdRanges : AttrRanges
|
|
620
|
+
setA.clients.forEach((_aRanges, client) => {
|
|
621
|
+
/**
|
|
622
|
+
* @type {Array<IdRange>}
|
|
623
|
+
*/
|
|
624
|
+
const resRanges = []
|
|
625
|
+
const _bRanges = setB.clients.get(client)
|
|
626
|
+
const aRanges = _aRanges.getIds()
|
|
627
|
+
if (_bRanges != null) {
|
|
628
|
+
const bRanges = _bRanges.getIds()
|
|
629
|
+
for (let a = 0, b = 0; a < aRanges.length && b < bRanges.length;) {
|
|
630
|
+
const aRange = aRanges[a]
|
|
631
|
+
const bRange = bRanges[b]
|
|
632
|
+
// construct overlap
|
|
633
|
+
const clock = math.max(aRange.clock, bRange.clock)
|
|
634
|
+
const len = math.min(aRange.len - (clock - aRange.clock), bRange.len - (clock - bRange.clock))
|
|
635
|
+
if (len > 0) {
|
|
636
|
+
resRanges.push(aRange instanceof AttrRange
|
|
637
|
+
? new AttrRange(clock, len, /** @type {Array<any>} */ (aRange.attrs).concat(bRange.attrs))
|
|
638
|
+
: new IdRange(clock, len)
|
|
639
|
+
)
|
|
640
|
+
}
|
|
641
|
+
if (aRange.clock + aRange.len < bRange.clock + bRange.len) {
|
|
642
|
+
a++
|
|
643
|
+
} else {
|
|
644
|
+
b++
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
}
|
|
648
|
+
// @ts-ignore
|
|
649
|
+
if (resRanges.length > 0) res.clients.set(client, /** @type {any} */ (new Ranges(resRanges)))
|
|
650
|
+
})
|
|
651
|
+
return /** @type {any} */ (res)
|
|
652
|
+
}
|
|
653
|
+
|
|
654
|
+
export const intersectSets = _intersectSets
|
|
655
|
+
|
|
656
|
+
export const createIdSet = () => new IdSet()
|
|
657
|
+
|
|
658
|
+
/**
|
|
659
|
+
* @param {StructStore} ss
|
|
660
|
+
* @return {IdSet}
|
|
661
|
+
*
|
|
662
|
+
* @private
|
|
663
|
+
* @function
|
|
664
|
+
*/
|
|
665
|
+
export const createDeleteSetFromStructStore = ss => {
|
|
666
|
+
const ds = createIdSet()
|
|
667
|
+
ss.clients.forEach((structs, client) => {
|
|
668
|
+
/**
|
|
669
|
+
* @type {Array<IdRange>}
|
|
670
|
+
*/
|
|
671
|
+
const dsitems = []
|
|
672
|
+
for (let i = 0; i < structs.length; i++) {
|
|
673
|
+
const struct = structs[i]
|
|
674
|
+
if (struct.deleted) {
|
|
675
|
+
const clock = struct.id.clock
|
|
676
|
+
let len = struct.length
|
|
677
|
+
if (i + 1 < structs.length) {
|
|
678
|
+
for (let next = structs[i + 1]; i + 1 < structs.length && next.deleted; next = structs[++i + 1]) {
|
|
679
|
+
len += next.length
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
dsitems.push(new IdRange(clock, len))
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
if (dsitems.length > 0) {
|
|
686
|
+
ds.clients.set(client, new IdRanges(dsitems))
|
|
687
|
+
}
|
|
688
|
+
})
|
|
689
|
+
return ds
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
/**
|
|
693
|
+
* @param {Array<GC | Item | Skip>} structs
|
|
694
|
+
* @param {boolean} filterDeleted
|
|
695
|
+
*
|
|
696
|
+
*/
|
|
697
|
+
export const _createInsertSliceFromStructs = (structs, filterDeleted) => {
|
|
698
|
+
/**
|
|
699
|
+
* @type {Array<IdRange>}
|
|
700
|
+
*/
|
|
701
|
+
const iditems = []
|
|
702
|
+
for (let i = 0; i < structs.length; i++) {
|
|
703
|
+
const struct = structs[i]
|
|
704
|
+
if (!(filterDeleted && struct.deleted)) {
|
|
705
|
+
const clock = struct.id.clock
|
|
706
|
+
let len = struct.length
|
|
707
|
+
if (i + 1 < structs.length) {
|
|
708
|
+
// eslint-disable-next-line
|
|
709
|
+
for (let next = structs[i + 1]; i + 1 < structs.length && !(filterDeleted && next.deleted); next = structs[++i + 1]) {
|
|
710
|
+
len += next.length
|
|
711
|
+
}
|
|
712
|
+
}
|
|
713
|
+
iditems.push(new IdRange(clock, len))
|
|
714
|
+
}
|
|
715
|
+
}
|
|
716
|
+
return iditems
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
/**
|
|
720
|
+
* @param {StructStore} ss
|
|
721
|
+
* @param {boolean} filterDeleted
|
|
722
|
+
*/
|
|
723
|
+
export const createInsertSetFromStructStore = (ss, filterDeleted) => {
|
|
724
|
+
const idset = createIdSet()
|
|
725
|
+
ss.clients.forEach((structs, client) => {
|
|
726
|
+
const iditems = _createInsertSliceFromStructs(structs, filterDeleted)
|
|
727
|
+
if (iditems.length !== 0) {
|
|
728
|
+
idset.clients.set(client, new IdRanges(iditems))
|
|
729
|
+
}
|
|
730
|
+
})
|
|
731
|
+
return idset
|
|
732
|
+
}
|
|
733
|
+
|
|
734
|
+
/**
|
|
735
|
+
* @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
|
|
736
|
+
* @param {IdSet} idSet
|
|
737
|
+
*
|
|
738
|
+
* @private
|
|
739
|
+
* @function
|
|
740
|
+
*/
|
|
741
|
+
export const writeIdSet = (encoder, idSet) => {
|
|
742
|
+
encoding.writeVarUint(encoder.restEncoder, idSet.clients.size)
|
|
743
|
+
// Ensure that the delete set is written in a deterministic order
|
|
744
|
+
array.from(idSet.clients.entries())
|
|
745
|
+
.sort((a, b) => b[0] - a[0])
|
|
746
|
+
.forEach(([client, _idRanges]) => {
|
|
747
|
+
const idRanges = _idRanges.getIds()
|
|
748
|
+
encoder.resetIdSetCurVal()
|
|
749
|
+
encoding.writeVarUint(encoder.restEncoder, client)
|
|
750
|
+
const len = idRanges.length
|
|
751
|
+
encoding.writeVarUint(encoder.restEncoder, len)
|
|
752
|
+
for (let i = 0; i < len; i++) {
|
|
753
|
+
const item = idRanges[i]
|
|
754
|
+
encoder.writeIdSetClock(item.clock)
|
|
755
|
+
encoder.writeIdSetLen(item.len)
|
|
756
|
+
}
|
|
757
|
+
})
|
|
758
|
+
}
|
|
759
|
+
|
|
760
|
+
/**
|
|
761
|
+
* @param {IdSetDecoderV1 | IdSetDecoderV2} decoder
|
|
762
|
+
* @return {IdSet}
|
|
763
|
+
*
|
|
764
|
+
* @private
|
|
765
|
+
* @function
|
|
766
|
+
*/
|
|
767
|
+
export const readIdSet = decoder => {
|
|
768
|
+
const ds = new IdSet()
|
|
769
|
+
const numClients = decoding.readVarUint(decoder.restDecoder)
|
|
770
|
+
for (let i = 0; i < numClients; i++) {
|
|
771
|
+
decoder.resetDsCurVal()
|
|
772
|
+
const client = decoding.readVarUint(decoder.restDecoder)
|
|
773
|
+
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
|
|
774
|
+
if (numberOfDeletes > 0) {
|
|
775
|
+
/**
|
|
776
|
+
* @type {Array<IdRange>}
|
|
777
|
+
*/
|
|
778
|
+
const dsRanges = []
|
|
779
|
+
for (let i = 0; i < numberOfDeletes; i++) {
|
|
780
|
+
dsRanges.push(new IdRange(decoder.readDsClock(), decoder.readDsLen()))
|
|
781
|
+
}
|
|
782
|
+
ds.clients.set(client, new IdRanges(dsRanges))
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
return ds
|
|
786
|
+
}
|
|
787
|
+
|
|
788
|
+
/**
|
|
789
|
+
* @param {IdSet} idSet
|
|
790
|
+
* @return {Uint8Array<ArrayBuffer>}
|
|
791
|
+
*/
|
|
792
|
+
export const encodeIdSet = idSet => {
|
|
793
|
+
const encoder = new IdSetEncoderV2()
|
|
794
|
+
writeIdSet(encoder, idSet)
|
|
795
|
+
return encoder.toUint8Array()
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
/**
|
|
799
|
+
* @param {Uint8Array} data
|
|
800
|
+
* @return {IdSet}
|
|
801
|
+
*/
|
|
802
|
+
export const decodeIdSet = data => readIdSet(new IdSetDecoderV2(decoding.createDecoder(data)))
|
|
803
|
+
|
|
804
|
+
/**
|
|
805
|
+
* @todo YDecoder also contains references to String and other Decoders. Would make sense to exchange YDecoder.toUint8Array for YDecoder.DsToUint8Array()..
|
|
806
|
+
*/
|
|
807
|
+
|
|
808
|
+
/**
|
|
809
|
+
* @param {IdSetDecoderV1 | IdSetDecoderV2} decoder
|
|
810
|
+
* @param {Transaction} transaction
|
|
811
|
+
* @param {StructStore} store
|
|
812
|
+
* @return {Uint8Array<ArrayBuffer>|null} Returns a v2 update containing all deletes that couldn't be applied yet; or null if all deletes were applied successfully.
|
|
813
|
+
*
|
|
814
|
+
* @private
|
|
815
|
+
* @function
|
|
816
|
+
*/
|
|
817
|
+
export const readAndApplyDeleteSet = (decoder, transaction, store) => {
|
|
818
|
+
const unappliedDS = new IdSet()
|
|
819
|
+
const numClients = decoding.readVarUint(decoder.restDecoder)
|
|
820
|
+
for (let i = 0; i < numClients; i++) {
|
|
821
|
+
decoder.resetDsCurVal()
|
|
822
|
+
const client = decoding.readVarUint(decoder.restDecoder)
|
|
823
|
+
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
|
|
824
|
+
const structs = store.clients.get(client) || []
|
|
825
|
+
const state = store.getClock(client)
|
|
826
|
+
for (let i = 0; i < numberOfDeletes; i++) {
|
|
827
|
+
const clock = decoder.readDsClock()
|
|
828
|
+
const clockEnd = clock + decoder.readDsLen()
|
|
829
|
+
if (clock < state) {
|
|
830
|
+
if (state < clockEnd) {
|
|
831
|
+
unappliedDS.add(client, state, clockEnd - state)
|
|
832
|
+
}
|
|
833
|
+
let index = findIndexSS(structs, clock)
|
|
834
|
+
/**
|
|
835
|
+
* We can ignore the case of GC and Delete structs, because we are going to skip them
|
|
836
|
+
* @type {Item | GC | Skip}
|
|
837
|
+
*/
|
|
838
|
+
let struct = structs[index]
|
|
839
|
+
// split the first item if necessary
|
|
840
|
+
if (!struct.deleted && struct.id.clock < clock && struct.isItem) {
|
|
841
|
+
// increment index, we now want to use the next struct
|
|
842
|
+
structs.splice(++index, 0, /** @type {Item} */ (struct).split(transaction, clock - struct.id.clock))
|
|
843
|
+
}
|
|
844
|
+
while (index < structs.length) {
|
|
845
|
+
// @ts-ignore
|
|
846
|
+
struct = structs[index++]
|
|
847
|
+
if (struct.id.clock < clockEnd) {
|
|
848
|
+
if (!struct.deleted) {
|
|
849
|
+
if (struct.isItem) {
|
|
850
|
+
if (clockEnd < struct.id.clock + struct.length) {
|
|
851
|
+
structs.splice(index, 0, /** @type {Item} */ (struct).split(transaction, clockEnd - struct.id.clock))
|
|
852
|
+
}
|
|
853
|
+
struct.delete(transaction)
|
|
854
|
+
} else { // is a Skip - add range to unappliedDS
|
|
855
|
+
const c = math.max(struct.id.clock, clock)
|
|
856
|
+
unappliedDS.add(client, c, math.min(struct.length, clockEnd - c))
|
|
857
|
+
}
|
|
858
|
+
}
|
|
859
|
+
} else {
|
|
860
|
+
break
|
|
861
|
+
}
|
|
862
|
+
}
|
|
863
|
+
} else {
|
|
864
|
+
unappliedDS.add(client, clock, clockEnd - clock)
|
|
865
|
+
}
|
|
866
|
+
}
|
|
867
|
+
}
|
|
868
|
+
if (unappliedDS.clients.size > 0) {
|
|
869
|
+
const ds = new UpdateEncoderV2()
|
|
870
|
+
encoding.writeVarUint(ds.restEncoder, 0) // encode 0 structs
|
|
871
|
+
writeIdSet(ds, unappliedDS)
|
|
872
|
+
return ds.toUint8Array()
|
|
873
|
+
}
|
|
874
|
+
return null
|
|
875
|
+
}
|
|
876
|
+
|
|
877
|
+
/**
|
|
878
|
+
* @template Attrs
|
|
879
|
+
*/
|
|
880
|
+
export class AttrRange {
|
|
881
|
+
/**
|
|
882
|
+
* @param {number} clock
|
|
883
|
+
* @param {number} len
|
|
884
|
+
* @param {Array<ContentAttribute<Attrs>>} attrs
|
|
885
|
+
*/
|
|
886
|
+
constructor (clock, len, attrs) {
|
|
887
|
+
/**
|
|
888
|
+
* @readonly
|
|
889
|
+
*/
|
|
890
|
+
this.clock = clock
|
|
891
|
+
/**
|
|
892
|
+
* @readonly
|
|
893
|
+
*/
|
|
894
|
+
this.len = len
|
|
895
|
+
/**
|
|
896
|
+
* @readonly
|
|
897
|
+
*/
|
|
898
|
+
this.attrs = attrs
|
|
899
|
+
}
|
|
900
|
+
|
|
901
|
+
/**
|
|
902
|
+
* @param {number} clock
|
|
903
|
+
* @param {number} len
|
|
904
|
+
*/
|
|
905
|
+
copyWith (clock, len) {
|
|
906
|
+
return new AttrRange(clock, len, this.attrs)
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
/**
|
|
911
|
+
* @todo rename this to `Attribute`
|
|
912
|
+
* @template V
|
|
913
|
+
*/
|
|
914
|
+
export class ContentAttribute {
|
|
915
|
+
/**
|
|
916
|
+
* @param {string} name
|
|
917
|
+
* @param {V} val
|
|
918
|
+
*/
|
|
919
|
+
constructor (name, val) {
|
|
920
|
+
this.name = name
|
|
921
|
+
this.val = val
|
|
922
|
+
}
|
|
923
|
+
|
|
924
|
+
hash () {
|
|
925
|
+
const encoder = encoding.createEncoder()
|
|
926
|
+
encoding.writeVarString(encoder, this.name)
|
|
927
|
+
encoding.writeAny(encoder, /** @type {any} */ (this.val))
|
|
928
|
+
return buf.toBase64(rabin.fingerprint(rabin.StandardIrreducible128, encoding.toUint8Array(encoder)))
|
|
929
|
+
}
|
|
930
|
+
}
|
|
931
|
+
|
|
932
|
+
/**
|
|
933
|
+
* @template V
|
|
934
|
+
* @param {string} name
|
|
935
|
+
* @param {V} val
|
|
936
|
+
* @return {ContentAttribute<V>}
|
|
937
|
+
*/
|
|
938
|
+
export const createContentAttribute = (name, val) => new ContentAttribute(name, val)
|
|
939
|
+
|
|
940
|
+
/**
|
|
941
|
+
* @template Attrs
|
|
942
|
+
* @typedef {{ clock: number, len: number, attrs: Array<ContentAttribute<Attrs>>? }} MaybeAttrRange
|
|
943
|
+
*/
|
|
944
|
+
|
|
945
|
+
/**
|
|
946
|
+
* @template Attrs
|
|
947
|
+
*
|
|
948
|
+
* @param {number} clock
|
|
949
|
+
* @param {number} len
|
|
950
|
+
* @param {Array<ContentAttribute<Attrs>>?} attrs
|
|
951
|
+
* @return {MaybeAttrRange<Attrs>}
|
|
952
|
+
*/
|
|
953
|
+
export const createMaybeAttrRange = (clock, len, attrs) => new AttrRange(clock, len, /** @type {any} */ (attrs))
|
|
954
|
+
|
|
955
|
+
/**
|
|
956
|
+
* @template T
|
|
957
|
+
* @param {Array<T>} a
|
|
958
|
+
* @param {Array<T>} b
|
|
959
|
+
*/
|
|
960
|
+
const idmapAttrRangeJoin = (a, b) => a.concat(b.filter(attr => !idmapAttrsHas(a, attr)))
|
|
961
|
+
|
|
962
|
+
/**
|
|
963
|
+
* Whenever this is instantiated, it must receive a fresh array of ops, not something copied.
|
|
964
|
+
*
|
|
965
|
+
* @template Attrs
|
|
966
|
+
*/
|
|
967
|
+
export class AttrRanges {
|
|
968
|
+
/**
|
|
969
|
+
* @param {Array<AttrRange<Attrs>>} ids
|
|
970
|
+
*/
|
|
971
|
+
constructor (ids) {
|
|
972
|
+
this.sorted = false
|
|
973
|
+
/**
|
|
974
|
+
* @private
|
|
975
|
+
*/
|
|
976
|
+
this._ids = ids
|
|
977
|
+
}
|
|
978
|
+
|
|
979
|
+
copy () {
|
|
980
|
+
const cpy = new AttrRanges(this._ids.slice())
|
|
981
|
+
cpy.sorted = this.sorted
|
|
982
|
+
return cpy
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
/**
|
|
986
|
+
* @param {number} clock
|
|
987
|
+
* @param {number} length
|
|
988
|
+
* @param {Array<ContentAttribute<Attrs>>} attrs
|
|
989
|
+
*/
|
|
990
|
+
add (clock, length, attrs) {
|
|
991
|
+
if (length === 0) return
|
|
992
|
+
this.sorted = false
|
|
993
|
+
this._ids.push(new AttrRange(clock, length, attrs))
|
|
994
|
+
}
|
|
995
|
+
|
|
996
|
+
/**
|
|
997
|
+
* Return the list of id ranges, sorted and merged.
|
|
998
|
+
*/
|
|
999
|
+
getIds () {
|
|
1000
|
+
const ids = this._ids
|
|
1001
|
+
if (!this.sorted) {
|
|
1002
|
+
this.sorted = true
|
|
1003
|
+
ids.sort((a, b) => a.clock - b.clock)
|
|
1004
|
+
/**
|
|
1005
|
+
* algorithm thoughts:
|
|
1006
|
+
* - sort (by clock AND by length), bigger length is to the right (or not, we can't make
|
|
1007
|
+
* assumptions abouth length after long length has been split)
|
|
1008
|
+
* -- maybe better: sort by clock+length. Then split items from right to left. This way, items are always
|
|
1009
|
+
* in the right order. But I also need to swap if left items is smaller after split
|
|
1010
|
+
* --- thought: there is no way to go around swapping. Unless, for each item from left to
|
|
1011
|
+
* right, when I have to split because one of the look-ahead items is overlapping, i split
|
|
1012
|
+
* it and merge the attributes into the following ones (that I also need to split). Best is
|
|
1013
|
+
* probably left to right with lookahead.
|
|
1014
|
+
* - left to right, split overlapping items so that we can make the assumption that either an
|
|
1015
|
+
* item is overlapping with the next 1-on-1 or it is not overlapping at all (when splitting,
|
|
1016
|
+
* we can already incorporate the attributes)
|
|
1017
|
+
* -- better: for each item, go left to right and add own attributes to overlapping items.
|
|
1018
|
+
* Split them if necessary. After split, i must insert the retainer at a valid position.
|
|
1019
|
+
* - merge items if neighbor has same attributes
|
|
1020
|
+
*/
|
|
1021
|
+
for (let i = 0; i < ids.length - 1;) {
|
|
1022
|
+
const range = ids[i]
|
|
1023
|
+
const nextRange = ids[i + 1]
|
|
1024
|
+
// find out how to split range. it must match with next range.
|
|
1025
|
+
// 1) we have space. Split if necessary.
|
|
1026
|
+
// 2) concat attributes in range to the next range. Split range and splice the remainder at
|
|
1027
|
+
// the correct position.
|
|
1028
|
+
if (range.clock < nextRange.clock) { // might need to split range
|
|
1029
|
+
if (range.clock + range.len > nextRange.clock) {
|
|
1030
|
+
// is overlapping
|
|
1031
|
+
const diff = nextRange.clock - range.clock
|
|
1032
|
+
ids[i] = new AttrRange(range.clock, diff, range.attrs)
|
|
1033
|
+
ids.splice(i + 1, 0, new AttrRange(nextRange.clock, range.len - diff, range.attrs))
|
|
1034
|
+
}
|
|
1035
|
+
i++
|
|
1036
|
+
continue
|
|
1037
|
+
}
|
|
1038
|
+
// now we know that range.clock === nextRange.clock
|
|
1039
|
+
// merge range with nextRange
|
|
1040
|
+
const largerRange = range.len > nextRange.len ? range : nextRange
|
|
1041
|
+
const smallerLen = range.len < nextRange.len ? range.len : nextRange.len
|
|
1042
|
+
ids[i] = new AttrRange(range.clock, smallerLen, idmapAttrRangeJoin(range.attrs, nextRange.attrs))
|
|
1043
|
+
if (range.len === nextRange.len) {
|
|
1044
|
+
ids.splice(i + 1, 1)
|
|
1045
|
+
} else {
|
|
1046
|
+
ids[i + 1] = new AttrRange(range.clock + smallerLen, largerRange.len - smallerLen, largerRange.attrs)
|
|
1047
|
+
array.bubblesortItem(ids, i + 1, (a, b) => a.clock - b.clock)
|
|
1048
|
+
}
|
|
1049
|
+
if (smallerLen === 0) i++
|
|
1050
|
+
}
|
|
1051
|
+
while (ids.length > 0 && ids[0].len === 0) {
|
|
1052
|
+
ids.splice(0, 1)
|
|
1053
|
+
}
|
|
1054
|
+
// merge items without filtering or splicing the array.
|
|
1055
|
+
// i is the current pointer
|
|
1056
|
+
// j refers to the current insert position for the pointed item
|
|
1057
|
+
// try to merge dels[i] into dels[j-1] or set dels[j]=dels[i]
|
|
1058
|
+
let i, j
|
|
1059
|
+
for (i = 1, j = 1; i < ids.length; i++) {
|
|
1060
|
+
const left = ids[j - 1]
|
|
1061
|
+
const right = ids[i]
|
|
1062
|
+
if (left.clock + left.len === right.clock && idmapAttrsEqual(left.attrs, right.attrs)) {
|
|
1063
|
+
ids[j - 1] = new AttrRange(left.clock, left.len + right.len, left.attrs)
|
|
1064
|
+
} else if (right.len !== 0) {
|
|
1065
|
+
if (j < i) {
|
|
1066
|
+
ids[j] = right
|
|
1067
|
+
}
|
|
1068
|
+
j++
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
ids.length = ids.length === 0 ? 0 : (ids[j - 1].len === 0 ? j - 1 : j)
|
|
1072
|
+
}
|
|
1073
|
+
return ids
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
1076
|
+
|
|
1077
|
+
/**
|
|
1078
|
+
* @template Attrs
|
|
1079
|
+
*/
|
|
1080
|
+
export class IdMap {
|
|
1081
|
+
constructor () {
|
|
1082
|
+
/**
|
|
1083
|
+
* @type {Map<number,AttrRanges<Attrs>>}
|
|
1084
|
+
*/
|
|
1085
|
+
this.clients = new Map()
|
|
1086
|
+
/**
|
|
1087
|
+
* @type {Map<string, ContentAttribute<Attrs>>}
|
|
1088
|
+
*/
|
|
1089
|
+
this.attrsH = new Map()
|
|
1090
|
+
/**
|
|
1091
|
+
* @type {Set<ContentAttribute<Attrs>>}
|
|
1092
|
+
*/
|
|
1093
|
+
this.attrs = new Set()
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
/**
|
|
1097
|
+
* @param {(attrRange:AttrRange<Attrs>, client:number) => void} f
|
|
1098
|
+
*/
|
|
1099
|
+
forEach (f) {
|
|
1100
|
+
this.clients.forEach((ranges, client) => {
|
|
1101
|
+
ranges.getIds().forEach((range) => {
|
|
1102
|
+
f(range, client)
|
|
1103
|
+
})
|
|
1104
|
+
})
|
|
1105
|
+
}
|
|
1106
|
+
|
|
1107
|
+
isEmpty () {
|
|
1108
|
+
return this.clients.size === 0
|
|
1109
|
+
}
|
|
1110
|
+
|
|
1111
|
+
/**
|
|
1112
|
+
* @param {ID} id
|
|
1113
|
+
* @return {boolean}
|
|
1114
|
+
*/
|
|
1115
|
+
hasId (id) {
|
|
1116
|
+
return this.has(id.client, id.clock)
|
|
1117
|
+
}
|
|
1118
|
+
|
|
1119
|
+
/**
|
|
1120
|
+
* @param {number} client
|
|
1121
|
+
* @param {number} clock
|
|
1122
|
+
* @return {boolean}
|
|
1123
|
+
*/
|
|
1124
|
+
has (client, clock) {
|
|
1125
|
+
const dr = this.clients.get(client)
|
|
1126
|
+
if (dr) {
|
|
1127
|
+
return findIndexInIdRanges(dr.getIds(), clock) !== null
|
|
1128
|
+
}
|
|
1129
|
+
return false
|
|
1130
|
+
}
|
|
1131
|
+
|
|
1132
|
+
/**
|
|
1133
|
+
* Return attributions for a slice of ids.
|
|
1134
|
+
*
|
|
1135
|
+
* @param {ID} id
|
|
1136
|
+
* @param {number} len
|
|
1137
|
+
* @return {Array<MaybeAttrRange<Attrs>>}
|
|
1138
|
+
*/
|
|
1139
|
+
sliceId (id, len) {
|
|
1140
|
+
return this.slice(id.client, id.clock, len)
|
|
1141
|
+
}
|
|
1142
|
+
|
|
1143
|
+
/**
|
|
1144
|
+
* Return attributions for a slice of ids.
|
|
1145
|
+
*
|
|
1146
|
+
* @param {number} client
|
|
1147
|
+
* @param {number} clock
|
|
1148
|
+
* @param {number} len
|
|
1149
|
+
* @return {Array<MaybeAttrRange<Attrs>>}
|
|
1150
|
+
*/
|
|
1151
|
+
slice (client, clock, len) {
|
|
1152
|
+
const dr = this.clients.get(client)
|
|
1153
|
+
/**
|
|
1154
|
+
* @type {Array<MaybeAttrRange<Attrs>>}
|
|
1155
|
+
*/
|
|
1156
|
+
const res = []
|
|
1157
|
+
if (dr) {
|
|
1158
|
+
/**
|
|
1159
|
+
* @type {Array<AttrRange<Attrs>>}
|
|
1160
|
+
*/
|
|
1161
|
+
const ranges = dr.getIds()
|
|
1162
|
+
let index = findRangeStartInIdRanges(ranges, clock)
|
|
1163
|
+
if (index !== null) {
|
|
1164
|
+
let prev = null
|
|
1165
|
+
while (index < ranges.length) {
|
|
1166
|
+
let r = ranges[index]
|
|
1167
|
+
if (r.clock < clock) {
|
|
1168
|
+
r = new AttrRange(clock, r.len - (clock - r.clock), r.attrs)
|
|
1169
|
+
}
|
|
1170
|
+
if (r.clock + r.len > clock + len) {
|
|
1171
|
+
r = new AttrRange(r.clock, clock + len - r.clock, r.attrs)
|
|
1172
|
+
}
|
|
1173
|
+
if (r.len <= 0) break
|
|
1174
|
+
const prevEnd = prev != null ? prev.clock + prev.len : clock
|
|
1175
|
+
if (prevEnd < r.clock) {
|
|
1176
|
+
res.push(createMaybeAttrRange(prevEnd, r.clock - prevEnd, null))
|
|
1177
|
+
}
|
|
1178
|
+
prev = r
|
|
1179
|
+
res.push(r)
|
|
1180
|
+
index++
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
if (res.length > 0) {
|
|
1185
|
+
const last = res[res.length - 1]
|
|
1186
|
+
const end = last.clock + last.len
|
|
1187
|
+
if (end < clock + len) {
|
|
1188
|
+
res.push(createMaybeAttrRange(end, clock + len - end, null))
|
|
1189
|
+
}
|
|
1190
|
+
} else {
|
|
1191
|
+
res.push(createMaybeAttrRange(clock, len, null))
|
|
1192
|
+
}
|
|
1193
|
+
return res
|
|
1194
|
+
}
|
|
1195
|
+
|
|
1196
|
+
/**
|
|
1197
|
+
* @param {number} client
|
|
1198
|
+
* @param {number} clock
|
|
1199
|
+
* @param {number} len
|
|
1200
|
+
* @param {Array<ContentAttribute<Attrs>>} attrs
|
|
1201
|
+
*/
|
|
1202
|
+
add (client, clock, len, attrs) {
|
|
1203
|
+
if (len === 0) return
|
|
1204
|
+
attrs = _ensureAttrs(this, attrs)
|
|
1205
|
+
const ranges = this.clients.get(client)
|
|
1206
|
+
if (ranges == null) {
|
|
1207
|
+
this.clients.set(client, new AttrRanges([new AttrRange(clock, len, attrs)]))
|
|
1208
|
+
} else {
|
|
1209
|
+
ranges.add(clock, len, attrs)
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
|
|
1213
|
+
/**
|
|
1214
|
+
* @param {number} client
|
|
1215
|
+
* @param {number} clock
|
|
1216
|
+
* @param {number} len
|
|
1217
|
+
*/
|
|
1218
|
+
delete (client, clock, len) {
|
|
1219
|
+
_deleteRangeFromIdSet(this, client, clock, len)
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
|
|
1223
|
+
/**
|
|
1224
|
+
* @template T
|
|
1225
|
+
* @param {Array<T>} attrs
|
|
1226
|
+
* @param {T} attr
|
|
1227
|
+
*
|
|
1228
|
+
*/
|
|
1229
|
+
const idmapAttrsHas = (attrs, attr) => attrs.find(a => a === attr)
|
|
1230
|
+
|
|
1231
|
+
/**
|
|
1232
|
+
* @template T
|
|
1233
|
+
* @param {Array<T>} a
|
|
1234
|
+
* @param {Array<T>} b
|
|
1235
|
+
*/
|
|
1236
|
+
export const idmapAttrsEqual = (a, b) => a.length === b.length && a.every(v => idmapAttrsHas(b, v))
|
|
1237
|
+
|
|
1238
|
+
/**
|
|
1239
|
+
* Merge multiple idmaps. Ensures that there are no redundant attribution definitions (two
|
|
1240
|
+
* Attributions that describe the same thing).
|
|
1241
|
+
*
|
|
1242
|
+
* @template T
|
|
1243
|
+
* @param {Array<IdMap<T>>} ams
|
|
1244
|
+
* @return {IdMap<T>} A fresh IdSet
|
|
1245
|
+
*/
|
|
1246
|
+
export const mergeIdMaps = ams => {
|
|
1247
|
+
/**
|
|
1248
|
+
* Maps attribution to the attribution of the merged idmap.
|
|
1249
|
+
*
|
|
1250
|
+
* @type {Map<ContentAttribute<any>,ContentAttribute<any>>}
|
|
1251
|
+
*/
|
|
1252
|
+
const attrMapper = new Map()
|
|
1253
|
+
const merged = createIdMap()
|
|
1254
|
+
for (let amsI = 0; amsI < ams.length; amsI++) {
|
|
1255
|
+
ams[amsI].clients.forEach((rangesLeft, client) => {
|
|
1256
|
+
if (!merged.clients.has(client)) {
|
|
1257
|
+
// Write all missing keys from current set and all following.
|
|
1258
|
+
// If merged already contains `client` current ds has already been added.
|
|
1259
|
+
let ids = rangesLeft.getIds().slice()
|
|
1260
|
+
for (let i = amsI + 1; i < ams.length; i++) {
|
|
1261
|
+
const nextIds = ams[i].clients.get(client)
|
|
1262
|
+
if (nextIds) {
|
|
1263
|
+
array.appendTo(ids, nextIds.getIds())
|
|
1264
|
+
}
|
|
1265
|
+
}
|
|
1266
|
+
ids = ids.map(id => new AttrRange(id.clock, id.len, id.attrs.map(attr =>
|
|
1267
|
+
map.setIfUndefined(attrMapper, attr, () =>
|
|
1268
|
+
_ensureAttrs(merged, [attr])[0]
|
|
1269
|
+
)
|
|
1270
|
+
)))
|
|
1271
|
+
merged.clients.set(client, new AttrRanges(ids))
|
|
1272
|
+
}
|
|
1273
|
+
})
|
|
1274
|
+
}
|
|
1275
|
+
return merged
|
|
1276
|
+
}
|
|
1277
|
+
|
|
1278
|
+
/**
|
|
1279
|
+
* @param {IdSet} idset
|
|
1280
|
+
* @param {Array<ContentAttribute<any>>} attrs
|
|
1281
|
+
*/
|
|
1282
|
+
export const createIdMapFromIdSet = (idset, attrs) => {
|
|
1283
|
+
const idmap = createIdMap()
|
|
1284
|
+
// map attrs to idmap
|
|
1285
|
+
attrs = _ensureAttrs(idmap, attrs)
|
|
1286
|
+
// filter out duplicates
|
|
1287
|
+
/**
|
|
1288
|
+
* @type {Array<ContentAttribute<any>>}
|
|
1289
|
+
*/
|
|
1290
|
+
const checkedAttrs = []
|
|
1291
|
+
attrs.forEach(attr => {
|
|
1292
|
+
if (!idmapAttrsHas(checkedAttrs, attr)) {
|
|
1293
|
+
checkedAttrs.push(attr)
|
|
1294
|
+
}
|
|
1295
|
+
})
|
|
1296
|
+
idset.clients.forEach((ranges, client) => {
|
|
1297
|
+
const attrRanges = new AttrRanges(ranges.getIds().map(range => new AttrRange(range.clock, range.len, checkedAttrs)))
|
|
1298
|
+
attrRanges.sorted = true // is sorted because idset is sorted
|
|
1299
|
+
idmap.clients.set(client, attrRanges)
|
|
1300
|
+
})
|
|
1301
|
+
return idmap
|
|
1302
|
+
}
|
|
1303
|
+
|
|
1304
|
+
/**
|
|
1305
|
+
* Create an IdSet from an IdMap by stripping the attributes.
|
|
1306
|
+
*
|
|
1307
|
+
* @param {IdMap<any>} idmap
|
|
1308
|
+
* @return {IdSet}
|
|
1309
|
+
*/
|
|
1310
|
+
export const createIdSetFromIdMap = idmap => {
|
|
1311
|
+
const idset = createIdSet()
|
|
1312
|
+
idmap.clients.forEach((ranges, client) => {
|
|
1313
|
+
const idRanges = new IdRanges([])
|
|
1314
|
+
ranges.getIds().forEach(range => idRanges.add(range.clock, range.len))
|
|
1315
|
+
idset.clients.set(client, idRanges)
|
|
1316
|
+
})
|
|
1317
|
+
return idset
|
|
1318
|
+
}
|
|
1319
|
+
|
|
1320
|
+
/**
|
|
1321
|
+
* Efficiently encodes IdMap to a binary form. Ensures that information is de-duplicated when
|
|
1322
|
+
* written. Attribute.names are referenced by id. Attributes themselfs are also referenced by id.
|
|
1323
|
+
*
|
|
1324
|
+
* @template Attr
|
|
1325
|
+
* @param {IdSetEncoderV1 | IdSetEncoderV2} encoder
|
|
1326
|
+
* @param {IdMap<Attr>} idmap
|
|
1327
|
+
*
|
|
1328
|
+
* @private
|
|
1329
|
+
* @function
|
|
1330
|
+
*/
|
|
1331
|
+
export const writeIdMap = (encoder, idmap) => {
|
|
1332
|
+
encoding.writeVarUint(encoder.restEncoder, idmap.clients.size)
|
|
1333
|
+
let lastWrittenClientId = 0
|
|
1334
|
+
/**
|
|
1335
|
+
* @type {Map<ContentAttribute<Attr>, number>}
|
|
1336
|
+
*/
|
|
1337
|
+
const visitedAttributions = map.create()
|
|
1338
|
+
/**
|
|
1339
|
+
* @type {Map<string, number>}
|
|
1340
|
+
*/
|
|
1341
|
+
const visitedAttrNames = map.create()
|
|
1342
|
+
// Ensure that the ids are written in a deterministic order (smaller clientids first)
|
|
1343
|
+
array.from(idmap.clients.entries())
|
|
1344
|
+
.sort((a, b) => a[0] - b[0])
|
|
1345
|
+
.forEach(([client, _idRanges]) => {
|
|
1346
|
+
const attrRanges = _idRanges.getIds()
|
|
1347
|
+
encoder.resetIdSetCurVal()
|
|
1348
|
+
const diff = client - lastWrittenClientId
|
|
1349
|
+
encoding.writeVarUint(encoder.restEncoder, diff)
|
|
1350
|
+
lastWrittenClientId = client
|
|
1351
|
+
const len = attrRanges.length
|
|
1352
|
+
encoding.writeVarUint(encoder.restEncoder, len)
|
|
1353
|
+
for (let i = 0; i < len; i++) {
|
|
1354
|
+
const item = attrRanges[i]
|
|
1355
|
+
const attrs = item.attrs
|
|
1356
|
+
const attrLen = attrs.length
|
|
1357
|
+
encoder.writeIdSetClock(item.clock)
|
|
1358
|
+
encoder.writeIdSetLen(item.len)
|
|
1359
|
+
encoding.writeVarUint(encoder.restEncoder, attrLen)
|
|
1360
|
+
for (let j = 0; j < attrLen; j++) {
|
|
1361
|
+
const attr = attrs[j]
|
|
1362
|
+
const attrId = visitedAttributions.get(attr)
|
|
1363
|
+
if (attrId != null) {
|
|
1364
|
+
encoding.writeVarUint(encoder.restEncoder, attrId)
|
|
1365
|
+
} else {
|
|
1366
|
+
const newAttrId = visitedAttributions.size
|
|
1367
|
+
visitedAttributions.set(attr, newAttrId)
|
|
1368
|
+
encoding.writeVarUint(encoder.restEncoder, newAttrId)
|
|
1369
|
+
const attrNameId = visitedAttrNames.get(attr.name)
|
|
1370
|
+
// write attr.name
|
|
1371
|
+
if (attrNameId != null) {
|
|
1372
|
+
encoding.writeVarUint(encoder.restEncoder, attrNameId)
|
|
1373
|
+
} else {
|
|
1374
|
+
const newAttrNameId = visitedAttrNames.size
|
|
1375
|
+
encoding.writeVarUint(encoder.restEncoder, newAttrNameId)
|
|
1376
|
+
encoding.writeVarString(encoder.restEncoder, attr.name)
|
|
1377
|
+
visitedAttrNames.set(attr.name, newAttrNameId)
|
|
1378
|
+
}
|
|
1379
|
+
encoding.writeAny(encoder.restEncoder, /** @type {any} */ (attr.val))
|
|
1380
|
+
}
|
|
1381
|
+
}
|
|
1382
|
+
}
|
|
1383
|
+
})
|
|
1384
|
+
}
|
|
1385
|
+
|
|
1386
|
+
/**
|
|
1387
|
+
* @param {IdMap<any>} idmap
|
|
1388
|
+
*/
|
|
1389
|
+
export const encodeIdMap = idmap => {
|
|
1390
|
+
const encoder = new IdSetEncoderV2()
|
|
1391
|
+
writeIdMap(encoder, idmap)
|
|
1392
|
+
return encoder.toUint8Array()
|
|
1393
|
+
}
|
|
1394
|
+
|
|
1395
|
+
/**
|
|
1396
|
+
* @param {IdSetDecoderV1 | IdSetDecoderV2} decoder
|
|
1397
|
+
* @return {IdMap<any>}
|
|
1398
|
+
*
|
|
1399
|
+
* @private
|
|
1400
|
+
* @function
|
|
1401
|
+
*/
|
|
1402
|
+
export const readIdMap = decoder => {
|
|
1403
|
+
const idmap = new IdMap()
|
|
1404
|
+
const numClients = decoding.readVarUint(decoder.restDecoder)
|
|
1405
|
+
/**
|
|
1406
|
+
* @type {Array<ContentAttribute<any>>}
|
|
1407
|
+
*/
|
|
1408
|
+
const visitedAttributions = []
|
|
1409
|
+
/**
|
|
1410
|
+
* @type {Array<string>}
|
|
1411
|
+
*/
|
|
1412
|
+
const visitedAttrNames = []
|
|
1413
|
+
let lastClientId = 0
|
|
1414
|
+
for (let i = 0; i < numClients; i++) {
|
|
1415
|
+
decoder.resetDsCurVal()
|
|
1416
|
+
const client = lastClientId + decoding.readVarUint(decoder.restDecoder)
|
|
1417
|
+
lastClientId = client
|
|
1418
|
+
const numberOfDeletes = decoding.readVarUint(decoder.restDecoder)
|
|
1419
|
+
/**
|
|
1420
|
+
* @type {Array<AttrRange<any>>}
|
|
1421
|
+
*/
|
|
1422
|
+
const attrRanges = []
|
|
1423
|
+
for (let i = 0; i < numberOfDeletes; i++) {
|
|
1424
|
+
const rangeClock = decoder.readDsClock()
|
|
1425
|
+
const rangeLen = decoder.readDsLen()
|
|
1426
|
+
/**
|
|
1427
|
+
* @type {Array<ContentAttribute<any>>}
|
|
1428
|
+
*/
|
|
1429
|
+
const attrs = []
|
|
1430
|
+
const attrsLen = decoding.readVarUint(decoder.restDecoder)
|
|
1431
|
+
for (let j = 0; j < attrsLen; j++) {
|
|
1432
|
+
const attrId = decoding.readVarUint(decoder.restDecoder)
|
|
1433
|
+
if (attrId >= visitedAttributions.length) {
|
|
1434
|
+
// attrId not known yet
|
|
1435
|
+
const attrNameId = decoding.readVarUint(decoder.restDecoder)
|
|
1436
|
+
if (attrNameId >= visitedAttrNames.length) {
|
|
1437
|
+
visitedAttrNames.push(decoding.readVarString(decoder.restDecoder))
|
|
1438
|
+
}
|
|
1439
|
+
visitedAttributions.push(new ContentAttribute(visitedAttrNames[attrNameId], decoding.readAny(decoder.restDecoder)))
|
|
1440
|
+
}
|
|
1441
|
+
attrs.push(visitedAttributions[attrId])
|
|
1442
|
+
}
|
|
1443
|
+
attrRanges.push(new AttrRange(rangeClock, rangeLen, attrs))
|
|
1444
|
+
}
|
|
1445
|
+
idmap.clients.set(client, new AttrRanges(attrRanges))
|
|
1446
|
+
}
|
|
1447
|
+
visitedAttributions.forEach(attr => {
|
|
1448
|
+
idmap.attrs.add(attr)
|
|
1449
|
+
idmap.attrsH.set(attr.hash(), attr)
|
|
1450
|
+
})
|
|
1451
|
+
return idmap
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
/**
|
|
1455
|
+
* @param {Uint8Array} data
|
|
1456
|
+
* @return {IdMap<any>}
|
|
1457
|
+
*/
|
|
1458
|
+
export const decodeIdMap = data => readIdMap(new IdSetDecoderV2(decoding.createDecoder(data)))
|
|
1459
|
+
|
|
1460
|
+
/**
|
|
1461
|
+
* @template Attrs
|
|
1462
|
+
* @param {IdMap<Attrs>} idmap
|
|
1463
|
+
* @param {Array<ContentAttribute<Attrs>>} attrs
|
|
1464
|
+
* @return {Array<ContentAttribute<Attrs>>}
|
|
1465
|
+
*/
|
|
1466
|
+
const _ensureAttrs = (idmap, attrs) => attrs.map(attr =>
|
|
1467
|
+
idmap.attrs.has(attr)
|
|
1468
|
+
? attr
|
|
1469
|
+
: map.setIfUndefined(idmap.attrsH, attr.hash(), () => {
|
|
1470
|
+
idmap.attrs.add(attr)
|
|
1471
|
+
return attr
|
|
1472
|
+
}))
|
|
1473
|
+
|
|
1474
|
+
export const createIdMap = () => new IdMap()
|
|
1475
|
+
|
|
1476
|
+
/**
|
|
1477
|
+
* Remove all ranges from `exclude` from `ds`. The result is a fresh IdMap containing all ranges from `idSet` that are not
|
|
1478
|
+
* in `exclude`.
|
|
1479
|
+
*
|
|
1480
|
+
* @todo this should be called "excludeIdMap"
|
|
1481
|
+
*
|
|
1482
|
+
* @template {IdMap<any>} ISet
|
|
1483
|
+
* @param {ISet} set
|
|
1484
|
+
* @param {IdSet | IdMap<any>} exclude
|
|
1485
|
+
* @return {ISet}
|
|
1486
|
+
*/
|
|
1487
|
+
export const diffIdMap = (set, exclude) => {
|
|
1488
|
+
const diffed = _diffSet(set, exclude)
|
|
1489
|
+
diffed.attrs = set.attrs
|
|
1490
|
+
diffed.attrsH = set.attrsH
|
|
1491
|
+
return diffed
|
|
1492
|
+
}
|
|
1493
|
+
|
|
1494
|
+
export const intersectMaps = _intersectSets
|
|
1495
|
+
|
|
1496
|
+
/**
|
|
1497
|
+
* Filter attributes in an IdMap based on a predicate function.
|
|
1498
|
+
* Returns a new IdMap containing idranges that match the predicate.
|
|
1499
|
+
*
|
|
1500
|
+
* @template Attrs
|
|
1501
|
+
* @param {IdMap<Attrs>} idmap
|
|
1502
|
+
* @param {(attr: Array<ContentAttribute<Attrs>>) => boolean} predicate
|
|
1503
|
+
* @return {IdMap<Attrs>}
|
|
1504
|
+
*/
|
|
1505
|
+
export const filterIdMap = (idmap, predicate) => {
|
|
1506
|
+
const filtered = createIdMap()
|
|
1507
|
+
idmap.clients.forEach((ranges, client) => {
|
|
1508
|
+
/**
|
|
1509
|
+
* @type {Array<AttrRange<Attrs>>}
|
|
1510
|
+
*/
|
|
1511
|
+
const attrRanges = []
|
|
1512
|
+
ranges.getIds().forEach((range) => {
|
|
1513
|
+
if (predicate(range.attrs)) {
|
|
1514
|
+
const rangeCpy = range.copyWith(range.clock, range.len)
|
|
1515
|
+
attrRanges.push(rangeCpy)
|
|
1516
|
+
rangeCpy.attrs.forEach(attr => {
|
|
1517
|
+
filtered.attrs.add(attr)
|
|
1518
|
+
filtered.attrsH.set(attr.hash(), attr)
|
|
1519
|
+
})
|
|
1520
|
+
}
|
|
1521
|
+
})
|
|
1522
|
+
if (attrRanges.length > 0) {
|
|
1523
|
+
filtered.clients.set(client, new AttrRanges(attrRanges))
|
|
1524
|
+
}
|
|
1525
|
+
})
|
|
1526
|
+
return filtered
|
|
1527
|
+
}
|