@colyseus/schema 3.0.42 → 3.0.44

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 (52) hide show
  1. package/build/cjs/index.js +365 -219
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +365 -219
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +365 -219
  6. package/lib/Schema.d.ts +4 -3
  7. package/lib/Schema.js +22 -4
  8. package/lib/Schema.js.map +1 -1
  9. package/lib/bench_encode.d.ts +1 -0
  10. package/lib/bench_encode.js +130 -0
  11. package/lib/bench_encode.js.map +1 -0
  12. package/lib/debug.d.ts +1 -0
  13. package/lib/debug.js +51 -0
  14. package/lib/debug.js.map +1 -0
  15. package/lib/decoder/Decoder.js +8 -9
  16. package/lib/decoder/Decoder.js.map +1 -1
  17. package/lib/encoder/ChangeTree.d.ts +57 -7
  18. package/lib/encoder/ChangeTree.js +172 -106
  19. package/lib/encoder/ChangeTree.js.map +1 -1
  20. package/lib/encoder/Encoder.js +19 -20
  21. package/lib/encoder/Encoder.js.map +1 -1
  22. package/lib/encoder/Root.d.ts +9 -8
  23. package/lib/encoder/Root.js +84 -27
  24. package/lib/encoder/Root.js.map +1 -1
  25. package/lib/encoder/StateView.d.ts +1 -1
  26. package/lib/encoder/StateView.js +28 -23
  27. package/lib/encoder/StateView.js.map +1 -1
  28. package/lib/types/custom/ArraySchema.js +7 -5
  29. package/lib/types/custom/ArraySchema.js.map +1 -1
  30. package/lib/types/custom/CollectionSchema.js +1 -1
  31. package/lib/types/custom/CollectionSchema.js.map +1 -1
  32. package/lib/types/custom/MapSchema.js +9 -4
  33. package/lib/types/custom/MapSchema.js.map +1 -1
  34. package/lib/types/symbols.d.ts +14 -14
  35. package/lib/types/symbols.js +14 -14
  36. package/lib/types/symbols.js.map +1 -1
  37. package/lib/utils.js +7 -3
  38. package/lib/utils.js.map +1 -1
  39. package/package.json +2 -1
  40. package/src/Schema.ts +23 -7
  41. package/src/bench_encode.ts +108 -0
  42. package/src/debug.ts +55 -0
  43. package/src/decoder/Decoder.ts +9 -13
  44. package/src/encoder/ChangeTree.ts +203 -116
  45. package/src/encoder/Encoder.ts +21 -19
  46. package/src/encoder/Root.ts +90 -29
  47. package/src/encoder/StateView.ts +34 -26
  48. package/src/types/custom/ArraySchema.ts +8 -6
  49. package/src/types/custom/CollectionSchema.ts +1 -1
  50. package/src/types/custom/MapSchema.ts +10 -4
  51. package/src/types/symbols.ts +15 -15
  52. package/src/utils.ts +9 -3
@@ -36,17 +36,53 @@ export interface IndexedOperations {
36
36
  [index: number]: OPERATION;
37
37
  }
38
38
 
39
+ // Linked list node for change trees
40
+ export interface ChangeTreeNode {
41
+ changeTree: ChangeTree;
42
+ next?: ChangeTreeNode;
43
+ prev?: ChangeTreeNode;
44
+ }
45
+
46
+ // Linked list for change trees
47
+ export interface ChangeTreeList {
48
+ next?: ChangeTreeNode;
49
+ tail?: ChangeTreeNode;
50
+ length: number;
51
+ }
52
+
39
53
  export interface ChangeSet {
40
54
  // field index -> operation index
41
55
  indexes: { [index: number]: number };
42
56
  operations: number[];
43
- queueRootIndex?: number; // index of ChangeTree structure in `root.changes` or `root.filteredChanges`
57
+ queueRootNode?: ChangeTreeNode; // direct reference to ChangeTreeNode in the linked list
44
58
  }
45
59
 
46
60
  function createChangeSet(): ChangeSet {
47
61
  return { indexes: {}, operations: [] };
48
62
  }
49
63
 
