@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.
Files changed (63) hide show
  1. package/bin/schema-debug +4 -3
  2. package/build/cjs/index.js +465 -303
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +465 -303
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +465 -303
  7. package/lib/Metadata.d.ts +5 -5
  8. package/lib/Metadata.js +17 -17
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Schema.js +24 -17
  11. package/lib/Schema.js.map +1 -1
  12. package/lib/annotations.js +11 -11
  13. package/lib/annotations.js.map +1 -1
  14. package/lib/bench_encode.js +12 -5
  15. package/lib/bench_encode.js.map +1 -1
  16. package/lib/decoder/Decoder.js +1 -1
  17. package/lib/decoder/Decoder.js.map +1 -1
  18. package/lib/encoder/ChangeTree.d.ts +23 -7
  19. package/lib/encoder/ChangeTree.js +183 -106
  20. package/lib/encoder/ChangeTree.js.map +1 -1
  21. package/lib/encoder/EncodeOperation.d.ts +2 -1
  22. package/lib/encoder/EncodeOperation.js +2 -2
  23. package/lib/encoder/EncodeOperation.js.map +1 -1
  24. package/lib/encoder/Encoder.d.ts +3 -5
  25. package/lib/encoder/Encoder.js +93 -61
  26. package/lib/encoder/Encoder.js.map +1 -1
  27. package/lib/encoder/Root.d.ts +12 -7
  28. package/lib/encoder/Root.js +41 -20
  29. package/lib/encoder/Root.js.map +1 -1
  30. package/lib/encoder/StateView.d.ts +5 -5
  31. package/lib/encoder/StateView.js +29 -23
  32. package/lib/encoder/StateView.js.map +1 -1
  33. package/lib/encoding/encode.js +12 -9
  34. package/lib/encoding/encode.js.map +1 -1
  35. package/lib/types/TypeContext.js +2 -1
  36. package/lib/types/TypeContext.js.map +1 -1
  37. package/lib/types/custom/ArraySchema.js +27 -13
  38. package/lib/types/custom/ArraySchema.js.map +1 -1
  39. package/lib/types/custom/MapSchema.d.ts +3 -1
  40. package/lib/types/custom/MapSchema.js +7 -4
  41. package/lib/types/custom/MapSchema.js.map +1 -1
  42. package/lib/types/symbols.d.ts +8 -6
  43. package/lib/types/symbols.js +9 -7
  44. package/lib/types/symbols.js.map +1 -1
  45. package/lib/utils.js +6 -3
  46. package/lib/utils.js.map +1 -1
  47. package/package.json +1 -1
  48. package/src/Metadata.ts +22 -22
  49. package/src/Schema.ts +33 -25
  50. package/src/annotations.ts +12 -12
  51. package/src/bench_encode.ts +15 -6
  52. package/src/decoder/Decoder.ts +1 -1
  53. package/src/encoder/ChangeTree.ts +220 -115
  54. package/src/encoder/EncodeOperation.ts +5 -1
  55. package/src/encoder/Encoder.ts +110 -68
  56. package/src/encoder/Root.ts +41 -21
  57. package/src/encoder/StateView.ts +32 -28
  58. package/src/encoding/encode.ts +12 -9
  59. package/src/types/TypeContext.ts +2 -1
  60. package/src/types/custom/ArraySchema.ts +39 -17
  61. package/src/types/custom/MapSchema.ts +12 -5
  62. package/src/types/symbols.ts +10 -9
  63. package/src/utils.ts +7 -3
