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