@colyseus/schema 3.0.76 → 4.0.1
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 +781 -434
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +779 -435
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +781 -434
- package/lib/Metadata.js +1 -5
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +50 -17
- package/lib/Reflection.js +151 -202
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +13 -1
- package/lib/Schema.js +73 -9
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +6 -1
- package/lib/annotations.js +8 -34
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +34 -1
- package/lib/bench_encode.js.map +1 -1
- package/lib/codegen/api.js +35 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/cli.js +4 -1
- package/lib/codegen/cli.js.map +1 -1
- package/lib/codegen/parser.js +35 -2
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +34 -1
- package/lib/codegen/types.js.map +1 -1
- package/lib/decoder/DecodeOperation.d.ts +2 -2
- package/lib/decoder/DecodeOperation.js +3 -3
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +3 -3
- package/lib/decoder/Decoder.js +2 -2
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/ReferenceTracker.d.ts +0 -1
- package/lib/decoder/ReferenceTracker.js +9 -7
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/decoder/strategy/Callbacks.d.ts +154 -0
- package/lib/decoder/strategy/Callbacks.js +340 -0
- package/lib/decoder/strategy/Callbacks.js.map +1 -0
- package/lib/decoder/strategy/{StateCallbacks.d.ts → getDecoderStateCallbacks.d.ts} +6 -0
- package/lib/decoder/strategy/{StateCallbacks.js → getDecoderStateCallbacks.js} +17 -10
- package/lib/decoder/strategy/getDecoderStateCallbacks.js.map +1 -0
- package/lib/encoder/ChangeTree.d.ts +2 -2
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +2 -2
- package/lib/encoder/EncodeOperation.js +3 -3
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +6 -6
- package/lib/encoder/Encoder.js +19 -18
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.js +17 -14
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.js +13 -12
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/decode.d.ts +2 -2
- package/lib/encoding/encode.d.ts +3 -1
- package/lib/encoding/encode.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +7 -3
- package/lib/index.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +7 -14
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/custom/ArraySchema.d.ts +2 -1
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.d.ts +2 -1
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -2
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.d.ts +2 -1
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/symbols.d.ts +1 -0
- package/lib/types/symbols.js +2 -1
- package/lib/types/symbols.js.map +1 -1
- package/lib/utils.js +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +12 -16
- package/src/Metadata.ts +1 -5
- package/src/Reflection.ts +185 -174
- package/src/Schema.ts +81 -13
- package/src/annotations.ts +14 -40
- package/src/codegen/parser.ts +1 -1
- package/src/decoder/DecodeOperation.ts +9 -9
- package/src/decoder/Decoder.ts +6 -6
- package/src/decoder/ReferenceTracker.ts +10 -8
- package/src/decoder/strategy/Callbacks.ts +547 -0
- package/src/decoder/strategy/{StateCallbacks.ts → getDecoderStateCallbacks.ts} +17 -11
- package/src/encoder/ChangeTree.ts +4 -7
- package/src/encoder/EncodeOperation.ts +9 -9
- package/src/encoder/Encoder.ts +47 -22
- package/src/encoder/Root.ts +20 -15
- package/src/encoder/StateView.ts +15 -13
- package/src/encoding/encode.ts +1 -1
- package/src/index.ts +3 -2
- package/src/types/HelperTypes.ts +13 -11
- package/src/types/custom/ArraySchema.ts +2 -1
- package/src/types/custom/CollectionSchema.ts +4 -2
- package/src/types/custom/MapSchema.ts +4 -2
- package/src/types/custom/SetSchema.ts +3 -1
- package/src/types/symbols.ts +1 -0
- package/src/utils.ts +2 -2
- package/lib/Decoder.d.ts +0 -16
- package/lib/Decoder.js +0 -182
- package/lib/Decoder.js.map +0 -1
- package/lib/Encoder.d.ts +0 -13
- package/lib/Encoder.js +0 -79
- package/lib/Encoder.js.map +0 -1
- package/lib/changes/ChangeSet.d.ts +0 -12
- package/lib/changes/ChangeSet.js +0 -35
- package/lib/changes/ChangeSet.js.map +0 -1
- package/lib/changes/ChangeTree.d.ts +0 -53
- package/lib/changes/ChangeTree.js +0 -202
- package/lib/changes/ChangeTree.js.map +0 -1
- package/lib/changes/DecodeOperation.d.ts +0 -15
- package/lib/changes/DecodeOperation.js +0 -186
- package/lib/changes/DecodeOperation.js.map +0 -1
- package/lib/changes/EncodeOperation.d.ts +0 -18
- package/lib/changes/EncodeOperation.js +0 -130
- package/lib/changes/EncodeOperation.js.map +0 -1
- package/lib/changes/ReferenceTracker.d.ts +0 -14
- package/lib/changes/ReferenceTracker.js +0 -83
- package/lib/changes/ReferenceTracker.js.map +0 -1
- package/lib/changes/consts.d.ts +0 -14
- package/lib/changes/consts.js +0 -18
- package/lib/changes/consts.js.map +0 -1
- package/lib/decoder/strategy/StateCallbacks.js.map +0 -1
- package/lib/decoding/decode.d.ts +0 -48
- package/lib/decoding/decode.js +0 -267
- package/lib/decoding/decode.js.map +0 -1
- package/lib/ecs.d.ts +0 -11
- package/lib/ecs.js +0 -160
- package/lib/ecs.js.map +0 -1
- package/lib/filters/index.d.ts +0 -8
- package/lib/filters/index.js +0 -24
- package/lib/filters/index.js.map +0 -1
- package/lib/spec.d.ts +0 -13
- package/lib/spec.js +0 -42
- package/lib/spec.js.map +0 -1
- package/lib/types/ArraySchema.d.ts +0 -238
- package/lib/types/ArraySchema.js +0 -555
- package/lib/types/ArraySchema.js.map +0 -1
- package/lib/types/CollectionSchema.d.ts +0 -35
- package/lib/types/CollectionSchema.js +0 -150
- package/lib/types/CollectionSchema.js.map +0 -1
- package/lib/types/MapSchema.d.ts +0 -38
- package/lib/types/MapSchema.js +0 -215
- package/lib/types/MapSchema.js.map +0 -1
- package/lib/types/SetSchema.d.ts +0 -32
- package/lib/types/SetSchema.js +0 -162
- package/lib/types/SetSchema.js.map +0 -1
- package/lib/types/typeRegistry.d.ts +0 -5
- package/lib/types/typeRegistry.js +0 -13
- package/lib/types/typeRegistry.js.map +0 -1
- package/lib/usage.d.ts +0 -1
- package/lib/usage.js +0 -22
- package/lib/usage.js.map +0 -1
- package/lib/v3.d.ts +0 -1
- package/lib/v3.js +0 -427
- package/lib/v3.js.map +0 -1
- package/lib/v3_experiment.d.ts +0 -1
- package/lib/v3_experiment.js +0 -407
- package/lib/v3_experiment.js.map +0 -1
package/build/umd/index.js
CHANGED
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
|
|
33
33
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
34
34
|
|
|
35
|
+
const $refId = "~refId";
|
|
35
36
|
const $track = "~track";
|
|
36
37
|
const $encoder = "~encoder";
|
|
37
38
|
const $decoder = "~decoder";
|
|
@@ -562,12 +563,16 @@
|
|
|
562
563
|
}
|
|
563
564
|
|
|
564
565
|
class TypeContext {
|
|
566
|
+
types = {};
|
|
567
|
+
schemas = new Map();
|
|
568
|
+
hasFilters = false;
|
|
569
|
+
parentFiltered = {};
|
|
565
570
|
/**
|
|
566
571
|
* For inheritance support
|
|
567
572
|
* Keeps track of which classes extends which. (parent -> children)
|
|
568
573
|
*/
|
|
569
|
-
static
|
|
570
|
-
static
|
|
574
|
+
static inheritedTypes = new Map();
|
|
575
|
+
static cachedContexts = new Map();
|
|
571
576
|
static register(target) {
|
|
572
577
|
const parent = Object.getPrototypeOf(target);
|
|
573
578
|
if (parent !== Schema) {
|
|
@@ -588,10 +593,6 @@
|
|
|
588
593
|
return context;
|
|
589
594
|
}
|
|
590
595
|
constructor(rootClass) {
|
|
591
|
-
this.types = {};
|
|
592
|
-
this.schemas = new Map();
|
|
593
|
-
this.hasFilters = false;
|
|
594
|
-
this.parentFiltered = {};
|
|
595
596
|
if (rootClass) {
|
|
596
597
|
this.discoverTypes(rootClass);
|
|
597
598
|
}
|
|
@@ -932,11 +933,7 @@
|
|
|
932
933
|
});
|
|
933
934
|
}
|
|
934
935
|
}
|
|
935
|
-
|
|
936
|
-
value: metadata,
|
|
937
|
-
writable: false,
|
|
938
|
-
configurable: true
|
|
939
|
-
});
|
|
936
|
+
constructor[Symbol.metadata] = metadata;
|
|
940
937
|
return metadata;
|
|
941
938
|
},
|
|
942
939
|
isValidInstance(klass) {
|
|
@@ -988,25 +985,33 @@
|
|
|
988
985
|
delete changeSet.indexes[index];
|
|
989
986
|
}
|
|
990
987
|
class ChangeTree {
|
|
988
|
+
ref;
|
|
989
|
+
metadata;
|
|
990
|
+
root;
|
|
991
|
+
parentChain; // Linked list for tracking parents
|
|
992
|
+
/**
|
|
993
|
+
* Whether this structure is parent of a filtered structure.
|
|
994
|
+
*/
|
|
995
|
+
isFiltered = false;
|
|
996
|
+
isVisibilitySharedWithParent; // See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'
|
|
997
|
+
indexedOperations = {};
|
|
998
|
+
//
|
|
999
|
+
// TODO:
|
|
1000
|
+
// try storing the index + operation per item.
|
|
1001
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
1002
|
+
//
|
|
1003
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
1004
|
+
//
|
|
1005
|
+
changes = { indexes: {}, operations: [] };
|
|
1006
|
+
allChanges = { indexes: {}, operations: [] };
|
|
1007
|
+
filteredChanges;
|
|
1008
|
+
allFilteredChanges;
|
|
1009
|
+
indexes; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
|
|
1010
|
+
/**
|
|
1011
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
1012
|
+
*/
|
|
1013
|
+
isNew = true;
|
|
991
1014
|
constructor(ref) {
|
|
992
|
-
/**
|
|
993
|
-
* Whether this structure is parent of a filtered structure.
|
|
994
|
-
*/
|
|
995
|
-
this.isFiltered = false;
|
|
996
|
-
this.indexedOperations = {};
|
|
997
|
-
//
|
|
998
|
-
// TODO:
|
|
999
|
-
// try storing the index + operation per item.
|
|
1000
|
-
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
1001
|
-
//
|
|
1002
|
-
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
1003
|
-
//
|
|
1004
|
-
this.changes = { indexes: {}, operations: [] };
|
|
1005
|
-
this.allChanges = { indexes: {}, operations: [] };
|
|
1006
|
-
/**
|
|
1007
|
-
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
1008
|
-
*/
|
|
1009
|
-
this.isNew = true;
|
|
1010
1015
|
this.ref = ref;
|
|
1011
1016
|
this.metadata = ref.constructor[Symbol.metadata];
|
|
1012
1017
|
//
|
|
@@ -1478,7 +1483,7 @@
|
|
|
1478
1483
|
// Encode refId for this instance.
|
|
1479
1484
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1480
1485
|
//
|
|
1481
|
-
encode.number(bytes, value[$
|
|
1486
|
+
encode.number(bytes, value[$refId], it);
|
|
1482
1487
|
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
1483
1488
|
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
1484
1489
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
@@ -1489,7 +1494,7 @@
|
|
|
1489
1494
|
// Encode refId for this instance.
|
|
1490
1495
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1491
1496
|
//
|
|
1492
|
-
encode.number(bytes, value[$
|
|
1497
|
+
encode.number(bytes, value[$refId], it);
|
|
1493
1498
|
}
|
|
1494
1499
|
}
|
|
1495
1500
|
/**
|
|
@@ -1565,7 +1570,7 @@
|
|
|
1565
1570
|
if (!item) {
|
|
1566
1571
|
return;
|
|
1567
1572
|
}
|
|
1568
|
-
refOrIndex = item[$
|
|
1573
|
+
refOrIndex = item[$refId];
|
|
1569
1574
|
if (operation === exports.OPERATION.DELETE) {
|
|
1570
1575
|
operation = exports.OPERATION.DELETE_BY_REFID;
|
|
1571
1576
|
}
|
|
@@ -1605,7 +1610,7 @@
|
|
|
1605
1610
|
let value;
|
|
1606
1611
|
if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
1607
1612
|
// Flag `refId` for garbage collection.
|
|
1608
|
-
const previousRefId = $
|
|
1613
|
+
const previousRefId = previousValue?.[$refId];
|
|
1609
1614
|
if (previousRefId !== undefined) {
|
|
1610
1615
|
$root.removeRef(previousRefId);
|
|
1611
1616
|
}
|
|
@@ -1646,7 +1651,7 @@
|
|
|
1646
1651
|
value = valueRef.clone(true);
|
|
1647
1652
|
value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
|
|
1648
1653
|
if (previousValue) {
|
|
1649
|
-
let previousRefId = $
|
|
1654
|
+
let previousRefId = previousValue[$refId];
|
|
1650
1655
|
if (previousRefId !== undefined && refId !== previousRefId) {
|
|
1651
1656
|
//
|
|
1652
1657
|
// enqueue onRemove if structure has been replaced.
|
|
@@ -1657,7 +1662,7 @@
|
|
|
1657
1662
|
const [key, value] = iter.value;
|
|
1658
1663
|
// if value is a schema, remove its reference
|
|
1659
1664
|
if (typeof (value) === "object") {
|
|
1660
|
-
previousRefId = $
|
|
1665
|
+
previousRefId = value[$refId];
|
|
1661
1666
|
$root.removeRef(previousRefId);
|
|
1662
1667
|
}
|
|
1663
1668
|
allChanges.push({
|
|
@@ -1886,7 +1891,6 @@
|
|
|
1886
1891
|
}
|
|
1887
1892
|
}
|
|
1888
1893
|
|
|
1889
|
-
var _a$4, _b$4;
|
|
1890
1894
|
const DEFAULT_SORT = (a, b) => {
|
|
1891
1895
|
const A = a.toString();
|
|
1892
1896
|
const B = b.toString();
|
|
@@ -1898,8 +1902,15 @@
|
|
|
1898
1902
|
return 0;
|
|
1899
1903
|
};
|
|
1900
1904
|
class ArraySchema {
|
|
1901
|
-
|
|
1902
|
-
|
|
1905
|
+
[$changes];
|
|
1906
|
+
[$refId];
|
|
1907
|
+
[$childType];
|
|
1908
|
+
items = [];
|
|
1909
|
+
tmpItems = [];
|
|
1910
|
+
deletedIndexes = {};
|
|
1911
|
+
isMovingItems = false;
|
|
1912
|
+
static [$encoder] = encodeArray;
|
|
1913
|
+
static [$decoder] = decodeArray;
|
|
1903
1914
|
/**
|
|
1904
1915
|
* Determine if a property must be filtered.
|
|
1905
1916
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -1909,7 +1920,7 @@
|
|
|
1909
1920
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
1910
1921
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
1911
1922
|
*/
|
|
1912
|
-
static [
|
|
1923
|
+
static [$filter](ref, index, view) {
|
|
1913
1924
|
return (!view ||
|
|
1914
1925
|
typeof (ref[$childType]) === "string" ||
|
|
1915
1926
|
view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
|
|
@@ -1925,10 +1936,6 @@
|
|
|
1925
1936
|
return new ArraySchema(...Array.from(iterable));
|
|
1926
1937
|
}
|
|
1927
1938
|
constructor(...items) {
|
|
1928
|
-
this.items = [];
|
|
1929
|
-
this.tmpItems = [];
|
|
1930
|
-
this.deletedIndexes = {};
|
|
1931
|
-
this.isMovingItems = false;
|
|
1932
1939
|
Object.defineProperty(this, $childType, {
|
|
1933
1940
|
value: undefined,
|
|
1934
1941
|
enumerable: false,
|
|
@@ -2445,6 +2452,10 @@
|
|
|
2445
2452
|
static get [Symbol.species]() {
|
|
2446
2453
|
return ArraySchema;
|
|
2447
2454
|
}
|
|
2455
|
+
// WORKAROUND for compatibility
|
|
2456
|
+
// - TypeScript 4 defines @@unscopables as a function
|
|
2457
|
+
// - TypeScript 5 defines @@unscopables as an object
|
|
2458
|
+
[Symbol.unscopables];
|
|
2448
2459
|
/**
|
|
2449
2460
|
* Returns an iterable of key, value pairs for every entry in the array
|
|
2450
2461
|
*/
|
|
@@ -2554,7 +2565,7 @@
|
|
|
2554
2565
|
this.isMovingItems = false;
|
|
2555
2566
|
return this;
|
|
2556
2567
|
}
|
|
2557
|
-
[
|
|
2568
|
+
[$getByIndex](index, isEncodeAll = false) {
|
|
2558
2569
|
//
|
|
2559
2570
|
// TODO: avoid unecessary `this.tmpItems` check during decoding.
|
|
2560
2571
|
//
|
|
@@ -2609,10 +2620,16 @@
|
|
|
2609
2620
|
}
|
|
2610
2621
|
registerType("array", { constructor: ArraySchema });
|
|
2611
2622
|
|
|
2612
|
-
var _a$3, _b$3;
|
|
2613
2623
|
class MapSchema {
|
|
2614
|
-
|
|
2615
|
-
|
|
2624
|
+
[$changes];
|
|
2625
|
+
[$refId];
|
|
2626
|
+
childType;
|
|
2627
|
+
[$childType];
|
|
2628
|
+
$items = new Map();
|
|
2629
|
+
$indexes = new Map();
|
|
2630
|
+
deletedItems = {};
|
|
2631
|
+
static [$encoder] = encodeKeyValueOperation;
|
|
2632
|
+
static [$decoder] = decodeKeyValueOperation;
|
|
2616
2633
|
/**
|
|
2617
2634
|
* Determine if a property must be filtered.
|
|
2618
2635
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2622,7 +2639,7 @@
|
|
|
2622
2639
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2623
2640
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2624
2641
|
*/
|
|
2625
|
-
static [
|
|
2642
|
+
static [$filter](ref, index, view) {
|
|
2626
2643
|
return (!view ||
|
|
2627
2644
|
typeof (ref[$childType]) === "string" ||
|
|
2628
2645
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2631,9 +2648,6 @@
|
|
|
2631
2648
|
return type['map'] !== undefined;
|
|
2632
2649
|
}
|
|
2633
2650
|
constructor(initialValues) {
|
|
2634
|
-
this.$items = new Map();
|
|
2635
|
-
this.$indexes = new Map();
|
|
2636
|
-
this.deletedItems = {};
|
|
2637
2651
|
const changeTree = new ChangeTree(this);
|
|
2638
2652
|
changeTree.indexes = {};
|
|
2639
2653
|
Object.defineProperty(this, $changes, {
|
|
@@ -2824,10 +2838,16 @@
|
|
|
2824
2838
|
}
|
|
2825
2839
|
registerType("map", { constructor: MapSchema });
|
|
2826
2840
|
|
|
2827
|
-
var _a$2, _b$2;
|
|
2828
2841
|
class CollectionSchema {
|
|
2829
|
-
|
|
2830
|
-
|
|
2842
|
+
[$changes];
|
|
2843
|
+
[$refId];
|
|
2844
|
+
[$childType];
|
|
2845
|
+
$items = new Map();
|
|
2846
|
+
$indexes = new Map();
|
|
2847
|
+
deletedItems = {};
|
|
2848
|
+
$refId = 0;
|
|
2849
|
+
static [$encoder] = encodeKeyValueOperation;
|
|
2850
|
+
static [$decoder] = decodeKeyValueOperation;
|
|
2831
2851
|
/**
|
|
2832
2852
|
* Determine if a property must be filtered.
|
|
2833
2853
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2837,7 +2857,7 @@
|
|
|
2837
2857
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2838
2858
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2839
2859
|
*/
|
|
2840
|
-
static [
|
|
2860
|
+
static [$filter](ref, index, view) {
|
|
2841
2861
|
return (!view ||
|
|
2842
2862
|
typeof (ref[$childType]) === "string" ||
|
|
2843
2863
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2846,10 +2866,6 @@
|
|
|
2846
2866
|
return type['collection'] !== undefined;
|
|
2847
2867
|
}
|
|
2848
2868
|
constructor(initialValues) {
|
|
2849
|
-
this.$items = new Map();
|
|
2850
|
-
this.$indexes = new Map();
|
|
2851
|
-
this.deletedItems = {};
|
|
2852
|
-
this.$refId = 0;
|
|
2853
2869
|
this[$changes] = new ChangeTree(this);
|
|
2854
2870
|
this[$changes].indexes = {};
|
|
2855
2871
|
if (initialValues) {
|
|
@@ -2988,10 +3004,16 @@
|
|
|
2988
3004
|
}
|
|
2989
3005
|
registerType("collection", { constructor: CollectionSchema, });
|
|
2990
3006
|
|
|
2991
|
-
var _a$1, _b$1;
|
|
2992
3007
|
class SetSchema {
|
|
2993
|
-
|
|
2994
|
-
|
|
3008
|
+
[$changes];
|
|
3009
|
+
[$refId];
|
|
3010
|
+
[$childType];
|
|
3011
|
+
$items = new Map();
|
|
3012
|
+
$indexes = new Map();
|
|
3013
|
+
deletedItems = {};
|
|
3014
|
+
$refId = 0;
|
|
3015
|
+
static [$encoder] = encodeKeyValueOperation;
|
|
3016
|
+
static [$decoder] = decodeKeyValueOperation;
|
|
2995
3017
|
/**
|
|
2996
3018
|
* Determine if a property must be filtered.
|
|
2997
3019
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -3001,7 +3023,7 @@
|
|
|
3001
3023
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
3002
3024
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
3003
3025
|
*/
|
|
3004
|
-
static [
|
|
3026
|
+
static [$filter](ref, index, view) {
|
|
3005
3027
|
return (!view ||
|
|
3006
3028
|
typeof (ref[$childType]) === "string" ||
|
|
3007
3029
|
view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -3010,10 +3032,6 @@
|
|
|
3010
3032
|
return type['set'] !== undefined;
|
|
3011
3033
|
}
|
|
3012
3034
|
constructor(initialValues) {
|
|
3013
|
-
this.$items = new Map();
|
|
3014
|
-
this.$indexes = new Map();
|
|
3015
|
-
this.deletedItems = {};
|
|
3016
|
-
this.$refId = 0;
|
|
3017
3035
|
this[$changes] = new ChangeTree(this);
|
|
3018
3036
|
this[$changes].indexes = {};
|
|
3019
3037
|
if (initialValues) {
|
|
@@ -3512,7 +3530,10 @@
|
|
|
3512
3530
|
? DEFAULT_VIEW_TAG
|
|
3513
3531
|
: value['view'];
|
|
3514
3532
|
}
|
|
3515
|
-
|
|
3533
|
+
// allow to define a field as not synced
|
|
3534
|
+
if (value['sync'] !== false) {
|
|
3535
|
+
fields[fieldName] = getNormalizedType(value);
|
|
3536
|
+
}
|
|
3516
3537
|
// If no explicit default provided, handle automatic instantiation for collection types
|
|
3517
3538
|
if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
|
|
3518
3539
|
// TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
|
|
@@ -3534,12 +3555,7 @@
|
|
|
3534
3555
|
}
|
|
3535
3556
|
else if (value['type'] !== undefined && Schema.is(value['type'])) {
|
|
3536
3557
|
// Direct Schema type: Type → new Type()
|
|
3537
|
-
|
|
3538
|
-
// only auto-initialize Schema instances if:
|
|
3539
|
-
// - they don't have an initialize method
|
|
3540
|
-
// - or initialize method doesn't accept any parameters
|
|
3541
|
-
defaultValues[fieldName] = new value['type']();
|
|
3542
|
-
}
|
|
3558
|
+
defaultValues[fieldName] = new value['type']();
|
|
3543
3559
|
}
|
|
3544
3560
|
}
|
|
3545
3561
|
else {
|
|
@@ -3549,12 +3565,7 @@
|
|
|
3549
3565
|
else if (typeof (value) === "function") {
|
|
3550
3566
|
if (Schema.is(value)) {
|
|
3551
3567
|
// Direct Schema type: Type → new Type()
|
|
3552
|
-
|
|
3553
|
-
// only auto-initialize Schema instances if:
|
|
3554
|
-
// - they don't have an initialize method
|
|
3555
|
-
// - or initialize method doesn't accept any parameters
|
|
3556
|
-
defaultValues[fieldName] = new value();
|
|
3557
|
-
}
|
|
3568
|
+
defaultValues[fieldName] = new value();
|
|
3558
3569
|
fields[fieldName] = getNormalizedType(value);
|
|
3559
3570
|
}
|
|
3560
3571
|
else {
|
|
@@ -3581,32 +3592,13 @@
|
|
|
3581
3592
|
}
|
|
3582
3593
|
return defaults;
|
|
3583
3594
|
};
|
|
3584
|
-
const getParentProps = (props) => {
|
|
3585
|
-
const fieldNames = Object.keys(fields);
|
|
3586
|
-
const parentProps = {};
|
|
3587
|
-
for (const key in props) {
|
|
3588
|
-
if (!fieldNames.includes(key)) {
|
|
3589
|
-
parentProps[key] = props[key];
|
|
3590
|
-
}
|
|
3591
|
-
}
|
|
3592
|
-
return parentProps;
|
|
3593
|
-
};
|
|
3594
3595
|
/** @codegen-ignore */
|
|
3595
3596
|
const klass = Metadata.setFields(class extends inherits {
|
|
3596
3597
|
constructor(...args) {
|
|
3598
|
+
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3597
3599
|
// call initialize method
|
|
3598
3600
|
if (methods.initialize && typeof methods.initialize === 'function') {
|
|
3599
|
-
|
|
3600
|
-
/**
|
|
3601
|
-
* only call initialize() in the current class, not the parent ones.
|
|
3602
|
-
* see "should not call initialize automatically when creating an instance of inherited Schema"
|
|
3603
|
-
*/
|
|
3604
|
-
if (new.target === klass) {
|
|
3605
|
-
methods.initialize.apply(this, args);
|
|
3606
|
-
}
|
|
3607
|
-
}
|
|
3608
|
-
else {
|
|
3609
|
-
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3601
|
+
methods.initialize.apply(this, args);
|
|
3610
3602
|
}
|
|
3611
3603
|
}
|
|
3612
3604
|
}, fields);
|
|
@@ -3643,7 +3635,7 @@
|
|
|
3643
3635
|
continue;
|
|
3644
3636
|
}
|
|
3645
3637
|
const changes = changeTree.indexedOperations;
|
|
3646
|
-
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3638
|
+
dump.refs.push(`refId#${changeTree.ref[$refId]}`);
|
|
3647
3639
|
for (const index in changes) {
|
|
3648
3640
|
const op = changes[index];
|
|
3649
3641
|
const opName = exports.OPERATION[op];
|
|
@@ -3657,13 +3649,14 @@
|
|
|
3657
3649
|
return dump;
|
|
3658
3650
|
}
|
|
3659
3651
|
|
|
3660
|
-
var _a, _b;
|
|
3661
3652
|
/**
|
|
3662
3653
|
* Schema encoder / decoder
|
|
3663
3654
|
*/
|
|
3664
3655
|
class Schema {
|
|
3665
|
-
static
|
|
3666
|
-
static
|
|
3656
|
+
static [Symbol.metadata];
|
|
3657
|
+
static [$encoder] = encodeSchemaOperation;
|
|
3658
|
+
static [$decoder] = decodeSchemaOperation;
|
|
3659
|
+
[$refId];
|
|
3667
3660
|
/**
|
|
3668
3661
|
* Assign the property descriptors required to track changes on this instance.
|
|
3669
3662
|
* @param instance
|
|
@@ -3682,7 +3675,7 @@
|
|
|
3682
3675
|
/**
|
|
3683
3676
|
* Track property changes
|
|
3684
3677
|
*/
|
|
3685
|
-
static [
|
|
3678
|
+
static [$track](changeTree, index, operation = exports.OPERATION.ADD) {
|
|
3686
3679
|
changeTree.change(index, operation);
|
|
3687
3680
|
}
|
|
3688
3681
|
/**
|
|
@@ -3729,10 +3722,74 @@
|
|
|
3729
3722
|
Object.assign(this, arg);
|
|
3730
3723
|
}
|
|
3731
3724
|
}
|
|
3725
|
+
/**
|
|
3726
|
+
* Assign properties to the instance.
|
|
3727
|
+
* @param props Properties to assign to the instance
|
|
3728
|
+
* @returns
|
|
3729
|
+
*/
|
|
3732
3730
|
assign(props) {
|
|
3733
3731
|
Object.assign(this, props);
|
|
3734
3732
|
return this;
|
|
3735
3733
|
}
|
|
3734
|
+
/**
|
|
3735
|
+
* Restore the instance from JSON data.
|
|
3736
|
+
* @param jsonData JSON data to restore the instance from
|
|
3737
|
+
* @returns
|
|
3738
|
+
*/
|
|
3739
|
+
restore(jsonData) {
|
|
3740
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3741
|
+
for (const fieldIndex in metadata) {
|
|
3742
|
+
const field = metadata[fieldIndex];
|
|
3743
|
+
const fieldName = field.name;
|
|
3744
|
+
const fieldType = field.type;
|
|
3745
|
+
const value = jsonData[fieldName];
|
|
3746
|
+
if (value === undefined || value === null) {
|
|
3747
|
+
continue;
|
|
3748
|
+
}
|
|
3749
|
+
if (typeof fieldType === "string") {
|
|
3750
|
+
// Primitive type: assign directly
|
|
3751
|
+
this[fieldName] = value;
|
|
3752
|
+
}
|
|
3753
|
+
else if (Schema.is(fieldType)) {
|
|
3754
|
+
// Schema type: create instance and restore
|
|
3755
|
+
const instance = new fieldType();
|
|
3756
|
+
instance.restore(value);
|
|
3757
|
+
this[fieldName] = instance;
|
|
3758
|
+
}
|
|
3759
|
+
else if (typeof fieldType === "object") {
|
|
3760
|
+
// Collection types: { map: ... }, { array: ... }, etc.
|
|
3761
|
+
const collectionType = Object.keys(fieldType)[0];
|
|
3762
|
+
const childType = fieldType[collectionType];
|
|
3763
|
+
if (collectionType === "map") {
|
|
3764
|
+
const mapSchema = this[fieldName];
|
|
3765
|
+
for (const key in value) {
|
|
3766
|
+
if (Schema.is(childType)) {
|
|
3767
|
+
const childInstance = new childType();
|
|
3768
|
+
childInstance.restore(value[key]);
|
|
3769
|
+
mapSchema.set(key, childInstance);
|
|
3770
|
+
}
|
|
3771
|
+
else {
|
|
3772
|
+
mapSchema.set(key, value[key]);
|
|
3773
|
+
}
|
|
3774
|
+
}
|
|
3775
|
+
}
|
|
3776
|
+
else if (collectionType === "array") {
|
|
3777
|
+
const arraySchema = this[fieldName];
|
|
3778
|
+
for (let i = 0; i < value.length; i++) {
|
|
3779
|
+
if (Schema.is(childType)) {
|
|
3780
|
+
const childInstance = new childType();
|
|
3781
|
+
childInstance.restore(value[i]);
|
|
3782
|
+
arraySchema.push(childInstance);
|
|
3783
|
+
}
|
|
3784
|
+
else {
|
|
3785
|
+
arraySchema.push(value[i]);
|
|
3786
|
+
}
|
|
3787
|
+
}
|
|
3788
|
+
}
|
|
3789
|
+
}
|
|
3790
|
+
}
|
|
3791
|
+
return this;
|
|
3792
|
+
}
|
|
3736
3793
|
/**
|
|
3737
3794
|
* (Server-side): Flag a property to be encoded for the next patch.
|
|
3738
3795
|
* @param instance Schema instance
|
|
@@ -3805,7 +3862,7 @@
|
|
|
3805
3862
|
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3806
3863
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3807
3864
|
const changeTree = ref[$changes];
|
|
3808
|
-
const refId =
|
|
3865
|
+
const refId = ref[$refId];
|
|
3809
3866
|
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3810
3867
|
// log reference count if > 1
|
|
3811
3868
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
@@ -3828,7 +3885,7 @@
|
|
|
3828
3885
|
let current = ref[$changes].root[changeSet].next;
|
|
3829
3886
|
while (current) {
|
|
3830
3887
|
if (current.changeTree) {
|
|
3831
|
-
encodeOrder.push(current.changeTree.refId);
|
|
3888
|
+
encodeOrder.push(current.changeTree.ref[$refId]);
|
|
3832
3889
|
}
|
|
3833
3890
|
current = current.next;
|
|
3834
3891
|
}
|
|
@@ -3849,7 +3906,7 @@
|
|
|
3849
3906
|
const changeTree = instance[$changes];
|
|
3850
3907
|
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3851
3908
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3852
|
-
let output = `${instance.constructor.name} (${
|
|
3909
|
+
let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
|
|
3853
3910
|
function dumpChangeSet(changeSet) {
|
|
3854
3911
|
changeSet.operations
|
|
3855
3912
|
.filter(op => op)
|
|
@@ -3863,14 +3920,14 @@
|
|
|
3863
3920
|
if (!isEncodeAll &&
|
|
3864
3921
|
changeTree.filteredChanges &&
|
|
3865
3922
|
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3866
|
-
output += `${instance.constructor.name} (${
|
|
3923
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
|
|
3867
3924
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3868
3925
|
}
|
|
3869
3926
|
// display filtered changes
|
|
3870
3927
|
if (isEncodeAll &&
|
|
3871
3928
|
changeTree.allFilteredChanges &&
|
|
3872
3929
|
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3873
|
-
output += `${instance.constructor.name} (${
|
|
3930
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
|
|
3874
3931
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3875
3932
|
}
|
|
3876
3933
|
return output;
|
|
@@ -3905,13 +3962,13 @@
|
|
|
3905
3962
|
}
|
|
3906
3963
|
}
|
|
3907
3964
|
if (includeChangeTree) {
|
|
3908
|
-
instanceRefIds.push(changeTree.refId);
|
|
3965
|
+
instanceRefIds.push(changeTree.ref[$refId]);
|
|
3909
3966
|
totalOperations += Object.keys(changes).length;
|
|
3910
3967
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3911
3968
|
}
|
|
3912
3969
|
}
|
|
3913
3970
|
output += "---\n";
|
|
3914
|
-
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3971
|
+
output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
|
|
3915
3972
|
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3916
3973
|
output += `Total changes: ${totalOperations}\n`;
|
|
3917
3974
|
output += "---\n";
|
|
@@ -3920,7 +3977,7 @@
|
|
|
3920
3977
|
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3921
3978
|
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3922
3979
|
if (!visitedParents.has(parentChangeTree)) {
|
|
3923
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3980
|
+
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
|
|
3924
3981
|
visitedParents.add(parentChangeTree);
|
|
3925
3982
|
}
|
|
3926
3983
|
});
|
|
@@ -3928,7 +3985,7 @@
|
|
|
3928
3985
|
const level = parentChangeTrees.length;
|
|
3929
3986
|
const indent = getIndent(level);
|
|
3930
3987
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3931
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3988
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.ref[$refId]}) - changes: ${Object.keys(changes).length}\n`;
|
|
3932
3989
|
for (const index in changes) {
|
|
3933
3990
|
const operation = changes[index];
|
|
3934
3991
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
@@ -3938,61 +3995,35 @@
|
|
|
3938
3995
|
}
|
|
3939
3996
|
}
|
|
3940
3997
|
|
|
3941
|
-
/******************************************************************************
|
|
3942
|
-
Copyright (c) Microsoft Corporation.
|
|
3943
|
-
|
|
3944
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
3945
|
-
purpose with or without fee is hereby granted.
|
|
3946
|
-
|
|
3947
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
3948
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
3949
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
3950
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
3951
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
3952
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3953
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
3954
|
-
***************************************************************************** */
|
|
3955
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
3956
|
-
|
|
3957
|
-
|
|
3958
|
-
function __decorate(decorators, target, key, desc) {
|
|
3959
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3960
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3961
|
-
else for (var i = decorators.length - 1; i >= 0; i--) if (d = decorators[i]) r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
|
|
3962
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3963
|
-
}
|
|
3964
|
-
|
|
3965
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
3966
|
-
var e = new Error(message);
|
|
3967
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3968
|
-
};
|
|
3969
|
-
|
|
3970
3998
|
class Root {
|
|
3999
|
+
types;
|
|
4000
|
+
nextUniqueId = 0;
|
|
4001
|
+
refCount = {};
|
|
4002
|
+
changeTrees = {};
|
|
4003
|
+
// all changes
|
|
4004
|
+
allChanges = createChangeTreeList();
|
|
4005
|
+
allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
4006
|
+
// pending changes to be encoded
|
|
4007
|
+
changes = createChangeTreeList();
|
|
4008
|
+
filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3971
4009
|
constructor(types) {
|
|
3972
4010
|
this.types = types;
|
|
3973
|
-
this.nextUniqueId = 0;
|
|
3974
|
-
this.refCount = {};
|
|
3975
|
-
this.changeTrees = {};
|
|
3976
|
-
// all changes
|
|
3977
|
-
this.allChanges = createChangeTreeList();
|
|
3978
|
-
this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3979
|
-
// pending changes to be encoded
|
|
3980
|
-
this.changes = createChangeTreeList();
|
|
3981
|
-
this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3982
4011
|
}
|
|
3983
4012
|
getNextUniqueId() {
|
|
3984
4013
|
return this.nextUniqueId++;
|
|
3985
4014
|
}
|
|
3986
4015
|
add(changeTree) {
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
|
|
4016
|
+
const ref = changeTree.ref;
|
|
4017
|
+
// Assign unique `refId` to ref if it doesn't have one yet.
|
|
4018
|
+
if (ref[$refId] === undefined) {
|
|
4019
|
+
ref[$refId] = this.getNextUniqueId();
|
|
3990
4020
|
}
|
|
3991
|
-
const
|
|
4021
|
+
const refId = ref[$refId];
|
|
4022
|
+
const isNewChangeTree = (this.changeTrees[refId] === undefined);
|
|
3992
4023
|
if (isNewChangeTree) {
|
|
3993
|
-
this.changeTrees[
|
|
4024
|
+
this.changeTrees[refId] = changeTree;
|
|
3994
4025
|
}
|
|
3995
|
-
const previousRefCount = this.refCount[
|
|
4026
|
+
const previousRefCount = this.refCount[refId];
|
|
3996
4027
|
if (previousRefCount === 0) {
|
|
3997
4028
|
//
|
|
3998
4029
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
@@ -4005,30 +4036,31 @@
|
|
|
4005
4036
|
setOperationAtIndex(changeTree.changes, len);
|
|
4006
4037
|
}
|
|
4007
4038
|
}
|
|
4008
|
-
this.refCount[
|
|
4009
|
-
// console.log("ADD", { refId
|
|
4039
|
+
this.refCount[refId] = (previousRefCount || 0) + 1;
|
|
4040
|
+
// console.log("ADD", { refId, ref: ref.constructor.name, refCount: this.refCount[refId], isNewChangeTree });
|
|
4010
4041
|
return isNewChangeTree;
|
|
4011
4042
|
}
|
|
4012
4043
|
remove(changeTree) {
|
|
4013
|
-
const
|
|
4014
|
-
|
|
4044
|
+
const refId = changeTree.ref[$refId];
|
|
4045
|
+
const refCount = (this.refCount[refId]) - 1;
|
|
4046
|
+
// console.log("REMOVE", { refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
|
|
4015
4047
|
if (refCount <= 0) {
|
|
4016
4048
|
//
|
|
4017
4049
|
// Only remove "root" reference if it's the last reference
|
|
4018
4050
|
//
|
|
4019
4051
|
changeTree.root = undefined;
|
|
4020
|
-
delete this.changeTrees[
|
|
4052
|
+
delete this.changeTrees[refId];
|
|
4021
4053
|
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
4022
4054
|
this.removeChangeFromChangeSet("changes", changeTree);
|
|
4023
4055
|
if (changeTree.filteredChanges) {
|
|
4024
4056
|
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
4025
4057
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
4026
4058
|
}
|
|
4027
|
-
this.refCount[
|
|
4059
|
+
this.refCount[refId] = 0;
|
|
4028
4060
|
changeTree.forEachChild((child, _) => {
|
|
4029
4061
|
if (child.removeParent(changeTree.ref)) {
|
|
4030
4062
|
if ((child.parentChain === undefined || // no parent, remove it
|
|
4031
|
-
(child.parentChain && this.refCount[child.refId] > 0) // parent is still in use, but has more than one reference, remove it
|
|
4063
|
+
(child.parentChain && this.refCount[child.ref[$refId]] > 0) // parent is still in use, but has more than one reference, remove it
|
|
4032
4064
|
)) {
|
|
4033
4065
|
this.remove(child);
|
|
4034
4066
|
}
|
|
@@ -4040,7 +4072,7 @@
|
|
|
4040
4072
|
});
|
|
4041
4073
|
}
|
|
4042
4074
|
else {
|
|
4043
|
-
this.refCount[
|
|
4075
|
+
this.refCount[refId] = refCount;
|
|
4044
4076
|
//
|
|
4045
4077
|
// When losing a reference to an instance, it is best to move the
|
|
4046
4078
|
// ChangeTree next to its parent in the encoding queue.
|
|
@@ -4190,10 +4222,19 @@
|
|
|
4190
4222
|
}
|
|
4191
4223
|
}
|
|
4192
4224
|
|
|
4225
|
+
function concatBytes(a, b) {
|
|
4226
|
+
const result = new Uint8Array(a.length + b.length);
|
|
4227
|
+
result.set(a, 0);
|
|
4228
|
+
result.set(b, a.length);
|
|
4229
|
+
return result;
|
|
4230
|
+
}
|
|
4193
4231
|
class Encoder {
|
|
4194
|
-
static
|
|
4232
|
+
static BUFFER_SIZE = 8 * 1024; // 8KB
|
|
4233
|
+
sharedBuffer = new Uint8Array(Encoder.BUFFER_SIZE);
|
|
4234
|
+
context;
|
|
4235
|
+
state;
|
|
4236
|
+
root;
|
|
4195
4237
|
constructor(state) {
|
|
4196
|
-
this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
|
|
4197
4238
|
//
|
|
4198
4239
|
// Use .cache() here to avoid re-creating a new context for every new room instance.
|
|
4199
4240
|
//
|
|
@@ -4221,7 +4262,7 @@
|
|
|
4221
4262
|
const changeTree = current.changeTree;
|
|
4222
4263
|
if (hasView) {
|
|
4223
4264
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
4224
|
-
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
|
|
4265
|
+
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.ref[$refId], raw: changeTree.ref.toJSON() });
|
|
4225
4266
|
view.invisible.add(changeTree);
|
|
4226
4267
|
continue; // skip this change tree
|
|
4227
4268
|
}
|
|
@@ -4242,7 +4283,7 @@
|
|
|
4242
4283
|
// (unless it "hasView", which will need to revisit the root)
|
|
4243
4284
|
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
4244
4285
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4245
|
-
encode.number(buffer,
|
|
4286
|
+
encode.number(buffer, ref[$refId], it);
|
|
4246
4287
|
}
|
|
4247
4288
|
for (let j = 0; j < numChanges; j++) {
|
|
4248
4289
|
const fieldIndex = changeSet.operations[j];
|
|
@@ -4271,10 +4312,9 @@
|
|
|
4271
4312
|
}
|
|
4272
4313
|
}
|
|
4273
4314
|
if (it.offset > buffer.byteLength) {
|
|
4274
|
-
// we can assume that n + 1
|
|
4275
|
-
// multiples of
|
|
4276
|
-
|
|
4277
|
-
const newSize = Math.ceil(it.offset / (Buffer.poolSize ?? 8 * 1024)) * (Buffer.poolSize ?? 8 * 1024);
|
|
4315
|
+
// we can assume that n + 1 BUFFER_SIZE will suffice given that we are likely done with encoding at this point
|
|
4316
|
+
// multiples of BUFFER_SIZE are faster to allocate than arbitrary sizes
|
|
4317
|
+
const newSize = Math.ceil(it.offset / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
|
|
4278
4318
|
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
4279
4319
|
|
|
4280
4320
|
import { Encoder } from "@colyseus/schema";
|
|
@@ -4284,7 +4324,9 @@
|
|
|
4284
4324
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
4285
4325
|
// -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
|
|
4286
4326
|
//
|
|
4287
|
-
|
|
4327
|
+
const newBuffer = new Uint8Array(newSize);
|
|
4328
|
+
newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
|
|
4329
|
+
buffer = newBuffer;
|
|
4288
4330
|
// assign resized buffer to local sharedBuffer
|
|
4289
4331
|
if (buffer === this.sharedBuffer) {
|
|
4290
4332
|
this.sharedBuffer = buffer;
|
|
@@ -4302,10 +4344,7 @@
|
|
|
4302
4344
|
const viewOffset = it.offset;
|
|
4303
4345
|
// try to encode "filtered" changes
|
|
4304
4346
|
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4305
|
-
return
|
|
4306
|
-
bytes.subarray(0, sharedOffset),
|
|
4307
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4308
|
-
]);
|
|
4347
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4309
4348
|
}
|
|
4310
4349
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4311
4350
|
const viewOffset = it.offset;
|
|
@@ -4329,7 +4368,7 @@
|
|
|
4329
4368
|
const encoder = ctor[$encoder];
|
|
4330
4369
|
const metadata = ctor[Symbol.metadata];
|
|
4331
4370
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4332
|
-
encode.number(bytes,
|
|
4371
|
+
encode.number(bytes, ref[$refId], it);
|
|
4333
4372
|
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
4334
4373
|
const index = Number(keys[i]);
|
|
4335
4374
|
// workaround when using view.add() on item that has been deleted from state (see test "adding to view item that has been removed from state")
|
|
@@ -4348,10 +4387,7 @@
|
|
|
4348
4387
|
view.changes.clear();
|
|
4349
4388
|
// try to encode "filtered" changes
|
|
4350
4389
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4351
|
-
return
|
|
4352
|
-
bytes.subarray(0, sharedOffset),
|
|
4353
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4354
|
-
]);
|
|
4390
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4355
4391
|
}
|
|
4356
4392
|
discardChanges() {
|
|
4357
4393
|
// discard shared changes
|
|
@@ -4407,25 +4443,22 @@
|
|
|
4407
4443
|
}
|
|
4408
4444
|
}
|
|
4409
4445
|
class ReferenceTracker {
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
|
|
4416
|
-
|
|
4417
|
-
|
|
4418
|
-
|
|
4419
|
-
this.callbacks = {};
|
|
4420
|
-
this.nextUniqueId = 0;
|
|
4421
|
-
}
|
|
4446
|
+
//
|
|
4447
|
+
// Relation of refId => Schema structure
|
|
4448
|
+
// For direct access of structures during decoding time.
|
|
4449
|
+
//
|
|
4450
|
+
refs = new Map();
|
|
4451
|
+
refCount = {};
|
|
4452
|
+
deletedRefs = new Set();
|
|
4453
|
+
callbacks = {};
|
|
4454
|
+
nextUniqueId = 0;
|
|
4422
4455
|
getNextUniqueId() {
|
|
4423
4456
|
return this.nextUniqueId++;
|
|
4424
4457
|
}
|
|
4425
4458
|
// for decoding
|
|
4426
4459
|
addRef(refId, ref, incrementCount = true) {
|
|
4427
4460
|
this.refs.set(refId, ref);
|
|
4428
|
-
|
|
4461
|
+
ref[$refId] = refId;
|
|
4429
4462
|
if (incrementCount) {
|
|
4430
4463
|
this.refCount[refId] = (this.refCount[refId] || 0) + 1;
|
|
4431
4464
|
}
|
|
@@ -4482,9 +4515,12 @@
|
|
|
4482
4515
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4483
4516
|
for (const index in metadata) {
|
|
4484
4517
|
const field = metadata[index].name;
|
|
4485
|
-
const
|
|
4486
|
-
if (
|
|
4487
|
-
|
|
4518
|
+
const child = ref[field];
|
|
4519
|
+
if (typeof (child) === "object" && child) {
|
|
4520
|
+
const childRefId = child[$refId];
|
|
4521
|
+
if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
|
|
4522
|
+
this.removeRef(childRefId);
|
|
4523
|
+
}
|
|
4488
4524
|
}
|
|
4489
4525
|
}
|
|
4490
4526
|
}
|
|
@@ -4492,8 +4528,8 @@
|
|
|
4492
4528
|
if (typeof (ref[$childType]) === "function") {
|
|
4493
4529
|
Array.from(ref.values())
|
|
4494
4530
|
.forEach((child) => {
|
|
4495
|
-
const childRefId =
|
|
4496
|
-
if (!this.deletedRefs.has(childRefId)) {
|
|
4531
|
+
const childRefId = child[$refId];
|
|
4532
|
+
if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
|
|
4497
4533
|
this.removeRef(childRefId);
|
|
4498
4534
|
}
|
|
4499
4535
|
});
|
|
@@ -4531,8 +4567,12 @@
|
|
|
4531
4567
|
}
|
|
4532
4568
|
|
|
4533
4569
|
class Decoder {
|
|
4570
|
+
context;
|
|
4571
|
+
state;
|
|
4572
|
+
root;
|
|
4573
|
+
currentRefId = 0;
|
|
4574
|
+
triggerChanges;
|
|
4534
4575
|
constructor(root, context) {
|
|
4535
|
-
this.currentRefId = 0;
|
|
4536
4576
|
this.setState(root);
|
|
4537
4577
|
this.context = context || new TypeContext(root.constructor);
|
|
4538
4578
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
@@ -4621,7 +4661,7 @@
|
|
|
4621
4661
|
}
|
|
4622
4662
|
removeChildRefs(ref, allChanges) {
|
|
4623
4663
|
const needRemoveRef = typeof (ref[$childType]) !== "string";
|
|
4624
|
-
const refId =
|
|
4664
|
+
const refId = ref[$refId];
|
|
4625
4665
|
ref.forEach((value, key) => {
|
|
4626
4666
|
allChanges.push({
|
|
4627
4667
|
ref: ref,
|
|
@@ -4632,7 +4672,7 @@
|
|
|
4632
4672
|
previousValue: value
|
|
4633
4673
|
});
|
|
4634
4674
|
if (needRemoveRef) {
|
|
4635
|
-
this.root.removeRef(
|
|
4675
|
+
this.root.removeRef(value[$refId]);
|
|
4636
4676
|
}
|
|
4637
4677
|
});
|
|
4638
4678
|
}
|
|
@@ -4641,217 +4681,182 @@
|
|
|
4641
4681
|
/**
|
|
4642
4682
|
* Reflection
|
|
4643
4683
|
*/
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
|
|
4671
|
-
|
|
4672
|
-
|
|
4673
|
-
|
|
4674
|
-
|
|
4675
|
-
|
|
4676
|
-
|
|
4677
|
-
|
|
4678
|
-
|
|
4679
|
-
|
|
4680
|
-
* @returns
|
|
4681
|
-
*/
|
|
4682
|
-
static encode(encoder, it = { offset: 0 }) {
|
|
4683
|
-
const context = encoder.context;
|
|
4684
|
-
const reflection = new Reflection();
|
|
4685
|
-
const reflectionEncoder = new Encoder(reflection);
|
|
4686
|
-
// rootType is usually the first schema passed to the Encoder
|
|
4687
|
-
// (unless it inherits from another schema)
|
|
4688
|
-
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4689
|
-
if (rootType > 0) {
|
|
4690
|
-
reflection.rootType = rootType;
|
|
4691
|
-
}
|
|
4692
|
-
const includedTypeIds = new Set();
|
|
4693
|
-
const pendingReflectionTypes = {};
|
|
4694
|
-
// add type to reflection in a way that respects inheritance
|
|
4695
|
-
// (parent types should be added before their children)
|
|
4696
|
-
const addType = (type) => {
|
|
4697
|
-
if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
|
|
4698
|
-
includedTypeIds.add(type.id);
|
|
4699
|
-
reflection.types.push(type);
|
|
4700
|
-
const deps = pendingReflectionTypes[type.id];
|
|
4701
|
-
if (deps !== undefined) {
|
|
4702
|
-
delete pendingReflectionTypes[type.id];
|
|
4703
|
-
deps.forEach((childType) => addType(childType));
|
|
4704
|
-
}
|
|
4684
|
+
const ReflectionField = schema({
|
|
4685
|
+
name: "string",
|
|
4686
|
+
type: "string",
|
|
4687
|
+
referencedType: "number",
|
|
4688
|
+
});
|
|
4689
|
+
const ReflectionType = schema({
|
|
4690
|
+
id: "number",
|
|
4691
|
+
extendsId: "number",
|
|
4692
|
+
fields: [ReflectionField],
|
|
4693
|
+
});
|
|
4694
|
+
const Reflection = schema({
|
|
4695
|
+
types: [ReflectionType],
|
|
4696
|
+
rootType: "number",
|
|
4697
|
+
});
|
|
4698
|
+
Reflection.encode = function (encoder, it = { offset: 0 }) {
|
|
4699
|
+
const context = encoder.context;
|
|
4700
|
+
const reflection = new Reflection();
|
|
4701
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
4702
|
+
// rootType is usually the first schema passed to the Encoder
|
|
4703
|
+
// (unless it inherits from another schema)
|
|
4704
|
+
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4705
|
+
if (rootType > 0) {
|
|
4706
|
+
reflection.rootType = rootType;
|
|
4707
|
+
}
|
|
4708
|
+
const includedTypeIds = new Set();
|
|
4709
|
+
const pendingReflectionTypes = {};
|
|
4710
|
+
// add type to reflection in a way that respects inheritance
|
|
4711
|
+
// (parent types should be added before their children)
|
|
4712
|
+
const addType = (type) => {
|
|
4713
|
+
if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
|
|
4714
|
+
includedTypeIds.add(type.id);
|
|
4715
|
+
reflection.types.push(type);
|
|
4716
|
+
const deps = pendingReflectionTypes[type.id];
|
|
4717
|
+
if (deps !== undefined) {
|
|
4718
|
+
delete pendingReflectionTypes[type.id];
|
|
4719
|
+
deps.forEach((childType) => addType(childType));
|
|
4705
4720
|
}
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
pendingReflectionTypes[type.extendsId].push(type);
|
|
4711
|
-
}
|
|
4712
|
-
};
|
|
4713
|
-
context.schemas.forEach((typeid, klass) => {
|
|
4714
|
-
const type = new ReflectionType();
|
|
4715
|
-
type.id = Number(typeid);
|
|
4716
|
-
// support inheritance
|
|
4717
|
-
const inheritFrom = Object.getPrototypeOf(klass);
|
|
4718
|
-
if (inheritFrom !== Schema) {
|
|
4719
|
-
type.extendsId = context.schemas.get(inheritFrom);
|
|
4721
|
+
}
|
|
4722
|
+
else {
|
|
4723
|
+
if (pendingReflectionTypes[type.extendsId] === undefined) {
|
|
4724
|
+
pendingReflectionTypes[type.extendsId] = [];
|
|
4720
4725
|
}
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
|
|
4738
|
-
|
|
4739
|
-
|
|
4726
|
+
pendingReflectionTypes[type.extendsId].push(type);
|
|
4727
|
+
}
|
|
4728
|
+
};
|
|
4729
|
+
context.schemas.forEach((typeid, klass) => {
|
|
4730
|
+
const type = new ReflectionType();
|
|
4731
|
+
type.id = Number(typeid);
|
|
4732
|
+
// support inheritance
|
|
4733
|
+
const inheritFrom = Object.getPrototypeOf(klass);
|
|
4734
|
+
if (inheritFrom !== Schema) {
|
|
4735
|
+
type.extendsId = context.schemas.get(inheritFrom);
|
|
4736
|
+
}
|
|
4737
|
+
const metadata = klass[Symbol.metadata];
|
|
4738
|
+
//
|
|
4739
|
+
// FIXME: this is a workaround for inherited types without additional fields
|
|
4740
|
+
// if metadata is the same reference as the parent class - it means the class has no own metadata
|
|
4741
|
+
//
|
|
4742
|
+
if (metadata !== inheritFrom[Symbol.metadata]) {
|
|
4743
|
+
for (const fieldIndex in metadata) {
|
|
4744
|
+
const index = Number(fieldIndex);
|
|
4745
|
+
const fieldName = metadata[index].name;
|
|
4746
|
+
// skip fields from parent classes
|
|
4747
|
+
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
4748
|
+
continue;
|
|
4749
|
+
}
|
|
4750
|
+
const reflectionField = new ReflectionField();
|
|
4751
|
+
reflectionField.name = fieldName;
|
|
4752
|
+
let fieldType;
|
|
4753
|
+
const field = metadata[index];
|
|
4754
|
+
if (typeof (field.type) === "string") {
|
|
4755
|
+
fieldType = field.type;
|
|
4756
|
+
}
|
|
4757
|
+
else {
|
|
4758
|
+
let childTypeSchema;
|
|
4759
|
+
//
|
|
4760
|
+
// TODO: refactor below.
|
|
4761
|
+
//
|
|
4762
|
+
if (Schema.is(field.type)) {
|
|
4763
|
+
fieldType = "ref";
|
|
4764
|
+
childTypeSchema = field.type;
|
|
4740
4765
|
}
|
|
4741
4766
|
else {
|
|
4742
|
-
|
|
4743
|
-
|
|
4744
|
-
|
|
4745
|
-
//
|
|
4746
|
-
if (Schema.is(field.type)) {
|
|
4747
|
-
fieldType = "ref";
|
|
4748
|
-
childTypeSchema = field.type;
|
|
4767
|
+
fieldType = Object.keys(field.type)[0];
|
|
4768
|
+
if (typeof (field.type[fieldType]) === "string") {
|
|
4769
|
+
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4749
4770
|
}
|
|
4750
4771
|
else {
|
|
4751
|
-
|
|
4752
|
-
if (typeof (field.type[fieldType]) === "string") {
|
|
4753
|
-
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4754
|
-
}
|
|
4755
|
-
else {
|
|
4756
|
-
childTypeSchema = field.type[fieldType];
|
|
4757
|
-
}
|
|
4772
|
+
childTypeSchema = field.type[fieldType];
|
|
4758
4773
|
}
|
|
4759
|
-
reflectionField.referencedType = (childTypeSchema)
|
|
4760
|
-
? context.getTypeId(childTypeSchema)
|
|
4761
|
-
: -1;
|
|
4762
4774
|
}
|
|
4763
|
-
reflectionField.
|
|
4764
|
-
|
|
4775
|
+
reflectionField.referencedType = (childTypeSchema)
|
|
4776
|
+
? context.getTypeId(childTypeSchema)
|
|
4777
|
+
: -1;
|
|
4765
4778
|
}
|
|
4779
|
+
reflectionField.type = fieldType;
|
|
4780
|
+
type.fields.push(reflectionField);
|
|
4766
4781
|
}
|
|
4767
|
-
addType(type);
|
|
4768
|
-
});
|
|
4769
|
-
// in case there are types that were not added due to inheritance
|
|
4770
|
-
for (const typeid in pendingReflectionTypes) {
|
|
4771
|
-
pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
|
|
4772
4782
|
}
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4783
|
+
addType(type);
|
|
4784
|
+
});
|
|
4785
|
+
// in case there are types that were not added due to inheritance
|
|
4786
|
+
for (const typeid in pendingReflectionTypes) {
|
|
4787
|
+
pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
|
|
4776
4788
|
}
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
const
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
|
|
4806
|
-
|
|
4807
|
-
|
|
4808
|
-
|
|
4809
|
-
const typeInfo = field.type.split(":");
|
|
4810
|
-
fieldType = typeInfo[0];
|
|
4811
|
-
refType = typeInfo[1]; // string
|
|
4812
|
-
}
|
|
4813
|
-
if (fieldType === "ref") {
|
|
4814
|
-
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
4815
|
-
}
|
|
4816
|
-
else {
|
|
4817
|
-
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4818
|
-
}
|
|
4789
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
4790
|
+
return buf.slice(0, it.offset);
|
|
4791
|
+
};
|
|
4792
|
+
Reflection.decode = function (bytes, it) {
|
|
4793
|
+
const reflection = new Reflection();
|
|
4794
|
+
const reflectionDecoder = new Decoder(reflection);
|
|
4795
|
+
reflectionDecoder.decode(bytes, it);
|
|
4796
|
+
const typeContext = new TypeContext();
|
|
4797
|
+
// 1st pass, initialize metadata + inheritance
|
|
4798
|
+
reflection.types.forEach((reflectionType) => {
|
|
4799
|
+
const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
4800
|
+
const schema = class _ extends parentClass {
|
|
4801
|
+
};
|
|
4802
|
+
// register for inheritance support
|
|
4803
|
+
TypeContext.register(schema);
|
|
4804
|
+
typeContext.add(schema, reflectionType.id);
|
|
4805
|
+
}, {});
|
|
4806
|
+
// define fields
|
|
4807
|
+
const addFields = (metadata, reflectionType, parentFieldIndex) => {
|
|
4808
|
+
reflectionType.fields.forEach((field, i) => {
|
|
4809
|
+
const fieldIndex = parentFieldIndex + i;
|
|
4810
|
+
if (field.referencedType !== undefined) {
|
|
4811
|
+
let fieldType = field.type;
|
|
4812
|
+
let refType = typeContext.get(field.referencedType);
|
|
4813
|
+
// map or array of primitive type (-1)
|
|
4814
|
+
if (!refType) {
|
|
4815
|
+
const typeInfo = field.type.split(":");
|
|
4816
|
+
fieldType = typeInfo[0];
|
|
4817
|
+
refType = typeInfo[1]; // string
|
|
4818
|
+
}
|
|
4819
|
+
if (fieldType === "ref") {
|
|
4820
|
+
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
4819
4821
|
}
|
|
4820
4822
|
else {
|
|
4821
|
-
Metadata.addField(metadata, fieldIndex, field.name,
|
|
4823
|
+
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4822
4824
|
}
|
|
4823
|
-
}
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4827
|
-
const schema = typeContext.get(reflectionType.id);
|
|
4828
|
-
// for inheritance support
|
|
4829
|
-
const metadata = Metadata.initialize(schema);
|
|
4830
|
-
const inheritedTypes = [];
|
|
4831
|
-
let parentType = reflectionType;
|
|
4832
|
-
do {
|
|
4833
|
-
inheritedTypes.push(parentType);
|
|
4834
|
-
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4835
|
-
} while (parentType);
|
|
4836
|
-
let parentFieldIndex = 0;
|
|
4837
|
-
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4838
|
-
// add fields from all inherited classes
|
|
4839
|
-
// TODO: refactor this to avoid adding fields from parent classes
|
|
4840
|
-
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4841
|
-
parentFieldIndex += reflectionType.fields.length;
|
|
4842
|
-
});
|
|
4825
|
+
}
|
|
4826
|
+
else {
|
|
4827
|
+
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
4828
|
+
}
|
|
4843
4829
|
});
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
4850
|
-
|
|
4851
|
-
|
|
4852
|
-
|
|
4853
|
-
|
|
4830
|
+
};
|
|
4831
|
+
// 2nd pass, set fields
|
|
4832
|
+
reflection.types.forEach((reflectionType) => {
|
|
4833
|
+
const schema = typeContext.get(reflectionType.id);
|
|
4834
|
+
// for inheritance support
|
|
4835
|
+
const metadata = Metadata.initialize(schema);
|
|
4836
|
+
const inheritedTypes = [];
|
|
4837
|
+
let parentType = reflectionType;
|
|
4838
|
+
do {
|
|
4839
|
+
inheritedTypes.push(parentType);
|
|
4840
|
+
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4841
|
+
} while (parentType);
|
|
4842
|
+
let parentFieldIndex = 0;
|
|
4843
|
+
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4844
|
+
// add fields from all inherited classes
|
|
4845
|
+
// TODO: refactor this to avoid adding fields from parent classes
|
|
4846
|
+
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4847
|
+
parentFieldIndex += reflectionType.fields.length;
|
|
4848
|
+
});
|
|
4849
|
+
});
|
|
4850
|
+
const state = new (typeContext.get(reflection.rootType || 0))();
|
|
4851
|
+
return new Decoder(state, typeContext);
|
|
4852
|
+
};
|
|
4854
4853
|
|
|
4854
|
+
/**
|
|
4855
|
+
* Legacy callback system
|
|
4856
|
+
*
|
|
4857
|
+
* @param decoder
|
|
4858
|
+
* @returns
|
|
4859
|
+
*/
|
|
4855
4860
|
function getDecoderStateCallbacks(decoder) {
|
|
4856
4861
|
const $root = decoder.root;
|
|
4857
4862
|
const callbacks = $root.callbacks;
|
|
@@ -4872,7 +4877,7 @@
|
|
|
4872
4877
|
//
|
|
4873
4878
|
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4874
4879
|
change.previousValue instanceof Schema) {
|
|
4875
|
-
const deleteCallbacks = callbacks[
|
|
4880
|
+
const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[exports.OPERATION.DELETE];
|
|
4876
4881
|
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4877
4882
|
deleteCallbacks[i]();
|
|
4878
4883
|
}
|
|
@@ -4961,7 +4966,7 @@
|
|
|
4961
4966
|
) {
|
|
4962
4967
|
callback(context.instance[prop], undefined);
|
|
4963
4968
|
}
|
|
4964
|
-
return $root.addCallback($
|
|
4969
|
+
return $root.addCallback(ref[$refId], prop, callback);
|
|
4965
4970
|
};
|
|
4966
4971
|
/**
|
|
4967
4972
|
* Schema instances
|
|
@@ -4981,7 +4986,7 @@
|
|
|
4981
4986
|
}
|
|
4982
4987
|
},
|
|
4983
4988
|
onChange: function onChange(callback) {
|
|
4984
|
-
return $root.addCallback(
|
|
4989
|
+
return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, callback);
|
|
4985
4990
|
},
|
|
4986
4991
|
//
|
|
4987
4992
|
// TODO: refactor `bindTo()` implementation.
|
|
@@ -4991,7 +4996,7 @@
|
|
|
4991
4996
|
if (!properties) {
|
|
4992
4997
|
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4993
4998
|
}
|
|
4994
|
-
return $root.addCallback(
|
|
4999
|
+
return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, () => {
|
|
4995
5000
|
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4996
5001
|
});
|
|
4997
5002
|
}
|
|
@@ -5010,13 +5015,13 @@
|
|
|
5010
5015
|
unbind?.();
|
|
5011
5016
|
}, false);
|
|
5012
5017
|
// has existing value
|
|
5013
|
-
if ($
|
|
5018
|
+
if (instance?.[$refId] !== undefined) {
|
|
5014
5019
|
callback(instance, true);
|
|
5015
5020
|
}
|
|
5016
5021
|
});
|
|
5017
5022
|
return getProxy(metadataField.type, {
|
|
5018
5023
|
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
5019
|
-
instance: ($
|
|
5024
|
+
instance: (instance?.[$refId] !== undefined && instance),
|
|
5020
5025
|
parentInstance: context.instance,
|
|
5021
5026
|
onInstanceAvailable,
|
|
5022
5027
|
});
|
|
@@ -5040,7 +5045,7 @@
|
|
|
5040
5045
|
if (immediate) {
|
|
5041
5046
|
ref.forEach((v, k) => callback(v, k));
|
|
5042
5047
|
}
|
|
5043
|
-
return $root.addCallback($
|
|
5048
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.ADD, (value, key) => {
|
|
5044
5049
|
onAddCalls.set(callback, true);
|
|
5045
5050
|
currentOnAddCallback = callback;
|
|
5046
5051
|
callback(value, key);
|
|
@@ -5049,10 +5054,10 @@
|
|
|
5049
5054
|
});
|
|
5050
5055
|
};
|
|
5051
5056
|
const onRemove = function (ref, callback) {
|
|
5052
|
-
return $root.addCallback($
|
|
5057
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.DELETE, callback);
|
|
5053
5058
|
};
|
|
5054
5059
|
const onChange = function (ref, callback) {
|
|
5055
|
-
return $root.addCallback($
|
|
5060
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.REPLACE, callback);
|
|
5056
5061
|
};
|
|
5057
5062
|
return new Proxy({
|
|
5058
5063
|
onAdd: function (callback, immediate = true) {
|
|
@@ -5121,22 +5126,360 @@
|
|
|
5121
5126
|
decoder.triggerChanges = callback;
|
|
5122
5127
|
}
|
|
5123
5128
|
|
|
5129
|
+
/**
|
|
5130
|
+
* State Callbacks handler
|
|
5131
|
+
*
|
|
5132
|
+
* Usage:
|
|
5133
|
+
* ```ts
|
|
5134
|
+
* const $ = Callbacks.get(decoder);
|
|
5135
|
+
*
|
|
5136
|
+
* // Listen to property changes
|
|
5137
|
+
* $.listen("currentTurn", (currentValue, previousValue) => { ... });
|
|
5138
|
+
*
|
|
5139
|
+
* // Listen to collection additions
|
|
5140
|
+
* $.onAdd("entities", (sessionId, entity) => {
|
|
5141
|
+
* // Nested property listening
|
|
5142
|
+
* $.listen(entity, "hp", (currentHp, previousHp) => { ... });
|
|
5143
|
+
* });
|
|
5144
|
+
*
|
|
5145
|
+
* // Listen to collection removals
|
|
5146
|
+
* $.onRemove("entities", (sessionId, entity) => { ... });
|
|
5147
|
+
*
|
|
5148
|
+
* // Listen to any property change on an instance
|
|
5149
|
+
* $.onChange(entity, () => { ... });
|
|
5150
|
+
*
|
|
5151
|
+
* // Bind properties to another object
|
|
5152
|
+
* $.bindTo(player, playerVisual);
|
|
5153
|
+
* ```
|
|
5154
|
+
*/
|
|
5155
|
+
class StateCallbackStrategy {
|
|
5156
|
+
decoder;
|
|
5157
|
+
uniqueRefIds = new Set();
|
|
5158
|
+
isTriggering = false;
|
|
5159
|
+
constructor(decoder) {
|
|
5160
|
+
this.decoder = decoder;
|
|
5161
|
+
this.decoder.triggerChanges = this.triggerChanges.bind(this);
|
|
5162
|
+
}
|
|
5163
|
+
get callbacks() {
|
|
5164
|
+
return this.decoder.root.callbacks;
|
|
5165
|
+
}
|
|
5166
|
+
get state() {
|
|
5167
|
+
return this.decoder.state;
|
|
5168
|
+
}
|
|
5169
|
+
addCallback(refId, operationOrProperty, handler) {
|
|
5170
|
+
const $root = this.decoder.root;
|
|
5171
|
+
return $root.addCallback(refId, operationOrProperty, handler);
|
|
5172
|
+
}
|
|
5173
|
+
addCallbackOrWaitCollectionAvailable(instance, propertyName, operation, handler, immediate = true) {
|
|
5174
|
+
let removeHandler = () => { };
|
|
5175
|
+
const removeOnAdd = () => removeHandler();
|
|
5176
|
+
const collection = instance[propertyName];
|
|
5177
|
+
// Collection not available yet. Listen for its availability before attaching the handler.
|
|
5178
|
+
if (collection === null || collection === undefined) {
|
|
5179
|
+
removeHandler = this.addCallback(instance[$refId], propertyName, (value, _) => {
|
|
5180
|
+
if (value !== null && value !== undefined) {
|
|
5181
|
+
removeHandler = this.addCallback(value[$refId], operation, handler);
|
|
5182
|
+
}
|
|
5183
|
+
});
|
|
5184
|
+
return removeOnAdd;
|
|
5185
|
+
}
|
|
5186
|
+
else {
|
|
5187
|
+
//
|
|
5188
|
+
// Call immediately if collection is already available, if it's an ADD operation.
|
|
5189
|
+
//
|
|
5190
|
+
immediate = immediate && this.isTriggering === false;
|
|
5191
|
+
if (operation === exports.OPERATION.ADD && immediate) {
|
|
5192
|
+
collection.forEach((value, key) => {
|
|
5193
|
+
handler(key, value);
|
|
5194
|
+
});
|
|
5195
|
+
}
|
|
5196
|
+
return this.addCallback(collection[$refId], operation, handler);
|
|
5197
|
+
}
|
|
5198
|
+
}
|
|
5199
|
+
listen(...args) {
|
|
5200
|
+
if (typeof args[0] === 'string') {
|
|
5201
|
+
// listen(property, handler, immediate?)
|
|
5202
|
+
return this.listenInstance(this.state, args[0], args[1], args[2]);
|
|
5203
|
+
}
|
|
5204
|
+
else {
|
|
5205
|
+
// listen(instance, property, handler, immediate?)
|
|
5206
|
+
return this.listenInstance(args[0], args[1], args[2], args[3]);
|
|
5207
|
+
}
|
|
5208
|
+
}
|
|
5209
|
+
listenInstance(instance, propertyName, handler, immediate = true) {
|
|
5210
|
+
immediate = immediate && this.isTriggering === false;
|
|
5211
|
+
//
|
|
5212
|
+
// Call handler immediately if property is already available.
|
|
5213
|
+
//
|
|
5214
|
+
const currentValue = instance[propertyName];
|
|
5215
|
+
if (immediate && currentValue !== null && currentValue !== undefined) {
|
|
5216
|
+
handler(currentValue, undefined);
|
|
5217
|
+
}
|
|
5218
|
+
return this.addCallback(instance[$refId], propertyName, handler);
|
|
5219
|
+
}
|
|
5220
|
+
onChange(...args) {
|
|
5221
|
+
if (args.length === 2 && typeof args[0] !== 'string') {
|
|
5222
|
+
// onChange(instance, handler) - instance change
|
|
5223
|
+
const instance = args[0];
|
|
5224
|
+
const handler = args[1];
|
|
5225
|
+
return this.addCallback(instance[$refId], exports.OPERATION.REPLACE, handler);
|
|
5226
|
+
}
|
|
5227
|
+
if (typeof args[0] === 'string') {
|
|
5228
|
+
// onChange(property, handler) - collection on root state
|
|
5229
|
+
return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.REPLACE, args[1]);
|
|
5230
|
+
}
|
|
5231
|
+
else {
|
|
5232
|
+
// onChange(instance, property, handler) - nested collection
|
|
5233
|
+
return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.REPLACE, args[2]);
|
|
5234
|
+
}
|
|
5235
|
+
}
|
|
5236
|
+
onAdd(...args) {
|
|
5237
|
+
if (typeof args[0] === 'string') {
|
|
5238
|
+
// onAdd(property, handler, immediate?) - collection on root state
|
|
5239
|
+
return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.ADD, args[1], args[2] !== false);
|
|
5240
|
+
}
|
|
5241
|
+
else {
|
|
5242
|
+
// onAdd(instance, property, handler, immediate?) - nested collection
|
|
5243
|
+
return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.ADD, args[2], args[3] !== false);
|
|
5244
|
+
}
|
|
5245
|
+
}
|
|
5246
|
+
onRemove(...args) {
|
|
5247
|
+
if (typeof args[0] === 'string') {
|
|
5248
|
+
// onRemove(property, handler) - collection on root state
|
|
5249
|
+
return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.DELETE, args[1]);
|
|
5250
|
+
}
|
|
5251
|
+
else {
|
|
5252
|
+
// onRemove(instance, property, handler) - nested collection
|
|
5253
|
+
return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.DELETE, args[2]);
|
|
5254
|
+
}
|
|
5255
|
+
}
|
|
5256
|
+
/**
|
|
5257
|
+
* Bind properties from a Schema instance to a target object.
|
|
5258
|
+
* Changes will be automatically reflected on the target object.
|
|
5259
|
+
*/
|
|
5260
|
+
bindTo(from, to, properties, immediate = true) {
|
|
5261
|
+
const metadata = from.constructor[Symbol.metadata];
|
|
5262
|
+
// If no properties specified, bind all properties
|
|
5263
|
+
if (!properties) {
|
|
5264
|
+
properties = Object.keys(metadata)
|
|
5265
|
+
.filter(key => !isNaN(Number(key)))
|
|
5266
|
+
.map((index) => metadata[index].name);
|
|
5267
|
+
}
|
|
5268
|
+
const action = () => {
|
|
5269
|
+
for (const prop of properties) {
|
|
5270
|
+
const fromValue = from[prop];
|
|
5271
|
+
if (fromValue !== undefined) {
|
|
5272
|
+
to[prop] = fromValue;
|
|
5273
|
+
}
|
|
5274
|
+
}
|
|
5275
|
+
};
|
|
5276
|
+
if (immediate) {
|
|
5277
|
+
action();
|
|
5278
|
+
}
|
|
5279
|
+
return this.addCallback(from[$refId], exports.OPERATION.REPLACE, action);
|
|
5280
|
+
}
|
|
5281
|
+
triggerChanges(allChanges) {
|
|
5282
|
+
this.uniqueRefIds.clear();
|
|
5283
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
5284
|
+
const change = allChanges[i];
|
|
5285
|
+
const refId = change.refId;
|
|
5286
|
+
const ref = change.ref;
|
|
5287
|
+
const $callbacks = this.callbacks[refId];
|
|
5288
|
+
if (!$callbacks) {
|
|
5289
|
+
continue;
|
|
5290
|
+
}
|
|
5291
|
+
//
|
|
5292
|
+
// trigger onRemove on child structure.
|
|
5293
|
+
//
|
|
5294
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
5295
|
+
change.previousValue instanceof Schema) {
|
|
5296
|
+
const childRefId = change.previousValue[$refId];
|
|
5297
|
+
const deleteCallbacks = this.callbacks[childRefId]?.[exports.OPERATION.DELETE];
|
|
5298
|
+
if (deleteCallbacks) {
|
|
5299
|
+
for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
|
|
5300
|
+
deleteCallbacks[j]();
|
|
5301
|
+
}
|
|
5302
|
+
}
|
|
5303
|
+
}
|
|
5304
|
+
if (ref instanceof Schema) {
|
|
5305
|
+
//
|
|
5306
|
+
// Handle Schema instance
|
|
5307
|
+
//
|
|
5308
|
+
if (!this.uniqueRefIds.has(refId)) {
|
|
5309
|
+
// trigger onChange
|
|
5310
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
5311
|
+
if (replaceCallbacks) {
|
|
5312
|
+
for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
|
|
5313
|
+
try {
|
|
5314
|
+
replaceCallbacks[j]();
|
|
5315
|
+
}
|
|
5316
|
+
catch (e) {
|
|
5317
|
+
console.error(e);
|
|
5318
|
+
}
|
|
5319
|
+
}
|
|
5320
|
+
}
|
|
5321
|
+
}
|
|
5322
|
+
// trigger field callbacks
|
|
5323
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
5324
|
+
if (fieldCallbacks) {
|
|
5325
|
+
for (let j = fieldCallbacks.length - 1; j >= 0; j--) {
|
|
5326
|
+
try {
|
|
5327
|
+
this.isTriggering = true;
|
|
5328
|
+
fieldCallbacks[j](change.value, change.previousValue);
|
|
5329
|
+
}
|
|
5330
|
+
catch (e) {
|
|
5331
|
+
console.error(e);
|
|
5332
|
+
}
|
|
5333
|
+
finally {
|
|
5334
|
+
this.isTriggering = false;
|
|
5335
|
+
}
|
|
5336
|
+
}
|
|
5337
|
+
}
|
|
5338
|
+
}
|
|
5339
|
+
else {
|
|
5340
|
+
//
|
|
5341
|
+
// Handle collection of items
|
|
5342
|
+
//
|
|
5343
|
+
const dynamicIndex = change.dynamicIndex ?? change.field;
|
|
5344
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
5345
|
+
//
|
|
5346
|
+
// FIXME: `previousValue` should always be available.
|
|
5347
|
+
//
|
|
5348
|
+
if (change.previousValue !== undefined) {
|
|
5349
|
+
// trigger onRemove (key, value)
|
|
5350
|
+
const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
|
|
5351
|
+
if (deleteCallbacks) {
|
|
5352
|
+
for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
|
|
5353
|
+
deleteCallbacks[j](dynamicIndex, change.previousValue);
|
|
5354
|
+
}
|
|
5355
|
+
}
|
|
5356
|
+
}
|
|
5357
|
+
// Handle DELETE_AND_ADD operation
|
|
5358
|
+
if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
5359
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
5360
|
+
if (addCallbacks) {
|
|
5361
|
+
this.isTriggering = true;
|
|
5362
|
+
for (let j = addCallbacks.length - 1; j >= 0; j--) {
|
|
5363
|
+
addCallbacks[j](dynamicIndex, change.value);
|
|
5364
|
+
}
|
|
5365
|
+
this.isTriggering = false;
|
|
5366
|
+
}
|
|
5367
|
+
}
|
|
5368
|
+
}
|
|
5369
|
+
else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD &&
|
|
5370
|
+
change.previousValue !== change.value) {
|
|
5371
|
+
// trigger onAdd (key, value)
|
|
5372
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
5373
|
+
if (addCallbacks) {
|
|
5374
|
+
this.isTriggering = true;
|
|
5375
|
+
for (let j = addCallbacks.length - 1; j >= 0; j--) {
|
|
5376
|
+
addCallbacks[j](dynamicIndex, change.value);
|
|
5377
|
+
}
|
|
5378
|
+
this.isTriggering = false;
|
|
5379
|
+
}
|
|
5380
|
+
}
|
|
5381
|
+
// trigger onChange (key, value)
|
|
5382
|
+
if (change.value !== change.previousValue) {
|
|
5383
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
5384
|
+
if (replaceCallbacks) {
|
|
5385
|
+
for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
|
|
5386
|
+
replaceCallbacks[j](dynamicIndex, change.value);
|
|
5387
|
+
}
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
this.uniqueRefIds.add(refId);
|
|
5392
|
+
}
|
|
5393
|
+
}
|
|
5394
|
+
}
|
|
5395
|
+
/**
|
|
5396
|
+
* Factory class for retrieving the callbacks API.
|
|
5397
|
+
*/
|
|
5398
|
+
const Callbacks = {
|
|
5399
|
+
/**
|
|
5400
|
+
* Get the new callbacks standard API.
|
|
5401
|
+
*
|
|
5402
|
+
* Usage:
|
|
5403
|
+
* ```ts
|
|
5404
|
+
* const callbacks = Callbacks.get(roomOrDecoder);
|
|
5405
|
+
*
|
|
5406
|
+
* // Listen to property changes
|
|
5407
|
+
* callbacks.listen("currentTurn", (currentValue, previousValue) => { ... });
|
|
5408
|
+
*
|
|
5409
|
+
* // Listen to collection additions
|
|
5410
|
+
* callbacks.onAdd("entities", (sessionId, entity) => {
|
|
5411
|
+
* // Nested property listening
|
|
5412
|
+
* callbacks.listen(entity, "hp", (currentHp, previousHp) => { ... });
|
|
5413
|
+
* });
|
|
5414
|
+
*
|
|
5415
|
+
* // Listen to collection removals
|
|
5416
|
+
* callbacks.onRemove("entities", (sessionId, entity) => { ... });
|
|
5417
|
+
*
|
|
5418
|
+
* // Listen to any property change on an instance
|
|
5419
|
+
* callbacks.onChange(entity, () => { ... });
|
|
5420
|
+
*
|
|
5421
|
+
* // Bind properties to another object
|
|
5422
|
+
* callbacks.bindTo(player, playerVisual);
|
|
5423
|
+
* ```
|
|
5424
|
+
*
|
|
5425
|
+
* @param roomOrDecoder - Room or Decoder instance to get the callbacks for.
|
|
5426
|
+
* @returns the new callbacks standard API.
|
|
5427
|
+
*/
|
|
5428
|
+
get(roomOrDecoder) {
|
|
5429
|
+
if (roomOrDecoder instanceof Decoder) {
|
|
5430
|
+
return new StateCallbackStrategy(roomOrDecoder);
|
|
5431
|
+
}
|
|
5432
|
+
else if (roomOrDecoder.serializer.decoder) {
|
|
5433
|
+
return new StateCallbackStrategy(roomOrDecoder.serializer.decoder);
|
|
5434
|
+
}
|
|
5435
|
+
else {
|
|
5436
|
+
throw new Error('Invalid room or decoder');
|
|
5437
|
+
}
|
|
5438
|
+
},
|
|
5439
|
+
/**
|
|
5440
|
+
* Get the legacy callbacks API.
|
|
5441
|
+
*
|
|
5442
|
+
* We aim to deprecate this API on 1.0, and iterate on improving Callbacks.get() API.
|
|
5443
|
+
*
|
|
5444
|
+
* @param roomOrDecoder - Room or Decoder instance to get the legacy callbacks for.
|
|
5445
|
+
* @returns the legacy callbacks API.
|
|
5446
|
+
*/
|
|
5447
|
+
getLegacy(roomOrDecoder) {
|
|
5448
|
+
if (roomOrDecoder instanceof Decoder) {
|
|
5449
|
+
return getDecoderStateCallbacks(roomOrDecoder);
|
|
5450
|
+
}
|
|
5451
|
+
else if (roomOrDecoder.serializer.decoder) {
|
|
5452
|
+
return getDecoderStateCallbacks(roomOrDecoder.serializer.decoder);
|
|
5453
|
+
}
|
|
5454
|
+
},
|
|
5455
|
+
getRawChanges(decoder, callback) {
|
|
5456
|
+
return getRawChangesCallback(decoder, callback);
|
|
5457
|
+
}
|
|
5458
|
+
};
|
|
5459
|
+
|
|
5124
5460
|
class StateView {
|
|
5461
|
+
iterable;
|
|
5462
|
+
/**
|
|
5463
|
+
* Iterable list of items that are visible to this view
|
|
5464
|
+
* (Available only if constructed with `iterable: true`)
|
|
5465
|
+
*/
|
|
5466
|
+
items;
|
|
5467
|
+
/**
|
|
5468
|
+
* List of ChangeTree's that are visible to this view
|
|
5469
|
+
*/
|
|
5470
|
+
visible = new WeakSet();
|
|
5471
|
+
/**
|
|
5472
|
+
* List of ChangeTree's that are invisible to this view
|
|
5473
|
+
*/
|
|
5474
|
+
invisible = new WeakSet();
|
|
5475
|
+
tags; // TODO: use bit manipulation instead of Set<number> ()
|
|
5476
|
+
/**
|
|
5477
|
+
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
5478
|
+
* (This is used to force encoding a property, even if it was not changed)
|
|
5479
|
+
*/
|
|
5480
|
+
changes = new Map();
|
|
5125
5481
|
constructor(iterable = false) {
|
|
5126
5482
|
this.iterable = iterable;
|
|
5127
|
-
/**
|
|
5128
|
-
* List of ChangeTree's that are visible to this view
|
|
5129
|
-
*/
|
|
5130
|
-
this.visible = new WeakSet();
|
|
5131
|
-
/**
|
|
5132
|
-
* List of ChangeTree's that are invisible to this view
|
|
5133
|
-
*/
|
|
5134
|
-
this.invisible = new WeakSet();
|
|
5135
|
-
/**
|
|
5136
|
-
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
5137
|
-
* (This is used to force encoding a property, even if it was not changed)
|
|
5138
|
-
*/
|
|
5139
|
-
this.changes = new Map();
|
|
5140
5483
|
if (iterable) {
|
|
5141
5484
|
this.items = [];
|
|
5142
5485
|
}
|
|
@@ -5150,7 +5493,7 @@
|
|
|
5150
5493
|
return false;
|
|
5151
5494
|
}
|
|
5152
5495
|
else if (!parentChangeTree &&
|
|
5153
|
-
|
|
5496
|
+
obj[$refId] !== 0 // allow root object
|
|
5154
5497
|
) {
|
|
5155
5498
|
/**
|
|
5156
5499
|
* TODO: can we avoid this?
|
|
@@ -5174,11 +5517,11 @@
|
|
|
5174
5517
|
if (checkIncludeParent && parentChangeTree) {
|
|
5175
5518
|
this.addParentOf(changeTree, tag);
|
|
5176
5519
|
}
|
|
5177
|
-
let changes = this.changes.get(
|
|
5520
|
+
let changes = this.changes.get(obj[$refId]);
|
|
5178
5521
|
if (changes === undefined) {
|
|
5179
5522
|
changes = {};
|
|
5180
5523
|
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5181
|
-
this.changes.set(
|
|
5524
|
+
this.changes.set(obj[$refId], changes);
|
|
5182
5525
|
}
|
|
5183
5526
|
let isChildAdded = false;
|
|
5184
5527
|
//
|
|
@@ -5258,10 +5601,10 @@
|
|
|
5258
5601
|
}
|
|
5259
5602
|
// add parent's tag properties
|
|
5260
5603
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
5261
|
-
let changes = this.changes.get(changeTree.refId);
|
|
5604
|
+
let changes = this.changes.get(changeTree.ref[$refId]);
|
|
5262
5605
|
if (changes === undefined) {
|
|
5263
5606
|
changes = {};
|
|
5264
|
-
this.changes.set(changeTree.refId, changes);
|
|
5607
|
+
this.changes.set(changeTree.ref[$refId], changes);
|
|
5265
5608
|
}
|
|
5266
5609
|
if (!this.tags) {
|
|
5267
5610
|
this.tags = new WeakMap();
|
|
@@ -5293,27 +5636,28 @@
|
|
|
5293
5636
|
}
|
|
5294
5637
|
const ref = changeTree.ref;
|
|
5295
5638
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5296
|
-
|
|
5639
|
+
const refId = ref[$refId];
|
|
5640
|
+
let changes = this.changes.get(refId);
|
|
5297
5641
|
if (changes === undefined) {
|
|
5298
5642
|
changes = {};
|
|
5299
|
-
this.changes.set(
|
|
5643
|
+
this.changes.set(refId, changes);
|
|
5300
5644
|
}
|
|
5301
5645
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5302
5646
|
// parent is collection (Map/Array)
|
|
5303
5647
|
const parent = changeTree.parent;
|
|
5304
5648
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5305
|
-
const
|
|
5306
|
-
let changes = this.changes.get(
|
|
5649
|
+
const parentRefId = parent[$refId];
|
|
5650
|
+
let changes = this.changes.get(parentRefId);
|
|
5307
5651
|
if (changes === undefined) {
|
|
5308
5652
|
changes = {};
|
|
5309
|
-
this.changes.set(
|
|
5653
|
+
this.changes.set(parentRefId, changes);
|
|
5310
5654
|
}
|
|
5311
5655
|
else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5312
5656
|
//
|
|
5313
5657
|
// SAME PATCH ADD + REMOVE:
|
|
5314
5658
|
// The 'changes' of deleted structure should be ignored.
|
|
5315
5659
|
//
|
|
5316
|
-
this.changes.delete(
|
|
5660
|
+
this.changes.delete(refId);
|
|
5317
5661
|
}
|
|
5318
5662
|
// DELETE / DELETE BY REF ID
|
|
5319
5663
|
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
@@ -5373,7 +5717,7 @@
|
|
|
5373
5717
|
if (!isVisible && changeTree.isVisibilitySharedWithParent) {
|
|
5374
5718
|
// console.log("CHECK AGAINST PARENT...", {
|
|
5375
5719
|
// ref: changeTree.ref.constructor.name,
|
|
5376
|
-
// refId: changeTree.refId,
|
|
5720
|
+
// refId: changeTree.ref[$refId],
|
|
5377
5721
|
// parent: changeTree.parent.constructor.name,
|
|
5378
5722
|
// });
|
|
5379
5723
|
if (this.visible.has(changeTree.parent[$changes])) {
|
|
@@ -5403,8 +5747,10 @@
|
|
|
5403
5747
|
exports.$encoder = $encoder;
|
|
5404
5748
|
exports.$filter = $filter;
|
|
5405
5749
|
exports.$getByIndex = $getByIndex;
|
|
5750
|
+
exports.$refId = $refId;
|
|
5406
5751
|
exports.$track = $track;
|
|
5407
5752
|
exports.ArraySchema = ArraySchema;
|
|
5753
|
+
exports.Callbacks = Callbacks;
|
|
5408
5754
|
exports.ChangeTree = ChangeTree;
|
|
5409
5755
|
exports.CollectionSchema = CollectionSchema;
|
|
5410
5756
|
exports.Decoder = Decoder;
|
|
@@ -5416,6 +5762,7 @@
|
|
|
5416
5762
|
exports.ReflectionType = ReflectionType;
|
|
5417
5763
|
exports.Schema = Schema;
|
|
5418
5764
|
exports.SetSchema = SetSchema;
|
|
5765
|
+
exports.StateCallbackStrategy = StateCallbackStrategy;
|
|
5419
5766
|
exports.StateView = StateView;
|
|
5420
5767
|
exports.TypeContext = TypeContext;
|
|
5421
5768
|
exports.decode = decode;
|