@colyseus/schema 3.0.0-alpha.35 → 3.0.0-alpha.37

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.
@@ -73,6 +73,102 @@ function getType(identifier) {
73
73
  return registeredTypes[identifier];
74
74
  }
75
75
 
76
+ class TypeContext {
77
+ /**
78
+ * For inheritance support
79
+ * Keeps track of which classes extends which. (parent -> children)
80
+ */
81
+ static { this.inheritedTypes = new Map(); }
82
+ static register(target) {
83
+ const parent = Object.getPrototypeOf(target);
84
+ if (parent !== Schema) {
85
+ let inherits = TypeContext.inheritedTypes.get(parent);
86
+ if (!inherits) {
87
+ inherits = new Set();
88
+ TypeContext.inheritedTypes.set(parent, inherits);
89
+ }
90
+ inherits.add(target);
91
+ }
92
+ }
93
+ constructor(rootClass) {
94
+ this.types = {};
95
+ this.schemas = new Map();
96
+ this.hasFilters = false;
97
+ this.parentFiltered = {};
98
+ if (rootClass) {
99
+ this.discoverTypes(rootClass);
100
+ }
101
+ }
102
+ has(schema) {
103
+ return this.schemas.has(schema);
104
+ }
105
+ get(typeid) {
106
+ return this.types[typeid];
107
+ }
108
+ add(schema, typeid = this.schemas.size) {
109
+ // skip if already registered
110
+ if (this.schemas.has(schema)) {
111
+ return false;
112
+ }
113
+ this.types[typeid] = schema;
114
+ //
115
+ // Workaround to allow using an empty Schema (with no `@type()` fields)
116
+ //
117
+ if (schema[Symbol.metadata] === undefined) {
118
+ Metadata.init(schema);
119
+ }
120
+ this.schemas.set(schema, typeid);
121
+ return true;
122
+ }
123
+ getTypeId(klass) {
124
+ return this.schemas.get(klass);
125
+ }
126
+ discoverTypes(klass, parentIndex, parentFieldViewTag) {
127
+ if (!this.add(klass)) {
128
+ return;
129
+ }
130
+ // add classes inherited from this base class
131
+ TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
132
+ this.discoverTypes(child, parentIndex, parentFieldViewTag);
133
+ });
134
+ const metadata = (klass[Symbol.metadata] ??= {});
135
+ // if any schema/field has filters, mark "context" as having filters.
136
+ if (metadata[$viewFieldIndexes]) {
137
+ this.hasFilters = true;
138
+ }
139
+ if (parentFieldViewTag !== undefined) {
140
+ this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
141
+ }
142
+ for (const fieldIndex in metadata) {
143
+ const index = fieldIndex;
144
+ const fieldType = metadata[index].type;
145
+ const viewTag = metadata[index].tag;
146
+ if (typeof (fieldType) === "string") {
147
+ continue;
148
+ }
149
+ if (Array.isArray(fieldType)) {
150
+ const type = fieldType[0];
151
+ // skip primitive types
152
+ if (type === "string") {
153
+ continue;
154
+ }
155
+ this.discoverTypes(type, index, viewTag);
156
+ }
157
+ else if (typeof (fieldType) === "function") {
158
+ this.discoverTypes(fieldType, viewTag);
159
+ }
160
+ else {
161
+ const type = Object.values(fieldType)[0];
162
+ // skip primitive types
163
+ if (typeof (type) === "string") {
164
+ continue;
165
+ }
166
+ this.discoverTypes(type, index, viewTag);
167
+ }
168
+ }
169
+ }
170
+ }
171
+
76
172
  const Metadata = {
77
173
  addField(metadata, index, name, type, descriptor) {
78
174
  if (index > 64) {
@@ -157,17 +253,42 @@ const Metadata = {
157
253
  metadata[$fieldIndexesByViewTag][tag].push(index);
158
254
  },
159
255
  setFields(target, fields) {
160
- const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
161
- let index = 0;
256
+ // for inheritance support
257
+ const constructor = target.prototype.constructor;
258
+ TypeContext.register(constructor);
259
+ const parentClass = Object.getPrototypeOf(constructor);
260
+ const parentMetadata = parentClass && parentClass[Symbol.metadata];
261
+ const metadata = Metadata.initialize(constructor, parentMetadata);
262
+ // Use Schema's methods if not defined in the class
263
+ if (!constructor[$track]) {
264
+ constructor[$track] = Schema[$track];
265
+ }
266
+ if (!constructor[$encoder]) {
267
+ constructor[$encoder] = Schema[$encoder];
268
+ }
269
+ if (!constructor[$decoder]) {
270
+ constructor[$decoder] = Schema[$decoder];
271
+ }
272
+ if (!constructor.prototype.toJSON) {
273
+ constructor.prototype.toJSON = Schema.prototype.toJSON;
274
+ }
275
+ //
276
+ // detect index for this field, considering inheritance
277
+ //
278
+ let fieldIndex = metadata[$numFields] // current structure already has fields defined
279
+ ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
280
+ ?? -1; // no fields defined
281
+ fieldIndex++;
162
282
  for (const field in fields) {
163
283
  const type = fields[field];
164
284
  // FIXME: this code is duplicated from @type() annotation
165
285
  const complexTypeKlass = (Array.isArray(type))
166
286
  ? getType("array")
167
287
  : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
168
- Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
169
- index++;
288
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, type, complexTypeKlass));
289
+ fieldIndex++;
170
290
  }
291
+ return target;
171
292
  },
172
293
  isDeprecated(metadata, field) {
173
294
  return metadata[field].deprecated === true;
@@ -2475,102 +2596,6 @@ class MapSchema {
2475
2596
  }
2476
2597
  registerType("map", { constructor: MapSchema });
2477
2598
 
2478
- class TypeContext {
2479
- /**
2480
- * For inheritance support
2481
- * Keeps track of which classes extends which. (parent -> children)
2482
- */
2483
- static { this.inheritedTypes = new Map(); }
2484
- static register(target) {
2485
- const parent = Object.getPrototypeOf(target);
2486
- if (parent !== Schema) {
2487
- let inherits = TypeContext.inheritedTypes.get(parent);
2488
- if (!inherits) {
2489
- inherits = new Set();
2490
- TypeContext.inheritedTypes.set(parent, inherits);
2491
- }
2492
- inherits.add(target);
2493
- }
2494
- }
2495
- constructor(rootClass) {
2496
- this.types = {};
2497
- this.schemas = new Map();
2498
- this.hasFilters = false;
2499
- this.parentFiltered = {};
2500
- if (rootClass) {
2501
- this.discoverTypes(rootClass);
2502
- }
2503
- }
2504
- has(schema) {
2505
- return this.schemas.has(schema);
2506
- }
2507
- get(typeid) {
2508
- return this.types[typeid];
2509
- }
2510
- add(schema, typeid = this.schemas.size) {
2511
- // skip if already registered
2512
- if (this.schemas.has(schema)) {
2513
- return false;
2514
- }
2515
- this.types[typeid] = schema;
2516
- //
2517
- // Workaround to allow using an empty Schema (with no `@type()` fields)
2518
- //
2519
- if (schema[Symbol.metadata] === undefined) {
2520
- Metadata.init(schema);
2521
- }
2522
- this.schemas.set(schema, typeid);
2523
- return true;
2524
- }
2525
- getTypeId(klass) {
2526
- return this.schemas.get(klass);
2527
- }
2528
- discoverTypes(klass, parentIndex, parentFieldViewTag) {
2529
- if (!this.add(klass)) {
2530
- return;
2531
- }
2532
- // add classes inherited from this base class
2533
- TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
2534
- this.discoverTypes(child, parentIndex, parentFieldViewTag);
2535
- });
2536
- const metadata = (klass[Symbol.metadata] ??= {});
2537
- // if any schema/field has filters, mark "context" as having filters.
2538
- if (metadata[$viewFieldIndexes]) {
2539
- this.hasFilters = true;
2540
- }
2541
- if (parentFieldViewTag !== undefined) {
2542
- this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2543
- }
2544
- for (const fieldIndex in metadata) {
2545
- const index = fieldIndex;
2546
- const fieldType = metadata[index].type;
2547
- const viewTag = metadata[index].tag;
2548
- if (typeof (fieldType) === "string") {
2549
- continue;
2550
- }
2551
- if (Array.isArray(fieldType)) {
2552
- const type = fieldType[0];
2553
- // skip primitive types
2554
- if (type === "string") {
2555
- continue;
2556
- }
2557
- this.discoverTypes(type, index, viewTag);
2558
- }
2559
- else if (typeof (fieldType) === "function") {
2560
- this.discoverTypes(fieldType, viewTag);
2561
- }
2562
- else {
2563
- const type = Object.values(fieldType)[0];
2564
- // skip primitive types
2565
- if (typeof (type) === "string") {
2566
- continue;
2567
- }
2568
- this.discoverTypes(type, index, viewTag);
2569
- }
2570
- }
2571
- }
2572
- }
2573
-
2574
2599
  const DEFAULT_VIEW_TAG = -1;
