@colyseus/schema 5.0.5 → 5.0.8
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/build/Reflection.d.ts +53 -0
- package/build/Schema.d.ts +26 -1
- package/build/annotations.d.ts +8 -1
- package/build/codegen/cli.cjs +74 -64
- package/build/codegen/cli.cjs.map +1 -1
- package/build/codegen/types.d.ts +0 -1
- package/build/decoder/Decoder.d.ts +28 -0
- package/build/decoder/Resync.d.ts +61 -0
- package/build/encoder/ChangeTree.d.ts +15 -0
- package/build/encoder/Encoder.d.ts +13 -0
- package/build/encoder/Pool.d.ts +62 -0
- package/build/encoder/Root.d.ts +5 -4
- package/build/encoder/StateView.d.ts +12 -3
- package/build/index.cjs +1192 -284
- package/build/index.cjs.map +1 -1
- package/build/index.d.ts +4 -1
- package/build/index.js +1192 -284
- package/build/index.mjs +1189 -285
- package/build/index.mjs.map +1 -1
- package/build/input/InputDecoder.d.ts +9 -4
- package/build/input/InputEncoder.d.ts +44 -53
- package/build/input/index.cjs +102 -7321
- package/build/input/index.cjs.map +1 -1
- package/build/input/index.mjs +97 -7316
- package/build/input/index.mjs.map +1 -1
- package/build/types/HelperTypes.d.ts +25 -0
- package/build/types/builder.d.ts +57 -4
- package/build/types/custom/ArraySchema.d.ts +11 -1
- package/build/types/custom/CollectionSchema.d.ts +9 -1
- package/build/types/custom/MapSchema.d.ts +9 -1
- package/build/types/custom/SetSchema.d.ts +9 -1
- package/build/types/custom/StreamSchema.d.ts +2 -1
- package/build/types/quantize.d.ts +102 -0
- package/build/types/symbols.d.ts +15 -0
- package/package.json +8 -1
- package/src/Metadata.ts +34 -9
- package/src/Reflection.ts +49 -11
- package/src/Schema.ts +64 -2
- package/src/annotations.ts +133 -51
- package/src/bench_churn.ts +121 -0
- package/src/codegen/languages/haxe.ts +0 -16
- package/src/codegen/parser.ts +29 -11
- package/src/codegen/types.ts +45 -63
- package/src/decoder/DecodeOperation.ts +46 -4
- package/src/decoder/Decoder.ts +48 -0
- package/src/decoder/ReferenceTracker.ts +9 -5
- package/src/decoder/Resync.ts +170 -0
- package/src/encoder/ChangeTree.ts +59 -0
- package/src/encoder/Encoder.ts +53 -12
- package/src/encoder/Pool.ts +90 -0
- package/src/encoder/Root.ts +22 -32
- package/src/encoder/StateView.ts +101 -50
- package/src/encoder/changeTree/liveIteration.ts +4 -0
- package/src/encoder/changeTree/treeAttachment.ts +28 -24
- package/src/index.ts +12 -2
- package/src/input/InputDecoder.ts +11 -5
- package/src/input/InputEncoder.ts +85 -126
- package/src/types/HelperTypes.ts +30 -0
- package/src/types/TypeContext.ts +7 -0
- package/src/types/builder.ts +62 -5
- package/src/types/custom/ArraySchema.ts +70 -12
- package/src/types/custom/CollectionSchema.ts +36 -1
- package/src/types/custom/MapSchema.ts +50 -1
- package/src/types/custom/SetSchema.ts +36 -1
- package/src/types/custom/StreamSchema.ts +7 -0
- package/src/types/quantize.ts +173 -0
- package/src/types/symbols.ts +16 -0
- package/build/encoder/RefIdAllocator.d.ts +0 -35
- package/src/encoder/RefIdAllocator.ts +0 -68
package/build/codegen/types.d.ts
CHANGED
|
@@ -11,9 +11,37 @@ export declare class Decoder<T extends IRef = any> {
|
|
|
11
11
|
root: ReferenceTracker;
|
|
12
12
|
currentRefId: number;
|
|
13
13
|
triggerChanges?: (allChanges: DataChange[]) => void;
|
|
14
|
+
/**
|
|
15
|
+
* @internal Non-null only while a `decodeResync()` walk is in progress:
|
|
16
|
+
* collection refId → entry identities the payload visited (map string
|
|
17
|
+
* keys; array/set/collection/stream indexes). Written by the collection
|
|
18
|
+
* DecodeOperation functions, read by the post-decode sweep.
|
|
19
|
+
*/
|
|
20
|
+
resyncVisited: Map<number, Set<number | string>> | null;
|
|
21
|
+
/**
|
|
22
|
+
* @internal Set when a structure had to be skipped during a resync
|
|
23
|
+
* decode — visited data is incomplete, so the sweep must not delete.
|
|
24
|
+
*/
|
|
25
|
+
resyncDamaged: boolean;
|
|
14
26
|
constructor(root: T, context?: TypeContext);
|
|
15
27
|
protected setState(root: T): void;
|
|
16
28
|
decode(bytes: Uint8Array, it?: Iterator, ref?: IRef): DataChange<any, string>[];
|
|
29
|
+
/**
|
|
30
|
+
* Full-snapshot reconciliation ("resync") decode.
|
|
31
|
+
*
|
|
32
|
+
* Behaves exactly like {@link decode}, plus: every collection entry the
|
|
33
|
+
* payload does NOT mention is removed through the regular DELETE path —
|
|
34
|
+
* `onRemove` callbacks fire with the real previous value and released
|
|
35
|
+
* refs are garbage-collected. Use it to apply a rejoin/reconnect full
|
|
36
|
+
* state over an existing decoded tree: DELETEs that happened while the
|
|
37
|
+
* client was off the wire are reconciled as if they had been received,
|
|
38
|
+
* while surviving entries keep their instance identity and callbacks.
|
|
39
|
+
*
|
|
40
|
+
* ONLY valid for full-snapshot payloads (`encodeAll` / `encodeAllView`
|
|
41
|
+
* output). Calling it on an incremental patch would prune everything
|
|
42
|
+
* the patch doesn't touch.
|
|
43
|
+
*/
|
|
44
|
+
decodeResync(bytes: Uint8Array, it?: Iterator): DataChange<any, string>[];
|
|
17
45
|
skipCurrentStructure(bytes: Uint8Array, it: Iterator, totalBytes: number): void;
|
|
18
46
|
getInstanceType(bytes: Uint8Array, it: Iterator, defaultType: typeof Schema): typeof Schema;
|
|
19
47
|
createInstanceOfType(type: typeof Schema): Schema;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { OPERATION } from "../encoding/spec.js";
|
|
2
|
+
import type { Decoder } from "./Decoder.js";
|
|
3
|
+
import type { DataChange } from "./DecodeOperation.js";
|
|
4
|
+
/**
|
|
5
|
+
* Resync ("full-snapshot reconciliation") support for {@link Decoder.decodeResync}.
|
|
6
|
+
*
|
|
7
|
+
* A rejoin snapshot is authoritative for everything it contains — but the
|
|
8
|
+
* plain decode path is additive: entries deleted (or hidden by a view)
|
|
9
|
+
* while the client was off the wire survive as ghosts. This module owns the
|
|
10
|
+
* generic reconciliation algorithm:
|
|
11
|
+
*
|
|
12
|
+
* - during the decode walk, the collection DecodeOperation functions report
|
|
13
|
+
* every entry the payload touches ({@link resyncRecordVisit}) and every
|
|
14
|
+
* collection that appears at all ({@link resyncMarkPresent});
|
|
15
|
+
* - after the walk, {@link resyncSweep} removes whatever was never reported,
|
|
16
|
+
* through the same DELETE bookkeeping the regular decode path uses
|
|
17
|
+
* (DataChange DELETE → onRemove; removeRef → GC).
|
|
18
|
+
*
|
|
19
|
+
* Storage-specific pruning (journal upkeep, array compaction, stream
|
|
20
|
+
* exemption) lives on each collection class as `[$resyncPrune]` — declared
|
|
21
|
+
* on the `Collection` interface, so every collection kind must state its
|
|
22
|
+
* own sweep semantics.
|
|
23
|
+
*
|
|
24
|
+
* All entry points are guarded by `decoder.resyncVisited !== null` at the
|
|
25
|
+
* call sites — the normal decode path never pays for any of this.
|
|
26
|
+
*/
|
|
27
|
+
/**
|
|
28
|
+
* Record that the current structure's entry at `identity` (map string key /
|
|
29
|
+
* element index) appeared in the payload — even when its value is unchanged
|
|
30
|
+
* (`allChanges` cannot serve as this record: its pushes are guarded by
|
|
31
|
+
* `previousValue !== value`, so unchanged entries would look unvisited).
|
|
32
|
+
*
|
|
33
|
+
* Also releases a replaced occupant: full-sync emits plain ADD (never
|
|
34
|
+
* DELETE_AND_ADD), so an entry whose instance changed while this client was
|
|
35
|
+
* off the wire would otherwise leak its previous ref (no onRemove, never
|
|
36
|
+
* GC'd). This release is correct ONLY under a full snapshot — a live patch's
|
|
37
|
+
* plain ADD over a different instance can be a positional rewrite (array
|
|
38
|
+
* shift/unshift) where the occupant *moved* and is still alive; a snapshot
|
|
39
|
+
* re-adds moved instances elsewhere, so the refcounts balance.
|
|
40
|
+
*/
|
|
41
|
+
export declare function resyncTouchEntry(decoder: Decoder, ref: any, operation: OPERATION, identity: number | string, previousValue: any, value: any, allChanges: DataChange[] | null): void;
|
|
42
|
+
/**
|
|
43
|
+
* Mark a collection as present in the payload — even with zero entries.
|
|
44
|
+
* The sweep only prunes collections reported here: absence means "not part
|
|
45
|
+
* of full-sync" (@transient, view-invisible), where pruning would destroy
|
|
46
|
+
* live data. Reflected clients have no @transient metadata, so payload
|
|
47
|
+
* presence is the only reliable signal.
|
|
48
|
+
*/
|
|
49
|
+
export declare function resyncMarkPresent(decoder: Decoder, refId: number): void;
|
|
50
|
+
/**
|
|
51
|
+
* Post-decode phase of {@link Decoder.decodeResync}: remove every collection
|
|
52
|
+
* entry the snapshot did not visit.
|
|
53
|
+
*
|
|
54
|
+
* Walks the tree from the root — NOT `root.refs` — for three reasons:
|
|
55
|
+
* `@transient` fields are never part of a snapshot and must be left alone;
|
|
56
|
+
* entries of subtrees removed by the sweep itself are left to the GC's
|
|
57
|
+
* transitive walk (sweeping them directly would double-decrement shared
|
|
58
|
+
* children); and collections the snapshot never mentions (emptied
|
|
59
|
+
* server-side) are still reachable and get pruned.
|
|
60
|
+
*/
|
|
61
|
+
export declare function resyncSweep(decoder: Decoder, allChanges: DataChange[] | null): void;
|
|
@@ -61,6 +61,7 @@ export interface ParentChain {
|
|
|
61
61
|
export declare const IS_FILTERED = 1, IS_VISIBILITY_SHARED = 2, IS_NEW = 4;
|
|
62
62
|
export declare const IS_UNRELIABLE = 8, IS_TRANSIENT = 16, IS_STATIC = 32;
|
|
63
63
|
export declare const IS_STREAM_COLLECTION = 64;
|
|
64
|
+
export declare const NEEDS_RESTAGE = 128;
|
|
64
65
|
/**
|
|
65
66
|
* Flags a child inherits from its parent's own transitive state via
|
|
66
67
|
* `checkInheritedFlags`. Read as a bitwise mask so the inheritance step
|
|
@@ -154,6 +155,8 @@ export declare class ChangeTree<T extends Ref = any> implements ChangeRecorder {
|
|
|
154
155
|
set isStatic(v: boolean);
|
|
155
156
|
get isStreamCollection(): boolean;
|
|
156
157
|
set isStreamCollection(v: boolean);
|
|
158
|
+
get needsRestage(): boolean;
|
|
159
|
+
set needsRestage(v: boolean);
|
|
157
160
|
get hasFilteredFields(): boolean;
|
|
158
161
|
ensureUnreliableRecorder(): ChangeRecorder;
|
|
159
162
|
isFieldUnreliable(index: number): boolean;
|
|
@@ -174,6 +177,18 @@ export declare class ChangeTree<T extends Ref = any> implements ChangeRecorder {
|
|
|
174
177
|
size(): number;
|
|
175
178
|
has(): boolean;
|
|
176
179
|
reset(): void;
|
|
180
|
+
/**
|
|
181
|
+
* Full reset to construction defaults so the owning ref can be returned to
|
|
182
|
+
* a pool and reused for a different logical entity (see encoder/Pool.ts +
|
|
183
|
+
* Schema.reset). Unlike `reset()` / `endEncode()` (which only clear the
|
|
184
|
+
* dirty bucket for the next encode), this also drops parent links, queue
|
|
185
|
+
* nodes and per-view bitmaps, and re-arms IS_NEW.
|
|
186
|
+
*
|
|
187
|
+
* Precondition: the tree must already be detached from the encoder
|
|
188
|
+
* (`root === undefined`) — i.e. the ref was removed from its parent
|
|
189
|
+
* collection/field, which `Root.remove` does before this runs.
|
|
190
|
+
*/
|
|
191
|
+
recycle(): void;
|
|
177
192
|
shift(shiftIndex: number): void;
|
|
178
193
|
setRoot(root: Root): void;
|
|
179
194
|
setParent(parent: Ref, root?: Root, parentIndex?: number): void;
|
|
@@ -4,6 +4,17 @@ import type { Iterator } from "../encoding/decode.js";
|
|
|
4
4
|
import { Root } from "./Root.js";
|
|
5
5
|
import type { StateView } from "./StateView.js";
|
|
6
6
|
export declare class Encoder<T extends Schema = any> {
|
|
7
|
+
/**
|
|
8
|
+
* Per-encoder shared output buffer size. The encoder auto-grows on
|
|
9
|
+
* overflow and logs a one-time warning suggesting a higher value, so
|
|
10
|
+
* the default just needs to comfortably cover typical room state.
|
|
11
|
+
*
|
|
12
|
+
* Sized to fit ~100 items in a `MapSchema<{x,y,z}>` keyed by
|
|
13
|
+
* `nanoid(9)` (~4.5 KB worst-case full encode, float64-heavy) with
|
|
14
|
+
* ~3-4× headroom for surrounding state (player list, world refs,
|
|
15
|
+
* etc.). Raise per app via `Encoder.BUFFER_SIZE = N * 1024` before
|
|
16
|
+
* constructing any Encoder.
|
|
17
|
+
*/
|
|
7
18
|
static BUFFER_SIZE: number;
|
|
8
19
|
sharedBuffer: Uint8Array;
|
|
9
20
|
context: TypeContext;
|
|
@@ -38,6 +49,8 @@ export declare class Encoder<T extends Schema = any> {
|
|
|
38
49
|
private _resizeBuffer;
|
|
39
50
|
encodeAll(it?: Iterator, buffer?: Uint8Array): Uint8Array<ArrayBufferLike>;
|
|
40
51
|
encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes?: Uint8Array): Uint8Array<ArrayBufferLike>;
|
|
52
|
+
/** Grow `buffer` to keep BUFFER_SIZE free bytes past `offset`, preserving `[0, offset)`. */
|
|
53
|
+
protected ensureCapacity(buffer: Uint8Array, offset: number): Uint8Array;
|
|
41
54
|
encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes?: Uint8Array): Uint8Array<ArrayBufferLike>;
|
|
42
55
|
/**
|
|
43
56
|
* Per-view unreliable encode. Walks `root.unreliableChanges` and emits
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { Schema } from "../Schema.js";
|
|
2
|
+
/**
|
|
3
|
+
* Server-side object pool for Schema instances.
|
|
4
|
+
*
|
|
5
|
+
* Constructing a Schema is comparatively expensive (per-instance ChangeTree +
|
|
6
|
+
* `Object.defineProperty($changes)` + values array, multiplied by the number
|
|
7
|
+
* of instances in the entity tree). For workloads that spawn/despawn entities
|
|
8
|
+
* (ECS, matchmaking bots, projectiles), reusing instances avoids that cost.
|
|
9
|
+
*
|
|
10
|
+
* The pool relies on {@link Schema.reset} to return a detached instance to a
|
|
11
|
+
* pristine, construction-default state — including dropping its `$refId` so a
|
|
12
|
+
* re-add re-acquires a fresh id (making reuse wire-format-identical to `new`).
|
|
13
|
+
*
|
|
14
|
+
* Lifecycle:
|
|
15
|
+
* ```ts
|
|
16
|
+
* const pool = createPool(Entity, { preallocate: 1000 });
|
|
17
|
+
*
|
|
18
|
+
* // spawn
|
|
19
|
+
* const e = pool.acquire();
|
|
20
|
+
* e.x = 10; e.y = 20; // re-assign fields (pool does NOT reset primitives)
|
|
21
|
+
* state.entities.set(id, e);
|
|
22
|
+
*
|
|
23
|
+
* // despawn — remove from the state tree FIRST, then release
|
|
24
|
+
* state.entities.delete(id);
|
|
25
|
+
* pool.release(e);
|
|
26
|
+
* ```
|
|
27
|
+
*
|
|
28
|
+
* Not supported (throws on release): instances shared across multiple parents,
|
|
29
|
+
* `@stream` collections, and decoder-side (mirror) instances.
|
|
30
|
+
*/
|
|
31
|
+
export interface PoolOptions {
|
|
32
|
+
/** Construct this many instances up front, so the first spawns reuse rather than allocate. */
|
|
33
|
+
preallocate?: number;
|
|
34
|
+
/**
|
|
35
|
+
* Cap on retained free instances. Releases beyond the cap are dropped (and
|
|
36
|
+
* garbage-collected), keeping a long-running pool bounded. Default: no cap.
|
|
37
|
+
*/
|
|
38
|
+
maxSize?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Object pool for Schema instances. Exported as a **type only** — construct one
|
|
42
|
+
* via {@link createPool}, which is the public entry point. The class is public
|
|
43
|
+
* for typing (`SchemaPool<Entity>` annotations) and `instanceof` checks.
|
|
44
|
+
*/
|
|
45
|
+
export declare class SchemaPool<T extends Schema> {
|
|
46
|
+
private readonly _free;
|
|
47
|
+
private readonly _factory;
|
|
48
|
+
private readonly _maxSize;
|
|
49
|
+
constructor(factory: () => T, opts?: PoolOptions);
|
|
50
|
+
/** Pop a pre-reset free instance, or construct a fresh one. */
|
|
51
|
+
acquire(): T;
|
|
52
|
+
/**
|
|
53
|
+
* Reset `instance` to construction defaults and return it to the pool.
|
|
54
|
+
* PRECONDITION: the instance must already be detached from the state tree
|
|
55
|
+
* (removed from its parent collection/field, so the encoder released it).
|
|
56
|
+
*/
|
|
57
|
+
release(instance: T): void;
|
|
58
|
+
/** Number of instances currently available for reuse. */
|
|
59
|
+
get size(): number;
|
|
60
|
+
}
|
|
61
|
+
/** Convenience factory: `createPool(Entity, { preallocate: 64 })`. */
|
|
62
|
+
export declare function createPool<T extends Schema>(ctor: new () => T, opts?: PoolOptions): SchemaPool<T>;
|
package/build/encoder/Root.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { TypeContext } from "../types/TypeContext.js";
|
|
2
2
|
import { ChangeTree, ChangeTreeList, type ChangeTreeNode } from "./ChangeTree.js";
|
|
3
3
|
import { $changes, $refId } from "../types/symbols.js";
|
|
4
|
-
import { RefIdAllocator } from "./RefIdAllocator.js";
|
|
5
4
|
import type { StateView } from "./StateView.js";
|
|
6
5
|
import type { StreamableState } from "./streaming.js";
|
|
7
6
|
/**
|
|
@@ -22,10 +21,12 @@ export interface Streamable {
|
|
|
22
21
|
export declare class Root {
|
|
23
22
|
types: TypeContext;
|
|
24
23
|
/**
|
|
25
|
-
*
|
|
26
|
-
*
|
|
24
|
+
* Monotonic refId counter. RefIds are never recycled — a refId is a
|
|
25
|
+
* stable identity for the lifetime of the room, so a client that
|
|
26
|
+
* missed DELETEs (reconnect) can never see an old id rebound to a
|
|
27
|
+
* different instance. DevMode reads/writes this across HMR cycles.
|
|
27
28
|
*/
|
|
28
|
-
|
|
29
|
+
protected nextUniqueId: number;
|
|
29
30
|
refCount: {
|
|
30
31
|
[id: number]: number;
|
|
31
32
|
};
|
|
@@ -70,11 +70,20 @@ export declare class StateView {
|
|
|
70
70
|
markInvisible(tree: ChangeTree): void;
|
|
71
71
|
/** Clear invisible bit. */
|
|
72
72
|
unmarkInvisible(tree: ChangeTree): void;
|
|
73
|
-
/**
|
|
73
|
+
/**
|
|
74
|
+
* True iff this view shares at least one tag bit with `tree`.
|
|
75
|
+
*
|
|
76
|
+
* `tagViews` is keyed by individual power-of-two bits (custom tags must
|
|
77
|
+
* be powers of two; `@view(A|B)` field masks are decomposed on store).
|
|
78
|
+
* A field whose mask is `tag` is visible if the view was `add()`ed with
|
|
79
|
+
* any overlapping bit — so we walk `tag`'s set bits and return on the
|
|
80
|
+
* first match. Passing DEFAULT_VIEW_TAG (-1, all bits) answers "does
|
|
81
|
+
* this view hold ANY custom tag on the tree".
|
|
82
|
+
*/
|
|
74
83
|
hasTagOnTree(tree: ChangeTree, tag: number): boolean;
|
|
75
|
-
/** Mark `tree` as carrying `tag` for this view. */
|
|
84
|
+
/** Mark `tree` as carrying `tag` (each of its bits) for this view. */
|
|
76
85
|
addTag(tree: ChangeTree, tag: number): void;
|
|
77
|
-
/** Clear
|
|
86
|
+
/** Clear each of `tag`'s bits for this view on `tree`. */
|
|
78
87
|
removeTag(tree: ChangeTree, tag: number): void;
|
|
79
88
|
/** Clear ALL tag bits this view holds on `tree` (used when the per-tag isn't known). */
|
|
80
89
|
removeAllTagsOnTree(tree: ChangeTree): void;
|