@colyseus/schema 3.0.0-alpha.9 → 3.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 +148 -62
- package/bin/schema-debug +94 -0
- package/build/cjs/index.js +2222 -1513
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +2223 -1516
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +2225 -1516
- package/lib/Metadata.d.ts +21 -9
- package/lib/Metadata.js +169 -32
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +19 -4
- package/lib/Reflection.js +66 -31
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +12 -5
- package/lib/Schema.js +57 -56
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +31 -34
- package/lib/annotations.js +110 -160
- package/lib/annotations.js.map +1 -1
- 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 +9 -46
- package/lib/codegen/languages/csharp.js.map +1 -1
- package/lib/codegen/languages/haxe.js +4 -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 +23 -25
- 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 +85 -3
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +6 -3
- package/lib/codegen/types.js.map +1 -1
- package/lib/decoder/DecodeOperation.d.ts +3 -4
- package/lib/decoder/DecodeOperation.js +35 -17
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +5 -6
- package/lib/decoder/Decoder.js +10 -10
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/ReferenceTracker.js +4 -2
- package/lib/decoder/ReferenceTracker.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 +44 -11
- package/lib/decoder/strategy/StateCallbacks.js +74 -64
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +28 -20
- package/lib/encoder/ChangeTree.js +242 -188
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +3 -6
- package/lib/encoder/EncodeOperation.js +51 -65
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +8 -7
- package/lib/encoder/Encoder.js +128 -79
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +22 -0
- package/lib/encoder/Root.js +81 -0
- package/lib/encoder/Root.js.map +1 -0
- package/lib/encoder/StateView.d.ts +7 -7
- package/lib/encoder/StateView.js +72 -74
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/assert.d.ts +7 -6
- package/lib/encoding/assert.js +13 -5
- package/lib/encoding/assert.js.map +1 -1
- package/lib/encoding/decode.d.ts +36 -19
- package/lib/encoding/decode.js +54 -84
- package/lib/encoding/decode.js.map +1 -1
- package/lib/encoding/encode.d.ts +36 -18
- package/lib/encoding/encode.js +61 -48
- package/lib/encoding/encode.js.map +1 -1
- package/lib/encoding/spec.d.ts +4 -5
- package/lib/encoding/spec.js +1 -2
- package/lib/encoding/spec.js.map +1 -1
- package/lib/index.d.ts +10 -9
- package/lib/index.js +24 -17
- package/lib/index.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +34 -2
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/TypeContext.d.ts +29 -0
- package/lib/types/TypeContext.js +151 -0
- package/lib/types/TypeContext.js.map +1 -0
- package/lib/types/custom/ArraySchema.d.ts +2 -2
- package/lib/types/custom/ArraySchema.js +33 -22
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.d.ts +2 -2
- package/lib/types/custom/CollectionSchema.js +1 -0
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +18 -16
- package/lib/types/custom/MapSchema.js +12 -4
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.d.ts +2 -2
- package/lib/types/custom/SetSchema.js +1 -0
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/registry.d.ts +8 -1
- package/lib/types/registry.js +23 -6
- package/lib/types/registry.js.map +1 -1
- package/lib/types/symbols.d.ts +8 -5
- package/lib/types/symbols.js +9 -6
- package/lib/types/symbols.js.map +1 -1
- package/lib/types/utils.js +1 -2
- package/lib/types/utils.js.map +1 -1
- package/lib/utils.js +9 -7
- package/lib/utils.js.map +1 -1
- package/package.json +19 -18
- package/src/Metadata.ts +190 -42
- package/src/Reflection.ts +76 -38
- package/src/Schema.ts +72 -70
- package/src/annotations.ts +156 -202
- package/src/codegen/languages/csharp.ts +8 -47
- package/src/codegen/languages/haxe.ts +4 -0
- package/src/codegen/languages/lua.ts +19 -27
- package/src/codegen/parser.ts +107 -0
- package/src/codegen/types.ts +1 -0
- package/src/decoder/DecodeOperation.ts +43 -15
- package/src/decoder/Decoder.ts +12 -10
- package/src/decoder/ReferenceTracker.ts +5 -3
- package/src/decoder/strategy/StateCallbacks.ts +152 -81
- package/src/encoder/ChangeTree.ts +282 -209
- package/src/encoder/EncodeOperation.ts +78 -78
- package/src/encoder/Encoder.ts +152 -88
- package/src/encoder/Root.ts +93 -0
- package/src/encoder/StateView.ts +80 -88
- package/src/encoding/assert.ts +17 -8
- package/src/encoding/decode.ts +73 -93
- package/src/encoding/encode.ts +76 -45
- package/src/encoding/spec.ts +3 -5
- package/src/index.ts +12 -20
- package/src/types/HelperTypes.ts +54 -2
- package/src/types/TypeContext.ts +175 -0
- package/src/types/custom/ArraySchema.ts +49 -19
- package/src/types/custom/CollectionSchema.ts +1 -0
- package/src/types/custom/MapSchema.ts +30 -17
- package/src/types/custom/SetSchema.ts +1 -0
- package/src/types/registry.ts +22 -3
- package/src/types/symbols.ts +10 -7
- package/src/utils.ts +7 -3
- 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/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
|
@@ -1,17 +1,15 @@
|
|
|
1
1
|
import { OPERATION } from "../encoding/spec";
|
|
2
|
-
import { $changes } from "../types/symbols";
|
|
3
|
-
import { getType } from "../types/registry";
|
|
2
|
+
import { $changes, $childType, $getByIndex } from "../types/symbols";
|
|
4
3
|
|
|
5
|
-
import
|
|
6
|
-
import { EncodeSchemaError, assertInstanceType, assertType } from "../encoding/assert";
|
|
4
|
+
import { encode } from "../encoding/encode";
|
|
7
5
|
|
|
8
6
|
import type { ChangeTree, Ref } from "./ChangeTree";
|
|
9
7
|
import type { Encoder } from "./Encoder";
|
|
10
8
|
import type { Schema } from "../Schema";
|
|
11
|
-
import type { PrimitiveType } from "../annotations";
|
|
12
9
|
|
|
13
10
|
import type { Iterator } from "../encoding/decode";
|
|
14
11
|
import type { ArraySchema } from "../types/custom/ArraySchema";
|
|
12
|
+
import type { Metadata } from "../Metadata";
|
|
15
13
|
|
|
16
14
|
export type EncodeOperation<T extends Ref = any> = (
|
|
17
15
|
encoder: Encoder,
|
|
@@ -22,43 +20,21 @@ export type EncodeOperation<T extends Ref = any> = (
|
|
|
22
20
|
it: Iterator,
|
|
23
21
|
isEncodeAll: boolean,
|
|
24
22
|
hasView: boolean,
|
|
23
|
+
metadata?: Metadata,
|
|
25
24
|
) => void;
|
|
26
25
|
|
|
27
|
-
export function encodePrimitiveType(
|
|
28
|
-
type: PrimitiveType,
|
|
29
|
-
bytes: Buffer,
|
|
30
|
-
value: any,
|
|
31
|
-
klass: Schema,
|
|
32
|
-
field: string | number,
|
|
33
|
-
it: Iterator,
|
|
34
|
-
) {
|
|
35
|
-
assertType(value, type as string, klass, field);
|
|
36
|
-
|
|
37
|
-
const encodeFunc = encode[type as string];
|
|
38
|
-
|
|
39
|
-
if (encodeFunc) {
|
|
40
|
-
encodeFunc(bytes, value, it);
|
|
41
|
-
// encodeFunc(bytes, value);
|
|
42
|
-
|
|
43
|
-
} else {
|
|
44
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
45
|
-
}
|
|
46
|
-
};
|
|
47
|
-
|
|
48
26
|
export function encodeValue(
|
|
49
27
|
encoder: Encoder,
|
|
50
28
|
bytes: Buffer,
|
|
51
|
-
ref: Ref,
|
|
52
29
|
type: any,
|
|
53
30
|
value: any,
|
|
54
|
-
field: string | number,
|
|
55
31
|
operation: OPERATION,
|
|
56
32
|
it: Iterator,
|
|
57
33
|
) {
|
|
58
|
-
if (type
|
|
59
|
-
|
|
60
|
-
assertInstanceType(value, type as typeof Schema, ref as Schema, field);
|
|
34
|
+
if (typeof (type) === "string") {
|
|
35
|
+
encode[type]?.(bytes, value, it);
|
|
61
36
|
|
|
37
|
+
} else if (type[Symbol.metadata] !== undefined) {
|
|
62
38
|
//
|
|
63
39
|
// Encode refId for this instance.
|
|
64
40
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -70,23 +46,7 @@ export function encodeValue(
|
|
|
70
46
|
encoder.tryEncodeTypeId(bytes, type as typeof Schema, value.constructor as typeof Schema, it);
|
|
71
47
|
}
|
|
72
48
|
|
|
73
|
-
} else if (typeof (type) === "string") {
|
|
74
|
-
//
|
|
75
|
-
// Primitive values
|
|
76
|
-
//
|
|
77
|
-
encodePrimitiveType(type as PrimitiveType, bytes, value, ref as Schema, field, it);
|
|
78
|
-
|
|
79
49
|
} else {
|
|
80
|
-
//
|
|
81
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
82
|
-
//
|
|
83
|
-
const definition = getType(Object.keys(type)[0]);
|
|
84
|
-
|
|
85
|
-
//
|
|
86
|
-
// ensure a ArraySchema has been provided
|
|
87
|
-
//
|
|
88
|
-
assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
|
|
89
|
-
|
|
90
50
|
//
|
|
91
51
|
// Encode refId for this instance.
|
|
92
52
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -106,14 +66,10 @@ export const encodeSchemaOperation: EncodeOperation = function (
|
|
|
106
66
|
index: number,
|
|
107
67
|
operation: OPERATION,
|
|
108
68
|
it: Iterator,
|
|
69
|
+
_: any,
|
|
70
|
+
__: any,
|
|
71
|
+
metadata: Metadata,
|
|
109
72
|
) {
|
|
110
|
-
const ref = changeTree.ref;
|
|
111
|
-
const metadata = ref['constructor'][Symbol.metadata];
|
|
112
|
-
|
|
113
|
-
const field = metadata[index];
|
|
114
|
-
const type = metadata[field].type;
|
|
115
|
-
const value = ref[field];
|
|
116
|
-
|
|
117
73
|
// "compress" field index + operation
|
|
118
74
|
bytes[it.offset++] = (index | operation) & 255;
|
|
119
75
|
|
|
@@ -122,8 +78,18 @@ export const encodeSchemaOperation: EncodeOperation = function (
|
|
|
122
78
|
return;
|
|
123
79
|
}
|
|
124
80
|
|
|
81
|
+
const ref = changeTree.ref;
|
|
82
|
+
const field = metadata[index];
|
|
83
|
+
|
|
125
84
|
// TODO: inline this function call small performance gain
|
|
126
|
-
encodeValue(
|
|
85
|
+
encodeValue(
|
|
86
|
+
encoder,
|
|
87
|
+
bytes,
|
|
88
|
+
metadata[index].type,
|
|
89
|
+
ref[field.name],
|
|
90
|
+
operation,
|
|
91
|
+
it
|
|
92
|
+
);
|
|
127
93
|
}
|
|
128
94
|
|
|
129
95
|
/**
|
|
@@ -134,12 +100,10 @@ export const encodeKeyValueOperation: EncodeOperation = function (
|
|
|
134
100
|
encoder: Encoder,
|
|
135
101
|
bytes: Buffer,
|
|
136
102
|
changeTree: ChangeTree,
|
|
137
|
-
|
|
103
|
+
index: number,
|
|
138
104
|
operation: OPERATION,
|
|
139
105
|
it: Iterator,
|
|
140
106
|
) {
|
|
141
|
-
const ref = changeTree.ref;
|
|
142
|
-
|
|
143
107
|
// encode operation
|
|
144
108
|
bytes[it.offset++] = operation & 255;
|
|
145
109
|
|
|
@@ -149,31 +113,53 @@ export const encodeKeyValueOperation: EncodeOperation = function (
|
|
|
149
113
|
}
|
|
150
114
|
|
|
151
115
|
// encode index
|
|
152
|
-
encode.number(bytes,
|
|
116
|
+
encode.number(bytes, index, it);
|
|
153
117
|
|
|
154
118
|
// Do not encode value for DELETE operations
|
|
155
119
|
if (operation === OPERATION.DELETE) {
|
|
156
120
|
return;
|
|
157
121
|
}
|
|
158
122
|
|
|
123
|
+
const ref = changeTree.ref;
|
|
124
|
+
|
|
159
125
|
//
|
|
160
126
|
// encode "alias" for dynamic fields (maps)
|
|
161
127
|
//
|
|
162
|
-
if ((operation & OPERATION.ADD)
|
|
128
|
+
if ((operation & OPERATION.ADD) === OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
163
129
|
if (typeof(ref['set']) === "function") {
|
|
164
130
|
//
|
|
165
131
|
// MapSchema dynamic key
|
|
166
132
|
//
|
|
167
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(
|
|
133
|
+
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
168
134
|
encode.string(bytes, dynamicIndex, it);
|
|
169
135
|
}
|
|
170
136
|
}
|
|
171
137
|
|
|
172
|
-
const type =
|
|
173
|
-
const value =
|
|
138
|
+
const type = ref[$childType];
|
|
139
|
+
const value = ref[$getByIndex](index);
|
|
140
|
+
|
|
141
|
+
// try { throw new Error(); } catch (e) {
|
|
142
|
+
// // only print if not coming from Reflection.ts
|
|
143
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
144
|
+
// console.log("encodeKeyValueOperation -> ", {
|
|
145
|
+
// ref: changeTree.ref.constructor.name,
|
|
146
|
+
// field,
|
|
147
|
+
// operation: OPERATION[operation],
|
|
148
|
+
// value: value?.toJSON(),
|
|
149
|
+
// items: ref.toJSON(),
|
|
150
|
+
// });
|
|
151
|
+
// }
|
|
152
|
+
// }
|
|
174
153
|
|
|
175
154
|
// TODO: inline this function call small performance gain
|
|
176
|
-
encodeValue(
|
|
155
|
+
encodeValue(
|
|
156
|
+
encoder,
|
|
157
|
+
bytes,
|
|
158
|
+
type,
|
|
159
|
+
value,
|
|
160
|
+
operation,
|
|
161
|
+
it
|
|
162
|
+
);
|
|
177
163
|
}
|
|
178
164
|
|
|
179
165
|
/**
|
|
@@ -191,30 +177,37 @@ export const encodeArray: EncodeOperation = function (
|
|
|
191
177
|
hasView: boolean,
|
|
192
178
|
) {
|
|
193
179
|
const ref = changeTree.ref;
|
|
180
|
+
const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
|
|
194
181
|
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
182
|
+
let refOrIndex: number;
|
|
183
|
+
|
|
184
|
+
if (useOperationByRefId) {
|
|
185
|
+
refOrIndex = ref['tmpItems'][field][$changes].refId;
|
|
186
|
+
|
|
187
|
+
if (operation === OPERATION.DELETE) {
|
|
188
|
+
operation = OPERATION.DELETE_BY_REFID;
|
|
189
|
+
|
|
190
|
+
} else if (operation === OPERATION.ADD) {
|
|
191
|
+
operation = OPERATION.ADD_BY_REFID;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
} else {
|
|
195
|
+
refOrIndex = field;
|
|
206
196
|
}
|
|
207
197
|
|
|
208
198
|
// encode operation
|
|
209
199
|
bytes[it.offset++] = operation & 255;
|
|
210
200
|
|
|
211
201
|
// custom operations
|
|
212
|
-
if (
|
|
202
|
+
if (
|
|
203
|
+
operation === OPERATION.CLEAR ||
|
|
204
|
+
operation === OPERATION.REVERSE
|
|
205
|
+
) {
|
|
213
206
|
return;
|
|
214
207
|
}
|
|
215
208
|
|
|
216
209
|
// encode index
|
|
217
|
-
encode.number(bytes,
|
|
210
|
+
encode.number(bytes, refOrIndex, it);
|
|
218
211
|
|
|
219
212
|
// Do not encode value for DELETE operations
|
|
220
213
|
if (operation === OPERATION.DELETE) {
|
|
@@ -233,5 +226,12 @@ export const encodeArray: EncodeOperation = function (
|
|
|
233
226
|
// });
|
|
234
227
|
|
|
235
228
|
// TODO: inline this function call small performance gain
|
|
236
|
-
encodeValue(
|
|
229
|
+
encodeValue(
|
|
230
|
+
encoder,
|
|
231
|
+
bytes,
|
|
232
|
+
type,
|
|
233
|
+
value,
|
|
234
|
+
operation,
|
|
235
|
+
it
|
|
236
|
+
);
|
|
237
237
|
}
|
package/src/encoder/Encoder.ts
CHANGED
|
@@ -1,32 +1,34 @@
|
|
|
1
1
|
import type { Schema } from "../Schema";
|
|
2
|
-
import { TypeContext } from "../
|
|
3
|
-
import { $changes, $encoder, $filter } from "../types/symbols";
|
|
2
|
+
import { TypeContext } from "../types/TypeContext";
|
|
3
|
+
import { $changes, $encoder, $filter, $onEncodeEnd } from "../types/symbols";
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { encode } from "../encoding/encode";
|
|
6
6
|
import type { Iterator } from "../encoding/decode";
|
|
7
7
|
|
|
8
|
-
import { SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
|
|
9
|
-
import { Root } from "./
|
|
10
|
-
|
|
8
|
+
import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
|
|
9
|
+
import { Root } from "./Root";
|
|
10
|
+
|
|
11
11
|
import type { StateView } from "./StateView";
|
|
12
|
+
import type { Metadata } from "../Metadata";
|
|
12
13
|
|
|
13
14
|
export class Encoder<T extends Schema = any> {
|
|
14
|
-
static BUFFER_SIZE = 8 * 1024
|
|
15
|
-
sharedBuffer = Buffer.
|
|
15
|
+
static BUFFER_SIZE = (typeof(Buffer) !== "undefined") && Buffer.poolSize || 8 * 1024; // 8KB
|
|
16
|
+
sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
|
|
16
17
|
|
|
17
18
|
context: TypeContext;
|
|
18
19
|
state: T;
|
|
19
20
|
|
|
20
21
|
root: Root;
|
|
21
22
|
|
|
22
|
-
constructor(
|
|
23
|
-
this.setRoot(root);
|
|
24
|
-
|
|
23
|
+
constructor(state: T) {
|
|
25
24
|
//
|
|
26
25
|
// TODO: cache and restore "Context" based on root schema
|
|
27
26
|
// (to avoid creating a new context for every new room)
|
|
28
27
|
//
|
|
29
|
-
this.context = new TypeContext(
|
|
28
|
+
this.context = new TypeContext(state.constructor as typeof Schema);
|
|
29
|
+
this.root = new Root(this.context);
|
|
30
|
+
|
|
31
|
+
this.setState(state);
|
|
30
32
|
|
|
31
33
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
32
34
|
// this.context.schemas.forEach((id, schema) => {
|
|
@@ -34,32 +36,35 @@ export class Encoder<T extends Schema = any> {
|
|
|
34
36
|
// });
|
|
35
37
|
}
|
|
36
38
|
|
|
37
|
-
protected
|
|
38
|
-
this.root = new Root();
|
|
39
|
+
protected setState(state: T) {
|
|
39
40
|
this.state = state;
|
|
40
|
-
state[$changes].setRoot(this.root);
|
|
41
|
+
this.state[$changes].setRoot(this.root);
|
|
41
42
|
}
|
|
42
43
|
|
|
43
44
|
encode(
|
|
44
45
|
it: Iterator = { offset: 0 },
|
|
45
46
|
view?: StateView,
|
|
46
47
|
buffer = this.sharedBuffer,
|
|
47
|
-
|
|
48
|
+
changeSetName: "changes" | "allChanges" | "filteredChanges" | "allFilteredChanges" = "changes",
|
|
49
|
+
isEncodeAll = changeSetName === "allChanges",
|
|
50
|
+
initialOffset = it.offset // cache current offset in case we need to resize the buffer
|
|
48
51
|
): Buffer {
|
|
49
|
-
const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
|
|
50
|
-
|
|
51
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
52
52
|
const hasView = (view !== undefined);
|
|
53
53
|
const rootChangeTree = this.state[$changes];
|
|
54
54
|
|
|
55
|
-
const
|
|
55
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
56
|
+
const changeTrees = this.root[changeSetName];
|
|
57
|
+
|
|
58
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
59
|
+
const changeTree = changeTrees[i];
|
|
56
60
|
|
|
57
|
-
|
|
61
|
+
const operations = changeTree[changeSetName];
|
|
58
62
|
const ref = changeTree.ref;
|
|
59
63
|
|
|
60
|
-
const ctor = ref
|
|
64
|
+
const ctor = ref.constructor;
|
|
61
65
|
const encoder = ctor[$encoder];
|
|
62
66
|
const filter = ctor[$filter];
|
|
67
|
+
const metadata = ctor[Symbol.metadata];
|
|
63
68
|
|
|
64
69
|
if (hasView) {
|
|
65
70
|
if (!view.items.has(changeTree)) {
|
|
@@ -72,14 +77,21 @@ export class Encoder<T extends Schema = any> {
|
|
|
72
77
|
}
|
|
73
78
|
|
|
74
79
|
// skip root `refId` if it's the first change tree
|
|
75
|
-
|
|
80
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
81
|
+
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
76
82
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
77
83
|
encode.number(buffer, changeTree.refId, it);
|
|
78
84
|
}
|
|
79
85
|
|
|
80
|
-
|
|
86
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
87
|
+
const fieldIndex = operations.operations[j];
|
|
88
|
+
|
|
89
|
+
const operation = (fieldIndex < 0)
|
|
90
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
91
|
+
: (isEncodeAll)
|
|
92
|
+
? OPERATION.ADD
|
|
93
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
81
94
|
|
|
82
|
-
for (const [fieldIndex, operation] of changesIterator) {
|
|
83
95
|
//
|
|
84
96
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
85
97
|
// they will be encoded per client, based on their view.
|
|
@@ -87,49 +99,59 @@ export class Encoder<T extends Schema = any> {
|
|
|
87
99
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
88
100
|
// TODO: avoid checking if no view tags were defined
|
|
89
101
|
//
|
|
90
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
91
|
-
// console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
|
|
92
|
-
|
|
102
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
93
103
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
94
104
|
// view?.invisible.add(changeTree);
|
|
95
105
|
continue;
|
|
96
106
|
}
|
|
97
107
|
|
|
98
|
-
|
|
99
|
-
// ref: changeTree.ref.constructor.name,
|
|
100
|
-
// fieldIndex,
|
|
101
|
-
// operation: OPERATION[operation],
|
|
102
|
-
// });
|
|
103
|
-
|
|
104
|
-
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
|
|
108
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
105
109
|
}
|
|
110
|
+
|
|
111
|
+
// if (shouldDiscardChanges) {
|
|
112
|
+
// changeTree.discard();
|
|
113
|
+
// changeTree.isNew = false; // Not a new instance anymore
|
|
114
|
+
// }
|
|
106
115
|
}
|
|
107
116
|
|
|
108
117
|
if (it.offset > buffer.byteLength) {
|
|
109
|
-
|
|
110
|
-
|
|
118
|
+
// we can assume that n + 1 poolSize will suffice given that we are likely done with encoding at this point
|
|
119
|
+
// multiples of poolSize are faster to allocate than arbitrary sizes
|
|
120
|
+
// if we are on an older platform that doesn't implement pooling use 8kb as poolSize (that's the default for node)
|
|
121
|
+
const newSize = Math.ceil(it.offset / (Buffer.poolSize ?? 8 * 1024)) * (Buffer.poolSize ?? 8 * 1024);
|
|
122
|
+
|
|
123
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
124
|
+
|
|
125
|
+
import { Encoder } from "@colyseus/schema";
|
|
126
|
+
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
127
|
+
`);
|
|
111
128
|
|
|
112
129
|
//
|
|
113
130
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
131
|
+
// -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
|
|
114
132
|
//
|
|
115
|
-
buffer = Buffer.
|
|
133
|
+
buffer = Buffer.alloc(newSize, buffer); // fill with buffer here to memcpy previous encoding steps beyond the initialOffset
|
|
116
134
|
|
|
117
135
|
// assign resized buffer to local sharedBuffer
|
|
118
136
|
if (buffer === this.sharedBuffer) {
|
|
119
137
|
this.sharedBuffer = buffer;
|
|
120
138
|
}
|
|
121
139
|
|
|
122
|
-
return this.encode({ offset: initialOffset }, view, buffer);
|
|
140
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
123
141
|
|
|
124
142
|
} else {
|
|
125
143
|
//
|
|
126
144
|
// only clear changes after making sure buffer resize is not required.
|
|
127
145
|
//
|
|
128
|
-
if (
|
|
146
|
+
if (shouldDiscardChanges) {
|
|
129
147
|
//
|
|
130
|
-
//
|
|
148
|
+
// TODO: avoid iterating over change trees twice.
|
|
131
149
|
//
|
|
132
|
-
|
|
150
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
151
|
+
const changeTree = changeTrees[i];
|
|
152
|
+
changeTree.discard();
|
|
153
|
+
changeTree.isNew = false; // Not a new instance anymore
|
|
154
|
+
}
|
|
133
155
|
}
|
|
134
156
|
|
|
135
157
|
return buffer.subarray(0, it.offset);
|
|
@@ -137,23 +159,14 @@ export class Encoder<T extends Schema = any> {
|
|
|
137
159
|
}
|
|
138
160
|
|
|
139
161
|
encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
// Array.from(this.root.allChanges.entries()).map((item) => {
|
|
143
|
-
// console.log("->", item[0].refId, item[0].ref.toJSON());
|
|
144
|
-
// });
|
|
145
|
-
|
|
146
|
-
return this.encode(it, undefined, buffer, this.root.allChanges);
|
|
162
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
147
163
|
}
|
|
148
164
|
|
|
149
165
|
encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
|
|
150
166
|
const viewOffset = it.offset;
|
|
151
167
|
|
|
152
|
-
// console.log(`encodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
|
|
153
|
-
// this.debugAllFilteredChanges();
|
|
154
|
-
|
|
155
168
|
// try to encode "filtered" changes
|
|
156
|
-
this.encode(it, view, bytes,
|
|
169
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
157
170
|
|
|
158
171
|
return Buffer.concat([
|
|
159
172
|
bytes.subarray(0, sharedOffset),
|
|
@@ -161,47 +174,63 @@ export class Encoder<T extends Schema = any> {
|
|
|
161
174
|
]);
|
|
162
175
|
}
|
|
163
176
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
177
|
+
debugChanges(field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges") {
|
|
178
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
179
|
+
? this.root[field]
|
|
180
|
+
: field;
|
|
181
|
+
|
|
182
|
+
rootChangeSet.forEach((changeTree) => {
|
|
183
|
+
const changeSet = changeTree[field];
|
|
184
|
+
|
|
185
|
+
const metadata: Metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
186
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
187
|
+
for (const index in changeSet) {
|
|
188
|
+
const op = changeSet[index];
|
|
189
|
+
console.log(" ->", {
|
|
190
|
+
index,
|
|
191
|
+
field: metadata?.[index],
|
|
192
|
+
op: OPERATION[op],
|
|
193
|
+
});
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
}
|
|
175
197
|
|
|
176
198
|
encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
|
|
177
199
|
const viewOffset = it.offset;
|
|
178
200
|
|
|
179
|
-
// try to encode "filtered" changes
|
|
180
|
-
this.encode(it, view, bytes, this.root.filteredChanges);
|
|
181
|
-
|
|
182
201
|
// encode visibility changes (add/remove for this view)
|
|
183
|
-
const
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
202
|
+
const refIds = Object.keys(view.changes);
|
|
203
|
+
|
|
204
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
205
|
+
const refId = refIds[i];
|
|
206
|
+
const changes = view.changes[refId];
|
|
207
|
+
const changeTree = this.root.changeTrees[refId];
|
|
208
|
+
|
|
209
|
+
if (
|
|
210
|
+
changeTree === undefined ||
|
|
211
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
212
|
+
) {
|
|
213
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
188
214
|
continue;
|
|
189
215
|
}
|
|
190
216
|
|
|
191
217
|
const ref = changeTree.ref;
|
|
192
218
|
|
|
193
|
-
const ctor = ref
|
|
219
|
+
const ctor = ref.constructor;
|
|
194
220
|
const encoder = ctor[$encoder];
|
|
221
|
+
const metadata = ctor[Symbol.metadata];
|
|
195
222
|
|
|
196
223
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
197
224
|
encode.number(bytes, changeTree.refId, it);
|
|
198
225
|
|
|
199
|
-
const
|
|
226
|
+
const keys = Object.keys(changes);
|
|
227
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
228
|
+
const key = keys[i];
|
|
229
|
+
const operation = changes[key];
|
|
200
230
|
|
|
201
|
-
for (const [fieldIndex, operation] of changesIterator) {
|
|
202
231
|
// isEncodeAll = false
|
|
203
232
|
// hasView = true
|
|
204
|
-
encoder(this, bytes, changeTree,
|
|
233
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
205
234
|
}
|
|
206
235
|
}
|
|
207
236
|
|
|
@@ -210,7 +239,10 @@ export class Encoder<T extends Schema = any> {
|
|
|
210
239
|
// (to allow re-using StateView's for multiple clients)
|
|
211
240
|
//
|
|
212
241
|
// clear "view" changes after encoding
|
|
213
|
-
view.changes
|
|
242
|
+
view.changes = {};
|
|
243
|
+
|
|
244
|
+
// try to encode "filtered" changes
|
|
245
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
214
246
|
|
|
215
247
|
return Buffer.concat([
|
|
216
248
|
bytes.subarray(0, sharedOffset),
|
|
@@ -219,22 +251,42 @@ export class Encoder<T extends Schema = any> {
|
|
|
219
251
|
}
|
|
220
252
|
|
|
221
253
|
onEndEncode(changeTrees = this.root.changes) {
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
254
|
+
// changeTrees.forEach(function(changeTree) {
|
|
255
|
+
// changeTree.endEncode();
|
|
256
|
+
// });
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
// for (const refId in changeTrees) {
|
|
260
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
261
|
+
// changeTree.endEncode();
|
|
262
|
+
|
|
263
|
+
// // changeTree.changes.clear();
|
|
264
|
+
|
|
265
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
266
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
267
|
+
|
|
268
|
+
// // // Not a new instance anymore
|
|
269
|
+
// // delete changeTree[$isNew];
|
|
270
|
+
// }
|
|
226
271
|
}
|
|
227
272
|
|
|
228
273
|
discardChanges() {
|
|
229
274
|
// discard shared changes
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
275
|
+
let length = this.root.changes.length;
|
|
276
|
+
if (length > 0) {
|
|
277
|
+
while (length--) {
|
|
278
|
+
this.root.changes[length]?.endEncode();
|
|
279
|
+
}
|
|
280
|
+
this.root.changes.length = 0;
|
|
233
281
|
}
|
|
282
|
+
|
|
234
283
|
// discard filtered changes
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
284
|
+
length = this.root.filteredChanges.length;
|
|
285
|
+
if (length > 0) {
|
|
286
|
+
while (length--) {
|
|
287
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
288
|
+
}
|
|
289
|
+
this.root.filteredChanges.length = 0;
|
|
238
290
|
}
|
|
239
291
|
}
|
|
240
292
|
|
|
@@ -242,9 +294,21 @@ export class Encoder<T extends Schema = any> {
|
|
|
242
294
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
243
295
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
244
296
|
|
|
297
|
+
if (targetTypeId === undefined) {
|
|
298
|
+
console.warn(`@colyseus/schema WARNING: Class "${targetType.name}" is not registered on TypeRegistry - Please either tag the class with @entity or define a @type() field.`);
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
|
|
245
302
|
if (baseTypeId !== targetTypeId) {
|
|
246
303
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
247
304
|
encode.number(bytes, targetTypeId, it);
|
|
248
305
|
}
|
|
249
306
|
}
|
|
250
|
-
|
|
307
|
+
|
|
308
|
+
get hasChanges() {
|
|
309
|
+
return (
|
|
310
|
+
this.root.changes.length > 0 ||
|
|
311
|
+
this.root.filteredChanges.length > 0
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
}
|