@colyseus/schema 3.0.75 → 4.0.0
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 +780 -429
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +778 -430
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +780 -429
- 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/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 +26 -18
- 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
|
}
|
|
@@ -984,25 +985,33 @@
|
|
|
984
985
|
delete changeSet.indexes[index];
|
|
985
986
|
}
|
|
986
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;
|
|
987
1014
|
constructor(ref) {
|
|
988
|
-
/**
|
|
989
|
-
* Whether this structure is parent of a filtered structure.
|
|
990
|
-
*/
|
|
991
|
-
this.isFiltered = false;
|
|
992
|
-
this.indexedOperations = {};
|
|
993
|
-
//
|
|
994
|
-
// TODO:
|
|
995
|
-
// try storing the index + operation per item.
|
|
996
|
-
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
997
|
-
//
|
|
998
|
-
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
999
|
-
//
|
|
1000
|
-
this.changes = { indexes: {}, operations: [] };
|
|
1001
|
-
this.allChanges = { indexes: {}, operations: [] };
|
|
1002
|
-
/**
|
|
1003
|
-
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
1004
|
-
*/
|
|
1005
|
-
this.isNew = true;
|
|
1006
1015
|
this.ref = ref;
|
|
1007
1016
|
this.metadata = ref.constructor[Symbol.metadata];
|
|
1008
1017
|
//
|
|
@@ -1474,7 +1483,7 @@
|
|
|
1474
1483
|
// Encode refId for this instance.
|
|
1475
1484
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1476
1485
|
//
|
|
1477
|
-
encode.number(bytes, value[$
|
|
1486
|
+
encode.number(bytes, value[$refId], it);
|
|
1478
1487
|
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
1479
1488
|
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
1480
1489
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
@@ -1485,7 +1494,7 @@
|
|
|
1485
1494
|
// Encode refId for this instance.
|
|
1486
1495
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1487
1496
|
//
|
|
1488
|
-
encode.number(bytes, value[$
|
|
1497
|
+
encode.number(bytes, value[$refId], it);
|
|
1489
1498
|
}
|
|
1490
1499
|
}
|
|
1491
1500
|
/**
|
|
@@ -1561,7 +1570,7 @@
|
|
|
1561
1570
|
if (!item) {
|
|
1562
1571
|
return;
|
|
1563
1572
|
}
|
|
1564
|
-
refOrIndex = item[$
|
|
1573
|
+
refOrIndex = item[$refId];
|
|
1565
1574
|
if (operation === exports.OPERATION.DELETE) {
|
|
1566
1575
|
operation = exports.OPERATION.DELETE_BY_REFID;
|
|
1567
1576
|
}
|
|
@@ -1601,7 +1610,7 @@
|
|
|
1601
1610
|
let value;
|
|
1602
1611
|
if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
1603
1612
|
// Flag `refId` for garbage collection.
|
|
1604
|
-
const previousRefId = $
|
|
1613
|
+
const previousRefId = previousValue?.[$refId];
|
|
1605
1614
|
if (previousRefId !== undefined) {
|
|
1606
1615
|
$root.removeRef(previousRefId);
|
|
1607
1616
|
}
|
|
@@ -1642,7 +1651,7 @@
|
|
|
1642
1651
|
value = valueRef.clone(true);
|
|
1643
1652
|
value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
|
|
1644
1653
|
if (previousValue) {
|
|
1645
|
-
let previousRefId = $
|
|
1654
|
+
let previousRefId = previousValue[$refId];
|
|
1646
1655
|
if (previousRefId !== undefined && refId !== previousRefId) {
|
|
1647
1656
|
//
|
|
1648
1657
|
// enqueue onRemove if structure has been replaced.
|
|
@@ -1653,7 +1662,7 @@
|
|
|
1653
1662
|
const [key, value] = iter.value;
|
|
1654
1663
|
// if value is a schema, remove its reference
|
|
1655
1664
|
if (typeof (value) === "object") {
|
|
1656
|
-
previousRefId = $
|
|
1665
|
+
previousRefId = value[$refId];
|
|
1657
1666
|
$root.removeRef(previousRefId);
|
|
1658
1667
|
}
|
|
1659
1668
|
allChanges.push({
|
|
@@ -1882,7 +1891,6 @@
|
|
|
1882
1891
|
}
|
|
1883
1892
|
}
|
|
1884
1893
|
|
|
1885
|
-
var _a$4, _b$4;
|
|
1886
1894
|
const DEFAULT_SORT = (a, b) => {
|
|
1887
1895
|
const A = a.toString();
|
|
1888
1896
|
const B = b.toString();
|
|
@@ -1894,8 +1902,15 @@
|
|
|
1894
1902
|
return 0;
|
|
1895
1903
|
};
|
|
1896
1904
|
class ArraySchema {
|
|
1897
|
-
|
|
1898
|
-
|
|
1905
|
+
[$changes];
|
|
1906
|
+
[$refId];
|
|
1907
|
+
[$childType];
|
|
1908
|
+
items = [];
|
|
1909
|
+
tmpItems = [];
|
|
1910
|
+
deletedIndexes = {};
|
|
1911
|
+
isMovingItems = false;
|
|
1912
|
+
static [$encoder] = encodeArray;
|
|
1913
|
+
static [$decoder] = decodeArray;
|
|
1899
1914
|
/**
|
|
1900
1915
|
* Determine if a property must be filtered.
|
|
1901
1916
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -1905,7 +1920,7 @@
|
|
|
1905
1920
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
1906
1921
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
1907
1922
|
*/
|
|
1908
|
-
static [
|
|
1923
|
+
static [$filter](ref, index, view) {
|
|
1909
1924
|
return (!view ||
|
|
1910
1925
|
typeof (ref[$childType]) === "string" ||
|
|
1911
1926
|
view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
|
|
@@ -1921,10 +1936,6 @@
|
|
|
1921
1936
|
return new ArraySchema(...Array.from(iterable));
|
|
1922
1937
|
}
|
|
1923
1938
|
constructor(...items) {
|
|
1924
|
-
this.items = [];
|
|
1925
|
-
this.tmpItems = [];
|
|
1926
|
-
this.deletedIndexes = {};
|
|
1927
|
-
this.isMovingItems = false;
|
|
1928
1939
|
Object.defineProperty(this, $childType, {
|
|
1929
1940
|
value: undefined,
|
|
1930
1941
|
enumerable: false,
|
|
@@ -2441,6 +2452,10 @@
|
|
|
2441
2452
|
static get [Symbol.species]() {
|
|
2442
2453
|
return ArraySchema;
|
|
2443
2454
|
}
|
|
2455
|
+
// WORKAROUND for compatibility
|
|
2456
|
+
// - TypeScript 4 defines @@unscopables as a function
|
|
2457
|
+
// - TypeScript 5 defines @@unscopables as an object
|
|
2458
|
+
[Symbol.unscopables];
|
|
2444
2459
|
/**
|
|
2445
2460
|
* Returns an iterable of key, value pairs for every entry in the array
|
|
2446
2461
|
*/
|
|
@@ -2550,7 +2565,7 @@
|
|
|
2550
2565
|
this.isMovingItems = false;
|
|
2551
2566
|
return this;
|
|
2552
2567
|
}
|
|
2553
|
-
[
|
|
2568
|
+
[$getByIndex](index, isEncodeAll = false) {
|
|
2554
2569
|
//
|
|
2555
2570
|
// TODO: avoid unecessary `this.tmpItems` check during decoding.
|
|
2556
2571
|
//
|
|
@@ -2605,10 +2620,16 @@
|
|
|
2605
2620
|
}
|
|
2606
2621
|
registerType("array", { constructor: ArraySchema });
|
|
2607
2622
|
|
|
2608
|
-
var _a$3, _b$3;
|
|
2609
2623
|
class MapSchema {
|
|
2610
|
-
|
|
2611
|
-
|
|
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;
|
|
2612
2633
|
/**
|
|
2613
2634
|
* Determine if a property must be filtered.
|
|
2614
2635
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2618,7 +2639,7 @@
|
|
|
2618
2639
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2619
2640
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2620
2641
|
*/
|
|
2621
|
-
static [
|
|
2642
|
+
static [$filter](ref, index, view) {
|
|
2622
2643
|
return (!view ||
|
|
2623
2644
|
typeof (ref[$childType]) === "string" ||
|
|
2624
2645
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2627,9 +2648,6 @@
|
|
|
2627
2648
|
return type['map'] !== undefined;
|
|
2628
2649
|
}
|
|
2629
2650
|
constructor(initialValues) {
|
|
2630
|
-
this.$items = new Map();
|
|
2631
|
-
this.$indexes = new Map();
|
|
2632
|
-
this.deletedItems = {};
|
|
2633
2651
|
const changeTree = new ChangeTree(this);
|
|
2634
2652
|
changeTree.indexes = {};
|
|
2635
2653
|
Object.defineProperty(this, $changes, {
|
|
@@ -2820,10 +2838,16 @@
|
|
|
2820
2838
|
}
|
|
2821
2839
|
registerType("map", { constructor: MapSchema });
|
|
2822
2840
|
|
|
2823
|
-
var _a$2, _b$2;
|
|
2824
2841
|
class CollectionSchema {
|
|
2825
|
-
|
|
2826
|
-
|
|
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;
|
|
2827
2851
|
/**
|
|
2828
2852
|
* Determine if a property must be filtered.
|
|
2829
2853
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2833,7 +2857,7 @@
|
|
|
2833
2857
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2834
2858
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2835
2859
|
*/
|
|
2836
|
-
static [
|
|
2860
|
+
static [$filter](ref, index, view) {
|
|
2837
2861
|
return (!view ||
|
|
2838
2862
|
typeof (ref[$childType]) === "string" ||
|
|
2839
2863
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2842,10 +2866,6 @@
|
|
|
2842
2866
|
return type['collection'] !== undefined;
|
|
2843
2867
|
}
|
|
2844
2868
|
constructor(initialValues) {
|
|
2845
|
-
this.$items = new Map();
|
|
2846
|
-
this.$indexes = new Map();
|
|
2847
|
-
this.deletedItems = {};
|
|
2848
|
-
this.$refId = 0;
|
|
2849
2869
|
this[$changes] = new ChangeTree(this);
|
|
2850
2870
|
this[$changes].indexes = {};
|
|
2851
2871
|
if (initialValues) {
|
|
@@ -2984,10 +3004,16 @@
|
|
|
2984
3004
|
}
|
|
2985
3005
|
registerType("collection", { constructor: CollectionSchema, });
|
|
2986
3006
|
|
|
2987
|
-
var _a$1, _b$1;
|
|
2988
3007
|
class SetSchema {
|
|
2989
|
-
|
|
2990
|
-
|
|
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;
|
|
2991
3017
|
/**
|
|
2992
3018
|
* Determine if a property must be filtered.
|
|
2993
3019
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2997,7 +3023,7 @@
|
|
|
2997
3023
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2998
3024
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2999
3025
|
*/
|
|
3000
|
-
static [
|
|
3026
|
+
static [$filter](ref, index, view) {
|
|
3001
3027
|
return (!view ||
|
|
3002
3028
|
typeof (ref[$childType]) === "string" ||
|
|
3003
3029
|
view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -3006,10 +3032,6 @@
|
|
|
3006
3032
|
return type['set'] !== undefined;
|
|
3007
3033
|
}
|
|
3008
3034
|
constructor(initialValues) {
|
|
3009
|
-
this.$items = new Map();
|
|
3010
|
-
this.$indexes = new Map();
|
|
3011
|
-
this.deletedItems = {};
|
|
3012
|
-
this.$refId = 0;
|
|
3013
3035
|
this[$changes] = new ChangeTree(this);
|
|
3014
3036
|
this[$changes].indexes = {};
|
|
3015
3037
|
if (initialValues) {
|
|
@@ -3508,7 +3530,10 @@
|
|
|
3508
3530
|
? DEFAULT_VIEW_TAG
|
|
3509
3531
|
: value['view'];
|
|
3510
3532
|
}
|
|
3511
|
-
|
|
3533
|
+
// allow to define a field as not synced
|
|
3534
|
+
if (value['sync'] !== false) {
|
|
3535
|
+
fields[fieldName] = getNormalizedType(value);
|
|
3536
|
+
}
|
|
3512
3537
|
// If no explicit default provided, handle automatic instantiation for collection types
|
|
3513
3538
|
if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
|
|
3514
3539
|
// TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
|
|
@@ -3530,12 +3555,7 @@
|
|
|
3530
3555
|
}
|
|
3531
3556
|
else if (value['type'] !== undefined && Schema.is(value['type'])) {
|
|
3532
3557
|
// Direct Schema type: Type → new Type()
|
|
3533
|
-
|
|
3534
|
-
// only auto-initialize Schema instances if:
|
|
3535
|
-
// - they don't have an initialize method
|
|
3536
|
-
// - or initialize method doesn't accept any parameters
|
|
3537
|
-
defaultValues[fieldName] = new value['type']();
|
|
3538
|
-
}
|
|
3558
|
+
defaultValues[fieldName] = new value['type']();
|
|
3539
3559
|
}
|
|
3540
3560
|
}
|
|
3541
3561
|
else {
|
|
@@ -3545,12 +3565,7 @@
|
|
|
3545
3565
|
else if (typeof (value) === "function") {
|
|
3546
3566
|
if (Schema.is(value)) {
|
|
3547
3567
|
// Direct Schema type: Type → new Type()
|
|
3548
|
-
|
|
3549
|
-
// only auto-initialize Schema instances if:
|
|
3550
|
-
// - they don't have an initialize method
|
|
3551
|
-
// - or initialize method doesn't accept any parameters
|
|
3552
|
-
defaultValues[fieldName] = new value();
|
|
3553
|
-
}
|
|
3568
|
+
defaultValues[fieldName] = new value();
|
|
3554
3569
|
fields[fieldName] = getNormalizedType(value);
|
|
3555
3570
|
}
|
|
3556
3571
|
else {
|
|
@@ -3577,32 +3592,13 @@
|
|
|
3577
3592
|
}
|
|
3578
3593
|
return defaults;
|
|
3579
3594
|
};
|
|
3580
|
-
const getParentProps = (props) => {
|
|
3581
|
-
const fieldNames = Object.keys(fields);
|
|
3582
|
-
const parentProps = {};
|
|
3583
|
-
for (const key in props) {
|
|
3584
|
-
if (!fieldNames.includes(key)) {
|
|
3585
|
-
parentProps[key] = props[key];
|
|
3586
|
-
}
|
|
3587
|
-
}
|
|
3588
|
-
return parentProps;
|
|
3589
|
-
};
|
|
3590
3595
|
/** @codegen-ignore */
|
|
3591
3596
|
const klass = Metadata.setFields(class extends inherits {
|
|
3592
3597
|
constructor(...args) {
|
|
3598
|
+
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3593
3599
|
// call initialize method
|
|
3594
3600
|
if (methods.initialize && typeof methods.initialize === 'function') {
|
|
3595
|
-
|
|
3596
|
-
/**
|
|
3597
|
-
* only call initialize() in the current class, not the parent ones.
|
|
3598
|
-
* see "should not call initialize automatically when creating an instance of inherited Schema"
|
|
3599
|
-
*/
|
|
3600
|
-
if (new.target === klass) {
|
|
3601
|
-
methods.initialize.apply(this, args);
|
|
3602
|
-
}
|
|
3603
|
-
}
|
|
3604
|
-
else {
|
|
3605
|
-
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3601
|
+
methods.initialize.apply(this, args);
|
|
3606
3602
|
}
|
|
3607
3603
|
}
|
|
3608
3604
|
}, fields);
|
|
@@ -3639,7 +3635,7 @@
|
|
|
3639
3635
|
continue;
|
|
3640
3636
|
}
|
|
3641
3637
|
const changes = changeTree.indexedOperations;
|
|
3642
|
-
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3638
|
+
dump.refs.push(`refId#${changeTree.ref[$refId]}`);
|
|
3643
3639
|
for (const index in changes) {
|
|
3644
3640
|
const op = changes[index];
|
|
3645
3641
|
const opName = exports.OPERATION[op];
|
|
@@ -3653,13 +3649,14 @@
|
|
|
3653
3649
|
return dump;
|
|
3654
3650
|
}
|
|
3655
3651
|
|
|
3656
|
-
var _a, _b;
|
|
3657
3652
|
/**
|
|
3658
3653
|
* Schema encoder / decoder
|
|
3659
3654
|
*/
|
|
3660
3655
|
class Schema {
|
|
3661
|
-
static
|
|
3662
|
-
static
|
|
3656
|
+
static [Symbol.metadata];
|
|
3657
|
+
static [$encoder] = encodeSchemaOperation;
|
|
3658
|
+
static [$decoder] = decodeSchemaOperation;
|
|
3659
|
+
[$refId];
|
|
3663
3660
|
/**
|
|
3664
3661
|
* Assign the property descriptors required to track changes on this instance.
|
|
3665
3662
|
* @param instance
|
|
@@ -3678,7 +3675,7 @@
|
|
|
3678
3675
|
/**
|
|
3679
3676
|
* Track property changes
|
|
3680
3677
|
*/
|
|
3681
|
-
static [
|
|
3678
|
+
static [$track](changeTree, index, operation = exports.OPERATION.ADD) {
|
|
3682
3679
|
changeTree.change(index, operation);
|
|
3683
3680
|
}
|
|
3684
3681
|
/**
|
|
@@ -3725,10 +3722,74 @@
|
|
|
3725
3722
|
Object.assign(this, arg);
|
|
3726
3723
|
}
|
|
3727
3724
|
}
|
|
3725
|
+
/**
|
|
3726
|
+
* Assign properties to the instance.
|
|
3727
|
+
* @param props Properties to assign to the instance
|
|
3728
|
+
* @returns
|
|
3729
|
+
*/
|
|
3728
3730
|
assign(props) {
|
|
3729
3731
|
Object.assign(this, props);
|
|
3730
3732
|
return this;
|
|
3731
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
|
+
}
|
|
3732
3793
|
/**
|
|
3733
3794
|
* (Server-side): Flag a property to be encoded for the next patch.
|
|
3734
3795
|
* @param instance Schema instance
|
|
@@ -3801,7 +3862,7 @@
|
|
|
3801
3862
|
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3802
3863
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3803
3864
|
const changeTree = ref[$changes];
|
|
3804
|
-
const refId =
|
|
3865
|
+
const refId = ref[$refId];
|
|
3805
3866
|
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3806
3867
|
// log reference count if > 1
|
|
3807
3868
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
@@ -3824,7 +3885,7 @@
|
|
|
3824
3885
|
let current = ref[$changes].root[changeSet].next;
|
|
3825
3886
|
while (current) {
|
|
3826
3887
|
if (current.changeTree) {
|
|
3827
|
-
encodeOrder.push(current.changeTree.refId);
|
|
3888
|
+
encodeOrder.push(current.changeTree.ref[$refId]);
|
|
3828
3889
|
}
|
|
3829
3890
|
current = current.next;
|
|
3830
3891
|
}
|
|
@@ -3845,7 +3906,7 @@
|
|
|
3845
3906
|
const changeTree = instance[$changes];
|
|
3846
3907
|
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3847
3908
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3848
|
-
let output = `${instance.constructor.name} (${
|
|
3909
|
+
let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
|
|
3849
3910
|
function dumpChangeSet(changeSet) {
|
|
3850
3911
|
changeSet.operations
|
|
3851
3912
|
.filter(op => op)
|
|
@@ -3859,14 +3920,14 @@
|
|
|
3859
3920
|
if (!isEncodeAll &&
|
|
3860
3921
|
changeTree.filteredChanges &&
|
|
3861
3922
|
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3862
|
-
output += `${instance.constructor.name} (${
|
|
3923
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
|
|
3863
3924
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3864
3925
|
}
|
|
3865
3926
|
// display filtered changes
|
|
3866
3927
|
if (isEncodeAll &&
|
|
3867
3928
|
changeTree.allFilteredChanges &&
|
|
3868
3929
|
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3869
|
-
output += `${instance.constructor.name} (${
|
|
3930
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
|
|
3870
3931
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3871
3932
|
}
|
|
3872
3933
|
return output;
|
|
@@ -3901,13 +3962,13 @@
|
|
|
3901
3962
|
}
|
|
3902
3963
|
}
|
|
3903
3964
|
if (includeChangeTree) {
|
|
3904
|
-
instanceRefIds.push(changeTree.refId);
|
|
3965
|
+
instanceRefIds.push(changeTree.ref[$refId]);
|
|
3905
3966
|
totalOperations += Object.keys(changes).length;
|
|
3906
3967
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3907
3968
|
}
|
|
3908
3969
|
}
|
|
3909
3970
|
output += "---\n";
|
|
3910
|
-
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3971
|
+
output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
|
|
3911
3972
|
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3912
3973
|
output += `Total changes: ${totalOperations}\n`;
|
|
3913
3974
|
output += "---\n";
|
|
@@ -3916,7 +3977,7 @@
|
|
|
3916
3977
|
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3917
3978
|
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3918
3979
|
if (!visitedParents.has(parentChangeTree)) {
|
|
3919
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3980
|
+
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
|
|
3920
3981
|
visitedParents.add(parentChangeTree);
|
|
3921
3982
|
}
|
|
3922
3983
|
});
|
|
@@ -3924,7 +3985,7 @@
|
|
|
3924
3985
|
const level = parentChangeTrees.length;
|
|
3925
3986
|
const indent = getIndent(level);
|
|
3926
3987
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3927
|
-
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`;
|
|
3928
3989
|
for (const index in changes) {
|
|
3929
3990
|
const operation = changes[index];
|
|
3930
3991
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
@@ -3934,61 +3995,35 @@
|
|
|
3934
3995
|
}
|
|
3935
3996
|
}
|
|
3936
3997
|
|
|
3937
|
-
/******************************************************************************
|
|
3938
|
-
Copyright (c) Microsoft Corporation.
|
|
3939
|
-
|
|
3940
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
3941
|
-
purpose with or without fee is hereby granted.
|
|
3942
|
-
|
|
3943
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
3944
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
3945
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
3946
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
3947
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
3948
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3949
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
3950
|
-
***************************************************************************** */
|
|
3951
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
3952
|
-
|
|
3953
|
-
|
|
3954
|
-
function __decorate(decorators, target, key, desc) {
|
|
3955
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3956
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3957
|
-
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;
|
|
3958
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3959
|
-
}
|
|
3960
|
-
|
|
3961
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
3962
|
-
var e = new Error(message);
|
|
3963
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3964
|
-
};
|
|
3965
|
-
|
|
3966
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
|
|
3967
4009
|
constructor(types) {
|
|
3968
4010
|
this.types = types;
|
|
3969
|
-
this.nextUniqueId = 0;
|
|
3970
|
-
this.refCount = {};
|
|
3971
|
-
this.changeTrees = {};
|
|
3972
|
-
// all changes
|
|
3973
|
-
this.allChanges = createChangeTreeList();
|
|
3974
|
-
this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3975
|
-
// pending changes to be encoded
|
|
3976
|
-
this.changes = createChangeTreeList();
|
|
3977
|
-
this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3978
4011
|
}
|
|
3979
4012
|
getNextUniqueId() {
|
|
3980
4013
|
return this.nextUniqueId++;
|
|
3981
4014
|
}
|
|
3982
4015
|
add(changeTree) {
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
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();
|
|
3986
4020
|
}
|
|
3987
|
-
const
|
|
4021
|
+
const refId = ref[$refId];
|
|
4022
|
+
const isNewChangeTree = (this.changeTrees[refId] === undefined);
|
|
3988
4023
|
if (isNewChangeTree) {
|
|
3989
|
-
this.changeTrees[
|
|
4024
|
+
this.changeTrees[refId] = changeTree;
|
|
3990
4025
|
}
|
|
3991
|
-
const previousRefCount = this.refCount[
|
|
4026
|
+
const previousRefCount = this.refCount[refId];
|
|
3992
4027
|
if (previousRefCount === 0) {
|
|
3993
4028
|
//
|
|
3994
4029
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
@@ -4001,30 +4036,31 @@
|
|
|
4001
4036
|
setOperationAtIndex(changeTree.changes, len);
|
|
4002
4037
|
}
|
|
4003
4038
|
}
|
|
4004
|
-
this.refCount[
|
|
4005
|
-
// 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 });
|
|
4006
4041
|
return isNewChangeTree;
|
|
4007
4042
|
}
|
|
4008
4043
|
remove(changeTree) {
|
|
4009
|
-
const
|
|
4010
|
-
|
|
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 });
|
|
4011
4047
|
if (refCount <= 0) {
|
|
4012
4048
|
//
|
|
4013
4049
|
// Only remove "root" reference if it's the last reference
|
|
4014
4050
|
//
|
|
4015
4051
|
changeTree.root = undefined;
|
|
4016
|
-
delete this.changeTrees[
|
|
4052
|
+
delete this.changeTrees[refId];
|
|
4017
4053
|
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
4018
4054
|
this.removeChangeFromChangeSet("changes", changeTree);
|
|
4019
4055
|
if (changeTree.filteredChanges) {
|
|
4020
4056
|
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
4021
4057
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
4022
4058
|
}
|
|
4023
|
-
this.refCount[
|
|
4059
|
+
this.refCount[refId] = 0;
|
|
4024
4060
|
changeTree.forEachChild((child, _) => {
|
|
4025
4061
|
if (child.removeParent(changeTree.ref)) {
|
|
4026
4062
|
if ((child.parentChain === undefined || // no parent, remove it
|
|
4027
|
-
(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
|
|
4028
4064
|
)) {
|
|
4029
4065
|
this.remove(child);
|
|
4030
4066
|
}
|
|
@@ -4036,7 +4072,7 @@
|
|
|
4036
4072
|
});
|
|
4037
4073
|
}
|
|
4038
4074
|
else {
|
|
4039
|
-
this.refCount[
|
|
4075
|
+
this.refCount[refId] = refCount;
|
|
4040
4076
|
//
|
|
4041
4077
|
// When losing a reference to an instance, it is best to move the
|
|
4042
4078
|
// ChangeTree next to its parent in the encoding queue.
|
|
@@ -4186,10 +4222,19 @@
|
|
|
4186
4222
|
}
|
|
4187
4223
|
}
|
|
4188
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
|
+
}
|
|
4189
4231
|
class Encoder {
|
|
4190
|
-
static
|
|
4232
|
+
static BUFFER_SIZE = 8 * 1024; // 8KB
|
|
4233
|
+
sharedBuffer = new Uint8Array(Encoder.BUFFER_SIZE);
|
|
4234
|
+
context;
|
|
4235
|
+
state;
|
|
4236
|
+
root;
|
|
4191
4237
|
constructor(state) {
|
|
4192
|
-
this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
|
|
4193
4238
|
//
|
|
4194
4239
|
// Use .cache() here to avoid re-creating a new context for every new room instance.
|
|
4195
4240
|
//
|
|
@@ -4217,7 +4262,7 @@
|
|
|
4217
4262
|
const changeTree = current.changeTree;
|
|
4218
4263
|
if (hasView) {
|
|
4219
4264
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
4220
|
-
// 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() });
|
|
4221
4266
|
view.invisible.add(changeTree);
|
|
4222
4267
|
continue; // skip this change tree
|
|
4223
4268
|
}
|
|
@@ -4238,7 +4283,7 @@
|
|
|
4238
4283
|
// (unless it "hasView", which will need to revisit the root)
|
|
4239
4284
|
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
4240
4285
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4241
|
-
encode.number(buffer,
|
|
4286
|
+
encode.number(buffer, ref[$refId], it);
|
|
4242
4287
|
}
|
|
4243
4288
|
for (let j = 0; j < numChanges; j++) {
|
|
4244
4289
|
const fieldIndex = changeSet.operations[j];
|
|
@@ -4267,10 +4312,9 @@
|
|
|
4267
4312
|
}
|
|
4268
4313
|
}
|
|
4269
4314
|
if (it.offset > buffer.byteLength) {
|
|
4270
|
-
// we can assume that n + 1
|
|
4271
|
-
// multiples of
|
|
4272
|
-
|
|
4273
|
-
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;
|
|
4274
4318
|
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
4275
4319
|
|
|
4276
4320
|
import { Encoder } from "@colyseus/schema";
|
|
@@ -4280,7 +4324,9 @@
|
|
|
4280
4324
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
4281
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
|
|
4282
4326
|
//
|
|
4283
|
-
|
|
4327
|
+
const newBuffer = new Uint8Array(newSize);
|
|
4328
|
+
newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
|
|
4329
|
+
buffer = newBuffer;
|
|
4284
4330
|
// assign resized buffer to local sharedBuffer
|
|
4285
4331
|
if (buffer === this.sharedBuffer) {
|
|
4286
4332
|
this.sharedBuffer = buffer;
|
|
@@ -4298,10 +4344,7 @@
|
|
|
4298
4344
|
const viewOffset = it.offset;
|
|
4299
4345
|
// try to encode "filtered" changes
|
|
4300
4346
|
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4301
|
-
return
|
|
4302
|
-
bytes.subarray(0, sharedOffset),
|
|
4303
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4304
|
-
]);
|
|
4347
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4305
4348
|
}
|
|
4306
4349
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4307
4350
|
const viewOffset = it.offset;
|
|
@@ -4325,7 +4368,7 @@
|
|
|
4325
4368
|
const encoder = ctor[$encoder];
|
|
4326
4369
|
const metadata = ctor[Symbol.metadata];
|
|
4327
4370
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4328
|
-
encode.number(bytes,
|
|
4371
|
+
encode.number(bytes, ref[$refId], it);
|
|
4329
4372
|
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
4330
4373
|
const index = Number(keys[i]);
|
|
4331
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")
|
|
@@ -4344,10 +4387,7 @@
|
|
|
4344
4387
|
view.changes.clear();
|
|
4345
4388
|
// try to encode "filtered" changes
|
|
4346
4389
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4347
|
-
return
|
|
4348
|
-
bytes.subarray(0, sharedOffset),
|
|
4349
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4350
|
-
]);
|
|
4390
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4351
4391
|
}
|
|
4352
4392
|
discardChanges() {
|
|
4353
4393
|
// discard shared changes
|
|
@@ -4403,25 +4443,22 @@
|
|
|
4403
4443
|
}
|
|
4404
4444
|
}
|
|
4405
4445
|
class ReferenceTracker {
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
this.callbacks = {};
|
|
4416
|
-
this.nextUniqueId = 0;
|
|
4417
|
-
}
|
|
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;
|
|
4418
4455
|
getNextUniqueId() {
|
|
4419
4456
|
return this.nextUniqueId++;
|
|
4420
4457
|
}
|
|
4421
4458
|
// for decoding
|
|
4422
4459
|
addRef(refId, ref, incrementCount = true) {
|
|
4423
4460
|
this.refs.set(refId, ref);
|
|
4424
|
-
|
|
4461
|
+
ref[$refId] = refId;
|
|
4425
4462
|
if (incrementCount) {
|
|
4426
4463
|
this.refCount[refId] = (this.refCount[refId] || 0) + 1;
|
|
4427
4464
|
}
|
|
@@ -4478,9 +4515,12 @@
|
|
|
4478
4515
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4479
4516
|
for (const index in metadata) {
|
|
4480
4517
|
const field = metadata[index].name;
|
|
4481
|
-
const
|
|
4482
|
-
if (
|
|
4483
|
-
|
|
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
|
+
}
|
|
4484
4524
|
}
|
|
4485
4525
|
}
|
|
4486
4526
|
}
|
|
@@ -4488,8 +4528,8 @@
|
|
|
4488
4528
|
if (typeof (ref[$childType]) === "function") {
|
|
4489
4529
|
Array.from(ref.values())
|
|
4490
4530
|
.forEach((child) => {
|
|
4491
|
-
const childRefId =
|
|
4492
|
-
if (!this.deletedRefs.has(childRefId)) {
|
|
4531
|
+
const childRefId = child[$refId];
|
|
4532
|
+
if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
|
|
4493
4533
|
this.removeRef(childRefId);
|
|
4494
4534
|
}
|
|
4495
4535
|
});
|
|
@@ -4527,8 +4567,12 @@
|
|
|
4527
4567
|
}
|
|
4528
4568
|
|
|
4529
4569
|
class Decoder {
|
|
4570
|
+
context;
|
|
4571
|
+
state;
|
|
4572
|
+
root;
|
|
4573
|
+
currentRefId = 0;
|
|
4574
|
+
triggerChanges;
|
|
4530
4575
|
constructor(root, context) {
|
|
4531
|
-
this.currentRefId = 0;
|
|
4532
4576
|
this.setState(root);
|
|
4533
4577
|
this.context = context || new TypeContext(root.constructor);
|
|
4534
4578
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
@@ -4617,7 +4661,7 @@
|
|
|
4617
4661
|
}
|
|
4618
4662
|
removeChildRefs(ref, allChanges) {
|
|
4619
4663
|
const needRemoveRef = typeof (ref[$childType]) !== "string";
|
|
4620
|
-
const refId =
|
|
4664
|
+
const refId = ref[$refId];
|
|
4621
4665
|
ref.forEach((value, key) => {
|
|
4622
4666
|
allChanges.push({
|
|
4623
4667
|
ref: ref,
|
|
@@ -4628,7 +4672,7 @@
|
|
|
4628
4672
|
previousValue: value
|
|
4629
4673
|
});
|
|
4630
4674
|
if (needRemoveRef) {
|
|
4631
|
-
this.root.removeRef(
|
|
4675
|
+
this.root.removeRef(value[$refId]);
|
|
4632
4676
|
}
|
|
4633
4677
|
});
|
|
4634
4678
|
}
|
|
@@ -4637,217 +4681,182 @@
|
|
|
4637
4681
|
/**
|
|
4638
4682
|
* Reflection
|
|
4639
4683
|
*/
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
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
|
-
* @returns
|
|
4677
|
-
*/
|
|
4678
|
-
static encode(encoder, it = { offset: 0 }) {
|
|
4679
|
-
const context = encoder.context;
|
|
4680
|
-
const reflection = new Reflection();
|
|
4681
|
-
const reflectionEncoder = new Encoder(reflection);
|
|
4682
|
-
// rootType is usually the first schema passed to the Encoder
|
|
4683
|
-
// (unless it inherits from another schema)
|
|
4684
|
-
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4685
|
-
if (rootType > 0) {
|
|
4686
|
-
reflection.rootType = rootType;
|
|
4687
|
-
}
|
|
4688
|
-
const includedTypeIds = new Set();
|
|
4689
|
-
const pendingReflectionTypes = {};
|
|
4690
|
-
// add type to reflection in a way that respects inheritance
|
|
4691
|
-
// (parent types should be added before their children)
|
|
4692
|
-
const addType = (type) => {
|
|
4693
|
-
if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
|
|
4694
|
-
includedTypeIds.add(type.id);
|
|
4695
|
-
reflection.types.push(type);
|
|
4696
|
-
const deps = pendingReflectionTypes[type.id];
|
|
4697
|
-
if (deps !== undefined) {
|
|
4698
|
-
delete pendingReflectionTypes[type.id];
|
|
4699
|
-
deps.forEach((childType) => addType(childType));
|
|
4700
|
-
}
|
|
4701
|
-
}
|
|
4702
|
-
else {
|
|
4703
|
-
if (pendingReflectionTypes[type.extendsId] === undefined) {
|
|
4704
|
-
pendingReflectionTypes[type.extendsId] = [];
|
|
4705
|
-
}
|
|
4706
|
-
pendingReflectionTypes[type.extendsId].push(type);
|
|
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));
|
|
4707
4720
|
}
|
|
4708
|
-
}
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
// support inheritance
|
|
4713
|
-
const inheritFrom = Object.getPrototypeOf(klass);
|
|
4714
|
-
if (inheritFrom !== Schema) {
|
|
4715
|
-
type.extendsId = context.schemas.get(inheritFrom);
|
|
4721
|
+
}
|
|
4722
|
+
else {
|
|
4723
|
+
if (pendingReflectionTypes[type.extendsId] === undefined) {
|
|
4724
|
+
pendingReflectionTypes[type.extendsId] = [];
|
|
4716
4725
|
}
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
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;
|
|
4736
4765
|
}
|
|
4737
4766
|
else {
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
//
|
|
4742
|
-
if (Schema.is(field.type)) {
|
|
4743
|
-
fieldType = "ref";
|
|
4744
|
-
childTypeSchema = field.type;
|
|
4767
|
+
fieldType = Object.keys(field.type)[0];
|
|
4768
|
+
if (typeof (field.type[fieldType]) === "string") {
|
|
4769
|
+
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4745
4770
|
}
|
|
4746
4771
|
else {
|
|
4747
|
-
|
|
4748
|
-
if (typeof (field.type[fieldType]) === "string") {
|
|
4749
|
-
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4750
|
-
}
|
|
4751
|
-
else {
|
|
4752
|
-
childTypeSchema = field.type[fieldType];
|
|
4753
|
-
}
|
|
4772
|
+
childTypeSchema = field.type[fieldType];
|
|
4754
4773
|
}
|
|
4755
|
-
reflectionField.referencedType = (childTypeSchema)
|
|
4756
|
-
? context.getTypeId(childTypeSchema)
|
|
4757
|
-
: -1;
|
|
4758
4774
|
}
|
|
4759
|
-
reflectionField.
|
|
4760
|
-
|
|
4775
|
+
reflectionField.referencedType = (childTypeSchema)
|
|
4776
|
+
? context.getTypeId(childTypeSchema)
|
|
4777
|
+
: -1;
|
|
4761
4778
|
}
|
|
4779
|
+
reflectionField.type = fieldType;
|
|
4780
|
+
type.fields.push(reflectionField);
|
|
4762
4781
|
}
|
|
4763
|
-
addType(type);
|
|
4764
|
-
});
|
|
4765
|
-
// in case there are types that were not added due to inheritance
|
|
4766
|
-
for (const typeid in pendingReflectionTypes) {
|
|
4767
|
-
pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
|
|
4768
4782
|
}
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
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));
|
|
4772
4788
|
}
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
const
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
|
|
4802
|
-
|
|
4803
|
-
|
|
4804
|
-
|
|
4805
|
-
const typeInfo = field.type.split(":");
|
|
4806
|
-
fieldType = typeInfo[0];
|
|
4807
|
-
refType = typeInfo[1]; // string
|
|
4808
|
-
}
|
|
4809
|
-
if (fieldType === "ref") {
|
|
4810
|
-
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
4811
|
-
}
|
|
4812
|
-
else {
|
|
4813
|
-
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4814
|
-
}
|
|
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);
|
|
4815
4821
|
}
|
|
4816
4822
|
else {
|
|
4817
|
-
Metadata.addField(metadata, fieldIndex, field.name,
|
|
4823
|
+
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4818
4824
|
}
|
|
4819
|
-
}
|
|
4820
|
-
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
const schema = typeContext.get(reflectionType.id);
|
|
4824
|
-
// for inheritance support
|
|
4825
|
-
const metadata = Metadata.initialize(schema);
|
|
4826
|
-
const inheritedTypes = [];
|
|
4827
|
-
let parentType = reflectionType;
|
|
4828
|
-
do {
|
|
4829
|
-
inheritedTypes.push(parentType);
|
|
4830
|
-
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4831
|
-
} while (parentType);
|
|
4832
|
-
let parentFieldIndex = 0;
|
|
4833
|
-
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4834
|
-
// add fields from all inherited classes
|
|
4835
|
-
// TODO: refactor this to avoid adding fields from parent classes
|
|
4836
|
-
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4837
|
-
parentFieldIndex += reflectionType.fields.length;
|
|
4838
|
-
});
|
|
4825
|
+
}
|
|
4826
|
+
else {
|
|
4827
|
+
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
4828
|
+
}
|
|
4839
4829
|
});
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
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
|
+
};
|
|
4850
4853
|
|
|
4854
|
+
/**
|
|
4855
|
+
* Legacy callback system
|
|
4856
|
+
*
|
|
4857
|
+
* @param decoder
|
|
4858
|
+
* @returns
|
|
4859
|
+
*/
|
|
4851
4860
|
function getDecoderStateCallbacks(decoder) {
|
|
4852
4861
|
const $root = decoder.root;
|
|
4853
4862
|
const callbacks = $root.callbacks;
|
|
@@ -4868,7 +4877,7 @@
|
|
|
4868
4877
|
//
|
|
4869
4878
|
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4870
4879
|
change.previousValue instanceof Schema) {
|
|
4871
|
-
const deleteCallbacks = callbacks[
|
|
4880
|
+
const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[exports.OPERATION.DELETE];
|
|
4872
4881
|
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4873
4882
|
deleteCallbacks[i]();
|
|
4874
4883
|
}
|
|
@@ -4957,7 +4966,7 @@
|
|
|
4957
4966
|
) {
|
|
4958
4967
|
callback(context.instance[prop], undefined);
|
|
4959
4968
|
}
|
|
4960
|
-
return $root.addCallback($
|
|
4969
|
+
return $root.addCallback(ref[$refId], prop, callback);
|
|
4961
4970
|
};
|
|
4962
4971
|
/**
|
|
4963
4972
|
* Schema instances
|
|
@@ -4977,7 +4986,7 @@
|
|
|
4977
4986
|
}
|
|
4978
4987
|
},
|
|
4979
4988
|
onChange: function onChange(callback) {
|
|
4980
|
-
return $root.addCallback(
|
|
4989
|
+
return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, callback);
|
|
4981
4990
|
},
|
|
4982
4991
|
//
|
|
4983
4992
|
// TODO: refactor `bindTo()` implementation.
|
|
@@ -4987,7 +4996,7 @@
|
|
|
4987
4996
|
if (!properties) {
|
|
4988
4997
|
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4989
4998
|
}
|
|
4990
|
-
return $root.addCallback(
|
|
4999
|
+
return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, () => {
|
|
4991
5000
|
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4992
5001
|
});
|
|
4993
5002
|
}
|
|
@@ -5006,13 +5015,13 @@
|
|
|
5006
5015
|
unbind?.();
|
|
5007
5016
|
}, false);
|
|
5008
5017
|
// has existing value
|
|
5009
|
-
if ($
|
|
5018
|
+
if (instance?.[$refId] !== undefined) {
|
|
5010
5019
|
callback(instance, true);
|
|
5011
5020
|
}
|
|
5012
5021
|
});
|
|
5013
5022
|
return getProxy(metadataField.type, {
|
|
5014
5023
|
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
5015
|
-
instance: ($
|
|
5024
|
+
instance: (instance?.[$refId] !== undefined && instance),
|
|
5016
5025
|
parentInstance: context.instance,
|
|
5017
5026
|
onInstanceAvailable,
|
|
5018
5027
|
});
|
|
@@ -5036,7 +5045,7 @@
|
|
|
5036
5045
|
if (immediate) {
|
|
5037
5046
|
ref.forEach((v, k) => callback(v, k));
|
|
5038
5047
|
}
|
|
5039
|
-
return $root.addCallback($
|
|
5048
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.ADD, (value, key) => {
|
|
5040
5049
|
onAddCalls.set(callback, true);
|
|
5041
5050
|
currentOnAddCallback = callback;
|
|
5042
5051
|
callback(value, key);
|
|
@@ -5045,10 +5054,10 @@
|
|
|
5045
5054
|
});
|
|
5046
5055
|
};
|
|
5047
5056
|
const onRemove = function (ref, callback) {
|
|
5048
|
-
return $root.addCallback($
|
|
5057
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.DELETE, callback);
|
|
5049
5058
|
};
|
|
5050
5059
|
const onChange = function (ref, callback) {
|
|
5051
|
-
return $root.addCallback($
|
|
5060
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.REPLACE, callback);
|
|
5052
5061
|
};
|
|
5053
5062
|
return new Proxy({
|
|
5054
5063
|
onAdd: function (callback, immediate = true) {
|
|
@@ -5117,22 +5126,360 @@
|
|
|
5117
5126
|
decoder.triggerChanges = callback;
|
|
5118
5127
|
}
|
|
5119
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
|
+
|
|
5120
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();
|
|
5121
5481
|
constructor(iterable = false) {
|
|
5122
5482
|
this.iterable = iterable;
|
|
5123
|
-
/**
|
|
5124
|
-
* List of ChangeTree's that are visible to this view
|
|
5125
|
-
*/
|
|
5126
|
-
this.visible = new WeakSet();
|
|
5127
|
-
/**
|
|
5128
|
-
* List of ChangeTree's that are invisible to this view
|
|
5129
|
-
*/
|
|
5130
|
-
this.invisible = new WeakSet();
|
|
5131
|
-
/**
|
|
5132
|
-
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
5133
|
-
* (This is used to force encoding a property, even if it was not changed)
|
|
5134
|
-
*/
|
|
5135
|
-
this.changes = new Map();
|
|
5136
5483
|
if (iterable) {
|
|
5137
5484
|
this.items = [];
|
|
5138
5485
|
}
|
|
@@ -5146,7 +5493,7 @@
|
|
|
5146
5493
|
return false;
|
|
5147
5494
|
}
|
|
5148
5495
|
else if (!parentChangeTree &&
|
|
5149
|
-
|
|
5496
|
+
obj[$refId] !== 0 // allow root object
|
|
5150
5497
|
) {
|
|
5151
5498
|
/**
|
|
5152
5499
|
* TODO: can we avoid this?
|
|
@@ -5170,11 +5517,11 @@
|
|
|
5170
5517
|
if (checkIncludeParent && parentChangeTree) {
|
|
5171
5518
|
this.addParentOf(changeTree, tag);
|
|
5172
5519
|
}
|
|
5173
|
-
let changes = this.changes.get(
|
|
5520
|
+
let changes = this.changes.get(obj[$refId]);
|
|
5174
5521
|
if (changes === undefined) {
|
|
5175
5522
|
changes = {};
|
|
5176
5523
|
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5177
|
-
this.changes.set(
|
|
5524
|
+
this.changes.set(obj[$refId], changes);
|
|
5178
5525
|
}
|
|
5179
5526
|
let isChildAdded = false;
|
|
5180
5527
|
//
|
|
@@ -5254,10 +5601,10 @@
|
|
|
5254
5601
|
}
|
|
5255
5602
|
// add parent's tag properties
|
|
5256
5603
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
5257
|
-
let changes = this.changes.get(changeTree.refId);
|
|
5604
|
+
let changes = this.changes.get(changeTree.ref[$refId]);
|
|
5258
5605
|
if (changes === undefined) {
|
|
5259
5606
|
changes = {};
|
|
5260
|
-
this.changes.set(changeTree.refId, changes);
|
|
5607
|
+
this.changes.set(changeTree.ref[$refId], changes);
|
|
5261
5608
|
}
|
|
5262
5609
|
if (!this.tags) {
|
|
5263
5610
|
this.tags = new WeakMap();
|
|
@@ -5289,27 +5636,28 @@
|
|
|
5289
5636
|
}
|
|
5290
5637
|
const ref = changeTree.ref;
|
|
5291
5638
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5292
|
-
|
|
5639
|
+
const refId = ref[$refId];
|
|
5640
|
+
let changes = this.changes.get(refId);
|
|
5293
5641
|
if (changes === undefined) {
|
|
5294
5642
|
changes = {};
|
|
5295
|
-
this.changes.set(
|
|
5643
|
+
this.changes.set(refId, changes);
|
|
5296
5644
|
}
|
|
5297
5645
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5298
5646
|
// parent is collection (Map/Array)
|
|
5299
5647
|
const parent = changeTree.parent;
|
|
5300
5648
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5301
|
-
const
|
|
5302
|
-
let changes = this.changes.get(
|
|
5649
|
+
const parentRefId = parent[$refId];
|
|
5650
|
+
let changes = this.changes.get(parentRefId);
|
|
5303
5651
|
if (changes === undefined) {
|
|
5304
5652
|
changes = {};
|
|
5305
|
-
this.changes.set(
|
|
5653
|
+
this.changes.set(parentRefId, changes);
|
|
5306
5654
|
}
|
|
5307
5655
|
else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5308
5656
|
//
|
|
5309
5657
|
// SAME PATCH ADD + REMOVE:
|
|
5310
5658
|
// The 'changes' of deleted structure should be ignored.
|
|
5311
5659
|
//
|
|
5312
|
-
this.changes.delete(
|
|
5660
|
+
this.changes.delete(refId);
|
|
5313
5661
|
}
|
|
5314
5662
|
// DELETE / DELETE BY REF ID
|
|
5315
5663
|
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
@@ -5369,7 +5717,7 @@
|
|
|
5369
5717
|
if (!isVisible && changeTree.isVisibilitySharedWithParent) {
|
|
5370
5718
|
// console.log("CHECK AGAINST PARENT...", {
|
|
5371
5719
|
// ref: changeTree.ref.constructor.name,
|
|
5372
|
-
// refId: changeTree.refId,
|
|
5720
|
+
// refId: changeTree.ref[$refId],
|
|
5373
5721
|
// parent: changeTree.parent.constructor.name,
|
|
5374
5722
|
// });
|
|
5375
5723
|
if (this.visible.has(changeTree.parent[$changes])) {
|
|
@@ -5399,8 +5747,10 @@
|
|
|
5399
5747
|
exports.$encoder = $encoder;
|
|
5400
5748
|
exports.$filter = $filter;
|
|
5401
5749
|
exports.$getByIndex = $getByIndex;
|
|
5750
|
+
exports.$refId = $refId;
|
|
5402
5751
|
exports.$track = $track;
|
|
5403
5752
|
exports.ArraySchema = ArraySchema;
|
|
5753
|
+
exports.Callbacks = Callbacks;
|
|
5404
5754
|
exports.ChangeTree = ChangeTree;
|
|
5405
5755
|
exports.CollectionSchema = CollectionSchema;
|
|
5406
5756
|
exports.Decoder = Decoder;
|
|
@@ -5412,6 +5762,7 @@
|
|
|
5412
5762
|
exports.ReflectionType = ReflectionType;
|
|
5413
5763
|
exports.Schema = Schema;
|
|
5414
5764
|
exports.SetSchema = SetSchema;
|
|
5765
|
+
exports.StateCallbackStrategy = StateCallbackStrategy;
|
|
5415
5766
|
exports.StateView = StateView;
|
|
5416
5767
|
exports.TypeContext = TypeContext;
|
|
5417
5768
|
exports.decode = decode;
|