@colyseus/schema 3.0.0-alpha.27 → 3.0.0-alpha.29

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 (50) hide show
  1. package/build/cjs/index.js +114 -88
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +114 -88
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +114 -88
  6. package/lib/Metadata.d.ts +3 -0
  7. package/lib/Metadata.js.map +1 -1
  8. package/lib/Reflection.d.ts +1 -1
  9. package/lib/Reflection.js +4 -3
  10. package/lib/Reflection.js.map +1 -1
  11. package/lib/annotations.d.ts +0 -19
  12. package/lib/annotations.js +4 -106
  13. package/lib/annotations.js.map +1 -1
  14. package/lib/bench_encode.js +54 -27
  15. package/lib/bench_encode.js.map +1 -1
  16. package/lib/decoder/Decoder.d.ts +1 -1
  17. package/lib/decoder/Decoder.js +2 -2
  18. package/lib/decoder/Decoder.js.map +1 -1
  19. package/lib/decoder/strategy/StateCallbacks.js +5 -4
  20. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  21. package/lib/encoder/ChangeTree.d.ts +1 -12
  22. package/lib/encoder/ChangeTree.js +24 -51
  23. package/lib/encoder/ChangeTree.js.map +1 -1
  24. package/lib/encoder/Encoder.d.ts +6 -5
  25. package/lib/encoder/Encoder.js +28 -19
  26. package/lib/encoder/Encoder.js.map +1 -1
  27. package/lib/encoder/Root.d.ts +17 -0
  28. package/lib/encoder/Root.js +44 -0
  29. package/lib/encoder/Root.js.map +1 -0
  30. package/lib/index.d.ts +2 -1
  31. package/lib/index.js +3 -3
  32. package/lib/index.js.map +1 -1
  33. package/lib/types/TypeContext.d.ts +23 -0
  34. package/lib/types/TypeContext.js +109 -0
  35. package/lib/types/TypeContext.js.map +1 -0
  36. package/lib/types/custom/ArraySchema.js +0 -1
  37. package/lib/types/custom/ArraySchema.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/Metadata.ts +1 -0
  40. package/src/Reflection.ts +2 -1
  41. package/src/annotations.ts +1 -126
  42. package/src/bench_encode.ts +47 -16
  43. package/src/decoder/Decoder.ts +1 -1
  44. package/src/decoder/strategy/StateCallbacks.ts +5 -4
  45. package/src/encoder/ChangeTree.ts +30 -59
  46. package/src/encoder/Encoder.ts +36 -23
  47. package/src/encoder/Root.ts +51 -0
  48. package/src/index.ts +3 -11
  49. package/src/types/TypeContext.ts +127 -0
  50. package/src/types/custom/ArraySchema.ts +0 -1
@@ -194,44 +194,6 @@ const Metadata = {
194
194
  };
195
195
 
196
196
  var _a$5;
