@colyseus/schema 3.0.26 → 3.0.28

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.
@@ -1,5 +1,7 @@
1
1
  'use strict';
2
2
 
3
+ require('util');
4
+
3
5
  const SWITCH_TO_STRUCTURE = 255; // (decoding collides with DELETE_AND_ADD + fieldIndex = 63)
4
6
  const TYPE_ID = 213;
5
7
  /**
@@ -973,10 +975,18 @@ function setOperationAtIndex(changeSet, index) {
973
975
  }
974
976
  }
975
977
  function deleteOperationAtIndex(changeSet, index) {
976
- const operationsIndex = changeSet.indexes[index];
977
- if (operationsIndex !== undefined) {
978
- changeSet.operations[operationsIndex] = undefined;
978
+ let operationsIndex = changeSet.indexes[index];
979
+ if (operationsIndex === undefined) {
980
+ //
981
+ // if index is not found, we need to find the last operation
982
+ // FIXME: this is not very efficient
983
+ //
984
+ // > See "should allow consecutive splices (same place)" tests
985
+ //
986
+ operationsIndex = Object.values(changeSet.indexes).at(-1);
987
+ index = Object.entries(changeSet.indexes).find(([_, value]) => value === operationsIndex)?.[0];
979
988
  }
989
+ changeSet.operations[operationsIndex] = undefined;
980
990
  delete changeSet.indexes[index];
981
991
  }
982
992
  function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
@@ -1161,14 +1171,9 @@ class ChangeTree {
1161
1171
  }
1162
1172
  _shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
1163
1173
  const newIndexes = {};
1174
+ let newKey = 0;
1164
1175
  for (const key in changeSet.indexes) {
1165
- const index = changeSet.indexes[key];
1166
- if (index > startIndex) {
1167
- newIndexes[Number(key) + shiftIndex] = index;
1168
- }
1169
- else {
1170
- newIndexes[key] = index;
1171
- }
1176
+ newIndexes[newKey++] = changeSet.indexes[key];
1172
1177
  }
1173
1178
  changeSet.indexes = newIndexes;
1174
1179
  for (let i = 0; i < changeSet.operations.length; i++) {
@@ -1358,25 +1363,35 @@ class ChangeTree {
1358
1363
  const refType = Metadata.isValidInstance(this.ref)
1359
1364
  ? this.ref.constructor
1360
1365
  : this.ref[$childType];
1361
- if (!Metadata.isValidInstance(parent)) {
1362
- const parentChangeTree = parent[$changes];
1366
+ let parentChangeTree;
1367
+ let parentIsCollection = !Metadata.isValidInstance(parent);
1368
+ if (parentIsCollection) {
1369
+ parentChangeTree = parent[$changes];
1363
1370
  parent = parentChangeTree.parent;
1364
1371
  parentIndex = parentChangeTree.parentIndex;
1365
1372
  }
1373
+ else {
1374
+ parentChangeTree = parent[$changes];
1375
+ }
1366
1376
  const parentConstructor = parent.constructor;
1367
1377
  let key = `${this.root.types.getTypeId(refType)}`;
1368
1378
  if (parentConstructor) {
1369
1379
  key += `-${this.root.types.schemas.get(parentConstructor)}`;
1370
1380
  }
1371
1381
  key += `-${parentIndex}`;
1382
+ const fieldHasViewTag = parentConstructor?.[Symbol.metadata]?.[$viewFieldIndexes]?.includes(parentIndex);
1372
1383
  this.isFiltered = parent[$changes].isFiltered // in case parent is already filtered
1373
1384
  || this.root.types.parentFiltered[key]
1374
- || parentConstructor?.[Symbol.metadata]?.[$viewFieldIndexes]?.includes(parentIndex);
1385
+ || fieldHasViewTag;
1375
1386
  //
1376
1387
  // "isFiltered" may not be imedialely available during `change()` due to the instance not being attached to the root yet.
1377
1388
  // when it's available, we need to enqueue the "changes" changeset into the "filteredChanges" changeset.
1378
1389
  //
1379
1390
  if (this.isFiltered) {
1391
+ this.isVisibilitySharedWithParent = (parentChangeTree.isFiltered &&
1392
+ typeof (refType) !== "string" &&
1393
+ !fieldHasViewTag &&
1394
+ parentIsCollection);
1380
1395
  if (!this.filteredChanges) {
1381
1396
  this.filteredChanges = createChangeSet();
1382
1397
  this.allFilteredChanges = createChangeSet();
@@ -1512,6 +1527,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
1512
1527
  }
1513
1528
  const type = changeTree.getType(field);
1514
1529
  const value = changeTree.getValue(field, isEncodeAll);
1530
+ // console.log({ type, field, value });
1515
1531
  // console.log("encodeArray -> ", {
1516
1532
  // ref: changeTree.ref.constructor.name,
1517
1533
  // field,
@@ -1833,8 +1849,7 @@ class ArraySchema {
1833
1849
  static [(_a$4 = $encoder, _b$4 = $decoder, $filter)](ref, index, view) {
1834
1850
  return (!view ||
1835
1851
  typeof (ref[$childType]) === "string" ||
1836
- // view.items.has(ref[$getByIndex](index)[$changes])
1837
- view.visible.has(ref['tmpItems'][index]?.[$changes]));
1852
+ view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
1838
1853
  }
1839
1854
  static is(type) {
1840
1855
  return (
@@ -1990,8 +2005,6 @@ class ArraySchema {
1990
2005
  return undefined;
1991
2006
  }
1992
2007
  this[$changes].delete(index, undefined, this.items.length - 1);
1993
- // this.tmpItems[index] = undefined;
1994
- // this.tmpItems.pop();
1995
2008
  this.deletedIndexes[index] = true;
1996
2009
  return this.items.pop();
1997
2010
  }
@@ -2142,31 +2155,50 @@ class ArraySchema {
2142
2155
  * @param deleteCount The number of elements to remove.
2143
2156
  * @param insertItems Elements to insert into the array in place of the deleted elements.
2144
2157
  */
