@colyseus/schema 3.0.75 → 4.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/index.js +780 -429
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +778 -430
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +780 -429
- package/lib/Reflection.d.ts +50 -17
- package/lib/Reflection.js +151 -202
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +13 -1
- package/lib/Schema.js +73 -9
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +6 -1
- package/lib/annotations.js +8 -34
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +34 -1
- package/lib/bench_encode.js.map +1 -1
- package/lib/codegen/api.js +35 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/cli.js +4 -1
- package/lib/codegen/cli.js.map +1 -1
- package/lib/codegen/parser.js +35 -2
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +34 -1
- package/lib/codegen/types.js.map +1 -1
- package/lib/decoder/DecodeOperation.d.ts +2 -2
- package/lib/decoder/DecodeOperation.js +3 -3
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +3 -3
- package/lib/decoder/Decoder.js +2 -2
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/ReferenceTracker.d.ts +0 -1
- package/lib/decoder/ReferenceTracker.js +9 -7
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/decoder/strategy/Callbacks.d.ts +154 -0
- package/lib/decoder/strategy/Callbacks.js +340 -0
- package/lib/decoder/strategy/Callbacks.js.map +1 -0
- package/lib/decoder/strategy/{StateCallbacks.d.ts → getDecoderStateCallbacks.d.ts} +6 -0
- package/lib/decoder/strategy/{StateCallbacks.js → getDecoderStateCallbacks.js} +17 -10
- package/lib/decoder/strategy/getDecoderStateCallbacks.js.map +1 -0
- package/lib/encoder/ChangeTree.d.ts +2 -2
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +2 -2
- package/lib/encoder/EncodeOperation.js +3 -3
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +6 -6
- package/lib/encoder/Encoder.js +19 -18
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.js +17 -14
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.js +13 -12
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/decode.d.ts +2 -2
- package/lib/encoding/encode.d.ts +3 -1
- package/lib/encoding/encode.js.map +1 -1
- package/lib/index.d.ts +3 -2
- package/lib/index.js +7 -3
- package/lib/index.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +7 -14
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/custom/ArraySchema.d.ts +2 -1
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.d.ts +2 -1
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -2
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.d.ts +2 -1
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/symbols.d.ts +1 -0
- package/lib/types/symbols.js +2 -1
- package/lib/types/symbols.js.map +1 -1
- package/lib/utils.js +1 -1
- package/lib/utils.js.map +1 -1
- package/package.json +12 -16
- package/src/Reflection.ts +185 -174
- package/src/Schema.ts +81 -13
- package/src/annotations.ts +14 -40
- package/src/codegen/parser.ts +1 -1
- package/src/decoder/DecodeOperation.ts +9 -9
- package/src/decoder/Decoder.ts +6 -6
- package/src/decoder/ReferenceTracker.ts +10 -8
- package/src/decoder/strategy/Callbacks.ts +547 -0
- package/src/decoder/strategy/{StateCallbacks.ts → getDecoderStateCallbacks.ts} +17 -11
- package/src/encoder/ChangeTree.ts +4 -7
- package/src/encoder/EncodeOperation.ts +9 -9
- package/src/encoder/Encoder.ts +26 -18
- package/src/encoder/Root.ts +20 -15
- package/src/encoder/StateView.ts +15 -13
- package/src/encoding/encode.ts +1 -1
- package/src/index.ts +3 -2
- package/src/types/HelperTypes.ts +13 -11
- package/src/types/custom/ArraySchema.ts +2 -1
- package/src/types/custom/CollectionSchema.ts +4 -2
- package/src/types/custom/MapSchema.ts +4 -2
- package/src/types/custom/SetSchema.ts +3 -1
- package/src/types/symbols.ts +1 -0
- package/src/utils.ts +2 -2
- package/lib/Decoder.d.ts +0 -16
- package/lib/Decoder.js +0 -182
- package/lib/Decoder.js.map +0 -1
- package/lib/Encoder.d.ts +0 -13
- package/lib/Encoder.js +0 -79
- package/lib/Encoder.js.map +0 -1
- package/lib/changes/ChangeSet.d.ts +0 -12
- package/lib/changes/ChangeSet.js +0 -35
- package/lib/changes/ChangeSet.js.map +0 -1
- package/lib/changes/ChangeTree.d.ts +0 -53
- package/lib/changes/ChangeTree.js +0 -202
- package/lib/changes/ChangeTree.js.map +0 -1
- package/lib/changes/DecodeOperation.d.ts +0 -15
- package/lib/changes/DecodeOperation.js +0 -186
- package/lib/changes/DecodeOperation.js.map +0 -1
- package/lib/changes/EncodeOperation.d.ts +0 -18
- package/lib/changes/EncodeOperation.js +0 -130
- package/lib/changes/EncodeOperation.js.map +0 -1
- package/lib/changes/ReferenceTracker.d.ts +0 -14
- package/lib/changes/ReferenceTracker.js +0 -83
- package/lib/changes/ReferenceTracker.js.map +0 -1
- package/lib/changes/consts.d.ts +0 -14
- package/lib/changes/consts.js +0 -18
- package/lib/changes/consts.js.map +0 -1
- package/lib/decoder/strategy/StateCallbacks.js.map +0 -1
- package/lib/decoding/decode.d.ts +0 -48
- package/lib/decoding/decode.js +0 -267
- package/lib/decoding/decode.js.map +0 -1
- package/lib/ecs.d.ts +0 -11
- package/lib/ecs.js +0 -160
- package/lib/ecs.js.map +0 -1
- package/lib/filters/index.d.ts +0 -8
- package/lib/filters/index.js +0 -24
- package/lib/filters/index.js.map +0 -1
- package/lib/spec.d.ts +0 -13
- package/lib/spec.js +0 -42
- package/lib/spec.js.map +0 -1
- package/lib/types/ArraySchema.d.ts +0 -238
- package/lib/types/ArraySchema.js +0 -555
- package/lib/types/ArraySchema.js.map +0 -1
- package/lib/types/CollectionSchema.d.ts +0 -35
- package/lib/types/CollectionSchema.js +0 -150
- package/lib/types/CollectionSchema.js.map +0 -1
- package/lib/types/MapSchema.d.ts +0 -38
- package/lib/types/MapSchema.js +0 -215
- package/lib/types/MapSchema.js.map +0 -1
- package/lib/types/SetSchema.d.ts +0 -32
- package/lib/types/SetSchema.js +0 -162
- package/lib/types/SetSchema.js.map +0 -1
- package/lib/types/typeRegistry.d.ts +0 -5
- package/lib/types/typeRegistry.js +0 -13
- package/lib/types/typeRegistry.js.map +0 -1
- package/lib/usage.d.ts +0 -1
- package/lib/usage.js +0 -22
- package/lib/usage.js.map +0 -1
- package/lib/v3.d.ts +0 -1
- package/lib/v3.js +0 -427
- package/lib/v3.js.map +0 -1
- package/lib/v3_experiment.d.ts +0 -1
- package/lib/v3_experiment.js +0 -407
- package/lib/v3_experiment.js.map +0 -1
package/build/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
|
}
|
|
@@ -978,25 +979,33 @@ function deleteOperationAtIndex(changeSet, index) {
|
|
|
978
979
|
delete changeSet.indexes[index];
|
|
979
980
|
}
|
|
980
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;
|
|
981
1008
|
constructor(ref) {
|
|
982
|
-
/**
|
|
983
|
-
* Whether this structure is parent of a filtered structure.
|
|
984
|
-
*/
|
|
985
|
-
this.isFiltered = false;
|
|
986
|
-
this.indexedOperations = {};
|
|
987
|
-
//
|
|
988
|
-
// TODO:
|
|
989
|
-
// try storing the index + operation per item.
|
|
990
|
-
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
991
|
-
//
|
|
992
|
-
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
993
|
-
//
|
|
994
|
-
this.changes = { indexes: {}, operations: [] };
|
|
995
|
-
this.allChanges = { indexes: {}, operations: [] };
|
|
996
|
-
/**
|
|
997
|
-
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
998
|
-
*/
|
|
999
|
-
this.isNew = true;
|
|
1000
1009
|
this.ref = ref;
|
|
1001
1010
|
this.metadata = ref.constructor[Symbol.metadata];
|
|
1002
1011
|
//
|
|
@@ -1468,7 +1477,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
1468
1477
|
// Encode refId for this instance.
|
|
1469
1478
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1470
1479
|
//
|
|
1471
|
-
encode.number(bytes, value[$
|
|
1480
|
+
encode.number(bytes, value[$refId], it);
|
|
1472
1481
|
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
1473
1482
|
if ((operation & OPERATION.ADD) === OPERATION.ADD) {
|
|
1474
1483
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
@@ -1479,7 +1488,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
1479
1488
|
// Encode refId for this instance.
|
|
1480
1489
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1481
1490
|
//
|
|
1482
|
-
encode.number(bytes, value[$
|
|
1491
|
+
encode.number(bytes, value[$refId], it);
|
|
1483
1492
|
}
|
|
1484
1493
|
}
|
|
1485
1494
|
/**
|
|
@@ -1555,7 +1564,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
|
|
|
1555
1564
|
if (!item) {
|
|
1556
1565
|
return;
|
|
1557
1566
|
}
|
|
1558
|
-
refOrIndex = item[$
|
|
1567
|
+
refOrIndex = item[$refId];
|
|
1559
1568
|
if (operation === OPERATION.DELETE) {
|
|
1560
1569
|
operation = OPERATION.DELETE_BY_REFID;
|
|
1561
1570
|
}
|
|
@@ -1595,7 +1604,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1595
1604
|
let value;
|
|
1596
1605
|
if ((operation & OPERATION.DELETE) === OPERATION.DELETE) {
|
|
1597
1606
|
// Flag `refId` for garbage collection.
|
|
1598
|
-
const previousRefId = $
|
|
1607
|
+
const previousRefId = previousValue?.[$refId];
|
|
1599
1608
|
if (previousRefId !== undefined) {
|
|
1600
1609
|
$root.removeRef(previousRefId);
|
|
1601
1610
|
}
|
|
@@ -1636,7 +1645,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1636
1645
|
value = valueRef.clone(true);
|
|
1637
1646
|
value[$childType] = Object.values(type)[0]; // cache childType for ArraySchema and MapSchema
|
|
1638
1647
|
if (previousValue) {
|
|
1639
|
-
let previousRefId = $
|
|
1648
|
+
let previousRefId = previousValue[$refId];
|
|
1640
1649
|
if (previousRefId !== undefined && refId !== previousRefId) {
|
|
1641
1650
|
//
|
|
1642
1651
|
// enqueue onRemove if structure has been replaced.
|
|
@@ -1647,7 +1656,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1647
1656
|
const [key, value] = iter.value;
|
|
1648
1657
|
// if value is a schema, remove its reference
|
|
1649
1658
|
if (typeof (value) === "object") {
|
|
1650
|
-
previousRefId = $
|
|
1659
|
+
previousRefId = value[$refId];
|
|
1651
1660
|
$root.removeRef(previousRefId);
|
|
1652
1661
|
}
|
|
1653
1662
|
allChanges.push({
|
|
@@ -1876,7 +1885,6 @@ function assertInstanceType(value, type, instance, field) {
|
|
|
1876
1885
|
}
|
|
1877
1886
|
}
|
|
1878
1887
|
|
|
1879
|
-
var _a$4, _b$4;
|
|
1880
1888
|
const DEFAULT_SORT = (a, b) => {
|
|
1881
1889
|
const A = a.toString();
|
|
1882
1890
|
const B = b.toString();
|
|
@@ -1888,8 +1896,15 @@ const DEFAULT_SORT = (a, b) => {
|
|
|
1888
1896
|
return 0;
|
|
1889
1897
|
};
|
|
1890
1898
|
class ArraySchema {
|
|
1891
|
-
|
|
1892
|
-
|
|
1899
|
+
[$changes];
|
|
1900
|
+
[$refId];
|
|
1901
|
+
[$childType];
|
|
1902
|
+
items = [];
|
|
1903
|
+
tmpItems = [];
|
|
1904
|
+
deletedIndexes = {};
|
|
1905
|
+
isMovingItems = false;
|
|
1906
|
+
static [$encoder] = encodeArray;
|
|
1907
|
+
static [$decoder] = decodeArray;
|
|
1893
1908
|
/**
|
|
1894
1909
|
* Determine if a property must be filtered.
|
|
1895
1910
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -1899,7 +1914,7 @@ class ArraySchema {
|
|
|
1899
1914
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
1900
1915
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
1901
1916
|
*/
|
|
1902
|
-
static [
|
|
1917
|
+
static [$filter](ref, index, view) {
|
|
1903
1918
|
return (!view ||
|
|
1904
1919
|
typeof (ref[$childType]) === "string" ||
|
|
1905
1920
|
view.isChangeTreeVisible(ref['tmpItems'][index]?.[$changes]));
|
|
@@ -1915,10 +1930,6 @@ class ArraySchema {
|
|
|
1915
1930
|
return new ArraySchema(...Array.from(iterable));
|
|
1916
1931
|
}
|
|
1917
1932
|
constructor(...items) {
|
|
1918
|
-
this.items = [];
|
|
1919
|
-
this.tmpItems = [];
|
|
1920
|
-
this.deletedIndexes = {};
|
|
1921
|
-
this.isMovingItems = false;
|
|
1922
1933
|
Object.defineProperty(this, $childType, {
|
|
1923
1934
|
value: undefined,
|
|
1924
1935
|
enumerable: false,
|
|
@@ -2435,6 +2446,10 @@ class ArraySchema {
|
|
|
2435
2446
|
static get [Symbol.species]() {
|
|
2436
2447
|
return ArraySchema;
|
|
2437
2448
|
}
|
|
2449
|
+
// WORKAROUND for compatibility
|
|
2450
|
+
// - TypeScript 4 defines @@unscopables as a function
|
|
2451
|
+
// - TypeScript 5 defines @@unscopables as an object
|
|
2452
|
+
[Symbol.unscopables];
|
|
2438
2453
|
/**
|
|
2439
2454
|
* Returns an iterable of key, value pairs for every entry in the array
|
|
2440
2455
|
*/
|
|
@@ -2544,7 +2559,7 @@ class ArraySchema {
|
|
|
2544
2559
|
this.isMovingItems = false;
|
|
2545
2560
|
return this;
|
|
2546
2561
|
}
|
|
2547
|
-
[
|
|
2562
|
+
[$getByIndex](index, isEncodeAll = false) {
|
|
2548
2563
|
//
|
|
2549
2564
|
// TODO: avoid unecessary `this.tmpItems` check during decoding.
|
|
2550
2565
|
//
|
|
@@ -2599,10 +2614,16 @@ class ArraySchema {
|
|
|
2599
2614
|
}
|
|
2600
2615
|
registerType("array", { constructor: ArraySchema });
|
|
2601
2616
|
|
|
2602
|
-
var _a$3, _b$3;
|
|
2603
2617
|
class MapSchema {
|
|
2604
|
-
|
|
2605
|
-
|
|
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;
|
|
2606
2627
|
/**
|
|
2607
2628
|
* Determine if a property must be filtered.
|
|
2608
2629
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2612,7 +2633,7 @@ class MapSchema {
|
|
|
2612
2633
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2613
2634
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2614
2635
|
*/
|
|
2615
|
-
static [
|
|
2636
|
+
static [$filter](ref, index, view) {
|
|
2616
2637
|
return (!view ||
|
|
2617
2638
|
typeof (ref[$childType]) === "string" ||
|
|
2618
2639
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2621,9 +2642,6 @@ class MapSchema {
|
|
|
2621
2642
|
return type['map'] !== undefined;
|
|
2622
2643
|
}
|
|
2623
2644
|
constructor(initialValues) {
|
|
2624
|
-
this.$items = new Map();
|
|
2625
|
-
this.$indexes = new Map();
|
|
2626
|
-
this.deletedItems = {};
|
|
2627
2645
|
const changeTree = new ChangeTree(this);
|
|
2628
2646
|
changeTree.indexes = {};
|
|
2629
2647
|
Object.defineProperty(this, $changes, {
|
|
@@ -2814,10 +2832,16 @@ class MapSchema {
|
|
|
2814
2832
|
}
|
|
2815
2833
|
registerType("map", { constructor: MapSchema });
|
|
2816
2834
|
|
|
2817
|
-
var _a$2, _b$2;
|
|
2818
2835
|
class CollectionSchema {
|
|
2819
|
-
|
|
2820
|
-
|
|
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;
|
|
2821
2845
|
/**
|
|
2822
2846
|
* Determine if a property must be filtered.
|
|
2823
2847
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2827,7 +2851,7 @@ class CollectionSchema {
|
|
|
2827
2851
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2828
2852
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2829
2853
|
*/
|
|
2830
|
-
static [
|
|
2854
|
+
static [$filter](ref, index, view) {
|
|
2831
2855
|
return (!view ||
|
|
2832
2856
|
typeof (ref[$childType]) === "string" ||
|
|
2833
2857
|
view.isChangeTreeVisible((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -2836,10 +2860,6 @@ class CollectionSchema {
|
|
|
2836
2860
|
return type['collection'] !== undefined;
|
|
2837
2861
|
}
|
|
2838
2862
|
constructor(initialValues) {
|
|
2839
|
-
this.$items = new Map();
|
|
2840
|
-
this.$indexes = new Map();
|
|
2841
|
-
this.deletedItems = {};
|
|
2842
|
-
this.$refId = 0;
|
|
2843
2863
|
this[$changes] = new ChangeTree(this);
|
|
2844
2864
|
this[$changes].indexes = {};
|
|
2845
2865
|
if (initialValues) {
|
|
@@ -2978,10 +2998,16 @@ class CollectionSchema {
|
|
|
2978
2998
|
}
|
|
2979
2999
|
registerType("collection", { constructor: CollectionSchema, });
|
|
2980
3000
|
|
|
2981
|
-
var _a$1, _b$1;
|
|
2982
3001
|
class SetSchema {
|
|
2983
|
-
|
|
2984
|
-
|
|
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;
|
|
2985
3011
|
/**
|
|
2986
3012
|
* Determine if a property must be filtered.
|
|
2987
3013
|
* - If returns false, the property is NOT going to be encoded.
|
|
@@ -2991,7 +3017,7 @@ class SetSchema {
|
|
|
2991
3017
|
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
2992
3018
|
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
2993
3019
|
*/
|
|
2994
|
-
static [
|
|
3020
|
+
static [$filter](ref, index, view) {
|
|
2995
3021
|
return (!view ||
|
|
2996
3022
|
typeof (ref[$childType]) === "string" ||
|
|
2997
3023
|
view.visible.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes]));
|
|
@@ -3000,10 +3026,6 @@ class SetSchema {
|
|
|
3000
3026
|
return type['set'] !== undefined;
|
|
3001
3027
|
}
|
|
3002
3028
|
constructor(initialValues) {
|
|
3003
|
-
this.$items = new Map();
|
|
3004
|
-
this.$indexes = new Map();
|
|
3005
|
-
this.deletedItems = {};
|
|
3006
|
-
this.$refId = 0;
|
|
3007
3029
|
this[$changes] = new ChangeTree(this);
|
|
3008
3030
|
this[$changes].indexes = {};
|
|
3009
3031
|
if (initialValues) {
|
|
@@ -3502,7 +3524,10 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3502
3524
|
? DEFAULT_VIEW_TAG
|
|
3503
3525
|
: value['view'];
|
|
3504
3526
|
}
|
|
3505
|
-
|
|
3527
|
+
// allow to define a field as not synced
|
|
3528
|
+
if (value['sync'] !== false) {
|
|
3529
|
+
fields[fieldName] = getNormalizedType(value);
|
|
3530
|
+
}
|
|
3506
3531
|
// If no explicit default provided, handle automatic instantiation for collection types
|
|
3507
3532
|
if (!Object.prototype.hasOwnProperty.call(value, 'default')) {
|
|
3508
3533
|
// TODO: remove Array.isArray() check. Use ['array'] !== undefined only.
|
|
@@ -3524,12 +3549,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3524
3549
|
}
|
|
3525
3550
|
else if (value['type'] !== undefined && Schema.is(value['type'])) {
|
|
3526
3551
|
// Direct Schema type: Type → new Type()
|
|
3527
|
-
|
|
3528
|
-
// only auto-initialize Schema instances if:
|
|
3529
|
-
// - they don't have an initialize method
|
|
3530
|
-
// - or initialize method doesn't accept any parameters
|
|
3531
|
-
defaultValues[fieldName] = new value['type']();
|
|
3532
|
-
}
|
|
3552
|
+
defaultValues[fieldName] = new value['type']();
|
|
3533
3553
|
}
|
|
3534
3554
|
}
|
|
3535
3555
|
else {
|
|
@@ -3539,12 +3559,7 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3539
3559
|
else if (typeof (value) === "function") {
|
|
3540
3560
|
if (Schema.is(value)) {
|
|
3541
3561
|
// Direct Schema type: Type → new Type()
|
|
3542
|
-
|
|
3543
|
-
// only auto-initialize Schema instances if:
|
|
3544
|
-
// - they don't have an initialize method
|
|
3545
|
-
// - or initialize method doesn't accept any parameters
|
|
3546
|
-
defaultValues[fieldName] = new value();
|
|
3547
|
-
}
|
|
3562
|
+
defaultValues[fieldName] = new value();
|
|
3548
3563
|
fields[fieldName] = getNormalizedType(value);
|
|
3549
3564
|
}
|
|
3550
3565
|
else {
|
|
@@ -3571,32 +3586,13 @@ function schema(fieldsAndMethods, name, inherits = Schema) {
|
|
|
3571
3586
|
}
|
|
3572
3587
|
return defaults;
|
|
3573
3588
|
};
|
|
3574
|
-
const getParentProps = (props) => {
|
|
3575
|
-
const fieldNames = Object.keys(fields);
|
|
3576
|
-
const parentProps = {};
|
|
3577
|
-
for (const key in props) {
|
|
3578
|
-
if (!fieldNames.includes(key)) {
|
|
3579
|
-
parentProps[key] = props[key];
|
|
3580
|
-
}
|
|
3581
|
-
}
|
|
3582
|
-
return parentProps;
|
|
3583
|
-
};
|
|
3584
3589
|
/** @codegen-ignore */
|
|
3585
3590
|
const klass = Metadata.setFields(class extends inherits {
|
|
3586
3591
|
constructor(...args) {
|
|
3592
|
+
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3587
3593
|
// call initialize method
|
|
3588
3594
|
if (methods.initialize && typeof methods.initialize === 'function') {
|
|
3589
|
-
|
|
3590
|
-
/**
|
|
3591
|
-
* only call initialize() in the current class, not the parent ones.
|
|
3592
|
-
* see "should not call initialize automatically when creating an instance of inherited Schema"
|
|
3593
|
-
*/
|
|
3594
|
-
if (new.target === klass) {
|
|
3595
|
-
methods.initialize.apply(this, args);
|
|
3596
|
-
}
|
|
3597
|
-
}
|
|
3598
|
-
else {
|
|
3599
|
-
super(Object.assign({}, getDefaultValues(), args[0] || {}));
|
|
3595
|
+
methods.initialize.apply(this, args);
|
|
3600
3596
|
}
|
|
3601
3597
|
}
|
|
3602
3598
|
}, fields);
|
|
@@ -3633,7 +3629,7 @@ function dumpChanges(schema) {
|
|
|
3633
3629
|
continue;
|
|
3634
3630
|
}
|
|
3635
3631
|
const changes = changeTree.indexedOperations;
|
|
3636
|
-
dump.refs.push(`refId#${changeTree.refId}`);
|
|
3632
|
+
dump.refs.push(`refId#${changeTree.ref[$refId]}`);
|
|
3637
3633
|
for (const index in changes) {
|
|
3638
3634
|
const op = changes[index];
|
|
3639
3635
|
const opName = OPERATION[op];
|
|
@@ -3647,13 +3643,14 @@ function dumpChanges(schema) {
|
|
|
3647
3643
|
return dump;
|
|
3648
3644
|
}
|
|
3649
3645
|
|
|
3650
|
-
var _a, _b;
|
|
3651
3646
|
/**
|
|
3652
3647
|
* Schema encoder / decoder
|
|
3653
3648
|
*/
|
|
3654
3649
|
class Schema {
|
|
3655
|
-
static
|
|
3656
|
-
static
|
|
3650
|
+
static [Symbol.metadata];
|
|
3651
|
+
static [$encoder] = encodeSchemaOperation;
|
|
3652
|
+
static [$decoder] = decodeSchemaOperation;
|
|
3653
|
+
[$refId];
|
|
3657
3654
|
/**
|
|
3658
3655
|
* Assign the property descriptors required to track changes on this instance.
|
|
3659
3656
|
* @param instance
|
|
@@ -3672,7 +3669,7 @@ class Schema {
|
|
|
3672
3669
|
/**
|
|
3673
3670
|
* Track property changes
|
|
3674
3671
|
*/
|
|
3675
|
-
static [
|
|
3672
|
+
static [$track](changeTree, index, operation = OPERATION.ADD) {
|
|
3676
3673
|
changeTree.change(index, operation);
|
|
3677
3674
|
}
|
|
3678
3675
|
/**
|
|
@@ -3719,10 +3716,74 @@ class Schema {
|
|
|
3719
3716
|
Object.assign(this, arg);
|
|
3720
3717
|
}
|
|
3721
3718
|
}
|
|
3719
|
+
/**
|
|
3720
|
+
* Assign properties to the instance.
|
|
3721
|
+
* @param props Properties to assign to the instance
|
|
3722
|
+
* @returns
|
|
3723
|
+
*/
|
|
3722
3724
|
assign(props) {
|
|
3723
3725
|
Object.assign(this, props);
|
|
3724
3726
|
return this;
|
|
3725
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
|
+
}
|
|
3726
3787
|
/**
|
|
3727
3788
|
* (Server-side): Flag a property to be encoded for the next patch.
|
|
3728
3789
|
* @param instance Schema instance
|
|
@@ -3795,7 +3856,7 @@ class Schema {
|
|
|
3795
3856
|
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3796
3857
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3797
3858
|
const changeTree = ref[$changes];
|
|
3798
|
-
const refId =
|
|
3859
|
+
const refId = ref[$refId];
|
|
3799
3860
|
const root = (decoder) ? decoder.root : changeTree.root;
|
|
3800
3861
|
// log reference count if > 1
|
|
3801
3862
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
@@ -3818,7 +3879,7 @@ class Schema {
|
|
|
3818
3879
|
let current = ref[$changes].root[changeSet].next;
|
|
3819
3880
|
while (current) {
|
|
3820
3881
|
if (current.changeTree) {
|
|
3821
|
-
encodeOrder.push(current.changeTree.refId);
|
|
3882
|
+
encodeOrder.push(current.changeTree.ref[$refId]);
|
|
3822
3883
|
}
|
|
3823
3884
|
current = current.next;
|
|
3824
3885
|
}
|
|
@@ -3839,7 +3900,7 @@ class Schema {
|
|
|
3839
3900
|
const changeTree = instance[$changes];
|
|
3840
3901
|
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
3841
3902
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3842
|
-
let output = `${instance.constructor.name} (${
|
|
3903
|
+
let output = `${instance.constructor.name} (${instance[$refId]}) -> .${changeSetName}:\n`;
|
|
3843
3904
|
function dumpChangeSet(changeSet) {
|
|
3844
3905
|
changeSet.operations
|
|
3845
3906
|
.filter(op => op)
|
|
@@ -3853,14 +3914,14 @@ class Schema {
|
|
|
3853
3914
|
if (!isEncodeAll &&
|
|
3854
3915
|
changeTree.filteredChanges &&
|
|
3855
3916
|
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3856
|
-
output += `${instance.constructor.name} (${
|
|
3917
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .filteredChanges:\n`;
|
|
3857
3918
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3858
3919
|
}
|
|
3859
3920
|
// display filtered changes
|
|
3860
3921
|
if (isEncodeAll &&
|
|
3861
3922
|
changeTree.allFilteredChanges &&
|
|
3862
3923
|
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3863
|
-
output += `${instance.constructor.name} (${
|
|
3924
|
+
output += `${instance.constructor.name} (${instance[$refId]}) -> .allFilteredChanges:\n`;
|
|
3864
3925
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3865
3926
|
}
|
|
3866
3927
|
return output;
|
|
@@ -3895,13 +3956,13 @@ class Schema {
|
|
|
3895
3956
|
}
|
|
3896
3957
|
}
|
|
3897
3958
|
if (includeChangeTree) {
|
|
3898
|
-
instanceRefIds.push(changeTree.refId);
|
|
3959
|
+
instanceRefIds.push(changeTree.ref[$refId]);
|
|
3899
3960
|
totalOperations += Object.keys(changes).length;
|
|
3900
3961
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3901
3962
|
}
|
|
3902
3963
|
}
|
|
3903
3964
|
output += "---\n";
|
|
3904
|
-
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
3965
|
+
output += `root refId: ${rootChangeTree.ref[$refId]}\n`;
|
|
3905
3966
|
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3906
3967
|
output += `Total changes: ${totalOperations}\n`;
|
|
3907
3968
|
output += "---\n";
|
|
@@ -3910,7 +3971,7 @@ class Schema {
|
|
|
3910
3971
|
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
3911
3972
|
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
3912
3973
|
if (!visitedParents.has(parentChangeTree)) {
|
|
3913
|
-
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
3974
|
+
output += `${getIndent(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.ref[$refId]})\n`;
|
|
3914
3975
|
visitedParents.add(parentChangeTree);
|
|
3915
3976
|
}
|
|
3916
3977
|
});
|
|
@@ -3918,7 +3979,7 @@ class Schema {
|
|
|
3918
3979
|
const level = parentChangeTrees.length;
|
|
3919
3980
|
const indent = getIndent(level);
|
|
3920
3981
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3921
|
-
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`;
|
|
3922
3983
|
for (const index in changes) {
|
|
3923
3984
|
const operation = changes[index];
|
|
3924
3985
|
output += `${getIndent(level + 1)}${OPERATION[operation]}: ${index}\n`;
|
|
@@ -3928,61 +3989,35 @@ class Schema {
|
|
|
3928
3989
|
}
|
|
3929
3990
|
}
|
|
3930
3991
|
|
|
3931
|
-
/******************************************************************************
|
|
3932
|
-
Copyright (c) Microsoft Corporation.
|
|
3933
|
-
|
|
3934
|
-
Permission to use, copy, modify, and/or distribute this software for any
|
|
3935
|
-
purpose with or without fee is hereby granted.
|
|
3936
|
-
|
|
3937
|
-
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH
|
|
3938
|
-
REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY
|
|
3939
|
-
AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT,
|
|
3940
|
-
INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM
|
|
3941
|
-
LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
3942
|
-
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3943
|
-
PERFORMANCE OF THIS SOFTWARE.
|
|
3944
|
-
***************************************************************************** */
|
|
3945
|
-
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
3946
|
-
|
|
3947
|
-
|
|
3948
|
-
function __decorate(decorators, target, key, desc) {
|
|
3949
|
-
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
3950
|
-
if (typeof Reflect === "object" && typeof Reflect.decorate === "function") r = Reflect.decorate(decorators, target, key, desc);
|
|
3951
|
-
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;
|
|
3952
|
-
return c > 3 && r && Object.defineProperty(target, key, r), r;
|
|
3953
|
-
}
|
|
3954
|
-
|
|
3955
|
-
typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
3956
|
-
var e = new Error(message);
|
|
3957
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3958
|
-
};
|
|
3959
|
-
|
|
3960
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
|
|
3961
4003
|
constructor(types) {
|
|
3962
4004
|
this.types = types;
|
|
3963
|
-
this.nextUniqueId = 0;
|
|
3964
|
-
this.refCount = {};
|
|
3965
|
-
this.changeTrees = {};
|
|
3966
|
-
// all changes
|
|
3967
|
-
this.allChanges = createChangeTreeList();
|
|
3968
|
-
this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3969
|
-
// pending changes to be encoded
|
|
3970
|
-
this.changes = createChangeTreeList();
|
|
3971
|
-
this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3972
4005
|
}
|
|
3973
4006
|
getNextUniqueId() {
|
|
3974
4007
|
return this.nextUniqueId++;
|
|
3975
4008
|
}
|
|
3976
4009
|
add(changeTree) {
|
|
3977
|
-
|
|
3978
|
-
|
|
3979
|
-
|
|
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();
|
|
3980
4014
|
}
|
|
3981
|
-
const
|
|
4015
|
+
const refId = ref[$refId];
|
|
4016
|
+
const isNewChangeTree = (this.changeTrees[refId] === undefined);
|
|
3982
4017
|
if (isNewChangeTree) {
|
|
3983
|
-
this.changeTrees[
|
|
4018
|
+
this.changeTrees[refId] = changeTree;
|
|
3984
4019
|
}
|
|
3985
|
-
const previousRefCount = this.refCount[
|
|
4020
|
+
const previousRefCount = this.refCount[refId];
|
|
3986
4021
|
if (previousRefCount === 0) {
|
|
3987
4022
|
//
|
|
3988
4023
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
@@ -3995,30 +4030,31 @@ class Root {
|
|
|
3995
4030
|
setOperationAtIndex(changeTree.changes, len);
|
|
3996
4031
|
}
|
|
3997
4032
|
}
|
|
3998
|
-
this.refCount[
|
|
3999
|
-
// 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 });
|
|
4000
4035
|
return isNewChangeTree;
|
|
4001
4036
|
}
|
|
4002
4037
|
remove(changeTree) {
|
|
4003
|
-
const
|
|
4004
|
-
|
|
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 });
|
|
4005
4041
|
if (refCount <= 0) {
|
|
4006
4042
|
//
|
|
4007
4043
|
// Only remove "root" reference if it's the last reference
|
|
4008
4044
|
//
|
|
4009
4045
|
changeTree.root = undefined;
|
|
4010
|
-
delete this.changeTrees[
|
|
4046
|
+
delete this.changeTrees[refId];
|
|
4011
4047
|
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
4012
4048
|
this.removeChangeFromChangeSet("changes", changeTree);
|
|
4013
4049
|
if (changeTree.filteredChanges) {
|
|
4014
4050
|
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
4015
4051
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
4016
4052
|
}
|
|
4017
|
-
this.refCount[
|
|
4053
|
+
this.refCount[refId] = 0;
|
|
4018
4054
|
changeTree.forEachChild((child, _) => {
|
|
4019
4055
|
if (child.removeParent(changeTree.ref)) {
|
|
4020
4056
|
if ((child.parentChain === undefined || // no parent, remove it
|
|
4021
|
-
(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
|
|
4022
4058
|
)) {
|
|
4023
4059
|
this.remove(child);
|
|
4024
4060
|
}
|
|
@@ -4030,7 +4066,7 @@ class Root {
|
|
|
4030
4066
|
});
|
|
4031
4067
|
}
|
|
4032
4068
|
else {
|
|
4033
|
-
this.refCount[
|
|
4069
|
+
this.refCount[refId] = refCount;
|
|
4034
4070
|
//
|
|
4035
4071
|
// When losing a reference to an instance, it is best to move the
|
|
4036
4072
|
// ChangeTree next to its parent in the encoding queue.
|
|
@@ -4180,10 +4216,19 @@ class Root {
|
|
|
4180
4216
|
}
|
|
4181
4217
|
}
|
|
4182
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
|
+
}
|
|
4183
4225
|
class Encoder {
|
|
4184
|
-
static
|
|
4226
|
+
static BUFFER_SIZE = 8 * 1024; // 8KB
|
|
4227
|
+
sharedBuffer = new Uint8Array(Encoder.BUFFER_SIZE);
|
|
4228
|
+
context;
|
|
4229
|
+
state;
|
|
4230
|
+
root;
|
|
4185
4231
|
constructor(state) {
|
|
4186
|
-
this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
|
|
4187
4232
|
//
|
|
4188
4233
|
// Use .cache() here to avoid re-creating a new context for every new room instance.
|
|
4189
4234
|
//
|
|
@@ -4211,7 +4256,7 @@ class Encoder {
|
|
|
4211
4256
|
const changeTree = current.changeTree;
|
|
4212
4257
|
if (hasView) {
|
|
4213
4258
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
4214
|
-
// 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() });
|
|
4215
4260
|
view.invisible.add(changeTree);
|
|
4216
4261
|
continue; // skip this change tree
|
|
4217
4262
|
}
|
|
@@ -4232,7 +4277,7 @@ class Encoder {
|
|
|
4232
4277
|
// (unless it "hasView", which will need to revisit the root)
|
|
4233
4278
|
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
4234
4279
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4235
|
-
encode.number(buffer,
|
|
4280
|
+
encode.number(buffer, ref[$refId], it);
|
|
4236
4281
|
}
|
|
4237
4282
|
for (let j = 0; j < numChanges; j++) {
|
|
4238
4283
|
const fieldIndex = changeSet.operations[j];
|
|
@@ -4261,10 +4306,9 @@ class Encoder {
|
|
|
4261
4306
|
}
|
|
4262
4307
|
}
|
|
4263
4308
|
if (it.offset > buffer.byteLength) {
|
|
4264
|
-
// we can assume that n + 1
|
|
4265
|
-
// multiples of
|
|
4266
|
-
|
|
4267
|
-
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;
|
|
4268
4312
|
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
4269
4313
|
|
|
4270
4314
|
import { Encoder } from "@colyseus/schema";
|
|
@@ -4274,7 +4318,9 @@ class Encoder {
|
|
|
4274
4318
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
4275
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
|
|
4276
4320
|
//
|
|
4277
|
-
|
|
4321
|
+
const newBuffer = new Uint8Array(newSize);
|
|
4322
|
+
newBuffer.set(buffer); // copy previous encoding steps beyond the initialOffset
|
|
4323
|
+
buffer = newBuffer;
|
|
4278
4324
|
// assign resized buffer to local sharedBuffer
|
|
4279
4325
|
if (buffer === this.sharedBuffer) {
|
|
4280
4326
|
this.sharedBuffer = buffer;
|
|
@@ -4292,10 +4338,7 @@ class Encoder {
|
|
|
4292
4338
|
const viewOffset = it.offset;
|
|
4293
4339
|
// try to encode "filtered" changes
|
|
4294
4340
|
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
4295
|
-
return
|
|
4296
|
-
bytes.subarray(0, sharedOffset),
|
|
4297
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4298
|
-
]);
|
|
4341
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4299
4342
|
}
|
|
4300
4343
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4301
4344
|
const viewOffset = it.offset;
|
|
@@ -4319,7 +4362,7 @@ class Encoder {
|
|
|
4319
4362
|
const encoder = ctor[$encoder];
|
|
4320
4363
|
const metadata = ctor[Symbol.metadata];
|
|
4321
4364
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
4322
|
-
encode.number(bytes,
|
|
4365
|
+
encode.number(bytes, ref[$refId], it);
|
|
4323
4366
|
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
4324
4367
|
const index = Number(keys[i]);
|
|
4325
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")
|
|
@@ -4338,10 +4381,7 @@ class Encoder {
|
|
|
4338
4381
|
view.changes.clear();
|
|
4339
4382
|
// try to encode "filtered" changes
|
|
4340
4383
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
4341
|
-
return
|
|
4342
|
-
bytes.subarray(0, sharedOffset),
|
|
4343
|
-
bytes.subarray(viewOffset, it.offset)
|
|
4344
|
-
]);
|
|
4384
|
+
return concatBytes(bytes.subarray(0, sharedOffset), bytes.subarray(viewOffset, it.offset));
|
|
4345
4385
|
}
|
|
4346
4386
|
discardChanges() {
|
|
4347
4387
|
// discard shared changes
|
|
@@ -4397,25 +4437,22 @@ class DecodingWarning extends Error {
|
|
|
4397
4437
|
}
|
|
4398
4438
|
}
|
|
4399
4439
|
class ReferenceTracker {
|
|
4400
|
-
|
|
4401
|
-
|
|
4402
|
-
|
|
4403
|
-
|
|
4404
|
-
|
|
4405
|
-
|
|
4406
|
-
|
|
4407
|
-
|
|
4408
|
-
|
|
4409
|
-
this.callbacks = {};
|
|
4410
|
-
this.nextUniqueId = 0;
|
|
4411
|
-
}
|
|
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;
|
|
4412
4449
|
getNextUniqueId() {
|
|
4413
4450
|
return this.nextUniqueId++;
|
|
4414
4451
|
}
|
|
4415
4452
|
// for decoding
|
|
4416
4453
|
addRef(refId, ref, incrementCount = true) {
|
|
4417
4454
|
this.refs.set(refId, ref);
|
|
4418
|
-
|
|
4455
|
+
ref[$refId] = refId;
|
|
4419
4456
|
if (incrementCount) {
|
|
4420
4457
|
this.refCount[refId] = (this.refCount[refId] || 0) + 1;
|
|
4421
4458
|
}
|
|
@@ -4472,9 +4509,12 @@ class ReferenceTracker {
|
|
|
4472
4509
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4473
4510
|
for (const index in metadata) {
|
|
4474
4511
|
const field = metadata[index].name;
|
|
4475
|
-
const
|
|
4476
|
-
if (
|
|
4477
|
-
|
|
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
|
+
}
|
|
4478
4518
|
}
|
|
4479
4519
|
}
|
|
4480
4520
|
}
|
|
@@ -4482,8 +4522,8 @@ class ReferenceTracker {
|
|
|
4482
4522
|
if (typeof (ref[$childType]) === "function") {
|
|
4483
4523
|
Array.from(ref.values())
|
|
4484
4524
|
.forEach((child) => {
|
|
4485
|
-
const childRefId =
|
|
4486
|
-
if (!this.deletedRefs.has(childRefId)) {
|
|
4525
|
+
const childRefId = child[$refId];
|
|
4526
|
+
if (childRefId !== undefined && !this.deletedRefs.has(childRefId)) {
|
|
4487
4527
|
this.removeRef(childRefId);
|
|
4488
4528
|
}
|
|
4489
4529
|
});
|
|
@@ -4521,8 +4561,12 @@ class ReferenceTracker {
|
|
|
4521
4561
|
}
|
|
4522
4562
|
|
|
4523
4563
|
class Decoder {
|
|
4564
|
+
context;
|
|
4565
|
+
state;
|
|
4566
|
+
root;
|
|
4567
|
+
currentRefId = 0;
|
|
4568
|
+
triggerChanges;
|
|
4524
4569
|
constructor(root, context) {
|
|
4525
|
-
this.currentRefId = 0;
|
|
4526
4570
|
this.setState(root);
|
|
4527
4571
|
this.context = context || new TypeContext(root.constructor);
|
|
4528
4572
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
@@ -4611,7 +4655,7 @@ class Decoder {
|
|
|
4611
4655
|
}
|
|
4612
4656
|
removeChildRefs(ref, allChanges) {
|
|
4613
4657
|
const needRemoveRef = typeof (ref[$childType]) !== "string";
|
|
4614
|
-
const refId =
|
|
4658
|
+
const refId = ref[$refId];
|
|
4615
4659
|
ref.forEach((value, key) => {
|
|
4616
4660
|
allChanges.push({
|
|
4617
4661
|
ref: ref,
|
|
@@ -4622,7 +4666,7 @@ class Decoder {
|
|
|
4622
4666
|
previousValue: value
|
|
4623
4667
|
});
|
|
4624
4668
|
if (needRemoveRef) {
|
|
4625
|
-
this.root.removeRef(
|
|
4669
|
+
this.root.removeRef(value[$refId]);
|
|
4626
4670
|
}
|
|
4627
4671
|
});
|
|
4628
4672
|
}
|
|
@@ -4631,217 +4675,182 @@ class Decoder {
|
|
|
4631
4675
|
/**
|
|
4632
4676
|
* Reflection
|
|
4633
4677
|
*/
|
|
4634
|
-
|
|
4635
|
-
|
|
4636
|
-
|
|
4637
|
-
|
|
4638
|
-
|
|
4639
|
-
|
|
4640
|
-
|
|
4641
|
-
|
|
4642
|
-
|
|
4643
|
-
|
|
4644
|
-
|
|
4645
|
-
|
|
4646
|
-
|
|
4647
|
-
|
|
4648
|
-
|
|
4649
|
-
|
|
4650
|
-
|
|
4651
|
-
|
|
4652
|
-
|
|
4653
|
-
|
|
4654
|
-
|
|
4655
|
-
|
|
4656
|
-
|
|
4657
|
-
|
|
4658
|
-
|
|
4659
|
-
|
|
4660
|
-
|
|
4661
|
-
|
|
4662
|
-
|
|
4663
|
-
|
|
4664
|
-
|
|
4665
|
-
|
|
4666
|
-
|
|
4667
|
-
|
|
4668
|
-
|
|
4669
|
-
|
|
4670
|
-
* @returns
|
|
4671
|
-
*/
|
|
4672
|
-
static encode(encoder, it = { offset: 0 }) {
|
|
4673
|
-
const context = encoder.context;
|
|
4674
|
-
const reflection = new Reflection();
|
|
4675
|
-
const reflectionEncoder = new Encoder(reflection);
|
|
4676
|
-
// rootType is usually the first schema passed to the Encoder
|
|
4677
|
-
// (unless it inherits from another schema)
|
|
4678
|
-
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4679
|
-
if (rootType > 0) {
|
|
4680
|
-
reflection.rootType = rootType;
|
|
4681
|
-
}
|
|
4682
|
-
const includedTypeIds = new Set();
|
|
4683
|
-
const pendingReflectionTypes = {};
|
|
4684
|
-
// add type to reflection in a way that respects inheritance
|
|
4685
|
-
// (parent types should be added before their children)
|
|
4686
|
-
const addType = (type) => {
|
|
4687
|
-
if (type.extendsId === undefined || includedTypeIds.has(type.extendsId)) {
|
|
4688
|
-
includedTypeIds.add(type.id);
|
|
4689
|
-
reflection.types.push(type);
|
|
4690
|
-
const deps = pendingReflectionTypes[type.id];
|
|
4691
|
-
if (deps !== undefined) {
|
|
4692
|
-
delete pendingReflectionTypes[type.id];
|
|
4693
|
-
deps.forEach((childType) => addType(childType));
|
|
4694
|
-
}
|
|
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));
|
|
4695
4714
|
}
|
|
4696
|
-
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
|
|
4700
|
-
pendingReflectionTypes[type.extendsId].push(type);
|
|
4715
|
+
}
|
|
4716
|
+
else {
|
|
4717
|
+
if (pendingReflectionTypes[type.extendsId] === undefined) {
|
|
4718
|
+
pendingReflectionTypes[type.extendsId] = [];
|
|
4701
4719
|
}
|
|
4702
|
-
|
|
4703
|
-
|
|
4704
|
-
|
|
4705
|
-
|
|
4706
|
-
|
|
4707
|
-
|
|
4708
|
-
|
|
4709
|
-
|
|
4710
|
-
|
|
4711
|
-
|
|
4712
|
-
|
|
4713
|
-
|
|
4714
|
-
|
|
4715
|
-
|
|
4716
|
-
|
|
4717
|
-
|
|
4718
|
-
|
|
4719
|
-
|
|
4720
|
-
|
|
4721
|
-
|
|
4722
|
-
|
|
4723
|
-
|
|
4724
|
-
|
|
4725
|
-
|
|
4726
|
-
|
|
4727
|
-
|
|
4728
|
-
|
|
4729
|
-
|
|
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;
|
|
4730
4759
|
}
|
|
4731
4760
|
else {
|
|
4732
|
-
|
|
4733
|
-
|
|
4734
|
-
|
|
4735
|
-
//
|
|
4736
|
-
if (Schema.is(field.type)) {
|
|
4737
|
-
fieldType = "ref";
|
|
4738
|
-
childTypeSchema = field.type;
|
|
4761
|
+
fieldType = Object.keys(field.type)[0];
|
|
4762
|
+
if (typeof (field.type[fieldType]) === "string") {
|
|
4763
|
+
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4739
4764
|
}
|
|
4740
4765
|
else {
|
|
4741
|
-
|
|
4742
|
-
if (typeof (field.type[fieldType]) === "string") {
|
|
4743
|
-
fieldType += ":" + field.type[fieldType]; // array:string
|
|
4744
|
-
}
|
|
4745
|
-
else {
|
|
4746
|
-
childTypeSchema = field.type[fieldType];
|
|
4747
|
-
}
|
|
4766
|
+
childTypeSchema = field.type[fieldType];
|
|
4748
4767
|
}
|
|
4749
|
-
reflectionField.referencedType = (childTypeSchema)
|
|
4750
|
-
? context.getTypeId(childTypeSchema)
|
|
4751
|
-
: -1;
|
|
4752
4768
|
}
|
|
4753
|
-
reflectionField.
|
|
4754
|
-
|
|
4769
|
+
reflectionField.referencedType = (childTypeSchema)
|
|
4770
|
+
? context.getTypeId(childTypeSchema)
|
|
4771
|
+
: -1;
|
|
4755
4772
|
}
|
|
4773
|
+
reflectionField.type = fieldType;
|
|
4774
|
+
type.fields.push(reflectionField);
|
|
4756
4775
|
}
|
|
4757
|
-
addType(type);
|
|
4758
|
-
});
|
|
4759
|
-
// in case there are types that were not added due to inheritance
|
|
4760
|
-
for (const typeid in pendingReflectionTypes) {
|
|
4761
|
-
pendingReflectionTypes[typeid].forEach((type) => reflection.types.push(type));
|
|
4762
4776
|
}
|
|
4763
|
-
|
|
4764
|
-
|
|
4765
|
-
|
|
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));
|
|
4766
4782
|
}
|
|
4767
|
-
|
|
4768
|
-
|
|
4769
|
-
|
|
4770
|
-
|
|
4771
|
-
|
|
4772
|
-
|
|
4773
|
-
|
|
4774
|
-
|
|
4775
|
-
|
|
4776
|
-
|
|
4777
|
-
|
|
4778
|
-
const
|
|
4779
|
-
|
|
4780
|
-
|
|
4781
|
-
|
|
4782
|
-
|
|
4783
|
-
|
|
4784
|
-
|
|
4785
|
-
|
|
4786
|
-
|
|
4787
|
-
|
|
4788
|
-
|
|
4789
|
-
|
|
4790
|
-
|
|
4791
|
-
|
|
4792
|
-
|
|
4793
|
-
|
|
4794
|
-
|
|
4795
|
-
|
|
4796
|
-
|
|
4797
|
-
|
|
4798
|
-
|
|
4799
|
-
const typeInfo = field.type.split(":");
|
|
4800
|
-
fieldType = typeInfo[0];
|
|
4801
|
-
refType = typeInfo[1]; // string
|
|
4802
|
-
}
|
|
4803
|
-
if (fieldType === "ref") {
|
|
4804
|
-
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
4805
|
-
}
|
|
4806
|
-
else {
|
|
4807
|
-
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4808
|
-
}
|
|
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);
|
|
4809
4815
|
}
|
|
4810
4816
|
else {
|
|
4811
|
-
Metadata.addField(metadata, fieldIndex, field.name,
|
|
4817
|
+
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
4812
4818
|
}
|
|
4813
|
-
}
|
|
4814
|
-
|
|
4815
|
-
|
|
4816
|
-
|
|
4817
|
-
const schema = typeContext.get(reflectionType.id);
|
|
4818
|
-
// for inheritance support
|
|
4819
|
-
const metadata = Metadata.initialize(schema);
|
|
4820
|
-
const inheritedTypes = [];
|
|
4821
|
-
let parentType = reflectionType;
|
|
4822
|
-
do {
|
|
4823
|
-
inheritedTypes.push(parentType);
|
|
4824
|
-
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4825
|
-
} while (parentType);
|
|
4826
|
-
let parentFieldIndex = 0;
|
|
4827
|
-
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4828
|
-
// add fields from all inherited classes
|
|
4829
|
-
// TODO: refactor this to avoid adding fields from parent classes
|
|
4830
|
-
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4831
|
-
parentFieldIndex += reflectionType.fields.length;
|
|
4832
|
-
});
|
|
4819
|
+
}
|
|
4820
|
+
else {
|
|
4821
|
+
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
4822
|
+
}
|
|
4833
4823
|
});
|
|
4834
|
-
|
|
4835
|
-
|
|
4836
|
-
|
|
4837
|
-
|
|
4838
|
-
|
|
4839
|
-
|
|
4840
|
-
|
|
4841
|
-
|
|
4842
|
-
|
|
4843
|
-
|
|
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
|
+
};
|
|
4844
4847
|
|
|
4848
|
+
/**
|
|
4849
|
+
* Legacy callback system
|
|
4850
|
+
*
|
|
4851
|
+
* @param decoder
|
|
4852
|
+
* @returns
|
|
4853
|
+
*/
|
|
4845
4854
|
function getDecoderStateCallbacks(decoder) {
|
|
4846
4855
|
const $root = decoder.root;
|
|
4847
4856
|
const callbacks = $root.callbacks;
|
|
@@ -4862,7 +4871,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4862
4871
|
//
|
|
4863
4872
|
if ((change.op & OPERATION.DELETE) === OPERATION.DELETE &&
|
|
4864
4873
|
change.previousValue instanceof Schema) {
|
|
4865
|
-
const deleteCallbacks = callbacks[
|
|
4874
|
+
const deleteCallbacks = callbacks[change.previousValue[$refId]]?.[OPERATION.DELETE];
|
|
4866
4875
|
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4867
4876
|
deleteCallbacks[i]();
|
|
4868
4877
|
}
|
|
@@ -4951,7 +4960,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4951
4960
|
) {
|
|
4952
4961
|
callback(context.instance[prop], undefined);
|
|
4953
4962
|
}
|
|
4954
|
-
return $root.addCallback($
|
|
4963
|
+
return $root.addCallback(ref[$refId], prop, callback);
|
|
4955
4964
|
};
|
|
4956
4965
|
/**
|
|
4957
4966
|
* Schema instances
|
|
@@ -4971,7 +4980,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4971
4980
|
}
|
|
4972
4981
|
},
|
|
4973
4982
|
onChange: function onChange(callback) {
|
|
4974
|
-
return $root.addCallback(
|
|
4983
|
+
return $root.addCallback(context.instance[$refId], OPERATION.REPLACE, callback);
|
|
4975
4984
|
},
|
|
4976
4985
|
//
|
|
4977
4986
|
// TODO: refactor `bindTo()` implementation.
|
|
@@ -4981,7 +4990,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4981
4990
|
if (!properties) {
|
|
4982
4991
|
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4983
4992
|
}
|
|
4984
|
-
return $root.addCallback(
|
|
4993
|
+
return $root.addCallback(context.instance[$refId], OPERATION.REPLACE, () => {
|
|
4985
4994
|
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4986
4995
|
});
|
|
4987
4996
|
}
|
|
@@ -5000,13 +5009,13 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5000
5009
|
unbind?.();
|
|
5001
5010
|
}, false);
|
|
5002
5011
|
// has existing value
|
|
5003
|
-
if ($
|
|
5012
|
+
if (instance?.[$refId] !== undefined) {
|
|
5004
5013
|
callback(instance, true);
|
|
5005
5014
|
}
|
|
5006
5015
|
});
|
|
5007
5016
|
return getProxy(metadataField.type, {
|
|
5008
5017
|
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
5009
|
-
instance: ($
|
|
5018
|
+
instance: (instance?.[$refId] !== undefined && instance),
|
|
5010
5019
|
parentInstance: context.instance,
|
|
5011
5020
|
onInstanceAvailable,
|
|
5012
5021
|
});
|
|
@@ -5030,7 +5039,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5030
5039
|
if (immediate) {
|
|
5031
5040
|
ref.forEach((v, k) => callback(v, k));
|
|
5032
5041
|
}
|
|
5033
|
-
return $root.addCallback($
|
|
5042
|
+
return $root.addCallback(ref[$refId], OPERATION.ADD, (value, key) => {
|
|
5034
5043
|
onAddCalls.set(callback, true);
|
|
5035
5044
|
currentOnAddCallback = callback;
|
|
5036
5045
|
callback(value, key);
|
|
@@ -5039,10 +5048,10 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
5039
5048
|
});
|
|
5040
5049
|
};
|
|
5041
5050
|
const onRemove = function (ref, callback) {
|
|
5042
|
-
return $root.addCallback($
|
|
5051
|
+
return $root.addCallback(ref[$refId], OPERATION.DELETE, callback);
|
|
5043
5052
|
};
|
|
5044
5053
|
const onChange = function (ref, callback) {
|
|
5045
|
-
return $root.addCallback($
|
|
5054
|
+
return $root.addCallback(ref[$refId], OPERATION.REPLACE, callback);
|
|
5046
5055
|
};
|
|
5047
5056
|
return new Proxy({
|
|
5048
5057
|
onAdd: function (callback, immediate = true) {
|
|
@@ -5111,22 +5120,360 @@ function getRawChangesCallback(decoder, callback) {
|
|
|
5111
5120
|
decoder.triggerChanges = callback;
|
|
5112
5121
|
}
|
|
5113
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
|
+
|
|
5114
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();
|
|
5115
5475
|
constructor(iterable = false) {
|
|
5116
5476
|
this.iterable = iterable;
|
|
5117
|
-
/**
|
|
5118
|
-
* List of ChangeTree's that are visible to this view
|
|
5119
|
-
*/
|
|
5120
|
-
this.visible = new WeakSet();
|
|
5121
|
-
/**
|
|
5122
|
-
* List of ChangeTree's that are invisible to this view
|
|
5123
|
-
*/
|
|
5124
|
-
this.invisible = new WeakSet();
|
|
5125
|
-
/**
|
|
5126
|
-
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
5127
|
-
* (This is used to force encoding a property, even if it was not changed)
|
|
5128
|
-
*/
|
|
5129
|
-
this.changes = new Map();
|
|
5130
5477
|
if (iterable) {
|
|
5131
5478
|
this.items = [];
|
|
5132
5479
|
}
|
|
@@ -5140,7 +5487,7 @@ class StateView {
|
|
|
5140
5487
|
return false;
|
|
5141
5488
|
}
|
|
5142
5489
|
else if (!parentChangeTree &&
|
|
5143
|
-
|
|
5490
|
+
obj[$refId] !== 0 // allow root object
|
|
5144
5491
|
) {
|
|
5145
5492
|
/**
|
|
5146
5493
|
* TODO: can we avoid this?
|
|
@@ -5164,11 +5511,11 @@ class StateView {
|
|
|
5164
5511
|
if (checkIncludeParent && parentChangeTree) {
|
|
5165
5512
|
this.addParentOf(changeTree, tag);
|
|
5166
5513
|
}
|
|
5167
|
-
let changes = this.changes.get(
|
|
5514
|
+
let changes = this.changes.get(obj[$refId]);
|
|
5168
5515
|
if (changes === undefined) {
|
|
5169
5516
|
changes = {};
|
|
5170
5517
|
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
5171
|
-
this.changes.set(
|
|
5518
|
+
this.changes.set(obj[$refId], changes);
|
|
5172
5519
|
}
|
|
5173
5520
|
let isChildAdded = false;
|
|
5174
5521
|
//
|
|
@@ -5248,10 +5595,10 @@ class StateView {
|
|
|
5248
5595
|
}
|
|
5249
5596
|
// add parent's tag properties
|
|
5250
5597
|
if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
|
|
5251
|
-
let changes = this.changes.get(changeTree.refId);
|
|
5598
|
+
let changes = this.changes.get(changeTree.ref[$refId]);
|
|
5252
5599
|
if (changes === undefined) {
|
|
5253
5600
|
changes = {};
|
|
5254
|
-
this.changes.set(changeTree.refId, changes);
|
|
5601
|
+
this.changes.set(changeTree.ref[$refId], changes);
|
|
5255
5602
|
}
|
|
5256
5603
|
if (!this.tags) {
|
|
5257
5604
|
this.tags = new WeakMap();
|
|
@@ -5283,27 +5630,28 @@ class StateView {
|
|
|
5283
5630
|
}
|
|
5284
5631
|
const ref = changeTree.ref;
|
|
5285
5632
|
const metadata = ref.constructor[Symbol.metadata]; // ArraySchema/MapSchema do not have metadata
|
|
5286
|
-
|
|
5633
|
+
const refId = ref[$refId];
|
|
5634
|
+
let changes = this.changes.get(refId);
|
|
5287
5635
|
if (changes === undefined) {
|
|
5288
5636
|
changes = {};
|
|
5289
|
-
this.changes.set(
|
|
5637
|
+
this.changes.set(refId, changes);
|
|
5290
5638
|
}
|
|
5291
5639
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
5292
5640
|
// parent is collection (Map/Array)
|
|
5293
5641
|
const parent = changeTree.parent;
|
|
5294
5642
|
if (parent && !Metadata.isValidInstance(parent) && changeTree.isFiltered) {
|
|
5295
|
-
const
|
|
5296
|
-
let changes = this.changes.get(
|
|
5643
|
+
const parentRefId = parent[$refId];
|
|
5644
|
+
let changes = this.changes.get(parentRefId);
|
|
5297
5645
|
if (changes === undefined) {
|
|
5298
5646
|
changes = {};
|
|
5299
|
-
this.changes.set(
|
|
5647
|
+
this.changes.set(parentRefId, changes);
|
|
5300
5648
|
}
|
|
5301
5649
|
else if (changes[changeTree.parentIndex] === OPERATION.ADD) {
|
|
5302
5650
|
//
|
|
5303
5651
|
// SAME PATCH ADD + REMOVE:
|
|
5304
5652
|
// The 'changes' of deleted structure should be ignored.
|
|
5305
5653
|
//
|
|
5306
|
-
this.changes.delete(
|
|
5654
|
+
this.changes.delete(refId);
|
|
5307
5655
|
}
|
|
5308
5656
|
// DELETE / DELETE BY REF ID
|
|
5309
5657
|
changes[changeTree.parentIndex] = OPERATION.DELETE;
|
|
@@ -5363,7 +5711,7 @@ class StateView {
|
|
|
5363
5711
|
if (!isVisible && changeTree.isVisibilitySharedWithParent) {
|
|
5364
5712
|
// console.log("CHECK AGAINST PARENT...", {
|
|
5365
5713
|
// ref: changeTree.ref.constructor.name,
|
|
5366
|
-
// refId: changeTree.refId,
|
|
5714
|
+
// refId: changeTree.ref[$refId],
|
|
5367
5715
|
// parent: changeTree.parent.constructor.name,
|
|
5368
5716
|
// });
|
|
5369
5717
|
if (this.visible.has(changeTree.parent[$changes])) {
|
|
@@ -5386,5 +5734,5 @@ registerType("array", { constructor: ArraySchema });
|
|
|
5386
5734
|
registerType("set", { constructor: SetSchema });
|
|
5387
5735
|
registerType("collection", { constructor: CollectionSchema, });
|
|
5388
5736
|
|
|
5389
|
-
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 };
|
|
5390
5738
|
//# sourceMappingURL=index.mjs.map
|