@colyseus/schema 3.0.41 → 3.0.43
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 +334 -185
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +334 -185
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +334 -185
- package/lib/Schema.d.ts +2 -1
- package/lib/Schema.js +21 -3
- package/lib/Schema.js.map +1 -1
- package/lib/bench_encode.d.ts +1 -0
- package/lib/bench_encode.js +130 -0
- package/lib/bench_encode.js.map +1 -0
- package/lib/debug.d.ts +1 -0
- package/lib/debug.js +51 -0
- package/lib/debug.js.map +1 -0
- package/lib/decoder/Decoder.js +7 -8
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +57 -7
- package/lib/encoder/ChangeTree.js +171 -106
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/Encoder.js +19 -20
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +8 -7
- package/lib/encoder/Root.js +81 -26
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.js +7 -0
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +5 -3
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/MapSchema.js +7 -2
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/symbols.d.ts +14 -14
- package/lib/types/symbols.js +14 -14
- package/lib/types/symbols.js.map +1 -1
- package/lib/utils.js +7 -3
- package/lib/utils.js.map +1 -1
- package/package.json +1 -1
- package/src/Schema.ts +21 -5
- package/src/bench_encode.ts +108 -0
- package/src/debug.ts +55 -0
- package/src/decoder/Decoder.ts +8 -12
- package/src/encoder/ChangeTree.ts +201 -115
- package/src/encoder/Encoder.ts +21 -19
- package/src/encoder/Root.ts +87 -28
- package/src/encoder/StateView.ts +8 -0
- package/src/types/custom/ArraySchema.ts +6 -4
- package/src/types/custom/MapSchema.ts +8 -2
- package/src/types/symbols.ts +14 -14
- package/src/utils.ts +9 -3
package/build/cjs/index.js
CHANGED
|
@@ -28,37 +28,37 @@ exports.OPERATION = void 0;
|
|
|
28
28
|
|
|
29
29
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
30
30
|
|
|
31
|
-
const $track =
|
|
32
|
-
const $encoder =
|
|
33
|
-
const $decoder =
|
|
34
|
-
const $filter =
|
|
35
|
-
const $getByIndex =
|
|
36
|
-
const $deleteByIndex =
|
|
31
|
+
const $track = "~track";
|
|
32
|
+
const $encoder = "~encoder";
|
|
33
|
+
const $decoder = "~decoder";
|
|
34
|
+
const $filter = "~filter";
|
|
35
|
+
const $getByIndex = "~getByIndex";
|
|
36
|
+
const $deleteByIndex = "~deleteByIndex";
|
|
37
37
|
/**
|
|
38
38
|
* Used to hold ChangeTree instances whitin the structures
|
|
39
39
|
*/
|
|
40
|
-
const $changes =
|
|
40
|
+
const $changes = '~changes';
|
|
41
41
|
/**
|
|
42
42
|
* Used to keep track of the type of the child elements of a collection
|
|
43
43
|
* (MapSchema, ArraySchema, etc.)
|
|
44
44
|
*/
|
|
45
|
-
const $childType =
|
|
45
|
+
const $childType = '~childType';
|
|
46
46
|
/**
|
|
47
47
|
* Optional "discard" method for custom types (ArraySchema)
|
|
48
48
|
* (Discards changes for next serialization)
|
|
49
49
|
*/
|
|
50
|
-
const $onEncodeEnd =
|
|
50
|
+
const $onEncodeEnd = '~onEncodeEnd';
|
|
51
51
|
/**
|
|
52
52
|
* When decoding, this method is called after the instance is fully decoded
|
|
53
53
|
*/
|
|
54
|
-
const $onDecodeEnd =
|
|
54
|
+
const $onDecodeEnd = "~onDecodeEnd";
|
|
55
55
|
/**
|
|
56
56
|
* Metadata
|
|
57
57
|
*/
|
|
58
|
-
const $descriptors =
|
|
59
|
-
const $numFields = "
|
|
60
|
-
const $refTypeFieldIndexes = "
|
|
61
|
-
const $viewFieldIndexes = "
|
|
58
|
+
const $descriptors = "~descriptors";
|
|
59
|
+
const $numFields = "~__numFields";
|
|
60
|
+
const $refTypeFieldIndexes = "~__refTypeFieldIndexes";
|
|
61
|
+
const $viewFieldIndexes = "~__viewFieldIndexes";
|
|
62
62
|
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
63
63
|
|
|
64
64
|
/**
|
|
@@ -966,6 +966,24 @@ const Metadata = {
|
|
|
966
966
|
function createChangeSet() {
|
|
967
967
|
return { indexes: {}, operations: [] };
|
|
968
968
|
}
|
|
969
|
+
// Linked list helper functions
|
|
970
|
+
function createChangeTreeList() {
|
|
971
|
+
return { next: undefined, tail: undefined, length: 0 };
|
|
972
|
+
}
|
|
973
|
+
function addToChangeTreeList(list, changeTree) {
|
|
974
|
+
const node = { changeTree, next: undefined, prev: undefined };
|
|
975
|
+
if (!list.next) {
|
|
976
|
+
list.next = node;
|
|
977
|
+
list.tail = node;
|
|
978
|
+
}
|
|
979
|
+
else {
|
|
980
|
+
node.prev = list.tail;
|
|
981
|
+
list.tail.next = node;
|
|
982
|
+
list.tail = node;
|
|
983
|
+
}
|
|
984
|
+
list.length++;
|
|
985
|
+
return node;
|
|
986
|
+
}
|
|
969
987
|
function setOperationAtIndex(changeSet, index) {
|
|
970
988
|
const operationsIndex = changeSet.indexes[index];
|
|
971
989
|
if (operationsIndex === undefined) {
|
|
@@ -990,13 +1008,15 @@ function deleteOperationAtIndex(changeSet, index) {
|
|
|
990
1008
|
changeSet.operations[operationsIndex] = undefined;
|
|
991
1009
|
delete changeSet.indexes[index];
|
|
992
1010
|
}
|
|
993
|
-
function enqueueChangeTree(root, changeTree, changeSet,
|
|
1011
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootNode = changeTree[changeSet].queueRootNode) {
|
|
1012
|
+
// skip
|
|
994
1013
|
if (!root) {
|
|
995
|
-
// skip
|
|
996
1014
|
return;
|
|
997
1015
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1016
|
+
if (queueRootNode) ;
|
|
1017
|
+
else {
|
|
1018
|
+
// Add to linked list if not already present
|
|
1019
|
+
changeTree[changeSet].queueRootNode = addToChangeTreeList(root[changeSet], changeTree);
|
|
1000
1020
|
}
|
|
1001
1021
|
}
|
|
1002
1022
|
class ChangeTree {
|
|
@@ -1020,83 +1040,58 @@ class ChangeTree {
|
|
|
1020
1040
|
*/
|
|
1021
1041
|
this.isNew = true;
|
|
1022
1042
|
this.ref = ref;
|
|
1043
|
+
this.metadata = ref.constructor[Symbol.metadata];
|
|
1023
1044
|
//
|
|
1024
1045
|
// Does this structure have "filters" declared?
|
|
1025
1046
|
//
|
|
1026
|
-
|
|
1027
|
-
if (metadata?.[$viewFieldIndexes]) {
|
|
1047
|
+
if (this.metadata?.[$viewFieldIndexes]) {
|
|
1028
1048
|
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
1029
1049
|
this.filteredChanges = { indexes: {}, operations: [] };
|
|
1030
1050
|
}
|
|
1031
1051
|
}
|
|
1032
1052
|
setRoot(root) {
|
|
1033
1053
|
this.root = root;
|
|
1034
|
-
this.
|
|
1035
|
-
|
|
1036
|
-
// TODO: refactor and possibly unify .setRoot() and .setParent()
|
|
1037
|
-
//
|
|
1054
|
+
const isNewChangeTree = this.root.add(this);
|
|
1055
|
+
this.checkIsFiltered(this.parent, this.parentIndex, isNewChangeTree);
|
|
1038
1056
|
// Recursively set root on child structures
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const changeTree = this.ref[field.name]?.[$changes];
|
|
1044
|
-
if (changeTree) {
|
|
1045
|
-
if (changeTree.root !== root) {
|
|
1046
|
-
changeTree.setRoot(root);
|
|
1047
|
-
}
|
|
1048
|
-
else {
|
|
1049
|
-
root.add(changeTree); // increment refCount
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1054
|
-
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
1055
|
-
// MapSchema / ArraySchema, etc.
|
|
1056
|
-
this.ref.forEach((value, key) => {
|
|
1057
|
-
const changeTree = value[$changes];
|
|
1058
|
-
if (changeTree.root !== root) {
|
|
1059
|
-
changeTree.setRoot(root);
|
|
1057
|
+
if (isNewChangeTree) {
|
|
1058
|
+
this.forEachChild((child, _) => {
|
|
1059
|
+
if (child.root !== root) {
|
|
1060
|
+
child.setRoot(root);
|
|
1060
1061
|
}
|
|
1061
1062
|
else {
|
|
1062
|
-
root.add(
|
|
1063
|
+
root.add(child); // increment refCount
|
|
1063
1064
|
}
|
|
1064
1065
|
});
|
|
1065
1066
|
}
|
|
1066
1067
|
}
|
|
1067
1068
|
setParent(parent, root, parentIndex) {
|
|
1068
|
-
this.parent
|
|
1069
|
-
this.parentIndex = parentIndex;
|
|
1069
|
+
this.addParent(parent, parentIndex);
|
|
1070
1070
|
// avoid setting parents with empty `root`
|
|
1071
1071
|
if (!root) {
|
|
1072
1072
|
return;
|
|
1073
1073
|
}
|
|
1074
|
+
const isNewChangeTree = root.add(this);
|
|
1074
1075
|
// skip if parent is already set
|
|
1075
1076
|
if (root !== this.root) {
|
|
1076
1077
|
this.root = root;
|
|
1077
|
-
this.checkIsFiltered(parent, parentIndex);
|
|
1078
|
-
}
|
|
1079
|
-
else {
|
|
1080
|
-
root.add(this);
|
|
1078
|
+
this.checkIsFiltered(parent, parentIndex, isNewChangeTree);
|
|
1081
1079
|
}
|
|
1082
1080
|
// assign same parent on child structures
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
this.ref.forEach((value, key) => {
|
|
1096
|
-
const changeTree = value[$changes];
|
|
1097
|
-
if (changeTree.root !== root) {
|
|
1098
|
-
changeTree.setParent(this.ref, root, this.indexes[key] ?? key);
|
|
1081
|
+
if (isNewChangeTree) {
|
|
1082
|
+
//
|
|
1083
|
+
// assign same parent on child structures
|
|
1084
|
+
//
|
|
1085
|
+
this.forEachChild((child, index) => {
|
|
1086
|
+
if (child.root === root) {
|
|
1087
|
+
//
|
|
1088
|
+
// re-assigning a child of the same root, move it to the end
|
|
1089
|
+
// of the changes queue so encoding order is preserved
|
|
1090
|
+
//
|
|
1091
|
+
root.moveToEndOfChanges(child);
|
|
1092
|
+
return;
|
|
1099
1093
|
}
|
|
1094
|
+
child.setParent(this.ref, root, index);
|
|
1100
1095
|
});
|
|
1101
1096
|
}
|
|
1102
1097
|
}
|
|
@@ -1104,21 +1099,23 @@ class ChangeTree {
|
|
|
1104
1099
|
//
|
|
1105
1100
|
// assign same parent on child structures
|
|
1106
1101
|
//
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
if (value) {
|
|
1113
|
-
callback(value[$changes], index);
|
|
1102
|
+
if (this.ref[$childType]) {
|
|
1103
|
+
if (typeof (this.ref[$childType]) !== "string") {
|
|
1104
|
+
// MapSchema / ArraySchema, etc.
|
|
1105
|
+
for (const [key, value] of this.ref.entries()) {
|
|
1106
|
+
callback(value[$changes], key);
|
|
1114
1107
|
}
|
|
1115
|
-
}
|
|
1108
|
+
}
|
|
1116
1109
|
}
|
|
1117
|
-
else
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1110
|
+
else {
|
|
1111
|
+
for (const index of this.metadata?.[$refTypeFieldIndexes] ?? []) {
|
|
1112
|
+
const field = this.metadata[index];
|
|
1113
|
+
const value = this.ref[field.name];
|
|
1114
|
+
if (!value) {
|
|
1115
|
+
continue;
|
|
1116
|
+
}
|
|
1117
|
+
callback(value[$changes], index);
|
|
1118
|
+
}
|
|
1122
1119
|
}
|
|
1123
1120
|
}
|
|
1124
1121
|
operation(op) {
|
|
@@ -1134,8 +1131,7 @@ class ChangeTree {
|
|
|
1134
1131
|
}
|
|
1135
1132
|
}
|
|
1136
1133
|
change(index, operation = exports.OPERATION.ADD) {
|
|
1137
|
-
const
|
|
1138
|
-
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
1134
|
+
const isFiltered = this.isFiltered || (this.metadata?.[index]?.tag !== undefined);
|
|
1139
1135
|
const changeSet = (isFiltered)
|
|
1140
1136
|
? this.filteredChanges
|
|
1141
1137
|
: this.changes;
|
|
@@ -1225,19 +1221,16 @@ class ChangeTree {
|
|
|
1225
1221
|
}
|
|
1226
1222
|
}
|
|
1227
1223
|
getType(index) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
//
|
|
1237
|
-
|
|
1238
|
-
//
|
|
1239
|
-
return this.ref[$childType];
|
|
1240
|
-
}
|
|
1224
|
+
return (
|
|
1225
|
+
//
|
|
1226
|
+
// Get the child type from parent structure.
|
|
1227
|
+
// - ["string"] => "string"
|
|
1228
|
+
// - { map: "string" } => "string"
|
|
1229
|
+
// - { set: "string" } => "string"
|
|
1230
|
+
//
|
|
1231
|
+
this.ref[$childType] || // ArraySchema | MapSchema | SetSchema | CollectionSchema
|
|
1232
|
+
this.metadata[index].type // Schema
|
|
1233
|
+
);
|
|
1241
1234
|
}
|
|
1242
1235
|
getChange(index) {
|
|
1243
1236
|
return this.indexedOperations[index];
|
|
@@ -1297,9 +1290,7 @@ class ChangeTree {
|
|
|
1297
1290
|
endEncode(changeSetName) {
|
|
1298
1291
|
this.indexedOperations = {};
|
|
1299
1292
|
// clear changeset
|
|
1300
|
-
this[changeSetName]
|
|
1301
|
-
this[changeSetName].operations.length = 0;
|
|
1302
|
-
this[changeSetName].queueRootIndex = undefined;
|
|
1293
|
+
this[changeSetName] = createChangeSet();
|
|
1303
1294
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
1304
1295
|
this.ref[$onEncodeEnd]?.();
|
|
1305
1296
|
// Not a new instance anymore
|
|
@@ -1313,20 +1304,14 @@ class ChangeTree {
|
|
|
1313
1304
|
//
|
|
1314
1305
|
this.ref[$onEncodeEnd]?.();
|
|
1315
1306
|
this.indexedOperations = {};
|
|
1316
|
-
this.changes
|
|
1317
|
-
this.changes.operations.length = 0;
|
|
1318
|
-
this.changes.queueRootIndex = undefined;
|
|
1307
|
+
this.changes = createChangeSet();
|
|
1319
1308
|
if (this.filteredChanges !== undefined) {
|
|
1320
|
-
this.filteredChanges
|
|
1321
|
-
this.filteredChanges.operations.length = 0;
|
|
1322
|
-
this.filteredChanges.queueRootIndex = undefined;
|
|
1309
|
+
this.filteredChanges = createChangeSet();
|
|
1323
1310
|
}
|
|
1324
1311
|
if (discardAll) {
|
|
1325
|
-
this.allChanges
|
|
1326
|
-
this.allChanges.operations.length = 0;
|
|
1312
|
+
this.allChanges = createChangeSet();
|
|
1327
1313
|
if (this.allFilteredChanges !== undefined) {
|
|
1328
|
-
this.allFilteredChanges
|
|
1329
|
-
this.allFilteredChanges.operations.length = 0;
|
|
1314
|
+
this.allFilteredChanges = createChangeSet();
|
|
1330
1315
|
}
|
|
1331
1316
|
}
|
|
1332
1317
|
}
|
|
@@ -1344,18 +1329,10 @@ class ChangeTree {
|
|
|
1344
1329
|
}
|
|
1345
1330
|
this.discard();
|
|
1346
1331
|
}
|
|
1347
|
-
ensureRefId() {
|
|
1348
|
-
// skip if refId is already set.
|
|
1349
|
-
if (this.refId !== undefined) {
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
this.refId = this.root.getNextUniqueId();
|
|
1353
|
-
}
|
|
1354
1332
|
get changed() {
|
|
1355
1333
|
return (Object.entries(this.indexedOperations).length > 0);
|
|
1356
1334
|
}
|
|
1357
|
-
checkIsFiltered(parent, parentIndex) {
|
|
1358
|
-
const isNewChangeTree = this.root.add(this);
|
|
1335
|
+
checkIsFiltered(parent, parentIndex, isNewChangeTree) {
|
|
1359
1336
|
if (this.root.types.hasFilters) {
|
|
1360
1337
|
//
|
|
1361
1338
|
// At Schema initialization, the "root" structure might not be available
|
|
@@ -1367,14 +1344,14 @@ class ChangeTree {
|
|
|
1367
1344
|
if (this.filteredChanges !== undefined) {
|
|
1368
1345
|
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1369
1346
|
if (isNewChangeTree) {
|
|
1370
|
-
this.root
|
|
1347
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
1371
1348
|
}
|
|
1372
1349
|
}
|
|
1373
1350
|
}
|
|
1374
1351
|
if (!this.isFiltered) {
|
|
1375
1352
|
enqueueChangeTree(this.root, this, 'changes');
|
|
1376
1353
|
if (isNewChangeTree) {
|
|
1377
|
-
this.root
|
|
1354
|
+
enqueueChangeTree(this.root, this, 'allChanges');
|
|
1378
1355
|
}
|
|
1379
1356
|
}
|
|
1380
1357
|
}
|
|
@@ -1431,6 +1408,90 @@ class ChangeTree {
|
|
|
1431
1408
|
}
|
|
1432
1409
|
}
|
|
1433
1410
|
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Get the immediate parent
|
|
1413
|
+
*/
|
|
1414
|
+
get parent() {
|
|
1415
|
+
return this.parentChain?.ref;
|
|
1416
|
+
}
|
|
1417
|
+
/**
|
|
1418
|
+
* Get the immediate parent index
|
|
1419
|
+
*/
|
|
1420
|
+
get parentIndex() {
|
|
1421
|
+
return this.parentChain?.index;
|
|
1422
|
+
}
|
|
1423
|
+
/**
|
|
1424
|
+
* Add a parent to the chain
|
|
1425
|
+
*/
|
|
1426
|
+
addParent(parent, index) {
|
|
1427
|
+
// Check if this parent already exists in the chain
|
|
1428
|
+
if (this.hasParent((p, i) => p === parent && i === index)) {
|
|
1429
|
+
return;
|
|
1430
|
+
}
|
|
1431
|
+
this.parentChain = {
|
|
1432
|
+
ref: parent,
|
|
1433
|
+
index,
|
|
1434
|
+
next: this.parentChain
|
|
1435
|
+
};
|
|
1436
|
+
}
|
|
1437
|
+
/**
|
|
1438
|
+
* Remove a parent from the chain
|
|
1439
|
+
* @param parent - The parent to remove
|
|
1440
|
+
* @returns true if parent was removed
|
|
1441
|
+
*/
|
|
1442
|
+
removeParent(parent) {
|
|
1443
|
+
let current = this.parentChain;
|
|
1444
|
+
let previous = null;
|
|
1445
|
+
while (current) {
|
|
1446
|
+
//
|
|
1447
|
+
// FIXME: it is required to check against `$changes` here because
|
|
1448
|
+
// ArraySchema is instance of Proxy
|
|
1449
|
+
//
|
|
1450
|
+
if (current.ref[$changes] === parent[$changes]) {
|
|
1451
|
+
if (previous) {
|
|
1452
|
+
previous.next = current.next;
|
|
1453
|
+
}
|
|
1454
|
+
else {
|
|
1455
|
+
this.parentChain = current.next;
|
|
1456
|
+
}
|
|
1457
|
+
return true;
|
|
1458
|
+
}
|
|
1459
|
+
previous = current;
|
|
1460
|
+
current = current.next;
|
|
1461
|
+
}
|
|
1462
|
+
return this.parentChain === undefined;
|
|
1463
|
+
}
|
|
1464
|
+
/**
|
|
1465
|
+
* Find a specific parent in the chain
|
|
1466
|
+
*/
|
|
1467
|
+
findParent(predicate) {
|
|
1468
|
+
let current = this.parentChain;
|
|
1469
|
+
while (current) {
|
|
1470
|
+
if (predicate(current.ref, current.index)) {
|
|
1471
|
+
return current;
|
|
1472
|
+
}
|
|
1473
|
+
current = current.next;
|
|
1474
|
+
}
|
|
1475
|
+
return undefined;
|
|
1476
|
+
}
|
|
1477
|
+
/**
|
|
1478
|
+
* Check if this ChangeTree has a specific parent
|
|
1479
|
+
*/
|
|
1480
|
+
hasParent(predicate) {
|
|
1481
|
+
return this.findParent(predicate) !== undefined;
|
|
1482
|
+
}
|
|
1483
|
+
/**
|
|
1484
|
+
* Get all parents as an array (for debugging/testing)
|
|
1485
|
+
*/
|
|
1486
|
+
getAllParents() {
|
|
1487
|
+
const parents = [];
|
|
1488
|
+
let current = this.parentChain;
|
|
1489
|
+
while (current) {
|
|
1490
|
+
parents.push({ ref: current.ref, index: current.index });
|
|
1491
|
+
current = current.next;
|
|
1492
|
+
}
|
|
1493
|
+
return parents;
|
|
1494
|
+
}
|
|
1434
1495
|
}
|
|
1435
1496
|
|
|
1436
1497
|
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
@@ -1979,8 +2040,11 @@ class ArraySchema {
|
|
|
1979
2040
|
return Reflect.has(obj, key);
|
|
1980
2041
|
}
|
|
1981
2042
|
});
|
|
1982
|
-
this
|
|
1983
|
-
|
|
2043
|
+
Object.defineProperty(this, $changes, {
|
|
2044
|
+
value: new ChangeTree(proxy),
|
|
2045
|
+
enumerable: false,
|
|
2046
|
+
writable: true,
|
|
2047
|
+
});
|
|
1984
2048
|
if (items.length > 0) {
|
|
1985
2049
|
this.push(...items);
|
|
1986
2050
|
}
|
|
@@ -2141,7 +2205,6 @@ class ArraySchema {
|
|
|
2141
2205
|
if (this.items.length === 0) {
|
|
2142
2206
|
return undefined;
|
|
2143
2207
|
}
|
|
2144
|
-
// const index = Number(Object.keys(changeTree.indexes)[0]);
|
|
2145
2208
|
const changeTree = this[$changes];
|
|
2146
2209
|
const index = this.tmpItems.findIndex(item => item === this.items[0]);
|
|
2147
2210
|
const allChangesIndex = this.items.findIndex(item => item === this.items[0]);
|
|
@@ -2600,8 +2663,13 @@ class MapSchema {
|
|
|
2600
2663
|
this.$items = new Map();
|
|
2601
2664
|
this.$indexes = new Map();
|
|
2602
2665
|
this.deletedItems = {};
|
|
2603
|
-
|
|
2604
|
-
|
|
2666
|
+
const changeTree = new ChangeTree(this);
|
|
2667
|
+
changeTree.indexes = {};
|
|
2668
|
+
Object.defineProperty(this, $changes, {
|
|
2669
|
+
value: changeTree,
|
|
2670
|
+
enumerable: false,
|
|
2671
|
+
writable: true,
|
|
2672
|
+
});
|
|
2605
2673
|
if (initialValues) {
|
|
2606
2674
|
if (initialValues instanceof Map ||
|
|
2607
2675
|
initialValues instanceof MapSchema) {
|
|
@@ -3145,10 +3213,13 @@ function dumpChanges(schema) {
|
|
|
3145
3213
|
refs: []
|
|
3146
3214
|
};
|
|
3147
3215
|
// for (const refId in $root.changes) {
|
|
3148
|
-
$root.changes.
|
|
3216
|
+
let current = $root.changes.next;
|
|
3217
|
+
while (current) {
|
|
3218
|
+
const changeTree = current.changeTree;
|
|
3149
3219
|
// skip if ChangeTree is undefined
|
|
3150
3220
|
if (changeTree === undefined) {
|
|
3151
|
-
|
|
3221
|
+
current = current.next;
|
|
3222
|
+
continue;
|
|
3152
3223
|
}
|
|
3153
3224
|
const changes = changeTree.indexedOperations;
|
|
3154
3225
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
@@ -3160,7 +3231,8 @@ function dumpChanges(schema) {
|
|
|
3160
3231
|
}
|
|
3161
3232
|
dump.ops[exports.OPERATION[op]]++;
|
|
3162
3233
|
}
|
|
3163
|
-
|
|
3234
|
+
current = current.next;
|
|
3235
|
+
}
|
|
3164
3236
|
return dump;
|
|
3165
3237
|
}
|
|
3166
3238
|
|
|
@@ -3310,7 +3382,7 @@ class Schema {
|
|
|
3310
3382
|
* @param showContents display JSON contents of the instance
|
|
3311
3383
|
* @returns
|
|
3312
3384
|
*/
|
|
3313
|
-
static debugRefIds(ref, showContents = false, level = 0, decoder) {
|
|
3385
|
+
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3314
3386
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3315
3387
|
const changeTree = ref[$changes];
|
|
3316
3388
|
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
@@ -3319,10 +3391,24 @@ class Schema {
|
|
|
3319
3391
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
3320
3392
|
? ` [×${root.refCount[refId]}]`
|
|
3321
3393
|
: '';
|
|
3322
|
-
let output = `${getIndent(level)}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3323
|
-
changeTree.forEachChild((childChangeTree) =>
|
|
3394
|
+
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3395
|
+
changeTree.forEachChild((childChangeTree, key) => {
|
|
3396
|
+
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3397
|
+
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3398
|
+
});
|
|
3324
3399
|
return output;
|
|
3325
3400
|
}
|
|
3401
|
+
static debugRefIdEncodeOrder(ref) {
|
|
3402
|
+
let encodeOrder = [];
|
|
3403
|
+
let current = ref[$changes].root.allChanges.next;
|
|
3404
|
+
while (current) {
|
|
3405
|
+
if (current.changeTree) {
|
|
3406
|
+
encodeOrder.push(current.changeTree.refId);
|
|
3407
|
+
}
|
|
3408
|
+
current = current.next;
|
|
3409
|
+
}
|
|
3410
|
+
return encodeOrder;
|
|
3411
|
+
}
|
|
3326
3412
|
static debugRefIdsDecoder(decoder) {
|
|
3327
3413
|
return this.debugRefIds(decoder.state, false, 0, decoder);
|
|
3328
3414
|
}
|
|
@@ -3372,8 +3458,12 @@ class Schema {
|
|
|
3372
3458
|
const changeTrees = new Map();
|
|
3373
3459
|
const instanceRefIds = [];
|
|
3374
3460
|
let totalOperations = 0;
|
|
3461
|
+
// TODO: FIXME: this method is not working as expected
|
|
3375
3462
|
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3376
3463
|
const changeTree = root.changeTrees[refId];
|
|
3464
|
+
if (!changeTree) {
|
|
3465
|
+
continue;
|
|
3466
|
+
}
|
|
3377
3467
|
let includeChangeTree = false;
|
|
3378
3468
|
let parentChangeTrees = [];
|
|
3379
3469
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3796,18 +3886,20 @@ class Root {
|
|
|
3796
3886
|
this.refCount = {};
|
|
3797
3887
|
this.changeTrees = {};
|
|
3798
3888
|
// all changes
|
|
3799
|
-
this.allChanges =
|
|
3800
|
-
this.allFilteredChanges =
|
|
3889
|
+
this.allChanges = createChangeTreeList();
|
|
3890
|
+
this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3801
3891
|
// pending changes to be encoded
|
|
3802
|
-
this.changes =
|
|
3803
|
-
this.filteredChanges =
|
|
3892
|
+
this.changes = createChangeTreeList();
|
|
3893
|
+
this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3804
3894
|
}
|
|
3805
3895
|
getNextUniqueId() {
|
|
3806
3896
|
return this.nextUniqueId++;
|
|
3807
3897
|
}
|
|
3808
3898
|
add(changeTree) {
|
|
3809
|
-
//
|
|
3810
|
-
changeTree.
|
|
3899
|
+
// Assign unique `refId` to changeTree if it doesn't have one yet.
|
|
3900
|
+
if (changeTree.refId === undefined) {
|
|
3901
|
+
changeTree.refId = this.getNextUniqueId();
|
|
3902
|
+
}
|
|
3811
3903
|
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3812
3904
|
if (isNewChangeTree) {
|
|
3813
3905
|
this.changeTrees[changeTree.refId] = changeTree;
|
|
@@ -3843,7 +3935,19 @@ class Root {
|
|
|
3843
3935
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3844
3936
|
}
|
|
3845
3937
|
this.refCount[changeTree.refId] = 0;
|
|
3846
|
-
changeTree.forEachChild((child, _) =>
|
|
3938
|
+
changeTree.forEachChild((child, _) => {
|
|
3939
|
+
if (child.removeParent(changeTree.ref)) {
|
|
3940
|
+
if ((child.parentChain === undefined || // no parent, remove it
|
|
3941
|
+
(child.parentChain && this.refCount[child.refId] > 1) // parent is still in use, but has more than one reference, remove it
|
|
3942
|
+
)) {
|
|
3943
|
+
this.remove(child);
|
|
3944
|
+
}
|
|
3945
|
+
else if (child.parentChain) {
|
|
3946
|
+
// re-assigning a child of the same root, move it to the end
|
|
3947
|
+
this.moveToEndOfChanges(child);
|
|
3948
|
+
}
|
|
3949
|
+
}
|
|
3950
|
+
});
|
|
3847
3951
|
}
|
|
3848
3952
|
else {
|
|
3849
3953
|
this.refCount[changeTree.refId] = refCount;
|
|
@@ -3856,32 +3960,73 @@ class Root {
|
|
|
3856
3960
|
// containing instance is not available, the Decoder will throw
|
|
3857
3961
|
// "refId not found" error.
|
|
3858
3962
|
//
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
enqueueChangeTree(this, changeTree, "filteredChanges");
|
|
3862
|
-
}
|
|
3863
|
-
else {
|
|
3864
|
-
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3865
|
-
enqueueChangeTree(this, changeTree, "changes");
|
|
3866
|
-
}
|
|
3963
|
+
this.moveToEndOfChanges(changeTree);
|
|
3964
|
+
changeTree.forEachChild((child, _) => this.moveToEndOfChanges(child));
|
|
3867
3965
|
}
|
|
3868
3966
|
return refCount;
|
|
3869
3967
|
}
|
|
3968
|
+
moveToEndOfChanges(changeTree) {
|
|
3969
|
+
if (changeTree.filteredChanges) {
|
|
3970
|
+
this.moveToEndOfChangeTreeList("filteredChanges", changeTree);
|
|
3971
|
+
this.moveToEndOfChangeTreeList("allFilteredChanges", changeTree);
|
|
3972
|
+
}
|
|
3973
|
+
else {
|
|
3974
|
+
this.moveToEndOfChangeTreeList("changes", changeTree);
|
|
3975
|
+
this.moveToEndOfChangeTreeList("allChanges", changeTree);
|
|
3976
|
+
}
|
|
3977
|
+
}
|
|
3978
|
+
moveToEndOfChangeTreeList(changeSetName, changeTree) {
|
|
3979
|
+
const changeSet = this[changeSetName];
|
|
3980
|
+
const node = changeTree[changeSetName].queueRootNode;
|
|
3981
|
+
if (!node || node === changeSet.tail)
|
|
3982
|
+
return;
|
|
3983
|
+
// Remove from current position
|
|
3984
|
+
if (node.prev) {
|
|
3985
|
+
node.prev.next = node.next;
|
|
3986
|
+
}
|
|
3987
|
+
else {
|
|
3988
|
+
changeSet.next = node.next;
|
|
3989
|
+
}
|
|
3990
|
+
if (node.next) {
|
|
3991
|
+
node.next.prev = node.prev;
|
|
3992
|
+
}
|
|
3993
|
+
else {
|
|
3994
|
+
changeSet.tail = node.prev;
|
|
3995
|
+
}
|
|
3996
|
+
// Add to end
|
|
3997
|
+
node.prev = changeSet.tail;
|
|
3998
|
+
node.next = undefined;
|
|
3999
|
+
if (changeSet.tail) {
|
|
4000
|
+
changeSet.tail.next = node;
|
|
4001
|
+
}
|
|
4002
|
+
else {
|
|
4003
|
+
changeSet.next = node;
|
|
4004
|
+
}
|
|
4005
|
+
changeSet.tail = node;
|
|
4006
|
+
}
|
|
3870
4007
|
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3871
4008
|
const changeSet = this[changeSetName];
|
|
3872
|
-
const
|
|
3873
|
-
if (
|
|
3874
|
-
|
|
3875
|
-
|
|
4009
|
+
const node = changeTree[changeSetName].queueRootNode;
|
|
4010
|
+
if (node && node.changeTree === changeTree) {
|
|
4011
|
+
// Remove the node from the linked list
|
|
4012
|
+
if (node.prev) {
|
|
4013
|
+
node.prev.next = node.next;
|
|
4014
|
+
}
|
|
4015
|
+
else {
|
|
4016
|
+
changeSet.next = node.next;
|
|
4017
|
+
}
|
|
4018
|
+
if (node.next) {
|
|
4019
|
+
node.next.prev = node.prev;
|
|
4020
|
+
}
|
|
4021
|
+
else {
|
|
4022
|
+
changeSet.tail = node.prev;
|
|
4023
|
+
}
|
|
4024
|
+
changeSet.length--;
|
|
4025
|
+
// Clear ChangeTree reference
|
|
4026
|
+
changeTree[changeSetName].queueRootNode = undefined;
|
|
3876
4027
|
return true;
|
|
3877
4028
|
}
|
|
3878
|
-
|
|
3879
|
-
// changeTree[changeSetName].queueRootIndex = -1;
|
|
3880
|
-
// return true;
|
|
3881
|
-
// }
|
|
3882
|
-
}
|
|
3883
|
-
clear() {
|
|
3884
|
-
this.changes.length = 0;
|
|
4029
|
+
return false;
|
|
3885
4030
|
}
|
|
3886
4031
|
}
|
|
3887
4032
|
|
|
@@ -3911,12 +4056,9 @@ class Encoder {
|
|
|
3911
4056
|
) {
|
|
3912
4057
|
const hasView = (view !== undefined);
|
|
3913
4058
|
const rootChangeTree = this.state[$changes];
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
const changeTree =
|
|
3917
|
-
if (!changeTree) {
|
|
3918
|
-
continue;
|
|
3919
|
-
}
|
|
4059
|
+
let current = this.root[changeSetName];
|
|
4060
|
+
while (current = current.next) {
|
|
4061
|
+
const changeTree = current.changeTree;
|
|
3920
4062
|
if (hasView) {
|
|
3921
4063
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
3922
4064
|
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
|
|
@@ -4005,7 +4147,9 @@ class Encoder {
|
|
|
4005
4147
|
const rootChangeSet = (typeof (field) === "string")
|
|
4006
4148
|
? this.root[field]
|
|
4007
4149
|
: field;
|
|
4008
|
-
rootChangeSet.
|
|
4150
|
+
let current = rootChangeSet.next;
|
|
4151
|
+
while (current) {
|
|
4152
|
+
const changeTree = current.changeTree;
|
|
4009
4153
|
const changeSet = changeTree[field];
|
|
4010
4154
|
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
4011
4155
|
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
@@ -4017,7 +4161,8 @@ class Encoder {
|
|
|
4017
4161
|
op: exports.OPERATION[op],
|
|
4018
4162
|
});
|
|
4019
4163
|
}
|
|
4020
|
-
|
|
4164
|
+
current = current.next;
|
|
4165
|
+
}
|
|
4021
4166
|
}
|
|
4022
4167
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4023
4168
|
const viewOffset = it.offset;
|
|
@@ -4067,21 +4212,19 @@ class Encoder {
|
|
|
4067
4212
|
}
|
|
4068
4213
|
discardChanges() {
|
|
4069
4214
|
// discard shared changes
|
|
4070
|
-
let
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
}
|
|
4075
|
-
this.root.changes.length = 0;
|
|
4215
|
+
let current = this.root.changes.next;
|
|
4216
|
+
while (current) {
|
|
4217
|
+
current.changeTree.endEncode('changes');
|
|
4218
|
+
current = current.next;
|
|
4076
4219
|
}
|
|
4220
|
+
this.root.changes = createChangeTreeList();
|
|
4077
4221
|
// discard filtered changes
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
}
|
|
4083
|
-
this.root.filteredChanges.length = 0;
|
|
4222
|
+
current = this.root.filteredChanges.next;
|
|
4223
|
+
while (current) {
|
|
4224
|
+
current.changeTree.endEncode('filteredChanges');
|
|
4225
|
+
current = current.next;
|
|
4084
4226
|
}
|
|
4227
|
+
this.root.filteredChanges = createChangeTreeList();
|
|
4085
4228
|
}
|
|
4086
4229
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
4087
4230
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
@@ -4271,21 +4414,24 @@ class Decoder {
|
|
|
4271
4414
|
//
|
|
4272
4415
|
if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
|
|
4273
4416
|
it.offset++;
|
|
4417
|
+
ref[$onDecodeEnd]?.();
|
|
4274
4418
|
const nextRefId = decode.number(bytes, it);
|
|
4275
4419
|
const nextRef = $root.refs.get(nextRefId);
|
|
4276
4420
|
//
|
|
4277
4421
|
// Trying to access a reference that haven't been decoded yet.
|
|
4278
4422
|
//
|
|
4279
4423
|
if (!nextRef) {
|
|
4424
|
+
// throw new Error(`"refId" not found: ${nextRefId}`);
|
|
4280
4425
|
console.error(`"refId" not found: ${nextRefId}`, { previousRef: ref, previousRefId: this.currentRefId });
|
|
4281
4426
|
console.warn("Please report this to the developers. All refIds =>");
|
|
4282
4427
|
console.warn(Schema.debugRefIdsDecoder(this));
|
|
4283
4428
|
this.skipCurrentStructure(bytes, it, totalBytes);
|
|
4284
4429
|
}
|
|
4285
|
-
|
|
4286
|
-
|
|
4287
|
-
|
|
4288
|
-
|
|
4430
|
+
else {
|
|
4431
|
+
ref = nextRef;
|
|
4432
|
+
decoder = ref.constructor[$decoder];
|
|
4433
|
+
this.currentRefId = nextRefId;
|
|
4434
|
+
}
|
|
4289
4435
|
continue;
|
|
4290
4436
|
}
|
|
4291
4437
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -4329,10 +4475,6 @@ class Decoder {
|
|
|
4329
4475
|
return type || defaultType;
|
|
4330
4476
|
}
|
|
4331
4477
|
createInstanceOfType(type) {
|
|
4332
|
-
// let instance: Schema = new (type as any)();
|
|
4333
|
-
// // assign root on $changes
|
|
4334
|
-
// instance[$changes].root = this.root[$changes].root;
|
|
4335
|
-
// return instance;
|
|
4336
4478
|
return new type();
|
|
4337
4479
|
}
|
|
4338
4480
|
removeChildRefs(ref, allChanges) {
|
|
@@ -5018,6 +5160,13 @@ class StateView {
|
|
|
5018
5160
|
changes = {};
|
|
5019
5161
|
this.changes.set(parentChangeTree.refId, changes);
|
|
5020
5162
|
}
|
|
5163
|
+
else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5164
|
+
//
|
|
5165
|
+
// SAME PATCH ADD + REMOVE:
|
|
5166
|
+
// The 'changes' of deleted structure should be ignored.
|
|
5167
|
+
//
|
|
5168
|
+
this.changes.delete(changeTree.refId);
|
|
5169
|
+
}
|
|
5021
5170
|
// DELETE / DELETE BY REF ID
|
|
5022
5171
|
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
5023
5172
|
// Remove child schema from visible set
|