@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.
@@ -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);
@@ -2425,13 +2427,13 @@ class TypeContext {
2425
2427
  getTypeId(klass) {
2426
2428
  return this.schemas.get(klass);
2427
2429
  }
2428
- discoverTypes(klass) {
2430
+ discoverTypes(klass, parentFieldViewTag) {
2429
2431
  if (!this.add(klass)) {
2430
2432
  return;
2431
2433
  }
2432
2434
  // add classes inherited from this base class
2433
2435
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
2434
- this.discoverTypes(child);
2436
+ this.discoverTypes(child, parentFieldViewTag);
2435
2437
  });
2436
2438
  // skip if no fields are defined for this class.
2437
2439
  if (klass[Symbol.metadata] === undefined) {
@@ -2444,7 +2446,15 @@ class TypeContext {
2444
2446
  this.hasFilters = true;
2445
2447
  }
2446
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
+ }
2447
2456
  const fieldType = metadata[field].type;
2457
+ const viewTag = metadata[field].tag;
2448
2458
  if (typeof (fieldType) === "string") {
2449
2459
  continue;
2450
2460
  }
@@ -2453,10 +2463,10 @@ class TypeContext {
2453
2463
  if (type === "string") {
2454
2464
  continue;
2455
2465
  }
2456
- this.discoverTypes(type);
2466
+ this.discoverTypes(type, viewTag);
2457
2467
  }
2458
2468
  else if (typeof (fieldType) === "function") {
2459
- this.discoverTypes(fieldType);
2469
+ this.discoverTypes(fieldType, viewTag);
2460
2470
  }
2461
2471
  else {
2462
2472
  const type = Object.values(fieldType)[0];
@@ -2464,7 +2474,7 @@ class TypeContext {
2464
2474
  if (typeof (type) === "string") {
2465
2475
  continue;
2466
2476
  }
2467
- this.discoverTypes(type);
2477
+ this.discoverTypes(type, viewTag);
2468
2478
  }
2469
2479
  }
2470
2480
  }
@@ -3023,13 +3033,13 @@ class Schema {
3023
3033
  }
3024
3034
  return output;
3025
3035
  }
