@colyseus/schema 3.0.0-alpha.9 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/README.md +148 -62
  2. package/bin/schema-debug +94 -0
  3. package/build/cjs/index.js +2222 -1513
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/esm/index.mjs +2223 -1516
  6. package/build/esm/index.mjs.map +1 -1
  7. package/build/umd/index.js +2225 -1516
  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 -31
  13. package/lib/Reflection.js.map +1 -1
  14. package/lib/Schema.d.ts +12 -5
  15. package/lib/Schema.js +57 -56
  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 +9 -46
  28. package/lib/codegen/languages/csharp.js.map +1 -1
  29. package/lib/codegen/languages/haxe.js +4 -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 +23 -25
  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 +3 -4
  47. package/lib/decoder/DecodeOperation.js +35 -17
  48. package/lib/decoder/DecodeOperation.js.map +1 -1
  49. package/lib/decoder/Decoder.d.ts +5 -6
  50. package/lib/decoder/Decoder.js +10 -10
  51. package/lib/decoder/Decoder.js.map +1 -1
  52. package/lib/decoder/ReferenceTracker.js +4 -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 +74 -64
  58. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  59. package/lib/encoder/ChangeTree.d.ts +28 -20
  60. package/lib/encoder/ChangeTree.js +242 -188
  61. package/lib/encoder/ChangeTree.js.map +1 -1
  62. package/lib/encoder/EncodeOperation.d.ts +3 -6
  63. package/lib/encoder/EncodeOperation.js +51 -65
  64. package/lib/encoder/EncodeOperation.js.map +1 -1
  65. package/lib/encoder/Encoder.d.ts +8 -7
  66. package/lib/encoder/Encoder.js +128 -79
  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 +72 -74
  73. package/lib/encoder/StateView.js.map +1 -1
  74. package/lib/encoding/assert.d.ts +7 -6
  75. package/lib/encoding/assert.js +13 -5
  76. package/lib/encoding/assert.js.map +1 -1
  77. package/lib/encoding/decode.d.ts +36 -19
  78. package/lib/encoding/decode.js +54 -84
  79. package/lib/encoding/decode.js.map +1 -1
  80. package/lib/encoding/encode.d.ts +36 -18
  81. package/lib/encoding/encode.js +61 -48
  82. package/lib/encoding/encode.js.map +1 -1
  83. package/lib/encoding/spec.d.ts +4 -5
  84. package/lib/encoding/spec.js +1 -2
  85. package/lib/encoding/spec.js.map +1 -1
  86. package/lib/index.d.ts +10 -9
  87. package/lib/index.js +24 -17
  88. package/lib/index.js.map +1 -1
  89. package/lib/types/HelperTypes.d.ts +34 -2
  90. package/lib/types/HelperTypes.js.map +1 -1
  91. package/lib/types/TypeContext.d.ts +29 -0
  92. package/lib/types/TypeContext.js +151 -0
  93. package/lib/types/TypeContext.js.map +1 -0
  94. package/lib/types/custom/ArraySchema.d.ts +2 -2
  95. package/lib/types/custom/ArraySchema.js +33 -22
  96. package/lib/types/custom/ArraySchema.js.map +1 -1
  97. package/lib/types/custom/CollectionSchema.d.ts +2 -2
  98. package/lib/types/custom/CollectionSchema.js +1 -0
  99. package/lib/types/custom/CollectionSchema.js.map +1 -1
  100. package/lib/types/custom/MapSchema.d.ts +18 -16
  101. package/lib/types/custom/MapSchema.js +12 -4
  102. package/lib/types/custom/MapSchema.js.map +1 -1
  103. package/lib/types/custom/SetSchema.d.ts +2 -2
  104. package/lib/types/custom/SetSchema.js +1 -0
  105. package/lib/types/custom/SetSchema.js.map +1 -1
  106. package/lib/types/registry.d.ts +8 -1
  107. package/lib/types/registry.js +23 -6
  108. package/lib/types/registry.js.map +1 -1
  109. package/lib/types/symbols.d.ts +8 -5
  110. package/lib/types/symbols.js +9 -6
  111. package/lib/types/symbols.js.map +1 -1
  112. package/lib/types/utils.js +1 -2
  113. package/lib/types/utils.js.map +1 -1
  114. package/lib/utils.js +9 -7
  115. package/lib/utils.js.map +1 -1
  116. package/package.json +19 -18
  117. package/src/Metadata.ts +190 -42
  118. package/src/Reflection.ts +76 -38
  119. package/src/Schema.ts +72 -70
  120. package/src/annotations.ts +156 -202
  121. package/src/bench_encode.ts +108 -0
  122. package/src/codegen/languages/csharp.ts +8 -47
  123. package/src/codegen/languages/haxe.ts +4 -0
  124. package/src/codegen/languages/lua.ts +19 -27
  125. package/src/codegen/parser.ts +107 -0
  126. package/src/codegen/types.ts +1 -0
  127. package/src/debug.ts +55 -0
  128. package/src/decoder/DecodeOperation.ts +43 -15
  129. package/src/decoder/Decoder.ts +12 -10
  130. package/src/decoder/ReferenceTracker.ts +5 -3
  131. package/src/decoder/strategy/StateCallbacks.ts +152 -81
  132. package/src/encoder/ChangeTree.ts +282 -209
  133. package/src/encoder/EncodeOperation.ts +78 -78
  134. package/src/encoder/Encoder.ts +152 -87
  135. package/src/encoder/Root.ts +93 -0
  136. package/src/encoder/StateView.ts +80 -88
  137. package/src/encoding/assert.ts +17 -8
  138. package/src/encoding/decode.ts +73 -93
  139. package/src/encoding/encode.ts +76 -45
  140. package/src/encoding/spec.ts +3 -5
  141. package/src/index.ts +12 -20
  142. package/src/types/HelperTypes.ts +54 -2
  143. package/src/types/TypeContext.ts +175 -0
  144. package/src/types/custom/ArraySchema.ts +49 -19
  145. package/src/types/custom/CollectionSchema.ts +1 -0
  146. package/src/types/custom/MapSchema.ts +30 -17
  147. package/src/types/custom/SetSchema.ts +1 -0
  148. package/src/types/registry.ts +22 -3
  149. package/src/types/symbols.ts +10 -7
  150. 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" |
