@colyseus/schema 3.0.0-alpha.34 → 3.0.0-alpha.36
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 +4 -3
- package/build/cjs/index.js +612 -408
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +612 -408
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +612 -408
- package/lib/Metadata.d.ts +6 -6
- package/lib/Metadata.js +48 -21
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +17 -2
- package/lib/Reflection.js +19 -6
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.js +24 -17
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +1 -1
- package/lib/annotations.js +13 -16
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +12 -5
- package/lib/bench_encode.js.map +1 -1
- package/lib/debug.js +1 -2
- package/lib/debug.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 +97 -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 +1 -1
- package/src/Metadata.ts +51 -27
- package/src/Reflection.ts +21 -8
- package/src/Schema.ts +33 -25
- package/src/annotations.ts +14 -18
- package/src/bench_encode.ts +15 -6
- package/src/debug.ts +1 -2
- package/src/decoder/Decoder.ts +1 -1
- package/src/encoder/ChangeTree.ts +220 -115
- package/src/encoder/EncodeOperation.ts +5 -4
- package/src/encoder/Encoder.ts +115 -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/umd/index.js
CHANGED
|
@@ -40,7 +40,6 @@
|
|
|
40
40
|
const $filter = Symbol("$filter");
|
|
41
41
|
const $getByIndex = Symbol("$getByIndex");
|
|
42
42
|
const $deleteByIndex = Symbol("$deleteByIndex");
|
|
43
|
-
const $descriptors = Symbol("$descriptors");
|
|
44
43
|
/**
|
|
45
44
|
* Used to hold ChangeTree instances whitin the structures
|
|
46
45
|
*/
|
|
@@ -50,11 +49,6 @@
|
|
|
50
49
|
* (MapSchema, ArraySchema, etc.)
|
|
51
50
|
*/
|
|
52
51
|
const $childType = Symbol('$childType');
|
|
53
|
-
/**
|
|
54
|
-
* Special ChangeTree property to identify new instances
|
|
55
|
-
* (Once they're encoded, they're not new anymore)
|
|
56
|
-
*/
|
|
57
|
-
const $isNew = Symbol("$isNew");
|
|
58
52
|
/**
|
|
59
53
|
* Optional "discard" method for custom types (ArraySchema)
|
|
60
54
|
* (Discards changes for next serialization)
|
|
@@ -64,6 +58,14 @@
|
|
|
64
58
|
* When decoding, this method is called after the instance is fully decoded
|
|
65
59
|
*/
|
|
66
60
|
const $onDecodeEnd = Symbol("$onDecodeEnd");
|
|
61
|
+
/**
|
|
62
|
+
* Metadata
|
|
63
|
+
*/
|
|
64
|
+
const $descriptors = Symbol("$descriptors");
|
|
65
|
+
const $numFields = "$__numFields";
|
|
66
|
+
const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
|
|
67
|
+
const $viewFieldIndexes = "$__viewFieldIndexes";
|
|
68
|
+
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
67
69
|
|
|
68
70
|
const registeredTypes = {};
|
|
69
71
|
const identifiers = new Map();
|
|
@@ -75,6 +77,102 @@
|
|
|
75
77
|
return registeredTypes[identifier];
|
|
76
78
|
}
|
|
77
79
|
|
|
80
|
+
class TypeContext {
|
|
81
|
+
/**
|
|
82
|
+
* For inheritance support
|
|
83
|
+
* Keeps track of which classes extends which. (parent -> children)
|
|
84
|
+
*/
|
|
85
|
+
static { this.inheritedTypes = new Map(); }
|
|
86
|
+
static register(target) {
|
|
87
|
+
const parent = Object.getPrototypeOf(target);
|
|
88
|
+
if (parent !== Schema) {
|
|
89
|
+
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
90
|
+
if (!inherits) {
|
|
91
|
+
inherits = new Set();
|
|
92
|
+
TypeContext.inheritedTypes.set(parent, inherits);
|
|
93
|
+
}
|
|
94
|
+
inherits.add(target);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
constructor(rootClass) {
|
|
98
|
+
this.types = {};
|
|
99
|
+
this.schemas = new Map();
|
|
100
|
+
this.hasFilters = false;
|
|
101
|
+
this.parentFiltered = {};
|
|
102
|
+
if (rootClass) {
|
|
103
|
+
this.discoverTypes(rootClass);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
has(schema) {
|
|
107
|
+
return this.schemas.has(schema);
|
|
108
|
+
}
|
|
109
|
+
get(typeid) {
|
|
110
|
+
return this.types[typeid];
|
|
111
|
+
}
|
|
112
|
+
add(schema, typeid = this.schemas.size) {
|
|
113
|
+
// skip if already registered
|
|
114
|
+
if (this.schemas.has(schema)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
this.types[typeid] = schema;
|
|
118
|
+
//
|
|
119
|
+
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
120
|
+
//
|
|
121
|
+
if (schema[Symbol.metadata] === undefined) {
|
|
122
|
+
Metadata.init(schema);
|
|
123
|
+
}
|
|
124
|
+
this.schemas.set(schema, typeid);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
getTypeId(klass) {
|
|
128
|
+
return this.schemas.get(klass);
|
|
129
|
+
}
|
|
130
|
+
discoverTypes(klass, parentIndex, parentFieldViewTag) {
|
|
131
|
+
if (!this.add(klass)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// add classes inherited from this base class
|
|
135
|
+
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
136
|
+
this.discoverTypes(child, parentIndex, parentFieldViewTag);
|
|
137
|
+
});
|
|
138
|
+
const metadata = (klass[Symbol.metadata] ??= {});
|
|
139
|
+
// if any schema/field has filters, mark "context" as having filters.
|
|
140
|
+
if (metadata[$viewFieldIndexes]) {
|
|
141
|
+
this.hasFilters = true;
|
|
142
|
+
}
|
|
143
|
+
if (parentFieldViewTag !== undefined) {
|
|
144
|
+
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
145
|
+
}
|
|
146
|
+
for (const fieldIndex in metadata) {
|
|
147
|
+
const index = fieldIndex;
|
|
148
|
+
const fieldType = metadata[index].type;
|
|
149
|
+
const viewTag = metadata[index].tag;
|
|
150
|
+
if (typeof (fieldType) === "string") {
|
|
151
|
+
continue;
|
|
152
|
+
}
|
|
153
|
+
if (Array.isArray(fieldType)) {
|
|
154
|
+
const type = fieldType[0];
|
|
155
|
+
// skip primitive types
|
|
156
|
+
if (type === "string") {
|
|
157
|
+
continue;
|
|
158
|
+
}
|
|
159
|
+
this.discoverTypes(type, index, viewTag);
|
|
160
|
+
}
|
|
161
|
+
else if (typeof (fieldType) === "function") {
|
|
162
|
+
this.discoverTypes(fieldType, viewTag);
|
|
163
|
+
}
|
|
164
|
+
else {
|
|
165
|
+
const type = Object.values(fieldType)[0];
|
|
166
|
+
// skip primitive types
|
|
167
|
+
if (typeof (type) === "string") {
|
|
168
|
+
continue;
|
|
169
|
+
}
|
|
170
|
+
this.discoverTypes(type, index, viewTag);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
|
|
78
176
|
const Metadata = {
|
|
79
177
|
addField(metadata, index, name, type, descriptor) {
|
|
80
178
|
if (index > 64) {
|
|
@@ -110,7 +208,7 @@
|
|
|
110
208
|
};
|
|
111
209
|
}
|
|
112
210
|
// map -1 as last field index
|
|
113
|
-
Object.defineProperty(metadata,
|
|
211
|
+
Object.defineProperty(metadata, $numFields, {
|
|
114
212
|
value: index,
|
|
115
213
|
enumerable: false,
|
|
116
214
|
configurable: true
|
|
@@ -123,14 +221,14 @@
|
|
|
123
221
|
});
|
|
124
222
|
// if child Ref/complex type, add to -4
|
|
125
223
|
if (typeof (metadata[index].type) !== "string") {
|
|
126
|
-
if (metadata[
|
|
127
|
-
Object.defineProperty(metadata,
|
|
224
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
225
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
128
226
|
value: [],
|
|
129
227
|
enumerable: false,
|
|
130
228
|
configurable: true,
|
|
131
229
|
});
|
|
132
230
|
}
|
|
133
|
-
metadata[
|
|
231
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
134
232
|
}
|
|
135
233
|
},
|
|
136
234
|
setTag(metadata, fieldName, tag) {
|
|
@@ -138,38 +236,63 @@
|
|
|
138
236
|
const field = metadata[index];
|
|
139
237
|
// add 'tag' to the field
|
|
140
238
|
field.tag = tag;
|
|
141
|
-
if (!metadata[
|
|
239
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
142
240
|
// -2: all field indexes with "view" tag
|
|
143
|
-
Object.defineProperty(metadata,
|
|
241
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
144
242
|
value: [],
|
|
145
243
|
enumerable: false,
|
|
146
244
|
configurable: true
|
|
147
245
|
});
|
|
148
246
|
// -3: field indexes by "view" tag
|
|
149
|
-
Object.defineProperty(metadata,
|
|
247
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
150
248
|
value: {},
|
|
151
249
|
enumerable: false,
|
|
152
250
|
configurable: true
|
|
153
251
|
});
|
|
154
252
|
}
|
|
155
|
-
metadata[
|
|
156
|
-
if (!metadata[
|
|
157
|
-
metadata[
|
|
253
|
+
metadata[$viewFieldIndexes].push(index);
|
|
254
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
255
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
158
256
|
}
|
|
159
|
-
metadata[
|
|
257
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
160
258
|
},
|
|
161
259
|
setFields(target, fields) {
|
|
162
|
-
|
|
163
|
-
|
|
260
|
+
// for inheritance support
|
|
261
|
+
const constructor = target.prototype.constructor;
|
|
262
|
+
TypeContext.register(constructor);
|
|
263
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
264
|
+
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
265
|
+
const metadata = Metadata.initialize(constructor, parentMetadata);
|
|
266
|
+
// Use Schema's methods if not defined in the class
|
|
267
|
+
if (!constructor[$track]) {
|
|
268
|
+
constructor[$track] = Schema[$track];
|
|
269
|
+
}
|
|
270
|
+
if (!constructor[$encoder]) {
|
|
271
|
+
constructor[$encoder] = Schema[$encoder];
|
|
272
|
+
}
|
|
273
|
+
if (!constructor[$decoder]) {
|
|
274
|
+
constructor[$decoder] = Schema[$decoder];
|
|
275
|
+
}
|
|
276
|
+
if (!constructor.prototype.toJSON) {
|
|
277
|
+
constructor.prototype.toJSON = Schema.prototype.toJSON;
|
|
278
|
+
}
|
|
279
|
+
//
|
|
280
|
+
// detect index for this field, considering inheritance
|
|
281
|
+
//
|
|
282
|
+
let fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
283
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
284
|
+
?? -1; // no fields defined
|
|
285
|
+
fieldIndex++;
|
|
164
286
|
for (const field in fields) {
|
|
165
287
|
const type = fields[field];
|
|
166
288
|
// FIXME: this code is duplicated from @type() annotation
|
|
167
289
|
const complexTypeKlass = (Array.isArray(type))
|
|
168
290
|
? getType("array")
|
|
169
291
|
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
170
|
-
Metadata.addField(metadata,
|
|
171
|
-
|
|
292
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, type, complexTypeKlass));
|
|
293
|
+
fieldIndex++;
|
|
172
294
|
}
|
|
295
|
+
return target;
|
|
173
296
|
},
|
|
174
297
|
isDeprecated(metadata, field) {
|
|
175
298
|
return metadata[field].deprecated === true;
|
|
@@ -181,7 +304,7 @@
|
|
|
181
304
|
//
|
|
182
305
|
const metadata = {};
|
|
183
306
|
klass[Symbol.metadata] = metadata;
|
|
184
|
-
Object.defineProperty(metadata,
|
|
307
|
+
Object.defineProperty(metadata, $numFields, {
|
|
185
308
|
value: 0,
|
|
186
309
|
enumerable: false,
|
|
187
310
|
configurable: true,
|
|
@@ -195,7 +318,7 @@
|
|
|
195
318
|
if (parentMetadata) {
|
|
196
319
|
// assign parent metadata to current
|
|
197
320
|
Object.assign(metadata, parentMetadata);
|
|
198
|
-
for (let i = 0; i <= parentMetadata[
|
|
321
|
+
for (let i = 0; i <= parentMetadata[$numFields]; i++) {
|
|
199
322
|
const fieldName = parentMetadata[i].name;
|
|
200
323
|
Object.defineProperty(metadata, fieldName, {
|
|
201
324
|
value: parentMetadata[fieldName],
|
|
@@ -203,8 +326,8 @@
|
|
|
203
326
|
configurable: true,
|
|
204
327
|
});
|
|
205
328
|
}
|
|
206
|
-
Object.defineProperty(metadata,
|
|
207
|
-
value: parentMetadata[
|
|
329
|
+
Object.defineProperty(metadata, $numFields, {
|
|
330
|
+
value: parentMetadata[$numFields],
|
|
208
331
|
enumerable: false,
|
|
209
332
|
configurable: true,
|
|
210
333
|
writable: true,
|
|
@@ -216,40 +339,69 @@
|
|
|
216
339
|
},
|
|
217
340
|
isValidInstance(klass) {
|
|
218
341
|
return (klass.constructor[Symbol.metadata] &&
|
|
219
|
-
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata],
|
|
342
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
220
343
|
},
|
|
221
344
|
getFields(klass) {
|
|
222
345
|
const metadata = klass[Symbol.metadata];
|
|
223
346
|
const fields = {};
|
|
224
|
-
for (let i = 0; i <= metadata[
|
|
347
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
225
348
|
fields[metadata[i].name] = metadata[i].type;
|
|
226
349
|
}
|
|
227
350
|
return fields;
|
|
228
351
|
}
|
|
229
352
|
};
|
|
230
353
|
|
|
231
|
-
|
|
354
|
+
function setOperationAtIndex(changeSet, index) {
|
|
355
|
+
const operationsIndex = changeSet.indexes[index];
|
|
356
|
+
if (operationsIndex === undefined) {
|
|
357
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
358
|
+
}
|
|
359
|
+
else {
|
|
360
|
+
changeSet.operations[operationsIndex] = index;
|
|
361
|
+
}
|
|
362
|
+
}
|
|
363
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
364
|
+
const operationsIndex = changeSet.indexes[index];
|
|
365
|
+
if (operationsIndex !== undefined) {
|
|
366
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
367
|
+
}
|
|
368
|
+
delete changeSet.indexes[index];
|
|
369
|
+
}
|
|
370
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
371
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
372
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
232
375
|
class ChangeTree {
|
|
233
|
-
static { _a$5 = $isNew; }
|
|
234
376
|
constructor(ref) {
|
|
235
377
|
this.isFiltered = false;
|
|
236
378
|
this.isPartiallyFiltered = false;
|
|
237
|
-
this.
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
379
|
+
this.indexedOperations = {};
|
|
380
|
+
//
|
|
381
|
+
// TODO:
|
|
382
|
+
// try storing the index + operation per item.
|
|
383
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
384
|
+
//
|
|
385
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
386
|
+
//
|
|
387
|
+
this.changes = { indexes: {}, operations: [] };
|
|
388
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
389
|
+
/**
|
|
390
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
391
|
+
*/
|
|
392
|
+
this.isNew = true;
|
|
241
393
|
this.ref = ref;
|
|
242
394
|
//
|
|
243
395
|
// Does this structure have "filters" declared?
|
|
244
396
|
//
|
|
245
|
-
if (ref.constructor[Symbol.metadata]?.[
|
|
246
|
-
this.allFilteredChanges =
|
|
247
|
-
this.filteredChanges =
|
|
397
|
+
if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
|
|
398
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
399
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
248
400
|
}
|
|
249
401
|
}
|
|
250
402
|
setRoot(root) {
|
|
251
403
|
this.root = root;
|
|
252
|
-
this.root.add(this);
|
|
404
|
+
const isNewChangeTree = this.root.add(this);
|
|
253
405
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
254
406
|
if (this.root.types.hasFilters) {
|
|
255
407
|
//
|
|
@@ -260,22 +412,24 @@
|
|
|
260
412
|
//
|
|
261
413
|
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
262
414
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
263
|
-
|
|
264
|
-
|
|
415
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
416
|
+
if (isNewChangeTree) {
|
|
417
|
+
this.root.allFilteredChanges.push(this);
|
|
418
|
+
}
|
|
265
419
|
}
|
|
266
420
|
}
|
|
267
421
|
if (!this.isFiltered) {
|
|
268
|
-
|
|
269
|
-
|
|
422
|
+
enqueueChangeTree(root, this, 'changes');
|
|
423
|
+
if (isNewChangeTree) {
|
|
424
|
+
this.root.allChanges.push(this);
|
|
425
|
+
}
|
|
270
426
|
}
|
|
271
|
-
|
|
427
|
+
// Recursively set root on child structures
|
|
272
428
|
if (metadata) {
|
|
273
|
-
metadata[
|
|
429
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
274
430
|
const field = metadata[index];
|
|
275
431
|
const value = this.ref[field.name];
|
|
276
|
-
|
|
277
|
-
value[$changes].setRoot(root);
|
|
278
|
-
}
|
|
432
|
+
value?.[$changes].setRoot(root);
|
|
279
433
|
});
|
|
280
434
|
}
|
|
281
435
|
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
@@ -292,31 +446,36 @@
|
|
|
292
446
|
if (!root) {
|
|
293
447
|
return;
|
|
294
448
|
}
|
|
295
|
-
root.add(this);
|
|
296
449
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
297
450
|
// skip if parent is already set
|
|
298
451
|
if (root !== this.root) {
|
|
299
452
|
this.root = root;
|
|
453
|
+
const isNewChangeTree = root.add(this);
|
|
300
454
|
if (root.types.hasFilters) {
|
|
301
455
|
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
302
456
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
303
|
-
|
|
304
|
-
|
|
457
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
458
|
+
if (isNewChangeTree) {
|
|
459
|
+
this.root.allFilteredChanges.push(this);
|
|
460
|
+
}
|
|
305
461
|
}
|
|
306
462
|
}
|
|
307
463
|
if (!this.isFiltered) {
|
|
308
|
-
|
|
309
|
-
|
|
464
|
+
enqueueChangeTree(root, this, 'changes');
|
|
465
|
+
if (isNewChangeTree) {
|
|
466
|
+
this.root.allChanges.push(this);
|
|
467
|
+
}
|
|
310
468
|
}
|
|
311
|
-
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
root.add(this);
|
|
312
472
|
}
|
|
313
473
|
// assign same parent on child structures
|
|
314
474
|
if (metadata) {
|
|
315
|
-
metadata[
|
|
475
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
316
476
|
const field = metadata[index];
|
|
317
477
|
const value = this.ref[field.name];
|
|
318
478
|
value?.[$changes].setParent(this.ref, root, index);
|
|
319
|
-
// console.log(this.ref.constructor.name, field.name, value);
|
|
320
479
|
// try { throw new Error(); } catch (e) {
|
|
321
480
|
// console.log(e.stack);
|
|
322
481
|
// }
|
|
@@ -335,7 +494,7 @@
|
|
|
335
494
|
//
|
|
336
495
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
337
496
|
if (metadata) {
|
|
338
|
-
metadata[
|
|
497
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
339
498
|
const field = metadata[index];
|
|
340
499
|
const value = this.ref[field.name];
|
|
341
500
|
if (value) {
|
|
@@ -351,8 +510,10 @@
|
|
|
351
510
|
}
|
|
352
511
|
}
|
|
353
512
|
operation(op) {
|
|
354
|
-
|
|
355
|
-
this.
|
|
513
|
+
// operations without index use negative values to represent them
|
|
514
|
+
// this is checked during .encode() time.
|
|
515
|
+
this.changes.operations.push(-op);
|
|
516
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
356
517
|
}
|
|
357
518
|
change(index, operation = exports.OPERATION.ADD) {
|
|
358
519
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
@@ -360,7 +521,7 @@
|
|
|
360
521
|
const changeSet = (isFiltered)
|
|
361
522
|
? this.filteredChanges
|
|
362
523
|
: this.changes;
|
|
363
|
-
const previousOperation =
|
|
524
|
+
const previousOperation = this.indexedOperations[index];
|
|
364
525
|
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
365
526
|
const op = (!previousOperation)
|
|
366
527
|
? operation
|
|
@@ -370,16 +531,19 @@
|
|
|
370
531
|
//
|
|
371
532
|
// TODO: are DELETE operations being encoded as ADD here ??
|
|
372
533
|
//
|
|
373
|
-
|
|
534
|
+
this.indexedOperations[index] = op;
|
|
374
535
|
}
|
|
536
|
+
setOperationAtIndex(changeSet, index);
|
|
375
537
|
if (isFiltered) {
|
|
376
|
-
this.allFilteredChanges
|
|
377
|
-
this.root
|
|
378
|
-
|
|
538
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
539
|
+
if (this.root) {
|
|
540
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
541
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
542
|
+
}
|
|
379
543
|
}
|
|
380
544
|
else {
|
|
381
|
-
this.allChanges
|
|
382
|
-
this.root
|
|
545
|
+
setOperationAtIndex(this.allChanges, index);
|
|
546
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
383
547
|
}
|
|
384
548
|
}
|
|
385
549
|
shiftChangeIndexes(shiftIndex) {
|
|
@@ -391,12 +555,15 @@
|
|
|
391
555
|
const changeSet = (this.isFiltered)
|
|
392
556
|
? this.filteredChanges
|
|
393
557
|
: this.changes;
|
|
394
|
-
const
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
558
|
+
const newIndexedOperations = {};
|
|
559
|
+
const newIndexes = {};
|
|
560
|
+
for (const index in this.indexedOperations) {
|
|
561
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
562
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
399
563
|
}
|
|
564
|
+
this.indexedOperations = newIndexedOperations;
|
|
565
|
+
changeSet.indexes = newIndexes;
|
|
566
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
400
567
|
}
|
|
401
568
|
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
402
569
|
//
|
|
@@ -412,24 +579,36 @@
|
|
|
412
579
|
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
413
580
|
}
|
|
414
581
|
}
|
|
415
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0,
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
582
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
583
|
+
const newIndexes = {};
|
|
584
|
+
for (const key in changeSet.indexes) {
|
|
585
|
+
const index = changeSet.indexes[key];
|
|
586
|
+
if (index > startIndex) {
|
|
587
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
420
588
|
}
|
|
421
|
-
|
|
589
|
+
else {
|
|
590
|
+
newIndexes[key] = index;
|
|
591
|
+
}
|
|
592
|
+
}
|
|
593
|
+
changeSet.indexes = newIndexes;
|
|
594
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
595
|
+
const index = changeSet.operations[i];
|
|
596
|
+
if (index > startIndex) {
|
|
597
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
598
|
+
}
|
|
599
|
+
}
|
|
422
600
|
}
|
|
423
601
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
this.
|
|
427
|
-
this.
|
|
602
|
+
this.indexedOperations[index] = operation;
|
|
603
|
+
if (this.filteredChanges) {
|
|
604
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
605
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
606
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
428
607
|
}
|
|
429
608
|
else {
|
|
430
|
-
this.allChanges
|
|
431
|
-
this.changes
|
|
432
|
-
this.root
|
|
609
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
610
|
+
setOperationAtIndex(this.changes, index);
|
|
611
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
433
612
|
}
|
|
434
613
|
}
|
|
435
614
|
getType(index) {
|
|
@@ -448,8 +627,7 @@
|
|
|
448
627
|
}
|
|
449
628
|
}
|
|
450
629
|
getChange(index) {
|
|
451
|
-
|
|
452
|
-
return this.changes.get(index) ?? this.filteredChanges?.get(index);
|
|
630
|
+
return this.indexedOperations[index];
|
|
453
631
|
}
|
|
454
632
|
//
|
|
455
633
|
// used during `.encode()`
|
|
@@ -473,8 +651,9 @@
|
|
|
473
651
|
const changeSet = (this.filteredChanges)
|
|
474
652
|
? this.filteredChanges
|
|
475
653
|
: this.changes;
|
|
654
|
+
this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
|
|
655
|
+
setOperationAtIndex(changeSet, index);
|
|
476
656
|
const previousValue = this.getValue(index);
|
|
477
|
-
changeSet.set(index, operation ?? exports.OPERATION.DELETE);
|
|
478
657
|
// remove `root` reference
|
|
479
658
|
if (previousValue && previousValue[$changes]) {
|
|
480
659
|
//
|
|
@@ -490,23 +669,26 @@
|
|
|
490
669
|
this.root?.remove(previousValue[$changes]);
|
|
491
670
|
}
|
|
492
671
|
//
|
|
493
|
-
// FIXME: this is looking a
|
|
672
|
+
// FIXME: this is looking a ugly and repeated
|
|
494
673
|
//
|
|
495
674
|
if (this.filteredChanges) {
|
|
496
|
-
this.
|
|
497
|
-
this.
|
|
675
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
676
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
498
677
|
}
|
|
499
678
|
else {
|
|
500
|
-
this.
|
|
501
|
-
this.
|
|
679
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
680
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
502
681
|
}
|
|
503
682
|
}
|
|
504
683
|
endEncode() {
|
|
505
|
-
this.
|
|
684
|
+
this.indexedOperations = {};
|
|
685
|
+
// // clear changes
|
|
686
|
+
// this.changes.indexes = {};
|
|
687
|
+
// this.changes.operations.length = 0;
|
|
506
688
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
507
689
|
this.ref[$onEncodeEnd]?.();
|
|
508
690
|
// Not a new instance anymore
|
|
509
|
-
|
|
691
|
+
this.isNew = false;
|
|
510
692
|
}
|
|
511
693
|
discard(discardAll = false) {
|
|
512
694
|
//
|
|
@@ -515,13 +697,22 @@
|
|
|
515
697
|
// REPLACE in case same key is used on next patches.
|
|
516
698
|
//
|
|
517
699
|
this.ref[$onEncodeEnd]?.();
|
|
518
|
-
this.
|
|
519
|
-
this.
|
|
520
|
-
|
|
521
|
-
this.
|
|
700
|
+
this.indexedOperations = {};
|
|
701
|
+
this.changes.indexes = {};
|
|
702
|
+
this.changes.operations.length = 0;
|
|
703
|
+
this.changes.queueRootIndex = undefined;
|
|
704
|
+
if (this.filteredChanges !== undefined) {
|
|
705
|
+
this.filteredChanges.indexes = {};
|
|
706
|
+
this.filteredChanges.operations.length = 0;
|
|
707
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
708
|
+
}
|
|
522
709
|
if (discardAll) {
|
|
523
|
-
this.allChanges.
|
|
524
|
-
this.
|
|
710
|
+
this.allChanges.indexes = {};
|
|
711
|
+
this.allChanges.operations.length = 0;
|
|
712
|
+
if (this.allFilteredChanges !== undefined) {
|
|
713
|
+
this.allFilteredChanges.indexes = {};
|
|
714
|
+
this.allFilteredChanges.operations.length = 0;
|
|
715
|
+
}
|
|
525
716
|
// remove children references
|
|
526
717
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
527
718
|
}
|
|
@@ -530,12 +721,13 @@
|
|
|
530
721
|
* Recursively discard all changes from this, and child structures.
|
|
531
722
|
*/
|
|
532
723
|
discardAll() {
|
|
533
|
-
this.
|
|
534
|
-
|
|
724
|
+
const keys = Object.keys(this.indexedOperations);
|
|
725
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
726
|
+
const value = this.getValue(Number(keys[i]));
|
|
535
727
|
if (value && value[$changes]) {
|
|
536
728
|
value[$changes].discardAll();
|
|
537
729
|
}
|
|
538
|
-
}
|
|
730
|
+
}
|
|
539
731
|
this.discard();
|
|
540
732
|
}
|
|
541
733
|
ensureRefId() {
|
|
@@ -546,42 +738,48 @@
|
|
|
546
738
|
this.refId = this.root.getNextUniqueId();
|
|
547
739
|
}
|
|
548
740
|
get changed() {
|
|
549
|
-
return this.
|
|
741
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
550
742
|
}
|
|
551
743
|
checkIsFiltered(metadata, parent, parentIndex) {
|
|
552
744
|
// Detect if current structure has "filters" declared
|
|
553
|
-
this.isPartiallyFiltered = metadata?.[
|
|
745
|
+
this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
|
|
554
746
|
if (this.isPartiallyFiltered) {
|
|
555
|
-
this.filteredChanges = this.filteredChanges ||
|
|
556
|
-
this.allFilteredChanges = this.allFilteredChanges ||
|
|
747
|
+
this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
|
|
748
|
+
this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
|
|
557
749
|
}
|
|
558
|
-
if
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
750
|
+
// skip if parent is not set
|
|
751
|
+
if (!parent) {
|
|
752
|
+
return;
|
|
753
|
+
}
|
|
754
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
755
|
+
const parentChangeTree = parent[$changes];
|
|
756
|
+
parent = parentChangeTree.parent;
|
|
757
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
758
|
+
}
|
|
759
|
+
const parentMetadata = parent.constructor?.[Symbol.metadata];
|
|
760
|
+
this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
761
|
+
//
|
|
762
|
+
// TODO: refactor this!
|
|
763
|
+
//
|
|
764
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
765
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
766
|
+
//
|
|
767
|
+
if (this.isFiltered) {
|
|
768
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
769
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
770
|
+
if (this.changes.operations.length > 0) {
|
|
771
|
+
// swap changes reference
|
|
772
|
+
const changes = this.changes;
|
|
773
|
+
this.changes = this.filteredChanges;
|
|
774
|
+
this.filteredChanges = changes;
|
|
775
|
+
// swap "all changes" reference
|
|
776
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
777
|
+
this.allFilteredChanges = this.allChanges;
|
|
778
|
+
this.allChanges = allFilteredChanges;
|
|
779
|
+
// console.log("SWAP =>", {
|
|
780
|
+
// "this.allFilteredChanges": this.allFilteredChanges,
|
|
781
|
+
// "this.allChanges": this.allChanges
|
|
782
|
+
// })
|
|
585
783
|
}
|
|
586
784
|
}
|
|
587
785
|
}
|
|
@@ -650,21 +848,24 @@
|
|
|
650
848
|
view[it.offset++] = c;
|
|
651
849
|
}
|
|
652
850
|
else if (c < 0x800) {
|
|
653
|
-
view[it.offset
|
|
654
|
-
view[it.offset
|
|
851
|
+
view[it.offset] = 0xc0 | (c >> 6);
|
|
852
|
+
view[it.offset + 1] = 0x80 | (c & 0x3f);
|
|
853
|
+
it.offset += 2;
|
|
655
854
|
}
|
|
656
855
|
else if (c < 0xd800 || c >= 0xe000) {
|
|
657
|
-
view[it.offset
|
|
658
|
-
view[it.offset
|
|
659
|
-
view[it.offset
|
|
856
|
+
view[it.offset] = 0xe0 | (c >> 12);
|
|
857
|
+
view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
|
|
858
|
+
view[it.offset + 2] = 0x80 | (c & 0x3f);
|
|
859
|
+
it.offset += 3;
|
|
660
860
|
}
|
|
661
861
|
else {
|
|
662
862
|
i++;
|
|
663
863
|
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
664
|
-
view[it.offset
|
|
665
|
-
view[it.offset
|
|
666
|
-
view[it.offset
|
|
667
|
-
view[it.offset
|
|
864
|
+
view[it.offset] = 0xf0 | (c >> 18);
|
|
865
|
+
view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
|
|
866
|
+
view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
|
|
867
|
+
view[it.offset + 3] = 0x80 | (c & 0x3f);
|
|
868
|
+
it.offset += 4;
|
|
668
869
|
}
|
|
669
870
|
}
|
|
670
871
|
}
|
|
@@ -893,7 +1094,7 @@
|
|
|
893
1094
|
* Used for Schema instances.
|
|
894
1095
|
* @private
|
|
895
1096
|
*/
|
|
896
|
-
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
1097
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
897
1098
|
// "compress" field index + operation
|
|
898
1099
|
bytes[it.offset++] = (index | operation) & 255;
|
|
899
1100
|
// Do not encode value for DELETE operations
|
|
@@ -901,7 +1102,7 @@
|
|
|
901
1102
|
return;
|
|
902
1103
|
}
|
|
903
1104
|
const ref = changeTree.ref;
|
|
904
|
-
const metadata = ref.constructor[Symbol.metadata];
|
|
1105
|
+
// const metadata: Metadata = ref.constructor[Symbol.metadata];
|
|
905
1106
|
const field = metadata[index];
|
|
906
1107
|
// TODO: inline this function call small performance gain
|
|
907
1108
|
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
@@ -1596,6 +1797,7 @@
|
|
|
1596
1797
|
const proxy = new Proxy(this, {
|
|
1597
1798
|
get: (obj, prop) => {
|
|
1598
1799
|
if (typeof (prop) !== "symbol" &&
|
|
1800
|
+
// FIXME: d8 accuses this as low performance
|
|
1599
1801
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1600
1802
|
) {
|
|
1601
1803
|
return this.items[prop];
|
|
@@ -1613,7 +1815,7 @@
|
|
|
1613
1815
|
if (setValue[$changes]) {
|
|
1614
1816
|
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1615
1817
|
if (obj.items[key] !== undefined) {
|
|
1616
|
-
if (setValue[$changes]
|
|
1818
|
+
if (setValue[$changes].isNew) {
|
|
1617
1819
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
1618
1820
|
}
|
|
1619
1821
|
else {
|
|
@@ -1625,7 +1827,7 @@
|
|
|
1625
1827
|
}
|
|
1626
1828
|
}
|
|
1627
1829
|
}
|
|
1628
|
-
else if (setValue[$changes]
|
|
1830
|
+
else if (setValue[$changes].isNew) {
|
|
1629
1831
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
|
|
1630
1832
|
}
|
|
1631
1833
|
}
|
|
@@ -1659,7 +1861,9 @@
|
|
|
1659
1861
|
});
|
|
1660
1862
|
this[$changes] = new ChangeTree(proxy);
|
|
1661
1863
|
this[$changes].indexes = {};
|
|
1662
|
-
|
|
1864
|
+
if (items.length > 0) {
|
|
1865
|
+
this.push(...items);
|
|
1866
|
+
}
|
|
1663
1867
|
return proxy;
|
|
1664
1868
|
}
|
|
1665
1869
|
set length(newLength) {
|
|
@@ -1678,15 +1882,18 @@
|
|
|
1678
1882
|
}
|
|
1679
1883
|
push(...values) {
|
|
1680
1884
|
let length = this.tmpItems.length;
|
|
1681
|
-
|
|
1682
|
-
|
|
1885
|
+
const changeTree = this[$changes];
|
|
1886
|
+
// values.forEach((value, i) => {
|
|
1887
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1888
|
+
const value = values[i];
|
|
1683
1889
|
if (value === undefined || value === null) {
|
|
1890
|
+
// skip null values
|
|
1684
1891
|
return;
|
|
1685
1892
|
}
|
|
1686
1893
|
else if (typeof (value) === "object" && this[$childType]) {
|
|
1687
1894
|
assertInstanceType(value, this[$childType], this, i);
|
|
1895
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1688
1896
|
}
|
|
1689
|
-
const changeTree = this[$changes];
|
|
1690
1897
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1691
1898
|
this.items.push(value);
|
|
1692
1899
|
this.tmpItems.push(value);
|
|
@@ -1695,8 +1902,9 @@
|
|
|
1695
1902
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1696
1903
|
//
|
|
1697
1904
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1698
|
-
|
|
1699
|
-
|
|
1905
|
+
}
|
|
1906
|
+
// length++;
|
|
1907
|
+
// });
|
|
1700
1908
|
return length;
|
|
1701
1909
|
}
|
|
1702
1910
|
/**
|
|
@@ -1717,6 +1925,7 @@
|
|
|
1717
1925
|
}
|
|
1718
1926
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1719
1927
|
// this.tmpItems[index] = undefined;
|
|
1928
|
+
// this.tmpItems.pop();
|
|
1720
1929
|
this.deletedIndexes[index] = true;
|
|
1721
1930
|
return this.items.pop();
|
|
1722
1931
|
}
|
|
@@ -1781,9 +1990,12 @@
|
|
|
1781
1990
|
//
|
|
1782
1991
|
// TODO: do not use [$changes] at decoding time.
|
|
1783
1992
|
//
|
|
1784
|
-
changeTree.root
|
|
1785
|
-
|
|
1786
|
-
|
|
1993
|
+
const root = changeTree.root;
|
|
1994
|
+
if (root !== undefined) {
|
|
1995
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
1996
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
1997
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
1998
|
+
}
|
|
1787
1999
|
});
|
|
1788
2000
|
changeTree.discard(true);
|
|
1789
2001
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -1827,6 +2039,7 @@
|
|
|
1827
2039
|
const changeTree = this[$changes];
|
|
1828
2040
|
changeTree.delete(index);
|
|
1829
2041
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
2042
|
+
// this.deletedIndexes[index] = true;
|
|
1830
2043
|
return this.items.shift();
|
|
1831
2044
|
}
|
|
1832
2045
|
/**
|
|
@@ -1907,10 +2120,12 @@
|
|
|
1907
2120
|
changeTree.shiftChangeIndexes(items.length);
|
|
1908
2121
|
// new index
|
|
1909
2122
|
if (changeTree.isFiltered) {
|
|
1910
|
-
changeTree.filteredChanges
|
|
2123
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
2124
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1911
2125
|
}
|
|
1912
2126
|
else {
|
|
1913
|
-
changeTree.allChanges
|
|
2127
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2128
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1914
2129
|
}
|
|
1915
2130
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1916
2131
|
items.forEach((_, index) => {
|
|
@@ -2251,7 +2466,7 @@
|
|
|
2251
2466
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2252
2467
|
const index = (isReplace)
|
|
2253
2468
|
? changeTree.indexes[key]
|
|
2254
|
-
: changeTree.indexes[
|
|
2469
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2255
2470
|
let operation = (isReplace)
|
|
2256
2471
|
? exports.OPERATION.REPLACE
|
|
2257
2472
|
: exports.OPERATION.ADD;
|
|
@@ -2263,7 +2478,7 @@
|
|
|
2263
2478
|
if (!isReplace) {
|
|
2264
2479
|
this.$indexes.set(index, key);
|
|
2265
2480
|
changeTree.indexes[key] = index;
|
|
2266
|
-
changeTree.indexes[
|
|
2481
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2267
2482
|
}
|
|
2268
2483
|
else if (!isRef &&
|
|
2269
2484
|
this.$items.get(key) === value) {
|
|
@@ -2338,8 +2553,11 @@
|
|
|
2338
2553
|
}
|
|
2339
2554
|
[$onEncodeEnd]() {
|
|
2340
2555
|
const changeTree = this[$changes];
|
|
2341
|
-
const
|
|
2342
|
-
for (
|
|
2556
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2557
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2558
|
+
const key = keys[i];
|
|
2559
|
+
const fieldIndex = Number(key);
|
|
2560
|
+
const operation = changeTree.indexedOperations[key];
|
|
2343
2561
|
if (operation === exports.OPERATION.DELETE) {
|
|
2344
2562
|
const index = this[$getByIndex](fieldIndex);
|
|
2345
2563
|
delete changeTree.indexes[index];
|
|
@@ -2382,102 +2600,6 @@
|
|
|
2382
2600
|
}
|
|
2383
2601
|
registerType("map", { constructor: MapSchema });
|
|
2384
2602
|
|
|
2385
|
-
class TypeContext {
|
|
2386
|
-
/**
|
|
2387
|
-
* For inheritance support
|
|
2388
|
-
* Keeps track of which classes extends which. (parent -> children)
|
|
2389
|
-
*/
|
|
2390
|
-
static { this.inheritedTypes = new Map(); }
|
|
2391
|
-
static register(target) {
|
|
2392
|
-
const parent = Object.getPrototypeOf(target);
|
|
2393
|
-
if (parent !== Schema) {
|
|
2394
|
-
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
2395
|
-
if (!inherits) {
|
|
2396
|
-
inherits = new Set();
|
|
2397
|
-
TypeContext.inheritedTypes.set(parent, inherits);
|
|
2398
|
-
}
|
|
2399
|
-
inherits.add(target);
|
|
2400
|
-
}
|
|
2401
|
-
}
|
|
2402
|
-
constructor(rootClass) {
|
|
2403
|
-
this.types = {};
|
|
2404
|
-
this.schemas = new Map();
|
|
2405
|
-
this.hasFilters = false;
|
|
2406
|
-
this.parentFiltered = {};
|
|
2407
|
-
if (rootClass) {
|
|
2408
|
-
this.discoverTypes(rootClass);
|
|
2409
|
-
}
|
|
2410
|
-
}
|
|
2411
|
-
has(schema) {
|
|
2412
|
-
return this.schemas.has(schema);
|
|
2413
|
-
}
|
|
2414
|
-
get(typeid) {
|
|
2415
|
-
return this.types[typeid];
|
|
2416
|
-
}
|
|
2417
|
-
add(schema, typeid = this.schemas.size) {
|
|
2418
|
-
// skip if already registered
|
|
2419
|
-
if (this.schemas.has(schema)) {
|
|
2420
|
-
return false;
|
|
2421
|
-
}
|
|
2422
|
-
this.types[typeid] = schema;
|
|
2423
|
-
//
|
|
2424
|
-
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
2425
|
-
//
|
|
2426
|
-
if (schema[Symbol.metadata] === undefined) {
|
|
2427
|
-
Metadata.init(schema);
|
|
2428
|
-
}
|
|
2429
|
-
this.schemas.set(schema, typeid);
|
|
2430
|
-
return true;
|
|
2431
|
-
}
|
|
2432
|
-
getTypeId(klass) {
|
|
2433
|
-
return this.schemas.get(klass);
|
|
2434
|
-
}
|
|
2435
|
-
discoverTypes(klass, parentIndex, parentFieldViewTag) {
|
|
2436
|
-
if (!this.add(klass)) {
|
|
2437
|
-
return;
|
|
2438
|
-
}
|
|
2439
|
-
// add classes inherited from this base class
|
|
2440
|
-
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
2441
|
-
this.discoverTypes(child, parentIndex, parentFieldViewTag);
|
|
2442
|
-
});
|
|
2443
|
-
const metadata = (klass[Symbol.metadata] ??= {});
|
|
2444
|
-
// if any schema/field has filters, mark "context" as having filters.
|
|
2445
|
-
if (metadata[-2]) {
|
|
2446
|
-
this.hasFilters = true;
|
|
2447
|
-
}
|
|
2448
|
-
if (parentFieldViewTag !== undefined) {
|
|
2449
|
-
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
2450
|
-
}
|
|
2451
|
-
for (const fieldIndex in metadata) {
|
|
2452
|
-
const index = fieldIndex;
|
|
2453
|
-
const fieldType = metadata[index].type;
|
|
2454
|
-
const viewTag = metadata[index].tag;
|
|
2455
|
-
if (typeof (fieldType) === "string") {
|
|
2456
|
-
continue;
|
|
2457
|
-
}
|
|
2458
|
-
if (Array.isArray(fieldType)) {
|
|
2459
|
-
const type = fieldType[0];
|
|
2460
|
-
// skip primitive types
|
|
2461
|
-
if (type === "string") {
|
|
2462
|
-
continue;
|
|
2463
|
-
}
|
|
2464
|
-
this.discoverTypes(type, index, viewTag);
|
|
2465
|
-
}
|
|
2466
|
-
else if (typeof (fieldType) === "function") {
|
|
2467
|
-
this.discoverTypes(fieldType, viewTag);
|
|
2468
|
-
}
|
|
2469
|
-
else {
|
|
2470
|
-
const type = Object.values(fieldType)[0];
|
|
2471
|
-
// skip primitive types
|
|
2472
|
-
if (typeof (type) === "string") {
|
|
2473
|
-
continue;
|
|
2474
|
-
}
|
|
2475
|
-
this.discoverTypes(type, index, viewTag);
|
|
2476
|
-
}
|
|
2477
|
-
}
|
|
2478
|
-
}
|
|
2479
|
-
}
|
|
2480
|
-
|
|
2481
2603
|
const DEFAULT_VIEW_TAG = -1;
|
|
2482
2604
|
/**
|
|
2483
2605
|
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
@@ -2505,8 +2627,8 @@
|
|
|
2505
2627
|
// // detect index for this field, considering inheritance
|
|
2506
2628
|
// //
|
|
2507
2629
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2508
|
-
// let fieldIndex: number = context.metadata[
|
|
2509
|
-
// ?? (parent && parent[
|
|
2630
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2631
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2510
2632
|
// ?? -1; // no fields defined
|
|
2511
2633
|
// fieldIndex++;
|
|
2512
2634
|
// if (
|
|
@@ -2635,8 +2757,8 @@
|
|
|
2635
2757
|
// //
|
|
2636
2758
|
// metadata[fieldIndex] = {
|
|
2637
2759
|
// type: undefined,
|
|
2638
|
-
// index: (metadata[
|
|
2639
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2760
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2761
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2640
2762
|
// ?? -1) + 1 // no fields defined
|
|
2641
2763
|
// }
|
|
2642
2764
|
// }
|
|
@@ -2679,8 +2801,8 @@
|
|
|
2679
2801
|
//
|
|
2680
2802
|
// detect index for this field, considering inheritance
|
|
2681
2803
|
//
|
|
2682
|
-
fieldIndex = metadata[
|
|
2683
|
-
?? (parentMetadata && parentMetadata[
|
|
2804
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2805
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2684
2806
|
?? -1; // no fields defined
|
|
2685
2807
|
fieldIndex++;
|
|
2686
2808
|
}
|
|
@@ -2707,7 +2829,7 @@
|
|
|
2707
2829
|
return {
|
|
2708
2830
|
get: function () { return this[fieldCached]; },
|
|
2709
2831
|
set: function (value) {
|
|
2710
|
-
const previousValue = this[fieldCached]
|
|
2832
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2711
2833
|
// skip if value is the same as cached.
|
|
2712
2834
|
if (value === previousValue) {
|
|
2713
2835
|
return;
|
|
@@ -2779,8 +2901,8 @@
|
|
|
2779
2901
|
// //
|
|
2780
2902
|
// metadata[field] = {
|
|
2781
2903
|
// type: undefined,
|
|
2782
|
-
// index: (metadata[
|
|
2783
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2904
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2905
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2784
2906
|
// ?? -1) + 1 // no fields defined
|
|
2785
2907
|
// }
|
|
2786
2908
|
// }
|
|
@@ -2818,15 +2940,18 @@
|
|
|
2818
2940
|
ops: {},
|
|
2819
2941
|
refs: []
|
|
2820
2942
|
};
|
|
2821
|
-
$root.changes
|
|
2943
|
+
// for (const refId in $root.changes) {
|
|
2944
|
+
$root.changes.forEach(changeTree => {
|
|
2945
|
+
const changes = changeTree.indexedOperations;
|
|
2822
2946
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2823
|
-
|
|
2947
|
+
for (const index in changes) {
|
|
2948
|
+
const op = changes[index];
|
|
2824
2949
|
const opName = exports.OPERATION[op];
|
|
2825
2950
|
if (!dump.ops[opName]) {
|
|
2826
2951
|
dump.ops[opName] = 0;
|
|
2827
2952
|
}
|
|
2828
2953
|
dump.ops[exports.OPERATION[op]]++;
|
|
2829
|
-
}
|
|
2954
|
+
}
|
|
2830
2955
|
});
|
|
2831
2956
|
return dump;
|
|
2832
2957
|
}
|
|
@@ -2852,6 +2977,7 @@
|
|
|
2852
2977
|
class Schema {
|
|
2853
2978
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2854
2979
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
2980
|
+
// public [$changes]: ChangeTree;
|
|
2855
2981
|
/**
|
|
2856
2982
|
* Assign the property descriptors required to track changes on this instance.
|
|
2857
2983
|
* @param instance
|
|
@@ -2911,12 +3037,7 @@
|
|
|
2911
3037
|
// inline
|
|
2912
3038
|
// Schema.initialize(this);
|
|
2913
3039
|
//
|
|
2914
|
-
|
|
2915
|
-
value: new ChangeTree(this),
|
|
2916
|
-
enumerable: false,
|
|
2917
|
-
writable: true
|
|
2918
|
-
});
|
|
2919
|
-
Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
3040
|
+
Schema.initialize(this);
|
|
2920
3041
|
//
|
|
2921
3042
|
// Assign initial values
|
|
2922
3043
|
//
|
|
@@ -2990,7 +3111,7 @@
|
|
|
2990
3111
|
const changeTree = ref[$changes];
|
|
2991
3112
|
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2992
3113
|
let output = "";
|
|
2993
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
3114
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
2994
3115
|
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
2995
3116
|
return output;
|
|
2996
3117
|
}
|
|
@@ -3008,18 +3129,26 @@
|
|
|
3008
3129
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3009
3130
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3010
3131
|
function dumpChangeSet(changeSet) {
|
|
3011
|
-
|
|
3012
|
-
.
|
|
3013
|
-
.forEach((
|
|
3132
|
+
changeSet.operations
|
|
3133
|
+
.filter(op => op)
|
|
3134
|
+
.forEach((index) => {
|
|
3135
|
+
const operation = changeTree.indexedOperations[index];
|
|
3136
|
+
console.log({ index, operation });
|
|
3137
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3138
|
+
});
|
|
3014
3139
|
}
|
|
3015
3140
|
dumpChangeSet(changeSet);
|
|
3016
3141
|
// display filtered changes
|
|
3017
|
-
if (!isEncodeAll &&
|
|
3142
|
+
if (!isEncodeAll &&
|
|
3143
|
+
changeTree.filteredChanges &&
|
|
3144
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3018
3145
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3019
3146
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3020
3147
|
}
|
|
3021
3148
|
// display filtered changes
|
|
3022
|
-
if (isEncodeAll &&
|
|
3149
|
+
if (isEncodeAll &&
|
|
3150
|
+
changeTree.allFilteredChanges &&
|
|
3151
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3023
3152
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
3024
3153
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3025
3154
|
}
|
|
@@ -3028,10 +3157,12 @@
|
|
|
3028
3157
|
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
3029
3158
|
let output = "";
|
|
3030
3159
|
const rootChangeTree = ref[$changes];
|
|
3160
|
+
const root = rootChangeTree.root;
|
|
3031
3161
|
const changeTrees = new Map();
|
|
3032
3162
|
let totalInstances = 0;
|
|
3033
3163
|
let totalOperations = 0;
|
|
3034
|
-
for (const [
|
|
3164
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3165
|
+
const changeTree = root.changeTrees[refId];
|
|
3035
3166
|
let includeChangeTree = false;
|
|
3036
3167
|
let parentChangeTrees = [];
|
|
3037
3168
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3050,7 +3181,7 @@
|
|
|
3050
3181
|
}
|
|
3051
3182
|
if (includeChangeTree) {
|
|
3052
3183
|
totalInstances += 1;
|
|
3053
|
-
totalOperations += changes.
|
|
3184
|
+
totalOperations += Object.keys(changes).length;
|
|
3054
3185
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3055
3186
|
}
|
|
3056
3187
|
}
|
|
@@ -3068,12 +3199,13 @@
|
|
|
3068
3199
|
visitedParents.add(parentChangeTree);
|
|
3069
3200
|
}
|
|
3070
3201
|
});
|
|
3071
|
-
const changes = changeTree.
|
|
3202
|
+
const changes = changeTree.indexedOperations;
|
|
3072
3203
|
const level = parentChangeTrees.length;
|
|
3073
3204
|
const indent = getIndent(level);
|
|
3074
3205
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3075
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3076
|
-
for (const
|
|
3206
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3207
|
+
for (const index in changes) {
|
|
3208
|
+
const operation = changes[index];
|
|
3077
3209
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3078
3210
|
}
|
|
3079
3211
|
}
|
|
@@ -3434,59 +3566,90 @@
|
|
|
3434
3566
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3435
3567
|
};
|
|
3436
3568
|
|
|
3569
|
+
function spliceOne(arr, index) {
|
|
3570
|
+
// manually splice an array
|
|
3571
|
+
if (index === -1 || index >= arr.length) {
|
|
3572
|
+
return false;
|
|
3573
|
+
}
|
|
3574
|
+
const len = arr.length - 1;
|
|
3575
|
+
for (let i = index; i < len; i++) {
|
|
3576
|
+
arr[i] = arr[i + 1];
|
|
3577
|
+
}
|
|
3578
|
+
arr.length = len;
|
|
3579
|
+
return true;
|
|
3580
|
+
}
|
|
3581
|
+
|
|
3437
3582
|
class Root {
|
|
3438
3583
|
constructor(types) {
|
|
3439
3584
|
this.types = types;
|
|
3440
3585
|
this.nextUniqueId = 0;
|
|
3441
|
-
this.refCount =
|
|
3586
|
+
this.refCount = {};
|
|
3587
|
+
this.changeTrees = {};
|
|
3442
3588
|
// all changes
|
|
3443
|
-
this.allChanges =
|
|
3444
|
-
this.allFilteredChanges =
|
|
3589
|
+
this.allChanges = [];
|
|
3590
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3445
3591
|
// pending changes to be encoded
|
|
3446
|
-
this.changes =
|
|
3447
|
-
this.filteredChanges =
|
|
3592
|
+
this.changes = [];
|
|
3593
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3448
3594
|
}
|
|
3449
3595
|
getNextUniqueId() {
|
|
3450
3596
|
return this.nextUniqueId++;
|
|
3451
3597
|
}
|
|
3452
3598
|
add(changeTree) {
|
|
3453
|
-
|
|
3599
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3600
|
+
changeTree.ensureRefId();
|
|
3601
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3602
|
+
if (isNewChangeTree) {
|
|
3603
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3604
|
+
}
|
|
3605
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3454
3606
|
if (previousRefCount === 0) {
|
|
3455
3607
|
//
|
|
3456
3608
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3457
3609
|
// We need to re-add all changes to the `changes` map.
|
|
3458
3610
|
//
|
|
3459
|
-
changeTree.allChanges.
|
|
3460
|
-
|
|
3461
|
-
|
|
3611
|
+
const ops = changeTree.allChanges.operations;
|
|
3612
|
+
let len = ops.length;
|
|
3613
|
+
while (len--) {
|
|
3614
|
+
changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
|
|
3615
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3616
|
+
}
|
|
3462
3617
|
}
|
|
3463
|
-
|
|
3464
|
-
|
|
3465
|
-
return refCount;
|
|
3618
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3619
|
+
return isNewChangeTree;
|
|
3466
3620
|
}
|
|
3467
3621
|
remove(changeTree) {
|
|
3468
|
-
const refCount = (this.refCount.
|
|
3622
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3469
3623
|
if (refCount <= 0) {
|
|
3470
3624
|
//
|
|
3471
3625
|
// Only remove "root" reference if it's the last reference
|
|
3472
3626
|
//
|
|
3473
3627
|
changeTree.root = undefined;
|
|
3474
|
-
this.
|
|
3475
|
-
this.
|
|
3628
|
+
delete this.changeTrees[changeTree.refId];
|
|
3629
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3630
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3476
3631
|
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3477
|
-
this.allFilteredChanges
|
|
3478
|
-
this.filteredChanges
|
|
3632
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3633
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3479
3634
|
}
|
|
3480
|
-
this.refCount.
|
|
3635
|
+
this.refCount[changeTree.refId] = 0;
|
|
3481
3636
|
}
|
|
3482
3637
|
else {
|
|
3483
|
-
this.refCount.
|
|
3638
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3484
3639
|
}
|
|
3485
3640
|
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3486
3641
|
return refCount;
|
|
3487
3642
|
}
|
|
3643
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3644
|
+
const changeSet = this[changeSetName];
|
|
3645
|
+
const index = changeSet.indexOf(changeTree);
|
|
3646
|
+
if (index !== -1) {
|
|
3647
|
+
spliceOne(changeSet, index);
|
|
3648
|
+
// changeSet[index] = undefined;
|
|
3649
|
+
}
|
|
3650
|
+
}
|
|
3488
3651
|
clear() {
|
|
3489
|
-
this.changes.
|
|
3652
|
+
this.changes.length = 0;
|
|
3490
3653
|
}
|
|
3491
3654
|
}
|
|
3492
3655
|
|
|
@@ -3510,20 +3673,26 @@
|
|
|
3510
3673
|
this.state = state;
|
|
3511
3674
|
this.state[$changes].setRoot(this.root);
|
|
3512
3675
|
}
|
|
3513
|
-
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer,
|
|
3676
|
+
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
|
|
3514
3677
|
) {
|
|
3515
3678
|
const hasView = (view !== undefined);
|
|
3516
3679
|
const rootChangeTree = this.state[$changes];
|
|
3517
|
-
const
|
|
3518
|
-
|
|
3680
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3681
|
+
const changeTrees = this.root[changeSetName];
|
|
3682
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3683
|
+
const changeTree = changeTrees[i];
|
|
3684
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
3685
|
+
// if (changeTree === undefined) { continue; }
|
|
3686
|
+
const operations = changeTree[changeSetName];
|
|
3519
3687
|
const ref = changeTree.ref;
|
|
3520
3688
|
const ctor = ref.constructor;
|
|
3521
3689
|
const encoder = ctor[$encoder];
|
|
3522
3690
|
const filter = ctor[$filter];
|
|
3691
|
+
const metadata = ctor[Symbol.metadata];
|
|
3523
3692
|
// try { throw new Error(); } catch (e) {
|
|
3524
3693
|
// // only print if not coming from Reflection.ts
|
|
3525
3694
|
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3526
|
-
// console.log("ChangeTree:", { ref: ref.constructor.name
|
|
3695
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
3527
3696
|
// }
|
|
3528
3697
|
// }
|
|
3529
3698
|
if (hasView) {
|
|
@@ -3541,7 +3710,13 @@
|
|
|
3541
3710
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3542
3711
|
number$1(buffer, changeTree.refId, it);
|
|
3543
3712
|
}
|
|
3544
|
-
for (
|
|
3713
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3714
|
+
const fieldIndex = operations.operations[j];
|
|
3715
|
+
const operation = (fieldIndex < 0)
|
|
3716
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3717
|
+
: (isEncodeAll)
|
|
3718
|
+
? exports.OPERATION.ADD
|
|
3719
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3545
3720
|
//
|
|
3546
3721
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3547
3722
|
// they will be encoded per client, based on their view.
|
|
@@ -3549,7 +3724,7 @@
|
|
|
3549
3724
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3550
3725
|
// TODO: avoid checking if no view tags were defined
|
|
3551
3726
|
//
|
|
3552
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3727
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3553
3728
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3554
3729
|
// view?.invisible.add(changeTree);
|
|
3555
3730
|
continue;
|
|
@@ -3564,16 +3739,14 @@
|
|
|
3564
3739
|
// });
|
|
3565
3740
|
// }
|
|
3566
3741
|
// }
|
|
3567
|
-
|
|
3742
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
3743
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3744
|
+
}
|
|
3745
|
+
if (shouldDiscardChanges) {
|
|
3746
|
+
changeTree.discard();
|
|
3747
|
+
// Not a new instance anymore
|
|
3748
|
+
changeTree.isNew = false;
|
|
3568
3749
|
}
|
|
3569
|
-
// if (shouldClearChanges) {
|
|
3570
|
-
// // changeTree.endEncode();
|
|
3571
|
-
// changeTree.changes.clear();
|
|
3572
|
-
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3573
|
-
// changeTree.ref[$onEncodeEnd]?.();
|
|
3574
|
-
// // Not a new instance anymore
|
|
3575
|
-
// delete changeTree[$isNew];
|
|
3576
|
-
// }
|
|
3577
3750
|
}
|
|
3578
3751
|
if (it.offset > buffer.byteLength) {
|
|
3579
3752
|
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
@@ -3590,51 +3763,54 @@
|
|
|
3590
3763
|
if (buffer === this.sharedBuffer) {
|
|
3591
3764
|
this.sharedBuffer = buffer;
|
|
3592
3765
|
}
|
|
3593
|
-
return this.encode({ offset: initialOffset }, view, buffer,
|
|
3766
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3594
3767
|
}
|
|
3595
3768
|
else {
|
|
3596
|
-
//
|
|
3597
|
-
// only clear changes after making sure buffer resize is not required.
|
|
3598
|
-
//
|
|
3599
|
-
if (shouldClearChanges) {
|
|
3600
|
-
|
|
3601
|
-
|
|
3602
|
-
|
|
3603
|
-
|
|
3604
|
-
}
|
|
3769
|
+
// //
|
|
3770
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
3771
|
+
// //
|
|
3772
|
+
// if (shouldClearChanges) {
|
|
3773
|
+
// //
|
|
3774
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
3775
|
+
// //
|
|
3776
|
+
// this.onEndEncode(changeTrees);
|
|
3777
|
+
// }
|
|
3605
3778
|
return buffer.subarray(0, it.offset);
|
|
3606
3779
|
}
|
|
3607
3780
|
}
|
|
3608
3781
|
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3609
|
-
// console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.
|
|
3782
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
3610
3783
|
// this.debugChanges("allChanges");
|
|
3611
|
-
return this.encode(it, undefined, buffer,
|
|
3784
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3612
3785
|
}
|
|
3613
3786
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3614
3787
|
const viewOffset = it.offset;
|
|
3615
|
-
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.
|
|
3788
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
3616
3789
|
// this.debugChanges("allFilteredChanges");
|
|
3790
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
3617
3791
|
// try to encode "filtered" changes
|
|
3618
|
-
this.encode(it, view, bytes,
|
|
3792
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3619
3793
|
return Buffer.concat([
|
|
3620
3794
|
bytes.subarray(0, sharedOffset),
|
|
3621
3795
|
bytes.subarray(viewOffset, it.offset)
|
|
3622
3796
|
]);
|
|
3623
3797
|
}
|
|
3624
3798
|
debugChanges(field) {
|
|
3625
|
-
const
|
|
3799
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3626
3800
|
? this.root[field]
|
|
3627
3801
|
: field;
|
|
3628
|
-
|
|
3629
|
-
const
|
|
3630
|
-
|
|
3631
|
-
|
|
3802
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3803
|
+
const changeSet = changeTree[field];
|
|
3804
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3805
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3806
|
+
for (const index in changeSet) {
|
|
3807
|
+
const op = changeSet[index];
|
|
3632
3808
|
console.log(" ->", {
|
|
3633
3809
|
index,
|
|
3634
3810
|
field: metadata?.[index],
|
|
3635
3811
|
op: exports.OPERATION[op],
|
|
3636
3812
|
});
|
|
3637
|
-
}
|
|
3813
|
+
}
|
|
3638
3814
|
});
|
|
3639
3815
|
}
|
|
3640
3816
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
@@ -3644,23 +3820,31 @@
|
|
|
3644
3820
|
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3645
3821
|
// this.debugChanges("filteredChanges");
|
|
3646
3822
|
// encode visibility changes (add/remove for this view)
|
|
3647
|
-
const
|
|
3648
|
-
|
|
3649
|
-
|
|
3650
|
-
|
|
3651
|
-
|
|
3823
|
+
const refIds = Object.keys(view.changes);
|
|
3824
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
3825
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3826
|
+
const refId = refIds[i];
|
|
3827
|
+
const changes = view.changes[refId];
|
|
3828
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3829
|
+
if (changeTree === undefined ||
|
|
3830
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3831
|
+
) {
|
|
3832
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3652
3833
|
continue;
|
|
3653
3834
|
}
|
|
3654
3835
|
const ref = changeTree.ref;
|
|
3655
|
-
const ctor = ref
|
|
3836
|
+
const ctor = ref.constructor;
|
|
3656
3837
|
const encoder = ctor[$encoder];
|
|
3838
|
+
const metadata = ctor[Symbol.metadata];
|
|
3657
3839
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3658
3840
|
number$1(bytes, changeTree.refId, it);
|
|
3659
|
-
const
|
|
3660
|
-
for (
|
|
3841
|
+
const keys = Object.keys(changes);
|
|
3842
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3843
|
+
const key = keys[i];
|
|
3844
|
+
const operation = changes[key];
|
|
3661
3845
|
// isEncodeAll = false
|
|
3662
3846
|
// hasView = true
|
|
3663
|
-
encoder(this, bytes, changeTree,
|
|
3847
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3664
3848
|
}
|
|
3665
3849
|
}
|
|
3666
3850
|
//
|
|
@@ -3668,40 +3852,55 @@
|
|
|
3668
3852
|
// (to allow re-using StateView's for multiple clients)
|
|
3669
3853
|
//
|
|
3670
3854
|
// clear "view" changes after encoding
|
|
3671
|
-
view.changes
|
|
3855
|
+
view.changes = {};
|
|
3856
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
3672
3857
|
// try to encode "filtered" changes
|
|
3673
|
-
this.encode(it, view, bytes,
|
|
3858
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3674
3859
|
return Buffer.concat([
|
|
3675
3860
|
bytes.subarray(0, sharedOffset),
|
|
3676
3861
|
bytes.subarray(viewOffset, it.offset)
|
|
3677
3862
|
]);
|
|
3678
3863
|
}
|
|
3679
3864
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3685
|
-
|
|
3686
|
-
|
|
3687
|
-
|
|
3688
|
-
|
|
3865
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3866
|
+
// changeTree.endEncode();
|
|
3867
|
+
// });
|
|
3868
|
+
// for (const refId in changeTrees) {
|
|
3869
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3870
|
+
// changeTree.endEncode();
|
|
3871
|
+
// // changeTree.changes.clear();
|
|
3872
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3873
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3874
|
+
// // // Not a new instance anymore
|
|
3875
|
+
// // delete changeTree[$isNew];
|
|
3876
|
+
// }
|
|
3689
3877
|
}
|
|
3690
3878
|
discardChanges() {
|
|
3879
|
+
// console.log("DISCARD CHANGES!");
|
|
3691
3880
|
// discard shared changes
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3881
|
+
let length = this.root.changes.length;
|
|
3882
|
+
if (length > 0) {
|
|
3883
|
+
while (length--) {
|
|
3884
|
+
this.root.changes[length]?.endEncode();
|
|
3885
|
+
}
|
|
3886
|
+
this.root.changes.length = 0;
|
|
3695
3887
|
}
|
|
3696
3888
|
// discard filtered changes
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3889
|
+
length = this.root.filteredChanges.length;
|
|
3890
|
+
if (length > 0) {
|
|
3891
|
+
while (length--) {
|
|
3892
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3893
|
+
}
|
|
3894
|
+
this.root.filteredChanges.length = 0;
|
|
3700
3895
|
}
|
|
3701
3896
|
}
|
|
3702
3897
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
3703
3898
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
3704
3899
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
3900
|
+
if (targetTypeId === undefined) {
|
|
3901
|
+
console.warn(`@colyseus/schema WARNING: Class "${targetType.name}" is not registered on TypeRegistry - Please either tag the class with @entity or define a @type() field.`);
|
|
3902
|
+
return;
|
|
3903
|
+
}
|
|
3705
3904
|
if (baseTypeId !== targetTypeId) {
|
|
3706
3905
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
3707
3906
|
number$1(bytes, targetTypeId, it);
|
|
@@ -3709,19 +3908,6 @@
|
|
|
3709
3908
|
}
|
|
3710
3909
|
}
|
|
3711
3910
|
|
|
3712
|
-
function spliceOne(arr, index) {
|
|
3713
|
-
// manually splice an array
|
|
3714
|
-
if (index === -1 || index >= arr.length) {
|
|
3715
|
-
return false;
|
|
3716
|
-
}
|
|
3717
|
-
const len = arr.length - 1;
|
|
3718
|
-
for (let i = index; i < len; i++) {
|
|
3719
|
-
arr[i] = arr[i + 1];
|
|
3720
|
-
}
|
|
3721
|
-
arr.length = len;
|
|
3722
|
-
return true;
|
|
3723
|
-
}
|
|
3724
|
-
|
|
3725
3911
|
class DecodingWarning extends Error {
|
|
3726
3912
|
constructor(message) {
|
|
3727
3913
|
super(message);
|
|
@@ -3883,7 +4069,7 @@
|
|
|
3883
4069
|
}
|
|
3884
4070
|
ref[$onDecodeEnd]?.();
|
|
3885
4071
|
ref = nextRef;
|
|
3886
|
-
decoder = ref
|
|
4072
|
+
decoder = ref.constructor[$decoder];
|
|
3887
4073
|
continue;
|
|
3888
4074
|
}
|
|
3889
4075
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -3984,10 +4170,16 @@
|
|
|
3984
4170
|
super(...arguments);
|
|
3985
4171
|
this.types = new ArraySchema();
|
|
3986
4172
|
}
|
|
3987
|
-
|
|
3988
|
-
|
|
4173
|
+
/**
|
|
4174
|
+
* Encodes the TypeContext of an Encoder into a buffer.
|
|
4175
|
+
*
|
|
4176
|
+
* @param context TypeContext instance
|
|
4177
|
+
* @param it
|
|
4178
|
+
* @returns
|
|
4179
|
+
*/
|
|
4180
|
+
static encode(context, it = { offset: 0 }) {
|
|
3989
4181
|
const reflection = new Reflection();
|
|
3990
|
-
const
|
|
4182
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
3991
4183
|
const buildType = (currentType, metadata) => {
|
|
3992
4184
|
for (const fieldIndex in metadata) {
|
|
3993
4185
|
const index = Number(fieldIndex);
|
|
@@ -4041,9 +4233,16 @@
|
|
|
4041
4233
|
}
|
|
4042
4234
|
buildType(type, klass[Symbol.metadata]);
|
|
4043
4235
|
}
|
|
4044
|
-
const buf =
|
|
4236
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
4045
4237
|
return Buffer.from(buf, 0, it.offset);
|
|
4046
4238
|
}
|
|
4239
|
+
/**
|
|
4240
|
+
* Decodes the TypeContext from a buffer into a Decoder instance.
|
|
4241
|
+
*
|
|
4242
|
+
* @param bytes Reflection.encode() output
|
|
4243
|
+
* @param it
|
|
4244
|
+
* @returns Decoder instance
|
|
4245
|
+
*/
|
|
4047
4246
|
static decode(bytes, it) {
|
|
4048
4247
|
const reflection = new Reflection();
|
|
4049
4248
|
const reflectionDecoder = new Decoder(reflection);
|
|
@@ -4089,8 +4288,8 @@
|
|
|
4089
4288
|
}
|
|
4090
4289
|
});
|
|
4091
4290
|
});
|
|
4092
|
-
|
|
4093
|
-
return new (typeContext
|
|
4291
|
+
const state = new (typeContext.get(0))();
|
|
4292
|
+
return new Decoder(state, typeContext);
|
|
4094
4293
|
}
|
|
4095
4294
|
}
|
|
4096
4295
|
__decorate([
|
|
@@ -4360,7 +4559,7 @@
|
|
|
4360
4559
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
4361
4560
|
* (This is used to force encoding a property, even if it was not changed)
|
|
4362
4561
|
*/
|
|
4363
|
-
this.changes =
|
|
4562
|
+
this.changes = {};
|
|
4364
4563
|
}
|
|
4365
4564
|
// TODO: allow to set multiple tags at once
|
|
4366
4565
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
@@ -4382,10 +4581,10 @@
|
|
|
4382
4581
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
4383
4582
|
// be set (only the parent's changes are set)
|
|
4384
4583
|
//
|
|
4385
|
-
let changes = this.changes.
|
|
4584
|
+
let changes = this.changes[changeTree.refId];
|
|
4386
4585
|
if (changes === undefined) {
|
|
4387
|
-
changes =
|
|
4388
|
-
this.changes.
|
|
4586
|
+
changes = {};
|
|
4587
|
+
this.changes[changeTree.refId] = changes;
|
|
4389
4588
|
}
|
|
4390
4589
|
// set tag
|
|
4391
4590
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -4402,9 +4601,9 @@
|
|
|
4402
4601
|
}
|
|
4403
4602
|
tags.add(tag);
|
|
4404
4603
|
// Ref: add tagged properties
|
|
4405
|
-
metadata?.[
|
|
4604
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4406
4605
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
4407
|
-
changes
|
|
4606
|
+
changes[index] = exports.OPERATION.ADD;
|
|
4408
4607
|
}
|
|
4409
4608
|
});
|
|
4410
4609
|
}
|
|
@@ -4413,16 +4612,21 @@
|
|
|
4413
4612
|
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4414
4613
|
? changeTree.allFilteredChanges
|
|
4415
4614
|
: changeTree.allChanges;
|
|
4416
|
-
|
|
4615
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4616
|
+
const index = changeSet.operations[i];
|
|
4617
|
+
if (index === undefined) {
|
|
4618
|
+
continue;
|
|
4619
|
+
} // skip "undefined" indexes
|
|
4620
|
+
const op = changeTree.indexedOperations[index];
|
|
4417
4621
|
const tagAtIndex = metadata?.[index].tag;
|
|
4418
4622
|
if ((isInvisible || // if "invisible", include all
|
|
4419
4623
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4420
4624
|
tagAtIndex === tag // tagged property
|
|
4421
4625
|
) &&
|
|
4422
4626
|
op !== exports.OPERATION.DELETE) {
|
|
4423
|
-
changes
|
|
4627
|
+
changes[index] = op;
|
|
4424
4628
|
}
|
|
4425
|
-
}
|
|
4629
|
+
}
|
|
4426
4630
|
}
|
|
4427
4631
|
// Add children of this ChangeTree to this view
|
|
4428
4632
|
changeTree.forEachChild((change, index) => {
|
|
@@ -4448,10 +4652,10 @@
|
|
|
4448
4652
|
}
|
|
4449
4653
|
// add parent's tag properties
|
|
4450
4654
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4451
|
-
let changes = this.changes.
|
|
4655
|
+
let changes = this.changes[changeTree.refId];
|
|
4452
4656
|
if (changes === undefined) {
|
|
4453
|
-
changes =
|
|
4454
|
-
this.changes.
|
|
4657
|
+
changes = {};
|
|
4658
|
+
this.changes[changeTree.refId] = changes;
|
|
4455
4659
|
}
|
|
4456
4660
|
if (!this.tags) {
|
|
4457
4661
|
this.tags = new WeakMap();
|
|
@@ -4465,7 +4669,7 @@
|
|
|
4465
4669
|
tags = this.tags.get(changeTree);
|
|
4466
4670
|
}
|
|
4467
4671
|
tags.add(tag);
|
|
4468
|
-
changes
|
|
4672
|
+
changes[parentIndex] = exports.OPERATION.ADD;
|
|
4469
4673
|
}
|
|
4470
4674
|
}
|
|
4471
4675
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4477,32 +4681,32 @@
|
|
|
4477
4681
|
this.items.delete(changeTree);
|
|
4478
4682
|
const ref = changeTree.ref;
|
|
4479
4683
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4480
|
-
let changes = this.changes.
|
|
4684
|
+
let changes = this.changes[changeTree.refId];
|
|
4481
4685
|
if (changes === undefined) {
|
|
4482
|
-
changes =
|
|
4483
|
-
this.changes.
|
|
4686
|
+
changes = {};
|
|
4687
|
+
this.changes[changeTree.refId] = changes;
|
|
4484
4688
|
}
|
|
4485
4689
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4486
4690
|
// parent is collection (Map/Array)
|
|
4487
4691
|
const parent = changeTree.parent;
|
|
4488
4692
|
if (!Metadata.isValidInstance(parent)) {
|
|
4489
4693
|
const parentChangeTree = parent[$changes];
|
|
4490
|
-
let changes = this.changes.
|
|
4694
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4491
4695
|
if (changes === undefined) {
|
|
4492
|
-
changes =
|
|
4493
|
-
this.changes.
|
|
4696
|
+
changes = {};
|
|
4697
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4494
4698
|
}
|
|
4495
4699
|
// DELETE / DELETE BY REF ID
|
|
4496
|
-
changes
|
|
4700
|
+
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
4497
4701
|
}
|
|
4498
4702
|
else {
|
|
4499
4703
|
// delete all "tagged" properties.
|
|
4500
|
-
metadata[
|
|
4704
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4501
4705
|
}
|
|
4502
4706
|
}
|
|
4503
4707
|
else {
|
|
4504
4708
|
// delete only tagged properties
|
|
4505
|
-
metadata[
|
|
4709
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4506
4710
|
}
|
|
4507
4711
|
// remove tag
|
|
4508
4712
|
if (this.tags && this.tags.has(changeTree)) {
|