@colyseus/schema 3.0.0-alpha.41 → 3.0.0-alpha.44

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.
Files changed (38) hide show
  1. package/build/cjs/index.js +125 -79
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +125 -79
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +125 -79
  6. package/lib/Metadata.js.map +1 -1
  7. package/lib/Schema.js +3 -3
  8. package/lib/Schema.js.map +1 -1
  9. package/lib/codegen/languages/haxe.js +2 -1
  10. package/lib/codegen/languages/haxe.js.map +1 -1
  11. package/lib/codegen/languages/lua.js +21 -22
  12. package/lib/codegen/languages/lua.js.map +1 -1
  13. package/lib/decoder/ReferenceTracker.js +1 -0
  14. package/lib/decoder/ReferenceTracker.js.map +1 -1
  15. package/lib/encoder/ChangeTree.d.ts +5 -3
  16. package/lib/encoder/ChangeTree.js +52 -62
  17. package/lib/encoder/ChangeTree.js.map +1 -1
  18. package/lib/encoder/Root.js +1 -1
  19. package/lib/encoder/Root.js.map +1 -1
  20. package/lib/encoder/StateView.js +6 -4
  21. package/lib/encoder/StateView.js.map +1 -1
  22. package/lib/encoding/decode.d.ts +2 -0
  23. package/lib/encoding/decode.js +14 -0
  24. package/lib/encoding/decode.js.map +1 -1
  25. package/lib/types/TypeContext.d.ts +6 -0
  26. package/lib/types/TypeContext.js +49 -9
  27. package/lib/types/TypeContext.js.map +1 -1
  28. package/package.json +1 -1
  29. package/src/Metadata.ts +1 -1
  30. package/src/Schema.ts +3 -3
  31. package/src/codegen/languages/haxe.ts +2 -1
  32. package/src/codegen/languages/lua.ts +18 -26
  33. package/src/decoder/ReferenceTracker.ts +2 -1
  34. package/src/encoder/ChangeTree.ts +59 -76
  35. package/src/encoder/Root.ts +1 -1
  36. package/src/encoder/StateView.ts +9 -5
  37. package/src/encoding/decode.ts +16 -1
  38. package/src/types/TypeContext.ts +55 -13
@@ -538,6 +538,18 @@ function number(bytes, it) {
538
538
  return (0xff - prefix + 1) * -1;
539
539
  }
540
540
  }
