@colyseus/schema 3.0.0-alpha.24 → 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.
@@ -148,7 +148,7 @@ const Metadata = {
148
148
  // TODO: remove/refactor this...
149
149
  //
150
150
  const metadata = {};
151
- klass.constructor[Symbol.metadata] = metadata;
151
+ klass[Symbol.metadata] = metadata;
152
152
  Object.defineProperty(metadata, -1, {
153
153
  value: 0,
154
154
  enumerable: false,
@@ -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);
@@ -2415,19 +2417,25 @@ class TypeContext {
2415
2417
  return false;
2416
2418
  }
2417
2419
  this.types[typeid] = schema;
2420
+ //
2421
+ // Workaround to allow using an empty Schema (with no `@type()` fields)
2422
+ //
2423
+ if (schema[Symbol.metadata] === undefined) {
2424
+ Metadata.init(schema);
2425
+ }
2418
2426
  this.schemas.set(schema, typeid);
2419
2427
  return true;
2420
2428
  }
2421
2429
  getTypeId(klass) {
2422
2430
  return this.schemas.get(klass);
2423
2431
  }
2424
- discoverTypes(klass) {
2432
+ discoverTypes(klass, parentFieldViewTag) {
2425
2433
  if (!this.add(klass)) {
2426
2434
  return;
2427
2435
  }
2428
2436
  // add classes inherited from this base class
2429
2437
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
2430
- this.discoverTypes(child);
2438
+ this.discoverTypes(child, parentFieldViewTag);
2431
2439
  });
2432
2440
  // skip if no fields are defined for this class.
2433
2441
  if (klass[Symbol.metadata] === undefined) {
@@ -2440,7 +2448,15 @@ class TypeContext {
2440
2448
  this.hasFilters = true;
2441
2449
  }
2442
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
+ }
2443
2458
  const fieldType = metadata[field].type;
2459
+ const viewTag = metadata[field].tag;
2444
2460
  if (typeof (fieldType) === "string") {
2445
2461
  continue;
2446
2462
  }
@@ -2449,10 +2465,10 @@ class TypeContext {
2449
2465
  if (type === "string") {
2450
2466
  continue;
2451
2467
  }
2452
- this.discoverTypes(type);
2468
+ this.discoverTypes(type, viewTag);
2453
2469
  }
2454
2470
  else if (typeof (fieldType) === "function") {
2455
- this.discoverTypes(fieldType);
2471
+ this.discoverTypes(fieldType, viewTag);
2456
2472
  }
2457
2473
  else {
2458
2474
  const type = Object.values(fieldType)[0];
@@ -2460,7 +2476,7 @@ class TypeContext {
2460
2476
  if (typeof (type) === "string") {
2461
2477
  continue;
2462
2478
  }
2463
- this.discoverTypes(type);
2479
+ this.discoverTypes(type, viewTag);
2464
2480
  }
2465
2481
  }
2466
2482
  }
@@ -3019,13 +3035,13 @@ class Schema {
3019
3035
  }
3020
3036
  return output;
3021
3037
  }
