@colyseus/schema 4.0.30 → 4.0.31

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.
@@ -110,6 +110,15 @@ export declare class ChangeTree<T extends Ref = any> {
110
110
  * Add a parent to the chain
111
111
  */
112
112
  addParent(parent: Ref, index: number): void;
113
+ /**
114
+ * Move `parent`'s existing chain entry to `index`, skipping the work
115
+ * `addParent` does. `parent` must already be a parent of this tree.
116
+ *
117
+ * Called by collections whose wire slots shift (ArraySchema): StateView
118
+ * addresses per-view ADD/DELETE by that index, so it has to follow the
119
+ * element it names.
120
+ */
121
+ setParentIndex(parent: Ref, index: number): void;
113
122
  /**
114
123
  * Remove a parent from the chain
115
124
  * @param parent - The parent to remove
package/build/index.cjs CHANGED
@@ -1452,6 +1452,33 @@ class ChangeTree {
1452
1452
  next: this.parentChain
1453
1453
  };
1454
1454
  }
1455
+ /**
1456
+ * Move `parent`'s existing chain entry to `index`, skipping the work
1457
+ * `addParent` does. `parent` must already be a parent of this tree.
1458
+ *
1459
+ * Called by collections whose wire slots shift (ArraySchema): StateView
1460
+ * addresses per-view ADD/DELETE by that index, so it has to follow the
1461
+ * element it names.
1462
+ */
1463
+ setParentIndex(parent, index) {
1464
+ const chain = this.parentChain;
1465
+ if (chain === undefined) {
1466
+ return;
1467
+ }
1468
+ if (chain.next === undefined) {
1469
+ chain.index = index; // sole parent, so it is `parent`
1470
+ return;
1471
+ }
1472
+ // Shared instance — move only the entry `parent` owns. Matching goes
1473
+ // through `$changes` because ArraySchema arrives proxied (see
1474
+ // removeParent below).
1475
+ for (let entry = chain; entry !== undefined; entry = entry.next) {
1476
+ if (entry.ref[$changes] === parent[$changes]) {
1477
+ entry.index = index;
1478
+ return;
1479
+ }
1480
+ }
1481
+ }
1455
1482
  /**
1456
1483
  * Remove a parent from the chain
1457
1484
  * @param parent - The parent to remove
@@ -2088,6 +2115,39 @@ class ArraySchema {
2088
2115
  get length() {
2089
2116
  return this.items.length;
2090
2117
  }
2118
+ /**
2119
+ * Re-point children at their wire slot. `ChangeTree.parentIndex` caches
2120
+ * the slot a child holds in `tmpItems`, and StateView addresses per-view
2121
+ * ADD/DELETE with it — so a reorder that leaves it behind aims those ops
2122
+ * at whichever element inherited the slot (issue #231).
2123
+ *
2124
+ * The filter check is a correctness boundary, not a tunable: StateView is
2125
+ * the only reader, and an array without `filteredChanges` never has a slot
2126
+ * read back. Everything else stops at that check instead of walking its
2127
+ * children every tick.
2128
+ *
2129
+ * Callers name the lowest slot that moved as `from`. Compaction cannot, so
2130
+ * it hands over the pre-compaction layout as `staged` and the unchanged
2131
+ * prefix is skipped instead. Either way tail churn walks nothing.
2132
+ */
2133
+ $reindexChildren(from, staged) {
2134
+ if (this[$changes].filteredChanges === undefined) {
2135
+ return;
2136
+ } // nothing will read the cache
2137
+ if (typeof this[$childType] === "string") {
2138
+ return;
2139
+ } // primitives have no child tree
2140
+ const tmpItems = this.tmpItems;
2141
+ const length = tmpItems.length;
2142
+ if (staged !== undefined) {
2143
+ while (from < length && tmpItems[from] === staged[from]) {
2144
+ from++;
2145
+ }
2146
+ }
2147
+ for (let i = from; i < length; i++) {
2148
+ tmpItems[i]?.[$changes]?.setParentIndex(this, i);
2149
+ }
2150
+ }
2091
2151
  push(...values) {
2092
2152
  let length = this.tmpItems.length;
2093
2153
  const changeTree = this[$changes];
@@ -2220,6 +2280,7 @@ class ArraySchema {
2220
2280
  this[$changes].operation(exports.OPERATION.REVERSE);
2221
2281
  this.items.reverse();
2222
2282
  this.tmpItems.reverse();
2283
+ this.$reindexChildren(0);
2223
2284
  return this;
2224
2285
  }
2225
2286
  /**
@@ -2263,6 +2324,7 @@ class ArraySchema {
2263
2324
  // wouldn't OPERATION.MOVE make more sense here?
2264
2325
  sortedItems.forEach((_, i) => changeTree.change(i, exports.OPERATION.REPLACE));
2265
2326
  this.tmpItems.sort(compareFn);
2327
+ this.$reindexChildren(0);
2266
2328
  this.isMovingItems = false;
2267
2329
  return this;
2268
2330
  }
@@ -2358,6 +2420,7 @@ class ArraySchema {
2358
2420
  changeTree.change(index, exports.OPERATION.ADD);
2359
2421
  });
2360
2422
  this.tmpItems.unshift(...items);
2423
+ this.$reindexChildren(0); // from 0: nothing above placed the new items either
2361
2424
  return this.items.unshift(...items);
2362
2425
  }
2363
2426
  /**
@@ -2629,7 +2692,13 @@ class ArraySchema {
2629
2692
  this.tmpItems[index] = undefined; // TODO: do not try to get "tmpItems" at decoding time.
2630
2693
  }
2631
2694
  [$onEncodeEnd]() {
2695
+ const staged = this.tmpItems;
2632
2696
  this.tmpItems = this.items.slice();
2697
+ // Compaction just closed the staged holes — everything above the
2698
+ // lowest one slid down a slot. There is no cheap "were there any"
2699
+ // test to gate this on: `deletedIndexes` is an object here, and a
2700
+ // `for...in` probe measured slower than the prefix scan it skips.
2701
+ this.$reindexChildren(0, staged);
2633
2702
  this.deletedIndexes = {};
2634
2703
  }
2635
2704
  [$onDecodeEnd]() {