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