@colyseus/schema 3.0.0-alpha.30 → 3.0.0-alpha.32
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 +389 -354
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +389 -354
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +389 -354
- 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 +25 -22
- package/lib/bench_encode.js.map +1 -1
- package/lib/decoder/DecodeOperation.js +7 -9
- 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 +135 -117
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +1 -4
- package/lib/encoder/EncodeOperation.js +17 -47
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.js +18 -6
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +2 -2
- package/lib/encoder/Root.js +18 -6
- package/lib/encoder/Root.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 +29 -27
- package/src/decoder/DecodeOperation.ts +12 -11
- package/src/decoder/ReferenceTracker.ts +3 -2
- package/src/decoder/strategy/StateCallbacks.ts +4 -3
- package/src/encoder/ChangeTree.ts +154 -138
- package/src/encoder/EncodeOperation.ts +42 -62
- package/src/encoder/Encoder.ts +25 -8
- package/src/encoder/Root.ts +23 -6
- 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/esm/index.mjs
CHANGED
|
@@ -34,6 +34,7 @@ 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");
|
|
37
38
|
/**
|
|
38
39
|
* Used to hold ChangeTree instances whitin the structures
|
|
39
40
|
*/
|
|
@@ -69,34 +70,67 @@ function getType(identifier) {
|
|
|
69
70
|
}
|
|
70
71
|
|
|
71
72
|
const Metadata = {
|
|
72
|
-
addField(metadata, index,
|
|
73
|
+
addField(metadata, index, name, type, descriptor) {
|
|
73
74
|
if (index > 64) {
|
|
74
|
-
throw new Error(`Can't define field '${
|
|
75
|
+
throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
|
|
75
76
|
}
|
|
76
|
-
metadata[
|
|
77
|
+
metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
|
|
77
78
|
{
|
|
78
79
|
type: (Array.isArray(type))
|
|
79
80
|
? { array: type[0] }
|
|
80
81
|
: type,
|
|
81
82
|
index,
|
|
82
|
-
|
|
83
|
+
name,
|
|
83
84
|
});
|
|
85
|
+
// create "descriptors" map
|
|
86
|
+
metadata[$descriptors] ??= {};
|
|
87
|
+
if (descriptor) {
|
|
88
|
+
// for encoder
|
|
89
|
+
metadata[$descriptors][name] = descriptor;
|
|
90
|
+
metadata[$descriptors][`_${name}`] = {
|
|
91
|
+
value: undefined,
|
|
92
|
+
writable: true,
|
|
93
|
+
enumerable: false,
|
|
94
|
+
configurable: true,
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
else {
|
|
98
|
+
// for decoder
|
|
99
|
+
metadata[$descriptors][name] = {
|
|
100
|
+
value: undefined,
|
|
101
|
+
writable: true,
|
|
102
|
+
enumerable: true,
|
|
103
|
+
configurable: true,
|
|
104
|
+
};
|
|
105
|
+
}
|
|
84
106
|
// map -1 as last field index
|
|
85
107
|
Object.defineProperty(metadata, -1, {
|
|
86
108
|
value: index,
|
|
87
109
|
enumerable: false,
|
|
88
110
|
configurable: true
|
|
89
111
|
});
|
|
90
|
-
// map
|
|
91
|
-
Object.defineProperty(metadata,
|
|
92
|
-
value:
|
|
112
|
+
// map field name => index (non enumerable)
|
|
113
|
+
Object.defineProperty(metadata, name, {
|
|
114
|
+
value: index,
|
|
93
115
|
enumerable: false,
|
|
94
116
|
configurable: true,
|
|
95
117
|
});
|
|
118
|
+
// if child Ref/complex type, add to -4
|
|
119
|
+
if (typeof (metadata[index].type) !== "string") {
|
|
120
|
+
if (metadata[-4] === undefined) {
|
|
121
|
+
Object.defineProperty(metadata, -4, {
|
|
122
|
+
value: [],
|
|
123
|
+
enumerable: false,
|
|
124
|
+
configurable: true,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
metadata[-4].push(index);
|
|
128
|
+
}
|
|
96
129
|
},
|
|
97
130
|
setTag(metadata, fieldName, tag) {
|
|
131
|
+
const index = metadata[fieldName];
|
|
132
|
+
const field = metadata[index];
|
|
98
133
|
// add 'tag' to the field
|
|
99
|
-
const field = metadata[fieldName];
|
|
100
134
|
field.tag = tag;
|
|
101
135
|
if (!metadata[-2]) {
|
|
102
136
|
// -2: all field indexes with "view" tag
|
|
@@ -112,20 +146,14 @@ const Metadata = {
|
|
|
112
146
|
configurable: true
|
|
113
147
|
});
|
|
114
148
|
}
|
|
115
|
-
metadata[-2].push(
|
|
149
|
+
metadata[-2].push(index);
|
|
116
150
|
if (!metadata[-3][tag]) {
|
|
117
151
|
metadata[-3][tag] = [];
|
|
118
152
|
}
|
|
119
|
-
metadata[-3][tag].push(
|
|
153
|
+
metadata[-3][tag].push(index);
|
|
120
154
|
},
|
|
121
155
|
setFields(target, fields) {
|
|
122
156
|
const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
|
|
123
|
-
// target[$track] = function (changeTree, index: number, operation: OPERATION = OPERATION.ADD) {
|
|
124
|
-
// changeTree.change(index, operation, encodeSchemaOperation);
|
|
125
|
-
// };
|
|
126
|
-
// target[$encoder] = encodeSchemaOperation;
|
|
127
|
-
// target[$decoder] = decodeSchemaOperation;
|
|
128
|
-
// if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
|
|
129
157
|
let index = 0;
|
|
130
158
|
for (const field in fields) {
|
|
131
159
|
const type = fields[field];
|
|
@@ -133,7 +161,7 @@ const Metadata = {
|
|
|
133
161
|
const complexTypeKlass = (Array.isArray(type))
|
|
134
162
|
? getType("array")
|
|
135
163
|
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
136
|
-
Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass
|
|
164
|
+
Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
|
|
137
165
|
index++;
|
|
138
166
|
}
|
|
139
167
|
},
|
|
@@ -162,8 +190,9 @@ const Metadata = {
|
|
|
162
190
|
// assign parent metadata to current
|
|
163
191
|
Object.assign(metadata, parentMetadata);
|
|
164
192
|
for (let i = 0; i <= parentMetadata[-1]; i++) {
|
|
165
|
-
|
|
166
|
-
|
|
193
|
+
const fieldName = parentMetadata[i].name;
|
|
194
|
+
Object.defineProperty(metadata, fieldName, {
|
|
195
|
+
value: parentMetadata[fieldName],
|
|
167
196
|
enumerable: false,
|
|
168
197
|
configurable: true,
|
|
169
198
|
});
|
|
@@ -187,7 +216,7 @@ const Metadata = {
|
|
|
187
216
|
const metadata = klass[Symbol.metadata];
|
|
188
217
|
const fields = {};
|
|
189
218
|
for (let i = 0; i <= metadata[-1]; i++) {
|
|
190
|
-
fields[metadata[i]] = metadata[
|
|
219
|
+
fields[metadata[i].name] = metadata[i].type;
|
|
191
220
|
}
|
|
192
221
|
return fields;
|
|
193
222
|
}
|
|
@@ -196,48 +225,59 @@ const Metadata = {
|
|
|
196
225
|
var _a$5;
|
|
197
226
|
class ChangeTree {
|
|
198
227
|
static { _a$5 = $isNew; }
|
|
199
|
-
;
|
|
200
228
|
constructor(ref) {
|
|
201
|
-
this.
|
|
229
|
+
this.isFiltered = false;
|
|
230
|
+
this.isPartiallyFiltered = false;
|
|
202
231
|
this.currentOperationIndex = 0;
|
|
203
|
-
this.allChanges = new Map();
|
|
204
|
-
this.allFilteredChanges = new Map();
|
|
205
232
|
this.changes = new Map();
|
|
206
|
-
this.
|
|
233
|
+
this.allChanges = new Map();
|
|
207
234
|
this[_a$5] = true;
|
|
208
235
|
this.ref = ref;
|
|
236
|
+
//
|
|
237
|
+
// Does this structure have "filters" declared?
|
|
238
|
+
//
|
|
239
|
+
if (ref.constructor[Symbol.metadata]?.[-2]) {
|
|
240
|
+
this.allFilteredChanges = new Map();
|
|
241
|
+
this.filteredChanges = new Map();
|
|
242
|
+
}
|
|
209
243
|
}
|
|
210
244
|
setRoot(root) {
|
|
211
245
|
this.root = root;
|
|
212
246
|
this.root.add(this);
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
247
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
248
|
+
if (this.root.types.hasFilters) {
|
|
249
|
+
//
|
|
250
|
+
// At Schema initialization, the "root" structure might not be available
|
|
251
|
+
// yet, as it only does once the "Encoder" has been set up.
|
|
252
|
+
//
|
|
253
|
+
// So the "parent" may be already set without a "root".
|
|
254
|
+
//
|
|
255
|
+
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
256
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
257
|
+
this.root.allFilteredChanges.set(this, this.allFilteredChanges);
|
|
258
|
+
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
259
|
+
}
|
|
260
|
+
}
|
|
222
261
|
if (!this.isFiltered) {
|
|
223
262
|
this.root.changes.set(this, this.changes);
|
|
263
|
+
this.root.allChanges.set(this, this.allChanges);
|
|
224
264
|
}
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
265
|
+
this.ensureRefId();
|
|
266
|
+
if (metadata) {
|
|
267
|
+
metadata[-4]?.forEach((index) => {
|
|
268
|
+
const field = metadata[index];
|
|
269
|
+
const value = this.ref[field.name];
|
|
270
|
+
if (value) {
|
|
271
|
+
value[$changes].setRoot(root);
|
|
272
|
+
}
|
|
273
|
+
});
|
|
228
274
|
}
|
|
229
|
-
if (
|
|
230
|
-
|
|
275
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
276
|
+
// MapSchema / ArraySchema, etc.
|
|
277
|
+
this.ref.forEach((value, key) => {
|
|
278
|
+
value[$changes].setRoot(root);
|
|
279
|
+
});
|
|
231
280
|
}
|
|
232
|
-
this.forEachChild((changeTree, _) => {
|
|
233
|
-
changeTree.setRoot(root);
|
|
234
|
-
});
|
|
235
|
-
// this.allChanges.forEach((_, index) => {
|
|
236
|
-
// const childRef = this.ref[$getByIndex](index);
|
|
237
|
-
// if (childRef && childRef[$changes]) {
|
|
238
|
-
// childRef[$changes].setRoot(root);
|
|
239
|
-
// }
|
|
240
|
-
// });
|
|
241
281
|
}
|
|
242
282
|
setParent(parent, root, parentIndex) {
|
|
243
283
|
this.parent = parent;
|
|
@@ -247,48 +287,60 @@ class ChangeTree {
|
|
|
247
287
|
return;
|
|
248
288
|
}
|
|
249
289
|
root.add(this);
|
|
290
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
250
291
|
// skip if parent is already set
|
|
251
|
-
if (root
|
|
252
|
-
this.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
292
|
+
if (root !== this.root) {
|
|
293
|
+
this.root = root;
|
|
294
|
+
if (root.types.hasFilters) {
|
|
295
|
+
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
296
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
297
|
+
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
298
|
+
this.root.allFilteredChanges.set(this, this.filteredChanges);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
if (!this.isFiltered) {
|
|
302
|
+
this.root.changes.set(this, this.changes);
|
|
303
|
+
this.root.allChanges.set(this, this.allChanges);
|
|
304
|
+
}
|
|
305
|
+
this.ensureRefId();
|
|
256
306
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
307
|
+
// assign same parent on child structures
|
|
308
|
+
if (metadata) {
|
|
309
|
+
metadata[-4]?.forEach((index) => {
|
|
310
|
+
const field = metadata[index];
|
|
311
|
+
const value = this.ref[field.name];
|
|
312
|
+
value?.[$changes].setParent(this.ref, root, index);
|
|
313
|
+
// console.log(this.ref.constructor.name, field.name, value);
|
|
314
|
+
// try { throw new Error(); } catch (e) {
|
|
315
|
+
// console.log(e.stack);
|
|
316
|
+
// }
|
|
317
|
+
});
|
|
262
318
|
}
|
|
263
|
-
if (this.
|
|
264
|
-
|
|
265
|
-
this.
|
|
319
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
320
|
+
// MapSchema / ArraySchema, etc.
|
|
321
|
+
this.ref.forEach((value, key) => {
|
|
322
|
+
value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
|
|
323
|
+
});
|
|
266
324
|
}
|
|
267
|
-
this.ensureRefId();
|
|
268
|
-
this.forEachChild((changeTree, atIndex) => {
|
|
269
|
-
changeTree.setParent(this.ref, root, atIndex);
|
|
270
|
-
});
|
|
271
325
|
}
|
|
272
326
|
forEachChild(callback) {
|
|
273
327
|
//
|
|
274
328
|
// assign same parent on child structures
|
|
275
329
|
//
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
const value = this.ref[field];
|
|
281
|
-
if (value
|
|
282
|
-
callback(value[$changes],
|
|
330
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
331
|
+
if (metadata) {
|
|
332
|
+
metadata[-4]?.forEach((index) => {
|
|
333
|
+
const field = metadata[index];
|
|
334
|
+
const value = this.ref[field.name];
|
|
335
|
+
if (value) {
|
|
336
|
+
callback(value[$changes], index);
|
|
283
337
|
}
|
|
284
|
-
}
|
|
338
|
+
});
|
|
285
339
|
}
|
|
286
|
-
else if (typeof (this.ref)
|
|
340
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
287
341
|
// MapSchema / ArraySchema, etc.
|
|
288
342
|
this.ref.forEach((value, key) => {
|
|
289
|
-
|
|
290
|
-
callback(value[$changes], this.ref[$changes].indexes[key] ?? key);
|
|
291
|
-
}
|
|
343
|
+
callback(value[$changes], this.indexes[key] ?? key);
|
|
292
344
|
});
|
|
293
345
|
}
|
|
294
346
|
}
|
|
@@ -297,8 +349,8 @@ class ChangeTree {
|
|
|
297
349
|
this.root?.changes.set(this, this.changes);
|
|
298
350
|
}
|
|
299
351
|
change(index, operation = OPERATION.ADD) {
|
|
300
|
-
const metadata = this.ref
|
|
301
|
-
const isFiltered = this.isFiltered || (metadata
|
|
352
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
353
|
+
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
302
354
|
const changeSet = (isFiltered)
|
|
303
355
|
? this.filteredChanges
|
|
304
356
|
: this.changes;
|
|
@@ -309,14 +361,14 @@ class ChangeTree {
|
|
|
309
361
|
: (previousOperation === OPERATION.DELETE)
|
|
310
362
|
? OPERATION.DELETE_AND_ADD
|
|
311
363
|
: operation;
|
|
364
|
+
//
|
|
365
|
+
// TODO: are DELETE operations being encoded as ADD here ??
|
|
366
|
+
//
|
|
312
367
|
changeSet.set(index, op);
|
|
313
368
|
}
|
|
314
|
-
//
|
|
315
|
-
// TODO: are DELETE operations being encoded as ADD here ??
|
|
316
|
-
//
|
|
317
369
|
if (isFiltered) {
|
|
318
|
-
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
319
370
|
this.allFilteredChanges.set(index, OPERATION.ADD);
|
|
371
|
+
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
320
372
|
this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
|
|
321
373
|
}
|
|
322
374
|
else {
|
|
@@ -363,9 +415,7 @@ class ChangeTree {
|
|
|
363
415
|
});
|
|
364
416
|
}
|
|
365
417
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
366
|
-
|
|
367
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
368
|
-
if (isFiltered) {
|
|
418
|
+
if (this.filteredChanges !== undefined) {
|
|
369
419
|
this.allFilteredChanges.set(allChangesIndex, OPERATION.ADD);
|
|
370
420
|
this.filteredChanges.set(index, operation);
|
|
371
421
|
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
@@ -378,8 +428,8 @@ class ChangeTree {
|
|
|
378
428
|
}
|
|
379
429
|
getType(index) {
|
|
380
430
|
if (Metadata.isValidInstance(this.ref)) {
|
|
381
|
-
const metadata = this.ref
|
|
382
|
-
return metadata[
|
|
431
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
432
|
+
return metadata[index].type;
|
|
383
433
|
}
|
|
384
434
|
else {
|
|
385
435
|
//
|
|
@@ -393,7 +443,7 @@ class ChangeTree {
|
|
|
393
443
|
}
|
|
394
444
|
getChange(index) {
|
|
395
445
|
// TODO: optimize this. avoid checking against multiple instances
|
|
396
|
-
return this.changes.get(index) ?? this.filteredChanges
|
|
446
|
+
return this.changes.get(index) ?? this.filteredChanges?.get(index);
|
|
397
447
|
}
|
|
398
448
|
//
|
|
399
449
|
// used during `.encode()`
|
|
@@ -414,16 +464,13 @@ class ChangeTree {
|
|
|
414
464
|
}
|
|
415
465
|
return;
|
|
416
466
|
}
|
|
417
|
-
const
|
|
418
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
419
|
-
const changeSet = (isFiltered)
|
|
467
|
+
const changeSet = (this.filteredChanges)
|
|
420
468
|
? this.filteredChanges
|
|
421
469
|
: this.changes;
|
|
422
470
|
const previousValue = this.getValue(index);
|
|
423
471
|
changeSet.set(index, operation ?? OPERATION.DELETE);
|
|
424
472
|
// remove `root` reference
|
|
425
473
|
if (previousValue && previousValue[$changes]) {
|
|
426
|
-
previousValue[$changes].root = undefined;
|
|
427
474
|
//
|
|
428
475
|
// FIXME: this.root is "undefined"
|
|
429
476
|
//
|
|
@@ -434,12 +481,18 @@ class ChangeTree {
|
|
|
434
481
|
//
|
|
435
482
|
// (the property descriptors should NOT be used at decoding time. only at encoding time.)
|
|
436
483
|
//
|
|
437
|
-
this.root?.remove(previousValue[$changes]);
|
|
484
|
+
const refCount = this.root?.remove(previousValue[$changes]);
|
|
485
|
+
//
|
|
486
|
+
// Only remove "root" reference if it's the last reference
|
|
487
|
+
//
|
|
488
|
+
if (refCount <= 0) {
|
|
489
|
+
previousValue[$changes].root = undefined;
|
|
490
|
+
}
|
|
438
491
|
}
|
|
439
492
|
//
|
|
440
493
|
// FIXME: this is looking a bit ugly (and repeated from `.change()`)
|
|
441
494
|
//
|
|
442
|
-
if (
|
|
495
|
+
if (this.filteredChanges) {
|
|
443
496
|
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
444
497
|
this.allFilteredChanges.delete(allChangesIndex);
|
|
445
498
|
}
|
|
@@ -450,6 +503,7 @@ class ChangeTree {
|
|
|
450
503
|
}
|
|
451
504
|
endEncode() {
|
|
452
505
|
this.changes.clear();
|
|
506
|
+
// ArraySchema and MapSchema have a custom "encode end" method
|
|
453
507
|
this.ref[$onEncodeEnd]?.();
|
|
454
508
|
// Not a new instance anymore
|
|
455
509
|
delete this[$isNew];
|
|
@@ -462,12 +516,12 @@ class ChangeTree {
|
|
|
462
516
|
//
|
|
463
517
|
this.ref[$onEncodeEnd]?.();
|
|
464
518
|
this.changes.clear();
|
|
465
|
-
this.filteredChanges
|
|
519
|
+
this.filteredChanges?.clear();
|
|
466
520
|
// reset operation index
|
|
467
521
|
this.currentOperationIndex = 0;
|
|
468
522
|
if (discardAll) {
|
|
469
523
|
this.allChanges.clear();
|
|
470
|
-
this.allFilteredChanges
|
|
524
|
+
this.allFilteredChanges?.clear();
|
|
471
525
|
// remove children references
|
|
472
526
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
473
527
|
}
|
|
@@ -494,48 +548,41 @@ class ChangeTree {
|
|
|
494
548
|
get changed() {
|
|
495
549
|
return this.changes.size > 0;
|
|
496
550
|
}
|
|
497
|
-
checkIsFiltered(parent, parentIndex) {
|
|
551
|
+
checkIsFiltered(metadata, parent, parentIndex) {
|
|
498
552
|
// Detect if current structure has "filters" declared
|
|
499
|
-
this.isPartiallyFiltered =
|
|
500
|
-
if (
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
const changes = this.changes;
|
|
533
|
-
this.changes = this.filteredChanges;
|
|
534
|
-
this.filteredChanges = changes;
|
|
535
|
-
// swap "all changes" reference
|
|
536
|
-
const allFilteredChanges = this.allFilteredChanges;
|
|
537
|
-
this.allFilteredChanges = this.allChanges;
|
|
538
|
-
this.allChanges = allFilteredChanges;
|
|
553
|
+
this.isPartiallyFiltered = metadata?.[-2] !== undefined;
|
|
554
|
+
if (this.isPartiallyFiltered) {
|
|
555
|
+
this.filteredChanges = this.filteredChanges || new Map();
|
|
556
|
+
this.allFilteredChanges = this.allFilteredChanges || new Map();
|
|
557
|
+
}
|
|
558
|
+
if (parent) {
|
|
559
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
560
|
+
const parentChangeTree = parent[$changes];
|
|
561
|
+
parent = parentChangeTree.parent;
|
|
562
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
563
|
+
}
|
|
564
|
+
const parentMetadata = parent?.constructor?.[Symbol.metadata];
|
|
565
|
+
this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
|
|
566
|
+
//
|
|
567
|
+
// TODO: refactor this!
|
|
568
|
+
//
|
|
569
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
570
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
571
|
+
//
|
|
572
|
+
if (this.isFiltered) {
|
|
573
|
+
this.filteredChanges = new Map();
|
|
574
|
+
this.allFilteredChanges = new Map();
|
|
575
|
+
if (this.changes.size > 0) {
|
|
576
|
+
// swap changes reference
|
|
577
|
+
const changes = this.changes;
|
|
578
|
+
this.changes = this.filteredChanges;
|
|
579
|
+
this.filteredChanges = changes;
|
|
580
|
+
// swap "all changes" reference
|
|
581
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
582
|
+
this.allFilteredChanges = this.allChanges;
|
|
583
|
+
this.allChanges = allFilteredChanges;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
539
586
|
}
|
|
540
587
|
}
|
|
541
588
|
}
|
|
@@ -819,62 +866,11 @@ var encode = /*#__PURE__*/Object.freeze({
|
|
|
819
866
|
writeFloat64: writeFloat64
|
|
820
867
|
});
|
|
821
868
|
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
|
|
825
|
-
let typeofTarget;
|
|
826
|
-
let allowNull = false;
|
|
827
|
-
switch (type) {
|
|
828
|
-
case "number":
|
|
829
|
-
case "int8":
|
|
830
|
-
case "uint8":
|
|
831
|
-
case "int16":
|
|
832
|
-
case "uint16":
|
|
833
|
-
case "int32":
|
|
834
|
-
case "uint32":
|
|
835
|
-
case "int64":
|
|
836
|
-
case "uint64":
|
|
837
|
-
case "float32":
|
|
838
|
-
case "float64":
|
|
839
|
-
typeofTarget = "number";
|
|
840
|
-
if (isNaN(value)) {
|
|
841
|
-
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
842
|
-
}
|
|
843
|
-
break;
|
|
844
|
-
case "string":
|
|
845
|
-
typeofTarget = "string";
|
|
846
|
-
allowNull = true;
|
|
847
|
-
break;
|
|
848
|
-
case "boolean":
|
|
849
|
-
// boolean is always encoded as true/false based on truthiness
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
853
|
-
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
854
|
-
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
855
|
-
}
|
|
856
|
-
}
|
|
857
|
-
function assertInstanceType(value, type, klass, field) {
|
|
858
|
-
if (!(value instanceof type)) {
|
|
859
|
-
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
function encodePrimitiveType(type, bytes, value, klass, field, it) {
|
|
864
|
-
assertType(value, type, klass, field);
|
|
865
|
-
const encodeFunc = encode[type];
|
|
866
|
-
if (encodeFunc) {
|
|
867
|
-
encodeFunc(bytes, value, it);
|
|
868
|
-
// encodeFunc(bytes, value);
|
|
869
|
-
}
|
|
870
|
-
else {
|
|
871
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
869
|
+
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
870
|
+
if (typeof (type) === "string") {
|
|
871
|
+
encode[type]?.(bytes, value, it);
|
|
872
872
|
}
|
|
873
|
-
|
|
874
|
-
function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
|
|
875
|
-
if (type[Symbol.metadata] !== undefined) {
|
|
876
|
-
// TODO: move this to the `@type()` annotation
|
|
877
|
-
assertInstanceType(value, type, ref, field);
|
|
873
|
+
else if (type[Symbol.metadata] !== undefined) {
|
|
878
874
|
//
|
|
879
875
|
// Encode refId for this instance.
|
|
880
876
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -885,21 +881,7 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
|
|
|
885
881
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
886
882
|
}
|
|
887
883
|
}
|
|
888
|
-
else if (typeof (type) === "string") {
|
|
889
|
-
//
|
|
890
|
-
// Primitive values
|
|
891
|
-
//
|
|
892
|
-
encodePrimitiveType(type, bytes, value, ref, field, it);
|
|
893
|
-
}
|
|
894
884
|
else {
|
|
895
|
-
//
|
|
896
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
897
|
-
//
|
|
898
|
-
const definition = getType(Object.keys(type)[0]);
|
|
899
|
-
//
|
|
900
|
-
// ensure a ArraySchema has been provided
|
|
901
|
-
//
|
|
902
|
-
assertInstanceType(ref[field], definition.constructor, ref, field);
|
|
903
885
|
//
|
|
904
886
|
// Encode refId for this instance.
|
|
905
887
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -912,26 +894,23 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
|
|
|
912
894
|
* @private
|
|
913
895
|
*/
|
|
914
896
|
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
915
|
-
const ref = changeTree.ref;
|
|
916
|
-
const metadata = ref['constructor'][Symbol.metadata];
|
|
917
|
-
const field = metadata[index];
|
|
918
|
-
const type = metadata[field].type;
|
|
919
|
-
const value = ref[field];
|
|
920
897
|
// "compress" field index + operation
|
|
921
898
|
bytes[it.offset++] = (index | operation) & 255;
|
|
922
899
|
// Do not encode value for DELETE operations
|
|
923
900
|
if (operation === OPERATION.DELETE) {
|
|
924
901
|
return;
|
|
925
902
|
}
|
|
903
|
+
const ref = changeTree.ref;
|
|
904
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
905
|
+
const field = metadata[index];
|
|
926
906
|
// TODO: inline this function call small performance gain
|
|
927
|
-
encodeValue(encoder, bytes,
|
|
907
|
+
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
928
908
|
};
|
|
929
909
|
/**
|
|
930
910
|
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
931
911
|
* @private
|
|
932
912
|
*/
|
|
933
|
-
const encodeKeyValueOperation = function (encoder, bytes, changeTree,
|
|
934
|
-
const ref = changeTree.ref;
|
|
913
|
+
const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
935
914
|
// encode operation
|
|
936
915
|
bytes[it.offset++] = operation & 255;
|
|
937
916
|
// custom operations
|
|
@@ -939,25 +918,26 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
|
|
|
939
918
|
return;
|
|
940
919
|
}
|
|
941
920
|
// encode index
|
|
942
|
-
number$1(bytes,
|
|
921
|
+
number$1(bytes, index, it);
|
|
943
922
|
// Do not encode value for DELETE operations
|
|
944
923
|
if (operation === OPERATION.DELETE) {
|
|
945
924
|
return;
|
|
946
925
|
}
|
|
926
|
+
const ref = changeTree.ref;
|
|
947
927
|
//
|
|
948
928
|
// encode "alias" for dynamic fields (maps)
|
|
949
929
|
//
|
|
950
|
-
if ((operation & OPERATION.ADD)
|
|
930
|
+
if ((operation & OPERATION.ADD) === OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
951
931
|
if (typeof (ref['set']) === "function") {
|
|
952
932
|
//
|
|
953
933
|
// MapSchema dynamic key
|
|
954
934
|
//
|
|
955
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(
|
|
935
|
+
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
956
936
|
string$1(bytes, dynamicIndex, it);
|
|
957
937
|
}
|
|
958
938
|
}
|
|
959
|
-
const type =
|
|
960
|
-
const value =
|
|
939
|
+
const type = ref[$childType];
|
|
940
|
+
const value = ref[$getByIndex](index);
|
|
961
941
|
// try { throw new Error(); } catch (e) {
|
|
962
942
|
// // only print if not coming from Reflection.ts
|
|
963
943
|
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
@@ -971,7 +951,7 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
|
|
|
971
951
|
// }
|
|
972
952
|
// }
|
|
973
953
|
// TODO: inline this function call small performance gain
|
|
974
|
-
encodeValue(encoder, bytes,
|
|
954
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
975
955
|
};
|
|
976
956
|
/**
|
|
977
957
|
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
@@ -1015,7 +995,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
|
|
|
1015
995
|
// items: ref.toJSON(),
|
|
1016
996
|
// });
|
|
1017
997
|
// TODO: inline this function call small performance gain
|
|
1018
|
-
encodeValue(encoder, bytes,
|
|
998
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
1019
999
|
};
|
|
1020
1000
|
|
|
1021
1001
|
/**
|
|
@@ -1321,7 +1301,9 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1321
1301
|
if (!value) {
|
|
1322
1302
|
value = decoder.createInstanceOfType(childType);
|
|
1323
1303
|
}
|
|
1324
|
-
$root.addRef(refId, value, (value !== previousValue
|
|
1304
|
+
$root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed
|
|
1305
|
+
(operation === OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
|
|
1306
|
+
));
|
|
1325
1307
|
}
|
|
1326
1308
|
}
|
|
1327
1309
|
else if (typeof (type) === "string") {
|
|
@@ -1372,7 +1354,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1372
1354
|
}
|
|
1373
1355
|
const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
1374
1356
|
const first_byte = bytes[it.offset++];
|
|
1375
|
-
const metadata = ref
|
|
1357
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1376
1358
|
// "compressed" index + operation
|
|
1377
1359
|
const operation = (first_byte >> 6) << 6;
|
|
1378
1360
|
const index = first_byte % (operation || 255);
|
|
@@ -1382,9 +1364,9 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1382
1364
|
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
1383
1365
|
return DEFINITION_MISMATCH;
|
|
1384
1366
|
}
|
|
1385
|
-
const { value, previousValue } = decodeValue(decoder, operation, ref, index,
|
|
1367
|
+
const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
|
|
1386
1368
|
if (value !== null && value !== undefined) {
|
|
1387
|
-
ref[field] = value;
|
|
1369
|
+
ref[field.name] = value;
|
|
1388
1370
|
}
|
|
1389
1371
|
// add change
|
|
1390
1372
|
if (previousValue !== value) {
|
|
@@ -1392,7 +1374,7 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1392
1374
|
ref,
|
|
1393
1375
|
refId: decoder.currentRefId,
|
|
1394
1376
|
op: operation,
|
|
1395
|
-
field: field,
|
|
1377
|
+
field: field.name,
|
|
1396
1378
|
value,
|
|
1397
1379
|
previousValue,
|
|
1398
1380
|
});
|
|
@@ -1490,7 +1472,6 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1490
1472
|
return;
|
|
1491
1473
|
}
|
|
1492
1474
|
else if (operation === OPERATION.ADD_BY_REFID) {
|
|
1493
|
-
// operation = OPERATION.ADD;
|
|
1494
1475
|
const refId = number(bytes, it);
|
|
1495
1476
|
const itemByRefId = decoder.root.refs.get(refId);
|
|
1496
1477
|
// use existing index, or push new value
|
|
@@ -1524,6 +1505,47 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1524
1505
|
}
|
|
1525
1506
|
};
|
|
1526
1507
|
|
|
1508
|
+
class EncodeSchemaError extends Error {
|
|
1509
|
+
}
|
|
1510
|
+
function assertType(value, type, klass, field) {
|
|
1511
|
+
let typeofTarget;
|
|
1512
|
+
let allowNull = false;
|
|
1513
|
+
switch (type) {
|
|
1514
|
+
case "number":
|
|
1515
|
+
case "int8":
|
|
1516
|
+
case "uint8":
|
|
1517
|
+
case "int16":
|
|
1518
|
+
case "uint16":
|
|
1519
|
+
case "int32":
|
|
1520
|
+
case "uint32":
|
|
1521
|
+
case "int64":
|
|
1522
|
+
case "uint64":
|
|
1523
|
+
case "float32":
|
|
1524
|
+
case "float64":
|
|
1525
|
+
typeofTarget = "number";
|
|
1526
|
+
if (isNaN(value)) {
|
|
1527
|
+
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
1528
|
+
}
|
|
1529
|
+
break;
|
|
1530
|
+
case "string":
|
|
1531
|
+
typeofTarget = "string";
|
|
1532
|
+
allowNull = true;
|
|
1533
|
+
break;
|
|
1534
|
+
case "boolean":
|
|
1535
|
+
// boolean is always encoded as true/false based on truthiness
|
|
1536
|
+
return;
|
|
1537
|
+
}
|
|
1538
|
+
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
1539
|
+
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
1540
|
+
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
1541
|
+
}
|
|
1542
|
+
}
|
|
1543
|
+
function assertInstanceType(value, type, instance, field) {
|
|
1544
|
+
if (!(value instanceof type)) {
|
|
1545
|
+
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
|
|
1546
|
+
}
|
|
1547
|
+
}
|
|
1548
|
+
|
|
1527
1549
|
var _a$4, _b$4;
|
|
1528
1550
|
const DEFAULT_SORT = (a, b) => {
|
|
1529
1551
|
const A = a.toString();
|
|
@@ -1589,6 +1611,7 @@ class ArraySchema {
|
|
|
1589
1611
|
}
|
|
1590
1612
|
else {
|
|
1591
1613
|
if (setValue[$changes]) {
|
|
1614
|
+
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1592
1615
|
if (obj.items[key] !== undefined) {
|
|
1593
1616
|
if (setValue[$changes][$isNew]) {
|
|
1594
1617
|
this[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
|
|
@@ -1635,6 +1658,7 @@ class ArraySchema {
|
|
|
1635
1658
|
}
|
|
1636
1659
|
});
|
|
1637
1660
|
this[$changes] = new ChangeTree(proxy);
|
|
1661
|
+
this[$changes].indexes = {};
|
|
1638
1662
|
this.push.apply(this, items);
|
|
1639
1663
|
return proxy;
|
|
1640
1664
|
}
|
|
@@ -1659,6 +1683,9 @@ class ArraySchema {
|
|
|
1659
1683
|
if (value === undefined || value === null) {
|
|
1660
1684
|
return;
|
|
1661
1685
|
}
|
|
1686
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
1687
|
+
assertInstanceType(value, this[$childType], this, i);
|
|
1688
|
+
}
|
|
1662
1689
|
const changeTree = this[$changes];
|
|
1663
1690
|
changeTree.indexedOperation(length, OPERATION.ADD, this.items.length);
|
|
1664
1691
|
this.items.push(value);
|
|
@@ -2186,6 +2213,7 @@ class MapSchema {
|
|
|
2186
2213
|
this.$items = new Map();
|
|
2187
2214
|
this.$indexes = new Map();
|
|
2188
2215
|
this[$changes] = new ChangeTree(this);
|
|
2216
|
+
this[$changes].indexes = {};
|
|
2189
2217
|
if (initialValues) {
|
|
2190
2218
|
if (initialValues instanceof Map ||
|
|
2191
2219
|
initialValues instanceof MapSchema) {
|
|
@@ -2212,6 +2240,9 @@ class MapSchema {
|
|
|
2212
2240
|
if (value === undefined || value === null) {
|
|
2213
2241
|
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
|
|
2214
2242
|
}
|
|
2243
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
2244
|
+
assertInstanceType(value, this[$childType], this, key);
|
|
2245
|
+
}
|
|
2215
2246
|
// Force "key" as string
|
|
2216
2247
|
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
|
|
2217
2248
|
key = key.toString();
|
|
@@ -2417,27 +2448,20 @@ class TypeContext {
|
|
|
2417
2448
|
if (parentFieldViewTag !== undefined) {
|
|
2418
2449
|
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
2419
2450
|
}
|
|
2420
|
-
for (const
|
|
2421
|
-
|
|
2422
|
-
|
|
2423
|
-
|
|
2424
|
-
// if (
|
|
2425
|
-
// parentFieldViewTag !== undefined &&
|
|
2426
|
-
// metadata[field].tag === undefined
|
|
2427
|
-
// ) {
|
|
2428
|
-
// metadata[field].tag = parentFieldViewTag;
|
|
2429
|
-
// }
|
|
2430
|
-
const fieldType = metadata[field].type;
|
|
2431
|
-
const viewTag = metadata[field].tag;
|
|
2451
|
+
for (const fieldIndex in metadata) {
|
|
2452
|
+
const index = fieldIndex;
|
|
2453
|
+
const fieldType = metadata[index].type;
|
|
2454
|
+
const viewTag = metadata[index].tag;
|
|
2432
2455
|
if (typeof (fieldType) === "string") {
|
|
2433
2456
|
continue;
|
|
2434
2457
|
}
|
|
2435
2458
|
if (Array.isArray(fieldType)) {
|
|
2436
2459
|
const type = fieldType[0];
|
|
2460
|
+
// skip primitive types
|
|
2437
2461
|
if (type === "string") {
|
|
2438
2462
|
continue;
|
|
2439
2463
|
}
|
|
2440
|
-
this.discoverTypes(type,
|
|
2464
|
+
this.discoverTypes(type, index, viewTag);
|
|
2441
2465
|
}
|
|
2442
2466
|
else if (typeof (fieldType) === "function") {
|
|
2443
2467
|
this.discoverTypes(fieldType, viewTag);
|
|
@@ -2448,7 +2472,7 @@ class TypeContext {
|
|
|
2448
2472
|
if (typeof (type) === "string") {
|
|
2449
2473
|
continue;
|
|
2450
2474
|
}
|
|
2451
|
-
this.discoverTypes(type,
|
|
2475
|
+
this.discoverTypes(type, index, viewTag);
|
|
2452
2476
|
}
|
|
2453
2477
|
}
|
|
2454
2478
|
}
|
|
@@ -2604,17 +2628,18 @@ function view(tag = DEFAULT_VIEW_TAG) {
|
|
|
2604
2628
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2605
2629
|
// TODO: use Metadata.initialize()
|
|
2606
2630
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2607
|
-
|
|
2608
|
-
|
|
2609
|
-
|
|
2610
|
-
|
|
2611
|
-
|
|
2612
|
-
|
|
2613
|
-
|
|
2614
|
-
|
|
2615
|
-
|
|
2616
|
-
|
|
2617
|
-
}
|
|
2631
|
+
// const fieldIndex = metadata[fieldName];
|
|
2632
|
+
// if (!metadata[fieldIndex]) {
|
|
2633
|
+
// //
|
|
2634
|
+
// // detect index for this field, considering inheritance
|
|
2635
|
+
// //
|
|
2636
|
+
// metadata[fieldIndex] = {
|
|
2637
|
+
// type: undefined,
|
|
2638
|
+
// index: (metadata[-1] // current structure already has fields defined
|
|
2639
|
+
// ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
|
|
2640
|
+
// ?? -1) + 1 // no fields defined
|
|
2641
|
+
// }
|
|
2642
|
+
// }
|
|
2618
2643
|
Metadata.setTag(metadata, fieldName, tag);
|
|
2619
2644
|
};
|
|
2620
2645
|
}
|
|
@@ -2629,16 +2654,16 @@ function type(type, options) {
|
|
|
2629
2654
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2630
2655
|
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
2631
2656
|
const metadata = Metadata.initialize(constructor, parentMetadata);
|
|
2632
|
-
let fieldIndex;
|
|
2657
|
+
let fieldIndex = metadata[field];
|
|
2633
2658
|
/**
|
|
2634
2659
|
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
2635
2660
|
*/
|
|
2636
|
-
if (metadata[
|
|
2637
|
-
if (metadata[
|
|
2661
|
+
if (metadata[fieldIndex]) {
|
|
2662
|
+
if (metadata[fieldIndex].deprecated) {
|
|
2638
2663
|
// do not create accessors for deprecated properties.
|
|
2639
2664
|
return;
|
|
2640
2665
|
}
|
|
2641
|
-
else if (metadata[
|
|
2666
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
2642
2667
|
// trying to define same property multiple times across inheritance.
|
|
2643
2668
|
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
2644
2669
|
try {
|
|
@@ -2649,9 +2674,6 @@ function type(type, options) {
|
|
|
2649
2674
|
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
2650
2675
|
}
|
|
2651
2676
|
}
|
|
2652
|
-
else {
|
|
2653
|
-
fieldIndex = metadata[field].index;
|
|
2654
|
-
}
|
|
2655
2677
|
}
|
|
2656
2678
|
else {
|
|
2657
2679
|
//
|
|
@@ -2677,11 +2699,11 @@ function type(type, options) {
|
|
|
2677
2699
|
const childType = (complexTypeKlass)
|
|
2678
2700
|
? Object.values(type)[0]
|
|
2679
2701
|
: type;
|
|
2680
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass
|
|
2702
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
2681
2703
|
}
|
|
2682
2704
|
};
|
|
2683
2705
|
}
|
|
2684
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass
|
|
2706
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
2685
2707
|
return {
|
|
2686
2708
|
get: function () { return this[fieldCached]; },
|
|
2687
2709
|
set: function (value) {
|
|
@@ -2703,22 +2725,27 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass,
|
|
|
2703
2725
|
}
|
|
2704
2726
|
value[$childType] = type;
|
|
2705
2727
|
}
|
|
2728
|
+
else if (typeof (type) !== "string") {
|
|
2729
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
2730
|
+
}
|
|
2731
|
+
else {
|
|
2732
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
2733
|
+
}
|
|
2734
|
+
const changeTree = this[$changes];
|
|
2706
2735
|
//
|
|
2707
2736
|
// Replacing existing "ref", remove it from root.
|
|
2708
2737
|
// TODO: if there are other references to this instance, we should not remove it from root.
|
|
2709
2738
|
//
|
|
2710
2739
|
if (previousValue !== undefined && previousValue[$changes]) {
|
|
2711
|
-
|
|
2740
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
2712
2741
|
}
|
|
2713
2742
|
// flag the change for encoding.
|
|
2714
|
-
this.constructor[$track](
|
|
2743
|
+
this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
|
|
2715
2744
|
//
|
|
2716
2745
|
// call setParent() recursively for this and its child
|
|
2717
2746
|
// structures.
|
|
2718
2747
|
//
|
|
2719
|
-
|
|
2720
|
-
value[$changes].setParent(this, this[$changes].root, metadata[field].index);
|
|
2721
|
-
}
|
|
2748
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
2722
2749
|
}
|
|
2723
2750
|
else if (previousValue !== undefined) {
|
|
2724
2751
|
//
|
|
@@ -2745,20 +2772,22 @@ function deprecated(throws = true) {
|
|
|
2745
2772
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2746
2773
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2747
2774
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2748
|
-
|
|
2749
|
-
|
|
2750
|
-
|
|
2751
|
-
|
|
2752
|
-
|
|
2753
|
-
|
|
2754
|
-
|
|
2755
|
-
|
|
2756
|
-
|
|
2757
|
-
|
|
2758
|
-
}
|
|
2759
|
-
|
|
2775
|
+
const fieldIndex = metadata[field];
|
|
2776
|
+
// if (!metadata[field]) {
|
|
2777
|
+
// //
|
|
2778
|
+
// // detect index for this field, considering inheritance
|
|
2779
|
+
// //
|
|
2780
|
+
// metadata[field] = {
|
|
2781
|
+
// type: undefined,
|
|
2782
|
+
// index: (metadata[-1] // current structure already has fields defined
|
|
2783
|
+
// ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
|
|
2784
|
+
// ?? -1) + 1 // no fields defined
|
|
2785
|
+
// }
|
|
2786
|
+
// }
|
|
2787
|
+
metadata[fieldIndex].deprecated = true;
|
|
2760
2788
|
if (throws) {
|
|
2761
|
-
metadata[
|
|
2789
|
+
metadata[$descriptors] ??= {};
|
|
2790
|
+
metadata[$descriptors][field] = {
|
|
2762
2791
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
2763
2792
|
set: function (value) { },
|
|
2764
2793
|
enumerable: false,
|
|
@@ -2766,8 +2795,8 @@ function deprecated(throws = true) {
|
|
|
2766
2795
|
};
|
|
2767
2796
|
}
|
|
2768
2797
|
// flag metadata[field] as non-enumerable
|
|
2769
|
-
Object.defineProperty(metadata,
|
|
2770
|
-
value: metadata[
|
|
2798
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
2799
|
+
value: metadata[fieldIndex],
|
|
2771
2800
|
enumerable: false,
|
|
2772
2801
|
configurable: true
|
|
2773
2802
|
});
|
|
@@ -2833,35 +2862,7 @@ class Schema {
|
|
|
2833
2862
|
enumerable: false,
|
|
2834
2863
|
writable: true
|
|
2835
2864
|
});
|
|
2836
|
-
|
|
2837
|
-
// Define property descriptors
|
|
2838
|
-
for (const field in metadata) {
|
|
2839
|
-
if (metadata[field].descriptor) {
|
|
2840
|
-
// for encoder
|
|
2841
|
-
Object.defineProperty(instance, `_${field}`, {
|
|
2842
|
-
value: undefined,
|
|
2843
|
-
writable: true,
|
|
2844
|
-
enumerable: false,
|
|
2845
|
-
configurable: true,
|
|
2846
|
-
});
|
|
2847
|
-
Object.defineProperty(instance, field, metadata[field].descriptor);
|
|
2848
|
-
}
|
|
2849
|
-
else {
|
|
2850
|
-
// for decoder
|
|
2851
|
-
Object.defineProperty(instance, field, {
|
|
2852
|
-
value: undefined,
|
|
2853
|
-
writable: true,
|
|
2854
|
-
enumerable: true,
|
|
2855
|
-
configurable: true,
|
|
2856
|
-
});
|
|
2857
|
-
}
|
|
2858
|
-
// Object.defineProperty(instance, field, {
|
|
2859
|
-
// ...instance.constructor[Symbol.metadata][field].descriptor
|
|
2860
|
-
// });
|
|
2861
|
-
// if (args[0]?.hasOwnProperty(field)) {
|
|
2862
|
-
// instance[field] = args[0][field];
|
|
2863
|
-
// }
|
|
2864
|
-
}
|
|
2865
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2865
2866
|
}
|
|
2866
2867
|
static is(type) {
|
|
2867
2868
|
return typeof (type[Symbol.metadata]) === "object";
|
|
@@ -2885,7 +2886,7 @@ class Schema {
|
|
|
2885
2886
|
*/
|
|
2886
2887
|
static [$filter](ref, index, view) {
|
|
2887
2888
|
const metadata = ref.constructor[Symbol.metadata];
|
|
2888
|
-
const tag = metadata[
|
|
2889
|
+
const tag = metadata[index]?.tag;
|
|
2889
2890
|
if (view === undefined) {
|
|
2890
2891
|
// shared pass/encode: encode if doesn't have a tag
|
|
2891
2892
|
return tag === undefined;
|
|
@@ -2906,12 +2907,21 @@ class Schema {
|
|
|
2906
2907
|
}
|
|
2907
2908
|
// allow inherited classes to have a constructor
|
|
2908
2909
|
constructor(...args) {
|
|
2909
|
-
|
|
2910
|
+
//
|
|
2911
|
+
// inline
|
|
2912
|
+
// Schema.initialize(this);
|
|
2913
|
+
//
|
|
2914
|
+
Object.defineProperty(this, $changes, {
|
|
2915
|
+
value: new ChangeTree(this),
|
|
2916
|
+
enumerable: false,
|
|
2917
|
+
writable: true
|
|
2918
|
+
});
|
|
2919
|
+
Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2910
2920
|
//
|
|
2911
2921
|
// Assign initial values
|
|
2912
2922
|
//
|
|
2913
2923
|
if (args[0]) {
|
|
2914
|
-
|
|
2924
|
+
Object.assign(this, args[0]);
|
|
2915
2925
|
}
|
|
2916
2926
|
}
|
|
2917
2927
|
assign(props) {
|
|
@@ -2925,7 +2935,8 @@ class Schema {
|
|
|
2925
2935
|
* @param operation OPERATION to perform (detected automatically)
|
|
2926
2936
|
*/
|
|
2927
2937
|
setDirty(property, operation) {
|
|
2928
|
-
this
|
|
2938
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
2939
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
2929
2940
|
}
|
|
2930
2941
|
clone() {
|
|
2931
2942
|
const cloned = new (this.constructor);
|
|
@@ -2934,7 +2945,9 @@ class Schema {
|
|
|
2934
2945
|
// TODO: clone all properties, not only annotated ones
|
|
2935
2946
|
//
|
|
2936
2947
|
// for (const field in this) {
|
|
2937
|
-
for (const
|
|
2948
|
+
for (const fieldIndex in metadata) {
|
|
2949
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
2950
|
+
const field = metadata[fieldIndex].name;
|
|
2938
2951
|
if (typeof (this[field]) === "object" &&
|
|
2939
2952
|
typeof (this[field]?.clone) === "function") {
|
|
2940
2953
|
// deep clone
|
|
@@ -2948,10 +2961,11 @@ class Schema {
|
|
|
2948
2961
|
return cloned;
|
|
2949
2962
|
}
|
|
2950
2963
|
toJSON() {
|
|
2951
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
2952
2964
|
const obj = {};
|
|
2953
|
-
|
|
2954
|
-
|
|
2965
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
2966
|
+
for (const index in metadata) {
|
|
2967
|
+
const field = metadata[index];
|
|
2968
|
+
const fieldName = field.name;
|
|
2955
2969
|
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
2956
2970
|
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
2957
2971
|
? this[fieldName]['toJSON']()
|
|
@@ -2964,10 +2978,12 @@ class Schema {
|
|
|
2964
2978
|
this[$changes].discardAll();
|
|
2965
2979
|
}
|
|
2966
2980
|
[$getByIndex](index) {
|
|
2967
|
-
|
|
2981
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
2982
|
+
return this[metadata[index].name];
|
|
2968
2983
|
}
|
|
2969
2984
|
[$deleteByIndex](index) {
|
|
2970
|
-
this
|
|
2985
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
2986
|
+
this[metadata[index].name] = undefined;
|
|
2971
2987
|
}
|
|
2972
2988
|
static debugRefIds(instance, jsonContents = true, level = 0) {
|
|
2973
2989
|
const ref = instance;
|
|
@@ -3091,6 +3107,7 @@ class CollectionSchema {
|
|
|
3091
3107
|
this.$indexes = new Map();
|
|
3092
3108
|
this.$refId = 0;
|
|
3093
3109
|
this[$changes] = new ChangeTree(this);
|
|
3110
|
+
this[$changes].indexes = {};
|
|
3094
3111
|
if (initialValues) {
|
|
3095
3112
|
initialValues.forEach((v) => this.add(v));
|
|
3096
3113
|
}
|
|
@@ -3246,6 +3263,7 @@ class SetSchema {
|
|
|
3246
3263
|
this.$indexes = new Map();
|
|
3247
3264
|
this.$refId = 0;
|
|
3248
3265
|
this[$changes] = new ChangeTree(this);
|
|
3266
|
+
this[$changes].indexes = {};
|
|
3249
3267
|
if (initialValues) {
|
|
3250
3268
|
initialValues.forEach((v) => this.add(v));
|
|
3251
3269
|
}
|
|
@@ -3432,24 +3450,36 @@ class Root {
|
|
|
3432
3450
|
return this.nextUniqueId++;
|
|
3433
3451
|
}
|
|
3434
3452
|
add(changeTree) {
|
|
3435
|
-
const
|
|
3436
|
-
|
|
3453
|
+
const previousRefCount = this.refCount.get(changeTree);
|
|
3454
|
+
if (previousRefCount === 0) {
|
|
3455
|
+
//
|
|
3456
|
+
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3457
|
+
// We need to re-add all changes to the `changes` map.
|
|
3458
|
+
//
|
|
3459
|
+
changeTree.allChanges.forEach((operation, index) => {
|
|
3460
|
+
changeTree.changes.set(index, operation);
|
|
3461
|
+
});
|
|
3462
|
+
}
|
|
3463
|
+
const refCount = (previousRefCount || 0) + 1;
|
|
3464
|
+
this.refCount.set(changeTree, refCount);
|
|
3465
|
+
return refCount;
|
|
3437
3466
|
}
|
|
3438
3467
|
remove(changeTree) {
|
|
3439
|
-
const refCount = this.refCount.get(changeTree);
|
|
3440
|
-
if (refCount <=
|
|
3468
|
+
const refCount = (this.refCount.get(changeTree)) - 1;
|
|
3469
|
+
if (refCount <= 0) {
|
|
3441
3470
|
this.allChanges.delete(changeTree);
|
|
3442
3471
|
this.changes.delete(changeTree);
|
|
3443
3472
|
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3444
3473
|
this.allFilteredChanges.delete(changeTree);
|
|
3445
3474
|
this.filteredChanges.delete(changeTree);
|
|
3446
3475
|
}
|
|
3447
|
-
this.refCount.
|
|
3476
|
+
this.refCount.set(changeTree, 0);
|
|
3448
3477
|
}
|
|
3449
3478
|
else {
|
|
3450
|
-
this.refCount.set(changeTree, refCount
|
|
3479
|
+
this.refCount.set(changeTree, refCount);
|
|
3451
3480
|
}
|
|
3452
3481
|
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3482
|
+
return refCount;
|
|
3453
3483
|
}
|
|
3454
3484
|
clear() {
|
|
3455
3485
|
this.changes.clear();
|
|
@@ -3480,10 +3510,10 @@ class Encoder {
|
|
|
3480
3510
|
) {
|
|
3481
3511
|
const hasView = (view !== undefined);
|
|
3482
3512
|
const rootChangeTree = this.state[$changes];
|
|
3483
|
-
const
|
|
3484
|
-
for (const [changeTree, changes] of
|
|
3513
|
+
const shouldClearChanges = !isEncodeAll && !hasView;
|
|
3514
|
+
for (const [changeTree, changes] of changeTrees.entries()) {
|
|
3485
3515
|
const ref = changeTree.ref;
|
|
3486
|
-
const ctor = ref
|
|
3516
|
+
const ctor = ref.constructor;
|
|
3487
3517
|
const encoder = ctor[$encoder];
|
|
3488
3518
|
const filter = ctor[$filter];
|
|
3489
3519
|
// try { throw new Error(); } catch (e) {
|
|
@@ -3507,8 +3537,7 @@ class Encoder {
|
|
|
3507
3537
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3508
3538
|
number$1(buffer, changeTree.refId, it);
|
|
3509
3539
|
}
|
|
3510
|
-
const
|
|
3511
|
-
for (const [fieldIndex, operation] of changesIterator) {
|
|
3540
|
+
for (const [fieldIndex, operation] of changes.entries()) {
|
|
3512
3541
|
//
|
|
3513
3542
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3514
3543
|
// they will be encoded per client, based on their view.
|
|
@@ -3533,6 +3562,14 @@ class Encoder {
|
|
|
3533
3562
|
// }
|
|
3534
3563
|
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
|
|
3535
3564
|
}
|
|
3565
|
+
// if (shouldClearChanges) {
|
|
3566
|
+
// // changeTree.endEncode();
|
|
3567
|
+
// changeTree.changes.clear();
|
|
3568
|
+
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3569
|
+
// changeTree.ref[$onEncodeEnd]?.();
|
|
3570
|
+
// // Not a new instance anymore
|
|
3571
|
+
// delete changeTree[$isNew];
|
|
3572
|
+
// }
|
|
3536
3573
|
}
|
|
3537
3574
|
if (it.offset > buffer.byteLength) {
|
|
3538
3575
|
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
@@ -3555,7 +3592,7 @@ class Encoder {
|
|
|
3555
3592
|
//
|
|
3556
3593
|
// only clear changes after making sure buffer resize is not required.
|
|
3557
3594
|
//
|
|
3558
|
-
if (
|
|
3595
|
+
if (shouldClearChanges) {
|
|
3559
3596
|
//
|
|
3560
3597
|
// FIXME: avoid iterating over change trees twice.
|
|
3561
3598
|
//
|
|
@@ -3639,6 +3676,11 @@ class Encoder {
|
|
|
3639
3676
|
const changeTreesIterator = changeTrees.entries();
|
|
3640
3677
|
for (const [changeTree, _] of changeTreesIterator) {
|
|
3641
3678
|
changeTree.endEncode();
|
|
3679
|
+
// changeTree.changes.clear();
|
|
3680
|
+
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3681
|
+
// changeTree.ref[$onEncodeEnd]?.();
|
|
3682
|
+
// // Not a new instance anymore
|
|
3683
|
+
// delete changeTree[$isNew];
|
|
3642
3684
|
}
|
|
3643
3685
|
}
|
|
3644
3686
|
discardChanges() {
|
|
@@ -3754,8 +3796,9 @@ class ReferenceTracker {
|
|
|
3754
3796
|
// Ensure child schema instances have their references removed as well.
|
|
3755
3797
|
//
|
|
3756
3798
|
if (Metadata.isValidInstance(ref)) {
|
|
3757
|
-
const metadata = ref
|
|
3758
|
-
for (const
|
|
3799
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
3800
|
+
for (const index in metadata) {
|
|
3801
|
+
const field = metadata[index].name;
|
|
3759
3802
|
const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
|
|
3760
3803
|
if (childRefId) {
|
|
3761
3804
|
this.removeRef(childRefId);
|
|
@@ -3942,7 +3985,9 @@ class Reflection extends Schema {
|
|
|
3942
3985
|
const reflection = new Reflection();
|
|
3943
3986
|
const encoder = new Encoder(reflection);
|
|
3944
3987
|
const buildType = (currentType, metadata) => {
|
|
3945
|
-
for (const
|
|
3988
|
+
for (const fieldIndex in metadata) {
|
|
3989
|
+
const index = Number(fieldIndex);
|
|
3990
|
+
const fieldName = metadata[index].name;
|
|
3946
3991
|
// skip fields from parent classes
|
|
3947
3992
|
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
3948
3993
|
continue;
|
|
@@ -3950,7 +3995,7 @@ class Reflection extends Schema {
|
|
|
3950
3995
|
const field = new ReflectionField();
|
|
3951
3996
|
field.name = fieldName;
|
|
3952
3997
|
let fieldType;
|
|
3953
|
-
const type = metadata[
|
|
3998
|
+
const type = metadata[index].type;
|
|
3954
3999
|
if (typeof (type) === "string") {
|
|
3955
4000
|
fieldType = type;
|
|
3956
4001
|
}
|
|
@@ -4016,18 +4061,7 @@ class Reflection extends Schema {
|
|
|
4016
4061
|
reflection.types.forEach((reflectionType) => {
|
|
4017
4062
|
const schemaType = typeContext.get(reflectionType.id);
|
|
4018
4063
|
const metadata = schemaType[Symbol.metadata];
|
|
4019
|
-
// FIXME: use metadata[-1] to get field count
|
|
4020
4064
|
const parentFieldIndex = 0;
|
|
4021
|
-
// console.log("--------------------");
|
|
4022
|
-
// // console.log("reflectionType", reflectionType.toJSON());
|
|
4023
|
-
// console.log("reflectionType.fields", reflectionType.fields.toJSON());
|
|
4024
|
-
// console.log("parentFieldIndex", parentFieldIndex);
|
|
4025
|
-
//
|
|
4026
|
-
// FIXME: set fields using parentKlass as well
|
|
4027
|
-
// currently the fields are duplicated on inherited classes
|
|
4028
|
-
//
|
|
4029
|
-
// // const parentKlass = reflection.types[reflectionType.extendsId];
|
|
4030
|
-
// // parentKlass.fields
|
|
4031
4065
|
reflectionType.fields.forEach((field, i) => {
|
|
4032
4066
|
const fieldIndex = parentFieldIndex + i;
|
|
4033
4067
|
if (field.referencedType !== undefined) {
|
|
@@ -4192,7 +4226,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4192
4226
|
//
|
|
4193
4227
|
bindTo: function bindTo(targetObject, properties) {
|
|
4194
4228
|
if (!properties) {
|
|
4195
|
-
properties = Object.keys(metadata);
|
|
4229
|
+
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4196
4230
|
}
|
|
4197
4231
|
return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, () => {
|
|
4198
4232
|
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
@@ -4200,7 +4234,8 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4200
4234
|
}
|
|
4201
4235
|
}, {
|
|
4202
4236
|
get(target, prop) {
|
|
4203
|
-
|
|
4237
|
+
const metadataField = metadata[metadata[prop]];
|
|
4238
|
+
if (metadataField) {
|
|
4204
4239
|
const instance = context.instance?.[prop];
|
|
4205
4240
|
const onInstanceAvailable = ((callback) => {
|
|
4206
4241
|
const unbind = $(context.instance).listen(prop, (value, _) => {
|
|
@@ -4216,7 +4251,7 @@ function getDecoderStateCallbacks(decoder) {
|
|
|
4216
4251
|
callback(instance, true);
|
|
4217
4252
|
}
|
|
4218
4253
|
});
|
|
4219
|
-
return getProxy(
|
|
4254
|
+
return getProxy(metadataField.type, {
|
|
4220
4255
|
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
4221
4256
|
instance: ($root.refIds.get(instance) && instance),
|
|
4222
4257
|
parentInstance: context.instance,
|
|
@@ -4329,7 +4364,7 @@ class StateView {
|
|
|
4329
4364
|
console.warn("StateView#add(), invalid object:", obj);
|
|
4330
4365
|
return this;
|
|
4331
4366
|
}
|
|
4332
|
-
// FIXME: ArraySchema/MapSchema
|
|
4367
|
+
// FIXME: ArraySchema/MapSchema do not have metadata
|
|
4333
4368
|
const metadata = obj.constructor[Symbol.metadata];
|
|
4334
4369
|
const changeTree = obj[$changes];
|
|
4335
4370
|
this.items.add(changeTree);
|
|
@@ -4375,7 +4410,7 @@ class StateView {
|
|
|
4375
4410
|
? changeTree.allFilteredChanges
|
|
4376
4411
|
: changeTree.allChanges;
|
|
4377
4412
|
changeSet.forEach((op, index) => {
|
|
4378
|
-
const tagAtIndex = metadata?.[
|
|
4413
|
+
const tagAtIndex = metadata?.[index].tag;
|
|
4379
4414
|
if ((isInvisible || // if "invisible", include all
|
|
4380
4415
|
tagAtIndex === undefined || // "all change" with no tag
|
|
4381
4416
|
tagAtIndex === tag // tagged property
|
|
@@ -4388,7 +4423,7 @@ class StateView {
|
|
|
4388
4423
|
// Add children of this ChangeTree to this view
|
|
4389
4424
|
changeTree.forEachChild((change, index) => {
|
|
4390
4425
|
// Do not ADD children that don't have the same tag
|
|
4391
|
-
if (metadata && metadata[
|
|
4426
|
+
if (metadata && metadata[index].tag !== tag) {
|
|
4392
4427
|
return;
|
|
4393
4428
|
}
|
|
4394
4429
|
this.add(change.ref, tag, false);
|