@colyseus/schema 3.0.0-alpha.34 → 3.0.0-alpha.35
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/bin/schema-debug +4 -3
- package/build/cjs/index.js +465 -303
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +465 -303
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +465 -303
- package/lib/Metadata.d.ts +5 -5
- package/lib/Metadata.js +17 -17
- package/lib/Metadata.js.map +1 -1
- package/lib/Schema.js +24 -17
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.js +11 -11
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +12 -5
- package/lib/bench_encode.js.map +1 -1
- package/lib/decoder/Decoder.js +1 -1
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +23 -7
- package/lib/encoder/ChangeTree.js +183 -106
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +2 -1
- package/lib/encoder/EncodeOperation.js +2 -2
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +3 -5
- package/lib/encoder/Encoder.js +93 -61
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +12 -7
- package/lib/encoder/Root.js +41 -20
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.d.ts +5 -5
- package/lib/encoder/StateView.js +29 -23
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/encode.js +12 -9
- package/lib/encoding/encode.js.map +1 -1
- package/lib/types/TypeContext.js +2 -1
- package/lib/types/TypeContext.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +27 -13
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -1
- package/lib/types/custom/MapSchema.js +7 -4
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/symbols.d.ts +8 -6
- package/lib/types/symbols.js +9 -7
- package/lib/types/symbols.js.map +1 -1
- package/lib/utils.js +6 -3
- package/lib/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/Metadata.ts +22 -22
- package/src/Schema.ts +33 -25
- package/src/annotations.ts +12 -12
- package/src/bench_encode.ts +15 -6
- package/src/decoder/Decoder.ts +1 -1
- package/src/encoder/ChangeTree.ts +220 -115
- package/src/encoder/EncodeOperation.ts +5 -1
- package/src/encoder/Encoder.ts +110 -68
- package/src/encoder/Root.ts +41 -21
- package/src/encoder/StateView.ts +32 -28
- package/src/encoding/encode.ts +12 -9
- package/src/types/TypeContext.ts +2 -1
- package/src/types/custom/ArraySchema.ts +39 -17
- package/src/types/custom/MapSchema.ts +12 -5
- package/src/types/symbols.ts +10 -9
- package/src/utils.ts +7 -3
package/build/umd/index.js
CHANGED
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
const $filter = Symbol("$filter");
|
|
41
41
|
const $getByIndex = Symbol("$getByIndex");
|
|
42
42
|
const $deleteByIndex = Symbol("$deleteByIndex");
|
|
43
|
-
const $descriptors = Symbol("$descriptors");
|
|
44
43
|
/**
|
|
45
44
|
* Used to hold ChangeTree instances whitin the structures
|
|
46
45
|
*/
|
|
@@ -50,11 +49,6 @@
|
|
|
50
49
|
* (MapSchema, ArraySchema, etc.)
|
|
51
50
|
*/
|
|
52
51
|
const $childType = Symbol('$childType');
|
|
53
|
-
/**
|
|
54
|
-
* Special ChangeTree property to identify new instances
|
|
55
|
-
* (Once they're encoded, they're not new anymore)
|
|
56
|
-
*/
|
|
57
|
-
const $isNew = Symbol("$isNew");
|
|
58
52
|
/**
|
|
59
53
|
* Optional "discard" method for custom types (ArraySchema)
|
|
60
54
|
* (Discards changes for next serialization)
|
|
@@ -64,6 +58,14 @@
|
|
|
64
58
|
* When decoding, this method is called after the instance is fully decoded
|
|
65
59
|
*/
|
|
66
60
|
const $onDecodeEnd = Symbol("$onDecodeEnd");
|
|
61
|
+
/**
|
|
62
|
+
* Metadata
|
|
63
|
+
*/
|
|
64
|
+
const $descriptors = Symbol("$descriptors");
|
|
65
|
+
const $numFields = "$__numFields";
|
|
66
|
+
const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
|
|
67
|
+
const $viewFieldIndexes = "$__viewFieldIndexes";
|
|
68
|
+
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
67
69
|
|
|
68
70
|
const registeredTypes = {};
|
|
69
71
|
const identifiers = new Map();
|
|
@@ -110,7 +112,7 @@
|
|
|
110
112
|
};
|
|
111
113
|
}
|
|
112
114
|
// map -1 as last field index
|
|
113
|
-
Object.defineProperty(metadata,
|
|
115
|
+
Object.defineProperty(metadata, $numFields, {
|
|
114
116
|
value: index,
|
|
115
117
|
enumerable: false,
|
|
116
118
|
configurable: true
|
|
@@ -123,14 +125,14 @@
|
|
|
123
125
|
});
|
|
124
126
|
// if child Ref/complex type, add to -4
|
|
125
127
|
if (typeof (metadata[index].type) !== "string") {
|
|
126
|
-
if (metadata[
|
|
127
|
-
Object.defineProperty(metadata,
|
|
128
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
129
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
128
130
|
value: [],
|
|
129
131
|
enumerable: false,
|
|
130
132
|
configurable: true,
|
|
131
133
|
});
|
|
132
134
|
}
|
|
133
|
-
metadata[
|
|
135
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
134
136
|
}
|
|
135
137
|
},
|
|
136
138
|
setTag(metadata, fieldName, tag) {
|
|
@@ -138,25 +140,25 @@
|
|
|
138
140
|
const field = metadata[index];
|
|
139
141
|
// add 'tag' to the field
|
|
140
142
|
field.tag = tag;
|
|
141
|
-
if (!metadata[
|
|
143
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
142
144
|
// -2: all field indexes with "view" tag
|
|
143
|
-
Object.defineProperty(metadata,
|
|
145
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
144
146
|
value: [],
|
|
145
147
|
enumerable: false,
|
|
146
148
|
configurable: true
|
|
147
149
|
});
|
|
148
150
|
// -3: field indexes by "view" tag
|
|
149
|
-
Object.defineProperty(metadata,
|
|
151
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
150
152
|
value: {},
|
|
151
153
|
enumerable: false,
|
|
152
154
|
configurable: true
|
|
153
155
|
});
|
|
154
156
|
}
|
|
155
|
-
metadata[
|
|
156
|
-
if (!metadata[
|
|
157
|
-
metadata[
|
|
157
|
+
metadata[$viewFieldIndexes].push(index);
|
|
158
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
159
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
158
160
|
}
|
|
159
|
-
metadata[
|
|
161
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
160
162
|
},
|
|
161
163
|
setFields(target, fields) {
|
|
162
164
|
const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
|
|
@@ -181,7 +183,7 @@
|
|
|
181
183
|
//
|
|
182
184
|
const metadata = {};
|
|
183
185
|
klass[Symbol.metadata] = metadata;
|
|
184
|
-
Object.defineProperty(metadata,
|
|
186
|
+
Object.defineProperty(metadata, $numFields, {
|
|
185
187
|
value: 0,
|
|
186
188
|
enumerable: false,
|
|
187
189
|
configurable: true,
|
|
@@ -195,7 +197,7 @@
|
|
|
195
197
|
if (parentMetadata) {
|
|
196
198
|
// assign parent metadata to current
|
|
197
199
|
Object.assign(metadata, parentMetadata);
|
|
198
|
-
for (let i = 0; i <= parentMetadata[
|
|
200
|
+
for (let i = 0; i <= parentMetadata[$numFields]; i++) {
|
|
199
201
|
const fieldName = parentMetadata[i].name;
|
|
200
202
|
Object.defineProperty(metadata, fieldName, {
|
|
201
203
|
value: parentMetadata[fieldName],
|
|
@@ -203,8 +205,8 @@
|
|
|
203
205
|
configurable: true,
|
|
204
206
|
});
|
|
205
207
|
}
|
|
206
|
-
Object.defineProperty(metadata,
|
|
207
|
-
value: parentMetadata[
|
|
208
|
+
Object.defineProperty(metadata, $numFields, {
|
|
209
|
+
value: parentMetadata[$numFields],
|
|
208
210
|
enumerable: false,
|
|
209
211
|
configurable: true,
|
|
210
212
|
writable: true,
|
|
@@ -216,40 +218,69 @@
|
|
|
216
218
|
},
|
|
217
219
|
isValidInstance(klass) {
|
|
218
220
|
return (klass.constructor[Symbol.metadata] &&
|
|
219
|
-
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata],
|
|
221
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
220
222
|
},
|
|
221
223
|
getFields(klass) {
|
|
222
224
|
const metadata = klass[Symbol.metadata];
|
|
223
225
|
const fields = {};
|
|
224
|
-
for (let i = 0; i <= metadata[
|
|
226
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
225
227
|
fields[metadata[i].name] = metadata[i].type;
|
|
226
228
|
}
|
|
227
229
|
return fields;
|
|
228
230
|
}
|
|
229
231
|
};
|
|
230
232
|
|
|
231
|
-
|
|
233
|
+
function setOperationAtIndex(changeSet, index) {
|
|
234
|
+
const operationsIndex = changeSet.indexes[index];
|
|
235
|
+
if (operationsIndex === undefined) {
|
|
236
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
237
|
+
}
|
|
238
|
+
else {
|
|
239
|
+
changeSet.operations[operationsIndex] = index;
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
243
|
+
const operationsIndex = changeSet.indexes[index];
|
|
244
|
+
if (operationsIndex !== undefined) {
|
|
245
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
246
|
+
}
|
|
247
|
+
delete changeSet.indexes[index];
|
|
248
|
+
}
|
|
249
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
250
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
251
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
252
|
+
}
|
|
253
|
+
}
|
|
232
254
|
class ChangeTree {
|
|
233
|
-
static { _a$5 = $isNew; }
|
|
234
255
|
constructor(ref) {
|
|
235
256
|
this.isFiltered = false;
|
|
236
257
|
this.isPartiallyFiltered = false;
|
|
237
|
-
this.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
258
|
+
this.indexedOperations = {};
|
|
259
|
+
//
|
|
260
|
+
// TODO:
|
|
261
|
+
// try storing the index + operation per item.
|
|
262
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
263
|
+
//
|
|
264
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
265
|
+
//
|
|
266
|
+
this.changes = { indexes: {}, operations: [] };
|
|
267
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
268
|
+
/**
|
|
269
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
270
|
+
*/
|
|
271
|
+
this.isNew = true;
|
|
241
272
|
this.ref = ref;
|
|
242
273
|
//
|
|
243
274
|
// Does this structure have "filters" declared?
|
|
244
275
|
//
|
|
245
|
-
if (ref.constructor[Symbol.metadata]?.[
|
|
246
|
-
this.allFilteredChanges =
|
|
247
|
-
this.filteredChanges =
|
|
276
|
+
if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
|
|
277
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
278
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
248
279
|
}
|
|
249
280
|
}
|
|
250
281
|
setRoot(root) {
|
|
251
282
|
this.root = root;
|
|
252
|
-
this.root.add(this);
|
|
283
|
+
const isNewChangeTree = this.root.add(this);
|
|
253
284
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
254
285
|
if (this.root.types.hasFilters) {
|
|
255
286
|
//
|
|
@@ -260,22 +291,24 @@
|
|
|
260
291
|
//
|
|
261
292
|
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
262
293
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
263
|
-
|
|
264
|
-
|
|
294
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
295
|
+
if (isNewChangeTree) {
|
|
296
|
+
this.root.allFilteredChanges.push(this);
|
|
297
|
+
}
|
|
265
298
|
}
|
|
266
299
|
}
|
|
267
300
|
if (!this.isFiltered) {
|
|
268
|
-
|
|
269
|
-
|
|
301
|
+
enqueueChangeTree(root, this, 'changes');
|
|
302
|
+
if (isNewChangeTree) {
|
|
303
|
+
this.root.allChanges.push(this);
|
|
304
|
+
}
|
|
270
305
|
}
|
|
271
|
-
|
|
306
|
+
// Recursively set root on child structures
|
|
272
307
|
if (metadata) {
|
|
273
|
-
metadata[
|
|
308
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
274
309
|
const field = metadata[index];
|
|
275
310
|
const value = this.ref[field.name];
|
|
276
|
-
|
|
277
|
-
value[$changes].setRoot(root);
|
|
278
|
-
}
|
|
311
|
+
value?.[$changes].setRoot(root);
|
|
279
312
|
});
|
|
280
313
|
}
|
|
281
314
|
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
@@ -292,31 +325,36 @@
|
|
|
292
325
|
if (!root) {
|
|
293
326
|
return;
|
|
294
327
|
}
|
|
295
|
-
root.add(this);
|
|
296
328
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
297
329
|
// skip if parent is already set
|
|
298
330
|
if (root !== this.root) {
|
|
299
331
|
this.root = root;
|
|
332
|
+
const isNewChangeTree = root.add(this);
|
|
300
333
|
if (root.types.hasFilters) {
|
|
301
334
|
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
302
335
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
303
|
-
|
|
304
|
-
|
|
336
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
337
|
+
if (isNewChangeTree) {
|
|
338
|
+
this.root.allFilteredChanges.push(this);
|
|
339
|
+
}
|
|
305
340
|
}
|
|
306
341
|
}
|
|
307
342
|
if (!this.isFiltered) {
|
|
308
|
-
|
|
309
|
-
|
|
343
|
+
enqueueChangeTree(root, this, 'changes');
|
|
344
|
+
if (isNewChangeTree) {
|
|
345
|
+
this.root.allChanges.push(this);
|
|
346
|
+
}
|
|
310
347
|
}
|
|
311
|
-
|
|
348
|
+
}
|
|
349
|
+
else {
|
|
350
|
+
root.add(this);
|
|
312
351
|
}
|
|
313
352
|
// assign same parent on child structures
|
|
314
353
|
if (metadata) {
|
|
315
|
-
metadata[
|
|
354
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
316
355
|
const field = metadata[index];
|
|
317
356
|
const value = this.ref[field.name];
|
|
318
357
|
value?.[$changes].setParent(this.ref, root, index);
|
|
319
|
-
// console.log(this.ref.constructor.name, field.name, value);
|
|
320
358
|
// try { throw new Error(); } catch (e) {
|
|
321
359
|
// console.log(e.stack);
|
|
322
360
|
// }
|
|
@@ -335,7 +373,7 @@
|
|
|
335
373
|
//
|
|
336
374
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
337
375
|
if (metadata) {
|
|
338
|
-
metadata[
|
|
376
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
339
377
|
const field = metadata[index];
|
|
340
378
|
const value = this.ref[field.name];
|
|
341
379
|
if (value) {
|
|
@@ -351,8 +389,10 @@
|
|
|
351
389
|
}
|
|
352
390
|
}
|
|
353
391
|
operation(op) {
|
|
354
|
-
|
|
355
|
-
this.
|
|
392
|
+
// operations without index use negative values to represent them
|
|
393
|
+
// this is checked during .encode() time.
|
|
394
|
+
this.changes.operations.push(-op);
|
|
395
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
356
396
|
}
|
|
357
397
|
change(index, operation = exports.OPERATION.ADD) {
|
|
358
398
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
@@ -360,7 +400,7 @@
|
|
|
360
400
|
const changeSet = (isFiltered)
|
|
361
401
|
? this.filteredChanges
|
|
362
402
|
: this.changes;
|
|
363
|
-
const previousOperation =
|
|
403
|
+
const previousOperation = this.indexedOperations[index];
|
|
364
404
|
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
365
405
|
const op = (!previousOperation)
|
|
366
406
|
? operation
|
|
@@ -370,16 +410,19 @@
|
|
|
370
410
|
//
|
|
371
411
|
// TODO: are DELETE operations being encoded as ADD here ??
|
|
372
412
|
//
|
|
373
|
-
|
|
413
|
+
this.indexedOperations[index] = op;
|
|
374
414
|
}
|
|
415
|
+
setOperationAtIndex(changeSet, index);
|
|
375
416
|
if (isFiltered) {
|
|
376
|
-
this.allFilteredChanges
|
|
377
|
-
this.root
|
|
378
|
-
|
|
417
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
418
|
+
if (this.root) {
|
|
419
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
420
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
421
|
+
}
|
|
379
422
|
}
|
|
380
423
|
else {
|
|
381
|
-
this.allChanges
|
|
382
|
-
this.root
|
|
424
|
+
setOperationAtIndex(this.allChanges, index);
|
|
425
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
383
426
|
}
|
|
384
427
|
}
|
|
385
428
|
shiftChangeIndexes(shiftIndex) {
|
|
@@ -391,12 +434,15 @@
|
|
|
391
434
|
const changeSet = (this.isFiltered)
|
|
392
435
|
? this.filteredChanges
|
|
393
436
|
: this.changes;
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
437
|
+
const newIndexedOperations = {};
|
|
438
|
+
const newIndexes = {};
|
|
439
|
+
for (const index in this.indexedOperations) {
|
|
440
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
441
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
399
442
|
}
|
|
443
|
+
this.indexedOperations = newIndexedOperations;
|
|
444
|
+
changeSet.indexes = newIndexes;
|
|
445
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
400
446
|
}
|
|
401
447
|
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
402
448
|
//
|
|
@@ -412,24 +458,36 @@
|
|
|
412
458
|
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
413
459
|
}
|
|
414
460
|
}
|
|
415
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
461
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
462
|
+
const newIndexes = {};
|
|
463
|
+
for (const key in changeSet.indexes) {
|
|
464
|
+
const index = changeSet.indexes[key];
|
|
465
|
+
if (index > startIndex) {
|
|
466
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
420
467
|
}
|
|
421
|
-
|
|
468
|
+
else {
|
|
469
|
+
newIndexes[key] = index;
|
|
470
|
+
}
|
|
471
|
+
}
|
|
472
|
+
changeSet.indexes = newIndexes;
|
|
473
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
474
|
+
const index = changeSet.operations[i];
|
|
475
|
+
if (index > startIndex) {
|
|
476
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
477
|
+
}
|
|
478
|
+
}
|
|
422
479
|
}
|
|
423
480
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
this.
|
|
427
|
-
this.
|
|
481
|
+
this.indexedOperations[index] = operation;
|
|
482
|
+
if (this.filteredChanges) {
|
|
483
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
484
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
485
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
428
486
|
}
|
|
429
487
|
else {
|
|
430
|
-
this.allChanges
|
|
431
|
-
this.changes
|
|
432
|
-
this.root
|
|
488
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
489
|
+
setOperationAtIndex(this.changes, index);
|
|
490
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
433
491
|
}
|
|
434
492
|
}
|
|
435
493
|
getType(index) {
|
|
@@ -448,8 +506,7 @@
|
|
|
448
506
|
}
|
|
449
507
|
}
|
|
450
508
|
getChange(index) {
|
|
451
|
-
|
|
452
|
-
return this.changes.get(index) ?? this.filteredChanges?.get(index);
|
|
509
|
+
return this.indexedOperations[index];
|
|
453
510
|
}
|
|
454
511
|
//
|
|
455
512
|
// used during `.encode()`
|
|
@@ -473,8 +530,9 @@
|
|
|
473
530
|
const changeSet = (this.filteredChanges)
|
|
474
531
|
? this.filteredChanges
|
|
475
532
|
: this.changes;
|
|
533
|
+
this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
|
|
534
|
+
setOperationAtIndex(changeSet, index);
|
|
476
535
|
const previousValue = this.getValue(index);
|
|
477
|
-
changeSet.set(index, operation ?? exports.OPERATION.DELETE);
|
|
478
536
|
// remove `root` reference
|
|
479
537
|
if (previousValue && previousValue[$changes]) {
|
|
480
538
|
//
|
|
@@ -490,23 +548,26 @@
|
|
|
490
548
|
this.root?.remove(previousValue[$changes]);
|
|
491
549
|
}
|
|
492
550
|
//
|
|
493
|
-
// FIXME: this is looking a
|
|
551
|
+
// FIXME: this is looking a ugly and repeated
|
|
494
552
|
//
|
|
495
553
|
if (this.filteredChanges) {
|
|
496
|
-
this.
|
|
497
|
-
this.
|
|
554
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
555
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
498
556
|
}
|
|
499
557
|
else {
|
|
500
|
-
this.
|
|
501
|
-
this.
|
|
558
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
559
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
502
560
|
}
|
|
503
561
|
}
|
|
504
562
|
endEncode() {
|
|
505
|
-
this.
|
|
563
|
+
this.indexedOperations = {};
|
|
564
|
+
// // clear changes
|
|
565
|
+
// this.changes.indexes = {};
|
|
566
|
+
// this.changes.operations.length = 0;
|
|
506
567
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
507
568
|
this.ref[$onEncodeEnd]?.();
|
|
508
569
|
// Not a new instance anymore
|
|
509
|
-
|
|
570
|
+
this.isNew = false;
|
|
510
571
|
}
|
|
511
572
|
discard(discardAll = false) {
|
|
512
573
|
//
|
|
@@ -515,13 +576,22 @@
|
|
|
515
576
|
// REPLACE in case same key is used on next patches.
|
|
516
577
|
//
|
|
517
578
|
this.ref[$onEncodeEnd]?.();
|
|
518
|
-
this.
|
|
519
|
-
this.
|
|
520
|
-
|
|
521
|
-
this.
|
|
579
|
+
this.indexedOperations = {};
|
|
580
|
+
this.changes.indexes = {};
|
|
581
|
+
this.changes.operations.length = 0;
|
|
582
|
+
this.changes.queueRootIndex = undefined;
|
|
583
|
+
if (this.filteredChanges !== undefined) {
|
|
584
|
+
this.filteredChanges.indexes = {};
|
|
585
|
+
this.filteredChanges.operations.length = 0;
|
|
586
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
587
|
+
}
|
|
522
588
|
if (discardAll) {
|
|
523
|
-
this.allChanges.
|
|
524
|
-
this.
|
|
589
|
+
this.allChanges.indexes = {};
|
|
590
|
+
this.allChanges.operations.length = 0;
|
|
591
|
+
if (this.allFilteredChanges !== undefined) {
|
|
592
|
+
this.allFilteredChanges.indexes = {};
|
|
593
|
+
this.allFilteredChanges.operations.length = 0;
|
|
594
|
+
}
|
|
525
595
|
// remove children references
|
|
526
596
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
527
597
|
}
|
|
@@ -530,12 +600,13 @@
|
|
|
530
600
|
* Recursively discard all changes from this, and child structures.
|
|
531
601
|
*/
|
|
532
602
|
discardAll() {
|
|
533
|
-
this.
|
|
534
|
-
|
|
603
|
+
const keys = Object.keys(this.indexedOperations);
|
|
604
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
605
|
+
const value = this.getValue(Number(keys[i]));
|
|
535
606
|
if (value && value[$changes]) {
|
|
536
607
|
value[$changes].discardAll();
|
|
537
608
|
}
|
|
538
|
-
}
|
|
609
|
+
}
|
|
539
610
|
this.discard();
|
|
540
611
|
}
|
|
541
612
|
ensureRefId() {
|
|
@@ -546,42 +617,48 @@
|
|
|
546
617
|
this.refId = this.root.getNextUniqueId();
|
|
547
618
|
}
|
|
548
619
|
get changed() {
|
|
549
|
-
return this.
|
|
620
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
550
621
|
}
|
|
551
622
|
checkIsFiltered(metadata, parent, parentIndex) {
|
|
552
623
|
// Detect if current structure has "filters" declared
|
|
553
|
-
this.isPartiallyFiltered = metadata?.[
|
|
624
|
+
this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
|
|
554
625
|
if (this.isPartiallyFiltered) {
|
|
555
|
-
this.filteredChanges = this.filteredChanges ||
|
|
556
|
-
this.allFilteredChanges = this.allFilteredChanges ||
|
|
626
|
+
this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
|
|
627
|
+
this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
|
|
557
628
|
}
|
|
558
|
-
if
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
629
|
+
// skip if parent is not set
|
|
630
|
+
if (!parent) {
|
|
631
|
+
return;
|
|
632
|
+
}
|
|
633
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
634
|
+
const parentChangeTree = parent[$changes];
|
|
635
|
+
parent = parentChangeTree.parent;
|
|
636
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
637
|
+
}
|
|
638
|
+
const parentMetadata = parent.constructor?.[Symbol.metadata];
|
|
639
|
+
this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
640
|
+
//
|
|
641
|
+
// TODO: refactor this!
|
|
642
|
+
//
|
|
643
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
644
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
645
|
+
//
|
|
646
|
+
if (this.isFiltered) {
|
|
647
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
648
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
649
|
+
if (this.changes.operations.length > 0) {
|
|
650
|
+
// swap changes reference
|
|
651
|
+
const changes = this.changes;
|
|
652
|
+
this.changes = this.filteredChanges;
|
|
653
|
+
this.filteredChanges = changes;
|
|
654
|
+
// swap "all changes" reference
|
|
655
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
656
|
+
this.allFilteredChanges = this.allChanges;
|
|
657
|
+
this.allChanges = allFilteredChanges;
|
|
658
|
+
// console.log("SWAP =>", {
|
|
659
|
+
// "this.allFilteredChanges": this.allFilteredChanges,
|
|
660
|
+
// "this.allChanges": this.allChanges
|
|
661
|
+
// })
|
|
585
662
|
}
|
|
586
663
|
}
|
|
587
664
|
}
|
|
@@ -650,21 +727,24 @@
|
|
|
650
727
|
view[it.offset++] = c;
|
|
651
728
|
}
|
|
652
729
|
else if (c < 0x800) {
|
|
653
|
-
view[it.offset
|
|
654
|
-
view[it.offset
|
|
730
|
+
view[it.offset] = 0xc0 | (c >> 6);
|
|
731
|
+
view[it.offset + 1] = 0x80 | (c & 0x3f);
|
|
732
|
+
it.offset += 2;
|
|
655
733
|
}
|
|
656
734
|
else if (c < 0xd800 || c >= 0xe000) {
|
|
657
|
-
view[it.offset
|
|
658
|
-
view[it.offset
|
|
659
|
-
view[it.offset
|
|
735
|
+
view[it.offset] = 0xe0 | (c >> 12);
|
|
736
|
+
view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
|
|
737
|
+
view[it.offset + 2] = 0x80 | (c & 0x3f);
|
|
738
|
+
it.offset += 3;
|
|
660
739
|
}
|
|
661
740
|
else {
|
|
662
741
|
i++;
|
|
663
742
|
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
664
|
-
view[it.offset
|
|
665
|
-
view[it.offset
|
|
666
|
-
view[it.offset
|
|
667
|
-
view[it.offset
|
|
743
|
+
view[it.offset] = 0xf0 | (c >> 18);
|
|
744
|
+
view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
|
|
745
|
+
view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
|
|
746
|
+
view[it.offset + 3] = 0x80 | (c & 0x3f);
|
|
747
|
+
it.offset += 4;
|
|
668
748
|
}
|
|
669
749
|
}
|
|
670
750
|
}
|
|
@@ -893,7 +973,7 @@
|
|
|
893
973
|
* Used for Schema instances.
|
|
894
974
|
* @private
|
|
895
975
|
*/
|
|
896
|
-
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
976
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
897
977
|
// "compress" field index + operation
|
|
898
978
|
bytes[it.offset++] = (index | operation) & 255;
|
|
899
979
|
// Do not encode value for DELETE operations
|
|
@@ -901,7 +981,7 @@
|
|
|
901
981
|
return;
|
|
902
982
|
}
|
|
903
983
|
const ref = changeTree.ref;
|
|
904
|
-
const metadata = ref.constructor[Symbol.metadata];
|
|
984
|
+
// const metadata: Metadata = ref.constructor[Symbol.metadata];
|
|
905
985
|
const field = metadata[index];
|
|
906
986
|
// TODO: inline this function call small performance gain
|
|
907
987
|
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
@@ -1596,6 +1676,7 @@
|
|
|
1596
1676
|
const proxy = new Proxy(this, {
|
|
1597
1677
|
get: (obj, prop) => {
|
|
1598
1678
|
if (typeof (prop) !== "symbol" &&
|
|
1679
|
+
// FIXME: d8 accuses this as low performance
|
|
1599
1680
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1600
1681
|
) {
|
|
1601
1682
|
return this.items[prop];
|
|
@@ -1613,7 +1694,7 @@
|
|
|
1613
1694
|
if (setValue[$changes]) {
|
|
1614
1695
|
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1615
1696
|
if (obj.items[key] !== undefined) {
|
|
1616
|
-
if (setValue[$changes]
|
|
1697
|
+
if (setValue[$changes].isNew) {
|
|
1617
1698
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
1618
1699
|
}
|
|
1619
1700
|
else {
|
|
@@ -1625,7 +1706,7 @@
|
|
|
1625
1706
|
}
|
|
1626
1707
|
}
|
|
1627
1708
|
}
|
|
1628
|
-
else if (setValue[$changes]
|
|
1709
|
+
else if (setValue[$changes].isNew) {
|
|
1629
1710
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
|
|
1630
1711
|
}
|
|
1631
1712
|
}
|
|
@@ -1659,7 +1740,9 @@
|
|
|
1659
1740
|
});
|
|
1660
1741
|
this[$changes] = new ChangeTree(proxy);
|
|
1661
1742
|
this[$changes].indexes = {};
|
|
1662
|
-
|
|
1743
|
+
if (items.length > 0) {
|
|
1744
|
+
this.push(...items);
|
|
1745
|
+
}
|
|
1663
1746
|
return proxy;
|
|
1664
1747
|
}
|
|
1665
1748
|
set length(newLength) {
|
|
@@ -1678,15 +1761,18 @@
|
|
|
1678
1761
|
}
|
|
1679
1762
|
push(...values) {
|
|
1680
1763
|
let length = this.tmpItems.length;
|
|
1681
|
-
|
|
1682
|
-
|
|
1764
|
+
const changeTree = this[$changes];
|
|
1765
|
+
// values.forEach((value, i) => {
|
|
1766
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1767
|
+
const value = values[i];
|
|
1683
1768
|
if (value === undefined || value === null) {
|
|
1769
|
+
// skip null values
|
|
1684
1770
|
return;
|
|
1685
1771
|
}
|
|
1686
1772
|
else if (typeof (value) === "object" && this[$childType]) {
|
|
1687
1773
|
assertInstanceType(value, this[$childType], this, i);
|
|
1774
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1688
1775
|
}
|
|
1689
|
-
const changeTree = this[$changes];
|
|
1690
1776
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1691
1777
|
this.items.push(value);
|
|
1692
1778
|
this.tmpItems.push(value);
|
|
@@ -1695,8 +1781,9 @@
|
|
|
1695
1781
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1696
1782
|
//
|
|
1697
1783
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1698
|
-
|
|
1699
|
-
|
|
1784
|
+
}
|
|
1785
|
+
// length++;
|
|
1786
|
+
// });
|
|
1700
1787
|
return length;
|
|
1701
1788
|
}
|
|
1702
1789
|
/**
|
|
@@ -1717,6 +1804,7 @@
|
|
|
1717
1804
|
}
|
|
1718
1805
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1719
1806
|
// this.tmpItems[index] = undefined;
|
|
1807
|
+
// this.tmpItems.pop();
|
|
1720
1808
|
this.deletedIndexes[index] = true;
|
|
1721
1809
|
return this.items.pop();
|
|
1722
1810
|
}
|
|
@@ -1781,9 +1869,12 @@
|
|
|
1781
1869
|
//
|
|
1782
1870
|
// TODO: do not use [$changes] at decoding time.
|
|
1783
1871
|
//
|
|
1784
|
-
changeTree.root
|
|
1785
|
-
|
|
1786
|
-
|
|
1872
|
+
const root = changeTree.root;
|
|
1873
|
+
if (root !== undefined) {
|
|
1874
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
1875
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
1876
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
1877
|
+
}
|
|
1787
1878
|
});
|
|
1788
1879
|
changeTree.discard(true);
|
|
1789
1880
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -1827,6 +1918,7 @@
|
|
|
1827
1918
|
const changeTree = this[$changes];
|
|
1828
1919
|
changeTree.delete(index);
|
|
1829
1920
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
1921
|
+
// this.deletedIndexes[index] = true;
|
|
1830
1922
|
return this.items.shift();
|
|
1831
1923
|
}
|
|
1832
1924
|
/**
|
|
@@ -1907,10 +1999,12 @@
|
|
|
1907
1999
|
changeTree.shiftChangeIndexes(items.length);
|
|
1908
2000
|
// new index
|
|
1909
2001
|
if (changeTree.isFiltered) {
|
|
1910
|
-
changeTree.filteredChanges
|
|
2002
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
2003
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1911
2004
|
}
|
|
1912
2005
|
else {
|
|
1913
|
-
changeTree.allChanges
|
|
2006
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2007
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1914
2008
|
}
|
|
1915
2009
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1916
2010
|
items.forEach((_, index) => {
|
|
@@ -2251,7 +2345,7 @@
|
|
|
2251
2345
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2252
2346
|
const index = (isReplace)
|
|
2253
2347
|
? changeTree.indexes[key]
|
|
2254
|
-
: changeTree.indexes[
|
|
2348
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2255
2349
|
let operation = (isReplace)
|
|
2256
2350
|
? exports.OPERATION.REPLACE
|
|
2257
2351
|
: exports.OPERATION.ADD;
|
|
@@ -2263,7 +2357,7 @@
|
|
|
2263
2357
|
if (!isReplace) {
|
|
2264
2358
|
this.$indexes.set(index, key);
|
|
2265
2359
|
changeTree.indexes[key] = index;
|
|
2266
|
-
changeTree.indexes[
|
|
2360
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2267
2361
|
}
|
|
2268
2362
|
else if (!isRef &&
|
|
2269
2363
|
this.$items.get(key) === value) {
|
|
@@ -2338,8 +2432,11 @@
|
|
|
2338
2432
|
}
|
|
2339
2433
|
[$onEncodeEnd]() {
|
|
2340
2434
|
const changeTree = this[$changes];
|
|
2341
|
-
const
|
|
2342
|
-
for (
|
|
2435
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2436
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2437
|
+
const key = keys[i];
|
|
2438
|
+
const fieldIndex = Number(key);
|
|
2439
|
+
const operation = changeTree.indexedOperations[key];
|
|
2343
2440
|
if (operation === exports.OPERATION.DELETE) {
|
|
2344
2441
|
const index = this[$getByIndex](fieldIndex);
|
|
2345
2442
|
delete changeTree.indexes[index];
|
|
@@ -2442,7 +2539,7 @@
|
|
|
2442
2539
|
});
|
|
2443
2540
|
const metadata = (klass[Symbol.metadata] ??= {});
|
|
2444
2541
|
// if any schema/field has filters, mark "context" as having filters.
|
|
2445
|
-
if (metadata[
|
|
2542
|
+
if (metadata[$viewFieldIndexes]) {
|
|
2446
2543
|
this.hasFilters = true;
|
|
2447
2544
|
}
|
|
2448
2545
|
if (parentFieldViewTag !== undefined) {
|
|
@@ -2505,8 +2602,8 @@
|
|
|
2505
2602
|
// // detect index for this field, considering inheritance
|
|
2506
2603
|
// //
|
|
2507
2604
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2508
|
-
// let fieldIndex: number = context.metadata[
|
|
2509
|
-
// ?? (parent && parent[
|
|
2605
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2606
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2510
2607
|
// ?? -1; // no fields defined
|
|
2511
2608
|
// fieldIndex++;
|
|
2512
2609
|
// if (
|
|
@@ -2635,8 +2732,8 @@
|
|
|
2635
2732
|
// //
|
|
2636
2733
|
// metadata[fieldIndex] = {
|
|
2637
2734
|
// type: undefined,
|
|
2638
|
-
// index: (metadata[
|
|
2639
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2735
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2736
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2640
2737
|
// ?? -1) + 1 // no fields defined
|
|
2641
2738
|
// }
|
|
2642
2739
|
// }
|
|
@@ -2679,8 +2776,8 @@
|
|
|
2679
2776
|
//
|
|
2680
2777
|
// detect index for this field, considering inheritance
|
|
2681
2778
|
//
|
|
2682
|
-
fieldIndex = metadata[
|
|
2683
|
-
?? (parentMetadata && parentMetadata[
|
|
2779
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2780
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2684
2781
|
?? -1; // no fields defined
|
|
2685
2782
|
fieldIndex++;
|
|
2686
2783
|
}
|
|
@@ -2707,7 +2804,7 @@
|
|
|
2707
2804
|
return {
|
|
2708
2805
|
get: function () { return this[fieldCached]; },
|
|
2709
2806
|
set: function (value) {
|
|
2710
|
-
const previousValue = this[fieldCached]
|
|
2807
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2711
2808
|
// skip if value is the same as cached.
|
|
2712
2809
|
if (value === previousValue) {
|
|
2713
2810
|
return;
|
|
@@ -2779,8 +2876,8 @@
|
|
|
2779
2876
|
// //
|
|
2780
2877
|
// metadata[field] = {
|
|
2781
2878
|
// type: undefined,
|
|
2782
|
-
// index: (metadata[
|
|
2783
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2879
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2880
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2784
2881
|
// ?? -1) + 1 // no fields defined
|
|
2785
2882
|
// }
|
|
2786
2883
|
// }
|
|
@@ -2818,15 +2915,18 @@
|
|
|
2818
2915
|
ops: {},
|
|
2819
2916
|
refs: []
|
|
2820
2917
|
};
|
|
2821
|
-
$root.changes
|
|
2918
|
+
// for (const refId in $root.changes) {
|
|
2919
|
+
$root.changes.forEach(changeTree => {
|
|
2920
|
+
const changes = changeTree.indexedOperations;
|
|
2822
2921
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2823
|
-
|
|
2922
|
+
for (const index in changes) {
|
|
2923
|
+
const op = changes[index];
|
|
2824
2924
|
const opName = exports.OPERATION[op];
|
|
2825
2925
|
if (!dump.ops[opName]) {
|
|
2826
2926
|
dump.ops[opName] = 0;
|
|
2827
2927
|
}
|
|
2828
2928
|
dump.ops[exports.OPERATION[op]]++;
|
|
2829
|
-
}
|
|
2929
|
+
}
|
|
2830
2930
|
});
|
|
2831
2931
|
return dump;
|
|
2832
2932
|
}
|
|
@@ -2852,6 +2952,7 @@
|
|
|
2852
2952
|
class Schema {
|
|
2853
2953
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2854
2954
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
2955
|
+
// public [$changes]: ChangeTree;
|
|
2855
2956
|
/**
|
|
2856
2957
|
* Assign the property descriptors required to track changes on this instance.
|
|
2857
2958
|
* @param instance
|
|
@@ -2911,12 +3012,7 @@
|
|
|
2911
3012
|
// inline
|
|
2912
3013
|
// Schema.initialize(this);
|
|
2913
3014
|
//
|
|
2914
|
-
|
|
2915
|
-
value: new ChangeTree(this),
|
|
2916
|
-
enumerable: false,
|
|
2917
|
-
writable: true
|
|
2918
|
-
});
|
|
2919
|
-
Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
3015
|
+
Schema.initialize(this);
|
|
2920
3016
|
//
|
|
2921
3017
|
// Assign initial values
|
|
2922
3018
|
//
|
|
@@ -2990,7 +3086,7 @@
|
|
|
2990
3086
|
const changeTree = ref[$changes];
|
|
2991
3087
|
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2992
3088
|
let output = "";
|
|
2993
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
3089
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
2994
3090
|
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
2995
3091
|
return output;
|
|
2996
3092
|
}
|
|
@@ -3008,18 +3104,26 @@
|
|
|
3008
3104
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3009
3105
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3010
3106
|
function dumpChangeSet(changeSet) {
|
|
3011
|
-
|
|
3012
|
-
.
|
|
3013
|
-
.forEach((
|
|
3107
|
+
changeSet.operations
|
|
3108
|
+
.filter(op => op)
|
|
3109
|
+
.forEach((index) => {
|
|
3110
|
+
const operation = changeTree.indexedOperations[index];
|
|
3111
|
+
console.log({ index, operation });
|
|
3112
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3113
|
+
});
|
|
3014
3114
|
}
|
|
3015
3115
|
dumpChangeSet(changeSet);
|
|
3016
3116
|
// display filtered changes
|
|
3017
|
-
if (!isEncodeAll &&
|
|
3117
|
+
if (!isEncodeAll &&
|
|
3118
|
+
changeTree.filteredChanges &&
|
|
3119
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3018
3120
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3019
3121
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3020
3122
|
}
|
|
3021
3123
|
// display filtered changes
|
|
3022
|
-
if (isEncodeAll &&
|
|
3124
|
+
if (isEncodeAll &&
|
|
3125
|
+
changeTree.allFilteredChanges &&
|
|
3126
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3023
3127
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
3024
3128
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3025
3129
|
}
|
|
@@ -3028,10 +3132,12 @@
|
|
|
3028
3132
|
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
3029
3133
|
let output = "";
|
|
3030
3134
|
const rootChangeTree = ref[$changes];
|
|
3135
|
+
const root = rootChangeTree.root;
|
|
3031
3136
|
const changeTrees = new Map();
|
|
3032
3137
|
let totalInstances = 0;
|
|
3033
3138
|
let totalOperations = 0;
|
|
3034
|
-
for (const [
|
|
3139
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3140
|
+
const changeTree = root.changeTrees[refId];
|
|
3035
3141
|
let includeChangeTree = false;
|
|
3036
3142
|
let parentChangeTrees = [];
|
|
3037
3143
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3050,7 +3156,7 @@
|
|
|
3050
3156
|
}
|
|
3051
3157
|
if (includeChangeTree) {
|
|
3052
3158
|
totalInstances += 1;
|
|
3053
|
-
totalOperations += changes.
|
|
3159
|
+
totalOperations += Object.keys(changes).length;
|
|
3054
3160
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3055
3161
|
}
|
|
3056
3162
|
}
|
|
@@ -3068,12 +3174,13 @@
|
|
|
3068
3174
|
visitedParents.add(parentChangeTree);
|
|
3069
3175
|
}
|
|
3070
3176
|
});
|
|
3071
|
-
const changes = changeTree.
|
|
3177
|
+
const changes = changeTree.indexedOperations;
|
|
3072
3178
|
const level = parentChangeTrees.length;
|
|
3073
3179
|
const indent = getIndent(level);
|
|
3074
3180
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3075
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3076
|
-
for (const
|
|
3181
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3182
|
+
for (const index in changes) {
|
|
3183
|
+
const operation = changes[index];
|
|
3077
3184
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3078
3185
|
}
|
|
3079
3186
|
}
|
|
@@ -3434,59 +3541,90 @@
|
|
|
3434
3541
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3435
3542
|
};
|
|
3436
3543
|
|
|
3544
|
+
function spliceOne(arr, index) {
|
|
3545
|
+
// manually splice an array
|
|
3546
|
+
if (index === -1 || index >= arr.length) {
|
|
3547
|
+
return false;
|
|
3548
|
+
}
|
|
3549
|
+
const len = arr.length - 1;
|
|
3550
|
+
for (let i = index; i < len; i++) {
|
|
3551
|
+
arr[i] = arr[i + 1];
|
|
3552
|
+
}
|
|
3553
|
+
arr.length = len;
|
|
3554
|
+
return true;
|
|
3555
|
+
}
|
|
3556
|
+
|
|
3437
3557
|
class Root {
|
|
3438
3558
|
constructor(types) {
|
|
3439
3559
|
this.types = types;
|
|
3440
3560
|
this.nextUniqueId = 0;
|
|
3441
|
-
this.refCount =
|
|
3561
|
+
this.refCount = {};
|
|
3562
|
+
this.changeTrees = {};
|
|
3442
3563
|
// all changes
|
|
3443
|
-
this.allChanges =
|
|
3444
|
-
this.allFilteredChanges =
|
|
3564
|
+
this.allChanges = [];
|
|
3565
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3445
3566
|
// pending changes to be encoded
|
|
3446
|
-
this.changes =
|
|
3447
|
-
this.filteredChanges =
|
|
3567
|
+
this.changes = [];
|
|
3568
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3448
3569
|
}
|
|
3449
3570
|
getNextUniqueId() {
|
|
3450
3571
|
return this.nextUniqueId++;
|
|
3451
3572
|
}
|
|
3452
3573
|
add(changeTree) {
|
|
3453
|
-
|
|
3574
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3575
|
+
changeTree.ensureRefId();
|
|
3576
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3577
|
+
if (isNewChangeTree) {
|
|
3578
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3579
|
+
}
|
|
3580
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3454
3581
|
if (previousRefCount === 0) {
|
|
3455
3582
|
//
|
|
3456
3583
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3457
3584
|
// We need to re-add all changes to the `changes` map.
|
|
3458
3585
|
//
|
|
3459
|
-
changeTree.allChanges.
|
|
3460
|
-
|
|
3461
|
-
|
|
3586
|
+
const ops = changeTree.allChanges.operations;
|
|
3587
|
+
let len = ops.length;
|
|
3588
|
+
while (len--) {
|
|
3589
|
+
changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
|
|
3590
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3591
|
+
}
|
|
3462
3592
|
}
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
return refCount;
|
|
3593
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3594
|
+
return isNewChangeTree;
|
|
3466
3595
|
}
|
|
3467
3596
|
remove(changeTree) {
|
|
3468
|
-
const refCount = (this.refCount.
|
|
3597
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3469
3598
|
if (refCount <= 0) {
|
|
3470
3599
|
//
|
|
3471
3600
|
// Only remove "root" reference if it's the last reference
|
|
3472
3601
|
//
|
|
3473
3602
|
changeTree.root = undefined;
|
|
3474
|
-
this.
|
|
3475
|
-
this.
|
|
3603
|
+
delete this.changeTrees[changeTree.refId];
|
|
3604
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3605
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3476
3606
|
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3477
|
-
this.allFilteredChanges
|
|
3478
|
-
this.filteredChanges
|
|
3607
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3608
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3479
3609
|
}
|
|
3480
|
-
this.refCount.
|
|
3610
|
+
this.refCount[changeTree.refId] = 0;
|
|
3481
3611
|
}
|
|
3482
3612
|
else {
|
|
3483
|
-
this.refCount.
|
|
3613
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3484
3614
|
}
|
|
3485
3615
|
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3486
3616
|
return refCount;
|
|
3487
3617
|
}
|
|
3618
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3619
|
+
const changeSet = this[changeSetName];
|
|
3620
|
+
const index = changeSet.indexOf(changeTree);
|
|
3621
|
+
if (index !== -1) {
|
|
3622
|
+
spliceOne(changeSet, index);
|
|
3623
|
+
// changeSet[index] = undefined;
|
|
3624
|
+
}
|
|
3625
|
+
}
|
|
3488
3626
|
clear() {
|
|
3489
|
-
this.changes.
|
|
3627
|
+
this.changes.length = 0;
|
|
3490
3628
|
}
|
|
3491
3629
|
}
|
|
3492
3630
|
|
|
@@ -3510,20 +3648,26 @@
|
|
|
3510
3648
|
this.state = state;
|
|
3511
3649
|
this.state[$changes].setRoot(this.root);
|
|
3512
3650
|
}
|
|
3513
|
-
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer,
|
|
3651
|
+
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeSetName = "changes", isEncodeAll = changeSetName === "allChanges", initialOffset = it.offset // cache current offset in case we need to resize the buffer
|
|
3514
3652
|
) {
|
|
3515
3653
|
const hasView = (view !== undefined);
|
|
3516
3654
|
const rootChangeTree = this.state[$changes];
|
|
3517
|
-
const
|
|
3518
|
-
|
|
3655
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3656
|
+
const changeTrees = this.root[changeSetName];
|
|
3657
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3658
|
+
const changeTree = changeTrees[i];
|
|
3659
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
3660
|
+
// if (changeTree === undefined) { continue; }
|
|
3661
|
+
const operations = changeTree[changeSetName];
|
|
3519
3662
|
const ref = changeTree.ref;
|
|
3520
3663
|
const ctor = ref.constructor;
|
|
3521
3664
|
const encoder = ctor[$encoder];
|
|
3522
3665
|
const filter = ctor[$filter];
|
|
3666
|
+
const metadata = ctor[Symbol.metadata];
|
|
3523
3667
|
// try { throw new Error(); } catch (e) {
|
|
3524
3668
|
// // only print if not coming from Reflection.ts
|
|
3525
3669
|
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3526
|
-
// console.log("ChangeTree:", { ref: ref.constructor.name
|
|
3670
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
3527
3671
|
// }
|
|
3528
3672
|
// }
|
|
3529
3673
|
if (hasView) {
|
|
@@ -3541,7 +3685,13 @@
|
|
|
3541
3685
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3542
3686
|
number$1(buffer, changeTree.refId, it);
|
|
3543
3687
|
}
|
|
3544
|
-
for (
|
|
3688
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3689
|
+
const fieldIndex = operations.operations[j];
|
|
3690
|
+
const operation = (fieldIndex < 0)
|
|
3691
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3692
|
+
: (isEncodeAll)
|
|
3693
|
+
? exports.OPERATION.ADD
|
|
3694
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3545
3695
|
//
|
|
3546
3696
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3547
3697
|
// they will be encoded per client, based on their view.
|
|
@@ -3549,7 +3699,7 @@
|
|
|
3549
3699
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3550
3700
|
// TODO: avoid checking if no view tags were defined
|
|
3551
3701
|
//
|
|
3552
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3702
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3553
3703
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3554
3704
|
// view?.invisible.add(changeTree);
|
|
3555
3705
|
continue;
|
|
@@ -3564,16 +3714,14 @@
|
|
|
3564
3714
|
// });
|
|
3565
3715
|
// }
|
|
3566
3716
|
// }
|
|
3567
|
-
|
|
3717
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
3718
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3719
|
+
}
|
|
3720
|
+
if (shouldDiscardChanges) {
|
|
3721
|
+
changeTree.discard();
|
|
3722
|
+
// Not a new instance anymore
|
|
3723
|
+
changeTree.isNew = false;
|
|
3568
3724
|
}
|
|
3569
|
-
// if (shouldClearChanges) {
|
|
3570
|
-
// // changeTree.endEncode();
|
|
3571
|
-
// changeTree.changes.clear();
|
|
3572
|
-
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3573
|
-
// changeTree.ref[$onEncodeEnd]?.();
|
|
3574
|
-
// // Not a new instance anymore
|
|
3575
|
-
// delete changeTree[$isNew];
|
|
3576
|
-
// }
|
|
3577
3725
|
}
|
|
3578
3726
|
if (it.offset > buffer.byteLength) {
|
|
3579
3727
|
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
@@ -3590,51 +3738,54 @@
|
|
|
3590
3738
|
if (buffer === this.sharedBuffer) {
|
|
3591
3739
|
this.sharedBuffer = buffer;
|
|
3592
3740
|
}
|
|
3593
|
-
return this.encode({ offset: initialOffset }, view, buffer,
|
|
3741
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3594
3742
|
}
|
|
3595
3743
|
else {
|
|
3596
|
-
//
|
|
3597
|
-
// only clear changes after making sure buffer resize is not required.
|
|
3598
|
-
//
|
|
3599
|
-
if (shouldClearChanges) {
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
}
|
|
3744
|
+
// //
|
|
3745
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
3746
|
+
// //
|
|
3747
|
+
// if (shouldClearChanges) {
|
|
3748
|
+
// //
|
|
3749
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
3750
|
+
// //
|
|
3751
|
+
// this.onEndEncode(changeTrees);
|
|
3752
|
+
// }
|
|
3605
3753
|
return buffer.subarray(0, it.offset);
|
|
3606
3754
|
}
|
|
3607
3755
|
}
|
|
3608
3756
|
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3609
|
-
// console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.
|
|
3757
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
3610
3758
|
// this.debugChanges("allChanges");
|
|
3611
|
-
return this.encode(it, undefined, buffer,
|
|
3759
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3612
3760
|
}
|
|
3613
3761
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3614
3762
|
const viewOffset = it.offset;
|
|
3615
|
-
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.
|
|
3763
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
3616
3764
|
// this.debugChanges("allFilteredChanges");
|
|
3765
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
3617
3766
|
// try to encode "filtered" changes
|
|
3618
|
-
this.encode(it, view, bytes,
|
|
3767
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3619
3768
|
return Buffer.concat([
|
|
3620
3769
|
bytes.subarray(0, sharedOffset),
|
|
3621
3770
|
bytes.subarray(viewOffset, it.offset)
|
|
3622
3771
|
]);
|
|
3623
3772
|
}
|
|
3624
3773
|
debugChanges(field) {
|
|
3625
|
-
const
|
|
3774
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3626
3775
|
? this.root[field]
|
|
3627
3776
|
: field;
|
|
3628
|
-
|
|
3629
|
-
const
|
|
3630
|
-
|
|
3631
|
-
|
|
3777
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3778
|
+
const changeSet = changeTree[field];
|
|
3779
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3780
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3781
|
+
for (const index in changeSet) {
|
|
3782
|
+
const op = changeSet[index];
|
|
3632
3783
|
console.log(" ->", {
|
|
3633
3784
|
index,
|
|
3634
3785
|
field: metadata?.[index],
|
|
3635
3786
|
op: exports.OPERATION[op],
|
|
3636
3787
|
});
|
|
3637
|
-
}
|
|
3788
|
+
}
|
|
3638
3789
|
});
|
|
3639
3790
|
}
|
|
3640
3791
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
@@ -3644,23 +3795,31 @@
|
|
|
3644
3795
|
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3645
3796
|
// this.debugChanges("filteredChanges");
|
|
3646
3797
|
// encode visibility changes (add/remove for this view)
|
|
3647
|
-
const
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3798
|
+
const refIds = Object.keys(view.changes);
|
|
3799
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
3800
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3801
|
+
const refId = refIds[i];
|
|
3802
|
+
const changes = view.changes[refId];
|
|
3803
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3804
|
+
if (changeTree === undefined ||
|
|
3805
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3806
|
+
) {
|
|
3807
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3652
3808
|
continue;
|
|
3653
3809
|
}
|
|
3654
3810
|
const ref = changeTree.ref;
|
|
3655
|
-
const ctor = ref
|
|
3811
|
+
const ctor = ref.constructor;
|
|
3656
3812
|
const encoder = ctor[$encoder];
|
|
3813
|
+
const metadata = ctor[Symbol.metadata];
|
|
3657
3814
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3658
3815
|
number$1(bytes, changeTree.refId, it);
|
|
3659
|
-
const
|
|
3660
|
-
for (
|
|
3816
|
+
const keys = Object.keys(changes);
|
|
3817
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3818
|
+
const key = keys[i];
|
|
3819
|
+
const operation = changes[key];
|
|
3661
3820
|
// isEncodeAll = false
|
|
3662
3821
|
// hasView = true
|
|
3663
|
-
encoder(this, bytes, changeTree,
|
|
3822
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3664
3823
|
}
|
|
3665
3824
|
}
|
|
3666
3825
|
//
|
|
@@ -3668,35 +3827,46 @@
|
|
|
3668
3827
|
// (to allow re-using StateView's for multiple clients)
|
|
3669
3828
|
//
|
|
3670
3829
|
// clear "view" changes after encoding
|
|
3671
|
-
view.changes
|
|
3830
|
+
view.changes = {};
|
|
3831
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
3672
3832
|
// try to encode "filtered" changes
|
|
3673
|
-
this.encode(it, view, bytes,
|
|
3833
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3674
3834
|
return Buffer.concat([
|
|
3675
3835
|
bytes.subarray(0, sharedOffset),
|
|
3676
3836
|
bytes.subarray(viewOffset, it.offset)
|
|
3677
3837
|
]);
|
|
3678
3838
|
}
|
|
3679
3839
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3840
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3841
|
+
// changeTree.endEncode();
|
|
3842
|
+
// });
|
|
3843
|
+
// for (const refId in changeTrees) {
|
|
3844
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3845
|
+
// changeTree.endEncode();
|
|
3846
|
+
// // changeTree.changes.clear();
|
|
3847
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3848
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3849
|
+
// // // Not a new instance anymore
|
|
3850
|
+
// // delete changeTree[$isNew];
|
|
3851
|
+
// }
|
|
3689
3852
|
}
|
|
3690
3853
|
discardChanges() {
|
|
3854
|
+
// console.log("DISCARD CHANGES!");
|
|
3691
3855
|
// discard shared changes
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3856
|
+
let length = this.root.changes.length;
|
|
3857
|
+
if (length > 0) {
|
|
3858
|
+
while (length--) {
|
|
3859
|
+
this.root.changes[length]?.endEncode();
|
|
3860
|
+
}
|
|
3861
|
+
this.root.changes.length = 0;
|
|
3695
3862
|
}
|
|
3696
3863
|
// discard filtered changes
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3864
|
+
length = this.root.filteredChanges.length;
|
|
3865
|
+
if (length > 0) {
|
|
3866
|
+
while (length--) {
|
|
3867
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3868
|
+
}
|
|
3869
|
+
this.root.filteredChanges.length = 0;
|
|
3700
3870
|
}
|
|
3701
3871
|
}
|
|
3702
3872
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
@@ -3709,19 +3879,6 @@
|
|
|
3709
3879
|
}
|
|
3710
3880
|
}
|
|
3711
3881
|
|
|
3712
|
-
function spliceOne(arr, index) {
|
|
3713
|
-
// manually splice an array
|
|
3714
|
-
if (index === -1 || index >= arr.length) {
|
|
3715
|
-
return false;
|
|
3716
|
-
}
|
|
3717
|
-
const len = arr.length - 1;
|
|
3718
|
-
for (let i = index; i < len; i++) {
|
|
3719
|
-
arr[i] = arr[i + 1];
|
|
3720
|
-
}
|
|
3721
|
-
arr.length = len;
|
|
3722
|
-
return true;
|
|
3723
|
-
}
|
|
3724
|
-
|
|
3725
3882
|
class DecodingWarning extends Error {
|
|
3726
3883
|
constructor(message) {
|
|
3727
3884
|
super(message);
|
|
@@ -3883,7 +4040,7 @@
|
|
|
3883
4040
|
}
|
|
3884
4041
|
ref[$onDecodeEnd]?.();
|
|
3885
4042
|
ref = nextRef;
|
|
3886
|
-
decoder = ref
|
|
4043
|
+
decoder = ref.constructor[$decoder];
|
|
3887
4044
|
continue;
|
|
3888
4045
|
}
|
|
3889
4046
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -4360,7 +4517,7 @@
|
|
|
4360
4517
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
4361
4518
|
* (This is used to force encoding a property, even if it was not changed)
|
|
4362
4519
|
*/
|
|
4363
|
-
this.changes =
|
|
4520
|
+
this.changes = {};
|
|
4364
4521
|
}
|
|
4365
4522
|
// TODO: allow to set multiple tags at once
|
|
4366
4523
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
@@ -4382,10 +4539,10 @@
|
|
|
4382
4539
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
4383
4540
|
// be set (only the parent's changes are set)
|
|
4384
4541
|
//
|
|
4385
|
-
let changes = this.changes.
|
|
4542
|
+
let changes = this.changes[changeTree.refId];
|
|
4386
4543
|
if (changes === undefined) {
|
|
4387
|
-
changes =
|
|
4388
|
-
this.changes.
|
|
4544
|
+
changes = {};
|
|
4545
|
+
this.changes[changeTree.refId] = changes;
|
|
4389
4546
|
}
|
|
4390
4547
|
// set tag
|
|
4391
4548
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -4402,9 +4559,9 @@
|
|
|
4402
4559
|
}
|
|
4403
4560
|
tags.add(tag);
|
|
4404
4561
|
// Ref: add tagged properties
|
|
4405
|
-
metadata?.[
|
|
4562
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4406
4563
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
4407
|
-
changes
|
|
4564
|
+
changes[index] = exports.OPERATION.ADD;
|
|
4408
4565
|
}
|
|
4409
4566
|
});
|
|
4410
4567
|
}
|
|
@@ -4413,16 +4570,21 @@
|
|
|
4413
4570
|
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4414
4571
|
? changeTree.allFilteredChanges
|
|
4415
4572
|
: changeTree.allChanges;
|
|
4416
|
-
|
|
4573
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4574
|
+
const index = changeSet.operations[i];
|
|
4575
|
+
if (index === undefined) {
|
|
4576
|
+
continue;
|
|
4577
|
+
} // skip "undefined" indexes
|
|
4578
|
+
const op = changeTree.indexedOperations[index];
|
|
4417
4579
|
const tagAtIndex = metadata?.[index].tag;
|
|
4418
4580
|
if ((isInvisible || // if "invisible", include all
|
|
4419
4581
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4420
4582
|
tagAtIndex === tag // tagged property
|
|
4421
4583
|
) &&
|
|
4422
4584
|
op !== exports.OPERATION.DELETE) {
|
|
4423
|
-
changes
|
|
4585
|
+
changes[index] = op;
|
|
4424
4586
|
}
|
|
4425
|
-
}
|
|
4587
|
+
}
|
|
4426
4588
|
}
|
|
4427
4589
|
// Add children of this ChangeTree to this view
|
|
4428
4590
|
changeTree.forEachChild((change, index) => {
|
|
@@ -4448,10 +4610,10 @@
|
|
|
4448
4610
|
}
|
|
4449
4611
|
// add parent's tag properties
|
|
4450
4612
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4451
|
-
let changes = this.changes.
|
|
4613
|
+
let changes = this.changes[changeTree.refId];
|
|
4452
4614
|
if (changes === undefined) {
|
|
4453
|
-
changes =
|
|
4454
|
-
this.changes.
|
|
4615
|
+
changes = {};
|
|
4616
|
+
this.changes[changeTree.refId] = changes;
|
|
4455
4617
|
}
|
|
4456
4618
|
if (!this.tags) {
|
|
4457
4619
|
this.tags = new WeakMap();
|
|
@@ -4465,7 +4627,7 @@
|
|
|
4465
4627
|
tags = this.tags.get(changeTree);
|
|
4466
4628
|
}
|
|
4467
4629
|
tags.add(tag);
|
|
4468
|
-
changes
|
|
4630
|
+
changes[parentIndex] = exports.OPERATION.ADD;
|
|
4469
4631
|
}
|
|
4470
4632
|
}
|
|
4471
4633
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4477,32 +4639,32 @@
|
|
|
4477
4639
|
this.items.delete(changeTree);
|
|
4478
4640
|
const ref = changeTree.ref;
|
|
4479
4641
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4480
|
-
let changes = this.changes.
|
|
4642
|
+
let changes = this.changes[changeTree.refId];
|
|
4481
4643
|
if (changes === undefined) {
|
|
4482
|
-
changes =
|
|
4483
|
-
this.changes.
|
|
4644
|
+
changes = {};
|
|
4645
|
+
this.changes[changeTree.refId] = changes;
|
|
4484
4646
|
}
|
|
4485
4647
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4486
4648
|
// parent is collection (Map/Array)
|
|
4487
4649
|
const parent = changeTree.parent;
|
|
4488
4650
|
if (!Metadata.isValidInstance(parent)) {
|
|
4489
4651
|
const parentChangeTree = parent[$changes];
|
|
4490
|
-
let changes = this.changes.
|
|
4652
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4491
4653
|
if (changes === undefined) {
|
|
4492
|
-
changes =
|
|
4493
|
-
this.changes.
|
|
4654
|
+
changes = {};
|
|
4655
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4494
4656
|
}
|
|
4495
4657
|
// DELETE / DELETE BY REF ID
|
|
4496
|
-
changes
|
|
4658
|
+
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
4497
4659
|
}
|
|
4498
4660
|
else {
|
|
4499
4661
|
// delete all "tagged" properties.
|
|
4500
|
-
metadata[
|
|
4662
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4501
4663
|
}
|
|
4502
4664
|
}
|
|
4503
4665
|
else {
|
|
4504
4666
|
// delete only tagged properties
|
|
4505
|
-
metadata[
|
|
4667
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4506
4668
|
}
|
|
4507
4669
|
// remove tag
|
|
4508
4670
|
if (this.tags && this.tags.has(changeTree)) {
|