@colyseus/schema 3.0.0-alpha.30 → 3.0.0-alpha.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/index.js +383 -341
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +383 -341
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +383 -341
- package/lib/Metadata.d.ts +14 -5
- package/lib/Metadata.js +49 -20
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.js +4 -13
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.js +26 -39
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +1 -2
- package/lib/annotations.js +58 -52
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +33 -11
- package/lib/bench_encode.js.map +1 -1
- package/lib/decoder/DecodeOperation.js +4 -8
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/ReferenceTracker.js +3 -2
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/decoder/strategy/StateCallbacks.js +4 -3
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +8 -7
- package/lib/encoder/ChangeTree.js +128 -115
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +1 -4
- package/lib/encoder/EncodeOperation.js +46 -46
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.js +11 -3
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/StateView.js +3 -3
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/assert.d.ts +2 -1
- package/lib/encoding/assert.js +2 -2
- package/lib/encoding/assert.js.map +1 -1
- package/lib/index.d.ts +1 -2
- package/lib/index.js +11 -10
- package/lib/index.js.map +1 -1
- package/lib/types/TypeContext.js +7 -14
- package/lib/types/TypeContext.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +6 -0
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.js +1 -0
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.js +5 -0
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.js +1 -0
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/symbols.d.ts +1 -0
- package/lib/types/symbols.js +2 -1
- package/lib/types/symbols.js.map +1 -1
- package/package.json +1 -1
- package/src/Metadata.ts +60 -29
- package/src/Reflection.ts +5 -15
- package/src/Schema.ts +33 -45
- package/src/annotations.ts +75 -67
- package/src/bench_encode.ts +37 -13
- package/src/decoder/DecodeOperation.ts +4 -10
- package/src/decoder/ReferenceTracker.ts +3 -2
- package/src/decoder/strategy/StateCallbacks.ts +4 -3
- package/src/encoder/ChangeTree.ts +146 -135
- package/src/encoder/EncodeOperation.ts +64 -58
- package/src/encoder/Encoder.ts +16 -4
- package/src/encoder/StateView.ts +4 -4
- package/src/encoding/assert.ts +4 -3
- package/src/index.ts +1 -4
- package/src/types/TypeContext.ts +10 -15
- package/src/types/custom/ArraySchema.ts +8 -0
- package/src/types/custom/CollectionSchema.ts +1 -0
- package/src/types/custom/MapSchema.ts +6 -0
- package/src/types/custom/SetSchema.ts +1 -0
- package/src/types/symbols.ts +2 -0
package/build/umd/index.js
CHANGED
|
@@ -40,6 +40,7 @@
|
|
|
40
40
|
const $filter = Symbol("$filter");
|
|
41
41
|
const $getByIndex = Symbol("$getByIndex");
|
|
42
42
|
const $deleteByIndex = Symbol("$deleteByIndex");
|
|
43
|
+
const $descriptors = Symbol("$descriptors");
|
|
43
44
|
/**
|
|
44
45
|
* Used to hold ChangeTree instances whitin the structures
|
|
45
46
|
*/
|
|
@@ -75,34 +76,67 @@
|
|
|
75
76
|
}
|
|
76
77
|
|
|
77
78
|
const Metadata = {
|
|
78
|
-
addField(metadata, index,
|
|
79
|
+
addField(metadata, index, name, type, descriptor) {
|
|
79
80
|
if (index > 64) {
|
|
80
|
-
throw new Error(`Can't define field '${
|
|
81
|
+
throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
|
|
81
82
|
}
|
|
82
|
-
metadata[
|
|
83
|
+
metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
|
|
83
84
|
{
|
|
84
85
|
type: (Array.isArray(type))
|
|
85
86
|
? { array: type[0] }
|
|
86
87
|
: type,
|
|
87
88
|
index,
|
|
88
|
-
|
|
89
|
+
name,
|
|
89
90
|
});
|
|
91
|
+
// create "descriptors" map
|
|
92
|
+
metadata[$descriptors] ??= {};
|
|
93
|
+
if (descriptor) {
|
|
94
|
+
// for encoder
|
|
95
|
+
metadata[$descriptors][name] = descriptor;
|
|
96
|
+
metadata[$descriptors][`_${name}`] = {
|
|
97
|
+
value: undefined,
|
|
98
|
+
writable: true,
|
|
99
|
+
enumerable: false,
|
|
100
|
+
configurable: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// for decoder
|
|
105
|
+
metadata[$descriptors][name] = {
|
|
106
|
+
value: undefined,
|
|
107
|
+
writable: true,
|
|
108
|
+
enumerable: true,
|
|
109
|
+
configurable: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
90
112
|
// map -1 as last field index
|
|
91
113
|
Object.defineProperty(metadata, -1, {
|
|
92
114
|
value: index,
|
|
93
115
|
enumerable: false,
|
|
94
116
|
configurable: true
|
|
95
117
|
});
|
|
96
|
-
// map
|
|
97
|
-
Object.defineProperty(metadata,
|
|
98
|
-
value:
|
|
118
|
+
// map field name => index (non enumerable)
|
|
119
|
+
Object.defineProperty(metadata, name, {
|
|
120
|
+
value: index,
|
|
99
121
|
enumerable: false,
|
|
100
122
|
configurable: true,
|
|
101
123
|
});
|
|
124
|
+
// if child Ref/complex type, add to -4
|
|
125
|
+
if (typeof (metadata[index].type) !== "string") {
|
|
126
|
+
if (metadata[-4] === undefined) {
|
|
127
|
+
Object.defineProperty(metadata, -4, {
|
|
128
|
+
value: [],
|
|
129
|
+
enumerable: false,
|
|
130
|
+
configurable: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
metadata[-4].push(index);
|
|
134
|
+
}
|
|
102
135
|
},
|
|
103
136
|
setTag(metadata, fieldName, tag) {
|
|
137
|
+
const index = metadata[fieldName];
|
|
138
|
+
const field = metadata[index];
|
|
104
139
|
// add 'tag' to the field
|
|
105
|
-
const field = metadata[fieldName];
|
|
106
140
|
field.tag = tag;
|
|
107
141
|
if (!metadata[-2]) {
|
|
108
142
|
// -2: all field indexes with "view" tag
|
|
@@ -118,20 +152,14 @@
|
|
|
118
152
|
configurable: true
|
|
119
153
|
});
|
|
120
154
|
}
|
|
121
|
-
metadata[-2].push(
|
|
155
|
+
metadata[-2].push(index);
|
|
122
156
|
if (!metadata[-3][tag]) {
|
|
123
157
|
metadata[-3][tag] = [];
|
|
124
158
|
}
|
|
125
|
-
metadata[-3][tag].push(
|
|
159
|
+
metadata[-3][tag].push(index);
|
|
126
160
|
},
|
|
127
161
|
setFields(target, fields) {
|
|
128
162
|
const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
|
|
129
|
-
// target[$track] = function (changeTree, index: number, operation: OPERATION = OPERATION.ADD) {
|
|
130
|
-
// changeTree.change(index, operation, encodeSchemaOperation);
|
|
131
|
-
// };
|
|
132
|
-
// target[$encoder] = encodeSchemaOperation;
|
|
133
|
-
// target[$decoder] = decodeSchemaOperation;
|
|
134
|
-
// if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
|
|
135
163
|
let index = 0;
|
|
136
164
|
for (const field in fields) {
|
|
137
165
|
const type = fields[field];
|
|
@@ -139,7 +167,7 @@
|
|
|
139
167
|
const complexTypeKlass = (Array.isArray(type))
|
|
140
168
|
? getType("array")
|
|
141
169
|
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
142
|
-
Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass
|
|
170
|
+
Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
|
|
143
171
|
index++;
|
|
144
172
|
}
|
|
145
173
|
},
|
|
@@ -168,8 +196,9 @@
|
|
|
168
196
|
// assign parent metadata to current
|
|
169
197
|
Object.assign(metadata, parentMetadata);
|
|
170
198
|
for (let i = 0; i <= parentMetadata[-1]; i++) {
|
|
171
|
-
|
|
172
|
-
|
|
199
|
+
const fieldName = parentMetadata[i].name;
|
|
200
|
+
Object.defineProperty(metadata, fieldName, {
|
|
201
|
+
value: parentMetadata[fieldName],
|
|
173
202
|
enumerable: false,
|
|
174
203
|
configurable: true,
|
|
175
204
|
});
|
|
@@ -193,7 +222,7 @@
|
|
|
193
222
|
const metadata = klass[Symbol.metadata];
|
|
194
223
|
const fields = {};
|
|
195
224
|
for (let i = 0; i <= metadata[-1]; i++) {
|
|
196
|
-
fields[metadata[i]] = metadata[
|
|
225
|
+
fields[metadata[i].name] = metadata[i].type;
|
|
197
226
|
}
|
|
198
227
|
return fields;
|
|
199
228
|
}
|
|
@@ -202,48 +231,59 @@
|
|
|
202
231
|
var _a$5;
|
|
203
232
|
class ChangeTree {
|
|
204
233
|
static { _a$5 = $isNew; }
|
|
205
|
-
;
|
|
206
234
|
constructor(ref) {
|
|
207
|
-
this.
|
|
235
|
+
this.isFiltered = false;
|
|
236
|
+
this.isPartiallyFiltered = false;
|
|
208
237
|
this.currentOperationIndex = 0;
|
|
209
|
-
this.allChanges = new Map();
|
|
210
|
-
this.allFilteredChanges = new Map();
|
|
211
238
|
this.changes = new Map();
|
|
212
|
-
this.
|
|
239
|
+
this.allChanges = new Map();
|
|
213
240
|
this[_a$5] = true;
|
|
214
241
|
this.ref = ref;
|
|
242
|
+
//
|
|
243
|
+
// Does this structure have "filters" declared?
|
|
244
|
+
//
|
|
245
|
+
if (ref.constructor[Symbol.metadata]?.[-2]) {
|
|
246
|
+
this.allFilteredChanges = new Map();
|
|
247
|
+
this.filteredChanges = new Map();
|
|
248
|
+
}
|
|
215
249
|
}
|
|
216
250
|
setRoot(root) {
|
|
217
251
|
this.root = root;
|
|
218
252
|
this.root.add(this);
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
253
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
254
|
+
if (this.root.types.hasFilters) {
|
|
255
|
+
//
|
|
256
|
+
// At Schema initialization, the "root" structure might not be available
|
|
257
|
+
// yet, as it only does once the "Encoder" has been set up.
|
|
258
|
+
//
|
|
259
|
+
// So the "parent" may be already set without a "root".
|
|
260
|
+
//
|
|
261
|
+
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
262
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
263
|
+
this.root.allFilteredChanges.set(this, this.allFilteredChanges);
|
|
264
|
+
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
228
267
|
if (!this.isFiltered) {
|
|
229
268
|
this.root.changes.set(this, this.changes);
|
|
269
|
+
this.root.allChanges.set(this, this.allChanges);
|
|
230
270
|
}
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
271
|
+
this.ensureRefId();
|
|
272
|
+
if (metadata) {
|
|
273
|
+
metadata[-4]?.forEach((index) => {
|
|
274
|
+
const field = metadata[index];
|
|
275
|
+
const value = this.ref[field.name];
|
|
276
|
+
if (value) {
|
|
277
|
+
value[$changes].setRoot(root);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
234
280
|
}
|
|
235
|
-
if (
|
|
236
|
-
|
|
281
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
282
|
+
// MapSchema / ArraySchema, etc.
|
|
283
|
+
this.ref.forEach((value, key) => {
|
|
284
|
+
value[$changes].setRoot(root);
|
|
285
|
+
});
|
|
237
286
|
}
|
|
238
|
-
this.forEachChild((changeTree, _) => {
|
|
239
|
-
changeTree.setRoot(root);
|
|
240
|
-
});
|
|
241
|
-
// this.allChanges.forEach((_, index) => {
|
|
242
|
-
// const childRef = this.ref[$getByIndex](index);
|
|
243
|
-
// if (childRef && childRef[$changes]) {
|
|
244
|
-
// childRef[$changes].setRoot(root);
|
|
245
|
-
// }
|
|
246
|
-
// });
|
|
247
287
|
}
|
|
248
288
|
setParent(parent, root, parentIndex) {
|
|
249
289
|
this.parent = parent;
|
|
@@ -253,48 +293,60 @@
|
|
|
253
293
|
return;
|
|
254
294
|
}
|
|
255
295
|
root.add(this);
|
|
296
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
256
297
|
// skip if parent is already set
|
|
257
|
-
if (root
|
|
258
|
-
this.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
298
|
+
if (root !== this.root) {
|
|
299
|
+
this.root = root;
|
|
300
|
+
if (root.types.hasFilters) {
|
|
301
|
+
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
302
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
303
|
+
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
304
|
+
this.root.allFilteredChanges.set(this, this.filteredChanges);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (!this.isFiltered) {
|
|
308
|
+
this.root.changes.set(this, this.changes);
|
|
309
|
+
this.root.allChanges.set(this, this.allChanges);
|
|
310
|
+
}
|
|
311
|
+
this.ensureRefId();
|
|
262
312
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
313
|
+
// assign same parent on child structures
|
|
314
|
+
if (metadata) {
|
|
315
|
+
metadata[-4]?.forEach((index) => {
|
|
316
|
+
const field = metadata[index];
|
|
317
|
+
const value = this.ref[field.name];
|
|
318
|
+
value?.[$changes].setParent(this.ref, root, index);
|
|
319
|
+
// console.log(this.ref.constructor.name, field.name, value);
|
|
320
|
+
// try { throw new Error(); } catch (e) {
|
|
321
|
+
// console.log(e.stack);
|
|
322
|
+
// }
|
|
323
|
+
});
|
|
268
324
|
}
|
|
269
|
-
if (this.
|
|
270
|
-
|
|
271
|
-
this.
|
|
325
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
326
|
+
// MapSchema / ArraySchema, etc.
|
|
327
|
+
this.ref.forEach((value, key) => {
|
|
328
|
+
value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
|
|
329
|
+
});
|
|
272
330
|
}
|
|
273
|
-
this.ensureRefId();
|
|
274
|
-
this.forEachChild((changeTree, atIndex) => {
|
|
275
|
-
changeTree.setParent(this.ref, root, atIndex);
|
|
276
|
-
});
|
|
277
331
|
}
|
|
278
332
|
forEachChild(callback) {
|
|
279
333
|
//
|
|
280
334
|
// assign same parent on child structures
|
|
281
335
|
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
const value = this.ref[field];
|
|
287
|
-
if (value
|
|
288
|
-
callback(value[$changes],
|
|
336
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
337
|
+
if (metadata) {
|
|
338
|
+
metadata[-4]?.forEach((index) => {
|
|
339
|
+
const field = metadata[index];
|
|
340
|
+
const value = this.ref[field.name];
|
|
341
|
+
if (value) {
|
|
342
|
+
callback(value[$changes], index);
|
|
289
343
|
}
|
|
290
|
-
}
|
|
344
|
+
});
|
|
291
345
|
}
|
|
292
|
-
else if (typeof (this.ref)
|
|
346
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
293
347
|
// MapSchema / ArraySchema, etc.
|
|
294
348
|
this.ref.forEach((value, key) => {
|
|
295
|
-
|
|
296
|
-
callback(value[$changes], this.ref[$changes].indexes[key] ?? key);
|
|
297
|
-
}
|
|
349
|
+
callback(value[$changes], this.indexes[key] ?? key);
|
|
298
350
|
});
|
|
299
351
|
}
|
|
300
352
|
}
|
|
@@ -303,8 +355,8 @@
|
|
|
303
355
|
this.root?.changes.set(this, this.changes);
|
|
304
356
|
}
|
|
305
357
|
change(index, operation = exports.OPERATION.ADD) {
|
|
306
|
-
const metadata = this.ref
|
|
307
|
-
const isFiltered = this.isFiltered || (metadata
|
|
358
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
359
|
+
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
308
360
|
const changeSet = (isFiltered)
|
|
309
361
|
? this.filteredChanges
|
|
310
362
|
: this.changes;
|
|
@@ -315,14 +367,14 @@
|
|
|
315
367
|
: (previousOperation === exports.OPERATION.DELETE)
|
|
316
368
|
? exports.OPERATION.DELETE_AND_ADD
|
|
317
369
|
: operation;
|
|
370
|
+
//
|
|
371
|
+
// TODO: are DELETE operations being encoded as ADD here ??
|
|
372
|
+
//
|
|
318
373
|
changeSet.set(index, op);
|
|
319
374
|
}
|
|
320
|
-
//
|
|
321
|
-
// TODO: are DELETE operations being encoded as ADD here ??
|
|
322
|
-
//
|
|
323
375
|
if (isFiltered) {
|
|
324
|
-
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
325
376
|
this.allFilteredChanges.set(index, exports.OPERATION.ADD);
|
|
377
|
+
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
326
378
|
this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
|
|
327
379
|
}
|
|
328
380
|
else {
|
|
@@ -369,9 +421,7 @@
|
|
|
369
421
|
});
|
|
370
422
|
}
|
|
371
423
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
372
|
-
|
|
373
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
374
|
-
if (isFiltered) {
|
|
424
|
+
if (this.filteredChanges !== undefined) {
|
|
375
425
|
this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
|
|
376
426
|
this.filteredChanges.set(index, operation);
|
|
377
427
|
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
@@ -384,8 +434,8 @@
|
|
|
384
434
|
}
|
|
385
435
|
getType(index) {
|
|
386
436
|
if (Metadata.isValidInstance(this.ref)) {
|
|
387
|
-
const metadata = this.ref
|
|
388
|
-
return metadata[
|
|
437
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
438
|
+
return metadata[index].type;
|
|
389
439
|
}
|
|
390
440
|
else {
|
|
391
441
|
//
|
|
@@ -399,7 +449,7 @@
|
|
|
399
449
|
}
|
|
400
450
|
getChange(index) {
|
|
401
451
|
// TODO: optimize this. avoid checking against multiple instances
|
|
402
|
-
return this.changes.get(index) ?? this.filteredChanges
|
|
452
|
+
return this.changes.get(index) ?? this.filteredChanges?.get(index);
|
|
403
453
|
}
|
|
404
454
|
//
|
|
405
455
|
// used during `.encode()`
|
|
@@ -420,9 +470,7 @@
|
|
|
420
470
|
}
|
|
421
471
|
return;
|
|
422
472
|
}
|
|
423
|
-
const
|
|
424
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
425
|
-
const changeSet = (isFiltered)
|
|
473
|
+
const changeSet = (this.filteredChanges)
|
|
426
474
|
? this.filteredChanges
|
|
427
475
|
: this.changes;
|
|
428
476
|
const previousValue = this.getValue(index);
|
|
@@ -445,7 +493,7 @@
|
|
|
445
493
|
//
|
|
446
494
|
// FIXME: this is looking a bit ugly (and repeated from `.change()`)
|
|
447
495
|
//
|
|
448
|
-
if (
|
|
496
|
+
if (this.filteredChanges) {
|
|
449
497
|
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
450
498
|
this.allFilteredChanges.delete(allChangesIndex);
|
|
451
499
|
}
|
|
@@ -456,6 +504,7 @@
|
|
|
456
504
|
}
|
|
457
505
|
endEncode() {
|
|
458
506
|
this.changes.clear();
|
|
507
|
+
// ArraySchema and MapSchema have a custom "encode end" method
|
|
459
508
|
this.ref[$onEncodeEnd]?.();
|
|
460
509
|
// Not a new instance anymore
|
|
461
510
|
delete this[$isNew];
|
|
@@ -468,12 +517,12 @@
|
|
|
468
517
|
//
|
|
469
518
|
this.ref[$onEncodeEnd]?.();
|
|
470
519
|
this.changes.clear();
|
|
471
|
-
this.filteredChanges
|
|
520
|
+
this.filteredChanges?.clear();
|
|
472
521
|
// reset operation index
|
|
473
522
|
this.currentOperationIndex = 0;
|
|
474
523
|
if (discardAll) {
|
|
475
524
|
this.allChanges.clear();
|
|
476
|
-
this.allFilteredChanges
|
|
525
|
+
this.allFilteredChanges?.clear();
|
|
477
526
|
// remove children references
|
|
478
527
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
479
528
|
}
|
|
@@ -500,48 +549,41 @@
|
|
|
500
549
|
get changed() {
|
|
501
550
|
return this.changes.size > 0;
|
|
502
551
|
}
|
|
503
|
-
checkIsFiltered(parent, parentIndex) {
|
|
552
|
+
checkIsFiltered(metadata, parent, parentIndex) {
|
|
504
553
|
// Detect if current structure has "filters" declared
|
|
505
|
-
this.isPartiallyFiltered =
|
|
506
|
-
if (
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
const changes = this.changes;
|
|
539
|
-
this.changes = this.filteredChanges;
|
|
540
|
-
this.filteredChanges = changes;
|
|
541
|
-
// swap "all changes" reference
|
|
542
|
-
const allFilteredChanges = this.allFilteredChanges;
|
|
543
|
-
this.allFilteredChanges = this.allChanges;
|
|
544
|
-
this.allChanges = allFilteredChanges;
|
|
554
|
+
this.isPartiallyFiltered = metadata?.[-2] !== undefined;
|
|
555
|
+
if (this.isPartiallyFiltered) {
|
|
556
|
+
this.filteredChanges = this.filteredChanges || new Map();
|
|
557
|
+
this.allFilteredChanges = this.allFilteredChanges || new Map();
|
|
558
|
+
}
|
|
559
|
+
if (parent) {
|
|
560
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
561
|
+
const parentChangeTree = parent[$changes];
|
|
562
|
+
parent = parentChangeTree.parent;
|
|
563
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
564
|
+
}
|
|
565
|
+
const parentMetadata = parent?.constructor?.[Symbol.metadata];
|
|
566
|
+
this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
|
|
567
|
+
//
|
|
568
|
+
// TODO: refactor this!
|
|
569
|
+
//
|
|
570
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
571
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
572
|
+
//
|
|
573
|
+
if (this.isFiltered) {
|
|
574
|
+
this.filteredChanges = new Map();
|
|
575
|
+
this.allFilteredChanges = new Map();
|
|
576
|
+
if (this.changes.size > 0) {
|
|
577
|
+
// swap changes reference
|
|
578
|
+
const changes = this.changes;
|
|
579
|
+
this.changes = this.filteredChanges;
|
|
580
|
+
this.filteredChanges = changes;
|
|
581
|
+
// swap "all changes" reference
|
|
582
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
583
|
+
this.allFilteredChanges = this.allChanges;
|
|
584
|
+
this.allChanges = allFilteredChanges;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
545
587
|
}
|
|
546
588
|
}
|
|
547
589
|
}
|
|
@@ -825,62 +867,21 @@
|
|
|
825
867
|
writeFloat64: writeFloat64
|
|
826
868
|
});
|
|
827
869
|
|
|
828
|
-
|
|
829
|
-
|
|
830
|
-
|
|
831
|
-
|
|
832
|
-
|
|
833
|
-
|
|
834
|
-
|
|
835
|
-
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
case "int32":
|
|
840
|
-
case "uint32":
|
|
841
|
-
case "int64":
|
|
842
|
-
case "uint64":
|
|
843
|
-
case "float32":
|
|
844
|
-
case "float64":
|
|
845
|
-
typeofTarget = "number";
|
|
846
|
-
if (isNaN(value)) {
|
|
847
|
-
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
848
|
-
}
|
|
849
|
-
break;
|
|
850
|
-
case "string":
|
|
851
|
-
typeofTarget = "string";
|
|
852
|
-
allowNull = true;
|
|
853
|
-
break;
|
|
854
|
-
case "boolean":
|
|
855
|
-
// boolean is always encoded as true/false based on truthiness
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
859
|
-
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
860
|
-
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
861
|
-
}
|
|
862
|
-
}
|
|
863
|
-
function assertInstanceType(value, type, klass, field) {
|
|
864
|
-
if (!(value instanceof type)) {
|
|
865
|
-
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
|
|
866
|
-
}
|
|
867
|
-
}
|
|
868
|
-
|
|
869
|
-
function encodePrimitiveType(type, bytes, value, klass, field, it) {
|
|
870
|
-
assertType(value, type, klass, field);
|
|
871
|
-
const encodeFunc = encode[type];
|
|
872
|
-
if (encodeFunc) {
|
|
873
|
-
encodeFunc(bytes, value, it);
|
|
874
|
-
// encodeFunc(bytes, value);
|
|
875
|
-
}
|
|
876
|
-
else {
|
|
877
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
870
|
+
function encodeValue(encoder, bytes,
|
|
871
|
+
// ref: Ref,
|
|
872
|
+
type, value,
|
|
873
|
+
// field: string | number,
|
|
874
|
+
operation, it) {
|
|
875
|
+
if (typeof (type) === "string") {
|
|
876
|
+
//
|
|
877
|
+
// Primitive values
|
|
878
|
+
//
|
|
879
|
+
// assertType(value, type as string, ref as Schema, field);
|
|
880
|
+
encode[type]?.(bytes, value, it);
|
|
878
881
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
|
|
882
|
-
// TODO: move this to the `@type()` annotation
|
|
883
|
-
assertInstanceType(value, type, ref, field);
|
|
882
|
+
else if (type[Symbol.metadata] !== undefined) {
|
|
883
|
+
// // TODO: move this to the `@type()` annotation
|
|
884
|
+
// assertInstanceType(value, type as typeof Schema, ref as Schema, field);
|
|
884
885
|
//
|
|
885
886
|
// Encode refId for this instance.
|
|
886
887
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -891,21 +892,15 @@
|
|
|
891
892
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
892
893
|
}
|
|
893
894
|
}
|
|
894
|
-
else if (typeof (type) === "string") {
|
|
895
|
-
//
|
|
896
|
-
// Primitive values
|
|
897
|
-
//
|
|
898
|
-
encodePrimitiveType(type, bytes, value, ref, field, it);
|
|
899
|
-
}
|
|
900
895
|
else {
|
|
901
|
-
//
|
|
902
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
903
|
-
//
|
|
904
|
-
const definition = getType(Object.keys(type)[0]);
|
|
905
|
-
//
|
|
906
|
-
// ensure a ArraySchema has been provided
|
|
907
|
-
//
|
|
908
|
-
assertInstanceType(ref[field], definition.constructor, ref, field);
|
|
896
|
+
// //
|
|
897
|
+
// // Custom type (MapSchema, ArraySchema, etc)
|
|
898
|
+
// //
|
|
899
|
+
// const definition = getType(Object.keys(type)[0]);
|
|
900
|
+
// //
|
|
901
|
+
// // ensure a ArraySchema has been provided
|
|
902
|
+
// //
|
|
903
|
+
// assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
|
|
909
904
|
//
|
|
910
905
|
// Encode refId for this instance.
|
|
911
906
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -918,26 +913,27 @@
|
|
|
918
913
|
* @private
|
|
919
914
|
*/
|
|
920
915
|
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
921
|
-
const ref = changeTree.ref;
|
|
922
|
-
const metadata = ref['constructor'][Symbol.metadata];
|
|
923
|
-
const field = metadata[index];
|
|
924
|
-
const type = metadata[field].type;
|
|
925
|
-
const value = ref[field];
|
|
926
916
|
// "compress" field index + operation
|
|
927
917
|
bytes[it.offset++] = (index | operation) & 255;
|
|
928
918
|
// Do not encode value for DELETE operations
|
|
929
919
|
if (operation === exports.OPERATION.DELETE) {
|
|
930
920
|
return;
|
|
931
921
|
}
|
|
922
|
+
const ref = changeTree.ref;
|
|
923
|
+
const metadata = ref['constructor'][Symbol.metadata];
|
|
924
|
+
const field = metadata[index];
|
|
932
925
|
// TODO: inline this function call small performance gain
|
|
933
|
-
encodeValue(encoder, bytes,
|
|
926
|
+
encodeValue(encoder, bytes,
|
|
927
|
+
// ref,
|
|
928
|
+
metadata[index].type, ref[field.name],
|
|
929
|
+
// index,
|
|
930
|
+
operation, it);
|
|
934
931
|
};
|
|
935
932
|
/**
|
|
936
933
|
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
937
934
|
* @private
|
|
938
935
|
*/
|
|
939
|
-
const encodeKeyValueOperation = function (encoder, bytes, changeTree,
|
|
940
|
-
const ref = changeTree.ref;
|
|
936
|
+
const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
941
937
|
// encode operation
|
|
942
938
|
bytes[it.offset++] = operation & 255;
|
|
943
939
|
// custom operations
|
|
@@ -945,11 +941,12 @@
|
|
|
945
941
|
return;
|
|
946
942
|
}
|
|
947
943
|
// encode index
|
|
948
|
-
number$1(bytes,
|
|
944
|
+
number$1(bytes, index, it);
|
|
949
945
|
// Do not encode value for DELETE operations
|
|
950
946
|
if (operation === exports.OPERATION.DELETE) {
|
|
951
947
|
return;
|
|
952
948
|
}
|
|
949
|
+
const ref = changeTree.ref;
|
|
953
950
|
//
|
|
954
951
|
// encode "alias" for dynamic fields (maps)
|
|
955
952
|
//
|
|
@@ -958,12 +955,12 @@
|
|
|
958
955
|
//
|
|
959
956
|
// MapSchema dynamic key
|
|
960
957
|
//
|
|
961
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(
|
|
958
|
+
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
962
959
|
string$1(bytes, dynamicIndex, it);
|
|
963
960
|
}
|
|
964
961
|
}
|
|
965
|
-
const type = changeTree.getType(
|
|
966
|
-
const value = changeTree.getValue(
|
|
962
|
+
const type = changeTree.getType(index);
|
|
963
|
+
const value = changeTree.getValue(index);
|
|
967
964
|
// try { throw new Error(); } catch (e) {
|
|
968
965
|
// // only print if not coming from Reflection.ts
|
|
969
966
|
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
@@ -977,7 +974,11 @@
|
|
|
977
974
|
// }
|
|
978
975
|
// }
|
|
979
976
|
// TODO: inline this function call small performance gain
|
|
980
|
-
encodeValue(encoder, bytes,
|
|
977
|
+
encodeValue(encoder, bytes,
|
|
978
|
+
// ref,
|
|
979
|
+
type, value,
|
|
980
|
+
// index,
|
|
981
|
+
operation, it);
|
|
981
982
|
};
|
|
982
983
|
/**
|
|
983
984
|
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
@@ -1021,7 +1022,11 @@
|
|
|
1021
1022
|
// items: ref.toJSON(),
|
|
1022
1023
|
// });
|
|
1023
1024
|
// TODO: inline this function call small performance gain
|
|
1024
|
-
encodeValue(encoder, bytes,
|
|
1025
|
+
encodeValue(encoder, bytes,
|
|
1026
|
+
// ref,
|
|
1027
|
+
type, value,
|
|
1028
|
+
// field,
|
|
1029
|
+
operation, it);
|
|
1025
1030
|
};
|
|
1026
1031
|
|
|
1027
1032
|
/**
|
|
@@ -1378,7 +1383,7 @@
|
|
|
1378
1383
|
}
|
|
1379
1384
|
const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
1380
1385
|
const first_byte = bytes[it.offset++];
|
|
1381
|
-
const metadata = ref
|
|
1386
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1382
1387
|
// "compressed" index + operation
|
|
1383
1388
|
const operation = (first_byte >> 6) << 6;
|
|
1384
1389
|
const index = first_byte % (operation || 255);
|
|
@@ -1388,9 +1393,9 @@
|
|
|
1388
1393
|
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
1389
1394
|
return DEFINITION_MISMATCH;
|
|
1390
1395
|
}
|
|
1391
|
-
const { value, previousValue } = decodeValue(decoder, operation, ref, index,
|
|
1396
|
+
const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
|
|
1392
1397
|
if (value !== null && value !== undefined) {
|
|
1393
|
-
ref[field] = value;
|
|
1398
|
+
ref[field.name] = value;
|
|
1394
1399
|
}
|
|
1395
1400
|
// add change
|
|
1396
1401
|
if (previousValue !== value) {
|
|
@@ -1398,7 +1403,7 @@
|
|
|
1398
1403
|
ref,
|
|
1399
1404
|
refId: decoder.currentRefId,
|
|
1400
1405
|
op: operation,
|
|
1401
|
-
field: field,
|
|
1406
|
+
field: field.name,
|
|
1402
1407
|
value,
|
|
1403
1408
|
previousValue,
|
|
1404
1409
|
});
|
|
@@ -1496,7 +1501,6 @@
|
|
|
1496
1501
|
return;
|
|
1497
1502
|
}
|
|
1498
1503
|
else if (operation === exports.OPERATION.ADD_BY_REFID) {
|
|
1499
|
-
// operation = OPERATION.ADD;
|
|
1500
1504
|
const refId = number(bytes, it);
|
|
1501
1505
|
const itemByRefId = decoder.root.refs.get(refId);
|
|
1502
1506
|
// use existing index, or push new value
|
|
@@ -1530,6 +1534,47 @@
|
|
|
1530
1534
|
}
|
|
1531
1535
|
};
|
|
1532
1536
|
|
|
1537
|
+
class EncodeSchemaError extends Error {
|
|
1538
|
+
}
|
|
1539
|
+
function assertType(value, type, klass, field) {
|
|
1540
|
+
let typeofTarget;
|
|
1541
|
+
let allowNull = false;
|
|
1542
|
+
switch (type) {
|
|
1543
|
+
case "number":
|
|
1544
|
+
case "int8":
|
|
1545
|
+
case "uint8":
|
|
1546
|
+
case "int16":
|
|
1547
|
+
case "uint16":
|
|
1548
|
+
case "int32":
|
|
1549
|
+
case "uint32":
|
|
1550
|
+
case "int64":
|
|
1551
|
+
case "uint64":
|
|
1552
|
+
case "float32":
|
|
1553
|
+
case "float64":
|
|
1554
|
+
typeofTarget = "number";
|
|
1555
|
+
if (isNaN(value)) {
|
|
1556
|
+
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
1557
|
+
}
|
|
1558
|
+
break;
|
|
1559
|
+
case "string":
|
|
1560
|
+
typeofTarget = "string";
|
|
1561
|
+
allowNull = true;
|
|
1562
|
+
break;
|
|
1563
|
+
case "boolean":
|
|
1564
|
+
// boolean is always encoded as true/false based on truthiness
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
1568
|
+
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
1569
|
+
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function assertInstanceType(value, type, instance, field) {
|
|
1573
|
+
if (!(value instanceof type)) {
|
|
1574
|
+
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1533
1578
|
var _a$4, _b$4;
|
|
1534
1579
|
const DEFAULT_SORT = (a, b) => {
|
|
1535
1580
|
const A = a.toString();
|
|
@@ -1595,6 +1640,7 @@
|
|
|
1595
1640
|
}
|
|
1596
1641
|
else {
|
|
1597
1642
|
if (setValue[$changes]) {
|
|
1643
|
+
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1598
1644
|
if (obj.items[key] !== undefined) {
|
|
1599
1645
|
if (setValue[$changes][$isNew]) {
|
|
1600
1646
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
@@ -1641,6 +1687,7 @@
|
|
|
1641
1687
|
}
|
|
1642
1688
|
});
|
|
1643
1689
|
this[$changes] = new ChangeTree(proxy);
|
|
1690
|
+
this[$changes].indexes = {};
|
|
1644
1691
|
this.push.apply(this, items);
|
|
1645
1692
|
return proxy;
|
|
1646
1693
|
}
|
|
@@ -1665,6 +1712,9 @@
|
|
|
1665
1712
|
if (value === undefined || value === null) {
|
|
1666
1713
|
return;
|
|
1667
1714
|
}
|
|
1715
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
1716
|
+
assertInstanceType(value, this[$childType], this, i);
|
|
1717
|
+
}
|
|
1668
1718
|
const changeTree = this[$changes];
|
|
1669
1719
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1670
1720
|
this.items.push(value);
|
|
@@ -2192,6 +2242,7 @@
|
|
|
2192
2242
|
this.$items = new Map();
|
|
2193
2243
|
this.$indexes = new Map();
|
|
2194
2244
|
this[$changes] = new ChangeTree(this);
|
|
2245
|
+
this[$changes].indexes = {};
|
|
2195
2246
|
if (initialValues) {
|
|
2196
2247
|
if (initialValues instanceof Map ||
|
|
2197
2248
|
initialValues instanceof MapSchema) {
|
|
@@ -2218,6 +2269,9 @@
|
|
|
2218
2269
|
if (value === undefined || value === null) {
|
|
2219
2270
|
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
|
|
2220
2271
|
}
|
|
2272
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
2273
|
+
assertInstanceType(value, this[$childType], this, key);
|
|
2274
|
+
}
|
|
2221
2275
|
// Force "key" as string
|
|
2222
2276
|
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
|
|
2223
2277
|
key = key.toString();
|
|
@@ -2423,27 +2477,20 @@
|
|
|
2423
2477
|
if (parentFieldViewTag !== undefined) {
|
|
2424
2478
|
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
2425
2479
|
}
|
|
2426
|
-
for (const
|
|
2427
|
-
|
|
2428
|
-
|
|
2429
|
-
|
|
2430
|
-
// if (
|
|
2431
|
-
// parentFieldViewTag !== undefined &&
|
|
2432
|
-
// metadata[field].tag === undefined
|
|
2433
|
-
// ) {
|
|
2434
|
-
// metadata[field].tag = parentFieldViewTag;
|
|
2435
|
-
// }
|
|
2436
|
-
const fieldType = metadata[field].type;
|
|
2437
|
-
const viewTag = metadata[field].tag;
|
|
2480
|
+
for (const fieldIndex in metadata) {
|
|
2481
|
+
const index = fieldIndex;
|
|
2482
|
+
const fieldType = metadata[index].type;
|
|
2483
|
+
const viewTag = metadata[index].tag;
|
|
2438
2484
|
if (typeof (fieldType) === "string") {
|
|
2439
2485
|
continue;
|
|
2440
2486
|
}
|
|
2441
2487
|
if (Array.isArray(fieldType)) {
|
|
2442
2488
|
const type = fieldType[0];
|
|
2489
|
+
// skip primitive types
|
|
2443
2490
|
if (type === "string") {
|
|
2444
2491
|
continue;
|
|
2445
2492
|
}
|
|
2446
|
-
this.discoverTypes(type,
|
|
2493
|
+
this.discoverTypes(type, index, viewTag);
|
|
2447
2494
|
}
|
|
2448
2495
|
else if (typeof (fieldType) === "function") {
|
|
2449
2496
|
this.discoverTypes(fieldType, viewTag);
|
|
@@ -2454,7 +2501,7 @@
|
|
|
2454
2501
|
if (typeof (type) === "string") {
|
|
2455
2502
|
continue;
|
|
2456
2503
|
}
|
|
2457
|
-
this.discoverTypes(type,
|
|
2504
|
+
this.discoverTypes(type, index, viewTag);
|
|
2458
2505
|
}
|
|
2459
2506
|
}
|
|
2460
2507
|
}
|
|
@@ -2610,17 +2657,18 @@
|
|
|
2610
2657
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2611
2658
|
// TODO: use Metadata.initialize()
|
|
2612
2659
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
|
|
2618
|
-
|
|
2619
|
-
|
|
2620
|
-
|
|
2621
|
-
|
|
2622
|
-
|
|
2623
|
-
}
|
|
2660
|
+
// const fieldIndex = metadata[fieldName];
|
|
2661
|
+
// if (!metadata[fieldIndex]) {
|
|
2662
|
+
// //
|
|
2663
|
+
// // detect index for this field, considering inheritance
|
|
2664
|
+
// //
|
|
2665
|
+
// metadata[fieldIndex] = {
|
|
2666
|
+
// type: undefined,
|
|
2667
|
+
// index: (metadata[-1] // current structure already has fields defined
|
|
2668
|
+
// ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
|
|
2669
|
+
// ?? -1) + 1 // no fields defined
|
|
2670
|
+
// }
|
|
2671
|
+
// }
|
|
2624
2672
|
Metadata.setTag(metadata, fieldName, tag);
|
|
2625
2673
|
};
|
|
2626
2674
|
}
|
|
@@ -2635,16 +2683,16 @@
|
|
|
2635
2683
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2636
2684
|
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
2637
2685
|
const metadata = Metadata.initialize(constructor, parentMetadata);
|
|
2638
|
-
let fieldIndex;
|
|
2686
|
+
let fieldIndex = metadata[field];
|
|
2639
2687
|
/**
|
|
2640
2688
|
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
2641
2689
|
*/
|
|
2642
|
-
if (metadata[
|
|
2643
|
-
if (metadata[
|
|
2690
|
+
if (metadata[fieldIndex]) {
|
|
2691
|
+
if (metadata[fieldIndex].deprecated) {
|
|
2644
2692
|
// do not create accessors for deprecated properties.
|
|
2645
2693
|
return;
|
|
2646
2694
|
}
|
|
2647
|
-
else if (metadata[
|
|
2695
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
2648
2696
|
// trying to define same property multiple times across inheritance.
|
|
2649
2697
|
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
2650
2698
|
try {
|
|
@@ -2655,9 +2703,6 @@
|
|
|
2655
2703
|
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
2656
2704
|
}
|
|
2657
2705
|
}
|
|
2658
|
-
else {
|
|
2659
|
-
fieldIndex = metadata[field].index;
|
|
2660
|
-
}
|
|
2661
2706
|
}
|
|
2662
2707
|
else {
|
|
2663
2708
|
//
|
|
@@ -2683,11 +2728,11 @@
|
|
|
2683
2728
|
const childType = (complexTypeKlass)
|
|
2684
2729
|
? Object.values(type)[0]
|
|
2685
2730
|
: type;
|
|
2686
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass
|
|
2731
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
2687
2732
|
}
|
|
2688
2733
|
};
|
|
2689
2734
|
}
|
|
2690
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass
|
|
2735
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
2691
2736
|
return {
|
|
2692
2737
|
get: function () { return this[fieldCached]; },
|
|
2693
2738
|
set: function (value) {
|
|
@@ -2709,22 +2754,27 @@
|
|
|
2709
2754
|
}
|
|
2710
2755
|
value[$childType] = type;
|
|
2711
2756
|
}
|
|
2757
|
+
else if (typeof (type) !== "string") {
|
|
2758
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
2759
|
+
}
|
|
2760
|
+
else {
|
|
2761
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
2762
|
+
}
|
|
2763
|
+
const changeTree = this[$changes];
|
|
2712
2764
|
//
|
|
2713
2765
|
// Replacing existing "ref", remove it from root.
|
|
2714
2766
|
// TODO: if there are other references to this instance, we should not remove it from root.
|
|
2715
2767
|
//
|
|
2716
2768
|
if (previousValue !== undefined && previousValue[$changes]) {
|
|
2717
|
-
|
|
2769
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
2718
2770
|
}
|
|
2719
2771
|
// flag the change for encoding.
|
|
2720
|
-
this.constructor[$track](
|
|
2772
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
2721
2773
|
//
|
|
2722
2774
|
// call setParent() recursively for this and its child
|
|
2723
2775
|
// structures.
|
|
2724
2776
|
//
|
|
2725
|
-
|
|
2726
|
-
value[$changes].setParent(this, this[$changes].root, metadata[field].index);
|
|
2727
|
-
}
|
|
2777
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
2728
2778
|
}
|
|
2729
2779
|
else if (previousValue !== undefined) {
|
|
2730
2780
|
//
|
|
@@ -2751,20 +2801,22 @@
|
|
|
2751
2801
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2752
2802
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2753
2803
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
|
|
2759
|
-
|
|
2760
|
-
|
|
2761
|
-
|
|
2762
|
-
|
|
2763
|
-
|
|
2764
|
-
}
|
|
2765
|
-
|
|
2804
|
+
const fieldIndex = metadata[field];
|
|
2805
|
+
// if (!metadata[field]) {
|
|
2806
|
+
// //
|
|
2807
|
+
// // detect index for this field, considering inheritance
|
|
2808
|
+
// //
|
|
2809
|
+
// metadata[field] = {
|
|
2810
|
+
// type: undefined,
|
|
2811
|
+
// index: (metadata[-1] // current structure already has fields defined
|
|
2812
|
+
// ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
|
|
2813
|
+
// ?? -1) + 1 // no fields defined
|
|
2814
|
+
// }
|
|
2815
|
+
// }
|
|
2816
|
+
metadata[fieldIndex].deprecated = true;
|
|
2766
2817
|
if (throws) {
|
|
2767
|
-
metadata[
|
|
2818
|
+
metadata[$descriptors] ??= {};
|
|
2819
|
+
metadata[$descriptors][field] = {
|
|
2768
2820
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
2769
2821
|
set: function (value) { },
|
|
2770
2822
|
enumerable: false,
|
|
@@ -2772,8 +2824,8 @@
|
|
|
2772
2824
|
};
|
|
2773
2825
|
}
|
|
2774
2826
|
// flag metadata[field] as non-enumerable
|
|
2775
|
-
Object.defineProperty(metadata,
|
|
2776
|
-
value: metadata[
|
|
2827
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
2828
|
+
value: metadata[fieldIndex],
|
|
2777
2829
|
enumerable: false,
|
|
2778
2830
|
configurable: true
|
|
2779
2831
|
});
|
|
@@ -2839,35 +2891,7 @@
|
|
|
2839
2891
|
enumerable: false,
|
|
2840
2892
|
writable: true
|
|
2841
2893
|
});
|
|
2842
|
-
|
|
2843
|
-
// Define property descriptors
|
|
2844
|
-
for (const field in metadata) {
|
|
2845
|
-
if (metadata[field].descriptor) {
|
|
2846
|
-
// for encoder
|
|
2847
|
-
Object.defineProperty(instance, `_${field}`, {
|
|
2848
|
-
value: undefined,
|
|
2849
|
-
writable: true,
|
|
2850
|
-
enumerable: false,
|
|
2851
|
-
configurable: true,
|
|
2852
|
-
});
|
|
2853
|
-
Object.defineProperty(instance, field, metadata[field].descriptor);
|
|
2854
|
-
}
|
|
2855
|
-
else {
|
|
2856
|
-
// for decoder
|
|
2857
|
-
Object.defineProperty(instance, field, {
|
|
2858
|
-
value: undefined,
|
|
2859
|
-
writable: true,
|
|
2860
|
-
enumerable: true,
|
|
2861
|
-
configurable: true,
|
|
2862
|
-
});
|
|
2863
|
-
}
|
|
2864
|
-
// Object.defineProperty(instance, field, {
|
|
2865
|
-
// ...instance.constructor[Symbol.metadata][field].descriptor
|
|
2866
|
-
// });
|
|
2867
|
-
// if (args[0]?.hasOwnProperty(field)) {
|
|
2868
|
-
// instance[field] = args[0][field];
|
|
2869
|
-
// }
|
|
2870
|
-
}
|
|
2894
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2871
2895
|
}
|
|
2872
2896
|
static is(type) {
|
|
2873
2897
|
return typeof (type[Symbol.metadata]) === "object";
|
|
@@ -2891,7 +2915,7 @@
|
|
|
2891
2915
|
*/
|
|
2892
2916
|
static [$filter](ref, index, view) {
|
|
2893
2917
|
const metadata = ref.constructor[Symbol.metadata];
|
|
2894
|
-
const tag = metadata[
|
|
2918
|
+
const tag = metadata[index]?.tag;
|
|
2895
2919
|
if (view === undefined) {
|
|
2896
2920
|
// shared pass/encode: encode if doesn't have a tag
|
|
2897
2921
|
return tag === undefined;
|
|
@@ -2912,12 +2936,21 @@
|
|
|
2912
2936
|
}
|
|
2913
2937
|
// allow inherited classes to have a constructor
|
|
2914
2938
|
constructor(...args) {
|
|
2915
|
-
|
|
2939
|
+
//
|
|
2940
|
+
// inline
|
|
2941
|
+
// Schema.initialize(this);
|
|
2942
|
+
//
|
|
2943
|
+
Object.defineProperty(this, $changes, {
|
|
2944
|
+
value: new ChangeTree(this),
|
|
2945
|
+
enumerable: false,
|
|
2946
|
+
writable: true
|
|
2947
|
+
});
|
|
2948
|
+
Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2916
2949
|
//
|
|
2917
2950
|
// Assign initial values
|
|
2918
2951
|
//
|
|
2919
2952
|
if (args[0]) {
|
|
2920
|
-
|
|
2953
|
+
Object.assign(this, args[0]);
|
|
2921
2954
|
}
|
|
2922
2955
|
}
|
|
2923
2956
|
assign(props) {
|
|
@@ -2931,7 +2964,8 @@
|
|
|
2931
2964
|
* @param operation OPERATION to perform (detected automatically)
|
|
2932
2965
|
*/
|
|
2933
2966
|
setDirty(property, operation) {
|
|
2934
|
-
this
|
|
2967
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
2968
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
2935
2969
|
}
|
|
2936
2970
|
clone() {
|
|
2937
2971
|
const cloned = new (this.constructor);
|
|
@@ -2940,7 +2974,9 @@
|
|
|
2940
2974
|
// TODO: clone all properties, not only annotated ones
|
|
2941
2975
|
//
|
|
2942
2976
|
// for (const field in this) {
|
|
2943
|
-
for (const
|
|
2977
|
+
for (const fieldIndex in metadata) {
|
|
2978
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
2979
|
+
const field = metadata[fieldIndex].name;
|
|
2944
2980
|
if (typeof (this[field]) === "object" &&
|
|
2945
2981
|
typeof (this[field]?.clone) === "function") {
|
|
2946
2982
|
// deep clone
|
|
@@ -2954,10 +2990,11 @@
|
|
|
2954
2990
|
return cloned;
|
|
2955
2991
|
}
|
|
2956
2992
|
toJSON() {
|
|
2957
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
2958
2993
|
const obj = {};
|
|
2959
|
-
|
|
2960
|
-
|
|
2994
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
2995
|
+
for (const index in metadata) {
|
|
2996
|
+
const field = metadata[index];
|
|
2997
|
+
const fieldName = field.name;
|
|
2961
2998
|
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
2962
2999
|
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
2963
3000
|
? this[fieldName]['toJSON']()
|
|
@@ -2970,10 +3007,12 @@
|
|
|
2970
3007
|
this[$changes].discardAll();
|
|
2971
3008
|
}
|
|
2972
3009
|
[$getByIndex](index) {
|
|
2973
|
-
|
|
3010
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3011
|
+
return this[metadata[index].name];
|
|
2974
3012
|
}
|
|
2975
3013
|
[$deleteByIndex](index) {
|
|
2976
|
-
this
|
|
3014
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3015
|
+
this[metadata[index].name] = undefined;
|
|
2977
3016
|
}
|
|
2978
3017
|
static debugRefIds(instance, jsonContents = true, level = 0) {
|
|
2979
3018
|
const ref = instance;
|
|
@@ -3097,6 +3136,7 @@
|
|
|
3097
3136
|
this.$indexes = new Map();
|
|
3098
3137
|
this.$refId = 0;
|
|
3099
3138
|
this[$changes] = new ChangeTree(this);
|
|
3139
|
+
this[$changes].indexes = {};
|
|
3100
3140
|
if (initialValues) {
|
|
3101
3141
|
initialValues.forEach((v) => this.add(v));
|
|
3102
3142
|
}
|
|
@@ -3252,6 +3292,7 @@
|
|
|
3252
3292
|
this.$indexes = new Map();
|
|
3253
3293
|
this.$refId = 0;
|
|
3254
3294
|
this[$changes] = new ChangeTree(this);
|
|
3295
|
+
this[$changes].indexes = {};
|
|
3255
3296
|
if (initialValues) {
|
|
3256
3297
|
initialValues.forEach((v) => this.add(v));
|
|
3257
3298
|
}
|
|
@@ -3486,8 +3527,8 @@
|
|
|
3486
3527
|
) {
|
|
3487
3528
|
const hasView = (view !== undefined);
|
|
3488
3529
|
const rootChangeTree = this.state[$changes];
|
|
3489
|
-
const
|
|
3490
|
-
for (const [changeTree, changes] of
|
|
3530
|
+
const shouldClearChanges = !isEncodeAll && !hasView;
|
|
3531
|
+
for (const [changeTree, changes] of changeTrees.entries()) {
|
|
3491
3532
|
const ref = changeTree.ref;
|
|
3492
3533
|
const ctor = ref['constructor'];
|
|
3493
3534
|
const encoder = ctor[$encoder];
|
|
@@ -3539,6 +3580,9 @@
|
|
|
3539
3580
|
// }
|
|
3540
3581
|
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
|
|
3541
3582
|
}
|
|
3583
|
+
// if (shouldClearChanges) {
|
|
3584
|
+
// changeTree.endEncode();
|
|
3585
|
+
// }
|
|
3542
3586
|
}
|
|
3543
3587
|
if (it.offset > buffer.byteLength) {
|
|
3544
3588
|
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
@@ -3561,7 +3605,7 @@
|
|
|
3561
3605
|
//
|
|
3562
3606
|
// only clear changes after making sure buffer resize is not required.
|
|
3563
3607
|
//
|
|
3564
|
-
if (
|
|
3608
|
+
if (shouldClearChanges) {
|
|
3565
3609
|
//
|
|
3566
3610
|
// FIXME: avoid iterating over change trees twice.
|
|
3567
3611
|
//
|
|
@@ -3645,6 +3689,11 @@
|
|
|
3645
3689
|
const changeTreesIterator = changeTrees.entries();
|
|
3646
3690
|
for (const [changeTree, _] of changeTreesIterator) {
|
|
3647
3691
|
changeTree.endEncode();
|
|
3692
|
+
// changeTree.changes.clear();
|
|
3693
|
+
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3694
|
+
// changeTree.ref[$onEncodeEnd]?.();
|
|
3695
|
+
// // Not a new instance anymore
|
|
3696
|
+
// delete changeTree[$isNew];
|
|
3648
3697
|
}
|
|
3649
3698
|
}
|
|
3650
3699
|
discardChanges() {
|
|
@@ -3760,8 +3809,9 @@
|
|
|
3760
3809
|
// Ensure child schema instances have their references removed as well.
|
|
3761
3810
|
//
|
|
3762
3811
|
if (Metadata.isValidInstance(ref)) {
|
|
3763
|
-
const metadata = ref
|
|
3764
|
-
for (const
|
|
3812
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
3813
|
+
for (const index in metadata) {
|
|
3814
|
+
const field = metadata[index].name;
|
|
3765
3815
|
const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
|
|
3766
3816
|
if (childRefId) {
|
|
3767
3817
|
this.removeRef(childRefId);
|
|
@@ -3948,7 +3998,9 @@
|
|
|
3948
3998
|
const reflection = new Reflection();
|
|
3949
3999
|
const encoder = new Encoder(reflection);
|
|
3950
4000
|
const buildType = (currentType, metadata) => {
|
|
3951
|
-
for (const
|
|
4001
|
+
for (const fieldIndex in metadata) {
|
|
4002
|
+
const index = Number(fieldIndex);
|
|
4003
|
+
const fieldName = metadata[index].name;
|
|
3952
4004
|
// skip fields from parent classes
|
|
3953
4005
|
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
3954
4006
|
continue;
|
|
@@ -3956,7 +4008,7 @@
|
|
|
3956
4008
|
const field = new ReflectionField();
|
|
3957
4009
|
field.name = fieldName;
|
|
3958
4010
|
let fieldType;
|
|
3959
|
-
const type = metadata[
|
|
4011
|
+
const type = metadata[index].type;
|
|
3960
4012
|
if (typeof (type) === "string") {
|
|
3961
4013
|
fieldType = type;
|
|
3962
4014
|
}
|
|
@@ -4022,18 +4074,7 @@
|
|
|
4022
4074
|
reflection.types.forEach((reflectionType) => {
|
|
4023
4075
|
const schemaType = typeContext.get(reflectionType.id);
|
|
4024
4076
|
const metadata = schemaType[Symbol.metadata];
|
|
4025
|
-
// FIXME: use metadata[-1] to get field count
|
|
4026
4077
|
const parentFieldIndex = 0;
|
|
4027
|
-
// console.log("--------------------");
|
|
4028
|
-
// // console.log("reflectionType", reflectionType.toJSON());
|
|
4029
|
-
// console.log("reflectionType.fields", reflectionType.fields.toJSON());
|
|
4030
|
-
// console.log("parentFieldIndex", parentFieldIndex);
|
|
4031
|
-
//
|
|
4032
|
-
// FIXME: set fields using parentKlass as well
|
|
4033
|
-
// currently the fields are duplicated on inherited classes
|
|
4034
|
-
//
|
|
4035
|
-
// // const parentKlass = reflection.types[reflectionType.extendsId];
|
|
4036
|
-
// // parentKlass.fields
|
|
4037
4078
|
reflectionType.fields.forEach((field, i) => {
|
|
4038
4079
|
const fieldIndex = parentFieldIndex + i;
|
|
4039
4080
|
if (field.referencedType !== undefined) {
|
|
@@ -4198,7 +4239,7 @@
|
|
|
4198
4239
|
//
|
|
4199
4240
|
bindTo: function bindTo(targetObject, properties) {
|
|
4200
4241
|
if (!properties) {
|
|
4201
|
-
properties = Object.keys(metadata);
|
|
4242
|
+
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4202
4243
|
}
|
|
4203
4244
|
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
|
|
4204
4245
|
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
@@ -4206,7 +4247,8 @@
|
|
|
4206
4247
|
}
|
|
4207
4248
|
}, {
|
|
4208
4249
|
get(target, prop) {
|
|
4209
|
-
|
|
4250
|
+
const metadataField = metadata[metadata[prop]];
|
|
4251
|
+
if (metadataField) {
|
|
4210
4252
|
const instance = context.instance?.[prop];
|
|
4211
4253
|
const onInstanceAvailable = ((callback) => {
|
|
4212
4254
|
const unbind = $(context.instance).listen(prop, (value, _) => {
|
|
@@ -4222,7 +4264,7 @@
|
|
|
4222
4264
|
callback(instance, true);
|
|
4223
4265
|
}
|
|
4224
4266
|
});
|
|
4225
|
-
return getProxy(
|
|
4267
|
+
return getProxy(metadataField.type, {
|
|
4226
4268
|
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
4227
4269
|
instance: ($root.refIds.get(instance) && instance),
|
|
4228
4270
|
parentInstance: context.instance,
|
|
@@ -4335,7 +4377,7 @@
|
|
|
4335
4377
|
console.warn("StateView#add(), invalid object:", obj);
|
|
4336
4378
|
return this;
|
|
4337
4379
|
}
|
|
4338
|
-
// FIXME: ArraySchema/MapSchema
|
|
4380
|
+
// FIXME: ArraySchema/MapSchema do not have metadata
|
|
4339
4381
|
const metadata = obj.constructor[Symbol.metadata];
|
|
4340
4382
|
const changeTree = obj[$changes];
|
|
4341
4383
|
this.items.add(changeTree);
|
|
@@ -4381,7 +4423,7 @@
|
|
|
4381
4423
|
? changeTree.allFilteredChanges
|
|
4382
4424
|
: changeTree.allChanges;
|
|
4383
4425
|
changeSet.forEach((op, index) => {
|
|
4384
|
-
const tagAtIndex = metadata?.[
|
|
4426
|
+
const tagAtIndex = metadata?.[index].tag;
|
|
4385
4427
|
if ((isInvisible || // if "invisible", include all
|
|
4386
4428
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4387
4429
|
tagAtIndex === tag // tagged property
|
|
@@ -4394,7 +4436,7 @@
|
|
|
4394
4436
|
// Add children of this ChangeTree to this view
|
|
4395
4437
|
changeTree.forEachChild((change, index) => {
|
|
4396
4438
|
// Do not ADD children that don't have the same tag
|
|
4397
|
-
if (metadata && metadata[
|
|
4439
|
+
if (metadata && metadata[index].tag !== tag) {
|
|
4398
4440
|
return;
|
|
4399
4441
|
}
|
|
4400
4442
|
this.add(change.ref, tag, false);
|