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