@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
|
@@ -7,17 +7,60 @@ export interface DataChange<T = any, F = string> {
|
|
|
7
7
|
ref: IRef;
|
|
8
8
|
refId: number;
|
|
9
9
|
op: OPERATION;
|
|
10
|
-
field
|
|
10
|
+
/** Set for Schema field changes; omitted for collection item changes (which carry a `dynamicIndex` instead). */
|
|
11
|
+
field?: F;
|
|
11
12
|
dynamicIndex?: number | string;
|
|
12
13
|
value: T;
|
|
13
14
|
previousValue: T;
|
|
14
15
|
}
|
|
15
16
|
export declare const DEFINITION_MISMATCH = -1;
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
/**
|
|
18
|
+
* When no `triggerChanges` subscriber is attached, `Decoder.decode` passes
|
|
19
|
+
* `null` so the per-field change objects are never allocated. Every push
|
|
20
|
+
* site uses `allChanges?.push(...)` — optional chaining also short-circuits
|
|
21
|
+
* the object literal, so there's nothing to collect and nothing to throw
|
|
22
|
+
* away.
|
|
23
|
+
*/
|
|
24
|
+
export type DecodeOperation<T extends Schema = any> = (decoder: Decoder<T>, bytes: Uint8Array, it: Iterator, ref: IRef, allChanges: DataChange[] | null) => number | void;
|
|
25
|
+
/**
|
|
26
|
+
* Collection-kind discriminator declared on each collection class as
|
|
27
|
+
* `static COLLECTION_KIND = CollectionKind.X`. The decoder's key/value
|
|
28
|
+
* dispatch used to make three back-to-back `typeof(ref.method) ===
|
|
29
|
+
* "function"` checks per entry; those collapse into one switch on the
|
|
30
|
+
* target's class tag. Missing / `undefined` on a ref hits the switch's
|
|
31
|
+
* `default` branch and logs a warning — a guard for future collection
|
|
32
|
+
* types that land without a tag.
|
|
33
|
+
*
|
|
34
|
+
* Declared as a `const` object (not a TS `enum`) so the codegen parser —
|
|
35
|
+
* which picks up every `EnumDeclaration` in the lib source via transitive
|
|
36
|
+
* imports — doesn't emit a generated .cs file for it.
|
|
37
|
+
*/
|
|
38
|
+
export declare const CollectionKind: {
|
|
39
|
+
readonly Map: 1;
|
|
40
|
+
readonly Array: 2;
|
|
41
|
+
readonly Set: 3;
|
|
42
|
+
readonly Collection: 4;
|
|
43
|
+
readonly Stream: 5;
|
|
20
44
|
};
|
|
45
|
+
export type CollectionKind = typeof CollectionKind[keyof typeof CollectionKind];
|
|
46
|
+
/**
|
|
47
|
+
* Structural type for any class that participates in the `decodeKeyValue-
|
|
48
|
+
* Operation` dispatch. Lets the hot-path read `tgt.constructor.COLLECTION_KIND`
|
|
49
|
+
* without an `any` cast.
|
|
50
|
+
*/
|
|
51
|
+
export interface CollectionCtor {
|
|
52
|
+
readonly COLLECTION_KIND: CollectionKind;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Decode the next wire value for `ref[index]`. Returns the decoded value.
|
|
56
|
+
*
|
|
57
|
+
* Callers pass `previousValue` explicitly — it's the current value at the
|
|
58
|
+
* slot before decoding and is needed for ref-count bookkeeping (on DELETE)
|
|
59
|
+
* and for the DELETE_AND_ADD self-reassign case. Keeping it as a parameter
|
|
60
|
+
* lets this function return a single primitive instead of a pair, so the
|
|
61
|
+
* hot call path allocates nothing.
|
|
62
|
+
*/
|
|
63
|
+
export declare function decodeValue<T extends Ref>(decoder: Decoder, operation: OPERATION, ref: T, index: number, previousValue: any, type: any, bytes: Uint8Array, it: Iterator, allChanges: DataChange[] | null): any;
|
|
21
64
|
export declare const decodeSchemaOperation: DecodeOperation;
|
|
22
65
|
export declare const decodeKeyValueOperation: DecodeOperation;
|
|
23
66
|
export declare const decodeArray: DecodeOperation;
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { TypeContext } from "../types/TypeContext.js";
|
|
2
2
|
import { Schema } from "../Schema.js";
|
|
3
|
-
import type
|
|
3
|
+
import { type IRef } from "../encoder/ChangeTree.js";
|
|
4
4
|
import type { Iterator } from "../encoding/decode.js";
|
|
5
5
|
import { ReferenceTracker } from "./ReferenceTracker.js";
|
|
6
6
|
import { type DataChange } from "./DecodeOperation.js";
|
|
@@ -17,5 +17,5 @@ export declare class Decoder<T extends IRef = any> {
|
|
|
17
17
|
skipCurrentStructure(bytes: Uint8Array, it: Iterator, totalBytes: number): void;
|
|
18
18
|
getInstanceType(bytes: Uint8Array, it: Iterator, defaultType: typeof Schema): typeof Schema;
|
|
19
19
|
createInstanceOfType(type: typeof Schema): Schema;
|
|
20
|
-
removeChildRefs(ref: Collection, allChanges: DataChange[]): void;
|
|
20
|
+
removeChildRefs(ref: Collection, allChanges: DataChange[] | null): void;
|
|
21
21
|
}
|
|
@@ -36,7 +36,7 @@ export declare class StateCallbackStrategy<TState extends IRef> {
|
|
|
36
36
|
/**
|
|
37
37
|
* Listen to property changes on a nested instance.
|
|
38
38
|
*/
|
|
39
|
-
listen<TInstance, K extends PublicPropNames<TInstance>>(instance: TInstance, property: K, handler: PropertyChangeCallback<TInstance[K]>, immediate?: boolean): () => void;
|
|
39
|
+
listen<TInstance extends Schema, K extends PublicPropNames<TInstance>>(instance: TInstance, property: K, handler: PropertyChangeCallback<TInstance[K]>, immediate?: boolean): () => void;
|
|
40
40
|
protected listenInstance<TInstance extends IRef>(instance: TInstance, propertyName: string, handler: PropertyChangeCallback<any>, immediate?: boolean): () => void;
|
|
41
41
|
/**
|
|
42
42
|
* Listen to any property change on an instance.
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import { OPERATION } from "../encoding/spec.js";
|
|
2
|
+
/**
|
|
3
|
+
* ChangeRecorder — "what changed this tick" for a single ref.
|
|
4
|
+
*
|
|
5
|
+
* This file holds the two standalone recorder classes used for the
|
|
6
|
+
* unreliable channel (lazy, opt-in). The reliable channel is inlined on
|
|
7
|
+
* `ChangeTree` for perf; see `ChangeTree._isSchema` dispatch.
|
|
8
|
+
*
|
|
9
|
+
* Interface design (ISP):
|
|
10
|
+
* - {@link ChangeRecorder}: common ops, implemented by both Schema and
|
|
11
|
+
* Collection recorders.
|
|
12
|
+
* - {@link ICollectionChangeRecorder}: extends with `recordPure` +
|
|
13
|
+
* `shift` — collection-only. Schema recorders do NOT carry these.
|
|
14
|
+
*
|
|
15
|
+
* Per-field filter/visibility is decided at encode time, not record time.
|
|
16
|
+
* Full-sync output is derived structurally via `ChangeTree.forEachLive`.
|
|
17
|
+
*/
|
|
18
|
+
export interface ChangeRecorder {
|
|
19
|
+
/**
|
|
20
|
+
* Record a change at the given index. Handles op merge
|
|
21
|
+
* (DELETE followed by ADD becomes DELETE_AND_ADD).
|
|
22
|
+
*/
|
|
23
|
+
record(index: number, op: OPERATION): void;
|
|
24
|
+
/** Record a DELETE at the given index. */
|
|
25
|
+
recordDelete(index: number, op: OPERATION): void;
|
|
26
|
+
/**
|
|
27
|
+
* Record an operation without op-merge semantics. Used by ArraySchema
|
|
28
|
+
* positional writes where DELETE→ADD merge is undesirable.
|
|
29
|
+
*/
|
|
30
|
+
recordRaw(index: number, op: OPERATION): void;
|
|
31
|
+
/** Current operation at index, or undefined if none. */
|
|
32
|
+
operationAt(index: number): OPERATION | undefined;
|
|
33
|
+
/** Overwrite the operation at index. */
|
|
34
|
+
setOperationAt(index: number, op: OPERATION): void;
|
|
35
|
+
/**
|
|
36
|
+
* Iterate (index, op) pairs in record order.
|
|
37
|
+
* Pure operations emit with index = -op (wire convention).
|
|
38
|
+
*/
|
|
39
|
+
forEach(cb: (index: number, op: OPERATION) => void): void;
|
|
40
|
+
/** Closure-free forEach variant for the hot encode path. */
|
|
41
|
+
forEachWithCtx<T>(ctx: T, cb: (ctx: T, index: number, op: OPERATION) => void): void;
|
|
42
|
+
size(): number;
|
|
43
|
+
has(): boolean;
|
|
44
|
+
reset(): void;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Extended recorder for collection types — adds `recordPure` (CLEAR /
|
|
48
|
+
* REVERSE) and `shift` (ArraySchema.unshift support).
|
|
49
|
+
*/
|
|
50
|
+
export interface ICollectionChangeRecorder extends ChangeRecorder {
|
|
51
|
+
/**
|
|
52
|
+
* Record a pure operation (CLEAR / REVERSE) with no index.
|
|
53
|
+
* Interleaves with indexed ops at record order.
|
|
54
|
+
*/
|
|
55
|
+
recordPure(op: OPERATION): void;
|
|
56
|
+
/** Shift current-tick dirty indexes by `shiftIndex`. */
|
|
57
|
+
shift(shiftIndex: number): void;
|
|
58
|
+
}
|
|
59
|
+
/**
|
|
60
|
+
* Schema field operations are limited to ADD(128), DELETE(64), and
|
|
61
|
+
* DELETE_AND_ADD(192). REPLACE(0) is collection-only, so `ops[i] === 0`
|
|
62
|
+
* is a safe "no operation" sentinel.
|
|
63
|
+
*/
|
|
64
|
+
export declare class SchemaChangeRecorder implements ChangeRecorder {
|
|
65
|
+
private dirtyLow;
|
|
66
|
+
private dirtyHigh;
|
|
67
|
+
private readonly ops;
|
|
68
|
+
constructor(numFields: number);
|
|
69
|
+
record(index: number, op: OPERATION): void;
|
|
70
|
+
recordDelete(index: number, op: OPERATION): void;
|
|
71
|
+
recordRaw(index: number, op: OPERATION): void;
|
|
72
|
+
operationAt(index: number): OPERATION | undefined;
|
|
73
|
+
setOperationAt(index: number, op: OPERATION): void;
|
|
74
|
+
forEach(cb: (index: number, op: OPERATION) => void): void;
|
|
75
|
+
forEachWithCtx<T>(ctx: T, cb: (ctx: T, index: number, op: OPERATION) => void): void;
|
|
76
|
+
size(): number;
|
|
77
|
+
has(): boolean;
|
|
78
|
+
reset(): void;
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Collection items have sparse indexes (e.g. 0, 7, 1024) exceeding the
|
|
82
|
+
* 64-field cap Schema imposes. Map-based storage handles arbitrary
|
|
83
|
+
* indexes; the value at each entry is the OPERATION.
|
|
84
|
+
*
|
|
85
|
+
* Pure operations (CLEAR, REVERSE) live in `pureOps` as `[position, op]`
|
|
86
|
+
* entries where `position` is `dirty.size` at record time — preserves
|
|
87
|
+
* insertion-order interleaving with indexed ops (e.g. CLEAR must emit
|
|
88
|
+
* BEFORE subsequent ADDs).
|
|
89
|
+
*/
|
|
90
|
+
export declare class CollectionChangeRecorder implements ICollectionChangeRecorder {
|
|
91
|
+
private dirty;
|
|
92
|
+
private pureOps;
|
|
93
|
+
record(index: number, op: OPERATION): void;
|
|
94
|
+
recordDelete(index: number, op: OPERATION): void;
|
|
95
|
+
recordRaw(index: number, op: OPERATION): void;
|
|
96
|
+
recordPure(op: OPERATION): void;
|
|
97
|
+
operationAt(index: number): OPERATION | undefined;
|
|
98
|
+
setOperationAt(index: number, op: OPERATION): void;
|
|
99
|
+
forEach(cb: (index: number, op: OPERATION) => void): void;
|
|
100
|
+
forEachWithCtx<T>(ctx: T, cb: (ctx: T, index: number, op: OPERATION) => void): void;
|
|
101
|
+
size(): number;
|
|
102
|
+
has(): boolean;
|
|
103
|
+
reset(): void;
|
|
104
|
+
shift(shiftIndex: number): void;
|
|
105
|
+
}
|
|
106
|
+
/** 32-bit Hamming weight (popcount). */
|
|
107
|
+
export declare function popcount32(n: number): number;
|
|
@@ -1,3 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ChangeTree — the per-`Ref` mutation tracker attached via `$changes`.
|
|
3
|
+
*
|
|
4
|
+
* This file owns: class shape (fields, flags, ctor), inline
|
|
5
|
+
* ChangeRecorder implementation (record / forEach / …), mutation API
|
|
6
|
+
* (change / delete / operation / …), and encode lifecycle (endEncode /
|
|
7
|
+
* discard / …). Helpers split out into ./changeTree/:
|
|
8
|
+
*
|
|
9
|
+
* - parentChain.ts addParent / removeParent / find / has / getAll
|
|
10
|
+
* - liveIteration.ts forEachLive
|
|
11
|
+
* - inheritedFlags.ts filter / unreliable / transient / static inheritance
|
|
12
|
+
* - treeAttachment.ts setRoot / setParent / forEachChild(+WithCtx)
|
|
13
|
+
*
|
|
14
|
+
* Public surface on ChangeTree is unchanged — methods are thin pass-throughs
|
|
15
|
+
* into the helpers. V8 inlines the pass-throughs; the runtime shape stays
|
|
16
|
+
* a single class to preserve hidden-class + IC behavior.
|
|
17
|
+
*/
|
|
1
18
|
import { OPERATION } from "../encoding/spec.js";
|
|
2
19
|
import { Schema } from "../Schema.js";
|
|
3
20
|
import { $changes, $decoder, $encoder, $getByIndex, $refId, type $deleteByIndex } from "../types/symbols.js";
|
|
@@ -5,9 +22,12 @@ import type { MapSchema } from "../types/custom/MapSchema.js";
|
|
|
5
22
|
import type { ArraySchema } from "../types/custom/ArraySchema.js";
|
|
6
23
|
import type { CollectionSchema } from "../types/custom/CollectionSchema.js";
|
|
7
24
|
import type { SetSchema } from "../types/custom/SetSchema.js";
|
|
25
|
+
import type { StreamSchema } from "../types/custom/StreamSchema.js";
|
|
8
26
|
import { Root } from "./Root.js";
|
|
9
27
|
import { Metadata } from "../Metadata.js";
|
|
28
|
+
import { type ChangeRecorder } from "./ChangeRecorder.js";
|
|
10
29
|
import type { EncodeOperation } from "./EncodeOperation.js";
|
|
30
|
+
import { type EncodeDescriptor } from "./EncodeDescriptor.js";
|
|
11
31
|
import type { DecodeOperation } from "../decoder/DecodeOperation.js";
|
|
12
32
|
declare global {
|
|
13
33
|
interface Object {
|
|
@@ -18,14 +38,10 @@ declare global {
|
|
|
18
38
|
}
|
|
19
39
|
export interface IRef {
|
|
20
40
|
[$refId]?: number;
|
|
21
|
-
[$getByIndex]
|
|
22
|
-
[$deleteByIndex]
|
|
23
|
-
}
|
|
24
|
-
export type Ref = Schema | ArraySchema | MapSchema | CollectionSchema | SetSchema;
|
|
25
|
-
export type ChangeSetName = "changes" | "allChanges" | "filteredChanges" | "allFilteredChanges";
|
|
26
|
-
export interface IndexedOperations {
|
|
27
|
-
[index: number]: OPERATION;
|
|
41
|
+
[$getByIndex](index: number, isEncodeAll?: boolean): any;
|
|
42
|
+
[$deleteByIndex](index: number): void;
|
|
28
43
|
}
|
|
44
|
+
export type Ref = Schema | ArraySchema | MapSchema | CollectionSchema | SetSchema | StreamSchema;
|
|
29
45
|
export interface ChangeTreeNode {
|
|
30
46
|
changeTree: ChangeTree;
|
|
31
47
|
next?: ChangeTreeNode;
|
|
@@ -36,99 +52,232 @@ export interface ChangeTreeList {
|
|
|
36
52
|
next?: ChangeTreeNode;
|
|
37
53
|
tail?: ChangeTreeNode;
|
|
38
54
|
}
|
|
39
|
-
export interface ChangeSet {
|
|
40
|
-
indexes: {
|
|
41
|
-
[index: number]: number;
|
|
42
|
-
};
|
|
43
|
-
operations: number[];
|
|
44
|
-
queueRootNode?: ChangeTreeNode;
|
|
45
|
-
}
|
|
46
55
|
export declare function createChangeTreeList(): ChangeTreeList;
|
|
47
|
-
export declare function setOperationAtIndex(changeSet: ChangeSet, index: number): void;
|
|
48
|
-
export declare function deleteOperationAtIndex(changeSet: ChangeSet, index: number | string): void;
|
|
49
|
-
export declare function debugChangeSet(label: string, changeSet: ChangeSet): void;
|
|
50
56
|
export interface ParentChain {
|
|
51
57
|
ref: Ref;
|
|
52
58
|
index: number;
|
|
53
59
|
next?: ParentChain;
|
|
54
60
|
}
|
|
55
|
-
export declare
|
|
61
|
+
export declare const IS_FILTERED = 1, IS_VISIBILITY_SHARED = 2, IS_NEW = 4;
|
|
62
|
+
export declare const IS_UNRELIABLE = 8, IS_TRANSIENT = 16, IS_STATIC = 32;
|
|
63
|
+
export declare const IS_STREAM_COLLECTION = 64;
|
|
64
|
+
/**
|
|
65
|
+
* Flags a child inherits from its parent's own transitive state via
|
|
66
|
+
* `checkInheritedFlags`. Read as a bitwise mask so the inheritance step
|
|
67
|
+
* is a single OR instead of three getter/setter pairs.
|
|
68
|
+
*
|
|
69
|
+
* `IS_UNRELIABLE` is intentionally excluded: `@unreliable` is rejected
|
|
70
|
+
* at decoration time for ref-type fields (see `Metadata.setUnreliable`)
|
|
71
|
+
* because an unreliable ADD/DELETE could leave the decoder unable to
|
|
72
|
+
* interpret later packets referencing an orphan refId. Tree-level
|
|
73
|
+
* unreliable is therefore dead on every Schema/Collection tree today;
|
|
74
|
+
* the bit and its machinery are kept in place so this can be
|
|
75
|
+
* reconsidered if a safe semantics (e.g. reliable ADD + unreliable
|
|
76
|
+
* field mutations only) is designed later.
|
|
77
|
+
*/
|
|
78
|
+
export declare const INHERITABLE_FLAGS: number;
|
|
79
|
+
export declare class ChangeTree<T extends Ref = any> implements ChangeRecorder {
|
|
56
80
|
ref: T;
|
|
81
|
+
/**
|
|
82
|
+
* Non-Proxy target of `ref` for encoder hot-path reads. For
|
|
83
|
+
* `ArraySchema`, `ref` is the Proxy users interact with; every property
|
|
84
|
+
* access on it runs through the `get` trap (even for symbol keys, which
|
|
85
|
+
* fall through to `Reflect.get` — one extra hop per lookup). The encoder
|
|
86
|
+
* loop reads `[$getByIndex]`, `[$childType]`, `.items`, `.tmpItems` at
|
|
87
|
+
* high frequency during `encode()` / `encodeAll()`; going through
|
|
88
|
+
* `refTarget` skips all of those traps.
|
|
89
|
+
*
|
|
90
|
+
* For non-proxied types (Schema, MapSchema, SetSchema, CollectionSchema,
|
|
91
|
+
* StreamSchema), `refTarget === ref`. Consumers that need the user-
|
|
92
|
+
* facing identity (debug output, callback parents) keep using `ref`.
|
|
93
|
+
*/
|
|
94
|
+
refTarget: T;
|
|
57
95
|
metadata: Metadata;
|
|
96
|
+
/**
|
|
97
|
+
* Per-class cache of encoder fn / filter fn / isSchema / filterBitmask /
|
|
98
|
+
* metadata, looked up once at construction. The encode loop reads
|
|
99
|
+
* `tree.encDescriptor` and never touches `ref.constructor` again. See
|
|
100
|
+
* EncodeDescriptor.ts.
|
|
101
|
+
*/
|
|
102
|
+
encDescriptor: EncodeDescriptor;
|
|
58
103
|
root?: Root;
|
|
59
|
-
|
|
104
|
+
parentRef?: Ref;
|
|
105
|
+
_parentIndex?: number;
|
|
106
|
+
extraParents?: ParentChain;
|
|
107
|
+
flags: number;
|
|
60
108
|
/**
|
|
61
|
-
*
|
|
109
|
+
* Per-walk visit stamp written by `Encoder.encodeFullSync`'s DFS. A
|
|
110
|
+
* tree is considered "already visited by the current walk" iff
|
|
111
|
+
* `tree._fullSyncGen === ctx.gen` — the encoder bumps its generation
|
|
112
|
+
* counter once per walk, then stamps each tree with that value on
|
|
113
|
+
* first visit; any later encounter of the same tree (shared refs
|
|
114
|
+
* reachable through multiple parents) short-circuits on the equality
|
|
115
|
+
* check instead of recursing again.
|
|
62
116
|
*/
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
117
|
+
_fullSyncGen: number;
|
|
118
|
+
_isSchema: boolean;
|
|
119
|
+
dirtyLow: number;
|
|
120
|
+
dirtyHigh: number;
|
|
121
|
+
opsLow: number;
|
|
122
|
+
opsHigh: number;
|
|
123
|
+
ops?: Uint8Array;
|
|
124
|
+
collDirty?: Map<number, OPERATION>;
|
|
125
|
+
collPureOps?: Array<[number, OPERATION]>;
|
|
126
|
+
unreliableRecorder?: ChangeRecorder;
|
|
127
|
+
paused: boolean;
|
|
128
|
+
changesNode?: ChangeTreeNode;
|
|
129
|
+
unreliableChangesNode?: ChangeTreeNode;
|
|
130
|
+
visibleViews?: number[];
|
|
131
|
+
invisibleViews?: number[];
|
|
132
|
+
tagViews?: Map<number, number[]>;
|
|
73
133
|
/**
|
|
74
|
-
*
|
|
134
|
+
* Per-view subscription bitmap — same layout as `visibleViews`. Set by
|
|
135
|
+
* `StateView.subscribe(collection)` to mark the view as persistently
|
|
136
|
+
* interested in this collection's contents. When a new child is
|
|
137
|
+
* attached to a subscribed collection (setParent hook), it's
|
|
138
|
+
* auto-propagated to every subscribed view (force-shipped for
|
|
139
|
+
* Array/Map/Set/Collection; enqueued into per-view pending for
|
|
140
|
+
* streams). Undefined until the first subscribe.
|
|
75
141
|
*/
|
|
76
|
-
|
|
142
|
+
subscribedViews?: number[];
|
|
143
|
+
get isFiltered(): boolean;
|
|
144
|
+
set isFiltered(v: boolean);
|
|
145
|
+
get isVisibilitySharedWithParent(): boolean;
|
|
146
|
+
set isVisibilitySharedWithParent(v: boolean);
|
|
147
|
+
get isNew(): boolean;
|
|
148
|
+
set isNew(v: boolean);
|
|
149
|
+
get isUnreliable(): boolean;
|
|
150
|
+
set isUnreliable(v: boolean);
|
|
151
|
+
get isTransient(): boolean;
|
|
152
|
+
set isTransient(v: boolean);
|
|
153
|
+
get isStatic(): boolean;
|
|
154
|
+
set isStatic(v: boolean);
|
|
155
|
+
get isStreamCollection(): boolean;
|
|
156
|
+
set isStreamCollection(v: boolean);
|
|
157
|
+
get hasFilteredFields(): boolean;
|
|
158
|
+
ensureUnreliableRecorder(): ChangeRecorder;
|
|
159
|
+
isFieldUnreliable(index: number): boolean;
|
|
160
|
+
isFieldStatic(index: number): boolean;
|
|
161
|
+
isFieldStream(index: number): boolean;
|
|
77
162
|
constructor(ref: T);
|
|
163
|
+
private _opAt;
|
|
164
|
+
private _opPut;
|
|
165
|
+
private _markDirty;
|
|
166
|
+
record(index: number, op: OPERATION): void;
|
|
167
|
+
recordDelete(index: number, op: OPERATION): void;
|
|
168
|
+
recordRaw(index: number, op: OPERATION): void;
|
|
169
|
+
recordPure(op: OPERATION): void;
|
|
170
|
+
operationAt(index: number): OPERATION | undefined;
|
|
171
|
+
setOperationAt(index: number, op: OPERATION): void;
|
|
172
|
+
forEach(cb: (index: number, op: OPERATION) => void): void;
|
|
173
|
+
forEachWithCtx<C>(ctx: C, cb: (ctx: C, index: number, op: OPERATION) => void): void;
|
|
174
|
+
size(): number;
|
|
175
|
+
has(): boolean;
|
|
176
|
+
reset(): void;
|
|
177
|
+
shift(shiftIndex: number): void;
|
|
78
178
|
setRoot(root: Root): void;
|
|
79
179
|
setParent(parent: Ref, root?: Root, parentIndex?: number): void;
|
|
80
|
-
forEachChild(
|
|
180
|
+
forEachChild(cb: (change: ChangeTree, at: any) => void): void;
|
|
181
|
+
forEachChildWithCtx<C>(ctx: C, cb: (ctx: C, change: ChangeTree, at: any) => void): void;
|
|
182
|
+
forEachLive(cb: (index: number) => void): void;
|
|
183
|
+
forEachLiveWithCtx<C>(ctx: C, cb: (ctx: C, index: number) => void): void;
|
|
81
184
|
operation(op: OPERATION): void;
|
|
185
|
+
/**
|
|
186
|
+
* Route a field-level mutation to the reliable or unreliable channel
|
|
187
|
+
* and enqueue into the matching queue. Shared by `change` and
|
|
188
|
+
* `indexedOperation`; `raw=true` bypasses DELETE→ADD merge
|
|
189
|
+
* (ArraySchema positional writes), `raw=false` merges inside `record`.
|
|
190
|
+
*
|
|
191
|
+
* Note: record() on both channels handles DELETE→ADD merge internally,
|
|
192
|
+
* so callers do not need to pre-compute the merged op.
|
|
193
|
+
*
|
|
194
|
+
* `@unreliable` is decoration-time-validated to apply only to primitive
|
|
195
|
+
* fields (see annotations.ts), so the per-field unreliable flag here
|
|
196
|
+
* always means "primitive value updates" — the structural-ADD-routes-
|
|
197
|
+
* reliable footgun for ref-type fields can't reach this code path.
|
|
198
|
+
*/
|
|
199
|
+
private _routeAndRecord;
|
|
82
200
|
change(index: number, operation?: OPERATION): void;
|
|
201
|
+
indexedOperation(index: number, operation: OPERATION): void;
|
|
83
202
|
shiftChangeIndexes(shiftIndex: number): void;
|
|
84
|
-
shiftAllChangeIndexes(shiftIndex: number, startIndex?: number): void;
|
|
85
|
-
private _shiftAllChangeIndexes;
|
|
86
|
-
indexedOperation(index: number, operation: OPERATION, allChangesIndex?: number): void;
|
|
87
|
-
getType(index?: number): any;
|
|
88
203
|
getChange(index: number): OPERATION;
|
|
204
|
+
pause(): void;
|
|
205
|
+
resume(): void;
|
|
206
|
+
untracked<T>(fn: () => T): T;
|
|
207
|
+
markDirty(index: number, operation?: OPERATION): void;
|
|
89
208
|
getValue(index: number, isEncodeAll?: boolean): any;
|
|
90
|
-
delete(index: number, operation?: OPERATION
|
|
91
|
-
endEncode(
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
* Recursively discard all changes from this, and child structures.
|
|
95
|
-
* (Used in tests only)
|
|
96
|
-
*/
|
|
209
|
+
delete(index: number, operation?: OPERATION): any;
|
|
210
|
+
endEncode(): void;
|
|
211
|
+
endEncodeUnreliable(): void;
|
|
212
|
+
discard(): void;
|
|
97
213
|
discardAll(): void;
|
|
98
214
|
get changed(): boolean;
|
|
99
|
-
|
|
100
|
-
protected _checkFilteredByParent(parent: Ref, parentIndex: number): void;
|
|
101
|
-
/**
|
|
102
|
-
* Get the immediate parent
|
|
103
|
-
*/
|
|
215
|
+
/** Immediate parent (primary). See `extraParents` for the 2nd+ chain. */
|
|
104
216
|
get parent(): Ref | undefined;
|
|
105
|
-
/**
|
|
106
|
-
* Get the immediate parent index
|
|
107
|
-
*/
|
|
108
217
|
get parentIndex(): number | undefined;
|
|
109
|
-
/**
|
|
110
|
-
* Add a parent to the chain
|
|
111
|
-
*/
|
|
112
218
|
addParent(parent: Ref, index: number): void;
|
|
113
|
-
/**
|
|
114
|
-
* Remove a parent from the chain
|
|
115
|
-
* @param parent - The parent to remove
|
|
116
|
-
* @returns true if parent was removed
|
|
117
|
-
*/
|
|
219
|
+
/** @returns true if parent was found and removed */
|
|
118
220
|
removeParent(parent?: Ref): boolean;
|
|
119
|
-
/**
|
|
120
|
-
* Find a specific parent in the chain
|
|
121
|
-
*/
|
|
122
221
|
findParent(predicate: (parent: Ref, index: number) => boolean): ParentChain | undefined;
|
|
123
|
-
/**
|
|
124
|
-
* Check if this ChangeTree has a specific parent
|
|
125
|
-
*/
|
|
126
222
|
hasParent(predicate: (parent: Ref, index: number) => boolean): boolean;
|
|
127
|
-
/**
|
|
128
|
-
* Get all parents as an array (for debugging/testing)
|
|
129
|
-
*/
|
|
130
223
|
getAllParents(): Array<{
|
|
131
224
|
ref: Ref;
|
|
132
225
|
index: number;
|
|
133
226
|
}>;
|
|
134
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Lightweight per-instance no-op ChangeTree used for instances the decoder
|
|
230
|
+
* builds. Those instances never feed back into an Encoder, so the full
|
|
231
|
+
* `ChangeTree` machinery (EncodeDescriptor lookup, recorder state, Maps /
|
|
232
|
+
* Uint8Arrays for change slots) is pure overhead — this stub carries only a
|
|
233
|
+
* `ref` back-pointer and no-op methods, so tree walkers and debug tooling
|
|
234
|
+
* continue to work.
|
|
235
|
+
*
|
|
236
|
+
* Plug-in contract: each collection class and the `Decoder` pick between
|
|
237
|
+
* `new ChangeTree(ref)` and `createUntrackedChangeTree(ref)` explicitly via
|
|
238
|
+
* dedicated factories (`initializeForDecoder` on collections,
|
|
239
|
+
* `createInstanceOfType` on the `Decoder`). There is no global state — every
|
|
240
|
+
* decision is local to the call site.
|
|
241
|
+
*/
|
|
242
|
+
export declare class UntrackedChangeTree {
|
|
243
|
+
ref: Ref;
|
|
244
|
+
root: undefined;
|
|
245
|
+
parentRef: undefined;
|
|
246
|
+
paused: boolean;
|
|
247
|
+
isNew: boolean;
|
|
248
|
+
flags: number;
|
|
249
|
+
constructor(ref: Ref);
|
|
250
|
+
change(): void;
|
|
251
|
+
delete(): void;
|
|
252
|
+
indexedOperation(): void;
|
|
253
|
+
operation(): void;
|
|
254
|
+
setParent(): void;
|
|
255
|
+
addParent(): void;
|
|
256
|
+
removeParent(): boolean;
|
|
257
|
+
getChange(): number;
|
|
258
|
+
discard(): void;
|
|
259
|
+
discardAll(): void;
|
|
260
|
+
pause(): void;
|
|
261
|
+
resume(): void;
|
|
262
|
+
untracked<T>(fn: () => T): T;
|
|
263
|
+
markDirty(): void;
|
|
264
|
+
forEachChild(callback: (change: any, at: any) => void): void;
|
|
265
|
+
forEachChildWithCtx<C>(ctx: C, callback: (ctx: C, change: any, at: any) => void): void;
|
|
266
|
+
forEachLive(): void;
|
|
267
|
+
forEachLiveWithCtx(): void;
|
|
268
|
+
forEach(): void;
|
|
269
|
+
}
|
|
270
|
+
export declare function createUntrackedChangeTree(ref: Ref): ChangeTree;
|
|
271
|
+
/**
|
|
272
|
+
* Install a non-enumerable `$changes: UntrackedChangeTree` on `target`.
|
|
273
|
+
* Shared by `Schema.initializeForDecoder` and every collection's
|
|
274
|
+
* `initializeForDecoder`. `publicRef` defaults to `target` — pass a Proxy
|
|
275
|
+
* instead (ArraySchema) so children attached to this tree see the Proxy
|
|
276
|
+
* as their parent, not the raw target.
|
|
277
|
+
*
|
|
278
|
+
* `enumerable: false` is load-bearing — tests use `deepStrictEqual` on
|
|
279
|
+
* decoded instances and walking into `$changes` would recurse through
|
|
280
|
+
* circular refs. Same descriptor shape as the tracked `Schema.initialize`
|
|
281
|
+
* + collection ctors.
|
|
282
|
+
*/
|
|
283
|
+
export declare function installUntrackedChangeTree(target: object, publicRef?: object): void;
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
import type { StateView } from "./StateView.js";
|
|
2
|
+
import type { EncodeOperation } from "./EncodeOperation.js";
|
|
3
|
+
export interface EncodeDescriptor {
|
|
4
|
+
encoder: EncodeOperation;
|
|
5
|
+
filter: ((ref: any, index: number, view?: StateView) => boolean) | undefined;
|
|
6
|
+
metadata: any;
|
|
7
|
+
isSchema: boolean;
|
|
8
|
+
/**
|
|
9
|
+
* Bit i set iff field i has a @view tag. 0 for collection trees.
|
|
10
|
+
* Lets `encodeChangeCb` do a single bitwise op instead of a
|
|
11
|
+
* per-field metadata[i]?.tag chase.
|
|
12
|
+
*/
|
|
13
|
+
filterBitmask: number;
|
|
14
|
+
/**
|
|
15
|
+
* Class-level "any field has the flag" booleans + per-field bitmasks.
|
|
16
|
+
* Hot path: per-mutation `_routeAndRecord` calls `isFieldStatic` and
|
|
17
|
+
* `isFieldUnreliable`. The common case is "no static/unreliable fields
|
|
18
|
+
* anywhere on this class" (booleans short-circuit before the symbol-keyed
|
|
19
|
+
* metadata lookup); the secondary common case is "this class has some
|
|
20
|
+
* such fields and we need to know if THIS field is one" — the bitmask
|
|
21
|
+
* answers in one bitwise op instead of an `Array.includes` linear scan.
|
|
22
|
+
*
|
|
23
|
+
* Bitmasks cover fields 0–31 only (matches the `filterBitmask` limitation).
|
|
24
|
+
* Fields ≥32 fall back to `Metadata.hasXAtIndex` — same handling as the
|
|
25
|
+
* filter-bitmask path.
|
|
26
|
+
*/
|
|
27
|
+
hasAnyStatic: boolean;
|
|
28
|
+
hasAnyUnreliable: boolean;
|
|
29
|
+
hasAnyStream: boolean;
|
|
30
|
+
/**
|
|
31
|
+
* Class-level "any field carries a `@view` tag" — covers fields both
|
|
32
|
+
* within and beyond index 31 (unlike `filterBitmask`, which only
|
|
33
|
+
* captures the low 32). Read by `ChangeTree.hasFilteredFields` to
|
|
34
|
+
* decide whether a parent tree must be included in a view's bootstrap.
|
|
35
|
+
*/
|
|
36
|
+
hasAnyView: boolean;
|
|
37
|
+
staticBitmask: number;
|
|
38
|
+
unreliableBitmask: number;
|
|
39
|
+
/**
|
|
40
|
+
* Bit i set iff field i holds a `t.stream(...)` collection. Hot encode
|
|
41
|
+
* path reads this to dispatch stream fields into the priority/budget
|
|
42
|
+
* gate instead of the normal recorder iteration.
|
|
43
|
+
*/
|
|
44
|
+
streamBitmask: number;
|
|
45
|
+
/**
|
|
46
|
+
* Per-field parallel arrays — Schemas only (empty arrays for
|
|
47
|
+
* collections). Replaces hot-path `metadata[i].name` / `metadata[i].type`
|
|
48
|
+
* / `metadata[i].tag` chains with direct array indexing on a small
|
|
49
|
+
* fixed-shape object.
|
|
50
|
+
*
|
|
51
|
+
* Sparse where natural: `tags[i]` is undefined unless field i carries
|
|
52
|
+
* a @view tag; readers should null-check before comparing.
|
|
53
|
+
*
|
|
54
|
+
* `encoders[i]` mirrors `metadata[$encoders]` — the pre-computed
|
|
55
|
+
* encoder fn for primitive-typed fields. Cached here so encode loops
|
|
56
|
+
* skip a `metadata[$encoders]?.[i]` symbol-chain per emission.
|
|
57
|
+
*/
|
|
58
|
+
names: string[];
|
|
59
|
+
types: any[];
|
|
60
|
+
tags: (number | undefined)[];
|
|
61
|
+
encoders: (((bytes: Uint8Array, value: any, it: any) => void) | undefined)[];
|
|
62
|
+
}
|
|
63
|
+
export declare function getEncodeDescriptor(ref: any): EncodeDescriptor;
|
|
@@ -4,16 +4,39 @@ import type { Encoder } from "./Encoder.js";
|
|
|
4
4
|
import type { Iterator } from "../encoding/decode.js";
|
|
5
5
|
import type { Metadata } from "../Metadata.js";
|
|
6
6
|
export type EncodeOperation<T extends Ref = any> = (encoder: Encoder, bytes: Uint8Array, changeTree: ChangeTree<T>, index: number, operation: OPERATION, it: Iterator, isEncodeAll: boolean, hasView: boolean, metadata?: Metadata) => void;
|
|
7
|
-
export declare function encodeValue(encoder: Encoder, bytes: Uint8Array, type: any, value: any, operation: OPERATION, it: Iterator): void;
|
|
7
|
+
export declare function encodeValue(encoder: Encoder, bytes: Uint8Array, type: any, value: any, operation: OPERATION, it: Iterator, encoderFn?: (bytes: Uint8Array, value: any, it: Iterator) => void): void;
|
|
8
8
|
/**
|
|
9
9
|
* Used for Schema instances.
|
|
10
10
|
* @private
|
|
11
11
|
*/
|
|
12
12
|
export declare const encodeSchemaOperation: EncodeOperation;
|
|
13
13
|
/**
|
|
14
|
-
*
|
|
14
|
+
* Encode a single MapSchema entry. Splits the legacy
|
|
15
|
+
* `encodeKeyValueOperation` so the per-emission `typeof ref['set']` check
|
|
16
|
+
* is gone — MapSchema instances are routed here via their `[$encoder]`
|
|
17
|
+
* static, the dynamic-key string emission is unconditional on ADD.
|
|
18
|
+
*
|
|
15
19
|
* @private
|
|
16
20
|
*/
|
|
21
|
+
export declare const encodeMapEntry: EncodeOperation;
|
|
22
|
+
/**
|
|
23
|
+
* Encode a single SetSchema / CollectionSchema entry. Wire format is the
|
|
24
|
+
* same as MapSchema minus the dynamic-key string, so this path skips the
|
|
25
|
+
* legacy `typeof ref['set']` check entirely.
|
|
26
|
+
*
|
|
27
|
+
* @private
|
|
28
|
+
*/
|
|
29
|
+
export declare const encodeIndexedEntry: EncodeOperation;
|
|
30
|
+
/**
|
|
31
|
+
* Unified encoder kept for back-compat with external consumers that may
|
|
32
|
+
* have registered it directly via `static [$encoder] =
|
|
33
|
+
* encodeKeyValueOperation`. New code (and all internal collections)
|
|
34
|
+
* should use the split variants — `encodeMapEntry` for MapSchema and
|
|
35
|
+
* `encodeIndexedEntry` for SetSchema / CollectionSchema.
|
|
36
|
+
*
|
|
37
|
+
* The runtime `typeof ref['set']` check below is the per-emission cost
|
|
38
|
+
* the split is designed to remove.
|
|
39
|
+
*/
|
|
17
40
|
export declare const encodeKeyValueOperation: EncodeOperation;
|
|
18
41
|
/**
|
|
19
42
|
* Used for collections (MapSchema, ArraySchema, etc.)
|