@@ -24,15 +26,19 @@ export type PrimitiveType =
24
26
  "uint64" |
25
27
  "float32" |
26
28
  "float64" |
27
- typeof Schema |
28
- object;
29
+ "bigint64" |
30
+ "biguint64";
29
31
 
30
- export type DefinitionType = PrimitiveType
31
- | PrimitiveType[]
32
- | { array: PrimitiveType }
33
- | { map: PrimitiveType }
34
- | { collection: PrimitiveType }
35
- | { set: PrimitiveType };
32
+ export type PrimitiveType = RawPrimitiveType | typeof Schema | object;
33
+
34
+ // TODO: infer "default" value type correctly.
35
+ export type DefinitionType<T extends PrimitiveType = PrimitiveType> = T
36
+ | T[]
37
+ | { type: T, default?: InferValueType<T>, view?: boolean | number }
38
+ | { array: T, default?: ArraySchema<InferValueType<T>>, view?: boolean | number }
39
+ | { map: T, default?: MapSchema<InferValueType<T>>, view?: boolean | number }
40
+ | { collection: T, default?: CollectionSchema<InferValueType<T>>, view?: boolean | number }
41
+ | { set: T, default?: SetSchema<InferValueType<T>>, view?: boolean | number };
36
42
 
37
43
  export type Definition = { [field: string]: DefinitionType };
38
44
 
@@ -42,119 +48,8 @@ export interface TypeOptions {
42
48
 
43
49
  export const DEFAULT_VIEW_TAG = -1;
44
50
 
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
-
51
+ export function entity(constructor) {
52
+ TypeContext.register(constructor);
158
53
  return constructor;
159
54
  }
160
55
 
@@ -186,8 +81,8 @@ export function entity(constructor, context: ClassDecoratorContext) {
186
81
  // // detect index for this field, considering inheritance
187
82
  // //
188
83
  // 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
84
+ // let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
85
+ // ?? (parent && parent[$numFields]) // parent structure has fields defined
191
86
  // ?? -1; // no fields defined
192
87
  // fieldIndex++;
193
88
 
@@ -330,19 +225,22 @@ export function view<T> (tag: number = DEFAULT_VIEW_TAG) {
330
225
 
331
226
  const parentClass = Object.getPrototypeOf(constructor);
332
227
  const parentMetadata = parentClass[Symbol.metadata];
333
- const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
334
228
 
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
- }
229
+ // TODO: use Metadata.initialize()
230
+ const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
231
+ // const fieldIndex = metadata[fieldName];
232
+
233
+ // if (!metadata[fieldIndex]) {
234
+ // //
235
+ // // detect index for this field, considering inheritance
236
+ // //
237
+ // metadata[fieldIndex] = {
238
+ // type: undefined,
239
+ // index: (metadata[$numFields] // current structure already has fields defined
240
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
241
+ // ?? -1) + 1 // no fields defined
242
+ // }
243
+ // }
346
244
 
347
245
  Metadata.setTag(metadata, fieldName, tag);
348
246
  }
@@ -356,22 +254,24 @@ export function unreliable<T> (target: T, field: string) {
356
254
 
357
255
  const parentClass = Object.getPrototypeOf(constructor);
358
256
  const parentMetadata = parentClass[Symbol.metadata];
257
+
258
+ // TODO: use Metadata.initialize()
359
259
  const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
360
260
 
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
- }
261
+ // if (!metadata[field]) {
262
+ // //
263
+ // // detect index for this field, considering inheritance
264
+ // //
265
+ // metadata[field] = {
266
+ // type: undefined,
267
+ // index: (metadata[$numFields] // current structure already has fields defined
268
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
269
+ // ?? -1) + 1 // no fields defined
270
+ // }
271
+ // }
372
272
 
373
273
  // add owned flag to the field
374
- metadata[field].unreliable = true;
274
+ metadata[metadata[field]].unreliable = true;
375
275
  }
