@colyseus/schema 3.0.19 → 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
  }
@@ -3313,7 +3317,7 @@ class CollectionSchema {
3313
3317
  static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
3314
3318
  return (!view ||
3315
3319
  typeof (ref[$childType]) === "string" ||
3316
- view.items.has(ref[$getByIndex](index)[$changes]));
3320
+ view.items.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
3317
3321
  }
3318
3322
  static is(type) {
3319
3323
  return type['collection'] !== undefined;
@@ -3321,6 +3325,7 @@ class CollectionSchema {
3321
3325
  constructor(initialValues) {
3322
3326
  this.$items = new Map();
3323
3327
  this.$indexes = new Map();
3328
+ this.deletedItems = {};
3324
3329
  this.$refId = 0;
3325
3330
  this[$changes] = new ChangeTree(this);
3326
3331
  this[$changes].indexes = {};
@@ -3370,7 +3375,7 @@ class CollectionSchema {
3370
3375
  if (index === undefined) {
3371
3376
  return false;
3372
3377
  }
3373
- this[$changes].delete(index);
3378
+ this.deletedItems[index] = this[$changes].delete(index);
3374
3379
  this.$indexes.delete(index);
3375
3380
  return this.$items.delete(index);
3376
3381
  }
@@ -3415,6 +3420,9 @@ class CollectionSchema {
3415
3420
  this.$items.delete(key);
3416
3421
  this.$indexes.delete(index);
3417
3422
  }
3423
+ [$onEncodeEnd]() {
3424
+ this.deletedItems = {};
3425
+ }
3418
3426
  toArray() {
3419
3427
  return Array.from(this.$items.values());
3420
3428
  }
@@ -3469,7 +3477,7 @@ class SetSchema {
3469
3477
  static [(_a = $encoder, _b = $decoder, $filter)](ref, index, view) {
3470
3478
  return (!view ||
3471
3479
  typeof (ref[$childType]) === "string" ||
3472
- view.items.has(ref[$getByIndex](index)[$changes]));
3480
+ view.items.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
3473
3481
  }
3474
3482
  static is(type) {
3475
3483
  return type['set'] !== undefined;
@@ -3477,6 +3485,7 @@ class SetSchema {
3477
3485
  constructor(initialValues) {
3478
3486
  this.$items = new Map();
3479
3487
  this.$indexes = new Map();
3488
+ this.deletedItems = {};
3480
3489
  this.$refId = 0;
3481
3490
  this[$changes] = new ChangeTree(this);
3482
3491
  this[$changes].indexes = {};
@@ -3526,7 +3535,7 @@ class SetSchema {
3526
3535
  if (index === undefined) {
3527
3536
  return false;
3528
3537
  }
3529
- this[$changes].delete(index);
3538
+ this.deletedItems[index] = this[$changes].delete(index);
3530
3539
  this.$indexes.delete(index);
3531
3540
  return this.$items.delete(index);
3532
3541
  }
@@ -3583,6 +3592,9 @@ class SetSchema {
3583
3592
  this.$items.delete(key);
3584
3593
  this.$indexes.delete(index);
3585
3594
  }
3595
+ [$onEncodeEnd]() {
3596
+ this.deletedItems = {};
3597
+ }
3586
3598
  toArray() {
3587
3599
  return Array.from(this.$items.values());
3588
3600
  }
@@ -3759,10 +3771,12 @@ class Encoder {
3759
3771
  constructor(state) {
3760
3772
  this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
3761
3773
  //
3762
- // TODO: cache and restore "Context" based on root schema
3763
- // (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
3764
3778
  //
3765
- this.context = new TypeContext(state.constructor);
3779
+ this.context = TypeContext.cache(state.constructor);
3766
3780
  this.root = new Root(this.context);
3767
3781
  this.setState(state);
3768
3782
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
@@ -3787,7 +3801,7 @@ class Encoder {
3787
3801
  view.invisible.add(changeTree);
3788
3802
  continue; // skip this change tree
3789
3803
  }
3790
- else if (view.invisible.has(changeTree)) {
3804
+ else {
3791
3805
  view.invisible.delete(changeTree); // remove from invisible list
3792
3806
  }
3793
3807
  }
@@ -4267,50 +4281,28 @@ class Reflection extends Schema {
4267
4281
  if (rootType > 0) {
4268
4282
  reflection.rootType = rootType;
4269
4283
  }
4270
- const buildType = (currentType, metadata) => {
4271
- for (const fieldIndex in metadata) {
4272
- const index = Number(fieldIndex);
4273
- const fieldName = metadata[index].name;
4274
- // skip fields from parent classes
4275
- if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
4276
- continue;
4277
- }
4278
- const field = new ReflectionField();
4279
- field.name = fieldName;
4280
- let fieldType;
4281
- const type = metadata[index].type;
4282
- if (typeof (type) === "string") {
4283
- 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));
4284
4296
  }
4285
- else {
4286
- let childTypeSchema;
4287
- //
4288
- // TODO: refactor below.
4289
- //
4290
- if (Schema.is(type)) {
4291
- fieldType = "ref";
4292
- childTypeSchema = type;
4293
- }
4294
- else {
4295
- fieldType = Object.keys(type)[0];
4296
- if (typeof (type[fieldType]) === "string") {
4297
- fieldType += ":" + type[fieldType]; // array:string
4298
- }
4299
- else {
4300
- childTypeSchema = type[fieldType];
4301
- }
4302
- }
4303
- field.referencedType = (childTypeSchema)
4304
- ? context.getTypeId(childTypeSchema)
4305
- : -1;
4297
+ }
4298
+ else {
4299
+ if (pendingReflectionTypes[type.extendsId] === undefined) {
4300
+ pendingReflectionTypes[type.extendsId] = [];
4306
4301
  }
4307
- field.type = fieldType;
4308
- currentType.fields.push(field);
4302
+ pendingReflectionTypes[type.extendsId].push(type);
4309
4303
  }
4310
- reflection.types.push(currentType);
4311
4304
  };
4312
- for (let typeid in context.types) {
4313
- const klass = context.types[typeid];
4305
+ context.schemas.forEach((typeid, klass) => {
4314
4306
  const type = new ReflectionType();
4315
4307
  type.id = Number(typeid);
4316
4308
  // support inheritance
@@ -4318,7 +4310,57 @@ class Reflection extends Schema {
4318
4310
  if (inheritFrom !== Schema) {
4319
4311
  type.extendsId = context.schemas.get(inheritFrom);
4320
4312
  }
4321
- 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));
4322
4364
  }
4323
4365
  const buf = reflectionEncoder.encodeAll(it);
4324
4366
  return Buffer.from(buf, 0, it.offset);
@@ -4743,10 +4785,11 @@ class StateView {
4743
4785
  } // skip "undefined" indexes
4744
4786
  const op = changeTree.indexedOperations[index] ?? exports.OPERATION.ADD;
4745
4787
  const tagAtIndex = metadata?.[index].tag;
4746
- if ((isInvisible || // if "invisible", include all
4747
- tagAtIndex === undefined || // "all change" with no tag
4748
- tagAtIndex === tag // tagged property
4749
- ) &&
4788
+ if (!changeTree.isNew && // new structures will be added as part of .encode() call, no need to force it to .encodeView()
4789
+ (isInvisible || // if "invisible", include all
4790
+ tagAtIndex === undefined || // "all change" with no tag
4791
+ tagAtIndex === tag // tagged property
4792
+ ) &&
4750
4793
  op !== exports.OPERATION.DELETE) {
4751
4794
  changes[index] = op;
4752
4795
  }
@@ -4775,10 +4818,8 @@ class StateView {
4775
4818
  if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4776
4819
  this.addParentOf(changeTree, tag);
4777
4820
  }
4778
- // parent is already available, no need to add it!
4779
- if (!this.invisible.has(changeTree)) {
4780
- return;
4781
- }
4821
+ // // parent is already available, no need to add it!
4822
+ // if (!this.invisible.has(changeTree)) { return; }
4782
4823
  }
4783
4824
  // add parent's tag properties
4784
4825
  if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {