@colyseus/schema 4.0.20 → 5.0.1
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 +56 -2
- package/build/Reflection.d.ts +28 -34
- package/build/Schema.d.ts +70 -9
- package/build/annotations.d.ts +64 -17
- 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 +5258 -1549
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +7 -3
- package/build/index.js +5258 -1549
- package/build/index.mjs +5249 -1549
- 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 +7453 -0
- package/build/input/index.cjs.map +1 -0
- package/build/input/index.d.ts +3 -0
- package/build/input/index.mjs +7450 -0
- package/build/input/index.mjs.map +1 -0
- package/build/types/HelperTypes.d.ts +67 -9
- package/build/types/TypeContext.d.ts +9 -0
- package/build/types/builder.d.ts +192 -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 +259 -31
- package/src/Reflection.ts +15 -13
- package/src/Schema.ts +176 -134
- package/src/annotations.ts +365 -252
- 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 +121 -24
- package/src/types/TypeContext.ts +14 -2
- package/src/types/builder.ts +331 -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 +93 -6
- package/src/utils.ts +4 -6
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
import { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $onEncodeEnd, $refId } from "../symbols.js";
|
|
2
|
+
import { ChangeTree, type IRef } from "../../encoder/ChangeTree.js";
|
|
3
|
+
import { type StreamableState } from "../../encoder/streaming.js";
|
|
4
|
+
import type { StateView } from "../../encoder/StateView.js";
|
|
5
|
+
import type { Schema } from "../../Schema.js";
|
|
6
|
+
/**
|
|
7
|
+
* `t.stream(Entity)` — priority-batched collection of Schema instances.
|
|
8
|
+
*
|
|
9
|
+
* Designed for ECS-style use cases where many entities spawn/despawn each
|
|
10
|
+
* tick and the full set won't fit in one encode budget. Adds are queued
|
|
11
|
+
* per-client and drained in priority order (callback on StateView) up to
|
|
12
|
+
* `maxPerTick` per encode pass. Field mutations on already-sent elements
|
|
13
|
+
* propagate through the normal reliable channel without consuming the
|
|
14
|
+
* per-tick budget. Chain `.static()` on the field builder to suppress
|
|
15
|
+
* post-add mutation tracking entirely.
|
|
16
|
+
*/
|
|
17
|
+
export declare class StreamSchema<V = any> implements IRef {
|
|
18
|
+
[$changes]: ChangeTree;
|
|
19
|
+
[$refId]?: number;
|
|
20
|
+
protected [$childType]: string | typeof Schema;
|
|
21
|
+
/**
|
|
22
|
+
* Wire-keyed storage: `position → element`. Position is a monotonic
|
|
23
|
+
* counter assigned by `add()` — stable identity even when elements
|
|
24
|
+
* are removed, so pending/sent view state can keep using the same
|
|
25
|
+
* keys across ticks. Map (not Array) so `$items.keys()` / `.values()`
|
|
26
|
+
* skip removed positions without a sparse-slot check.
|
|
27
|
+
*/
|
|
28
|
+
protected $items: Map<number, V>;
|
|
29
|
+
/** Monotonic position counter. Incremented on every `add()`. */
|
|
30
|
+
protected $nextPosition: number;
|
|
31
|
+
/** Reverse lookup for O(1) `remove(el)`. */
|
|
32
|
+
protected _itemIndex: Map<V, number>;
|
|
33
|
+
/**
|
|
34
|
+
* Streamable state — holds per-view and broadcast bookkeeping. Lazily
|
|
35
|
+
* allocated when the stream is attached to a Root (or when the user
|
|
36
|
+
* touches `maxPerTick`). `undefined` on detached streams so
|
|
37
|
+
* construction is cheap.
|
|
38
|
+
*/
|
|
39
|
+
_stream?: StreamableState;
|
|
40
|
+
/** Max element ADDs emitted per encode tick (per view, or broadcast). */
|
|
41
|
+
get maxPerTick(): number;
|
|
42
|
+
set maxPerTick(n: number);
|
|
43
|
+
/**
|
|
44
|
+
* Per-view priority callback. Initialized from the schema declaration
|
|
45
|
+
* (`.priority(fn)` or `@type({ stream, priority })`); assigning here
|
|
46
|
+
* overrides the class-level default for this instance. Only fires
|
|
47
|
+
* during `encodeView` — broadcast mode drains FIFO.
|
|
48
|
+
*/
|
|
49
|
+
get priority(): ((view: any, element: V) => number) | undefined;
|
|
50
|
+
set priority(fn: ((view: any, element: V) => number) | undefined);
|
|
51
|
+
/**
|
|
52
|
+
* Brand used by Root / StateView to detect stream trees without
|
|
53
|
+
* importing this class (avoids circular deps). The `isStreamCollection`
|
|
54
|
+
* ChangeTree flag (set via `inheritedFlags`) is the preferred runtime
|
|
55
|
+
* check — this brand is kept for back-compat.
|
|
56
|
+
*/
|
|
57
|
+
static readonly $isStream: true;
|
|
58
|
+
static [$encoder]: import("../../encoder/EncodeOperation.js").EncodeOperation<any>;
|
|
59
|
+
static [$decoder]: import("../../decoder/DecodeOperation.js").DecodeOperation<any>;
|
|
60
|
+
/** Integer tag read by `decodeKeyValueOperation` — see `CollectionKind`. */
|
|
61
|
+
static readonly COLLECTION_KIND: 5;
|
|
62
|
+
/**
|
|
63
|
+
* Element-level visibility. Identical to SetSchema's filter: stream
|
|
64
|
+
* elements are always per-view, the filter just defers to the view's
|
|
65
|
+
* per-tree visibility bitmap.
|
|
66
|
+
*/
|
|
67
|
+
static [$filter](ref: StreamSchema, index: number, view: StateView): boolean;
|
|
68
|
+
static is(type: any): boolean;
|
|
69
|
+
constructor();
|
|
70
|
+
/**
|
|
71
|
+
* Decoder-side factory. Skips the tracking `ChangeTree` allocation;
|
|
72
|
+
* `Object.create` also bypasses the class-field initializers, so we
|
|
73
|
+
* replicate the minimum slot init here. Must stay in sync with the
|
|
74
|
+
* class-field declarations above.
|
|
75
|
+
*/
|
|
76
|
+
static initializeForDecoder<V = any>(): StreamSchema<V>;
|
|
77
|
+
/**
|
|
78
|
+
* Append an element to the stream. Returns the assigned position,
|
|
79
|
+
* or -1 if the element was already in the stream.
|
|
80
|
+
*/
|
|
81
|
+
add(value: V): number;
|
|
82
|
+
/**
|
|
83
|
+
* Remove an element by reference. If the element was pending (never sent
|
|
84
|
+
* to a view), the pending entry is dropped silently. If already sent,
|
|
85
|
+
* a DELETE op is forced on next `encodeView` for that view.
|
|
86
|
+
*/
|
|
87
|
+
remove(value: V): boolean;
|
|
88
|
+
has(value: V): boolean;
|
|
89
|
+
/** Remove every element; queue DELETE wire ops for already-sent items. */
|
|
90
|
+
clear(): void;
|
|
91
|
+
forEach(callback: (value: V, index: number, collection: StreamSchema<V>) => void): void;
|
|
92
|
+
values(): IterableIterator<V>;
|
|
93
|
+
/**
|
|
94
|
+
* Iterate `[position, value]` pairs in insertion order. Used by
|
|
95
|
+
* `setParent` recursion when the stream is reassigned to a new parent.
|
|
96
|
+
*/
|
|
97
|
+
entries(): IterableIterator<[number, V]>;
|
|
98
|
+
[Symbol.iterator](): IterableIterator<V>;
|
|
99
|
+
/** Live element count. */
|
|
100
|
+
get size(): number;
|
|
101
|
+
/** Alias for `size`. */
|
|
102
|
+
get length(): number;
|
|
103
|
+
protected setIndex(_index: number, _key: number): void;
|
|
104
|
+
protected getIndex(index: number): number;
|
|
105
|
+
[$getByIndex](index: number): V;
|
|
106
|
+
[$deleteByIndex](index: number): void;
|
|
107
|
+
protected [$onEncodeEnd](): void;
|
|
108
|
+
toArray(): V[];
|
|
109
|
+
toJSON(): any[];
|
|
110
|
+
clone(isDecoding?: boolean): StreamSchema<V>;
|
|
111
|
+
_dropView(viewId: number): void;
|
|
112
|
+
/** Called by Root.remove when the stream's refcount hits zero. */
|
|
113
|
+
_unregister(): void;
|
|
114
|
+
}
|
package/build/types/symbols.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
export declare const $refId
|
|
1
|
+
export declare const $refId: unique symbol;
|
|
2
2
|
export declare const $track = "~track";
|
|
3
3
|
export declare const $encoder = "~encoder";
|
|
4
4
|
export declare const $decoder = "~decoder";
|
|
@@ -6,14 +6,24 @@ export declare const $filter = "~filter";
|
|
|
6
6
|
export declare const $getByIndex = "~getByIndex";
|
|
7
7
|
export declare const $deleteByIndex = "~deleteByIndex";
|
|
8
8
|
/**
|
|
9
|
-
* Used to hold ChangeTree instances whitin the structures
|
|
9
|
+
* Used to hold ChangeTree instances whitin the structures.
|
|
10
|
+
*
|
|
11
|
+
* Real JS Symbol — see the `$values` comment for rationale.
|
|
10
12
|
*/
|
|
11
|
-
export declare const $changes
|
|
13
|
+
export declare const $changes: unique symbol;
|
|
12
14
|
/**
|
|
13
15
|
* Used to keep track of the type of the child elements of a collection
|
|
14
|
-
* (MapSchema, ArraySchema, etc.)
|
|
16
|
+
* (MapSchema, ArraySchema, etc.). Real Symbol — same rationale as $values.
|
|
15
17
|
*/
|
|
16
|
-
export declare const $childType
|
|
18
|
+
export declare const $childType: unique symbol;
|
|
19
|
+
/**
|
|
20
|
+
* Self-reference an instance sets on `this` so its own methods can recover
|
|
21
|
+
* the underlying object even when `this` is a Proxy wrapper. Used by
|
|
22
|
+
* ArraySchema (whose public API is a Proxy) to grab the underlying instance
|
|
23
|
+
* once at the top of hot methods and then access fields directly without
|
|
24
|
+
* paying the Proxy.get cost on every read.
|
|
25
|
+
*/
|
|
26
|
+
export declare const $proxyTarget: unique symbol;
|
|
17
27
|
/**
|
|
18
28
|
* Optional "discard" method for custom types (ArraySchema)
|
|
19
29
|
* (Discards changes for next serialization)
|
|
@@ -23,11 +33,44 @@ export declare const $onEncodeEnd = "~onEncodeEnd";
|
|
|
23
33
|
* When decoding, this method is called after the instance is fully decoded
|
|
24
34
|
*/
|
|
25
35
|
export declare const $onDecodeEnd = "~onDecodeEnd";
|
|
36
|
+
/**
|
|
37
|
+
* Per-instance dense array holding field values by index.
|
|
38
|
+
* Replaces per-field _fieldName shadow properties.
|
|
39
|
+
*
|
|
40
|
+
* Real JS Symbol (not "~"-prefixed string) so plain assignment is safe —
|
|
41
|
+
* symbols are non-enumerable to Object.keys / JSON.stringify / for-in,
|
|
42
|
+
* which means we can drop Object.defineProperty(...{ enumerable: false })
|
|
43
|
+
* and avoid the slow-path / dictionary-mode hazards that come with it.
|
|
44
|
+
*/
|
|
45
|
+
export declare const $values: unique symbol;
|
|
46
|
+
/**
|
|
47
|
+
* Brand for FieldBuilder instances so schema() can detect them.
|
|
48
|
+
*/
|
|
49
|
+
export declare const $builder = "~builder";
|
|
26
50
|
/**
|
|
27
51
|
* Metadata
|
|
28
52
|
*/
|
|
29
53
|
export declare const $descriptors = "~descriptors";
|
|
54
|
+
/**
|
|
55
|
+
* Per-class bitmask: bit i set iff field i carries a @view tag.
|
|
56
|
+
* Lazily computed from $viewFieldIndexes on first encode pass.
|
|
57
|
+
* Skips the per-field metadata[i].tag property chase in the hot encode loop.
|
|
58
|
+
*/
|
|
59
|
+
export declare const $filterBitmask = "~__filterBitmask";
|
|
60
|
+
/**
|
|
61
|
+
* Cached per-class encode descriptor: bundles encoder fn, filter fn,
|
|
62
|
+
* metadata, isSchema flag, and filterBitmask into one object stashed on
|
|
63
|
+
* the constructor. Replaces 5 separate per-tree property chases /
|
|
64
|
+
* function calls in the encode loop with a single property load.
|
|
65
|
+
*/
|
|
66
|
+
export declare const $encodeDescriptor = "~__encodeDescriptor";
|
|
67
|
+
export declare const $encoders = "~encoders";
|
|
30
68
|
export declare const $numFields = "~__numFields";
|
|
31
69
|
export declare const $refTypeFieldIndexes = "~__refTypeFieldIndexes";
|
|
32
70
|
export declare const $viewFieldIndexes = "~__viewFieldIndexes";
|
|
33
71
|
export declare const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
72
|
+
export declare const $unreliableFieldIndexes = "~__unreliableFieldIndexes";
|
|
73
|
+
export declare const $transientFieldIndexes = "~__transientFieldIndexes";
|
|
74
|
+
export declare const $staticFieldIndexes = "~__staticFieldIndexes";
|
|
75
|
+
export declare const $streamFieldIndexes = "~__streamFieldIndexes";
|
|
76
|
+
export declare const $streamPriorities = "~__streamPriorities";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@colyseus/schema",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "5.0.1",
|
|
4
4
|
"description": "Binary state serializer with delta encoding for games",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,8 +10,8 @@
|
|
|
10
10
|
"scripts": {
|
|
11
11
|
"build": "tsc -p tsconfig.build.json && rollup -c rollup.config.mjs",
|
|
12
12
|
"watch": "tsc -p tsconfig.build.json -w",
|
|
13
|
+
"typecheck": "tsc -p tsconfig.build.json --noEmit",
|
|
13
14
|
"test": "tsx --tsconfig tsconfig.test.json ./node_modules/.bin/mocha test/*.test.ts test/**/*.test.ts",
|
|
14
|
-
"typecheck": "tsc -p tsconfig.test.json",
|
|
15
15
|
"coverage": "c8 npm run test",
|
|
16
16
|
"generate-test-1": "bin/schema-codegen test-external/PrimitiveTypes.ts --namespace SchemaTest.PrimitiveTypes --output ../colyseus-unity-sdk/Assets/Colyseus/Tests/Editor/ColyseusTests/Schema/PrimitiveTypes",
|
|
17
17
|
"generate-test-2": "bin/schema-codegen test-external/ChildSchemaTypes.ts --namespace SchemaTest.ChildSchemaTypes --output ../colyseus-unity-sdk/Assets/Colyseus/Tests/Editor/ColyseusTests/Schema/ChildSchemaTypes",
|
|
@@ -42,6 +42,12 @@
|
|
|
42
42
|
"import": "./build/index.mjs",
|
|
43
43
|
"require": "./build/index.cjs",
|
|
44
44
|
"types": "./build/index.d.ts"
|
|
45
|
+
},
|
|
46
|
+
"./input": {
|
|
47
|
+
"browser": "./build/input/index.mjs",
|
|
48
|
+
"import": "./build/input/index.mjs",
|
|
49
|
+
"require": "./build/input/index.cjs",
|
|
50
|
+
"types": "./build/input/index.d.ts"
|
|
45
51
|
}
|
|
46
52
|
},
|
|
47
53
|
"repository": {
|
|
@@ -82,7 +88,7 @@
|
|
|
82
88
|
"typescript": "^5.9.3"
|
|
83
89
|
},
|
|
84
90
|
"peerDependencies": {
|
|
85
|
-
"typescript": "^5.
|
|
91
|
+
"typescript": "^5.9.3"
|
|
86
92
|
},
|
|
87
93
|
"c8": {
|
|
88
94
|
"include": [
|
package/src/Metadata.ts
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
1
|
import { DefinitionType, getPropertyDescriptor } from "./annotations.js";
|
|
2
2
|
import { Schema } from "./Schema.js";
|
|
3
|
-
import { getType, registeredTypes } from "./types/registry.js";
|
|
4
|
-
import { $decoder, $descriptors, $encoder, $fieldIndexesByViewTag, $numFields, $refTypeFieldIndexes, $track, $viewFieldIndexes } from "./types/symbols.js";
|
|
3
|
+
import { getType, registeredTypes, TypeDefinition } from "./types/registry.js";
|
|
4
|
+
import { $decoder, $descriptors, $encoder, $encoders, $fieldIndexesByViewTag, $numFields, $refTypeFieldIndexes, $staticFieldIndexes, $streamFieldIndexes, $streamPriorities, $track, $transientFieldIndexes, $unreliableFieldIndexes, $viewFieldIndexes } from "./types/symbols.js";
|
|
5
|
+
import { ARRAY_STREAM_NOT_SUPPORTED } from "./encoder/streaming.js";
|
|
6
|
+
import { encode } from "./encoding/encode.js";
|
|
5
7
|
import { TypeContext } from "./types/TypeContext.js";
|
|
6
8
|
|
|
7
9
|
export type MetadataField = {
|
|
@@ -10,7 +12,12 @@ export type MetadataField = {
|
|
|
10
12
|
index: number,
|
|
11
13
|
tag?: number,
|
|
12
14
|
unreliable?: boolean,
|
|
15
|
+
transient?: boolean,
|
|
13
16
|
deprecated?: boolean,
|
|
17
|
+
owned?: boolean,
|
|
18
|
+
static?: boolean,
|
|
19
|
+
stream?: boolean,
|
|
20
|
+
optional?: boolean,
|
|
14
21
|
};
|
|
15
22
|
|
|
16
23
|
export type Metadata =
|
|
@@ -18,10 +25,31 @@ export type Metadata =
|
|
|
18
25
|
{ [$viewFieldIndexes]: number[]; } & // all field indexes with "view" tag
|
|
19
26
|
{ [$fieldIndexesByViewTag]: {[tag: number]: number[]}; } & // field indexes by "view" tag
|
|
20
27
|
{ [$refTypeFieldIndexes]: number[]; } & // all field indexes containing Ref types (Schema, ArraySchema, MapSchema, etc)
|
|
28
|
+
{ [$unreliableFieldIndexes]: number[]; } & // all field indexes tagged with @unreliable
|
|
29
|
+
{ [$transientFieldIndexes]: number[]; } & // all field indexes tagged with @transient (not persisted to snapshots)
|
|
30
|
+
{ [$staticFieldIndexes]: number[]; } & // all field indexes tagged with @static (not tracked after assignment)
|
|
31
|
+
{ [$streamFieldIndexes]: number[]; } & // all field indexes holding a t.stream(...) collection
|
|
32
|
+
{ [$streamPriorities]: { [field: number]: (view: any, element: any) => number }; } & // per-stream-field priority callback declared at schema definition time
|
|
33
|
+
{ [$encoders]: Array<(bytes: Uint8Array, value: any, it: any) => void>; } & // pre-computed encoder fn per primitive field
|
|
21
34
|
{ [field: number]: MetadataField; } & // index => field name
|
|
22
35
|
{ [field: string]: number; } & // field name => field metadata
|
|
23
36
|
{ [$descriptors]: { [field: string]: PropertyDescriptor } } // property descriptors
|
|
24
37
|
|
|
38
|
+
/**
|
|
39
|
+
* Given a normalized field type (`"number"`, `{ map: Foo }`, `Player`,
|
|
40
|
+
* etc.), split into the collection-type descriptor (`{ constructor:
|
|
41
|
+
* MapSchema, ... }`) if applicable and the inner child type. Shared by
|
|
42
|
+
* `@type()` decoration and `Metadata.setFields` — both need to build a
|
|
43
|
+
* property accessor that knows whether the slot holds a collection.
|
|
44
|
+
*/
|
|
45
|
+
export function resolveFieldType(type: any): { complexTypeKlass: TypeDefinition | false, childType: any } {
|
|
46
|
+
const complexTypeKlass = typeof (Object.keys(type)[0]) === "string" && getType(Object.keys(type)[0]);
|
|
47
|
+
return {
|
|
48
|
+
complexTypeKlass,
|
|
49
|
+
childType: complexTypeKlass ? Object.values(type)[0] : type,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
25
53
|
export function getNormalizedType(type: any): DefinitionType {
|
|
26
54
|
if (Array.isArray(type)) {
|
|
27
55
|
return { array: getNormalizedType(type[0]) };
|
|
@@ -91,16 +119,11 @@ export const Metadata = {
|
|
|
91
119
|
});
|
|
92
120
|
|
|
93
121
|
if (descriptor) {
|
|
94
|
-
// for
|
|
122
|
+
// Accessor descriptor for the public field name.
|
|
123
|
+
// Installed on the prototype at class-definition time.
|
|
95
124
|
metadata[$descriptors][name] = descriptor;
|
|
96
|
-
metadata[$descriptors][`_${name}`] = {
|
|
97
|
-
value: undefined,
|
|
98
|
-
writable: true,
|
|
99
|
-
enumerable: false,
|
|
100
|
-
configurable: true,
|
|
101
|
-
};
|
|
102
125
|
} else {
|
|
103
|
-
//
|
|
126
|
+
// For decoder: simple writable slot, also on prototype.
|
|
104
127
|
metadata[$descriptors][name] = {
|
|
105
128
|
value: undefined,
|
|
106
129
|
writable: true,
|
|
@@ -134,6 +157,38 @@ export const Metadata = {
|
|
|
134
157
|
}
|
|
135
158
|
metadata[$refTypeFieldIndexes].push(index);
|
|
136
159
|
}
|
|
160
|
+
|
|
161
|
+
// `{ stream: ... }` collections are always view-scoped (priority-
|
|
162
|
+
// batched emit). Auto-flag here so both `@type({stream: ...})` and
|
|
163
|
+
// the `t.stream(...)` builder route into the same filter / encoder
|
|
164
|
+
// dispatch without the caller needing an extra setStream() call.
|
|
165
|
+
const t = metadata[index].type;
|
|
166
|
+
if (t && typeof t === "object" && (t as any)["stream"] !== undefined) {
|
|
167
|
+
// Reject the combined shorthand `@type({ array: X, stream:
|
|
168
|
+
// true })` at decoration time — same diagnostic as the
|
|
169
|
+
// builder chainable throws for `t.array(X).stream()`.
|
|
170
|
+
if ((t as any).array !== undefined) {
|
|
171
|
+
throw new Error(ARRAY_STREAM_NOT_SUPPORTED);
|
|
172
|
+
}
|
|
173
|
+
metadata[index].stream = true;
|
|
174
|
+
if (!metadata[$streamFieldIndexes]) {
|
|
175
|
+
Object.defineProperty(metadata, $streamFieldIndexes, {
|
|
176
|
+
value: [],
|
|
177
|
+
enumerable: false,
|
|
178
|
+
configurable: true,
|
|
179
|
+
writable: true,
|
|
180
|
+
});
|
|
181
|
+
}
|
|
182
|
+
if (!metadata[$streamFieldIndexes].includes(index)) {
|
|
183
|
+
metadata[$streamFieldIndexes].push(index);
|
|
184
|
+
}
|
|
185
|
+
// Pick up the declaration-scope priority callback if present in
|
|
186
|
+
// the `@type({ stream: X, priority: fn })` shorthand.
|
|
187
|
+
const priorityFn = (type as any)?.priority;
|
|
188
|
+
if (typeof priorityFn === "function") {
|
|
189
|
+
Metadata.setStreamPriority(metadata as any, name, priorityFn);
|
|
190
|
+
}
|
|
191
|
+
}
|
|
137
192
|
},
|
|
138
193
|
|
|
139
194
|
setTag(metadata: Metadata, fieldName: string, tag: number) {
|
|
@@ -168,6 +223,112 @@ export const Metadata = {
|
|
|
168
223
|
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
169
224
|
},
|
|
170
225
|
|
|
226
|
+
setUnreliable(metadata: Metadata, fieldName: string) {
|
|
227
|
+
const index = metadata[fieldName];
|
|
228
|
+
const fieldType = metadata[index].type;
|
|
229
|
+
// `@unreliable` is only valid on primitive fields. Ref-type fields
|
|
230
|
+
// (Schema sub-classes, MapSchema, ArraySchema, SetSchema,
|
|
231
|
+
// CollectionSchema) carry refIds whose ADD/DELETE must arrive
|
|
232
|
+
// on the reliable channel — otherwise a dropped unreliable packet
|
|
233
|
+
// would leave the decoder unable to interpret subsequent packets
|
|
234
|
+
// referencing the orphan refId. Primitive types are encoded as
|
|
235
|
+
// strings ("number", "string", "int32", ...); anything else is a
|
|
236
|
+
// ref. Reject at decoration time so the bug surfaces in dev, not
|
|
237
|
+
// under packet loss in prod.
|
|
238
|
+
if (typeof fieldType !== "string") {
|
|
239
|
+
throw new Error(
|
|
240
|
+
`@unreliable cannot be applied to ref-type field "${fieldName}". ` +
|
|
241
|
+
`For ref-type fields, mark each primitive sub-field with @unreliable instead. ` +
|
|
242
|
+
`See README "Limitations and best practices".`
|
|
243
|
+
);
|
|
244
|
+
}
|
|
245
|
+
metadata[index].unreliable = true;
|
|
246
|
+
|
|
247
|
+
if (!metadata[$unreliableFieldIndexes]) {
|
|
248
|
+
Object.defineProperty(metadata, $unreliableFieldIndexes, {
|
|
249
|
+
value: [],
|
|
250
|
+
enumerable: false,
|
|
251
|
+
configurable: true,
|
|
252
|
+
writable: true,
|
|
253
|
+
});
|
|
254
|
+
}
|
|
255
|
+
metadata[$unreliableFieldIndexes].push(index);
|
|
256
|
+
},
|
|
257
|
+
|
|
258
|
+
setTransient(metadata: Metadata, fieldName: string) {
|
|
259
|
+
const index = metadata[fieldName];
|
|
260
|
+
metadata[index].transient = true;
|
|
261
|
+
|
|
262
|
+
if (!metadata[$transientFieldIndexes]) {
|
|
263
|
+
Object.defineProperty(metadata, $transientFieldIndexes, {
|
|
264
|
+
value: [],
|
|
265
|
+
enumerable: false,
|
|
266
|
+
configurable: true,
|
|
267
|
+
writable: true,
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
metadata[$transientFieldIndexes].push(index);
|
|
271
|
+
},
|
|
272
|
+
|
|
273
|
+
setStatic(metadata: Metadata, fieldName: string) {
|
|
274
|
+
const index = metadata[fieldName];
|
|
275
|
+
metadata[index].static = true;
|
|
276
|
+
|
|
277
|
+
if (!metadata[$staticFieldIndexes]) {
|
|
278
|
+
Object.defineProperty(metadata, $staticFieldIndexes, {
|
|
279
|
+
value: [],
|
|
280
|
+
enumerable: false,
|
|
281
|
+
configurable: true,
|
|
282
|
+
writable: true,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
metadata[$staticFieldIndexes].push(index);
|
|
286
|
+
},
|
|
287
|
+
|
|
288
|
+
setStream(metadata: Metadata, fieldName: string) {
|
|
289
|
+
const index = metadata[fieldName];
|
|
290
|
+
metadata[index].stream = true;
|
|
291
|
+
|
|
292
|
+
if (!metadata[$streamFieldIndexes]) {
|
|
293
|
+
Object.defineProperty(metadata, $streamFieldIndexes, {
|
|
294
|
+
value: [],
|
|
295
|
+
enumerable: false,
|
|
296
|
+
configurable: true,
|
|
297
|
+
writable: true,
|
|
298
|
+
});
|
|
299
|
+
}
|
|
300
|
+
metadata[$streamFieldIndexes].push(index);
|
|
301
|
+
},
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* Attach a declaration-scope priority callback to a stream field.
|
|
305
|
+
* Called at schema definition time (via `t.stream(X).priority(fn)` or
|
|
306
|
+
* `@type({ stream: X, priority: fn })`), looked up at stream-attach
|
|
307
|
+
* time to seed the instance's `_stream.priority` slot. The callback
|
|
308
|
+
* signature is `(view: StateView, element: V) => number` — only fires
|
|
309
|
+
* during `encodeView`, broadcast mode emits FIFO regardless.
|
|
310
|
+
*/
|
|
311
|
+
setStreamPriority(
|
|
312
|
+
metadata: Metadata,
|
|
313
|
+
fieldName: string,
|
|
314
|
+
fn: (view: any, element: any) => number,
|
|
315
|
+
) {
|
|
316
|
+
const index = metadata[fieldName];
|
|
317
|
+
if (!metadata[$streamPriorities]) {
|
|
318
|
+
Object.defineProperty(metadata, $streamPriorities, {
|
|
319
|
+
value: {},
|
|
320
|
+
enumerable: false,
|
|
321
|
+
configurable: true,
|
|
322
|
+
writable: true,
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
metadata[$streamPriorities][index] = fn;
|
|
326
|
+
},
|
|
327
|
+
|
|
328
|
+
getStreamPriority(metadata: Metadata | undefined, index: number) {
|
|
329
|
+
return metadata?.[$streamPriorities]?.[index];
|
|
330
|
+
},
|
|
331
|
+
|
|
171
332
|
setFields<T extends { new (...args: any[]): InstanceType<T> } = any>(target: T, fields: { [field in keyof InstanceType<T>]?: DefinitionType }) {
|
|
172
333
|
// for inheritance support
|
|
173
334
|
const constructor = target.prototype.constructor;
|
|
@@ -192,24 +353,39 @@ export const Metadata = {
|
|
|
192
353
|
|
|
193
354
|
fieldIndex++;
|
|
194
355
|
|
|
356
|
+
// Pre-computed encoder function table: metadata[$encoders][fieldIndex] = encode.uint8 etc.
|
|
357
|
+
if (!metadata[$encoders]) {
|
|
358
|
+
Object.defineProperty(metadata, $encoders, {
|
|
359
|
+
value: parentMetadata?.[$encoders] ? [...parentMetadata[$encoders]] : [],
|
|
360
|
+
enumerable: false,
|
|
361
|
+
configurable: true,
|
|
362
|
+
writable: true,
|
|
363
|
+
});
|
|
364
|
+
}
|
|
365
|
+
|
|
195
366
|
for (const field in fields) {
|
|
196
367
|
const type = getNormalizedType(fields[field]);
|
|
197
368
|
|
|
198
|
-
|
|
199
|
-
const complexTypeKlass = typeof(Object.keys(type)[0]) === "string" && getType(Object.keys(type)[0]);
|
|
200
|
-
|
|
201
|
-
const childType = (complexTypeKlass)
|
|
202
|
-
? Object.values(type)[0]
|
|
203
|
-
: type;
|
|
369
|
+
const { complexTypeKlass, childType } = resolveFieldType(type);
|
|
204
370
|
|
|
205
371
|
Metadata.addField(
|
|
206
372
|
metadata,
|
|
207
373
|
fieldIndex,
|
|
208
374
|
field,
|
|
209
375
|
type,
|
|
210
|
-
getPropertyDescriptor(
|
|
376
|
+
getPropertyDescriptor(field, fieldIndex, childType, complexTypeKlass)
|
|
211
377
|
);
|
|
212
378
|
|
|
379
|
+
// Install accessor descriptor on the prototype (once per class field).
|
|
380
|
+
if (metadata[$descriptors][field]) {
|
|
381
|
+
Object.defineProperty(target.prototype, field, metadata[$descriptors][field]);
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
// Pre-compute encoder function for primitive types.
|
|
385
|
+
if (typeof type === "string") {
|
|
386
|
+
metadata[$encoders][fieldIndex] = (encode as any)[type];
|
|
387
|
+
}
|
|
388
|
+
|
|
213
389
|
fieldIndex++;
|
|
214
390
|
}
|
|
215
391
|
|
|
@@ -220,20 +396,6 @@ export const Metadata = {
|
|
|
220
396
|
return metadata[field].deprecated === true;
|
|
221
397
|
},
|
|
222
398
|
|
|
223
|
-
init(klass: any) {
|
|
224
|
-
//
|
|
225
|
-
// Used only to initialize an empty Schema (Encoder#constructor)
|
|
226
|
-
// TODO: remove/refactor this...
|
|
227
|
-
//
|
|
228
|
-
const metadata = {};
|
|
229
|
-
klass[Symbol.metadata] = metadata;
|
|
230
|
-
Object.defineProperty(metadata, $numFields, {
|
|
231
|
-
value: 0,
|
|
232
|
-
enumerable: false,
|
|
233
|
-
configurable: true,
|
|
234
|
-
});
|
|
235
|
-
},
|
|
236
|
-
|
|
237
399
|
initialize(constructor: any) {
|
|
238
400
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
239
401
|
const parentMetadata: Metadata = parentClass[Symbol.metadata];
|
|
@@ -284,6 +446,46 @@ export const Metadata = {
|
|
|
284
446
|
});
|
|
285
447
|
}
|
|
286
448
|
|
|
449
|
+
// $unreliableFieldIndexes
|
|
450
|
+
if (parentMetadata[$unreliableFieldIndexes] !== undefined) {
|
|
451
|
+
Object.defineProperty(metadata, $unreliableFieldIndexes, {
|
|
452
|
+
value: [...parentMetadata[$unreliableFieldIndexes]],
|
|
453
|
+
enumerable: false,
|
|
454
|
+
configurable: true,
|
|
455
|
+
writable: true,
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// $transientFieldIndexes
|
|
460
|
+
if (parentMetadata[$transientFieldIndexes] !== undefined) {
|
|
461
|
+
Object.defineProperty(metadata, $transientFieldIndexes, {
|
|
462
|
+
value: [...parentMetadata[$transientFieldIndexes]],
|
|
463
|
+
enumerable: false,
|
|
464
|
+
configurable: true,
|
|
465
|
+
writable: true,
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// $staticFieldIndexes
|
|
470
|
+
if (parentMetadata[$staticFieldIndexes] !== undefined) {
|
|
471
|
+
Object.defineProperty(metadata, $staticFieldIndexes, {
|
|
472
|
+
value: [...parentMetadata[$staticFieldIndexes]],
|
|
473
|
+
enumerable: false,
|
|
474
|
+
configurable: true,
|
|
475
|
+
writable: true,
|
|
476
|
+
});
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// $streamFieldIndexes
|
|
480
|
+
if (parentMetadata[$streamFieldIndexes] !== undefined) {
|
|
481
|
+
Object.defineProperty(metadata, $streamFieldIndexes, {
|
|
482
|
+
value: [...parentMetadata[$streamFieldIndexes]],
|
|
483
|
+
enumerable: false,
|
|
484
|
+
configurable: true,
|
|
485
|
+
writable: true,
|
|
486
|
+
});
|
|
487
|
+
}
|
|
488
|
+
|
|
287
489
|
// $descriptors
|
|
288
490
|
Object.defineProperty(metadata, $descriptors, {
|
|
289
491
|
value: { ...parentMetadata[$descriptors] },
|
|
@@ -291,6 +493,16 @@ export const Metadata = {
|
|
|
291
493
|
configurable: true,
|
|
292
494
|
writable: true,
|
|
293
495
|
});
|
|
496
|
+
|
|
497
|
+
// $encoders
|
|
498
|
+
if (parentMetadata[$encoders] !== undefined) {
|
|
499
|
+
Object.defineProperty(metadata, $encoders, {
|
|
500
|
+
value: [...parentMetadata[$encoders]],
|
|
501
|
+
enumerable: false,
|
|
502
|
+
configurable: true,
|
|
503
|
+
writable: true,
|
|
504
|
+
});
|
|
505
|
+
}
|
|
294
506
|
}
|
|
295
507
|
}
|
|
296
508
|
|
|
@@ -321,5 +533,21 @@ export const Metadata = {
|
|
|
321
533
|
|
|
322
534
|
hasViewTagAtIndex(metadata: Metadata, index: number) {
|
|
323
535
|
return metadata?.[$viewFieldIndexes]?.includes(index);
|
|
536
|
+
},
|
|
537
|
+
|
|
538
|
+
hasUnreliableAtIndex(metadata: Metadata, index: number) {
|
|
539
|
+
return metadata?.[$unreliableFieldIndexes]?.includes(index);
|
|
540
|
+
},
|
|
541
|
+
|
|
542
|
+
hasTransientAtIndex(metadata: Metadata, index: number) {
|
|
543
|
+
return metadata?.[$transientFieldIndexes]?.includes(index);
|
|
544
|
+
},
|
|
545
|
+
|
|
546
|
+
hasStaticAtIndex(metadata: Metadata, index: number) {
|
|
547
|
+
return metadata?.[$staticFieldIndexes]?.includes(index);
|
|
548
|
+
},
|
|
549
|
+
|
|
550
|
+
hasStreamAtIndex(metadata: Metadata, index: number) {
|
|
551
|
+
return metadata?.[$streamFieldIndexes]?.includes(index);
|
|
324
552
|
}
|
|
325
553
|
}
|
package/src/Reflection.ts
CHANGED
|
@@ -5,6 +5,8 @@ import { Iterator } from "./encoding/decode.js";
|
|
|
5
5
|
import { Encoder } from "./encoder/Encoder.js";
|
|
6
6
|
import { Decoder } from "./decoder/Decoder.js";
|
|
7
7
|
import { Schema } from "./Schema.js";
|
|
8
|
+
import { t, FieldBuilder } from "./types/builder.js";
|
|
9
|
+
import { ArraySchema } from "./types/custom/ArraySchema.js";
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Static methods available on Reflection
|
|
@@ -33,25 +35,25 @@ interface ReflectionStatic {
|
|
|
33
35
|
* Reflection
|
|
34
36
|
*/
|
|
35
37
|
export const ReflectionField = schema({
|
|
36
|
-
name:
|
|
37
|
-
type:
|
|
38
|
-
referencedType:
|
|
39
|
-
})
|
|
38
|
+
name: t.string(),
|
|
39
|
+
type: t.string(),
|
|
40
|
+
referencedType: t.number(),
|
|
41
|
+
}, "ReflectionField");
|
|
40
42
|
export type ReflectionField = SchemaType<typeof ReflectionField>;
|
|
41
43
|
|
|
42
44
|
export const ReflectionType = schema({
|
|
43
|
-
id:
|
|
44
|
-
extendsId:
|
|
45
|
-
fields:
|
|
46
|
-
})
|
|
45
|
+
id: t.number(),
|
|
46
|
+
extendsId: t.number(),
|
|
47
|
+
fields: t.array(ReflectionField),
|
|
48
|
+
}, "ReflectionType");
|
|
47
49
|
export type ReflectionType = SchemaType<typeof ReflectionType>;
|
|
48
50
|
|
|
49
51
|
export const Reflection = schema({
|
|
50
|
-
types:
|
|
51
|
-
rootType:
|
|
52
|
-
}) as ReturnType<typeof schema<{
|
|
53
|
-
types:
|
|
54
|
-
rootType:
|
|
52
|
+
types: t.array(ReflectionType),
|
|
53
|
+
rootType: t.number(),
|
|
54
|
+
}, "Reflection") as ReturnType<typeof schema<{
|
|
55
|
+
types: FieldBuilder<ArraySchema<ReflectionType>, true, false>;
|
|
56
|
+
rootType: FieldBuilder<number, false, false>;
|
|
55
57
|
}>> & ReflectionStatic;
|
|
56
58
|
|
|
57
59
|
export type Reflection = SchemaType<typeof Reflection>;
|