@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.
@@ -1,6 +1,7 @@
1
1
  import { OPERATION } from "../encoding/spec";
2
2
  import { TypeContext } from "../types/TypeContext";
3
3
  import { ChangeTree, setOperationAtIndex, ChangeTreeList, createChangeTreeList, ChangeSetName, type ChangeTreeNode } from "./ChangeTree";
4
+ import { $changes } from "../types/symbols";
4
5
 
5
6
  export class Root {
6
7
  protected nextUniqueId: number = 0;
@@ -83,8 +84,8 @@ export class Root {
83
84
  this.remove(child);
84
85
 
85
86
  } else if (child.parentChain) {
86
- // re-assigning a child of the same root, move it to the end
87
- this.moveToEndOfChanges(child);
87
+ // re-assigning a child of the same root, move it next to parent
88
+ this.moveNextToParent(child);
88
89
  }
89
90
  }
90
91
  });
@@ -94,36 +95,57 @@ export class Root {
94
95
 
95
96
  //
96
97
  // When losing a reference to an instance, it is best to move the
97
- // ChangeTree to the end of the encoding queue.
98
+ // ChangeTree next to its parent in the encoding queue.
98
99
  //
99
100
  // This way, at decoding time, the instance that contains the
100
101
  // ChangeTree will be available before the ChangeTree itself. If the
101
102
  // containing instance is not available, the Decoder will throw
102
103
  // "refId not found" error.
103
104
  //
104
- this.moveToEndOfChanges(changeTree);
105
- changeTree.forEachChild((child, _) => this.moveToEndOfChanges(child));
105
+ this.recursivelyMoveNextToParent(changeTree);
106
106
  }
107
107
 
108
108
  return refCount;
109
109
  }
110
110
 
