@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/umd/index.js
CHANGED
|
@@ -32,37 +32,37 @@
|
|
|
32
32
|
|
|
33
33
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
34
34
|
|
|
35
|
-
const $track =
|
|
36
|
-
const $encoder =
|
|
37
|
-
const $decoder =
|
|
38
|
-
const $filter =
|
|
39
|
-
const $getByIndex =
|
|
40
|
-
const $deleteByIndex =
|
|
35
|
+
const $track = "~track";
|
|
36
|
+
const $encoder = "~encoder";
|
|
37
|
+
const $decoder = "~decoder";
|
|
38
|
+
const $filter = "~filter";
|
|
39
|
+
const $getByIndex = "~getByIndex";
|
|
40
|
+
const $deleteByIndex = "~deleteByIndex";
|
|
41
41
|
/**
|
|
42
42
|
* Used to hold ChangeTree instances whitin the structures
|
|
43
43
|
*/
|
|
44
|
-
const $changes =
|
|
44
|
+
const $changes = '~changes';
|
|
45
45
|
/**
|
|
46
46
|
* Used to keep track of the type of the child elements of a collection
|
|
47
47
|
* (MapSchema, ArraySchema, etc.)
|
|
48
48
|
*/
|
|
49
|
-
const $childType =
|
|
49
|
+
const $childType = '~childType';
|
|
50
50
|
/**
|
|
51
51
|
* Optional "discard" method for custom types (ArraySchema)
|
|
52
52
|
* (Discards changes for next serialization)
|
|
53
53
|
*/
|
|
54
|
-
const $onEncodeEnd =
|
|
54
|
+
const $onEncodeEnd = '~onEncodeEnd';
|
|
55
55
|
/**
|
|
56
56
|
* When decoding, this method is called after the instance is fully decoded
|
|
57
57
|
*/
|
|
58
|
-
const $onDecodeEnd =
|
|
58
|
+
const $onDecodeEnd = "~onDecodeEnd";
|
|
59
59
|
/**
|
|
60
60
|
* Metadata
|
|
61
61
|
*/
|
|
62
|
-
const $descriptors =
|
|
63
|
-
const $numFields = "
|
|
64
|
-
const $refTypeFieldIndexes = "
|
|
65
|
-
const $viewFieldIndexes = "
|
|
62
|
+
const $descriptors = "~descriptors";
|
|
63
|
+
const $numFields = "~__numFields";
|
|
64
|
+
const $refTypeFieldIndexes = "~__refTypeFieldIndexes";
|
|
65
|
+
const $viewFieldIndexes = "~__viewFieldIndexes";
|
|
66
66
|
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
67
67
|
|
|
68
68
|
/**
|
|
@@ -970,6 +970,24 @@
|
|
|
970
970
|
function createChangeSet() {
|
|
971
971
|
return { indexes: {}, operations: [] };
|
|
972
972
|
}
|
|
973
|
+
// Linked list helper functions
|
|
974
|
+
function createChangeTreeList() {
|
|
975
|
+
return { next: undefined, tail: undefined, length: 0 };
|
|
976
|
+
}
|
|
977
|
+
function addToChangeTreeList(list, changeTree) {
|
|
978
|
+
const node = { changeTree, next: undefined, prev: undefined };
|
|
979
|
+
if (!list.next) {
|
|
980
|
+
list.next = node;
|
|
981
|
+
list.tail = node;
|
|
982
|
+
}
|
|
983
|
+
else {
|
|
984
|
+
node.prev = list.tail;
|
|
985
|
+
list.tail.next = node;
|
|
986
|
+
list.tail = node;
|
|
987
|
+
}
|
|
988
|
+
list.length++;
|
|
989
|
+
return node;
|
|
990
|
+
}
|
|
973
991
|
function setOperationAtIndex(changeSet, index) {
|
|
974
992
|
const operationsIndex = changeSet.indexes[index];
|
|
975
993
|
if (operationsIndex === undefined) {
|
|
@@ -994,13 +1012,15 @@
|
|
|
994
1012
|
changeSet.operations[operationsIndex] = undefined;
|
|
995
1013
|
delete changeSet.indexes[index];
|
|
996
1014
|
}
|
|
997
|
-
function enqueueChangeTree(root, changeTree, changeSet,
|
|
1015
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootNode = changeTree[changeSet].queueRootNode) {
|
|
1016
|
+
// skip
|
|
998
1017
|
if (!root) {
|
|
999
|
-
// skip
|
|
1000
1018
|
return;
|
|
1001
1019
|
}
|
|
1002
|
-
|
|
1003
|
-
|
|
1020
|
+
if (queueRootNode) ;
|
|
1021
|
+
else {
|
|
1022
|
+
// Add to linked list if not already present
|
|
1023
|
+
changeTree[changeSet].queueRootNode = addToChangeTreeList(root[changeSet], changeTree);
|
|
1004
1024
|
}
|
|
1005
1025
|
}
|
|
1006
1026
|
class ChangeTree {
|
|
@@ -1024,83 +1044,58 @@
|
|
|
1024
1044
|
*/
|
|
1025
1045
|
this.isNew = true;
|
|
1026
1046
|
this.ref = ref;
|
|
1047
|
+
this.metadata = ref.constructor[Symbol.metadata];
|
|
1027
1048
|
//
|
|
1028
1049
|
// Does this structure have "filters" declared?
|
|
1029
1050
|
//
|
|
1030
|
-
|
|
1031
|
-
if (metadata?.[$viewFieldIndexes]) {
|
|
1051
|
+
if (this.metadata?.[$viewFieldIndexes]) {
|
|
1032
1052
|
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
1033
1053
|
this.filteredChanges = { indexes: {}, operations: [] };
|
|
1034
1054
|
}
|
|
1035
1055
|
}
|
|
1036
1056
|
setRoot(root) {
|
|
1037
1057
|
this.root = root;
|
|
1038
|
-
this.
|
|
1039
|
-
|
|
1040
|
-
// TODO: refactor and possibly unify .setRoot() and .setParent()
|
|
1041
|
-
//
|
|
1058
|
+
const isNewChangeTree = this.root.add(this);
|
|
1059
|
+
this.checkIsFiltered(this.parent, this.parentIndex, isNewChangeTree);
|
|
1042
1060
|
// Recursively set root on child structures
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
const changeTree = this.ref[field.name]?.[$changes];
|
|
1048
|
-
if (changeTree) {
|
|
1049
|
-
if (changeTree.root !== root) {
|
|
1050
|
-
changeTree.setRoot(root);
|
|
1051
|
-
}
|
|
1052
|
-
else {
|
|
1053
|
-
root.add(changeTree); // increment refCount
|
|
1054
|
-
}
|
|
1055
|
-
}
|
|
1056
|
-
});
|
|
1057
|
-
}
|
|
1058
|
-
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
1059
|
-
// MapSchema / ArraySchema, etc.
|
|
1060
|
-
this.ref.forEach((value, key) => {
|
|
1061
|
-
const changeTree = value[$changes];
|
|
1062
|
-
if (changeTree.root !== root) {
|
|
1063
|
-
changeTree.setRoot(root);
|
|
1061
|
+
if (isNewChangeTree) {
|
|
1062
|
+
this.forEachChild((child, _) => {
|
|
1063
|
+
if (child.root !== root) {
|
|
1064
|
+
child.setRoot(root);
|
|
1064
1065
|
}
|
|
1065
1066
|
else {
|
|
1066
|
-
root.add(
|
|
1067
|
+
root.add(child); // increment refCount
|
|
1067
1068
|
}
|
|
1068
1069
|
});
|
|
1069
1070
|
}
|
|
1070
1071
|
}
|
|
1071
1072
|
setParent(parent, root, parentIndex) {
|
|
1072
|
-
this.parent
|
|
1073
|
-
this.parentIndex = parentIndex;
|
|
1073
|
+
this.addParent(parent, parentIndex);
|
|
1074
1074
|
// avoid setting parents with empty `root`
|
|
1075
1075
|
if (!root) {
|
|
1076
1076
|
return;
|
|
1077
1077
|
}
|
|
1078
|
+
const isNewChangeTree = root.add(this);
|
|
1078
1079
|
// skip if parent is already set
|
|
1079
1080
|
if (root !== this.root) {
|
|
1080
1081
|
this.root = root;
|
|
1081
|
-
this.checkIsFiltered(parent, parentIndex);
|
|
1082
|
-
}
|
|
1083
|
-
else {
|
|
1084
|
-
root.add(this);
|
|
1082
|
+
this.checkIsFiltered(parent, parentIndex, isNewChangeTree);
|
|
1085
1083
|
}
|
|
1086
1084
|
// assign same parent on child structures
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
if (
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
this.ref.forEach((value, key) => {
|
|
1100
|
-
const changeTree = value[$changes];
|
|
1101
|
-
if (changeTree.root !== root) {
|
|
1102
|
-
changeTree.setParent(this.ref, root, this.indexes[key] ?? key);
|
|
1085
|
+
if (isNewChangeTree) {
|
|
1086
|
+
//
|
|
1087
|
+
// assign same parent on child structures
|
|
1088
|
+
//
|
|
1089
|
+
this.forEachChild((child, index) => {
|
|
1090
|
+
if (child.root === root) {
|
|
1091
|
+
//
|
|
1092
|
+
// re-assigning a child of the same root, move it to the end
|
|
1093
|
+
// of the changes queue so encoding order is preserved
|
|
1094
|
+
//
|
|
1095
|
+
root.moveToEndOfChanges(child);
|
|
1096
|
+
return;
|
|
1103
1097
|
}
|
|
1098
|
+
child.setParent(this.ref, root, index);
|
|
1104
1099
|
});
|
|
1105
1100
|
}
|
|
1106
1101
|
}
|
|
@@ -1108,21 +1103,23 @@
|
|
|
1108
1103
|
//
|
|
1109
1104
|
// assign same parent on child structures
|
|
1110
1105
|
//
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
const
|
|
1115
|
-
|
|
1116
|
-
if (value) {
|
|
1117
|
-
callback(value[$changes], index);
|
|
1106
|
+
if (this.ref[$childType]) {
|
|
1107
|
+
if (typeof (this.ref[$childType]) !== "string") {
|
|
1108
|
+
// MapSchema / ArraySchema, etc.
|
|
1109
|
+
for (const [key, value] of this.ref.entries()) {
|
|
1110
|
+
callback(value[$changes], key);
|
|
1118
1111
|
}
|
|
1119
|
-
}
|
|
1112
|
+
}
|
|
1120
1113
|
}
|
|
1121
|
-
else
|
|
1122
|
-
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1114
|
+
else {
|
|
1115
|
+
for (const index of this.metadata?.[$refTypeFieldIndexes] ?? []) {
|
|
1116
|
+
const field = this.metadata[index];
|
|
1117
|
+
const value = this.ref[field.name];
|
|
1118
|
+
if (!value) {
|
|
1119
|
+
continue;
|
|
1120
|
+
}
|
|
1121
|
+
callback(value[$changes], index);
|
|
1122
|
+
}
|
|
1126
1123
|
}
|
|
1127
1124
|
}
|
|
1128
1125
|
operation(op) {
|
|
@@ -1138,8 +1135,7 @@
|
|
|
1138
1135
|
}
|
|
1139
1136
|
}
|
|
1140
1137
|
change(index, operation = exports.OPERATION.ADD) {
|
|
1141
|
-
const
|
|
1142
|
-
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
1138
|
+
const isFiltered = this.isFiltered || (this.metadata?.[index]?.tag !== undefined);
|
|
1143
1139
|
const changeSet = (isFiltered)
|
|
1144
1140
|
? this.filteredChanges
|
|
1145
1141
|
: this.changes;
|
|
@@ -1229,19 +1225,16 @@
|
|
|
1229
1225
|
}
|
|
1230
1226
|
}
|
|
1231
1227
|
getType(index) {
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
//
|
|
1241
|
-
|
|
1242
|
-
//
|
|
1243
|
-
return this.ref[$childType];
|
|
1244
|
-
}
|
|
1228
|
+
return (
|
|
1229
|
+
//
|
|
1230
|
+
// Get the child type from parent structure.
|
|
1231
|
+
// - ["string"] => "string"
|
|
1232
|
+
// - { map: "string" } => "string"
|
|
1233
|
+
// - { set: "string" } => "string"
|
|
1234
|
+
//
|
|
1235
|
+
this.ref[$childType] || // ArraySchema | MapSchema | SetSchema | CollectionSchema
|
|
1236
|
+
this.metadata[index].type // Schema
|
|
1237
|
+
);
|
|
1245
1238
|
}
|
|
1246
1239
|
getChange(index) {
|
|
1247
1240
|
return this.indexedOperations[index];
|
|
@@ -1301,9 +1294,7 @@
|
|
|
1301
1294
|
endEncode(changeSetName) {
|
|
1302
1295
|
this.indexedOperations = {};
|
|
1303
1296
|
// clear changeset
|
|
1304
|
-
this[changeSetName]
|
|
1305
|
-
this[changeSetName].operations.length = 0;
|
|
1306
|
-
this[changeSetName].queueRootIndex = undefined;
|
|
1297
|
+
this[changeSetName] = createChangeSet();
|
|
1307
1298
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
1308
1299
|
this.ref[$onEncodeEnd]?.();
|
|
1309
1300
|
// Not a new instance anymore
|
|
@@ -1317,20 +1308,14 @@
|
|
|
1317
1308
|
//
|
|
1318
1309
|
this.ref[$onEncodeEnd]?.();
|
|
1319
1310
|
this.indexedOperations = {};
|
|
1320
|
-
this.changes
|
|
1321
|
-
this.changes.operations.length = 0;
|
|
1322
|
-
this.changes.queueRootIndex = undefined;
|
|
1311
|
+
this.changes = createChangeSet();
|
|
1323
1312
|
if (this.filteredChanges !== undefined) {
|
|
1324
|
-
this.filteredChanges
|
|
1325
|
-
this.filteredChanges.operations.length = 0;
|
|
1326
|
-
this.filteredChanges.queueRootIndex = undefined;
|
|
1313
|
+
this.filteredChanges = createChangeSet();
|
|
1327
1314
|
}
|
|
1328
1315
|
if (discardAll) {
|
|
1329
|
-
this.allChanges
|
|
1330
|
-
this.allChanges.operations.length = 0;
|
|
1316
|
+
this.allChanges = createChangeSet();
|
|
1331
1317
|
if (this.allFilteredChanges !== undefined) {
|
|
1332
|
-
this.allFilteredChanges
|
|
1333
|
-
this.allFilteredChanges.operations.length = 0;
|
|
1318
|
+
this.allFilteredChanges = createChangeSet();
|
|
1334
1319
|
}
|
|
1335
1320
|
}
|
|
1336
1321
|
}
|
|
@@ -1348,18 +1333,10 @@
|
|
|
1348
1333
|
}
|
|
1349
1334
|
this.discard();
|
|
1350
1335
|
}
|
|
1351
|
-
ensureRefId() {
|
|
1352
|
-
// skip if refId is already set.
|
|
1353
|
-
if (this.refId !== undefined) {
|
|
1354
|
-
return;
|
|
1355
|
-
}
|
|
1356
|
-
this.refId = this.root.getNextUniqueId();
|
|
1357
|
-
}
|
|
1358
1336
|
get changed() {
|
|
1359
1337
|
return (Object.entries(this.indexedOperations).length > 0);
|
|
1360
1338
|
}
|
|
1361
|
-
checkIsFiltered(parent, parentIndex) {
|
|
1362
|
-
const isNewChangeTree = this.root.add(this);
|
|
1339
|
+
checkIsFiltered(parent, parentIndex, isNewChangeTree) {
|
|
1363
1340
|
if (this.root.types.hasFilters) {
|
|
1364
1341
|
//
|
|
1365
1342
|
// At Schema initialization, the "root" structure might not be available
|
|
@@ -1371,14 +1348,14 @@
|
|
|
1371
1348
|
if (this.filteredChanges !== undefined) {
|
|
1372
1349
|
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1373
1350
|
if (isNewChangeTree) {
|
|
1374
|
-
this.root
|
|
1351
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
1375
1352
|
}
|
|
1376
1353
|
}
|
|
1377
1354
|
}
|
|
1378
1355
|
if (!this.isFiltered) {
|
|
1379
1356
|
enqueueChangeTree(this.root, this, 'changes');
|
|
1380
1357
|
if (isNewChangeTree) {
|
|
1381
|
-
this.root
|
|
1358
|
+
enqueueChangeTree(this.root, this, 'allChanges');
|
|
1382
1359
|
}
|
|
1383
1360
|
}
|
|
1384
1361
|
}
|
|
@@ -1435,6 +1412,90 @@
|
|
|
1435
1412
|
}
|
|
1436
1413
|
}
|
|
1437
1414
|
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Get the immediate parent
|
|
1417
|
+
*/
|
|
1418
|
+
get parent() {
|
|
1419
|
+
return this.parentChain?.ref;
|
|
1420
|
+
}
|
|
1421
|
+
/**
|
|
1422
|
+
* Get the immediate parent index
|
|
1423
|
+
*/
|
|
1424
|
+
get parentIndex() {
|
|
1425
|
+
return this.parentChain?.index;
|
|
1426
|
+
}
|
|
1427
|
+
/**
|
|
1428
|
+
* Add a parent to the chain
|
|
1429
|
+
*/
|
|
1430
|
+
addParent(parent, index) {
|
|
1431
|
+
// Check if this parent already exists in the chain
|
|
1432
|
+
if (this.hasParent((p, i) => p === parent && i === index)) {
|
|
1433
|
+
return;
|
|
1434
|
+
}
|
|
1435
|
+
this.parentChain = {
|
|
1436
|
+
ref: parent,
|
|
1437
|
+
index,
|
|
1438
|
+
next: this.parentChain
|
|
1439
|
+
};
|
|
1440
|
+
}
|
|
1441
|
+
/**
|
|
1442
|
+
* Remove a parent from the chain
|
|
1443
|
+
* @param parent - The parent to remove
|
|
1444
|
+
* @returns true if parent was removed
|
|
1445
|
+
*/
|
|
1446
|
+
removeParent(parent) {
|
|
1447
|
+
let current = this.parentChain;
|
|
1448
|
+
let previous = null;
|
|
1449
|
+
while (current) {
|
|
1450
|
+
//
|
|
1451
|
+
// FIXME: it is required to check against `$changes` here because
|
|
1452
|
+
// ArraySchema is instance of Proxy
|
|
1453
|
+
//
|
|
1454
|
+
if (current.ref[$changes] === parent[$changes]) {
|
|
1455
|
+
if (previous) {
|
|
1456
|
+
previous.next = current.next;
|
|
1457
|
+
}
|
|
1458
|
+
else {
|
|
1459
|
+
this.parentChain = current.next;
|
|
1460
|
+
}
|
|
1461
|
+
return true;
|
|
1462
|
+
}
|
|
1463
|
+
previous = current;
|
|
1464
|
+
current = current.next;
|
|
1465
|
+
}
|
|
1466
|
+
return this.parentChain === undefined;
|
|
1467
|
+
}
|
|
1468
|
+
/**
|
|
1469
|
+
* Find a specific parent in the chain
|
|
1470
|
+
*/
|
|
1471
|
+
findParent(predicate) {
|
|
1472
|
+
let current = this.parentChain;
|
|
1473
|
+
while (current) {
|
|
1474
|
+
if (predicate(current.ref, current.index)) {
|
|
1475
|
+
return current;
|
|
1476
|
+
}
|
|
1477
|
+
current = current.next;
|
|
1478
|
+
}
|
|
1479
|
+
return undefined;
|
|
1480
|
+
}
|
|
1481
|
+
/**
|
|
1482
|
+
* Check if this ChangeTree has a specific parent
|
|
1483
|
+
*/
|
|
1484
|
+
hasParent(predicate) {
|
|
1485
|
+
return this.findParent(predicate) !== undefined;
|
|
1486
|
+
}
|
|
1487
|
+
/**
|
|
1488
|
+
* Get all parents as an array (for debugging/testing)
|
|
1489
|
+
*/
|
|
1490
|
+
getAllParents() {
|
|
1491
|
+
const parents = [];
|
|
1492
|
+
let current = this.parentChain;
|
|
1493
|
+
while (current) {
|
|
1494
|
+
parents.push({ ref: current.ref, index: current.index });
|
|
1495
|
+
current = current.next;
|
|
1496
|
+
}
|
|
1497
|
+
return parents;
|
|
1498
|
+
}
|
|
1438
1499
|
}
|
|
1439
1500
|
|
|
1440
1501
|
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
@@ -1983,8 +2044,11 @@
|
|
|
1983
2044
|
return Reflect.has(obj, key);
|
|
1984
2045
|
}
|
|
1985
2046
|
});
|
|
1986
|
-
this
|
|
1987
|
-
|
|
2047
|
+
Object.defineProperty(this, $changes, {
|
|
2048
|
+
value: new ChangeTree(proxy),
|
|
2049
|
+
enumerable: false,
|
|
2050
|
+
writable: true,
|
|
2051
|
+
});
|
|
1988
2052
|
if (items.length > 0) {
|
|
1989
2053
|
this.push(...items);
|
|
1990
2054
|
}
|
|
@@ -2145,7 +2209,6 @@
|
|
|
2145
2209
|
if (this.items.length === 0) {
|
|
2146
2210
|
return undefined;
|
|
2147
2211
|
}
|
|
2148
|
-
// const index = Number(Object.keys(changeTree.indexes)[0]);
|
|
2149
2212
|
const changeTree = this[$changes];
|
|
2150
2213
|
const index = this.tmpItems.findIndex(item => item === this.items[0]);
|
|
2151
2214
|
const allChangesIndex = this.items.findIndex(item => item === this.items[0]);
|
|
@@ -2604,8 +2667,13 @@
|
|
|
2604
2667
|
this.$items = new Map();
|
|
2605
2668
|
this.$indexes = new Map();
|
|
2606
2669
|
this.deletedItems = {};
|
|
2607
|
-
|
|
2608
|
-
|
|
2670
|
+
const changeTree = new ChangeTree(this);
|
|
2671
|
+
changeTree.indexes = {};
|
|
2672
|
+
Object.defineProperty(this, $changes, {
|
|
2673
|
+
value: changeTree,
|
|
2674
|
+
enumerable: false,
|
|
2675
|
+
writable: true,
|
|
2676
|
+
});
|
|
2609
2677
|
if (initialValues) {
|
|
2610
2678
|
if (initialValues instanceof Map ||
|
|
2611
2679
|
initialValues instanceof MapSchema) {
|
|
@@ -3149,10 +3217,13 @@
|
|
|
3149
3217
|
refs: []
|
|
3150
3218
|
};
|
|
3151
3219
|
// for (const refId in $root.changes) {
|
|
3152
|
-
$root.changes.
|
|
3220
|
+
let current = $root.changes.next;
|
|
3221
|
+
while (current) {
|
|
3222
|
+
const changeTree = current.changeTree;
|
|
3153
3223
|
// skip if ChangeTree is undefined
|
|
3154
3224
|
if (changeTree === undefined) {
|
|
3155
|
-
|
|
3225
|
+
current = current.next;
|
|
3226
|
+
continue;
|
|
3156
3227
|
}
|
|
3157
3228
|
const changes = changeTree.indexedOperations;
|
|
3158
3229
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
@@ -3164,7 +3235,8 @@
|
|
|
3164
3235
|
}
|
|
3165
3236
|
dump.ops[exports.OPERATION[op]]++;
|
|
3166
3237
|
}
|
|
3167
|
-
|
|
3238
|
+
current = current.next;
|
|
3239
|
+
}
|
|
3168
3240
|
return dump;
|
|
3169
3241
|
}
|
|
3170
3242
|
|
|
@@ -3314,7 +3386,7 @@
|
|
|
3314
3386
|
* @param showContents display JSON contents of the instance
|
|
3315
3387
|
* @returns
|
|
3316
3388
|
*/
|
|
3317
|
-
static debugRefIds(ref, showContents = false, level = 0, decoder) {
|
|
3389
|
+
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3318
3390
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3319
3391
|
const changeTree = ref[$changes];
|
|
3320
3392
|
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
@@ -3323,10 +3395,24 @@
|
|
|
3323
3395
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
3324
3396
|
? ` [×${root.refCount[refId]}]`
|
|
3325
3397
|
: '';
|
|
3326
|
-
let output = `${getIndent(level)}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3327
|
-
changeTree.forEachChild((childChangeTree) =>
|
|
3398
|
+
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3399
|
+
changeTree.forEachChild((childChangeTree, key) => {
|
|
3400
|
+
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3401
|
+
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3402
|
+
});
|
|
3328
3403
|
return output;
|
|
3329
3404
|
}
|
|
3405
|
+
static debugRefIdEncodeOrder(ref) {
|
|
3406
|
+
let encodeOrder = [];
|
|
3407
|
+
let current = ref[$changes].root.allChanges.next;
|
|
3408
|
+
while (current) {
|
|
3409
|
+
if (current.changeTree) {
|
|
3410
|
+
encodeOrder.push(current.changeTree.refId);
|
|
3411
|
+
}
|
|
3412
|
+
current = current.next;
|
|
3413
|
+
}
|
|
3414
|
+
return encodeOrder;
|
|
3415
|
+
}
|
|
3330
3416
|
static debugRefIdsDecoder(decoder) {
|
|
3331
3417
|
return this.debugRefIds(decoder.state, false, 0, decoder);
|
|
3332
3418
|
}
|
|
@@ -3376,8 +3462,12 @@
|
|
|
3376
3462
|
const changeTrees = new Map();
|
|
3377
3463
|
const instanceRefIds = [];
|
|
3378
3464
|
let totalOperations = 0;
|
|
3465
|
+
// TODO: FIXME: this method is not working as expected
|
|
3379
3466
|
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3380
3467
|
const changeTree = root.changeTrees[refId];
|
|
3468
|
+
if (!changeTree) {
|
|
3469
|
+
continue;
|
|
3470
|
+
}
|
|
3381
3471
|
let includeChangeTree = false;
|
|
3382
3472
|
let parentChangeTrees = [];
|
|
3383
3473
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3800,18 +3890,20 @@
|
|
|
3800
3890
|
this.refCount = {};
|
|
3801
3891
|
this.changeTrees = {};
|
|
3802
3892
|
// all changes
|
|
3803
|
-
this.allChanges =
|
|
3804
|
-
this.allFilteredChanges =
|
|
3893
|
+
this.allChanges = createChangeTreeList();
|
|
3894
|
+
this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3805
3895
|
// pending changes to be encoded
|
|
3806
|
-
this.changes =
|
|
3807
|
-
this.filteredChanges =
|
|
3896
|
+
this.changes = createChangeTreeList();
|
|
3897
|
+
this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3808
3898
|
}
|
|
3809
3899
|
getNextUniqueId() {
|
|
3810
3900
|
return this.nextUniqueId++;
|
|
3811
3901
|
}
|
|
3812
3902
|
add(changeTree) {
|
|
3813
|
-
//
|
|
3814
|
-
changeTree.
|
|
3903
|
+
// Assign unique `refId` to changeTree if it doesn't have one yet.
|
|
3904
|
+
if (changeTree.refId === undefined) {
|
|
3905
|
+
changeTree.refId = this.getNextUniqueId();
|
|
3906
|
+
}
|
|
3815
3907
|
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3816
3908
|
if (isNewChangeTree) {
|
|
3817
3909
|
this.changeTrees[changeTree.refId] = changeTree;
|
|
@@ -3847,7 +3939,19 @@
|
|
|
3847
3939
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3848
3940
|
}
|
|
3849
3941
|
this.refCount[changeTree.refId] = 0;
|
|
3850
|
-
changeTree.forEachChild((child, _) =>
|
|
3942
|
+
changeTree.forEachChild((child, _) => {
|
|
3943
|
+
if (child.removeParent(changeTree.ref)) {
|
|
3944
|
+
if ((child.parentChain === undefined || // no parent, remove it
|
|
3945
|
+
(child.parentChain && this.refCount[child.refId] > 1) // parent is still in use, but has more than one reference, remove it
|
|
3946
|
+
)) {
|
|
3947
|
+
this.remove(child);
|
|
3948
|
+
}
|
|
3949
|
+
else if (child.parentChain) {
|
|
3950
|
+
// re-assigning a child of the same root, move it to the end
|
|
3951
|
+
this.moveToEndOfChanges(child);
|
|
3952
|
+
}
|
|
3953
|
+
}
|
|
3954
|
+
});
|
|
3851
3955
|
}
|
|
3852
3956
|
else {
|
|
3853
3957
|
this.refCount[changeTree.refId] = refCount;
|
|
@@ -3860,32 +3964,73 @@
|
|
|
3860
3964
|
// containing instance is not available, the Decoder will throw
|
|
3861
3965
|
// "refId not found" error.
|
|
3862
3966
|
//
|
|
3863
|
-
|
|
3864
|
-
|
|
3865
|
-
enqueueChangeTree(this, changeTree, "filteredChanges");
|
|
3866
|
-
}
|
|
3867
|
-
else {
|
|
3868
|
-
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3869
|
-
enqueueChangeTree(this, changeTree, "changes");
|
|
3870
|
-
}
|
|
3967
|
+
this.moveToEndOfChanges(changeTree);
|
|
3968
|
+
changeTree.forEachChild((child, _) => this.moveToEndOfChanges(child));
|
|
3871
3969
|
}
|
|
3872
3970
|
return refCount;
|
|
3873
3971
|
}
|
|
3972
|
+
moveToEndOfChanges(changeTree) {
|
|
3973
|
+
if (changeTree.filteredChanges) {
|
|
3974
|
+
this.moveToEndOfChangeTreeList("filteredChanges", changeTree);
|
|
3975
|
+
this.moveToEndOfChangeTreeList("allFilteredChanges", changeTree);
|
|
3976
|
+
}
|
|
3977
|
+
else {
|
|
3978
|
+
this.moveToEndOfChangeTreeList("changes", changeTree);
|
|
3979
|
+
this.moveToEndOfChangeTreeList("allChanges", changeTree);
|
|
3980
|
+
}
|
|
3981
|
+
}
|
|
3982
|
+
moveToEndOfChangeTreeList(changeSetName, changeTree) {
|
|
3983
|
+
const changeSet = this[changeSetName];
|
|
3984
|
+
const node = changeTree[changeSetName].queueRootNode;
|
|
3985
|
+
if (!node || node === changeSet.tail)
|
|
3986
|
+
return;
|
|
3987
|
+
// Remove from current position
|
|
3988
|
+
if (node.prev) {
|
|
3989
|
+
node.prev.next = node.next;
|
|
3990
|
+
}
|
|
3991
|
+
else {
|
|
3992
|
+
changeSet.next = node.next;
|
|
3993
|
+
}
|
|
3994
|
+
if (node.next) {
|
|
3995
|
+
node.next.prev = node.prev;
|
|
3996
|
+
}
|
|
3997
|
+
else {
|
|
3998
|
+
changeSet.tail = node.prev;
|
|
3999
|
+
}
|
|
4000
|
+
// Add to end
|
|
4001
|
+
node.prev = changeSet.tail;
|
|
4002
|
+
node.next = undefined;
|
|
4003
|
+
if (changeSet.tail) {
|
|
4004
|
+
changeSet.tail.next = node;
|
|
4005
|
+
}
|
|
4006
|
+
else {
|
|
4007
|
+
changeSet.next = node;
|
|
4008
|
+
}
|
|
4009
|
+
changeSet.tail = node;
|
|
4010
|
+
}
|
|
3874
4011
|
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3875
4012
|
const changeSet = this[changeSetName];
|
|
3876
|
-
const
|
|
3877
|
-
if (
|
|
3878
|
-
|
|
3879
|
-
|
|
4013
|
+
const node = changeTree[changeSetName].queueRootNode;
|
|
4014
|
+
if (node && node.changeTree === changeTree) {
|
|
4015
|
+
// Remove the node from the linked list
|
|
4016
|
+
if (node.prev) {
|
|
4017
|
+
node.prev.next = node.next;
|
|
4018
|
+
}
|
|
4019
|
+
else {
|
|
4020
|
+
changeSet.next = node.next;
|
|
4021
|
+
}
|
|
4022
|
+
if (node.next) {
|
|
4023
|
+
node.next.prev = node.prev;
|
|
4024
|
+
}
|
|
4025
|
+
else {
|
|
4026
|
+
changeSet.tail = node.prev;
|
|
4027
|
+
}
|
|
4028
|
+
changeSet.length--;
|
|
4029
|
+
// Clear ChangeTree reference
|
|
4030
|
+
changeTree[changeSetName].queueRootNode = undefined;
|
|
3880
4031
|
return true;
|
|
3881
4032
|
}
|
|
3882
|
-
|
|
3883
|
-
// changeTree[changeSetName].queueRootIndex = -1;
|
|
3884
|
-
// return true;
|
|
3885
|
-
// }
|
|
3886
|
-
}
|
|
3887
|
-
clear() {
|
|
3888
|
-
this.changes.length = 0;
|
|
4033
|
+
return false;
|
|
3889
4034
|
}
|
|
3890
4035
|
}
|
|
3891
4036
|
|
|
@@ -3915,12 +4060,9 @@
|
|
|
3915
4060
|
) {
|
|
3916
4061
|
const hasView = (view !== undefined);
|
|
3917
4062
|
const rootChangeTree = this.state[$changes];
|
|
3918
|
-
|
|
3919
|
-
|
|
3920
|
-
const changeTree =
|
|
3921
|
-
if (!changeTree) {
|
|
3922
|
-
continue;
|
|
3923
|
-
}
|
|
4063
|
+
let current = this.root[changeSetName];
|
|
4064
|
+
while (current = current.next) {
|
|
4065
|
+
const changeTree = current.changeTree;
|
|
3924
4066
|
if (hasView) {
|
|
3925
4067
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
3926
4068
|
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
|
|
@@ -4009,7 +4151,9 @@
|
|
|
4009
4151
|
const rootChangeSet = (typeof (field) === "string")
|
|
4010
4152
|
? this.root[field]
|
|
4011
4153
|
: field;
|
|
4012
|
-
rootChangeSet.
|
|
4154
|
+
let current = rootChangeSet.next;
|
|
4155
|
+
while (current) {
|
|
4156
|
+
const changeTree = current.changeTree;
|
|
4013
4157
|
const changeSet = changeTree[field];
|
|
4014
4158
|
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
4015
4159
|
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
@@ -4021,7 +4165,8 @@
|
|
|
4021
4165
|
op: exports.OPERATION[op],
|
|
4022
4166
|
});
|
|
4023
4167
|
}
|
|
4024
|
-
|
|
4168
|
+
current = current.next;
|
|
4169
|
+
}
|
|
4025
4170
|
}
|
|
4026
4171
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4027
4172
|
const viewOffset = it.offset;
|
|
@@ -4071,21 +4216,19 @@
|
|
|
4071
4216
|
}
|
|
4072
4217
|
discardChanges() {
|
|
4073
4218
|
// discard shared changes
|
|
4074
|
-
let
|
|
4075
|
-
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
}
|
|
4079
|
-
this.root.changes.length = 0;
|
|
4219
|
+
let current = this.root.changes.next;
|
|
4220
|
+
while (current) {
|
|
4221
|
+
current.changeTree.endEncode('changes');
|
|
4222
|
+
current = current.next;
|
|
4080
4223
|
}
|
|
4224
|
+
this.root.changes = createChangeTreeList();
|
|
4081
4225
|
// discard filtered changes
|
|
4082
|
-
|
|
4083
|
-
|
|
4084
|
-
|
|
4085
|
-
|
|
4086
|
-
}
|
|
4087
|
-
this.root.filteredChanges.length = 0;
|
|
4226
|
+
current = this.root.filteredChanges.next;
|
|
4227
|
+
while (current) {
|
|
4228
|
+
current.changeTree.endEncode('filteredChanges');
|
|
4229
|
+
current = current.next;
|
|
4088
4230
|
}
|
|
4231
|
+
this.root.filteredChanges = createChangeTreeList();
|
|
4089
4232
|
}
|
|
4090
4233
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
4091
4234
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
@@ -4275,21 +4418,24 @@
|
|
|
4275
4418
|
//
|
|
4276
4419
|
if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
|
|
4277
4420
|
it.offset++;
|
|
4421
|
+
ref[$onDecodeEnd]?.();
|
|
4278
4422
|
const nextRefId = decode.number(bytes, it);
|
|
4279
4423
|
const nextRef = $root.refs.get(nextRefId);
|
|
4280
4424
|
//
|
|
4281
4425
|
// Trying to access a reference that haven't been decoded yet.
|
|
4282
4426
|
//
|
|
4283
4427
|
if (!nextRef) {
|
|
4428
|
+
// throw new Error(`"refId" not found: ${nextRefId}`);
|
|
4284
4429
|
console.error(`"refId" not found: ${nextRefId}`, { previousRef: ref, previousRefId: this.currentRefId });
|
|
4285
4430
|
console.warn("Please report this to the developers. All refIds =>");
|
|
4286
4431
|
console.warn(Schema.debugRefIdsDecoder(this));
|
|
4287
4432
|
this.skipCurrentStructure(bytes, it, totalBytes);
|
|
4288
4433
|
}
|
|
4289
|
-
|
|
4290
|
-
|
|
4291
|
-
|
|
4292
|
-
|
|
4434
|
+
else {
|
|
4435
|
+
ref = nextRef;
|
|
4436
|
+
decoder = ref.constructor[$decoder];
|
|
4437
|
+
this.currentRefId = nextRefId;
|
|
4438
|
+
}
|
|
4293
4439
|
continue;
|
|
4294
4440
|
}
|
|
4295
4441
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -4333,10 +4479,6 @@
|
|
|
4333
4479
|
return type || defaultType;
|
|
4334
4480
|
}
|
|
4335
4481
|
createInstanceOfType(type) {
|
|
4336
|
-
// let instance: Schema = new (type as any)();
|
|
4337
|
-
// // assign root on $changes
|
|
4338
|
-
// instance[$changes].root = this.root[$changes].root;
|
|
4339
|
-
// return instance;
|
|
4340
4482
|
return new type();
|
|
4341
4483
|
}
|
|
4342
4484
|
removeChildRefs(ref, allChanges) {
|
|
@@ -5022,6 +5164,13 @@
|
|
|
5022
5164
|
changes = {};
|
|
5023
5165
|
this.changes.set(parentChangeTree.refId, changes);
|
|
5024
5166
|
}
|
|
5167
|
+
else if (changes[changeTree.parentIndex] === exports.OPERATION.ADD) {
|
|
5168
|
+
//
|
|
5169
|
+
// SAME PATCH ADD + REMOVE:
|
|
5170
|
+
// The 'changes' of deleted structure should be ignored.
|
|
5171
|
+
//
|
|
5172
|
+
this.changes.delete(changeTree.refId);
|
|
5173
|
+
}
|
|
5025
5174
|
// DELETE / DELETE BY REF ID
|
|
5026
5175
|
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
5027
5176
|
// Remove child schema from visible set
|