@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.
- package/README.md +11 -5
- package/build/Metadata.d.ts +10 -1
- package/build/annotations.d.ts +6 -5
- package/build/codegen/api.d.ts +2 -0
- package/build/codegen/cli.cjs +322 -31
- package/build/codegen/cli.cjs.map +1 -1
- package/build/codegen/parser.d.ts +6 -1
- package/build/codegen/resolve.d.ts +25 -0
- package/build/codegen/types.d.ts +2 -0
- package/build/decoder/strategy/Callbacks.d.ts +7 -8
- package/build/decoder/strategy/getDecoderStateCallbacks.d.ts +2 -2
- package/build/encoder/ChangeTree.d.ts +40 -12
- package/build/encoder/Encoder.d.ts +1 -1
- package/build/encoder/Root.d.ts +9 -0
- package/build/encoder/StateView.d.ts +38 -1
- package/build/encoder/changeTree/inheritedFlags.d.ts +13 -19
- package/build/encoder/changeTree/liveIteration.d.ts +8 -0
- package/build/encoder/changeTree/parentChain.d.ts +30 -8
- package/build/encoder/streaming.d.ts +1 -1
- package/build/index.cjs +3232 -2830
- package/build/index.cjs.map +1 -1
- package/build/index.js +3228 -2826
- package/build/index.mjs +3232 -2830
- package/build/index.mjs.map +1 -1
- package/build/types/HelperTypes.d.ts +24 -14
- package/build/types/TypeContext.d.ts +0 -17
- package/build/types/builder.d.ts +1 -5
- package/build/types/symbols.d.ts +1 -0
- package/package.json +9 -8
- package/src/Metadata.ts +59 -77
- package/src/Reflection.ts +9 -5
- package/src/annotations.ts +28 -18
- package/src/codegen/api.ts +3 -1
- package/src/codegen/cli.ts +5 -2
- package/src/codegen/parser.ts +69 -31
- package/src/codegen/resolve.ts +322 -0
- package/src/codegen/types.ts +4 -1
- package/src/decoder/DecodeOperation.ts +13 -2
- package/src/decoder/strategy/Callbacks.ts +7 -8
- package/src/decoder/strategy/getDecoderStateCallbacks.ts +2 -2
- package/src/encoder/ChangeTree.ts +76 -25
- package/src/encoder/EncodeOperation.ts +10 -1
- package/src/encoder/Encoder.ts +52 -2
- package/src/encoder/Root.ts +28 -8
- package/src/encoder/StateView.ts +150 -66
- package/src/encoder/changeTree/inheritedFlags.ts +164 -45
- package/src/encoder/changeTree/liveIteration.ts +24 -3
- package/src/encoder/changeTree/parentChain.ts +72 -15
- package/src/encoder/streaming.ts +2 -1
- package/src/types/HelperTypes.ts +43 -34
- package/src/types/TypeContext.ts +5 -52
- package/src/types/builder.ts +14 -10
- package/src/types/custom/ArraySchema.ts +57 -14
- 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, $
|
|
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,
|
|
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
|
-
|
|
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
|
-
//
|
|
331
|
-
//
|
|
332
|
-
//
|
|
333
|
-
|
|
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
|
|
596
|
-
* `+count`, then record ADDs for
|
|
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:
|
|
599
|
-
*
|
|
600
|
-
*
|
|
601
|
-
*
|
|
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
|
-
|
|
604
|
-
if (this._isSchema) throw new Error("ChangeTree (Schema):
|
|
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
|
-
|
|
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
|
-
|
|
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.
|
|
821
|
+
if (!this._isSchema) (this.refTarget as any)[$onEncodeEnd]?.();
|
|
774
822
|
}
|
|
775
823
|
|
|
776
824
|
discard() {
|
|
777
|
-
(this.
|
|
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):
|
|
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
|
-
|
|
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) {
|
package/src/encoder/Encoder.ts
CHANGED
|
@@ -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
|
|
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 [
|
|
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);
|
package/src/encoder/Root.ts
CHANGED
|
@@ -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.
|
|
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.
|