2575
2600
  /**
2576
2601
  * [See documentation](https://docs.colyseus.io/state/schema/)
@@ -3868,11 +3893,19 @@ class Encoder {
3868
3893
  tryEncodeTypeId(bytes, baseType, targetType, it) {
3869
3894
  const baseTypeId = this.context.getTypeId(baseType);
3870
3895
  const targetTypeId = this.context.getTypeId(targetType);
3896
+ if (targetTypeId === undefined) {
3897
+ 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.`);
3898
+ return;
3899
+ }
3871
3900
  if (baseTypeId !== targetTypeId) {
3872
3901
  bytes[it.offset++] = TYPE_ID & 255;
3873
3902
  number$1(bytes, targetTypeId, it);
3874
3903
  }
3875
3904
  }
3905
+ get hasChanges() {
3906
+ return (this.root.changes.length > 0 ||
3907
+ this.root.filteredChanges.length > 0);
3908
+ }
3876
3909
  }
3877
3910
 
3878
3911
  class DecodingWarning extends Error {
@@ -4137,10 +4170,16 @@ class Reflection extends Schema {
4137
4170
  super(...arguments);
4138
4171
  this.types = new ArraySchema();
4139
4172
  }
4140
- static encode(instance, context, it = { offset: 0 }) {
4141
- context ??= new TypeContext(instance.constructor);
4173
+ /**
4174
+ * Encodes the TypeContext of an Encoder into a buffer.
4175
+ *
4176
+ * @param context TypeContext instance
4177
+ * @param it
4178
+ * @returns
4179
+ */
4180
+ static encode(context, it = { offset: 0 }) {
4142
4181
  const reflection = new Reflection();
4143
- const encoder = new Encoder(reflection);
4182
+ const reflectionEncoder = new Encoder(reflection);
4144
4183
  const buildType = (currentType, metadata) => {
4145
4184
  for (const fieldIndex in metadata) {
4146
4185
  const index = Number(fieldIndex);
@@ -4194,9 +4233,16 @@ class Reflection extends Schema {
4194
4233
  }
4195
4234
  buildType(type, klass[Symbol.metadata]);
4196
4235
  }
4197
- const buf = encoder.encodeAll(it);
4236
+ const buf = reflectionEncoder.encodeAll(it);
4198
4237
  return Buffer.from(buf, 0, it.offset);
4199
4238
  }
4239
+ /**
4240
+ * Decodes the TypeContext from a buffer into a Decoder instance.
4241
+ *
4242
+ * @param bytes Reflection.encode() output
4243
+ * @param it
4244
+ * @returns Decoder instance
4245
+ */
4200
4246
  static decode(bytes, it) {
4201
4247
  const reflection = new Reflection();
4202
4248
  const reflectionDecoder = new Decoder(reflection);
@@ -4242,8 +4288,8 @@ class Reflection extends Schema {
4242
4288
  }
4243
4289
  });
4244
4290
  });
4245
- // @ts-ignore
4246
- return new (typeContext.get(0))();
4291
+ const state = new (typeContext.get(0))();
4292
+ return new Decoder(state, typeContext);
4247
4293
  }
4248
4294
  }
4249
4295
  __decorate([