@@ -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, -1, {
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[-4] === undefined) {
123
- Object.defineProperty(metadata, -4, {
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[-4].push(index);
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[-2]) {
139
+ if (!metadata[$viewFieldIndexes]) {
138
140
  // -2: all field indexes with "view" tag
139
- Object.defineProperty(metadata, -2, {
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, -3, {
147
+ Object.defineProperty(metadata, $fieldIndexesByViewTag, {
146
148
  value: {},
147
149
  enumerable: false,
148
150
  configurable: true
149
151
  });
150
152
  }
151
- metadata[-2].push(index);
152
- if (!metadata[-3][tag]) {
153
- metadata[-3][tag] = [];
153
+ metadata[$viewFieldIndexes].push(index);
154
+ if (!metadata[$fieldIndexesByViewTag][tag]) {
155
+ metadata[$fieldIndexesByViewTag][tag] = [];
154
156
  }
155
- metadata[-3][tag].push(index);
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, -1, {
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[-1]; i++) {
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, -1, {
203
- value: parentMetadata[-1],
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], -1));
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[-1]; i++) {
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
- var _a$5;
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.currentOperationIndex = 0;
234
- this.changes = new Map();
235
- this.allChanges = new Map();
236
- this[_a$5] = true;
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]?.[-2]) {
242
- this.allFilteredChanges = new Map();
243
- this.filteredChanges = new Map();
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
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
260
- this.root.filteredChanges.set(this, this.filteredChanges);
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
- this.root.changes.set(this, this.changes);
265
- this.root.allChanges.set(this, this.allChanges);
297
+ enqueueChangeTree(root, this, 'changes');
298
+ if (isNewChangeTree) {
299
+ this.root.allChanges.push(this);
300
+ }
266
301
  }
267
- this.ensureRefId();
302
+ // Recursively set root on child structures
268
303
  if (metadata) {
269
- metadata[-4]?.forEach((index) => {
304
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
270
305
  const field = metadata[index];
271
306
  const value = this.ref[field.name];
272
- if (value) {
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
- this.root.filteredChanges.set(this, this.filteredChanges);
300
- this.root.allFilteredChanges.set(this, this.filteredChanges);
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
- this.root.changes.set(this, this.changes);
305
- this.root.allChanges.set(this, this.allChanges);
339
+ enqueueChangeTree(root, this, 'changes');
340
+ if (isNewChangeTree) {
341
+ this.root.allChanges.push(this);
342
+ }
306
343
  }
307
- this.ensureRefId();
344
+ }
345
+ else {
346
+ root.add(this);
308
347
  }
309
348
  // assign same parent on child structures
310
349
  if (metadata) {
311
- metadata[-4]?.forEach((index) => {
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[-4]?.forEach((index) => {
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
- this.changes.set(--this.currentOperationIndex, op);
351
- this.root?.changes.set(this, this.changes);
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 = changeSet.get(index);
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
- changeSet.set(index, op);
409
+ this.indexedOperations[index] = op;
370
410
  }
411
+ setOperationAtIndex(changeSet, index);
371
412
  if (isFiltered) {
372
- this.allFilteredChanges.set(index, exports.OPERATION.ADD);
373
- this.root?.filteredChanges.set(this, this.filteredChanges);
374
- this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
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.set(index, exports.OPERATION.ADD);
378
- this.root?.changes.set(this, this.changes);
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 changeSetEntries = Array.from(changeSet.entries());
391
- changeSet.clear();
392
- // Re-insert each entry with the shifted index
393
- for (const [index, op] of changeSetEntries) {
394
- changeSet.set(index + shiftIndex, op);
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, allChangeSet) {
412
- Array.from(allChangeSet.entries()).forEach(([index, op]) => {
413
- if (index >= startIndex) {
414
- allChangeSet.delete(index);
415
- allChangeSet.set(index + shiftIndex, op);
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
- if (this.filteredChanges !== undefined) {
421
- this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
422
- this.filteredChanges.set(index, operation);
423
- this.root?.filteredChanges.set(this, this.filteredChanges);
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.set(allChangesIndex, exports.OPERATION.ADD);
427
- this.changes.set(index, operation);
428
- this.root?.changes.set(this, this.changes);
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
- // TODO: optimize this. avoid checking against multiple instances
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 bit ugly (and repeated from `.change()`)
547
+ // FIXME: this is looking a ugly and repeated
490
548
  //
491
549
  if (this.filteredChanges) {
492
- this.root?.filteredChanges.set(this, this.filteredChanges);
493
- this.allFilteredChanges.delete(allChangesIndex);
550
+ deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
551
+ enqueueChangeTree(this.root, this, 'filteredChanges');
494
552
  }
495
553
  else {
496
- this.root?.changes.set(this, this.changes);
497
- this.allChanges.delete(allChangesIndex);
554
+ deleteOperationAtIndex(this.allChanges, allChangesIndex);
555
+ enqueueChangeTree(this.root, this, 'changes');
498
556
  }
499
557
  }
500
558
  endEncode() {
501
- this.changes.clear();
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
- delete this[$isNew];
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.changes.clear();
515
- this.filteredChanges?.clear();
516
- // reset operation index
517
- this.currentOperationIndex = 0;
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.clear();
520
- this.allFilteredChanges?.clear();
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.changes.forEach((_, fieldIndex) => {
530
- const value = this.getValue(fieldIndex);
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.changes.size > 0;
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?.[-2] !== undefined;
620
+ this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
550
621
  if (this.isPartiallyFiltered) {
551
- this.filteredChanges = this.filteredChanges || new Map();
552
- this.allFilteredChanges = this.allFilteredChanges || new Map();
622
+ this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
623
+ this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
553
624
  }
554
- if (parent) {
555
- if (!Metadata.isValidInstance(parent)) {
556
- const parentChangeTree = parent[$changes];
557
- parent = parentChangeTree.parent;
558
- parentIndex = parentChangeTree.parentIndex;
559
- }
560
- const parentMetadata = parent?.constructor?.[Symbol.metadata];
561
- this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
562
- //
563
- // TODO: refactor this!
564
- //
565
- // swapping `changes` and `filteredChanges` is required here
566
- // because "isFiltered" may not be imedialely available on `change()`
567
- //
568
- if (this.isFiltered) {
569
- this.filteredChanges = new Map();
570
- this.allFilteredChanges = new Map();
571
- if (this.changes.size > 0) {
572
- // swap changes reference
573
- const changes = this.changes;
574
- this.changes = this.filteredChanges;
575
- this.filteredChanges = changes;
576
- // swap "all changes" reference
577
- const allFilteredChanges = this.allFilteredChanges;
578
- this.allFilteredChanges = this.allChanges;
579
- this.allChanges = allFilteredChanges;
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++] = 0xc0 | (c >> 6);
650
- view[it.offset++] = 0x80 | (c & 0x3f);
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++] = 0xe0 | (c >> 12);
654
- view[it.offset++] = 0x80 | (c >> 6 & 0x3f);
655
- view[it.offset++] = 0x80 | (c & 0x3f);
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++] = 0xf0 | (c >> 18);
661
- view[it.offset++] = 0x80 | (c >> 12 & 0x3f);
662
- view[it.offset++] = 0x80 | (c >> 6 & 0x3f);
663
- view[it.offset++] = 0x80 | (c & 0x3f);
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][$isNew]) {
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][$isNew]) {
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
- this.push.apply(this, items);
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
- values.forEach((value, i) => {
1678
- // skip null values
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
- length++;
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?.changes.delete(changeTree);
1781
- changeTree.root?.allChanges.delete(changeTree);
1782
- changeTree.root?.allFilteredChanges.delete(changeTree);
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.set(this.items.length, exports.OPERATION.ADD);
1998
+ setOperationAtIndex(changeTree.filteredChanges, this.items.length);
1999
+ // changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
1907
2000
  }
1908
2001
  else {
1909
- changeTree.allChanges.set(this.items.length, exports.OPERATION.ADD);
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[-1] ?? 0;
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[-1] = index + 1;
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 changes = changeTree.changes.entries();
2338
- for (const [fieldIndex, operation] of changes) {
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[-2]) {
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[-1] // current structure already has fields defined
2505
- // ?? (parent && parent[-1]) // parent structure has fields defined
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[-1] // current structure already has fields defined
2635
- // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
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[-1] // current structure already has fields defined
2679
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
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] || undefined;
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[-1] // current structure already has fields defined
2779
- // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
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.forEach((operations, changeTree) => {
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
- operations.forEach((op, index) => {
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
- Object.defineProperty(this, $changes, {
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
- Array.from(changeSet)
3008
- .sort((a, b) => a[0] - b[0])
3009
- .forEach(([index, operation]) => output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(index, isEncodeAll))})\n`);
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 && changeTree.filteredChanges?.size > 0) {
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 && changeTree.allFilteredChanges?.size > 0) {
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 [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
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.size;
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.changes;
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.size}\n`;
3072
- for (const [index, operation] of changes) {
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 = new WeakMap();
3557
+ this.refCount = {};
3558
+ this.changeTrees = {};
3438
3559
  // all changes
3439
- this.allChanges = new Map();
3440
- this.allFilteredChanges = new Map();
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 = new Map();
3443
- this.filteredChanges = new Map();
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
- const previousRefCount = this.refCount.get(changeTree);
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.forEach((operation, index) => {
3456
- changeTree.changes.set(index, operation);
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
- const refCount = (previousRefCount || 0) + 1;
3460
- this.refCount.set(changeTree, refCount);
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.get(changeTree)) - 1;
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.allChanges.delete(changeTree);
3471
- this.changes.delete(changeTree);
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.delete(changeTree);
3474
- this.filteredChanges.delete(changeTree);
3603
+ this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
3604
+ this.removeChangeFromChangeSet("filteredChanges", changeTree);
3475
3605
  }
3476
- this.refCount.set(changeTree, 0);
3606
+ this.refCount[changeTree.refId] = 0;
3477
3607
  }
3478
3608
  else {
3479
- this.refCount.set(changeTree, 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.clear();
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, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees, initialOffset = it.offset // cache current offset in case we need to resize the buffer
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 shouldClearChanges = !isEncodeAll && !hasView;
3514
- for (const [changeTree, changes] of changeTrees.entries()) {
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 (const [fieldIndex, operation] of changes.entries()) {
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
- encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
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, changeTrees, isEncodeAll);
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
- // FIXME: avoid iterating over change trees twice.
3598
- //
3599
- this.onEndEncode(changeTrees);
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.size})`);
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, this.root.allChanges, true);
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.size})`);
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, this.root.allFilteredChanges, true, viewOffset);
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 changeSet = (typeof (field) === "string")
3770
+ const rootChangeSet = (typeof (field) === "string")
3622
3771
  ? this.root[field]
3623
3772
  : field;
3624
- Array.from(changeSet.entries()).map((item) => {
3625
- const metadata = item[0].ref.constructor[Symbol.metadata];
3626
- console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3627
- item[1].forEach((op, index) => {
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 viewChangesIterator = view.changes.entries();
3644
- for (const [changeTree, changes] of viewChangesIterator) {
3645
- if (changes.size === 0) {
3646
- // FIXME: avoid having empty changes if no changes were made
3647
- // console.log("changes.size === 0", changeTree.ref.constructor.name);
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['constructor'];
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 changesIterator = changes.entries();
3656
- for (const [fieldIndex, operation] of changesIterator) {
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, fieldIndex, operation, it, false, true);
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.clear();
3826
+ view.changes = {};
3827
+ // console.log("FILTERED CHANGES:", this.root.filteredChanges);
3668
3828
  // try to encode "filtered" changes
3669
- this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
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
- const changeTreesIterator = changeTrees.entries();
3677
- for (const [changeTree, _] of changeTreesIterator) {
3678
- changeTree.endEncode();
3679
- // changeTree.changes.clear();
3680
- // // ArraySchema and MapSchema have a custom "encode end" method
3681
- // changeTree.ref[$onEncodeEnd]?.();
3682
- // // Not a new instance anymore
3683
- // delete changeTree[$isNew];
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
- if (this.root.changes.size > 0) {
3689
- this.onEndEncode(this.root.changes);
3690
- this.root.changes.clear();
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
- if (this.root.filteredChanges.size > 0) {
3694
- this.onEndEncode(this.root.filteredChanges);
3695
- this.root.filteredChanges.clear();
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['constructor'][$decoder];
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 = new Map();
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.get(changeTree);
4538
+ let changes = this.changes[changeTree.refId];
4382
4539
  if (changes === undefined) {
4383
- changes = new Map();
4384
- this.changes.set(changeTree, 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?.[-3]?.[tag]?.forEach((index) => {
4558
+ metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
4402
4559
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
4403
- changes.set(index, exports.OPERATION.ADD);
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
- changeSet.forEach((op, index) => {
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.set(index, op);
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.get(changeTree);
4609
+ let changes = this.changes[changeTree.refId];
4448
4610
  if (changes === undefined) {
4449
- changes = new Map();
4450
- this.changes.set(changeTree, 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.set(parentIndex, exports.OPERATION.ADD);
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.get(changeTree);
4638
+ let changes = this.changes[changeTree.refId];
4477
4639
  if (changes === undefined) {
4478
- changes = new Map();
4479
- this.changes.set(changeTree, 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.get(parentChangeTree);
4648
+ let changes = this.changes[parentChangeTree.refId];
4487
4649
  if (changes === undefined) {
4488
- changes = new Map();
4489
- this.changes.set(parentChangeTree, changes);
4650
+ changes = {};
4651
+ this.changes[parentChangeTree.refId] = changes;
4490
4652
  }
4491
4653
  // DELETE / DELETE BY REF ID
4492
- changes.set(changeTree.parentIndex, exports.OPERATION.DELETE);
4654
+ changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
4493
4655
  }
4494
4656
  else {
4495
4657
  // delete all "tagged" properties.
4496
- metadata[-2].forEach((index) => changes.set(index, exports.OPERATION.DELETE));
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[-3][tag].forEach((index) => changes.set(index, exports.OPERATION.DELETE));
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)) {