@colyseus/schema 3.0.75 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/index.js +780 -429
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +778 -430
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +780 -429
- package/lib/Reflection.d.ts +50 -17
- package/lib/Reflection.js +151 -202
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +13 -1
- package/lib/Schema.js +73 -9
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +6 -1
- package/lib/annotations.js +8 -34
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +34 -1
- package/lib/bench_encode.js.map +1 -1
- package/lib/codegen/api.js +35 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/cli.js +4 -1
- package/lib/codegen/cli.js.map +1 -1
- package/lib/codegen/parser.js +35 -2
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +34 -1
- package/lib/codegen/types.js.map +1 -1
- package/lib/decoder/DecodeOperation.d.ts +2 -2
- package/lib/decoder/DecodeOperation.js +3 -3
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +3 -3
- package/lib/decoder/Decoder.js +2 -2
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/ReferenceTracker.d.ts +0 -1
- package/lib/decoder/ReferenceTracker.js +9 -7
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/decoder/strategy/Callbacks.d.ts +154 -0
- package/lib/decoder/strategy/Callbacks.js +340 -0
- package/lib/decoder/strategy/Callbacks.js.map +1 -0
- package/lib/decoder/strategy/{StateCallbacks.d.ts → getDecoderStateCallbacks.d.ts} +6 -0
- package/lib/decoder/strategy/{StateCallbacks.js → getDecoderStateCallbacks.js} +17 -10
- package/lib/decoder/strategy/getDecoderStateCallbacks.js.map +1 -0
- package/lib/encoder/ChangeTree.d.ts +2 -2
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +2 -2
- package/lib/encoder/EncodeOperation.js +3 -3
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +6 -6
- package/lib/encoder/Encoder.js +19 -18
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.js +17 -14
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.js +13 -12
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/decode.d.ts +2 -2
- package/lib/encoding/encode.d.ts +3 -1
- package/lib/encoding/encode.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +7 -3
- package/lib/index.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +7 -14
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/custom/ArraySchema.d.ts +2 -1
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.d.ts +2 -1
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -2
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.d.ts +2 -1
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/symbols.d.ts +1 -0
- package/lib/types/symbols.js +2 -1
- package/lib/types/symbols.js.map +1 -1
- package/lib/utils.js +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +12 -16
- package/src/Reflection.ts +185 -174
- package/src/Schema.ts +81 -13
- package/src/annotations.ts +14 -40
- package/src/codegen/parser.ts +1 -1
- package/src/decoder/DecodeOperation.ts +9 -9
- package/src/decoder/Decoder.ts +6 -6
- package/src/decoder/ReferenceTracker.ts +10 -8
- package/src/decoder/strategy/Callbacks.ts +547 -0
- package/src/decoder/strategy/{StateCallbacks.ts → getDecoderStateCallbacks.ts} +17 -11
- package/src/encoder/ChangeTree.ts +4 -7
- package/src/encoder/EncodeOperation.ts +9 -9
- package/src/encoder/Encoder.ts +26 -18
- package/src/encoder/Root.ts +20 -15
- package/src/encoder/StateView.ts +15 -13
- package/src/encoding/encode.ts +1 -1
- package/src/index.ts +3 -2
- package/src/types/HelperTypes.ts +13 -11
- package/src/types/custom/ArraySchema.ts +2 -1
- package/src/types/custom/CollectionSchema.ts +4 -2
- package/src/types/custom/MapSchema.ts +4 -2
- package/src/types/custom/SetSchema.ts +3 -1
- package/src/types/symbols.ts +1 -0
- package/src/utils.ts +2 -2
- package/lib/Decoder.d.ts +0 -16
- package/lib/Decoder.js +0 -182
- package/lib/Decoder.js.map +0 -1
- package/lib/Encoder.d.ts +0 -13
- package/lib/Encoder.js +0 -79
- package/lib/Encoder.js.map +0 -1
- package/lib/changes/ChangeSet.d.ts +0 -12
- package/lib/changes/ChangeSet.js +0 -35
- package/lib/changes/ChangeSet.js.map +0 -1
- package/lib/changes/ChangeTree.d.ts +0 -53
- package/lib/changes/ChangeTree.js +0 -202
- package/lib/changes/ChangeTree.js.map +0 -1
- package/lib/changes/DecodeOperation.d.ts +0 -15
- package/lib/changes/DecodeOperation.js +0 -186
- package/lib/changes/DecodeOperation.js.map +0 -1
- package/lib/changes/EncodeOperation.d.ts +0 -18
- package/lib/changes/EncodeOperation.js +0 -130
- package/lib/changes/EncodeOperation.js.map +0 -1
- package/lib/changes/ReferenceTracker.d.ts +0 -14
- package/lib/changes/ReferenceTracker.js +0 -83
- package/lib/changes/ReferenceTracker.js.map +0 -1
- package/lib/changes/consts.d.ts +0 -14
- package/lib/changes/consts.js +0 -18
- package/lib/changes/consts.js.map +0 -1
- package/lib/decoder/strategy/StateCallbacks.js.map +0 -1
- package/lib/decoding/decode.d.ts +0 -48
- package/lib/decoding/decode.js +0 -267
- package/lib/decoding/decode.js.map +0 -1
- package/lib/ecs.d.ts +0 -11
- package/lib/ecs.js +0 -160
- package/lib/ecs.js.map +0 -1
- package/lib/filters/index.d.ts +0 -8
- package/lib/filters/index.js +0 -24
- package/lib/filters/index.js.map +0 -1
- package/lib/spec.d.ts +0 -13
- package/lib/spec.js +0 -42
- package/lib/spec.js.map +0 -1
- package/lib/types/ArraySchema.d.ts +0 -238
- package/lib/types/ArraySchema.js +0 -555
- package/lib/types/ArraySchema.js.map +0 -1
- package/lib/types/CollectionSchema.d.ts +0 -35
- package/lib/types/CollectionSchema.js +0 -150
- package/lib/types/CollectionSchema.js.map +0 -1
- package/lib/types/MapSchema.d.ts +0 -38
- package/lib/types/MapSchema.js +0 -215
- package/lib/types/MapSchema.js.map +0 -1
- package/lib/types/SetSchema.d.ts +0 -32
- package/lib/types/SetSchema.js +0 -162
- package/lib/types/SetSchema.js.map +0 -1
- package/lib/types/typeRegistry.d.ts +0 -5
- package/lib/types/typeRegistry.js +0 -13
- package/lib/types/typeRegistry.js.map +0 -1
- package/lib/usage.d.ts +0 -1
- package/lib/usage.js +0 -22
- package/lib/usage.js.map +0 -1
- package/lib/v3.d.ts +0 -1
- package/lib/v3.js +0 -427
- package/lib/v3.js.map +0 -1
- package/lib/v3_experiment.d.ts +0 -1
- package/lib/v3_experiment.js +0 -407
- package/lib/v3_experiment.js.map +0 -1
|
@@ -0,0 +1,547 @@
|
|
|
1
|
+
import { Metadata } from "../../Metadata";
|
|
2
|
+
import { Collection, NonFunctionPropNames } from "../../types/HelperTypes";
|
|
3
|
+
import { Ref } from "../../encoder/ChangeTree";
|
|
4
|
+
import { Decoder } from "../Decoder";
|
|
5
|
+
import { DataChange } from "../DecodeOperation";
|
|
6
|
+
import { OPERATION } from "../../encoding/spec";
|
|
7
|
+
import { Schema } from "../../Schema";
|
|
8
|
+
import { $refId } from "../../types/symbols";
|
|
9
|
+
import { MapSchema } from "../../types/custom/MapSchema";
|
|
10
|
+
import { ArraySchema } from "../../types/custom/ArraySchema";
|
|
11
|
+
import { getDecoderStateCallbacks, type SchemaCallbackProxy } from "./getDecoderStateCallbacks";
|
|
12
|
+
import { getRawChangesCallback } from "./RawChanges";
|
|
13
|
+
|
|
14
|
+
//
|
|
15
|
+
// C#-style Callbacks API
|
|
16
|
+
// Matches the API from: https://docs.colyseus.io/state/callbacks
|
|
17
|
+
//
|
|
18
|
+
// Key features:
|
|
19
|
+
// - Uses string property names with TypeScript auto-completion
|
|
20
|
+
// - Consistent parameter order (key, value) for collection callbacks
|
|
21
|
+
// - Overloaded methods for nested instance callbacks
|
|
22
|
+
//
|
|
23
|
+
|
|
24
|
+
type PropertyChangeCallback<K> = (currentValue: K, previousValue: K) => void;
|
|
25
|
+
type KeyValueCallback<K, V> = (key: K, value: V) => void;
|
|
26
|
+
type InstanceChangeCallback = () => void;
|
|
27
|
+
|
|
28
|
+
// Exclude internal properties from valid property names
|
|
29
|
+
type PublicPropNames<T> = Exclude<NonFunctionPropNames<T>, typeof $refId> & string;
|
|
30
|
+
|
|
31
|
+
// Extract only properties that extend Collection
|
|
32
|
+
type CollectionPropNames<T> = Exclude<{
|
|
33
|
+
[K in keyof T]: T[K] extends Collection<any, any> ? K : never
|
|
34
|
+
}[keyof T] & string, typeof $refId>;
|
|
35
|
+
|
|
36
|
+
// Infer the value type of a collection property
|
|
37
|
+
type CollectionValueType<T, K extends keyof T> =
|
|
38
|
+
T[K] extends MapSchema<infer V, any> ? V :
|
|
39
|
+
T[K] extends ArraySchema<infer V> ? V :
|
|
40
|
+
T[K] extends Collection<any, infer V, any> ? V : never;
|
|
41
|
+
|
|
42
|
+
// Infer the key type of a collection property
|
|
43
|
+
type CollectionKeyType<T, K extends keyof T> =
|
|
44
|
+
T[K] extends MapSchema<any, infer Key> ? Key :
|
|
45
|
+
T[K] extends ArraySchema<any> ? number :
|
|
46
|
+
T[K] extends Collection<infer Key, any, any> ? Key : never;
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* State Callbacks handler
|
|
50
|
+
*
|
|
51
|
+
* Usage:
|
|
52
|
+
* ```ts
|
|
53
|
+
* const $ = Callbacks.get(decoder);
|
|
54
|
+
*
|
|
55
|
+
* // Listen to property changes
|
|
56
|
+
* $.listen("currentTurn", (currentValue, previousValue) => { ... });
|
|
57
|
+
*
|
|
58
|
+
* // Listen to collection additions
|
|
59
|
+
* $.onAdd("entities", (sessionId, entity) => {
|
|
60
|
+
* // Nested property listening
|
|
61
|
+
* $.listen(entity, "hp", (currentHp, previousHp) => { ... });
|
|
62
|
+
* });
|
|
63
|
+
*
|
|
64
|
+
* // Listen to collection removals
|
|
65
|
+
* $.onRemove("entities", (sessionId, entity) => { ... });
|
|
66
|
+
*
|
|
67
|
+
* // Listen to any property change on an instance
|
|
68
|
+
* $.onChange(entity, () => { ... });
|
|
69
|
+
*
|
|
70
|
+
* // Bind properties to another object
|
|
71
|
+
* $.bindTo(player, playerVisual);
|
|
72
|
+
* ```
|
|
73
|
+
*/
|
|
74
|
+
export class StateCallbackStrategy<TState extends Schema> {
|
|
75
|
+
protected decoder: Decoder<TState>;
|
|
76
|
+
protected uniqueRefIds: Set<number> = new Set();
|
|
77
|
+
protected isTriggering: boolean = false;
|
|
78
|
+
|
|
79
|
+
constructor(decoder: Decoder<TState>) {
|
|
80
|
+
this.decoder = decoder;
|
|
81
|
+
this.decoder.triggerChanges = this.triggerChanges.bind(this);
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
protected get callbacks() {
|
|
85
|
+
return this.decoder.root.callbacks;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
protected get state() {
|
|
89
|
+
return this.decoder.state;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
protected addCallback(
|
|
93
|
+
refId: number,
|
|
94
|
+
operationOrProperty: OPERATION | string,
|
|
95
|
+
handler: Function
|
|
96
|
+
): () => void {
|
|
97
|
+
const $root = this.decoder.root;
|
|
98
|
+
return $root.addCallback(refId, operationOrProperty, handler);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
protected addCallbackOrWaitCollectionAvailable<TInstance extends Schema, TReturn extends Ref>(
|
|
102
|
+
instance: TInstance,
|
|
103
|
+
propertyName: string,
|
|
104
|
+
operation: OPERATION,
|
|
105
|
+
handler: Function,
|
|
106
|
+
immediate: boolean = true
|
|
107
|
+
): () => void {
|
|
108
|
+
let removeHandler: () => void = () => {};
|
|
109
|
+
const removeOnAdd = () => removeHandler();
|
|
110
|
+
|
|
111
|
+
const collection = (instance as any)[propertyName] as TReturn;
|
|
112
|
+
|
|
113
|
+
// Collection not available yet. Listen for its availability before attaching the handler.
|
|
114
|
+
if (collection === null || collection === undefined) {
|
|
115
|
+
removeHandler = this.addCallback(
|
|
116
|
+
instance[$refId],
|
|
117
|
+
propertyName,
|
|
118
|
+
(value: TReturn, _: TReturn) => {
|
|
119
|
+
if (value !== null && value !== undefined) {
|
|
120
|
+
removeHandler = this.addCallback(value[$refId], operation, handler);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
);
|
|
124
|
+
return removeOnAdd;
|
|
125
|
+
|
|
126
|
+
} else {
|
|
127
|
+
//
|
|
128
|
+
// Call immediately if collection is already available, if it's an ADD operation.
|
|
129
|
+
//
|
|
130
|
+
immediate = immediate && this.isTriggering === false;
|
|
131
|
+
|
|
132
|
+
if (operation === OPERATION.ADD && immediate) {
|
|
133
|
+
(collection as Collection<any, any>).forEach((value: any, key: any) => {
|
|
134
|
+
handler(key, value);
|
|
135
|
+
});
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return this.addCallback(collection[$refId], operation, handler);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
/**
|
|
143
|
+
* Listen to property changes on the root state.
|
|
144
|
+
*/
|
|
145
|
+
listen<K extends PublicPropNames<TState>>(
|
|
146
|
+
property: K,
|
|
147
|
+
handler: PropertyChangeCallback<TState[K]>,
|
|
148
|
+
immediate?: boolean
|
|
149
|
+
): () => void;
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* Listen to property changes on a nested instance.
|
|
153
|
+
*/
|
|
154
|
+
listen<TInstance extends Schema, K extends PublicPropNames<TInstance>>(
|
|
155
|
+
instance: TInstance,
|
|
156
|
+
property: K,
|
|
157
|
+
handler: PropertyChangeCallback<TInstance[K]>,
|
|
158
|
+
immediate?: boolean
|
|
159
|
+
): () => void;
|
|
160
|
+
|
|
161
|
+
listen(...args: any[]): () => void {
|
|
162
|
+
if (typeof args[0] === 'string') {
|
|
163
|
+
// listen(property, handler, immediate?)
|
|
164
|
+
return this.listenInstance(this.state, args[0], args[1], args[2]);
|
|
165
|
+
} else {
|
|
166
|
+
// listen(instance, property, handler, immediate?)
|
|
167
|
+
return this.listenInstance(args[0], args[1], args[2], args[3]);
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
protected listenInstance<TInstance extends Schema>(
|
|
172
|
+
instance: TInstance,
|
|
173
|
+
propertyName: string,
|
|
174
|
+
handler: PropertyChangeCallback<any>,
|
|
175
|
+
immediate: boolean = true
|
|
176
|
+
): () => void {
|
|
177
|
+
immediate = immediate && this.isTriggering === false;
|
|
178
|
+
|
|
179
|
+
//
|
|
180
|
+
// Call handler immediately if property is already available.
|
|
181
|
+
//
|
|
182
|
+
const currentValue = (instance as any)[propertyName];
|
|
183
|
+
if (immediate && currentValue !== null && currentValue !== undefined) {
|
|
184
|
+
handler(currentValue, undefined as any);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
return this.addCallback(instance[$refId], propertyName, handler);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* Listen to any property change on an instance.
|
|
192
|
+
*/
|
|
193
|
+
onChange<TInstance extends Schema>(
|
|
194
|
+
instance: TInstance,
|
|
195
|
+
handler: InstanceChangeCallback
|
|
196
|
+
): () => void;
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Listen to item changes in a collection on root state.
|
|
200
|
+
*/
|
|
201
|
+
onChange<K extends CollectionPropNames<TState>>(
|
|
202
|
+
property: K,
|
|
203
|
+
handler: KeyValueCallback<CollectionKeyType<TState, K>, CollectionValueType<TState, K>>
|
|
204
|
+
): () => void;
|
|
205
|
+
|
|
206
|
+
/**
|
|
207
|
+
* Listen to item changes in a nested collection.
|
|
208
|
+
*/
|
|
209
|
+
onChange<TInstance extends Schema, K extends CollectionPropNames<TInstance>>(
|
|
210
|
+
instance: TInstance,
|
|
211
|
+
property: K,
|
|
212
|
+
handler: KeyValueCallback<CollectionKeyType<TInstance, K>, CollectionValueType<TInstance, K>>
|
|
213
|
+
): () => void;
|
|
214
|
+
|
|
215
|
+
onChange(...args: any[]): () => void {
|
|
216
|
+
if (args.length === 2 && typeof args[0] !== 'string') {
|
|
217
|
+
// onChange(instance, handler) - instance change
|
|
218
|
+
const instance = args[0] as Schema;
|
|
219
|
+
const handler = args[1] as InstanceChangeCallback;
|
|
220
|
+
return this.addCallback(instance[$refId], OPERATION.REPLACE, handler);
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
if (typeof args[0] === 'string') {
|
|
224
|
+
// onChange(property, handler) - collection on root state
|
|
225
|
+
return this.addCallbackOrWaitCollectionAvailable(
|
|
226
|
+
this.state,
|
|
227
|
+
args[0],
|
|
228
|
+
OPERATION.REPLACE,
|
|
229
|
+
args[1]
|
|
230
|
+
);
|
|
231
|
+
} else {
|
|
232
|
+
// onChange(instance, property, handler) - nested collection
|
|
233
|
+
return this.addCallbackOrWaitCollectionAvailable(
|
|
234
|
+
args[0],
|
|
235
|
+
args[1],
|
|
236
|
+
OPERATION.REPLACE,
|
|
237
|
+
args[2]
|
|
238
|
+
);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Listen to items added to a collection on root state.
|
|
244
|
+
*/
|
|
245
|
+
onAdd<K extends CollectionPropNames<TState>>(
|
|
246
|
+
property: K,
|
|
247
|
+
handler: KeyValueCallback<CollectionKeyType<TState, K>, CollectionValueType<TState, K>>,
|
|
248
|
+
immediate?: boolean
|
|
249
|
+
): () => void;
|
|
250
|
+
|
|
251
|
+
/**
|
|
252
|
+
* Listen to items added to a nested collection.
|
|
253
|
+
*/
|
|
254
|
+
onAdd<TInstance extends Schema, K extends CollectionPropNames<TInstance>>(
|
|
255
|
+
instance: TInstance,
|
|
256
|
+
property: K,
|
|
257
|
+
handler: KeyValueCallback<CollectionKeyType<TInstance, K>, CollectionValueType<TInstance, K>>,
|
|
258
|
+
immediate?: boolean
|
|
259
|
+
): () => void;
|
|
260
|
+
|
|
261
|
+
onAdd(...args: any[]): () => void {
|
|
262
|
+
if (typeof args[0] === 'string') {
|
|
263
|
+
// onAdd(property, handler, immediate?) - collection on root state
|
|
264
|
+
return this.addCallbackOrWaitCollectionAvailable(
|
|
265
|
+
this.state,
|
|
266
|
+
args[0],
|
|
267
|
+
OPERATION.ADD,
|
|
268
|
+
args[1],
|
|
269
|
+
args[2] !== false
|
|
270
|
+
);
|
|
271
|
+
} else {
|
|
272
|
+
// onAdd(instance, property, handler, immediate?) - nested collection
|
|
273
|
+
return this.addCallbackOrWaitCollectionAvailable(
|
|
274
|
+
args[0],
|
|
275
|
+
args[1],
|
|
276
|
+
OPERATION.ADD,
|
|
277
|
+
args[2],
|
|
278
|
+
args[3] !== false
|
|
279
|
+
);
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
/**
|
|
284
|
+
* Listen to items removed from a collection on root state.
|
|
285
|
+
*/
|
|
286
|
+
onRemove<K extends CollectionPropNames<TState>>(
|
|
287
|
+
property: K,
|
|
288
|
+
handler: KeyValueCallback<CollectionKeyType<TState, K>, CollectionValueType<TState, K>>
|
|
289
|
+
): () => void;
|
|
290
|
+
|
|
291
|
+
/**
|
|
292
|
+
* Listen to items removed from a nested collection.
|
|
293
|
+
*/
|
|
294
|
+
onRemove<TInstance extends Schema, K extends CollectionPropNames<TInstance>>(
|
|
295
|
+
instance: TInstance,
|
|
296
|
+
property: K,
|
|
297
|
+
handler: KeyValueCallback<CollectionKeyType<TInstance, K>, CollectionValueType<TInstance, K>>
|
|
298
|
+
): () => void;
|
|
299
|
+
|
|
300
|
+
onRemove(...args: any[]): () => void {
|
|
301
|
+
if (typeof args[0] === 'string') {
|
|
302
|
+
// onRemove(property, handler) - collection on root state
|
|
303
|
+
return this.addCallbackOrWaitCollectionAvailable(
|
|
304
|
+
this.state,
|
|
305
|
+
args[0],
|
|
306
|
+
OPERATION.DELETE,
|
|
307
|
+
args[1]
|
|
308
|
+
);
|
|
309
|
+
} else {
|
|
310
|
+
// onRemove(instance, property, handler) - nested collection
|
|
311
|
+
return this.addCallbackOrWaitCollectionAvailable(
|
|
312
|
+
args[0],
|
|
313
|
+
args[1],
|
|
314
|
+
OPERATION.DELETE,
|
|
315
|
+
args[2]
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
/**
|
|
321
|
+
* Bind properties from a Schema instance to a target object.
|
|
322
|
+
* Changes will be automatically reflected on the target object.
|
|
323
|
+
*/
|
|
324
|
+
bindTo<TInstance extends Schema, TTarget>(
|
|
325
|
+
from: TInstance,
|
|
326
|
+
to: TTarget,
|
|
327
|
+
properties?: string[],
|
|
328
|
+
immediate: boolean = true
|
|
329
|
+
): () => void {
|
|
330
|
+
const metadata: Metadata = (from.constructor as typeof Schema)[Symbol.metadata];
|
|
331
|
+
|
|
332
|
+
// If no properties specified, bind all properties
|
|
333
|
+
if (!properties) {
|
|
334
|
+
properties = Object.keys(metadata)
|
|
335
|
+
.filter(key => !isNaN(Number(key)))
|
|
336
|
+
.map((index) => metadata[index as any as number].name);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
const action = () => {
|
|
340
|
+
for (const prop of properties!) {
|
|
341
|
+
const fromValue = (from as any)[prop];
|
|
342
|
+
if (fromValue !== undefined) {
|
|
343
|
+
(to as any)[prop] = fromValue;
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
};
|
|
347
|
+
|
|
348
|
+
if (immediate) {
|
|
349
|
+
action();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return this.addCallback(from[$refId], OPERATION.REPLACE, action);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
protected triggerChanges(allChanges: DataChange[]): void {
|
|
356
|
+
this.uniqueRefIds.clear();
|
|
357
|
+
|
|
358
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
359
|
+
const change = allChanges[i];
|
|
360
|
+
const refId = change.refId;
|
|
361
|
+
const ref = change.ref;
|
|
362
|
+
|
|
363
|
+
const $callbacks = this.callbacks[refId];
|
|
364
|
+
if (!$callbacks) {
|
|
365
|
+
continue;
|
|
366
|
+
}
|
|
367
|
+
|
|
368
|
+
//
|
|
369
|
+
// trigger onRemove on child structure.
|
|
370
|
+
//
|
|
371
|
+
if (
|
|
372
|
+
(change.op & OPERATION.DELETE) === OPERATION.DELETE &&
|
|
373
|
+
change.previousValue instanceof Schema
|
|
374
|
+
) {
|
|
375
|
+
const childRefId = (change.previousValue as Ref)[$refId];
|
|
376
|
+
const deleteCallbacks = this.callbacks[childRefId]?.[OPERATION.DELETE];
|
|
377
|
+
if (deleteCallbacks) {
|
|
378
|
+
for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
|
|
379
|
+
deleteCallbacks[j]();
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (ref instanceof Schema) {
|
|
385
|
+
//
|
|
386
|
+
// Handle Schema instance
|
|
387
|
+
//
|
|
388
|
+
|
|
389
|
+
if (!this.uniqueRefIds.has(refId)) {
|
|
390
|
+
// trigger onChange
|
|
391
|
+
const replaceCallbacks = $callbacks[OPERATION.REPLACE];
|
|
392
|
+
if (replaceCallbacks) {
|
|
393
|
+
for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
|
|
394
|
+
try {
|
|
395
|
+
replaceCallbacks[j]();
|
|
396
|
+
} catch (e) {
|
|
397
|
+
console.error(e);
|
|
398
|
+
}
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
// trigger field callbacks
|
|
404
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
405
|
+
if (fieldCallbacks) {
|
|
406
|
+
for (let j = fieldCallbacks.length - 1; j >= 0; j--) {
|
|
407
|
+
try {
|
|
408
|
+
this.isTriggering = true;
|
|
409
|
+
fieldCallbacks[j](change.value, change.previousValue);
|
|
410
|
+
} catch (e) {
|
|
411
|
+
console.error(e);
|
|
412
|
+
} finally {
|
|
413
|
+
this.isTriggering = false;
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
} else {
|
|
419
|
+
//
|
|
420
|
+
// Handle collection of items
|
|
421
|
+
//
|
|
422
|
+
const dynamicIndex = change.dynamicIndex ?? change.field;
|
|
423
|
+
|
|
424
|
+
if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
|
|
425
|
+
//
|
|
426
|
+
// FIXME: `previousValue` should always be available.
|
|
427
|
+
//
|
|
428
|
+
if (change.previousValue !== undefined) {
|
|
429
|
+
// trigger onRemove (key, value)
|
|
430
|
+
const deleteCallbacks = $callbacks[OPERATION.DELETE];
|
|
431
|
+
if (deleteCallbacks) {
|
|
432
|
+
for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
|
|
433
|
+
deleteCallbacks[j](dynamicIndex, change.previousValue);
|
|
434
|
+
}
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
|
|
438
|
+
// Handle DELETE_AND_ADD operation
|
|
439
|
+
if ((change.op & OPERATION.ADD) === OPERATION.ADD) {
|
|
440
|
+
const addCallbacks = $callbacks[OPERATION.ADD];
|
|
441
|
+
if (addCallbacks) {
|
|
442
|
+
this.isTriggering = true;
|
|
443
|
+
for (let j = addCallbacks.length - 1; j >= 0; j--) {
|
|
444
|
+
addCallbacks[j](dynamicIndex, change.value);
|
|
445
|
+
}
|
|
446
|
+
this.isTriggering = false;
|
|
447
|
+
}
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
} else if (
|
|
451
|
+
(change.op & OPERATION.ADD) === OPERATION.ADD &&
|
|
452
|
+
change.previousValue !== change.value
|
|
453
|
+
) {
|
|
454
|
+
// trigger onAdd (key, value)
|
|
455
|
+
const addCallbacks = $callbacks[OPERATION.ADD];
|
|
456
|
+
if (addCallbacks) {
|
|
457
|
+
this.isTriggering = true;
|
|
458
|
+
for (let j = addCallbacks.length - 1; j >= 0; j--) {
|
|
459
|
+
addCallbacks[j](dynamicIndex, change.value);
|
|
460
|
+
}
|
|
461
|
+
this.isTriggering = false;
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// trigger onChange (key, value)
|
|
466
|
+
if (change.value !== change.previousValue) {
|
|
467
|
+
const replaceCallbacks = $callbacks[OPERATION.REPLACE];
|
|
468
|
+
if (replaceCallbacks) {
|
|
469
|
+
for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
|
|
470
|
+
replaceCallbacks[j](dynamicIndex, change.value);
|
|
471
|
+
}
|
|
472
|
+
}
|
|
473
|
+
}
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
this.uniqueRefIds.add(refId);
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
/**
|
|
482
|
+
* Factory class for retrieving the callbacks API.
|
|
483
|
+
*/
|
|
484
|
+
export const Callbacks = {
|
|
485
|
+
/**
|
|
486
|
+
* Get the new callbacks standard API.
|
|
487
|
+
*
|
|
488
|
+
* Usage:
|
|
489
|
+
* ```ts
|
|
490
|
+
* const callbacks = Callbacks.get(roomOrDecoder);
|
|
491
|
+
*
|
|
492
|
+
* // Listen to property changes
|
|
493
|
+
* callbacks.listen("currentTurn", (currentValue, previousValue) => { ... });
|
|
494
|
+
*
|
|
495
|
+
* // Listen to collection additions
|
|
496
|
+
* callbacks.onAdd("entities", (sessionId, entity) => {
|
|
497
|
+
* // Nested property listening
|
|
498
|
+
* callbacks.listen(entity, "hp", (currentHp, previousHp) => { ... });
|
|
499
|
+
* });
|
|
500
|
+
*
|
|
501
|
+
* // Listen to collection removals
|
|
502
|
+
* callbacks.onRemove("entities", (sessionId, entity) => { ... });
|
|
503
|
+
*
|
|
504
|
+
* // Listen to any property change on an instance
|
|
505
|
+
* callbacks.onChange(entity, () => { ... });
|
|
506
|
+
*
|
|
507
|
+
* // Bind properties to another object
|
|
508
|
+
* callbacks.bindTo(player, playerVisual);
|
|
509
|
+
* ```
|
|
510
|
+
*
|
|
511
|
+
* @param roomOrDecoder - Room or Decoder instance to get the callbacks for.
|
|
512
|
+
* @returns the new callbacks standard API.
|
|
513
|
+
*/
|
|
514
|
+
get<T extends Schema>(roomOrDecoder: Decoder<T> | { serializer: { decoder: Decoder<T> } }): StateCallbackStrategy<T> {
|
|
515
|
+
if (roomOrDecoder instanceof Decoder) {
|
|
516
|
+
return new StateCallbackStrategy<T>(roomOrDecoder);
|
|
517
|
+
|
|
518
|
+
} else if (roomOrDecoder.serializer.decoder) {
|
|
519
|
+
return new StateCallbackStrategy<T>(roomOrDecoder.serializer.decoder);
|
|
520
|
+
|
|
521
|
+
} else {
|
|
522
|
+
throw new Error('Invalid room or decoder');
|
|
523
|
+
}
|
|
524
|
+
},
|
|
525
|
+
|
|
526
|
+
/**
|
|
527
|
+
* Get the legacy callbacks API.
|
|
528
|
+
*
|
|
529
|
+
* We aim to deprecate this API on 1.0, and iterate on improving Callbacks.get() API.
|
|
530
|
+
*
|
|
531
|
+
* @param roomOrDecoder - Room or Decoder instance to get the legacy callbacks for.
|
|
532
|
+
* @returns the legacy callbacks API.
|
|
533
|
+
*/
|
|
534
|
+
getLegacy<T extends Schema>(roomOrDecoder: Decoder<T> | { serializer: { decoder: Decoder<T> } }): SchemaCallbackProxy<T> {
|
|
535
|
+
if (roomOrDecoder instanceof Decoder) {
|
|
536
|
+
return getDecoderStateCallbacks(roomOrDecoder);
|
|
537
|
+
|
|
538
|
+
} else if (roomOrDecoder.serializer.decoder) {
|
|
539
|
+
return getDecoderStateCallbacks(roomOrDecoder.serializer.decoder);
|
|
540
|
+
}
|
|
541
|
+
},
|
|
542
|
+
|
|
543
|
+
getRawChanges(decoder: Decoder, callback: (changes: DataChange[]) => void) {
|
|
544
|
+
return getRawChangesCallback(decoder, callback);
|
|
545
|
+
}
|
|
546
|
+
};
|
|
547
|
+
|
|
@@ -1,10 +1,11 @@
|
|
|
1
1
|
import { Metadata } from "../../Metadata";
|
|
2
2
|
import { Collection, NonFunctionNonPrimitivePropNames, NonFunctionPropNames } from "../../types/HelperTypes";
|
|
3
|
-
import { Ref } from "../../encoder/ChangeTree";
|
|
3
|
+
import { IRef, Ref } from "../../encoder/ChangeTree";
|
|
4
4
|
import { Decoder } from "../Decoder";
|
|
5
5
|
import { DataChange } from "../DecodeOperation";
|
|
6
6
|
import { OPERATION } from "../../encoding/spec";
|
|
7
7
|
import { Schema } from "../../Schema";
|
|
8
|
+
import { $refId } from "../../types/symbols";
|
|
8
9
|
import type { DefinitionType } from "../../annotations";
|
|
9
10
|
import type { CollectionSchema } from "../../types/custom/CollectionSchema";
|
|
10
11
|
|
|
@@ -107,7 +108,12 @@ type CallContext = {
|
|
|
107
108
|
onInstanceAvailable?: OnInstanceAvailableCallback,
|
|
108
109
|
}
|
|
109
110
|
|
|
110
|
-
|
|
111
|
+
/**
|
|
112
|
+
* Legacy callback system
|
|
113
|
+
*
|
|
114
|
+
* @param decoder
|
|
115
|
+
* @returns
|
|
116
|
+
*/
|
|
111
117
|
export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>): SchemaCallbackProxy<T> {
|
|
112
118
|
const $root = decoder.root;
|
|
113
119
|
const callbacks = $root.callbacks;
|
|
@@ -133,7 +139,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
133
139
|
(change.op & OPERATION.DELETE) === OPERATION.DELETE &&
|
|
134
140
|
change.previousValue instanceof Schema
|
|
135
141
|
) {
|
|
136
|
-
const deleteCallbacks = callbacks[
|
|
142
|
+
const deleteCallbacks = callbacks[(change.previousValue as Ref)[$refId]]?.[OPERATION.DELETE];
|
|
137
143
|
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
138
144
|
deleteCallbacks[i]();
|
|
139
145
|
}
|
|
@@ -247,7 +253,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
247
253
|
) {
|
|
248
254
|
callback(context.instance[prop], undefined);
|
|
249
255
|
}
|
|
250
|
-
return $root.addCallback($
|
|
256
|
+
return $root.addCallback(ref[$refId], prop, callback);
|
|
251
257
|
}
|
|
252
258
|
|
|
253
259
|
/**
|
|
@@ -272,7 +278,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
272
278
|
|
|
273
279
|
onChange: function onChange(callback: () => void) {
|
|
274
280
|
return $root.addCallback(
|
|
275
|
-
|
|
281
|
+
context.instance[$refId],
|
|
276
282
|
OPERATION.REPLACE,
|
|
277
283
|
callback
|
|
278
284
|
);
|
|
@@ -287,7 +293,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
287
293
|
properties = Object.keys(metadata).map((index) => metadata[index as any as number].name);
|
|
288
294
|
}
|
|
289
295
|
return $root.addCallback(
|
|
290
|
-
|
|
296
|
+
context.instance[$refId],
|
|
291
297
|
OPERATION.REPLACE,
|
|
292
298
|
() => {
|
|
293
299
|
properties.forEach((prop) =>
|
|
@@ -313,7 +319,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
313
319
|
}, false);
|
|
314
320
|
|
|
315
321
|
// has existing value
|
|
316
|
-
if ($
|
|
322
|
+
if (instance?.[$refId] !== undefined) {
|
|
317
323
|
callback(instance, true);
|
|
318
324
|
}
|
|
319
325
|
}
|
|
@@ -321,7 +327,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
321
327
|
|
|
322
328
|
return getProxy(metadataField.type, {
|
|
323
329
|
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
324
|
-
instance: ($
|
|
330
|
+
instance: (instance?.[$refId] !== undefined && instance),
|
|
325
331
|
parentInstance: context.instance,
|
|
326
332
|
onInstanceAvailable,
|
|
327
333
|
});
|
|
@@ -347,7 +353,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
347
353
|
(ref as CollectionSchema).forEach((v, k) => callback(v, k));
|
|
348
354
|
}
|
|
349
355
|
|
|
350
|
-
return $root.addCallback($
|
|
356
|
+
return $root.addCallback(ref[$refId], OPERATION.ADD, (value: any, key: any) => {
|
|
351
357
|
onAddCalls.set(callback, true);
|
|
352
358
|
currentOnAddCallback = callback;
|
|
353
359
|
callback(value, key);
|
|
@@ -357,11 +363,11 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
357
363
|
};
|
|
358
364
|
|
|
359
365
|
const onRemove = function (ref: Ref, callback: (value: any, key: any) => void) {
|
|
360
|
-
return $root.addCallback($
|
|
366
|
+
return $root.addCallback(ref[$refId], OPERATION.DELETE, callback);
|
|
361
367
|
};
|
|
362
368
|
|
|
363
369
|
const onChange = function (ref: Ref, callback: (value: any, key: any) => void) {
|
|
364
|
-
return $root.addCallback($
|
|
370
|
+
return $root.addCallback(ref[$refId], OPERATION.REPLACE, callback);
|
|
365
371
|
};
|
|
366
372
|
|
|
367
373
|
return new Proxy({
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { OPERATION } from "../encoding/spec";
|
|
2
2
|
import { Schema } from "../Schema";
|
|
3
|
-
import { $changes, $childType, $decoder, $onEncodeEnd, $encoder, $getByIndex, $refTypeFieldIndexes, $viewFieldIndexes, type $deleteByIndex } from "../types/symbols";
|
|
3
|
+
import { $changes, $childType, $decoder, $onEncodeEnd, $encoder, $getByIndex, $refId, $refTypeFieldIndexes, $viewFieldIndexes, type $deleteByIndex } from "../types/symbols";
|
|
4
4
|
|
|
5
5
|
import type { MapSchema } from "../types/custom/MapSchema";
|
|
6
6
|
import type { ArraySchema } from "../types/custom/ArraySchema";
|
|
@@ -16,6 +16,7 @@ declare global {
|
|
|
16
16
|
interface Object {
|
|
17
17
|
// FIXME: not a good practice to extend globals here
|
|
18
18
|
[$changes]?: ChangeTree;
|
|
19
|
+
// [$refId]?: number;
|
|
19
20
|
[$encoder]?: EncodeOperation,
|
|
20
21
|
[$decoder]?: DecodeOperation,
|
|
21
22
|
}
|
|
@@ -23,15 +24,12 @@ declare global {
|
|
|
23
24
|
|
|
24
25
|
export interface IRef {
|
|
25
26
|
[$changes]?: ChangeTree;
|
|
27
|
+
[$refId]?: number;
|
|
26
28
|
[$getByIndex]?: (index: number, isEncodeAll?: boolean) => any;
|
|
27
29
|
[$deleteByIndex]?: (index: number) => void;
|
|
28
30
|
}
|
|
29
31
|
|
|
30
|
-
export type Ref = Schema
|
|
31
|
-
| ArraySchema
|
|
32
|
-
| MapSchema
|
|
33
|
-
| CollectionSchema
|
|
34
|
-
| SetSchema;
|
|
32
|
+
export type Ref = Schema | ArraySchema | MapSchema | CollectionSchema | SetSchema;
|
|
35
33
|
|
|
36
34
|
export type ChangeSetName = "changes"
|
|
37
35
|
| "allChanges"
|
|
@@ -126,7 +124,6 @@ export interface ParentChain {
|
|
|
126
124
|
|
|
127
125
|
export class ChangeTree<T extends Ref = any> {
|
|
128
126
|
ref: T;
|
|
129
|
-
refId: number;
|
|
130
127
|
metadata: Metadata;
|
|
131
128
|
|
|
132
129
|
root?: Root;
|