@colyseus/schema 3.0.76 → 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 +781 -434
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +779 -435
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +781 -434
- package/lib/Metadata.js +1 -5
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +50 -17
- package/lib/Reflection.js +151 -202
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +13 -1
- package/lib/Schema.js +73 -9
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +6 -1
- package/lib/annotations.js +8 -34
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +34 -1
- package/lib/bench_encode.js.map +1 -1
- package/lib/codegen/api.js +35 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/cli.js +4 -1
- package/lib/codegen/cli.js.map +1 -1
- package/lib/codegen/parser.js +35 -2
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +34 -1
- package/lib/codegen/types.js.map +1 -1
- package/lib/decoder/DecodeOperation.d.ts +2 -2
- package/lib/decoder/DecodeOperation.js +3 -3
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +3 -3
- package/lib/decoder/Decoder.js +2 -2
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/ReferenceTracker.d.ts +0 -1
- package/lib/decoder/ReferenceTracker.js +9 -7
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/decoder/strategy/Callbacks.d.ts +154 -0
- package/lib/decoder/strategy/Callbacks.js +340 -0
- package/lib/decoder/strategy/Callbacks.js.map +1 -0
- package/lib/decoder/strategy/{StateCallbacks.d.ts → getDecoderStateCallbacks.d.ts} +6 -0
- package/lib/decoder/strategy/{StateCallbacks.js → getDecoderStateCallbacks.js} +17 -10
- package/lib/decoder/strategy/getDecoderStateCallbacks.js.map +1 -0
- package/lib/encoder/ChangeTree.d.ts +2 -2
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +2 -2
- package/lib/encoder/EncodeOperation.js +3 -3
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +6 -6
- package/lib/encoder/Encoder.js +19 -18
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.js +17 -14
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.js +13 -12
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/decode.d.ts +2 -2
- package/lib/encoding/encode.d.ts +3 -1
- package/lib/encoding/encode.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +7 -3
- package/lib/index.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +7 -14
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/custom/ArraySchema.d.ts +2 -1
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.d.ts +2 -1
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -2
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.d.ts +2 -1
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/symbols.d.ts +1 -0
- package/lib/types/symbols.js +2 -1
- package/lib/types/symbols.js.map +1 -1
- package/lib/utils.js +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +12 -16
- package/src/Metadata.ts +1 -5
- package/src/Reflection.ts +185 -174
- package/src/Schema.ts +81 -13
- package/src/annotations.ts +14 -40
- package/src/codegen/parser.ts +1 -1
- package/src/decoder/DecodeOperation.ts +9 -9
- package/src/decoder/Decoder.ts +6 -6
- package/src/decoder/ReferenceTracker.ts +10 -8
- package/src/decoder/strategy/Callbacks.ts +547 -0
- package/src/decoder/strategy/{StateCallbacks.ts → getDecoderStateCallbacks.ts} +17 -11
- package/src/encoder/ChangeTree.ts +4 -7
- package/src/encoder/EncodeOperation.ts +9 -9
- package/src/encoder/Encoder.ts +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
|
}
|
|
@@ -928,11 +929,7 @@ const Metadata = {
|
|
|
928
929
|
});
|
|
929
930
|
}
|
|
930
931
|
}
|
|
931
|
-
|
|
932
|
-
value: metadata,
|
|
933
|
-
writable: false,
|
|
934
|
-
configurable: true
|
|
935
|
-
});
|
|
932
|
+
constructor[Symbol.metadata] = metadata;
|
|
936
933
|
return metadata;
|
|
937
934
|
},
|
|
938
935
|
isValidInstance(klass) {
|
|
@@ -984,25 +981,33 @@ function deleteOperationAtIndex(changeSet, index) {
|
|
|
984
981
|
delete changeSet.indexes[index];
|
|
985
982
|
}
|
|
986
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;
|
|
987
1010
|
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
1011
|
this.ref = ref;
|
|
1007
1012
|
this.metadata = ref.constructor[Symbol.metadata];
|
|
1008
1013
|
//
|
|
@@ -1474,7 +1479,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
1474
1479
|
// Encode refId for this instance.
|
|
1475
1480
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1476
1481
|
//
|
|
1477
|
-
encode.number(bytes, value[$
|
|
1482
|
+
encode.number(bytes, value[$refId], it);
|
|
1478
1483
|
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
1479
1484
|
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
1480
1485
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
@@ -1485,7 +1490,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
1485
1490
|
// Encode refId for this instance.
|
|
1486
1491
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1487
1492
|
//
|
|
1488
|
-
encode.number(bytes, value[$
|
|
1493
|
+
encode.number(bytes, value[$refId], it);
|
|
1489
1494
|
}
|
|
1490
1495
|
}
|
|
1491
1496
|
/**
|
|
@@ -1561,7 +1566,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
|
|
|
1561
1566
|
if (!item) {
|
|
1562
1567
|
return;
|
|
1563
1568
|
}
|
|
1564
|
-
refOrIndex = item[$
|
|
1569
|
+
refOrIndex = item[$refId];
|
|
1565
1570
|
if (operation === exports.OPERATION.DELETE) {
|
|
1566
1571
|
operation = exports.OPERATION.DELETE_BY_REFID;
|
|
1567
1572
|
}
|
|
@@ -1601,7 +1606,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1601
1606
|
let value;
|
|
1602
1607
|
if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
1603
1608
|
// Flag `refId` for garbage collection.
|
|
1604
|
-
const previousRefId = $
|
|
1609
|
+
const previousRefId = previousValue?.[$refId];
|
|
1605
1610
|
if (previousRefId !== undefined) {
|
|
1606
1611
|
$root.removeRef(previousRefId);
|
|
1607
1612
|
}
|
|
@@ -1642,7 +1647,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1642
1647
|
value = valueRef.clone(true);
|
|
1643
1648
|
value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
|
|
1644
1649
|
if (previousValue) {
|
|
1645
|
-
let previousRefId = $
|
|
1650
|
+
let previousRefId = previousValue[$refId];
|
|
1646
1651
|
if (previousRefId !== undefined && refId !== previousRefId) {
|
|
1647
1652
|
//
|
|
1648
1653
|
// enqueue onRemove if structure has been replaced.
|
|
@@ -1653,7 +1658,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1653
1658
|
const [key, value] = iter.value;
|
|
1654
1659
|
// if value is a schema, remove its reference
|
|
1655
1660
|
if (typeof (value) === "object") {
|
|
1656
|
-
previousRefId = $
|
|
1661
|
+
previousRefId = value[$refId];
|
|
1657
1662
|
$root.removeRef(previousRefId);
|
|
1658
1663
|
}
|
|
1659
1664
|
allChanges.push({
|
|
@@ -1882,7 +1887,6 @@ function assertInstanceType(value, type, instance, field) {
|
|
|
1882
1887
|
}
|
|
1883
1888
|
}
|
|
1884
1889
|
|
|
1885
|
-
var _a$4, _b$4;
|
|
1886
1890
|
const DEFAULT_SORT = (a, b) => {
|
|
1887
1891
|
const A = a.toString();
|
|
1888
1892
|
const B = b.toString();
|
|
@@ -1894,8 +1898,15 @@ const DEFAULT_SORT = (a, b) => {
|
|
|
1894
1898
|
return 0;
|
|
1895
1899
|
};
|
|
1896
1900
|
class ArraySchema {
|
|
1897
|
-
|
|
1898
|
-
|
|
1901
|
+
[$changes];
|
|
1902
|
+
[$refId];
|
|
1903
|
+
[$childType];
|
|
1904
|
+
items = [];
|
|
1905
|
+
tmpItems = [];
|
|
1906
|
+
deletedIndexes = {};
|
|
1907
|
+
isMovingItems = false;
|
|
1908
|
+
static [$encoder] = encodeArray;
|
|
1909
|
+
static [$decoder] = decodeArray;
|
|
1899
1910
|
/**
|
|
1900
1911
|
* Determine if a property must be filtered.
|
|
1901
1912
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -1905,7 +1916,7 @@ class ArraySchema {
|
|
|
1905
1916
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
1906
1917
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
1907
1918
|
*/
|
|
1908
|
-
static [
|
|
1919
|
+
static [$filter](ref, index, view) {
|
|
1909
1920
|
return (!view ||
|
|
1910
1921
|
typeof (ref[$childType]) === "string" ||
|
|
1911
1922
|
view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
|
|
@@ -1921,10 +1932,6 @@ class ArraySchema {
|
|
|
1921
1932
|
return new ArraySchema(...Array.from(iterable));
|
|
1922
1933
|
}
|
|
1923
1934
|
constructor(...items) {
|
|
1924
|
-
this.items = [];
|
|
1925
|
-
this.tmpItems = [];
|
|
1926
|
-
this.deletedIndexes = {};
|
|
1927
|
-
this.isMovingItems = false;
|
|
1928
1935
|
Object.defineProperty(this, $childType, {
|
|
1929
1936
|
value: undefined,
|
|
1930
1937
|
enumerable: false,
|
|
@@ -2441,6 +2448,10 @@ class ArraySchema {
|
|
|
2441
2448
|
static get [Symbol.species]() {
|
|
2442
2449
|
return ArraySchema;
|
|
2443
2450
|
}
|
|
2451
|
+
// WORKAROUND for compatibility
|
|
2452
|
+
// - TypeScript 4 defines @@unscopables as a function
|
|
2453
|
+
// - TypeScript 5 defines @@unscopables as an object
|
|
2454
|
+
[Symbol.unscopables];
|
|
2444
2455
|
/**
|
|
2445
2456
|
* Returns an iterable of key, value pairs for every entry in the array
|
|
2446
2457
|
*/
|
|
@@ -2550,7 +2561,7 @@ class ArraySchema {
|
|
|
2550
2561
|
this.isMovingItems = false;
|
|
2551
2562
|
return this;
|
|
2552
2563
|
}
|
|
2553
|
-
[
|
|
2564
|
+
[$getByIndex](index, isEncodeAll = false) {
|
|
2554
2565
|
//
|
|
2555
2566
|
// TODO: avoid unecessary `this.tmpItems` check during decoding.
|
|
2556
2567
|
//
|
|
@@ -2605,10 +2616,16 @@ class ArraySchema {
|
|
|
2605
2616
|
}
|
|
2606
2617
|
registerType("array", { constructor: ArraySchema });
|
|
2607
2618
|
|
|
2608
|
-
var _a$3, _b$3;
|
|
2609
2619
|
class MapSchema {
|
|
2610
|
-
|
|
2611
|
-
|
|
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;
|
|
2612
2629
|
/**
|
|
2613
2630
|
* Determine if a property must be filtered.
|
|
2614
2631
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2618,7 +2635,7 @@ class MapSchema {
|
|
|
2618
2635
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2619
2636
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2620
2637
|
*/
|
|
2621
|
-
static [
|
|
2638
|
+
static [$filter](ref, index, view) {
|
|
2622
2639
|
return (!view ||
|
|
2623
2640
|
typeof (ref[$childType]) === "string" ||
|
|
2624
2641
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2627,9 +2644,6 @@ class MapSchema {
|
|
|
2627
2644
|
return type['map'] !== undefined;
|
|
2628
2645
|
}
|
|
2629
2646
|
constructor(initialValues) {
|
|
2630
|
-
this.$items = new Map();
|
|
2631
|
-
this.$indexes = new Map();
|
|
2632
|
-
this.deletedItems = {};
|
|
2633
2647
|
const changeTree = new ChangeTree(this);
|
|
2634
2648
|
changeTree.indexes = {};
|
|
2635
2649
|
Object.defineProperty(this, $changes, {
|
|
@@ -2820,10 +2834,16 @@ class MapSchema {
|
|
|
2820
2834
|
}
|
|
2821
2835
|
registerType("map", { constructor: MapSchema });
|
|
2822
2836
|
|
|
2823
|
-
var _a$2, _b$2;
|
|
2824
2837
|
class CollectionSchema {
|
|
2825
|
-
|
|
2826
|
-
|
|
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;
|
|
2827
2847
|
/**
|
|
2828
2848
|
* Determine if a property must be filtered.
|
|
2829
2849
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2833,7 +2853,7 @@ class CollectionSchema {
|
|
|
2833
2853
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2834
2854
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2835
2855
|
*/
|
|
2836
|
-
static [
|
|
2856
|
+
static [$filter](ref, index, view) {
|
|
2837
2857
|
return (!view ||
|
|
2838
2858
|
typeof (ref[$childType]) === "string" ||
|
|
2839
2859
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2842,10 +2862,6 @@ class CollectionSchema {
|
|
|
2842
2862
|
return type['collection'] !== undefined;
|
|
2843
2863
|
}
|
|
2844
2864
|
constructor(initialValues) {
|
|
2845
|
-
this.$items = new Map();
|
|
2846
|
-
this.$indexes = new Map();
|
|
2847
|
-
this.deletedItems = {};
|
|
2848
|
-
this.$refId = 0;
|
|
2849
2865
|
this[$changes] = new ChangeTree(this);
|
|
2850
2866
|
this[$changes].indexes = {};
|
|
2851
2867
|
if (initialValues) {
|
|
@@ -2984,10 +3000,16 @@ class CollectionSchema {
|
|
|
2984
3000
|
}
|
|
2985
3001
|
registerType("collection", { constructor: CollectionSchema, });
|
|
2986
3002
|
|
|
2987
|
-
var _a$1, _b$1;
|
|
2988
3003
|
class SetSchema {
|
|
2989
|
-
|
|
2990
|
-
|
|
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;
|
|
2991
3013
|
/**
|
|
2992
3014
|
* Determine if a property must be filtered.
|
|
2993
3015
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2997,7 +3019,7 @@ class SetSchema {
|
|
|
2997
3019
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2998
3020
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2999
3021
|
*/
|
|
3000
|
-
static [
|
|
3022
|
+
static [$filter](ref, index, view) {
|
|
3001
3023
|
return (!view ||
|
|
3002
3024
|
typeof (ref[$childType]) === "string" ||
|
|
3003
3025
|
view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -3006,10 +3028,6 @@ class SetSchema {
|
|
|
3006
3028
|
return type['set'] !== undefined;
|
|
3007
3029
|
}
|
|
3008
3030
|
constructor(initialValues) {
|
|
3009
|
-
this.$items = new Map();
|
|
3010
|
-
this.$indexes = new Map();
|
|
3011
|
-
this.deletedItems = {};
|
|
3012
|
-
this.$refId = 0;
|
|
3013
3031
|
this[$changes] = new ChangeTree(this);
|
|
3014
3032
|
this[$changes].indexes = {};
|
|
3015
3033
|
if (initialValues) {
|
|
@@ -3508,7 +3526,10 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3508
3526
|
? DEFAULT_VIEW_TAG
|
|
3509
3527
|
: value['view'];
|
|
3510
3528
|
}
|
|
3511
|
-
|
|
3529
|
+
// allow to define a field as not synced
|
|
3530
|
+
if (value['sync'] !== false) {
|
|
3531
|
+
fields[fieldName] = getNormalizedType(value);
|
|
3532
|
+
}
|
|
3512
3533
|
// If no explicit default provided, handle automatic instantiation for collection types
|
|
3513
3534
|
if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
|
|
3514
3535
|
// TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
|
|
@@ -3530,12 +3551,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3530
3551
|
}
|
|
3531
3552
|
else if (value['type'] !== undefined && Schema.is(value['type'])) {
|
|
3532
3553
|
// 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
|
-
}
|
|
3554
|
+
defaultValues[fieldName] = new value['type']();
|
|
3539
3555
|
}
|
|
3540
3556
|
}
|
|
3541
3557
|
else {
|
|
@@ -3545,12 +3561,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3545
3561
|
else if (typeof (value) === "function") {
|
|
3546
3562
|
if (Schema.is(value)) {
|
|
3547
3563
|
// 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
|
-
}
|
|
3564
|
+
defaultValues[fieldName] = new value();
|
|
3554
3565
|
fields[fieldName] = getNormalizedType(value);
|
|
3555
3566
|
}
|
|
3556
3567
|
else {
|
|
@@ -3577,32 +3588,13 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3577
3588
|
}
|
|
3578
3589
|
return defaults;
|
|
3579
3590
|
};
|
|
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
3591
|
/** @codegen-ignore */
|
|
3591
3592
|
const klass = Metadata.setFields(class extends inherits {
|
|
3592
3593
|
constructor(...args) {
|
|
3594
|
+
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3593
3595
|
// call initialize method
|
|
3594
3596
|
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] || {}));
|
|
3597
|
+
methods.initialize.apply(this, args);
|
|
3606
3598
|
}
|
|
3607
3599
|
}
|
|
3608
3600
|
}, fields);
|
|
@@ -3639,7 +3631,7 @@ function dumpChanges(schema) {
|
|
|
3639
3631
|
continue;
|
|
3640
3632
|
}
|
|
3641
3633
|
const changes = changeTree.indexedOperations;
|
|
3642
|
-
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3634
|
+
dump.refs.push(`refId#${changeTree.ref[$refId]}`);
|
|
3643
3635
|
for (const index in changes) {
|
|
3644
3636
|
const op = changes[index];
|
|
3645
3637
|
const opName = exports.OPERATION[op];
|
|
@@ -3653,13 +3645,14 @@ function dumpChanges(schema) {
|
|
|
3653
3645
|
return dump;
|
|
3654
3646
|
}
|
|
3655
3647
|
|
|
3656
|
-
var _a, _b;
|
|
3657
3648
|
/**
|
|
3658
3649
|
* Schema encoder / decoder
|
|
3659
3650
|
*/
|
|
3660
3651
|
class Schema {
|
|
3661
|
-
static
|
|
3662
|
-
static
|
|
3652
|
+
static [Symbol.metadata];
|
|
3653
|
+
static [$encoder] = encodeSchemaOperation;
|
|
3654
|
+
static [$decoder] = decodeSchemaOperation;
|
|
3655
|
+
[$refId];
|
|
3663
3656
|
/**
|
|
3664
3657
|
* Assign the property descriptors required to track changes on this instance.
|
|
3665
3658
|
* @param instance
|
|
@@ -3678,7 +3671,7 @@ class Schema {
|
|
|
3678
3671
|
/**
|
|
3679
3672
|
* Track property changes
|
|
3680
3673
|
*/
|
|
3681
|
-
static [
|
|
3674
|
+
static [$track](changeTree, index, operation = exports.OPERATION.ADD) {
|
|
3682
3675
|
changeTree.change(index, operation);
|
|
3683
3676
|
}
|
|
3684
3677
|
/**
|
|
@@ -3725,10 +3718,74 @@ class Schema {
|
|
|
3725
3718
|
Object.assign(this, arg);
|
|
3726
3719
|
}
|
|
3727
3720
|
}
|
|
3721
|
+
/**
|
|
3722
|
+
* Assign properties to the instance.
|
|
3723
|
+
* @param props Properties to assign to the instance
|
|
3724
|
+
* @returns
|
|
3725
|
+
*/
|
|
3728
3726
|
assign(props) {
|
|
3729
3727
|
Object.assign(this, props);
|
|
3730
3728
|
return this;
|
|
3731
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
|
+
}
|
|
3732
3789
|
/**
|
|
3733
3790
|
* (Server-side): Flag a property to be encoded for the next patch.
|
|
3734
3791
|
* @param instance Schema instance
|
|
@@ -3801,7 +3858,7 @@ class Schema {
|
|
|
3801
3858
|
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3802
3859
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3803
3860
|
const changeTree = ref[$changes];
|
|
3804
|
-
const refId =
|
|
3861
|
+
const refId = ref[$refId];
|
|
3805
3862
|
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3806
3863
|
// log reference count if > 1
|
|
3807
3864
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
@@ -3824,7 +3881,7 @@ class Schema {
|
|
|
3824
3881
|
let current = ref[$changes].root[changeSet].next;
|
|
3825
3882
|
while (current) {
|
|
3826
3883
|
if (current.changeTree) {
|
|
3827
|
-
encodeOrder.push(current.changeTree.refId);
|
|
3884
|
+
encodeOrder.push(current.changeTree.ref[$refId]);
|
|
3828
3885
|
}
|
|
3829
3886
|
current = current.next;
|
|
3830
3887
|
}
|
|
@@ -3845,7 +3902,7 @@ class Schema {
|
|
|
3845
3902
|
const changeTree = instance[$changes];
|
|
3846
3903
|
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3847
3904
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3848
|
-
let output = `${instance.constructor.name} (${
|
|
3905
|
+
let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
|
|
3849
3906
|
function dumpChangeSet(changeSet) {
|
|
3850
3907
|
changeSet.operations
|
|
3851
3908
|
.filter(op => op)
|
|
@@ -3859,14 +3916,14 @@ class Schema {
|
|
|
3859
3916
|
if (!isEncodeAll &&
|
|
3860
3917
|
changeTree.filteredChanges &&
|
|
3861
3918
|
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3862
|
-
output += `${instance.constructor.name} (${
|
|
3919
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
|
|
3863
3920
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3864
3921
|
}
|
|
3865
3922
|
// display filtered changes
|
|
3866
3923
|
if (isEncodeAll &&
|
|
3867
3924
|
changeTree.allFilteredChanges &&
|
|
3868
3925
|
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3869
|
-
output += `${instance.constructor.name} (${
|
|
3926
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
|
|
3870
3927
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3871
3928
|
}
|
|
3872
3929
|
return output;
|
|
@@ -3901,13 +3958,13 @@ class Schema {
|
|
|
3901
3958
|
}
|
|
3902
3959
|
}
|
|
3903
3960
|
if (includeChangeTree) {
|
|
3904
|
-
instanceRefIds.push(changeTree.refId);
|
|
3961
|
+
instanceRefIds.push(changeTree.ref[$refId]);
|
|
3905
3962
|
totalOperations += Object.keys(changes).length;
|
|
3906
3963
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3907
3964
|
}
|
|
3908
3965
|
}
|
|
3909
3966
|
output += "---\n";
|
|
3910
|
-
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3967
|
+
output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
|
|
3911
3968
|
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3912
3969
|
output += `Total changes: ${totalOperations}\n`;
|
|
3913
3970
|
output += "---\n";
|
|
@@ -3916,7 +3973,7 @@ class Schema {
|
|
|
3916
3973
|
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3917
3974
|
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3918
3975
|
if (!visitedParents.has(parentChangeTree)) {
|
|
3919
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3976
|
+
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
|
|
3920
3977
|
visitedParents.add(parentChangeTree);
|
|
3921
3978
|
}
|
|
3922
3979
|
});
|
|
@@ -3924,7 +3981,7 @@ class Schema {
|
|
|
3924
3981
|
const level = parentChangeTrees.length;
|
|
3925
3982
|
const indent = getIndent(level);
|
|
3926
3983
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3927
|
-
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`;
|
|
3928
3985
|
for (const index in changes) {
|
|
3929
3986
|
const operation = changes[index];
|
|
3930
3987
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
@@ -3934,61 +3991,35 @@ class Schema {
|
|
|
3934
3991
|
}
|
|
3935
3992
|
}
|
|
3936
3993
|
|
|
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
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
|
|
3967
4005
|
constructor(types) {
|
|
3968
4006
|
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
4007
|
}
|
|
3979
4008
|
getNextUniqueId() {
|
|
3980
4009
|
return this.nextUniqueId++;
|
|
3981
4010
|
}
|
|
3982
4011
|
add(changeTree) {
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
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();
|
|
3986
4016
|
}
|
|
3987
|
-
const
|
|
4017
|
+
const refId = ref[$refId];
|
|
4018
|
+
const isNewChangeTree = (this.changeTrees[refId] === undefined);
|
|
3988
4019
|
if (isNewChangeTree) {
|
|
3989
|
-
this.changeTrees[
|
|
4020
|
+
this.changeTrees[refId] = changeTree;
|
|
3990
4021
|
}
|
|
3991
|
-
const previousRefCount = this.refCount[
|
|
4022
|
+
const previousRefCount = this.refCount[refId];
|
|
3992
4023
|
if (previousRefCount === 0) {
|
|
3993
4024
|
//
|
|
3994
4025
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
@@ -4001,30 +4032,31 @@ class Root {
|
|
|
4001
4032
|
setOperationAtIndex(changeTree.changes, len);
|
|
4002
4033
|
}
|
|
4003
4034
|
}
|
|
4004
|
-
this.refCount[
|
|
4005
|
-
// 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 });
|
|
4006
4037
|
return isNewChangeTree;
|
|
4007
4038
|
}
|
|
4008
4039
|
remove(changeTree) {
|
|
4009
|
-
const
|
|
4010
|
-
|
|
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 });
|
|
4011
4043
|
if (refCount <= 0) {
|
|
4012
4044
|
//
|
|
4013
4045
|
// Only remove "root" reference if it's the last reference
|
|
4014
4046
|
//
|
|
4015
4047
|
changeTree.root = undefined;
|
|
4016
|
-
delete this.changeTrees[
|
|
4048
|
+
delete this.changeTrees[refId];
|
|
4017
4049
|
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
4018
4050
|
this.removeChangeFromChangeSet("changes", changeTree);
|
|
4019
4051
|
if (changeTree.filteredChanges) {
|
|
4020
4052
|
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
4021
4053
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
4022
4054
|
}
|
|
4023
|
-
this.refCount[
|
|
4055
|
+
this.refCount[refId] = 0;
|
|
4024
4056
|
changeTree.forEachChild((child, _) => {
|
|
4025
4057
|
if (child.removeParent(changeTree.ref)) {
|
|
4026
4058
|
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
|
|
4059
|
+
(child.parentChain && this.refCount[child.ref[$refId]] > 0) // parent is still in use, but has more than one reference, remove it
|
|
4028
4060
|
)) {
|
|
4029
4061
|
this.remove(child);
|
|
4030
4062
|
}
|
|
@@ -4036,7 +4068,7 @@ class Root {
|
|
|
4036
4068
|
});
|
|
4037
4069
|
}
|
|
4038
4070
|
else {
|
|
4039
|
-
this.refCount[
|
|
4071
|
+
this.refCount[refId] = refCount;
|
|
4040
4072
|
//
|
|
4041
4073
|
// When losing a reference to an instance, it is best to move the
|
|
4042
4074
|
// ChangeTree next to its parent in the encoding queue.
|
|
@@ -4186,10 +4218,19 @@ class Root {
|
|
|
4186
4218
|
}
|
|
4187
4219
|
}
|
|
4188
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
|
+
}
|
|
4189
4227
|
class Encoder {
|
|
4190
|
-
static
|
|
4228
|
+
static BUFFER_SIZE = 8 * 1024; // 8KB
|
|
4229
|
+
sharedBuffer = new Uint8Array(Encoder.BUFFER_SIZE);
|
|
4230
|
+
context;
|
|
4231
|
+
state;
|
|
4232
|
+
root;
|
|
4191
4233
|
constructor(state) {
|
|
4192
|
-
this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
|
|
4193
4234
|
//
|
|
4194
4235
|
// Use .cache() here to avoid re-creating a new context for every new room instance.
|
|
4195
4236
|
//
|
|
@@ -4217,7 +4258,7 @@ class Encoder {
|
|
|
4217
4258
|
const changeTree = current.changeTree;
|
|
4218
4259
|
if (hasView) {
|
|
4219
4260
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
4220
|
-
// 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() });
|
|
4221
4262
|
view.invisible.add(changeTree);
|
|
4222
4263
|
continue; // skip this change tree
|
|
4223
4264
|
}
|
|
@@ -4238,7 +4279,7 @@ class Encoder {
|
|
|
4238
4279
|
// (unless it "hasView", which will need to revisit the root)
|
|
4239
4280
|
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
4240
4281
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4241
|
-
encode.number(buffer,
|
|
4282
|
+
encode.number(buffer, ref[$refId], it);
|
|
4242
4283
|
}
|
|
4243
4284
|
for (let j = 0; j < numChanges; j++) {
|
|
4244
4285
|
const fieldIndex = changeSet.operations[j];
|
|
@@ -4267,10 +4308,9 @@ class Encoder {
|
|
|
4267
4308
|
}
|
|
4268
4309
|
}
|
|
4269
4310
|
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);
|
|
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;
|
|
4274
4314
|
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
4275
4315
|
|
|
4276
4316
|
import { Encoder } from "@colyseus/schema";
|
|
@@ -4280,7 +4320,9 @@ class Encoder {
|
|
|
4280
4320
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
4281
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
|
|
4282
4322
|
//
|
|
4283
|
-
|
|
4323
|
+
const newBuffer = new Uint8Array(newSize);
|
|
4324
|
+
newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
|
|
4325
|
+
buffer = newBuffer;
|
|
4284
4326
|
// assign resized buffer to local sharedBuffer
|
|
4285
4327
|
if (buffer === this.sharedBuffer) {
|
|
4286
4328
|
this.sharedBuffer = buffer;
|
|
@@ -4298,10 +4340,7 @@ class Encoder {
|
|
|
4298
4340
|
const viewOffset = it.offset;
|
|
4299
4341
|
// try to encode "filtered" changes
|
|
4300
4342
|
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4301
|
-
return
|
|
4302
|
-
bytes.subarray(0, sharedOffset),
|
|
4303
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4304
|
-
]);
|
|
4343
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4305
4344
|
}
|
|
4306
4345
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4307
4346
|
const viewOffset = it.offset;
|
|
@@ -4325,7 +4364,7 @@ class Encoder {
|
|
|
4325
4364
|
const encoder = ctor[$encoder];
|
|
4326
4365
|
const metadata = ctor[Symbol.metadata];
|
|
4327
4366
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4328
|
-
encode.number(bytes,
|
|
4367
|
+
encode.number(bytes, ref[$refId], it);
|
|
4329
4368
|
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
4330
4369
|
const index = Number(keys[i]);
|
|
4331
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")
|
|
@@ -4344,10 +4383,7 @@ class Encoder {
|
|
|
4344
4383
|
view.changes.clear();
|
|
4345
4384
|
// try to encode "filtered" changes
|
|
4346
4385
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4347
|
-
return
|
|
4348
|
-
bytes.subarray(0, sharedOffset),
|
|
4349
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4350
|
-
]);
|
|
4386
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4351
4387
|
}
|
|
4352
4388
|
discardChanges() {
|
|
4353
4389
|
// discard shared changes
|
|
@@ -4403,25 +4439,22 @@ class DecodingWarning extends Error {
|
|
|
4403
4439
|
}
|
|
4404
4440
|
}
|
|
4405
4441
|
class ReferenceTracker {
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
|
|
4410
|
-
|
|
4411
|
-
|
|
4412
|
-
|
|
4413
|
-
|
|
4414
|
-
|
|
4415
|
-
this.callbacks = {};
|
|
4416
|
-
this.nextUniqueId = 0;
|
|
4417
|
-
}
|
|
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;
|
|
4418
4451
|
getNextUniqueId() {
|
|
4419
4452
|
return this.nextUniqueId++;
|
|
4420
4453
|
}
|
|
4421
4454
|
// for decoding
|
|
4422
4455
|
addRef(refId, ref, incrementCount = true) {
|
|
4423
4456
|
this.refs.set(refId, ref);
|
|
4424
|
-
|
|
4457
|
+
ref[$refId] = refId;
|
|
4425
4458
|
if (incrementCount) {
|
|
4426
4459
|
this.refCount[refId] = (this.refCount[refId] || 0) + 1;
|
|
4427
4460
|
}
|
|
@@ -4478,9 +4511,12 @@ class ReferenceTracker {
|
|
|
4478
4511
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4479
4512
|
for (const index in metadata) {
|
|
4480
4513
|
const field = metadata[index].name;
|
|
4481
|
-
const
|
|
4482
|
-
if (
|
|
4483
|
-
|
|
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
|
+
}
|
|
4484
4520
|
}
|
|
4485
4521
|
}
|
|
4486
4522
|
}
|
|
@@ -4488,8 +4524,8 @@ class ReferenceTracker {
|
|
|
4488
4524
|
if (typeof (ref[$childType]) === "function") {
|
|
4489
4525
|
Array.from(ref.values())
|
|
4490
4526
|
.forEach((child) => {
|
|
4491
|
-
const childRefId =
|
|
4492
|
-
if (!this.deletedRefs.has(childRefId)) {
|
|
4527
|
+
const childRefId = child[$refId];
|
|
4528
|
+
if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
|
|
4493
4529
|
this.removeRef(childRefId);
|
|
4494
4530
|
}
|
|
4495
4531
|
});
|
|
@@ -4527,8 +4563,12 @@ class ReferenceTracker {
|
|
|
4527
4563
|
}
|
|
4528
4564
|
|
|
4529
4565
|
class Decoder {
|
|
4566
|
+
context;
|
|
4567
|
+
state;
|
|
4568
|
+
root;
|
|
4569
|
+
currentRefId = 0;
|
|
4570
|
+
triggerChanges;
|
|
4530
4571
|
constructor(root, context) {
|
|
4531
|
-
this.currentRefId = 0;
|
|
4532
4572
|
this.setState(root);
|
|
4533
4573
|
this.context = context || new TypeContext(root.constructor);
|
|
4534
4574
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
@@ -4617,7 +4657,7 @@ class Decoder {
|
|
|
4617
4657
|
}
|
|
4618
4658
|
removeChildRefs(ref, allChanges) {
|
|
4619
4659
|
const needRemoveRef = typeof (ref[$childType]) !== "string";
|
|
4620
|
-
const refId =
|
|
4660
|
+
const refId = ref[$refId];
|
|
4621
4661
|
ref.forEach((value, key) => {
|
|
4622
4662
|
allChanges.push({
|
|
4623
4663
|
ref: ref,
|
|
@@ -4628,7 +4668,7 @@ class Decoder {
|
|
|
4628
4668
|
previousValue: value
|
|
4629
4669
|
});
|
|
4630
4670
|
if (needRemoveRef) {
|
|
4631
|
-
this.root.removeRef(
|
|
4671
|
+
this.root.removeRef(value[$refId]);
|
|
4632
4672
|
}
|
|
4633
4673
|
});
|
|
4634
4674
|
}
|
|
@@ -4637,217 +4677,182 @@ class Decoder {
|
|
|
4637
4677
|
/**
|
|
4638
4678
|
* Reflection
|
|
4639
4679
|
*/
|
|
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
|
-
}
|
|
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));
|
|
4701
4716
|
}
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
pendingReflectionTypes[type.extendsId].push(type);
|
|
4717
|
+
}
|
|
4718
|
+
else {
|
|
4719
|
+
if (pendingReflectionTypes[type.extendsId] === undefined) {
|
|
4720
|
+
pendingReflectionTypes[type.extendsId] = [];
|
|
4707
4721
|
}
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
4730
|
-
|
|
4731
|
-
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
|
|
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;
|
|
4736
4761
|
}
|
|
4737
4762
|
else {
|
|
4738
|
-
|
|
4739
|
-
|
|
4740
|
-
|
|
4741
|
-
//
|
|
4742
|
-
if (Schema.is(field.type)) {
|
|
4743
|
-
fieldType = "ref";
|
|
4744
|
-
childTypeSchema = field.type;
|
|
4763
|
+
fieldType = Object.keys(field.type)[0];
|
|
4764
|
+
if (typeof (field.type[fieldType]) === "string") {
|
|
4765
|
+
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4745
4766
|
}
|
|
4746
4767
|
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
|
-
}
|
|
4768
|
+
childTypeSchema = field.type[fieldType];
|
|
4754
4769
|
}
|
|
4755
|
-
reflectionField.referencedType = (childTypeSchema)
|
|
4756
|
-
? context.getTypeId(childTypeSchema)
|
|
4757
|
-
: -1;
|
|
4758
4770
|
}
|
|
4759
|
-
reflectionField.
|
|
4760
|
-
|
|
4771
|
+
reflectionField.referencedType = (childTypeSchema)
|
|
4772
|
+
? context.getTypeId(childTypeSchema)
|
|
4773
|
+
: -1;
|
|
4761
4774
|
}
|
|
4775
|
+
reflectionField.type = fieldType;
|
|
4776
|
+
type.fields.push(reflectionField);
|
|
4762
4777
|
}
|
|
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
4778
|
}
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
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));
|
|
4772
4784
|
}
|
|
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
|
-
}
|
|
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);
|
|
4815
4817
|
}
|
|
4816
4818
|
else {
|
|
4817
|
-
Metadata.addField(metadata, fieldIndex, field.name,
|
|
4819
|
+
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4818
4820
|
}
|
|
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
|
-
});
|
|
4821
|
+
}
|
|
4822
|
+
else {
|
|
4823
|
+
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
4824
|
+
}
|
|
4839
4825
|
});
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
4844
|
-
|
|
4845
|
-
|
|
4846
|
-
|
|
4847
|
-
|
|
4848
|
-
|
|
4849
|
-
|
|
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
|
+
};
|
|
4850
4849
|
|
|
4850
|
+
/**
|
|
4851
|
+
* Legacy callback system
|
|
4852
|
+
*
|
|
4853
|
+
* @param decoder
|
|
4854
|
+
* @returns
|
|
4855
|
+
*/
|
|
4851
4856
|
function getDecoderStateCallbacks(decoder) {
|
|
4852
4857
|
const $root = decoder.root;
|
|
4853
4858
|
const callbacks = $root.callbacks;
|
|
@@ -4868,7 +4873,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4868
4873
|
//
|
|
4869
4874
|
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4870
4875
|
change.previousValue instanceof Schema) {
|
|
4871
|
-
const deleteCallbacks = callbacks[
|
|
4876
|
+
const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[exports.OPERATION.DELETE];
|
|
4872
4877
|
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4873
4878
|
deleteCallbacks[i]();
|
|
4874
4879
|
}
|
|
@@ -4957,7 +4962,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4957
4962
|
) {
|
|
4958
4963
|
callback(context.instance[prop], undefined);
|
|
4959
4964
|
}
|
|
4960
|
-
return $root.addCallback($
|
|
4965
|
+
return $root.addCallback(ref[$refId], prop, callback);
|
|
4961
4966
|
};
|
|
4962
4967
|
/**
|
|
4963
4968
|
* Schema instances
|
|
@@ -4977,7 +4982,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4977
4982
|
}
|
|
4978
4983
|
},
|
|
4979
4984
|
onChange: function onChange(callback) {
|
|
4980
|
-
return $root.addCallback(
|
|
4985
|
+
return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, callback);
|
|
4981
4986
|
},
|
|
4982
4987
|
//
|
|
4983
4988
|
// TODO: refactor `bindTo()` implementation.
|
|
@@ -4987,7 +4992,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4987
4992
|
if (!properties) {
|
|
4988
4993
|
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4989
4994
|
}
|
|
4990
|
-
return $root.addCallback(
|
|
4995
|
+
return $root.addCallback(context.instance[$refId], exports.OPERATION.REPLACE, () => {
|
|
4991
4996
|
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4992
4997
|
});
|
|
4993
4998
|
}
|
|
@@ -5006,13 +5011,13 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5006
5011
|
unbind?.();
|
|
5007
5012
|
}, false);
|
|
5008
5013
|
// has existing value
|
|
5009
|
-
if ($
|
|
5014
|
+
if (instance?.[$refId] !== undefined) {
|
|
5010
5015
|
callback(instance, true);
|
|
5011
5016
|
}
|
|
5012
5017
|
});
|
|
5013
5018
|
return getProxy(metadataField.type, {
|
|
5014
5019
|
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
5015
|
-
instance: ($
|
|
5020
|
+
instance: (instance?.[$refId] !== undefined && instance),
|
|
5016
5021
|
parentInstance: context.instance,
|
|
5017
5022
|
onInstanceAvailable,
|
|
5018
5023
|
});
|
|
@@ -5036,7 +5041,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5036
5041
|
if (immediate) {
|
|
5037
5042
|
ref.forEach((v, k) => callback(v, k));
|
|
5038
5043
|
}
|
|
5039
|
-
return $root.addCallback($
|
|
5044
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.ADD, (value, key) => {
|
|
5040
5045
|
onAddCalls.set(callback, true);
|
|
5041
5046
|
currentOnAddCallback = callback;
|
|
5042
5047
|
callback(value, key);
|
|
@@ -5045,10 +5050,10 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5045
5050
|
});
|
|
5046
5051
|
};
|
|
5047
5052
|
const onRemove = function (ref, callback) {
|
|
5048
|
-
return $root.addCallback($
|
|
5053
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.DELETE, callback);
|
|
5049
5054
|
};
|
|
5050
5055
|
const onChange = function (ref, callback) {
|
|
5051
|
-
return $root.addCallback($
|
|
5056
|
+
return $root.addCallback(ref[$refId], exports.OPERATION.REPLACE, callback);
|
|
5052
5057
|
};
|
|
5053
5058
|
return new Proxy({
|
|
5054
5059
|
onAdd: function (callback, immediate = true) {
|
|
@@ -5117,22 +5122,360 @@ function getRawChangesCallback(decoder, callback) {
|
|
|
5117
5122
|
decoder.triggerChanges = callback;
|
|
5118
5123
|
}
|
|
5119
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
|
+
|
|
5120
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();
|
|
5121
5477
|
constructor(iterable = false) {
|
|
5122
5478
|
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
5479
|
if (iterable) {
|
|
5137
5480
|
this.items = [];
|
|
5138
5481
|
}
|
|
@@ -5146,7 +5489,7 @@ class StateView {
|
|
|
5146
5489
|
return false;
|
|
5147
5490
|
}
|
|
5148
5491
|
else if (!parentChangeTree &&
|
|
5149
|
-
|
|
5492
|
+
obj[$refId] !== 0 // allow root object
|
|
5150
5493
|
) {
|
|
5151
5494
|
/**
|
|
5152
5495
|
* TODO: can we avoid this?
|
|
@@ -5170,11 +5513,11 @@ class StateView {
|
|
|
5170
5513
|
if (checkIncludeParent && parentChangeTree) {
|
|
5171
5514
|
this.addParentOf(changeTree, tag);
|
|
5172
5515
|
}
|
|
5173
|
-
let changes = this.changes.get(
|
|
5516
|
+
let changes = this.changes.get(obj[$refId]);
|
|
5174
5517
|
if (changes === undefined) {
|
|
5175
5518
|
changes = {};
|
|
5176
5519
|
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5177
|
-
this.changes.set(
|
|
5520
|
+
this.changes.set(obj[$refId], changes);
|
|
5178
5521
|
}
|
|
5179
5522
|
let isChildAdded = false;
|
|
5180
5523
|
//
|
|
@@ -5254,10 +5597,10 @@ class StateView {
|
|
|
5254
5597
|
}
|
|
5255
5598
|
// add parent's tag properties
|
|
5256
5599
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
5257
|
-
let changes = this.changes.get(changeTree.refId);
|
|
5600
|
+
let changes = this.changes.get(changeTree.ref[$refId]);
|
|
5258
5601
|
if (changes === undefined) {
|
|
5259
5602
|
changes = {};
|
|
5260
|
-
this.changes.set(changeTree.refId, changes);
|
|
5603
|
+
this.changes.set(changeTree.ref[$refId], changes);
|
|
5261
5604
|
}
|
|
5262
5605
|
if (!this.tags) {
|
|
5263
5606
|
this.tags = new WeakMap();
|
|
@@ -5289,27 +5632,28 @@ class StateView {
|
|
|
5289
5632
|
}
|
|
5290
5633
|
const ref = changeTree.ref;
|
|
5291
5634
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5292
|
-
|
|
5635
|
+
const refId = ref[$refId];
|
|
5636
|
+
let changes = this.changes.get(refId);
|
|
5293
5637
|
if (changes === undefined) {
|
|
5294
5638
|
changes = {};
|
|
5295
|
-
this.changes.set(
|
|
5639
|
+
this.changes.set(refId, changes);
|
|
5296
5640
|
}
|
|
5297
5641
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5298
5642
|
// parent is collection (Map/Array)
|
|
5299
5643
|
const parent = changeTree.parent;
|
|
5300
5644
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5301
|
-
const
|
|
5302
|
-
let changes = this.changes.get(
|
|
5645
|
+
const parentRefId = parent[$refId];
|
|
5646
|
+
let changes = this.changes.get(parentRefId);
|
|
5303
5647
|
if (changes === undefined) {
|
|
5304
5648
|
changes = {};
|
|
5305
|
-
this.changes.set(
|
|
5649
|
+
this.changes.set(parentRefId, changes);
|
|
5306
5650
|
}
|
|
5307
5651
|
else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5308
5652
|
//
|
|
5309
5653
|
// SAME PATCH ADD + REMOVE:
|
|
5310
5654
|
// The 'changes' of deleted structure should be ignored.
|
|
5311
5655
|
//
|
|
5312
|
-
this.changes.delete(
|
|
5656
|
+
this.changes.delete(refId);
|
|
5313
5657
|
}
|
|
5314
5658
|
// DELETE / DELETE BY REF ID
|
|
5315
5659
|
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
@@ -5369,7 +5713,7 @@ class StateView {
|
|
|
5369
5713
|
if (!isVisible && changeTree.isVisibilitySharedWithParent) {
|
|
5370
5714
|
// console.log("CHECK AGAINST PARENT...", {
|
|
5371
5715
|
// ref: changeTree.ref.constructor.name,
|
|
5372
|
-
// refId: changeTree.refId,
|
|
5716
|
+
// refId: changeTree.ref[$refId],
|
|
5373
5717
|
// parent: changeTree.parent.constructor.name,
|
|
5374
5718
|
// });
|
|
5375
5719
|
if (this.visible.has(changeTree.parent[$changes])) {
|
|
@@ -5399,8 +5743,10 @@ exports.$deleteByIndex = $deleteByIndex;
|
|
|
5399
5743
|
exports.$encoder = $encoder;
|
|
5400
5744
|
exports.$filter = $filter;
|
|
5401
5745
|
exports.$getByIndex = $getByIndex;
|
|
5746
|
+
exports.$refId = $refId;
|
|
5402
5747
|
exports.$track = $track;
|
|
5403
5748
|
exports.ArraySchema = ArraySchema;
|
|
5749
|
+
exports.Callbacks = Callbacks;
|
|
5404
5750
|
exports.ChangeTree = ChangeTree;
|
|
5405
5751
|
exports.CollectionSchema = CollectionSchema;
|
|
5406
5752
|
exports.Decoder = Decoder;
|
|
@@ -5412,6 +5758,7 @@ exports.ReflectionField = ReflectionField;
|
|
|
5412
5758
|
exports.ReflectionType = ReflectionType;
|
|
5413
5759
|
exports.Schema = Schema;
|
|
5414
5760
|
exports.SetSchema = SetSchema;
|
|
5761
|
+
exports.StateCallbackStrategy = StateCallbackStrategy;
|
|
5415
5762
|
exports.StateView = StateView;
|
|
5416
5763
|
exports.TypeContext = TypeContext;
|
|
5417
5764
|
exports.decode = decode;
|