197
- class Root {
198
- constructor() {
199
- this.nextUniqueId = 0;
200
- this.refCount = new WeakMap();
201
- // all changes
202
- this.allChanges = new Map();
203
- this.allFilteredChanges = new Map();
204
- // pending changes to be encoded
205
- this.changes = new Map();
206
- this.filteredChanges = new Map();
207
- }
208
- getNextUniqueId() {
209
- return this.nextUniqueId++;
210
- }
211
- add(changeTree) {
212
- const refCount = this.refCount.get(changeTree) || 0;
213
- this.refCount.set(changeTree, refCount + 1);
214
- }
215
- remove(changeTree) {
216
- const refCount = this.refCount.get(changeTree);
217
- if (refCount <= 1) {
218
- this.allChanges.delete(changeTree);
219
- this.changes.delete(changeTree);
220
- if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
221
- this.allFilteredChanges.delete(changeTree);
222
- this.filteredChanges.delete(changeTree);
223
- }
224
- this.refCount.delete(changeTree);
225
- }
226
- else {
227
- this.refCount.set(changeTree, refCount - 1);
228
- }
229
- changeTree.forEachChild((child, _) => this.remove(child));
230
- }
231
- clear() {
232
- this.changes.clear();
233
- }
234
- }
235
197
  class ChangeTree {
236
198
  static { _a$5 = $isNew; }
237
199
  ;
@@ -263,8 +225,6 @@ class ChangeTree {
263
225
  if (this.isFiltered || this.isPartiallyFiltered) {
264
226
  this.root.allFilteredChanges.set(this, this.allFilteredChanges);
265
227
  this.root.filteredChanges.set(this, this.filteredChanges);
266
- // } else {
267
- // this.root.allChanges.set(this, this.allChanges);
268
228
  }
269
229
  if (!this.isFiltered) {
270
230
  this.root.allChanges.set(this, this.allChanges);
@@ -537,15 +497,30 @@ class ChangeTree {
537
497
  checkIsFiltered(parent, parentIndex) {
538
498
  // Detect if current structure has "filters" declared
539
499
  this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
540
- // TODO: support "partially filtered", where the instance is visible, but only a field is not.
541
- // Detect if parent has "filters" declared
542
- while (parent && !this.isFiltered) {
543
- const metadata = parent['constructor'][Symbol.metadata];
544
- const fieldName = metadata?.[parentIndex];
545
- const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
546
- this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
547
- parent = parent[$changes].parent;
548
- }
500
+ if (parent && !Metadata.isValidInstance(parent)) {
501
+ const parentChangeTree = parent[$changes];
502
+ parent = parentChangeTree.parent;
503
+ parentIndex = parentChangeTree.parentIndex;
504
+ }
505
+ const parentMetadata = parent?.['constructor']?.[Symbol.metadata];
506
+ this.isFiltered = (parent &&
507
+ parentMetadata?.[-2]?.includes(parentIndex));
508
+ // this.isFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-4];
509
+ // // Detect if parent has "filters" declared
510
+ // while (parent && !this.isFiltered) {
511
+ // const metadata: Metadata = parent['constructor'][Symbol.metadata];
512
+ // // this.isFiltered = metadata?.[-4];
513
+ // const fieldName = metadata?.[parentIndex];
514
+ // const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
515
+ // this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
516
+ // parent = parent[$changes].parent;
517
+ // };
518
+ // console.log("ChangeTree.checkIsFiltered", {
519
+ // parent: parent?.constructor.name,
520
+ // ref: this.ref.constructor.name,
521
+ // isFiltered: this.isFiltered,
522
+ // isPartiallyFiltered: this.isPartiallyFiltered,
523
+ // });
549
524
  //
550
525
  // TODO: refactor this!
551
526
  //
@@ -1686,7 +1661,6 @@ class ArraySchema {
1686
1661
  }
1687
1662
  const changeTree = this[$changes];
1688
1663
  changeTree.indexedOperation(length, OPERATION.ADD, this.items.length);
1689
- // changeTree.indexes[length] = length;
1690
1664
  this.items.push(value);
1691
1665
  this.tmpItems.push(value);
1692
1666
  //
@@ -2377,7 +2351,6 @@ class MapSchema {
2377
2351
  }
2378
2352
  registerType("map", { constructor: MapSchema });
2379
2353
 
2380
- const DEFAULT_VIEW_TAG = -1;
2381
2354
  class TypeContext {
2382
2355
  /**
2383
2356
  * For inheritance support
@@ -2399,6 +2372,7 @@ class TypeContext {
2399
2372
  this.types = {};
2400
2373
  this.schemas = new Map();
2401
2374
  this.hasFilters = false;
2375
+ this.parentFiltered = {};
2402
2376
  if (rootClass) {
2403
2377
  this.discoverTypes(rootClass);
2404
2378
  }
@@ -2427,32 +2401,32 @@ class TypeContext {
2427
2401
  getTypeId(klass) {
2428
2402
  return this.schemas.get(klass);
2429
2403
  }
2430
- discoverTypes(klass, parentFieldViewTag) {
2404
+ discoverTypes(klass, parentIndex, parentFieldViewTag) {
2431
2405
  if (!this.add(klass)) {
2432
2406
  return;
2433
2407
  }
2434
2408
  // add classes inherited from this base class
2435
2409
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
2436
- this.discoverTypes(child, parentFieldViewTag);
2410
+ this.discoverTypes(child, parentIndex, parentFieldViewTag);
2437
2411
  });
2438
- // skip if no fields are defined for this class.
2439
- if (klass[Symbol.metadata] === undefined) {
2440
- klass[Symbol.metadata] = {};
2441
- }
2442
- // const metadata = Metadata.getFor(klass);
2443
- const metadata = klass[Symbol.metadata];
2412
+ const metadata = (klass[Symbol.metadata] ??= {});
2444
2413
  // if any schema/field has filters, mark "context" as having filters.
2445
2414
  if (metadata[-2]) {
2446
2415
  this.hasFilters = true;
2447
2416
  }
2417
+ if (parentFieldViewTag !== undefined) {
2418
+ this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2419
+ }
2448
2420
  for (const field in metadata) {
2449
- //
2450
- // Modify the field's metadata to include the parent field's view tag
2451
- //
2452
- if (parentFieldViewTag !== undefined &&
2453
- metadata[field].tag === undefined) {
2454
- metadata[field].tag = parentFieldViewTag;
2455
- }
2421
+ // //
2422
+ // // Modify the field's metadata to include the parent field's view tag
2423
+ // //
2424
+ // if (
2425
+ // parentFieldViewTag !== undefined &&
2426
+ // metadata[field].tag === undefined
2427
+ // ) {
2428
+ // metadata[field].tag = parentFieldViewTag;
2429
+ // }
2456
2430
  const fieldType = metadata[field].type;
2457
2431
  const viewTag = metadata[field].tag;
2458
2432
  if (typeof (fieldType) === "string") {
@@ -2463,7 +2437,7 @@ class TypeContext {
2463
2437
  if (type === "string") {
2464
2438
  continue;
2465
2439
  }
2466
- this.discoverTypes(type, viewTag);
2440
+ this.discoverTypes(type, metadata[field].index, viewTag);
2467
2441
  }
2468
2442
  else if (typeof (fieldType) === "function") {
2469
2443
  this.discoverTypes(fieldType, viewTag);
@@ -2474,11 +2448,13 @@ class TypeContext {
2474
2448
  if (typeof (type) === "string") {
2475
2449
  continue;
2476
2450
  }
2477
- this.discoverTypes(type, viewTag);
2451
+ this.discoverTypes(type, metadata[field].index, viewTag);
2478
2452
  }
2479
2453
  }
2480
2454
  }
2481
2455
  }
2456
+
2457
+ const DEFAULT_VIEW_TAG = -1;
2482
2458
  /**
2483
2459
  * [See documentation](https://docs.colyseus.io/state/schema/)
2484
2460
  *
@@ -3440,16 +3416,56 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
3440
3416
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3441
3417
  };
3442
3418
 
3419
+ class Root {
3420
+ constructor(types) {
3421
+ this.types = types;
3422
+ this.nextUniqueId = 0;
3423
+ this.refCount = new WeakMap();
3424
+ // all changes
3425
+ this.allChanges = new Map();
3426
+ this.allFilteredChanges = new Map();
3427
+ // pending changes to be encoded
3428
+ this.changes = new Map();
3429
+ this.filteredChanges = new Map();
3430
+ }
3431
+ getNextUniqueId() {
3432
+ return this.nextUniqueId++;
3433
+ }
3434
+ add(changeTree) {
3435
+ const refCount = this.refCount.get(changeTree) || 0;
3436
+ this.refCount.set(changeTree, refCount + 1);
3437
+ }
3438
+ remove(changeTree) {
3439
+ const refCount = this.refCount.get(changeTree);
3440
+ if (refCount <= 1) {
3441
+ this.allChanges.delete(changeTree);
3442
+ this.changes.delete(changeTree);
3443
+ if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
3444
+ this.allFilteredChanges.delete(changeTree);
3445
+ this.filteredChanges.delete(changeTree);
3446
+ }
3447
+ this.refCount.delete(changeTree);
3448
+ }
3449
+ else {
3450
+ this.refCount.set(changeTree, refCount - 1);
3451
+ }
3452
+ changeTree.forEachChild((child, _) => this.remove(child));
3453
+ }
3454
+ clear() {
3455
+ this.changes.clear();
3456
+ }
3457
+ }
3458
+
3443
3459
  class Encoder {
3444
3460
  static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
3445
3461
  constructor(state) {
3446
3462
  this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
3447
- this.root = new Root();
3448
3463
  //
3449
3464
  // TODO: cache and restore "Context" based on root schema
3450
3465
  // (to avoid creating a new context for every new room)
3451
3466
  //
3452
3467
  this.context = new TypeContext(state.constructor);
3468
+ this.root = new Root(this.context);
3453
3469
  this.setState(state);
3454
3470
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
3455
3471
  // this.context.schemas.forEach((id, schema) => {
@@ -3486,7 +3502,8 @@ class Encoder {
3486
3502
  }
3487
3503
  }
3488
3504
  // skip root `refId` if it's the first change tree
3489
- if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
3505
+ // (unless it "hasView", which will need to revisit the root)
3506
+ if (hasView || changeTree !== rootChangeTree) {
3490
3507
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3491
3508
  number$1(buffer, changeTree.refId, it);
3492
3509
  }
@@ -3548,35 +3565,43 @@ class Encoder {
3548
3565
  }
3549
3566
  }
3550
3567
  encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3551
- // console.log(`encodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3552
- // Array.from(this.root.allChanges.entries()).map((item) => {
3553
- // console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3554
- // });
3568
+ // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3569
+ // this.debugChanges("allChanges");
3555
3570
  return this.encode(it, undefined, buffer, this.root.allChanges, true);
3556
3571
  }
3557
3572
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3558
3573
  const viewOffset = it.offset;
3559
- // console.log(`encodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3560
- // this.debugAllFilteredChanges();
3574
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3575
+ // this.debugChanges("allFilteredChanges");
3561
3576
  // try to encode "filtered" changes
3562
- this.encode(it, view, bytes, this.root.allFilteredChanges, true);
3577
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
3563
3578
  return Buffer.concat([
3564
3579
  bytes.subarray(0, sharedOffset),
3565
3580
  bytes.subarray(viewOffset, it.offset)
3566
3581
  ]);
3567
3582
  }
3568
- debugAllFilteredChanges() {
3569
- Array.from(this.root.allFilteredChanges.entries()).map((item) => {
3570
- console.log("->", { refId: item[0].refId, changes: item[1].size }, item[0].ref.toJSON());
3571
- if (Array.isArray(item[0].ref.toJSON())) {
3572
- item[1].forEach((op, key) => {
3573
- console.log(" ->", { key, op: OPERATION[op] });
3583
+ debugChanges(field) {
3584
+ const changeSet = (typeof (field) === "string")
3585
+ ? this.root[field]
3586
+ : field;
3587
+ Array.from(changeSet.entries()).map((item) => {
3588
+ const metadata = item[0].ref.constructor[Symbol.metadata];
3589
+ console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3590
+ item[1].forEach((op, index) => {
3591
+ console.log(" ->", {
3592
+ index,
3593
+ field: metadata?.[index],
3594
+ op: OPERATION[op],
3574
3595
  });
3575
- }
3596
+ });
3576
3597
  });
3577
3598
  }
3578
3599
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3579
3600
  const viewOffset = it.offset;
3601
+ // console.log(`\nencodeView(), view.changes (${view.changes.size})`);
3602
+ // this.debugChanges(view.changes);
3603
+ // console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
3604
+ // this.debugChanges("filteredChanges");
3580
3605
  // encode visibility changes (add/remove for this view)
3581
3606
  const viewChangesIterator = view.changes.entries();
3582
3607
  for (const [changeTree, changes] of viewChangesIterator) {
@@ -4131,11 +4156,11 @@ function getDecoderStateCallbacks(decoder) {
4131
4156
  let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4132
4157
  (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4133
4158
  if (metadata && !isCollection) {
4134
- const onAdd = function (ref, prop, callback, immediate) {
4159
+ const onAddListen = function (ref, prop, callback, immediate) {
4135
4160
  // immediate trigger
4136
4161
  if (immediate &&
4137
4162
  context.instance[prop] !== undefined &&
4138
- !onAddCalls.has(callback) // Workaround for https://github.com/colyseus/schema/issues/147
4163
+ !onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
4139
4164
  ) {
4140
4165
  callback(context.instance[prop], undefined);
4141
4166
  }
@@ -4147,13 +4172,13 @@ function getDecoderStateCallbacks(decoder) {
4147
4172
  return new Proxy({
4148
4173
  listen: function listen(prop, callback, immediate = true) {
4149
4174
  if (context.instance) {
4150
- return onAdd(context.instance, prop, callback, immediate);
4175
+ return onAddListen(context.instance, prop, callback, immediate);
4151
4176
  }
4152
4177
  else {
4153
4178
  // collection instance not received yet
4154
4179
  let detachCallback = () => { };
4155
4180
  context.onInstanceAvailable((ref, existing) => {
4156
- detachCallback = onAdd(ref, prop, callback, immediate && existing);
4181
+ detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4157
4182
  });
4158
4183
  return () => detachCallback();
4159
4184
  }
@@ -4222,6 +4247,7 @@ function getDecoderStateCallbacks(decoder) {
4222
4247
  currentOnAddCallback = callback;
4223
4248
  callback(value, key);
4224
4249
  onAddCalls.delete(callback);
4250
+ currentOnAddCallback = undefined;
4225
4251
  });
4226
4252
  };
4227
4253
  const onRemove = function (ref, callback) {