@colyseus/schema 3.0.26 → 3.0.28
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.
- package/build/cjs/index.js +101 -44
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +101 -44
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +101 -46
- package/lib/Schema.js +1 -1
- package/lib/Schema.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +4 -2
- package/lib/encoder/ChangeTree.js +45 -13
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.js +1 -0
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +2 -1
- package/lib/encoder/Encoder.js +6 -7
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/StateView.d.ts +1 -0
- package/lib/encoder/StateView.js +19 -0
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/index.d.ts +1 -1
- package/lib/index.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +44 -21
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.js +1 -1
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.js +1 -1
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/package.json +1 -1
- package/src/Schema.ts +1 -1
- package/src/encoder/ChangeTree.ts +58 -15
- package/src/encoder/EncodeOperation.ts +2 -0
- package/src/encoder/Encoder.ts +8 -9
- package/src/encoder/StateView.ts +24 -0
- package/src/index.ts +1 -1
- package/src/types/custom/ArraySchema.ts +52 -24
- package/src/types/custom/CollectionSchema.ts +1 -1
- package/src/types/custom/MapSchema.ts +1 -1
|
@@ -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:
|
|
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
|
-
|
|
61
|
-
if (operationsIndex
|
|
62
|
-
|
|
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,
|
|
@@ -91,6 +122,7 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
91
122
|
* Whether this structure is parent of a filtered structure.
|
|
92
123
|
*/
|
|
93
124
|
isFiltered: boolean = false;
|
|
125
|
+
isVisibilitySharedWithParent?: boolean; // See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'
|
|
94
126
|
|
|
95
127
|
indexedOperations: IndexedOperations = {};
|
|
96
128
|
|
|
@@ -292,14 +324,9 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
292
324
|
|
|
293
325
|
private _shiftAllChangeIndexes(shiftIndex: number, startIndex: number = 0, changeSet: ChangeSet) {
|
|
294
326
|
const newIndexes = {};
|
|
295
|
-
|
|
327
|
+
let newKey = 0;
|
|
296
328
|
for (const key in changeSet.indexes) {
|
|
297
|
-
|
|
298
|
-
if (index > startIndex) {
|
|
299
|
-
newIndexes[Number(key) + shiftIndex] = index;
|
|
300
|
-
} else {
|
|
301
|
-
newIndexes[key] = index;
|
|
302
|
-
}
|
|
329
|
+
newIndexes[newKey++] = changeSet.indexes[key];
|
|
303
330
|
}
|
|
304
331
|
changeSet.indexes = newIndexes;
|
|
305
332
|
|
|
@@ -524,10 +551,16 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
524
551
|
? this.ref.constructor
|
|
525
552
|
: this.ref[$childType];
|
|
526
553
|
|
|
527
|
-
|
|
528
|
-
|
|
554
|
+
let parentChangeTree: ChangeTree;
|
|
555
|
+
|
|
556
|
+
let parentIsCollection = !Metadata.isValidInstance(parent);
|
|
557
|
+
if (parentIsCollection) {
|
|
558
|
+
parentChangeTree = parent[$changes];
|
|
529
559
|
parent = parentChangeTree.parent;
|
|
530
560
|
parentIndex = parentChangeTree.parentIndex;
|
|
561
|
+
|
|
562
|
+
} else {
|
|
563
|
+
parentChangeTree = parent[$changes]
|
|
531
564
|
}
|
|
532
565
|
|
|
533
566
|
const parentConstructor = parent.constructor as typeof Schema;
|
|
@@ -538,15 +571,25 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
538
571
|
}
|
|
539
572
|
key += `-${parentIndex}`;
|
|
540
573
|
|
|
574
|
+
const fieldHasViewTag = parentConstructor?.[Symbol.metadata]?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
575
|
+
|
|
541
576
|
this.isFiltered = parent[$changes].isFiltered // in case parent is already filtered
|
|
542
577
|
|| this.root.types.parentFiltered[key]
|
|
543
|
-
||
|
|
578
|
+
|| fieldHasViewTag;
|
|
544
579
|
|
|
545
580
|
//
|
|
546
581
|
// "isFiltered" may not be imedialely available during `change()` due to the instance not being attached to the root yet.
|
|
547
582
|
// when it's available, we need to enqueue the "changes" changeset into the "filteredChanges" changeset.
|
|
548
583
|
//
|
|
549
584
|
if (this.isFiltered) {
|
|
585
|
+
|
|
586
|
+
this.isVisibilitySharedWithParent = (
|
|
587
|
+
parentChangeTree.isFiltered &&
|
|
588
|
+
typeof (refType) !== "string" &&
|
|
589
|
+
!fieldHasViewTag &&
|
|
590
|
+
parentIsCollection
|
|
591
|
+
);
|
|
592
|
+
|
|
550
593
|
if (!this.filteredChanges) {
|
|
551
594
|
this.filteredChanges = createChangeSet();
|
|
552
595
|
this.allFilteredChanges = createChangeSet();
|
|
@@ -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,
|
package/src/encoder/Encoder.ts
CHANGED
|
@@ -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:
|
|
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 {
|
|
@@ -61,20 +61,19 @@ export class Encoder<T extends Schema = any> {
|
|
|
61
61
|
if (!changeTree) { continue; }
|
|
62
62
|
|
|
63
63
|
if (hasView) {
|
|
64
|
-
if (!view.
|
|
64
|
+
if (!view.isChangeTreeVisible(changeTree)) {
|
|
65
|
+
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
|
|
65
66
|
view.invisible.add(changeTree);
|
|
66
67
|
continue; // skip this change tree
|
|
67
|
-
|
|
68
|
-
} else {
|
|
69
|
-
view.invisible.delete(changeTree); // remove from invisible list
|
|
70
68
|
}
|
|
69
|
+
view.invisible.delete(changeTree); // remove from invisible list
|
|
71
70
|
}
|
|
72
71
|
|
|
73
|
-
const
|
|
72
|
+
const changeSet = changeTree[changeSetName];
|
|
74
73
|
const ref = changeTree.ref;
|
|
75
74
|
|
|
76
75
|
// TODO: avoid iterating over change tree if no changes were made
|
|
77
|
-
const numChanges =
|
|
76
|
+
const numChanges = changeSet.operations.length;
|
|
78
77
|
if (numChanges === 0) { continue; }
|
|
79
78
|
|
|
80
79
|
const ctor = ref.constructor;
|
|
@@ -90,7 +89,7 @@ export class Encoder<T extends Schema = any> {
|
|
|
90
89
|
}
|
|
91
90
|
|
|
92
91
|
for (let j = 0; j < numChanges; j++) {
|
|
93
|
-
const fieldIndex =
|
|
92
|
+
const fieldIndex = changeSet.operations[j];
|
|
94
93
|
|
|
95
94
|
const operation = (fieldIndex < 0)
|
|
96
95
|
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
package/src/encoder/StateView.ts
CHANGED
|
@@ -291,4 +291,28 @@ export class StateView {
|
|
|
291
291
|
// clear items array
|
|
292
292
|
this.items.length = 0;
|
|
293
293
|
}
|
|
294
|
+
|
|
295
|
+
isChangeTreeVisible(changeTree: ChangeTree) {
|
|
296
|
+
let isVisible = this.visible.has(changeTree);
|
|
297
|
+
|
|
298
|
+
//
|
|
299
|
+
// TODO: avoid checking for parent visibility, most of the time it's not needed
|
|
300
|
+
// See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'
|
|
301
|
+
//
|
|
302
|
+
if (!isVisible && changeTree.isVisibilitySharedWithParent){
|
|
303
|
+
|
|
304
|
+
// console.log("CHECK AGAINST PARENT...", {
|
|
305
|
+
// ref: changeTree.ref.constructor.name,
|
|
306
|
+
// refId: changeTree.refId,
|
|
307
|
+
// parent: changeTree.parent.constructor.name,
|
|
308
|
+
// });
|
|
309
|
+
|
|
310
|
+
if (this.visible.has(changeTree.parent[$changes])) {
|
|
311
|
+
this.visible.add(changeTree);
|
|
312
|
+
isVisible = true;
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
return isVisible;
|
|
317
|
+
}
|
|
294
318
|
}
|
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";
|
|
@@ -41,8 +41,7 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
|
|
|
41
41
|
return (
|
|
42
42
|
!view ||
|
|
43
43
|
typeof (ref[$childType]) === "string" ||
|
|
44
|
-
|
|
45
|
-
view.visible.has(ref['tmpItems'][index]?.[$changes])
|
|
44
|
+
view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes])
|
|
46
45
|
);
|
|
47
46
|
}
|
|
48
47
|
|
|
@@ -229,9 +228,6 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
|
|
|
229
228
|
|
|
230
229
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
231
230
|
|
|
232
|
-
// this.tmpItems[index] = undefined;
|
|
233
|
-
// this.tmpItems.pop();
|
|
234
|
-
|
|
235
231
|
this.deletedIndexes[index] = true;
|
|
236
232
|
|
|
237
233
|
return this.items.pop();
|
|
@@ -412,38 +408,63 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
|
|
|
412
408
|
*/
|
|
413
409
|
splice(
|
|
414
410
|
start: number,
|
|
415
|
-
deleteCount
|
|
411
|
+
deleteCount?: number,
|
|
416
412
|
...insertItems: V[]
|
|
417
413
|
): V[] {
|
|
418
414
|
const changeTree = this[$changes];
|
|
419
415
|
|
|
416
|
+
const itemsLength = this.items.length;
|
|
420
417
|
const tmpItemsLength = this.tmpItems.length;
|
|
421
418
|
const insertCount = insertItems.length;
|
|
422
419
|
|
|
423
420
|
// build up-to-date list of indexes, excluding removed values.
|
|
424
421
|
const indexes: number[] = [];
|
|
425
422
|
for (let i = 0; i < tmpItemsLength; i++) {
|
|
426
|
-
// if (this.tmpItems[i] !== undefined) {
|
|
427
423
|
if (this.deletedIndexes[i] !== true) {
|
|
428
424
|
indexes.push(i);
|
|
429
425
|
}
|
|
430
426
|
}
|
|
431
427
|
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
428
|
+
if (itemsLength > start) {
|
|
429
|
+
// if deleteCount is not provided, delete all items from start to end
|
|
430
|
+
if (deleteCount === undefined) {
|
|
431
|
+
deleteCount = itemsLength - start;
|
|
432
|
+
}
|
|
433
|
+
|
|
434
|
+
//
|
|
435
|
+
// delete operations at correct index
|
|
436
|
+
//
|
|
437
|
+
for (let i = start; i < start + deleteCount; i++) {
|
|
438
|
+
const index = indexes[i];
|
|
439
|
+
changeTree.delete(index, OPERATION.DELETE);
|
|
440
|
+
this.deletedIndexes[index] = true;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
} else {
|
|
444
|
+
// not enough items to delete
|
|
445
|
+
deleteCount = 0;
|
|
438
446
|
}
|
|
439
447
|
|
|
440
|
-
//
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
448
|
+
// insert operations
|
|
449
|
+
if (insertCount > 0) {
|
|
450
|
+
if (insertCount > deleteCount) {
|
|
451
|
+
console.error("Inserting more elements than deleting during ArraySchema#splice()");
|
|
452
|
+
throw new Error("ArraySchema#splice(): insertCount must be equal or lower than deleteCount.");
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
for (let i = 0; i < insertCount; i++) {
|
|
456
|
+
const addIndex = (indexes[start] ?? itemsLength) + i;
|
|
457
|
+
|
|
458
|
+
changeTree.indexedOperation(
|
|
459
|
+
addIndex,
|
|
460
|
+
(this.deletedIndexes[addIndex])
|
|
461
|
+
? OPERATION.DELETE_AND_ADD
|
|
462
|
+
: OPERATION.ADD
|
|
463
|
+
);
|
|
444
464
|
|
|
445
|
-
|
|
446
|
-
|
|
465
|
+
// set value's parent/root
|
|
466
|
+
insertItems[i][$changes]?.setParent(this, changeTree.root, addIndex);
|
|
467
|
+
}
|
|
447
468
|
}
|
|
448
469
|
|
|
449
470
|
//
|
|
@@ -452,6 +473,17 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
|
|
|
452
473
|
//
|
|
453
474
|
if (deleteCount > insertCount) {
|
|
454
475
|
changeTree.shiftAllChangeIndexes(-(deleteCount - insertCount), indexes[start + insertCount]);
|
|
476
|
+
// debugChangeSet("AFTER SHIFT indexes", changeTree.allChanges);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
//
|
|
480
|
+
// FIXME: this code block is duplicated on ChangeTree
|
|
481
|
+
//
|
|
482
|
+
if (changeTree.filteredChanges !== undefined) {
|
|
483
|
+
enqueueChangeTree(changeTree.root, changeTree, 'filteredChanges');
|
|
484
|
+
|
|
485
|
+
} else {
|
|
486
|
+
enqueueChangeTree(changeTree.root, changeTree, 'changes');
|
|
455
487
|
}
|
|
456
488
|
|
|
457
489
|
return this.items.splice(start, deleteCount, ...insertItems);
|
|
@@ -767,10 +799,6 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
|
|
|
767
799
|
: this.deletedIndexes[index]
|
|
768
800
|
? this.items[index]
|
|
769
801
|
: this.tmpItems[index] || this.items[index];
|
|
770
|
-
|
|
771
|
-
// return (isEncodeAll)
|
|
772
|
-
// ? this.items[index]
|
|
773
|
-
// : this.tmpItems[index] ?? this.items[index];
|
|
774
802
|
}
|
|
775
803
|
|
|
776
804
|
protected [$deleteByIndex](index: number) {
|
|
@@ -33,7 +33,7 @@ export class CollectionSchema<V=any> implements Collection<K, V>{
|
|
|
33
33
|
return (
|
|
34
34
|
!view ||
|
|
35
35
|
typeof (ref[$childType]) === "string" ||
|
|
36
|
-
view.
|
|
36
|
+
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes])
|
|
37
37
|
);
|
|
38
38
|
}
|
|
39
39
|
|
|
@@ -34,7 +34,7 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
|
|
|
34
34
|
return (
|
|
35
35
|
!view ||
|
|
36
36
|
typeof (ref[$childType]) === "string" ||
|
|
37
|
-
view.
|
|
37
|
+
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes])
|
|
38
38
|
);
|
|
39
39
|
}
|
|
40
40
|
|