@colyseus/schema 4.0.20 → 5.0.0
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 +2 -0
- package/build/Metadata.d.ts +55 -2
- package/build/Reflection.d.ts +24 -30
- package/build/Schema.d.ts +70 -9
- package/build/annotations.d.ts +56 -13
- package/build/codegen/cli.cjs +84 -67
- package/build/codegen/cli.cjs.map +1 -1
- package/build/decoder/DecodeOperation.d.ts +48 -5
- package/build/decoder/Decoder.d.ts +2 -2
- package/build/decoder/strategy/Callbacks.d.ts +1 -1
- package/build/encoder/ChangeRecorder.d.ts +107 -0
- package/build/encoder/ChangeTree.d.ts +218 -69
- package/build/encoder/EncodeDescriptor.d.ts +63 -0
- package/build/encoder/EncodeOperation.d.ts +25 -2
- package/build/encoder/Encoder.d.ts +59 -3
- package/build/encoder/MapJournal.d.ts +62 -0
- package/build/encoder/RefIdAllocator.d.ts +35 -0
- package/build/encoder/Root.d.ts +94 -13
- package/build/encoder/StateView.d.ts +116 -8
- package/build/encoder/changeTree/inheritedFlags.d.ts +34 -0
- package/build/encoder/changeTree/liveIteration.d.ts +3 -0
- package/build/encoder/changeTree/parentChain.d.ts +24 -0
- package/build/encoder/changeTree/treeAttachment.d.ts +13 -0
- package/build/encoder/streaming.d.ts +73 -0
- package/build/encoder/subscriptions.d.ts +25 -0
- package/build/index.cjs +5202 -1552
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +7 -3
- package/build/index.js +5202 -1552
- package/build/index.mjs +5193 -1552
- package/build/index.mjs.map +1 -1
- package/build/input/InputDecoder.d.ts +32 -0
- package/build/input/InputEncoder.d.ts +117 -0
- package/build/input/index.cjs +7429 -0
- package/build/input/index.cjs.map +1 -0
- package/build/input/index.d.ts +3 -0
- package/build/input/index.mjs +7426 -0
- package/build/input/index.mjs.map +1 -0
- package/build/types/HelperTypes.d.ts +22 -8
- package/build/types/TypeContext.d.ts +9 -0
- package/build/types/builder.d.ts +162 -0
- package/build/types/custom/ArraySchema.d.ts +25 -4
- package/build/types/custom/CollectionSchema.d.ts +30 -2
- package/build/types/custom/MapSchema.d.ts +52 -3
- package/build/types/custom/SetSchema.d.ts +32 -2
- package/build/types/custom/StreamSchema.d.ts +114 -0
- package/build/types/symbols.d.ts +48 -5
- package/package.json +9 -3
- package/src/Metadata.ts +258 -31
- package/src/Reflection.ts +15 -13
- package/src/Schema.ts +176 -134
- package/src/annotations.ts +308 -236
- package/src/bench_bloat.ts +173 -0
- package/src/bench_decode.ts +221 -0
- package/src/bench_decode_mem.ts +165 -0
- package/src/bench_encode.ts +108 -0
- package/src/bench_init.ts +150 -0
- package/src/bench_static.ts +109 -0
- package/src/bench_stream.ts +295 -0
- package/src/bench_view_cmp.ts +142 -0
- package/src/codegen/languages/csharp.ts +0 -24
- package/src/codegen/parser.ts +83 -61
- package/src/decoder/DecodeOperation.ts +168 -63
- package/src/decoder/Decoder.ts +20 -10
- package/src/decoder/ReferenceTracker.ts +4 -0
- package/src/decoder/strategy/Callbacks.ts +30 -26
- package/src/decoder/strategy/getDecoderStateCallbacks.ts +16 -13
- package/src/encoder/ChangeRecorder.ts +276 -0
- package/src/encoder/ChangeTree.ts +674 -519
- package/src/encoder/EncodeDescriptor.ts +213 -0
- package/src/encoder/EncodeOperation.ts +107 -65
- package/src/encoder/Encoder.ts +630 -119
- package/src/encoder/MapJournal.ts +124 -0
- package/src/encoder/RefIdAllocator.ts +68 -0
- package/src/encoder/Root.ts +247 -120
- package/src/encoder/StateView.ts +592 -121
- package/src/encoder/changeTree/inheritedFlags.ts +217 -0
- package/src/encoder/changeTree/liveIteration.ts +74 -0
- package/src/encoder/changeTree/parentChain.ts +131 -0
- package/src/encoder/changeTree/treeAttachment.ts +171 -0
- package/src/encoder/streaming.ts +232 -0
- package/src/encoder/subscriptions.ts +71 -0
- package/src/index.ts +15 -3
- package/src/input/InputDecoder.ts +57 -0
- package/src/input/InputEncoder.ts +303 -0
- package/src/input/index.ts +3 -0
- package/src/types/HelperTypes.ts +21 -9
- package/src/types/TypeContext.ts +14 -2
- package/src/types/builder.ts +285 -0
- package/src/types/custom/ArraySchema.ts +210 -197
- package/src/types/custom/CollectionSchema.ts +115 -35
- package/src/types/custom/MapSchema.ts +162 -58
- package/src/types/custom/SetSchema.ts +128 -39
- package/src/types/custom/StreamSchema.ts +310 -0
- package/src/types/symbols.ts +54 -6
- package/src/utils.ts +4 -6
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* EncodeDescriptor — per-class snapshot of the values the encode loop needs
|
|
3
|
+
* from a Ref's constructor. Lazily computed once per class (the first time
|
|
4
|
+
* a tree of that class is constructed) and stashed on the constructor via
|
|
5
|
+
* `$encodeDescriptor`. Each ChangeTree caches a reference to its class's
|
|
6
|
+
* descriptor at construction time, so the encode loop reads a single
|
|
7
|
+
* property from the tree instead of chasing 5 separate per-tree lookups:
|
|
8
|
+
*
|
|
9
|
+
* ctor[$encoder]
|
|
10
|
+
* ctor[$filter]
|
|
11
|
+
* ctor[Symbol.metadata]
|
|
12
|
+
* Metadata.isValidInstance(ref)
|
|
13
|
+
* getFilterBitmask(metadata)
|
|
14
|
+
*
|
|
15
|
+
* Lives in its own file to break the Encoder.ts ↔ ChangeTree.ts import
|
|
16
|
+
* cycle (ChangeTree caches descriptors at construction; Encoder reads them
|
|
17
|
+
* during encode).
|
|
18
|
+
*/
|
|
19
|
+
import { Metadata } from "../Metadata.js";
|
|
20
|
+
import { $encodeDescriptor, $encoder, $encoders, $filter, $filterBitmask, $numFields, $staticFieldIndexes, $streamFieldIndexes, $unreliableFieldIndexes, $viewFieldIndexes } from "../types/symbols.js";
|
|
21
|
+
import type { StateView } from "./StateView.js";
|
|
22
|
+
import type { EncodeOperation } from "./EncodeOperation.js";
|
|
23
|
+
|
|
24
|
+
export interface EncodeDescriptor {
|
|
25
|
+
encoder: EncodeOperation;
|
|
26
|
+
filter: ((ref: any, index: number, view?: StateView) => boolean) | undefined;
|
|
27
|
+
metadata: any;
|
|
28
|
+
isSchema: boolean;
|
|
29
|
+
/**
|
|
30
|
+
* Bit i set iff field i has a @view tag. 0 for collection trees.
|
|
31
|
+
* Lets `encodeChangeCb` do a single bitwise op instead of a
|
|
32
|
+
* per-field metadata[i]?.tag chase.
|
|
33
|
+
*/
|
|
34
|
+
filterBitmask: number;
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Class-level "any field has the flag" booleans + per-field bitmasks.
|
|
38
|
+
* Hot path: per-mutation `_routeAndRecord` calls `isFieldStatic` and
|
|
39
|
+
* `isFieldUnreliable`. The common case is "no static/unreliable fields
|
|
40
|
+
* anywhere on this class" (booleans short-circuit before the symbol-keyed
|
|
41
|
+
* metadata lookup); the secondary common case is "this class has some
|
|
42
|
+
* such fields and we need to know if THIS field is one" — the bitmask
|
|
43
|
+
* answers in one bitwise op instead of an `Array.includes` linear scan.
|
|
44
|
+
*
|
|
45
|
+
* Bitmasks cover fields 0–31 only (matches the `filterBitmask` limitation).
|
|
46
|
+
* Fields ≥32 fall back to `Metadata.hasXAtIndex` — same handling as the
|
|
47
|
+
* filter-bitmask path.
|
|
48
|
+
*/
|
|
49
|
+
hasAnyStatic: boolean;
|
|
50
|
+
hasAnyUnreliable: boolean;
|
|
51
|
+
hasAnyStream: boolean;
|
|
52
|
+
/**
|
|
53
|
+
* Class-level "any field carries a `@view` tag" — covers fields both
|
|
54
|
+
* within and beyond index 31 (unlike `filterBitmask`, which only
|
|
55
|
+
* captures the low 32). Read by `ChangeTree.hasFilteredFields` to
|
|
56
|
+
* decide whether a parent tree must be included in a view's bootstrap.
|
|
57
|
+
*/
|
|
58
|
+
hasAnyView: boolean;
|
|
59
|
+
staticBitmask: number;
|
|
60
|
+
unreliableBitmask: number;
|
|
61
|
+
/**
|
|
62
|
+
* Bit i set iff field i holds a `t.stream(...)` collection. Hot encode
|
|
63
|
+
* path reads this to dispatch stream fields into the priority/budget
|
|
64
|
+
* gate instead of the normal recorder iteration.
|
|
65
|
+
*/
|
|
66
|
+
streamBitmask: number;
|
|
67
|
+
|
|
68
|
+
/**
|
|
69
|
+
* Per-field parallel arrays — Schemas only (empty arrays for
|
|
70
|
+
* collections). Replaces hot-path `metadata[i].name` / `metadata[i].type`
|
|
71
|
+
* / `metadata[i].tag` chains with direct array indexing on a small
|
|
72
|
+
* fixed-shape object.
|
|
73
|
+
*
|
|
74
|
+
* Sparse where natural: `tags[i]` is undefined unless field i carries
|
|
75
|
+
* a @view tag; readers should null-check before comparing.
|
|
76
|
+
*
|
|
77
|
+
* `encoders[i]` mirrors `metadata[$encoders]` — the pre-computed
|
|
78
|
+
* encoder fn for primitive-typed fields. Cached here so encode loops
|
|
79
|
+
* skip a `metadata[$encoders]?.[i]` symbol-chain per emission.
|
|
80
|
+
*/
|
|
81
|
+
names: string[];
|
|
82
|
+
types: any[];
|
|
83
|
+
tags: (number | undefined)[];
|
|
84
|
+
encoders: (((bytes: Uint8Array, value: any, it: any) => void) | undefined)[];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
function computeFilterBitmask(metadata: any): number {
|
|
88
|
+
if (metadata === undefined) return 0;
|
|
89
|
+
let bm: number | undefined = metadata[$filterBitmask];
|
|
90
|
+
if (bm !== undefined) return bm;
|
|
91
|
+
bm = 0;
|
|
92
|
+
const tagged = metadata[$viewFieldIndexes];
|
|
93
|
+
if (tagged !== undefined) {
|
|
94
|
+
for (let i = 0, len = tagged.length; i < len; i++) bm |= (1 << tagged[i]);
|
|
95
|
+
}
|
|
96
|
+
// Non-enumerable so `for (const k in metadata)` iteration in TypeContext
|
|
97
|
+
// and elsewhere doesn't mistake this cache for a real field index.
|
|
98
|
+
Object.defineProperty(metadata, $filterBitmask, {
|
|
99
|
+
value: bm,
|
|
100
|
+
enumerable: false,
|
|
101
|
+
writable: true,
|
|
102
|
+
configurable: true,
|
|
103
|
+
});
|
|
104
|
+
return bm;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Bitmask of field indexes 0–31 in `indexes`. For fields ≥32 callers must
|
|
109
|
+
* fall back to the array lookup (same as `filterBitmask`).
|
|
110
|
+
*/
|
|
111
|
+
function indexesToBitmask(indexes: number[] | undefined): number {
|
|
112
|
+
if (indexes === undefined) return 0;
|
|
113
|
+
let bm = 0;
|
|
114
|
+
for (let i = 0, len = indexes.length; i < len; i++) {
|
|
115
|
+
const idx = indexes[i];
|
|
116
|
+
if (idx < 32) bm |= (1 << idx);
|
|
117
|
+
}
|
|
118
|
+
return bm;
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Build the per-field parallel arrays once at descriptor construction.
|
|
123
|
+
* For collection trees (no metadata or no $numFields) this returns empty
|
|
124
|
+
* arrays — readers branch on `isSchema` before touching them anyway.
|
|
125
|
+
*/
|
|
126
|
+
function buildFieldArrays(metadata: any): {
|
|
127
|
+
names: string[];
|
|
128
|
+
types: any[];
|
|
129
|
+
tags: (number | undefined)[];
|
|
130
|
+
encoders: (((bytes: Uint8Array, value: any, it: any) => void) | undefined)[];
|
|
131
|
+
} {
|
|
132
|
+
const names: string[] = [];
|
|
133
|
+
const types: any[] = [];
|
|
134
|
+
const tags: (number | undefined)[] = [];
|
|
135
|
+
const encoders: (((bytes: Uint8Array, value: any, it: any) => void) | undefined)[] = [];
|
|
136
|
+
|
|
137
|
+
if (metadata === undefined) return { names, types, tags, encoders };
|
|
138
|
+
|
|
139
|
+
const numFields = metadata[$numFields];
|
|
140
|
+
if (numFields === undefined) return { names, types, tags, encoders };
|
|
141
|
+
|
|
142
|
+
const srcEncoders = metadata[$encoders];
|
|
143
|
+
for (let i = 0; i <= numFields; i++) {
|
|
144
|
+
const field = metadata[i];
|
|
145
|
+
if (field === undefined) {
|
|
146
|
+
// Holes are normal — inheritance can leave gaps. Fill with
|
|
147
|
+
// undefined so indexing is valid.
|
|
148
|
+
names[i] = undefined!;
|
|
149
|
+
types[i] = undefined;
|
|
150
|
+
tags[i] = undefined;
|
|
151
|
+
encoders[i] = undefined;
|
|
152
|
+
continue;
|
|
153
|
+
}
|
|
154
|
+
names[i] = field.name;
|
|
155
|
+
types[i] = field.type;
|
|
156
|
+
tags[i] = field.tag;
|
|
157
|
+
encoders[i] = srcEncoders?.[i];
|
|
158
|
+
}
|
|
159
|
+
return { names, types, tags, encoders };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
export function getEncodeDescriptor(ref: any): EncodeDescriptor {
|
|
163
|
+
const ctor = ref.constructor;
|
|
164
|
+
|
|
165
|
+
// Use hasOwn — Object.defineProperty on a parent class would otherwise
|
|
166
|
+
// be inherited by every subclass via the prototype chain, and a
|
|
167
|
+
// subclass's instance would read the parent's metadata/encoder. See
|
|
168
|
+
// "should encode the correct class inside an array" for the regression.
|
|
169
|
+
if (Object.prototype.hasOwnProperty.call(ctor, $encodeDescriptor)) {
|
|
170
|
+
return ctor[$encodeDescriptor];
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
const metadata = ctor[Symbol.metadata];
|
|
174
|
+
const isSchema = Metadata.isValidInstance(ref);
|
|
175
|
+
const hasAnyView = (metadata?.[$viewFieldIndexes]?.length ?? 0) > 0;
|
|
176
|
+
const arrays = buildFieldArrays(metadata);
|
|
177
|
+
// For Schema classes with no `@view`-tagged fields, the per-field
|
|
178
|
+
// `ctx.filter(ref, index, view)` call on the encode hot path is a
|
|
179
|
+
// provable no-op: `Schema[$filter]` does `metadata[index]?.tag === undefined`
|
|
180
|
+
// which is always true when no field carries a tag. Setting filter to
|
|
181
|
+
// `undefined` here lets the `ctx.filter !== undefined && …` short-circuit
|
|
182
|
+
// in `encodeChangeCb` skip the call entirely — the metadata lookup +
|
|
183
|
+
// comparison adds up across 10k+ field encodes/tick.
|
|
184
|
+
// Collection classes keep their filter — their `[$filter]` does
|
|
185
|
+
// instance-level `ref[$childType]` / view-visibility checks that can't
|
|
186
|
+
// be decided class-wide.
|
|
187
|
+
const filter = (isSchema && !hasAnyView) ? undefined : ctor[$filter];
|
|
188
|
+
const desc: EncodeDescriptor = {
|
|
189
|
+
encoder: ctor[$encoder],
|
|
190
|
+
filter,
|
|
191
|
+
metadata,
|
|
192
|
+
isSchema,
|
|
193
|
+
filterBitmask: isSchema ? computeFilterBitmask(metadata) : 0,
|
|
194
|
+
hasAnyStatic: (metadata?.[$staticFieldIndexes]?.length ?? 0) > 0,
|
|
195
|
+
hasAnyUnreliable: (metadata?.[$unreliableFieldIndexes]?.length ?? 0) > 0,
|
|
196
|
+
hasAnyStream: (metadata?.[$streamFieldIndexes]?.length ?? 0) > 0,
|
|
197
|
+
hasAnyView,
|
|
198
|
+
staticBitmask: indexesToBitmask(metadata?.[$staticFieldIndexes]),
|
|
199
|
+
unreliableBitmask: indexesToBitmask(metadata?.[$unreliableFieldIndexes]),
|
|
200
|
+
streamBitmask: indexesToBitmask(metadata?.[$streamFieldIndexes]),
|
|
201
|
+
names: arrays.names,
|
|
202
|
+
types: arrays.types,
|
|
203
|
+
tags: arrays.tags,
|
|
204
|
+
encoders: arrays.encoders,
|
|
205
|
+
};
|
|
206
|
+
Object.defineProperty(ctor, $encodeDescriptor, {
|
|
207
|
+
value: desc,
|
|
208
|
+
enumerable: false,
|
|
209
|
+
writable: true,
|
|
210
|
+
configurable: true,
|
|
211
|
+
});
|
|
212
|
+
return desc;
|
|
213
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { OPERATION } from "../encoding/spec.js";
|
|
2
|
-
import { $changes, $childType, $getByIndex, $refId } from "../types/symbols.js";
|
|
2
|
+
import { $changes, $childType, $encoders, $getByIndex, $refId, $values } from "../types/symbols.js";
|
|
3
3
|
|
|
4
4
|
import { encode } from "../encoding/encode.js";
|
|
5
5
|
|
|
@@ -30,8 +30,14 @@ export function encodeValue(
|
|
|
30
30
|
value: any,
|
|
31
31
|
operation: OPERATION,
|
|
32
32
|
it: Iterator,
|
|
33
|
+
encoderFn?: (bytes: Uint8Array, value: any, it: Iterator) => void,
|
|
33
34
|
) {
|
|
34
|
-
if (
|
|
35
|
+
if (encoderFn !== undefined) {
|
|
36
|
+
// Fast path: pre-computed encoder for primitive types.
|
|
37
|
+
encoderFn(bytes, value, it);
|
|
38
|
+
|
|
39
|
+
} else if (typeof (type) === "string") {
|
|
40
|
+
// Fallback for types not pre-computed (e.g. runtime-constructed).
|
|
35
41
|
(encode as any)[type]?.(bytes, value, it);
|
|
36
42
|
|
|
37
43
|
} else if (type[Symbol.metadata] !== undefined) {
|
|
@@ -68,7 +74,6 @@ export const encodeSchemaOperation: EncodeOperation = function <T extends Schema
|
|
|
68
74
|
it: Iterator,
|
|
69
75
|
_: any,
|
|
70
76
|
__: any,
|
|
71
|
-
metadata: Metadata,
|
|
72
77
|
) {
|
|
73
78
|
// "compress" field index + operation
|
|
74
79
|
bytes[it.offset++] = (index | operation) & 255;
|
|
@@ -78,25 +83,37 @@ export const encodeSchemaOperation: EncodeOperation = function <T extends Schema
|
|
|
78
83
|
return;
|
|
79
84
|
}
|
|
80
85
|
|
|
81
|
-
|
|
82
|
-
|
|
86
|
+
// Read field info from the per-class descriptor's parallel arrays —
|
|
87
|
+
// replaces `metadata[index]` (returns a per-field obj) + `.name` /
|
|
88
|
+
// `.type` chains. The `encoders` array is also pre-baked here so we
|
|
89
|
+
// skip a `metadata[$encoders]?.[index]` symbol-keyed lookup per call.
|
|
90
|
+
const desc = changeTree.encDescriptor;
|
|
91
|
+
const ref = changeTree.ref as any;
|
|
92
|
+
|
|
93
|
+
// Direct $values[index] read — bypasses prototype getter + metadata name lookup.
|
|
94
|
+
// Falls back to named property for manual fields (which don't use $values).
|
|
95
|
+
const value = ref[$values][index] ?? ref[desc.names[index]];
|
|
83
96
|
|
|
84
|
-
// TODO: inline this function call small performance gain
|
|
85
97
|
encodeValue(
|
|
86
98
|
encoder,
|
|
87
99
|
bytes,
|
|
88
|
-
|
|
89
|
-
|
|
100
|
+
desc.types[index],
|
|
101
|
+
value,
|
|
90
102
|
operation,
|
|
91
|
-
it
|
|
103
|
+
it,
|
|
104
|
+
desc.encoders[index],
|
|
92
105
|
);
|
|
93
106
|
}
|
|
94
107
|
|
|
95
108
|
/**
|
|
96
|
-
*
|
|
109
|
+
* Encode a single MapSchema entry. Splits the legacy
|
|
110
|
+
* `encodeKeyValueOperation` so the per-emission `typeof ref['set']` check
|
|
111
|
+
* is gone — MapSchema instances are routed here via their `[$encoder]`
|
|
112
|
+
* static, the dynamic-key string emission is unconditional on ADD.
|
|
113
|
+
*
|
|
97
114
|
* @private
|
|
98
115
|
*/
|
|
99
|
-
export const
|
|
116
|
+
export const encodeMapEntry: EncodeOperation = function (
|
|
100
117
|
encoder: Encoder,
|
|
101
118
|
bytes: Uint8Array,
|
|
102
119
|
changeTree: ChangeTree,
|
|
@@ -104,59 +121,88 @@ export const encodeKeyValueOperation: EncodeOperation = function (
|
|
|
104
121
|
operation: OPERATION,
|
|
105
122
|
it: Iterator,
|
|
106
123
|
) {
|
|
107
|
-
// encode operation
|
|
108
124
|
bytes[it.offset++] = operation & 255;
|
|
109
|
-
|
|
110
|
-
// encode index
|
|
111
125
|
encode.number(bytes, index, it);
|
|
112
126
|
|
|
113
|
-
|
|
114
|
-
if (operation === OPERATION.DELETE) {
|
|
115
|
-
return;
|
|
116
|
-
}
|
|
127
|
+
if (operation === OPERATION.DELETE) return;
|
|
117
128
|
|
|
118
129
|
const ref = changeTree.ref;
|
|
119
130
|
|
|
120
|
-
//
|
|
121
|
-
//
|
|
122
|
-
//
|
|
123
|
-
if ((operation & OPERATION.ADD) === OPERATION.ADD) {
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
// MapSchema dynamic key
|
|
127
|
-
//
|
|
128
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
129
|
-
encode.string(bytes, dynamicIndex, it);
|
|
130
|
-
}
|
|
131
|
+
// ADD or DELETE_AND_ADD: emit the user-facing string key for dynamic
|
|
132
|
+
// map fields. SetSchema/CollectionSchema use a different encoder and
|
|
133
|
+
// skip this entirely (no dynamic key).
|
|
134
|
+
if ((operation & OPERATION.ADD) === OPERATION.ADD) {
|
|
135
|
+
const dynamicIndex = (ref as any)['$indexes'].get(index);
|
|
136
|
+
encode.string(bytes, dynamicIndex, it);
|
|
131
137
|
}
|
|
132
138
|
|
|
133
|
-
const type = ref[$childType];
|
|
134
|
-
const value = ref[$getByIndex](index);
|
|
135
|
-
|
|
136
|
-
// try { throw new Error(); } catch (e) {
|
|
137
|
-
// // only print if not coming from Reflection.ts
|
|
138
|
-
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
139
|
-
// console.log("encodeKeyValueOperation -> ", {
|
|
140
|
-
// ref: changeTree.ref.constructor.name,
|
|
141
|
-
// field,
|
|
142
|
-
// operation: OPERATION[operation],
|
|
143
|
-
// value: value?.toJSON(),
|
|
144
|
-
// items: ref.toJSON(),
|
|
145
|
-
// });
|
|
146
|
-
// }
|
|
147
|
-
// }
|
|
148
|
-
|
|
149
|
-
// TODO: inline this function call small performance gain
|
|
150
139
|
encodeValue(
|
|
151
140
|
encoder,
|
|
152
141
|
bytes,
|
|
153
|
-
|
|
154
|
-
|
|
142
|
+
(ref as any)[$childType],
|
|
143
|
+
ref[$getByIndex](index),
|
|
155
144
|
operation,
|
|
156
|
-
it
|
|
145
|
+
it,
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/**
|
|
150
|
+
* Encode a single SetSchema / CollectionSchema entry. Wire format is the
|
|
151
|
+
* same as MapSchema minus the dynamic-key string, so this path skips the
|
|
152
|
+
* legacy `typeof ref['set']` check entirely.
|
|
153
|
+
*
|
|
154
|
+
* @private
|
|
155
|
+
*/
|
|
156
|
+
export const encodeIndexedEntry: EncodeOperation = function (
|
|
157
|
+
encoder: Encoder,
|
|
158
|
+
bytes: Uint8Array,
|
|
159
|
+
changeTree: ChangeTree,
|
|
160
|
+
index: number,
|
|
161
|
+
operation: OPERATION,
|
|
162
|
+
it: Iterator,
|
|
163
|
+
) {
|
|
164
|
+
bytes[it.offset++] = operation & 255;
|
|
165
|
+
encode.number(bytes, index, it);
|
|
166
|
+
|
|
167
|
+
if (operation === OPERATION.DELETE) return;
|
|
168
|
+
|
|
169
|
+
const ref = changeTree.ref;
|
|
170
|
+
encodeValue(
|
|
171
|
+
encoder,
|
|
172
|
+
bytes,
|
|
173
|
+
(ref as any)[$childType],
|
|
174
|
+
ref[$getByIndex](index),
|
|
175
|
+
operation,
|
|
176
|
+
it,
|
|
157
177
|
);
|
|
158
178
|
}
|
|
159
179
|
|
|
180
|
+
/**
|
|
181
|
+
* Unified encoder kept for back-compat with external consumers that may
|
|
182
|
+
* have registered it directly via `static [$encoder] =
|
|
183
|
+
* encodeKeyValueOperation`. New code (and all internal collections)
|
|
184
|
+
* should use the split variants — `encodeMapEntry` for MapSchema and
|
|
185
|
+
* `encodeIndexedEntry` for SetSchema / CollectionSchema.
|
|
186
|
+
*
|
|
187
|
+
* The runtime `typeof ref['set']` check below is the per-emission cost
|
|
188
|
+
* the split is designed to remove.
|
|
189
|
+
*/
|
|
190
|
+
export const encodeKeyValueOperation: EncodeOperation = function (
|
|
191
|
+
encoder: Encoder,
|
|
192
|
+
bytes: Uint8Array,
|
|
193
|
+
changeTree: ChangeTree,
|
|
194
|
+
index: number,
|
|
195
|
+
operation: OPERATION,
|
|
196
|
+
it: Iterator,
|
|
197
|
+
) {
|
|
198
|
+
const ref = changeTree.ref as any;
|
|
199
|
+
if ((operation & OPERATION.ADD) === OPERATION.ADD && typeof ref['set'] === "function") {
|
|
200
|
+
encodeMapEntry(encoder, bytes, changeTree, index, operation, it, false, false);
|
|
201
|
+
} else {
|
|
202
|
+
encodeIndexedEntry(encoder, bytes, changeTree, index, operation, it, false, false);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
|
|
160
206
|
/**
|
|
161
207
|
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
162
208
|
* @private
|
|
@@ -171,13 +217,19 @@ export const encodeArray: EncodeOperation = function (
|
|
|
171
217
|
isEncodeAll: boolean,
|
|
172
218
|
hasView: boolean,
|
|
173
219
|
) {
|
|
174
|
-
|
|
175
|
-
|
|
220
|
+
// Read through `refTarget` so every property access below skips the
|
|
221
|
+
// ArraySchema Proxy `get` trap. `refTarget` points at the raw backing
|
|
222
|
+
// instance; `ref` (the Proxy) stays the user-facing identity.
|
|
223
|
+
const ref = changeTree.refTarget as any;
|
|
224
|
+
// ArraySchema stores its per-instance child type at `$childType`.
|
|
225
|
+
// This encoder is array-only — there's no Schema fallback to consider.
|
|
226
|
+
const type = ref[$childType];
|
|
227
|
+
const useOperationByRefId = hasView && changeTree.isFiltered && typeof type !== "string";
|
|
176
228
|
|
|
177
229
|
let refOrIndex: number;
|
|
178
230
|
|
|
179
231
|
if (useOperationByRefId) {
|
|
180
|
-
const item = ref
|
|
232
|
+
const item = ref.tmpItems[field];
|
|
181
233
|
|
|
182
234
|
// Skip encoding if item is undefined (e.g. when clear() is called)
|
|
183
235
|
if (!item) { return; }
|
|
@@ -206,20 +258,10 @@ export const encodeArray: EncodeOperation = function (
|
|
|
206
258
|
return;
|
|
207
259
|
}
|
|
208
260
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
// console.log({ type, field, value });
|
|
213
|
-
|
|
214
|
-
// console.log("encodeArray -> ", {
|
|
215
|
-
// ref: changeTree.ref.constructor.name,
|
|
216
|
-
// field,
|
|
217
|
-
// operation: OPERATION[operation],
|
|
218
|
-
// value: value?.toJSON(),
|
|
219
|
-
// items: ref.toJSON(),
|
|
220
|
-
// });
|
|
261
|
+
// `type` was already read above. Direct $getByIndex call — skips
|
|
262
|
+
// ChangeTree.getValue's pass-through wrapper.
|
|
263
|
+
const value = ref[$getByIndex](field, isEncodeAll);
|
|
221
264
|
|
|
222
|
-
// TODO: inline this function call small performance gain
|
|
223
265
|
encodeValue(
|
|
224
266
|
encoder,
|
|
225
267
|
bytes,
|