64
+ // Linked list helper functions
65
+ export function createChangeTreeList(): ChangeTreeList {
66
+ return { next: undefined, tail: undefined, length: 0 };
67
+ }
68
+
69
+ export function addToChangeTreeList(list: ChangeTreeList, changeTree: ChangeTree): ChangeTreeNode {
70
+ const node: ChangeTreeNode = { changeTree, next: undefined, prev: undefined };
71
+
72
+ if (!list.next) {
73
+ list.next = node;
74
+ list.tail = node;
75
+ } else {
76
+ node.prev = list.tail;
77
+ list.tail!.next = node;
78
+ list.tail = node;
79
+ }
80
+
81
+ list.length++;
82
+
83
+ return node;
84
+ }
85
+
50
86
  export function setOperationAtIndex(changeSet: ChangeSet, index: number) {
51
87
  const operationsIndex = changeSet.indexes[index];
52
88
  if (operationsIndex === undefined) {
@@ -96,25 +132,32 @@ export function debugChangeSet(label: string, changeSet: ChangeSet) {
96
132
  export function enqueueChangeTree(
97
133
  root: Root,
98
134
  changeTree: ChangeTree,
99
- changeSet: 'changes' | 'filteredChanges' | 'allFilteredChanges',
100
- queueRootIndex = changeTree[changeSet].queueRootIndex
135
+ changeSet: 'changes' | 'filteredChanges' | 'allFilteredChanges' | 'allChanges',
136
+ queueRootNode = changeTree[changeSet].queueRootNode
101
137
  ) {
102
- if (!root) {
103
- // skip
104
- return;
138
+ // skip
139
+ if (!root) { return; }
105
140
 
106
- } else if (root[changeSet][queueRootIndex] !== changeTree) {
107
- changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
141
+ if (queueRootNode) {
142
+ } else {
143
+ // Add to linked list if not already present
144
+ changeTree[changeSet].queueRootNode = addToChangeTreeList(root[changeSet], changeTree);
108
145
  }
109
146
  }
110
147
 
111
- export class ChangeTree<T extends Ref=any> {
148
+ export interface ParentChain {
149
+ ref: Ref;
150
+ index: number;
151
+ next?: ParentChain;
152
+ }
153
+
154
+ export class ChangeTree<T extends Ref = any> {
112
155
  ref: T;
113
156
  refId: number;
157
+ metadata: Metadata;
114
158
 
115
159
  root?: Root;
116
- parent?: Ref;
117
- parentIndex?: number;
160
+ parentChain?: ParentChain; // Linked list for tracking parents
118
161
 
119
162
  /**
120
163
  * Whether this structure is parent of a filtered structure.
@@ -136,7 +179,7 @@ export class ChangeTree<T extends Ref=any> {
136
179
  filteredChanges: ChangeSet;
137
180
  allFilteredChanges: ChangeSet;
138
181
 
139
- indexes: {[index: string]: any}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
182
+ indexes: { [index: string]: any }; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
140
183
 
141
184
  /**
142
185
  * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
@@ -145,12 +188,12 @@ export class ChangeTree<T extends Ref=any> {
145
188
 
146
189
  constructor(ref: T) {
147
190
  this.ref = ref;
191
+ this.metadata = ref.constructor[Symbol.metadata];
148
192
 
149
193
  //
150
194
  // Does this structure have "filters" declared?
151
195
  //
152
- const metadata = ref.constructor[Symbol.metadata];
153
- if (metadata?.[$viewFieldIndexes]) {
196
+ if (this.metadata?.[$viewFieldIndexes]) {
154
197
  this.allFilteredChanges = { indexes: {}, operations: [] };
155
198
  this.filteredChanges = { indexes: {}, operations: [] };
156
199
  }
@@ -158,35 +201,18 @@ export class ChangeTree<T extends Ref=any> {
158
201
 
159
202
  setRoot(root: Root) {
160
203
  this.root = root;
161
- this.checkIsFiltered(this.parent, this.parentIndex);
162
204
 
163
- //
164
- // TODO: refactor and possibly unify .setRoot() and .setParent()
165
- //
205
+ const isNewChangeTree = this.root.add(this);
166
206
 
167
- // Recursively set root on child structures
168
- const metadata: Metadata = this.ref.constructor[Symbol.metadata];
169
- if (metadata) {
170
- metadata[$refTypeFieldIndexes]?.forEach((index) => {
171
- const field = metadata[index as any as number];
172
- const changeTree: ChangeTree = this.ref[field.name]?.[$changes];
173
- if (changeTree) {
174
- if (changeTree.root !== root) {
175
- changeTree.setRoot(root);
176
- } else {
177
- root.add(changeTree); // increment refCount
178
- }
179
- }
180
- });
207
+ this.checkIsFiltered(this.parent, this.parentIndex, isNewChangeTree);
181
208
 
182
- } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== "string") {
183
- // MapSchema / ArraySchema, etc.
184
- (this.ref as MapSchema).forEach((value, key) => {
185
- const changeTree: ChangeTree = value[$changes];
186
- if (changeTree.root !== root) {
187
- changeTree.setRoot(root);
209
+ // Recursively set root on child structures
210
+ if (isNewChangeTree) {
211
+ this.forEachChild((child, _) => {
212
+ if (child.root !== root) {
213
+ child.setRoot(root);
188
214
  } else {
189
- root.add(changeTree); // increment refCount
215
+ root.add(child); // increment refCount
190
216
  }
191
217
  });
192
218
  }
@@ -197,63 +223,58 @@ export class ChangeTree<T extends Ref=any> {
197
223
  root?: Root,
198
224
  parentIndex?: number,
199
225
  ) {
200
- this.parent = parent;
201
- this.parentIndex = parentIndex;
226
+ this.addParent(parent, parentIndex);
202
227
 
203
228
  // avoid setting parents with empty `root`
204
229
  if (!root) { return; }
205
230
 
231
+ const isNewChangeTree = root.add(this);
232
+
206
233
  // skip if parent is already set
207
234
  if (root !== this.root) {
208
235
  this.root = root;
209
- this.checkIsFiltered(parent, parentIndex);
210
-
211
- } else {
212
- root.add(this);
236
+ this.checkIsFiltered(parent, parentIndex, isNewChangeTree);
213
237
  }
214
238
 
215
239
  // assign same parent on child structures
216
- const metadata: Metadata = this.ref.constructor[Symbol.metadata];
217
- if (metadata) {
218
- metadata[$refTypeFieldIndexes]?.forEach((index) => {
219
- const field = metadata[index as any as number];
220
- const changeTree: ChangeTree = this.ref[field.name]?.[$changes];
221
- if (changeTree && changeTree.root !== root) {
222
- changeTree.setParent(this.ref, root, index);
223
- }
224
- });
225
-
226
- } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== "string") {
227
- // MapSchema / ArraySchema, etc.
228
- (this.ref as MapSchema).forEach((value, key) => {
229
- const changeTree: ChangeTree = value[$changes];
230
- if (changeTree.root !== root) {
231
- changeTree.setParent(this.ref, root, this.indexes[key] ?? key);
240
+ if (isNewChangeTree) {
241
+ //
242
+ // assign same parent on child structures
243
+ //
244
+ this.forEachChild((child, index) => {
245
+ if (child.root === root) {
246
+ //
247
+ // re-assigning a child of the same root, move it to the end
248
+ // of the changes queue so encoding order is preserved
249
+ //
250
+ root.add(child);
251
+ root.moveToEndOfChanges(child);
252
+ return;
232
253
  }
254
+ child.setParent(this.ref, root, index);
233
255
  });
234
256
  }
235
-
236
257
  }
237
258
 
238
- forEachChild(callback: (change: ChangeTree, atIndex: number) => void) {
259
+ forEachChild(callback: (change: ChangeTree, at: any) => void) {
239
260
  //
240
261
  // assign same parent on child structures
241
262
  //
242
- const metadata: Metadata = this.ref.constructor[Symbol.metadata];
243
- if (metadata) {
244
- metadata[$refTypeFieldIndexes]?.forEach((index) => {
245
- const field = metadata[index as any as number];
246
- const value = this.ref[field.name];
247
- if (value) {
248
- callback(value[$changes], index);
249
- }
250
- });
263
+ if (this.ref[$childType]) {
264
+ if (typeof (this.ref[$childType]) !== "string") {
265
+ // MapSchema / ArraySchema, etc.
266
+ for (const [key, value] of (this.ref as MapSchema).entries()) {
267
+ callback(value[$changes], key);
268
+ };
269
+ }
251
270
 
252
- } else if (this.ref[$childType] && typeof(this.ref[$childType]) !== "string") {
253
- // MapSchema / ArraySchema, etc.
254
- (this.ref as MapSchema).forEach((value, key) => {
255
- callback(value[$changes], this.indexes[key] ?? key);
256
- });
271
+ } else {
272
+ for (const index of this.metadata?.[$refTypeFieldIndexes] ?? []) {
273
+ const field = this.metadata[index as any as number];
274
+ const value = this.ref[field.name];
275
+ if (!value) { continue; }
276
+ callback(value[$changes], index);
277
+ }
257
278
  }
258
279
  }
259
280
 
@@ -271,9 +292,7 @@ export class ChangeTree<T extends Ref=any> {
271
292
  }
272
293
 
273
294
  change(index: number, operation: OPERATION = OPERATION.ADD) {
274
- const metadata = this.ref.constructor[Symbol.metadata] as Metadata;
275
-
276
- const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
295
+ const isFiltered = this.isFiltered || (this.metadata?.[index]?.tag !== undefined);
277
296
  const changeSet = (isFiltered)
278
297
  ? this.filteredChanges
279
298
  : this.changes;
@@ -376,19 +395,16 @@ export class ChangeTree<T extends Ref=any> {
376
395
  }
377
396
 
378
397
  getType(index?: number) {
379
- if (Metadata.isValidInstance(this.ref)) {
380
- const metadata = this.ref.constructor[Symbol.metadata] as Metadata;
381
- return metadata[index].type;
382
-
383
- } else {
398
+ return (
384
399
  //
385
400
  // Get the child type from parent structure.
386
401
  // - ["string"] => "string"
387
402
  // - { map: "string" } => "string"
388
403
  // - { set: "string" } => "string"
389
404
  //
390
- return this.ref[$childType];
391
- }
405
+ this.ref[$childType] || // ArraySchema | MapSchema | SetSchema | CollectionSchema
406
+ this.metadata[index].type // Schema
407
+ );
392
408
  }
393
409
 
394
410
  getChange(index: number) {
@@ -458,9 +474,7 @@ export class ChangeTree<T extends Ref=any> {
458
474
  this.indexedOperations = {};
459
475
 
460
476
  // clear changeset
461
- this[changeSetName].indexes = {};
462
- this[changeSetName].operations.length = 0;
463
- this[changeSetName].queueRootIndex = undefined;
477
+ this[changeSetName] = createChangeSet();
464
478
 
465
479
  // ArraySchema and MapSchema have a custom "encode end" method
466
480
  this.ref[$onEncodeEnd]?.();
@@ -478,24 +492,17 @@ export class ChangeTree<T extends Ref=any> {
478
492
  this.ref[$onEncodeEnd]?.();
479
493
 
480
494
  this.indexedOperations = {};
481
-
482
- this.changes.indexes = {};
483
- this.changes.operations.length = 0;
484
- this.changes.queueRootIndex = undefined;
495
+ this.changes = createChangeSet();
485
496
 
486
497
  if (this.filteredChanges !== undefined) {
487
- this.filteredChanges.indexes = {};
488
- this.filteredChanges.operations.length = 0;
489
- this.filteredChanges.queueRootIndex = undefined;
498
+ this.filteredChanges = createChangeSet();
490
499
  }
491
500
 
492
501
  if (discardAll) {
493
- this.allChanges.indexes = {};
494
- this.allChanges.operations.length = 0;
502
+ this.allChanges = createChangeSet();
495
503
 
496
504
  if (this.allFilteredChanges !== undefined) {
497
- this.allFilteredChanges.indexes = {};
498
- this.allFilteredChanges.operations.length = 0;
505
+ this.allFilteredChanges = createChangeSet();
499
506
  }
500
507
  }
501
508
  }
@@ -517,22 +524,11 @@ export class ChangeTree<T extends Ref=any> {
517
524
  this.discard();
518
525
  }
519
526
 
520
- ensureRefId() {
521
- // skip if refId is already set.
522
- if (this.refId !== undefined) {
523
- return;
524
- }
525
-
526
- this.refId = this.root.getNextUniqueId();
527
- }
528
-
529
527
  get changed() {
530
528
  return (Object.entries(this.indexedOperations).length > 0);
531
529
  }
532
530
 
533
- protected checkIsFiltered(parent: Ref, parentIndex: number) {
534
- const isNewChangeTree = this.root.add(this);
535
-
531
+ protected checkIsFiltered(parent: Ref, parentIndex: number, isNewChangeTree: boolean) {
536
532
  if (this.root.types.hasFilters) {
537
533
  //
538
534
  // At Schema initialization, the "root" structure might not be available
@@ -545,7 +541,7 @@ export class ChangeTree<T extends Ref=any> {
545
541
  if (this.filteredChanges !== undefined) {
546
542
  enqueueChangeTree(this.root, this, 'filteredChanges');
547
543
  if (isNewChangeTree) {
548
- this.root.allFilteredChanges.push(this);
544
+ enqueueChangeTree(this.root, this, 'allFilteredChanges');
549
545
  }
550
546
  }
551
547
  }
@@ -553,7 +549,7 @@ export class ChangeTree<T extends Ref=any> {
553
549
  if (!this.isFiltered) {
554
550
  enqueueChangeTree(this.root, this, 'changes');
555
551
  if (isNewChangeTree) {
556
- this.root.allChanges.push(this);
552
+ enqueueChangeTree(this.root, this, 'allChanges');
557
553
  }
558
554
  }
559
555
  }
@@ -568,7 +564,7 @@ export class ChangeTree<T extends Ref=any> {
568
564
  //
569
565
  const refType = Metadata.isValidInstance(this.ref)
570
566
  ? this.ref.constructor
571
- : this.ref[$childType];
567
+ : this.ref[$childType];
572
568
 
573
569
  let parentChangeTree: ChangeTree;
574
570
 
@@ -627,4 +623,95 @@ export class ChangeTree<T extends Ref=any> {
627
623
  }
628
624
  }
629
625
 
626
+ /**
627
+ * Get the immediate parent
628
+ */
629
+ get parent(): Ref | undefined {
630
+ return this.parentChain?.ref;
631
+ }
632
+
633
+ /**
634
+ * Get the immediate parent index
635
+ */
636
+ get parentIndex(): number | undefined {
637
+ return this.parentChain?.index;
638
+ }
639
+
640
+ /**
641
+ * Add a parent to the chain
642
+ */
643
+ addParent(parent: Ref, index: number) {
644
+ // Check if this parent already exists in the chain
645
+ if (this.hasParent((p, i) => p[$changes] === parent[$changes] && i === index)) {
646
+ return;
647
+ }
648
+
649
+ this.parentChain = {
650
+ ref: parent,
651
+ index,
652
+ next: this.parentChain
653
+ };
654
+ }
655
+
656
+ /**
657
+ * Remove a parent from the chain
658
+ * @param parent - The parent to remove
659
+ * @returns true if parent was removed
660
+ */
661
+ removeParent(parent: Ref = this.parent): boolean {
662
+ let current = this.parentChain;
663
+ let previous = null;
664
+ while (current) {
665
+ //
666
+ // FIXME: it is required to check against `$changes` here because
667
+ // ArraySchema is instance of Proxy
668
+ //
669
+ if (current.ref[$changes] === parent[$changes]) {
670
+ if (previous) {
671
+ previous.next = current.next;
672
+ } else {
673
+ this.parentChain = current.next;
674
+ }
675
+ return true;
676
+ }
677
+ previous = current;
678
+ current = current.next;
679
+ }
680
+ return this.parentChain === undefined;
681
+ }
682
+
683
+ /**
684
+ * Find a specific parent in the chain
685
+ */
686
+ findParent(predicate: (parent: Ref, index: number) => boolean): ParentChain | undefined {
687
+ let current = this.parentChain;
688
+ while (current) {
689
+ if (predicate(current.ref, current.index)) {
690
+ return current;
691
+ }
692
+ current = current.next;
693
+ }
694
+ return undefined;
695
+ }
696
+
697
+ /**
698
+ * Check if this ChangeTree has a specific parent
699
+ */
700
+ hasParent(predicate: (parent: Ref, index: number) => boolean): boolean {
701
+ return this.findParent(predicate) !== undefined;
702
+ }
703
+
704
+ /**
705
+ * Get all parents as an array (for debugging/testing)
706
+ */
707
+ getAllParents(): Array<{ ref: Ref, index: number }> {
708
+ const parents: Array<{ ref: Ref, index: number }> = [];
709
+ let current = this.parentChain;
710
+ while (current) {
711
+ parents.push({ ref: current.ref, index: current.index });
712
+ current = current.next;
713
+ }
714
+ return parents;
715
+ }
716
+
630
717
  }
@@ -10,7 +10,8 @@ import { Root } from "./Root";
10
10
 
11
11
  import type { StateView } from "./StateView";
12
12
  import type { Metadata } from "../Metadata";
13
- import type { ChangeSetName, ChangeTree } from "./ChangeTree";
13
+ import type { ChangeSetName, ChangeTree, ChangeTreeList, ChangeTreeNode } from "./ChangeTree";
14
+ import { createChangeTreeList } from "./ChangeTree";
14
15
 
15
16
  export class Encoder<T extends Schema = any> {
16
17
  static BUFFER_SIZE = (typeof(Buffer) !== "undefined") && Buffer.poolSize || 8 * 1024; // 8KB
@@ -54,11 +55,11 @@ export class Encoder<T extends Schema = any> {
54
55
  ): Buffer {
55
56
  const hasView = (view !== undefined);
56
57
  const rootChangeTree = this.state[$changes];
57
- const changeTrees = this.root[changeSetName];
58
58
 
59
- for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
60
- const changeTree = changeTrees[i];
61
- if (!changeTree) { continue; }
59
+ let current: ChangeTreeList | ChangeTreeNode = this.root[changeSetName];
60
+
61
+ while (current = current.next) {
62
+ const changeTree = current.changeTree;
62
63
 
63
64
  if (hasView) {
64
65
  if (!view.isChangeTreeVisible(changeTree)) {
@@ -166,7 +167,9 @@ export class Encoder<T extends Schema = any> {
166
167
  ? this.root[field]
167
168
  : field;
168
169
 
169
- rootChangeSet.forEach((changeTree) => {
170
+ let current = rootChangeSet.next;
171
+ while (current) {
172
+ const changeTree = current.changeTree;
170
173
  const changeSet = changeTree[field];
171
174
 
172
175
  const metadata: Metadata = changeTree.ref.constructor[Symbol.metadata];
@@ -179,7 +182,8 @@ export class Encoder<T extends Schema = any> {
179
182
  op: OPERATION[op],
180
183
  });
181
184
  }
182
- });
185
+ current = current.next;
186
+ }
183
187
  }
184
188
 
185
189
  encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
@@ -242,22 +246,20 @@ export class Encoder<T extends Schema = any> {
242
246
 
243
247
  discardChanges() {
244
248
  // discard shared changes
245
- let length = this.root.changes.length;
246
- if (length > 0) {
247
- while (length--) {
248
- this.root.changes[length]?.endEncode('changes');
249
- }
250
- this.root.changes.length = 0;
249
+ let current = this.root.changes.next;
250
+ while (current) {
251
+ current.changeTree.endEncode('changes');
252
+ current = current.next;
251
253
  }
254
+ this.root.changes = createChangeTreeList();
252
255
 
253
256
  // discard filtered changes
254
- length = this.root.filteredChanges.length;
255
- if (length > 0) {
256
- while (length--) {
257
- this.root.filteredChanges[length]?.endEncode('filteredChanges');
258
- }
259
- this.root.filteredChanges.length = 0;
257
+ current = this.root.filteredChanges.next;
258
+ while (current) {
259
+ current.changeTree.endEncode('filteredChanges');
260
+ current = current.next;
260
261
  }
262
+ this.root.filteredChanges = createChangeTreeList();
261
263
  }
262
264
 
263
265
  tryEncodeTypeId (bytes: Buffer, baseType: typeof Schema, targetType: typeof Schema, it: Iterator) {