541
+ function stringCheck(bytes, it) {
542
+ const prefix = bytes[it.offset];
543
+ return (
544
+ // fixstr
545
+ (prefix < 0xc0 && prefix > 0xa0) ||
546
+ // str 8
547
+ prefix === 0xd9 ||
548
+ // str 16
549
+ prefix === 0xda ||
550
+ // str 32
551
+ prefix === 0xdb);
552
+ }
541
553
  const decode = {
542
554
  utf8Read,
543
555
  int8,
@@ -555,6 +567,7 @@ const decode = {
555
567
  boolean,
556
568
  string,
557
569
  number,
570
+ stringCheck,
558
571
  };
559
572
 
560
573
  const registeredTypes = {};
@@ -604,6 +617,11 @@ class TypeContext {
604
617
  this.hasFilters = false;
605
618
  this.parentFiltered = {};
606
619
  if (rootClass) {
620
+ //
621
+ // TODO:
622
+ // cache "discoverTypes" results for each rootClass
623
+ // to avoid re-discovering types for each new context/room
624
+ //
607
625
  this.discoverTypes(rootClass);
608
626
  }
609
627
  }
@@ -631,13 +649,17 @@ class TypeContext {
631
649
  getTypeId(klass) {
632
650
  return this.schemas.get(klass);
633
651
  }
634
- discoverTypes(klass, parentIndex, parentFieldViewTag) {
652
+ discoverTypes(klass, parentType, parentIndex, parentHasViewTag) {
653
+ if (parentHasViewTag) {
654
+ this.registerFilteredByParent(klass, parentType, parentIndex);
655
+ }
656
+ // skip if already registered
635
657
  if (!this.add(klass)) {
636
658
  return;
637
659
  }
638
660
  // add classes inherited from this base class
639
661
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
640
- this.discoverTypes(child, parentIndex, parentFieldViewTag);
662
+ this.discoverTypes(child, klass, parentIndex, parentHasViewTag);
641
663
  });
642
664
  // add parent classes
643
665
  let parent = klass;
@@ -652,13 +674,10 @@ class TypeContext {
652
674
  if (metadata[$viewFieldIndexes]) {
653
675
  this.hasFilters = true;
654
676
  }
655
- if (parentFieldViewTag !== undefined) {
656
- this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
657
- }
658
677
  for (const fieldIndex in metadata) {
659
678
  const index = fieldIndex;
660
679
  const fieldType = metadata[index].type;
661
- const viewTag = metadata[index].tag;
680
+ const fieldHasViewTag = (metadata[index].tag !== undefined);
662
681
  if (typeof (fieldType) === "string") {
663
682
  continue;
664
683
  }
@@ -668,10 +687,10 @@ class TypeContext {
668
687
  if (type === "string") {
669
688
  continue;
670
689
  }
671
- this.discoverTypes(type, index, viewTag);
690
+ this.discoverTypes(type, klass, index, parentHasViewTag || fieldHasViewTag);
672
691
  }
673
692
  else if (typeof (fieldType) === "function") {
674
- this.discoverTypes(fieldType, viewTag);
693
+ this.discoverTypes(fieldType, klass, index, parentHasViewTag || fieldHasViewTag);
675
694
  }
676
695
  else {
677
696
  const type = Object.values(fieldType)[0];
@@ -679,10 +698,44 @@ class TypeContext {
679
698
  if (typeof (type) === "string") {
680
699
  continue;
681
700
  }
682
- this.discoverTypes(type, index, viewTag);
701
+ this.discoverTypes(type, klass, index, parentHasViewTag || fieldHasViewTag);
683
702
  }
684
703
  }
685
704
  }
705
+ /**
706
+ * Keep track of which classes have filters applied.
707
+ * Format: `${typeid}-${parentTypeid}-${parentIndex}`
708
+ */
709
+ registerFilteredByParent(schema, parentType, parentIndex) {
710
+ const typeid = this.schemas.get(schema) ?? this.schemas.size;
711
+ let key = `${typeid}`;
712
+ if (parentType) {
713
+ key += `-${this.schemas.get(parentType)}`;
714
+ }
715
+ key += `-${parentIndex}`;
716
+ this.parentFiltered[key] = true;
717
+ }
718
+ debug() {
719
+ let parentFiltered = "";
720
+ for (const key in this.parentFiltered) {
721
+ const keys = key.split("-").map(Number);
722
+ const fieldIndex = keys.pop();
723
+ parentFiltered += `\n\t\t`;
724
+ parentFiltered += `${key}: ${keys.reverse().map((id, i) => {
725
+ const klass = this.types[id];
726
+ const metadata = klass[Symbol.metadata];
727
+ let txt = klass.name;
728
+ if (i === 0) {
729
+ txt += `[${metadata[fieldIndex].name}]`;
730
+ }
731
+ return `${txt}`;
732
+ }).join(" -> ")}`;
733
+ }
734
+ return `TypeContext ->\n` +
735
+ `\tSchema types: ${this.schemas.size}\n` +
736
+ `\thasFilters: ${this.hasFilters}\n` +
737
+ `\tparentFiltered:${parentFiltered}`;
738
+ }
686
739
  }
687
740
 
688
741
  function getNormalizedType(type) {
@@ -927,8 +980,10 @@ function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeT
927
980
  }
928
981
  class ChangeTree {
929
982
  constructor(ref) {
983
+ /**
984
+ * Whether this structure is parent of a filtered structure.
985
+ */
930
986
  this.isFiltered = false;
931
- this.isPartiallyFiltered = false;
932
987
  this.indexedOperations = {};
933
988
  //
934
989
  // TODO:
@@ -947,37 +1002,17 @@ class ChangeTree {
947
1002
  //
948
1003
  // Does this structure have "filters" declared?
949
1004
  //
950
- if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
1005
+ const metadata = ref.constructor[Symbol.metadata];
1006
+ if (metadata?.[$viewFieldIndexes]) {
951
1007
  this.allFilteredChanges = { indexes: {}, operations: [] };
952
1008
  this.filteredChanges = { indexes: {}, operations: [] };
953
1009
  }
954
1010
  }
955
1011
  setRoot(root) {
956
1012
  this.root = root;
957
- const isNewChangeTree = this.root.add(this);
958
- const metadata = this.ref.constructor[Symbol.metadata];
959
- if (this.root.types.hasFilters) {
960
- //
961
- // At Schema initialization, the "root" structure might not be available
962
- // yet, as it only does once the "Encoder" has been set up.
963
- //
964
- // So the "parent" may be already set without a "root".
965
- //
966
- this.checkIsFiltered(metadata, this.parent, this.parentIndex);
967
- if (this.isFiltered || this.isPartiallyFiltered) {
968
- enqueueChangeTree(root, this, 'filteredChanges');
969
- if (isNewChangeTree) {
970
- this.root.allFilteredChanges.push(this);
971
- }
972
- }
973
- }
974
- if (!this.isFiltered) {
975
- enqueueChangeTree(root, this, 'changes');
976
- if (isNewChangeTree) {
977
- this.root.allChanges.push(this);
978
- }
979
- }
1013
+ this.checkIsFiltered(this.parent, this.parentIndex);
980
1014
  // Recursively set root on child structures
1015
+ const metadata = this.ref.constructor[Symbol.metadata];
981
1016
  if (metadata) {
982
1017
  metadata[$refTypeFieldIndexes]?.forEach((index) => {
983
1018
  const field = metadata[index];
@@ -999,39 +1034,21 @@ class ChangeTree {
999
1034
  if (!root) {
1000
1035
  return;
1001
1036
  }
1002
- const metadata = this.ref.constructor[Symbol.metadata];
1003
1037
  // skip if parent is already set
1004
1038
  if (root !== this.root) {
1005
1039
  this.root = root;
1006
- const isNewChangeTree = root.add(this);
1007
- if (root.types.hasFilters) {
1008
- this.checkIsFiltered(metadata, parent, parentIndex);
1009
- if (this.isFiltered || this.isPartiallyFiltered) {
1010
- enqueueChangeTree(root, this, 'filteredChanges');
1011
- if (isNewChangeTree) {
1012
- this.root.allFilteredChanges.push(this);
1013
- }
1014
- }
1015
- }
1016
- if (!this.isFiltered) {
1017
- enqueueChangeTree(root, this, 'changes');
1018
- if (isNewChangeTree) {
1019
- this.root.allChanges.push(this);
1020
- }
1021
- }
1040
+ this.checkIsFiltered(parent, parentIndex);
1022
1041
  }
1023
1042
  else {
1024
1043
  root.add(this);
1025
1044
  }
1026
1045
  // assign same parent on child structures
1046
+ const metadata = this.ref.constructor[Symbol.metadata];
1027
1047
  if (metadata) {
1028
1048
  metadata[$refTypeFieldIndexes]?.forEach((index) => {
1029
1049
  const field = metadata[index];
1030
1050
  const value = this.ref[field.name];
1031
1051
  value?.[$changes].setParent(this.ref, root, index);
1032
- // try { throw new Error(); } catch (e) {
1033
- // console.log(e.stack);
1034
- // }
1035
1052
  });
1036
1053
  }
1037
1054
  else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
@@ -1124,7 +1141,7 @@ class ChangeTree {
1124
1141
  //
1125
1142
  // - ArraySchema#splice()
1126
1143
  //
1127
- if (this.isFiltered || this.isPartiallyFiltered) {
1144
+ if (this.filteredChanges !== undefined) {
1128
1145
  this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
1129
1146
  this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
1130
1147
  }
@@ -1153,7 +1170,7 @@ class ChangeTree {
1153
1170
  }
1154
1171
  indexedOperation(index, operation, allChangesIndex = index) {
1155
1172
  this.indexedOperations[index] = operation;
1156
- if (this.filteredChanges) {
1173
+ if (this.filteredChanges !== undefined) {
1157
1174
  setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
1158
1175
  setOperationAtIndex(this.filteredChanges, index);
1159
1176
  enqueueChangeTree(this.root, this, 'filteredChanges');
@@ -1201,7 +1218,7 @@ class ChangeTree {
1201
1218
  }
1202
1219
  return;
1203
1220
  }
1204
- const changeSet = (this.filteredChanges)
1221
+ const changeSet = (this.filteredChanges !== undefined)
1205
1222
  ? this.filteredChanges
1206
1223
  : this.changes;
1207
1224
  this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
@@ -1217,14 +1234,14 @@ class ChangeTree {
1217
1234
  // - This is due to using the concrete Schema class at decoding time.
1218
1235
  // - "Reflected" structures do not have this problem.
1219
1236
  //
1220
- // (the property descriptors should NOT be used at decoding time. only at encoding time.)
1237
+ // (The property descriptors should NOT be used at decoding time. only at encoding time.)
1221
1238
  //
1222
1239
  this.root?.remove(previousValue[$changes]);
1223
1240
  }
1224
1241
  //
1225
1242
  // FIXME: this is looking a ugly and repeated
1226
1243
  //
1227
- if (this.filteredChanges) {
1244
+ if (this.filteredChanges !== undefined) {
1228
1245
  deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
1229
1246
  enqueueChangeTree(this.root, this, 'filteredChanges');
1230
1247
  }
@@ -1293,29 +1310,59 @@ class ChangeTree {
1293
1310
  get changed() {
1294
1311
  return (Object.entries(this.indexedOperations).length > 0);
1295
1312
  }
1296
- checkIsFiltered(metadata, parent, parentIndex) {
1297
- // Detect if current structure has "filters" declared
1298
- this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
1299
- if (this.isPartiallyFiltered) {
1300
- this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
1301
- this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
1313
+ checkIsFiltered(parent, parentIndex) {
1314
+ const isNewChangeTree = this.root.add(this);
1315
+ if (this.root.types.hasFilters) {
1316
+ //
1317
+ // At Schema initialization, the "root" structure might not be available
1318
+ // yet, as it only does once the "Encoder" has been set up.
1319
+ //
1320
+ // So the "parent" may be already set without a "root".
1321
+ //
1322
+ this._checkFilteredByParent(parent, parentIndex);
1323
+ if (this.filteredChanges !== undefined) {
1324
+ enqueueChangeTree(this.root, this, 'filteredChanges');
1325
+ if (isNewChangeTree) {
1326
+ this.root.allFilteredChanges.push(this);
1327
+ }
1328
+ }
1302
1329
  }
1330
+ if (!this.isFiltered) {
1331
+ enqueueChangeTree(this.root, this, 'changes');
1332
+ if (isNewChangeTree) {
1333
+ this.root.allChanges.push(this);
1334
+ }
1335
+ }
1336
+ }
1337
+ _checkFilteredByParent(parent, parentIndex) {
1303
1338
  // skip if parent is not set
1304
1339
  if (!parent) {
1305
1340
  return;
1306
1341
  }
1342
+ // ArraySchema | MapSchema - get the child type
1343
+ const ref = Metadata.isValidInstance(this.ref)
1344
+ ? this.ref
1345
+ : new this.ref[$childType];
1307
1346
  if (!Metadata.isValidInstance(parent)) {
1308
1347
  const parentChangeTree = parent[$changes];
1309
1348
  parent = parentChangeTree.parent;
1310
1349
  parentIndex = parentChangeTree.parentIndex;
1311
1350
  }
1312
- const parentMetadata = parent.constructor?.[Symbol.metadata];
1313
- this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
1351
+ const parentConstructor = parent.constructor;
1352
+ let key = `${this.root.types.getTypeId(ref.constructor)}`;
1353
+ if (parentConstructor) {
1354
+ key += `-${this.root.types.schemas.get(parentConstructor)}`;
1355
+ }
1356
+ key += `-${parentIndex}`;
1357
+ this.isFiltered = this.root.types.parentFiltered[key];
1358
+ // const parentMetadata = parentConstructor?.[Symbol.metadata];
1359
+ // this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex) || this.root.types.parentFiltered[key];
1314
1360
  //
1315
1361
  // TODO: refactor this!
1316
1362
  //
1317
1363
  // swapping `changes` and `filteredChanges` is required here
1318
1364
  // because "isFiltered" may not be imedialely available on `change()`
1365
+ // (this happens when instance is detached from root or parent)
1319
1366
  //
1320
1367
  if (this.isFiltered) {
1321
1368
  this.filteredChanges = { indexes: {}, operations: [] };
@@ -1329,10 +1376,6 @@ class ChangeTree {
1329
1376
  const allFilteredChanges = this.allFilteredChanges;
1330
1377
  this.allFilteredChanges = this.allChanges;
1331
1378
  this.allChanges = allFilteredChanges;
1332
- // console.log("SWAP =>", {
1333
- // "this.allFilteredChanges": this.allFilteredChanges,
1334
- // "this.allChanges": this.allChanges
1335
- // })
1336
1379
  }
1337
1380
  }
1338
1381
  }
@@ -3220,7 +3263,7 @@ class Schema {
3220
3263
  const rootChangeTree = ref[$changes];
3221
3264
  const root = rootChangeTree.root;
3222
3265
  const changeTrees = new Map();
3223
- let totalInstances = 0;
3266
+ const instanceRefIds = [];
3224
3267
  let totalOperations = 0;
3225
3268
  for (const [refId, changes] of Object.entries(root[changeSetName])) {
3226
3269
  const changeTree = root.changeTrees[refId];
@@ -3241,14 +3284,14 @@ class Schema {
3241
3284
  }
3242
3285
  }
3243
3286
  if (includeChangeTree) {
3244
- totalInstances += 1;
3287
+ instanceRefIds.push(changeTree.refId);
3245
3288
  totalOperations += Object.keys(changes).length;
3246
3289
  changeTrees.set(changeTree, parentChangeTrees.reverse());
3247
3290
  }
3248
3291
  }
3249
3292
  output += "---\n";
3250
3293
  output += `root refId: ${rootChangeTree.refId}\n`;
3251
- output += `Total instances: ${totalInstances}\n`;
3294
+ output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
3252
3295
  output += `Total changes: ${totalOperations}\n`;
3253
3296
  output += "---\n";
3254
3297
  // based on root.changes, display a tree of changes that has the "ref" instance as parent
@@ -3689,7 +3732,7 @@ class Root {
3689
3732
  delete this.changeTrees[changeTree.refId];
3690
3733
  this.removeChangeFromChangeSet("allChanges", changeTree);
3691
3734
  this.removeChangeFromChangeSet("changes", changeTree);
3692
- if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
3735
+ if (changeTree.filteredChanges) {
3693
3736
  this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
3694
3737
  this.removeChangeFromChangeSet("filteredChanges", changeTree);
3695
3738
  }
@@ -4035,6 +4078,7 @@ class ReferenceTracker {
4035
4078
  clearRefs() {
4036
4079
  this.refs.clear();
4037
4080
  this.deletedRefs.clear();
4081
+ this.callbacks = {};
4038
4082
  this.refCounts = {};
4039
4083
  }
4040
4084
  // for decoding
@@ -4699,7 +4743,7 @@ class StateView {
4699
4743
  }
4700
4744
  else {
4701
4745
  const isInvisible = this.invisible.has(changeTree);
4702
- const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4746
+ const changeSet = (changeTree.filteredChanges !== undefined)
4703
4747
  ? changeTree.allFilteredChanges
4704
4748
  : changeTree.allChanges;
4705
4749
  for (let i = 0, len = changeSet.operations.length; i < len; i++) {
@@ -4707,7 +4751,7 @@ class StateView {
4707
4751
  if (index === undefined) {
4708
4752
  continue;
4709
4753
  } // skip "undefined" indexes
4710
- const op = changeTree.indexedOperations[index];
4754
+ const op = changeTree.indexedOperations[index] ?? exports.OPERATION.ADD;
4711
4755
  const tagAtIndex = metadata?.[index].tag;
4712
4756
  if ((isInvisible || // if "invisible", include all
4713
4757
  tagAtIndex === undefined || // "all change" with no tag
@@ -4721,7 +4765,9 @@ class StateView {
4721
4765
  // Add children of this ChangeTree to this view
4722
4766
  changeTree.forEachChild((change, index) => {
4723
4767
  // Do not ADD children that don't have the same tag
4724
- if (metadata && metadata[index].tag !== tag) {
4768
+ if (metadata &&
4769
+ metadata[index].tag !== undefined &&
4770
+ metadata[index].tag !== tag) {
4725
4771
  return;
4726
4772
  }
4727
4773
  this.add(change.ref, tag, false);
@@ -4733,7 +4779,7 @@ class StateView {
4733
4779
  this.items.add(changeTree);
4734
4780
  // add parent's parent
4735
4781
  const parentChangeTree = changeTree.parent?.[$changes];
4736
- if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
4782
+ if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4737
4783
  this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4738
4784
  }
4739
4785
  // parent is already available, no need to add it!