@colyseus/schema 3.0.0-alpha.4 → 3.0.0-alpha.40

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.
Files changed (141) hide show
  1. package/README.md +131 -61
  2. package/bin/schema-debug +94 -0
  3. package/build/cjs/index.js +1521 -809
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/esm/index.mjs +1519 -808
  6. package/build/esm/index.mjs.map +1 -1
  7. package/build/umd/index.js +1528 -816
  8. package/lib/Metadata.d.ts +21 -9
  9. package/lib/Metadata.js +169 -32
  10. package/lib/Metadata.js.map +1 -1
  11. package/lib/Reflection.d.ts +19 -4
  12. package/lib/Reflection.js +66 -32
  13. package/lib/Reflection.js.map +1 -1
  14. package/lib/Schema.d.ts +4 -4
  15. package/lib/Schema.js +44 -50
  16. package/lib/Schema.js.map +1 -1
  17. package/lib/annotations.d.ts +31 -34
  18. package/lib/annotations.js +110 -160
  19. package/lib/annotations.js.map +1 -1
  20. package/lib/bench_encode.d.ts +1 -0
  21. package/lib/bench_encode.js +130 -0
  22. package/lib/bench_encode.js.map +1 -0
  23. package/lib/codegen/api.js +1 -2
  24. package/lib/codegen/api.js.map +1 -1
  25. package/lib/codegen/languages/cpp.js +1 -2
  26. package/lib/codegen/languages/cpp.js.map +1 -1
  27. package/lib/codegen/languages/csharp.js +1 -2
  28. package/lib/codegen/languages/csharp.js.map +1 -1
  29. package/lib/codegen/languages/haxe.js +1 -2
  30. package/lib/codegen/languages/haxe.js.map +1 -1
  31. package/lib/codegen/languages/java.js +1 -2
  32. package/lib/codegen/languages/java.js.map +1 -1
  33. package/lib/codegen/languages/js.js +1 -2
  34. package/lib/codegen/languages/js.js.map +1 -1
  35. package/lib/codegen/languages/lua.js +1 -2
  36. package/lib/codegen/languages/lua.js.map +1 -1
  37. package/lib/codegen/languages/ts.js +1 -2
  38. package/lib/codegen/languages/ts.js.map +1 -1
  39. package/lib/codegen/parser.js +85 -3
  40. package/lib/codegen/parser.js.map +1 -1
  41. package/lib/codegen/types.js +6 -3
  42. package/lib/codegen/types.js.map +1 -1
  43. package/lib/debug.d.ts +1 -0
  44. package/lib/debug.js +51 -0
  45. package/lib/debug.js.map +1 -0
  46. package/lib/decoder/DecodeOperation.d.ts +0 -1
  47. package/lib/decoder/DecodeOperation.js +30 -12
  48. package/lib/decoder/DecodeOperation.js.map +1 -1
  49. package/lib/decoder/Decoder.d.ts +6 -7
  50. package/lib/decoder/Decoder.js +9 -9
  51. package/lib/decoder/Decoder.js.map +1 -1
  52. package/lib/decoder/ReferenceTracker.js +3 -2
  53. package/lib/decoder/ReferenceTracker.js.map +1 -1
  54. package/lib/decoder/strategy/RawChanges.js +1 -2
  55. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  56. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  57. package/lib/decoder/strategy/StateCallbacks.js +75 -65
  58. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  59. package/lib/encoder/ChangeTree.d.ts +27 -21
  60. package/lib/encoder/ChangeTree.js +246 -186
  61. package/lib/encoder/ChangeTree.js.map +1 -1
  62. package/lib/encoder/EncodeOperation.d.ts +3 -6
  63. package/lib/encoder/EncodeOperation.js +47 -61
  64. package/lib/encoder/EncodeOperation.js.map +1 -1
  65. package/lib/encoder/Encoder.d.ts +9 -8
  66. package/lib/encoder/Encoder.js +165 -88
  67. package/lib/encoder/Encoder.js.map +1 -1
  68. package/lib/encoder/Root.d.ts +22 -0
  69. package/lib/encoder/Root.js +81 -0
  70. package/lib/encoder/Root.js.map +1 -0
  71. package/lib/encoder/StateView.d.ts +7 -7
  72. package/lib/encoder/StateView.js +70 -74
  73. package/lib/encoder/StateView.js.map +1 -1
  74. package/lib/encoding/assert.d.ts +2 -1
  75. package/lib/encoding/assert.js +5 -5
  76. package/lib/encoding/assert.js.map +1 -1
  77. package/lib/encoding/decode.js +20 -21
  78. package/lib/encoding/decode.js.map +1 -1
  79. package/lib/encoding/encode.d.ts +2 -2
  80. package/lib/encoding/encode.js +52 -48
  81. package/lib/encoding/encode.js.map +1 -1
  82. package/lib/encoding/spec.d.ts +2 -1
  83. package/lib/encoding/spec.js +1 -0
  84. package/lib/encoding/spec.js.map +1 -1
  85. package/lib/index.d.ts +6 -3
  86. package/lib/index.js +19 -13
  87. package/lib/index.js.map +1 -1
  88. package/lib/types/HelperTypes.d.ts +34 -2
  89. package/lib/types/HelperTypes.js.map +1 -1
  90. package/lib/types/TypeContext.d.ts +23 -0
  91. package/lib/types/TypeContext.js +111 -0
  92. package/lib/types/TypeContext.js.map +1 -0
  93. package/lib/types/custom/ArraySchema.d.ts +2 -2
  94. package/lib/types/custom/ArraySchema.js +33 -22
  95. package/lib/types/custom/ArraySchema.js.map +1 -1
  96. package/lib/types/custom/CollectionSchema.js +1 -0
  97. package/lib/types/custom/CollectionSchema.js.map +1 -1
  98. package/lib/types/custom/MapSchema.d.ts +3 -1
  99. package/lib/types/custom/MapSchema.js +12 -4
  100. package/lib/types/custom/MapSchema.js.map +1 -1
  101. package/lib/types/custom/SetSchema.js +1 -0
  102. package/lib/types/custom/SetSchema.js.map +1 -1
  103. package/lib/types/registry.js +3 -4
  104. package/lib/types/registry.js.map +1 -1
  105. package/lib/types/symbols.d.ts +8 -5
  106. package/lib/types/symbols.js +9 -6
  107. package/lib/types/symbols.js.map +1 -1
  108. package/lib/types/utils.js +1 -2
  109. package/lib/types/utils.js.map +1 -1
  110. package/lib/utils.js +9 -7
  111. package/lib/utils.js.map +1 -1
  112. package/package.json +7 -6
  113. package/src/Metadata.ts +190 -42
  114. package/src/Reflection.ts +77 -39
  115. package/src/Schema.ts +59 -64
  116. package/src/annotations.ts +155 -203
  117. package/src/bench_encode.ts +108 -0
  118. package/src/codegen/parser.ts +107 -0
  119. package/src/codegen/types.ts +1 -0
  120. package/src/debug.ts +55 -0
  121. package/src/decoder/DecodeOperation.ts +40 -12
  122. package/src/decoder/Decoder.ts +14 -12
  123. package/src/decoder/ReferenceTracker.ts +3 -2
  124. package/src/decoder/strategy/StateCallbacks.ts +153 -82
  125. package/src/encoder/ChangeTree.ts +286 -202
  126. package/src/encoder/EncodeOperation.ts +77 -77
  127. package/src/encoder/Encoder.ts +201 -96
  128. package/src/encoder/Root.ts +93 -0
  129. package/src/encoder/StateView.ts +76 -88
  130. package/src/encoding/assert.ts +4 -3
  131. package/src/encoding/encode.ts +37 -31
  132. package/src/encoding/spec.ts +1 -0
  133. package/src/index.ts +8 -14
  134. package/src/types/HelperTypes.ts +54 -2
  135. package/src/types/TypeContext.ts +133 -0
  136. package/src/types/custom/ArraySchema.ts +49 -19
  137. package/src/types/custom/CollectionSchema.ts +1 -0
  138. package/src/types/custom/MapSchema.ts +18 -5
  139. package/src/types/custom/SetSchema.ts +1 -0
  140. package/src/types/symbols.ts +10 -7
  141. package/src/utils.ts +7 -3
