@colyseus/schema 5.0.14 → 5.0.20

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 (54) hide show
  1. package/README.md +11 -5
  2. package/build/Metadata.d.ts +10 -1
  3. package/build/annotations.d.ts +6 -5
  4. package/build/codegen/api.d.ts +2 -0
  5. package/build/codegen/cli.cjs +322 -31
  6. package/build/codegen/cli.cjs.map +1 -1
  7. package/build/codegen/parser.d.ts +6 -1
  8. package/build/codegen/resolve.d.ts +25 -0
  9. package/build/codegen/types.d.ts +2 -0
  10. package/build/decoder/strategy/Callbacks.d.ts +7 -8
  11. package/build/decoder/strategy/getDecoderStateCallbacks.d.ts +2 -2
  12. package/build/encoder/ChangeTree.d.ts +40 -12
  13. package/build/encoder/Encoder.d.ts +1 -1
  14. package/build/encoder/Root.d.ts +9 -0
  15. package/build/encoder/StateView.d.ts +38 -1
  16. package/build/encoder/changeTree/inheritedFlags.d.ts +13 -19
  17. package/build/encoder/changeTree/liveIteration.d.ts +8 -0
  18. package/build/encoder/changeTree/parentChain.d.ts +30 -8
  19. package/build/encoder/streaming.d.ts +1 -1
  20. package/build/index.cjs +3232 -2830
  21. package/build/index.cjs.map +1 -1
  22. package/build/index.js +3228 -2826
  23. package/build/index.mjs +3232 -2830
  24. package/build/index.mjs.map +1 -1
  25. package/build/types/HelperTypes.d.ts +24 -14
  26. package/build/types/TypeContext.d.ts +0 -17
  27. package/build/types/builder.d.ts +1 -5
  28. package/build/types/symbols.d.ts +1 -0
  29. package/package.json +9 -8
  30. package/src/Metadata.ts +59 -77
  31. package/src/Reflection.ts +9 -5
  32. package/src/annotations.ts +28 -18
  33. package/src/codegen/api.ts +3 -1
  34. package/src/codegen/cli.ts +5 -2
  35. package/src/codegen/parser.ts +69 -31
  36. package/src/codegen/resolve.ts +322 -0
  37. package/src/codegen/types.ts +4 -1
  38. package/src/decoder/DecodeOperation.ts +13 -2
  39. package/src/decoder/strategy/Callbacks.ts +7 -8
  40. package/src/decoder/strategy/getDecoderStateCallbacks.ts +2 -2
  41. package/src/encoder/ChangeTree.ts +76 -25
  42. package/src/encoder/EncodeOperation.ts +10 -1
  43. package/src/encoder/Encoder.ts +52 -2
  44. package/src/encoder/Root.ts +28 -8
  45. package/src/encoder/StateView.ts +150 -66
  46. package/src/encoder/changeTree/inheritedFlags.ts +164 -45
  47. package/src/encoder/changeTree/liveIteration.ts +24 -3
  48. package/src/encoder/changeTree/parentChain.ts +72 -15
  49. package/src/encoder/streaming.ts +2 -1
  50. package/src/types/HelperTypes.ts +43 -34
  51. package/src/types/TypeContext.ts +5 -52
  52. package/src/types/builder.ts +14 -10
  53. package/src/types/custom/ArraySchema.ts +57 -14
  54. package/src/types/symbols.ts +3 -0
@@ -17,7 +17,7 @@
17
17
  */
18
18
  import { OPERATION } from "../encoding/spec.js";
19
19
  import { Schema } from "../Schema.js";
20
- import { $changes, $childType, $decoder, $onEncodeEnd, $encoder, $getByIndex, $proxyTarget, $refId, $refTypeFieldIndexes, $numFields, type $deleteByIndex } from "../types/symbols.js";
20
+ import { $changes, $childType, $decoder, $onEncodeEnd, $encoder, $getByIndex, $refId, $refTypeFieldIndexes, $numFields, type $deleteByIndex } from "../types/symbols.js";
21
21
 
22
22
  import type { MapSchema } from "../types/custom/MapSchema.js";
23
23
  import type { ArraySchema } from "../types/custom/ArraySchema.js";
@@ -27,7 +27,7 @@ import type { StreamSchema } from "../types/custom/StreamSchema.js";
27
27
 
