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