@colyseus/schema 3.0.0-alpha.33 → 3.0.0-alpha.35
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/bin/schema-debug +94 -0
- package/build/cjs/index.js +465 -303
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +465 -303
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +465 -303
- package/lib/Metadata.d.ts +5 -5
- package/lib/Metadata.js +17 -17
- package/lib/Metadata.js.map +1 -1
- package/lib/Schema.js +24 -17
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.js +11 -11
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +12 -5
- package/lib/bench_encode.js.map +1 -1
- package/lib/decoder/Decoder.js +1 -1
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +23 -7
- package/lib/encoder/ChangeTree.js +183 -106
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +2 -1
- package/lib/encoder/EncodeOperation.js +2 -2
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +3 -5
- package/lib/encoder/Encoder.js +93 -61
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +12 -7
- package/lib/encoder/Root.js +41 -20
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.d.ts +5 -5
- package/lib/encoder/StateView.js +29 -23
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/encode.js +12 -9
- package/lib/encoding/encode.js.map +1 -1
- package/lib/types/TypeContext.js +2 -1
- package/lib/types/TypeContext.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +27 -13
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -1
- package/lib/types/custom/MapSchema.js +7 -4
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/symbols.d.ts +8 -6
- package/lib/types/symbols.js +9 -7
- package/lib/types/symbols.js.map +1 -1
- package/lib/utils.js +6 -3
- package/lib/utils.js.map +1 -1
- package/package.json +3 -2
- package/src/Metadata.ts +22 -22
- package/src/Schema.ts +33 -25
- package/src/annotations.ts +12 -12
- package/src/bench_encode.ts +15 -6
- package/src/decoder/Decoder.ts +1 -1
- package/src/encoder/ChangeTree.ts +220 -115
- package/src/encoder/EncodeOperation.ts +5 -1
- package/src/encoder/Encoder.ts +110 -68
- package/src/encoder/Root.ts +41 -21
- package/src/encoder/StateView.ts +32 -28
- package/src/encoding/encode.ts +12 -9
- package/src/types/TypeContext.ts +2 -1
- package/src/types/custom/ArraySchema.ts +39 -17
- package/src/types/custom/MapSchema.ts +12 -5
- package/src/types/symbols.ts +10 -9
- package/src/utils.ts +7 -3
package/build/esm/index.mjs
CHANGED
|
@@ -34,7 +34,6 @@ const $decoder = Symbol("$decoder");
|
|
|
34
34
|
const $filter = Symbol("$filter");
|
|
35
35
|
const $getByIndex = Symbol("$getByIndex");
|
|
36
36
|
const $deleteByIndex = Symbol("$deleteByIndex");
|
|
37
|
-
const $descriptors = Symbol("$descriptors");
|
|
38
37
|
/**
|
|
39
38
|
* Used to hold ChangeTree instances whitin the structures
|
|
40
39
|
*/
|
|
@@ -44,11 +43,6 @@ const $changes = Symbol('$changes');
|
|
|
44
43
|
* (MapSchema, ArraySchema, etc.)
|
|
45
44
|
*/
|
|
46
45
|
const $childType = Symbol('$childType');
|
|
47
|
-
/**
|
|
48
|
-
* Special ChangeTree property to identify new instances
|
|
49
|
-
* (Once they're encoded, they're not new anymore)
|
|
50
|
-
*/
|
|
51
|
-
const $isNew = Symbol("$isNew");
|
|
52
46
|
/**
|
|
53
47
|
* Optional "discard" method for custom types (ArraySchema)
|
|
54
48
|
* (Discards changes for next serialization)
|
|
@@ -58,6 +52,14 @@ const $onEncodeEnd = Symbol('$onEncodeEnd');
|
|
|
58
52
|
* When decoding, this method is called after the instance is fully decoded
|
|
59
53
|
*/
|
|
60
54
|
const $onDecodeEnd = Symbol("$onDecodeEnd");
|
|
55
|
+
/**
|
|
56
|
+
* Metadata
|
|
57
|
+
*/
|
|
58
|
+
const $descriptors = Symbol("$descriptors");
|
|
59
|
+
const $numFields = "$__numFields";
|
|
60
|
+
const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
|
|
61
|
+
const $viewFieldIndexes = "$__viewFieldIndexes";
|
|
62
|
+
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
61
63
|
|
|
62
64
|
const registeredTypes = {};
|
|
63
65
|
const identifiers = new Map();
|
|
@@ -104,7 +106,7 @@ const Metadata = {
|
|
|
104
106
|
};
|
|
105
107
|
}
|
|
106
108
|
// map -1 as last field index
|
|
107
|
-
Object.defineProperty(metadata,
|
|
109
|
+
Object.defineProperty(metadata, $numFields, {
|
|
108
110
|
value: index,
|
|
109
111
|
enumerable: false,
|
|
110
112
|
configurable: true
|
|
@@ -117,14 +119,14 @@ const Metadata = {
|
|
|
117
119
|
});
|
|
118
120
|
// if child Ref/complex type, add to -4
|
|
119
121
|
if (typeof (metadata[index].type) !== "string") {
|
|
120
|
-
if (metadata[
|
|
121
|
-
Object.defineProperty(metadata,
|
|
122
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
123
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
122
124
|
value: [],
|
|
123
125
|
enumerable: false,
|
|
124
126
|
configurable: true,
|
|
125
127
|
});
|
|
126
128
|
}
|
|
127
|
-
metadata[
|
|
129
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
128
130
|
}
|
|
129
131
|
},
|
|
130
132
|
setTag(metadata, fieldName, tag) {
|
|
@@ -132,25 +134,25 @@ const Metadata = {
|
|
|
132
134
|
const field = metadata[index];
|
|
133
135
|
// add 'tag' to the field
|
|
134
136
|
field.tag = tag;
|
|
135
|
-
if (!metadata[
|
|
137
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
136
138
|
// -2: all field indexes with "view" tag
|
|
137
|
-
Object.defineProperty(metadata,
|
|
139
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
138
140
|
value: [],
|
|
139
141
|
enumerable: false,
|
|
140
142
|
configurable: true
|
|
141
143
|
});
|
|
142
144
|
// -3: field indexes by "view" tag
|
|
143
|
-
Object.defineProperty(metadata,
|
|
145
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
144
146
|
value: {},
|
|
145
147
|
enumerable: false,
|
|
146
148
|
configurable: true
|
|
147
149
|
});
|
|
148
150
|
}
|
|
149
|
-
metadata[
|
|
150
|
-
if (!metadata[
|
|
151
|
-
metadata[
|
|
151
|
+
metadata[$viewFieldIndexes].push(index);
|
|
152
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
153
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
152
154
|
}
|
|
153
|
-
metadata[
|
|
155
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
154
156
|
},
|
|
155
157
|
setFields(target, fields) {
|
|
156
158
|
const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
|
|
@@ -175,7 +177,7 @@ const Metadata = {
|
|
|
175
177
|
//
|
|
176
178
|
const metadata = {};
|
|
177
179
|
klass[Symbol.metadata] = metadata;
|
|
178
|
-
Object.defineProperty(metadata,
|
|
180
|
+
Object.defineProperty(metadata, $numFields, {
|
|
179
181
|
value: 0,
|
|
180
182
|
enumerable: false,
|
|
181
183
|
configurable: true,
|
|
@@ -189,7 +191,7 @@ const Metadata = {
|
|
|
189
191
|
if (parentMetadata) {
|
|
190
192
|
// assign parent metadata to current
|
|
191
193
|
Object.assign(metadata, parentMetadata);
|
|
192
|
-
for (let i = 0; i <= parentMetadata[
|
|
194
|
+
for (let i = 0; i <= parentMetadata[$numFields]; i++) {
|
|
193
195
|
const fieldName = parentMetadata[i].name;
|
|
194
196
|
Object.defineProperty(metadata, fieldName, {
|
|
195
197
|
value: parentMetadata[fieldName],
|
|
@@ -197,8 +199,8 @@ const Metadata = {
|
|
|
197
199
|
configurable: true,
|
|
198
200
|
});
|
|
199
201
|
}
|
|
200
|
-
Object.defineProperty(metadata,
|
|
201
|
-
value: parentMetadata[
|
|
202
|
+
Object.defineProperty(metadata, $numFields, {
|
|
203
|
+
value: parentMetadata[$numFields],
|
|
202
204
|
enumerable: false,
|
|
203
205
|
configurable: true,
|
|
204
206
|
writable: true,
|
|
@@ -210,40 +212,69 @@ const Metadata = {
|
|
|
210
212
|
},
|
|
211
213
|
isValidInstance(klass) {
|
|
212
214
|
return (klass.constructor[Symbol.metadata] &&
|
|
213
|
-
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata],
|
|
215
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
214
216
|
},
|
|
215
217
|
getFields(klass) {
|
|
216
218
|
const metadata = klass[Symbol.metadata];
|
|
217
219
|
const fields = {};
|
|
218
|
-
for (let i = 0; i <= metadata[
|
|
220
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
219
221
|
fields[metadata[i].name] = metadata[i].type;
|
|
220
222
|
}
|
|
221
223
|
return fields;
|
|
222
224
|
}
|
|
223
225
|
};
|
|
224
226
|
|
|
225
|
-
|
|
227
|
+
function setOperationAtIndex(changeSet, index) {
|
|
228
|
+
const operationsIndex = changeSet.indexes[index];
|
|
229
|
+
if (operationsIndex === undefined) {
|
|
230
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
231
|
+
}
|
|
232
|
+
else {
|
|
233
|
+
changeSet.operations[operationsIndex] = index;
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
237
|
+
const operationsIndex = changeSet.indexes[index];
|
|
238
|
+
if (operationsIndex !== undefined) {
|
|
239
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
240
|
+
}
|
|
241
|
+
delete changeSet.indexes[index];
|
|
242
|
+
}
|
|
243
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
244
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
245
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
226
248
|
class ChangeTree {
|
|
227
|
-
static { _a$5 = $isNew; }
|
|
228
249
|
constructor(ref) {
|
|
229
250
|
this.isFiltered = false;
|
|
230
251
|
this.isPartiallyFiltered = false;
|
|
231
|
-
this.
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
252
|
+
this.indexedOperations = {};
|
|
253
|
+
//
|
|
254
|
+
// TODO:
|
|
255
|
+
// try storing the index + operation per item.
|
|
256
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
257
|
+
//
|
|
258
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
259
|
+
//
|
|
260
|
+
this.changes = { indexes: {}, operations: [] };
|
|
261
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
262
|
+
/**
|
|
263
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
264
|
+
*/
|
|
265
|
+
this.isNew = true;
|
|
235
266
|
this.ref = ref;
|
|
236
267
|
//
|
|
237
268
|
// Does this structure have "filters" declared?
|
|
238
269
|
//
|
|
239
|
-
if (ref.constructor[Symbol.metadata]?.[
|
|
240
|
-
this.allFilteredChanges =
|
|
241
|
-
this.filteredChanges =
|
|
270
|
+
if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
|
|
271
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
272
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
242
273
|
}
|
|
243
274
|
}
|
|
244
275
|
setRoot(root) {
|
|
245
276
|
this.root = root;
|
|
246
|
-
this.root.add(this);
|
|
277
|
+
const isNewChangeTree = this.root.add(this);
|
|
247
278
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
248
279
|
if (this.root.types.hasFilters) {
|
|
249
280
|
//
|
|
@@ -254,22 +285,24 @@ class ChangeTree {
|
|
|
254
285
|
//
|
|
255
286
|
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
256
287
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
257
|
-
|
|
258
|
-
|
|
288
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
289
|
+
if (isNewChangeTree) {
|
|
290
|
+
this.root.allFilteredChanges.push(this);
|
|
291
|
+
}
|
|
259
292
|
}
|
|
260
293
|
}
|
|
261
294
|
if (!this.isFiltered) {
|
|
262
|
-
|
|
263
|
-
|
|
295
|
+
enqueueChangeTree(root, this, 'changes');
|
|
296
|
+
if (isNewChangeTree) {
|
|
297
|
+
this.root.allChanges.push(this);
|
|
298
|
+
}
|
|
264
299
|
}
|
|
265
|
-
|
|
300
|
+
// Recursively set root on child structures
|
|
266
301
|
if (metadata) {
|
|
267
|
-
metadata[
|
|
302
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
268
303
|
const field = metadata[index];
|
|
269
304
|
const value = this.ref[field.name];
|
|
270
|
-
|
|
271
|
-
value[$changes].setRoot(root);
|
|
272
|
-
}
|
|
305
|
+
value?.[$changes].setRoot(root);
|
|
273
306
|
});
|
|
274
307
|
}
|
|
275
308
|
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
@@ -286,31 +319,36 @@ class ChangeTree {
|
|
|
286
319
|
if (!root) {
|
|
287
320
|
return;
|
|
288
321
|
}
|
|
289
|
-
root.add(this);
|
|
290
322
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
291
323
|
// skip if parent is already set
|
|
292
324
|
if (root !== this.root) {
|
|
293
325
|
this.root = root;
|
|
326
|
+
const isNewChangeTree = root.add(this);
|
|
294
327
|
if (root.types.hasFilters) {
|
|
295
328
|
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
296
329
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
297
|
-
|
|
298
|
-
|
|
330
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
331
|
+
if (isNewChangeTree) {
|
|
332
|
+
this.root.allFilteredChanges.push(this);
|
|
333
|
+
}
|
|
299
334
|
}
|
|
300
335
|
}
|
|
301
336
|
if (!this.isFiltered) {
|
|
302
|
-
|
|
303
|
-
|
|
337
|
+
enqueueChangeTree(root, this, 'changes');
|
|
338
|
+
if (isNewChangeTree) {
|
|
339
|
+
this.root.allChanges.push(this);
|
|
340
|
+
}
|
|
304
341
|
}
|
|
305
|
-
|
|
342
|
+
}
|
|
343
|
+
else {
|
|
344
|
+
root.add(this);
|
|
306
345
|
}
|
|
307
346
|
// assign same parent on child structures
|
|
308
347
|
if (metadata) {
|
|
309
|
-
metadata[
|
|
348
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
310
349
|
const field = metadata[index];
|
|
311
350
|
const value = this.ref[field.name];
|
|
312
351
|
value?.[$changes].setParent(this.ref, root, index);
|
|
313
|
-
// console.log(this.ref.constructor.name, field.name, value);
|
|
314
352
|
// try { throw new Error(); } catch (e) {
|
|
315
353
|
// console.log(e.stack);
|
|
316
354
|
// }
|
|
@@ -329,7 +367,7 @@ class ChangeTree {
|
|
|
329
367
|
//
|
|
330
368
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
331
369
|
if (metadata) {
|
|
332
|
-
metadata[
|
|
370
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
333
371
|
const field = metadata[index];
|
|
334
372
|
const value = this.ref[field.name];
|
|
335
373
|
if (value) {
|
|
@@ -345,8 +383,10 @@ class ChangeTree {
|
|
|
345
383
|
}
|
|
346
384
|
}
|
|
347
385
|
operation(op) {
|
|
348
|
-
|
|
349
|
-
this.
|
|
386
|
+
// operations without index use negative values to represent them
|
|
387
|
+
// this is checked during .encode() time.
|
|
388
|
+
this.changes.operations.push(-op);
|
|
389
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
350
390
|
}
|
|
351
391
|
change(index, operation = OPERATION.ADD) {
|
|
352
392
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
@@ -354,7 +394,7 @@ class ChangeTree {
|
|
|
354
394
|
const changeSet = (isFiltered)
|
|
355
395
|
? this.filteredChanges
|
|
356
396
|
: this.changes;
|
|
357
|
-
const previousOperation =
|
|
397
|
+
const previousOperation = this.indexedOperations[index];
|
|
358
398
|
if (!previousOperation || previousOperation === OPERATION.DELETE) {
|
|
359
399
|
const op = (!previousOperation)
|
|
360
400
|
? operation
|
|
@@ -364,16 +404,19 @@ class ChangeTree {
|
|
|
364
404
|
//
|
|
365
405
|
// TODO: are DELETE operations being encoded as ADD here ??
|
|
366
406
|
//
|
|
367
|
-
|
|
407
|
+
this.indexedOperations[index] = op;
|
|
368
408
|
}
|
|
409
|
+
setOperationAtIndex(changeSet, index);
|
|
369
410
|
if (isFiltered) {
|
|
370
|
-
this.allFilteredChanges
|
|
371
|
-
this.root
|
|
372
|
-
|
|
411
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
412
|
+
if (this.root) {
|
|
413
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
414
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
415
|
+
}
|
|
373
416
|
}
|
|
374
417
|
else {
|
|
375
|
-
this.allChanges
|
|
376
|
-
this.root
|
|
418
|
+
setOperationAtIndex(this.allChanges, index);
|
|
419
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
377
420
|
}
|
|
378
421
|
}
|
|
379
422
|
shiftChangeIndexes(shiftIndex) {
|
|
@@ -385,12 +428,15 @@ class ChangeTree {
|
|
|
385
428
|
const changeSet = (this.isFiltered)
|
|
386
429
|
? this.filteredChanges
|
|
387
430
|
: this.changes;
|
|
388
|
-
const
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
431
|
+
const newIndexedOperations = {};
|
|
432
|
+
const newIndexes = {};
|
|
433
|
+
for (const index in this.indexedOperations) {
|
|
434
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
435
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
393
436
|
}
|
|
437
|
+
this.indexedOperations = newIndexedOperations;
|
|
438
|
+
changeSet.indexes = newIndexes;
|
|
439
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
394
440
|
}
|
|
395
441
|
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
396
442
|
//
|
|
@@ -406,24 +452,36 @@ class ChangeTree {
|
|
|
406
452
|
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
407
453
|
}
|
|
408
454
|
}
|
|
409
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0,
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
455
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
456
|
+
const newIndexes = {};
|
|
457
|
+
for (const key in changeSet.indexes) {
|
|
458
|
+
const index = changeSet.indexes[key];
|
|
459
|
+
if (index > startIndex) {
|
|
460
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
414
461
|
}
|
|
415
|
-
|
|
462
|
+
else {
|
|
463
|
+
newIndexes[key] = index;
|
|
464
|
+
}
|
|
465
|
+
}
|
|
466
|
+
changeSet.indexes = newIndexes;
|
|
467
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
468
|
+
const index = changeSet.operations[i];
|
|
469
|
+
if (index > startIndex) {
|
|
470
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
471
|
+
}
|
|
472
|
+
}
|
|
416
473
|
}
|
|
417
474
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
this.
|
|
421
|
-
this.
|
|
475
|
+
this.indexedOperations[index] = operation;
|
|
476
|
+
if (this.filteredChanges) {
|
|
477
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
478
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
479
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
422
480
|
}
|
|
423
481
|
else {
|
|
424
|
-
this.allChanges
|
|
425
|
-
this.changes
|
|
426
|
-
this.root
|
|
482
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
483
|
+
setOperationAtIndex(this.changes, index);
|
|
484
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
427
485
|
}
|
|
428
486
|
}
|
|
429
487
|
getType(index) {
|
|
@@ -442,8 +500,7 @@ class ChangeTree {
|
|
|
442
500
|
}
|
|
443
501
|
}
|
|
444
502
|
getChange(index) {
|
|
445
|
-
|
|
446
|
-
return this.changes.get(index) ?? this.filteredChanges?.get(index);
|
|
503
|
+
return this.indexedOperations[index];
|
|
447
504
|
}
|
|
448
505
|
//
|
|
449
506
|
// used during `.encode()`
|
|
@@ -467,8 +524,9 @@ class ChangeTree {
|
|
|
467
524
|
const changeSet = (this.filteredChanges)
|
|
468
525
|
? this.filteredChanges
|
|
469
526
|
: this.changes;
|
|
527
|
+
this.indexedOperations[index] = operation ?? OPERATION.DELETE;
|
|
528
|
+
setOperationAtIndex(changeSet, index);
|
|
470
529
|
const previousValue = this.getValue(index);
|
|
471
|
-
changeSet.set(index, operation ?? OPERATION.DELETE);
|
|
472
530
|
// remove `root` reference
|
|
473
531
|
if (previousValue && previousValue[$changes]) {
|
|
474
532
|
//
|
|
@@ -484,23 +542,26 @@ class ChangeTree {
|
|
|
484
542
|
this.root?.remove(previousValue[$changes]);
|
|
485
543
|
}
|
|
486
544
|
//
|
|
487
|
-
// FIXME: this is looking a
|
|
545
|
+
// FIXME: this is looking a ugly and repeated
|
|
488
546
|
//
|
|
489
547
|
if (this.filteredChanges) {
|
|
490
|
-
this.
|
|
491
|
-
this.
|
|
548
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
549
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
492
550
|
}
|
|
493
551
|
else {
|
|
494
|
-
this.
|
|
495
|
-
this.
|
|
552
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
553
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
496
554
|
}
|
|
497
555
|
}
|
|
498
556
|
endEncode() {
|
|
499
|
-
this.
|
|
557
|
+
this.indexedOperations = {};
|
|
558
|
+
// // clear changes
|
|
559
|
+
// this.changes.indexes = {};
|
|
560
|
+
// this.changes.operations.length = 0;
|
|
500
561
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
501
562
|
this.ref[$onEncodeEnd]?.();
|
|
502
563
|
// Not a new instance anymore
|
|
503
|
-
|
|
564
|
+
this.isNew = false;
|
|
504
565
|
}
|
|
505
566
|
discard(discardAll = false) {
|
|
506
567
|
//
|
|
@@ -509,13 +570,22 @@ class ChangeTree {
|
|
|
509
570
|
// REPLACE in case same key is used on next patches.
|
|
510
571
|
//
|
|
511
572
|
this.ref[$onEncodeEnd]?.();
|
|
512
|
-
this.
|
|
513
|
-
this.
|
|
514
|
-
|
|
515
|
-
this.
|
|
573
|
+
this.indexedOperations = {};
|
|
574
|
+
this.changes.indexes = {};
|
|
575
|
+
this.changes.operations.length = 0;
|
|
576
|
+
this.changes.queueRootIndex = undefined;
|
|
577
|
+
if (this.filteredChanges !== undefined) {
|
|
578
|
+
this.filteredChanges.indexes = {};
|
|
579
|
+
this.filteredChanges.operations.length = 0;
|
|
580
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
581
|
+
}
|
|
516
582
|
if (discardAll) {
|
|
517
|
-
this.allChanges.
|
|
518
|
-
this.
|
|
583
|
+
this.allChanges.indexes = {};
|
|
584
|
+
this.allChanges.operations.length = 0;
|
|
585
|
+
if (this.allFilteredChanges !== undefined) {
|
|
586
|
+
this.allFilteredChanges.indexes = {};
|
|
587
|
+
this.allFilteredChanges.operations.length = 0;
|
|
588
|
+
}
|
|
519
589
|
// remove children references
|
|
520
590
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
521
591
|
}
|
|
@@ -524,12 +594,13 @@ class ChangeTree {
|
|
|
524
594
|
* Recursively discard all changes from this, and child structures.
|
|
525
595
|
*/
|
|
526
596
|
discardAll() {
|
|
527
|
-
this.
|
|
528
|
-
|
|
597
|
+
const keys = Object.keys(this.indexedOperations);
|
|
598
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
599
|
+
const value = this.getValue(Number(keys[i]));
|
|
529
600
|
if (value && value[$changes]) {
|
|
530
601
|
value[$changes].discardAll();
|
|
531
602
|
}
|
|
532
|
-
}
|
|
603
|
+
}
|
|
533
604
|
this.discard();
|
|
534
605
|
}
|
|
535
606
|
ensureRefId() {
|
|
@@ -540,42 +611,48 @@ class ChangeTree {
|
|
|
540
611
|
this.refId = this.root.getNextUniqueId();
|
|
541
612
|
}
|
|
542
613
|
get changed() {
|
|
543
|
-
return this.
|
|
614
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
544
615
|
}
|
|
545
616
|
checkIsFiltered(metadata, parent, parentIndex) {
|
|
546
617
|
// Detect if current structure has "filters" declared
|
|
547
|
-
this.isPartiallyFiltered = metadata?.[
|
|
618
|
+
this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
|
|
548
619
|
if (this.isPartiallyFiltered) {
|
|
549
|
-
this.filteredChanges = this.filteredChanges ||
|
|
550
|
-
this.allFilteredChanges = this.allFilteredChanges ||
|
|
620
|
+
this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
|
|
621
|
+
this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
|
|
551
622
|
}
|
|
552
|
-
if
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
623
|
+
// skip if parent is not set
|
|
624
|
+
if (!parent) {
|
|
625
|
+
return;
|
|
626
|
+
}
|
|
627
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
628
|
+
const parentChangeTree = parent[$changes];
|
|
629
|
+
parent = parentChangeTree.parent;
|
|
630
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
631
|
+
}
|
|
632
|
+
const parentMetadata = parent.constructor?.[Symbol.metadata];
|
|
633
|
+
this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
634
|
+
//
|
|
635
|
+
// TODO: refactor this!
|
|
636
|
+
//
|
|
637
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
638
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
639
|
+
//
|
|
640
|
+
if (this.isFiltered) {
|
|
641
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
642
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
643
|
+
if (this.changes.operations.length > 0) {
|
|
644
|
+
// swap changes reference
|
|
645
|
+
const changes = this.changes;
|
|
646
|
+
this.changes = this.filteredChanges;
|
|
647
|
+
this.filteredChanges = changes;
|
|
648
|
+
// swap "all changes" reference
|
|
649
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
650
|
+
this.allFilteredChanges = this.allChanges;
|
|
651
|
+
this.allChanges = allFilteredChanges;
|
|
652
|
+
// console.log("SWAP =>", {
|
|
653
|
+
// "this.allFilteredChanges": this.allFilteredChanges,
|
|
654
|
+
// "this.allChanges": this.allChanges
|
|
655
|
+
// })
|
|
579
656
|
}
|
|
580
657
|
}
|
|
581
658
|
}
|
|
@@ -644,21 +721,24 @@ function utf8Write(view, str, it) {
|
|
|
644
721
|
view[it.offset++] = c;
|
|
645
722
|
}
|
|
646
723
|
else if (c < 0x800) {
|
|
647
|
-
view[it.offset
|
|
648
|
-
view[it.offset
|
|
724
|
+
view[it.offset] = 0xc0 | (c >> 6);
|
|
725
|
+
view[it.offset + 1] = 0x80 | (c & 0x3f);
|
|
726
|
+
it.offset += 2;
|
|
649
727
|
}
|
|
650
728
|
else if (c < 0xd800 || c >= 0xe000) {
|
|
651
|
-
view[it.offset
|
|
652
|
-
view[it.offset
|
|
653
|
-
view[it.offset
|
|
729
|
+
view[it.offset] = 0xe0 | (c >> 12);
|
|
730
|
+
view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
|
|
731
|
+
view[it.offset + 2] = 0x80 | (c & 0x3f);
|
|
732
|
+
it.offset += 3;
|
|
654
733
|
}
|
|
655
734
|
else {
|
|
656
735
|
i++;
|
|
657
736
|
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
658
|
-
view[it.offset
|
|
659
|
-
view[it.offset
|
|
660
|
-
view[it.offset
|
|
661
|
-
view[it.offset
|
|
737
|
+
view[it.offset] = 0xf0 | (c >> 18);
|
|
738
|
+
view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
|
|
739
|
+
view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
|
|
740
|
+
view[it.offset + 3] = 0x80 | (c & 0x3f);
|
|
741
|
+
it.offset += 4;
|
|
662
742
|
}
|
|
663
743
|
}
|
|
664
744
|
}
|
|
@@ -887,7 +967,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
887
967
|
* Used for Schema instances.
|
|
888
968
|
* @private
|
|
889
969
|
*/
|
|
890
|
-
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
970
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
891
971
|
// "compress" field index + operation
|
|
892
972
|
bytes[it.offset++] = (index | operation) & 255;
|
|
893
973
|
// Do not encode value for DELETE operations
|
|
@@ -895,7 +975,7 @@ const encodeSchemaOperation = function (encoder, bytes, changeTree, index, opera
|
|
|
895
975
|
return;
|
|
896
976
|
}
|
|
897
977
|
const ref = changeTree.ref;
|
|
898
|
-
const metadata = ref.constructor[Symbol.metadata];
|
|
978
|
+
// const metadata: Metadata = ref.constructor[Symbol.metadata];
|
|
899
979
|
const field = metadata[index];
|
|
900
980
|
// TODO: inline this function call small performance gain
|
|
901
981
|
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
@@ -1590,6 +1670,7 @@ class ArraySchema {
|
|
|
1590
1670
|
const proxy = new Proxy(this, {
|
|
1591
1671
|
get: (obj, prop) => {
|
|
1592
1672
|
if (typeof (prop) !== "symbol" &&
|
|
1673
|
+
// FIXME: d8 accuses this as low performance
|
|
1593
1674
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1594
1675
|
) {
|
|
1595
1676
|
return this.items[prop];
|
|
@@ -1607,7 +1688,7 @@ class ArraySchema {
|
|
|
1607
1688
|
if (setValue[$changes]) {
|
|
1608
1689
|
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1609
1690
|
if (obj.items[key] !== undefined) {
|
|
1610
|
-
if (setValue[$changes]
|
|
1691
|
+
if (setValue[$changes].isNew) {
|
|
1611
1692
|
this[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
|
|
1612
1693
|
}
|
|
1613
1694
|
else {
|
|
@@ -1619,7 +1700,7 @@ class ArraySchema {
|
|
|
1619
1700
|
}
|
|
1620
1701
|
}
|
|
1621
1702
|
}
|
|
1622
|
-
else if (setValue[$changes]
|
|
1703
|
+
else if (setValue[$changes].isNew) {
|
|
1623
1704
|
this[$changes].indexedOperation(Number(key), OPERATION.ADD);
|
|
1624
1705
|
}
|
|
1625
1706
|
}
|
|
@@ -1653,7 +1734,9 @@ class ArraySchema {
|
|
|
1653
1734
|
});
|
|
1654
1735
|
this[$changes] = new ChangeTree(proxy);
|
|
1655
1736
|
this[$changes].indexes = {};
|
|
1656
|
-
|
|
1737
|
+
if (items.length > 0) {
|
|
1738
|
+
this.push(...items);
|
|
1739
|
+
}
|
|
1657
1740
|
return proxy;
|
|
1658
1741
|
}
|
|
1659
1742
|
set length(newLength) {
|
|
@@ -1672,15 +1755,18 @@ class ArraySchema {
|
|
|
1672
1755
|
}
|
|
1673
1756
|
push(...values) {
|
|
1674
1757
|
let length = this.tmpItems.length;
|
|
1675
|
-
|
|
1676
|
-
|
|
1758
|
+
const changeTree = this[$changes];
|
|
1759
|
+
// values.forEach((value, i) => {
|
|
1760
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1761
|
+
const value = values[i];
|
|
1677
1762
|
if (value === undefined || value === null) {
|
|
1763
|
+
// skip null values
|
|
1678
1764
|
return;
|
|
1679
1765
|
}
|
|
1680
1766
|
else if (typeof (value) === "object" && this[$childType]) {
|
|
1681
1767
|
assertInstanceType(value, this[$childType], this, i);
|
|
1768
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1682
1769
|
}
|
|
1683
|
-
const changeTree = this[$changes];
|
|
1684
1770
|
changeTree.indexedOperation(length, OPERATION.ADD, this.items.length);
|
|
1685
1771
|
this.items.push(value);
|
|
1686
1772
|
this.tmpItems.push(value);
|
|
@@ -1689,8 +1775,9 @@ class ArraySchema {
|
|
|
1689
1775
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1690
1776
|
//
|
|
1691
1777
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1692
|
-
|
|
1693
|
-
|
|
1778
|
+
}
|
|
1779
|
+
// length++;
|
|
1780
|
+
// });
|
|
1694
1781
|
return length;
|
|
1695
1782
|
}
|
|
1696
1783
|
/**
|
|
@@ -1711,6 +1798,7 @@ class ArraySchema {
|
|
|
1711
1798
|
}
|
|
1712
1799
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1713
1800
|
// this.tmpItems[index] = undefined;
|
|
1801
|
+
// this.tmpItems.pop();
|
|
1714
1802
|
this.deletedIndexes[index] = true;
|
|
1715
1803
|
return this.items.pop();
|
|
1716
1804
|
}
|
|
@@ -1775,9 +1863,12 @@ class ArraySchema {
|
|
|
1775
1863
|
//
|
|
1776
1864
|
// TODO: do not use [$changes] at decoding time.
|
|
1777
1865
|
//
|
|
1778
|
-
changeTree.root
|
|
1779
|
-
|
|
1780
|
-
|
|
1866
|
+
const root = changeTree.root;
|
|
1867
|
+
if (root !== undefined) {
|
|
1868
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
1869
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
1870
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
1871
|
+
}
|
|
1781
1872
|
});
|
|
1782
1873
|
changeTree.discard(true);
|
|
1783
1874
|
changeTree.operation(OPERATION.CLEAR);
|
|
@@ -1821,6 +1912,7 @@ class ArraySchema {
|
|
|
1821
1912
|
const changeTree = this[$changes];
|
|
1822
1913
|
changeTree.delete(index);
|
|
1823
1914
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
1915
|
+
// this.deletedIndexes[index] = true;
|
|
1824
1916
|
return this.items.shift();
|
|
1825
1917
|
}
|
|
1826
1918
|
/**
|
|
@@ -1901,10 +1993,12 @@ class ArraySchema {
|
|
|
1901
1993
|
changeTree.shiftChangeIndexes(items.length);
|
|
1902
1994
|
// new index
|
|
1903
1995
|
if (changeTree.isFiltered) {
|
|
1904
|
-
changeTree.filteredChanges
|
|
1996
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
1997
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1905
1998
|
}
|
|
1906
1999
|
else {
|
|
1907
|
-
changeTree.allChanges
|
|
2000
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2001
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1908
2002
|
}
|
|
1909
2003
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1910
2004
|
items.forEach((_, index) => {
|
|
@@ -2245,7 +2339,7 @@ class MapSchema {
|
|
|
2245
2339
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2246
2340
|
const index = (isReplace)
|
|
2247
2341
|
? changeTree.indexes[key]
|
|
2248
|
-
: changeTree.indexes[
|
|
2342
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2249
2343
|
let operation = (isReplace)
|
|
2250
2344
|
? OPERATION.REPLACE
|
|
2251
2345
|
: OPERATION.ADD;
|
|
@@ -2257,7 +2351,7 @@ class MapSchema {
|
|
|
2257
2351
|
if (!isReplace) {
|
|
2258
2352
|
this.$indexes.set(index, key);
|
|
2259
2353
|
changeTree.indexes[key] = index;
|
|
2260
|
-
changeTree.indexes[
|
|
2354
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2261
2355
|
}
|
|
2262
2356
|
else if (!isRef &&
|
|
2263
2357
|
this.$items.get(key) === value) {
|
|
@@ -2332,8 +2426,11 @@ class MapSchema {
|
|
|
2332
2426
|
}
|
|
2333
2427
|
[$onEncodeEnd]() {
|
|
2334
2428
|
const changeTree = this[$changes];
|
|
2335
|
-
const
|
|
2336
|
-
for (
|
|
2429
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2430
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2431
|
+
const key = keys[i];
|
|
2432
|
+
const fieldIndex = Number(key);
|
|
2433
|
+
const operation = changeTree.indexedOperations[key];
|
|
2337
2434
|
if (operation === OPERATION.DELETE) {
|
|
2338
2435
|
const index = this[$getByIndex](fieldIndex);
|
|
2339
2436
|
delete changeTree.indexes[index];
|
|
@@ -2436,7 +2533,7 @@ class TypeContext {
|
|
|
2436
2533
|
});
|
|
2437
2534
|
const metadata = (klass[Symbol.metadata] ??= {});
|
|
2438
2535
|
// if any schema/field has filters, mark "context" as having filters.
|
|
2439
|
-
if (metadata[
|
|
2536
|
+
if (metadata[$viewFieldIndexes]) {
|
|
2440
2537
|
this.hasFilters = true;
|
|
2441
2538
|
}
|
|
2442
2539
|
if (parentFieldViewTag !== undefined) {
|
|
@@ -2499,8 +2596,8 @@ const DEFAULT_VIEW_TAG = -1;
|
|
|
2499
2596
|
// // detect index for this field, considering inheritance
|
|
2500
2597
|
// //
|
|
2501
2598
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2502
|
-
// let fieldIndex: number = context.metadata[
|
|
2503
|
-
// ?? (parent && parent[
|
|
2599
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2600
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2504
2601
|
// ?? -1; // no fields defined
|
|
2505
2602
|
// fieldIndex++;
|
|
2506
2603
|
// if (
|
|
@@ -2629,8 +2726,8 @@ function view(tag = DEFAULT_VIEW_TAG) {
|
|
|
2629
2726
|
// //
|
|
2630
2727
|
// metadata[fieldIndex] = {
|
|
2631
2728
|
// type: undefined,
|
|
2632
|
-
// index: (metadata[
|
|
2633
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2729
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2730
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2634
2731
|
// ?? -1) + 1 // no fields defined
|
|
2635
2732
|
// }
|
|
2636
2733
|
// }
|
|
@@ -2673,8 +2770,8 @@ function type(type, options) {
|
|
|
2673
2770
|
//
|
|
2674
2771
|
// detect index for this field, considering inheritance
|
|
2675
2772
|
//
|
|
2676
|
-
fieldIndex = metadata[
|
|
2677
|
-
?? (parentMetadata && parentMetadata[
|
|
2773
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2774
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2678
2775
|
?? -1; // no fields defined
|
|
2679
2776
|
fieldIndex++;
|
|
2680
2777
|
}
|
|
@@ -2701,7 +2798,7 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass)
|
|
|
2701
2798
|
return {
|
|
2702
2799
|
get: function () { return this[fieldCached]; },
|
|
2703
2800
|
set: function (value) {
|
|
2704
|
-
const previousValue = this[fieldCached]
|
|
2801
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2705
2802
|
// skip if value is the same as cached.
|
|
2706
2803
|
if (value === previousValue) {
|
|
2707
2804
|
return;
|
|
@@ -2773,8 +2870,8 @@ function deprecated(throws = true) {
|
|
|
2773
2870
|
// //
|
|
2774
2871
|
// metadata[field] = {
|
|
2775
2872
|
// type: undefined,
|
|
2776
|
-
// index: (metadata[
|
|
2777
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2873
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2874
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2778
2875
|
// ?? -1) + 1 // no fields defined
|
|
2779
2876
|
// }
|
|
2780
2877
|
// }
|
|
@@ -2812,15 +2909,18 @@ function dumpChanges(schema) {
|
|
|
2812
2909
|
ops: {},
|
|
2813
2910
|
refs: []
|
|
2814
2911
|
};
|
|
2815
|
-
$root.changes
|
|
2912
|
+
// for (const refId in $root.changes) {
|
|
2913
|
+
$root.changes.forEach(changeTree => {
|
|
2914
|
+
const changes = changeTree.indexedOperations;
|
|
2816
2915
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2817
|
-
|
|
2916
|
+
for (const index in changes) {
|
|
2917
|
+
const op = changes[index];
|
|
2818
2918
|
const opName = OPERATION[op];
|
|
2819
2919
|
if (!dump.ops[opName]) {
|
|
2820
2920
|
dump.ops[opName] = 0;
|
|
2821
2921
|
}
|
|
2822
2922
|
dump.ops[OPERATION[op]]++;
|
|
2823
|
-
}
|
|
2923
|
+
}
|
|
2824
2924
|
});
|
|
2825
2925
|
return dump;
|
|
2826
2926
|
}
|
|
@@ -2846,6 +2946,7 @@ var _a$2, _b$2;
|
|
|
2846
2946
|
class Schema {
|
|
2847
2947
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2848
2948
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
2949
|
+
// public [$changes]: ChangeTree;
|
|
2849
2950
|
/**
|
|
2850
2951
|
* Assign the property descriptors required to track changes on this instance.
|
|
2851
2952
|
* @param instance
|
|
@@ -2905,12 +3006,7 @@ class Schema {
|
|
|
2905
3006
|
// inline
|
|
2906
3007
|
// Schema.initialize(this);
|
|
2907
3008
|
//
|
|
2908
|
-
|
|
2909
|
-
value: new ChangeTree(this),
|
|
2910
|
-
enumerable: false,
|
|
2911
|
-
writable: true
|
|
2912
|
-
});
|
|
2913
|
-
Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
3009
|
+
Schema.initialize(this);
|
|
2914
3010
|
//
|
|
2915
3011
|
// Assign initial values
|
|
2916
3012
|
//
|
|
@@ -2984,7 +3080,7 @@ class Schema {
|
|
|
2984
3080
|
const changeTree = ref[$changes];
|
|
2985
3081
|
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2986
3082
|
let output = "";
|
|
2987
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
3083
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
2988
3084
|
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
2989
3085
|
return output;
|
|
2990
3086
|
}
|
|
@@ -3002,18 +3098,26 @@ class Schema {
|
|
|
3002
3098
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3003
3099
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3004
3100
|
function dumpChangeSet(changeSet) {
|
|
3005
|
-
|
|
3006
|
-
.
|
|
3007
|
-
.forEach((
|
|
3101
|
+
changeSet.operations
|
|
3102
|
+
.filter(op => op)
|
|
3103
|
+
.forEach((index) => {
|
|
3104
|
+
const operation = changeTree.indexedOperations[index];
|
|
3105
|
+
console.log({ index, operation });
|
|
3106
|
+
output += `- [${index}]: ${OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3107
|
+
});
|
|
3008
3108
|
}
|
|
3009
3109
|
dumpChangeSet(changeSet);
|
|
3010
3110
|
// display filtered changes
|
|
3011
|
-
if (!isEncodeAll &&
|
|
3111
|
+
if (!isEncodeAll &&
|
|
3112
|
+
changeTree.filteredChanges &&
|
|
3113
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3012
3114
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3013
3115
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3014
3116
|
}
|
|
3015
3117
|
// display filtered changes
|
|
3016
|
-
if (isEncodeAll &&
|
|
3118
|
+
if (isEncodeAll &&
|
|
3119
|
+
changeTree.allFilteredChanges &&
|
|
3120
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3017
3121
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
3018
3122
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3019
3123
|
}
|
|
@@ -3022,10 +3126,12 @@ class Schema {
|
|
|
3022
3126
|
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
3023
3127
|
let output = "";
|
|
3024
3128
|
const rootChangeTree = ref[$changes];
|
|
3129
|
+
const root = rootChangeTree.root;
|
|
3025
3130
|
const changeTrees = new Map();
|
|
3026
3131
|
let totalInstances = 0;
|
|
3027
3132
|
let totalOperations = 0;
|
|
3028
|
-
for (const [
|
|
3133
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3134
|
+
const changeTree = root.changeTrees[refId];
|
|
3029
3135
|
let includeChangeTree = false;
|
|
3030
3136
|
let parentChangeTrees = [];
|
|
3031
3137
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3044,7 +3150,7 @@ class Schema {
|
|
|
3044
3150
|
}
|
|
3045
3151
|
if (includeChangeTree) {
|
|
3046
3152
|
totalInstances += 1;
|
|
3047
|
-
totalOperations += changes.
|
|
3153
|
+
totalOperations += Object.keys(changes).length;
|
|
3048
3154
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3049
3155
|
}
|
|
3050
3156
|
}
|
|
@@ -3062,12 +3168,13 @@ class Schema {
|
|
|
3062
3168
|
visitedParents.add(parentChangeTree);
|
|
3063
3169
|
}
|
|
3064
3170
|
});
|
|
3065
|
-
const changes = changeTree.
|
|
3171
|
+
const changes = changeTree.indexedOperations;
|
|
3066
3172
|
const level = parentChangeTrees.length;
|
|
3067
3173
|
const indent = getIndent(level);
|
|
3068
3174
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3069
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3070
|
-
for (const
|
|
3175
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3176
|
+
for (const index in changes) {
|
|
3177
|
+
const operation = changes[index];
|
|
3071
3178
|
output += `${getIndent(level + 1)}${OPERATION[operation]}: ${index}\n`;
|
|
3072
3179
|
}
|
|
3073
3180
|
}
|
|
@@ -3428,59 +3535,90 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
3428
3535
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3429
3536
|
};
|
|
3430
3537
|
|
|
3538
|
+
function spliceOne(arr, index) {
|
|
3539
|
+
// manually splice an array
|
|
3540
|
+
if (index === -1 || index >= arr.length) {
|
|
3541
|
+
return false;
|
|
3542
|
+
}
|
|
3543
|
+
const len = arr.length - 1;
|
|
3544
|
+
for (let i = index; i < len; i++) {
|
|
3545
|
+
arr[i] = arr[i + 1];
|
|
3546
|
+
}
|
|
3547
|
+
arr.length = len;
|
|
3548
|
+
return true;
|
|
3549
|
+
}
|
|
3550
|
+
|
|
3431
3551
|
class Root {
|
|
3432
3552
|
constructor(types) {
|
|
3433
3553
|
this.types = types;
|
|
3434
3554
|
this.nextUniqueId = 0;
|
|
3435
|
-
this.refCount =
|
|
3555
|
+
this.refCount = {};
|
|
3556
|
+
this.changeTrees = {};
|
|
3436
3557
|
// all changes
|
|
3437
|
-
this.allChanges =
|
|
3438
|
-
this.allFilteredChanges =
|
|
3558
|
+
this.allChanges = [];
|
|
3559
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3439
3560
|
// pending changes to be encoded
|
|
3440
|
-
this.changes =
|
|
3441
|
-
this.filteredChanges =
|
|
3561
|
+
this.changes = [];
|
|
3562
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3442
3563
|
}
|
|
3443
3564
|
getNextUniqueId() {
|
|
3444
3565
|
return this.nextUniqueId++;
|
|
3445
3566
|
}
|
|
3446
3567
|
add(changeTree) {
|
|
3447
|
-
|
|
3568
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3569
|
+
changeTree.ensureRefId();
|
|
3570
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3571
|
+
if (isNewChangeTree) {
|
|
3572
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3573
|
+
}
|
|
3574
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3448
3575
|
if (previousRefCount === 0) {
|
|
3449
3576
|
//
|
|
3450
3577
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3451
3578
|
// We need to re-add all changes to the `changes` map.
|
|
3452
3579
|
//
|
|
3453
|
-
changeTree.allChanges.
|
|
3454
|
-
|
|
3455
|
-
|
|
3580
|
+
const ops = changeTree.allChanges.operations;
|
|
3581
|
+
let len = ops.length;
|
|
3582
|
+
while (len--) {
|
|
3583
|
+
changeTree.indexedOperations[ops[len]] = OPERATION.ADD;
|
|
3584
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3585
|
+
}
|
|
3456
3586
|
}
|
|
3457
|
-
|
|
3458
|
-
|
|
3459
|
-
return refCount;
|
|
3587
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3588
|
+
return isNewChangeTree;
|
|
3460
3589
|
}
|
|
3461
3590
|
remove(changeTree) {
|
|
3462
|
-
const refCount = (this.refCount.
|
|
3591
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3463
3592
|
if (refCount <= 0) {
|
|
3464
3593
|
//
|
|
3465
3594
|
// Only remove "root" reference if it's the last reference
|
|
3466
3595
|
//
|
|
3467
3596
|
changeTree.root = undefined;
|
|
3468
|
-
this.
|
|
3469
|
-
this.
|
|
3597
|
+
delete this.changeTrees[changeTree.refId];
|
|
3598
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3599
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3470
3600
|
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3471
|
-
this.allFilteredChanges
|
|
3472
|
-
this.filteredChanges
|
|
3601
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3602
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3473
3603
|
}
|
|
3474
|
-
this.refCount.
|
|
3604
|
+
this.refCount[changeTree.refId] = 0;
|
|
3475
3605
|
}
|
|
3476
3606
|
else {
|
|
3477
|
-
this.refCount.
|
|
3607
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3478
3608
|
}
|
|
3479
3609
|
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3480
3610
|
return refCount;
|
|
3481
3611
|
}
|
|
3612
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3613
|
+
const changeSet = this[changeSetName];
|
|
3614
|
+
const index = changeSet.indexOf(changeTree);
|
|
3615
|
+
if (index !== -1) {
|
|
3616
|
+
spliceOne(changeSet, index);
|
|
3617
|
+
// changeSet[index] = undefined;
|
|
3618
|
+
}
|
|
3619
|
+
}
|
|
3482
3620
|
clear() {
|
|
3483
|
-
this.changes.
|
|
3621
|
+
this.changes.length = 0;
|
|
3484
3622
|
}
|
|
3485
3623
|
}
|
|
3486
3624
|
|
|
@@ -3504,20 +3642,26 @@ class Encoder {
|
|
|
3504
3642
|
this.state = state;
|
|
3505
3643
|
this.state[$changes].setRoot(this.root);
|
|
3506
3644
|
}
|
|
3507
|
-
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer,
|
|
3645
|
+
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeSetName = "changes", isEncodeAll = changeSetName === "allChanges", initialOffset = it.offset // cache current offset in case we need to resize the buffer
|
|
3508
3646
|
) {
|
|
3509
3647
|
const hasView = (view !== undefined);
|
|
3510
3648
|
const rootChangeTree = this.state[$changes];
|
|
3511
|
-
const
|
|
3512
|
-
|
|
3649
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3650
|
+
const changeTrees = this.root[changeSetName];
|
|
3651
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3652
|
+
const changeTree = changeTrees[i];
|
|
3653
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
3654
|
+
// if (changeTree === undefined) { continue; }
|
|
3655
|
+
const operations = changeTree[changeSetName];
|
|
3513
3656
|
const ref = changeTree.ref;
|
|
3514
3657
|
const ctor = ref.constructor;
|
|
3515
3658
|
const encoder = ctor[$encoder];
|
|
3516
3659
|
const filter = ctor[$filter];
|
|
3660
|
+
const metadata = ctor[Symbol.metadata];
|
|
3517
3661
|
// try { throw new Error(); } catch (e) {
|
|
3518
3662
|
// // only print if not coming from Reflection.ts
|
|
3519
3663
|
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3520
|
-
// console.log("ChangeTree:", { ref: ref.constructor.name
|
|
3664
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
3521
3665
|
// }
|
|
3522
3666
|
// }
|
|
3523
3667
|
if (hasView) {
|
|
@@ -3535,7 +3679,13 @@ class Encoder {
|
|
|
3535
3679
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3536
3680
|
number$1(buffer, changeTree.refId, it);
|
|
3537
3681
|
}
|
|
3538
|
-
for (
|
|
3682
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3683
|
+
const fieldIndex = operations.operations[j];
|
|
3684
|
+
const operation = (fieldIndex < 0)
|
|
3685
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3686
|
+
: (isEncodeAll)
|
|
3687
|
+
? OPERATION.ADD
|
|
3688
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3539
3689
|
//
|
|
3540
3690
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3541
3691
|
// they will be encoded per client, based on their view.
|
|
@@ -3543,7 +3693,7 @@ class Encoder {
|
|
|
3543
3693
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3544
3694
|
// TODO: avoid checking if no view tags were defined
|
|
3545
3695
|
//
|
|
3546
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3696
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3547
3697
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3548
3698
|
// view?.invisible.add(changeTree);
|
|
3549
3699
|
continue;
|
|
@@ -3558,16 +3708,14 @@ class Encoder {
|
|
|
3558
3708
|
// });
|
|
3559
3709
|
// }
|
|
3560
3710
|
// }
|
|
3561
|
-
|
|
3711
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
3712
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3713
|
+
}
|
|
3714
|
+
if (shouldDiscardChanges) {
|
|
3715
|
+
changeTree.discard();
|
|
3716
|
+
// Not a new instance anymore
|
|
3717
|
+
changeTree.isNew = false;
|
|
3562
3718
|
}
|
|
3563
|
-
// if (shouldClearChanges) {
|
|
3564
|
-
// // changeTree.endEncode();
|
|
3565
|
-
// changeTree.changes.clear();
|
|
3566
|
-
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3567
|
-
// changeTree.ref[$onEncodeEnd]?.();
|
|
3568
|
-
// // Not a new instance anymore
|
|
3569
|
-
// delete changeTree[$isNew];
|
|
3570
|
-
// }
|
|
3571
3719
|
}
|
|
3572
3720
|
if (it.offset > buffer.byteLength) {
|
|
3573
3721
|
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
@@ -3584,51 +3732,54 @@ class Encoder {
|
|
|
3584
3732
|
if (buffer === this.sharedBuffer) {
|
|
3585
3733
|
this.sharedBuffer = buffer;
|
|
3586
3734
|
}
|
|
3587
|
-
return this.encode({ offset: initialOffset }, view, buffer,
|
|
3735
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3588
3736
|
}
|
|
3589
3737
|
else {
|
|
3590
|
-
//
|
|
3591
|
-
// only clear changes after making sure buffer resize is not required.
|
|
3592
|
-
//
|
|
3593
|
-
if (shouldClearChanges) {
|
|
3594
|
-
|
|
3595
|
-
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
}
|
|
3738
|
+
// //
|
|
3739
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
3740
|
+
// //
|
|
3741
|
+
// if (shouldClearChanges) {
|
|
3742
|
+
// //
|
|
3743
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
3744
|
+
// //
|
|
3745
|
+
// this.onEndEncode(changeTrees);
|
|
3746
|
+
// }
|
|
3599
3747
|
return buffer.subarray(0, it.offset);
|
|
3600
3748
|
}
|
|
3601
3749
|
}
|
|
3602
3750
|
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3603
|
-
// console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.
|
|
3751
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
3604
3752
|
// this.debugChanges("allChanges");
|
|
3605
|
-
return this.encode(it, undefined, buffer,
|
|
3753
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3606
3754
|
}
|
|
3607
3755
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3608
3756
|
const viewOffset = it.offset;
|
|
3609
|
-
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.
|
|
3757
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
3610
3758
|
// this.debugChanges("allFilteredChanges");
|
|
3759
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
3611
3760
|
// try to encode "filtered" changes
|
|
3612
|
-
this.encode(it, view, bytes,
|
|
3761
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3613
3762
|
return Buffer.concat([
|
|
3614
3763
|
bytes.subarray(0, sharedOffset),
|
|
3615
3764
|
bytes.subarray(viewOffset, it.offset)
|
|
3616
3765
|
]);
|
|
3617
3766
|
}
|
|
3618
3767
|
debugChanges(field) {
|
|
3619
|
-
const
|
|
3768
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3620
3769
|
? this.root[field]
|
|
3621
3770
|
: field;
|
|
3622
|
-
|
|
3623
|
-
const
|
|
3624
|
-
|
|
3625
|
-
|
|
3771
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3772
|
+
const changeSet = changeTree[field];
|
|
3773
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3774
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3775
|
+
for (const index in changeSet) {
|
|
3776
|
+
const op = changeSet[index];
|
|
3626
3777
|
console.log(" ->", {
|
|
3627
3778
|
index,
|
|
3628
3779
|
field: metadata?.[index],
|
|
3629
3780
|
op: OPERATION[op],
|
|
3630
3781
|
});
|
|
3631
|
-
}
|
|
3782
|
+
}
|
|
3632
3783
|
});
|
|
3633
3784
|
}
|
|
3634
3785
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
@@ -3638,23 +3789,31 @@ class Encoder {
|
|
|
3638
3789
|
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3639
3790
|
// this.debugChanges("filteredChanges");
|
|
3640
3791
|
// encode visibility changes (add/remove for this view)
|
|
3641
|
-
const
|
|
3642
|
-
|
|
3643
|
-
|
|
3644
|
-
|
|
3645
|
-
|
|
3792
|
+
const refIds = Object.keys(view.changes);
|
|
3793
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
3794
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3795
|
+
const refId = refIds[i];
|
|
3796
|
+
const changes = view.changes[refId];
|
|
3797
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3798
|
+
if (changeTree === undefined ||
|
|
3799
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3800
|
+
) {
|
|
3801
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3646
3802
|
continue;
|
|
3647
3803
|
}
|
|
3648
3804
|
const ref = changeTree.ref;
|
|
3649
|
-
const ctor = ref
|
|
3805
|
+
const ctor = ref.constructor;
|
|
3650
3806
|
const encoder = ctor[$encoder];
|
|
3807
|
+
const metadata = ctor[Symbol.metadata];
|
|
3651
3808
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3652
3809
|
number$1(bytes, changeTree.refId, it);
|
|
3653
|
-
const
|
|
3654
|
-
for (
|
|
3810
|
+
const keys = Object.keys(changes);
|
|
3811
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3812
|
+
const key = keys[i];
|
|
3813
|
+
const operation = changes[key];
|
|
3655
3814
|
// isEncodeAll = false
|
|
3656
3815
|
// hasView = true
|
|
3657
|
-
encoder(this, bytes, changeTree,
|
|
3816
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3658
3817
|
}
|
|
3659
3818
|
}
|
|
3660
3819
|
//
|
|
@@ -3662,35 +3821,46 @@ class Encoder {
|
|
|
3662
3821
|
// (to allow re-using StateView's for multiple clients)
|
|
3663
3822
|
//
|
|
3664
3823
|
// clear "view" changes after encoding
|
|
3665
|
-
view.changes
|
|
3824
|
+
view.changes = {};
|
|
3825
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
3666
3826
|
// try to encode "filtered" changes
|
|
3667
|
-
this.encode(it, view, bytes,
|
|
3827
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3668
3828
|
return Buffer.concat([
|
|
3669
3829
|
bytes.subarray(0, sharedOffset),
|
|
3670
3830
|
bytes.subarray(viewOffset, it.offset)
|
|
3671
3831
|
]);
|
|
3672
3832
|
}
|
|
3673
3833
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3674
|
-
|
|
3675
|
-
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3834
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3835
|
+
// changeTree.endEncode();
|
|
3836
|
+
// });
|
|
3837
|
+
// for (const refId in changeTrees) {
|
|
3838
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3839
|
+
// changeTree.endEncode();
|
|
3840
|
+
// // changeTree.changes.clear();
|
|
3841
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3842
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3843
|
+
// // // Not a new instance anymore
|
|
3844
|
+
// // delete changeTree[$isNew];
|
|
3845
|
+
// }
|
|
3683
3846
|
}
|
|
3684
3847
|
discardChanges() {
|
|
3848
|
+
// console.log("DISCARD CHANGES!");
|
|
3685
3849
|
// discard shared changes
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3850
|
+
let length = this.root.changes.length;
|
|
3851
|
+
if (length > 0) {
|
|
3852
|
+
while (length--) {
|
|
3853
|
+
this.root.changes[length]?.endEncode();
|
|
3854
|
+
}
|
|
3855
|
+
this.root.changes.length = 0;
|
|
3689
3856
|
}
|
|
3690
3857
|
// discard filtered changes
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3858
|
+
length = this.root.filteredChanges.length;
|
|
3859
|
+
if (length > 0) {
|
|
3860
|
+
while (length--) {
|
|
3861
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3862
|
+
}
|
|
3863
|
+
this.root.filteredChanges.length = 0;
|
|
3694
3864
|
}
|
|
3695
3865
|
}
|
|
3696
3866
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
@@ -3703,19 +3873,6 @@ class Encoder {
|
|
|
3703
3873
|
}
|
|
3704
3874
|
}
|
|
3705
3875
|
|
|
3706
|
-
function spliceOne(arr, index) {
|
|
3707
|
-
// manually splice an array
|
|
3708
|
-
if (index === -1 || index >= arr.length) {
|
|
3709
|
-
return false;
|
|
3710
|
-
}
|
|
3711
|
-
const len = arr.length - 1;
|
|
3712
|
-
for (let i = index; i < len; i++) {
|
|
3713
|
-
arr[i] = arr[i + 1];
|
|
3714
|
-
}
|
|
3715
|
-
arr.length = len;
|
|
3716
|
-
return true;
|
|
3717
|
-
}
|
|
3718
|
-
|
|
3719
3876
|
class DecodingWarning extends Error {
|
|
3720
3877
|
constructor(message) {
|
|
3721
3878
|
super(message);
|
|
@@ -3877,7 +4034,7 @@ class Decoder {
|
|
|
3877
4034
|
}
|
|
3878
4035
|
ref[$onDecodeEnd]?.();
|
|
3879
4036
|
ref = nextRef;
|
|
3880
|
-
decoder = ref
|
|
4037
|
+
decoder = ref.constructor[$decoder];
|
|
3881
4038
|
continue;
|
|
3882
4039
|
}
|
|
3883
4040
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -4354,7 +4511,7 @@ class StateView {
|
|
|
4354
4511
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
4355
4512
|
* (This is used to force encoding a property, even if it was not changed)
|
|
4356
4513
|
*/
|
|
4357
|
-
this.changes =
|
|
4514
|
+
this.changes = {};
|
|
4358
4515
|
}
|
|
4359
4516
|
// TODO: allow to set multiple tags at once
|
|
4360
4517
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
@@ -4376,10 +4533,10 @@ class StateView {
|
|
|
4376
4533
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
4377
4534
|
// be set (only the parent's changes are set)
|
|
4378
4535
|
//
|
|
4379
|
-
let changes = this.changes.
|
|
4536
|
+
let changes = this.changes[changeTree.refId];
|
|
4380
4537
|
if (changes === undefined) {
|
|
4381
|
-
changes =
|
|
4382
|
-
this.changes.
|
|
4538
|
+
changes = {};
|
|
4539
|
+
this.changes[changeTree.refId] = changes;
|
|
4383
4540
|
}
|
|
4384
4541
|
// set tag
|
|
4385
4542
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -4396,9 +4553,9 @@ class StateView {
|
|
|
4396
4553
|
}
|
|
4397
4554
|
tags.add(tag);
|
|
4398
4555
|
// Ref: add tagged properties
|
|
4399
|
-
metadata?.[
|
|
4556
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4400
4557
|
if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
4401
|
-
changes
|
|
4558
|
+
changes[index] = OPERATION.ADD;
|
|
4402
4559
|
}
|
|
4403
4560
|
});
|
|
4404
4561
|
}
|
|
@@ -4407,16 +4564,21 @@ class StateView {
|
|
|
4407
4564
|
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4408
4565
|
? changeTree.allFilteredChanges
|
|
4409
4566
|
: changeTree.allChanges;
|
|
4410
|
-
|
|
4567
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4568
|
+
const index = changeSet.operations[i];
|
|
4569
|
+
if (index === undefined) {
|
|
4570
|
+
continue;
|
|
4571
|
+
} // skip "undefined" indexes
|
|
4572
|
+
const op = changeTree.indexedOperations[index];
|
|
4411
4573
|
const tagAtIndex = metadata?.[index].tag;
|
|
4412
4574
|
if ((isInvisible || // if "invisible", include all
|
|
4413
4575
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4414
4576
|
tagAtIndex === tag // tagged property
|
|
4415
4577
|
) &&
|
|
4416
4578
|
op !== OPERATION.DELETE) {
|
|
4417
|
-
changes
|
|
4579
|
+
changes[index] = op;
|
|
4418
4580
|
}
|
|
4419
|
-
}
|
|
4581
|
+
}
|
|
4420
4582
|
}
|
|
4421
4583
|
// Add children of this ChangeTree to this view
|
|
4422
4584
|
changeTree.forEachChild((change, index) => {
|
|
@@ -4442,10 +4604,10 @@ class StateView {
|
|
|
4442
4604
|
}
|
|
4443
4605
|
// add parent's tag properties
|
|
4444
4606
|
if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
|
|
4445
|
-
let changes = this.changes.
|
|
4607
|
+
let changes = this.changes[changeTree.refId];
|
|
4446
4608
|
if (changes === undefined) {
|
|
4447
|
-
changes =
|
|
4448
|
-
this.changes.
|
|
4609
|
+
changes = {};
|
|
4610
|
+
this.changes[changeTree.refId] = changes;
|
|
4449
4611
|
}
|
|
4450
4612
|
if (!this.tags) {
|
|
4451
4613
|
this.tags = new WeakMap();
|
|
@@ -4459,7 +4621,7 @@ class StateView {
|
|
|
4459
4621
|
tags = this.tags.get(changeTree);
|
|
4460
4622
|
}
|
|
4461
4623
|
tags.add(tag);
|
|
4462
|
-
changes
|
|
4624
|
+
changes[parentIndex] = OPERATION.ADD;
|
|
4463
4625
|
}
|
|
4464
4626
|
}
|
|
4465
4627
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4471,32 +4633,32 @@ class StateView {
|
|
|
4471
4633
|
this.items.delete(changeTree);
|
|
4472
4634
|
const ref = changeTree.ref;
|
|
4473
4635
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4474
|
-
let changes = this.changes.
|
|
4636
|
+
let changes = this.changes[changeTree.refId];
|
|
4475
4637
|
if (changes === undefined) {
|
|
4476
|
-
changes =
|
|
4477
|
-
this.changes.
|
|
4638
|
+
changes = {};
|
|
4639
|
+
this.changes[changeTree.refId] = changes;
|
|
4478
4640
|
}
|
|
4479
4641
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4480
4642
|
// parent is collection (Map/Array)
|
|
4481
4643
|
const parent = changeTree.parent;
|
|
4482
4644
|
if (!Metadata.isValidInstance(parent)) {
|
|
4483
4645
|
const parentChangeTree = parent[$changes];
|
|
4484
|
-
let changes = this.changes.
|
|
4646
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4485
4647
|
if (changes === undefined) {
|
|
4486
|
-
changes =
|
|
4487
|
-
this.changes.
|
|
4648
|
+
changes = {};
|
|
4649
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4488
4650
|
}
|
|
4489
4651
|
// DELETE / DELETE BY REF ID
|
|
4490
|
-
changes
|
|
4652
|
+
changes[changeTree.parentIndex] = OPERATION.DELETE;
|
|
4491
4653
|
}
|
|
4492
4654
|
else {
|
|
4493
4655
|
// delete all "tagged" properties.
|
|
4494
|
-
metadata[
|
|
4656
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = OPERATION.DELETE);
|
|
4495
4657
|
}
|
|
4496
4658
|
}
|
|
4497
4659
|
else {
|
|
4498
4660
|
// delete only tagged properties
|
|
4499
|
-
metadata[
|
|
4661
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = OPERATION.DELETE);
|
|
4500
4662
|
}
|
|
4501
4663
|
// remove tag
|
|
4502
4664
|
if (this.tags && this.tags.has(changeTree)) {
|