28
28
  import { Root } from "./Root.js";
29
29
  import { Metadata } from "../Metadata.js";
30
- import { type ChangeRecorder, type ICollectionChangeRecorder, SchemaChangeRecorder, CollectionChangeRecorder, popcount32 } from "./ChangeRecorder.js";
30
+ import { type ChangeRecorder, SchemaChangeRecorder, CollectionChangeRecorder, popcount32 } from "./ChangeRecorder.js";
31
31
  import type { EncodeOperation } from "./EncodeOperation.js";
32
32
  import { type EncodeDescriptor, getEncodeDescriptor } from "./EncodeDescriptor.js";
33
33
  import type { DecodeOperation } from "../decoder/DecodeOperation.js";
@@ -37,6 +37,7 @@ import {
37
37
  setParentIndex as _setParentIndex,
38
38
  findParent as _findParent, hasParent as _hasParent,
39
39
  getAllParents as _getAllParents,
40
+ indexInParent as _indexInParent,
40
41
  } from "./changeTree/parentChain.js";
41
42
  import { forEachLive as _forEachLive, forEachLiveWithCtx as _forEachLiveWithCtx } from "./changeTree/liveIteration.js";
42
43
  import {
@@ -113,12 +114,27 @@ export function createChangeTreeList(): ChangeTreeList {
113
114
  return { next: undefined, tail: undefined, nextPosition: 0 };
114
115
  }
115
116
 
117
+ /**
118
+ * Live node in a tree's parent chain — mutating one edits the chain. Only
119
+ * `parentChain.ts` should hold these.
120
+ */
116
121
  export interface ParentChain {
117
122
  ref: Ref;
118
123
  index: number;
119
124
  next?: ParentChain;
120
125
  }
121
126
 
127
+ /**
128
+ * Detached copy of one parent link, handed out by the query helpers. Distinct
129
+ * from `ParentChain` on purpose: it carries no `next`, so it cannot be walked
130
+ * as if it were the chain, and it is readonly, so it cannot be mistaken for a
131
+ * way to move a parent's index — `setParentIndex` does that.
132
+ */
133
+ export interface ParentEntry {
134
+ readonly ref: Ref;
135
+ readonly index: number;
136
+ }
137
+
122
138
  // Flags bitfield. *_UNRELIABLE / _PATCH_ONLY / _STATIC mirror the parent
123
139
  // field's annotation — inherited at setParent/setRoot time.
124
140
  export const IS_FILTERED = 1, IS_VISIBILITY_SHARED = 2, IS_NEW = 4;
@@ -135,6 +151,11 @@ export const IS_STREAM_COLLECTION = 64;
135
151
  // only fields assigned after `pool.acquire()` would reach the wire — the
136
152
  // retained ones (constructor-initialized children) would never be encoded.
137
153
  export const NEEDS_RESTAGE = 128;
154
+ // Queued in `Root.pendingFilterRefresh` — the tree's parent-edge set changed
155
+ // (instance sharing gained/lost an edge) and `isFiltered` /
156
+ // `isVisibilitySharedWithParent` must be re-derived from the LIVE edges
157
+ // before the next encode. See inheritedFlags.refreshFilterState.
158
+ export const PENDING_FILTER_REFRESH = 256;
138
159
  /**
139
160
  * Flags a child inherits from its parent's own transitive state via
140
161
  * `checkInheritedFlags`. Read as a bitwise mask so the inheritance step
@@ -169,6 +190,14 @@ export class ChangeTree<T extends Ref = any> implements ChangeRecorder {
169
190
  */
170
191
  refTarget: T;
171
192
 
193
+ /**
194
+ * True when `ref` is an ArraySchema — the only proxied type, so its
195
+ * user-facing identity differs from `refTarget`. Canonical predicate for
196
+ * "is this tree's ref an array" without probing `ref` (which would hit
197
+ * the Proxy trap) — two monomorphic loads on the tree itself.
198
+ */
199
+ get isArray(): boolean { return this.refTarget !== this.ref; }
200
+
172
201
  metadata: Metadata;
173
202
 
174
203
  /**
@@ -279,8 +308,7 @@ export class ChangeTree<T extends Ref = any> implements ChangeRecorder {
279
308
 
280
309
  ensureUnreliableRecorder(): ChangeRecorder {
281
310
  if (this.unreliableRecorder === undefined) {
282
- const isSchema = Metadata.isValidInstance(this.ref);
283
- this.unreliableRecorder = isSchema
311
+ this.unreliableRecorder = this._isSchema
284
312
  ? new SchemaChangeRecorder((this.metadata?.[$numFields] ?? 0) as number)
285
313
  : new CollectionChangeRecorder();
286
314
  }
@@ -325,12 +353,13 @@ export class ChangeTree<T extends Ref = any> implements ChangeRecorder {
325
353
  return Metadata.hasStreamAtIndex(this.metadata, index);
326
354
  }
327
355
 
328
- constructor(ref: T) {
356
+ constructor(ref: T, refTarget: T = ref) {
329
357
  this.ref = ref;
330
- // `$proxyTarget` is a self-reference set by ArraySchema on the raw
331
- // target; for non-proxied refs it's undefined and we fall back to
332
- // `ref`. Cached here so hot-path reads skip the Proxy `get` trap.
333
- this.refTarget = ((ref as any)[$proxyTarget] ?? ref) as T;
358
+ // Raw (non-Proxy) target, passed explicitly by ArraySchema's ctor
359
+ // the only proxied type. Defaulting to `ref` for everything else
360
+ // skips a guaranteed-miss megamorphic `$proxyTarget` probe per
361
+ // construction. Cached so hot-path reads skip the Proxy `get` trap.
362
+ this.refTarget = refTarget;
334
363
 
335
364
  // Single per-class lookup that subsumes Symbol.metadata,
336
365
  // isValidInstance, $encoder, $filter, and the filter bitmask.
@@ -592,28 +621,42 @@ export class ChangeTree<T extends Ref = any> implements ChangeRecorder {
592
621
  }
593
622
 
594
623
  /**
595
- * ArraySchema#unshift(): re-key pending ops on both channels by
596
- * `+count`, then record ADDs for the new items at indexes 0..count-1.
624
+ * ArraySchema insert (unshift / splice with more inserts than deletes):
625
+ * re-key pending ops at or above `at` by `+count`, then record ADDs for
626
+ * the new items at indexes `at..at+count-1`.
597
627
  *
598
- * The rebuilt map's insertion order IS the wire order: new ADDs first
599
- * (ascending — the decoder splice-inserts each one, which only works
600
- * lowest-index-first), then prior ops in their original relative order
601
- * at their shifted positions. See ArraySchema#$setAt.
628
+ * The rebuilt map's insertion order IS the wire order:
629
+ * 1. ops below `at` — the insert doesn't move them, and an insert of
630
+ * their own must still be applied before this one (ascending);
631
+ * 2. the new ADDs, ascending the decoder splice-inserts each one,
632
+ * which only works lowest-index-first;
633
+ * 3. the re-keyed ops, in their original relative order — their
634
+ * indexes now address the post-insert layout.
635
+ * See ArraySchema#$setAt.
602
636
  */
603
- unshift(count: number): void {
604
- if (this._isSchema) throw new Error("ChangeTree (Schema): unshift is not supported");
637
+ insertAt(at: number, count: number): void {
638
+ if (this._isSchema) throw new Error("ChangeTree (Schema): insertAt is not supported");
605
639
  const src = this.collDirty!;
606
640
  const dst = new Map<number, OPERATION>();
607
641
  const track = !this.paused && !this.isFullStateOnly;
642
+ if (at > 0) {
643
+ for (const [idx, val] of src) if (idx < at) dst.set(idx, val);
644
+ }
608
645
  if (track) {
609
- for (let i = 0; i < count; i++) dst.set(i, OPERATION.ADD);
646
+ for (let i = 0; i < count; i++) dst.set(at + i, OPERATION.ADD);
610
647
  }
611
- for (const [idx, val] of src) dst.set(idx + count, val);
648
+ for (const [idx, val] of src) if (idx >= at) dst.set(idx + count, val);
612
649
  this.collDirty = dst;
613
- (this.unreliableRecorder as ICollectionChangeRecorder | undefined)?.shift(count);
650
+ // no unreliable re-key — collection trees never carry an unreliable
651
+ // recorder (tree-level @unreliable is disabled, see isFieldUnreliable)
614
652
  if (track) this.root?.enqueueChangeTree(this);
615
653
  }
616
654
 
655
+ /** ArraySchema#unshift(): insert `count` items at the head. */
656
+ unshift(count: number): void {
657
+ this.insertAt(0, count);
658
+ }
659
+
617
660
  // Tree attachment + child iteration — see ./changeTree/treeAttachment.ts.
618
661
  setRoot(root: Root): void { _setRoot(this, root); }
619
662
  setParent(parent: Ref, root?: Root, parentIndex?: number): void { _setParent(this, parent, root, parentIndex); }
@@ -762,7 +805,12 @@ export class ChangeTree<T extends Ref = any> implements ChangeRecorder {
762
805
  endEncode() {
763
806
  this.reset();
764
807
  this.changesNode = undefined;
765
- (this.ref as any)[$onEncodeEnd]?.();
808
+ // Every collection class defines [$onEncodeEnd]; Schema never does —
809
+ // probing it was a guaranteed megamorphic miss per drained tree.
810
+ // `?.` stays: a FIELDLESS Schema has no metadata, so its tree is
811
+ // `_isSchema === false` too. refTarget receiver skips ArraySchema's
812
+ // proxy hops.
813
+ if (!this._isSchema) (this.refTarget as any)[$onEncodeEnd]?.();
766
814
  this.isNew = false;
767
815
  }
768
816
 
@@ -770,11 +818,11 @@ export class ChangeTree<T extends Ref = any> implements ChangeRecorder {
770
818
  endEncodeUnreliable() {
771
819
  this.unreliableRecorder?.reset();
772
820
  this.unreliableChangesNode = undefined;
773
- (this.ref as any)[$onEncodeEnd]?.();
821
+ if (!this._isSchema) (this.refTarget as any)[$onEncodeEnd]?.();
774
822
  }
775
823
 
776
824
  discard() {
777
- (this.ref as any)[$onEncodeEnd]?.();
825
+ if (!this._isSchema) (this.refTarget as any)[$onEncodeEnd]?.();
778
826
  this.reset();
779
827
  this.unreliableRecorder?.reset();
780
828
  }
@@ -811,7 +859,7 @@ export class ChangeTree<T extends Ref = any> implements ChangeRecorder {
811
859
  /** @returns true if parent was found and removed */
812
860
  removeParent(parent: Ref = this.parent): boolean { return _removeParent(this, parent); }
813
861
 
814
- findParent(predicate: (parent: Ref, index: number) => boolean): ParentChain | undefined {
862
+ findParent(predicate: (parent: Ref, index: number) => boolean): ParentEntry | undefined {
815
863
  return _findParent(this, predicate);
816
864
  }
817
865
 
@@ -819,7 +867,10 @@ export class ChangeTree<T extends Ref = any> implements ChangeRecorder {
819
867
  return _hasParent(this, predicate);
820
868
  }
821
869
 
822
- getAllParents(): Array<{ ref: Ref, index: number }> { return _getAllParents(this); }
870
+ /** Wire index this tree holds inside `parent`, or undefined if not a parent. */
871
+ indexInParent(parent: Ref): number | undefined { return _indexInParent(this, parent); }
872
+
873
+ getAllParents(): ParentEntry[] { return _getAllParents(this); }
823
874
 
824
875
  }
825
876
 
@@ -242,8 +242,17 @@ export const encodeArray: EncodeOperation = function (
242
242
  if (operation === OPERATION.DELETE) {
243
243
  operation = OPERATION.DELETE_BY_REFID;
244
244
 
245
- } else if (operation === OPERATION.ADD) {
245
+ } else if ((operation & OPERATION.ADD) === OPERATION.ADD) {
246
+ // ADD, DELETE_AND_ADD, MOVE_AND_ADD. The wire index below is a
247
+ // refId — positional ops would make the decoder misread it as a
248
+ // slot, so everything must degrade to a BY_REFID op here.
246
249
  operation = OPERATION.ADD_BY_REFID;
250
+
251
+ } else if ((operation & OPERATION.MOVE) === OPERATION.MOVE) {
252
+ // Pure reorder (MOVE / DELETE_AND_MOVE). Filtered clients hold
253
+ // per-view subsets, so element order is not synchronized for
254
+ // them (ADD_BY_REFID appends) — there is nothing to emit.
255
+ return;
247
256
  }
248
257
 
249
258
  } else if (operation === OPERATION.DELETE && isSchemaChild) {
@@ -9,11 +9,12 @@ import type { Iterator } from "../encoding/decode.js";
9
9
  import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec.js';
10
10
  import { Root } from "./Root.js";
11
11
 
12
- import type { StateView } from "./StateView.js";
12
+ import { ARRAY_SNAPSHOT, type StateView } from "./StateView.js";
13
13
  import type { ChangeTree, ChangeTreeList, ChangeTreeNode } from "./ChangeTree.js";
14
14
  import type { EncodeOperation } from "./EncodeOperation.js";
15
15
  import { forEachLiveWithCtx as _forEachLiveWithCtx } from "./changeTree/liveIteration.js";
16
16
  import { forEachChildWithCtx as _forEachChildWithCtx } from "./changeTree/treeAttachment.js";
17
+ import { drainFilterRefresh } from "./changeTree/inheritedFlags.js";
17
18
 
18
19
  /**
19
20
  * Reusable context passed to the recorder's forEachWithCtx to iterate changes
@@ -296,6 +297,10 @@ export class Encoder<T extends Schema = any> {
296
297
  initialOffset: number,
297
298
  unreliable: boolean,
298
299
  ): Uint8Array {
300
+ // Settle any pending per-edge filter re-derivations before routing
301
+ // fields to channels (see inheritedFlags.drainFilterRefresh).
302
+ if (this.root.pendingFilterRefresh.length > 0) drainFilterRefresh(this.root);
303
+
299
304
  const hasView = (view !== undefined);
300
305
  const rootChangeTree = this.state[$changes];
301
306
 
@@ -379,6 +384,9 @@ export class Encoder<T extends Schema = any> {
379
384
  view?: StateView,
380
385
  initialOffset: number = it.offset
381
386
  ): Uint8Array {
387
+ // Full-sync splits fields by the same isFiltered classification.
388
+ if (this.root.pendingFilterRefresh.length > 0) drainFilterRefresh(this.root);
389
+
382
390
  const hasView = (view !== undefined);
383
391
  const rootChangeTree = this.state[$changes];
384
392
 
@@ -521,7 +529,49 @@ export class Encoder<T extends Schema = any> {
521
529
 
522
530
  // Iterate entries directly — the inner Map gives us the (index, op)
523
531
  // pair without an intermediate keys array or Number() parse.
524
- for (const [index, op] of changes) {
532
+ for (const [key, op] of changes) {
533
+ // Element-binding entries under a collection parent are keyed
534
+ // by the child's ChangeTree — resolve its CURRENT wire slot
535
+ // here (a slot captured at view.add() time goes stale when
536
+ // the array reindexes later in the same tick).
537
+ let index: number;
538
+ if (key === ARRAY_SNAPSHOT) {
539
+ // Whole-array snapshot: emit an ADD per live element at
540
+ // its CURRENT slot. Structural walk at drain time — a
541
+ // reindex after view.add() cannot go stale, and staged
542
+ // holes (recorder DELETEs) are skipped by construction.
543
+ const tmpItems = refTarget.tmpItems;
544
+ const deletedIndexes = refTarget.deletedIndexes;
545
+ for (let slot = 0; slot < tmpItems.length; slot++) {
546
+ if (tmpItems[slot] === undefined || deletedIndexes[slot] === true) { continue; }
547
+ encoder(this, bytes, changeTree, slot, OPERATION.ADD, it, false, true, metadata);
548
+ }
549
+ continue;
550
+ }
551
+ if (typeof key === "number") {
552
+ index = key;
553
+ } else {
554
+ const resolved = key.indexInParent(ref);
555
+ if (resolved === undefined) { continue; } // detached and re-parented elsewhere
556
+ index = resolved;
557
+
558
+ // SAME PATCH view.add + state-removal: the element is
559
+ // leaving the array (recorder DELETE at its slot), so the
560
+ // binding is moot — and `$getByIndex` on a staged hole
561
+ // reads a DIFFERENT element (compacted `items`), which
562
+ // would ship a mismatched value payload. Cancel the
563
+ // binding AND the child's own pending entry (its refId
564
+ // was never introduced to this client). Topological
565
+ // drain order guarantees the child entry hasn't been
566
+ // visited yet. A recorder ADD at the slot needs no such
567
+ // guard — both channels emit ADD_BY_REFID and the
568
+ // decoder dedups by identity.
569
+ if (op === OPERATION.ADD && changeTree.getChange(index) === OPERATION.DELETE) {
570
+ view.changes.delete(key.ref[$refId]);
571
+ continue;
572
+ }
573
+ }
574
+
525
575
  // workaround when using view.add() on item that has been deleted from state
526
576
  // (see test "adding to view item that has been removed from state")
527
577
  const value = refTarget[$getByIndex](index);
@@ -1,6 +1,7 @@
1
1
  import { OPERATION } from "../encoding/spec.js";
2
2
  import { TypeContext } from "../types/TypeContext.js";
3
- import { ChangeTree, ChangeTreeList, createChangeTreeList, type ChangeTreeNode } from "./ChangeTree.js";
3
+ import { ChangeTree, ChangeTreeList, createChangeTreeList, PENDING_FILTER_REFRESH, type ChangeTreeNode } from "./ChangeTree.js";
4
+ import { restageLiveCb } from "./changeTree/liveIteration.js";
4
5
  import { $changes, $refId } from "../types/symbols.js";
5
6
  import type { StateView } from "./StateView.js";
6
7
  import type { StreamSchema } from "../types/custom/StreamSchema.js";
@@ -53,6 +54,22 @@ export class Root {
53
54
  */
54
55
  unreliableChanges: ChangeTreeList = createChangeTreeList();
55
56
 
57
+ /**
58
+ * Trees whose parent-edge set changed this tick (instance sharing
59
+ * gained or lost an edge). The encoder drains this before emission —
60
+ * `inheritedFlags.drainFilterRefresh` re-derives each tree's filter
61
+ * state against the then-settled containers. Only populated when the
62
+ * TypeContext has any @view/@stream field.
63
+ */
64
+ public pendingFilterRefresh: ChangeTree[] = [];
65
+
66
+ public enqueueFilterRefresh(tree: ChangeTree): void {
67
+ if (!this.types.hasFilters) return;
68
+ if (tree.flags & PENDING_FILTER_REFRESH) return;
69
+ tree.flags |= PENDING_FILTER_REFRESH;
70
+ this.pendingFilterRefresh.push(tree);
71
+ }
72
+
56
73
  /**
57
74
  * Free-list of ChangeTreeNode objects. Both queues share this pool —
58
75
  * a node carries no queue affinity, only `{ changeTree, prev, next, position }`.
@@ -172,17 +189,16 @@ export class Root {
172
189
  // values would otherwise never be encoded).
173
190
  //
174
191
  changeTree.needsRestage = false;
175
- changeTree.forEachLive((fieldIndex) => {
176
- if (changeTree.isFieldUnreliable(fieldIndex)) {
177
- changeTree.ensureUnreliableRecorder().record(fieldIndex, OPERATION.ADD);
178
- } else {
179
- changeTree.record(fieldIndex, OPERATION.ADD);
180
- }
181
- });
192
+ changeTree.forEachLiveWithCtx(changeTree, restageLiveCb);
182
193
  }
183
194
 
184
195
  this.refCount[refId] = (previousRefCount || 0) + 1;
185
196
 
197
+ // Gained a 2nd+ parent edge (instance sharing / re-assignment) —
198
+ // re-derive filter state before the next encode. Chokepoint for
199
+ // every attach path; mirrors the edge-loss enqueue in `remove()`.
200
+ if (previousRefCount > 0) this.enqueueFilterRefresh(changeTree);
201
+
186
202
  return isNewChangeTree;
187
203
  }
188
204
 
@@ -229,6 +245,10 @@ export class Root {
229
245
  } else {
230
246
  this.refCount[refId] = refCount;
231
247
 
248
+ // Lost one of several parent edges — the surviving edge set may
249
+ // no longer include a public path (or may have gained one).
250
+ this.enqueueFilterRefresh(changeTree);
251
+
232
252
  //
233
253
  // When losing a reference to an instance, it is best to move the
234
254
  // ChangeTree next to its parent in the encoding queue.