111
- moveToEndOfChanges(changeTree: ChangeTree) {
111
+ recursivelyMoveNextToParent(changeTree: ChangeTree) {
112
+ this.moveNextToParent(changeTree);
113
+ changeTree.forEachChild((child, _) => this.recursivelyMoveNextToParent(child));
114
+ }
115
+
116
+ moveNextToParent(changeTree: ChangeTree) {
112
117
  if (changeTree.filteredChanges) {
113
- this.moveToEndOfChangeTreeList("filteredChanges", changeTree);
114
- this.moveToEndOfChangeTreeList("allFilteredChanges", changeTree);
118
+ this.moveNextToParentInChangeTreeList("filteredChanges", changeTree);
119
+ this.moveNextToParentInChangeTreeList("allFilteredChanges", changeTree);
115
120
  } else {
116
- this.moveToEndOfChangeTreeList("changes", changeTree);
117
- this.moveToEndOfChangeTreeList("allChanges", changeTree);
121
+ this.moveNextToParentInChangeTreeList("changes", changeTree);
122
+ this.moveNextToParentInChangeTreeList("allChanges", changeTree);
118
123
  }
119
124
  }
120
125
 
121
- moveToEndOfChangeTreeList(changeSetName: ChangeSetName, changeTree: ChangeTree): void {
126
+ moveNextToParentInChangeTreeList(changeSetName: ChangeSetName, changeTree: ChangeTree): void {
122
127
  const changeSet = this[changeSetName];
123
128
  const node = changeTree[changeSetName].queueRootNode;
124
- if (!node || node === changeSet.tail) return;
129
+ if (!node) return;
130
+
131
+ // Find the parent in the linked list
132
+ const parent = changeTree.parent;
133
+ if (!parent || !parent[$changes]) return;
134
+
135
+ const parentNode = parent[$changes][changeSetName]?.queueRootNode;
136
+ if (!parentNode || parentNode === node) return;
137
+
138
+ // Use cached positions - no iteration needed!
139
+ const parentPosition = parentNode.position;
140
+ const childPosition = node.position;
125
141
 
126
- // Remove from current position
142
+ // If child is already after parent, no need to move
143
+ if (childPosition > parentPosition) return;
144
+
145
+ // Child is before parent, so we need to move it after parent
146
+ // This maintains decoding order (parent before child)
147
+
148
+ // Remove node from current position
127
149
  if (node.prev) {
128
150
  node.prev.next = node.next;
129
151
  } else {
@@ -136,17 +158,20 @@ export class Root {
136
158
  changeSet.tail = node.prev;
137
159
  }
138
160
 
139
- // Add to end
140
- node.prev = changeSet.tail;
141
- node.next = undefined;
161
+ // Insert node right after parent
162
+ node.prev = parentNode;
163
+ node.next = parentNode.next;
142
164
 
143
- if (changeSet.tail) {
144
- changeSet.tail.next = node;
165
+ if (parentNode.next) {
166
+ parentNode.next.prev = node;
145
167
  } else {
146
- changeSet.next = node;
168
+ changeSet.tail = node;
147
169
  }
148
170
 
149
- changeSet.tail = node;
171
+ parentNode.next = node;
172
+
173
+ // Update positions after the move
174
+ this.updatePositionsAfterMove(changeSet, node, parentPosition + 1);
150
175
  }
151
176
 
152
177
  public enqueueChangeTree(
@@ -162,7 +187,12 @@ export class Root {
162
187
  }
163
188
 
164
189
  protected addToChangeTreeList(list: ChangeTreeList, changeTree: ChangeTree): ChangeTreeNode {
165
- const node: ChangeTreeNode = { changeTree, next: undefined, prev: undefined };
190
+ const node: ChangeTreeNode = {
191
+ changeTree,
192
+ next: undefined,
193
+ prev: undefined,
194
+ position: list.tail ? list.tail.position + 1 : 0
195
+ };
166
196
 
167
197
  if (!list.next) {
168
198
  list.next = node;
@@ -173,16 +203,42 @@ export class Root {
173
203
  list.tail = node;
174
204
  }
175
205
 
176
- list.length++;
177
-
178
206
  return node;
179
207
  }
180
208
 
209
+ protected updatePositionsAfterRemoval(list: ChangeTreeList, removedPosition: number) {
210
+ // Update positions for all nodes after the removed position
211
+ let current = list.next;
212
+ let position = 0;
213
+
214
+ while (current) {
215
+ if (position >= removedPosition) {
216
+ current.position = position;
217
+ }
218
+ current = current.next;
219
+ position++;
220
+ }
221
+ }
222
+
223
+ protected updatePositionsAfterMove(list: ChangeTreeList, node: ChangeTreeNode, newPosition: number) {
224
+ // Recalculate all positions - this is more reliable than trying to be clever
225
+ let current = list.next;
226
+ let position = 0;
227
+
228
+ while (current) {
229
+ current.position = position;
230
+ current = current.next;
231
+ position++;
232
+ }
233
+ }
234
+
181
235
  public removeChangeFromChangeSet(changeSetName: ChangeSetName, changeTree: ChangeTree) {
182
236
  const changeSet = this[changeSetName];
183
237
  const node = changeTree[changeSetName].queueRootNode;
184
238
 
185
239
  if (node && node.changeTree === changeTree) {
240
+ const removedPosition = node.position;
241
+
186
242
  // Remove the node from the linked list
187
243
  if (node.prev) {
188
244
  node.prev.next = node.next;
@@ -196,7 +252,8 @@ export class Root {
196
252
  changeSet.tail = node.prev;
197
253
  }
198
254
 
199
- changeSet.length--;
255
+ // Update positions for nodes that came after the removed node
256
+ this.updatePositionsAfterRemoval(changeSet, removedPosition);
200
257
 
201
258
  // Clear ChangeTree reference
202
259
  changeTree[changeSetName].queueRootNode = undefined;
@@ -224,10 +224,15 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
224
224
  protected [$onEncodeEnd]() {
225
225
  const changeTree = this[$changes];
226
226
 
227
- // cleanup changeTree.indexes of deleted keys
227
+ // - cleanup changeTree.indexes
228
+ // - cleanup $indexes
228
229
  for (const indexStr in this.deletedItems) {
229
- const key = this.$indexes.get(parseInt(indexStr));
230
+ const index = parseInt(indexStr);
231
+ const key = this.$indexes.get(index);
232
+ // TODO: refactor this.
233
+ // it shouldn't be necessary to keep track of indexes both on changeTree and on $indexes
230
234
  delete changeTree.indexes[key];
235
+ this.$indexes.delete(index);
231
236
  }
232
237
 
233
238
  this.deletedItems = {};