@@ -3,15 +3,17 @@ import { Schema } from './Schema';
3
3
  import { ArraySchema } from './types/custom/ArraySchema';
4
4
  import { MapSchema } from './types/custom/MapSchema';
5
5
  import { Metadata } from "./Metadata";
6
- import { $changes, $childType, $track } from "./types/symbols";
6
+ import { $changes, $childType, $descriptors, $numFields, $track } from "./types/symbols";
7
7
  import { TypeDefinition, getType } from "./types/registry";
8
8
  import { OPERATION } from "./encoding/spec";
9
-
10
- /**
11
- * Data types
12
- */
13
- export type PrimitiveType =
14
- "string" |
9
+ import { TypeContext } from "./types/TypeContext";
10
+ import { assertInstanceType, assertType } from "./encoding/assert";
11
+ import type { Ref } from "./encoder/ChangeTree";
12
+ import type { DefinedSchemaType, InferValueType } from "./types/HelperTypes";
13
+ import type { CollectionSchema } from "./types/custom/CollectionSchema";
14
+ import type { SetSchema } from "./types/custom/SetSchema";
15
+
16
+ export type RawPrimitiveType = "string" |
15
17
  "number" |
16
18
  "boolean" |
17
19
  "int8" |
@@ -23,16 +25,18 @@ export type PrimitiveType =
23
25
  "int64" |