376
276
 
377
277
  export function type (
@@ -389,20 +289,20 @@ export function type (
389
289
  TypeContext.register(constructor);
390
290
 
391
291
  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)));
292
+ const parentMetadata = parentClass[Symbol.metadata];
293
+ const metadata = Metadata.initialize(constructor);
394
294
 
395
- let fieldIndex: number;
295
+ let fieldIndex: number = metadata[field];
396
296
 
397
297
  /**
398
298
  * skip if descriptor already exists for this field (`@deprecated()`)
399
299
  */
400
- if (metadata[field]) {
401
- if (metadata[field].deprecated) {
300
+ if (metadata[fieldIndex] !== undefined) {
301
+ if (metadata[fieldIndex].deprecated) {
402
302
  // do not create accessors for deprecated properties.
403
303
  return;
404
304
 
405
- } else if (metadata[field].descriptor !== undefined) {
305
+ } else if (metadata[fieldIndex].type !== undefined) {
406
306
  // trying to define same property multiple times across inheritance.
407
307
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
408
308
  try {
@@ -412,28 +312,31 @@ export function type (
412
312
  const definitionAtLine = e.stack.split("\n")[4].trim();
413
313
  throw new Error(`${e.message} ${definitionAtLine}`);
414
314
  }
415
-
416
- } else {
417
- fieldIndex = metadata[field].index;
418
315
  }
419
316
 
420
317
  } else {
421
318
  //
422
319
  // detect index for this field, considering inheritance
423
320
  //
424
- fieldIndex = metadata[-1] // current structure already has fields defined
425
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
321
+ fieldIndex = metadata[$numFields] // current structure already has fields defined
322
+ ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
426
323
  ?? -1; // no fields defined
427
324
  fieldIndex++;
428
325
  }
429
326
 
430
327
  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
- });
328
+ Metadata.addField(
329
+ metadata,
330
+ fieldIndex,
331
+ field,
332
+ type,
333
+ {
334
+ // do not declare getter/setter descriptor
335
+ enumerable: true,
336
+ configurable: true,
337
+ writable: true,
338
+ }
339
+ );
437
340
 
438
341
  } else {
439
342
  const complexTypeKlass = (Array.isArray(type))
@@ -449,7 +352,7 @@ export function type (
449
352
  fieldIndex,
450
353
  field,
451
354
  type,
452
- getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field)
355
+ getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass)
453
356
  );
454
357
  }
455
358
  }
