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