@colyseus/schema 3.0.0-alpha.25 → 3.0.0-alpha.26

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.
@@ -329,7 +329,7 @@ class ChangeTree {
329
329
  // MapSchema / ArraySchema, etc.
330
330
  this.ref.forEach((value, key) => {
331
331
  if (Metadata.isValidInstance(value)) {
332
- callback(value[$changes], this.ref[$changes].indexes[key]);
332
+ callback(value[$changes], this.ref[$changes].indexes[key] ?? key);
333
333
  }
334
334
  });
335
335
  }
@@ -357,8 +357,9 @@ class ChangeTree {
357
357
  // TODO: are DELETE operations being encoded as ADD here ??
358
358
  //
359
359
  if (isFiltered) {
360
- this.allFilteredChanges.set(index, exports.OPERATION.ADD);
361
360
  this.root?.filteredChanges.set(this, this.filteredChanges);
361
+ this.allFilteredChanges.set(index, exports.OPERATION.ADD);
362
+ this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
362
363
  }
363
364
  else {
364
365
  this.allChanges.set(index, exports.OPERATION.ADD);
@@ -1405,6 +1406,7 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1405
1406
  // skip early if field is not defined
1406
1407
  const field = metadata[index];
1407
1408
  if (field === undefined) {
1409
+ console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1408
1410
  return DEFINITION_MISMATCH;
1409
1411
  }
1410
1412
  const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
@@ -2427,13 +2429,13 @@ class TypeContext {
2427
2429
  getTypeId(klass) {
2428
2430
  return this.schemas.get(klass);
2429
2431
  }
2430
- discoverTypes(klass) {
2432
+ discoverTypes(klass, parentFieldViewTag) {
2431
2433
  if (!this.add(klass)) {
2432
2434
  return;
2433
2435
  }
2434
2436
  // add classes inherited from this base class
2435
2437
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
2436
- this.discoverTypes(child);
2438
+ this.discoverTypes(child, parentFieldViewTag);
2437
2439
  });
2438
2440
  // skip if no fields are defined for this class.
2439
2441
  if (klass[Symbol.metadata] === undefined) {
@@ -2446,7 +2448,15 @@ class TypeContext {
2446
2448
  this.hasFilters = true;
2447
2449
  }
2448
2450
  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
+ }
2449
2458
  const fieldType = metadata[field].type;
2459
+ const viewTag = metadata[field].tag;
2450
2460
  if (typeof (fieldType) === "string") {
2451
2461
  continue;
2452
2462
  }
@@ -2455,10 +2465,10 @@ class TypeContext {
2455
2465
  if (type === "string") {
2456
2466
  continue;
2457
2467
  }
2458
- this.discoverTypes(type);
2468
+ this.discoverTypes(type, viewTag);
2459
2469
  }
2460
2470
  else if (typeof (fieldType) === "function") {
2461
- this.discoverTypes(fieldType);
2471
+ this.discoverTypes(fieldType, viewTag);
2462
2472
  }
2463
2473
  else {
2464
2474
  const type = Object.values(fieldType)[0];
@@ -2466,7 +2476,7 @@ class TypeContext {
2466
2476
  if (typeof (type) === "string") {
2467
2477
  continue;
2468
2478
  }
2469
- this.discoverTypes(type);
2479
+ this.discoverTypes(type, viewTag);
2470
2480
  }
2471
2481
  }
2472
2482
  }
@@ -3025,13 +3035,13 @@ class Schema {
3025
3035
  }
3026
3036
  return output;
3027
3037
  }
3028
- static debugChangesDeep(ref) {
3038
+ static debugChangesDeep(ref, changeSetName = "changes") {
3029
3039
  let output = "";
3030
3040
  const rootChangeTree = ref[$changes];
3031
3041
  const changeTrees = new Map();
3032
3042
  let totalInstances = 0;
3033
3043
  let totalOperations = 0;
3034
- for (const [changeTree, changes] of (rootChangeTree.root.changes.entries())) {
3044
+ for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
3035
3045
  let includeChangeTree = false;
3036
3046
  let parentChangeTrees = [];
3037
3047
  let parentChangeTree = changeTree.parent?.[$changes];
@@ -3434,26 +3444,26 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
3434
3444
 
3435
3445
  class Encoder {
3436
3446
  static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
3437
- constructor(root) {
3447
+ constructor(state) {
3438
3448
  this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
3449
+ this.root = new Root();
3439
3450
  //
3440
3451
  // TODO: cache and restore "Context" based on root schema
3441
3452
  // (to avoid creating a new context for every new room)
3442
3453
  //
3443
- this.context = new TypeContext(root.constructor);
3444
- this.setRoot(root);
3454
+ this.context = new TypeContext(state.constructor);
3455
+ this.setState(state);
3445
3456
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
3446
3457
  // this.context.schemas.forEach((id, schema) => {
3447
3458
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3448
3459
  // });
3449
3460
  }
3450
- setRoot(state) {
3451
- this.root = new Root();
3461
+ setState(state) {
3452
3462
  this.state = state;
3453
- state[$changes].setRoot(this.root);
3463
+ this.state[$changes].setRoot(this.root);
3454
3464
  }
3455
- encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees) {
3456
- const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
3465
+ encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees, initialOffset = it.offset // cache current offset in case we need to resize the buffer
3466
+ ) {
3457
3467
  const hasView = (view !== undefined);
3458
3468
  const rootChangeTree = this.state[$changes];
3459
3469
  const changeTreesIterator = changeTrees.entries();
@@ -3492,7 +3502,6 @@ class Encoder {
3492
3502
  // TODO: avoid checking if no view tags were defined
3493
3503
  //
3494
3504
  if (filter && !filter(ref, fieldIndex, view)) {
3495
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
3496
3505
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
3497
3506
  // view?.invisible.add(changeTree);
3498
3507
  continue;
@@ -3570,8 +3579,6 @@ class Encoder {
3570
3579
  }
3571
3580
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3572
3581
  const viewOffset = it.offset;
3573
- // try to encode "filtered" changes
3574
- this.encode(it, view, bytes, this.root.filteredChanges);
3575
3582
  // encode visibility changes (add/remove for this view)
3576
3583
  const viewChangesIterator = view.changes.entries();
3577
3584
  for (const [changeTree, changes] of viewChangesIterator) {
@@ -3598,6 +3605,8 @@ class Encoder {
3598
3605
  //
3599
3606
  // clear "view" changes after encoding
3600
3607
  view.changes.clear();
3608
+ // try to encode "filtered" changes
3609
+ this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
3601
3610
  return Buffer.concat([
3602
3611
  bytes.subarray(0, sharedOffset),
3603
3612
  bytes.subarray(viewOffset, it.offset)
@@ -3770,14 +3779,14 @@ class ReferenceTracker {
3770
3779
  class Decoder {
3771
3780
  constructor(root, context) {
3772
3781
  this.currentRefId = 0;
3773
- this.setRoot(root);
3782
+ this.setState(root);
3774
3783
  this.context = context || new TypeContext(root.constructor);
3775
3784
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
3776
3785
  // this.context.schemas.forEach((id, schema) => {
3777
3786
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3778
3787
  // });
3779
3788
  }
3780
- setRoot(root) {
3789
+ setState(root) {
3781
3790
  this.state = root;
3782
3791
  this.root = new ReferenceTracker();
3783
3792
  this.root.addRef(0, root);
@@ -3906,9 +3915,7 @@ class Reflection extends Schema {
3906
3915
  this.types = new ArraySchema();
3907
3916
  }
3908
3917
  static encode(instance, context, it = { offset: 0 }) {
3909
- if (!context) {
3910
- context = new TypeContext(instance.constructor);
3911
- }
3918
+ context ??= new TypeContext(instance.constructor);
3912
3919
  const reflection = new Reflection();
3913
3920
  const encoder = new Encoder(reflection);
3914
3921
  const buildType = (currentType, metadata) => {
@@ -4290,26 +4297,21 @@ class StateView {
4290
4297
  this.changes = new Map();
4291
4298
  }
4292
4299
  // TODO: allow to set multiple tags at once
4293
- add(obj, tag = DEFAULT_VIEW_TAG) {
4300
+ add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
4294
4301
  if (!obj[$changes]) {
4295
4302
  console.warn("StateView#add(), invalid object:", obj);
4296
4303
  return this;
4297
4304
  }
4298
4305
  // FIXME: ArraySchema/MapSchema does not have metadata
4299
4306
  const metadata = obj.constructor[Symbol.metadata];
4300
- let changeTree = obj[$changes];
4307
+ const changeTree = obj[$changes];
4301
4308
  this.items.add(changeTree);
4302
- // Add children of this ChangeTree to this view
4303
- changeTree.forEachChild((change, index) => {
4304
- // Do not ADD children that don't have the same tag
4305
- if (metadata && metadata[metadata[index]].tag !== tag) {
4306
- return;
4307
- }
4308
- this.add(change.ref, tag);
4309
- });
4310
- // add parent ChangeTree's, if they are invisible to this view
4311
- // TODO: REFACTOR addParent()
4312
- this.addParent(changeTree, tag);
4309
+ // add parent ChangeTree's
4310
+ // - if it was invisible to this view
4311
+ // - if it were previously filtered out
4312
+ if (checkIncludeParent && changeTree.parent) {
4313
+ this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4314
+ }
4313
4315
  //
4314
4316
  // TODO: when adding an item of a MapSchema, the changes may not
4315
4317
  // be set (only the parent's changes are set)
@@ -4341,73 +4343,63 @@ class StateView {
4341
4343
  });
4342
4344
  }
4343
4345
  else {
4344
- // console.log("DEFAULT TAG", changeTree.allChanges);
4345
- // // add default tag properties
4346
- // metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
4347
- // if (changeTree.getChange(index) !== OPERATION.DELETE) {
4348
- // changes.set(index, OPERATION.ADD);
4349
- // }
4350
- // });
4351
- const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4346
+ const isInvisible = this.invisible.has(changeTree);
4347
+ const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4352
4348
  ? changeTree.allFilteredChanges
4353
4349
  : changeTree.allChanges;
4354
- const it = allChangesSet.keys();
4355
- const isInvisible = this.invisible.has(changeTree);
4356
- for (const index of it) {
4357
- if ((isInvisible || metadata?.[metadata?.[index]].tag === tag) &&
4358
- changeTree.getChange(index) !== exports.OPERATION.DELETE) {
4359
- changes.set(index, exports.OPERATION.ADD);
4350
+ changeSet.forEach((op, index) => {
4351
+ const tagAtIndex = metadata?.[metadata?.[index]].tag;
4352
+ if ((isInvisible || // if "invisible", include all
4353
+ tagAtIndex === undefined || // "all change" with no tag
4354
+ tagAtIndex === tag // tagged property
4355
+ ) &&
4356
+ op !== exports.OPERATION.DELETE) {
4357
+ changes.set(index, op);
4360
4358
  }
4361
- }
4362
- }
4363
- // TODO: avoid unnecessary iteration here
4364
- while (changeTree.parent &&
4365
- (changeTree = changeTree.parent[$changes]) &&
4366
- (changeTree.isFiltered || changeTree.isPartiallyFiltered)) {
4367
- this.items.add(changeTree);
4359
+ });
4368
4360
  }
4361
+ // Add children of this ChangeTree to this view
4362
+ changeTree.forEachChild((change, index) => {
4363
+ // Do not ADD children that don't have the same tag
4364
+ if (metadata && metadata[metadata[index]].tag !== tag) {
4365
+ return;
4366
+ }
4367
+ this.add(change.ref, tag, false);
4368
+ });
4369
4369
  return this;
4370
4370
  }
4371
- addParent(changeTree, tag) {
4372
- const parentRef = changeTree.parent;
4373
- if (!parentRef) {
4374
- return;
4371
+ addParent(changeTree, parentIndex, tag) {
4372
+ // view must have all "changeTree" parent tree
4373
+ this.items.add(changeTree);
4374
+ // add parent's parent
4375
+ const parentChangeTree = changeTree.parent?.[$changes];
4376
+ if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
4377
+ this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4375
4378
  }
4376
- const parentChangeTree = parentRef[$changes];
4377
- const parentIndex = changeTree.parentIndex;
4378
- if (!this.invisible.has(parentChangeTree)) {
4379
- // parent is already available, no need to add it!
4379
+ // parent is already available, no need to add it!
4380
+ if (!this.invisible.has(changeTree)) {
4380
4381
  return;
4381
4382
  }
4382
- this.addParent(parentChangeTree, tag);
4383
4383
  // add parent's tag properties
4384
- if (parentChangeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4385
- let parentChanges = this.changes.get(parentChangeTree);
4386
- if (parentChanges === undefined) {
4387
- parentChanges = new Map();
4388
- this.changes.set(parentChangeTree, parentChanges);
4384
+ if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4385
+ let changes = this.changes.get(changeTree);
4386
+ if (changes === undefined) {
4387
+ changes = new Map();
4388
+ this.changes.set(changeTree, changes);
4389
4389
  }
4390
- // console.log("add parent change", {
4391
- // parentIndex,
4392
- // parentChanges,
4393
- // parentChange: (
4394
- // parentChangeTree.getChange(parentIndex) &&
4395
- // OPERATION[parentChangeTree.getChange(parentIndex)]
4396
- // ),
4397
- // })
4398
4390
  if (!this.tags) {
4399
4391
  this.tags = new WeakMap();
4400
4392
  }
4401
4393
  let tags;
4402
- if (!this.tags.has(parentChangeTree)) {
4394
+ if (!this.tags.has(changeTree)) {
4403
4395
  tags = new Set();
4404
- this.tags.set(parentChangeTree, tags);
4396
+ this.tags.set(changeTree, tags);
4405
4397
  }
4406
4398
  else {
4407
- tags = this.tags.get(parentChangeTree);
4399
+ tags = this.tags.get(changeTree);
4408
4400
  }
4409
4401
  tags.add(tag);
4410
- parentChanges.set(parentIndex, exports.OPERATION.ADD);
4402
+ changes.set(parentIndex, exports.OPERATION.ADD);
4411
4403
  }
4412
4404
  }
4413
4405
  remove(obj, tag = DEFAULT_VIEW_TAG) {