@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/cjs/index.js
CHANGED
|
@@ -36,7 +36,6 @@ const $decoder = Symbol("$decoder");
|
|
|
36
36
|
const $filter = Symbol("$filter");
|
|
37
37
|
const $getByIndex = Symbol("$getByIndex");
|
|
38
38
|
const $deleteByIndex = Symbol("$deleteByIndex");
|
|
39
|
-
const $descriptors = Symbol("$descriptors");
|
|
40
39
|
/**
|
|
41
40
|
* Used to hold ChangeTree instances whitin the structures
|
|
42
41
|
*/
|
|
@@ -46,11 +45,6 @@ const $changes = Symbol('$changes');
|
|
|
46
45
|
* (MapSchema, ArraySchema, etc.)
|
|
47
46
|
*/
|
|
48
47
|
const $childType = Symbol('$childType');
|
|
49
|
-
/**
|
|
50
|
-
* Special ChangeTree property to identify new instances
|
|
51
|
-
* (Once they're encoded, they're not new anymore)
|
|
52
|
-
*/
|
|
53
|
-
const $isNew = Symbol("$isNew");
|
|
54
48
|
/**
|
|
55
49
|
* Optional "discard" method for custom types (ArraySchema)
|
|
56
50
|
* (Discards changes for next serialization)
|
|
@@ -60,6 +54,14 @@ const $onEncodeEnd = Symbol('$onEncodeEnd');
|
|
|
60
54
|
* When decoding, this method is called after the instance is fully decoded
|
|
61
55
|
*/
|
|
62
56
|
const $onDecodeEnd = Symbol("$onDecodeEnd");
|
|
57
|
+
/**
|
|
58
|
+
* Metadata
|
|
59
|
+
*/
|
|
60
|
+
const $descriptors = Symbol("$descriptors");
|
|
61
|
+
const $numFields = "$__numFields";
|
|
62
|
+
const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
|
|
63
|
+
const $viewFieldIndexes = "$__viewFieldIndexes";
|
|
64
|
+
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
63
65
|
|
|
64
66
|
const registeredTypes = {};
|
|
65
67
|
const identifiers = new Map();
|
|
@@ -106,7 +108,7 @@ const Metadata = {
|
|
|
106
108
|
};
|
|
107
109
|
}
|
|
108
110
|
// map -1 as last field index
|
|
109
|
-
Object.defineProperty(metadata,
|
|
111
|
+
Object.defineProperty(metadata, $numFields, {
|
|
110
112
|
value: index,
|
|
111
113
|
enumerable: false,
|
|
112
114
|
configurable: true
|
|
@@ -119,14 +121,14 @@ const Metadata = {
|
|
|
119
121
|
});
|
|
120
122
|
// if child Ref/complex type, add to -4
|
|
121
123
|
if (typeof (metadata[index].type) !== "string") {
|
|
122
|
-
if (metadata[
|
|
123
|
-
Object.defineProperty(metadata,
|
|
124
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
125
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
124
126
|
value: [],
|
|
125
127
|
enumerable: false,
|
|
126
128
|
configurable: true,
|
|
127
129
|
});
|
|
128
130
|
}
|
|
129
|
-
metadata[
|
|
131
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
130
132
|
}
|
|
131
133
|
},
|
|
132
134
|
setTag(metadata, fieldName, tag) {
|
|
@@ -134,25 +136,25 @@ const Metadata = {
|
|
|
134
136
|
const field = metadata[index];
|
|
135
137
|
// add 'tag' to the field
|
|
136
138
|
field.tag = tag;
|
|
137
|
-
if (!metadata[
|
|
139
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
138
140
|
// -2: all field indexes with "view" tag
|
|
139
|
-
Object.defineProperty(metadata,
|
|
141
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
140
142
|
value: [],
|
|
141
143
|
enumerable: false,
|
|
142
144
|
configurable: true
|
|
143
145
|
});
|
|
144
146
|
// -3: field indexes by "view" tag
|
|
145
|
-
Object.defineProperty(metadata,
|
|
147
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
146
148
|
value: {},
|
|
147
149
|
enumerable: false,
|
|
148
150
|
configurable: true
|
|
149
151
|
});
|
|
150
152
|
}
|
|
151
|
-
metadata[
|
|
152
|
-
if (!metadata[
|
|
153
|
-
metadata[
|
|
153
|
+
metadata[$viewFieldIndexes].push(index);
|
|
154
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
155
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
154
156
|
}
|
|
155
|
-
metadata[
|
|
157
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
156
158
|
},
|
|
157
159
|
setFields(target, fields) {
|
|
158
160
|
const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
|
|
@@ -177,7 +179,7 @@ const Metadata = {
|
|
|
177
179
|
//
|
|
178
180
|
const metadata = {};
|
|
179
181
|
klass[Symbol.metadata] = metadata;
|
|
180
|
-
Object.defineProperty(metadata,
|
|
182
|
+
Object.defineProperty(metadata, $numFields, {
|
|
181
183
|
value: 0,
|
|
182
184
|
enumerable: false,
|
|
183
185
|
configurable: true,
|
|
@@ -191,7 +193,7 @@ const Metadata = {
|
|
|
191
193
|
if (parentMetadata) {
|
|
192
194
|
// assign parent metadata to current
|
|
193
195
|
Object.assign(metadata, parentMetadata);
|
|
194
|
-
for (let i = 0; i <= parentMetadata[
|
|
196
|
+
for (let i = 0; i <= parentMetadata[$numFields]; i++) {
|
|
195
197
|
const fieldName = parentMetadata[i].name;
|
|
196
198
|
Object.defineProperty(metadata, fieldName, {
|
|
197
199
|
value: parentMetadata[fieldName],
|
|
@@ -199,8 +201,8 @@ const Metadata = {
|
|
|
199
201
|
configurable: true,
|
|
200
202
|
});
|
|
201
203
|
}
|
|
202
|
-
Object.defineProperty(metadata,
|
|
203
|
-
value: parentMetadata[
|
|
204
|
+
Object.defineProperty(metadata, $numFields, {
|
|
205
|
+
value: parentMetadata[$numFields],
|
|
204
206
|
enumerable: false,
|
|
205
207
|
configurable: true,
|
|
206
208
|
writable: true,
|
|
@@ -212,40 +214,69 @@ const Metadata = {
|
|
|
212
214
|
},
|
|
213
215
|
isValidInstance(klass) {
|
|
214
216
|
return (klass.constructor[Symbol.metadata] &&
|
|
215
|
-
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata],
|
|
217
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
216
218
|
},
|
|
217
219
|
getFields(klass) {
|
|
218
220
|
const metadata = klass[Symbol.metadata];
|
|
219
221
|
const fields = {};
|
|
220
|
-
for (let i = 0; i <= metadata[
|
|
222
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
221
223
|
fields[metadata[i].name] = metadata[i].type;
|
|
222
224
|
}
|
|
223
225
|
return fields;
|
|
224
226
|
}
|
|
225
227
|
};
|
|
226
228
|
|
|
227
|
-
|
|
229
|
+
function setOperationAtIndex(changeSet, index) {
|
|
230
|
+
const operationsIndex = changeSet.indexes[index];
|
|
231
|
+
if (operationsIndex === undefined) {
|
|
232
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
changeSet.operations[operationsIndex] = index;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
239
|
+
const operationsIndex = changeSet.indexes[index];
|
|
240
|
+
if (operationsIndex !== undefined) {
|
|
241
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
242
|
+
}
|
|
243
|
+
delete changeSet.indexes[index];
|
|
244
|
+
}
|
|
245
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
246
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
247
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
228
250
|
class ChangeTree {
|
|
229
|
-
static { _a$5 = $isNew; }
|
|
230
251
|
constructor(ref) {
|
|
231
252
|
this.isFiltered = false;
|
|
232
253
|
this.isPartiallyFiltered = false;
|
|
233
|
-
this.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
254
|
+
this.indexedOperations = {};
|
|
255
|
+
//
|
|
256
|
+
// TODO:
|
|
257
|
+
// try storing the index + operation per item.
|
|
258
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
259
|
+
//
|
|
260
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
261
|
+
//
|
|
262
|
+
this.changes = { indexes: {}, operations: [] };
|
|
263
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
264
|
+
/**
|
|
265
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
266
|
+
*/
|
|
267
|
+
this.isNew = true;
|
|
237
268
|
this.ref = ref;
|
|
238
269
|
//
|
|
239
270
|
// Does this structure have "filters" declared?
|
|
240
271
|
//
|
|
241
|
-
if (ref.constructor[Symbol.metadata]?.[
|
|
242
|
-
this.allFilteredChanges =
|
|
243
|
-
this.filteredChanges =
|
|
272
|
+
if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
|
|
273
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
274
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
244
275
|
}
|
|
245
276
|
}
|
|
246
277
|
setRoot(root) {
|
|
247
278
|
this.root = root;
|
|
248
|
-
this.root.add(this);
|
|
279
|
+
const isNewChangeTree = this.root.add(this);
|
|
249
280
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
250
281
|
if (this.root.types.hasFilters) {
|
|
251
282
|
//
|
|
@@ -256,22 +287,24 @@ class ChangeTree {
|
|
|
256
287
|
//
|
|
257
288
|
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
258
289
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
259
|
-
|
|
260
|
-
|
|
290
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
291
|
+
if (isNewChangeTree) {
|
|
292
|
+
this.root.allFilteredChanges.push(this);
|
|
293
|
+
}
|
|
261
294
|
}
|
|
262
295
|
}
|
|
263
296
|
if (!this.isFiltered) {
|
|
264
|
-
|
|
265
|
-
|
|
297
|
+
enqueueChangeTree(root, this, 'changes');
|
|
298
|
+
if (isNewChangeTree) {
|
|
299
|
+
this.root.allChanges.push(this);
|
|
300
|
+
}
|
|
266
301
|
}
|
|
267
|
-
|
|
302
|
+
// Recursively set root on child structures
|
|
268
303
|
if (metadata) {
|
|
269
|
-
metadata[
|
|
304
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
270
305
|
const field = metadata[index];
|
|
271
306
|
const value = this.ref[field.name];
|
|
272
|
-
|
|
273
|
-
value[$changes].setRoot(root);
|
|
274
|
-
}
|
|
307
|
+
value?.[$changes].setRoot(root);
|
|
275
308
|
});
|
|
276
309
|
}
|
|
277
310
|
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
@@ -288,31 +321,36 @@ class ChangeTree {
|
|
|
288
321
|
if (!root) {
|
|
289
322
|
return;
|
|
290
323
|
}
|
|
291
|
-
root.add(this);
|
|
292
324
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
293
325
|
// skip if parent is already set
|
|
294
326
|
if (root !== this.root) {
|
|
295
327
|
this.root = root;
|
|
328
|
+
const isNewChangeTree = root.add(this);
|
|
296
329
|
if (root.types.hasFilters) {
|
|
297
330
|
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
298
331
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
299
|
-
|
|
300
|
-
|
|
332
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
333
|
+
if (isNewChangeTree) {
|
|
334
|
+
this.root.allFilteredChanges.push(this);
|
|
335
|
+
}
|
|
301
336
|
}
|
|
302
337
|
}
|
|
303
338
|
if (!this.isFiltered) {
|
|
304
|
-
|
|
305
|
-
|
|
339
|
+
enqueueChangeTree(root, this, 'changes');
|
|
340
|
+
if (isNewChangeTree) {
|
|
341
|
+
this.root.allChanges.push(this);
|
|
342
|
+
}
|
|
306
343
|
}
|
|
307
|
-
|
|
344
|
+
}
|
|
345
|
+
else {
|
|
346
|
+
root.add(this);
|
|
308
347
|
}
|
|
309
348
|
// assign same parent on child structures
|
|
310
349
|
if (metadata) {
|
|
311
|
-
metadata[
|
|
350
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
312
351
|
const field = metadata[index];
|
|
313
352
|
const value = this.ref[field.name];
|
|
314
353
|
value?.[$changes].setParent(this.ref, root, index);
|
|
315
|
-
// console.log(this.ref.constructor.name, field.name, value);
|
|
316
354
|
// try { throw new Error(); } catch (e) {
|
|
317
355
|
// console.log(e.stack);
|
|
318
356
|
// }
|
|
@@ -331,7 +369,7 @@ class ChangeTree {
|
|
|
331
369
|
//
|
|
332
370
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
333
371
|
if (metadata) {
|
|
334
|
-
metadata[
|
|
372
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
335
373
|
const field = metadata[index];
|
|
336
374
|
const value = this.ref[field.name];
|
|
337
375
|
if (value) {
|
|
@@ -347,8 +385,10 @@ class ChangeTree {
|
|
|
347
385
|
}
|
|
348
386
|
}
|
|
349
387
|
operation(op) {
|
|
350
|
-
|
|
351
|
-
this.
|
|
388
|
+
// operations without index use negative values to represent them
|
|
389
|
+
// this is checked during .encode() time.
|
|
390
|
+
this.changes.operations.push(-op);
|
|
391
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
352
392
|
}
|
|
353
393
|
change(index, operation = exports.OPERATION.ADD) {
|
|
354
394
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
@@ -356,7 +396,7 @@ class ChangeTree {
|
|
|
356
396
|
const changeSet = (isFiltered)
|
|
357
397
|
? this.filteredChanges
|
|
358
398
|
: this.changes;
|
|
359
|
-
const previousOperation =
|
|
399
|
+
const previousOperation = this.indexedOperations[index];
|
|
360
400
|
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
361
401
|
const op = (!previousOperation)
|
|
362
402
|
? operation
|
|
@@ -366,16 +406,19 @@ class ChangeTree {
|
|
|
366
406
|
//
|
|
367
407
|
// TODO: are DELETE operations being encoded as ADD here ??
|
|
368
408
|
//
|
|
369
|
-
|
|
409
|
+
this.indexedOperations[index] = op;
|
|
370
410
|
}
|
|
411
|
+
setOperationAtIndex(changeSet, index);
|
|
371
412
|
if (isFiltered) {
|
|
372
|
-
this.allFilteredChanges
|
|
373
|
-
this.root
|
|
374
|
-
|
|
413
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
414
|
+
if (this.root) {
|
|
415
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
416
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
417
|
+
}
|
|
375
418
|
}
|
|
376
419
|
else {
|
|
377
|
-
this.allChanges
|
|
378
|
-
this.root
|
|
420
|
+
setOperationAtIndex(this.allChanges, index);
|
|
421
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
379
422
|
}
|
|
380
423
|
}
|
|
381
424
|
shiftChangeIndexes(shiftIndex) {
|
|
@@ -387,12 +430,15 @@ class ChangeTree {
|
|
|
387
430
|
const changeSet = (this.isFiltered)
|
|
388
431
|
? this.filteredChanges
|
|
389
432
|
: this.changes;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
433
|
+
const newIndexedOperations = {};
|
|
434
|
+
const newIndexes = {};
|
|
435
|
+
for (const index in this.indexedOperations) {
|
|
436
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
437
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
395
438
|
}
|
|
439
|
+
this.indexedOperations = newIndexedOperations;
|
|
440
|
+
changeSet.indexes = newIndexes;
|
|
441
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
396
442
|
}
|
|
397
443
|
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
398
444
|
//
|
|
@@ -408,24 +454,36 @@ class ChangeTree {
|
|
|
408
454
|
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
409
455
|
}
|
|
410
456
|
}
|
|
411
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
457
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
458
|
+
const newIndexes = {};
|
|
459
|
+
for (const key in changeSet.indexes) {
|
|
460
|
+
const index = changeSet.indexes[key];
|
|
461
|
+
if (index > startIndex) {
|
|
462
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
416
463
|
}
|
|
417
|
-
|
|
464
|
+
else {
|
|
465
|
+
newIndexes[key] = index;
|
|
466
|
+
}
|
|
467
|
+
}
|
|
468
|
+
changeSet.indexes = newIndexes;
|
|
469
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
470
|
+
const index = changeSet.operations[i];
|
|
471
|
+
if (index > startIndex) {
|
|
472
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
473
|
+
}
|
|
474
|
+
}
|
|
418
475
|
}
|
|
419
476
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
this.
|
|
423
|
-
this.
|
|
477
|
+
this.indexedOperations[index] = operation;
|
|
478
|
+
if (this.filteredChanges) {
|
|
479
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
480
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
481
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
424
482
|
}
|
|
425
483
|
else {
|
|
426
|
-
this.allChanges
|
|
427
|
-
this.changes
|
|
428
|
-
this.root
|
|
484
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
485
|
+
setOperationAtIndex(this.changes, index);
|
|
486
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
429
487
|
}
|
|
430
488
|
}
|
|
431
489
|
getType(index) {
|
|
@@ -444,8 +502,7 @@ class ChangeTree {
|
|
|
444
502
|
}
|
|
445
503
|
}
|
|
446
504
|
getChange(index) {
|
|
447
|
-
|
|
448
|
-
return this.changes.get(index) ?? this.filteredChanges?.get(index);
|
|
505
|
+
return this.indexedOperations[index];
|
|
449
506
|
}
|
|
450
507
|
//
|
|
451
508
|
// used during `.encode()`
|
|
@@ -469,8 +526,9 @@ class ChangeTree {
|
|
|
469
526
|
const changeSet = (this.filteredChanges)
|
|
470
527
|
? this.filteredChanges
|
|
471
528
|
: this.changes;
|
|
529
|
+
this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
|
|
530
|
+
setOperationAtIndex(changeSet, index);
|
|
472
531
|
const previousValue = this.getValue(index);
|
|
473
|
-
changeSet.set(index, operation ?? exports.OPERATION.DELETE);
|
|
474
532
|
// remove `root` reference
|
|
475
533
|
if (previousValue && previousValue[$changes]) {
|
|
476
534
|
//
|
|
@@ -486,23 +544,26 @@ class ChangeTree {
|
|
|
486
544
|
this.root?.remove(previousValue[$changes]);
|
|
487
545
|
}
|
|
488
546
|
//
|
|
489
|
-
// FIXME: this is looking a
|
|
547
|
+
// FIXME: this is looking a ugly and repeated
|
|
490
548
|
//
|
|
491
549
|
if (this.filteredChanges) {
|
|
492
|
-
this.
|
|
493
|
-
this.
|
|
550
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
551
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
494
552
|
}
|
|
495
553
|
else {
|
|
496
|
-
this.
|
|
497
|
-
this.
|
|
554
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
555
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
498
556
|
}
|
|
499
557
|
}
|
|
500
558
|
endEncode() {
|
|
501
|
-
this.
|
|
559
|
+
this.indexedOperations = {};
|
|
560
|
+
// // clear changes
|
|
561
|
+
// this.changes.indexes = {};
|
|
562
|
+
// this.changes.operations.length = 0;
|
|
502
563
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
503
564
|
this.ref[$onEncodeEnd]?.();
|
|
504
565
|
// Not a new instance anymore
|
|
505
|
-
|
|
566
|
+
this.isNew = false;
|
|
506
567
|
}
|
|
507
568
|
discard(discardAll = false) {
|
|
508
569
|
//
|
|
@@ -511,13 +572,22 @@ class ChangeTree {
|
|
|
511
572
|
// REPLACE in case same key is used on next patches.
|
|
512
573
|
//
|
|
513
574
|
this.ref[$onEncodeEnd]?.();
|
|
514
|
-
this.
|
|
515
|
-
this.
|
|
516
|
-
|
|
517
|
-
this.
|
|
575
|
+
this.indexedOperations = {};
|
|
576
|
+
this.changes.indexes = {};
|
|
577
|
+
this.changes.operations.length = 0;
|
|
578
|
+
this.changes.queueRootIndex = undefined;
|
|
579
|
+
if (this.filteredChanges !== undefined) {
|
|
580
|
+
this.filteredChanges.indexes = {};
|
|
581
|
+
this.filteredChanges.operations.length = 0;
|
|
582
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
583
|
+
}
|
|
518
584
|
if (discardAll) {
|
|
519
|
-
this.allChanges.
|
|
520
|
-
this.
|
|
585
|
+
this.allChanges.indexes = {};
|
|
586
|
+
this.allChanges.operations.length = 0;
|
|
587
|
+
if (this.allFilteredChanges !== undefined) {
|
|
588
|
+
this.allFilteredChanges.indexes = {};
|
|
589
|
+
this.allFilteredChanges.operations.length = 0;
|
|
590
|
+
}
|
|
521
591
|
// remove children references
|
|
522
592
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
523
593
|
}
|
|
@@ -526,12 +596,13 @@ class ChangeTree {
|
|
|
526
596
|
* Recursively discard all changes from this, and child structures.
|
|
527
597
|
*/
|
|
528
598
|
discardAll() {
|
|
529
|
-
this.
|
|
530
|
-
|
|
599
|
+
const keys = Object.keys(this.indexedOperations);
|
|
600
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
601
|
+
const value = this.getValue(Number(keys[i]));
|
|
531
602
|
if (value && value[$changes]) {
|
|
532
603
|
value[$changes].discardAll();
|
|
533
604
|
}
|
|
534
|
-
}
|
|
605
|
+
}
|
|
535
606
|
this.discard();
|
|
536
607
|
}
|
|
537
608
|
ensureRefId() {
|
|
@@ -542,42 +613,48 @@ class ChangeTree {
|
|
|
542
613
|
this.refId = this.root.getNextUniqueId();
|
|
543
614
|
}
|
|
544
615
|
get changed() {
|
|
545
|
-
return this.
|
|
616
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
546
617
|
}
|
|
547
618
|
checkIsFiltered(metadata, parent, parentIndex) {
|
|
548
619
|
// Detect if current structure has "filters" declared
|
|
549
|
-
this.isPartiallyFiltered = metadata?.[
|
|
620
|
+
this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
|
|
550
621
|
if (this.isPartiallyFiltered) {
|
|
551
|
-
this.filteredChanges = this.filteredChanges ||
|
|
552
|
-
this.allFilteredChanges = this.allFilteredChanges ||
|
|
622
|
+
this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
|
|
623
|
+
this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
|
|
553
624
|
}
|
|
554
|
-
if
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
625
|
+
// skip if parent is not set
|
|
626
|
+
if (!parent) {
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
630
|
+
const parentChangeTree = parent[$changes];
|
|
631
|
+
parent = parentChangeTree.parent;
|
|
632
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
633
|
+
}
|
|
634
|
+
const parentMetadata = parent.constructor?.[Symbol.metadata];
|
|
635
|
+
this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
636
|
+
//
|
|
637
|
+
// TODO: refactor this!
|
|
638
|
+
//
|
|
639
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
640
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
641
|
+
//
|
|
642
|
+
if (this.isFiltered) {
|
|
643
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
644
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
645
|
+
if (this.changes.operations.length > 0) {
|
|
646
|
+
// swap changes reference
|
|
647
|
+
const changes = this.changes;
|
|
648
|
+
this.changes = this.filteredChanges;
|
|
649
|
+
this.filteredChanges = changes;
|
|
650
|
+
// swap "all changes" reference
|
|
651
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
652
|
+
this.allFilteredChanges = this.allChanges;
|
|
653
|
+
this.allChanges = allFilteredChanges;
|
|
654
|
+
// console.log("SWAP =>", {
|
|
655
|
+
// "this.allFilteredChanges": this.allFilteredChanges,
|
|
656
|
+
// "this.allChanges": this.allChanges
|
|
657
|
+
// })
|
|
581
658
|
}
|
|
582
659
|
}
|
|
583
660
|
}
|
|
@@ -646,21 +723,24 @@ function utf8Write(view, str, it) {
|
|
|
646
723
|
view[it.offset++] = c;
|
|
647
724
|
}
|
|
648
725
|
else if (c < 0x800) {
|
|
649
|
-
view[it.offset
|
|
650
|
-
view[it.offset
|
|
726
|
+
view[it.offset] = 0xc0 | (c >> 6);
|
|
727
|
+
view[it.offset + 1] = 0x80 | (c & 0x3f);
|
|
728
|
+
it.offset += 2;
|
|
651
729
|
}
|
|
652
730
|
else if (c < 0xd800 || c >= 0xe000) {
|
|
653
|
-
view[it.offset
|
|
654
|
-
view[it.offset
|
|
655
|
-
view[it.offset
|
|
731
|
+
view[it.offset] = 0xe0 | (c >> 12);
|
|
732
|
+
view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
|
|
733
|
+
view[it.offset + 2] = 0x80 | (c & 0x3f);
|
|
734
|
+
it.offset += 3;
|
|
656
735
|
}
|
|
657
736
|
else {
|
|
658
737
|
i++;
|
|
659
738
|
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
660
|
-
view[it.offset
|
|
661
|
-
view[it.offset
|
|
662
|
-
view[it.offset
|
|
663
|
-
view[it.offset
|
|
739
|
+
view[it.offset] = 0xf0 | (c >> 18);
|
|
740
|
+
view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
|
|
741
|
+
view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
|
|
742
|
+
view[it.offset + 3] = 0x80 | (c & 0x3f);
|
|
743
|
+
it.offset += 4;
|
|
664
744
|
}
|
|
665
745
|
}
|
|
666
746
|
}
|
|
@@ -889,7 +969,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
889
969
|
* Used for Schema instances.
|
|
890
970
|
* @private
|
|
891
971
|
*/
|
|
892
|
-
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
972
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
893
973
|
// "compress" field index + operation
|
|
894
974
|
bytes[it.offset++] = (index | operation) & 255;
|
|
895
975
|
// Do not encode value for DELETE operations
|
|
@@ -897,7 +977,7 @@ const encodeSchemaOperation = function (encoder, bytes, changeTree, index, opera
|
|
|
897
977
|
return;
|
|
898
978
|
}
|
|
899
979
|
const ref = changeTree.ref;
|
|
900
|
-
const metadata = ref.constructor[Symbol.metadata];
|
|
980
|
+
// const metadata: Metadata = ref.constructor[Symbol.metadata];
|
|
901
981
|
const field = metadata[index];
|
|
902
982
|
// TODO: inline this function call small performance gain
|
|
903
983
|
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
@@ -1592,6 +1672,7 @@ class ArraySchema {
|
|
|
1592
1672
|
const proxy = new Proxy(this, {
|
|
1593
1673
|
get: (obj, prop) => {
|
|
1594
1674
|
if (typeof (prop) !== "symbol" &&
|
|
1675
|
+
// FIXME: d8 accuses this as low performance
|
|
1595
1676
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1596
1677
|
) {
|
|
1597
1678
|
return this.items[prop];
|
|
@@ -1609,7 +1690,7 @@ class ArraySchema {
|
|
|
1609
1690
|
if (setValue[$changes]) {
|
|
1610
1691
|
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1611
1692
|
if (obj.items[key] !== undefined) {
|
|
1612
|
-
if (setValue[$changes]
|
|
1693
|
+
if (setValue[$changes].isNew) {
|
|
1613
1694
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
1614
1695
|
}
|
|
1615
1696
|
else {
|
|
@@ -1621,7 +1702,7 @@ class ArraySchema {
|
|
|
1621
1702
|
}
|
|
1622
1703
|
}
|
|
1623
1704
|
}
|
|
1624
|
-
else if (setValue[$changes]
|
|
1705
|
+
else if (setValue[$changes].isNew) {
|
|
1625
1706
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
|
|
1626
1707
|
}
|
|
1627
1708
|
}
|
|
@@ -1655,7 +1736,9 @@ class ArraySchema {
|
|
|
1655
1736
|
});
|
|
1656
1737
|
this[$changes] = new ChangeTree(proxy);
|
|
1657
1738
|
this[$changes].indexes = {};
|
|
1658
|
-
|
|
1739
|
+
if (items.length > 0) {
|
|
1740
|
+
this.push(...items);
|
|
1741
|
+
}
|
|
1659
1742
|
return proxy;
|
|
1660
1743
|
}
|
|
1661
1744
|
set length(newLength) {
|
|
@@ -1674,15 +1757,18 @@ class ArraySchema {
|
|
|
1674
1757
|
}
|
|
1675
1758
|
push(...values) {
|
|
1676
1759
|
let length = this.tmpItems.length;
|
|
1677
|
-
|
|
1678
|
-
|
|
1760
|
+
const changeTree = this[$changes];
|
|
1761
|
+
// values.forEach((value, i) => {
|
|
1762
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1763
|
+
const value = values[i];
|
|
1679
1764
|
if (value === undefined || value === null) {
|
|
1765
|
+
// skip null values
|
|
1680
1766
|
return;
|
|
1681
1767
|
}
|
|
1682
1768
|
else if (typeof (value) === "object" && this[$childType]) {
|
|
1683
1769
|
assertInstanceType(value, this[$childType], this, i);
|
|
1770
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1684
1771
|
}
|
|
1685
|
-
const changeTree = this[$changes];
|
|
1686
1772
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1687
1773
|
this.items.push(value);
|
|
1688
1774
|
this.tmpItems.push(value);
|
|
@@ -1691,8 +1777,9 @@ class ArraySchema {
|
|
|
1691
1777
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1692
1778
|
//
|
|
1693
1779
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1694
|
-
|
|
1695
|
-
|
|
1780
|
+
}
|
|
1781
|
+
// length++;
|
|
1782
|
+
// });
|
|
1696
1783
|
return length;
|
|
1697
1784
|
}
|
|
1698
1785
|
/**
|
|
@@ -1713,6 +1800,7 @@ class ArraySchema {
|
|
|
1713
1800
|
}
|
|
1714
1801
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1715
1802
|
// this.tmpItems[index] = undefined;
|
|
1803
|
+
// this.tmpItems.pop();
|
|
1716
1804
|
this.deletedIndexes[index] = true;
|
|
1717
1805
|
return this.items.pop();
|
|
1718
1806
|
}
|
|
@@ -1777,9 +1865,12 @@ class ArraySchema {
|
|
|
1777
1865
|
//
|
|
1778
1866
|
// TODO: do not use [$changes] at decoding time.
|
|
1779
1867
|
//
|
|
1780
|
-
changeTree.root
|
|
1781
|
-
|
|
1782
|
-
|
|
1868
|
+
const root = changeTree.root;
|
|
1869
|
+
if (root !== undefined) {
|
|
1870
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
1871
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
1872
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
1873
|
+
}
|
|
1783
1874
|
});
|
|
1784
1875
|
changeTree.discard(true);
|
|
1785
1876
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -1823,6 +1914,7 @@ class ArraySchema {
|
|
|
1823
1914
|
const changeTree = this[$changes];
|
|
1824
1915
|
changeTree.delete(index);
|
|
1825
1916
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
1917
|
+
// this.deletedIndexes[index] = true;
|
|
1826
1918
|
return this.items.shift();
|
|
1827
1919
|
}
|
|
1828
1920
|
/**
|
|
@@ -1903,10 +1995,12 @@ class ArraySchema {
|
|
|
1903
1995
|
changeTree.shiftChangeIndexes(items.length);
|
|
1904
1996
|
// new index
|
|
1905
1997
|
if (changeTree.isFiltered) {
|
|
1906
|
-
changeTree.filteredChanges
|
|
1998
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
1999
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1907
2000
|
}
|
|
1908
2001
|
else {
|
|
1909
|
-
changeTree.allChanges
|
|
2002
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2003
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1910
2004
|
}
|
|
1911
2005
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1912
2006
|
items.forEach((_, index) => {
|
|
@@ -2247,7 +2341,7 @@ class MapSchema {
|
|
|
2247
2341
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2248
2342
|
const index = (isReplace)
|
|
2249
2343
|
? changeTree.indexes[key]
|
|
2250
|
-
: changeTree.indexes[
|
|
2344
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2251
2345
|
let operation = (isReplace)
|
|
2252
2346
|
? exports.OPERATION.REPLACE
|
|
2253
2347
|
: exports.OPERATION.ADD;
|
|
@@ -2259,7 +2353,7 @@ class MapSchema {
|
|
|
2259
2353
|
if (!isReplace) {
|
|
2260
2354
|
this.$indexes.set(index, key);
|
|
2261
2355
|
changeTree.indexes[key] = index;
|
|
2262
|
-
changeTree.indexes[
|
|
2356
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2263
2357
|
}
|
|
2264
2358
|
else if (!isRef &&
|
|
2265
2359
|
this.$items.get(key) === value) {
|
|
@@ -2334,8 +2428,11 @@ class MapSchema {
|
|
|
2334
2428
|
}
|
|
2335
2429
|
[$onEncodeEnd]() {
|
|
2336
2430
|
const changeTree = this[$changes];
|
|
2337
|
-
const
|
|
2338
|
-
for (
|
|
2431
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2432
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2433
|
+
const key = keys[i];
|
|
2434
|
+
const fieldIndex = Number(key);
|
|
2435
|
+
const operation = changeTree.indexedOperations[key];
|
|
2339
2436
|
if (operation === exports.OPERATION.DELETE) {
|
|
2340
2437
|
const index = this[$getByIndex](fieldIndex);
|
|
2341
2438
|
delete changeTree.indexes[index];
|
|
@@ -2438,7 +2535,7 @@ class TypeContext {
|
|
|
2438
2535
|
});
|
|
2439
2536
|
const metadata = (klass[Symbol.metadata] ??= {});
|
|
2440
2537
|
// if any schema/field has filters, mark "context" as having filters.
|
|
2441
|
-
if (metadata[
|
|
2538
|
+
if (metadata[$viewFieldIndexes]) {
|
|
2442
2539
|
this.hasFilters = true;
|
|
2443
2540
|
}
|
|
2444
2541
|
if (parentFieldViewTag !== undefined) {
|
|
@@ -2501,8 +2598,8 @@ const DEFAULT_VIEW_TAG = -1;
|
|
|
2501
2598
|
// // detect index for this field, considering inheritance
|
|
2502
2599
|
// //
|
|
2503
2600
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2504
|
-
// let fieldIndex: number = context.metadata[
|
|
2505
|
-
// ?? (parent && parent[
|
|
2601
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2602
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2506
2603
|
// ?? -1; // no fields defined
|
|
2507
2604
|
// fieldIndex++;
|
|
2508
2605
|
// if (
|
|
@@ -2631,8 +2728,8 @@ function view(tag = DEFAULT_VIEW_TAG) {
|
|
|
2631
2728
|
// //
|
|
2632
2729
|
// metadata[fieldIndex] = {
|
|
2633
2730
|
// type: undefined,
|
|
2634
|
-
// index: (metadata[
|
|
2635
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2731
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2732
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2636
2733
|
// ?? -1) + 1 // no fields defined
|
|
2637
2734
|
// }
|
|
2638
2735
|
// }
|
|
@@ -2675,8 +2772,8 @@ function type(type, options) {
|
|
|
2675
2772
|
//
|
|
2676
2773
|
// detect index for this field, considering inheritance
|
|
2677
2774
|
//
|
|
2678
|
-
fieldIndex = metadata[
|
|
2679
|
-
?? (parentMetadata && parentMetadata[
|
|
2775
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2776
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2680
2777
|
?? -1; // no fields defined
|
|
2681
2778
|
fieldIndex++;
|
|
2682
2779
|
}
|
|
@@ -2703,7 +2800,7 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass)
|
|
|
2703
2800
|
return {
|
|
2704
2801
|
get: function () { return this[fieldCached]; },
|
|
2705
2802
|
set: function (value) {
|
|
2706
|
-
const previousValue = this[fieldCached]
|
|
2803
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2707
2804
|
// skip if value is the same as cached.
|
|
2708
2805
|
if (value === previousValue) {
|
|
2709
2806
|
return;
|
|
@@ -2775,8 +2872,8 @@ function deprecated(throws = true) {
|
|
|
2775
2872
|
// //
|
|
2776
2873
|
// metadata[field] = {
|
|
2777
2874
|
// type: undefined,
|
|
2778
|
-
// index: (metadata[
|
|
2779
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2875
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2876
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2780
2877
|
// ?? -1) + 1 // no fields defined
|
|
2781
2878
|
// }
|
|
2782
2879
|
// }
|
|
@@ -2814,15 +2911,18 @@ function dumpChanges(schema) {
|
|
|
2814
2911
|
ops: {},
|
|
2815
2912
|
refs: []
|
|
2816
2913
|
};
|
|
2817
|
-
$root.changes
|
|
2914
|
+
// for (const refId in $root.changes) {
|
|
2915
|
+
$root.changes.forEach(changeTree => {
|
|
2916
|
+
const changes = changeTree.indexedOperations;
|
|
2818
2917
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2819
|
-
|
|
2918
|
+
for (const index in changes) {
|
|
2919
|
+
const op = changes[index];
|
|
2820
2920
|
const opName = exports.OPERATION[op];
|
|
2821
2921
|
if (!dump.ops[opName]) {
|
|
2822
2922
|
dump.ops[opName] = 0;
|
|
2823
2923
|
}
|
|
2824
2924
|
dump.ops[exports.OPERATION[op]]++;
|
|
2825
|
-
}
|
|
2925
|
+
}
|
|
2826
2926
|
});
|
|
2827
2927
|
return dump;
|
|
2828
2928
|
}
|
|
@@ -2848,6 +2948,7 @@ var _a$2, _b$2;
|
|
|
2848
2948
|
class Schema {
|
|
2849
2949
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2850
2950
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
2951
|
+
// public [$changes]: ChangeTree;
|
|
2851
2952
|
/**
|
|
2852
2953
|
* Assign the property descriptors required to track changes on this instance.
|
|
2853
2954
|
* @param instance
|
|
@@ -2907,12 +3008,7 @@ class Schema {
|
|
|
2907
3008
|
// inline
|
|
2908
3009
|
// Schema.initialize(this);
|
|
2909
3010
|
//
|
|
2910
|
-
|
|
2911
|
-
value: new ChangeTree(this),
|
|
2912
|
-
enumerable: false,
|
|
2913
|
-
writable: true
|
|
2914
|
-
});
|
|
2915
|
-
Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
3011
|
+
Schema.initialize(this);
|
|
2916
3012
|
//
|
|
2917
3013
|
// Assign initial values
|
|
2918
3014
|
//
|
|
@@ -2986,7 +3082,7 @@ class Schema {
|
|
|
2986
3082
|
const changeTree = ref[$changes];
|
|
2987
3083
|
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2988
3084
|
let output = "";
|
|
2989
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
3085
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
2990
3086
|
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
2991
3087
|
return output;
|
|
2992
3088
|
}
|
|
@@ -3004,18 +3100,26 @@ class Schema {
|
|
|
3004
3100
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3005
3101
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3006
3102
|
function dumpChangeSet(changeSet) {
|
|
3007
|
-
|
|
3008
|
-
.
|
|
3009
|
-
.forEach((
|
|
3103
|
+
changeSet.operations
|
|
3104
|
+
.filter(op => op)
|
|
3105
|
+
.forEach((index) => {
|
|
3106
|
+
const operation = changeTree.indexedOperations[index];
|
|
3107
|
+
console.log({ index, operation });
|
|
3108
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3109
|
+
});
|
|
3010
3110
|
}
|
|
3011
3111
|
dumpChangeSet(changeSet);
|
|
3012
3112
|
// display filtered changes
|
|
3013
|
-
if (!isEncodeAll &&
|
|
3113
|
+
if (!isEncodeAll &&
|
|
3114
|
+
changeTree.filteredChanges &&
|
|
3115
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3014
3116
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3015
3117
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3016
3118
|
}
|
|
3017
3119
|
// display filtered changes
|
|
3018
|
-
if (isEncodeAll &&
|
|
3120
|
+
if (isEncodeAll &&
|
|
3121
|
+
changeTree.allFilteredChanges &&
|
|
3122
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3019
3123
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
3020
3124
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3021
3125
|
}
|
|
@@ -3024,10 +3128,12 @@ class Schema {
|
|
|
3024
3128
|
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
3025
3129
|
let output = "";
|
|
3026
3130
|
const rootChangeTree = ref[$changes];
|
|
3131
|
+
const root = rootChangeTree.root;
|
|
3027
3132
|
const changeTrees = new Map();
|
|
3028
3133
|
let totalInstances = 0;
|
|
3029
3134
|
let totalOperations = 0;
|
|
3030
|
-
for (const [
|
|
3135
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3136
|
+
const changeTree = root.changeTrees[refId];
|
|
3031
3137
|
let includeChangeTree = false;
|
|
3032
3138
|
let parentChangeTrees = [];
|
|
3033
3139
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3046,7 +3152,7 @@ class Schema {
|
|
|
3046
3152
|
}
|
|
3047
3153
|
if (includeChangeTree) {
|
|
3048
3154
|
totalInstances += 1;
|
|
3049
|
-
totalOperations += changes.
|
|
3155
|
+
totalOperations += Object.keys(changes).length;
|
|
3050
3156
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3051
3157
|
}
|
|
3052
3158
|
}
|
|
@@ -3064,12 +3170,13 @@ class Schema {
|
|
|
3064
3170
|
visitedParents.add(parentChangeTree);
|
|
3065
3171
|
}
|
|
3066
3172
|
});
|
|
3067
|
-
const changes = changeTree.
|
|
3173
|
+
const changes = changeTree.indexedOperations;
|
|
3068
3174
|
const level = parentChangeTrees.length;
|
|
3069
3175
|
const indent = getIndent(level);
|
|
3070
3176
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3071
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3072
|
-
for (const
|
|
3177
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3178
|
+
for (const index in changes) {
|
|
3179
|
+
const operation = changes[index];
|
|
3073
3180
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3074
3181
|
}
|
|
3075
3182
|
}
|
|
@@ -3430,59 +3537,90 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
3430
3537
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3431
3538
|
};
|
|
3432
3539
|
|
|
3540
|
+
function spliceOne(arr, index) {
|
|
3541
|
+
// manually splice an array
|
|
3542
|
+
if (index === -1 || index >= arr.length) {
|
|
3543
|
+
return false;
|
|
3544
|
+
}
|
|
3545
|
+
const len = arr.length - 1;
|
|
3546
|
+
for (let i = index; i < len; i++) {
|
|
3547
|
+
arr[i] = arr[i + 1];
|
|
3548
|
+
}
|
|
3549
|
+
arr.length = len;
|
|
3550
|
+
return true;
|
|
3551
|
+
}
|
|
3552
|
+
|
|
3433
3553
|
class Root {
|
|
3434
3554
|
constructor(types) {
|
|
3435
3555
|
this.types = types;
|
|
3436
3556
|
this.nextUniqueId = 0;
|
|
3437
|
-
this.refCount =
|
|
3557
|
+
this.refCount = {};
|
|
3558
|
+
this.changeTrees = {};
|
|
3438
3559
|
// all changes
|
|
3439
|
-
this.allChanges =
|
|
3440
|
-
this.allFilteredChanges =
|
|
3560
|
+
this.allChanges = [];
|
|
3561
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3441
3562
|
// pending changes to be encoded
|
|
3442
|
-
this.changes =
|
|
3443
|
-
this.filteredChanges =
|
|
3563
|
+
this.changes = [];
|
|
3564
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3444
3565
|
}
|
|
3445
3566
|
getNextUniqueId() {
|
|
3446
3567
|
return this.nextUniqueId++;
|
|
3447
3568
|
}
|
|
3448
3569
|
add(changeTree) {
|
|
3449
|
-
|
|
3570
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3571
|
+
changeTree.ensureRefId();
|
|
3572
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3573
|
+
if (isNewChangeTree) {
|
|
3574
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3575
|
+
}
|
|
3576
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3450
3577
|
if (previousRefCount === 0) {
|
|
3451
3578
|
//
|
|
3452
3579
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3453
3580
|
// We need to re-add all changes to the `changes` map.
|
|
3454
3581
|
//
|
|
3455
|
-
changeTree.allChanges.
|
|
3456
|
-
|
|
3457
|
-
|
|
3582
|
+
const ops = changeTree.allChanges.operations;
|
|
3583
|
+
let len = ops.length;
|
|
3584
|
+
while (len--) {
|
|
3585
|
+
changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
|
|
3586
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3587
|
+
}
|
|
3458
3588
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
return refCount;
|
|
3589
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3590
|
+
return isNewChangeTree;
|
|
3462
3591
|
}
|
|
3463
3592
|
remove(changeTree) {
|
|
3464
|
-
const refCount = (this.refCount.
|
|
3593
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3465
3594
|
if (refCount <= 0) {
|
|
3466
3595
|
//
|
|
3467
3596
|
// Only remove "root" reference if it's the last reference
|
|
3468
3597
|
//
|
|
3469
3598
|
changeTree.root = undefined;
|
|
3470
|
-
this.
|
|
3471
|
-
this.
|
|
3599
|
+
delete this.changeTrees[changeTree.refId];
|
|
3600
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3601
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3472
3602
|
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3473
|
-
this.allFilteredChanges
|
|
3474
|
-
this.filteredChanges
|
|
3603
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3604
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3475
3605
|
}
|
|
3476
|
-
this.refCount.
|
|
3606
|
+
this.refCount[changeTree.refId] = 0;
|
|
3477
3607
|
}
|
|
3478
3608
|
else {
|
|
3479
|
-
this.refCount.
|
|
3609
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3480
3610
|
}
|
|
3481
3611
|
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3482
3612
|
return refCount;
|
|
3483
3613
|
}
|
|
3614
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3615
|
+
const changeSet = this[changeSetName];
|
|
3616
|
+
const index = changeSet.indexOf(changeTree);
|
|
3617
|
+
if (index !== -1) {
|
|
3618
|
+
spliceOne(changeSet, index);
|
|
3619
|
+
// changeSet[index] = undefined;
|
|
3620
|
+
}
|
|
3621
|
+
}
|
|
3484
3622
|
clear() {
|
|
3485
|
-
this.changes.
|
|
3623
|
+
this.changes.length = 0;
|
|
3486
3624
|
}
|
|
3487
3625
|
}
|
|
3488
3626
|
|
|
@@ -3506,20 +3644,26 @@ class Encoder {
|
|
|
3506
3644
|
this.state = state;
|
|
3507
3645
|
this.state[$changes].setRoot(this.root);
|
|
3508
3646
|
}
|
|
3509
|
-
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer,
|
|
3647
|
+
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
|
|
3510
3648
|
) {
|
|
3511
3649
|
const hasView = (view !== undefined);
|
|
3512
3650
|
const rootChangeTree = this.state[$changes];
|
|
3513
|
-
const
|
|
3514
|
-
|
|
3651
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3652
|
+
const changeTrees = this.root[changeSetName];
|
|
3653
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3654
|
+
const changeTree = changeTrees[i];
|
|
3655
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
3656
|
+
// if (changeTree === undefined) { continue; }
|
|
3657
|
+
const operations = changeTree[changeSetName];
|
|
3515
3658
|
const ref = changeTree.ref;
|
|
3516
3659
|
const ctor = ref.constructor;
|
|
3517
3660
|
const encoder = ctor[$encoder];
|
|
3518
3661
|
const filter = ctor[$filter];
|
|
3662
|
+
const metadata = ctor[Symbol.metadata];
|
|
3519
3663
|
// try { throw new Error(); } catch (e) {
|
|
3520
3664
|
// // only print if not coming from Reflection.ts
|
|
3521
3665
|
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3522
|
-
// console.log("ChangeTree:", { ref: ref.constructor.name
|
|
3666
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
3523
3667
|
// }
|
|
3524
3668
|
// }
|
|
3525
3669
|
if (hasView) {
|
|
@@ -3537,7 +3681,13 @@ class Encoder {
|
|
|
3537
3681
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3538
3682
|
number$1(buffer, changeTree.refId, it);
|
|
3539
3683
|
}
|
|
3540
|
-
for (
|
|
3684
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3685
|
+
const fieldIndex = operations.operations[j];
|
|
3686
|
+
const operation = (fieldIndex < 0)
|
|
3687
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3688
|
+
: (isEncodeAll)
|
|
3689
|
+
? exports.OPERATION.ADD
|
|
3690
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3541
3691
|
//
|
|
3542
3692
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3543
3693
|
// they will be encoded per client, based on their view.
|
|
@@ -3545,7 +3695,7 @@ class Encoder {
|
|
|
3545
3695
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3546
3696
|
// TODO: avoid checking if no view tags were defined
|
|
3547
3697
|
//
|
|
3548
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3698
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3549
3699
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3550
3700
|
// view?.invisible.add(changeTree);
|
|
3551
3701
|
continue;
|
|
@@ -3560,16 +3710,14 @@ class Encoder {
|
|
|
3560
3710
|
// });
|
|
3561
3711
|
// }
|
|
3562
3712
|
// }
|
|
3563
|
-
|
|
3713
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
3714
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3715
|
+
}
|
|
3716
|
+
if (shouldDiscardChanges) {
|
|
3717
|
+
changeTree.discard();
|
|
3718
|
+
// Not a new instance anymore
|
|
3719
|
+
changeTree.isNew = false;
|
|
3564
3720
|
}
|
|
3565
|
-
// if (shouldClearChanges) {
|
|
3566
|
-
// // changeTree.endEncode();
|
|
3567
|
-
// changeTree.changes.clear();
|
|
3568
|
-
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3569
|
-
// changeTree.ref[$onEncodeEnd]?.();
|
|
3570
|
-
// // Not a new instance anymore
|
|
3571
|
-
// delete changeTree[$isNew];
|
|
3572
|
-
// }
|
|
3573
3721
|
}
|
|
3574
3722
|
if (it.offset > buffer.byteLength) {
|
|
3575
3723
|
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
@@ -3586,51 +3734,54 @@ class Encoder {
|
|
|
3586
3734
|
if (buffer === this.sharedBuffer) {
|
|
3587
3735
|
this.sharedBuffer = buffer;
|
|
3588
3736
|
}
|
|
3589
|
-
return this.encode({ offset: initialOffset }, view, buffer,
|
|
3737
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3590
3738
|
}
|
|
3591
3739
|
else {
|
|
3592
|
-
//
|
|
3593
|
-
// only clear changes after making sure buffer resize is not required.
|
|
3594
|
-
//
|
|
3595
|
-
if (shouldClearChanges) {
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
}
|
|
3740
|
+
// //
|
|
3741
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
3742
|
+
// //
|
|
3743
|
+
// if (shouldClearChanges) {
|
|
3744
|
+
// //
|
|
3745
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
3746
|
+
// //
|
|
3747
|
+
// this.onEndEncode(changeTrees);
|
|
3748
|
+
// }
|
|
3601
3749
|
return buffer.subarray(0, it.offset);
|
|
3602
3750
|
}
|
|
3603
3751
|
}
|
|
3604
3752
|
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3605
|
-
// console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.
|
|
3753
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
3606
3754
|
// this.debugChanges("allChanges");
|
|
3607
|
-
return this.encode(it, undefined, buffer,
|
|
3755
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3608
3756
|
}
|
|
3609
3757
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3610
3758
|
const viewOffset = it.offset;
|
|
3611
|
-
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.
|
|
3759
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
3612
3760
|
// this.debugChanges("allFilteredChanges");
|
|
3761
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
3613
3762
|
// try to encode "filtered" changes
|
|
3614
|
-
this.encode(it, view, bytes,
|
|
3763
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3615
3764
|
return Buffer.concat([
|
|
3616
3765
|
bytes.subarray(0, sharedOffset),
|
|
3617
3766
|
bytes.subarray(viewOffset, it.offset)
|
|
3618
3767
|
]);
|
|
3619
3768
|
}
|
|
3620
3769
|
debugChanges(field) {
|
|
3621
|
-
const
|
|
3770
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3622
3771
|
? this.root[field]
|
|
3623
3772
|
: field;
|
|
3624
|
-
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
|
|
3773
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3774
|
+
const changeSet = changeTree[field];
|
|
3775
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3776
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3777
|
+
for (const index in changeSet) {
|
|
3778
|
+
const op = changeSet[index];
|
|
3628
3779
|
console.log(" ->", {
|
|
3629
3780
|
index,
|
|
3630
3781
|
field: metadata?.[index],
|
|
3631
3782
|
op: exports.OPERATION[op],
|
|
3632
3783
|
});
|
|
3633
|
-
}
|
|
3784
|
+
}
|
|
3634
3785
|
});
|
|
3635
3786
|
}
|
|
3636
3787
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
@@ -3640,23 +3791,31 @@ class Encoder {
|
|
|
3640
3791
|
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3641
3792
|
// this.debugChanges("filteredChanges");
|
|
3642
3793
|
// encode visibility changes (add/remove for this view)
|
|
3643
|
-
const
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3794
|
+
const refIds = Object.keys(view.changes);
|
|
3795
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
3796
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3797
|
+
const refId = refIds[i];
|
|
3798
|
+
const changes = view.changes[refId];
|
|
3799
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3800
|
+
if (changeTree === undefined ||
|
|
3801
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3802
|
+
) {
|
|
3803
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3648
3804
|
continue;
|
|
3649
3805
|
}
|
|
3650
3806
|
const ref = changeTree.ref;
|
|
3651
|
-
const ctor = ref
|
|
3807
|
+
const ctor = ref.constructor;
|
|
3652
3808
|
const encoder = ctor[$encoder];
|
|
3809
|
+
const metadata = ctor[Symbol.metadata];
|
|
3653
3810
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3654
3811
|
number$1(bytes, changeTree.refId, it);
|
|
3655
|
-
const
|
|
3656
|
-
for (
|
|
3812
|
+
const keys = Object.keys(changes);
|
|
3813
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3814
|
+
const key = keys[i];
|
|
3815
|
+
const operation = changes[key];
|
|
3657
3816
|
// isEncodeAll = false
|
|
3658
3817
|
// hasView = true
|
|
3659
|
-
encoder(this, bytes, changeTree,
|
|
3818
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3660
3819
|
}
|
|
3661
3820
|
}
|
|
3662
3821
|
//
|
|
@@ -3664,35 +3823,46 @@ class Encoder {
|
|
|
3664
3823
|
// (to allow re-using StateView's for multiple clients)
|
|
3665
3824
|
//
|
|
3666
3825
|
// clear "view" changes after encoding
|
|
3667
|
-
view.changes
|
|
3826
|
+
view.changes = {};
|
|
3827
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
3668
3828
|
// try to encode "filtered" changes
|
|
3669
|
-
this.encode(it, view, bytes,
|
|
3829
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3670
3830
|
return Buffer.concat([
|
|
3671
3831
|
bytes.subarray(0, sharedOffset),
|
|
3672
3832
|
bytes.subarray(viewOffset, it.offset)
|
|
3673
3833
|
]);
|
|
3674
3834
|
}
|
|
3675
3835
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3836
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3837
|
+
// changeTree.endEncode();
|
|
3838
|
+
// });
|
|
3839
|
+
// for (const refId in changeTrees) {
|
|
3840
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3841
|
+
// changeTree.endEncode();
|
|
3842
|
+
// // changeTree.changes.clear();
|
|
3843
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3844
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3845
|
+
// // // Not a new instance anymore
|
|
3846
|
+
// // delete changeTree[$isNew];
|
|
3847
|
+
// }
|
|
3685
3848
|
}
|
|
3686
3849
|
discardChanges() {
|
|
3850
|
+
// console.log("DISCARD CHANGES!");
|
|
3687
3851
|
// discard shared changes
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3852
|
+
let length = this.root.changes.length;
|
|
3853
|
+
if (length > 0) {
|
|
3854
|
+
while (length--) {
|
|
3855
|
+
this.root.changes[length]?.endEncode();
|
|
3856
|
+
}
|
|
3857
|
+
this.root.changes.length = 0;
|
|
3691
3858
|
}
|
|
3692
3859
|
// discard filtered changes
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3860
|
+
length = this.root.filteredChanges.length;
|
|
3861
|
+
if (length > 0) {
|
|
3862
|
+
while (length--) {
|
|
3863
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3864
|
+
}
|
|
3865
|
+
this.root.filteredChanges.length = 0;
|
|
3696
3866
|
}
|
|
3697
3867
|
}
|
|
3698
3868
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
@@ -3705,19 +3875,6 @@ class Encoder {
|
|
|
3705
3875
|
}
|
|
3706
3876
|
}
|
|
3707
3877
|
|
|
3708
|
-
function spliceOne(arr, index) {
|
|
3709
|
-
// manually splice an array
|
|
3710
|
-
if (index === -1 || index >= arr.length) {
|
|
3711
|
-
return false;
|
|
3712
|
-
}
|
|
3713
|
-
const len = arr.length - 1;
|
|
3714
|
-
for (let i = index; i < len; i++) {
|
|
3715
|
-
arr[i] = arr[i + 1];
|
|
3716
|
-
}
|
|
3717
|
-
arr.length = len;
|
|
3718
|
-
return true;
|
|
3719
|
-
}
|
|
3720
|
-
|
|
3721
3878
|
class DecodingWarning extends Error {
|
|
3722
3879
|
constructor(message) {
|
|
3723
3880
|
super(message);
|
|
@@ -3879,7 +4036,7 @@ class Decoder {
|
|
|
3879
4036
|
}
|
|
3880
4037
|
ref[$onDecodeEnd]?.();
|
|
3881
4038
|
ref = nextRef;
|
|
3882
|
-
decoder = ref
|
|
4039
|
+
decoder = ref.constructor[$decoder];
|
|
3883
4040
|
continue;
|
|
3884
4041
|
}
|
|
3885
4042
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -4356,7 +4513,7 @@ class StateView {
|
|
|
4356
4513
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
4357
4514
|
* (This is used to force encoding a property, even if it was not changed)
|
|
4358
4515
|
*/
|
|
4359
|
-
this.changes =
|
|
4516
|
+
this.changes = {};
|
|
4360
4517
|
}
|
|
4361
4518
|
// TODO: allow to set multiple tags at once
|
|
4362
4519
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
@@ -4378,10 +4535,10 @@ class StateView {
|
|
|
4378
4535
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
4379
4536
|
// be set (only the parent's changes are set)
|
|
4380
4537
|
//
|
|
4381
|
-
let changes = this.changes.
|
|
4538
|
+
let changes = this.changes[changeTree.refId];
|
|
4382
4539
|
if (changes === undefined) {
|
|
4383
|
-
changes =
|
|
4384
|
-
this.changes.
|
|
4540
|
+
changes = {};
|
|
4541
|
+
this.changes[changeTree.refId] = changes;
|
|
4385
4542
|
}
|
|
4386
4543
|
// set tag
|
|
4387
4544
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -4398,9 +4555,9 @@ class StateView {
|
|
|
4398
4555
|
}
|
|
4399
4556
|
tags.add(tag);
|
|
4400
4557
|
// Ref: add tagged properties
|
|
4401
|
-
metadata?.[
|
|
4558
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4402
4559
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
4403
|
-
changes
|
|
4560
|
+
changes[index] = exports.OPERATION.ADD;
|
|
4404
4561
|
}
|
|
4405
4562
|
});
|
|
4406
4563
|
}
|
|
@@ -4409,16 +4566,21 @@ class StateView {
|
|
|
4409
4566
|
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4410
4567
|
? changeTree.allFilteredChanges
|
|
4411
4568
|
: changeTree.allChanges;
|
|
4412
|
-
|
|
4569
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4570
|
+
const index = changeSet.operations[i];
|
|
4571
|
+
if (index === undefined) {
|
|
4572
|
+
continue;
|
|
4573
|
+
} // skip "undefined" indexes
|
|
4574
|
+
const op = changeTree.indexedOperations[index];
|
|
4413
4575
|
const tagAtIndex = metadata?.[index].tag;
|
|
4414
4576
|
if ((isInvisible || // if "invisible", include all
|
|
4415
4577
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4416
4578
|
tagAtIndex === tag // tagged property
|
|
4417
4579
|
) &&
|
|
4418
4580
|
op !== exports.OPERATION.DELETE) {
|
|
4419
|
-
changes
|
|
4581
|
+
changes[index] = op;
|
|
4420
4582
|
}
|
|
4421
|
-
}
|
|
4583
|
+
}
|
|
4422
4584
|
}
|
|
4423
4585
|
// Add children of this ChangeTree to this view
|
|
4424
4586
|
changeTree.forEachChild((change, index) => {
|
|
@@ -4444,10 +4606,10 @@ class StateView {
|
|
|
4444
4606
|
}
|
|
4445
4607
|
// add parent's tag properties
|
|
4446
4608
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4447
|
-
let changes = this.changes.
|
|
4609
|
+
let changes = this.changes[changeTree.refId];
|
|
4448
4610
|
if (changes === undefined) {
|
|
4449
|
-
changes =
|
|
4450
|
-
this.changes.
|
|
4611
|
+
changes = {};
|
|
4612
|
+
this.changes[changeTree.refId] = changes;
|
|
4451
4613
|
}
|
|
4452
4614
|
if (!this.tags) {
|
|
4453
4615
|
this.tags = new WeakMap();
|
|
@@ -4461,7 +4623,7 @@ class StateView {
|
|
|
4461
4623
|
tags = this.tags.get(changeTree);
|
|
4462
4624
|
}
|
|
4463
4625
|
tags.add(tag);
|
|
4464
|
-
changes
|
|
4626
|
+
changes[parentIndex] = exports.OPERATION.ADD;
|
|
4465
4627
|
}
|
|
4466
4628
|
}
|
|
4467
4629
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4473,32 +4635,32 @@ class StateView {
|
|
|
4473
4635
|
this.items.delete(changeTree);
|
|
4474
4636
|
const ref = changeTree.ref;
|
|
4475
4637
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4476
|
-
let changes = this.changes.
|
|
4638
|
+
let changes = this.changes[changeTree.refId];
|
|
4477
4639
|
if (changes === undefined) {
|
|
4478
|
-
changes =
|
|
4479
|
-
this.changes.
|
|
4640
|
+
changes = {};
|
|
4641
|
+
this.changes[changeTree.refId] = changes;
|
|
4480
4642
|
}
|
|
4481
4643
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4482
4644
|
// parent is collection (Map/Array)
|
|
4483
4645
|
const parent = changeTree.parent;
|
|
4484
4646
|
if (!Metadata.isValidInstance(parent)) {
|
|
4485
4647
|
const parentChangeTree = parent[$changes];
|
|
4486
|
-
let changes = this.changes.
|
|
4648
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4487
4649
|
if (changes === undefined) {
|
|
4488
|
-
changes =
|
|
4489
|
-
this.changes.
|
|
4650
|
+
changes = {};
|
|
4651
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4490
4652
|
}
|
|
4491
4653
|
// DELETE / DELETE BY REF ID
|
|
4492
|
-
changes
|
|
4654
|
+
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
4493
4655
|
}
|
|
4494
4656
|
else {
|
|
4495
4657
|
// delete all "tagged" properties.
|
|
4496
|
-
metadata[
|
|
4658
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4497
4659
|
}
|
|
4498
4660
|
}
|
|
4499
4661
|
else {
|
|
4500
4662
|
// delete only tagged properties
|
|
4501
|
-
metadata[
|
|
4663
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4502
4664
|
}
|
|
4503
4665
|
// remove tag
|
|
4504
4666
|
if (this.tags && this.tags.has(changeTree)) {
|