@@ -460,13 +363,11 @@ export function getPropertyDescriptor(
460
363
  fieldIndex: number,
461
364
  type: DefinitionType,
462
365
  complexTypeKlass: TypeDefinition,
463
- metadata: Metadata,
464
- field: string,
465
366
  ) {
466
367
  return {
467
368
  get: function () { return this[fieldCached]; },
468
369
  set: function (this: Schema, value: any) {
469
- const previousValue = this[fieldCached] || undefined;
370
+ const previousValue = this[fieldCached] ?? undefined;
470
371
 
471
372
  // skip if value is the same as cached.
472
373
  if (value === previousValue) { return; }
@@ -487,30 +388,32 @@ export function getPropertyDescriptor(
487
388
  }
488
389
 
489
390
  value[$childType] = type;
391
+
392
+ } else if (typeof (type) !== "string") {
393
+ assertInstanceType(value, type as typeof Schema, this, fieldCached.substring(1));
394
+
395
+ } else {
396
+ assertType(value, type, this, fieldCached.substring(1));
490
397
  }
491
398
 
399
+ const changeTree = this[$changes];
400
+
492
401
  //
493
402
  // Replacing existing "ref", remove it from root.
494
403
  // TODO: if there are other references to this instance, we should not remove it from root.
495
404
  //
496
405
  if (previousValue !== undefined && previousValue[$changes]) {
497
- this[$changes].root?.remove(previousValue[$changes]);
406
+ changeTree.root?.remove(previousValue[$changes]);
498
407
  }
499
408
 
500
409
  // flag the change for encoding.
501
- this.constructor[$track](this[$changes], fieldIndex, OPERATION.ADD);
410
+ this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
502
411
 
503
412
  //
504
413
  // call setParent() recursively for this and its child
505
414
  // structures.
506
415
  //
507
- if (value[$changes]) {
508
- value[$changes].setParent(
509
- this,
510
- this[$changes].root,
511
- metadata[field].index,
512
- );
513
- }
416
+ (value as Ref)[$changes]?.setParent(this, changeTree.root, fieldIndex);
514
417
 
515
418
  } else if (previousValue !== undefined) {
516
419
  //
@@ -542,23 +445,25 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
542
445
  const parentClass = Object.getPrototypeOf(constructor);
543
446
  const parentMetadata = parentClass[Symbol.metadata];
544
447
  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;
448
+ const fieldIndex = metadata[field];
449
+
450
+ // if (!metadata[field]) {
451
+ // //
452
+ // // detect index for this field, considering inheritance
453
+ // //
454
+ // metadata[field] = {
455
+ // type: undefined,
456
+ // index: (metadata[$numFields] // current structure already has fields defined
457
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
458
+ // ?? -1) + 1 // no fields defined
459
+ // }
460
+ // }
461
+
462
+ metadata[fieldIndex].deprecated = true;
559
463
 
560
464
  if (throws) {
561
- metadata[field].descriptor = {
465
+ metadata[$descriptors] ??= {};
466
+ metadata[$descriptors][field] = {
562
467
  get: function () { throw new Error(`${field} is deprecated.`); },
563
468
  set: function (this: Schema, value: any) { /* throw new Error(`${field} is deprecated.`); */ },
564
469
  enumerable: false,
@@ -567,8 +472,8 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
567
472
  }
568
473
 
569
474
  // flag metadata[field] as non-enumerable
570
- Object.defineProperty(metadata, field, {
571
- value: metadata[field],
475
+ Object.defineProperty(metadata, fieldIndex, {
476
+ value: metadata[fieldIndex],
572
477
  enumerable: false,
573
478
  configurable: true
574
479
  });
@@ -577,7 +482,7 @@ export function deprecated(throws: boolean = true): PropertyDecorator {
577
482
 
578
483
  export function defineTypes(
579
484
  target: typeof Schema,
580
- fields: { [property: string]: DefinitionType },
485
+ fields: Definition,
581
486
  options?: TypeOptions
582
487
  ) {
583
488
  for (let field in fields) {
@@ -585,3 +490,52 @@ export function defineTypes(
585
490
  }
586
491
  return target;
587
492
  }
493
+
494
+ export interface SchemaWithExtends<T extends Definition, P extends typeof Schema> extends DefinedSchemaType<T, P> {
495
+ extends: <T2 extends Definition>(
496
+ fields: T2,
497
+ name?: string
498
+ ) => SchemaWithExtends<T & T2, typeof this>;
499
+ }
500
+
501
+ export function schema<T extends Definition, P extends typeof Schema = typeof Schema>(
502
+ fields: T,
503
+ name?: string,
504
+ inherits: P = Schema as P
505
+ ): SchemaWithExtends<T, P> {
506
+ const defaultValues: any = {};
507
+ const viewTagFields: any = {};
508
+
509
+ for (let fieldName in fields) {
510
+ const field = fields[fieldName] as DefinitionType;
511
+ if (typeof (field) === "object") {
512
+ if (field['default'] !== undefined) {
513
+ defaultValues[fieldName] = field['default'];
514
+ }
515
+ if (field['view'] !== undefined) {
516
+ viewTagFields[fieldName] = (typeof (field['view']) === "boolean")
517
+ ? DEFAULT_VIEW_TAG
518
+ : field['view'];
519
+ }
520
+ }
521
+ }
522
+
523
+ const klass = Metadata.setFields(class extends inherits {
524
+ constructor (...args: any[]) {
525
+ args[0] = Object.assign({}, defaultValues, args[0]);
526
+ super(...args);
527
+ }
528
+ }, fields) as SchemaWithExtends<T, P>;
529
+
530
+ for (let fieldName in viewTagFields) {
531
+ view(viewTagFields[fieldName])(klass.prototype, fieldName);
532
+ }
533
+
534
+ if (name) {
535
+ Object.defineProperty(klass, "name", { value: name });
536
+ }
537
+
538
+ klass.extends = (fields, name) => schema(fields, name, klass);
539
+
540
+ return klass;
541
+ }
@@ -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");