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