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