@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/cjs/index.js
CHANGED
|
@@ -28,37 +28,37 @@ exports.OPERATION = void 0;
|
|
|
28
28
|
|
|
29
29
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
30
30
|
|
|
31
|
-
const $track =
|
|
32
|
-
const $encoder =
|
|
33
|
-
const $decoder =
|
|
34
|
-
const $filter =
|
|
35
|
-
const $getByIndex =
|
|
36
|
-
const $deleteByIndex =
|
|
31
|
+
const $track = "~track";
|
|
32
|
+
const $encoder = "~encoder";
|
|
33
|
+
const $decoder = "~decoder";
|
|
34
|
+
const $filter = "~filter";
|
|
35
|
+
const $getByIndex = "~getByIndex";
|
|
36
|
+
const $deleteByIndex = "~deleteByIndex";
|
|
37
37
|
/**
|
|
38
38
|
* Used to hold ChangeTree instances whitin the structures
|
|
39
39
|
*/
|
|
40
|
-
const $changes =
|
|
40
|
+
const $changes = '~changes';
|
|
41
41
|
/**
|
|
42
42
|
* Used to keep track of the type of the child elements of a collection
|
|
43
43
|
* (MapSchema, ArraySchema, etc.)
|
|
44
44
|
*/
|
|
45
|
-
const $childType =
|
|
45
|
+
const $childType = '~childType';
|
|
46
46
|
/**
|
|
47
47
|
* Optional "discard" method for custom types (ArraySchema)
|
|
48
48
|
* (Discards changes for next serialization)
|
|
49
49
|
*/
|
|
50
|
-
const $onEncodeEnd =
|
|
50
|
+
const $onEncodeEnd = '~onEncodeEnd';
|
|
51
51
|
/**
|
|
52
52
|
* When decoding, this method is called after the instance is fully decoded
|
|
53
53
|
*/
|
|
54
|
-
const $onDecodeEnd =
|
|
54
|
+
const $onDecodeEnd = "~onDecodeEnd";
|
|
55
55
|
/**
|
|
56
56
|
* Metadata
|
|
57
57
|
*/
|
|
58
|
-
const $descriptors =
|
|
59
|
-
const $numFields = "
|
|
60
|
-
const $refTypeFieldIndexes = "
|
|
61
|
-
const $viewFieldIndexes = "
|
|
58
|
+
const $descriptors = "~descriptors";
|
|
59
|
+
const $numFields = "~__numFields";
|
|
60
|
+
const $refTypeFieldIndexes = "~__refTypeFieldIndexes";
|
|
61
|
+
const $viewFieldIndexes = "~__viewFieldIndexes";
|
|
62
62
|
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
63
63
|
|
|
64
64
|
/**
|
|
@@ -966,6 +966,24 @@ const Metadata = {
|
|
|
966
966
|
function createChangeSet() {
|
|
967
967
|
return { indexes: {}, operations: [] };
|
|
968
968
|
}
|
|
969
|
+
// Linked list helper functions
|
|
970
|
+
function createChangeTreeList() {
|
|
971
|
+
return { next: undefined, tail: undefined, length: 0 };
|
|
972
|
+
}
|
|
973
|
+
function addToChangeTreeList(list, changeTree) {
|
|
974
|
+
const node = { changeTree, next: undefined, prev: undefined };
|
|
975
|
+
if (!list.next) {
|
|
976
|
+
list.next = node;
|
|
977
|
+
list.tail = node;
|
|
978
|
+
}
|
|
979
|
+
else {
|
|
980
|
+
node.prev = list.tail;
|
|
981
|
+
list.tail.next = node;
|
|
982
|
+
list.tail = node;
|
|
983
|
+
}
|
|
984
|
+
list.length++;
|
|
985
|
+
return node;
|
|
986
|
+
}
|
|
969
987
|
function setOperationAtIndex(changeSet, index) {
|
|
970
988
|
const operationsIndex = changeSet.indexes[index];
|
|
971
989
|
if (operationsIndex === undefined) {
|
|
@@ -990,13 +1008,15 @@ function deleteOperationAtIndex(changeSet, index) {
|
|
|
990
1008
|
changeSet.operations[operationsIndex] = undefined;
|
|
991
1009
|
delete changeSet.indexes[index];
|
|
992
1010
|
}
|
|
993
|
-
function enqueueChangeTree(root, changeTree, changeSet,
|
|
1011
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootNode = changeTree[changeSet].queueRootNode) {
|
|
1012
|
+
// skip
|
|
994
1013
|
if (!root) {
|
|
995
|
-
// skip
|
|
996
1014
|
return;
|
|
997
1015
|
}
|
|
998
|
-
|
|
999
|
-
|
|
1016
|
+
if (queueRootNode) ;
|
|
1017
|
+
else {
|
|
1018
|
+
// Add to linked list if not already present
|
|
1019
|
+
changeTree[changeSet].queueRootNode = addToChangeTreeList(root[changeSet], changeTree);
|
|
1000
1020
|
}
|
|
1001
1021
|
}
|
|
1002
1022
|
class ChangeTree {
|
|
@@ -1020,83 +1040,59 @@ class ChangeTree {
|
|
|
1020
1040
|
*/
|
|
1021
1041
|
this.isNew = true;
|
|
1022
1042
|
this.ref = ref;
|
|
1043
|
+
this.metadata = ref.constructor[Symbol.metadata];
|
|
1023
1044
|
//
|
|
1024
1045
|
// Does this structure have "filters" declared?
|
|
1025
1046
|
//
|
|
1026
|
-
|
|
1027
|
-
if (metadata?.[$viewFieldIndexes]) {
|
|
1047
|
+
if (this.metadata?.[$viewFieldIndexes]) {
|
|
1028
1048
|
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
1029
1049
|
this.filteredChanges = { indexes: {}, operations: [] };
|
|
1030
1050
|
}
|
|
1031
1051
|
}
|
|
1032
1052
|
setRoot(root) {
|
|
1033
1053
|
this.root = root;
|
|
1034
|
-
this.
|
|
1035
|
-
|
|
1036
|
-
// TODO: refactor and possibly unify .setRoot() and .setParent()
|
|
1037
|
-
//
|
|
1054
|
+
const isNewChangeTree = this.root.add(this);
|
|
1055
|
+
this.checkIsFiltered(this.parent, this.parentIndex, isNewChangeTree);
|
|
1038
1056
|
// Recursively set root on child structures
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
const changeTree = this.ref[field.name]?.[$changes];
|
|
1044
|
-
if (changeTree) {
|
|
1045
|
-
if (changeTree.root !== root) {
|
|
1046
|
-
changeTree.setRoot(root);
|
|
1047
|
-
}
|
|
1048
|
-
else {
|
|
1049
|
-
root.add(changeTree); // increment refCount
|
|
1050
|
-
}
|
|
1051
|
-
}
|
|
1052
|
-
});
|
|
1053
|
-
}
|
|
1054
|
-
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
1055
|
-
// MapSchema / ArraySchema, etc.
|
|
1056
|
-
this.ref.forEach((value, key) => {
|
|
1057
|
-
const changeTree = value[$changes];
|
|
1058
|
-
if (changeTree.root !== root) {
|
|
1059
|
-
changeTree.setRoot(root);
|
|
1057
|
+
if (isNewChangeTree) {
|
|
1058
|
+
this.forEachChild((child, _) => {
|
|
1059
|
+
if (child.root !== root) {
|
|
1060
|
+
child.setRoot(root);
|
|
1060
1061
|
}
|
|
1061
1062
|
else {
|
|
1062
|
-
root.add(
|
|
1063
|
+
root.add(child); // increment refCount
|
|
1063
1064
|
}
|
|
1064
1065
|
});
|
|
1065
1066
|
}
|
|
1066
1067
|
}
|
|
1067
1068
|
setParent(parent, root, parentIndex) {
|
|
1068
|
-
this.parent
|
|
1069
|
-
this.parentIndex = parentIndex;
|
|
1069
|
+
this.addParent(parent, parentIndex);
|
|
1070
1070
|
// avoid setting parents with empty `root`
|
|
1071
1071
|
if (!root) {
|
|
1072
1072
|
return;
|
|
1073
1073
|
}
|
|
1074
|
+
const isNewChangeTree = root.add(this);
|
|
1074
1075
|
// skip if parent is already set
|
|
1075
1076
|
if (root !== this.root) {
|
|
1076
1077
|
this.root = root;
|
|
1077
|
-
this.checkIsFiltered(parent, parentIndex);
|
|
1078
|
-
}
|
|
1079
|
-
else {
|
|
1080
|
-
root.add(this);
|
|
1078
|
+
this.checkIsFiltered(parent, parentIndex, isNewChangeTree);
|
|
1081
1079
|
}
|
|
1082
1080
|
// assign same parent on child structures
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
if (
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
const changeTree = value[$changes];
|
|
1097
|
-
if (changeTree.root !== root) {
|
|
1098
|
-
changeTree.setParent(this.ref, root, this.indexes[key] ?? key);
|
|
1081
|
+
if (isNewChangeTree) {
|
|
1082
|
+
//
|
|
1083
|
+
// assign same parent on child structures
|
|
1084
|
+
//
|
|
1085
|
+
this.forEachChild((child, index) => {
|
|
1086
|
+
if (child.root === root) {
|
|
1087
|
+
//
|
|
1088
|
+
// re-assigning a child of the same root, move it to the end
|
|
1089
|
+
// of the changes queue so encoding order is preserved
|
|
1090
|
+
//
|
|
1091
|
+
root.add(child);
|
|
1092
|
+
root.moveToEndOfChanges(child);
|
|
1093
|
+
return;
|
|
1099
1094
|
}
|
|
1095
|
+
child.setParent(this.ref, root, index);
|
|
1100
1096
|
});
|
|
1101
1097
|
}
|
|
1102
1098
|
}
|
|
@@ -1104,21 +1100,23 @@ class ChangeTree {
|
|
|
1104
1100
|
//
|
|
1105
1101
|
// assign same parent on child structures
|
|
1106
1102
|
//
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
const
|
|
1111
|
-
|
|
1112
|
-
if (value) {
|
|
1113
|
-
callback(value[$changes], index);
|
|
1103
|
+
if (this.ref[$childType]) {
|
|
1104
|
+
if (typeof (this.ref[$childType]) !== "string") {
|
|
1105
|
+
// MapSchema / ArraySchema, etc.
|
|
1106
|
+
for (const [key, value] of this.ref.entries()) {
|
|
1107
|
+
callback(value[$changes], key);
|
|
1114
1108
|
}
|
|
1115
|
-
}
|
|
1109
|
+
}
|
|
1116
1110
|
}
|
|
1117
|
-
else
|
|
1118
|
-
|
|
1119
|
-
|
|
1120
|
-
|
|
1121
|
-
|
|
1111
|
+
else {
|
|
1112
|
+
for (const index of this.metadata?.[$refTypeFieldIndexes] ?? []) {
|
|
1113
|
+
const field = this.metadata[index];
|
|
1114
|
+
const value = this.ref[field.name];
|
|
1115
|
+
if (!value) {
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
callback(value[$changes], index);
|
|
1119
|
+
}
|
|
1122
1120
|
}
|
|
1123
1121
|
}
|
|
1124
1122
|
operation(op) {
|
|
@@ -1134,8 +1132,7 @@ class ChangeTree {
|
|
|
1134
1132
|
}
|
|
1135
1133
|
}
|
|
1136
1134
|
change(index, operation = exports.OPERATION.ADD) {
|
|
1137
|
-
const
|
|
1138
|
-
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
1135
|
+
const isFiltered = this.isFiltered || (this.metadata?.[index]?.tag !== undefined);
|
|
1139
1136
|
const changeSet = (isFiltered)
|
|
1140
1137
|
? this.filteredChanges
|
|
1141
1138
|
: this.changes;
|
|
@@ -1225,19 +1222,16 @@ class ChangeTree {
|
|
|
1225
1222
|
}
|
|
1226
1223
|
}
|
|
1227
1224
|
getType(index) {
|
|
1228
|
-
|
|
1229
|
-
|
|
1230
|
-
|
|
1231
|
-
|
|
1232
|
-
|
|
1233
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
1236
|
-
//
|
|
1237
|
-
|
|
1238
|
-
//
|
|
1239
|
-
return this.ref[$childType];
|
|
1240
|
-
}
|
|
1225
|
+
return (
|
|
1226
|
+
//
|
|
1227
|
+
// Get the child type from parent structure.
|
|
1228
|
+
// - ["string"] => "string"
|
|
1229
|
+
// - { map: "string" } => "string"
|
|
1230
|
+
// - { set: "string" } => "string"
|
|
1231
|
+
//
|
|
1232
|
+
this.ref[$childType] || // ArraySchema | MapSchema | SetSchema | CollectionSchema
|
|
1233
|
+
this.metadata[index].type // Schema
|
|
1234
|
+
);
|
|
1241
1235
|
}
|
|
1242
1236
|
getChange(index) {
|
|
1243
1237
|
return this.indexedOperations[index];
|
|
@@ -1297,9 +1291,7 @@ class ChangeTree {
|
|
|
1297
1291
|
endEncode(changeSetName) {
|
|
1298
1292
|
this.indexedOperations = {};
|
|
1299
1293
|
// clear changeset
|
|
1300
|
-
this[changeSetName]
|
|
1301
|
-
this[changeSetName].operations.length = 0;
|
|
1302
|
-
this[changeSetName].queueRootIndex = undefined;
|
|
1294
|
+
this[changeSetName] = createChangeSet();
|
|
1303
1295
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
1304
1296
|
this.ref[$onEncodeEnd]?.();
|
|
1305
1297
|
// Not a new instance anymore
|
|
@@ -1313,20 +1305,14 @@ class ChangeTree {
|
|
|
1313
1305
|
//
|
|
1314
1306
|
this.ref[$onEncodeEnd]?.();
|
|
1315
1307
|
this.indexedOperations = {};
|
|
1316
|
-
this.changes
|
|
1317
|
-
this.changes.operations.length = 0;
|
|
1318
|
-
this.changes.queueRootIndex = undefined;
|
|
1308
|
+
this.changes = createChangeSet();
|
|
1319
1309
|
if (this.filteredChanges !== undefined) {
|
|
1320
|
-
this.filteredChanges
|
|
1321
|
-
this.filteredChanges.operations.length = 0;
|
|
1322
|
-
this.filteredChanges.queueRootIndex = undefined;
|
|
1310
|
+
this.filteredChanges = createChangeSet();
|
|
1323
1311
|
}
|
|
1324
1312
|
if (discardAll) {
|
|
1325
|
-
this.allChanges
|
|
1326
|
-
this.allChanges.operations.length = 0;
|
|
1313
|
+
this.allChanges = createChangeSet();
|
|
1327
1314
|
if (this.allFilteredChanges !== undefined) {
|
|
1328
|
-
this.allFilteredChanges
|
|
1329
|
-
this.allFilteredChanges.operations.length = 0;
|
|
1315
|
+
this.allFilteredChanges = createChangeSet();
|
|
1330
1316
|
}
|
|
1331
1317
|
}
|
|
1332
1318
|
}
|
|
@@ -1344,18 +1330,10 @@ class ChangeTree {
|
|
|
1344
1330
|
}
|
|
1345
1331
|
this.discard();
|
|
1346
1332
|
}
|
|
1347
|
-
ensureRefId() {
|
|
1348
|
-
// skip if refId is already set.
|
|
1349
|
-
if (this.refId !== undefined) {
|
|
1350
|
-
return;
|
|
1351
|
-
}
|
|
1352
|
-
this.refId = this.root.getNextUniqueId();
|
|
1353
|
-
}
|
|
1354
1333
|
get changed() {
|
|
1355
1334
|
return (Object.entries(this.indexedOperations).length > 0);
|
|
1356
1335
|
}
|
|
1357
|
-
checkIsFiltered(parent, parentIndex) {
|
|
1358
|
-
const isNewChangeTree = this.root.add(this);
|
|
1336
|
+
checkIsFiltered(parent, parentIndex, isNewChangeTree) {
|
|
1359
1337
|
if (this.root.types.hasFilters) {
|
|
1360
1338
|
//
|
|
1361
1339
|
// At Schema initialization, the "root" structure might not be available
|
|
@@ -1367,14 +1345,14 @@ class ChangeTree {
|
|
|
1367
1345
|
if (this.filteredChanges !== undefined) {
|
|
1368
1346
|
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1369
1347
|
if (isNewChangeTree) {
|
|
1370
|
-
this.root
|
|
1348
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
1371
1349
|
}
|
|
1372
1350
|
}
|
|
1373
1351
|
}
|
|
1374
1352
|
if (!this.isFiltered) {
|
|
1375
1353
|
enqueueChangeTree(this.root, this, 'changes');
|
|
1376
1354
|
if (isNewChangeTree) {
|
|
1377
|
-
this.root
|
|
1355
|
+
enqueueChangeTree(this.root, this, 'allChanges');
|
|
1378
1356
|
}
|
|
1379
1357
|
}
|
|
1380
1358
|
}
|
|
@@ -1431,6 +1409,90 @@ class ChangeTree {
|
|
|
1431
1409
|
}
|
|
1432
1410
|
}
|
|
1433
1411
|
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Get the immediate parent
|
|
1414
|
+
*/
|
|
1415
|
+
get parent() {
|
|
1416
|
+
return this.parentChain?.ref;
|
|
1417
|
+
}
|
|
1418
|
+
/**
|
|
1419
|
+
* Get the immediate parent index
|
|
1420
|
+
*/
|
|
1421
|
+
get parentIndex() {
|
|
1422
|
+
return this.parentChain?.index;
|
|
1423
|
+
}
|
|
1424
|
+
/**
|
|
1425
|
+
* Add a parent to the chain
|
|
1426
|
+
*/
|
|
1427
|
+
addParent(parent, index) {
|
|
1428
|
+
// Check if this parent already exists in the chain
|
|
1429
|
+
if (this.hasParent((p, i) => p[$changes] === parent[$changes] && i === index)) {
|
|
1430
|
+
return;
|
|
1431
|
+
}
|
|
1432
|
+
this.parentChain = {
|
|
1433
|
+
ref: parent,
|
|
1434
|
+
index,
|
|
1435
|
+
next: this.parentChain
|
|
1436
|
+
};
|
|
1437
|
+
}
|
|
1438
|
+
/**
|
|
1439
|
+
* Remove a parent from the chain
|
|
1440
|
+
* @param parent - The parent to remove
|
|
1441
|
+
* @returns true if parent was removed
|
|
1442
|
+
*/
|
|
1443
|
+
removeParent(parent = this.parent) {
|
|
1444
|
+
let current = this.parentChain;
|
|
1445
|
+
let previous = null;
|
|
1446
|
+
while (current) {
|
|
1447
|
+
//
|
|
1448
|
+
// FIXME: it is required to check against `$changes` here because
|
|
1449
|
+
// ArraySchema is instance of Proxy
|
|
1450
|
+
//
|
|
1451
|
+
if (current.ref[$changes] === parent[$changes]) {
|
|
1452
|
+
if (previous) {
|
|
1453
|
+
previous.next = current.next;
|
|
1454
|
+
}
|
|
1455
|
+
else {
|
|
1456
|
+
this.parentChain = current.next;
|
|
1457
|
+
}
|
|
1458
|
+
return true;
|
|
1459
|
+
}
|
|
1460
|
+
previous = current;
|
|
1461
|
+
current = current.next;
|
|
1462
|
+
}
|
|
1463
|
+
return this.parentChain === undefined;
|
|
1464
|
+
}
|
|
1465
|
+
/**
|
|
1466
|
+
* Find a specific parent in the chain
|
|
1467
|
+
*/
|
|
1468
|
+
findParent(predicate) {
|
|
1469
|
+
let current = this.parentChain;
|
|
1470
|
+
while (current) {
|
|
1471
|
+
if (predicate(current.ref, current.index)) {
|
|
1472
|
+
return current;
|
|
1473
|
+
}
|
|
1474
|
+
current = current.next;
|
|
1475
|
+
}
|
|
1476
|
+
return undefined;
|
|
1477
|
+
}
|
|
1478
|
+
/**
|
|
1479
|
+
* Check if this ChangeTree has a specific parent
|
|
1480
|
+
*/
|
|
1481
|
+
hasParent(predicate) {
|
|
1482
|
+
return this.findParent(predicate) !== undefined;
|
|
1483
|
+
}
|
|
1484
|
+
/**
|
|
1485
|
+
* Get all parents as an array (for debugging/testing)
|
|
1486
|
+
*/
|
|
1487
|
+
getAllParents() {
|
|
1488
|
+
const parents = [];
|
|
1489
|
+
let current = this.parentChain;
|
|
1490
|
+
while (current) {
|
|
1491
|
+
parents.push({ ref: current.ref, index: current.index });
|
|
1492
|
+
current = current.next;
|
|
1493
|
+
}
|
|
1494
|
+
return parents;
|
|
1495
|
+
}
|
|
1434
1496
|
}
|
|
1435
1497
|
|
|
1436
1498
|
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
@@ -1948,7 +2010,7 @@ class ArraySchema {
|
|
|
1948
2010
|
}
|
|
1949
2011
|
if (previousValue !== undefined) {
|
|
1950
2012
|
// remove root reference from previous value
|
|
1951
|
-
previousValue[$changes].root?.remove(previousValue[$changes]);
|
|
2013
|
+
previousValue[$changes].root?.remove(previousValue[$changes], obj);
|
|
1952
2014
|
}
|
|
1953
2015
|
}
|
|
1954
2016
|
else {
|
|
@@ -1979,8 +2041,11 @@ class ArraySchema {
|
|
|
1979
2041
|
return Reflect.has(obj, key);
|
|
1980
2042
|
}
|
|
1981
2043
|
});
|
|
1982
|
-
this
|
|
1983
|
-
|
|
2044
|
+
Object.defineProperty(this, $changes, {
|
|
2045
|
+
value: new ChangeTree(proxy),
|
|
2046
|
+
enumerable: false,
|
|
2047
|
+
writable: true,
|
|
2048
|
+
});
|
|
1984
2049
|
if (items.length > 0) {
|
|
1985
2050
|
this.push(...items);
|
|
1986
2051
|
}
|
|
@@ -2102,7 +2167,7 @@ class ArraySchema {
|
|
|
2102
2167
|
const changeTree = this[$changes];
|
|
2103
2168
|
// remove children references
|
|
2104
2169
|
changeTree.forEachChild((childChangeTree, _) => {
|
|
2105
|
-
changeTree.root?.remove(childChangeTree);
|
|
2170
|
+
changeTree.root?.remove(childChangeTree, this);
|
|
2106
2171
|
});
|
|
2107
2172
|
changeTree.discard(true);
|
|
2108
2173
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -2141,7 +2206,6 @@ class ArraySchema {
|
|
|
2141
2206
|
if (this.items.length === 0) {
|
|
2142
2207
|
return undefined;
|
|
2143
2208
|
}
|
|
2144
|
-
// const index = Number(Object.keys(changeTree.indexes)[0]);
|
|
2145
2209
|
const changeTree = this[$changes];
|
|
2146
2210
|
const index = this.tmpItems.findIndex(item => item === this.items[0]);
|
|
2147
2211
|
const allChangesIndex = this.items.findIndex(item => item === this.items[0]);
|
|
@@ -2600,8 +2664,13 @@ class MapSchema {
|
|
|
2600
2664
|
this.$items = new Map();
|
|
2601
2665
|
this.$indexes = new Map();
|
|
2602
2666
|
this.deletedItems = {};
|
|
2603
|
-
|
|
2604
|
-
|
|
2667
|
+
const changeTree = new ChangeTree(this);
|
|
2668
|
+
changeTree.indexes = {};
|
|
2669
|
+
Object.defineProperty(this, $changes, {
|
|
2670
|
+
value: changeTree,
|
|
2671
|
+
enumerable: false,
|
|
2672
|
+
writable: true,
|
|
2673
|
+
});
|
|
2605
2674
|
if (initialValues) {
|
|
2606
2675
|
if (initialValues instanceof Map ||
|
|
2607
2676
|
initialValues instanceof MapSchema) {
|
|
@@ -2652,7 +2721,7 @@ class MapSchema {
|
|
|
2652
2721
|
operation = exports.OPERATION.DELETE_AND_ADD;
|
|
2653
2722
|
// remove reference from previous value
|
|
2654
2723
|
if (previousValue !== undefined) {
|
|
2655
|
-
previousValue[$changes].root?.remove(previousValue[$changes]);
|
|
2724
|
+
previousValue[$changes].root?.remove(previousValue[$changes], this);
|
|
2656
2725
|
}
|
|
2657
2726
|
}
|
|
2658
2727
|
}
|
|
@@ -2689,7 +2758,7 @@ class MapSchema {
|
|
|
2689
2758
|
changeTree.indexes = {};
|
|
2690
2759
|
// remove children references
|
|
2691
2760
|
changeTree.forEachChild((childChangeTree, _) => {
|
|
2692
|
-
changeTree.root?.remove(childChangeTree);
|
|
2761
|
+
changeTree.root?.remove(childChangeTree, this);
|
|
2693
2762
|
});
|
|
2694
2763
|
// clear previous indexes
|
|
2695
2764
|
this.$indexes.clear();
|
|
@@ -3145,10 +3214,13 @@ function dumpChanges(schema) {
|
|
|
3145
3214
|
refs: []
|
|
3146
3215
|
};
|
|
3147
3216
|
// for (const refId in $root.changes) {
|
|
3148
|
-
$root.changes.
|
|
3217
|
+
let current = $root.changes.next;
|
|
3218
|
+
while (current) {
|
|
3219
|
+
const changeTree = current.changeTree;
|
|
3149
3220
|
// skip if ChangeTree is undefined
|
|
3150
3221
|
if (changeTree === undefined) {
|
|
3151
|
-
|
|
3222
|
+
current = current.next;
|
|
3223
|
+
continue;
|
|
3152
3224
|
}
|
|
3153
3225
|
const changes = changeTree.indexedOperations;
|
|
3154
3226
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
@@ -3160,7 +3232,8 @@ function dumpChanges(schema) {
|
|
|
3160
3232
|
}
|
|
3161
3233
|
dump.ops[exports.OPERATION[op]]++;
|
|
3162
3234
|
}
|
|
3163
|
-
|
|
3235
|
+
current = current.next;
|
|
3236
|
+
}
|
|
3164
3237
|
return dump;
|
|
3165
3238
|
}
|
|
3166
3239
|
|
|
@@ -3310,7 +3383,7 @@ class Schema {
|
|
|
3310
3383
|
* @param showContents display JSON contents of the instance
|
|
3311
3384
|
* @returns
|
|
3312
3385
|
*/
|
|
3313
|
-
static debugRefIds(ref, showContents = false, level = 0, decoder) {
|
|
3386
|
+
static debugRefIds(ref, showContents = false, level = 0, decoder, keyPrefix = "") {
|
|
3314
3387
|
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
3315
3388
|
const changeTree = ref[$changes];
|
|
3316
3389
|
const refId = (decoder) ? decoder.root.refIds.get(ref) : changeTree.refId;
|
|
@@ -3319,11 +3392,25 @@ class Schema {
|
|
|
3319
3392
|
const refCount = (root?.refCount?.[refId] > 1)
|
|
3320
3393
|
? ` [×${root.refCount[refId]}]`
|
|
3321
3394
|
: '';
|
|
3322
|
-
let output = `${getIndent(level)}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3323
|
-
changeTree.forEachChild((childChangeTree) =>
|
|
3395
|
+
let output = `${getIndent(level)}${keyPrefix}${ref.constructor.name} (refId: ${refId})${refCount}${contents}\n`;
|
|
3396
|
+
changeTree.forEachChild((childChangeTree, key) => {
|
|
3397
|
+
const keyPrefix = (ref['forEach'] !== undefined && key !== undefined) ? `["${key}"]: ` : "";
|
|
3398
|
+
output += this.debugRefIds(childChangeTree.ref, showContents, level + 1, decoder, keyPrefix);
|
|
3399
|
+
});
|
|
3324
3400
|
return output;
|
|
3325
3401
|
}
|
|
3326
|
-
static
|
|
3402
|
+
static debugRefIdEncodingOrder(ref, changeSet = 'allChanges') {
|
|
3403
|
+
let encodeOrder = [];
|
|
3404
|
+
let current = ref[$changes].root[changeSet].next;
|
|
3405
|
+
while (current) {
|
|
3406
|
+
if (current.changeTree) {
|
|
3407
|
+
encodeOrder.push(current.changeTree.refId);
|
|
3408
|
+
}
|
|
3409
|
+
current = current.next;
|
|
3410
|
+
}
|
|
3411
|
+
return encodeOrder;
|
|
3412
|
+
}
|
|
3413
|
+
static debugRefIdsFromDecoder(decoder) {
|
|
3327
3414
|
return this.debugRefIds(decoder.state, false, 0, decoder);
|
|
3328
3415
|
}
|
|
3329
3416
|
/**
|
|
@@ -3372,8 +3459,12 @@ class Schema {
|
|
|
3372
3459
|
const changeTrees = new Map();
|
|
3373
3460
|
const instanceRefIds = [];
|
|
3374
3461
|
let totalOperations = 0;
|
|
3462
|
+
// TODO: FIXME: this method is not working as expected
|
|
3375
3463
|
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3376
3464
|
const changeTree = root.changeTrees[refId];
|
|
3465
|
+
if (!changeTree) {
|
|
3466
|
+
continue;
|
|
3467
|
+
}
|
|
3377
3468
|
let includeChangeTree = false;
|
|
3378
3469
|
let parentChangeTrees = [];
|
|
3379
3470
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3509,7 +3600,7 @@ class CollectionSchema {
|
|
|
3509
3600
|
changeTree.indexes = {};
|
|
3510
3601
|
// remove children references
|
|
3511
3602
|
changeTree.forEachChild((childChangeTree, _) => {
|
|
3512
|
-
changeTree.root?.remove(childChangeTree);
|
|
3603
|
+
changeTree.root?.remove(childChangeTree, this);
|
|
3513
3604
|
});
|
|
3514
3605
|
// clear previous indexes
|
|
3515
3606
|
this.$indexes.clear();
|
|
@@ -3796,18 +3887,20 @@ class Root {
|
|
|
3796
3887
|
this.refCount = {};
|
|
3797
3888
|
this.changeTrees = {};
|
|
3798
3889
|
// all changes
|
|
3799
|
-
this.allChanges =
|
|
3800
|
-
this.allFilteredChanges =
|
|
3890
|
+
this.allChanges = createChangeTreeList();
|
|
3891
|
+
this.allFilteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3801
3892
|
// pending changes to be encoded
|
|
3802
|
-
this.changes =
|
|
3803
|
-
this.filteredChanges =
|
|
3893
|
+
this.changes = createChangeTreeList();
|
|
3894
|
+
this.filteredChanges = createChangeTreeList(); // TODO: do not initialize it if filters are not used
|
|
3804
3895
|
}
|
|
3805
3896
|
getNextUniqueId() {
|
|
3806
3897
|
return this.nextUniqueId++;
|
|
3807
3898
|
}
|
|
3808
3899
|
add(changeTree) {
|
|
3809
|
-
//
|
|
3810
|
-
changeTree.
|
|
3900
|
+
// Assign unique `refId` to changeTree if it doesn't have one yet.
|
|
3901
|
+
if (changeTree.refId === undefined) {
|
|
3902
|
+
changeTree.refId = this.getNextUniqueId();
|
|
3903
|
+
}
|
|
3811
3904
|
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3812
3905
|
if (isNewChangeTree) {
|
|
3813
3906
|
this.changeTrees[changeTree.refId] = changeTree;
|
|
@@ -3826,10 +3919,12 @@ class Root {
|
|
|
3826
3919
|
}
|
|
3827
3920
|
}
|
|
3828
3921
|
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3922
|
+
// console.log("ADD", { refId: changeTree.refId, refCount: this.refCount[changeTree.refId] });
|
|
3829
3923
|
return isNewChangeTree;
|
|
3830
3924
|
}
|
|
3831
|
-
remove(changeTree) {
|
|
3925
|
+
remove(changeTree, parent) {
|
|
3832
3926
|
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3927
|
+
// console.log("REMOVE", { refId: changeTree.refId, refCount });
|
|
3833
3928
|
if (refCount <= 0) {
|
|
3834
3929
|
//
|
|
3835
3930
|
// Only remove "root" reference if it's the last reference
|
|
@@ -3843,7 +3938,19 @@ class Root {
|
|
|
3843
3938
|
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3844
3939
|
}
|
|
3845
3940
|
this.refCount[changeTree.refId] = 0;
|
|
3846
|
-
changeTree.forEachChild((child, _) =>
|
|
3941
|
+
changeTree.forEachChild((child, _) => {
|
|
3942
|
+
if (child.removeParent(changeTree.ref)) {
|
|
3943
|
+
if ((child.parentChain === undefined || // no parent, remove it
|
|
3944
|
+
(child.parentChain && this.refCount[child.refId] > 1) // parent is still in use, but has more than one reference, remove it
|
|
3945
|
+
)) {
|
|
3946
|
+
this.remove(child, changeTree.ref);
|
|
3947
|
+
}
|
|
3948
|
+
else if (child.parentChain) {
|
|
3949
|
+
// re-assigning a child of the same root, move it to the end
|
|
3950
|
+
this.moveToEndOfChanges(child);
|
|
3951
|
+
}
|
|
3952
|
+
}
|
|
3953
|
+
});
|
|
3847
3954
|
}
|
|
3848
3955
|
else {
|
|
3849
3956
|
this.refCount[changeTree.refId] = refCount;
|
|
@@ -3856,32 +3963,73 @@ class Root {
|
|
|
3856
3963
|
// containing instance is not available, the Decoder will throw
|
|
3857
3964
|
// "refId not found" error.
|
|
3858
3965
|
//
|
|
3859
|
-
|
|
3860
|
-
|
|
3861
|
-
enqueueChangeTree(this, changeTree, "filteredChanges");
|
|
3862
|
-
}
|
|
3863
|
-
else {
|
|
3864
|
-
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3865
|
-
enqueueChangeTree(this, changeTree, "changes");
|
|
3866
|
-
}
|
|
3966
|
+
this.moveToEndOfChanges(changeTree);
|
|
3967
|
+
changeTree.forEachChild((child, _) => this.moveToEndOfChanges(child));
|
|
3867
3968
|
}
|
|
3868
3969
|
return refCount;
|
|
3869
3970
|
}
|
|
3971
|
+
moveToEndOfChanges(changeTree) {
|
|
3972
|
+
if (changeTree.filteredChanges) {
|
|
3973
|
+
this.moveToEndOfChangeTreeList("filteredChanges", changeTree);
|
|
3974
|
+
this.moveToEndOfChangeTreeList("allFilteredChanges", changeTree);
|
|
3975
|
+
}
|
|
3976
|
+
else {
|
|
3977
|
+
this.moveToEndOfChangeTreeList("changes", changeTree);
|
|
3978
|
+
this.moveToEndOfChangeTreeList("allChanges", changeTree);
|
|
3979
|
+
}
|
|
3980
|
+
}
|
|
3981
|
+
moveToEndOfChangeTreeList(changeSetName, changeTree) {
|
|
3982
|
+
const changeSet = this[changeSetName];
|
|
3983
|
+
const node = changeTree[changeSetName].queueRootNode;
|
|
3984
|
+
if (!node || node === changeSet.tail)
|
|
3985
|
+
return;
|
|
3986
|
+
// Remove from current position
|
|
3987
|
+
if (node.prev) {
|
|
3988
|
+
node.prev.next = node.next;
|
|
3989
|
+
}
|
|
3990
|
+
else {
|
|
3991
|
+
changeSet.next = node.next;
|
|
3992
|
+
}
|
|
3993
|
+
if (node.next) {
|
|
3994
|
+
node.next.prev = node.prev;
|
|
3995
|
+
}
|
|
3996
|
+
else {
|
|
3997
|
+
changeSet.tail = node.prev;
|
|
3998
|
+
}
|
|
3999
|
+
// Add to end
|
|
4000
|
+
node.prev = changeSet.tail;
|
|
4001
|
+
node.next = undefined;
|
|
4002
|
+
if (changeSet.tail) {
|
|
4003
|
+
changeSet.tail.next = node;
|
|
4004
|
+
}
|
|
4005
|
+
else {
|
|
4006
|
+
changeSet.next = node;
|
|
4007
|
+
}
|
|
4008
|
+
changeSet.tail = node;
|
|
4009
|
+
}
|
|
3870
4010
|
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3871
4011
|
const changeSet = this[changeSetName];
|
|
3872
|
-
const
|
|
3873
|
-
if (
|
|
3874
|
-
|
|
3875
|
-
|
|
4012
|
+
const node = changeTree[changeSetName].queueRootNode;
|
|
4013
|
+
if (node && node.changeTree === changeTree) {
|
|
4014
|
+
// Remove the node from the linked list
|
|
4015
|
+
if (node.prev) {
|
|
4016
|
+
node.prev.next = node.next;
|
|
4017
|
+
}
|
|
4018
|
+
else {
|
|
4019
|
+
changeSet.next = node.next;
|
|
4020
|
+
}
|
|
4021
|
+
if (node.next) {
|
|
4022
|
+
node.next.prev = node.prev;
|
|
4023
|
+
}
|
|
4024
|
+
else {
|
|
4025
|
+
changeSet.tail = node.prev;
|
|
4026
|
+
}
|
|
4027
|
+
changeSet.length--;
|
|
4028
|
+
// Clear ChangeTree reference
|
|
4029
|
+
changeTree[changeSetName].queueRootNode = undefined;
|
|
3876
4030
|
return true;
|
|
3877
4031
|
}
|
|
3878
|
-
|
|
3879
|
-
// changeTree[changeSetName].queueRootIndex = -1;
|
|
3880
|
-
// return true;
|
|
3881
|
-
// }
|
|
3882
|
-
}
|
|
3883
|
-
clear() {
|
|
3884
|
-
this.changes.length = 0;
|
|
4032
|
+
return false;
|
|
3885
4033
|
}
|
|
3886
4034
|
}
|
|
3887
4035
|
|
|
@@ -3911,12 +4059,9 @@ class Encoder {
|
|
|
3911
4059
|
) {
|
|
3912
4060
|
const hasView = (view !== undefined);
|
|
3913
4061
|
const rootChangeTree = this.state[$changes];
|
|
3914
|
-
|
|
3915
|
-
|
|
3916
|
-
const changeTree =
|
|
3917
|
-
if (!changeTree) {
|
|
3918
|
-
continue;
|
|
3919
|
-
}
|
|
4062
|
+
let current = this.root[changeSetName];
|
|
4063
|
+
while (current = current.next) {
|
|
4064
|
+
const changeTree = current.changeTree;
|
|
3920
4065
|
if (hasView) {
|
|
3921
4066
|
if (!view.isChangeTreeVisible(changeTree)) {
|
|
3922
4067
|
// console.log("MARK AS INVISIBLE:", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, raw: changeTree.ref.toJSON() });
|
|
@@ -4005,7 +4150,9 @@ class Encoder {
|
|
|
4005
4150
|
const rootChangeSet = (typeof (field) === "string")
|
|
4006
4151
|
? this.root[field]
|
|
4007
4152
|
: field;
|
|
4008
|
-
rootChangeSet.
|
|
4153
|
+
let current = rootChangeSet.next;
|
|
4154
|
+
while (current) {
|
|
4155
|
+
const changeTree = current.changeTree;
|
|
4009
4156
|
const changeSet = changeTree[field];
|
|
4010
4157
|
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
4011
4158
|
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
@@ -4017,7 +4164,8 @@ class Encoder {
|
|
|
4017
4164
|
op: exports.OPERATION[op],
|
|
4018
4165
|
});
|
|
4019
4166
|
}
|
|
4020
|
-
|
|
4167
|
+
current = current.next;
|
|
4168
|
+
}
|
|
4021
4169
|
}
|
|
4022
4170
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
4023
4171
|
const viewOffset = it.offset;
|
|
@@ -4067,21 +4215,19 @@ class Encoder {
|
|
|
4067
4215
|
}
|
|
4068
4216
|
discardChanges() {
|
|
4069
4217
|
// discard shared changes
|
|
4070
|
-
let
|
|
4071
|
-
|
|
4072
|
-
|
|
4073
|
-
|
|
4074
|
-
}
|
|
4075
|
-
this.root.changes.length = 0;
|
|
4218
|
+
let current = this.root.changes.next;
|
|
4219
|
+
while (current) {
|
|
4220
|
+
current.changeTree.endEncode('changes');
|
|
4221
|
+
current = current.next;
|
|
4076
4222
|
}
|
|
4223
|
+
this.root.changes = createChangeTreeList();
|
|
4077
4224
|
// discard filtered changes
|
|
4078
|
-
|
|
4079
|
-
|
|
4080
|
-
|
|
4081
|
-
|
|
4082
|
-
}
|
|
4083
|
-
this.root.filteredChanges.length = 0;
|
|
4225
|
+
current = this.root.filteredChanges.next;
|
|
4226
|
+
while (current) {
|
|
4227
|
+
current.changeTree.endEncode('filteredChanges');
|
|
4228
|
+
current = current.next;
|
|
4084
4229
|
}
|
|
4230
|
+
this.root.filteredChanges = createChangeTreeList();
|
|
4085
4231
|
}
|
|
4086
4232
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
4087
4233
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
@@ -4271,21 +4417,20 @@ class Decoder {
|
|
|
4271
4417
|
//
|
|
4272
4418
|
if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
|
|
4273
4419
|
it.offset++;
|
|
4420
|
+
ref[$onDecodeEnd]?.();
|
|
4274
4421
|
const nextRefId = decode.number(bytes, it);
|
|
4275
4422
|
const nextRef = $root.refs.get(nextRefId);
|
|
4276
4423
|
//
|
|
4277
4424
|
// Trying to access a reference that haven't been decoded yet.
|
|
4278
4425
|
//
|
|
4279
4426
|
if (!nextRef) {
|
|
4280
|
-
|
|
4281
|
-
|
|
4282
|
-
|
|
4283
|
-
|
|
4427
|
+
throw new Error(`"refId" not found: ${nextRefId}`);
|
|
4428
|
+
}
|
|
4429
|
+
else {
|
|
4430
|
+
ref = nextRef;
|
|
4431
|
+
decoder = ref.constructor[$decoder];
|
|
4432
|
+
this.currentRefId = nextRefId;
|
|
4284
4433
|
}
|
|
4285
|
-
ref[$onDecodeEnd]?.();
|
|
4286
|
-
this.currentRefId = nextRefId;
|
|
4287
|
-
ref = nextRef;
|
|
4288
|
-
decoder = ref.constructor[$decoder];
|
|
4289
4434
|
continue;
|
|
4290
4435
|
}
|
|
4291
4436
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -4329,10 +4474,6 @@ class Decoder {
|
|
|
4329
4474
|
return type || defaultType;
|
|
4330
4475
|
}
|
|
4331
4476
|
createInstanceOfType(type) {
|
|
4332
|
-
// let instance: Schema = new (type as any)();
|
|
4333
|
-
// // assign root on $changes
|
|
4334
|
-
// instance[$changes].root = this.root[$changes].root;
|
|
4335
|
-
// return instance;
|
|
4336
4477
|
return new type();
|
|
4337
4478
|
}
|
|
4338
4479
|
removeChildRefs(ref, allChanges) {
|
|
@@ -4859,11 +5000,12 @@ class StateView {
|
|
|
4859
5000
|
// TODO: allow to set multiple tags at once
|
|
4860
5001
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
4861
5002
|
const changeTree = obj?.[$changes];
|
|
5003
|
+
const parentChangeTree = changeTree.parent;
|
|
4862
5004
|
if (!changeTree) {
|
|
4863
5005
|
console.warn("StateView#add(), invalid object:", obj);
|
|
4864
|
-
return
|
|
5006
|
+
return false;
|
|
4865
5007
|
}
|
|
4866
|
-
else if (!
|
|
5008
|
+
else if (!parentChangeTree &&
|
|
4867
5009
|
changeTree.refId !== 0 // allow root object
|
|
4868
5010
|
) {
|
|
4869
5011
|
/**
|
|
@@ -4885,18 +5027,31 @@ class StateView {
|
|
|
4885
5027
|
// add parent ChangeTree's
|
|
4886
5028
|
// - if it was invisible to this view
|
|
4887
5029
|
// - if it were previously filtered out
|
|
4888
|
-
if (checkIncludeParent &&
|
|
5030
|
+
if (checkIncludeParent && parentChangeTree) {
|
|
4889
5031
|
this.addParentOf(changeTree, tag);
|
|
4890
5032
|
}
|
|
4891
|
-
//
|
|
4892
|
-
// TODO: when adding an item of a MapSchema, the changes may not
|
|
4893
|
-
// be set (only the parent's changes are set)
|
|
4894
|
-
//
|
|
4895
5033
|
let changes = this.changes.get(changeTree.refId);
|
|
4896
5034
|
if (changes === undefined) {
|
|
4897
5035
|
changes = {};
|
|
5036
|
+
// FIXME / OPTIMIZE: do not add if no changes are needed
|
|
4898
5037
|
this.changes.set(changeTree.refId, changes);
|
|
4899
5038
|
}
|
|
5039
|
+
let isChildAdded = false;
|
|
5040
|
+
//
|
|
5041
|
+
// Add children of this ChangeTree first.
|
|
5042
|
+
// If successful, we must link the current ChangeTree to the child.
|
|
5043
|
+
//
|
|
5044
|
+
changeTree.forEachChild((change, index) => {
|
|
5045
|
+
// Do not ADD children that don't have the same tag
|
|
5046
|
+
if (metadata &&
|
|
5047
|
+
metadata[index].tag !== undefined &&
|
|
5048
|
+
metadata[index].tag !== tag) {
|
|
5049
|
+
return;
|
|
5050
|
+
}
|
|
5051
|
+
if (this.add(change.ref, tag, false)) {
|
|
5052
|
+
isChildAdded = true;
|
|
5053
|
+
}
|
|
5054
|
+
});
|
|
4900
5055
|
// set tag
|
|
4901
5056
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
4902
5057
|
if (!this.tags) {
|
|
@@ -4918,11 +5073,12 @@ class StateView {
|
|
|
4918
5073
|
}
|
|
4919
5074
|
});
|
|
4920
5075
|
}
|
|
4921
|
-
else {
|
|
4922
|
-
|
|
5076
|
+
else if (!changeTree.isNew || isChildAdded) {
|
|
5077
|
+
// new structures will be added as part of .encode() call, no need to force it to .encodeView()
|
|
4923
5078
|
const changeSet = (changeTree.filteredChanges !== undefined)
|
|
4924
5079
|
? changeTree.allFilteredChanges
|
|
4925
5080
|
: changeTree.allChanges;
|
|
5081
|
+
const isInvisible = this.invisible.has(changeTree);
|
|
4926
5082
|
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4927
5083
|
const index = changeSet.operations[i];
|
|
4928
5084
|
if (index === undefined) {
|
|
@@ -4930,27 +5086,17 @@ class StateView {
|
|
|
4930
5086
|
} // skip "undefined" indexes
|
|
4931
5087
|
const op = changeTree.indexedOperations[index] ?? exports.OPERATION.ADD;
|
|
4932
5088
|
const tagAtIndex = metadata?.[index].tag;
|
|
4933
|
-
if (
|
|
5089
|
+
if (op !== exports.OPERATION.DELETE &&
|
|
4934
5090
|
(isInvisible || // if "invisible", include all
|
|
4935
5091
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4936
5092
|
tagAtIndex === tag // tagged property
|
|
4937
|
-
)
|
|
4938
|
-
op !== exports.OPERATION.DELETE) {
|
|
5093
|
+
)) {
|
|
4939
5094
|
changes[index] = op;
|
|
5095
|
+
isChildAdded = true; // FIXME: assign only once
|
|
4940
5096
|
}
|
|
4941
5097
|
}
|
|
4942
5098
|
}
|
|
4943
|
-
|
|
4944
|
-
changeTree.forEachChild((change, index) => {
|
|
4945
|
-
// Do not ADD children that don't have the same tag
|
|
4946
|
-
if (metadata &&
|
|
4947
|
-
metadata[index].tag !== undefined &&
|
|
4948
|
-
metadata[index].tag !== tag) {
|
|
4949
|
-
return;
|
|
4950
|
-
}
|
|
4951
|
-
this.add(change.ref, tag, false);
|
|
4952
|
-
});
|
|
4953
|
-
return this;
|
|
5099
|
+
return isChildAdded;
|
|
4954
5100
|
}
|
|
4955
5101
|
addParentOf(childChangeTree, tag) {
|
|
4956
5102
|
const changeTree = childChangeTree.parent[$changes];
|