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