@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/cjs/index.js
CHANGED
|
@@ -28,6 +28,7 @@ exports.OPERATION = void 0;
|
|
|
28
28
|
|
|
29
29
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
30
30
|
|
|
31
|
+
const $refId = "~refId";
|
|
31
32
|
const $track = "~track";
|
|
32
33
|
const $encoder = "~encoder";
|
|
33
34
|
const $decoder = "~decoder";
|
|
@@ -558,12 +559,16 @@ function defineCustomTypes(types) {
|
|
|
558
559
|
}
|
|
559
560
|
|
|
560
561
|
class TypeContext {
|
|
562
|
+
types = {};
|
|
563
|
+
schemas = new Map();
|
|
564
|
+
hasFilters = false;
|
|
565
|
+
parentFiltered = {};
|
|
561
566
|
/**
|
|
562
567
|
* For inheritance support
|
|
563
568
|
* Keeps track of which classes extends which. (parent -> children)
|
|
564
569
|
*/
|
|
565
|
-
static
|
|
566
|
-
static
|
|
570
|
+
static inheritedTypes = new Map();
|
|
571
|
+
static cachedContexts = new Map();
|
|
567
572
|
static register(target) {
|
|
568
573
|
const parent = Object.getPrototypeOf(target);
|
|
569
574
|
if (parent !== Schema) {
|
|
@@ -584,10 +589,6 @@ class TypeContext {
|
|
|
584
589
|
return context;
|
|
585
590
|
}
|
|
586
591
|
constructor(rootClass) {
|
|
587
|
-
this.types = {};
|
|
588
|
-
this.schemas = new Map();
|
|
589
|
-
this.hasFilters = false;
|
|
590
|
-
this.parentFiltered = {};
|
|
591
592
|
if (rootClass) {
|
|
592
593
|
this.discoverTypes(rootClass);
|
|
593
594
|
}
|
|
@@ -980,25 +981,33 @@ function deleteOperationAtIndex(changeSet, index) {
|
|
|
980
981
|
delete changeSet.indexes[index];
|
|
981
982
|
}
|
|
982
983
|
class ChangeTree {
|
|
984
|
+
ref;
|
|
985
|
+
metadata;
|
|
986
|
+
root;
|
|
987
|
+
parentChain; // Linked list for tracking parents
|
|
988
|
+
/**
|
|
989
|
+
* Whether this structure is parent of a filtered structure.
|
|
990
|
+
*/
|
|
991
|
+
isFiltered = false;
|
|
992
|
+
isVisibilitySharedWithParent; // See test case: 'should not be required to manually call view.add() items to child arrays without @view() tag'
|
|
993
|
+
indexedOperations = {};
|
|
994
|
+
//
|
|
995
|
+
// TODO:
|
|
996
|
+
// try storing the index + operation per item.
|
|
997
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
998
|
+
//
|
|
999
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
1000
|
+
//
|
|
1001
|
+
changes = { indexes: {}, operations: [] };
|
|
1002
|
+
allChanges = { indexes: {}, operations: [] };
|
|
1003
|
+
filteredChanges;
|
|
1004
|
+
allFilteredChanges;
|
|
1005
|
+
indexes; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
|
|
1006
|
+
/**
|
|
1007
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
1008
|
+
*/
|
|
1009
|
+
isNew = true;
|
|
983
1010
|
constructor(ref) {
|
|
984
|
-
/**
|
|
985
|
-
* Whether this structure is parent of a filtered structure.
|
|
986
|
-
*/
|
|
987
|
-
this.isFiltered = false;
|
|
988
|
-
this.indexedOperations = {};
|
|
989
|
-
//
|
|
990
|
-
// TODO:
|
|
991
|
-
// try storing the index + operation per item.
|
|
992
|
-
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
993
|
-
//
|
|
994
|
-
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
995
|
-
//
|
|
996
|
-
this.changes = { indexes: {}, operations: [] };
|
|
997
|
-
this.allChanges = { indexes: {}, operations: [] };
|
|
998
|
-
/**
|
|
999
|
-
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
1000
|
-
*/
|
|
1001
|
-
this.isNew = true;
|
|
1002
1011
|
this.ref = ref;
|
|
1003
1012
|
this.metadata = ref.constructor[Symbol.metadata];
|
|
1004
1013
|
//
|
|
@@ -1470,7 +1479,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
1470
1479
|
// Encode refId for this instance.
|
|
1471
1480
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1472
1481
|
//
|
|
1473
|
-
encode.number(bytes, value[$
|
|
1482
|
+
encode.number(bytes, value[$refId], it);
|
|
1474
1483
|
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
1475
1484
|
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
1476
1485
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
@@ -1481,7 +1490,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
1481
1490
|
// Encode refId for this instance.
|
|
1482
1491
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1483
1492
|
//
|
|
1484
|
-
encode.number(bytes, value[$
|
|
1493
|
+
encode.number(bytes, value[$refId], it);
|
|
1485
1494
|
}
|
|
1486
1495
|
}
|
|
1487
1496
|
/**
|
|
@@ -1557,7 +1566,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
|
|
|
1557
1566
|
if (!item) {
|
|
1558
1567
|
return;
|
|
1559
1568
|
}
|
|
1560
|
-
refOrIndex = item[$
|
|
1569
|
+
refOrIndex = item[$refId];
|
|
1561
1570
|
if (operation === exports.OPERATION.DELETE) {
|
|
1562
1571
|
operation = exports.OPERATION.DELETE_BY_REFID;
|
|
1563
1572
|
}
|
|
@@ -1597,7 +1606,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1597
1606
|
let value;
|
|
1598
1607
|
if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
1599
1608
|
// Flag `refId` for garbage collection.
|
|
1600
|
-
const previousRefId = $
|
|
1609
|
+
const previousRefId = previousValue?.[$refId];
|
|
1601
1610
|
if (previousRefId !== undefined) {
|
|
1602
1611
|
$root.removeRef(previousRefId);
|
|
1603
1612
|
}
|
|
@@ -1638,7 +1647,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1638
1647
|
value = valueRef.clone(true);
|
|
1639
1648
|
value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
|
|
1640
1649
|
if (previousValue) {
|
|
1641
|
-
let previousRefId = $
|
|
1650
|
+
let previousRefId = previousValue[$refId];
|
|
1642
1651
|
if (previousRefId !== undefined && refId !== previousRefId) {
|
|
1643
1652
|
//
|
|
1644
1653
|
// enqueue onRemove if structure has been replaced.
|
|
@@ -1649,7 +1658,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1649
1658
|
const [key, value] = iter.value;
|
|
1650
1659
|
// if value is a schema, remove its reference
|
|
1651
1660
|
if (typeof (value) === "object") {
|
|
1652
|
-
previousRefId = $
|
|
1661
|
+
previousRefId = value[$refId];
|
|
1653
1662
|
$root.removeRef(previousRefId);
|
|
1654
1663
|
}
|
|
1655
1664
|
allChanges.push({
|
|
@@ -1878,7 +1887,6 @@ function assertInstanceType(value, type, instance, field) {
|
|
|
1878
1887
|
}
|
|
1879
1888
|
}
|
|
1880
1889
|
|
|
1881
|
-
var _a$4, _b$4;
|
|
1882
1890
|
const DEFAULT_SORT = (a, b) => {
|
|
1883
1891
|
const A = a.toString();
|
|
1884
1892
|
const B = b.toString();
|
|
@@ -1890,8 +1898,15 @@ const DEFAULT_SORT = (a, b) => {
|
|
|
1890
1898
|
return 0;
|
|
1891
1899
|
};
|
|
1892
1900
|
class ArraySchema {
|
|
1893
|
-
|
|
1894
|
-
|
|
1901
|
+
[$changes];
|
|
1902
|
+
[$refId];
|
|
1903
|
+
[$childType];
|
|
1904
|
+
items = [];
|
|
1905
|
+
tmpItems = [];
|
|
1906
|
+
deletedIndexes = {};
|
|
1907
|
+
isMovingItems = false;
|
|
1908
|
+
static [$encoder] = encodeArray;
|
|
1909
|
+
static [$decoder] = decodeArray;
|
|
1895
1910
|
/**
|
|
1896
1911
|
* Determine if a property must be filtered.
|
|
1897
1912
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -1901,7 +1916,7 @@ class ArraySchema {
|
|
|
1901
1916
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
1902
1917
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
1903
1918
|
*/
|
|
1904
|
-
static [
|
|
1919
|
+
static [$filter](ref, index, view) {
|
|
1905
1920
|
return (!view ||
|
|
1906
1921
|
typeof (ref[$childType]) === "string" ||
|
|
1907
1922
|
view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
|
|
@@ -1917,10 +1932,6 @@ class ArraySchema {
|
|
|
1917
1932
|
return new ArraySchema(...Array.from(iterable));
|
|
1918
1933
|
}
|
|
1919
1934
|
constructor(...items) {
|
|
1920
|
-
this.items = [];
|
|
1921
|
-
this.tmpItems = [];
|
|
1922
|
-
this.deletedIndexes = {};
|
|
1923
|
-
this.isMovingItems = false;
|
|
1924
1935
|
Object.defineProperty(this, $childType, {
|
|
1925
1936
|
value: undefined,
|
|
1926
1937
|
enumerable: false,
|
|
@@ -2437,6 +2448,10 @@ class ArraySchema {
|
|
|
2437
2448
|
static get [Symbol.species]() {
|
|
2438
2449
|
return ArraySchema;
|
|
2439
2450
|
}
|
|
2451
|
+
// WORKAROUND for compatibility
|
|
2452
|
+
// - TypeScript 4 defines @@unscopables as a function
|
|
2453
|
+
// - TypeScript 5 defines @@unscopables as an object
|
|
2454
|
+
[Symbol.unscopables];
|
|
2440
2455
|
/**
|
|
2441
2456
|
* Returns an iterable of key, value pairs for every entry in the array
|
|
2442
2457
|
*/
|
|
@@ -2546,7 +2561,7 @@ class ArraySchema {
|
|
|
2546
2561
|
this.isMovingItems = false;
|
|
2547
2562
|
return this;
|
|
2548
2563
|
}
|
|
2549
|
-
[
|
|
2564
|
+
[$getByIndex](index, isEncodeAll = false) {
|
|
2550
2565
|
//
|
|
2551
2566
|
// TODO: avoid unecessary `this.tmpItems` check during decoding.
|
|
2552
2567
|
//
|
|
@@ -2601,10 +2616,16 @@ class ArraySchema {
|
|
|
2601
2616
|
}
|
|
2602
2617
|
registerType("array", { constructor: ArraySchema });
|
|
2603
2618
|
|
|
2604
|
-
var _a$3, _b$3;
|
|
2605
2619
|
class MapSchema {
|
|
2606
|
-
|
|
2607
|
-
|
|
2620
|
+
[$changes];
|
|
2621
|
+
[$refId];
|
|
2622
|
+
childType;
|
|
2623
|
+
[$childType];
|
|
2624
|
+
$items = new Map();
|
|
2625
|
+
$indexes = new Map();
|
|
2626
|
+
deletedItems = {};
|
|
2627
|
+
static [$encoder] = encodeKeyValueOperation;
|
|
2628
|
+
static [$decoder] = decodeKeyValueOperation;
|
|
2608
2629
|
/**
|
|
2609
2630
|
* Determine if a property must be filtered.
|
|
2610
2631
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2614,7 +2635,7 @@ class MapSchema {
|
|
|
2614
2635
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2615
2636
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2616
2637
|
*/
|
|
2617
|
-
static [
|
|
2638
|
+
static [$filter](ref, index, view) {
|
|
2618
2639
|
return (!view ||
|
|
2619
2640
|
typeof (ref[$childType]) === "string" ||
|
|
2620
2641
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2623,9 +2644,6 @@ class MapSchema {
|
|
|
2623
2644
|
return type['map'] !== undefined;
|
|
2624
2645
|
}
|
|
2625
2646
|
constructor(initialValues) {
|
|
2626
|
-
this.$items = new Map();
|
|
2627
|
-
this.$indexes = new Map();
|
|
2628
|
-
this.deletedItems = {};
|
|
2629
2647
|
const changeTree = new ChangeTree(this);
|
|
2630
2648
|
changeTree.indexes = {};
|
|
2631
2649
|
Object.defineProperty(this, $changes, {
|
|
@@ -2816,10 +2834,16 @@ class MapSchema {
|
|
|
2816
2834
|
}
|
|
2817
2835
|
registerType("map", { constructor: MapSchema });
|
|
2818
2836
|
|
|
2819
|
-
var _a$2, _b$2;
|
|
2820
2837
|
class CollectionSchema {
|
|
2821
|
-
|
|
2822
|
-
|
|
2838
|
+
[$changes];
|
|
2839
|
+
[$refId];
|
|
2840
|
+
[$childType];
|
|
2841
|
+
$items = new Map();
|
|
2842
|
+
$indexes = new Map();
|
|
2843
|
+
deletedItems = {};
|
|
2844
|
+
$refId = 0;
|
|
2845
|
+
static [$encoder] = encodeKeyValueOperation;
|
|
2846
|
+
static [$decoder] = decodeKeyValueOperation;
|
|
2823
2847
|
/**
|
|
2824
2848
|
* Determine if a property must be filtered.
|
|
2825
2849
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2829,7 +2853,7 @@ class CollectionSchema {
|
|
|
2829
2853
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2830
2854
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2831
2855
|
*/
|
|
2832
|
-
static [
|
|
2856
|
+
static [$filter](ref, index, view) {
|
|
2833
2857
|
return (!view ||
|
|
2834
2858
|
typeof (ref[$childType]) === "string" ||
|
|
2835
2859
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2838,10 +2862,6 @@ class CollectionSchema {
|
|
|
2838
2862
|
return type['collection'] !== undefined;
|
|
2839
2863
|
}
|
|
2840
2864
|
constructor(initialValues) {
|
|
2841
|
-
this.$items = new Map();
|
|
2842
|
-
this.$indexes = new Map();
|
|
2843
|
-
this.deletedItems = {};
|
|
2844
|
-
this.$refId = 0;
|
|
2845
2865
|
this[$changes] = new ChangeTree(this);
|
|
2846
2866
|
this[$changes].indexes = {};
|
|
2847
2867
|
if (initialValues) {
|
|
@@ -2980,10 +3000,16 @@ class CollectionSchema {
|
|
|
2980
3000
|
}
|
|
2981
3001
|
registerType("collection", { constructor: CollectionSchema, });
|
|
2982
3002
|
|
|
2983
|
-
var _a$1, _b$1;
|
|
2984
3003
|
class SetSchema {
|
|
2985
|
-
|
|
2986
|
-
|
|
3004
|
+
[$changes];
|
|
3005
|
+
[$refId];
|
|
3006
|
+
[$childType];
|
|
3007
|
+
$items = new Map();
|
|
3008
|
+
$indexes = new Map();
|
|
3009
|
+
deletedItems = {};
|
|
3010
|
+
$refId = 0;
|
|
3011
|
+
static [$encoder] = encodeKeyValueOperation;
|
|
3012
|
+
static [$decoder] = decodeKeyValueOperation;
|
|
2987
3013
|
/**
|
|
2988
3014
|
* Determine if a property must be filtered.
|
|
2989
3015
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2993,7 +3019,7 @@ class SetSchema {
|
|
|
2993
3019
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2994
3020
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2995
3021
|
*/
|
|
2996
|
-
static [
|
|
3022
|
+
static [$filter](ref, index, view) {
|
|
2997
3023
|
return (!view ||
|
|
2998
3024
|
typeof (ref[$childType]) === "string" ||
|
|
2999
3025
|
view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -3002,10 +3028,6 @@ class SetSchema {
|
|
|
3002
3028
|
return type['set'] !== undefined;
|
|
3003
3029
|
}
|
|
3004
3030
|
constructor(initialValues) {
|
|
3005
|
-
this.$items = new Map();
|
|
3006
|
-
this.$indexes = new Map();
|
|
3007
|
-
this.deletedItems = {};
|
|
3008
|
-
this.$refId = 0;
|
|
3009
3031
|
this[$changes] = new ChangeTree(this);
|
|
3010
3032
|
this[$changes].indexes = {};
|
|
3011
3033
|
if (initialValues) {
|
|
@@ -3504,7 +3526,10 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3504
3526
|
? DEFAULT_VIEW_TAG
|
|
3505
3527
|
: value['view'];
|
|
3506
3528
|
}
|
|
3507
|
-
|
|
3529
|
+
// allow to define a field as not synced
|
|
3530
|
+
if (value['sync'] !== false) {
|
|
3531
|
+
fields[fieldName] = getNormalizedType(value);
|
|
3532
|
+
}
|
|
3508
3533
|
// If no explicit default provided, handle automatic instantiation for collection types
|
|
3509
3534
|
if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
|
|
3510
3535
|
// TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
|
|
@@ -3526,12 +3551,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3526
3551
|
}
|
|
3527
3552
|
else if (value['type'] !== undefined && Schema.is(value['type'])) {
|
|
3528
3553
|
// Direct Schema type: Type → new Type()
|
|
3529
|
-
|
|
3530
|
-
// only auto-initialize Schema instances if:
|
|
3531
|
-
// - they don't have an initialize method
|
|
3532
|
-
// - or initialize method doesn't accept any parameters
|
|
3533
|
-
defaultValues[fieldName] = new value['type']();
|
|
3534
|
-
}
|
|
3554
|
+
defaultValues[fieldName] = new value['type']();
|
|
3535
3555
|
}
|
|
3536
3556
|
}
|
|
3537
3557
|
else {
|
|
@@ -3541,12 +3561,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3541
3561
|
else if (typeof (value) === "function") {
|
|
3542
3562
|
if (Schema.is(value)) {
|
|
3543
3563
|
// Direct Schema type: Type → new Type()
|
|
3544
|
-
|
|
3545
|
-
// only auto-initialize Schema instances if:
|
|
3546
|
-
// - they don't have an initialize method
|
|
3547
|
-
// - or initialize method doesn't accept any parameters
|
|
3548
|
-
defaultValues[fieldName] = new value();
|
|
3549
|
-
}
|
|
3564
|
+
defaultValues[fieldName] = new value();
|
|
3550
3565
|
fields[fieldName] = getNormalizedType(value);
|
|
3551
3566
|
}
|
|
3552
3567
|
else {
|
|
@@ -3573,32 +3588,13 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3573
3588
|
}
|
|
3574
3589
|
return defaults;
|
|
3575
3590
|
};
|
|
3576
|
-
const getParentProps = (props) => {
|
|
3577
|
-
const fieldNames = Object.keys(fields);
|
|
3578
|
-
const parentProps = {};
|
|
3579
|
-
for (const key in props) {
|
|
3580
|
-
if (!fieldNames.includes(key)) {
|
|
3581
|
-
parentProps[key] = props[key];
|
|
3582
|
-
}
|
|
3583
|
-
}
|
|
3584
|
-
return parentProps;
|
|
3585
|
-
};
|
|
3586
3591
|
/** @codegen-ignore */
|
|
3587
3592
|
const klass = Metadata.setFields(class extends inherits {
|
|
3588
3593
|
constructor(...args) {
|
|
3594
|
+
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3589
3595
|
// call initialize method
|
|
3590
3596
|
if (methods.initialize && typeof methods.initialize === 'function') {
|
|
3591
|
-
|
|
3592
|
-
/**
|
|
3593
|
-
* only call initialize() in the current class, not the parent ones.
|
|
3594
|
-
* see "should not call initialize automatically when creating an instance of inherited Schema"
|
|
3595
|
-
*/
|
|
3596
|
-
if (new.target === klass) {
|
|
3597
|
-
methods.initialize.apply(this, args);
|
|
3598
|
-
}
|
|
3599
|
-
}
|
|
3600
|
-
else {
|
|
3601
|
-
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3597
|
+
methods.initialize.apply(this, args);
|
|
3602
3598
|
}
|
|
3603
3599
|
}
|
|
3604
3600
|
}, fields);
|
|
@@ -3635,7 +3631,7 @@ function dumpChanges(schema) {
|
|
|
3635
3631
|
continue;
|
|
3636
3632
|
}
|
|
3637
3633
|
const changes = changeTree.indexedOperations;
|
|
3638
|
-
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3634
|
+
dump.refs.push(`refId#${changeTree.ref[$refId]}`);
|
|
3639
3635
|
for (const index in changes) {
|
|
3640
3636
|
const op = changes[index];
|
|
3641
3637
|
const opName = exports.OPERATION[op];
|
|
@@ -3649,13 +3645,14 @@ function dumpChanges(schema) {
|
|
|
3649
3645
|
return dump;
|
|
3650
3646
|
}
|
|
3651
3647
|
|
|
3652
|
-
var _a, _b;
|
|
3653
3648
|
/**
|
|
3654
3649
|
* Schema encoder / decoder
|
|
3655
3650
|
*/
|
|
3656
3651
|
class Schema {
|
|
3657
|
-
static
|
|
3658
|
-
static
|
|
3652
|
+
static [Symbol.metadata];
|
|
3653
|
+
static [$encoder] = encodeSchemaOperation;
|
|
3654
|
+
static [$decoder] = decodeSchemaOperation;
|
|
3655
|
+
[$refId];
|
|
3659
3656
|
/**
|
|
3660
3657
|
* Assign the property descriptors required to track changes on this instance.
|
|
3661
3658
|
* @param instance
|
|
@@ -3674,7 +3671,7 @@ class Schema {
|
|
|
3674
3671
|
/**
|
|
3675
3672
|
* Track property changes
|
|
3676
3673
|
*/
|
|
3677
|
-
static [
|
|
3674
|
+
static [$track](changeTree, index, operation = exports.OPERATION.ADD) {
|
|
3678
3675
|
changeTree.change(index, operation);
|
|
3679
3676
|
}
|
|
3680
3677
|
/**
|
|
@@ -3721,10 +3718,74 @@ class Schema {
|
|
|
3721
3718
|
Object.assign(this, arg);
|
|
3722
3719
|
}
|
|
3723
3720
|
}
|
|
3721
|
+
/**
|
|
3722
|
+
* Assign properties to the instance.
|
|
3723
|
+
* @param props Properties to assign to the instance
|
|
3724
|
+
* @returns
|
|
3725
|
+
*/
|
|
3724
3726
|
assign(props) {
|
|
3725
3727
|
Object.assign(this, props);
|
|
3726
3728
|
return this;
|
|
3727
3729
|
}
|
|
3730
|
+
/**
|
|
3731
|
+
* Restore the instance from JSON data.
|
|
3732
|
+
* @param jsonData JSON data to restore the instance from
|
|
3733
|
+
* @returns
|
|
3734
|
+
*/
|
|
3735
|
+
restore(jsonData) {
|
|
3736
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3737
|
+
for (const fieldIndex in metadata) {
|
|
3738
|
+
const field = metadata[fieldIndex];
|
|
3739
|
+
const fieldName = field.name;
|
|
3740
|
+
const fieldType = field.type;
|
|
3741
|
+
const value = jsonData[fieldName];
|
|
3742
|
+
if (value === undefined || value === null) {
|
|
3743
|
+
continue;
|
|
3744
|
+
}
|
|
3745
|
+
if (typeof fieldType === "string") {
|
|
3746
|
+
// Primitive type: assign directly
|
|
3747
|
+
this[fieldName] = value;
|
|
3748
|
+
}
|
|
3749
|
+
else if (Schema.is(fieldType)) {
|
|
3750
|
+
// Schema type: create instance and restore
|
|
3751
|
+
const instance = new fieldType();
|
|
3752
|
+
instance.restore(value);
|
|
3753
|
+
this[fieldName] = instance;
|
|
3754
|
+
}
|
|
3755
|
+
else if (typeof fieldType === "object") {
|
|
3756
|
+
// Collection types: { map: ... }, { array: ... }, etc.
|
|
3757
|
+
const collectionType = Object.keys(fieldType)[0];
|
|
3758
|
+
const childType = fieldType[collectionType];
|
|
3759
|
+
if (collectionType === "map") {
|
|
3760
|
+
const mapSchema = this[fieldName];
|
|
3761
|
+
for (const key in value) {
|
|
3762
|
+
if (Schema.is(childType)) {
|
|
3763
|
+
const childInstance = new childType();
|
|
3764
|
+
childInstance.restore(value[key]);
|
|
3765
|
+
mapSchema.set(key, childInstance);
|
|
3766
|
+
}
|
|
3767
|
+
else {
|
|
3768
|
+
mapSchema.set(key, value[key]);
|
|
3769
|
+
}
|
|
3770
|
+
}
|
|
3771
|
+
}
|
|
3772
|
+
else if (collectionType === "array") {
|
|
3773
|
+
const arraySchema = this[fieldName];
|
|
3774
|
+
for (let i = 0; i < value.length; i++) {
|
|
3775
|
+
if (Schema.is(childType)) {
|
|
3776
|
+
const childInstance = new childType();
|
|
3777
|
+
childInstance.restore(value[i]);
|
|
3778
|
+
arraySchema.push(childInstance);
|
|
3779
|
+
}
|
|
3780
|
+
else {
|
|
3781
|
+
arraySchema.push(value[i]);
|
|
3782
|
+
}
|
|
3783
|
+
}
|
|
3784
|
+
}
|
|
3785
|
+
}
|
|
3786
|
+
}
|
|
3787
|
+
return this;
|
|
3788
|
+
}
|
|
3728
3789
|
/**
|
|
3729
3790
|
* (Server-side): Flag a property to be encoded for the next patch.
|
|
3730
3791
|
* @param instance Schema instance
|
|
@@ -3797,7 +3858,7 @@ class Schema {
|
|
|
3797
3858
|
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3798
3859
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3799
3860
|
const changeTree = ref[$changes];
|
|
3800
|
-
const refId =
|
|
3861
|
+
const refId = ref[$refId];
|
|
3801
3862
|
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3802
3863
|
// log reference count if > 1
|
|
3803
3864
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
@@ -3820,7 +3881,7 @@ class Schema {
|
|
|
3820
3881
|
let current = ref[$changes].root[changeSet].next;
|
|
3821
3882
|
while (current) {
|
|
3822
3883
|
if (current.changeTree) {
|
|
3823
|
-
encodeOrder.push(current.changeTree.refId);
|
|
3884
|
+
encodeOrder.push(current.changeTree.ref[$refId]);
|
|
3824
3885
|
}
|
|
3825
3886
|
current = current.next;
|
|
3826
3887
|
}
|
|
@@ -3841,7 +3902,7 @@ class Schema {
|
|
|
3841
3902
|
const changeTree = instance[$changes];
|
|
3842
3903
|
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3843
3904
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3844
|
-
let output = `${instance.constructor.name} (${
|
|
3905
|
+
let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
|
|
3845
3906
|
function dumpChangeSet(changeSet) {
|
|
3846
3907
|
changeSet.operations
|
|
3847
3908
|
.filter(op => op)
|
|
@@ -3855,14 +3916,14 @@ class Schema {
|
|
|
3855
3916
|
if (!isEncodeAll &&
|
|
3856
3917
|
changeTree.filteredChanges &&
|
|
3857
3918
|
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3858
|
-
output += `${instance.constructor.name} (${
|
|
3919
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
|
|
3859
3920
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3860
3921
|
}
|
|
3861
3922
|
// display filtered changes
|
|
3862
3923
|
if (isEncodeAll &&
|
|
3863
3924
|
changeTree.allFilteredChanges &&
|
|
3864
3925
|
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3865
|
-
output += `${instance.constructor.name} (${
|
|
3926
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
|
|
3866
3927
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3867
3928
|
}
|
|
3868
3929
|
return output;
|
|
@@ -3897,13 +3958,13 @@ class Schema {
|
|
|
3897
3958
|
}
|
|
3898
3959
|
}
|
|
3899
3960
|
if (includeChangeTree) {
|
|
3900
|
-
instanceRefIds.push(changeTree.refId);
|
|
3961
|
+
instanceRefIds.push(changeTree.ref[$refId]);
|
|
3901
3962
|
totalOperations += Object.keys(changes).length;
|
|
3902
3963
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3903
3964
|
}
|
|
3904
3965
|
}
|
|
3905
3966
|
output += "---\n";
|
|
3906
|
-
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3967
|
+
output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
|
|
3907
3968
|
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3908
3969
|
output += `Total changes: ${totalOperations}\n`;
|
|
3909
3970
|
output += "---\n";
|
|
@@ -3912,7 +3973,7 @@ class Schema {
|
|
|
3912
3973
|
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3913
3974
|
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3914
3975
|
if (!visitedParents.has(parentChangeTree)) {
|
|
3915
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3976
|
+
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
|
|
3916
3977
|
visitedParents.add(parentChangeTree);
|
|
3917
3978
|
}
|
|
3918
3979
|
});
|
|
@@ -3920,7 +3981,7 @@ class Schema {
|
|
|
3920
3981
|
const level = parentChangeTrees.length;
|
|
3921
3982
|
const indent = getIndent(level);
|
|
3922
3983
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3923
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3984
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.ref[$refId]}) - changes: ${Object.keys(changes).length}\n`;
|
|
3924
3985
|
for (const index in changes) {
|
|
3925
3986
|
const operation = changes[index];
|
|
3926
3987
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
@@ -3930,61 +3991,35 @@ class Schema {
|
|
|
3930
3991
|
}
|
|
3931
3992
|
}
|
|
3932
3993
|
|
|
3933
|
-
/******************************************************************************
|
|
3934
|
-
Copyright (c) Microsoft Corporation.
|
|
3935
|
-
|
|
3936
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
3937
|
-
purpose with or without fee is hereby granted.
|
|
3938
|
-
|
|
3939
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
3940
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
3941
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
3942
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
3943
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
3944
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3945
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
3946
|
-
***************************************************************************** */
|
|
3947
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
3948
|
-
|
|
3949
|
-
|
|
3950
|
-
function __decorate(decorators, target, key, desc) {
|
|
3951
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3952
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3953
|
-
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;
|
|
3954
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3955
|
-
}
|
|
3956
|
-
|
|
3957
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
3958
|
-
var e = new Error(message);
|
|
3959
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3960
|
-
};
|
|
3961
|
-
|
|
3962
3994
|
class Root {
|
|
3995
|
+
types;
|
|
3996
|
+
nextUniqueId = 0;
|
|
3997
|
+
refCount = {};
|
|
3998
|
+
changeTrees = {};
|
|
3999
|
+
// all changes
|
|
4000
|
+
allChanges = createChangeTreeList();
|
|
4001
|
+
allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
4002
|
+
// pending changes to be encoded
|
|
4003
|
+
changes = createChangeTreeList();
|
|
4004
|
+
filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3963
4005
|
constructor(types) {
|
|
3964
4006
|
this.types = types;
|
|
3965
|
-
this.nextUniqueId = 0;
|
|
3966
|
-
this.refCount = {};
|
|
3967
|
-
this.changeTrees = {};
|
|
3968
|
-
// all changes
|
|
3969
|
-
this.allChanges = createChangeTreeList();
|
|
3970
|
-
this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3971
|
-
// pending changes to be encoded
|
|
3972
|
-
this.changes = createChangeTreeList();
|
|
3973
|
-
this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3974
4007
|
}
|
|
3975
4008
|
getNextUniqueId() {
|
|
3976
4009
|
return this.nextUniqueId++;
|
|
3977
4010
|
}
|
|
3978
4011
|
add(changeTree) {
|
|
3979
|
-
|
|
3980
|
-
|
|
3981
|
-
|
|
4012
|
+
const ref = changeTree.ref;
|
|
4013
|
+
// Assign unique `refId` to ref if it doesn't have one yet.
|
|
4014
|
+
if (ref[$refId] === undefined) {
|
|
4015
|
+
ref[$refId] = this.getNextUniqueId();
|
|
3982
4016
|
}
|
|
3983
|
-
const
|
|
4017
|
+
const refId = ref[$refId];
|
|
4018
|
+
const isNewChangeTree = (this.changeTrees[refId] === undefined);
|
|
3984
4019
|
if (isNewChangeTree) {
|
|
3985
|
-
this.changeTrees[
|
|
4020
|
+
this.changeTrees[refId] = changeTree;
|
|
3986
4021
|
}
|
|
3987
|
-
const previousRefCount = this.refCount[
|
|
4022
|
+
const previousRefCount = this.refCount[refId];
|
|
3988
4023
|
if (previousRefCount === 0) {
|
|
3989
4024
|
//
|
|
3990
4025
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
@@ -3997,30 +4032,31 @@ class Root {
|
|
|
3997
4032
|
setOperationAtIndex(changeTree.changes, len);
|
|
3998
4033
|
}
|
|
3999
4034
|
}
|
|
4000
|
-
this.refCount[
|
|
4001
|
-
// console.log("ADD", { refId
|
|
4035
|
+
this.refCount[refId] = (previousRefCount || 0) + 1;
|
|
4036
|
+
// console.log("ADD", { refId, ref: ref.constructor.name, refCount: this.refCount[refId], isNewChangeTree });
|
|
4002
4037
|
return isNewChangeTree;
|
|
4003
4038
|
}
|
|
4004
4039
|
remove(changeTree) {
|
|
4005
|
-
const
|
|
4006
|
-
|
|
4040
|
+
const refId = changeTree.ref[$refId];
|
|
4041
|
+
const refCount = (this.refCount[refId]) - 1;
|
|
4042
|
+
// console.log("REMOVE", { refId, ref: changeTree.ref.constructor.name, refCount, needRemove: refCount <= 0 });
|
|
4007
4043
|
if (refCount <= 0) {
|
|
4008
4044
|
//
|
|
4009
4045
|
// Only remove "root" reference if it's the last reference
|
|
4010
4046
|
//
|
|
4011
4047
|
changeTree.root = undefined;
|
|
4012
|
-
delete this.changeTrees[
|
|
4048
|
+
delete this.changeTrees[refId];
|
|
4013
4049
|
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
4014
4050
|
this.removeChangeFromChangeSet("changes", changeTree);
|
|
4015
4051
|
if (changeTree.filteredChanges) {
|
|
4016
4052
|
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
4017
4053
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
4018
4054
|
}
|
|
4019
|
-
this.refCount[
|
|
4055
|
+
this.refCount[refId] = 0;
|
|
4020
4056
|
changeTree.forEachChild((child, _) => {
|
|
4021
4057
|
if (child.removeParent(changeTree.ref)) {
|
|
4022
4058
|
if ((child.parentChain === undefined || // no parent, remove it
|
|
4023
|
-
(child.parentChain && this.refCount[child.refId] > 0) // parent is still in use, but has more than one reference, remove it
|
|
4059
|
+
(child.parentChain && this.refCount[child.ref[$refId]] > 0) // parent is still in use, but has more than one reference, remove it
|
|
4024
4060
|
)) {
|
|
4025
4061
|
this.remove(child);
|
|
4026
4062
|
}
|
|
@@ -4032,7 +4068,7 @@ class Root {
|
|
|
4032
4068
|
});
|
|
4033
4069
|
}
|
|
4034
4070
|
else {
|
|
4035
|
-
this.refCount[
|
|
4071
|
+
this.refCount[refId] = refCount;
|
|
4036
4072
|
//
|
|
4037
4073
|
// When losing a reference to an instance, it is best to move the
|
|
4038
4074
|
// ChangeTree next to its parent in the encoding queue.
|
|
@@ -4182,10 +4218,19 @@ class Root {
|
|
|
4182
4218
|
}
|
|
4183
4219
|
}
|
|
4184
4220
|
|
|
4221
|
+
function concatBytes(a, b) {
|
|
4222
|
+
const result = new Uint8Array(a.length + b.length);
|
|
4223
|
+
result.set(a, 0);
|
|
4224
|
+
result.set(b, a.length);
|
|
4225
|
+
return result;
|
|
4226
|
+
}
|
|
4185
4227
|
class Encoder {
|
|
4186
|
-
static
|
|
4228
|
+
static BUFFER_SIZE = 8 * 1024; // 8KB
|
|
4229
|
+
sharedBuffer = new Uint8Array(Encoder.BUFFER_SIZE);
|
|
4230
|
+
context;
|
|
4231
|
+
state;
|
|
4232
|
+
root;
|
|
4187
4233
|
constructor(state) {
|
|
4188
|
-
this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
|
|
4189
4234
|
//
|
|
4190
4235
|
// Use .cache() here to avoid re-creating a new context for every new room instance.
|
|
4191
4236
|
//
|
|
@@ -4213,7 +4258,7 @@ class Encoder {
|
|
|
4213
4258
|
const changeTree = current.changeTree;
|
|
4214
4259
|
if (hasView) {
|
|
4215
4260
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
4216
|
-
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
|
|
4261
|
+
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.ref[$refId], raw: changeTree.ref.toJSON() });
|
|
4217
4262
|
view.invisible.add(changeTree);
|
|
4218
4263
|
continue; // skip this change tree
|
|
4219
4264
|
}
|
|
@@ -4234,7 +4279,7 @@ class Encoder {
|
|
|
4234
4279
|
// (unless it "hasView", which will need to revisit the root)
|
|
4235
4280
|
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
4236
4281
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4237
|
-
encode.number(buffer,
|
|
4282
|
+
encode.number(buffer, ref[$refId], it);
|
|
4238
4283
|
}
|
|
4239
4284
|
for (let j = 0; j < numChanges; j++) {
|
|
4240
4285
|
const fieldIndex = changeSet.operations[j];
|
|
@@ -4263,10 +4308,9 @@ class Encoder {
|
|
|
4263
4308
|
}
|
|
4264
4309
|
}
|
|
4265
4310
|
if (it.offset > buffer.byteLength) {
|
|
4266
|
-
// we can assume that n + 1
|
|
4267
|
-
// multiples of
|
|
4268
|
-
|
|
4269
|
-
const newSize = Math.ceil(it.offset / (Buffer.poolSize ?? 8 * 1024)) * (Buffer.poolSize ?? 8 * 1024);
|
|
4311
|
+
// we can assume that n + 1 BUFFER_SIZE will suffice given that we are likely done with encoding at this point
|
|
4312
|
+
// multiples of BUFFER_SIZE are faster to allocate than arbitrary sizes
|
|
4313
|
+
const newSize = Math.ceil(it.offset / Encoder.BUFFER_SIZE) * Encoder.BUFFER_SIZE;
|
|
4270
4314
|
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
4271
4315
|
|
|
4272
4316
|
import { Encoder } from "@colyseus/schema";
|
|
@@ -4276,7 +4320,9 @@ class Encoder {
|
|
|
4276
4320
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
4277
4321
|
// -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
|
|
4278
4322
|
//
|
|
4279
|
-
|
|
4323
|
+
const newBuffer = new Uint8Array(newSize);
|
|
4324
|
+
newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
|
|
4325
|
+
buffer = newBuffer;
|
|
4280
4326
|
// assign resized buffer to local sharedBuffer
|
|
4281
4327
|
if (buffer === this.sharedBuffer) {
|
|
4282
4328
|
this.sharedBuffer = buffer;
|
|
@@ -4294,10 +4340,7 @@ class Encoder {
|
|
|
4294
4340
|
const viewOffset = it.offset;
|
|
4295
4341
|
// try to encode "filtered" changes
|
|
4296
4342
|
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4297
|
-
return
|
|
4298
|
-
bytes.subarray(0, sharedOffset),
|
|
4299
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4300
|
-
]);
|
|
4343
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4301
4344
|
}
|
|
4302
4345
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4303
4346
|
const viewOffset = it.offset;
|
|
@@ -4321,7 +4364,7 @@ class Encoder {
|
|
|
4321
4364
|
const encoder = ctor[$encoder];
|
|
4322
4365
|
const metadata = ctor[Symbol.metadata];
|
|
4323
4366
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4324
|
-
encode.number(bytes,
|
|
4367
|
+
encode.number(bytes, ref[$refId], it);
|
|
4325
4368
|
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
4326
4369
|
const index = Number(keys[i]);
|
|
4327
4370
|
// 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")
|
|
@@ -4340,10 +4383,7 @@ class Encoder {
|
|
|
4340
4383
|
view.changes.clear();
|
|
4341
4384
|
// try to encode "filtered" changes
|
|
4342
4385
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4343
|
-
return
|
|
4344
|
-
bytes.subarray(0, sharedOffset),
|
|
4345
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4346
|
-
]);
|
|
4386
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4347
4387
|
}
|
|
4348
4388
|
discardChanges() {
|
|
4349
4389
|
// discard shared changes
|
|
@@ -4399,25 +4439,22 @@ class DecodingWarning extends Error {
|
|
|
4399
4439
|
}
|
|
4400
4440
|
}
|
|
4401
4441
|
class ReferenceTracker {
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
this.callbacks = {};
|
|
4412
|
-
this.nextUniqueId = 0;
|
|
4413
|
-
}
|
|
4442
|
+
//
|
|
4443
|
+
// Relation of refId => Schema structure
|
|
4444
|
+
// For direct access of structures during decoding time.
|
|
4445
|
+
//
|
|
4446
|
+
refs = new Map();
|
|
4447
|
+
refCount = {};
|
|
4448
|
+
deletedRefs = new Set();
|
|
4449
|
+
callbacks = {};
|
|
4450
|
+
nextUniqueId = 0;
|
|
4414
4451
|
getNextUniqueId() {
|
|
4415
4452
|
return this.nextUniqueId++;
|
|
4416
4453
|
}
|
|
4417
4454
|
// for decoding
|
|
4418
4455
|
addRef(refId, ref, incrementCount = true) {
|
|
4419
4456
|
this.refs.set(refId, ref);
|
|
4420
|
-
|
|
4457
|
+
ref[$refId] = refId;
|
|
4421
4458
|
if (incrementCount) {
|
|
4422
4459
|
this.refCount[refId] = (this.refCount[refId] || 0) + 1;
|
|
4423
4460
|
}
|
|
@@ -4474,9 +4511,12 @@ class ReferenceTracker {
|
|
|
4474
4511
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4475
4512
|
for (const index in metadata) {
|
|
4476
4513
|
const field = metadata[index].name;
|
|
4477
|
-
const
|
|
4478
|
-
if (
|
|
4479
|
-
|
|
4514
|
+
const child = ref[field];
|
|
4515
|
+
if (typeof (child) === "object" && child) {
|
|
4516
|
+
const childRefId = child[$refId];
|
|
4517
|
+
if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
|
|
4518
|
+
this.removeRef(childRefId);
|
|
4519
|
+
}
|
|
4480
4520
|
}
|
|
4481
4521
|
}
|
|
4482
4522
|
}
|
|
@@ -4484,8 +4524,8 @@ class ReferenceTracker {
|
|
|
4484
4524
|
if (typeof (ref[$childType]) === "function") {
|
|
4485
4525
|
Array.from(ref.values())
|
|
4486
4526
|
.forEach((child) => {
|
|
4487
|
-
const childRefId =
|
|
4488
|
-
if (!this.deletedRefs.has(childRefId)) {
|
|
4527
|
+
const childRefId = child[$refId];
|
|
4528
|
+
if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
|
|
4489
4529
|
this.removeRef(childRefId);
|
|
4490
4530
|
}
|
|
4491
4531
|
});
|
|
@@ -4523,8 +4563,12 @@ class ReferenceTracker {
|
|
|
4523
4563
|
}
|
|
4524
4564
|
|
|
4525
4565
|
class Decoder {
|
|
4566
|
+
context;
|
|
4567
|
+
state;
|
|
4568
|
+
root;
|
|
4569
|
+
currentRefId = 0;
|
|
4570
|
+
triggerChanges;
|
|
4526
4571
|
constructor(root, context) {
|
|
4527
|
-
this.currentRefId = 0;
|
|
4528
4572
|
this.setState(root);
|
|
4529
4573
|
this.context = context || new TypeContext(root.constructor);
|
|
4530
4574
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
@@ -4613,7 +4657,7 @@ class Decoder {
|
|
|
4613
4657
|
}
|
|
4614
4658
|
removeChildRefs(ref, allChanges) {
|
|
4615
4659
|
const needRemoveRef = typeof (ref[$childType]) !== "string";
|
|
4616
|
-
const refId =
|
|
4660
|
+
const refId = ref[$refId];
|
|
4617
4661
|
ref.forEach((value, key) => {
|
|
4618
4662
|
allChanges.push({
|
|
4619
4663
|
ref: ref,
|
|
@@ -4624,7 +4668,7 @@ class Decoder {
|
|
|
4624
4668
|
previousValue: value
|
|
4625
4669
|
});
|
|
4626
4670
|
if (needRemoveRef) {
|
|
4627
|
-
this.root.removeRef(
|
|
4671
|
+
this.root.removeRef(value[$refId]);
|
|
4628
4672
|
}
|
|
4629
4673
|
});
|
|
4630
4674
|
}
|
|
@@ -4633,217 +4677,182 @@ class Decoder {
|
|
|
4633
4677
|
/**
|
|
4634
4678
|
* Reflection
|
|
4635
4679
|
*/
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
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
|
-
* @returns
|
|
4673
|
-
*/
|
|
4674
|
-
static encode(encoder, it = { offset: 0 }) {
|
|
4675
|
-
const context = encoder.context;
|
|
4676
|
-
const reflection = new Reflection();
|
|
4677
|
-
const reflectionEncoder = new Encoder(reflection);
|
|
4678
|
-
// rootType is usually the first schema passed to the Encoder
|
|
4679
|
-
// (unless it inherits from another schema)
|
|
4680
|
-
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4681
|
-
if (rootType > 0) {
|
|
4682
|
-
reflection.rootType = rootType;
|
|
4683
|
-
}
|
|
4684
|
-
const includedTypeIds = new Set();
|
|
4685
|
-
const pendingReflectionTypes = {};
|
|
4686
|
-
// add type to reflection in a way that respects inheritance
|
|
4687
|
-
// (parent types should be added before their children)
|
|
4688
|
-
const addType = (type) => {
|
|
4689
|
-
if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
|
|
4690
|
-
includedTypeIds.add(type.id);
|
|
4691
|
-
reflection.types.push(type);
|
|
4692
|
-
const deps = pendingReflectionTypes[type.id];
|
|
4693
|
-
if (deps !== undefined) {
|
|
4694
|
-
delete pendingReflectionTypes[type.id];
|
|
4695
|
-
deps.forEach((childType) => addType(childType));
|
|
4696
|
-
}
|
|
4680
|
+
const ReflectionField = schema({
|
|
4681
|
+
name: "string",
|
|
4682
|
+
type: "string",
|
|
4683
|
+
referencedType: "number",
|
|
4684
|
+
});
|
|
4685
|
+
const ReflectionType = schema({
|
|
4686
|
+
id: "number",
|
|
4687
|
+
extendsId: "number",
|
|
4688
|
+
fields: [ReflectionField],
|
|
4689
|
+
});
|
|
4690
|
+
const Reflection = schema({
|
|
4691
|
+
types: [ReflectionType],
|
|
4692
|
+
rootType: "number",
|
|
4693
|
+
});
|
|
4694
|
+
Reflection.encode = function (encoder, it = { offset: 0 }) {
|
|
4695
|
+
const context = encoder.context;
|
|
4696
|
+
const reflection = new Reflection();
|
|
4697
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
4698
|
+
// rootType is usually the first schema passed to the Encoder
|
|
4699
|
+
// (unless it inherits from another schema)
|
|
4700
|
+
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4701
|
+
if (rootType > 0) {
|
|
4702
|
+
reflection.rootType = rootType;
|
|
4703
|
+
}
|
|
4704
|
+
const includedTypeIds = new Set();
|
|
4705
|
+
const pendingReflectionTypes = {};
|
|
4706
|
+
// add type to reflection in a way that respects inheritance
|
|
4707
|
+
// (parent types should be added before their children)
|
|
4708
|
+
const addType = (type) => {
|
|
4709
|
+
if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
|
|
4710
|
+
includedTypeIds.add(type.id);
|
|
4711
|
+
reflection.types.push(type);
|
|
4712
|
+
const deps = pendingReflectionTypes[type.id];
|
|
4713
|
+
if (deps !== undefined) {
|
|
4714
|
+
delete pendingReflectionTypes[type.id];
|
|
4715
|
+
deps.forEach((childType) => addType(childType));
|
|
4697
4716
|
}
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
|
|
4701
|
-
|
|
4702
|
-
pendingReflectionTypes[type.extendsId].push(type);
|
|
4717
|
+
}
|
|
4718
|
+
else {
|
|
4719
|
+
if (pendingReflectionTypes[type.extendsId] === undefined) {
|
|
4720
|
+
pendingReflectionTypes[type.extendsId] = [];
|
|
4703
4721
|
}
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4722
|
+
pendingReflectionTypes[type.extendsId].push(type);
|
|
4723
|
+
}
|
|
4724
|
+
};
|
|
4725
|
+
context.schemas.forEach((typeid, klass) => {
|
|
4726
|
+
const type = new ReflectionType();
|
|
4727
|
+
type.id = Number(typeid);
|
|
4728
|
+
// support inheritance
|
|
4729
|
+
const inheritFrom = Object.getPrototypeOf(klass);
|
|
4730
|
+
if (inheritFrom !== Schema) {
|
|
4731
|
+
type.extendsId = context.schemas.get(inheritFrom);
|
|
4732
|
+
}
|
|
4733
|
+
const metadata = klass[Symbol.metadata];
|
|
4734
|
+
//
|
|
4735
|
+
// FIXME: this is a workaround for inherited types without additional fields
|
|
4736
|
+
// if metadata is the same reference as the parent class - it means the class has no own metadata
|
|
4737
|
+
//
|
|
4738
|
+
if (metadata !== inheritFrom[Symbol.metadata]) {
|
|
4739
|
+
for (const fieldIndex in metadata) {
|
|
4740
|
+
const index = Number(fieldIndex);
|
|
4741
|
+
const fieldName = metadata[index].name;
|
|
4742
|
+
// skip fields from parent classes
|
|
4743
|
+
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
4744
|
+
continue;
|
|
4745
|
+
}
|
|
4746
|
+
const reflectionField = new ReflectionField();
|
|
4747
|
+
reflectionField.name = fieldName;
|
|
4748
|
+
let fieldType;
|
|
4749
|
+
const field = metadata[index];
|
|
4750
|
+
if (typeof (field.type) === "string") {
|
|
4751
|
+
fieldType = field.type;
|
|
4752
|
+
}
|
|
4753
|
+
else {
|
|
4754
|
+
let childTypeSchema;
|
|
4755
|
+
//
|
|
4756
|
+
// TODO: refactor below.
|
|
4757
|
+
//
|
|
4758
|
+
if (Schema.is(field.type)) {
|
|
4759
|
+
fieldType = "ref";
|
|
4760
|
+
childTypeSchema = field.type;
|
|
4732
4761
|
}
|
|
4733
4762
|
else {
|
|
4734
|
-
|
|
4735
|
-
|
|
4736
|
-
|
|
4737
|
-
//
|
|
4738
|
-
if (Schema.is(field.type)) {
|
|
4739
|
-
fieldType = "ref";
|
|
4740
|
-
childTypeSchema = field.type;
|
|
4763
|
+
fieldType = Object.keys(field.type)[0];
|
|
4764
|
+
if (typeof (field.type[fieldType]) === "string") {
|
|
4765
|
+
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4741
4766
|
}
|
|
4742
4767
|
else {
|
|
4743
|
-
|
|
4744
|
-
if (typeof (field.type[fieldType]) === "string") {
|
|
4745
|
-
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4746
|
-
}
|
|
4747
|
-
else {
|
|
4748
|
-
childTypeSchema = field.type[fieldType];
|
|
4749
|
-
}
|
|
4768
|
+
childTypeSchema = field.type[fieldType];
|
|
4750
4769
|
}
|
|
4751
|
-
reflectionField.referencedType = (childTypeSchema)
|
|
4752
|
-
? context.getTypeId(childTypeSchema)
|
|
4753
|
-
: -1;
|
|
4754
4770
|
}
|
|
4755
|
-
reflectionField.
|
|
4756
|
-
|
|
4771
|
+
reflectionField.referencedType = (childTypeSchema)
|
|
4772
|
+
? context.getTypeId(childTypeSchema)
|
|
4773
|
+
: -1;
|
|
4757
4774
|
}
|
|
4775
|
+
reflectionField.type = fieldType;
|
|
4776
|
+
type.fields.push(reflectionField);
|
|
4758
4777
|
}
|
|
4759
|
-
addType(type);
|
|
4760
|
-
});
|
|
4761
|
-
// in case there are types that were not added due to inheritance
|
|
4762
|
-
for (const typeid in pendingReflectionTypes) {
|
|
4763
|
-
pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
|
|
4764
4778
|
}
|
|
4765
|
-
|
|
4766
|
-
|
|
4767
|
-
|
|
4779
|
+
addType(type);
|
|
4780
|
+
});
|
|
4781
|
+
// in case there are types that were not added due to inheritance
|
|
4782
|
+
for (const typeid in pendingReflectionTypes) {
|
|
4783
|
+
pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
|
|
4768
4784
|
}
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
|
|
4779
|
-
|
|
4780
|
-
const
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
|
|
4800
|
-
|
|
4801
|
-
const typeInfo = field.type.split(":");
|
|
4802
|
-
fieldType = typeInfo[0];
|
|
4803
|
-
refType = typeInfo[1]; // string
|
|
4804
|
-
}
|
|
4805
|
-
if (fieldType === "ref") {
|
|
4806
|
-
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
4807
|
-
}
|
|
4808
|
-
else {
|
|
4809
|
-
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4810
|
-
}
|
|
4785
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
4786
|
+
return buf.slice(0, it.offset);
|
|
4787
|
+
};
|
|
4788
|
+
Reflection.decode = function (bytes, it) {
|
|
4789
|
+
const reflection = new Reflection();
|
|
4790
|
+
const reflectionDecoder = new Decoder(reflection);
|
|
4791
|
+
reflectionDecoder.decode(bytes, it);
|
|
4792
|
+
const typeContext = new TypeContext();
|
|
4793
|
+
// 1st pass, initialize metadata + inheritance
|
|
4794
|
+
reflection.types.forEach((reflectionType) => {
|
|
4795
|
+
const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
4796
|
+
const schema = class _ extends parentClass {
|
|
4797
|
+
};
|
|
4798
|
+
// register for inheritance support
|
|
4799
|
+
TypeContext.register(schema);
|
|
4800
|
+
typeContext.add(schema, reflectionType.id);
|
|
4801
|
+
}, {});
|
|
4802
|
+
// define fields
|
|
4803
|
+
const addFields = (metadata, reflectionType, parentFieldIndex) => {
|
|
4804
|
+
reflectionType.fields.forEach((field, i) => {
|
|
4805
|
+
const fieldIndex = parentFieldIndex + i;
|
|
4806
|
+
if (field.referencedType !== undefined) {
|
|
4807
|
+
let fieldType = field.type;
|
|
4808
|
+
let refType = typeContext.get(field.referencedType);
|
|
4809
|
+
// map or array of primitive type (-1)
|
|
4810
|
+
if (!refType) {
|
|
4811
|
+
const typeInfo = field.type.split(":");
|
|
4812
|
+
fieldType = typeInfo[0];
|
|
4813
|
+
refType = typeInfo[1]; // string
|
|
4814
|
+
}
|
|
4815
|
+
if (fieldType === "ref") {
|
|
4816
|
+
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
4811
4817
|
}
|
|
4812
4818
|
else {
|
|
4813
|
-
Metadata.addField(metadata, fieldIndex, field.name,
|
|
4819
|
+
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4814
4820
|
}
|
|
4815
|
-
}
|
|
4816
|
-
|
|
4817
|
-
|
|
4818
|
-
|
|
4819
|
-
const schema = typeContext.get(reflectionType.id);
|
|
4820
|
-
// for inheritance support
|
|
4821
|
-
const metadata = Metadata.initialize(schema);
|
|
4822
|
-
const inheritedTypes = [];
|
|
4823
|
-
let parentType = reflectionType;
|
|
4824
|
-
do {
|
|
4825
|
-
inheritedTypes.push(parentType);
|
|
4826
|
-
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4827
|
-
} while (parentType);
|
|
4828
|
-
let parentFieldIndex = 0;
|
|
4829
|
-
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4830
|
-
// add fields from all inherited classes
|
|
4831
|
-
// TODO: refactor this to avoid adding fields from parent classes
|
|
4832
|
-
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4833
|
-
parentFieldIndex += reflectionType.fields.length;
|
|
4834
|
-
});
|
|
4821
|
+
}
|
|
4822
|
+
else {
|
|
4823
|
+
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
4824
|
+
}
|
|
4835
4825
|
});
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4826
|
+
};
|
|
4827
|
+
// 2nd pass, set fields
|
|
4828
|
+
reflection.types.forEach((reflectionType) => {
|
|
4829
|
+
const schema = typeContext.get(reflectionType.id);
|
|
4830
|
+
// for inheritance support
|
|
4831
|
+
const metadata = Metadata.initialize(schema);
|
|
4832
|
+
const inheritedTypes = [];
|
|
4833
|
+
let parentType = reflectionType;
|
|
4834
|
+
do {
|
|
4835
|
+
inheritedTypes.push(parentType);
|
|
4836
|
+
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4837
|
+
} while (parentType);
|
|
4838
|
+
let parentFieldIndex = 0;
|
|
4839
|
+
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4840
|
+
// add fields from all inherited classes
|
|
4841
|
+
// TODO: refactor this to avoid adding fields from parent classes
|
|
4842
|
+
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4843
|
+
parentFieldIndex += reflectionType.fields.length;
|
|
4844
|
+
});
|
|
4845
|
+
});
|
|
4846
|
+
const state = new (typeContext.get(reflection.rootType || 0))();
|
|
4847
|
+
return new Decoder(state, typeContext);
|
|
4848
|
+
};
|
|
4846
4849
|
|
|
4850
|
+
/**
|
|
4851
|
+
* Legacy callback system
|
|
4852
|
+
*
|
|
4853
|
+
* @param decoder
|
|
4854
|
+
* @returns
|
|
4855
|
+
*/
|
|
4847
4856
|
function getDecoderStateCallbacks(decoder) {
|
|
4848
4857
|
const $root = decoder.root;
|
|
4849
4858
|
const callbacks = $root.callbacks;
|
|
@@ -4864,7 +4873,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4864
4873
|
//
|
|
4865
4874
|
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4866
4875
|
change.previousValue instanceof Schema) {
|
|
4867
|
-
const deleteCallbacks = callbacks[
|
|
4876
|
+
const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[exports.OPERATION.DELETE];
|
|
4868
4877
|
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4869
4878
|
deleteCallbacks[i]();
|
|
4870
4879
|
}
|
|
@@ -4953,7 +4962,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4953
4962
|
) {
|
|
4954
4963
|
callback(context.instance[prop], undefined);
|
|
4955
4964
|
}
|
|
4956
|
-
return $root.addCallback($
|
|
4965
|
+
return $root.addCallback(ref[$refId], prop, callback);
|
|
4957
4966
|
};
|
|
4958
4967
|
/**
|
|
4959
4968
|
* Schema instances
|
|
@@ -4973,7 +4982,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4973
4982
|
}
|
|
4974
4983
|
},
|
|
4975
4984
|
onChange: function onChange(callback) {
|
|
4976
|
-
return $root.addCallback(
|
|
4985
|
+
return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, callback);
|
|
4977
4986
|
},
|
|
4978
4987
|
//
|
|
4979
4988
|
// TODO: refactor `bindTo()` implementation.
|
|
@@ -4983,7 +4992,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4983
4992
|
if (!properties) {
|
|
4984
4993
|
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4985
4994
|
}
|
|
4986
|
-
return $root.addCallback(
|
|
4995
|
+
return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, () => {
|
|
4987
4996
|
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4988
4997
|
});
|
|
4989
4998
|
}
|
|
@@ -5002,13 +5011,13 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5002
5011
|
unbind?.();
|
|
5003
5012
|
}, false);
|
|
5004
5013
|
// has existing value
|
|
5005
|
-
if ($
|
|
5014
|
+
if (instance?.[$refId] !== undefined) {
|
|
5006
5015
|
callback(instance, true);
|
|
5007
5016
|
}
|
|
5008
5017
|
});
|
|
5009
5018
|
return getProxy(metadataField.type, {
|
|
5010
5019
|
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
5011
|
-
instance: ($
|
|
5020
|
+
instance: (instance?.[$refId] !== undefined && instance),
|
|
5012
5021
|
parentInstance: context.instance,
|
|
5013
5022
|
onInstanceAvailable,
|
|
5014
5023
|
});
|
|
@@ -5032,7 +5041,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5032
5041
|
if (immediate) {
|
|
5033
5042
|
ref.forEach((v, k) => callback(v, k));
|
|
5034
5043
|
}
|
|
5035
|
-
return $root.addCallback($
|
|
5044
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.ADD, (value, key) => {
|
|
5036
5045
|
onAddCalls.set(callback, true);
|
|
5037
5046
|
currentOnAddCallback = callback;
|
|
5038
5047
|
callback(value, key);
|
|
@@ -5041,10 +5050,10 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5041
5050
|
});
|
|
5042
5051
|
};
|
|
5043
5052
|
const onRemove = function (ref, callback) {
|
|
5044
|
-
return $root.addCallback($
|
|
5053
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.DELETE, callback);
|
|
5045
5054
|
};
|
|
5046
5055
|
const onChange = function (ref, callback) {
|
|
5047
|
-
return $root.addCallback($
|
|
5056
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.REPLACE, callback);
|
|
5048
5057
|
};
|
|
5049
5058
|
return new Proxy({
|
|
5050
5059
|
onAdd: function (callback, immediate = true) {
|
|
@@ -5113,22 +5122,360 @@ function getRawChangesCallback(decoder, callback) {
|
|
|
5113
5122
|
decoder.triggerChanges = callback;
|
|
5114
5123
|
}
|
|
5115
5124
|
|
|
5125
|
+
/**
|
|
5126
|
+
* State Callbacks handler
|
|
5127
|
+
*
|
|
5128
|
+
* Usage:
|
|
5129
|
+
* ```ts
|
|
5130
|
+
* const $ = Callbacks.get(decoder);
|
|
5131
|
+
*
|
|
5132
|
+
* // Listen to property changes
|
|
5133
|
+
* $.listen("currentTurn", (currentValue, previousValue) => { ... });
|
|
5134
|
+
*
|
|
5135
|
+
* // Listen to collection additions
|
|
5136
|
+
* $.onAdd("entities", (sessionId, entity) => {
|
|
5137
|
+
* // Nested property listening
|
|
5138
|
+
* $.listen(entity, "hp", (currentHp, previousHp) => { ... });
|
|
5139
|
+
* });
|
|
5140
|
+
*
|
|
5141
|
+
* // Listen to collection removals
|
|
5142
|
+
* $.onRemove("entities", (sessionId, entity) => { ... });
|
|
5143
|
+
*
|
|
5144
|
+
* // Listen to any property change on an instance
|
|
5145
|
+
* $.onChange(entity, () => { ... });
|
|
5146
|
+
*
|
|
5147
|
+
* // Bind properties to another object
|
|
5148
|
+
* $.bindTo(player, playerVisual);
|
|
5149
|
+
* ```
|
|
5150
|
+
*/
|
|
5151
|
+
class StateCallbackStrategy {
|
|
5152
|
+
decoder;
|
|
5153
|
+
uniqueRefIds = new Set();
|
|
5154
|
+
isTriggering = false;
|
|
5155
|
+
constructor(decoder) {
|
|
5156
|
+
this.decoder = decoder;
|
|
5157
|
+
this.decoder.triggerChanges = this.triggerChanges.bind(this);
|
|
5158
|
+
}
|
|
5159
|
+
get callbacks() {
|
|
5160
|
+
return this.decoder.root.callbacks;
|
|
5161
|
+
}
|
|
5162
|
+
get state() {
|
|
5163
|
+
return this.decoder.state;
|
|
5164
|
+
}
|
|
5165
|
+
addCallback(refId, operationOrProperty, handler) {
|
|
5166
|
+
const $root = this.decoder.root;
|
|
5167
|
+
return $root.addCallback(refId, operationOrProperty, handler);
|
|
5168
|
+
}
|
|
5169
|
+
addCallbackOrWaitCollectionAvailable(instance, propertyName, operation, handler, immediate = true) {
|
|
5170
|
+
let removeHandler = () => { };
|
|
5171
|
+
const removeOnAdd = () => removeHandler();
|
|
5172
|
+
const collection = instance[propertyName];
|
|
5173
|
+
// Collection not available yet. Listen for its availability before attaching the handler.
|
|
5174
|
+
if (collection === null || collection === undefined) {
|
|
5175
|
+
removeHandler = this.addCallback(instance[$refId], propertyName, (value, _) => {
|
|
5176
|
+
if (value !== null && value !== undefined) {
|
|
5177
|
+
removeHandler = this.addCallback(value[$refId], operation, handler);
|
|
5178
|
+
}
|
|
5179
|
+
});
|
|
5180
|
+
return removeOnAdd;
|
|
5181
|
+
}
|
|
5182
|
+
else {
|
|
5183
|
+
//
|
|
5184
|
+
// Call immediately if collection is already available, if it's an ADD operation.
|
|
5185
|
+
//
|
|
5186
|
+
immediate = immediate && this.isTriggering === false;
|
|
5187
|
+
if (operation === exports.OPERATION.ADD && immediate) {
|
|
5188
|
+
collection.forEach((value, key) => {
|
|
5189
|
+
handler(key, value);
|
|
5190
|
+
});
|
|
5191
|
+
}
|
|
5192
|
+
return this.addCallback(collection[$refId], operation, handler);
|
|
5193
|
+
}
|
|
5194
|
+
}
|
|
5195
|
+
listen(...args) {
|
|
5196
|
+
if (typeof args[0] === 'string') {
|
|
5197
|
+
// listen(property, handler, immediate?)
|
|
5198
|
+
return this.listenInstance(this.state, args[0], args[1], args[2]);
|
|
5199
|
+
}
|
|
5200
|
+
else {
|
|
5201
|
+
// listen(instance, property, handler, immediate?)
|
|
5202
|
+
return this.listenInstance(args[0], args[1], args[2], args[3]);
|
|
5203
|
+
}
|
|
5204
|
+
}
|
|
5205
|
+
listenInstance(instance, propertyName, handler, immediate = true) {
|
|
5206
|
+
immediate = immediate && this.isTriggering === false;
|
|
5207
|
+
//
|
|
5208
|
+
// Call handler immediately if property is already available.
|
|
5209
|
+
//
|
|
5210
|
+
const currentValue = instance[propertyName];
|
|
5211
|
+
if (immediate && currentValue !== null && currentValue !== undefined) {
|
|
5212
|
+
handler(currentValue, undefined);
|
|
5213
|
+
}
|
|
5214
|
+
return this.addCallback(instance[$refId], propertyName, handler);
|
|
5215
|
+
}
|
|
5216
|
+
onChange(...args) {
|
|
5217
|
+
if (args.length === 2 && typeof args[0] !== 'string') {
|
|
5218
|
+
// onChange(instance, handler) - instance change
|
|
5219
|
+
const instance = args[0];
|
|
5220
|
+
const handler = args[1];
|
|
5221
|
+
return this.addCallback(instance[$refId], exports.OPERATION.REPLACE, handler);
|
|
5222
|
+
}
|
|
5223
|
+
if (typeof args[0] === 'string') {
|
|
5224
|
+
// onChange(property, handler) - collection on root state
|
|
5225
|
+
return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.REPLACE, args[1]);
|
|
5226
|
+
}
|
|
5227
|
+
else {
|
|
5228
|
+
// onChange(instance, property, handler) - nested collection
|
|
5229
|
+
return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.REPLACE, args[2]);
|
|
5230
|
+
}
|
|
5231
|
+
}
|
|
5232
|
+
onAdd(...args) {
|
|
5233
|
+
if (typeof args[0] === 'string') {
|
|
5234
|
+
// onAdd(property, handler, immediate?) - collection on root state
|
|
5235
|
+
return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.ADD, args[1], args[2] !== false);
|
|
5236
|
+
}
|
|
5237
|
+
else {
|
|
5238
|
+
// onAdd(instance, property, handler, immediate?) - nested collection
|
|
5239
|
+
return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.ADD, args[2], args[3] !== false);
|
|
5240
|
+
}
|
|
5241
|
+
}
|
|
5242
|
+
onRemove(...args) {
|
|
5243
|
+
if (typeof args[0] === 'string') {
|
|
5244
|
+
// onRemove(property, handler) - collection on root state
|
|
5245
|
+
return this.addCallbackOrWaitCollectionAvailable(this.state, args[0], exports.OPERATION.DELETE, args[1]);
|
|
5246
|
+
}
|
|
5247
|
+
else {
|
|
5248
|
+
// onRemove(instance, property, handler) - nested collection
|
|
5249
|
+
return this.addCallbackOrWaitCollectionAvailable(args[0], args[1], exports.OPERATION.DELETE, args[2]);
|
|
5250
|
+
}
|
|
5251
|
+
}
|
|
5252
|
+
/**
|
|
5253
|
+
* Bind properties from a Schema instance to a target object.
|
|
5254
|
+
* Changes will be automatically reflected on the target object.
|
|
5255
|
+
*/
|
|
5256
|
+
bindTo(from, to, properties, immediate = true) {
|
|
5257
|
+
const metadata = from.constructor[Symbol.metadata];
|
|
5258
|
+
// If no properties specified, bind all properties
|
|
5259
|
+
if (!properties) {
|
|
5260
|
+
properties = Object.keys(metadata)
|
|
5261
|
+
.filter(key => !isNaN(Number(key)))
|
|
5262
|
+
.map((index) => metadata[index].name);
|
|
5263
|
+
}
|
|
5264
|
+
const action = () => {
|
|
5265
|
+
for (const prop of properties) {
|
|
5266
|
+
const fromValue = from[prop];
|
|
5267
|
+
if (fromValue !== undefined) {
|
|
5268
|
+
to[prop] = fromValue;
|
|
5269
|
+
}
|
|
5270
|
+
}
|
|
5271
|
+
};
|
|
5272
|
+
if (immediate) {
|
|
5273
|
+
action();
|
|
5274
|
+
}
|
|
5275
|
+
return this.addCallback(from[$refId], exports.OPERATION.REPLACE, action);
|
|
5276
|
+
}
|
|
5277
|
+
triggerChanges(allChanges) {
|
|
5278
|
+
this.uniqueRefIds.clear();
|
|
5279
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
5280
|
+
const change = allChanges[i];
|
|
5281
|
+
const refId = change.refId;
|
|
5282
|
+
const ref = change.ref;
|
|
5283
|
+
const $callbacks = this.callbacks[refId];
|
|
5284
|
+
if (!$callbacks) {
|
|
5285
|
+
continue;
|
|
5286
|
+
}
|
|
5287
|
+
//
|
|
5288
|
+
// trigger onRemove on child structure.
|
|
5289
|
+
//
|
|
5290
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
5291
|
+
change.previousValue instanceof Schema) {
|
|
5292
|
+
const childRefId = change.previousValue[$refId];
|
|
5293
|
+
const deleteCallbacks = this.callbacks[childRefId]?.[exports.OPERATION.DELETE];
|
|
5294
|
+
if (deleteCallbacks) {
|
|
5295
|
+
for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
|
|
5296
|
+
deleteCallbacks[j]();
|
|
5297
|
+
}
|
|
5298
|
+
}
|
|
5299
|
+
}
|
|
5300
|
+
if (ref instanceof Schema) {
|
|
5301
|
+
//
|
|
5302
|
+
// Handle Schema instance
|
|
5303
|
+
//
|
|
5304
|
+
if (!this.uniqueRefIds.has(refId)) {
|
|
5305
|
+
// trigger onChange
|
|
5306
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
5307
|
+
if (replaceCallbacks) {
|
|
5308
|
+
for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
|
|
5309
|
+
try {
|
|
5310
|
+
replaceCallbacks[j]();
|
|
5311
|
+
}
|
|
5312
|
+
catch (e) {
|
|
5313
|
+
console.error(e);
|
|
5314
|
+
}
|
|
5315
|
+
}
|
|
5316
|
+
}
|
|
5317
|
+
}
|
|
5318
|
+
// trigger field callbacks
|
|
5319
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
5320
|
+
if (fieldCallbacks) {
|
|
5321
|
+
for (let j = fieldCallbacks.length - 1; j >= 0; j--) {
|
|
5322
|
+
try {
|
|
5323
|
+
this.isTriggering = true;
|
|
5324
|
+
fieldCallbacks[j](change.value, change.previousValue);
|
|
5325
|
+
}
|
|
5326
|
+
catch (e) {
|
|
5327
|
+
console.error(e);
|
|
5328
|
+
}
|
|
5329
|
+
finally {
|
|
5330
|
+
this.isTriggering = false;
|
|
5331
|
+
}
|
|
5332
|
+
}
|
|
5333
|
+
}
|
|
5334
|
+
}
|
|
5335
|
+
else {
|
|
5336
|
+
//
|
|
5337
|
+
// Handle collection of items
|
|
5338
|
+
//
|
|
5339
|
+
const dynamicIndex = change.dynamicIndex ?? change.field;
|
|
5340
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
5341
|
+
//
|
|
5342
|
+
// FIXME: `previousValue` should always be available.
|
|
5343
|
+
//
|
|
5344
|
+
if (change.previousValue !== undefined) {
|
|
5345
|
+
// trigger onRemove (key, value)
|
|
5346
|
+
const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
|
|
5347
|
+
if (deleteCallbacks) {
|
|
5348
|
+
for (let j = deleteCallbacks.length - 1; j >= 0; j--) {
|
|
5349
|
+
deleteCallbacks[j](dynamicIndex, change.previousValue);
|
|
5350
|
+
}
|
|
5351
|
+
}
|
|
5352
|
+
}
|
|
5353
|
+
// Handle DELETE_AND_ADD operation
|
|
5354
|
+
if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
5355
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
5356
|
+
if (addCallbacks) {
|
|
5357
|
+
this.isTriggering = true;
|
|
5358
|
+
for (let j = addCallbacks.length - 1; j >= 0; j--) {
|
|
5359
|
+
addCallbacks[j](dynamicIndex, change.value);
|
|
5360
|
+
}
|
|
5361
|
+
this.isTriggering = false;
|
|
5362
|
+
}
|
|
5363
|
+
}
|
|
5364
|
+
}
|
|
5365
|
+
else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD &&
|
|
5366
|
+
change.previousValue !== change.value) {
|
|
5367
|
+
// trigger onAdd (key, value)
|
|
5368
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
5369
|
+
if (addCallbacks) {
|
|
5370
|
+
this.isTriggering = true;
|
|
5371
|
+
for (let j = addCallbacks.length - 1; j >= 0; j--) {
|
|
5372
|
+
addCallbacks[j](dynamicIndex, change.value);
|
|
5373
|
+
}
|
|
5374
|
+
this.isTriggering = false;
|
|
5375
|
+
}
|
|
5376
|
+
}
|
|
5377
|
+
// trigger onChange (key, value)
|
|
5378
|
+
if (change.value !== change.previousValue) {
|
|
5379
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
5380
|
+
if (replaceCallbacks) {
|
|
5381
|
+
for (let j = replaceCallbacks.length - 1; j >= 0; j--) {
|
|
5382
|
+
replaceCallbacks[j](dynamicIndex, change.value);
|
|
5383
|
+
}
|
|
5384
|
+
}
|
|
5385
|
+
}
|
|
5386
|
+
}
|
|
5387
|
+
this.uniqueRefIds.add(refId);
|
|
5388
|
+
}
|
|
5389
|
+
}
|
|
5390
|
+
}
|
|
5391
|
+
/**
|
|
5392
|
+
* Factory class for retrieving the callbacks API.
|
|
5393
|
+
*/
|
|
5394
|
+
const Callbacks = {
|
|
5395
|
+
/**
|
|
5396
|
+
* Get the new callbacks standard API.
|
|
5397
|
+
*
|
|
5398
|
+
* Usage:
|
|
5399
|
+
* ```ts
|
|
5400
|
+
* const callbacks = Callbacks.get(roomOrDecoder);
|
|
5401
|
+
*
|
|
5402
|
+
* // Listen to property changes
|
|
5403
|
+
* callbacks.listen("currentTurn", (currentValue, previousValue) => { ... });
|
|
5404
|
+
*
|
|
5405
|
+
* // Listen to collection additions
|
|
5406
|
+
* callbacks.onAdd("entities", (sessionId, entity) => {
|
|
5407
|
+
* // Nested property listening
|
|
5408
|
+
* callbacks.listen(entity, "hp", (currentHp, previousHp) => { ... });
|
|
5409
|
+
* });
|
|
5410
|
+
*
|
|
5411
|
+
* // Listen to collection removals
|
|
5412
|
+
* callbacks.onRemove("entities", (sessionId, entity) => { ... });
|
|
5413
|
+
*
|
|
5414
|
+
* // Listen to any property change on an instance
|
|
5415
|
+
* callbacks.onChange(entity, () => { ... });
|
|
5416
|
+
*
|
|
5417
|
+
* // Bind properties to another object
|
|
5418
|
+
* callbacks.bindTo(player, playerVisual);
|
|
5419
|
+
* ```
|
|
5420
|
+
*
|
|
5421
|
+
* @param roomOrDecoder - Room or Decoder instance to get the callbacks for.
|
|
5422
|
+
* @returns the new callbacks standard API.
|
|
5423
|
+
*/
|
|
5424
|
+
get(roomOrDecoder) {
|
|
5425
|
+
if (roomOrDecoder instanceof Decoder) {
|
|
5426
|
+
return new StateCallbackStrategy(roomOrDecoder);
|
|
5427
|
+
}
|
|
5428
|
+
else if (roomOrDecoder.serializer.decoder) {
|
|
5429
|
+
return new StateCallbackStrategy(roomOrDecoder.serializer.decoder);
|
|
5430
|
+
}
|
|
5431
|
+
else {
|
|
5432
|
+
throw new Error('Invalid room or decoder');
|
|
5433
|
+
}
|
|
5434
|
+
},
|
|
5435
|
+
/**
|
|
5436
|
+
* Get the legacy callbacks API.
|
|
5437
|
+
*
|
|
5438
|
+
* We aim to deprecate this API on 1.0, and iterate on improving Callbacks.get() API.
|
|
5439
|
+
*
|
|
5440
|
+
* @param roomOrDecoder - Room or Decoder instance to get the legacy callbacks for.
|
|
5441
|
+
* @returns the legacy callbacks API.
|
|
5442
|
+
*/
|
|
5443
|
+
getLegacy(roomOrDecoder) {
|
|
5444
|
+
if (roomOrDecoder instanceof Decoder) {
|
|
5445
|
+
return getDecoderStateCallbacks(roomOrDecoder);
|
|
5446
|
+
}
|
|
5447
|
+
else if (roomOrDecoder.serializer.decoder) {
|
|
5448
|
+
return getDecoderStateCallbacks(roomOrDecoder.serializer.decoder);
|
|
5449
|
+
}
|
|
5450
|
+
},
|
|
5451
|
+
getRawChanges(decoder, callback) {
|
|
5452
|
+
return getRawChangesCallback(decoder, callback);
|
|
5453
|
+
}
|
|
5454
|
+
};
|
|
5455
|
+
|
|
5116
5456
|
class StateView {
|
|
5457
|
+
iterable;
|
|
5458
|
+
/**
|
|
5459
|
+
* Iterable list of items that are visible to this view
|
|
5460
|
+
* (Available only if constructed with `iterable: true`)
|
|
5461
|
+
*/
|
|
5462
|
+
items;
|
|
5463
|
+
/**
|
|
5464
|
+
* List of ChangeTree's that are visible to this view
|
|
5465
|
+
*/
|
|
5466
|
+
visible = new WeakSet();
|
|
5467
|
+
/**
|
|
5468
|
+
* List of ChangeTree's that are invisible to this view
|
|
5469
|
+
*/
|
|
5470
|
+
invisible = new WeakSet();
|
|
5471
|
+
tags; // TODO: use bit manipulation instead of Set<number> ()
|
|
5472
|
+
/**
|
|
5473
|
+
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
5474
|
+
* (This is used to force encoding a property, even if it was not changed)
|
|
5475
|
+
*/
|
|
5476
|
+
changes = new Map();
|
|
5117
5477
|
constructor(iterable = false) {
|
|
5118
5478
|
this.iterable = iterable;
|
|
5119
|
-
/**
|
|
5120
|
-
* List of ChangeTree's that are visible to this view
|
|
5121
|
-
*/
|
|
5122
|
-
this.visible = new WeakSet();
|
|
5123
|
-
/**
|
|
5124
|
-
* List of ChangeTree's that are invisible to this view
|
|
5125
|
-
*/
|
|
5126
|
-
this.invisible = new WeakSet();
|
|
5127
|
-
/**
|
|
5128
|
-
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
5129
|
-
* (This is used to force encoding a property, even if it was not changed)
|
|
5130
|
-
*/
|
|
5131
|
-
this.changes = new Map();
|
|
5132
5479
|
if (iterable) {
|
|
5133
5480
|
this.items = [];
|
|
5134
5481
|
}
|
|
@@ -5142,7 +5489,7 @@ class StateView {
|
|
|
5142
5489
|
return false;
|
|
5143
5490
|
}
|
|
5144
5491
|
else if (!parentChangeTree &&
|
|
5145
|
-
|
|
5492
|
+
obj[$refId] !== 0 // allow root object
|
|
5146
5493
|
) {
|
|
5147
5494
|
/**
|
|
5148
5495
|
* TODO: can we avoid this?
|
|
@@ -5166,11 +5513,11 @@ class StateView {
|
|
|
5166
5513
|
if (checkIncludeParent && parentChangeTree) {
|
|
5167
5514
|
this.addParentOf(changeTree, tag);
|
|
5168
5515
|
}
|
|
5169
|
-
let changes = this.changes.get(
|
|
5516
|
+
let changes = this.changes.get(obj[$refId]);
|
|
5170
5517
|
if (changes === undefined) {
|
|
5171
5518
|
changes = {};
|
|
5172
5519
|
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5173
|
-
this.changes.set(
|
|
5520
|
+
this.changes.set(obj[$refId], changes);
|
|
5174
5521
|
}
|
|
5175
5522
|
let isChildAdded = false;
|
|
5176
5523
|
//
|
|
@@ -5250,10 +5597,10 @@ class StateView {
|
|
|
5250
5597
|
}
|
|
5251
5598
|
// add parent's tag properties
|
|
5252
5599
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
5253
|
-
let changes = this.changes.get(changeTree.refId);
|
|
5600
|
+
let changes = this.changes.get(changeTree.ref[$refId]);
|
|
5254
5601
|
if (changes === undefined) {
|
|
5255
5602
|
changes = {};
|
|
5256
|
-
this.changes.set(changeTree.refId, changes);
|
|
5603
|
+
this.changes.set(changeTree.ref[$refId], changes);
|
|
5257
5604
|
}
|
|
5258
5605
|
if (!this.tags) {
|
|
5259
5606
|
this.tags = new WeakMap();
|
|
@@ -5285,27 +5632,28 @@ class StateView {
|
|
|
5285
5632
|
}
|
|
5286
5633
|
const ref = changeTree.ref;
|
|
5287
5634
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5288
|
-
|
|
5635
|
+
const refId = ref[$refId];
|
|
5636
|
+
let changes = this.changes.get(refId);
|
|
5289
5637
|
if (changes === undefined) {
|
|
5290
5638
|
changes = {};
|
|
5291
|
-
this.changes.set(
|
|
5639
|
+
this.changes.set(refId, changes);
|
|
5292
5640
|
}
|
|
5293
5641
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5294
5642
|
// parent is collection (Map/Array)
|
|
5295
5643
|
const parent = changeTree.parent;
|
|
5296
5644
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5297
|
-
const
|
|
5298
|
-
let changes = this.changes.get(
|
|
5645
|
+
const parentRefId = parent[$refId];
|
|
5646
|
+
let changes = this.changes.get(parentRefId);
|
|
5299
5647
|
if (changes === undefined) {
|
|
5300
5648
|
changes = {};
|
|
5301
|
-
this.changes.set(
|
|
5649
|
+
this.changes.set(parentRefId, changes);
|
|
5302
5650
|
}
|
|
5303
5651
|
else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5304
5652
|
//
|
|
5305
5653
|
// SAME PATCH ADD + REMOVE:
|
|
5306
5654
|
// The 'changes' of deleted structure should be ignored.
|
|
5307
5655
|
//
|
|
5308
|
-
this.changes.delete(
|
|
5656
|
+
this.changes.delete(refId);
|
|
5309
5657
|
}
|
|
5310
5658
|
// DELETE / DELETE BY REF ID
|
|
5311
5659
|
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
@@ -5365,7 +5713,7 @@ class StateView {
|
|
|
5365
5713
|
if (!isVisible && changeTree.isVisibilitySharedWithParent) {
|
|
5366
5714
|
// console.log("CHECK AGAINST PARENT...", {
|
|
5367
5715
|
// ref: changeTree.ref.constructor.name,
|
|
5368
|
-
// refId: changeTree.refId,
|
|
5716
|
+
// refId: changeTree.ref[$refId],
|
|
5369
5717
|
// parent: changeTree.parent.constructor.name,
|
|
5370
5718
|
// });
|
|
5371
5719
|
if (this.visible.has(changeTree.parent[$changes])) {
|
|
@@ -5395,8 +5743,10 @@ exports.$deleteByIndex = $deleteByIndex;
|
|
|
5395
5743
|
exports.$encoder = $encoder;
|
|
5396
5744
|
exports.$filter = $filter;
|
|
5397
5745
|
exports.$getByIndex = $getByIndex;
|
|
5746
|
+
exports.$refId = $refId;
|
|
5398
5747
|
exports.$track = $track;
|
|
5399
5748
|
exports.ArraySchema = ArraySchema;
|
|
5749
|
+
exports.Callbacks = Callbacks;
|
|
5400
5750
|
exports.ChangeTree = ChangeTree;
|
|
5401
5751
|
exports.CollectionSchema = CollectionSchema;
|
|
5402
5752
|
exports.Decoder = Decoder;
|
|
@@ -5408,6 +5758,7 @@ exports.ReflectionField = ReflectionField;
|
|
|
5408
5758
|
exports.ReflectionType = ReflectionType;
|
|
5409
5759
|
exports.Schema = Schema;
|
|
5410
5760
|
exports.SetSchema = SetSchema;
|
|
5761
|
+
exports.StateCallbackStrategy = StateCallbackStrategy;
|
|
5411
5762
|
exports.StateView = StateView;
|
|
5412
5763
|
exports.TypeContext = TypeContext;
|
|
5413
5764
|
exports.decode = decode;
|