@colyseus/schema 5.0.12 → 5.0.13

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.
@@ -251,6 +251,8 @@ export declare class ChangeTree<T extends Ref = any> implements ChangeRecorder {
251
251
  get parent(): Ref | undefined;
252
252
  get parentIndex(): number | undefined;
253
253
  addParent(parent: Ref, index: number): void;
254
+ /** Re-point an existing parent's cached index after the parent reindexed. */
255
+ setParentIndex(parent: Ref, index: number): void;
254
256
  /** @returns true if parent was found and removed */
255
257
  removeParent(parent?: Ref): boolean;
256
258
  findParent(predicate: (parent: Ref, index: number) => boolean): ParentChain | undefined;
@@ -288,6 +290,7 @@ export declare class UntrackedChangeTree {
288
290
  operation(): void;
289
291
  setParent(): void;
290
292
  addParent(): void;
293
+ setParentIndex(): void;
291
294
  removeParent(): boolean;
292
295
  getChange(): number;
293
296
  discard(): void;
@@ -5,6 +5,15 @@ import type { ChangeTree, ParentChain, Ref } from "../ChangeTree.js";
5
5
  * behavior).
6
6
  */
7
7
  export declare function addParent(tree: ChangeTree, parent: Ref, index: number): void;
8
+ /**
9
+ * Move `parent`'s existing chain entry to `index`, skipping the attachment
10
+ * work `addParent` does. `parent` must already be a parent of `tree`.
11
+ *
12
+ * Called by collections whose wire slots shift (ArraySchema): StateView
13
+ * addresses per-view ADD/DELETE by that index, so it has to follow the
14
+ * element it names.
15
+ */
16
+ export declare function setParentIndex(tree: ChangeTree, parent: Ref, index: number): void;
8
17
  /**
9
18
  * Remove a parent from the chain.
10
19
  * @returns true if parent was found and removed (Root.remove relies on this).
package/build/index.cjs CHANGED
@@ -1914,6 +1914,34 @@ function addParent(tree, parent, index) {
1914
1914
  tree._parentIndex = index;
1915
1915
  }
1916
1916
  }
1917
+ /**
1918
+ * Move `parent`'s existing chain entry to `index`, skipping the attachment
1919
+ * work `addParent` does. `parent` must already be a parent of `tree`.
1920
+ *
1921
+ * Called by collections whose wire slots shift (ArraySchema): StateView
1922
+ * addresses per-view ADD/DELETE by that index, so it has to follow the
1923
+ * element it names.
1924
+ */
1925
+ function setParentIndex(tree, parent, index) {
1926
+ if (tree.extraParents === undefined) {
1927
+ tree._parentIndex = index; // sole parent, so it is `parent`
1928
+ return;
1929
+ }
1930
+ // Shared instance — move only the entry `parent` owns. Matching goes
1931
+ // through `$changes` because ArraySchema arrives proxied (see removeParent
1932
+ // below), and `extraParents` only ever fills by demoting `parentRef`, so
1933
+ // the inline parent is set here.
1934
+ if (tree.parentRef[$changes] === parent[$changes]) {
1935
+ tree._parentIndex = index;
1936
+ return;
1937
+ }
1938
+ for (let entry = tree.extraParents; entry !== undefined; entry = entry.next) {
1939
+ if (entry.ref[$changes] === parent[$changes]) {
1940
+ entry.index = index;
1941
+ return;
1942
+ }
1943
+ }
1944
+ }
1917
1945
  /**
1918
1946
  * Remove a parent from the chain.
1919
1947
  * @returns true if parent was found and removed (Root.remove relies on this).
@@ -3158,6 +3186,8 @@ class ChangeTree {
3158
3186
  get parent() { return this.parentRef; }
3159
3187
  get parentIndex() { return this._parentIndex; }
3160
3188
  addParent(parent, index) { addParent(this, parent, index); }
3189
+ /** Re-point an existing parent's cached index after the parent reindexed. */
3190
+ setParentIndex(parent, index) { setParentIndex(this, parent, index); }
3161
3191
  /** @returns true if parent was found and removed */
3162
3192
  removeParent(parent = this.parent) { return removeParent(this, parent); }
3163
3193
  findParent(predicate) {
@@ -3202,6 +3232,7 @@ class UntrackedChangeTree {
3202
3232
  operation() { }
3203
3233
  setParent() { }
3204
3234
  addParent() { }
3235
+ setParentIndex() { }
3205
3236
  removeParent() { return false; }
3206
3237
  getChange() { return 0; }
3207
3238
  discard() { }
@@ -4257,6 +4288,40 @@ class ArraySchema {
4257
4288
  // beyond the live range: appends land after the staged tmpItems tail
4258
4289
  return tmpItems.length + (index - live);
4259
4290
  }
4291
+ /**
4292
+ * Re-point children at their wire slot. `ChangeTree._parentIndex` caches
4293
+ * the slot a child holds in `tmpItems`, and StateView addresses per-view
4294
+ * ADD/DELETE with it — so a reorder that leaves it behind aims those ops
4295
+ * at whichever element inherited the slot (issue #231).
4296
+ *
4297
+ * The filter check is a correctness boundary, not a tunable: StateView is
4298
+ * the only reader and reaches the index only through a filtered array
4299
+ * (`addParentOf` bails on `hasFilteredFields`, `remove` on the child's
4300
+ * `isFiltered`). Everything else stops at the flag read instead of walking
4301
+ * its children every tick.
4302
+ *
4303
+ * Callers name the lowest slot that moved as `from`. Compaction cannot, so
4304
+ * it hands over the pre-compaction layout as `staged` and the unchanged
4305
+ * prefix is skipped instead. Either way tail churn walks nothing.
4306
+ */
4307
+ $reindexChildren(from, staged) {
4308
+ if (!this[$changes].hasFilteredFields) {
4309
+ return;
4310
+ } // nothing will read the cache
4311
+ if (typeof this[$childType] === "string") {
4312
+ return;
4313
+ } // primitives have no child tree
4314
+ const tmpItems = this.tmpItems;
4315
+ const length = tmpItems.length;
4316
+ if (staged !== undefined) {
4317
+ while (from < length && tmpItems[from] === staged[from]) {
4318
+ from++;
4319
+ }
4320
+ }
4321
+ for (let i = from; i < length; i++) {
4322
+ tmpItems[i]?.[$changes]?.setParentIndex(this, i);
4323
+ }
4324
+ }
4260
4325
  // encoding only. Returns the wire index the change was recorded at
4261
4326
  // (undefined when nothing was recorded).
4262
4327
  $changeAt(index, value) {
@@ -4368,6 +4433,7 @@ class ArraySchema {
4368
4433
  self[$changes].operation(exports.OPERATION.REVERSE);
4369
4434
  self.items.reverse();
4370
4435
  self.tmpItems.reverse();
4436
+ self.$reindexChildren(0);
4371
4437
  return this;
4372
4438
  }
4373
4439
  /**
@@ -4419,6 +4485,7 @@ class ArraySchema {
4419
4485
  // wouldn't OPERATION.MOVE make more sense here?
4420
4486
  sortedItems.forEach((_, i) => changeTree.change(i, exports.OPERATION.REPLACE));
4421
4487
  self.tmpItems.sort(compareFn);
4488
+ self.$reindexChildren(0);
4422
4489
  self.isMovingItems = false;
4423
4490
  return this;
4424
4491
  }
@@ -4500,6 +4567,7 @@ class ArraySchema {
4500
4567
  deletedIndexes.unshift(...new Array(items.length).fill(false));
4501
4568
  }
4502
4569
  self.tmpItems.unshift(...items);
4570
+ self.$reindexChildren(items.length); // survivors only — the loop above placed the new items
4503
4571
  return self.items.unshift(...items);
4504
4572
  }
4505
4573
  /**
@@ -4768,8 +4836,14 @@ class ArraySchema {
4768
4836
  }
4769
4837
  [$onEncodeEnd]() {
4770
4838
  const self = this[$proxyTarget] ?? this;
4839
+ const staged = self.tmpItems;
4771
4840
  self.tmpItems = self.items.slice();
4772
- self.deletedIndexes.length = 0;
4841
+ if (self.deletedIndexes.length > 0) {
4842
+ // compaction just closed the staged holes — everything above the
4843
+ // lowest one slid down a slot
4844
+ self.$reindexChildren(0, staged);
4845
+ self.deletedIndexes.length = 0;
4846
+ }
4773
4847
  }
4774
4848
  [$onDecodeEnd]() {
4775
4849
  const self = this[$proxyTarget] ?? this;