@colyseus/schema 3.0.25 → 3.0.27

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,3 +1,5 @@
1
+ import * as util from "util";
2
+
1
3
  import { OPERATION } from "../encoding/spec";
2
4
  import { Schema } from "../Schema";
3
5
  import { $changes, $childType, $decoder, $onEncodeEnd, $encoder, $getByIndex, $refTypeFieldIndexes, $viewFieldIndexes } from "../types/symbols";
@@ -39,7 +41,7 @@ export interface IndexedOperations {
39
41
  export interface ChangeSet {
40
42
  // field index -> operation index
41
43
  indexes: { [index: number]: number };
42
- operations: OPERATION[]
44
+ operations: number[];
43
45
  queueRootIndex?: number; // index of ChangeTree structure in `root.changes` or `root.filteredChanges`
44
46
  }
45
47
 
@@ -56,14 +58,43 @@ export function setOperationAtIndex(changeSet: ChangeSet, index: number) {
56
58
  }
57
59
  }
58
60
 
59
- export function deleteOperationAtIndex(changeSet: ChangeSet, index: number) {
60
- const operationsIndex = changeSet.indexes[index];
61
- if (operationsIndex !== undefined) {
62
- changeSet.operations[operationsIndex] = undefined;
61
+ export function deleteOperationAtIndex(changeSet: ChangeSet, index: number | string) {
62
+ let operationsIndex = changeSet.indexes[index];
63
+ if (operationsIndex === undefined) {
64
+ //
65
+ // if index is not found, we need to find the last operation
66
+ // FIXME: this is not very efficient
67
+ //
68
+ // > See "should allow consecutive splices (same place)" tests
69
+ //
70
+ operationsIndex = Object.values(changeSet.indexes).at(-1);
71
+ index = Object.entries(changeSet.indexes).find(([_, value]) => value === operationsIndex)?.[0];
63
72
  }
73
+ changeSet.operations[operationsIndex] = undefined;
64
74
  delete changeSet.indexes[index];
65
75
  }
66
76
 
77
+ export function debugChangeSet(label: string, changeSet: ChangeSet) {
78
+ let indexes: string[] = [];
79
+ let operations: string[] = [];
80
+
81
+ for (const index in changeSet.indexes) {
82
+ indexes.push(`\t${util.inspect(index, { colors: true })} => [${util.inspect(changeSet.indexes[index], { colors: true })}]`);
83
+ }
84
+
85
+ for (let i = 0; i < changeSet.operations.length; i++) {
86
+ const index = changeSet.operations[i];
87
+ if (index !== undefined) {
88
+ operations.push(`\t[${util.inspect(i, { colors: true })}] => ${util.inspect(index, { colors: true })}`);
89
+ }
90
+ }
91
+
92
+ console.log(`${label} =>\nindexes (${Object.keys(changeSet.indexes).length}) {`);
93
+ console.log(indexes.join("\n"), "\n}");
94
+ console.log(`operations (${changeSet.operations.filter(op => op !== undefined).length}) {`);
95
+ console.log(operations.join("\n"), "\n}");
96
+ }
97
+
67
98
  export function enqueueChangeTree(
68
99
  root: Root,
69
100
  changeTree: ChangeTree,
@@ -267,7 +298,7 @@ export class ChangeTree<T extends Ref=any> {
267
298
  const newIndexes = {};
268
299
  for (const index in this.indexedOperations) {
269
300
  newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
270
- newIndexes[Number(index) + shiftIndex] = changeSet[index];
301
+ newIndexes[Number(index) + shiftIndex] = changeSet.indexes[index];
271
302
  }
272
303
  this.indexedOperations = newIndexedOperations;
273
304
  changeSet.indexes = newIndexes;
@@ -292,15 +323,24 @@ export class ChangeTree<T extends Ref=any> {
292
323
 
293
324
  private _shiftAllChangeIndexes(shiftIndex: number, startIndex: number = 0, changeSet: ChangeSet) {
294
325
  const newIndexes = {};
295
-
326
+ let newKey = 0;
296
327
  for (const key in changeSet.indexes) {
297
- const index = changeSet.indexes[key];
298
- if (index > startIndex) {
299
- newIndexes[Number(key) + shiftIndex] = index;
300
- } else {
301
- newIndexes[key] = index;
302
- }
328
+ newIndexes[newKey++] = changeSet.indexes[key];
303
329
  }
330
+
331
+ // const newIndexes = {};
332
+ // let newKey = 0;
333
+ // for (const key in changeSet.indexes) {
334
+ // const index = changeSet.indexes[key];
335
+ // newIndexes[newKey++] = changeSet.indexes[key];
336
+ // console.log("...shiftAllChangeIndexes", { index: key, targetIndex: index, startIndex, shiftIndex });
337
+ // if (index > startIndex) {
338
+ // newIndexes[Number(key) + shiftIndex] = index;
339
+ // } else {
340
+ // newIndexes[Number(key)] = index;
341
+ // }
342
+ // }
343
+
304
344
  changeSet.indexes = newIndexes;
305
345
 
306
346
  for (let i = 0; i < changeSet.operations.length; i++) {
@@ -372,6 +412,7 @@ export class ChangeTree<T extends Ref=any> {
372
412
 
373
413
  this.indexedOperations[index] = operation ?? OPERATION.DELETE;
374
414
  setOperationAtIndex(changeSet, index);
415
+ deleteOperationAtIndex(this.allChanges, allChangesIndex);
375
416
 
376
417
  const previousValue = this.getValue(index);
377
418
 
@@ -390,8 +431,6 @@ export class ChangeTree<T extends Ref=any> {
390
431
  this.root?.remove(previousValue[$changes]);
391
432
  }
392
433
 
393
- deleteOperationAtIndex(this.allChanges, allChangesIndex);
394
-
395
434
  //
396
435
  // FIXME: this is looking a ugly and repeated
397
436
  //
@@ -217,6 +217,8 @@ export const encodeArray: EncodeOperation = function (
217
217
  const type = changeTree.getType(field);
218
218
  const value = changeTree.getValue(field, isEncodeAll);
219
219
 
220
+ // console.log({ type, field, value });
221
+
220
222
  // console.log("encodeArray -> ", {
221
223
  // ref: changeTree.ref.constructor.name,
222
224
  // field,
@@ -10,7 +10,7 @@ import { Root } from "./Root";
10
10
 
11
11
  import type { StateView } from "./StateView";
12
12
  import type { Metadata } from "../Metadata";
13
- import type { ChangeTree } from "./ChangeTree";
13
+ import type { ChangeSetName, ChangeTree } from "./ChangeTree";
14
14
 
15
15
  export class Encoder<T extends Schema = any> {
16
16
  static BUFFER_SIZE = (typeof(Buffer) !== "undefined") && Buffer.poolSize || 8 * 1024; // 8KB
@@ -48,7 +48,7 @@ export class Encoder<T extends Schema = any> {
48
48
  it: Iterator = { offset: 0 },
49
49
  view?: StateView,
50
50
  buffer = this.sharedBuffer,
51
- changeSetName: "changes" | "allChanges" | "filteredChanges" | "allFilteredChanges" = "changes",
51
+ changeSetName: ChangeSetName = "changes",
52
52
  isEncodeAll = changeSetName === "allChanges",
53
53
  initialOffset = it.offset // cache current offset in case we need to resize the buffer
54
54
  ): Buffer {
@@ -70,11 +70,11 @@ export class Encoder<T extends Schema = any> {
70
70
  }
71
71
  }
72
72
 
73
- const operations = changeTree[changeSetName];
73
+ const changeSet = changeTree[changeSetName];
74
74
  const ref = changeTree.ref;
75
75
 
76
76
  // TODO: avoid iterating over change tree if no changes were made
77
- const numChanges = operations.operations.length;
77
+ const numChanges = changeSet.operations.length;
78
78
  if (numChanges === 0) { continue; }
79
79
 
80
80
  const ctor = ref.constructor;
@@ -90,7 +90,7 @@ export class Encoder<T extends Schema = any> {
90
90
  }
91
91
 
92
92
  for (let j = 0; j < numChanges; j++) {
93
- const fieldIndex = operations.operations[j];
93
+ const fieldIndex = changeSet.operations[j];
94
94
 
95
95
  const operation = (fieldIndex < 0)
96
96
  ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
package/src/index.ts CHANGED
@@ -50,7 +50,7 @@ export { getRawChangesCallback } from "./decoder/strategy/RawChanges";
50
50
 
51
51
  export { Encoder } from "./encoder/Encoder";
52
52
  export { encodeSchemaOperation, encodeArray, encodeKeyValueOperation } from "./encoder/EncodeOperation";
53
- export { ChangeTree, Ref } from "./encoder/ChangeTree";
53
+ export { ChangeTree, Ref, type ChangeSetName, type ChangeSet} from "./encoder/ChangeTree";
54
54
  export { StateView } from "./encoder/StateView";
55
55
 
56
56
  export { Decoder } from "./decoder/Decoder";
@@ -1,6 +1,6 @@
1
1
  import { $changes, $childType, $decoder, $deleteByIndex, $onEncodeEnd, $encoder, $filter, $getByIndex, $onDecodeEnd } from "../symbols";
2
2
  import type { Schema } from "../../Schema";
3
- import { ChangeTree, setOperationAtIndex } from "../../encoder/ChangeTree";
3
+ import { ChangeTree, debugChangeSet, deleteOperationAtIndex, enqueueChangeTree, setOperationAtIndex } from "../../encoder/ChangeTree";
4
4
  import { OPERATION } from "../../encoding/spec";
5
5
  import { registerType } from "../registry";
6
6
  import { Collection } from "../HelperTypes";
@@ -229,9 +229,6 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
229
229
 
230
230
  this[$changes].delete(index, undefined, this.items.length - 1);
231
231
 
232
- // this.tmpItems[index] = undefined;
233
- // this.tmpItems.pop();
234
-
235
232
  this.deletedIndexes[index] = true;
236
233
 
237
234
  return this.items.pop();
@@ -360,11 +357,13 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
360
357
  if (this.items.length === 0) { return undefined; }
361
358
 
362
359
  // const index = Number(Object.keys(changeTree.indexes)[0]);
363
- const index = this.tmpItems.findIndex((item, i) => item === this.items[0]);
364
360
  const changeTree = this[$changes];
365
361
 
366
- changeTree.delete(index);
367
- changeTree.shiftAllChangeIndexes(-1, index);
362
+ const index = this.tmpItems.findIndex(item => item === this.items[0]);
363
+ const allChangesIndex = this.items.findIndex(item => item === this.items[0]);
364
+
365
+ changeTree.delete(index, OPERATION.DELETE, allChangesIndex);
366
+ changeTree.shiftAllChangeIndexes(-1, allChangesIndex);
368
367
 
369
368
  // this.deletedIndexes[index] = true;
370
369
 
@@ -410,38 +409,63 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
410
409
  */
411
410
  splice(
412
411
  start: number,
413
- deleteCount: number = this.items.length - start,
412
+ deleteCount?: number,
414
413
  ...insertItems: V[]
415
414
  ): V[] {
416
415
  const changeTree = this[$changes];
417
416
 
417
+ const itemsLength = this.items.length;
418
418
  const tmpItemsLength = this.tmpItems.length;
419
419
  const insertCount = insertItems.length;
420
420
 
421
421
  // build up-to-date list of indexes, excluding removed values.
422
422
  const indexes: number[] = [];
423
423
  for (let i = 0; i < tmpItemsLength; i++) {
424
- // if (this.tmpItems[i] !== undefined) {
425
424
  if (this.deletedIndexes[i] !== true) {
426
425
  indexes.push(i);
427
426
  }
428
427
  }
429
428
 
430
- // delete operations at correct index
431
- for (let i = start; i < start + deleteCount; i++) {
432
- const index = indexes[i];
433
- changeTree.delete(index);
434
- // this.tmpItems[index] = undefined;
435
- this.deletedIndexes[index] = true;
429
+ if (itemsLength > start) {
430
+ // if deleteCount is not provided, delete all items from start to end
431
+ if (deleteCount === undefined) {
432
+ deleteCount = itemsLength - start;
433
+ }
434
+
435
+ //
436
+ // delete operations at correct index
437
+ //
438
+ for (let i = start; i < start + deleteCount; i++) {
439
+ const index = indexes[i];
440
+ changeTree.delete(index, OPERATION.DELETE);
441
+ this.deletedIndexes[index] = true;
442
+ }
443
+
444
+ } else {
445
+ // not enough items to delete
446
+ deleteCount = 0;
436
447
  }
437
448
 
438
- // force insert operations
439
- for (let i = 0; i < insertCount; i++) {
440
- const addIndex = indexes[start] + i;
441
- changeTree.indexedOperation(addIndex, OPERATION.ADD);
449
+ // insert operations
450
+ if (insertCount > 0) {
451
+ if (insertCount > deleteCount) {
452
+ console.error("Inserting more elements than deleting during ArraySchema#splice()");
453
+ throw new Error("ArraySchema#splice(): insertCount must be equal or lower than deleteCount.");
454
+ }
442
455
 
443
- // set value's parent/root
444
- insertItems[i][$changes]?.setParent(this, changeTree.root, addIndex);
456
+ for (let i = 0; i < insertCount; i++) {
457
+ const addIndex = (indexes[start] ?? itemsLength) + i;
458
+
459
+ changeTree.indexedOperation(
460
+ addIndex,
461
+ (this.deletedIndexes[addIndex])
462
+ ? OPERATION.DELETE_AND_ADD
463
+ : OPERATION.ADD
464
+ );
465
+
466
+ // set value's parent/root
467
+ insertItems[i][$changes]?.setParent(this, changeTree.root, addIndex);
468
+ }
445
469
  }
446
470
 
447
471
  //
@@ -450,6 +474,17 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
450
474
  //
451
475
  if (deleteCount > insertCount) {
452
476
  changeTree.shiftAllChangeIndexes(-(deleteCount - insertCount), indexes[start + insertCount]);
477
+ // debugChangeSet("AFTER SHIFT indexes", changeTree.allChanges);
478
+ }
479
+
480
+ //
481
+ // FIXME: this code block is duplicated on ChangeTree
482
+ //
483
+ if (changeTree.filteredChanges !== undefined) {
484
+ enqueueChangeTree(changeTree.root, changeTree, 'filteredChanges');
485
+
486
+ } else {
487
+ enqueueChangeTree(changeTree.root, changeTree, 'changes');
453
488
  }
454
489
 
455
490
  return this.items.splice(start, deleteCount, ...insertItems);
@@ -765,10 +800,6 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
765
800
  : this.deletedIndexes[index]
766
801
  ? this.items[index]
767
802
  : this.tmpItems[index] || this.items[index];
768
-
769
- // return (isEncodeAll)
770
- // ? this.items[index]
771
- // : this.tmpItems[index] ?? this.items[index];
772
803
  }
773
804
 
774
805
  protected [$deleteByIndex](index: number) {