3026
- static debugChangesDeep(ref) {
3036
+ static debugChangesDeep(ref, changeSetName = "changes") {
3027
3037
  let output = "";
3028
3038
  const rootChangeTree = ref[$changes];
3029
3039
  const changeTrees = new Map();
3030
3040
  let totalInstances = 0;
3031
3041
  let totalOperations = 0;
3032
- for (const [changeTree, changes] of (rootChangeTree.root.changes.entries())) {
3042
+ for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
3033
3043
  let includeChangeTree = false;
3034
3044
  let parentChangeTrees = [];
3035
3045
  let parentChangeTree = changeTree.parent?.[$changes];
@@ -3432,26 +3442,26 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
3432
3442
 
3433
3443
  class Encoder {
3434
3444
  static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
3435
- constructor(root) {
3445
+ constructor(state) {
3436
3446
  this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
3447
+ this.root = new Root();
3437
3448
  //
3438
3449
  // TODO: cache and restore "Context" based on root schema
3439
3450
  // (to avoid creating a new context for every new room)
3440
3451
  //
3441
- this.context = new TypeContext(root.constructor);
3442
- this.setRoot(root);
3452
+ this.context = new TypeContext(state.constructor);
3453
+ this.setState(state);
3443
3454
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
3444
3455
  // this.context.schemas.forEach((id, schema) => {
3445
3456
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3446
3457
  // });
3447
3458
  }
3448
- setRoot(state) {
3449
- this.root = new Root();
3459
+ setState(state) {
3450
3460
  this.state = state;
3451
- state[$changes].setRoot(this.root);
3461
+ this.state[$changes].setRoot(this.root);
3452
3462
  }
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
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
+ ) {
3455
3465
  const hasView = (view !== undefined);
3456
3466
  const rootChangeTree = this.state[$changes];
3457
3467
  const changeTreesIterator = changeTrees.entries();
@@ -3490,7 +3500,6 @@ class Encoder {
3490
3500
  // TODO: avoid checking if no view tags were defined
3491
3501
  //
3492
3502
  if (filter && !filter(ref, fieldIndex, view)) {
3493
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
3494
3503
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
3495
3504
  // view?.invisible.add(changeTree);
3496
3505
  continue;
@@ -3568,8 +3577,6 @@ class Encoder {
3568
3577
  }
3569
3578
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3570
3579
  const viewOffset = it.offset;
3571
- // try to encode "filtered" changes
3572
- this.encode(it, view, bytes, this.root.filteredChanges);
3573
3580
  // encode visibility changes (add/remove for this view)
3574
3581
  const viewChangesIterator = view.changes.entries();
3575
3582
  for (const [changeTree, changes] of viewChangesIterator) {
@@ -3596,6 +3603,8 @@ class Encoder {
3596
3603
  //
3597
3604
  // clear "view" changes after encoding
3598
3605
  view.changes.clear();
3606
+ // try to encode "filtered" changes
3607
+ this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
3599
3608
  return Buffer.concat([
3600
3609
  bytes.subarray(0, sharedOffset),
3601
3610
  bytes.subarray(viewOffset, it.offset)
@@ -3768,14 +3777,14 @@ class ReferenceTracker {
3768
3777
  class Decoder {
3769
3778
  constructor(root, context) {
3770
3779
  this.currentRefId = 0;
3771
- this.setRoot(root);
3780
+ this.setState(root);
3772
3781
  this.context = context || new TypeContext(root.constructor);
3773
3782
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
3774
3783
  // this.context.schemas.forEach((id, schema) => {
3775
3784
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3776
3785
  // });
3777
3786
  }
3778
- setRoot(root) {
3787
+ setState(root) {
3779
3788
  this.state = root;
3780
3789
  this.root = new ReferenceTracker();
3781
3790
  this.root.addRef(0, root);
@@ -3904,9 +3913,7 @@ class Reflection extends Schema {
3904
3913
  this.types = new ArraySchema();
3905
3914
  }
3906
3915
  static encode(instance, context, it = { offset: 0 }) {
3907
- if (!context) {
3908
- context = new TypeContext(instance.constructor);
3909
- }
3916
+ context ??= new TypeContext(instance.constructor);
3910
3917
  const reflection = new Reflection();
3911
3918
  const encoder = new Encoder(reflection);
3912
3919
  const buildType = (currentType, metadata) => {
@@ -4288,26 +4295,21 @@ class StateView {
4288
4295
  this.changes = new Map();
4289
4296
  }
4290
4297
  // TODO: allow to set multiple tags at once
4291
- add(obj, tag = DEFAULT_VIEW_TAG) {
4298
+ add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
4292
4299
  if (!obj[$changes]) {
4293
4300
  console.warn("StateView#add(), invalid object:", obj);
4294
4301
  return this;
4295
4302
  }
4296
4303
  // FIXME: ArraySchema/MapSchema does not have metadata
4297
4304
  const metadata = obj.constructor[Symbol.metadata];
4298
- let changeTree = obj[$changes];
4305
+ const changeTree = obj[$changes];
4299
4306
  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);
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
+ }
4311
4313
  //
4312
4314
  // TODO: when adding an item of a MapSchema, the changes may not
4313
4315
  // be set (only the parent's changes are set)
@@ -4339,73 +4341,63 @@ class StateView {
4339
4341
  });
4340
4342
  }
4341
4343
  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)
4344
+ const isInvisible = this.invisible.has(changeTree);
4345
+ const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4350
4346
  ? changeTree.allFilteredChanges
4351
4347
  : 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) !== OPERATION.DELETE) {
4357
- 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);
4358
4356
  }
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);
4357
+ });
4366
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
+ });
4367
4367
  return this;
4368
4368
  }
4369
- addParent(changeTree, tag) {
4370
- const parentRef = changeTree.parent;
4371
- if (!parentRef) {
4372
- 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);
4373
4376
  }
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!
4377
+ // parent is already available, no need to add it!
4378
+ if (!this.invisible.has(changeTree)) {
4378
4379
  return;
4379
4380
  }
4380
- this.addParent(parentChangeTree, tag);
4381
4381
  // add parent's tag properties
4382
- if (parentChangeTree.getChange(parentIndex) !== OPERATION.DELETE) {
4383
- let parentChanges = this.changes.get(parentChangeTree);
4384
- if (parentChanges === undefined) {
4385
- parentChanges = new Map();
4386
- 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);
4387
4387
  }
4388
- // console.log("add parent change", {
4389
- // parentIndex,
4390
- // parentChanges,
4391
- // parentChange: (
4392
- // parentChangeTree.getChange(parentIndex) &&
4393
- // OPERATION[parentChangeTree.getChange(parentIndex)]
4394
- // ),
4395
- // })
4396
4388
  if (!this.tags) {
4397
4389
  this.tags = new WeakMap();
4398
4390
  }
4399
4391
  let tags;
4400
- if (!this.tags.has(parentChangeTree)) {
4392
+ if (!this.tags.has(changeTree)) {
4401
4393
  tags = new Set();
4402
- this.tags.set(parentChangeTree, tags);
4394
+ this.tags.set(changeTree, tags);
4403
4395
  }
4404
4396
  else {
4405
- tags = this.tags.get(parentChangeTree);
4397
+ tags = this.tags.get(changeTree);
4406
4398
  }
4407
4399
  tags.add(tag);
4408
- parentChanges.set(parentIndex, OPERATION.ADD);
4400
+ changes.set(parentIndex, OPERATION.ADD);
4409
4401
  }
4410
4402
  }
4411
4403
  remove(obj, tag = DEFAULT_VIEW_TAG) {