@colyseus/schema 3.0.42 → 3.0.44
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 +365 -219
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +365 -219
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +365 -219
- package/lib/Schema.d.ts +4 -3
- package/lib/Schema.js +22 -4
- 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 +8 -9
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +57 -7
- package/lib/encoder/ChangeTree.js +172 -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 +9 -8
- package/lib/encoder/Root.js +84 -27
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.d.ts +1 -1
- package/lib/encoder/StateView.js +28 -23
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +7 -5
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.js +1 -1
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.js +9 -4
- 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 +2 -1
- package/src/Schema.ts +23 -7
- package/src/bench_encode.ts +108 -0
- package/src/debug.ts +55 -0
- package/src/decoder/Decoder.ts +9 -13
- package/src/encoder/ChangeTree.ts +203 -116
- package/src/encoder/Encoder.ts +21 -19
- package/src/encoder/Root.ts +90 -29
- package/src/encoder/StateView.ts +34 -26
- package/src/types/custom/ArraySchema.ts +8 -6
- package/src/types/custom/CollectionSchema.ts +1 -1
- package/src/types/custom/MapSchema.ts +10 -4
- package/src/types/symbols.ts +15 -15
- 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,59 @@ 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
|
-
|
|
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.add(child);
|
|
1090
|
+
root.moveToEndOfChanges(child);
|
|
1091
|
+
return;
|
|
1097
1092
|
}
|
|
1093
|
+
child.setParent(this.ref, root, index);
|
|
1098
1094
|
});
|
|
1099
1095
|
}
|
|
1100
1096
|
}
|
|
@@ -1102,21 +1098,23 @@ class ChangeTree {
|
|
|
1102
1098
|
//
|
|
1103
1099
|
// assign same parent on child structures
|
|
1104
1100
|
//
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
const
|
|
1109
|
-
|
|
1110
|
-
if (value) {
|
|
1111
|
-
callback(value[$changes], index);
|
|
1101
|
+
if (this.ref[$childType]) {
|
|
1102
|
+
if (typeof (this.ref[$childType]) !== "string") {
|
|
1103
|
+
// MapSchema / ArraySchema, etc.
|
|
1104
|
+
for (const [key, value] of this.ref.entries()) {
|
|
1105
|
+
callback(value[$changes], key);
|
|
1112
1106
|
}
|
|
1113
|
-
}
|
|
1107
|
+
}
|
|
1114
1108
|
}
|
|
1115
|
-
else
|
|
1116
|
-
|
|
1117
|
-
|
|
1118
|
-
|
|
1119
|
-
|
|
1109
|
+
else {
|
|
1110
|
+
for (const index of this.metadata?.[$refTypeFieldIndexes] ?? []) {
|
|
1111
|
+
const field = this.metadata[index];
|
|
1112
|
+
const value = this.ref[field.name];
|
|
1113
|
+
if (!value) {
|
|
1114
|
+
continue;
|
|
1115
|
+
}
|
|
1116
|
+
callback(value[$changes], index);
|
|
1117
|
+
}
|
|
1120
1118
|
}
|
|
1121
1119
|
}
|
|
1122
1120
|
operation(op) {
|
|
@@ -1132,8 +1130,7 @@ class ChangeTree {
|
|
|
1132
1130
|
}
|
|
1133
1131
|
}
|
|
1134
1132
|
change(index, operation = OPERATION.ADD) {
|
|
1135
|
-
const
|
|
1136
|
-
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
1133
|
+
const isFiltered = this.isFiltered || (this.metadata?.[index]?.tag !== undefined);
|
|
1137
1134
|
const changeSet = (isFiltered)
|
|
1138
1135
|
? this.filteredChanges
|
|
1139
1136
|
: this.changes;
|
|
@@ -1223,19 +1220,16 @@ class ChangeTree {
|
|
|
1223
1220
|
}
|
|
1224
1221
|
}
|
|
1225
1222
|
getType(index) {
|
|
1226
|
-
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
//
|
|
1235
|
-
|
|
1236
|
-
//
|
|
1237
|
-
return this.ref[$childType];
|
|
1238
|
-
}
|
|
1223
|
+
return (
|
|
1224
|
+
//
|
|
1225
|
+
// Get the child type from parent structure.
|
|
1226
|
+
// - ["string"] => "string"
|
|
1227
|
+
// - { map: "string" } => "string"
|
|
1228
|
+
// - { set: "string" } => "string"
|
|
1229
|
+
//
|
|
1230
|
+
this.ref[$childType] || // ArraySchema | MapSchema | SetSchema | CollectionSchema
|
|
1231
|
+
this.metadata[index].type // Schema
|
|
1232
|
+
);
|
|
1239
1233
|
}
|
|
1240
1234
|
getChange(index) {
|
|
1241
1235
|
return this.indexedOperations[index];
|
|
@@ -1295,9 +1289,7 @@ class ChangeTree {
|
|
|
1295
1289
|
endEncode(changeSetName) {
|
|
1296
1290
|
this.indexedOperations = {};
|
|
1297
1291
|
// clear changeset
|
|
1298
|
-
this[changeSetName]
|
|
1299
|
-
this[changeSetName].operations.length = 0;
|
|
1300
|
-
this[changeSetName].queueRootIndex = undefined;
|
|
1292
|
+
this[changeSetName] = createChangeSet();
|
|
1301
1293
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
1302
1294
|
this.ref[$onEncodeEnd]?.();
|
|
1303
1295
|
// Not a new instance anymore
|
|
@@ -1311,20 +1303,14 @@ class ChangeTree {
|
|
|
1311
1303
|
//
|
|
1312
1304
|
this.ref[$onEncodeEnd]?.();
|
|
1313
1305
|
this.indexedOperations = {};
|
|
1314
|
-
this.changes
|
|
1315
|
-
this.changes.operations.length = 0;
|
|
1316
|
-
this.changes.queueRootIndex = undefined;
|
|
1306
|
+
this.changes = createChangeSet();
|
|
1317
1307
|
if (this.filteredChanges !== undefined) {
|
|
1318
|
-
this.filteredChanges
|
|
1319
|
-
this.filteredChanges.operations.length = 0;
|
|
1320
|
-
this.filteredChanges.queueRootIndex = undefined;
|
|
1308
|
+
this.filteredChanges = createChangeSet();
|
|
1321
1309
|
}
|
|
1322
1310
|
if (discardAll) {
|
|
1323
|
-
this.allChanges
|
|
1324
|
-
this.allChanges.operations.length = 0;
|
|
1311
|
+
this.allChanges = createChangeSet();
|
|
1325
1312
|
if (this.allFilteredChanges !== undefined) {
|
|
1326
|
-
this.allFilteredChanges
|
|
1327
|
-
this.allFilteredChanges.operations.length = 0;
|
|
1313
|
+
this.allFilteredChanges = createChangeSet();
|
|
1328
1314
|
}
|
|
1329
1315
|
}
|
|
1330
1316
|
}
|
|
@@ -1342,18 +1328,10 @@ class ChangeTree {
|
|
|
1342
1328
|
}
|
|
1343
1329
|
this.discard();
|
|
1344
1330
|
}
|
|
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
1331
|
get changed() {
|
|
1353
1332
|
return (Object.entries(this.indexedOperations).length > 0);
|
|
1354
1333
|
}
|
|
1355
|
-
checkIsFiltered(parent, parentIndex) {
|
|
1356
|
-
const isNewChangeTree = this.root.add(this);
|
|
1334
|
+
checkIsFiltered(parent, parentIndex, isNewChangeTree) {
|
|
1357
1335
|
if (this.root.types.hasFilters) {
|
|
1358
1336
|
//
|
|
1359
1337
|
// At Schema initialization, the "root" structure might not be available
|
|
@@ -1365,14 +1343,14 @@ class ChangeTree {
|
|
|
1365
1343
|
if (this.filteredChanges !== undefined) {
|
|
1366
1344
|
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1367
1345
|
if (isNewChangeTree) {
|
|
1368
|
-
this.root
|
|
1346
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
1369
1347
|
}
|
|
1370
1348
|
}
|
|
1371
1349
|
}
|
|
1372
1350
|
if (!this.isFiltered) {
|
|
1373
1351
|
enqueueChangeTree(this.root, this, 'changes');
|
|
1374
1352
|
if (isNewChangeTree) {
|
|
1375
|
-
this.root
|
|
1353
|
+
enqueueChangeTree(this.root, this, 'allChanges');
|
|
1376
1354
|
}
|
|
1377
1355
|
}
|
|
1378
1356
|
}
|
|
@@ -1429,6 +1407,90 @@ class ChangeTree {
|
|
|
1429
1407
|
}
|
|
1430
1408
|
}
|
|
1431
1409
|
}
|
|
1410
|
+
/**
|
|
1411
|
+
* Get the immediate parent
|
|
1412
|
+
*/
|
|
1413
|
+
get parent() {
|
|
1414
|
+
return this.parentChain?.ref;
|
|
1415
|
+
}
|
|
1416
|
+
/**
|
|
1417
|
+
* Get the immediate parent index
|
|
1418
|
+
*/
|
|
1419
|
+
get parentIndex() {
|
|
1420
|
+
return this.parentChain?.index;
|
|
1421
|
+
}
|
|
1422
|
+
/**
|
|
1423
|
+
* Add a parent to the chain
|
|
1424
|
+
*/
|
|
1425
|
+
addParent(parent, index) {
|
|
1426
|
+
// Check if this parent already exists in the chain
|
|
1427
|
+
if (this.hasParent((p, i) => p[$changes] === parent[$changes] && i === index)) {
|
|
1428
|
+
return;
|
|
1429
|
+
}
|
|
1430
|
+
this.parentChain = {
|
|
1431
|
+
ref: parent,
|
|
1432
|
+
index,
|
|
1433
|
+
next: this.parentChain
|
|
1434
|
+
};
|
|
1435
|
+
}
|
|
1436
|
+
/**
|
|
1437
|
+
* Remove a parent from the chain
|
|
1438
|
+
* @param parent - The parent to remove
|
|
1439
|
+
* @returns true if parent was removed
|
|
1440
|
+
*/
|
|
1441
|
+
removeParent(parent = this.parent) {
|
|
1442
|
+
let current = this.parentChain;
|
|
1443
|
+
let previous = null;
|
|
1444
|
+
while (current) {
|
|
1445
|
+
//
|
|
1446
|
+
// FIXME: it is required to check against `$changes` here because
|
|
1447
|
+
// ArraySchema is instance of Proxy
|
|
1448
|
+
//
|
|
1449
|
+
if (current.ref[$changes] === parent[$changes]) {
|
|
1450
|
+
if (previous) {
|
|
1451
|
+
previous.next = current.next;
|
|
1452
|
+
}
|
|
1453
|
+
else {
|
|
1454
|
+
this.parentChain = current.next;
|
|
1455
|
+
}
|
|
1456
|
+
return true;
|
|
1457
|
+
}
|
|
1458
|
+
previous = current;
|
|
1459
|
+
current = current.next;
|
|
1460
|
+
}
|
|
1461
|
+
return this.parentChain === undefined;
|
|
1462
|
+
}
|
|
1463
|
+
/**
|
|
1464
|
+
* Find a specific parent in the chain
|
|
1465
|
+
*/
|
|
1466
|
+
findParent(predicate) {
|
|
1467
|
+
let current = this.parentChain;
|
|
1468
|
+
while (current) {
|
|
1469
|
+
if (predicate(current.ref, current.index)) {
|
|
1470
|
+
return current;
|
|
1471
|
+
}
|
|
1472
|
+
current = current.next;
|
|
1473
|
+
}
|
|
1474
|
+
return undefined;
|
|
1475
|
+
}
|
|
1476
|
+
/**
|
|
1477
|
+
* Check if this ChangeTree has a specific parent
|
|
1478
|
+
*/
|
|
1479
|
+
hasParent(predicate) {
|
|
1480
|
+
return this.findParent(predicate) !== undefined;
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Get all parents as an array (for debugging/testing)
|
|
1484
|
+
*/
|
|
1485
|
+
getAllParents() {
|
|
1486
|
+
const parents = [];
|
|
1487
|
+
let current = this.parentChain;
|
|
1488
|
+
while (current) {
|
|
1489
|
+
parents.push({ ref: current.ref, index: current.index });
|
|
1490
|
+
current = current.next;
|
|
1491
|
+
}
|
|
1492
|
+
return parents;
|
|
1493
|
+
}
|
|
1432
1494
|
}
|
|
1433
1495
|
|
|
1434
1496
|
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
@@ -1946,7 +2008,7 @@ class ArraySchema {
|
|
|
1946
2008
|
}
|
|
1947
2009
|
if (previousValue !== undefined) {
|
|
1948
2010
|
// remove root reference from previous value
|
|
1949
|
-
previousValue[$changes].root?.remove(previousValue[$changes]);
|
|
2011
|
+
previousValue[$changes].root?.remove(previousValue[$changes], obj);
|
|
1950
2012
|
}
|
|
1951
2013
|
}
|
|
1952
2014
|
else {
|
|
@@ -1977,8 +2039,11 @@ class ArraySchema {
|
|
|
1977
2039
|
return Reflect.has(obj, key);
|
|
1978
2040
|
}
|
|
1979
2041
|
});
|
|
1980
|
-
this
|
|
1981
|
-
|
|
2042
|
+
Object.defineProperty(this, $changes, {
|
|
2043
|
+
value: new ChangeTree(proxy),
|
|
2044
|
+
enumerable: false,
|
|
2045
|
+
writable: true,
|
|
2046
|
+
});
|
|
1982
2047
|
if (items.length > 0) {
|
|
1983
2048
|
this.push(...items);
|
|
1984
2049
|
}
|
|
@@ -2100,7 +2165,7 @@ class ArraySchema {
|
|
|
2100
2165
|
const changeTree = this[$changes];
|
|
2101
2166
|
// remove children references
|
|
2102
2167
|
changeTree.forEachChild((childChangeTree, _) => {
|
|
2103
|
-
changeTree.root?.remove(childChangeTree);
|
|
2168
|
+
changeTree.root?.remove(childChangeTree, this);
|
|
2104
2169
|
});
|
|
2105
2170
|
changeTree.discard(true);
|
|
2106
2171
|
changeTree.operation(OPERATION.CLEAR);
|
|
@@ -2139,7 +2204,6 @@ class ArraySchema {
|
|
|
2139
2204
|
if (this.items.length === 0) {
|
|
2140
2205
|
return undefined;
|
|
2141
2206
|
}
|
|
2142
|
-
// const index = Number(Object.keys(changeTree.indexes)[0]);
|
|
2143
2207
|
const changeTree = this[$changes];
|
|
2144
2208
|
const index = this.tmpItems.findIndex(item => item === this.items[0]);
|
|
2145
2209
|
const allChangesIndex = this.items.findIndex(item => item === this.items[0]);
|
|
@@ -2598,8 +2662,13 @@ class MapSchema {
|
|
|
2598
2662
|
this.$items = new Map();
|
|
2599
2663
|
this.$indexes = new Map();
|
|
2600
2664
|
this.deletedItems = {};
|
|
2601
|
-
|
|
2602
|
-
|
|
2665
|
+
const changeTree = new ChangeTree(this);
|
|
2666
|
+
changeTree.indexes = {};
|
|
2667
|
+
Object.defineProperty(this, $changes, {
|
|
2668
|
+
value: changeTree,
|
|
2669
|
+
enumerable: false,
|
|
2670
|
+
writable: true,
|
|
2671
|
+
});
|
|
2603
2672
|
if (initialValues) {
|
|
2604
2673
|
if (initialValues instanceof Map ||
|
|
2605
2674
|
initialValues instanceof MapSchema) {
|
|
@@ -2650,7 +2719,7 @@ class MapSchema {
|
|
|
2650
2719
|
operation = OPERATION.DELETE_AND_ADD;
|
|
2651
2720
|
// remove reference from previous value
|
|
2652
2721
|
if (previousValue !== undefined) {
|
|
2653
|
-
previousValue[$changes].root?.remove(previousValue[$changes]);
|
|
2722
|
+
previousValue[$changes].root?.remove(previousValue[$changes], this);
|
|
2654
2723
|
}
|
|
2655
2724
|
}
|
|
2656
2725
|
}
|
|
@@ -2687,7 +2756,7 @@ class MapSchema {
|
|
|
2687
2756
|
changeTree.indexes = {};
|
|
2688
2757
|
// remove children references
|
|
2689
2758
|
changeTree.forEachChild((childChangeTree, _) => {
|
|
2690
|
-
changeTree.root?.remove(childChangeTree);
|
|
2759
|
+
changeTree.root?.remove(childChangeTree, this);
|
|
2691
2760
|
});
|
|
2692
2761
|
// clear previous indexes
|
|
2693
2762
|
this.$indexes.clear();
|
|
@@ -3143,10 +3212,13 @@ function dumpChanges(schema) {
|
|
|
3143
3212
|
refs: []
|
|
3144
3213
|
};
|
|
3145
3214
|
// for (const refId in $root.changes) {
|
|
3146
|
-
$root.changes.
|
|
3215
|
+
let current = $root.changes.next;
|
|
3216
|
+
while (current) {
|
|
3217
|
+
const changeTree = current.changeTree;
|
|
3147
3218
|
// skip if ChangeTree is undefined
|
|
3148
3219
|
if (changeTree === undefined) {
|
|
3149
|
-
|
|
3220
|
+
current = current.next;
|
|
3221
|
+
continue;
|
|
3150
3222
|
}
|
|
3151
3223
|
const changes = changeTree.indexedOperations;
|
|
3152
3224
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
@@ -3158,7 +3230,8 @@ function dumpChanges(schema) {
|
|
|
3158
3230
|
}
|
|
3159
3231
|
dump.ops[OPERATION[op]]++;
|
|
3160
3232
|
}
|
|
3161
|
-
|
|
3233
|
+
current = current.next;
|
|
3234
|
+
}
|
|
3162
3235
|
return dump;
|
|
3163
3236
|
}
|
|
3164
3237
|
|
|
@@ -3308,7 +3381,7 @@ class Schema {
|
|
|
3308
3381
|
* @param showContents display JSON contents of the instance
|
|
3309
3382
|
* @returns
|
|
3310
3383
|
*/
|
|
3311
|
-
static debugRefIds(ref, showContents = false, level = 0, decoder) {
|
|
3384
|
+
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3312
3385
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3313
3386
|
const changeTree = ref[$changes];
|
|
3314
3387
|
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
@@ -3317,11 +3390,25 @@ class Schema {
|
|
|
3317
3390
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
3318
3391
|
? ` [×${root.refCount[refId]}]`
|
|
3319
3392
|
: '';
|
|
3320
|
-
let output = `${getIndent(level)}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3321
|
-
changeTree.forEachChild((childChangeTree) =>
|
|
3393
|
+
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3394
|
+
changeTree.forEachChild((childChangeTree, key) => {
|
|
3395
|
+
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3396
|
+
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3397
|
+
});
|
|
3322
3398
|
return output;
|
|
3323
3399
|
}
|
|
3324
|
-
static
|
|
3400
|
+
static debugRefIdEncodingOrder(ref, changeSet = 'allChanges') {
|
|
3401
|
+
let encodeOrder = [];
|
|
3402
|
+
let current = ref[$changes].root[changeSet].next;
|
|
3403
|
+
while (current) {
|
|
3404
|
+
if (current.changeTree) {
|
|
3405
|
+
encodeOrder.push(current.changeTree.refId);
|
|
3406
|
+
}
|
|
3407
|
+
current = current.next;
|
|
3408
|
+
}
|
|
3409
|
+
return encodeOrder;
|
|
3410
|
+
}
|
|
3411
|
+
static debugRefIdsFromDecoder(decoder) {
|
|
3325
3412
|
return this.debugRefIds(decoder.state, false, 0, decoder);
|
|
3326
3413
|
}
|
|
3327
3414
|
/**
|
|
@@ -3370,8 +3457,12 @@ class Schema {
|
|
|
3370
3457
|
const changeTrees = new Map();
|
|
3371
3458
|
const instanceRefIds = [];
|
|
3372
3459
|
let totalOperations = 0;
|
|
3460
|
+
// TODO: FIXME: this method is not working as expected
|
|
3373
3461
|
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3374
3462
|
const changeTree = root.changeTrees[refId];
|
|
3463
|
+
if (!changeTree) {
|
|
3464
|
+
continue;
|
|
3465
|
+
}
|
|
3375
3466
|
let includeChangeTree = false;
|
|
3376
3467
|
let parentChangeTrees = [];
|
|
3377
3468
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3507,7 +3598,7 @@ class CollectionSchema {
|
|
|
3507
3598
|
changeTree.indexes = {};
|
|
3508
3599
|
// remove children references
|
|
3509
3600
|
changeTree.forEachChild((childChangeTree, _) => {
|
|
3510
|
-
changeTree.root?.remove(childChangeTree);
|
|
3601
|
+
changeTree.root?.remove(childChangeTree, this);
|
|
3511
3602
|
});
|
|
3512
3603
|
// clear previous indexes
|
|
3513
3604
|
this.$indexes.clear();
|
|
@@ -3794,18 +3885,20 @@ class Root {
|
|
|
3794
3885
|
this.refCount = {};
|
|
3795
3886
|
this.changeTrees = {};
|
|
3796
3887
|
// all changes
|
|
3797
|
-
this.allChanges =
|
|
3798
|
-
this.allFilteredChanges =
|
|
3888
|
+
this.allChanges = createChangeTreeList();
|
|
3889
|
+
this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3799
3890
|
// pending changes to be encoded
|
|
3800
|
-
this.changes =
|
|
3801
|
-
this.filteredChanges =
|
|
3891
|
+
this.changes = createChangeTreeList();
|
|
3892
|
+
this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3802
3893
|
}
|
|
3803
3894
|
getNextUniqueId() {
|
|
3804
3895
|
return this.nextUniqueId++;
|
|
3805
3896
|
}
|
|
3806
3897
|
add(changeTree) {
|
|
3807
|
-
//
|
|
3808
|
-
changeTree.
|
|
3898
|
+
// Assign unique `refId` to changeTree if it doesn't have one yet.
|
|
3899
|
+
if (changeTree.refId === undefined) {
|
|
3900
|
+
changeTree.refId = this.getNextUniqueId();
|
|
3901
|
+
}
|
|
3809
3902
|
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3810
3903
|
if (isNewChangeTree) {
|
|
3811
3904
|
this.changeTrees[changeTree.refId] = changeTree;
|
|
@@ -3824,10 +3917,12 @@ class Root {
|
|
|
3824
3917
|
}
|
|
3825
3918
|
}
|
|
3826
3919
|
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3920
|
+
// console.log("ADD", { refId: changeTree.refId, refCount: this.refCount[changeTree.refId] });
|
|
3827
3921
|
return isNewChangeTree;
|
|
3828
3922
|
}
|
|
3829
|
-
remove(changeTree) {
|
|
3923
|
+
remove(changeTree, parent) {
|
|
3830
3924
|
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3925
|
+
// console.log("REMOVE", { refId: changeTree.refId, refCount });
|
|
3831
3926
|
if (refCount <= 0) {
|
|
3832
3927
|
//
|
|
3833
3928
|
// Only remove "root" reference if it's the last reference
|
|
@@ -3841,7 +3936,19 @@ class Root {
|
|
|
3841
3936
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3842
3937
|
}
|
|
3843
3938
|
this.refCount[changeTree.refId] = 0;
|
|
3844
|
-
changeTree.forEachChild((child, _) =>
|
|
3939
|
+
changeTree.forEachChild((child, _) => {
|
|
3940
|
+
if (child.removeParent(changeTree.ref)) {
|
|
3941
|
+
if ((child.parentChain === undefined || // no parent, remove it
|
|
3942
|
+
(child.parentChain && this.refCount[child.refId] > 1) // parent is still in use, but has more than one reference, remove it
|
|
3943
|
+
)) {
|
|
3944
|
+
this.remove(child, changeTree.ref);
|
|
3945
|
+
}
|
|
3946
|
+
else if (child.parentChain) {
|
|
3947
|
+
// re-assigning a child of the same root, move it to the end
|
|
3948
|
+
this.moveToEndOfChanges(child);
|
|
3949
|
+
}
|
|
3950
|
+
}
|
|
3951
|
+
});
|
|
3845
3952
|
}
|
|
3846
3953
|
else {
|
|
3847
3954
|
this.refCount[changeTree.refId] = refCount;
|
|
@@ -3854,32 +3961,73 @@ class Root {
|
|
|
3854
3961
|
// containing instance is not available, the Decoder will throw
|
|
3855
3962
|
// "refId not found" error.
|
|
3856
3963
|
//
|
|
3857
|
-
|
|
3858
|
-
|
|
3859
|
-
enqueueChangeTree(this, changeTree, "filteredChanges");
|
|
3860
|
-
}
|
|
3861
|
-
else {
|
|
3862
|
-
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3863
|
-
enqueueChangeTree(this, changeTree, "changes");
|
|
3864
|
-
}
|
|
3964
|
+
this.moveToEndOfChanges(changeTree);
|
|
3965
|
+
changeTree.forEachChild((child, _) => this.moveToEndOfChanges(child));
|
|
3865
3966
|
}
|
|
3866
3967
|
return refCount;
|
|
3867
3968
|
}
|
|
3969
|
+
moveToEndOfChanges(changeTree) {
|
|
3970
|
+
if (changeTree.filteredChanges) {
|
|
3971
|
+
this.moveToEndOfChangeTreeList("filteredChanges", changeTree);
|
|
3972
|
+
this.moveToEndOfChangeTreeList("allFilteredChanges", changeTree);
|
|
3973
|
+
}
|
|
3974
|
+
else {
|
|
3975
|
+
this.moveToEndOfChangeTreeList("changes", changeTree);
|
|
3976
|
+
this.moveToEndOfChangeTreeList("allChanges", changeTree);
|
|
3977
|
+
}
|
|
3978
|
+
}
|
|
3979
|
+
moveToEndOfChangeTreeList(changeSetName, changeTree) {
|
|
3980
|
+
const changeSet = this[changeSetName];
|
|
3981
|
+
const node = changeTree[changeSetName].queueRootNode;
|
|
3982
|
+
if (!node || node === changeSet.tail)
|
|
3983
|
+
return;
|
|
3984
|
+
// Remove from current position
|
|
3985
|
+
if (node.prev) {
|
|
3986
|
+
node.prev.next = node.next;
|
|
3987
|
+
}
|
|
3988
|
+
else {
|
|
3989
|
+
changeSet.next = node.next;
|
|
3990
|
+
}
|
|
3991
|
+
if (node.next) {
|
|
3992
|
+
node.next.prev = node.prev;
|
|
3993
|
+
}
|
|
3994
|
+
else {
|
|
3995
|
+
changeSet.tail = node.prev;
|
|
3996
|
+
}
|
|
3997
|
+
// Add to end
|
|
3998
|
+
node.prev = changeSet.tail;
|
|
3999
|
+
node.next = undefined;
|
|
4000
|
+
if (changeSet.tail) {
|
|
4001
|
+
changeSet.tail.next = node;
|
|
4002
|
+
}
|
|
4003
|
+
else {
|
|
4004
|
+
changeSet.next = node;
|
|
4005
|
+
}
|
|
4006
|
+
changeSet.tail = node;
|
|
4007
|
+
}
|
|
3868
4008
|
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3869
4009
|
const changeSet = this[changeSetName];
|
|
3870
|
-
const
|
|
3871
|
-
if (
|
|
3872
|
-
|
|
3873
|
-
|
|
4010
|
+
const node = changeTree[changeSetName].queueRootNode;
|
|
4011
|
+
if (node && node.changeTree === changeTree) {
|
|
4012
|
+
// Remove the node from the linked list
|
|
4013
|
+
if (node.prev) {
|
|
4014
|
+
node.prev.next = node.next;
|
|
4015
|
+
}
|
|
4016
|
+
else {
|
|
4017
|
+
changeSet.next = node.next;
|
|
4018
|
+
}
|
|
4019
|
+
if (node.next) {
|
|
4020
|
+
node.next.prev = node.prev;
|
|
4021
|
+
}
|
|
4022
|
+
else {
|
|
4023
|
+
changeSet.tail = node.prev;
|
|
4024
|
+
}
|
|
4025
|
+
changeSet.length--;
|
|
4026
|
+
// Clear ChangeTree reference
|
|
4027
|
+
changeTree[changeSetName].queueRootNode = undefined;
|
|
3874
4028
|
return true;
|
|
3875
4029
|
}
|
|
3876
|
-
|
|
3877
|
-
// changeTree[changeSetName].queueRootIndex = -1;
|
|
3878
|
-
// return true;
|
|
3879
|
-
// }
|
|
3880
|
-
}
|
|
3881
|
-
clear() {
|
|
3882
|
-
this.changes.length = 0;
|
|
4030
|
+
return false;
|
|
3883
4031
|
}
|
|
3884
4032
|
}
|
|
3885
4033
|
|
|
@@ -3909,12 +4057,9 @@ class Encoder {
|
|
|
3909
4057
|
) {
|
|
3910
4058
|
const hasView = (view !== undefined);
|
|
3911
4059
|
const rootChangeTree = this.state[$changes];
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
const changeTree =
|
|
3915
|
-
if (!changeTree) {
|
|
3916
|
-
continue;
|
|
3917
|
-
}
|
|
4060
|
+
let current = this.root[changeSetName];
|
|
4061
|
+
while (current = current.next) {
|
|
4062
|
+
const changeTree = current.changeTree;
|
|
3918
4063
|
if (hasView) {
|
|
3919
4064
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
3920
4065
|
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
|
|
@@ -4003,7 +4148,9 @@ class Encoder {
|
|
|
4003
4148
|
const rootChangeSet = (typeof (field) === "string")
|
|
4004
4149
|
? this.root[field]
|
|
4005
4150
|
: field;
|
|
4006
|
-
rootChangeSet.
|
|
4151
|
+
let current = rootChangeSet.next;
|
|
4152
|
+
while (current) {
|
|
4153
|
+
const changeTree = current.changeTree;
|
|
4007
4154
|
const changeSet = changeTree[field];
|
|
4008
4155
|
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
4009
4156
|
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
@@ -4015,7 +4162,8 @@ class Encoder {
|
|
|
4015
4162
|
op: OPERATION[op],
|
|
4016
4163
|
});
|
|
4017
4164
|
}
|
|
4018
|
-
|
|
4165
|
+
current = current.next;
|
|
4166
|
+
}
|
|
4019
4167
|
}
|
|
4020
4168
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4021
4169
|
const viewOffset = it.offset;
|
|
@@ -4065,21 +4213,19 @@ class Encoder {
|
|
|
4065
4213
|
}
|
|
4066
4214
|
discardChanges() {
|
|
4067
4215
|
// discard shared changes
|
|
4068
|
-
let
|
|
4069
|
-
|
|
4070
|
-
|
|
4071
|
-
|
|
4072
|
-
}
|
|
4073
|
-
this.root.changes.length = 0;
|
|
4216
|
+
let current = this.root.changes.next;
|
|
4217
|
+
while (current) {
|
|
4218
|
+
current.changeTree.endEncode('changes');
|
|
4219
|
+
current = current.next;
|
|
4074
4220
|
}
|
|
4221
|
+
this.root.changes = createChangeTreeList();
|
|
4075
4222
|
// discard filtered changes
|
|
4076
|
-
|
|
4077
|
-
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
}
|
|
4081
|
-
this.root.filteredChanges.length = 0;
|
|
4223
|
+
current = this.root.filteredChanges.next;
|
|
4224
|
+
while (current) {
|
|
4225
|
+
current.changeTree.endEncode('filteredChanges');
|
|
4226
|
+
current = current.next;
|
|
4082
4227
|
}
|
|
4228
|
+
this.root.filteredChanges = createChangeTreeList();
|
|
4083
4229
|
}
|
|
4084
4230
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
4085
4231
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
@@ -4269,21 +4415,20 @@ class Decoder {
|
|
|
4269
4415
|
//
|
|
4270
4416
|
if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
|
|
4271
4417
|
it.offset++;
|
|
4418
|
+
ref[$onDecodeEnd]?.();
|
|
4272
4419
|
const nextRefId = decode.number(bytes, it);
|
|
4273
4420
|
const nextRef = $root.refs.get(nextRefId);
|
|
4274
4421
|
//
|
|
4275
4422
|
// Trying to access a reference that haven't been decoded yet.
|
|
4276
4423
|
//
|
|
4277
4424
|
if (!nextRef) {
|
|
4278
|
-
|
|
4279
|
-
|
|
4280
|
-
|
|
4281
|
-
|
|
4425
|
+
throw new Error(`"refId" not found: ${nextRefId}`);
|
|
4426
|
+
}
|
|
4427
|
+
else {
|
|
4428
|
+
ref = nextRef;
|
|
4429
|
+
decoder = ref.constructor[$decoder];
|
|
4430
|
+
this.currentRefId = nextRefId;
|
|
4282
4431
|
}
|
|
4283
|
-
ref[$onDecodeEnd]?.();
|
|
4284
|
-
this.currentRefId = nextRefId;
|
|
4285
|
-
ref = nextRef;
|
|
4286
|
-
decoder = ref.constructor[$decoder];
|
|
4287
4432
|
continue;
|
|
4288
4433
|
}
|
|
4289
4434
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -4327,10 +4472,6 @@ class Decoder {
|
|
|
4327
4472
|
return type || defaultType;
|
|
4328
4473
|
}
|
|
4329
4474
|
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
4475
|
return new type();
|
|
4335
4476
|
}
|
|
4336
4477
|
removeChildRefs(ref, allChanges) {
|
|
@@ -4857,11 +4998,12 @@ class StateView {
|
|
|
4857
4998
|
// TODO: allow to set multiple tags at once
|
|
4858
4999
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
4859
5000
|
const changeTree = obj?.[$changes];
|
|
5001
|
+
const parentChangeTree = changeTree.parent;
|
|
4860
5002
|
if (!changeTree) {
|
|
4861
5003
|
console.warn("StateView#add(), invalid object:", obj);
|
|
4862
|
-
return
|
|
5004
|
+
return false;
|
|
4863
5005
|
}
|
|
4864
|
-
else if (!
|
|
5006
|
+
else if (!parentChangeTree &&
|
|
4865
5007
|
changeTree.refId !== 0 // allow root object
|
|
4866
5008
|
) {
|
|
4867
5009
|
/**
|
|
@@ -4883,18 +5025,31 @@ class StateView {
|
|
|
4883
5025
|
// add parent ChangeTree's
|
|
4884
5026
|
// - if it was invisible to this view
|
|
4885
5027
|
// - if it were previously filtered out
|
|
4886
|
-
if (checkIncludeParent &&
|
|
5028
|
+
if (checkIncludeParent && parentChangeTree) {
|
|
4887
5029
|
this.addParentOf(changeTree, tag);
|
|
4888
5030
|
}
|
|
4889
|
-
//
|
|
4890
|
-
// TODO: when adding an item of a MapSchema, the changes may not
|
|
4891
|
-
// be set (only the parent's changes are set)
|
|
4892
|
-
//
|
|
4893
5031
|
let changes = this.changes.get(changeTree.refId);
|
|
4894
5032
|
if (changes === undefined) {
|
|
4895
5033
|
changes = {};
|
|
5034
|
+
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
4896
5035
|
this.changes.set(changeTree.refId, changes);
|
|
4897
5036
|
}
|
|
5037
|
+
let isChildAdded = false;
|
|
5038
|
+
//
|
|
5039
|
+
// Add children of this ChangeTree first.
|
|
5040
|
+
// If successful, we must link the current ChangeTree to the child.
|
|
5041
|
+
//
|
|
5042
|
+
changeTree.forEachChild((change, index) => {
|
|
5043
|
+
// Do not ADD children that don't have the same tag
|
|
5044
|
+
if (metadata &&
|
|
5045
|
+
metadata[index].tag !== undefined &&
|
|
5046
|
+
metadata[index].tag !== tag) {
|
|
5047
|
+
return;
|
|
5048
|
+
}
|
|
5049
|
+
if (this.add(change.ref, tag, false)) {
|
|
5050
|
+
isChildAdded = true;
|
|
5051
|
+
}
|
|
5052
|
+
});
|
|
4898
5053
|
// set tag
|
|
4899
5054
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
4900
5055
|
if (!this.tags) {
|
|
@@ -4916,11 +5071,12 @@ class StateView {
|
|
|
4916
5071
|
}
|
|
4917
5072
|
});
|
|
4918
5073
|
}
|
|
4919
|
-
else {
|
|
4920
|
-
|
|
5074
|
+
else if (!changeTree.isNew || isChildAdded) {
|
|
5075
|
+
// new structures will be added as part of .encode() call, no need to force it to .encodeView()
|
|
4921
5076
|
const changeSet = (changeTree.filteredChanges !== undefined)
|
|
4922
5077
|
? changeTree.allFilteredChanges
|
|
4923
5078
|
: changeTree.allChanges;
|
|
5079
|
+
const isInvisible = this.invisible.has(changeTree);
|
|
4924
5080
|
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4925
5081
|
const index = changeSet.operations[i];
|
|
4926
5082
|
if (index === undefined) {
|
|
@@ -4928,27 +5084,17 @@ class StateView {
|
|
|
4928
5084
|
} // skip "undefined" indexes
|
|
4929
5085
|
const op = changeTree.indexedOperations[index] ?? OPERATION.ADD;
|
|
4930
5086
|
const tagAtIndex = metadata?.[index].tag;
|
|
4931
|
-
if (
|
|
5087
|
+
if (op !== OPERATION.DELETE &&
|
|
4932
5088
|
(isInvisible || // if "invisible", include all
|
|
4933
5089
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4934
5090
|
tagAtIndex === tag // tagged property
|
|
4935
|
-
)
|
|
4936
|
-
op !== OPERATION.DELETE) {
|
|
5091
|
+
)) {
|
|
4937
5092
|
changes[index] = op;
|
|
5093
|
+
isChildAdded = true; // FIXME: assign only once
|
|
4938
5094
|
}
|
|
4939
5095
|
}
|
|
4940
5096
|
}
|
|
4941
|
-
|
|
4942
|
-
changeTree.forEachChild((change, index) => {
|
|
4943
|
-
// Do not ADD children that don't have the same tag
|
|
4944
|
-
if (metadata &&
|
|
4945
|
-
metadata[index].tag !== undefined &&
|
|
4946
|
-
metadata[index].tag !== tag) {
|
|
4947
|
-
return;
|
|
4948
|
-
}
|
|
4949
|
-
this.add(change.ref, tag, false);
|
|
4950
|
-
});
|
|
4951
|
-
return this;
|
|
5097
|
+
return isChildAdded;
|
|
4952
5098
|
}
|
|
4953
5099
|
addParentOf(childChangeTree, tag) {
|
|
4954
5100
|
const changeTree = childChangeTree.parent[$changes];
|