@colyseus/schema 3.0.57 → 3.0.60

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.
@@ -990,7 +990,7 @@ function createChangeSet(queueRootNode) {
990
990
  }
991
991
  // Linked list helper functions
992
992
  function createChangeTreeList() {
993
- return { next: undefined, tail: undefined, length: 0 };
993
+ return { next: undefined, tail: undefined };
994
994
  }
995
995
  function setOperationAtIndex(changeSet, index) {
996
996
  const operationsIndex = changeSet.indexes[index];
@@ -1082,11 +1082,11 @@ class ChangeTree {
1082
1082
  this.forEachChild((child, index) => {
1083
1083
  if (child.root === root) {
1084
1084
  //
1085
- // re-assigning a child of the same root, move it to the end
1086
- // of the changes queue so encoding order is preserved
1085
+ // re-assigning a child of the same root, move it next to parent
1086
+ // so encoding order is preserved
1087
1087
  //
1088
1088
  root.add(child);
1089
- root.moveToEndOfChanges(child);
1089
+ root.moveNextToParent(child);
1090
1090
  return;
1091
1091
  }
1092
1092
  child.setParent(this.ref, root, index);
@@ -2799,10 +2799,15 @@ class MapSchema {
2799
2799
  }
2800
2800
  [$onEncodeEnd]() {
2801
2801
  const changeTree = this[$changes];
2802
- // cleanup changeTree.indexes of deleted keys
2802
+ // - cleanup changeTree.indexes
2803
+ // - cleanup $indexes
2803
2804
  for (const indexStr in this.deletedItems) {
2804
- const key = this.$indexes.get(parseInt(indexStr));
2805
+ const index = parseInt(indexStr);
2806
+ const key = this.$indexes.get(index);
2807
+ // TODO: refactor this.
2808
+ // it shouldn't be necessary to keep track of indexes both on changeTree and on $indexes
2805
2809
  delete changeTree.indexes[key];
2810
+ this.$indexes.delete(index);
2806
2811
  }
2807
2812
  this.deletedItems = {};
2808
2813
  }
@@ -4013,8 +4018,8 @@ class Root {
4013
4018
  this.remove(child);
4014
4019
  }
4015
4020
  else if (child.parentChain) {
4016
- // re-assigning a child of the same root, move it to the end
4017
- this.moveToEndOfChanges(child);
4021
+ // re-assigning a child of the same root, move it next to parent
4022
+ this.moveNextToParent(child);
4018
4023
  }
4019
4024
  }
4020
4025
  });
@@ -4023,34 +4028,52 @@ class Root {
4023
4028
  this.refCount[changeTree.refId] = refCount;
4024
4029
  //
4025
4030
  // When losing a reference to an instance, it is best to move the
4026
- // ChangeTree to the end of the encoding queue.
4031
+ // ChangeTree next to its parent in the encoding queue.
4027
4032
  //
4028
4033
  // This way, at decoding time, the instance that contains the
4029
4034
  // ChangeTree will be available before the ChangeTree itself. If the
4030
4035
  // containing instance is not available, the Decoder will throw
4031
4036
  // "refId not found" error.
4032
4037
  //
4033
- this.moveToEndOfChanges(changeTree);
4034
- changeTree.forEachChild((child, _) => this.moveToEndOfChanges(child));
4038
+ this.recursivelyMoveNextToParent(changeTree);
4035
4039
  }
4036
4040
  return refCount;
4037
4041
  }
