@colyseus/schema 3.0.0-alpha.15 → 3.0.0-alpha.16

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.
@@ -21,5 +21,6 @@ export declare enum OPERATION {
21
21
  UNSHIFT = 12,
22
22
  REVERSE = 15,
23
23
  MOVE = 32,
24
- DELETE_BY_REFID = 33
24
+ DELETE_BY_REFID = 33,// This operation is only used at ENCODING time. During DECODING, DELETE_BY_REFID is converted to DELETE
25
+ ADD_BY_REFID = 129
25
26
  }
@@ -26,5 +26,6 @@ var OPERATION;
26
26
  OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
27
27
  OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
28
28
  OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
29
+ OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
29
30
  })(OPERATION || (exports.OPERATION = OPERATION = {}));
30
31
  //# sourceMappingURL=spec.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/encoding/spec.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG,GAAG,CAAC,CAAC,4DAA4D;AACvF,QAAA,OAAO,GAAG,GAAG,CAAC;AAE3B;;GAEG;AACH,IAAY,SAsBX;AAtBD,WAAY,SAAS;IACjB,yCAAS,CAAA;IACT,+CAAW,CAAA;IACX,8CAAW,CAAA;IACX,gEAAoB,CAAA;IACpB,2DAAkB,CAAA;IAClB,+DAAoB,CAAA;IAEpB;;OAEG;IACH,4CAAU,CAAA;IAEV;;OAEG;IACH,0CAAS,CAAA;IACT,gDAAY,CAAA;IACZ,gDAAY,CAAA;IACZ,0CAAS,CAAA;IACT,gEAAoB,CAAA;AAExB,CAAC,EAtBW,SAAS,yBAAT,SAAS,QAsBpB","sourcesContent":["export const SWITCH_TO_STRUCTURE = 255; // (decoding collides with DELETE_AND_ADD + fieldIndex = 63)\nexport const TYPE_ID = 213;\n\n/**\n * Encoding Schema field operations.\n */\nexport enum OPERATION {\n ADD = 128, // (10000000) add new structure/primitive\n REPLACE = 0, // (00000001) replace structure/primitive\n DELETE = 64, // (01000000) delete field\n DELETE_AND_MOVE = 96, // () add new structure/primitive\n MOVE_AND_ADD = 160, // () add new structure/primitive\n DELETE_AND_ADD = 192, // (11000000) DELETE field, followed by an ADD\n\n /**\n * Collection operations\n */\n CLEAR = 10,\n\n /**\n * ArraySchema operations\n */\n PUSH = 11,\n UNSHIFT = 12,\n REVERSE = 15,\n MOVE = 32,\n DELETE_BY_REFID = 33, // This operation is only used at ENCODING time. During DECODING, DELETE_BY_REFID is converted to DELETE\n\n}\n"]}
1
+ {"version":3,"file":"spec.js","sourceRoot":"","sources":["../../src/encoding/spec.ts"],"names":[],"mappings":";;;AAAa,QAAA,mBAAmB,GAAG,GAAG,CAAC,CAAC,4DAA4D;AACvF,QAAA,OAAO,GAAG,GAAG,CAAC;AAE3B;;GAEG;AACH,IAAY,SAuBX;AAvBD,WAAY,SAAS;IACjB,yCAAS,CAAA;IACT,+CAAW,CAAA;IACX,8CAAW,CAAA;IACX,gEAAoB,CAAA;IACpB,2DAAkB,CAAA;IAClB,+DAAoB,CAAA;IAEpB;;OAEG;IACH,4CAAU,CAAA;IAEV;;OAEG;IACH,0CAAS,CAAA;IACT,gDAAY,CAAA;IACZ,gDAAY,CAAA;IACZ,0CAAS,CAAA;IACT,gEAAoB,CAAA;IACpB,2DAAkB,CAAA;AAEtB,CAAC,EAvBW,SAAS,yBAAT,SAAS,QAuBpB","sourcesContent":["export const SWITCH_TO_STRUCTURE = 255; // (decoding collides with DELETE_AND_ADD + fieldIndex = 63)\nexport const TYPE_ID = 213;\n\n/**\n * Encoding Schema field operations.\n */\nexport enum OPERATION {\n ADD = 128, // (10000000) add new structure/primitive\n REPLACE = 0, // (00000001) replace structure/primitive\n DELETE = 64, // (01000000) delete field\n DELETE_AND_MOVE = 96, // () add new structure/primitive\n MOVE_AND_ADD = 160, // () add new structure/primitive\n DELETE_AND_ADD = 192, // (11000000) DELETE field, followed by an ADD\n\n /**\n * Collection operations\n */\n CLEAR = 10,\n\n /**\n * ArraySchema operations\n */\n PUSH = 11,\n UNSHIFT = 12,\n REVERSE = 15,\n MOVE = 32,\n DELETE_BY_REFID = 33, // This operation is only used at ENCODING time. During DECODING, DELETE_BY_REFID is converted to DELETE\n ADD_BY_REFID = 129,\n\n}\n"]}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@colyseus/schema",
3
- "version": "3.0.0-alpha.15",
3
+ "version": "3.0.0-alpha.16",
4
4
  "description": "Binary state serializer with delta encoding for games",
5
5
  "bin": {
6
6
  "schema-codegen": "./bin/schema-codegen"
@@ -78,7 +78,7 @@
78
78
  "source-map-support": "^0.5.13",
79
79
  "ts-node": "^10.9.2",
80
80
  "tslib": "^2.1.0",
81
- "tsx": "^4.7.0",
81
+ "tsx": "^4.15.7",
82
82
  "typescript": "^5.3.3"
83
83
  },
