@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/cjs/index.js
CHANGED
|
@@ -36,7 +36,6 @@ const $decoder = Symbol("$decoder");
|
|
|
36
36
|
const $filter = Symbol("$filter");
|
|
37
37
|
const $getByIndex = Symbol("$getByIndex");
|
|
38
38
|
const $deleteByIndex = Symbol("$deleteByIndex");
|
|
39
|
-
const $descriptors = Symbol("$descriptors");
|
|
40
39
|
/**
|
|
41
40
|
* Used to hold ChangeTree instances whitin the structures
|
|
42
41
|
*/
|
|
@@ -46,11 +45,6 @@ const $changes = Symbol('$changes');
|
|
|
46
45
|
* (MapSchema, ArraySchema, etc.)
|
|
47
46
|
*/
|
|
48
47
|
const $childType = Symbol('$childType');
|
|
49
|
-
/**
|
|
50
|
-
* Special ChangeTree property to identify new instances
|
|
51
|
-
* (Once they're encoded, they're not new anymore)
|
|
52
|
-
*/
|
|
53
|
-
const $isNew = Symbol("$isNew");
|
|
54
48
|
/**
|
|
55
49
|
* Optional "discard" method for custom types (ArraySchema)
|
|
56
50
|
* (Discards changes for next serialization)
|
|
@@ -60,6 +54,14 @@ const $onEncodeEnd = Symbol('$onEncodeEnd');
|
|
|
60
54
|
* When decoding, this method is called after the instance is fully decoded
|
|
61
55
|
*/
|
|
62
56
|
const $onDecodeEnd = Symbol("$onDecodeEnd");
|
|
57
|
+
/**
|
|
58
|
+
* Metadata
|
|
59
|
+
*/
|
|
60
|
+
const $descriptors = Symbol("$descriptors");
|
|
61
|
+
const $numFields = "$__numFields";
|
|
62
|
+
const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
|
|
63
|
+
const $viewFieldIndexes = "$__viewFieldIndexes";
|
|
64
|
+
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
63
65
|
|
|
64
66
|
const registeredTypes = {};
|
|
65
67
|
const identifiers = new Map();
|
|
@@ -71,6 +73,102 @@ function getType(identifier) {
|
|
|
71
73
|
return registeredTypes[identifier];
|
|
72
74
|
}
|
|
73
75
|
|
|
76
|
+
class TypeContext {
|
|
77
|
+
/**
|
|
78
|
+
* For inheritance support
|
|
79
|
+
* Keeps track of which classes extends which. (parent -> children)
|
|
80
|
+
*/
|
|
81
|
+
static { this.inheritedTypes = new Map(); }
|
|
82
|
+
static register(target) {
|
|
83
|
+
const parent = Object.getPrototypeOf(target);
|
|
84
|
+
if (parent !== Schema) {
|
|
85
|
+
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
86
|
+
if (!inherits) {
|
|
87
|
+
inherits = new Set();
|
|
88
|
+
TypeContext.inheritedTypes.set(parent, inherits);
|
|
89
|
+
}
|
|
90
|
+
inherits.add(target);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
constructor(rootClass) {
|
|
94
|
+
this.types = {};
|
|
95
|
+
this.schemas = new Map();
|
|
96
|
+
this.hasFilters = false;
|
|
97
|
+
this.parentFiltered = {};
|
|
98
|
+
if (rootClass) {
|
|
99
|
+
this.discoverTypes(rootClass);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
has(schema) {
|
|
103
|
+
return this.schemas.has(schema);
|
|
104
|
+
}
|
|
105
|
+
get(typeid) {
|
|
106
|
+
return this.types[typeid];
|
|
107
|
+
}
|
|
108
|
+
add(schema, typeid = this.schemas.size) {
|
|
109
|
+
// skip if already registered
|
|
110
|
+
if (this.schemas.has(schema)) {
|
|
111
|
+
return false;
|
|
112
|
+
}
|
|
113
|
+
this.types[typeid] = schema;
|
|
114
|
+
//
|
|
115
|
+
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
116
|
+
//
|
|
117
|
+
if (schema[Symbol.metadata] === undefined) {
|
|
118
|
+
Metadata.init(schema);
|
|
119
|
+
}
|
|
120
|
+
this.schemas.set(schema, typeid);
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
getTypeId(klass) {
|
|
124
|
+
return this.schemas.get(klass);
|
|
125
|
+
}
|
|
126
|
+
discoverTypes(klass, parentIndex, parentFieldViewTag) {
|
|
127
|
+
if (!this.add(klass)) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
// add classes inherited from this base class
|
|
131
|
+
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
132
|
+
this.discoverTypes(child, parentIndex, parentFieldViewTag);
|
|
133
|
+
});
|
|
134
|
+
const metadata = (klass[Symbol.metadata] ??= {});
|
|
135
|
+
// if any schema/field has filters, mark "context" as having filters.
|
|
136
|
+
if (metadata[$viewFieldIndexes]) {
|
|
137
|
+
this.hasFilters = true;
|
|
138
|
+
}
|
|
139
|
+
if (parentFieldViewTag !== undefined) {
|
|
140
|
+
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
141
|
+
}
|
|
142
|
+
for (const fieldIndex in metadata) {
|
|
143
|
+
const index = fieldIndex;
|
|
144
|
+
const fieldType = metadata[index].type;
|
|
145
|
+
const viewTag = metadata[index].tag;
|
|
146
|
+
if (typeof (fieldType) === "string") {
|
|
147
|
+
continue;
|
|
148
|
+
}
|
|
149
|
+
if (Array.isArray(fieldType)) {
|
|
150
|
+
const type = fieldType[0];
|
|
151
|
+
// skip primitive types
|
|
152
|
+
if (type === "string") {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
this.discoverTypes(type, index, viewTag);
|
|
156
|
+
}
|
|
157
|
+
else if (typeof (fieldType) === "function") {
|
|
158
|
+
this.discoverTypes(fieldType, viewTag);
|
|
159
|
+
}
|
|
160
|
+
else {
|
|
161
|
+
const type = Object.values(fieldType)[0];
|
|
162
|
+
// skip primitive types
|
|
163
|
+
if (typeof (type) === "string") {
|
|
164
|
+
continue;
|
|
165
|
+
}
|
|
166
|
+
this.discoverTypes(type, index, viewTag);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
|
|
74
172
|
const Metadata = {
|
|
75
173
|
addField(metadata, index, name, type, descriptor) {
|
|
76
174
|
if (index > 64) {
|
|
@@ -106,7 +204,7 @@ const Metadata = {
|
|
|
106
204
|
};
|
|
107
205
|
}
|
|
108
206
|
// map -1 as last field index
|
|
109
|
-
Object.defineProperty(metadata,
|
|
207
|
+
Object.defineProperty(metadata, $numFields, {
|
|
110
208
|
value: index,
|
|
111
209
|
enumerable: false,
|
|
112
210
|
configurable: true
|
|
@@ -119,14 +217,14 @@ const Metadata = {
|
|
|
119
217
|
});
|
|
120
218
|
// if child Ref/complex type, add to -4
|
|
121
219
|
if (typeof (metadata[index].type) !== "string") {
|
|
122
|
-
if (metadata[
|
|
123
|
-
Object.defineProperty(metadata,
|
|
220
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
221
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
124
222
|
value: [],
|
|
125
223
|
enumerable: false,
|
|
126
224
|
configurable: true,
|
|
127
225
|
});
|
|
128
226
|
}
|
|
129
|
-
metadata[
|
|
227
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
130
228
|
}
|
|
131
229
|
},
|
|
132
230
|
setTag(metadata, fieldName, tag) {
|
|
@@ -134,38 +232,63 @@ const Metadata = {
|
|
|
134
232
|
const field = metadata[index];
|
|
135
233
|
// add 'tag' to the field
|
|
136
234
|
field.tag = tag;
|
|
137
|
-
if (!metadata[
|
|
235
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
138
236
|
// -2: all field indexes with "view" tag
|
|
139
|
-
Object.defineProperty(metadata,
|
|
237
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
140
238
|
value: [],
|
|
141
239
|
enumerable: false,
|
|
142
240
|
configurable: true
|
|
143
241
|
});
|
|
144
242
|
// -3: field indexes by "view" tag
|
|
145
|
-
Object.defineProperty(metadata,
|
|
243
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
146
244
|
value: {},
|
|
147
245
|
enumerable: false,
|
|
148
246
|
configurable: true
|
|
149
247
|
});
|
|
150
248
|
}
|
|
151
|
-
metadata[
|
|
152
|
-
if (!metadata[
|
|
153
|
-
metadata[
|
|
249
|
+
metadata[$viewFieldIndexes].push(index);
|
|
250
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
251
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
154
252
|
}
|
|
155
|
-
metadata[
|
|
253
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
156
254
|
},
|
|
157
255
|
setFields(target, fields) {
|
|
158
|
-
|
|
159
|
-
|
|
256
|
+
// for inheritance support
|
|
257
|
+
const constructor = target.prototype.constructor;
|
|
258
|
+
TypeContext.register(constructor);
|
|
259
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
260
|
+
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
261
|
+
const metadata = Metadata.initialize(constructor, parentMetadata);
|
|
262
|
+
// Use Schema's methods if not defined in the class
|
|
263
|
+
if (!constructor[$track]) {
|
|
264
|
+
constructor[$track] = Schema[$track];
|
|
265
|
+
}
|
|
266
|
+
if (!constructor[$encoder]) {
|
|
267
|
+
constructor[$encoder] = Schema[$encoder];
|
|
268
|
+
}
|
|
269
|
+
if (!constructor[$decoder]) {
|
|
270
|
+
constructor[$decoder] = Schema[$decoder];
|
|
271
|
+
}
|
|
272
|
+
if (!constructor.prototype.toJSON) {
|
|
273
|
+
constructor.prototype.toJSON = Schema.prototype.toJSON;
|
|
274
|
+
}
|
|
275
|
+
//
|
|
276
|
+
// detect index for this field, considering inheritance
|
|
277
|
+
//
|
|
278
|
+
let fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
279
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
280
|
+
?? -1; // no fields defined
|
|
281
|
+
fieldIndex++;
|
|
160
282
|
for (const field in fields) {
|
|
161
283
|
const type = fields[field];
|
|
162
284
|
// FIXME: this code is duplicated from @type() annotation
|
|
163
285
|
const complexTypeKlass = (Array.isArray(type))
|
|
164
286
|
? getType("array")
|
|
165
287
|
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
166
|
-
Metadata.addField(metadata,
|
|
167
|
-
|
|
288
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, type, complexTypeKlass));
|
|
289
|
+
fieldIndex++;
|
|
168
290
|
}
|
|
291
|
+
return target;
|
|
169
292
|
},
|
|
170
293
|
isDeprecated(metadata, field) {
|
|
171
294
|
return metadata[field].deprecated === true;
|
|
@@ -177,7 +300,7 @@ const Metadata = {
|
|
|
177
300
|
//
|
|
178
301
|
const metadata = {};
|
|
179
302
|
klass[Symbol.metadata] = metadata;
|
|
180
|
-
Object.defineProperty(metadata,
|
|
303
|
+
Object.defineProperty(metadata, $numFields, {
|
|
181
304
|
value: 0,
|
|
182
305
|
enumerable: false,
|
|
183
306
|
configurable: true,
|
|
@@ -191,7 +314,7 @@ const Metadata = {
|
|
|
191
314
|
if (parentMetadata) {
|
|
192
315
|
// assign parent metadata to current
|
|
193
316
|
Object.assign(metadata, parentMetadata);
|
|
194
|
-
for (let i = 0; i <= parentMetadata[
|
|
317
|
+
for (let i = 0; i <= parentMetadata[$numFields]; i++) {
|
|
195
318
|
const fieldName = parentMetadata[i].name;
|
|
196
319
|
Object.defineProperty(metadata, fieldName, {
|
|
197
320
|
value: parentMetadata[fieldName],
|
|
@@ -199,8 +322,8 @@ const Metadata = {
|
|
|
199
322
|
configurable: true,
|
|
200
323
|
});
|
|
201
324
|
}
|
|
202
|
-
Object.defineProperty(metadata,
|
|
203
|
-
value: parentMetadata[
|
|
325
|
+
Object.defineProperty(metadata, $numFields, {
|
|
326
|
+
value: parentMetadata[$numFields],
|
|
204
327
|
enumerable: false,
|
|
205
328
|
configurable: true,
|
|
206
329
|
writable: true,
|
|
@@ -212,40 +335,69 @@ const Metadata = {
|
|
|
212
335
|
},
|
|
213
336
|
isValidInstance(klass) {
|
|
214
337
|
return (klass.constructor[Symbol.metadata] &&
|
|
215
|
-
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata],
|
|
338
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
216
339
|
},
|
|
217
340
|
getFields(klass) {
|
|
218
341
|
const metadata = klass[Symbol.metadata];
|
|
219
342
|
const fields = {};
|
|
220
|
-
for (let i = 0; i <= metadata[
|
|
343
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
221
344
|
fields[metadata[i].name] = metadata[i].type;
|
|
222
345
|
}
|
|
223
346
|
return fields;
|
|
224
347
|
}
|
|
225
348
|
};
|
|
226
349
|
|
|
227
|
-
|
|
350
|
+
function setOperationAtIndex(changeSet, index) {
|
|
351
|
+
const operationsIndex = changeSet.indexes[index];
|
|
352
|
+
if (operationsIndex === undefined) {
|
|
353
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
354
|
+
}
|
|
355
|
+
else {
|
|
356
|
+
changeSet.operations[operationsIndex] = index;
|
|
357
|
+
}
|
|
358
|
+
}
|
|
359
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
360
|
+
const operationsIndex = changeSet.indexes[index];
|
|
361
|
+
if (operationsIndex !== undefined) {
|
|
362
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
363
|
+
}
|
|
364
|
+
delete changeSet.indexes[index];
|
|
365
|
+
}
|
|
366
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
367
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
368
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
369
|
+
}
|
|
370
|
+
}
|
|
228
371
|
class ChangeTree {
|
|
229
|
-
static { _a$5 = $isNew; }
|
|
230
372
|
constructor(ref) {
|
|
231
373
|
this.isFiltered = false;
|
|
232
374
|
this.isPartiallyFiltered = false;
|
|
233
|
-
this.
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
375
|
+
this.indexedOperations = {};
|
|
376
|
+
//
|
|
377
|
+
// TODO:
|
|
378
|
+
// try storing the index + operation per item.
|
|
379
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
380
|
+
//
|
|
381
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
382
|
+
//
|
|
383
|
+
this.changes = { indexes: {}, operations: [] };
|
|
384
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
385
|
+
/**
|
|
386
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
387
|
+
*/
|
|
388
|
+
this.isNew = true;
|
|
237
389
|
this.ref = ref;
|
|
238
390
|
//
|
|
239
391
|
// Does this structure have "filters" declared?
|
|
240
392
|
//
|
|
241
|
-
if (ref.constructor[Symbol.metadata]?.[
|
|
242
|
-
this.allFilteredChanges =
|
|
243
|
-
this.filteredChanges =
|
|
393
|
+
if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
|
|
394
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
395
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
244
396
|
}
|
|
245
397
|
}
|
|
246
398
|
setRoot(root) {
|
|
247
399
|
this.root = root;
|
|
248
|
-
this.root.add(this);
|
|
400
|
+
const isNewChangeTree = this.root.add(this);
|
|
249
401
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
250
402
|
if (this.root.types.hasFilters) {
|
|
251
403
|
//
|
|
@@ -256,22 +408,24 @@ class ChangeTree {
|
|
|
256
408
|
//
|
|
257
409
|
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
258
410
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
259
|
-
|
|
260
|
-
|
|
411
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
412
|
+
if (isNewChangeTree) {
|
|
413
|
+
this.root.allFilteredChanges.push(this);
|
|
414
|
+
}
|
|
261
415
|
}
|
|
262
416
|
}
|
|
263
417
|
if (!this.isFiltered) {
|
|
264
|
-
|
|
265
|
-
|
|
418
|
+
enqueueChangeTree(root, this, 'changes');
|
|
419
|
+
if (isNewChangeTree) {
|
|
420
|
+
this.root.allChanges.push(this);
|
|
421
|
+
}
|
|
266
422
|
}
|
|
267
|
-
|
|
423
|
+
// Recursively set root on child structures
|
|
268
424
|
if (metadata) {
|
|
269
|
-
metadata[
|
|
425
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
270
426
|
const field = metadata[index];
|
|
271
427
|
const value = this.ref[field.name];
|
|
272
|
-
|
|
273
|
-
value[$changes].setRoot(root);
|
|
274
|
-
}
|
|
428
|
+
value?.[$changes].setRoot(root);
|
|
275
429
|
});
|
|
276
430
|
}
|
|
277
431
|
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
@@ -288,31 +442,36 @@ class ChangeTree {
|
|
|
288
442
|
if (!root) {
|
|
289
443
|
return;
|
|
290
444
|
}
|
|
291
|
-
root.add(this);
|
|
292
445
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
293
446
|
// skip if parent is already set
|
|
294
447
|
if (root !== this.root) {
|
|
295
448
|
this.root = root;
|
|
449
|
+
const isNewChangeTree = root.add(this);
|
|
296
450
|
if (root.types.hasFilters) {
|
|
297
451
|
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
298
452
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
299
|
-
|
|
300
|
-
|
|
453
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
454
|
+
if (isNewChangeTree) {
|
|
455
|
+
this.root.allFilteredChanges.push(this);
|
|
456
|
+
}
|
|
301
457
|
}
|
|
302
458
|
}
|
|
303
459
|
if (!this.isFiltered) {
|
|
304
|
-
|
|
305
|
-
|
|
460
|
+
enqueueChangeTree(root, this, 'changes');
|
|
461
|
+
if (isNewChangeTree) {
|
|
462
|
+
this.root.allChanges.push(this);
|
|
463
|
+
}
|
|
306
464
|
}
|
|
307
|
-
|
|
465
|
+
}
|
|
466
|
+
else {
|
|
467
|
+
root.add(this);
|
|
308
468
|
}
|
|
309
469
|
// assign same parent on child structures
|
|
310
470
|
if (metadata) {
|
|
311
|
-
metadata[
|
|
471
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
312
472
|
const field = metadata[index];
|
|
313
473
|
const value = this.ref[field.name];
|
|
314
474
|
value?.[$changes].setParent(this.ref, root, index);
|
|
315
|
-
// console.log(this.ref.constructor.name, field.name, value);
|
|
316
475
|
// try { throw new Error(); } catch (e) {
|
|
317
476
|
// console.log(e.stack);
|
|
318
477
|
// }
|
|
@@ -331,7 +490,7 @@ class ChangeTree {
|
|
|
331
490
|
//
|
|
332
491
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
333
492
|
if (metadata) {
|
|
334
|
-
metadata[
|
|
493
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
335
494
|
const field = metadata[index];
|
|
336
495
|
const value = this.ref[field.name];
|
|
337
496
|
if (value) {
|
|
@@ -347,8 +506,10 @@ class ChangeTree {
|
|
|
347
506
|
}
|
|
348
507
|
}
|
|
349
508
|
operation(op) {
|
|
350
|
-
|
|
351
|
-
this.
|
|
509
|
+
// operations without index use negative values to represent them
|
|
510
|
+
// this is checked during .encode() time.
|
|
511
|
+
this.changes.operations.push(-op);
|
|
512
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
352
513
|
}
|
|
353
514
|
change(index, operation = exports.OPERATION.ADD) {
|
|
354
515
|
const metadata = this.ref.constructor[Symbol.metadata];
|
|
@@ -356,7 +517,7 @@ class ChangeTree {
|
|
|
356
517
|
const changeSet = (isFiltered)
|
|
357
518
|
? this.filteredChanges
|
|
358
519
|
: this.changes;
|
|
359
|
-
const previousOperation =
|
|
520
|
+
const previousOperation = this.indexedOperations[index];
|
|
360
521
|
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
361
522
|
const op = (!previousOperation)
|
|
362
523
|
? operation
|
|
@@ -366,16 +527,19 @@ class ChangeTree {
|
|
|
366
527
|
//
|
|
367
528
|
// TODO: are DELETE operations being encoded as ADD here ??
|
|
368
529
|
//
|
|
369
|
-
|
|
530
|
+
this.indexedOperations[index] = op;
|
|
370
531
|
}
|
|
532
|
+
setOperationAtIndex(changeSet, index);
|
|
371
533
|
if (isFiltered) {
|
|
372
|
-
this.allFilteredChanges
|
|
373
|
-
this.root
|
|
374
|
-
|
|
534
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
535
|
+
if (this.root) {
|
|
536
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
537
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
538
|
+
}
|
|
375
539
|
}
|
|
376
540
|
else {
|
|
377
|
-
this.allChanges
|
|
378
|
-
this.root
|
|
541
|
+
setOperationAtIndex(this.allChanges, index);
|
|
542
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
379
543
|
}
|
|
380
544
|
}
|
|
381
545
|
shiftChangeIndexes(shiftIndex) {
|
|
@@ -387,12 +551,15 @@ class ChangeTree {
|
|
|
387
551
|
const changeSet = (this.isFiltered)
|
|
388
552
|
? this.filteredChanges
|
|
389
553
|
: this.changes;
|
|
390
|
-
const
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
554
|
+
const newIndexedOperations = {};
|
|
555
|
+
const newIndexes = {};
|
|
556
|
+
for (const index in this.indexedOperations) {
|
|
557
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
558
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
395
559
|
}
|
|
560
|
+
this.indexedOperations = newIndexedOperations;
|
|
561
|
+
changeSet.indexes = newIndexes;
|
|
562
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
396
563
|
}
|
|
397
564
|
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
398
565
|
//
|
|
@@ -408,24 +575,36 @@ class ChangeTree {
|
|
|
408
575
|
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
409
576
|
}
|
|
410
577
|
}
|
|
411
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0,
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
578
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
579
|
+
const newIndexes = {};
|
|
580
|
+
for (const key in changeSet.indexes) {
|
|
581
|
+
const index = changeSet.indexes[key];
|
|
582
|
+
if (index > startIndex) {
|
|
583
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
416
584
|
}
|
|
417
|
-
|
|
585
|
+
else {
|
|
586
|
+
newIndexes[key] = index;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
changeSet.indexes = newIndexes;
|
|
590
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
591
|
+
const index = changeSet.operations[i];
|
|
592
|
+
if (index > startIndex) {
|
|
593
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
594
|
+
}
|
|
595
|
+
}
|
|
418
596
|
}
|
|
419
597
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
this.
|
|
423
|
-
this.
|
|
598
|
+
this.indexedOperations[index] = operation;
|
|
599
|
+
if (this.filteredChanges) {
|
|
600
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
601
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
602
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
424
603
|
}
|
|
425
604
|
else {
|
|
426
|
-
this.allChanges
|
|
427
|
-
this.changes
|
|
428
|
-
this.root
|
|
605
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
606
|
+
setOperationAtIndex(this.changes, index);
|
|
607
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
429
608
|
}
|
|
430
609
|
}
|
|
431
610
|
getType(index) {
|
|
@@ -444,8 +623,7 @@ class ChangeTree {
|
|
|
444
623
|
}
|
|
445
624
|
}
|
|
446
625
|
getChange(index) {
|
|
447
|
-
|
|
448
|
-
return this.changes.get(index) ?? this.filteredChanges?.get(index);
|
|
626
|
+
return this.indexedOperations[index];
|
|
449
627
|
}
|
|
450
628
|
//
|
|
451
629
|
// used during `.encode()`
|
|
@@ -469,8 +647,9 @@ class ChangeTree {
|
|
|
469
647
|
const changeSet = (this.filteredChanges)
|
|
470
648
|
? this.filteredChanges
|
|
471
649
|
: this.changes;
|
|
650
|
+
this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
|
|
651
|
+
setOperationAtIndex(changeSet, index);
|
|
472
652
|
const previousValue = this.getValue(index);
|
|
473
|
-
changeSet.set(index, operation ?? exports.OPERATION.DELETE);
|
|
474
653
|
// remove `root` reference
|
|
475
654
|
if (previousValue && previousValue[$changes]) {
|
|
476
655
|
//
|
|
@@ -486,23 +665,26 @@ class ChangeTree {
|
|
|
486
665
|
this.root?.remove(previousValue[$changes]);
|
|
487
666
|
}
|
|
488
667
|
//
|
|
489
|
-
// FIXME: this is looking a
|
|
668
|
+
// FIXME: this is looking a ugly and repeated
|
|
490
669
|
//
|
|
491
670
|
if (this.filteredChanges) {
|
|
492
|
-
this.
|
|
493
|
-
this.
|
|
671
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
672
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
494
673
|
}
|
|
495
674
|
else {
|
|
496
|
-
this.
|
|
497
|
-
this.
|
|
675
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
676
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
498
677
|
}
|
|
499
678
|
}
|
|
500
679
|
endEncode() {
|
|
501
|
-
this.
|
|
680
|
+
this.indexedOperations = {};
|
|
681
|
+
// // clear changes
|
|
682
|
+
// this.changes.indexes = {};
|
|
683
|
+
// this.changes.operations.length = 0;
|
|
502
684
|
// ArraySchema and MapSchema have a custom "encode end" method
|
|
503
685
|
this.ref[$onEncodeEnd]?.();
|
|
504
686
|
// Not a new instance anymore
|
|
505
|
-
|
|
687
|
+
this.isNew = false;
|
|
506
688
|
}
|
|
507
689
|
discard(discardAll = false) {
|
|
508
690
|
//
|
|
@@ -511,13 +693,22 @@ class ChangeTree {
|
|
|
511
693
|
// REPLACE in case same key is used on next patches.
|
|
512
694
|
//
|
|
513
695
|
this.ref[$onEncodeEnd]?.();
|
|
514
|
-
this.
|
|
515
|
-
this.
|
|
516
|
-
|
|
517
|
-
this.
|
|
696
|
+
this.indexedOperations = {};
|
|
697
|
+
this.changes.indexes = {};
|
|
698
|
+
this.changes.operations.length = 0;
|
|
699
|
+
this.changes.queueRootIndex = undefined;
|
|
700
|
+
if (this.filteredChanges !== undefined) {
|
|
701
|
+
this.filteredChanges.indexes = {};
|
|
702
|
+
this.filteredChanges.operations.length = 0;
|
|
703
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
704
|
+
}
|
|
518
705
|
if (discardAll) {
|
|
519
|
-
this.allChanges.
|
|
520
|
-
this.
|
|
706
|
+
this.allChanges.indexes = {};
|
|
707
|
+
this.allChanges.operations.length = 0;
|
|
708
|
+
if (this.allFilteredChanges !== undefined) {
|
|
709
|
+
this.allFilteredChanges.indexes = {};
|
|
710
|
+
this.allFilteredChanges.operations.length = 0;
|
|
711
|
+
}
|
|
521
712
|
// remove children references
|
|
522
713
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
523
714
|
}
|
|
@@ -526,12 +717,13 @@ class ChangeTree {
|
|
|
526
717
|
* Recursively discard all changes from this, and child structures.
|
|
527
718
|
*/
|
|
528
719
|
discardAll() {
|
|
529
|
-
this.
|
|
530
|
-
|
|
720
|
+
const keys = Object.keys(this.indexedOperations);
|
|
721
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
722
|
+
const value = this.getValue(Number(keys[i]));
|
|
531
723
|
if (value && value[$changes]) {
|
|
532
724
|
value[$changes].discardAll();
|
|
533
725
|
}
|
|
534
|
-
}
|
|
726
|
+
}
|
|
535
727
|
this.discard();
|
|
536
728
|
}
|
|
537
729
|
ensureRefId() {
|
|
@@ -542,42 +734,48 @@ class ChangeTree {
|
|
|
542
734
|
this.refId = this.root.getNextUniqueId();
|
|
543
735
|
}
|
|
544
736
|
get changed() {
|
|
545
|
-
return this.
|
|
737
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
546
738
|
}
|
|
547
739
|
checkIsFiltered(metadata, parent, parentIndex) {
|
|
548
740
|
// Detect if current structure has "filters" declared
|
|
549
|
-
this.isPartiallyFiltered = metadata?.[
|
|
741
|
+
this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
|
|
550
742
|
if (this.isPartiallyFiltered) {
|
|
551
|
-
this.filteredChanges = this.filteredChanges ||
|
|
552
|
-
this.allFilteredChanges = this.allFilteredChanges ||
|
|
743
|
+
this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
|
|
744
|
+
this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
|
|
553
745
|
}
|
|
554
|
-
if
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
746
|
+
// skip if parent is not set
|
|
747
|
+
if (!parent) {
|
|
748
|
+
return;
|
|
749
|
+
}
|
|
750
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
751
|
+
const parentChangeTree = parent[$changes];
|
|
752
|
+
parent = parentChangeTree.parent;
|
|
753
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
754
|
+
}
|
|
755
|
+
const parentMetadata = parent.constructor?.[Symbol.metadata];
|
|
756
|
+
this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
757
|
+
//
|
|
758
|
+
// TODO: refactor this!
|
|
759
|
+
//
|
|
760
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
761
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
762
|
+
//
|
|
763
|
+
if (this.isFiltered) {
|
|
764
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
765
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
766
|
+
if (this.changes.operations.length > 0) {
|
|
767
|
+
// swap changes reference
|
|
768
|
+
const changes = this.changes;
|
|
769
|
+
this.changes = this.filteredChanges;
|
|
770
|
+
this.filteredChanges = changes;
|
|
771
|
+
// swap "all changes" reference
|
|
772
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
773
|
+
this.allFilteredChanges = this.allChanges;
|
|
774
|
+
this.allChanges = allFilteredChanges;
|
|
775
|
+
// console.log("SWAP =>", {
|
|
776
|
+
// "this.allFilteredChanges": this.allFilteredChanges,
|
|
777
|
+
// "this.allChanges": this.allChanges
|
|
778
|
+
// })
|
|
581
779
|
}
|
|
582
780
|
}
|
|
583
781
|
}
|
|
@@ -646,21 +844,24 @@ function utf8Write(view, str, it) {
|
|
|
646
844
|
view[it.offset++] = c;
|
|
647
845
|
}
|
|
648
846
|
else if (c < 0x800) {
|
|
649
|
-
view[it.offset
|
|
650
|
-
view[it.offset
|
|
847
|
+
view[it.offset] = 0xc0 | (c >> 6);
|
|
848
|
+
view[it.offset + 1] = 0x80 | (c & 0x3f);
|
|
849
|
+
it.offset += 2;
|
|
651
850
|
}
|
|
652
851
|
else if (c < 0xd800 || c >= 0xe000) {
|
|
653
|
-
view[it.offset
|
|
654
|
-
view[it.offset
|
|
655
|
-
view[it.offset
|
|
852
|
+
view[it.offset] = 0xe0 | (c >> 12);
|
|
853
|
+
view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
|
|
854
|
+
view[it.offset + 2] = 0x80 | (c & 0x3f);
|
|
855
|
+
it.offset += 3;
|
|
656
856
|
}
|
|
657
857
|
else {
|
|
658
858
|
i++;
|
|
659
859
|
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
660
|
-
view[it.offset
|
|
661
|
-
view[it.offset
|
|
662
|
-
view[it.offset
|
|
663
|
-
view[it.offset
|
|
860
|
+
view[it.offset] = 0xf0 | (c >> 18);
|
|
861
|
+
view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
|
|
862
|
+
view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
|
|
863
|
+
view[it.offset + 3] = 0x80 | (c & 0x3f);
|
|
864
|
+
it.offset += 4;
|
|
664
865
|
}
|
|
665
866
|
}
|
|
666
867
|
}
|
|
@@ -889,7 +1090,7 @@ function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
|
889
1090
|
* Used for Schema instances.
|
|
890
1091
|
* @private
|
|
891
1092
|
*/
|
|
892
|
-
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
1093
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
893
1094
|
// "compress" field index + operation
|
|
894
1095
|
bytes[it.offset++] = (index | operation) & 255;
|
|
895
1096
|
// Do not encode value for DELETE operations
|
|
@@ -897,7 +1098,7 @@ const encodeSchemaOperation = function (encoder, bytes, changeTree, index, opera
|
|
|
897
1098
|
return;
|
|
898
1099
|
}
|
|
899
1100
|
const ref = changeTree.ref;
|
|
900
|
-
const metadata = ref.constructor[Symbol.metadata];
|
|
1101
|
+
// const metadata: Metadata = ref.constructor[Symbol.metadata];
|
|
901
1102
|
const field = metadata[index];
|
|
902
1103
|
// TODO: inline this function call small performance gain
|
|
903
1104
|
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
@@ -1592,6 +1793,7 @@ class ArraySchema {
|
|
|
1592
1793
|
const proxy = new Proxy(this, {
|
|
1593
1794
|
get: (obj, prop) => {
|
|
1594
1795
|
if (typeof (prop) !== "symbol" &&
|
|
1796
|
+
// FIXME: d8 accuses this as low performance
|
|
1595
1797
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1596
1798
|
) {
|
|
1597
1799
|
return this.items[prop];
|
|
@@ -1609,7 +1811,7 @@ class ArraySchema {
|
|
|
1609
1811
|
if (setValue[$changes]) {
|
|
1610
1812
|
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1611
1813
|
if (obj.items[key] !== undefined) {
|
|
1612
|
-
if (setValue[$changes]
|
|
1814
|
+
if (setValue[$changes].isNew) {
|
|
1613
1815
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
1614
1816
|
}
|
|
1615
1817
|
else {
|
|
@@ -1621,7 +1823,7 @@ class ArraySchema {
|
|
|
1621
1823
|
}
|
|
1622
1824
|
}
|
|
1623
1825
|
}
|
|
1624
|
-
else if (setValue[$changes]
|
|
1826
|
+
else if (setValue[$changes].isNew) {
|
|
1625
1827
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
|
|
1626
1828
|
}
|
|
1627
1829
|
}
|
|
@@ -1655,7 +1857,9 @@ class ArraySchema {
|
|
|
1655
1857
|
});
|
|
1656
1858
|
this[$changes] = new ChangeTree(proxy);
|
|
1657
1859
|
this[$changes].indexes = {};
|
|
1658
|
-
|
|
1860
|
+
if (items.length > 0) {
|
|
1861
|
+
this.push(...items);
|
|
1862
|
+
}
|
|
1659
1863
|
return proxy;
|
|
1660
1864
|
}
|
|
1661
1865
|
set length(newLength) {
|
|
@@ -1674,15 +1878,18 @@ class ArraySchema {
|
|
|
1674
1878
|
}
|
|
1675
1879
|
push(...values) {
|
|
1676
1880
|
let length = this.tmpItems.length;
|
|
1677
|
-
|
|
1678
|
-
|
|
1881
|
+
const changeTree = this[$changes];
|
|
1882
|
+
// values.forEach((value, i) => {
|
|
1883
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1884
|
+
const value = values[i];
|
|
1679
1885
|
if (value === undefined || value === null) {
|
|
1886
|
+
// skip null values
|
|
1680
1887
|
return;
|
|
1681
1888
|
}
|
|
1682
1889
|
else if (typeof (value) === "object" && this[$childType]) {
|
|
1683
1890
|
assertInstanceType(value, this[$childType], this, i);
|
|
1891
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1684
1892
|
}
|
|
1685
|
-
const changeTree = this[$changes];
|
|
1686
1893
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1687
1894
|
this.items.push(value);
|
|
1688
1895
|
this.tmpItems.push(value);
|
|
@@ -1691,8 +1898,9 @@ class ArraySchema {
|
|
|
1691
1898
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1692
1899
|
//
|
|
1693
1900
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1694
|
-
|
|
1695
|
-
|
|
1901
|
+
}
|
|
1902
|
+
// length++;
|
|
1903
|
+
// });
|
|
1696
1904
|
return length;
|
|
1697
1905
|
}
|
|
1698
1906
|
/**
|
|
@@ -1713,6 +1921,7 @@ class ArraySchema {
|
|
|
1713
1921
|
}
|
|
1714
1922
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1715
1923
|
// this.tmpItems[index] = undefined;
|
|
1924
|
+
// this.tmpItems.pop();
|
|
1716
1925
|
this.deletedIndexes[index] = true;
|
|
1717
1926
|
return this.items.pop();
|
|
1718
1927
|
}
|
|
@@ -1777,9 +1986,12 @@ class ArraySchema {
|
|
|
1777
1986
|
//
|
|
1778
1987
|
// TODO: do not use [$changes] at decoding time.
|
|
1779
1988
|
//
|
|
1780
|
-
changeTree.root
|
|
1781
|
-
|
|
1782
|
-
|
|
1989
|
+
const root = changeTree.root;
|
|
1990
|
+
if (root !== undefined) {
|
|
1991
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
1992
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
1993
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
1994
|
+
}
|
|
1783
1995
|
});
|
|
1784
1996
|
changeTree.discard(true);
|
|
1785
1997
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -1823,6 +2035,7 @@ class ArraySchema {
|
|
|
1823
2035
|
const changeTree = this[$changes];
|
|
1824
2036
|
changeTree.delete(index);
|
|
1825
2037
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
2038
|
+
// this.deletedIndexes[index] = true;
|
|
1826
2039
|
return this.items.shift();
|
|
1827
2040
|
}
|
|
1828
2041
|
/**
|
|
@@ -1903,10 +2116,12 @@ class ArraySchema {
|
|
|
1903
2116
|
changeTree.shiftChangeIndexes(items.length);
|
|
1904
2117
|
// new index
|
|
1905
2118
|
if (changeTree.isFiltered) {
|
|
1906
|
-
changeTree.filteredChanges
|
|
2119
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
2120
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1907
2121
|
}
|
|
1908
2122
|
else {
|
|
1909
|
-
changeTree.allChanges
|
|
2123
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2124
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1910
2125
|
}
|
|
1911
2126
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1912
2127
|
items.forEach((_, index) => {
|
|
@@ -2247,7 +2462,7 @@ class MapSchema {
|
|
|
2247
2462
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2248
2463
|
const index = (isReplace)
|
|
2249
2464
|
? changeTree.indexes[key]
|
|
2250
|
-
: changeTree.indexes[
|
|
2465
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2251
2466
|
let operation = (isReplace)
|
|
2252
2467
|
? exports.OPERATION.REPLACE
|
|
2253
2468
|
: exports.OPERATION.ADD;
|
|
@@ -2259,7 +2474,7 @@ class MapSchema {
|
|
|
2259
2474
|
if (!isReplace) {
|
|
2260
2475
|
this.$indexes.set(index, key);
|
|
2261
2476
|
changeTree.indexes[key] = index;
|
|
2262
|
-
changeTree.indexes[
|
|
2477
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2263
2478
|
}
|
|
2264
2479
|
else if (!isRef &&
|
|
2265
2480
|
this.$items.get(key) === value) {
|
|
@@ -2334,8 +2549,11 @@ class MapSchema {
|
|
|
2334
2549
|
}
|
|
2335
2550
|
[$onEncodeEnd]() {
|
|
2336
2551
|
const changeTree = this[$changes];
|
|
2337
|
-
const
|
|
2338
|
-
for (
|
|
2552
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2553
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2554
|
+
const key = keys[i];
|
|
2555
|
+
const fieldIndex = Number(key);
|
|
2556
|
+
const operation = changeTree.indexedOperations[key];
|
|
2339
2557
|
if (operation === exports.OPERATION.DELETE) {
|
|
2340
2558
|
const index = this[$getByIndex](fieldIndex);
|
|
2341
2559
|
delete changeTree.indexes[index];
|
|
@@ -2378,102 +2596,6 @@ class MapSchema {
|
|
|
2378
2596
|
}
|
|
2379
2597
|
registerType("map", { constructor: MapSchema });
|
|
2380
2598
|
|
|
2381
|
-
class TypeContext {
|
|
2382
|
-
/**
|
|
2383
|
-
* For inheritance support
|
|
2384
|
-
* Keeps track of which classes extends which. (parent -> children)
|
|
2385
|
-
*/
|
|
2386
|
-
static { this.inheritedTypes = new Map(); }
|
|
2387
|
-
static register(target) {
|
|
2388
|
-
const parent = Object.getPrototypeOf(target);
|
|
2389
|
-
if (parent !== Schema) {
|
|
2390
|
-
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
2391
|
-
if (!inherits) {
|
|
2392
|
-
inherits = new Set();
|
|
2393
|
-
TypeContext.inheritedTypes.set(parent, inherits);
|
|
2394
|
-
}
|
|
2395
|
-
inherits.add(target);
|
|
2396
|
-
}
|
|
2397
|
-
}
|
|
2398
|
-
constructor(rootClass) {
|
|
2399
|
-
this.types = {};
|
|
2400
|
-
this.schemas = new Map();
|
|
2401
|
-
this.hasFilters = false;
|
|
2402
|
-
this.parentFiltered = {};
|
|
2403
|
-
if (rootClass) {
|
|
2404
|
-
this.discoverTypes(rootClass);
|
|
2405
|
-
}
|
|
2406
|
-
}
|
|
2407
|
-
has(schema) {
|
|
2408
|
-
return this.schemas.has(schema);
|
|
2409
|
-
}
|
|
2410
|
-
get(typeid) {
|
|
2411
|
-
return this.types[typeid];
|
|
2412
|
-
}
|
|
2413
|
-
add(schema, typeid = this.schemas.size) {
|
|
2414
|
-
// skip if already registered
|
|
2415
|
-
if (this.schemas.has(schema)) {
|
|
2416
|
-
return false;
|
|
2417
|
-
}
|
|
2418
|
-
this.types[typeid] = schema;
|
|
2419
|
-
//
|
|
2420
|
-
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
2421
|
-
//
|
|
2422
|
-
if (schema[Symbol.metadata] === undefined) {
|
|
2423
|
-
Metadata.init(schema);
|
|
2424
|
-
}
|
|
2425
|
-
this.schemas.set(schema, typeid);
|
|
2426
|
-
return true;
|
|
2427
|
-
}
|
|
2428
|
-
getTypeId(klass) {
|
|
2429
|
-
return this.schemas.get(klass);
|
|
2430
|
-
}
|
|
2431
|
-
discoverTypes(klass, parentIndex, parentFieldViewTag) {
|
|
2432
|
-
if (!this.add(klass)) {
|
|
2433
|
-
return;
|
|
2434
|
-
}
|
|
2435
|
-
// add classes inherited from this base class
|
|
2436
|
-
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
2437
|
-
this.discoverTypes(child, parentIndex, parentFieldViewTag);
|
|
2438
|
-
});
|
|
2439
|
-
const metadata = (klass[Symbol.metadata] ??= {});
|
|
2440
|
-
// if any schema/field has filters, mark "context" as having filters.
|
|
2441
|
-
if (metadata[-2]) {
|
|
2442
|
-
this.hasFilters = true;
|
|
2443
|
-
}
|
|
2444
|
-
if (parentFieldViewTag !== undefined) {
|
|
2445
|
-
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
2446
|
-
}
|
|
2447
|
-
for (const fieldIndex in metadata) {
|
|
2448
|
-
const index = fieldIndex;
|
|
2449
|
-
const fieldType = metadata[index].type;
|
|
2450
|
-
const viewTag = metadata[index].tag;
|
|
2451
|
-
if (typeof (fieldType) === "string") {
|
|
2452
|
-
continue;
|
|
2453
|
-
}
|
|
2454
|
-
if (Array.isArray(fieldType)) {
|
|
2455
|
-
const type = fieldType[0];
|
|
2456
|
-
// skip primitive types
|
|
2457
|
-
if (type === "string") {
|
|
2458
|
-
continue;
|
|
2459
|
-
}
|
|
2460
|
-
this.discoverTypes(type, index, viewTag);
|
|
2461
|
-
}
|
|
2462
|
-
else if (typeof (fieldType) === "function") {
|
|
2463
|
-
this.discoverTypes(fieldType, viewTag);
|
|
2464
|
-
}
|
|
2465
|
-
else {
|
|
2466
|
-
const type = Object.values(fieldType)[0];
|
|
2467
|
-
// skip primitive types
|
|
2468
|
-
if (typeof (type) === "string") {
|
|
2469
|
-
continue;
|
|
2470
|
-
}
|
|
2471
|
-
this.discoverTypes(type, index, viewTag);
|
|
2472
|
-
}
|
|
2473
|
-
}
|
|
2474
|
-
}
|
|
2475
|
-
}
|
|
2476
|
-
|
|
2477
2599
|
const DEFAULT_VIEW_TAG = -1;
|
|
2478
2600
|
/**
|
|
2479
2601
|
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
@@ -2501,8 +2623,8 @@ const DEFAULT_VIEW_TAG = -1;
|
|
|
2501
2623
|
// // detect index for this field, considering inheritance
|
|
2502
2624
|
// //
|
|
2503
2625
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2504
|
-
// let fieldIndex: number = context.metadata[
|
|
2505
|
-
// ?? (parent && parent[
|
|
2626
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2627
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2506
2628
|
// ?? -1; // no fields defined
|
|
2507
2629
|
// fieldIndex++;
|
|
2508
2630
|
// if (
|
|
@@ -2631,8 +2753,8 @@ function view(tag = DEFAULT_VIEW_TAG) {
|
|
|
2631
2753
|
// //
|
|
2632
2754
|
// metadata[fieldIndex] = {
|
|
2633
2755
|
// type: undefined,
|
|
2634
|
-
// index: (metadata[
|
|
2635
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2756
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2757
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2636
2758
|
// ?? -1) + 1 // no fields defined
|
|
2637
2759
|
// }
|
|
2638
2760
|
// }
|
|
@@ -2675,8 +2797,8 @@ function type(type, options) {
|
|
|
2675
2797
|
//
|
|
2676
2798
|
// detect index for this field, considering inheritance
|
|
2677
2799
|
//
|
|
2678
|
-
fieldIndex = metadata[
|
|
2679
|
-
?? (parentMetadata && parentMetadata[
|
|
2800
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2801
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2680
2802
|
?? -1; // no fields defined
|
|
2681
2803
|
fieldIndex++;
|
|
2682
2804
|
}
|
|
@@ -2703,7 +2825,7 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass)
|
|
|
2703
2825
|
return {
|
|
2704
2826
|
get: function () { return this[fieldCached]; },
|
|
2705
2827
|
set: function (value) {
|
|
2706
|
-
const previousValue = this[fieldCached]
|
|
2828
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2707
2829
|
// skip if value is the same as cached.
|
|
2708
2830
|
if (value === previousValue) {
|
|
2709
2831
|
return;
|
|
@@ -2775,8 +2897,8 @@ function deprecated(throws = true) {
|
|
|
2775
2897
|
// //
|
|
2776
2898
|
// metadata[field] = {
|
|
2777
2899
|
// type: undefined,
|
|
2778
|
-
// index: (metadata[
|
|
2779
|
-
// ?? (parentMetadata && parentMetadata[
|
|
2900
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2901
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2780
2902
|
// ?? -1) + 1 // no fields defined
|
|
2781
2903
|
// }
|
|
2782
2904
|
// }
|
|
@@ -2814,15 +2936,18 @@ function dumpChanges(schema) {
|
|
|
2814
2936
|
ops: {},
|
|
2815
2937
|
refs: []
|
|
2816
2938
|
};
|
|
2817
|
-
$root.changes
|
|
2939
|
+
// for (const refId in $root.changes) {
|
|
2940
|
+
$root.changes.forEach(changeTree => {
|
|
2941
|
+
const changes = changeTree.indexedOperations;
|
|
2818
2942
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2819
|
-
|
|
2943
|
+
for (const index in changes) {
|
|
2944
|
+
const op = changes[index];
|
|
2820
2945
|
const opName = exports.OPERATION[op];
|
|
2821
2946
|
if (!dump.ops[opName]) {
|
|
2822
2947
|
dump.ops[opName] = 0;
|
|
2823
2948
|
}
|
|
2824
2949
|
dump.ops[exports.OPERATION[op]]++;
|
|
2825
|
-
}
|
|
2950
|
+
}
|
|
2826
2951
|
});
|
|
2827
2952
|
return dump;
|
|
2828
2953
|
}
|
|
@@ -2848,6 +2973,7 @@ var _a$2, _b$2;
|
|
|
2848
2973
|
class Schema {
|
|
2849
2974
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2850
2975
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
2976
|
+
// public [$changes]: ChangeTree;
|
|
2851
2977
|
/**
|
|
2852
2978
|
* Assign the property descriptors required to track changes on this instance.
|
|
2853
2979
|
* @param instance
|
|
@@ -2907,12 +3033,7 @@ class Schema {
|
|
|
2907
3033
|
// inline
|
|
2908
3034
|
// Schema.initialize(this);
|
|
2909
3035
|
//
|
|
2910
|
-
|
|
2911
|
-
value: new ChangeTree(this),
|
|
2912
|
-
enumerable: false,
|
|
2913
|
-
writable: true
|
|
2914
|
-
});
|
|
2915
|
-
Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
3036
|
+
Schema.initialize(this);
|
|
2916
3037
|
//
|
|
2917
3038
|
// Assign initial values
|
|
2918
3039
|
//
|
|
@@ -2986,7 +3107,7 @@ class Schema {
|
|
|
2986
3107
|
const changeTree = ref[$changes];
|
|
2987
3108
|
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2988
3109
|
let output = "";
|
|
2989
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
3110
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
2990
3111
|
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
2991
3112
|
return output;
|
|
2992
3113
|
}
|
|
@@ -3004,18 +3125,26 @@ class Schema {
|
|
|
3004
3125
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
3005
3126
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
3006
3127
|
function dumpChangeSet(changeSet) {
|
|
3007
|
-
|
|
3008
|
-
.
|
|
3009
|
-
.forEach((
|
|
3128
|
+
changeSet.operations
|
|
3129
|
+
.filter(op => op)
|
|
3130
|
+
.forEach((index) => {
|
|
3131
|
+
const operation = changeTree.indexedOperations[index];
|
|
3132
|
+
console.log({ index, operation });
|
|
3133
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3134
|
+
});
|
|
3010
3135
|
}
|
|
3011
3136
|
dumpChangeSet(changeSet);
|
|
3012
3137
|
// display filtered changes
|
|
3013
|
-
if (!isEncodeAll &&
|
|
3138
|
+
if (!isEncodeAll &&
|
|
3139
|
+
changeTree.filteredChanges &&
|
|
3140
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
3014
3141
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
3015
3142
|
dumpChangeSet(changeTree.filteredChanges);
|
|
3016
3143
|
}
|
|
3017
3144
|
// display filtered changes
|
|
3018
|
-
if (isEncodeAll &&
|
|
3145
|
+
if (isEncodeAll &&
|
|
3146
|
+
changeTree.allFilteredChanges &&
|
|
3147
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
3019
3148
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
3020
3149
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
3021
3150
|
}
|
|
@@ -3024,10 +3153,12 @@ class Schema {
|
|
|
3024
3153
|
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
3025
3154
|
let output = "";
|
|
3026
3155
|
const rootChangeTree = ref[$changes];
|
|
3156
|
+
const root = rootChangeTree.root;
|
|
3027
3157
|
const changeTrees = new Map();
|
|
3028
3158
|
let totalInstances = 0;
|
|
3029
3159
|
let totalOperations = 0;
|
|
3030
|
-
for (const [
|
|
3160
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3161
|
+
const changeTree = root.changeTrees[refId];
|
|
3031
3162
|
let includeChangeTree = false;
|
|
3032
3163
|
let parentChangeTrees = [];
|
|
3033
3164
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3046,7 +3177,7 @@ class Schema {
|
|
|
3046
3177
|
}
|
|
3047
3178
|
if (includeChangeTree) {
|
|
3048
3179
|
totalInstances += 1;
|
|
3049
|
-
totalOperations += changes.
|
|
3180
|
+
totalOperations += Object.keys(changes).length;
|
|
3050
3181
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
3051
3182
|
}
|
|
3052
3183
|
}
|
|
@@ -3064,12 +3195,13 @@ class Schema {
|
|
|
3064
3195
|
visitedParents.add(parentChangeTree);
|
|
3065
3196
|
}
|
|
3066
3197
|
});
|
|
3067
|
-
const changes = changeTree.
|
|
3198
|
+
const changes = changeTree.indexedOperations;
|
|
3068
3199
|
const level = parentChangeTrees.length;
|
|
3069
3200
|
const indent = getIndent(level);
|
|
3070
3201
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3071
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3072
|
-
for (const
|
|
3202
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3203
|
+
for (const index in changes) {
|
|
3204
|
+
const operation = changes[index];
|
|
3073
3205
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3074
3206
|
}
|
|
3075
3207
|
}
|
|
@@ -3430,59 +3562,90 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
3430
3562
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3431
3563
|
};
|
|
3432
3564
|
|
|
3565
|
+
function spliceOne(arr, index) {
|
|
3566
|
+
// manually splice an array
|
|
3567
|
+
if (index === -1 || index >= arr.length) {
|
|
3568
|
+
return false;
|
|
3569
|
+
}
|
|
3570
|
+
const len = arr.length - 1;
|
|
3571
|
+
for (let i = index; i < len; i++) {
|
|
3572
|
+
arr[i] = arr[i + 1];
|
|
3573
|
+
}
|
|
3574
|
+
arr.length = len;
|
|
3575
|
+
return true;
|
|
3576
|
+
}
|
|
3577
|
+
|
|
3433
3578
|
class Root {
|
|
3434
3579
|
constructor(types) {
|
|
3435
3580
|
this.types = types;
|
|
3436
3581
|
this.nextUniqueId = 0;
|
|
3437
|
-
this.refCount =
|
|
3582
|
+
this.refCount = {};
|
|
3583
|
+
this.changeTrees = {};
|
|
3438
3584
|
// all changes
|
|
3439
|
-
this.allChanges =
|
|
3440
|
-
this.allFilteredChanges =
|
|
3585
|
+
this.allChanges = [];
|
|
3586
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3441
3587
|
// pending changes to be encoded
|
|
3442
|
-
this.changes =
|
|
3443
|
-
this.filteredChanges =
|
|
3588
|
+
this.changes = [];
|
|
3589
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3444
3590
|
}
|
|
3445
3591
|
getNextUniqueId() {
|
|
3446
3592
|
return this.nextUniqueId++;
|
|
3447
3593
|
}
|
|
3448
3594
|
add(changeTree) {
|
|
3449
|
-
|
|
3595
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3596
|
+
changeTree.ensureRefId();
|
|
3597
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3598
|
+
if (isNewChangeTree) {
|
|
3599
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3600
|
+
}
|
|
3601
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3450
3602
|
if (previousRefCount === 0) {
|
|
3451
3603
|
//
|
|
3452
3604
|
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3453
3605
|
// We need to re-add all changes to the `changes` map.
|
|
3454
3606
|
//
|
|
3455
|
-
changeTree.allChanges.
|
|
3456
|
-
|
|
3457
|
-
|
|
3607
|
+
const ops = changeTree.allChanges.operations;
|
|
3608
|
+
let len = ops.length;
|
|
3609
|
+
while (len--) {
|
|
3610
|
+
changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
|
|
3611
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3612
|
+
}
|
|
3458
3613
|
}
|
|
3459
|
-
|
|
3460
|
-
|
|
3461
|
-
return refCount;
|
|
3614
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3615
|
+
return isNewChangeTree;
|
|
3462
3616
|
}
|
|
3463
3617
|
remove(changeTree) {
|
|
3464
|
-
const refCount = (this.refCount.
|
|
3618
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3465
3619
|
if (refCount <= 0) {
|
|
3466
3620
|
//
|
|
3467
3621
|
// Only remove "root" reference if it's the last reference
|
|
3468
3622
|
//
|
|
3469
3623
|
changeTree.root = undefined;
|
|
3470
|
-
this.
|
|
3471
|
-
this.
|
|
3624
|
+
delete this.changeTrees[changeTree.refId];
|
|
3625
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3626
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3472
3627
|
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3473
|
-
this.allFilteredChanges
|
|
3474
|
-
this.filteredChanges
|
|
3628
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3629
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3475
3630
|
}
|
|
3476
|
-
this.refCount.
|
|
3631
|
+
this.refCount[changeTree.refId] = 0;
|
|
3477
3632
|
}
|
|
3478
3633
|
else {
|
|
3479
|
-
this.refCount.
|
|
3634
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3480
3635
|
}
|
|
3481
3636
|
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3482
3637
|
return refCount;
|
|
3483
3638
|
}
|
|
3639
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3640
|
+
const changeSet = this[changeSetName];
|
|
3641
|
+
const index = changeSet.indexOf(changeTree);
|
|
3642
|
+
if (index !== -1) {
|
|
3643
|
+
spliceOne(changeSet, index);
|
|
3644
|
+
// changeSet[index] = undefined;
|
|
3645
|
+
}
|
|
3646
|
+
}
|
|
3484
3647
|
clear() {
|
|
3485
|
-
this.changes.
|
|
3648
|
+
this.changes.length = 0;
|
|
3486
3649
|
}
|
|
3487
3650
|
}
|
|
3488
3651
|
|
|
@@ -3506,20 +3669,26 @@ class Encoder {
|
|
|
3506
3669
|
this.state = state;
|
|
3507
3670
|
this.state[$changes].setRoot(this.root);
|
|
3508
3671
|
}
|
|
3509
|
-
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer,
|
|
3672
|
+
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
|
|
3510
3673
|
) {
|
|
3511
3674
|
const hasView = (view !== undefined);
|
|
3512
3675
|
const rootChangeTree = this.state[$changes];
|
|
3513
|
-
const
|
|
3514
|
-
|
|
3676
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3677
|
+
const changeTrees = this.root[changeSetName];
|
|
3678
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3679
|
+
const changeTree = changeTrees[i];
|
|
3680
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
3681
|
+
// if (changeTree === undefined) { continue; }
|
|
3682
|
+
const operations = changeTree[changeSetName];
|
|
3515
3683
|
const ref = changeTree.ref;
|
|
3516
3684
|
const ctor = ref.constructor;
|
|
3517
3685
|
const encoder = ctor[$encoder];
|
|
3518
3686
|
const filter = ctor[$filter];
|
|
3687
|
+
const metadata = ctor[Symbol.metadata];
|
|
3519
3688
|
// try { throw new Error(); } catch (e) {
|
|
3520
3689
|
// // only print if not coming from Reflection.ts
|
|
3521
3690
|
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3522
|
-
// console.log("ChangeTree:", { ref: ref.constructor.name
|
|
3691
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
3523
3692
|
// }
|
|
3524
3693
|
// }
|
|
3525
3694
|
if (hasView) {
|
|
@@ -3537,7 +3706,13 @@ class Encoder {
|
|
|
3537
3706
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3538
3707
|
number$1(buffer, changeTree.refId, it);
|
|
3539
3708
|
}
|
|
3540
|
-
for (
|
|
3709
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3710
|
+
const fieldIndex = operations.operations[j];
|
|
3711
|
+
const operation = (fieldIndex < 0)
|
|
3712
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3713
|
+
: (isEncodeAll)
|
|
3714
|
+
? exports.OPERATION.ADD
|
|
3715
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3541
3716
|
//
|
|
3542
3717
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3543
3718
|
// they will be encoded per client, based on their view.
|
|
@@ -3545,7 +3720,7 @@ class Encoder {
|
|
|
3545
3720
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3546
3721
|
// TODO: avoid checking if no view tags were defined
|
|
3547
3722
|
//
|
|
3548
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3723
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3549
3724
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3550
3725
|
// view?.invisible.add(changeTree);
|
|
3551
3726
|
continue;
|
|
@@ -3560,16 +3735,14 @@ class Encoder {
|
|
|
3560
3735
|
// });
|
|
3561
3736
|
// }
|
|
3562
3737
|
// }
|
|
3563
|
-
|
|
3738
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
3739
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3740
|
+
}
|
|
3741
|
+
if (shouldDiscardChanges) {
|
|
3742
|
+
changeTree.discard();
|
|
3743
|
+
// Not a new instance anymore
|
|
3744
|
+
changeTree.isNew = false;
|
|
3564
3745
|
}
|
|
3565
|
-
// if (shouldClearChanges) {
|
|
3566
|
-
// // changeTree.endEncode();
|
|
3567
|
-
// changeTree.changes.clear();
|
|
3568
|
-
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3569
|
-
// changeTree.ref[$onEncodeEnd]?.();
|
|
3570
|
-
// // Not a new instance anymore
|
|
3571
|
-
// delete changeTree[$isNew];
|
|
3572
|
-
// }
|
|
3573
3746
|
}
|
|
3574
3747
|
if (it.offset > buffer.byteLength) {
|
|
3575
3748
|
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
@@ -3586,51 +3759,54 @@ class Encoder {
|
|
|
3586
3759
|
if (buffer === this.sharedBuffer) {
|
|
3587
3760
|
this.sharedBuffer = buffer;
|
|
3588
3761
|
}
|
|
3589
|
-
return this.encode({ offset: initialOffset }, view, buffer,
|
|
3762
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3590
3763
|
}
|
|
3591
3764
|
else {
|
|
3592
|
-
//
|
|
3593
|
-
// only clear changes after making sure buffer resize is not required.
|
|
3594
|
-
//
|
|
3595
|
-
if (shouldClearChanges) {
|
|
3596
|
-
|
|
3597
|
-
|
|
3598
|
-
|
|
3599
|
-
|
|
3600
|
-
}
|
|
3765
|
+
// //
|
|
3766
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
3767
|
+
// //
|
|
3768
|
+
// if (shouldClearChanges) {
|
|
3769
|
+
// //
|
|
3770
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
3771
|
+
// //
|
|
3772
|
+
// this.onEndEncode(changeTrees);
|
|
3773
|
+
// }
|
|
3601
3774
|
return buffer.subarray(0, it.offset);
|
|
3602
3775
|
}
|
|
3603
3776
|
}
|
|
3604
3777
|
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3605
|
-
// console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.
|
|
3778
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
3606
3779
|
// this.debugChanges("allChanges");
|
|
3607
|
-
return this.encode(it, undefined, buffer,
|
|
3780
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3608
3781
|
}
|
|
3609
3782
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3610
3783
|
const viewOffset = it.offset;
|
|
3611
|
-
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.
|
|
3784
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
3612
3785
|
// this.debugChanges("allFilteredChanges");
|
|
3786
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
3613
3787
|
// try to encode "filtered" changes
|
|
3614
|
-
this.encode(it, view, bytes,
|
|
3788
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3615
3789
|
return Buffer.concat([
|
|
3616
3790
|
bytes.subarray(0, sharedOffset),
|
|
3617
3791
|
bytes.subarray(viewOffset, it.offset)
|
|
3618
3792
|
]);
|
|
3619
3793
|
}
|
|
3620
3794
|
debugChanges(field) {
|
|
3621
|
-
const
|
|
3795
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3622
3796
|
? this.root[field]
|
|
3623
3797
|
: field;
|
|
3624
|
-
|
|
3625
|
-
const
|
|
3626
|
-
|
|
3627
|
-
|
|
3798
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3799
|
+
const changeSet = changeTree[field];
|
|
3800
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3801
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3802
|
+
for (const index in changeSet) {
|
|
3803
|
+
const op = changeSet[index];
|
|
3628
3804
|
console.log(" ->", {
|
|
3629
3805
|
index,
|
|
3630
3806
|
field: metadata?.[index],
|
|
3631
3807
|
op: exports.OPERATION[op],
|
|
3632
3808
|
});
|
|
3633
|
-
}
|
|
3809
|
+
}
|
|
3634
3810
|
});
|
|
3635
3811
|
}
|
|
3636
3812
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
@@ -3640,23 +3816,31 @@ class Encoder {
|
|
|
3640
3816
|
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3641
3817
|
// this.debugChanges("filteredChanges");
|
|
3642
3818
|
// encode visibility changes (add/remove for this view)
|
|
3643
|
-
const
|
|
3644
|
-
|
|
3645
|
-
|
|
3646
|
-
|
|
3647
|
-
|
|
3819
|
+
const refIds = Object.keys(view.changes);
|
|
3820
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
3821
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3822
|
+
const refId = refIds[i];
|
|
3823
|
+
const changes = view.changes[refId];
|
|
3824
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3825
|
+
if (changeTree === undefined ||
|
|
3826
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3827
|
+
) {
|
|
3828
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3648
3829
|
continue;
|
|
3649
3830
|
}
|
|
3650
3831
|
const ref = changeTree.ref;
|
|
3651
|
-
const ctor = ref
|
|
3832
|
+
const ctor = ref.constructor;
|
|
3652
3833
|
const encoder = ctor[$encoder];
|
|
3834
|
+
const metadata = ctor[Symbol.metadata];
|
|
3653
3835
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3654
3836
|
number$1(bytes, changeTree.refId, it);
|
|
3655
|
-
const
|
|
3656
|
-
for (
|
|
3837
|
+
const keys = Object.keys(changes);
|
|
3838
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3839
|
+
const key = keys[i];
|
|
3840
|
+
const operation = changes[key];
|
|
3657
3841
|
// isEncodeAll = false
|
|
3658
3842
|
// hasView = true
|
|
3659
|
-
encoder(this, bytes, changeTree,
|
|
3843
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3660
3844
|
}
|
|
3661
3845
|
}
|
|
3662
3846
|
//
|
|
@@ -3664,40 +3848,55 @@ class Encoder {
|
|
|
3664
3848
|
// (to allow re-using StateView's for multiple clients)
|
|
3665
3849
|
//
|
|
3666
3850
|
// clear "view" changes after encoding
|
|
3667
|
-
view.changes
|
|
3851
|
+
view.changes = {};
|
|
3852
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
3668
3853
|
// try to encode "filtered" changes
|
|
3669
|
-
this.encode(it, view, bytes,
|
|
3854
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3670
3855
|
return Buffer.concat([
|
|
3671
3856
|
bytes.subarray(0, sharedOffset),
|
|
3672
3857
|
bytes.subarray(viewOffset, it.offset)
|
|
3673
3858
|
]);
|
|
3674
3859
|
}
|
|
3675
3860
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3676
|
-
|
|
3677
|
-
|
|
3678
|
-
|
|
3679
|
-
|
|
3680
|
-
|
|
3681
|
-
|
|
3682
|
-
|
|
3683
|
-
|
|
3684
|
-
|
|
3861
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3862
|
+
// changeTree.endEncode();
|
|
3863
|
+
// });
|
|
3864
|
+
// for (const refId in changeTrees) {
|
|
3865
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3866
|
+
// changeTree.endEncode();
|
|
3867
|
+
// // changeTree.changes.clear();
|
|
3868
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3869
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3870
|
+
// // // Not a new instance anymore
|
|
3871
|
+
// // delete changeTree[$isNew];
|
|
3872
|
+
// }
|
|
3685
3873
|
}
|
|
3686
3874
|
discardChanges() {
|
|
3875
|
+
// console.log("DISCARD CHANGES!");
|
|
3687
3876
|
// discard shared changes
|
|
3688
|
-
|
|
3689
|
-
|
|
3690
|
-
|
|
3877
|
+
let length = this.root.changes.length;
|
|
3878
|
+
if (length > 0) {
|
|
3879
|
+
while (length--) {
|
|
3880
|
+
this.root.changes[length]?.endEncode();
|
|
3881
|
+
}
|
|
3882
|
+
this.root.changes.length = 0;
|
|
3691
3883
|
}
|
|
3692
3884
|
// discard filtered changes
|
|
3693
|
-
|
|
3694
|
-
|
|
3695
|
-
|
|
3885
|
+
length = this.root.filteredChanges.length;
|
|
3886
|
+
if (length > 0) {
|
|
3887
|
+
while (length--) {
|
|
3888
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3889
|
+
}
|
|
3890
|
+
this.root.filteredChanges.length = 0;
|
|
3696
3891
|
}
|
|
3697
3892
|
}
|
|
3698
3893
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
3699
3894
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
3700
3895
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
3896
|
+
if (targetTypeId === undefined) {
|
|
3897
|
+
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.`);
|
|
3898
|
+
return;
|
|
3899
|
+
}
|
|
3701
3900
|
if (baseTypeId !== targetTypeId) {
|
|
3702
3901
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
3703
3902
|
number$1(bytes, targetTypeId, it);
|
|
@@ -3705,19 +3904,6 @@ class Encoder {
|
|
|
3705
3904
|
}
|
|
3706
3905
|
}
|
|
3707
3906
|
|
|
3708
|
-
function spliceOne(arr, index) {
|
|
3709
|
-
// manually splice an array
|
|
3710
|
-
if (index === -1 || index >= arr.length) {
|
|
3711
|
-
return false;
|
|
3712
|
-
}
|
|
3713
|
-
const len = arr.length - 1;
|
|
3714
|
-
for (let i = index; i < len; i++) {
|
|
3715
|
-
arr[i] = arr[i + 1];
|
|
3716
|
-
}
|
|
3717
|
-
arr.length = len;
|
|
3718
|
-
return true;
|
|
3719
|
-
}
|
|
3720
|
-
|
|
3721
3907
|
class DecodingWarning extends Error {
|
|
3722
3908
|
constructor(message) {
|
|
3723
3909
|
super(message);
|
|
@@ -3879,7 +4065,7 @@ class Decoder {
|
|
|
3879
4065
|
}
|
|
3880
4066
|
ref[$onDecodeEnd]?.();
|
|
3881
4067
|
ref = nextRef;
|
|
3882
|
-
decoder = ref
|
|
4068
|
+
decoder = ref.constructor[$decoder];
|
|
3883
4069
|
continue;
|
|
3884
4070
|
}
|
|
3885
4071
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -3980,10 +4166,16 @@ class Reflection extends Schema {
|
|
|
3980
4166
|
super(...arguments);
|
|
3981
4167
|
this.types = new ArraySchema();
|
|
3982
4168
|
}
|
|
3983
|
-
|
|
3984
|
-
|
|
4169
|
+
/**
|
|
4170
|
+
* Encodes the TypeContext of an Encoder into a buffer.
|
|
4171
|
+
*
|
|
4172
|
+
* @param context TypeContext instance
|
|
4173
|
+
* @param it
|
|
4174
|
+
* @returns
|
|
4175
|
+
*/
|
|
4176
|
+
static encode(context, it = { offset: 0 }) {
|
|
3985
4177
|
const reflection = new Reflection();
|
|
3986
|
-
const
|
|
4178
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
3987
4179
|
const buildType = (currentType, metadata) => {
|
|
3988
4180
|
for (const fieldIndex in metadata) {
|
|
3989
4181
|
const index = Number(fieldIndex);
|
|
@@ -4037,9 +4229,16 @@ class Reflection extends Schema {
|
|
|
4037
4229
|
}
|
|
4038
4230
|
buildType(type, klass[Symbol.metadata]);
|
|
4039
4231
|
}
|
|
4040
|
-
const buf =
|
|
4232
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
4041
4233
|
return Buffer.from(buf, 0, it.offset);
|
|
4042
4234
|
}
|
|
4235
|
+
/**
|
|
4236
|
+
* Decodes the TypeContext from a buffer into a Decoder instance.
|
|
4237
|
+
*
|
|
4238
|
+
* @param bytes Reflection.encode() output
|
|
4239
|
+
* @param it
|
|
4240
|
+
* @returns Decoder instance
|
|
4241
|
+
*/
|
|
4043
4242
|
static decode(bytes, it) {
|
|
4044
4243
|
const reflection = new Reflection();
|
|
4045
4244
|
const reflectionDecoder = new Decoder(reflection);
|
|
@@ -4085,8 +4284,8 @@ class Reflection extends Schema {
|
|
|
4085
4284
|
}
|
|
4086
4285
|
});
|
|
4087
4286
|
});
|
|
4088
|
-
|
|
4089
|
-
return new (typeContext
|
|
4287
|
+
const state = new (typeContext.get(0))();
|
|
4288
|
+
return new Decoder(state, typeContext);
|
|
4090
4289
|
}
|
|
4091
4290
|
}
|
|
4092
4291
|
__decorate([
|
|
@@ -4356,7 +4555,7 @@ class StateView {
|
|
|
4356
4555
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
4357
4556
|
* (This is used to force encoding a property, even if it was not changed)
|
|
4358
4557
|
*/
|
|
4359
|
-
this.changes =
|
|
4558
|
+
this.changes = {};
|
|
4360
4559
|
}
|
|
4361
4560
|
// TODO: allow to set multiple tags at once
|
|
4362
4561
|
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
@@ -4378,10 +4577,10 @@ class StateView {
|
|
|
4378
4577
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
4379
4578
|
// be set (only the parent's changes are set)
|
|
4380
4579
|
//
|
|
4381
|
-
let changes = this.changes.
|
|
4580
|
+
let changes = this.changes[changeTree.refId];
|
|
4382
4581
|
if (changes === undefined) {
|
|
4383
|
-
changes =
|
|
4384
|
-
this.changes.
|
|
4582
|
+
changes = {};
|
|
4583
|
+
this.changes[changeTree.refId] = changes;
|
|
4385
4584
|
}
|
|
4386
4585
|
// set tag
|
|
4387
4586
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -4398,9 +4597,9 @@ class StateView {
|
|
|
4398
4597
|
}
|
|
4399
4598
|
tags.add(tag);
|
|
4400
4599
|
// Ref: add tagged properties
|
|
4401
|
-
metadata?.[
|
|
4600
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4402
4601
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
4403
|
-
changes
|
|
4602
|
+
changes[index] = exports.OPERATION.ADD;
|
|
4404
4603
|
}
|
|
4405
4604
|
});
|
|
4406
4605
|
}
|
|
@@ -4409,16 +4608,21 @@ class StateView {
|
|
|
4409
4608
|
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4410
4609
|
? changeTree.allFilteredChanges
|
|
4411
4610
|
: changeTree.allChanges;
|
|
4412
|
-
|
|
4611
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4612
|
+
const index = changeSet.operations[i];
|
|
4613
|
+
if (index === undefined) {
|
|
4614
|
+
continue;
|
|
4615
|
+
} // skip "undefined" indexes
|
|
4616
|
+
const op = changeTree.indexedOperations[index];
|
|
4413
4617
|
const tagAtIndex = metadata?.[index].tag;
|
|
4414
4618
|
if ((isInvisible || // if "invisible", include all
|
|
4415
4619
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4416
4620
|
tagAtIndex === tag // tagged property
|
|
4417
4621
|
) &&
|
|
4418
4622
|
op !== exports.OPERATION.DELETE) {
|
|
4419
|
-
changes
|
|
4623
|
+
changes[index] = op;
|
|
4420
4624
|
}
|
|
4421
|
-
}
|
|
4625
|
+
}
|
|
4422
4626
|
}
|
|
4423
4627
|
// Add children of this ChangeTree to this view
|
|
4424
4628
|
changeTree.forEachChild((change, index) => {
|
|
@@ -4444,10 +4648,10 @@ class StateView {
|
|
|
4444
4648
|
}
|
|
4445
4649
|
// add parent's tag properties
|
|
4446
4650
|
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4447
|
-
let changes = this.changes.
|
|
4651
|
+
let changes = this.changes[changeTree.refId];
|
|
4448
4652
|
if (changes === undefined) {
|
|
4449
|
-
changes =
|
|
4450
|
-
this.changes.
|
|
4653
|
+
changes = {};
|
|
4654
|
+
this.changes[changeTree.refId] = changes;
|
|
4451
4655
|
}
|
|
4452
4656
|
if (!this.tags) {
|
|
4453
4657
|
this.tags = new WeakMap();
|
|
@@ -4461,7 +4665,7 @@ class StateView {
|
|
|
4461
4665
|
tags = this.tags.get(changeTree);
|
|
4462
4666
|
}
|
|
4463
4667
|
tags.add(tag);
|
|
4464
|
-
changes
|
|
4668
|
+
changes[parentIndex] = exports.OPERATION.ADD;
|
|
4465
4669
|
}
|
|
4466
4670
|
}
|
|
4467
4671
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4473,32 +4677,32 @@ class StateView {
|
|
|
4473
4677
|
this.items.delete(changeTree);
|
|
4474
4678
|
const ref = changeTree.ref;
|
|
4475
4679
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4476
|
-
let changes = this.changes.
|
|
4680
|
+
let changes = this.changes[changeTree.refId];
|
|
4477
4681
|
if (changes === undefined) {
|
|
4478
|
-
changes =
|
|
4479
|
-
this.changes.
|
|
4682
|
+
changes = {};
|
|
4683
|
+
this.changes[changeTree.refId] = changes;
|
|
4480
4684
|
}
|
|
4481
4685
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4482
4686
|
// parent is collection (Map/Array)
|
|
4483
4687
|
const parent = changeTree.parent;
|
|
4484
4688
|
if (!Metadata.isValidInstance(parent)) {
|
|
4485
4689
|
const parentChangeTree = parent[$changes];
|
|
4486
|
-
let changes = this.changes.
|
|
4690
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4487
4691
|
if (changes === undefined) {
|
|
4488
|
-
changes =
|
|
4489
|
-
this.changes.
|
|
4692
|
+
changes = {};
|
|
4693
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4490
4694
|
}
|
|
4491
4695
|
// DELETE / DELETE BY REF ID
|
|
4492
|
-
changes
|
|
4696
|
+
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
4493
4697
|
}
|
|
4494
4698
|
else {
|
|
4495
4699
|
// delete all "tagged" properties.
|
|
4496
|
-
metadata[
|
|
4700
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4497
4701
|
}
|
|
4498
4702
|
}
|
|
4499
4703
|
else {
|
|
4500
4704
|
// delete only tagged properties
|
|
4501
|
-
metadata[
|
|
4705
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4502
4706
|
}
|
|
4503
4707
|
// remove tag
|
|
4504
4708
|
if (this.tags && this.tags.has(changeTree)) {
|