4038
- moveToEndOfChanges(changeTree) {
4042
+ recursivelyMoveNextToParent(changeTree) {
4043
+ this.moveNextToParent(changeTree);
4044
+ changeTree.forEachChild((child, _) => this.recursivelyMoveNextToParent(child));
4045
+ }
4046
+ moveNextToParent(changeTree) {
4039
4047
  if (changeTree.filteredChanges) {
4040
- this.moveToEndOfChangeTreeList("filteredChanges", changeTree);
4041
- this.moveToEndOfChangeTreeList("allFilteredChanges", changeTree);
4048
+ this.moveNextToParentInChangeTreeList("filteredChanges", changeTree);
4049
+ this.moveNextToParentInChangeTreeList("allFilteredChanges", changeTree);
4042
4050
  }
4043
4051
  else {
4044
- this.moveToEndOfChangeTreeList("changes", changeTree);
4045
- this.moveToEndOfChangeTreeList("allChanges", changeTree);
4052
+ this.moveNextToParentInChangeTreeList("changes", changeTree);
4053
+ this.moveNextToParentInChangeTreeList("allChanges", changeTree);
4046
4054
  }
4047
4055
  }
4048
- moveToEndOfChangeTreeList(changeSetName, changeTree) {
4056
+ moveNextToParentInChangeTreeList(changeSetName, changeTree) {
4049
4057
  const changeSet = this[changeSetName];
4050
4058
  const node = changeTree[changeSetName].queueRootNode;
4051
- if (!node || node === changeSet.tail)
4059
+ if (!node)
4060
+ return;
4061
+ // Find the parent in the linked list
4062
+ const parent = changeTree.parent;
4063
+ if (!parent || !parent[$changes])
4064
+ return;
4065
+ const parentNode = parent[$changes][changeSetName]?.queueRootNode;
4066
+ if (!parentNode || parentNode === node)
4067
+ return;
4068
+ // Use cached positions - no iteration needed!
4069
+ const parentPosition = parentNode.position;
4070
+ const childPosition = node.position;
4071
+ // If child is already after parent, no need to move
4072
+ if (childPosition > parentPosition)
4052
4073
  return;
4053
- // Remove from current position
4074
+ // Child is before parent, so we need to move it after parent
4075
+ // This maintains decoding order (parent before child)
4076
+ // Remove node from current position
4054
4077
  if (node.prev) {
4055
4078
  node.prev.next = node.next;
4056
4079
  }
@@ -4063,16 +4086,18 @@ class Root {
4063
4086
  else {
4064
4087
  changeSet.tail = node.prev;
4065
4088
  }
4066
- // Add to end
4067
- node.prev = changeSet.tail;
4068
- node.next = undefined;
4069
- if (changeSet.tail) {
4070
- changeSet.tail.next = node;
4089
+ // Insert node right after parent
4090
+ node.prev = parentNode;
4091
+ node.next = parentNode.next;
4092
+ if (parentNode.next) {
4093
+ parentNode.next.prev = node;
4071
4094
  }
4072
4095
  else {
4073
- changeSet.next = node;
4096
+ changeSet.tail = node;
4074
4097
  }
4075
- changeSet.tail = node;
4098
+ parentNode.next = node;
4099
+ // Update positions after the move
4100
+ this.updatePositionsAfterMove(changeSet, node, parentPosition + 1);
4076
4101
  }
4077
4102
  enqueueChangeTree(changeTree, changeSet, queueRootNode = changeTree[changeSet].queueRootNode) {
4078
4103
  // skip
@@ -4083,7 +4108,12 @@ class Root {
4083
4108
  changeTree[changeSet].queueRootNode = this.addToChangeTreeList(this[changeSet], changeTree);
4084
4109
  }
4085
4110
  addToChangeTreeList(list, changeTree) {
4086
- const node = { changeTree, next: undefined, prev: undefined };
4111
+ const node = {
4112
+ changeTree,
4113
+ next: undefined,
4114
+ prev: undefined,
4115
+ position: list.tail ? list.tail.position + 1 : 0
4116
+ };
4087
4117
  if (!list.next) {
4088
4118
  list.next = node;
4089
4119
  list.tail = node;
@@ -4093,13 +4123,35 @@ class Root {
4093
4123
  list.tail.next = node;
4094
4124
  list.tail = node;
4095
4125
  }
4096
- list.length++;
4097
4126
  return node;
4098
4127
  }
4128
+ updatePositionsAfterRemoval(list, removedPosition) {
4129
+ // Update positions for all nodes after the removed position
4130
+ let current = list.next;
4131
+ let position = 0;
4132
+ while (current) {
4133
+ if (position >= removedPosition) {
4134
+ current.position = position;
4135
+ }
4136
+ current = current.next;
4137
+ position++;
4138
+ }
4139
+ }
4140
+ updatePositionsAfterMove(list, node, newPosition) {
4141
+ // Recalculate all positions - this is more reliable than trying to be clever
4142
+ let current = list.next;
4143
+ let position = 0;
4144
+ while (current) {
4145
+ current.position = position;
4146
+ current = current.next;
4147
+ position++;
4148
+ }
4149
+ }
4099
4150
  removeChangeFromChangeSet(changeSetName, changeTree) {
4100
4151
  const changeSet = this[changeSetName];
4101
4152
  const node = changeTree[changeSetName].queueRootNode;
4102
4153
  if (node && node.changeTree === changeTree) {
4154
+ const removedPosition = node.position;
4103
4155
  // Remove the node from the linked list
4104
4156
  if (node.prev) {
4105
4157
  node.prev.next = node.next;
@@ -4113,7 +4165,8 @@ class Root {
4113
4165
  else {
4114
4166
  changeSet.tail = node.prev;
4115
4167
  }
4116
- changeSet.length--;
4168
+ // Update positions for nodes that came after the removed node
4169
+ this.updatePositionsAfterRemoval(changeSet, removedPosition);
4117
4170
  // Clear ChangeTree reference
4118
4171
  changeTree[changeSetName].queueRootNode = undefined;
4119
4172
  return true;
@@ -4335,8 +4388,8 @@ class Encoder {
4335
4388
  }
4336
4389
  }
4337
4390
  get hasChanges() {
4338
- return (this.root.changes.length > 0 ||
4339
- this.root.filteredChanges.length > 0);
4391
+ return (this.root.changes.next !== undefined ||
4392
+ this.root.filteredChanges.next !== undefined);
4340
4393
  }
4341
4394
  }
4342
4395
 
@@ -4519,8 +4572,7 @@ class Decoder {
4519
4572
  if (!nextRef) {
4520
4573
  // throw new Error(`"refId" not found: ${nextRefId}`);
4521
4574
  console.error(`"refId" not found: ${nextRefId}`, { previousRef: ref, previousRefId: this.currentRefId });
4522
- console.warn("Please report this to the developers. All refIds =>");
4523
- console.warn(Schema.debugRefIdsFromDecoder(this));
4575
+ console.warn("Please report this issue to the developers.");
4524
4576
  this.skipCurrentStructure(bytes, it, totalBytes);
4525
4577
  }
4526
4578
  else {