24
26
  "uint64" |
25
27
  "float32" |
26
- "float64" |
27
- typeof Schema |
28
- object;
28
+ "float64";
29
29
 
30
- export type DefinitionType = PrimitiveType
31
- | PrimitiveType[]
32
- | { array: PrimitiveType }
33
- | { map: PrimitiveType }
34
- | { collection: PrimitiveType }
35
- | { set: PrimitiveType };
30
+ export type PrimitiveType = RawPrimitiveType | typeof Schema | object;
31
+
32
+ // TODO: infer "default" value type correctly.
33
+ export type DefinitionType<T extends PrimitiveType = PrimitiveType> = T
34
+ | T[]
35
+ | { type: T, default?: InferValueType<T>, view?: boolean | number }
36
+ | { array: T, default?: ArraySchema<InferValueType<T>>, view?: boolean | number }
37
+ | { map: T, default?: MapSchema<InferValueType<T>>, view?: boolean | number }
38
+ | { collection: T, default?: CollectionSchema<InferValueType<T>>, view?: boolean | number }
39
+ | { set: T, default?: SetSchema<InferValueType<T>>, view?: boolean | number };
36
40
 
37
41
  export type Definition = { [field: string]: DefinitionType };
38
42
 
@@ -42,119 +46,8 @@ export interface TypeOptions {
42
46
 
43
47
  export const DEFAULT_VIEW_TAG = -1;
44
48
 
45
- export class TypeContext {
46
- types: {[id: number]: typeof Schema} = {};
47
- schemas = new Map<typeof Schema, number>();
48
-
49
- hasFilters: boolean = false;
50
-
51
- /**
52
- * For inheritance support
53
- * Keeps track of which classes extends which. (parent -> children)
54
- */
55
- static inheritedTypes = new Map<typeof Schema, Set<typeof Schema>>();
56
-
57
- static register(target: typeof Schema) {
58
- const parent = Object.getPrototypeOf(target);
59
- if (parent !== Schema) {
60
- let inherits = TypeContext.inheritedTypes.get(parent);
61
- if (!inherits) {
62
- inherits = new Set<typeof Schema>();
63
- TypeContext.inheritedTypes.set(parent, inherits);
64
- }
65
- inherits.add(target);
66
- }
67
- }
68
-
69
- constructor(rootClass?: typeof Schema) {
70
- if (rootClass) {
71
- this.discoverTypes(rootClass);
72
- }
73
- }
74
-
75
- has(schema: typeof Schema) {
76
- return this.schemas.has(schema);
77
- }
78
-
79
- get(typeid: number) {
80
- return this.types[typeid];
81
- }
82
-
83
- add(schema: typeof Schema, typeid: number = this.schemas.size) {
84
- // skip if already registered
85
- if (this.schemas.has(schema)) {
86
- return false;
87
- }
88
-
89
- this.types[typeid] = schema;
90
- this.schemas.set(schema, typeid);
91
- return true;
92
- }
93
-
94
- getTypeId(klass: typeof Schema) {
95
- return this.schemas.get(klass);
96
- }
97
-
98
- private discoverTypes(klass: typeof Schema) {
99
- if (!this.add(klass)) {
100
- return;
101
- }
102
-
103
- // add classes inherited from this base class
104
- TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
105
- this.discoverTypes(child);
106
- });
107
-
108
- // skip if no fields are defined for this class.
109
- if (klass[Symbol.metadata] === undefined) {
110
- klass[Symbol.metadata] = {};
111
- }
112
-
113
- // const metadata = Metadata.getFor(klass);
114
- const metadata = klass[Symbol.metadata];
115
-
116
- // if any schema/field has filters, mark "context" as having filters.
117
- if (metadata[-2]) {
118
- this.hasFilters = true;
119
- }
120
-
121
- for (const field in metadata) {
122
- const fieldType = metadata[field].type;
123
-
124
- if (typeof(fieldType) === "string") {
125
- continue;
126
- }
127
-
128
- if (Array.isArray(fieldType)) {
129
- const type = fieldType[0];
130
- if (type === "string") {
131
- continue;
132
- }
133
- this.discoverTypes(type as typeof Schema);
134
-
135
- } else if (typeof(fieldType) === "function") {
136
- this.discoverTypes(fieldType);
137
-
138
- } else {
139
- const type = Object.values(fieldType)[0];
140
-
141
- // skip primitive types
142
- if (typeof(type) === "string") {
143
- continue;
144
- }
145
-
146
- this.discoverTypes(type as typeof Schema);
147
- }
148
- }
149
- }
150
- }
151
-
152
- export function entity(constructor, context: ClassDecoratorContext) {
153
- if (!constructor._definition) {
154
- // for inheritance support
155
- TypeContext.register(constructor);
156
- }
157
-
49
+ export function entity(constructor) {
50
+ TypeContext.register(constructor);
158
51
  return constructor;
159
52
  }
160
53
 
@@ -186,8 +79,8 @@ export function entity(constructor, context: ClassDecoratorContext) {
186
79
  // // detect index for this field, considering inheritance
187
80
  // //
188
81
  // const parent = Object.getPrototypeOf(context.metadata);
189
- // let fieldIndex: number = context.metadata[-1] // current structure already has fields defined
190
- // ?? (parent && parent[-1]) // parent structure has fields defined
82
+ // let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
83
+ // ?? (parent && parent[$numFields]) // parent structure has fields defined
191
84
  // ?? -1; // no fields defined
192
85
  // fieldIndex++;
193
86
 
@@ -330,19 +223,22 @@ export function view<T> (tag: number = DEFAULT_VIEW_TAG) {
330
223
 
331
224
  const parentClass = Object.getPrototypeOf(constructor);
332
225
  const parentMetadata = parentClass[Symbol.metadata];
333
- const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
334
226
 
335
- if (!metadata[fieldName]) {
336
- //
337
- // detect index for this field, considering inheritance
338
- //
339
- metadata[fieldName] = {
340
- type: undefined,
341
- index: (metadata[-1] // current structure already has fields defined
342
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
343
- ?? -1) + 1 // no fields defined
344
- }
345
- }
227
+ // TODO: use Metadata.initialize()
228
+ const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
229
+ // const fieldIndex = metadata[fieldName];
230
+
231
+ // if (!metadata[fieldIndex]) {
232
+ // //
233
+ // // detect index for this field, considering inheritance
234
+ // //
235
+ // metadata[fieldIndex] = {
236
+ // type: undefined,
237
+ // index: (metadata[$numFields] // current structure already has fields defined
238
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
239
+ // ?? -1) + 1 // no fields defined
240
+ // }
241
+ // }
346
242
 
347
243
  Metadata.setTag(metadata, fieldName, tag);
348
244
  }
@@ -356,22 +252,24 @@ export function unreliable<T> (target: T, field: string) {
356
252
 
357
253
  const parentClass = Object.getPrototypeOf(constructor);
358
254
  const parentMetadata = parentClass[Symbol.metadata];
255
+
256
+ // TODO: use Metadata.initialize()
359
257
  const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
360
258
 
361
- if (!metadata[field]) {
362
- //
363
- // detect index for this field, considering inheritance
364
- //
365
- metadata[field] = {
366
- type: undefined,
367
- index: (metadata[-1] // current structure already has fields defined
368
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
369
- ?? -1) + 1 // no fields defined
370
- }
371
- }
259
+ // if (!metadata[field]) {
260
+ // //
261
+ // // detect index for this field, considering inheritance
262
+ // //
263
+ // metadata[field] = {
264
+ // type: undefined,
265
+ // index: (metadata[$numFields] // current structure already has fields defined
266
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
267
+ // ?? -1) + 1 // no fields defined
268
+ // }
269
+ // }
372
270
 
373
271
  // add owned flag to the field
374
- metadata[field].unreliable = true;
272
+ metadata[metadata[field]].unreliable = true;
375
273
  }
376
274
 
377
275
  export function type (
@@ -389,20 +287,20 @@ export function type (
389
287
  TypeContext.register(constructor);
390
288
 
391
289
  const parentClass = Object.getPrototypeOf(constructor);
392
- const parentMetadata = parentClass[Symbol.metadata];
393
- const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
290
+ const parentMetadata = parentClass[Symbol.metadata];
291
+ const metadata = Metadata.initialize(constructor);
394
292
 
395
- let fieldIndex: number;
293
+ let fieldIndex: number = metadata[field];
396
294
 
397
295
  /**
398
296
  * skip if descriptor already exists for this field (`@deprecated()`)
399
297
  */
400
- if (metadata[field]) {
401
- if (metadata[field].deprecated) {
298
+ if (metadata[fieldIndex] !== undefined) {
299
+ if (metadata[fieldIndex].deprecated) {
402
300
  // do not create accessors for deprecated properties.
403
301
  return;
404
302
 
405
- } else if (metadata[field].descriptor !== undefined) {
303
+ } else if (metadata[fieldIndex].type !== undefined) {
406
304
  // trying to define same property multiple times across inheritance.
407
305
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
408
306
  try {
@@ -412,28 +310,31 @@ export function type (
412
310
  const definitionAtLine = e.stack.split("\n")[4].trim();
413
311
  throw new Error(`${e.message} ${definitionAtLine}`);
414
312
  }
415
-
416
- } else {
417
- fieldIndex = metadata[field].index;
418
313
  }
419
314
 
420
315
  } else {
421
316
  //
422
317
  // detect index for this field, considering inheritance
423
318
  //
424
- fieldIndex = metadata[-1] // current structure already has fields defined
425
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
319
+ fieldIndex = metadata[$numFields] // current structure already has fields defined
320
+ ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
426
321
  ?? -1; // no fields defined
427
322
  fieldIndex++;
428
323
  }
429
324
 
430
325
  if (options && options.manual) {
431
- Metadata.addField(metadata, fieldIndex, field, type, {
432
- // do not declare getter/setter descriptor
433
- enumerable: true,
434
- configurable: true,
435
- writable: true,
436
- });
326
+ Metadata.addField(
327
+ metadata,
328
+ fieldIndex,
329
+ field,
330
+ type,
331
+ {
332
+ // do not declare getter/setter descriptor
333
+ enumerable: true,
334
+ configurable: true,
335
+ writable: true,
336
+ }
337
+ );
437
338
 
438
339
  } else {
439
340
  const complexTypeKlass = (Array.isArray(type))
@@ -449,7 +350,7 @@ export function type (
449
350
  fieldIndex,
450
351
  field,
451
352
  type,
452
- getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field)
353
+ getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass)
453
354
  );
454
355
  }
455
356
  }
@@ -460,13 +361,11 @@ export function getPropertyDescriptor(
460
361
  fieldIndex: number,
461
362
  type: DefinitionType,
462
363
  complexTypeKlass: TypeDefinition,
463
- metadata: Metadata,
464
- field: string,
465
364
  ) {
466
365
  return {
467
366
  get: function () { return this[fieldCached]; },
468
367
  set: function (this: Schema, value: any) {
469
- const previousValue = this[fieldCached] || undefined;
368
+ const previousValue = this[fieldCached] ?? undefined;
470
369
 
471
370
  // skip if value is the same as cached.
472
371
  if (value === previousValue) { return; }
@@ -487,30 +386,32 @@ export function getPropertyDescriptor(
487
386
  }
488
387
 
489
388
  value[$childType] = type;
389
+
390
+ } else if (typeof (type) !== "string") {
391
+ assertInstanceType(value, type as typeof Schema, this, fieldCached.substring(1));
392
+
393
+ } else {
394
+ assertType(value, type, this, fieldCached.substring(1));
490
395
  }
491
396
 
397
+ const changeTree = this[$changes];
398
+
492
399
  //
493
400
  // Replacing existing "ref", remove it from root.
494
401
  // TODO: if there are other references to this instance, we should not remove it from root.
495
402
  //
496
403
  if (previousValue !== undefined && previousValue[$changes]) {
497
- this[$changes].root?.remove(previousValue[$changes]);
404
+ changeTree.root?.remove(previousValue[$changes]);
498
405
  }
499
406
 
500
407
  // flag the change for encoding.
501
- this.constructor[$track](this[$changes], fieldIndex, OPERATION.ADD);
408
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
502
409
 
503
410
  //
504
411
  // call setParent() recursively for this and its child
505
412
  // structures.
506
413
  //
507
- if (value[$changes]) {
508
- value[$changes].setParent(
509
- this,
510
- this[$changes].root,
511
- metadata[field].index,
512
- );
513
- }
414
+ (value as Ref)[$changes]?.setParent(this, changeTree.root, fieldIndex);
514
415
 
515
416
  } else if (previousValue !== undefined) {
516
417
  //
@@ -542,23 +443,25 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
542
443
  const parentClass = Object.getPrototypeOf(constructor);
543
444
  const parentMetadata = parentClass[Symbol.metadata];
544
445
  const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
545
-
546
- if (!metadata[field]) {
547
- //
548
- // detect index for this field, considering inheritance
549
- //
550
- metadata[field] = {
551
- type: undefined,
552
- index: (metadata[-1] // current structure already has fields defined
553
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
554
- ?? -1) + 1 // no fields defined
555
- }
556
- }
557
-
558
- metadata[field].deprecated = true;
446
+ const fieldIndex = metadata[field];
447
+
448
+ // if (!metadata[field]) {
449
+ // //
450
+ // // detect index for this field, considering inheritance
451
+ // //
452
+ // metadata[field] = {
453
+ // type: undefined,
454
+ // index: (metadata[$numFields] // current structure already has fields defined
455
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
456
+ // ?? -1) + 1 // no fields defined
457
+ // }
458
+ // }
459
+
460
+ metadata[fieldIndex].deprecated = true;
559
461
 
560
462
  if (throws) {
561
- metadata[field].descriptor = {
463
+ metadata[$descriptors] ??= {};
464
+ metadata[$descriptors][field] = {
562
465
  get: function () { throw new Error(`${field} is deprecated.`); },
563
466
  set: function (this: Schema, value: any) { /* throw new Error(`${field} is deprecated.`); */ },
564
467
  enumerable: false,
@@ -567,8 +470,8 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
567
470
  }
568
471
 
569
472
  // flag metadata[field] as non-enumerable
570
- Object.defineProperty(metadata, field, {
571
- value: metadata[field],
473
+ Object.defineProperty(metadata, fieldIndex, {
474
+ value: metadata[fieldIndex],
572
475
  enumerable: false,
573
476
  configurable: true
574
477
  });
@@ -577,7 +480,7 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
577
480
 
578
481
  export function defineTypes(
579
482
  target: typeof Schema,
580
- fields: { [property: string]: DefinitionType },
483
+ fields: Definition,
581
484
  options?: TypeOptions
582
485
  ) {
583
486
  for (let field in fields) {
@@ -585,3 +488,52 @@ export function defineTypes(
585
488
  }
586
489
  return target;
587
490
  }
491
+
492
+ export interface SchemaWithExtends<T extends Definition, P extends typeof Schema> extends DefinedSchemaType<T, P> {
493
+ extends: <T2 extends Definition>(
494
+ fields: T2,
495
+ name?: string
496
+ ) => SchemaWithExtends<T & T2, typeof this>;
497
+ }
498
+
499
+ export function schema<T extends Definition, P extends typeof Schema = typeof Schema>(
500
+ fields: T,
501
+ name?: string,
502
+ inherits: P = Schema as P
503
+ ): SchemaWithExtends<T, P> {
504
+ const defaultValues: any = {};
505
+ const viewTagFields: any = {};
506
+
507
+ for (let fieldName in fields) {
508
+ const field = fields[fieldName] as DefinitionType;
509
+ if (typeof (field) === "object") {
510
+ if (field['default'] !== undefined) {
511
+ defaultValues[fieldName] = field['default'];
512
+ }
513
+ if (field['view'] !== undefined) {
514
+ viewTagFields[fieldName] = (typeof (field['view']) === "boolean")
515
+ ? DEFAULT_VIEW_TAG
516
+ : field['view'];
517
+ }
518
+ }
519
+ }
520
+
521
+ const klass = Metadata.setFields(class extends inherits {
522
+ constructor (...args: any[]) {
523
+ args[0] = Object.assign({}, defaultValues, args[0]);
524
+ super(...args);
525
+ }
526
+ }, fields) as SchemaWithExtends<T, P>;
527
+
528
+ for (let fieldName in viewTagFields) {
529
+ view(viewTagFields[fieldName])(klass.prototype, fieldName);
530
+ }
531
+
532
+ if (name) {
533
+ Object.defineProperty(klass, "name", { value: name });
534
+ }
535
+
536
+ klass.extends = (fields, name) => schema(fields, name, klass);
537
+
538
+ return klass;
539
+ }
@@ -0,0 +1,108 @@
1
+ import { nanoid } from "nanoid";
2
+ import { Schema, type, MapSchema, ArraySchema, Encoder } from ".";
3
+
4
+ class Attribute extends Schema {
5
+ @type("string") name: string;
6
+ @type("number") value: number;
7
+ }
8
+
9
+ class Item extends Schema {
10
+ @type("number") price: number;
11
+ @type([ Attribute ]) attributes = new ArraySchema<Attribute>();
12
+ }
13
+
14
+ class Position extends Schema {
15
+ @type("number") x: number;
16
+ @type("number") y: number;
17
+ }
18
+
19
+ class Player extends Schema {
20
+ @type(Position) position = new Position();
21
+ @type({ map: Item }) items = new MapSchema<Item>();
22
+ }
23
+
24
+ class State extends Schema {
25
+ @type({ map: Player }) players = new MapSchema<Player>();
26
+ @type("string") currentTurn;
27
+ }
28
+
29
+ const state = new State();
30
+
31
+ Encoder.BUFFER_SIZE = 4096 * 4096;
32
+ const encoder = new Encoder(state);
33
+
34
+
35
+ let now = Date.now();
36
+
37
+ // for (let i = 0; i < 10000; i++) {
38
+ // const player = new Player();
39
+ // state.players.set(`p-${nanoid()}`, player);
40
+ //
41
+ // player.position.x = (i + 1) * 100;
42
+ // player.position.y = (i + 1) * 100;
43
+ // for (let j = 0; j < 10; j++) {
44
+ // const item = new Item();
45
+ // player.items.set(`item-${j}`, item);
46
+ // item.price = (i + 1) * 50;
47
+ // for (let k = 0; k < 5; k++) {
48
+ // const attr = new Attribute();
49
+ // attr.name = `Attribute ${k}`;
50
+ // attr.value = k;
51
+ // item.attributes.push(attr);
52
+ // }
53
+ // }
54
+ // }
55
+ // console.log("time to make changes:", Date.now() - now);
56
+
57
+
58
+ // measure time to .encodeAll()
59
+
60
+ now = Date.now();
61
+ // for (let i = 0; i < 1000; i++) {
62
+ // encoder.encodeAll();
63
+ // }
64
+ // console.log(Date.now() - now);
65
+
66
+ const total = 100;
67
+ const allEncodes = Date.now();
68
+
69
+ let avgTimeToEncode = 0;
70
+ let avgTimeToMakeChanges = 0;
71
+
72
+ for (let i = 0; i < total; i++) {
73
+ now = Date.now();
74
+ for (let j = 0; j < 50; j++) {
75
+ const player = new Player();
76
+ state.players.set(`p-${nanoid()}`, player);
77
+
78
+ player.position.x = (j + 1) * 100;
79
+ player.position.y = (j + 1) * 100;
80
+ for (let k = 0; k < 10; k++) {
81
+ const item = new Item();
82
+ item.price = (j + 1) * 50;
83
+ for (let l = 0; l < 5; l++) {
84
+ const attr = new Attribute();
85
+ attr.name = `Attribute ${l}`;
86
+ attr.value = l;
87
+ item.attributes.push(attr);
88
+ }
89
+ player.items.set(`item-${k}`, item);
90
+ }
91
+ }
92
+ const timeToMakeChanges = Date.now() - now;
93
+ console.log("time to make changes:", timeToMakeChanges);
94
+ avgTimeToMakeChanges += timeToMakeChanges;
95
+
96
+ now = Date.now();
97
+ encoder.encode();
98
+ encoder.discardChanges();
99
+
100
+ const timeToEncode = Date.now() - now;
101
+ console.log("time to encode:", timeToEncode);
102
+ avgTimeToEncode += timeToEncode;
103
+ }
104
+ console.log("avg time to encode:", (avgTimeToEncode) / total);
105
+ console.log("avg time to make changes:", (avgTimeToMakeChanges) / total);
106
+ console.log("time for all encodes:", Date.now() - allEncodes);
107
+
108
+ console.log(Array.from(encoder.encodeAll()).length, "bytes");