@colyseus/schema 3.0.0-alpha.2 → 3.0.0-alpha.22
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 +131 -61
- package/build/cjs/index.js +472 -150
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +471 -149
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +472 -150
- package/lib/Metadata.d.ts +2 -0
- package/lib/Metadata.js +39 -0
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +1 -2
- package/lib/Reflection.js +28 -22
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +1 -1
- package/lib/annotations.js +12 -10
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.d.ts +1 -0
- package/lib/bench_encode.js +93 -0
- package/lib/bench_encode.js.map +1 -0
- package/lib/codegen/api.js +1 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/languages/cpp.js +1 -2
- package/lib/codegen/languages/cpp.js.map +1 -1
- package/lib/codegen/languages/csharp.js +1 -2
- package/lib/codegen/languages/csharp.js.map +1 -1
- package/lib/codegen/languages/haxe.js +1 -2
- package/lib/codegen/languages/haxe.js.map +1 -1
- package/lib/codegen/languages/java.js +1 -2
- package/lib/codegen/languages/java.js.map +1 -1
- package/lib/codegen/languages/js.js +1 -2
- package/lib/codegen/languages/js.js.map +1 -1
- package/lib/codegen/languages/lua.js +1 -2
- package/lib/codegen/languages/lua.js.map +1 -1
- package/lib/codegen/languages/ts.js +1 -2
- package/lib/codegen/languages/ts.js.map +1 -1
- package/lib/codegen/parser.js +2 -3
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +3 -3
- package/lib/codegen/types.js.map +1 -1
- package/lib/decoder/DecodeOperation.d.ts +0 -1
- package/lib/decoder/DecodeOperation.js +22 -7
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +1 -2
- package/lib/decoder/Decoder.js +4 -4
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/strategy/RawChanges.js +1 -2
- package/lib/decoder/strategy/RawChanges.js.map +1 -1
- package/lib/decoder/strategy/StateCallbacks.d.ts +36 -7
- package/lib/decoder/strategy/StateCallbacks.js +39 -46
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.js +2 -5
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +0 -1
- package/lib/encoder/EncodeOperation.js +29 -13
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +5 -4
- package/lib/encoder/Encoder.js +56 -38
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/StateView.js +11 -6
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/assert.js +3 -3
- package/lib/encoding/assert.js.map +1 -1
- package/lib/encoding/decode.d.ts +21 -19
- package/lib/encoding/decode.js +24 -25
- package/lib/encoding/decode.js.map +1 -1
- package/lib/encoding/encode.d.ts +2 -2
- package/lib/encoding/encode.js +40 -39
- package/lib/encoding/encode.js.map +1 -1
- package/lib/encoding/spec.d.ts +2 -1
- package/lib/encoding/spec.js +1 -0
- package/lib/encoding/spec.js.map +1 -1
- package/lib/index.d.ts +3 -0
- package/lib/index.js +5 -1
- package/lib/index.js.map +1 -1
- package/lib/types/custom/ArraySchema.d.ts +2 -2
- package/lib/types/custom/ArraySchema.js +0 -8
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/registry.js +3 -4
- package/lib/types/registry.js.map +1 -1
- package/lib/types/utils.js +1 -2
- package/lib/types/utils.js.map +1 -1
- package/lib/utils.js +3 -4
- package/lib/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/Metadata.ts +47 -0
- package/src/Reflection.ts +31 -22
- package/src/annotations.ts +6 -2
- package/src/bench_encode.ts +66 -0
- package/src/decoder/DecodeOperation.ts +26 -6
- package/src/decoder/Decoder.ts +5 -5
- package/src/decoder/strategy/StateCallbacks.ts +93 -53
- package/src/encoder/ChangeTree.ts +2 -4
- package/src/encoder/EncodeOperation.ts +29 -12
- package/src/encoder/Encoder.ts +65 -41
- package/src/encoder/StateView.ts +10 -7
- package/src/encoding/decode.ts +24 -25
- package/src/encoding/encode.ts +25 -22
- package/src/encoding/spec.ts +1 -0
- package/src/index.ts +5 -0
- package/src/types/custom/ArraySchema.ts +2 -1
|
@@ -4,7 +4,7 @@ import { Schema } from "../Schema";
|
|
|
4
4
|
import type { Ref } from "../encoder/ChangeTree";
|
|
5
5
|
import type { Decoder } from "./Decoder";
|
|
6
6
|
import * as decode from "../encoding/decode";
|
|
7
|
-
import { $
|
|
7
|
+
import { $childType, $deleteByIndex, $getByIndex } from "../types/symbols";
|
|
8
8
|
|
|
9
9
|
import type { MapSchema } from "../types/custom/MapSchema";
|
|
10
10
|
import type { ArraySchema } from "../types/custom/ArraySchema";
|
|
@@ -43,7 +43,7 @@ export function decodeValue(
|
|
|
43
43
|
it: decode.Iterator,
|
|
44
44
|
allChanges: DataChange[],
|
|
45
45
|
) {
|
|
46
|
-
const $root = decoder
|
|
46
|
+
const $root = decoder.root;
|
|
47
47
|
const previousValue = ref[$getByIndex](index);
|
|
48
48
|
|
|
49
49
|
let value: any;
|
|
@@ -251,6 +251,7 @@ export const decodeKeyValueOperation: DecodeOperation = function (
|
|
|
251
251
|
dynamicIndex = ref['getIndex'](index);
|
|
252
252
|
}
|
|
253
253
|
|
|
254
|
+
|
|
254
255
|
const { value, previousValue } = decodeValue(
|
|
255
256
|
decoder,
|
|
256
257
|
operation,
|
|
@@ -303,7 +304,10 @@ export const decodeArray: DecodeOperation = function (
|
|
|
303
304
|
allChanges: DataChange[]
|
|
304
305
|
) {
|
|
305
306
|
// "uncompressed" index + operation (array/map items)
|
|
306
|
-
|
|
307
|
+
let operation = bytes[it.offset++];
|
|
308
|
+
|
|
309
|
+
let isSchemaChild: boolean;
|
|
310
|
+
let index: number;
|
|
307
311
|
|
|
308
312
|
if (operation === OPERATION.CLEAR) {
|
|
309
313
|
//
|
|
@@ -318,8 +322,8 @@ export const decodeArray: DecodeOperation = function (
|
|
|
318
322
|
} else if (operation === OPERATION.DELETE_BY_REFID) {
|
|
319
323
|
// TODO: refactor here, try to follow same flow as below
|
|
320
324
|
const refId = decode.number(bytes, it);
|
|
321
|
-
const previousValue = decoder
|
|
322
|
-
|
|
325
|
+
const previousValue = decoder.root.refs.get(refId);
|
|
326
|
+
index = ref.findIndex((value) => value === previousValue);
|
|
323
327
|
ref[$deleteByIndex](index);
|
|
324
328
|
allChanges.push({
|
|
325
329
|
ref,
|
|
@@ -330,10 +334,26 @@ export const decodeArray: DecodeOperation = function (
|
|
|
330
334
|
value: undefined,
|
|
331
335
|
previousValue,
|
|
332
336
|
});
|
|
337
|
+
|
|
333
338
|
return;
|
|
339
|
+
|
|
340
|
+
} else if (operation === OPERATION.ADD_BY_REFID) {
|
|
341
|
+
isSchemaChild = true;
|
|
342
|
+
// operation = OPERATION.ADD;
|
|
343
|
+
|
|
344
|
+
const refId = decode.number(bytes, it);
|
|
345
|
+
const itemByRefId = decoder.root.refs.get(refId);
|
|
346
|
+
|
|
347
|
+
// use existing index, or push new value
|
|
348
|
+
index = (itemByRefId)
|
|
349
|
+
? ref.findIndex((value) => value === itemByRefId)
|
|
350
|
+
: ref.length;
|
|
351
|
+
|
|
352
|
+
} else {
|
|
353
|
+
isSchemaChild = false;
|
|
354
|
+
index = decode.number(bytes, it);
|
|
334
355
|
}
|
|
335
356
|
|
|
336
|
-
const index = decode.number(bytes, it);
|
|
337
357
|
const type = ref[$childType];
|
|
338
358
|
|
|
339
359
|
let dynamicIndex: number | string = index;
|
package/src/decoder/Decoder.ts
CHANGED
|
@@ -14,7 +14,7 @@ export class Decoder<T extends Schema = any> {
|
|
|
14
14
|
context: TypeContext;
|
|
15
15
|
|
|
16
16
|
state: T;
|
|
17
|
-
|
|
17
|
+
root: ReferenceTracker;
|
|
18
18
|
|
|
19
19
|
currentRefId: number = 0;
|
|
20
20
|
|
|
@@ -32,8 +32,8 @@ export class Decoder<T extends Schema = any> {
|
|
|
32
32
|
|
|
33
33
|
protected setRoot(root: T) {
|
|
34
34
|
this.state = root;
|
|
35
|
-
this
|
|
36
|
-
this
|
|
35
|
+
this.root = new ReferenceTracker();
|
|
36
|
+
this.root.addRef(0, root);
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
decode(
|
|
@@ -43,7 +43,7 @@ export class Decoder<T extends Schema = any> {
|
|
|
43
43
|
) {
|
|
44
44
|
const allChanges: DataChange[] = [];
|
|
45
45
|
|
|
46
|
-
const $root = this
|
|
46
|
+
const $root = this.root;
|
|
47
47
|
const totalBytes = bytes.byteLength;
|
|
48
48
|
|
|
49
49
|
let decoder: DecodeOperation = ref['constructor'][$decoder];
|
|
@@ -146,7 +146,7 @@ export class Decoder<T extends Schema = any> {
|
|
|
146
146
|
});
|
|
147
147
|
|
|
148
148
|
if (needRemoveRef) {
|
|
149
|
-
this
|
|
149
|
+
this.root.removeRef(this.root.refIds.get(value));
|
|
150
150
|
}
|
|
151
151
|
});
|
|
152
152
|
}
|
|
@@ -17,27 +17,69 @@ import type { ArraySchema } from "../../types/custom/ArraySchema";
|
|
|
17
17
|
// - Avoid closures by allowing to pass a context. (https://github.com/colyseus/schema/issues/155#issuecomment-1804694081)
|
|
18
18
|
//
|
|
19
19
|
|
|
20
|
-
type
|
|
20
|
+
export type GetCallbackProxy = (<T extends Schema>(instance: T) => CallbackProxy<T>);
|
|
21
|
+
|
|
22
|
+
export type CallbackProxy<T> = unknown extends T // is "any"?
|
|
21
23
|
? InstanceCallback<T> & CollectionCallback<any, any>
|
|
22
24
|
: T extends Collection<infer K, infer V, infer _>
|
|
23
25
|
? CollectionCallback<K, V>
|
|
24
|
-
: InstanceCallback<T
|
|
26
|
+
: InstanceCallback<T>;
|
|
25
27
|
|
|
26
28
|
type InstanceCallback<T> = {
|
|
29
|
+
/**
|
|
30
|
+
* Trigger callback when value of a property changes.
|
|
31
|
+
*
|
|
32
|
+
* @param prop name of the property
|
|
33
|
+
* @param callback callback to be triggered on property change
|
|
34
|
+
* @param immediate trigger immediatelly if property has been already set.
|
|
35
|
+
*/
|
|
27
36
|
listen<K extends NonFunctionPropNames<T>>(
|
|
28
37
|
prop: K,
|
|
29
38
|
callback: (value: T[K], previousValue: T[K]) => void,
|
|
30
39
|
immediate?: boolean,
|
|
31
40
|
)
|
|
41
|
+
/**
|
|
42
|
+
* Trigger callback whenever any property changed within this instance.
|
|
43
|
+
*
|
|
44
|
+
* @param prop name of the property
|
|
45
|
+
* @param callback callback to be triggered on property change
|
|
46
|
+
* @param immediate trigger immediatelly if property has been already set.
|
|
47
|
+
*/
|
|
32
48
|
onChange(callback: () => void): void;
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Bind properties to another object. Changes on the properties will be reflected on the target object.
|
|
52
|
+
*
|
|
53
|
+
* @param targetObject object to bind properties to
|
|
54
|
+
* @param properties list of properties to bind. If not provided, all properties will be bound.
|
|
55
|
+
*/
|
|
33
56
|
bindTo(targetObject: any, properties?: Array<NonFunctionPropNames<T>>): void;
|
|
34
57
|
} & {
|
|
35
|
-
[K in NonFunctionNonPrimitivePropNames<T>]:
|
|
58
|
+
[K in NonFunctionNonPrimitivePropNames<T>]: CallbackProxy<T[K]>;
|
|
36
59
|
}
|
|
37
60
|
|
|
38
61
|
type CollectionCallback<K, V> = {
|
|
62
|
+
/**
|
|
63
|
+
* Trigger callback when an item has been added to the collection.
|
|
64
|
+
*
|
|
65
|
+
* @param callback
|
|
66
|
+
* @param immediate
|
|
67
|
+
*/
|
|
39
68
|
onAdd(callback: (item: V, index: K) => void, immediate?: boolean): void;
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Trigger callback when an item has been removed to the collection.
|
|
72
|
+
*
|
|
73
|
+
* @param callback
|
|
74
|
+
*/
|
|
40
75
|
onRemove(callback: (item: V, index: K) => void): void;
|
|
76
|
+
|
|
77
|
+
// /**
|
|
78
|
+
// * Trigger callback when an item has been removed to the collection.
|
|
79
|
+
// *
|
|
80
|
+
// * @param callback
|
|
81
|
+
// */
|
|
82
|
+
// onChange(callback: (item: V, index: K) => void): void;
|
|
41
83
|
};
|
|
42
84
|
|
|
43
85
|
type OnInstanceAvailableCallback = (callback: (ref: Ref, existing: boolean) => void) => void;
|
|
@@ -48,8 +90,9 @@ type CallContext = {
|
|
|
48
90
|
onInstanceAvailable?: OnInstanceAvailableCallback,
|
|
49
91
|
}
|
|
50
92
|
|
|
51
|
-
|
|
52
|
-
|
|
93
|
+
|
|
94
|
+
export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>): GetCallbackProxy {
|
|
95
|
+
const $root = decoder.root;
|
|
53
96
|
const callbacks = $root.callbacks;
|
|
54
97
|
|
|
55
98
|
let isTriggeringOnAdd = false;
|
|
@@ -76,8 +119,6 @@ export function getStateCallbacks(decoder: Decoder) {
|
|
|
76
119
|
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
77
120
|
deleteCallbacks[i]();
|
|
78
121
|
}
|
|
79
|
-
// callbacks[$root.refIds.get(change.previousValue)]?.[OPERATION.DELETE]?.forEach(callback =>
|
|
80
|
-
// callback());
|
|
81
122
|
}
|
|
82
123
|
|
|
83
124
|
if (ref instanceof Schema) {
|
|
@@ -86,47 +127,35 @@ export function getStateCallbacks(decoder: Decoder) {
|
|
|
86
127
|
//
|
|
87
128
|
|
|
88
129
|
if (!uniqueRefIds.has(refId)) {
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
console.error(e);
|
|
130
|
+
// trigger onChange
|
|
131
|
+
const replaceCallbacks = $callbacks?.[OPERATION.REPLACE];
|
|
132
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
133
|
+
replaceCallbacks[i]();
|
|
134
|
+
// try {
|
|
135
|
+
// } catch (e) {
|
|
136
|
+
// console.error(e);
|
|
137
|
+
// }
|
|
98
138
|
}
|
|
99
139
|
}
|
|
100
140
|
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
141
|
+
if ($callbacks.hasOwnProperty(change.field)) {
|
|
142
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
143
|
+
for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
|
|
144
|
+
fieldCallbacks[i](change.value, change.previousValue);
|
|
145
|
+
// try {
|
|
146
|
+
// } catch (e) {
|
|
147
|
+
// console.error(e);
|
|
148
|
+
// }
|
|
107
149
|
}
|
|
108
|
-
|
|
109
|
-
} catch (e) {
|
|
110
|
-
//
|
|
111
|
-
console.error(e);
|
|
112
150
|
}
|
|
113
151
|
|
|
152
|
+
|
|
114
153
|
} else {
|
|
115
154
|
//
|
|
116
155
|
// Handle collection of items
|
|
117
156
|
//
|
|
118
157
|
|
|
119
|
-
if (change.op
|
|
120
|
-
// triger onAdd
|
|
121
|
-
|
|
122
|
-
isTriggeringOnAdd = true;
|
|
123
|
-
const addCallbacks = $callbacks[OPERATION.ADD];
|
|
124
|
-
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
125
|
-
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
126
|
-
}
|
|
127
|
-
isTriggeringOnAdd = false;
|
|
128
|
-
|
|
129
|
-
} else if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
|
|
158
|
+
if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
|
|
130
159
|
//
|
|
131
160
|
// FIXME: `previousValue` should always be available.
|
|
132
161
|
//
|
|
@@ -146,6 +175,16 @@ export function getStateCallbacks(decoder: Decoder) {
|
|
|
146
175
|
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
147
176
|
}
|
|
148
177
|
}
|
|
178
|
+
|
|
179
|
+
} else if ((change.op & OPERATION.ADD) === OPERATION.ADD && change.previousValue === undefined) {
|
|
180
|
+
// triger onAdd
|
|
181
|
+
|
|
182
|
+
isTriggeringOnAdd = true;
|
|
183
|
+
const addCallbacks = $callbacks[OPERATION.ADD];
|
|
184
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
185
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
186
|
+
}
|
|
187
|
+
isTriggeringOnAdd = false;
|
|
149
188
|
}
|
|
150
189
|
|
|
151
190
|
// trigger onChange
|
|
@@ -206,10 +245,22 @@ export function getStateCallbacks(decoder: Decoder) {
|
|
|
206
245
|
OPERATION.REPLACE,
|
|
207
246
|
callback
|
|
208
247
|
);
|
|
209
|
-
|
|
210
248
|
},
|
|
211
249
|
bindTo: function bindTo(targetObject: any, properties?: string[]) {
|
|
212
|
-
|
|
250
|
+
//
|
|
251
|
+
// TODO: refactor this implementation. There is room for improvement here.
|
|
252
|
+
//
|
|
253
|
+
if (!properties) {
|
|
254
|
+
properties = Object.keys(metadata);
|
|
255
|
+
}
|
|
256
|
+
return $root.addCallback(
|
|
257
|
+
$root.refIds.get(context.instance),
|
|
258
|
+
OPERATION.REPLACE,
|
|
259
|
+
() => {
|
|
260
|
+
properties.forEach((prop) =>
|
|
261
|
+
targetObject[prop] = context.instance[prop])
|
|
262
|
+
}
|
|
263
|
+
);
|
|
213
264
|
}
|
|
214
265
|
}, {
|
|
215
266
|
get(target, prop: string) {
|
|
@@ -307,20 +358,9 @@ export function getStateCallbacks(decoder: Decoder) {
|
|
|
307
358
|
}
|
|
308
359
|
}
|
|
309
360
|
|
|
310
|
-
function $<T>(instance: T):
|
|
311
|
-
return getProxy(undefined, { instance }) as
|
|
312
|
-
}
|
|
313
|
-
|
|
314
|
-
return {
|
|
315
|
-
$,
|
|
316
|
-
trigger: function trigger(changes: DataChange[]) {
|
|
317
|
-
for (let i = 0, l = changes.length; i < l; i++) {
|
|
318
|
-
const change = changes[i];
|
|
319
|
-
|
|
320
|
-
change.op
|
|
321
|
-
change.ref
|
|
322
|
-
}
|
|
323
|
-
}
|
|
361
|
+
function $<T>(instance: T): CallbackProxy<T> {
|
|
362
|
+
return getProxy(undefined, { instance }) as CallbackProxy<T>;
|
|
324
363
|
}
|
|
325
364
|
|
|
365
|
+
return $;
|
|
326
366
|
}
|
|
@@ -170,13 +170,12 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
170
170
|
|
|
171
171
|
if (!this.isFiltered) {
|
|
172
172
|
this.root.changes.set(this, this.changes);
|
|
173
|
+
this.root.allChanges.set(this, this.allChanges);
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
176
177
|
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
177
178
|
this.root.allFilteredChanges.set(this, this.filteredChanges);
|
|
178
|
-
} else {
|
|
179
|
-
this.root.allChanges.set(this, this.allChanges);
|
|
180
179
|
}
|
|
181
180
|
|
|
182
181
|
this.ensureRefId();
|
|
@@ -285,7 +284,6 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
285
284
|
|
|
286
285
|
private _shiftAllChangeIndexes(shiftIndex: number, startIndex: number = 0, allChangeSet: Map<number, OPERATION>) {
|
|
287
286
|
Array.from(allChangeSet.entries()).forEach(([index, op]) => {
|
|
288
|
-
// console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
|
|
289
287
|
if (index >= startIndex) {
|
|
290
288
|
allChangeSet.delete(index);
|
|
291
289
|
allChangeSet.set(index + shiftIndex, op);
|
|
@@ -452,7 +450,7 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
452
450
|
|
|
453
451
|
protected checkIsFiltered(parent: Ref, parentIndex: number) {
|
|
454
452
|
// Detect if current structure has "filters" declared
|
|
455
|
-
this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
|
|
453
|
+
this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
|
|
456
454
|
|
|
457
455
|
// TODO: support "partially filtered", where the instance is visible, but only a field is not.
|
|
458
456
|
|
|
@@ -172,6 +172,19 @@ export const encodeKeyValueOperation: EncodeOperation = function (
|
|
|
172
172
|
const type = changeTree.getType(field);
|
|
173
173
|
const value = changeTree.getValue(field);
|
|
174
174
|
|
|
175
|
+
// try { throw new Error(); } catch (e) {
|
|
176
|
+
// // only print if not coming from Reflection.ts
|
|
177
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
178
|
+
// console.log("encodeKeyValueOperation -> ", {
|
|
179
|
+
// ref: changeTree.ref.constructor.name,
|
|
180
|
+
// field,
|
|
181
|
+
// operation: OPERATION[operation],
|
|
182
|
+
// value: value?.toJSON(),
|
|
183
|
+
// items: ref.toJSON(),
|
|
184
|
+
// });
|
|
185
|
+
// }
|
|
186
|
+
// }
|
|
187
|
+
|
|
175
188
|
// TODO: inline this function call small performance gain
|
|
176
189
|
encodeValue(encoder, bytes, ref, type, value, field, operation, it);
|
|
177
190
|
}
|
|
@@ -191,18 +204,22 @@ export const encodeArray: EncodeOperation = function (
|
|
|
191
204
|
hasView: boolean,
|
|
192
205
|
) {
|
|
193
206
|
const ref = changeTree.ref;
|
|
207
|
+
const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
|
|
194
208
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
209
|
+
let refOrIndex: number;
|
|
210
|
+
|
|
211
|
+
if (useOperationByRefId) {
|
|
212
|
+
refOrIndex = ref['tmpItems'][field][$changes].refId;
|
|
213
|
+
|
|
214
|
+
if (operation === OPERATION.DELETE) {
|
|
215
|
+
operation = OPERATION.DELETE_BY_REFID;
|
|
216
|
+
|
|
217
|
+
} else if (operation === OPERATION.ADD) {
|
|
218
|
+
operation = OPERATION.ADD_BY_REFID;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
} else {
|
|
222
|
+
refOrIndex = field;
|
|
206
223
|
}
|
|
207
224
|
|
|
208
225
|
// encode operation
|
|
@@ -214,7 +231,7 @@ export const encodeArray: EncodeOperation = function (
|
|
|
214
231
|
}
|
|
215
232
|
|
|
216
233
|
// encode index
|
|
217
|
-
encode.number(bytes,
|
|
234
|
+
encode.number(bytes, refOrIndex, it);
|
|
218
235
|
|
|
219
236
|
// Do not encode value for DELETE operations
|
|
220
237
|
if (operation === OPERATION.DELETE) {
|
package/src/encoder/Encoder.ts
CHANGED
|
@@ -5,10 +5,11 @@ import { $changes, $encoder, $filter } from "../types/symbols";
|
|
|
5
5
|
import * as encode from "../encoding/encode";
|
|
6
6
|
import type { Iterator } from "../encoding/decode";
|
|
7
7
|
|
|
8
|
-
import { SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
|
|
8
|
+
import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
|
|
9
9
|
import { Root } from "./ChangeTree";
|
|
10
10
|
import { getNextPowerOf2 } from "../utils";
|
|
11
11
|
import type { StateView } from "./StateView";
|
|
12
|
+
import { Metadata } from "../Metadata";
|
|
12
13
|
|
|
13
14
|
export class Encoder<T extends Schema = any> {
|
|
14
15
|
static BUFFER_SIZE = 8 * 1024;// 8KB
|
|
@@ -37,18 +38,24 @@ export class Encoder<T extends Schema = any> {
|
|
|
37
38
|
protected setRoot(state: T) {
|
|
38
39
|
this.root = new Root();
|
|
39
40
|
this.state = state;
|
|
41
|
+
|
|
42
|
+
// Workaround to allow using an empty Schema.
|
|
43
|
+
if (state.constructor[Symbol.metadata] === undefined) {
|
|
44
|
+
Metadata.init(state);
|
|
45
|
+
}
|
|
46
|
+
|
|
40
47
|
state[$changes].setRoot(this.root);
|
|
41
48
|
}
|
|
42
49
|
|
|
43
50
|
encode(
|
|
44
51
|
it: Iterator = { offset: 0 },
|
|
45
52
|
view?: StateView,
|
|
46
|
-
|
|
47
|
-
changeTrees = this.root.changes
|
|
53
|
+
buffer = this.sharedBuffer,
|
|
54
|
+
changeTrees = this.root.changes,
|
|
55
|
+
isEncodeAll = this.root.allChanges === changeTrees,
|
|
48
56
|
): Buffer {
|
|
49
57
|
const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
|
|
50
58
|
|
|
51
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
52
59
|
const hasView = (view !== undefined);
|
|
53
60
|
const rootChangeTree = this.state[$changes];
|
|
54
61
|
|
|
@@ -61,6 +68,13 @@ export class Encoder<T extends Schema = any> {
|
|
|
61
68
|
const encoder = ctor[$encoder];
|
|
62
69
|
const filter = ctor[$filter];
|
|
63
70
|
|
|
71
|
+
// try { throw new Error(); } catch (e) {
|
|
72
|
+
// // only print if not coming from Reflection.ts
|
|
73
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
74
|
+
// console.log("ChangeTree:", { ref: ref.constructor.name, });
|
|
75
|
+
// }
|
|
76
|
+
// }
|
|
77
|
+
|
|
64
78
|
if (hasView) {
|
|
65
79
|
if (!view.items.has(changeTree)) {
|
|
66
80
|
view.invisible.add(changeTree);
|
|
@@ -73,8 +87,8 @@ export class Encoder<T extends Schema = any> {
|
|
|
73
87
|
|
|
74
88
|
// skip root `refId` if it's the first change tree
|
|
75
89
|
if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
|
|
76
|
-
|
|
77
|
-
encode.number(
|
|
90
|
+
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
91
|
+
encode.number(buffer, changeTree.refId, it);
|
|
78
92
|
}
|
|
79
93
|
|
|
80
94
|
const changesIterator = changes.entries();
|
|
@@ -95,25 +109,36 @@ export class Encoder<T extends Schema = any> {
|
|
|
95
109
|
continue;
|
|
96
110
|
}
|
|
97
111
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
103
|
-
|
|
104
|
-
|
|
112
|
+
// try { throw new Error(); } catch (e) {
|
|
113
|
+
// // only print if not coming from Reflection.ts
|
|
114
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
115
|
+
// console.log("WILL ENCODE", {
|
|
116
|
+
// ref: changeTree.ref.constructor.name,
|
|
117
|
+
// fieldIndex,
|
|
118
|
+
// operation: OPERATION[operation],
|
|
119
|
+
// });
|
|
120
|
+
// }
|
|
121
|
+
// }
|
|
122
|
+
|
|
123
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
|
|
105
124
|
}
|
|
106
125
|
}
|
|
107
126
|
|
|
108
|
-
if (it.offset >
|
|
109
|
-
const newSize = getNextPowerOf2(
|
|
110
|
-
console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " +
|
|
127
|
+
if (it.offset > buffer.byteLength) {
|
|
128
|
+
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
129
|
+
console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + buffer.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
|
|
111
130
|
|
|
112
131
|
//
|
|
113
132
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
114
133
|
//
|
|
115
|
-
|
|
116
|
-
|
|
134
|
+
buffer = Buffer.allocUnsafeSlow(newSize);
|
|
135
|
+
|
|
136
|
+
// assign resized buffer to local sharedBuffer
|
|
137
|
+
if (buffer === this.sharedBuffer) {
|
|
138
|
+
this.sharedBuffer = buffer;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
|
|
117
142
|
|
|
118
143
|
} else {
|
|
119
144
|
//
|
|
@@ -126,47 +151,46 @@ export class Encoder<T extends Schema = any> {
|
|
|
126
151
|
this.onEndEncode(changeTrees);
|
|
127
152
|
}
|
|
128
153
|
|
|
129
|
-
|
|
130
|
-
return bytes.slice(0, it.offset);
|
|
154
|
+
return buffer.subarray(0, it.offset);
|
|
131
155
|
}
|
|
132
156
|
}
|
|
133
157
|
|
|
134
|
-
encodeAll(it: Iterator = { offset: 0 }) {
|
|
135
|
-
// console.log(`encodeAll(), this
|
|
158
|
+
encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
|
|
159
|
+
// console.log(`encodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
|
|
136
160
|
|
|
137
|
-
// Array.from(this
|
|
138
|
-
// console.log("->", item[0].
|
|
161
|
+
// Array.from(this.root.allChanges.entries()).map((item) => {
|
|
162
|
+
// console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
|
|
139
163
|
// });
|
|
140
164
|
|
|
141
|
-
return this.encode(it, undefined,
|
|
165
|
+
return this.encode(it, undefined, buffer, this.root.allChanges, true);
|
|
142
166
|
}
|
|
143
167
|
|
|
144
168
|
encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
|
|
145
169
|
const viewOffset = it.offset;
|
|
146
170
|
|
|
147
|
-
// console.log(`encodeAllView(), this
|
|
171
|
+
// console.log(`encodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
|
|
148
172
|
// this.debugAllFilteredChanges();
|
|
149
173
|
|
|
150
174
|
// try to encode "filtered" changes
|
|
151
|
-
this.encode(it, view, bytes, this.root.allFilteredChanges);
|
|
175
|
+
this.encode(it, view, bytes, this.root.allFilteredChanges, true);
|
|
152
176
|
|
|
153
177
|
return Buffer.concat([
|
|
154
|
-
bytes.
|
|
155
|
-
bytes.
|
|
178
|
+
bytes.subarray(0, sharedOffset),
|
|
179
|
+
bytes.subarray(viewOffset, it.offset)
|
|
156
180
|
]);
|
|
157
181
|
}
|
|
158
182
|
|
|
159
183
|
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
184
|
+
debugAllFilteredChanges() {
|
|
185
|
+
Array.from(this.root.allFilteredChanges.entries()).map((item) => {
|
|
186
|
+
console.log("->", { refId: item[0].refId, changes: item[1].size }, item[0].ref.toJSON());
|
|
187
|
+
if (Array.isArray(item[0].ref.toJSON())) {
|
|
188
|
+
item[1].forEach((op, key) => {
|
|
189
|
+
console.log(" ->", { key, op: OPERATION[op] });
|
|
190
|
+
})
|
|
191
|
+
}
|
|
192
|
+
});
|
|
193
|
+
}
|
|
170
194
|
|
|
171
195
|
encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
|
|
172
196
|
const viewOffset = it.offset;
|
|
@@ -208,8 +232,8 @@ export class Encoder<T extends Schema = any> {
|
|
|
208
232
|
view.changes.clear();
|
|
209
233
|
|
|
210
234
|
return Buffer.concat([
|
|
211
|
-
bytes.
|
|
212
|
-
bytes.
|
|
235
|
+
bytes.subarray(0, sharedOffset),
|
|
236
|
+
bytes.subarray(viewOffset, it.offset)
|
|
213
237
|
]);
|
|
214
238
|
}
|
|
215
239
|
|
package/src/encoder/StateView.ts
CHANGED
|
@@ -34,15 +34,20 @@ export class StateView {
|
|
|
34
34
|
return this;
|
|
35
35
|
}
|
|
36
36
|
|
|
37
|
+
// FIXME: ArraySchema/MapSchema does not have metadata
|
|
38
|
+
const metadata: Metadata = obj.constructor[Symbol.metadata];
|
|
39
|
+
|
|
37
40
|
let changeTree: ChangeTree = obj[$changes];
|
|
38
41
|
this.items.add(changeTree);
|
|
39
42
|
|
|
40
43
|
// Add children of this ChangeTree to this view
|
|
41
|
-
changeTree.forEachChild((change,
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
44
|
+
changeTree.forEachChild((change, index) => {
|
|
45
|
+
// Do not ADD children that don't have the same tag
|
|
46
|
+
if (metadata && metadata[metadata[index]].tag !== tag) {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
this.add(change.ref, tag);
|
|
50
|
+
});
|
|
46
51
|
|
|
47
52
|
// add parent ChangeTree's, if they are invisible to this view
|
|
48
53
|
// TODO: REFACTOR addParent()
|
|
@@ -72,8 +77,6 @@ export class StateView {
|
|
|
72
77
|
}
|
|
73
78
|
tags.add(tag);
|
|
74
79
|
|
|
75
|
-
// console.log("BY TAG:", tag);
|
|
76
|
-
|
|
77
80
|
// Ref: add tagged properties
|
|
78
81
|
metadata?.[-3]?.[tag]?.forEach((index) => {
|
|
79
82
|
if (changeTree.getChange(index) !== OPERATION.DELETE) {
|