@colyseus/schema 3.0.20 → 3.0.21

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.
@@ -598,6 +598,7 @@ class TypeContext {
598
598
  * Keeps track of which classes extends which. (parent -> children)
599
599
  */
600
600
  static { this.inheritedTypes = new Map(); }
601
+ static { this.cachedContexts = new Map(); }
601
602
  static register(target) {
602
603
  const parent = Object.getPrototypeOf(target);
603
604
  if (parent !== Schema) {
@@ -609,17 +610,20 @@ class TypeContext {
609
610
  inherits.add(target);
610
611
  }
611
612
  }
613
+ static cache(rootClass) {
614
+ let context = TypeContext.cachedContexts.get(rootClass);
615
+ if (!context) {
616
+ context = new TypeContext(rootClass);
617
+ TypeContext.cachedContexts.set(rootClass, context);
618
+ }
619
+ return context;
620
+ }
612
621
  constructor(rootClass) {
613
622
  this.types = {};
614
623
  this.schemas = new Map();
615
624
  this.hasFilters = false;
616
625
  this.parentFiltered = {};
617
626
  if (rootClass) {
618
- //
619
- // TODO:
620
- // cache "discoverTypes" results for each rootClass
621
- // to avoid re-discovering types for each new context/room
622
- //
623
627
  this.discoverTypes(rootClass);
624
628
  }
625
629
  }
@@ -3765,10 +3769,12 @@ class Encoder {
3765
3769
  constructor(state) {
3766
3770
  this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
3767
3771
  //
3768
- // TODO: cache and restore "Context" based on root schema
3769
- // (to avoid creating a new context for every new room)
3772
+ // Use .cache() here to avoid re-creating a new context for every new room instance.
3773
+ //
3774
+ // We may need to make this optional in case of dynamically created
3775
+ // schemas - which would lead to memory leaks
3770
3776
  //
3771
- this.context = new TypeContext(state.constructor);
3777
+ this.context = TypeContext.cache(state.constructor);
3772
3778
  this.root = new Root(this.context);
3773
3779
  this.setState(state);
3774
3780
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
@@ -4273,50 +4279,28 @@ class Reflection extends Schema {
4273
4279
  if (rootType > 0) {
4274
4280
  reflection.rootType = rootType;
4275
4281
  }
4276
- const buildType = (currentType, metadata) => {
4277
- for (const fieldIndex in metadata) {
4278
- const index = Number(fieldIndex);
4279
- const fieldName = metadata[index].name;
4280
- // skip fields from parent classes
4281
- if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4282
- continue;
4283
- }
4284
- const field = new ReflectionField();
4285
- field.name = fieldName;
4286
- let fieldType;
4287
- const type = metadata[index].type;
4288
- if (typeof (type) === "string") {
4289
- fieldType = type;
4282
+ const includedTypeIds = new Set();
4283
+ const pendingReflectionTypes = {};
4284
+ // add type to reflection in a way that respects inheritance
4285
+ // (parent types should be added before their children)
4286
+ const addType = (type) => {
4287
+ if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
4288
+ includedTypeIds.add(type.id);
4289
+ reflection.types.push(type);
4290
+ const deps = pendingReflectionTypes[type.id];
4291
+ if (deps !== undefined) {
4292
+ delete pendingReflectionTypes[type.id];
4293
+ deps.forEach((childType) => addType(childType));
4290
4294
  }
4291
- else {
4292
- let childTypeSchema;
4293
- //
4294
- // TODO: refactor below.
4295
- //
4296
- if (Schema.is(type)) {
4297
- fieldType = "ref";
4298
- childTypeSchema = type;
4299
- }
4300
- else {
4301
- fieldType = Object.keys(type)[0];
4302
- if (typeof (type[fieldType]) === "string") {
4303
- fieldType += ":" + type[fieldType]; // array:string
4304
- }
4305
- else {
4306
- childTypeSchema = type[fieldType];
4307
- }
4308
- }
4309
- field.referencedType = (childTypeSchema)
4310
- ? context.getTypeId(childTypeSchema)
4311
- : -1;
4295
+ }
4296
+ else {
4297
+ if (pendingReflectionTypes[type.extendsId] === undefined) {
4298
+ pendingReflectionTypes[type.extendsId] = [];
4312
4299
  }
4313
- field.type = fieldType;
4314
- currentType.fields.push(field);
4300
+ pendingReflectionTypes[type.extendsId].push(type);
4315
4301
  }
4316
- reflection.types.push(currentType);
4317
4302
  };
4318
- for (let typeid in context.types) {
4319
- const klass = context.types[typeid];
4303
+ context.schemas.forEach((typeid, klass) => {
4320
4304
  const type = new ReflectionType();
4321
4305
  type.id = Number(typeid);
4322
4306
  // support inheritance
@@ -4324,7 +4308,57 @@ class Reflection extends Schema {
4324
4308
  if (inheritFrom !== Schema) {
4325
4309
  type.extendsId = context.schemas.get(inheritFrom);
4326
4310
  }
4327
- buildType(type, klass[Symbol.metadata]);
4311
+ const metadata = klass[Symbol.metadata];
4312
+ //
4313
+ // FIXME: this is a workaround for inherited types without additional fields
4314
+ // if metadata is the same reference as the parent class - it means the class has no own metadata
4315
+ //
4316
+ if (metadata !== inheritFrom[Symbol.metadata]) {
4317
+ for (const fieldIndex in metadata) {
4318
+ const index = Number(fieldIndex);
4319
+ const fieldName = metadata[index].name;
4320
+ // skip fields from parent classes
4321
+ if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4322
+ continue;
4323
+ }
4324
+ const reflectionField = new ReflectionField();
4325
+ reflectionField.name = fieldName;
4326
+ let fieldType;
4327
+ const field = metadata[index];
4328
+ if (typeof (field.type) === "string") {
4329
+ fieldType = field.type;
4330
+ }
4331
+ else {
4332
+ let childTypeSchema;
4333
+ //
4334
+ // TODO: refactor below.
4335
+ //
4336
+ if (Schema.is(field.type)) {
4337
+ fieldType = "ref";
4338
+ childTypeSchema = field.type;
4339
+ }
4340
+ else {
4341
+ fieldType = Object.keys(field.type)[0];
4342
+ if (typeof (field.type[fieldType]) === "string") {
4343
+ fieldType += ":" + field.type[fieldType]; // array:string
4344
+ }
4345
+ else {
4346
+ childTypeSchema = field.type[fieldType];
4347
+ }
4348
+ }
4349
+ reflectionField.referencedType = (childTypeSchema)
4350
+ ? context.getTypeId(childTypeSchema)
4351
+ : -1;
4352
+ }
4353
+ reflectionField.type = fieldType;
4354
+ type.fields.push(reflectionField);
4355
+ }
4356
+ }
4357
+ addType(type);
4358
+ });
4359
+ // in case there are types that were not added due to inheritance
4360
+ for (const typeid in pendingReflectionTypes) {
4361
+ pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
4328
4362
  }
4329
4363
  const buf = reflectionEncoder.encodeAll(it);
4330
4364
  return Buffer.from(buf, 0, it.offset);