84
84
  "nyc": {
@@ -0,0 +1,66 @@
1
+ import { nanoid } from "nanoid";
2
+ import { Schema, type, MapSchema, ArraySchema, Encoder } from ".";
3
+ import * as benchmark from "benchmark";
4
+
5
+ const suite = new benchmark.Suite();
6
+
7
+ class AttributeNew extends Schema {
8
+ @type("string") name: string;
9
+ @type("number") value: number;
10
+ }
11
+
12
+ class ItemNew extends Schema {
13
+ @type("number") price: number;
14
+ @type([ AttributeNew ]) attributes = new ArraySchema<AttributeNew>();
15
+ }
16
+
17
+ class PositionNew extends Schema {
18
+ @type("number") x: number;
19
+ @type("number") y: number;
20
+ }
21
+
22
+ class PlayerNew extends Schema {
23
+ @type(PositionNew) position = new PositionNew();
24
+ @type({ map: ItemNew }) items = new MapSchema<ItemNew>();
25
+ }
26
+
27
+ class StateNew extends Schema {
28
+ @type({ map: PlayerNew }) players = new MapSchema<PlayerNew>();
29
+ @type("string") currentTurn;
30
+ }
31
+
32
+ const state = new StateNew();
33
+
34
+ for (let i = 0; i < 50; i++) {
35
+ const player = new PlayerNew();
36
+ state.players.set(`p-${nanoid()}`, player);
37
+
38
+ player.position.x = (i + 1) * 100;
39
+ player.position.y = (i + 1) * 100;
40
+ for (let j = 0; j < 10; j++) {
41
+ const item = new ItemNew();
42
+ item.price = (i + 1) * 50;
43
+ for (let k = 0; k < 5; k++) {
44
+ const attr = new AttributeNew();
45
+ attr.name = `Attribute ${k}`;
46
+ attr.value = k;
47
+ item.attributes.push(attr);
48
+
49
+ }
50
+ player.items.set(`item-${j}`, item);
51
+ }
52
+ }
53
+
54
+
55
+ Encoder.BUFFER_SIZE = 1024 * 128;
56
+ const encoder = new Encoder(state);
57
+
58
+ // measure time to .encodeAll()
59
+
60
+ const now = Date.now();
61
+ for (let i = 0; i < 1000; i++) {
62
+ encoder.encodeAll();
63
+ }
64
+ console.log(Date.now() - now);
65
+
66
+ console.log(Array.from(encoder.encodeAll()).join(","));
@@ -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
- const operation = bytes[it.offset++];
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
  //
@@ -319,7 +323,7 @@ export const decodeArray: DecodeOperation = function (
319
323
  // TODO: refactor here, try to follow same flow as below
320
324
  const refId = decode.number(bytes, it);
321
325
  const previousValue = decoder.root.refs.get(refId);
322
- const index = ref.findIndex((value) => value === previousValue);
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;
@@ -155,17 +155,7 @@ export function getStateCallbacks<T extends Schema>(decoder: Decoder<T>): GetCal
155
155
  // Handle collection of items
156
156
  //
157
157
 
158
- if (change.op === OPERATION.ADD && change.previousValue === undefined) {
159
- // triger onAdd
160
-
161
- isTriggeringOnAdd = true;
162
- const addCallbacks = $callbacks[OPERATION.ADD];
163
- for (let i = addCallbacks?.length - 1; i >= 0; i--) {
164
- addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
165
- }
166
- isTriggeringOnAdd = false;
167
-
168
- } else if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
158
+ if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
169
159
  //
170
160
  // FIXME: `previousValue` should always be available.
171
161
  //
@@ -185,6 +175,16 @@ export function getStateCallbacks<T extends Schema>(decoder: Decoder<T>): GetCal
185
175
  addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
186
176
  }
187
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;
188
188
  }
189
189
 
190
190
  // trigger onChange
@@ -204,18 +204,22 @@ export const encodeArray: EncodeOperation = function (
204
204
  hasView: boolean,
205
205
  ) {
206
206
  const ref = changeTree.ref;
207
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
207
208
 
208
- if (
209
- hasView &&
210
- operation === OPERATION.DELETE &&
211
- typeof (changeTree.getType(field)) !== "string"
212
- ) {
213
- // encode delete by refId (array of schemas)
214
- bytes[it.offset++] = OPERATION.DELETE_BY_REFID;
215
- const value = ref['tmpItems'][field];
216
- const refId = value[$changes].refId;
217
- encode.number(bytes, refId, it);
218
- return;
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;
219
223
  }
220
224
 
221
225
  // encode operation
@@ -227,7 +231,7 @@ export const encodeArray: EncodeOperation = function (
227
231
  }
228
232
 
229
233
  // encode index
230
- encode.number(bytes, field, it);
234
+ encode.number(bytes, refOrIndex, it);
231
235
 
232
236
  // Do not encode value for DELETE operations
233
237
  if (operation === OPERATION.DELETE) {
@@ -25,5 +25,6 @@ export enum OPERATION {
25
25
  REVERSE = 15,
26
26
  MOVE = 32,
27
27
  DELETE_BY_REFID = 33, // This operation is only used at ENCODING time. During DECODING, DELETE_BY_REFID is converted to DELETE
28
+ ADD_BY_REFID = 129,
28
29
 
29
30
  }