3022
- static debugChangesDeep(ref) {
3038
+ static debugChangesDeep(ref, changeSetName = "changes") {
3023
3039
  let output = "";
3024
3040
  const rootChangeTree = ref[$changes];
3025
3041
  const changeTrees = new Map();
3026
3042
  let totalInstances = 0;
3027
3043
  let totalOperations = 0;
3028
- for (const [changeTree, changes] of (rootChangeTree.root.changes.entries())) {
3044
+ for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
3029
3045
  let includeChangeTree = false;
3030
3046
  let parentChangeTrees = [];
3031
3047
  let parentChangeTree = changeTree.parent?.[$changes];
@@ -3428,30 +3444,26 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
3428
3444
 
3429
3445
  class Encoder {
3430
3446
  static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
3431
- constructor(root) {
3447
+ constructor(state) {
3432
3448
  this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
3433
- this.setRoot(root);
3449
+ this.root = new Root();
3434
3450
  //
3435
3451
  // TODO: cache and restore "Context" based on root schema
3436
3452
  // (to avoid creating a new context for every new room)
3437
3453
  //
3438
- this.context = new TypeContext(root.constructor);
3454
+ this.context = new TypeContext(state.constructor);
3455
+ this.setState(state);
3439
3456
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
3440
3457
  // this.context.schemas.forEach((id, schema) => {
3441
3458
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3442
3459
  // });
3443
3460
  }
3444
- setRoot(state) {
3445
- this.root = new Root();
3461
+ setState(state) {
3446
3462
  this.state = state;
3447
- // Workaround to allow using an empty Schema.
3448
- if (state.constructor[Symbol.metadata] === undefined) {
3449
- Metadata.init(state);
3450
- }
3451
- state[$changes].setRoot(this.root);
3463
+ this.state[$changes].setRoot(this.root);
3452
3464
  }
3453
- encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees) {
3454
- 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
+ ) {
3455
3467
  const hasView = (view !== undefined);
3456
3468
  const rootChangeTree = this.state[$changes];
3457
3469
  const changeTreesIterator = changeTrees.entries();
@@ -3490,7 +3502,6 @@ class Encoder {
3490
3502
  // TODO: avoid checking if no view tags were defined
3491
3503
  //
3492
3504
  if (filter && !filter(ref, fieldIndex, view)) {
3493
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
3494
3505
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
3495
3506
  // view?.invisible.add(changeTree);
3496
3507
  continue;
@@ -3568,8 +3579,6 @@ class Encoder {
3568
3579
  }
3569
3580
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3570
3581
  const viewOffset = it.offset;
3571
- // try to encode "filtered" changes
3572
- this.encode(it, view, bytes, this.root.filteredChanges);
3573
3582
  // encode visibility changes (add/remove for this view)
3574
3583
  const viewChangesIterator = view.changes.entries();
3575
3584
  for (const [changeTree, changes] of viewChangesIterator) {
@@ -3596,6 +3605,8 @@ class Encoder {
3596
3605
  //
3597
3606
  // clear "view" changes after encoding
3598
3607
  view.changes.clear();
3608
+ // try to encode "filtered" changes
3609
+ this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
3599
3610
  return Buffer.concat([
3600
3611
  bytes.subarray(0, sharedOffset),
3601
3612
  bytes.subarray(viewOffset, it.offset)
@@ -3768,14 +3779,14 @@ class ReferenceTracker {
3768
3779
  class Decoder {
3769
3780
  constructor(root, context) {
3770
3781
  this.currentRefId = 0;
3771
- this.setRoot(root);
3782
+ this.setState(root);
3772
3783
  this.context = context || new TypeContext(root.constructor);
3773
3784
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
3774
3785
  // this.context.schemas.forEach((id, schema) => {
3775
3786
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3776
3787
  // });
3777
3788
  }
3778
- setRoot(root) {
3789
+ setState(root) {
3779
3790
  this.state = root;
3780
3791
  this.root = new ReferenceTracker();
3781
3792
  this.root.addRef(0, root);
@@ -3904,9 +3915,7 @@ class Reflection extends Schema {
3904
3915
  this.types = new ArraySchema();
3905
3916
  }
3906
3917
  static encode(instance, context, it = { offset: 0 }) {
3907
- if (!context) {
3908
- context = new TypeContext(instance.constructor);
3909
- }
3918
+ context ??= new TypeContext(instance.constructor);
3910
3919
  const reflection = new Reflection();
3911
3920
  const encoder = new Encoder(reflection);
3912
3921
  const buildType = (currentType, metadata) => {
@@ -4288,26 +4297,21 @@ class StateView {
4288
4297
  this.changes = new Map();
4289
4298
  }
4290
4299
  // TODO: allow to set multiple tags at once
4291
- add(obj, tag = DEFAULT_VIEW_TAG) {
4300
+ add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
4292
4301
  if (!obj[$changes]) {
4293
4302
  console.warn("StateView#add(), invalid object:", obj);
4294
4303
  return this;
4295
4304
  }
4296
4305
  // FIXME: ArraySchema/MapSchema does not have metadata
4297
4306
  const metadata = obj.constructor[Symbol.metadata];
4298
- let changeTree = obj[$changes];
4307
+ const changeTree = obj[$changes];
4299
4308
  this.items.add(changeTree);
4300
- // Add children of this ChangeTree to this view
4301
- changeTree.forEachChild((change, index) => {
4302
- // Do not ADD children that don't have the same tag
4303
- if (metadata && metadata[metadata[index]].tag !== tag) {
4304
- return;
4305
- }
4306
- this.add(change.ref, tag);
4307
- });
4308
- // add parent ChangeTree's, if they are invisible to this view
4309
- // TODO: REFACTOR addParent()
4310
- 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
+ }
4311
4315
  //
4312
4316
  // TODO: when adding an item of a MapSchema, the changes may not
4313
4317
  // be set (only the parent's changes are set)
@@ -4339,73 +4343,63 @@ class StateView {
4339
4343
  });
4340
4344
  }
4341
4345
  else {
4342
- // console.log("DEFAULT TAG", changeTree.allChanges);
4343
- // // add default tag properties
4344
- // metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
4345
- // if (changeTree.getChange(index) !== OPERATION.DELETE) {
4346
- // changes.set(index, OPERATION.ADD);
4347
- // }
4348
- // });
4349
- const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4346
+ const isInvisible = this.invisible.has(changeTree);
4347
+ const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4350
4348
  ? changeTree.allFilteredChanges
4351
4349
  : changeTree.allChanges;
4352
- const it = allChangesSet.keys();
4353
- const isInvisible = this.invisible.has(changeTree);
4354
- for (const index of it) {
4355
- if ((isInvisible || metadata?.[metadata?.[index]].tag === tag) &&
4356
- changeTree.getChange(index) !== exports.OPERATION.DELETE) {
4357
- 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);
4358
4358
  }
4359
- }
4360
- }
4361
- // TODO: avoid unnecessary iteration here
4362
- while (changeTree.parent &&
4363
- (changeTree = changeTree.parent[$changes]) &&
4364
- (changeTree.isFiltered || changeTree.isPartiallyFiltered)) {
4365
- this.items.add(changeTree);
4359
+ });
4366
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
+ });
4367
4369
  return this;
4368
4370
  }
4369
- addParent(changeTree, tag) {
4370
- const parentRef = changeTree.parent;
4371
- if (!parentRef) {
4372
- 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);
4373
4378
  }
4374
- const parentChangeTree = parentRef[$changes];
4375
- const parentIndex = changeTree.parentIndex;
4376
- if (!this.invisible.has(parentChangeTree)) {
4377
- // 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)) {
4378
4381
  return;
4379
4382
  }
4380
- this.addParent(parentChangeTree, tag);
4381
4383
  // add parent's tag properties
4382
- if (parentChangeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4383
- let parentChanges = this.changes.get(parentChangeTree);
4384
- if (parentChanges === undefined) {
4385
- parentChanges = new Map();
4386
- 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);
4387
4389
  }
4388
- // console.log("add parent change", {
4389
- // parentIndex,
4390
- // parentChanges,
4391
- // parentChange: (
4392
- // parentChangeTree.getChange(parentIndex) &&
4393
- // OPERATION[parentChangeTree.getChange(parentIndex)]
4394
- // ),
4395
- // })
4396
4390
  if (!this.tags) {
4397
4391
  this.tags = new WeakMap();
4398
4392
  }
4399
4393
  let tags;
4400
- if (!this.tags.has(parentChangeTree)) {
4394
+ if (!this.tags.has(changeTree)) {
4401
4395
  tags = new Set();
4402
- this.tags.set(parentChangeTree, tags);
4396
+ this.tags.set(changeTree, tags);
4403
4397
  }
4404
4398
  else {
4405
- tags = this.tags.get(parentChangeTree);
4399
+ tags = this.tags.get(changeTree);
4406
4400
  }
4407
4401
  tags.add(tag);
4408
- parentChanges.set(parentIndex, exports.OPERATION.ADD);
4402
+ changes.set(parentIndex, exports.OPERATION.ADD);
4409
4403
  }
4410
4404
  }
4411
4405
  remove(obj, tag = DEFAULT_VIEW_TAG) {