2145
- splice(start, deleteCount = this.items.length - start, ...insertItems) {
2158
+ splice(start, deleteCount, ...insertItems) {
2146
2159
  const changeTree = this[$changes];
2160
+ const itemsLength = this.items.length;
2147
2161
  const tmpItemsLength = this.tmpItems.length;
2148
2162
  const insertCount = insertItems.length;
2149
2163
  // build up-to-date list of indexes, excluding removed values.
2150
2164
  const indexes = [];
2151
2165
  for (let i = 0; i < tmpItemsLength; i++) {
2152
- // if (this.tmpItems[i] !== undefined) {
2153
2166
  if (this.deletedIndexes[i] !== true) {
2154
2167
  indexes.push(i);
2155
2168
  }
2156
2169
  }
2157
- // delete operations at correct index
2158
- for (let i = start; i < start + deleteCount; i++) {
2159
- const index = indexes[i];
2160
- changeTree.delete(index);
2161
- // this.tmpItems[index] = undefined;
2162
- this.deletedIndexes[index] = true;
2170
+ if (itemsLength > start) {
2171
+ // if deleteCount is not provided, delete all items from start to end
2172
+ if (deleteCount === undefined) {
2173
+ deleteCount = itemsLength - start;
2174
+ }
2175
+ //
2176
+ // delete operations at correct index
2177
+ //
2178
+ for (let i = start; i < start + deleteCount; i++) {
2179
+ const index = indexes[i];
2180
+ changeTree.delete(index, exports.OPERATION.DELETE);
2181
+ this.deletedIndexes[index] = true;
2182
+ }
2163
2183
  }
2164
- // force insert operations
2165
- for (let i = 0; i < insertCount; i++) {
2166
- const addIndex = indexes[start] + i;
2167
- changeTree.indexedOperation(addIndex, exports.OPERATION.ADD);
2168
- // set value's parent/root
2169
- insertItems[i][$changes]?.setParent(this, changeTree.root, addIndex);
2184
+ else {
2185
+ // not enough items to delete
2186
+ deleteCount = 0;
2187
+ }
2188
+ // insert operations
2189
+ if (insertCount > 0) {
2190
+ if (insertCount > deleteCount) {
2191
+ console.error("Inserting more elements than deleting during ArraySchema#splice()");
2192
+ throw new Error("ArraySchema#splice(): insertCount must be equal or lower than deleteCount.");
2193
+ }
2194
+ for (let i = 0; i < insertCount; i++) {
2195
+ const addIndex = (indexes[start] ?? itemsLength) + i;
2196
+ changeTree.indexedOperation(addIndex, (this.deletedIndexes[addIndex])
2197
+ ? exports.OPERATION.DELETE_AND_ADD
2198
+ : exports.OPERATION.ADD);
2199
+ // set value's parent/root
2200
+ insertItems[i][$changes]?.setParent(this, changeTree.root, addIndex);
2201
+ }
2170
2202
  }
2171
2203
  //
2172
2204
  // delete exceeding indexes from "allChanges"
@@ -2174,6 +2206,16 @@ class ArraySchema {
2174
2206
  //
2175
2207
  if (deleteCount > insertCount) {
2176
2208
  changeTree.shiftAllChangeIndexes(-(deleteCount - insertCount), indexes[start + insertCount]);
2209
+ // debugChangeSet("AFTER SHIFT indexes", changeTree.allChanges);
2210
+ }
2211
+ //
2212
+ // FIXME: this code block is duplicated on ChangeTree
2213
+ //
2214
+ if (changeTree.filteredChanges !== undefined) {
2215
+ enqueueChangeTree(changeTree.root, changeTree, 'filteredChanges');
2216
+ }
2217
+ else {
2218
+ enqueueChangeTree(changeTree.root, changeTree, 'changes');
2177
2219
  }
2178
2220
  return this.items.splice(start, deleteCount, ...insertItems);
2179
2221
  }
@@ -2429,9 +2471,6 @@ class ArraySchema {
2429
2471
  : this.deletedIndexes[index]
2430
2472
  ? this.items[index]
2431
2473
  : this.tmpItems[index] || this.items[index];
2432
- // return (isEncodeAll)
2433
- // ? this.items[index]
2434
- // : this.tmpItems[index] ?? this.items[index];
2435
2474
  }
2436
2475
  [$deleteByIndex](index) {
2437
2476
  this.items[index] = undefined;
@@ -2491,7 +2530,7 @@ class MapSchema {
2491
2530
  static [(_a$3 = $encoder, _b$3 = $decoder, $filter)](ref, index, view) {
2492
2531
  return (!view ||
2493
2532
  typeof (ref[$childType]) === "string" ||
2494
- view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
2533
+ view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
2495
2534
  }
2496
2535
  static is(type) {
2497
2536
  return type['map'] !== undefined;
@@ -3108,7 +3147,7 @@ class Schema {
3108
3147
  }
3109
3148
  else if (tag === DEFAULT_VIEW_TAG) {
3110
3149
  // view pass: default tag
3111
- return view.visible.has(ref[$changes]);
3150
+ return view.isChangeTreeVisible(ref[$changes]);
3112
3151
  }
3113
3152
  else {
3114
3153
  // view pass: custom tag
@@ -3321,7 +3360,7 @@ class CollectionSchema {
3321
3360
  static [(_a$1 = $encoder, _b$1 = $decoder, $filter)](ref, index, view) {
3322
3361
  return (!view ||
3323
3362
  typeof (ref[$childType]) === "string" ||
3324
- view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
3363
+ view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
3325
3364
  }
3326
3365
  static is(type) {
3327
3366
  return type['collection'] !== undefined;
@@ -3795,18 +3834,17 @@ class Encoder {
3795
3834
  continue;
3796
3835
  }
3797
3836
  if (hasView) {
3798
- if (!view.visible.has(changeTree)) {
3837
+ if (!view.isChangeTreeVisible(changeTree)) {
3838
+ // console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
3799
3839
  view.invisible.add(changeTree);
3800
3840
  continue; // skip this change tree
3801
3841
  }
3802
- else {
3803
- view.invisible.delete(changeTree); // remove from invisible list
3804
- }
3842
+ view.invisible.delete(changeTree); // remove from invisible list
3805
3843
  }
3806
- const operations = changeTree[changeSetName];
3844
+ const changeSet = changeTree[changeSetName];
3807
3845
  const ref = changeTree.ref;
3808
3846
  // TODO: avoid iterating over change tree if no changes were made
3809
- const numChanges = operations.operations.length;
3847
+ const numChanges = changeSet.operations.length;
3810
3848
  if (numChanges === 0) {
3811
3849
  continue;
3812
3850
  }
@@ -3821,7 +3859,7 @@ class Encoder {
3821
3859
  encode.number(buffer, changeTree.refId, it);
3822
3860
  }
3823
3861
  for (let j = 0; j < numChanges; j++) {
3824
- const fieldIndex = operations.operations[j];
3862
+ const fieldIndex = changeSet.operations[j];
3825
3863
  const operation = (fieldIndex < 0)
3826
3864
  ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
3827
3865
  : (isEncodeAll)
@@ -4935,6 +4973,25 @@ class StateView {
4935
4973
  // clear items array
4936
4974
  this.items.length = 0;
4937
4975
  }
4976
+ isChangeTreeVisible(changeTree) {
4977
+ let isVisible = this.visible.has(changeTree);
4978
+ //
4979
+ // TODO: avoid checking for parent visibility, most of the time it's not needed
4980
+ // See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'
4981
+ //
4982
+ if (!isVisible && changeTree.isVisibilitySharedWithParent) {
4983
+ // console.log("CHECK AGAINST PARENT...", {
4984
+ // ref: changeTree.ref.constructor.name,
4985
+ // refId: changeTree.refId,
4986
+ // parent: changeTree.parent.constructor.name,
4987
+ // });
4988
+ if (this.visible.has(changeTree.parent[$changes])) {
4989
+ this.visible.add(changeTree);
4990
+ isVisible = true;
4991
+ }
4992
+ }
4993
+ return isVisible;
4994
+ }
4938
4995
  }
4939
4996
 
4940
4997
  registerType("map", { constructor: MapSchema });