@colyseus/schema 3.0.0-alpha.4 → 3.0.0-alpha.40
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/README.md +131 -61
- package/bin/schema-debug +94 -0
- package/build/cjs/index.js +1521 -809
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +1519 -808
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +1528 -816
- package/lib/Metadata.d.ts +21 -9
- package/lib/Metadata.js +169 -32
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +19 -4
- package/lib/Reflection.js +66 -32
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +4 -4
- package/lib/Schema.js +44 -50
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +31 -34
- package/lib/annotations.js +110 -160
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.d.ts +1 -0
- package/lib/bench_encode.js +130 -0
- package/lib/bench_encode.js.map +1 -0
- package/lib/codegen/api.js +1 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/languages/cpp.js +1 -2
- package/lib/codegen/languages/cpp.js.map +1 -1
- package/lib/codegen/languages/csharp.js +1 -2
- package/lib/codegen/languages/csharp.js.map +1 -1
- package/lib/codegen/languages/haxe.js +1 -2
- package/lib/codegen/languages/haxe.js.map +1 -1
- package/lib/codegen/languages/java.js +1 -2
- package/lib/codegen/languages/java.js.map +1 -1
- package/lib/codegen/languages/js.js +1 -2
- package/lib/codegen/languages/js.js.map +1 -1
- package/lib/codegen/languages/lua.js +1 -2
- package/lib/codegen/languages/lua.js.map +1 -1
- package/lib/codegen/languages/ts.js +1 -2
- package/lib/codegen/languages/ts.js.map +1 -1
- package/lib/codegen/parser.js +85 -3
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +6 -3
- package/lib/codegen/types.js.map +1 -1
- package/lib/debug.d.ts +1 -0
- package/lib/debug.js +51 -0
- package/lib/debug.js.map +1 -0
- package/lib/decoder/DecodeOperation.d.ts +0 -1
- package/lib/decoder/DecodeOperation.js +30 -12
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +6 -7
- package/lib/decoder/Decoder.js +9 -9
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/ReferenceTracker.js +3 -2
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/decoder/strategy/RawChanges.js +1 -2
- package/lib/decoder/strategy/RawChanges.js.map +1 -1
- package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
- package/lib/decoder/strategy/StateCallbacks.js +75 -65
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +27 -21
- package/lib/encoder/ChangeTree.js +246 -186
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +3 -6
- package/lib/encoder/EncodeOperation.js +47 -61
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +9 -8
- package/lib/encoder/Encoder.js +165 -88
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +22 -0
- package/lib/encoder/Root.js +81 -0
- package/lib/encoder/Root.js.map +1 -0
- package/lib/encoder/StateView.d.ts +7 -7
- package/lib/encoder/StateView.js +70 -74
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/assert.d.ts +2 -1
- package/lib/encoding/assert.js +5 -5
- package/lib/encoding/assert.js.map +1 -1
- package/lib/encoding/decode.js +20 -21
- package/lib/encoding/decode.js.map +1 -1
- package/lib/encoding/encode.d.ts +2 -2
- package/lib/encoding/encode.js +52 -48
- package/lib/encoding/encode.js.map +1 -1
- package/lib/encoding/spec.d.ts +2 -1
- package/lib/encoding/spec.js +1 -0
- package/lib/encoding/spec.js.map +1 -1
- package/lib/index.d.ts +6 -3
- package/lib/index.js +19 -13
- package/lib/index.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +34 -2
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/TypeContext.d.ts +23 -0
- package/lib/types/TypeContext.js +111 -0
- package/lib/types/TypeContext.js.map +1 -0
- package/lib/types/custom/ArraySchema.d.ts +2 -2
- package/lib/types/custom/ArraySchema.js +33 -22
- 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.d.ts +3 -1
- package/lib/types/custom/MapSchema.js +12 -4
- 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/registry.js +3 -4
- package/lib/types/registry.js.map +1 -1
- package/lib/types/symbols.d.ts +8 -5
- package/lib/types/symbols.js +9 -6
- package/lib/types/symbols.js.map +1 -1
- package/lib/types/utils.js +1 -2
- package/lib/types/utils.js.map +1 -1
- package/lib/utils.js +9 -7
- package/lib/utils.js.map +1 -1
- package/package.json +7 -6
- package/src/Metadata.ts +190 -42
- package/src/Reflection.ts +77 -39
- package/src/Schema.ts +59 -64
- package/src/annotations.ts +155 -203
- package/src/bench_encode.ts +108 -0
- package/src/codegen/parser.ts +107 -0
- package/src/codegen/types.ts +1 -0
- package/src/debug.ts +55 -0
- package/src/decoder/DecodeOperation.ts +40 -12
- package/src/decoder/Decoder.ts +14 -12
- package/src/decoder/ReferenceTracker.ts +3 -2
- package/src/decoder/strategy/StateCallbacks.ts +153 -82
- package/src/encoder/ChangeTree.ts +286 -202
- package/src/encoder/EncodeOperation.ts +77 -77
- package/src/encoder/Encoder.ts +201 -96
- package/src/encoder/Root.ts +93 -0
- package/src/encoder/StateView.ts +76 -88
- package/src/encoding/assert.ts +4 -3
- package/src/encoding/encode.ts +37 -31
- package/src/encoding/spec.ts +1 -0
- package/src/index.ts +8 -14
- package/src/types/HelperTypes.ts +54 -2
- package/src/types/TypeContext.ts +133 -0
- package/src/types/custom/ArraySchema.ts +49 -19
- package/src/types/custom/CollectionSchema.ts +1 -0
- package/src/types/custom/MapSchema.ts +18 -5
- package/src/types/custom/SetSchema.ts +1 -0
- package/src/types/symbols.ts +10 -7
- package/src/utils.ts +7 -3
package/build/umd/index.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
|
|
30
30
|
OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
|
|
31
31
|
OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
|
|
32
|
+
OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
|
|
32
33
|
})(exports.OPERATION || (exports.OPERATION = {}));
|
|
33
34
|
|
|
34
35
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
@@ -48,11 +49,6 @@
|
|
|
48
49
|
* (MapSchema, ArraySchema, etc.)
|
|
49
50
|
*/
|
|
50
51
|
const $childType = Symbol('$childType');
|
|
51
|
-
/**
|
|
52
|
-
* Special ChangeTree property to identify new instances
|
|
53
|
-
* (Once they're encoded, they're not new anymore)
|
|
54
|
-
*/
|
|
55
|
-
const $isNew = Symbol("$isNew");
|
|
56
52
|
/**
|
|
57
53
|
* Optional "discard" method for custom types (ArraySchema)
|
|
58
54
|
* (Discards changes for next serialization)
|
|
@@ -62,6 +58,14 @@
|
|
|
62
58
|
* When decoding, this method is called after the instance is fully decoded
|
|
63
59
|
*/
|
|
64
60
|
const $onDecodeEnd = Symbol("$onDecodeEnd");
|
|
61
|
+
/**
|
|
62
|
+
* Metadata
|
|
63
|
+
*/
|
|
64
|
+
const $descriptors = Symbol("$descriptors");
|
|
65
|
+
const $numFields = "$__numFields";
|
|
66
|
+
const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
|
|
67
|
+
const $viewFieldIndexes = "$__viewFieldIndexes";
|
|
68
|
+
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
65
69
|
|
|
66
70
|
const registeredTypes = {};
|
|
67
71
|
const identifiers = new Map();
|
|
@@ -73,177 +77,416 @@
|
|
|
73
77
|
return registeredTypes[identifier];
|
|
74
78
|
}
|
|
75
79
|
|
|
80
|
+
class TypeContext {
|
|
81
|
+
/**
|
|
82
|
+
* For inheritance support
|
|
83
|
+
* Keeps track of which classes extends which. (parent -> children)
|
|
84
|
+
*/
|
|
85
|
+
static { this.inheritedTypes = new Map(); }
|
|
86
|
+
static register(target) {
|
|
87
|
+
const parent = Object.getPrototypeOf(target);
|
|
88
|
+
if (parent !== Schema) {
|
|
89
|
+
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
90
|
+
if (!inherits) {
|
|
91
|
+
inherits = new Set();
|
|
92
|
+
TypeContext.inheritedTypes.set(parent, inherits);
|
|
93
|
+
}
|
|
94
|
+
inherits.add(target);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
constructor(rootClass) {
|
|
98
|
+
this.types = {};
|
|
99
|
+
this.schemas = new Map();
|
|
100
|
+
this.hasFilters = false;
|
|
101
|
+
this.parentFiltered = {};
|
|
102
|
+
if (rootClass) {
|
|
103
|
+
this.discoverTypes(rootClass);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
has(schema) {
|
|
107
|
+
return this.schemas.has(schema);
|
|
108
|
+
}
|
|
109
|
+
get(typeid) {
|
|
110
|
+
return this.types[typeid];
|
|
111
|
+
}
|
|
112
|
+
add(schema, typeid = this.schemas.size) {
|
|
113
|
+
// skip if already registered
|
|
114
|
+
if (this.schemas.has(schema)) {
|
|
115
|
+
return false;
|
|
116
|
+
}
|
|
117
|
+
this.types[typeid] = schema;
|
|
118
|
+
//
|
|
119
|
+
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
120
|
+
//
|
|
121
|
+
if (schema[Symbol.metadata] === undefined) {
|
|
122
|
+
Metadata.initialize(schema);
|
|
123
|
+
}
|
|
124
|
+
this.schemas.set(schema, typeid);
|
|
125
|
+
return true;
|
|
126
|
+
}
|
|
127
|
+
getTypeId(klass) {
|
|
128
|
+
return this.schemas.get(klass);
|
|
129
|
+
}
|
|
130
|
+
discoverTypes(klass, parentIndex, parentFieldViewTag) {
|
|
131
|
+
if (!this.add(klass)) {
|
|
132
|
+
return;
|
|
133
|
+
}
|
|
134
|
+
// add classes inherited from this base class
|
|
135
|
+
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
136
|
+
this.discoverTypes(child, parentIndex, parentFieldViewTag);
|
|
137
|
+
});
|
|
138
|
+
// add parent classes
|
|
139
|
+
let parent = klass;
|
|
140
|
+
while ((parent = Object.getPrototypeOf(parent)) &&
|
|
141
|
+
parent !== Schema && // stop at root (Schema)
|
|
142
|
+
parent !== Function.prototype // stop at root (non-Schema)
|
|
143
|
+
) {
|
|
144
|
+
this.discoverTypes(parent);
|
|
145
|
+
}
|
|
146
|
+
const metadata = (klass[Symbol.metadata] ??= {});
|
|
147
|
+
// if any schema/field has filters, mark "context" as having filters.
|
|
148
|
+
if (metadata[$viewFieldIndexes]) {
|
|
149
|
+
this.hasFilters = true;
|
|
150
|
+
}
|
|
151
|
+
if (parentFieldViewTag !== undefined) {
|
|
152
|
+
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
153
|
+
}
|
|
154
|
+
for (const fieldIndex in metadata) {
|
|
155
|
+
const index = fieldIndex;
|
|
156
|
+
const fieldType = metadata[index].type;
|
|
157
|
+
const viewTag = metadata[index].tag;
|
|
158
|
+
if (typeof (fieldType) === "string") {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
if (Array.isArray(fieldType)) {
|
|
162
|
+
const type = fieldType[0];
|
|
163
|
+
// skip primitive types
|
|
164
|
+
if (type === "string") {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
this.discoverTypes(type, index, viewTag);
|
|
168
|
+
}
|
|
169
|
+
else if (typeof (fieldType) === "function") {
|
|
170
|
+
this.discoverTypes(fieldType, viewTag);
|
|
171
|
+
}
|
|
172
|
+
else {
|
|
173
|
+
const type = Object.values(fieldType)[0];
|
|
174
|
+
// skip primitive types
|
|
175
|
+
if (typeof (type) === "string") {
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
this.discoverTypes(type, index, viewTag);
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function getNormalizedType(type) {
|
|
185
|
+
return (Array.isArray(type))
|
|
186
|
+
? { array: type[0] }
|
|
187
|
+
: (typeof (type['type']) !== "undefined")
|
|
188
|
+
? type['type']
|
|
189
|
+
: type;
|
|
190
|
+
}
|
|
76
191
|
const Metadata = {
|
|
77
|
-
addField(metadata, index,
|
|
192
|
+
addField(metadata, index, name, type, descriptor) {
|
|
78
193
|
if (index > 64) {
|
|
79
|
-
throw new Error(`Can't define field '${
|
|
194
|
+
throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
|
|
80
195
|
}
|
|
81
|
-
metadata[
|
|
196
|
+
metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
|
|
82
197
|
{
|
|
83
|
-
type: (
|
|
84
|
-
? { array: type[0] }
|
|
85
|
-
: type,
|
|
198
|
+
type: getNormalizedType(type),
|
|
86
199
|
index,
|
|
87
|
-
|
|
200
|
+
name,
|
|
88
201
|
});
|
|
202
|
+
// create "descriptors" map
|
|
203
|
+
Object.defineProperty(metadata, $descriptors, {
|
|
204
|
+
value: metadata[$descriptors] || {},
|
|
205
|
+
enumerable: false,
|
|
206
|
+
configurable: true,
|
|
207
|
+
});
|
|
208
|
+
if (descriptor) {
|
|
209
|
+
// for encoder
|
|
210
|
+
metadata[$descriptors][name] = descriptor;
|
|
211
|
+
metadata[$descriptors][`_${name}`] = {
|
|
212
|
+
value: undefined,
|
|
213
|
+
writable: true,
|
|
214
|
+
enumerable: false,
|
|
215
|
+
configurable: true,
|
|
216
|
+
};
|
|
217
|
+
}
|
|
218
|
+
else {
|
|
219
|
+
// for decoder
|
|
220
|
+
metadata[$descriptors][name] = {
|
|
221
|
+
value: undefined,
|
|
222
|
+
writable: true,
|
|
223
|
+
enumerable: true,
|
|
224
|
+
configurable: true,
|
|
225
|
+
};
|
|
226
|
+
}
|
|
89
227
|
// map -1 as last field index
|
|
90
|
-
Object.defineProperty(metadata,
|
|
228
|
+
Object.defineProperty(metadata, $numFields, {
|
|
91
229
|
value: index,
|
|
92
230
|
enumerable: false,
|
|
93
231
|
configurable: true
|
|
94
232
|
});
|
|
95
|
-
// map
|
|
96
|
-
Object.defineProperty(metadata,
|
|
97
|
-
value:
|
|
233
|
+
// map field name => index (non enumerable)
|
|
234
|
+
Object.defineProperty(metadata, name, {
|
|
235
|
+
value: index,
|
|
98
236
|
enumerable: false,
|
|
99
237
|
configurable: true,
|
|
100
238
|
});
|
|
239
|
+
// if child Ref/complex type, add to -4
|
|
240
|
+
if (typeof (metadata[index].type) !== "string") {
|
|
241
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
242
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
243
|
+
value: [],
|
|
244
|
+
enumerable: false,
|
|
245
|
+
configurable: true,
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
249
|
+
}
|
|
101
250
|
},
|
|
102
251
|
setTag(metadata, fieldName, tag) {
|
|
252
|
+
const index = metadata[fieldName];
|
|
253
|
+
const field = metadata[index];
|
|
103
254
|
// add 'tag' to the field
|
|
104
|
-
const field = metadata[fieldName];
|
|
105
255
|
field.tag = tag;
|
|
106
|
-
if (!metadata[
|
|
256
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
107
257
|
// -2: all field indexes with "view" tag
|
|
108
|
-
Object.defineProperty(metadata,
|
|
258
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
109
259
|
value: [],
|
|
110
260
|
enumerable: false,
|
|
111
261
|
configurable: true
|
|
112
262
|
});
|
|
113
263
|
// -3: field indexes by "view" tag
|
|
114
|
-
Object.defineProperty(metadata,
|
|
264
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
115
265
|
value: {},
|
|
116
266
|
enumerable: false,
|
|
117
267
|
configurable: true
|
|
118
268
|
});
|
|
119
269
|
}
|
|
120
|
-
metadata[
|
|
121
|
-
if (!metadata[
|
|
122
|
-
metadata[
|
|
270
|
+
metadata[$viewFieldIndexes].push(index);
|
|
271
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
272
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
123
273
|
}
|
|
124
|
-
metadata[
|
|
274
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
125
275
|
},
|
|
126
276
|
setFields(target, fields) {
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
// if
|
|
134
|
-
|
|
277
|
+
// for inheritance support
|
|
278
|
+
const constructor = target.prototype.constructor;
|
|
279
|
+
TypeContext.register(constructor);
|
|
280
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
281
|
+
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
282
|
+
const metadata = Metadata.initialize(constructor);
|
|
283
|
+
// Use Schema's methods if not defined in the class
|
|
284
|
+
if (!constructor[$track]) {
|
|
285
|
+
constructor[$track] = Schema[$track];
|
|
286
|
+
}
|
|
287
|
+
if (!constructor[$encoder]) {
|
|
288
|
+
constructor[$encoder] = Schema[$encoder];
|
|
289
|
+
}
|
|
290
|
+
if (!constructor[$decoder]) {
|
|
291
|
+
constructor[$decoder] = Schema[$decoder];
|
|
292
|
+
}
|
|
293
|
+
if (!constructor.prototype.toJSON) {
|
|
294
|
+
constructor.prototype.toJSON = Schema.prototype.toJSON;
|
|
295
|
+
}
|
|
296
|
+
//
|
|
297
|
+
// detect index for this field, considering inheritance
|
|
298
|
+
//
|
|
299
|
+
let fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
300
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
301
|
+
?? -1; // no fields defined
|
|
302
|
+
fieldIndex++;
|
|
135
303
|
for (const field in fields) {
|
|
136
304
|
const type = fields[field];
|
|
305
|
+
const normalizedType = getNormalizedType(type);
|
|
137
306
|
// FIXME: this code is duplicated from @type() annotation
|
|
138
307
|
const complexTypeKlass = (Array.isArray(type))
|
|
139
308
|
? getType("array")
|
|
140
309
|
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
141
|
-
|
|
142
|
-
|
|
310
|
+
const childType = (complexTypeKlass)
|
|
311
|
+
? Object.values(type)[0]
|
|
312
|
+
: normalizedType;
|
|
313
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
314
|
+
fieldIndex++;
|
|
143
315
|
}
|
|
316
|
+
return target;
|
|
144
317
|
},
|
|
145
318
|
isDeprecated(metadata, field) {
|
|
146
319
|
return metadata[field].deprecated === true;
|
|
147
320
|
},
|
|
321
|
+
init(klass) {
|
|
322
|
+
//
|
|
323
|
+
// Used only to initialize an empty Schema (Encoder#constructor)
|
|
324
|
+
// TODO: remove/refactor this...
|
|
325
|
+
//
|
|
326
|
+
const metadata = {};
|
|
327
|
+
klass[Symbol.metadata] = metadata;
|
|
328
|
+
Object.defineProperty(metadata, $numFields, {
|
|
329
|
+
value: 0,
|
|
330
|
+
enumerable: false,
|
|
331
|
+
configurable: true,
|
|
332
|
+
});
|
|
333
|
+
},
|
|
334
|
+
initialize(constructor) {
|
|
335
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
336
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
337
|
+
let metadata = constructor[Symbol.metadata] ?? Object.create(null);
|
|
338
|
+
// make sure inherited classes have their own metadata object.
|
|
339
|
+
if (parentClass !== Schema && metadata === parentMetadata) {
|
|
340
|
+
metadata = Object.create(null);
|
|
341
|
+
if (parentMetadata) {
|
|
342
|
+
//
|
|
343
|
+
// assign parent metadata to current
|
|
344
|
+
//
|
|
345
|
+
Object.setPrototypeOf(metadata, parentMetadata);
|
|
346
|
+
// $numFields
|
|
347
|
+
Object.defineProperty(metadata, $numFields, {
|
|
348
|
+
value: parentMetadata[$numFields],
|
|
349
|
+
enumerable: false,
|
|
350
|
+
configurable: true,
|
|
351
|
+
writable: true,
|
|
352
|
+
});
|
|
353
|
+
// $viewFieldIndexes / $fieldIndexesByViewTag
|
|
354
|
+
if (parentMetadata[$viewFieldIndexes] !== undefined) {
|
|
355
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
356
|
+
value: [...parentMetadata[$viewFieldIndexes]],
|
|
357
|
+
enumerable: false,
|
|
358
|
+
configurable: true,
|
|
359
|
+
writable: true,
|
|
360
|
+
});
|
|
361
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
362
|
+
value: { ...parentMetadata[$fieldIndexesByViewTag] },
|
|
363
|
+
enumerable: false,
|
|
364
|
+
configurable: true,
|
|
365
|
+
writable: true,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
// $refTypeFieldIndexes
|
|
369
|
+
if (parentMetadata[$refTypeFieldIndexes] !== undefined) {
|
|
370
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
371
|
+
value: [...parentMetadata[$refTypeFieldIndexes]],
|
|
372
|
+
enumerable: false,
|
|
373
|
+
configurable: true,
|
|
374
|
+
writable: true,
|
|
375
|
+
});
|
|
376
|
+
}
|
|
377
|
+
// $descriptors
|
|
378
|
+
Object.defineProperty(metadata, $descriptors, {
|
|
379
|
+
value: { ...parentMetadata[$descriptors] },
|
|
380
|
+
enumerable: false,
|
|
381
|
+
configurable: true,
|
|
382
|
+
writable: true,
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
constructor[Symbol.metadata] = metadata;
|
|
387
|
+
return metadata;
|
|
388
|
+
},
|
|
148
389
|
isValidInstance(klass) {
|
|
149
390
|
return (klass.constructor[Symbol.metadata] &&
|
|
150
|
-
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata],
|
|
391
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
151
392
|
},
|
|
152
393
|
getFields(klass) {
|
|
153
394
|
const metadata = klass[Symbol.metadata];
|
|
154
395
|
const fields = {};
|
|
155
|
-
for (let i = 0; i <= metadata[
|
|
156
|
-
fields[metadata[i]] = metadata[
|
|
396
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
397
|
+
fields[metadata[i].name] = metadata[i].type;
|
|
157
398
|
}
|
|
158
399
|
return fields;
|
|
159
400
|
}
|
|
160
401
|
};
|
|
161
402
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
this.refCount = new WeakMap();
|
|
167
|
-
// all changes
|
|
168
|
-
this.allChanges = new Map();
|
|
169
|
-
this.allFilteredChanges = new Map();
|
|
170
|
-
// pending changes to be encoded
|
|
171
|
-
this.changes = new Map();
|
|
172
|
-
this.filteredChanges = new Map();
|
|
173
|
-
}
|
|
174
|
-
getNextUniqueId() {
|
|
175
|
-
return this.nextUniqueId++;
|
|
403
|
+
function setOperationAtIndex(changeSet, index) {
|
|
404
|
+
const operationsIndex = changeSet.indexes[index];
|
|
405
|
+
if (operationsIndex === undefined) {
|
|
406
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
176
407
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
this.refCount.set(changeTree, refCount + 1);
|
|
408
|
+
else {
|
|
409
|
+
changeSet.operations[operationsIndex] = index;
|
|
180
410
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
187
|
-
this.allFilteredChanges.delete(changeTree);
|
|
188
|
-
this.filteredChanges.delete(changeTree);
|
|
189
|
-
}
|
|
190
|
-
this.refCount.delete(changeTree);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
this.refCount.set(changeTree, refCount - 1);
|
|
194
|
-
}
|
|
195
|
-
changeTree.forEachChild((child, _) => this.remove(child));
|
|
411
|
+
}
|
|
412
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
413
|
+
const operationsIndex = changeSet.indexes[index];
|
|
414
|
+
if (operationsIndex !== undefined) {
|
|
415
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
196
416
|
}
|
|
197
|
-
|
|
198
|
-
|
|
417
|
+
delete changeSet.indexes[index];
|
|
418
|
+
}
|
|
419
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
420
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
421
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
199
422
|
}
|
|
200
423
|
}
|
|
201
424
|
class ChangeTree {
|
|
202
|
-
static { _a$5 = $isNew; }
|
|
203
|
-
;
|
|
204
425
|
constructor(ref) {
|
|
205
|
-
this.
|
|
206
|
-
this.
|
|
207
|
-
this.
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
426
|
+
this.isFiltered = false;
|
|
427
|
+
this.isPartiallyFiltered = false;
|
|
428
|
+
this.indexedOperations = {};
|
|
429
|
+
//
|
|
430
|
+
// TODO:
|
|
431
|
+
// try storing the index + operation per item.
|
|
432
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
433
|
+
//
|
|
434
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
435
|
+
//
|
|
436
|
+
this.changes = { indexes: {}, operations: [] };
|
|
437
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
438
|
+
/**
|
|
439
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
440
|
+
*/
|
|
441
|
+
this.isNew = true;
|
|
212
442
|
this.ref = ref;
|
|
443
|
+
//
|
|
444
|
+
// Does this structure have "filters" declared?
|
|
445
|
+
//
|
|
446
|
+
if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
|
|
447
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
448
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
449
|
+
}
|
|
213
450
|
}
|
|
214
451
|
setRoot(root) {
|
|
215
452
|
this.root = root;
|
|
216
|
-
this.root.add(this);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
453
|
+
const isNewChangeTree = this.root.add(this);
|
|
454
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
455
|
+
if (this.root.types.hasFilters) {
|
|
456
|
+
//
|
|
457
|
+
// At Schema initialization, the "root" structure might not be available
|
|
458
|
+
// yet, as it only does once the "Encoder" has been set up.
|
|
459
|
+
//
|
|
460
|
+
// So the "parent" may be already set without a "root".
|
|
461
|
+
//
|
|
462
|
+
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
463
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
464
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
465
|
+
if (isNewChangeTree) {
|
|
466
|
+
this.root.allFilteredChanges.push(this);
|
|
467
|
+
}
|
|
468
|
+
}
|
|
469
|
+
}
|
|
226
470
|
if (!this.isFiltered) {
|
|
227
|
-
|
|
471
|
+
enqueueChangeTree(root, this, 'changes');
|
|
472
|
+
if (isNewChangeTree) {
|
|
473
|
+
this.root.allChanges.push(this);
|
|
474
|
+
}
|
|
228
475
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
476
|
+
// Recursively set root on child structures
|
|
477
|
+
if (metadata) {
|
|
478
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
479
|
+
const field = metadata[index];
|
|
480
|
+
const value = this.ref[field.name];
|
|
481
|
+
value?.[$changes].setRoot(root);
|
|
482
|
+
});
|
|
234
483
|
}
|
|
235
|
-
if (
|
|
236
|
-
|
|
484
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
485
|
+
// MapSchema / ArraySchema, etc.
|
|
486
|
+
this.ref.forEach((value, key) => {
|
|
487
|
+
value[$changes].setRoot(root);
|
|
488
|
+
});
|
|
237
489
|
}
|
|
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
490
|
}
|
|
248
491
|
setParent(parent, root, parentIndex) {
|
|
249
492
|
this.parent = parent;
|
|
@@ -252,83 +495,104 @@
|
|
|
252
495
|
if (!root) {
|
|
253
496
|
return;
|
|
254
497
|
}
|
|
255
|
-
|
|
498
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
256
499
|
// skip if parent is already set
|
|
257
|
-
if (root
|
|
258
|
-
this.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
500
|
+
if (root !== this.root) {
|
|
501
|
+
this.root = root;
|
|
502
|
+
const isNewChangeTree = root.add(this);
|
|
503
|
+
if (root.types.hasFilters) {
|
|
504
|
+
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
505
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
506
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
507
|
+
if (isNewChangeTree) {
|
|
508
|
+
this.root.allFilteredChanges.push(this);
|
|
509
|
+
}
|
|
510
|
+
}
|
|
511
|
+
}
|
|
512
|
+
if (!this.isFiltered) {
|
|
513
|
+
enqueueChangeTree(root, this, 'changes');
|
|
514
|
+
if (isNewChangeTree) {
|
|
515
|
+
this.root.allChanges.push(this);
|
|
516
|
+
}
|
|
517
|
+
}
|
|
262
518
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
if (!this.isFiltered) {
|
|
266
|
-
this.root.changes.set(this, this.changes);
|
|
519
|
+
else {
|
|
520
|
+
root.add(this);
|
|
267
521
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
522
|
+
// assign same parent on child structures
|
|
523
|
+
if (metadata) {
|
|
524
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
525
|
+
const field = metadata[index];
|
|
526
|
+
const value = this.ref[field.name];
|
|
527
|
+
value?.[$changes].setParent(this.ref, root, index);
|
|
528
|
+
// try { throw new Error(); } catch (e) {
|
|
529
|
+
// console.log(e.stack);
|
|
530
|
+
// }
|
|
531
|
+
});
|
|
271
532
|
}
|
|
272
|
-
else {
|
|
273
|
-
|
|
533
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
534
|
+
// MapSchema / ArraySchema, etc.
|
|
535
|
+
this.ref.forEach((value, key) => {
|
|
536
|
+
value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
|
|
537
|
+
});
|
|
274
538
|
}
|
|
275
|
-
this.ensureRefId();
|
|
276
|
-
this.forEachChild((changeTree, atIndex) => {
|
|
277
|
-
changeTree.setParent(this.ref, root, atIndex);
|
|
278
|
-
});
|
|
279
539
|
}
|
|
280
540
|
forEachChild(callback) {
|
|
281
541
|
//
|
|
282
542
|
// assign same parent on child structures
|
|
283
543
|
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const value = this.ref[field];
|
|
289
|
-
if (value
|
|
290
|
-
callback(value[$changes],
|
|
544
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
545
|
+
if (metadata) {
|
|
546
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
547
|
+
const field = metadata[index];
|
|
548
|
+
const value = this.ref[field.name];
|
|
549
|
+
if (value) {
|
|
550
|
+
callback(value[$changes], index);
|
|
291
551
|
}
|
|
292
|
-
}
|
|
552
|
+
});
|
|
293
553
|
}
|
|
294
|
-
else if (typeof (this.ref)
|
|
554
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
295
555
|
// MapSchema / ArraySchema, etc.
|
|
296
556
|
this.ref.forEach((value, key) => {
|
|
297
|
-
|
|
298
|
-
callback(value[$changes], this.ref[$changes].indexes[key]);
|
|
299
|
-
}
|
|
557
|
+
callback(value[$changes], this.indexes[key] ?? key);
|
|
300
558
|
});
|
|
301
559
|
}
|
|
302
560
|
}
|
|
303
561
|
operation(op) {
|
|
304
|
-
|
|
305
|
-
this.
|
|
562
|
+
// operations without index use negative values to represent them
|
|
563
|
+
// this is checked during .encode() time.
|
|
564
|
+
this.changes.operations.push(-op);
|
|
565
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
306
566
|
}
|
|
307
567
|
change(index, operation = exports.OPERATION.ADD) {
|
|
308
|
-
const metadata = this.ref
|
|
309
|
-
const isFiltered = this.isFiltered || (metadata
|
|
568
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
569
|
+
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
310
570
|
const changeSet = (isFiltered)
|
|
311
571
|
? this.filteredChanges
|
|
312
572
|
: this.changes;
|
|
313
|
-
const previousOperation =
|
|
573
|
+
const previousOperation = this.indexedOperations[index];
|
|
314
574
|
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
315
575
|
const op = (!previousOperation)
|
|
316
576
|
? operation
|
|
317
577
|
: (previousOperation === exports.OPERATION.DELETE)
|
|
318
578
|
? exports.OPERATION.DELETE_AND_ADD
|
|
319
579
|
: operation;
|
|
320
|
-
|
|
580
|
+
//
|
|
581
|
+
// TODO: are DELETE operations being encoded as ADD here ??
|
|
582
|
+
//
|
|
583
|
+
this.indexedOperations[index] = op;
|
|
321
584
|
}
|
|
322
|
-
|
|
323
|
-
// TODO: are DELETE operations being encoded as ADD here ??
|
|
324
|
-
//
|
|
585
|
+
setOperationAtIndex(changeSet, index);
|
|
325
586
|
if (isFiltered) {
|
|
326
|
-
this.allFilteredChanges
|
|
327
|
-
this.root
|
|
587
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
588
|
+
if (this.root) {
|
|
589
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
590
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
591
|
+
}
|
|
328
592
|
}
|
|
329
593
|
else {
|
|
330
|
-
this.allChanges
|
|
331
|
-
this.root
|
|
594
|
+
setOperationAtIndex(this.allChanges, index);
|
|
595
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
332
596
|
}
|
|
333
597
|
}
|
|
334
598
|
shiftChangeIndexes(shiftIndex) {
|
|
@@ -340,12 +604,15 @@
|
|
|
340
604
|
const changeSet = (this.isFiltered)
|
|
341
605
|
? this.filteredChanges
|
|
342
606
|
: this.changes;
|
|
343
|
-
const
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
607
|
+
const newIndexedOperations = {};
|
|
608
|
+
const newIndexes = {};
|
|
609
|
+
for (const index in this.indexedOperations) {
|
|
610
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
611
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
348
612
|
}
|
|
613
|
+
this.indexedOperations = newIndexedOperations;
|
|
614
|
+
changeSet.indexes = newIndexes;
|
|
615
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
349
616
|
}
|
|
350
617
|
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
351
618
|
//
|
|
@@ -361,33 +628,42 @@
|
|
|
361
628
|
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
362
629
|
}
|
|
363
630
|
}
|
|
364
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0,
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
631
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
632
|
+
const newIndexes = {};
|
|
633
|
+
for (const key in changeSet.indexes) {
|
|
634
|
+
const index = changeSet.indexes[key];
|
|
635
|
+
if (index > startIndex) {
|
|
636
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
370
637
|
}
|
|
371
|
-
|
|
638
|
+
else {
|
|
639
|
+
newIndexes[key] = index;
|
|
640
|
+
}
|
|
641
|
+
}
|
|
642
|
+
changeSet.indexes = newIndexes;
|
|
643
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
644
|
+
const index = changeSet.operations[i];
|
|
645
|
+
if (index > startIndex) {
|
|
646
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
647
|
+
}
|
|
648
|
+
}
|
|
372
649
|
}
|
|
373
650
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
this.
|
|
378
|
-
this.
|
|
379
|
-
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
651
|
+
this.indexedOperations[index] = operation;
|
|
652
|
+
if (this.filteredChanges) {
|
|
653
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
654
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
655
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
380
656
|
}
|
|
381
657
|
else {
|
|
382
|
-
this.allChanges
|
|
383
|
-
this.changes
|
|
384
|
-
this.root
|
|
658
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
659
|
+
setOperationAtIndex(this.changes, index);
|
|
660
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
385
661
|
}
|
|
386
662
|
}
|
|
387
663
|
getType(index) {
|
|
388
664
|
if (Metadata.isValidInstance(this.ref)) {
|
|
389
|
-
const metadata = this.ref
|
|
390
|
-
return metadata[
|
|
665
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
666
|
+
return metadata[index].type;
|
|
391
667
|
}
|
|
392
668
|
else {
|
|
393
669
|
//
|
|
@@ -400,8 +676,7 @@
|
|
|
400
676
|
}
|
|
401
677
|
}
|
|
402
678
|
getChange(index) {
|
|
403
|
-
|
|
404
|
-
return this.changes.get(index) ?? this.filteredChanges.get(index);
|
|
679
|
+
return this.indexedOperations[index];
|
|
405
680
|
}
|
|
406
681
|
//
|
|
407
682
|
// used during `.encode()`
|
|
@@ -422,16 +697,14 @@
|
|
|
422
697
|
}
|
|
423
698
|
return;
|
|
424
699
|
}
|
|
425
|
-
const
|
|
426
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
427
|
-
const changeSet = (isFiltered)
|
|
700
|
+
const changeSet = (this.filteredChanges)
|
|
428
701
|
? this.filteredChanges
|
|
429
702
|
: this.changes;
|
|
703
|
+
this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
|
|
704
|
+
setOperationAtIndex(changeSet, index);
|
|
430
705
|
const previousValue = this.getValue(index);
|
|
431
|
-
changeSet.set(index, operation ?? exports.OPERATION.DELETE);
|
|
432
706
|
// remove `root` reference
|
|
433
707
|
if (previousValue && previousValue[$changes]) {
|
|
434
|
-
previousValue[$changes].root = undefined;
|
|
435
708
|
//
|
|
436
709
|
// FIXME: this.root is "undefined"
|
|
437
710
|
//
|
|
@@ -445,22 +718,26 @@
|
|
|
445
718
|
this.root?.remove(previousValue[$changes]);
|
|
446
719
|
}
|
|
447
720
|
//
|
|
448
|
-
// FIXME: this is looking a
|
|
721
|
+
// FIXME: this is looking a ugly and repeated
|
|
449
722
|
//
|
|
450
|
-
if (
|
|
451
|
-
this.
|
|
452
|
-
this.
|
|
723
|
+
if (this.filteredChanges) {
|
|
724
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
725
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
453
726
|
}
|
|
454
727
|
else {
|
|
455
|
-
this.
|
|
456
|
-
this.
|
|
728
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
729
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
457
730
|
}
|
|
458
731
|
}
|
|
459
732
|
endEncode() {
|
|
460
|
-
this.
|
|
733
|
+
this.indexedOperations = {};
|
|
734
|
+
// // clear changes
|
|
735
|
+
// this.changes.indexes = {};
|
|
736
|
+
// this.changes.operations.length = 0;
|
|
737
|
+
// ArraySchema and MapSchema have a custom "encode end" method
|
|
461
738
|
this.ref[$onEncodeEnd]?.();
|
|
462
739
|
// Not a new instance anymore
|
|
463
|
-
|
|
740
|
+
this.isNew = false;
|
|
464
741
|
}
|
|
465
742
|
discard(discardAll = false) {
|
|
466
743
|
//
|
|
@@ -469,13 +746,22 @@
|
|
|
469
746
|
// REPLACE in case same key is used on next patches.
|
|
470
747
|
//
|
|
471
748
|
this.ref[$onEncodeEnd]?.();
|
|
472
|
-
this.
|
|
473
|
-
this.
|
|
474
|
-
|
|
475
|
-
this.
|
|
749
|
+
this.indexedOperations = {};
|
|
750
|
+
this.changes.indexes = {};
|
|
751
|
+
this.changes.operations.length = 0;
|
|
752
|
+
this.changes.queueRootIndex = undefined;
|
|
753
|
+
if (this.filteredChanges !== undefined) {
|
|
754
|
+
this.filteredChanges.indexes = {};
|
|
755
|
+
this.filteredChanges.operations.length = 0;
|
|
756
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
757
|
+
}
|
|
476
758
|
if (discardAll) {
|
|
477
|
-
this.allChanges.
|
|
478
|
-
this.
|
|
759
|
+
this.allChanges.indexes = {};
|
|
760
|
+
this.allChanges.operations.length = 0;
|
|
761
|
+
if (this.allFilteredChanges !== undefined) {
|
|
762
|
+
this.allFilteredChanges.indexes = {};
|
|
763
|
+
this.allFilteredChanges.operations.length = 0;
|
|
764
|
+
}
|
|
479
765
|
// remove children references
|
|
480
766
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
481
767
|
}
|
|
@@ -484,12 +770,13 @@
|
|
|
484
770
|
* Recursively discard all changes from this, and child structures.
|
|
485
771
|
*/
|
|
486
772
|
discardAll() {
|
|
487
|
-
this.
|
|
488
|
-
|
|
773
|
+
const keys = Object.keys(this.indexedOperations);
|
|
774
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
775
|
+
const value = this.getValue(Number(keys[i]));
|
|
489
776
|
if (value && value[$changes]) {
|
|
490
777
|
value[$changes].discardAll();
|
|
491
778
|
}
|
|
492
|
-
}
|
|
779
|
+
}
|
|
493
780
|
this.discard();
|
|
494
781
|
}
|
|
495
782
|
ensureRefId() {
|
|
@@ -500,35 +787,49 @@
|
|
|
500
787
|
this.refId = this.root.getNextUniqueId();
|
|
501
788
|
}
|
|
502
789
|
get changed() {
|
|
503
|
-
return this.
|
|
790
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
504
791
|
}
|
|
505
|
-
checkIsFiltered(parent, parentIndex) {
|
|
792
|
+
checkIsFiltered(metadata, parent, parentIndex) {
|
|
506
793
|
// Detect if current structure has "filters" declared
|
|
507
|
-
this.isPartiallyFiltered =
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const metadata = parent['constructor'][Symbol.metadata];
|
|
512
|
-
const fieldName = metadata?.[parentIndex];
|
|
513
|
-
const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
|
|
514
|
-
this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
|
|
515
|
-
parent = parent[$changes].parent;
|
|
794
|
+
this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
|
|
795
|
+
if (this.isPartiallyFiltered) {
|
|
796
|
+
this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
|
|
797
|
+
this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
|
|
516
798
|
}
|
|
799
|
+
// skip if parent is not set
|
|
800
|
+
if (!parent) {
|
|
801
|
+
return;
|
|
802
|
+
}
|
|
803
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
804
|
+
const parentChangeTree = parent[$changes];
|
|
805
|
+
parent = parentChangeTree.parent;
|
|
806
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
807
|
+
}
|
|
808
|
+
const parentMetadata = parent.constructor?.[Symbol.metadata];
|
|
809
|
+
this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
517
810
|
//
|
|
518
811
|
// TODO: refactor this!
|
|
519
812
|
//
|
|
520
813
|
// swapping `changes` and `filteredChanges` is required here
|
|
521
814
|
// because "isFiltered" may not be imedialely available on `change()`
|
|
522
815
|
//
|
|
523
|
-
if (this.isFiltered
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
this.changes
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
816
|
+
if (this.isFiltered) {
|
|
817
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
818
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
819
|
+
if (this.changes.operations.length > 0) {
|
|
820
|
+
// swap changes reference
|
|
821
|
+
const changes = this.changes;
|
|
822
|
+
this.changes = this.filteredChanges;
|
|
823
|
+
this.filteredChanges = changes;
|
|
824
|
+
// swap "all changes" reference
|
|
825
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
826
|
+
this.allFilteredChanges = this.allChanges;
|
|
827
|
+
this.allChanges = allFilteredChanges;
|
|
828
|
+
// console.log("SWAP =>", {
|
|
829
|
+
// "this.allFilteredChanges": this.allFilteredChanges,
|
|
830
|
+
// "this.allChanges": this.allChanges
|
|
831
|
+
// })
|
|
832
|
+
}
|
|
532
833
|
}
|
|
533
834
|
}
|
|
534
835
|
}
|
|
@@ -565,26 +866,29 @@
|
|
|
565
866
|
textEncoder = new TextEncoder();
|
|
566
867
|
}
|
|
567
868
|
catch (e) { }
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
869
|
+
const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
|
|
870
|
+
const utf8Length = (hasBufferByteLength)
|
|
871
|
+
? Buffer.byteLength // node
|
|
872
|
+
: function (str, _) {
|
|
873
|
+
var c = 0, length = 0;
|
|
874
|
+
for (var i = 0, l = str.length; i < l; i++) {
|
|
875
|
+
c = str.charCodeAt(i);
|
|
876
|
+
if (c < 0x80) {
|
|
877
|
+
length += 1;
|
|
878
|
+
}
|
|
879
|
+
else if (c < 0x800) {
|
|
880
|
+
length += 2;
|
|
881
|
+
}
|
|
882
|
+
else if (c < 0xd800 || c >= 0xe000) {
|
|
883
|
+
length += 3;
|
|
884
|
+
}
|
|
885
|
+
else {
|
|
886
|
+
i++;
|
|
887
|
+
length += 4;
|
|
888
|
+
}
|
|
584
889
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
890
|
+
return length;
|
|
891
|
+
};
|
|
588
892
|
function utf8Write(view, str, it) {
|
|
589
893
|
var c = 0;
|
|
590
894
|
for (var i = 0, l = str.length; i < l; i++) {
|
|
@@ -593,21 +897,24 @@
|
|
|
593
897
|
view[it.offset++] = c;
|
|
594
898
|
}
|
|
595
899
|
else if (c < 0x800) {
|
|
596
|
-
view[it.offset
|
|
597
|
-
view[it.offset
|
|
900
|
+
view[it.offset] = 0xc0 | (c >> 6);
|
|
901
|
+
view[it.offset + 1] = 0x80 | (c & 0x3f);
|
|
902
|
+
it.offset += 2;
|
|
598
903
|
}
|
|
599
904
|
else if (c < 0xd800 || c >= 0xe000) {
|
|
600
|
-
view[it.offset
|
|
601
|
-
view[it.offset
|
|
602
|
-
view[it.offset
|
|
905
|
+
view[it.offset] = 0xe0 | (c >> 12);
|
|
906
|
+
view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
|
|
907
|
+
view[it.offset + 2] = 0x80 | (c & 0x3f);
|
|
908
|
+
it.offset += 3;
|
|
603
909
|
}
|
|
604
910
|
else {
|
|
605
911
|
i++;
|
|
606
912
|
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
607
|
-
view[it.offset
|
|
608
|
-
view[it.offset
|
|
609
|
-
view[it.offset
|
|
610
|
-
view[it.offset
|
|
913
|
+
view[it.offset] = 0xf0 | (c >> 18);
|
|
914
|
+
view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
|
|
915
|
+
view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
|
|
916
|
+
view[it.offset + 3] = 0x80 | (c & 0x3f);
|
|
917
|
+
it.offset += 4;
|
|
611
918
|
}
|
|
612
919
|
}
|
|
613
920
|
}
|
|
@@ -679,8 +986,7 @@
|
|
|
679
986
|
if (!value) {
|
|
680
987
|
value = "";
|
|
681
988
|
}
|
|
682
|
-
|
|
683
|
-
let length = Buffer.byteLength(value, "utf8");
|
|
989
|
+
let length = utf8Length(value, "utf8");
|
|
684
990
|
let size = 0;
|
|
685
991
|
// fixstr
|
|
686
992
|
if (length < 0x20) {
|
|
@@ -791,81 +1097,30 @@
|
|
|
791
1097
|
|
|
792
1098
|
var encode = /*#__PURE__*/Object.freeze({
|
|
793
1099
|
__proto__: null,
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
uint8: uint8$1,
|
|
1100
|
+
boolean: boolean$1,
|
|
1101
|
+
float32: float32$1,
|
|
1102
|
+
float64: float64$1,
|
|
798
1103
|
int16: int16$1,
|
|
799
|
-
uint16: uint16$1,
|
|
800
1104
|
int32: int32$1,
|
|
801
|
-
uint32: uint32$1,
|
|
802
1105
|
int64: int64$1,
|
|
1106
|
+
int8: int8$1,
|
|
1107
|
+
number: number$1,
|
|
1108
|
+
string: string$1,
|
|
1109
|
+
uint16: uint16$1,
|
|
1110
|
+
uint32: uint32$1,
|
|
803
1111
|
uint64: uint64$1,
|
|
804
|
-
|
|
805
|
-
|
|
1112
|
+
uint8: uint8$1,
|
|
1113
|
+
utf8Length: utf8Length,
|
|
1114
|
+
utf8Write: utf8Write,
|
|
806
1115
|
writeFloat32: writeFloat32,
|
|
807
|
-
writeFloat64: writeFloat64
|
|
808
|
-
boolean: boolean$1,
|
|
809
|
-
string: string$1,
|
|
810
|
-
number: number$1
|
|
1116
|
+
writeFloat64: writeFloat64
|
|
811
1117
|
});
|
|
812
1118
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
let typeofTarget;
|
|
817
|
-
let allowNull = false;
|
|
818
|
-
switch (type) {
|
|
819
|
-
case "number":
|
|
820
|
-
case "int8":
|
|
821
|
-
case "uint8":
|
|
822
|
-
case "int16":
|
|
823
|
-
case "uint16":
|
|
824
|
-
case "int32":
|
|
825
|
-
case "uint32":
|
|
826
|
-
case "int64":
|
|
827
|
-
case "uint64":
|
|
828
|
-
case "float32":
|
|
829
|
-
case "float64":
|
|
830
|
-
typeofTarget = "number";
|
|
831
|
-
if (isNaN(value)) {
|
|
832
|
-
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
833
|
-
}
|
|
834
|
-
break;
|
|
835
|
-
case "string":
|
|
836
|
-
typeofTarget = "string";
|
|
837
|
-
allowNull = true;
|
|
838
|
-
break;
|
|
839
|
-
case "boolean":
|
|
840
|
-
// boolean is always encoded as true/false based on truthiness
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
844
|
-
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
845
|
-
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
function assertInstanceType(value, type, klass, field) {
|
|
849
|
-
if (!(value instanceof type)) {
|
|
850
|
-
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
function encodePrimitiveType(type, bytes, value, klass, field, it) {
|
|
855
|
-
assertType(value, type, klass, field);
|
|
856
|
-
const encodeFunc = encode[type];
|
|
857
|
-
if (encodeFunc) {
|
|
858
|
-
encodeFunc(bytes, value, it);
|
|
859
|
-
// encodeFunc(bytes, value);
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
1119
|
+
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
1120
|
+
if (typeof (type) === "string") {
|
|
1121
|
+
encode[type]?.(bytes, value, it);
|
|
863
1122
|
}
|
|
864
|
-
|
|
865
|
-
function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
|
|
866
|
-
if (type[Symbol.metadata] !== undefined) {
|
|
867
|
-
// TODO: move this to the `@type()` annotation
|
|
868
|
-
assertInstanceType(value, type, ref, field);
|
|
1123
|
+
else if (type[Symbol.metadata] !== undefined) {
|
|
869
1124
|
//
|
|
870
1125
|
// Encode refId for this instance.
|
|
871
1126
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -876,21 +1131,7 @@
|
|
|
876
1131
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
877
1132
|
}
|
|
878
1133
|
}
|
|
879
|
-
else if (typeof (type) === "string") {
|
|
880
|
-
//
|
|
881
|
-
// Primitive values
|
|
882
|
-
//
|
|
883
|
-
encodePrimitiveType(type, bytes, value, ref, field, it);
|
|
884
|
-
}
|
|
885
1134
|
else {
|
|
886
|
-
//
|
|
887
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
888
|
-
//
|
|
889
|
-
const definition = getType(Object.keys(type)[0]);
|
|
890
|
-
//
|
|
891
|
-
// ensure a ArraySchema has been provided
|
|
892
|
-
//
|
|
893
|
-
assertInstanceType(ref[field], definition.constructor, ref, field);
|
|
894
1135
|
//
|
|
895
1136
|
// Encode refId for this instance.
|
|
896
1137
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -902,27 +1143,23 @@
|
|
|
902
1143
|
* Used for Schema instances.
|
|
903
1144
|
* @private
|
|
904
1145
|
*/
|
|
905
|
-
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
906
|
-
const ref = changeTree.ref;
|
|
907
|
-
const metadata = ref['constructor'][Symbol.metadata];
|
|
908
|
-
const field = metadata[index];
|
|
909
|
-
const type = metadata[field].type;
|
|
910
|
-
const value = ref[field];
|
|
1146
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
911
1147
|
// "compress" field index + operation
|
|
912
1148
|
bytes[it.offset++] = (index | operation) & 255;
|
|
913
1149
|
// Do not encode value for DELETE operations
|
|
914
1150
|
if (operation === exports.OPERATION.DELETE) {
|
|
915
1151
|
return;
|
|
916
1152
|
}
|
|
1153
|
+
const ref = changeTree.ref;
|
|
1154
|
+
const field = metadata[index];
|
|
917
1155
|
// TODO: inline this function call small performance gain
|
|
918
|
-
encodeValue(encoder, bytes,
|
|
1156
|
+
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
919
1157
|
};
|
|
920
1158
|
/**
|
|
921
1159
|
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
922
1160
|
* @private
|
|
923
1161
|
*/
|
|
924
|
-
const encodeKeyValueOperation = function (encoder, bytes, changeTree,
|
|
925
|
-
const ref = changeTree.ref;
|
|
1162
|
+
const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
926
1163
|
// encode operation
|
|
927
1164
|
bytes[it.offset++] = operation & 255;
|
|
928
1165
|
// custom operations
|
|
@@ -930,27 +1167,40 @@
|
|
|
930
1167
|
return;
|
|
931
1168
|
}
|
|
932
1169
|
// encode index
|
|
933
|
-
number$1(bytes,
|
|
1170
|
+
number$1(bytes, index, it);
|
|
934
1171
|
// Do not encode value for DELETE operations
|
|
935
1172
|
if (operation === exports.OPERATION.DELETE) {
|
|
936
1173
|
return;
|
|
937
1174
|
}
|
|
1175
|
+
const ref = changeTree.ref;
|
|
938
1176
|
//
|
|
939
1177
|
// encode "alias" for dynamic fields (maps)
|
|
940
1178
|
//
|
|
941
|
-
if ((operation & exports.OPERATION.ADD)
|
|
1179
|
+
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
942
1180
|
if (typeof (ref['set']) === "function") {
|
|
943
1181
|
//
|
|
944
1182
|
// MapSchema dynamic key
|
|
945
1183
|
//
|
|
946
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(
|
|
1184
|
+
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
947
1185
|
string$1(bytes, dynamicIndex, it);
|
|
948
1186
|
}
|
|
949
1187
|
}
|
|
950
|
-
const type =
|
|
951
|
-
const value =
|
|
1188
|
+
const type = ref[$childType];
|
|
1189
|
+
const value = ref[$getByIndex](index);
|
|
1190
|
+
// try { throw new Error(); } catch (e) {
|
|
1191
|
+
// // only print if not coming from Reflection.ts
|
|
1192
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
1193
|
+
// console.log("encodeKeyValueOperation -> ", {
|
|
1194
|
+
// ref: changeTree.ref.constructor.name,
|
|
1195
|
+
// field,
|
|
1196
|
+
// operation: OPERATION[operation],
|
|
1197
|
+
// value: value?.toJSON(),
|
|
1198
|
+
// items: ref.toJSON(),
|
|
1199
|
+
// });
|
|
1200
|
+
// }
|
|
1201
|
+
// }
|
|
952
1202
|
// TODO: inline this function call small performance gain
|
|
953
|
-
encodeValue(encoder, bytes,
|
|
1203
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
954
1204
|
};
|
|
955
1205
|
/**
|
|
956
1206
|
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
@@ -958,24 +1208,29 @@
|
|
|
958
1208
|
*/
|
|
959
1209
|
const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
|
|
960
1210
|
const ref = changeTree.ref;
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
1211
|
+
const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
|
|
1212
|
+
let refOrIndex;
|
|
1213
|
+
if (useOperationByRefId) {
|
|
1214
|
+
refOrIndex = ref['tmpItems'][field][$changes].refId;
|
|
1215
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1216
|
+
operation = exports.OPERATION.DELETE_BY_REFID;
|
|
1217
|
+
}
|
|
1218
|
+
else if (operation === exports.OPERATION.ADD) {
|
|
1219
|
+
operation = exports.OPERATION.ADD_BY_REFID;
|
|
1220
|
+
}
|
|
1221
|
+
}
|
|
1222
|
+
else {
|
|
1223
|
+
refOrIndex = field;
|
|
970
1224
|
}
|
|
971
1225
|
// encode operation
|
|
972
1226
|
bytes[it.offset++] = operation & 255;
|
|
973
1227
|
// custom operations
|
|
974
|
-
if (operation === exports.OPERATION.CLEAR
|
|
1228
|
+
if (operation === exports.OPERATION.CLEAR ||
|
|
1229
|
+
operation === exports.OPERATION.REVERSE) {
|
|
975
1230
|
return;
|
|
976
1231
|
}
|
|
977
1232
|
// encode index
|
|
978
|
-
number$1(bytes,
|
|
1233
|
+
number$1(bytes, refOrIndex, it);
|
|
979
1234
|
// Do not encode value for DELETE operations
|
|
980
1235
|
if (operation === exports.OPERATION.DELETE) {
|
|
981
1236
|
return;
|
|
@@ -990,7 +1245,7 @@
|
|
|
990
1245
|
// items: ref.toJSON(),
|
|
991
1246
|
// });
|
|
992
1247
|
// TODO: inline this function call small performance gain
|
|
993
|
-
encodeValue(encoder, bytes,
|
|
1248
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
994
1249
|
};
|
|
995
1250
|
|
|
996
1251
|
/**
|
|
@@ -1224,31 +1479,31 @@
|
|
|
1224
1479
|
|
|
1225
1480
|
var decode = /*#__PURE__*/Object.freeze({
|
|
1226
1481
|
__proto__: null,
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
uint8: uint8,
|
|
1230
|
-
int16: int16,
|
|
1231
|
-
uint16: uint16,
|
|
1232
|
-
int32: int32,
|
|
1233
|
-
uint32: uint32,
|
|
1482
|
+
arrayCheck: arrayCheck,
|
|
1483
|
+
boolean: boolean,
|
|
1234
1484
|
float32: float32,
|
|
1235
1485
|
float64: float64,
|
|
1486
|
+
int16: int16,
|
|
1487
|
+
int32: int32,
|
|
1236
1488
|
int64: int64,
|
|
1237
|
-
|
|
1489
|
+
int8: int8,
|
|
1490
|
+
number: number,
|
|
1491
|
+
numberCheck: numberCheck,
|
|
1238
1492
|
readFloat32: readFloat32,
|
|
1239
1493
|
readFloat64: readFloat64,
|
|
1240
|
-
boolean: boolean,
|
|
1241
1494
|
string: string,
|
|
1242
1495
|
stringCheck: stringCheck,
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1496
|
+
switchStructureCheck: switchStructureCheck,
|
|
1497
|
+
uint16: uint16,
|
|
1498
|
+
uint32: uint32,
|
|
1499
|
+
uint64: uint64,
|
|
1500
|
+
uint8: uint8,
|
|
1501
|
+
utf8Read: utf8Read
|
|
1247
1502
|
});
|
|
1248
1503
|
|
|
1249
1504
|
const DEFINITION_MISMATCH = -1;
|
|
1250
1505
|
function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
|
|
1251
|
-
const $root = decoder
|
|
1506
|
+
const $root = decoder.root;
|
|
1252
1507
|
const previousValue = ref[$getByIndex](index);
|
|
1253
1508
|
let value;
|
|
1254
1509
|
if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
@@ -1296,7 +1551,9 @@
|
|
|
1296
1551
|
if (!value) {
|
|
1297
1552
|
value = decoder.createInstanceOfType(childType);
|
|
1298
1553
|
}
|
|
1299
|
-
$root.addRef(refId, value, (value !== previousValue
|
|
1554
|
+
$root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed
|
|
1555
|
+
(operation === exports.OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
|
|
1556
|
+
));
|
|
1300
1557
|
}
|
|
1301
1558
|
}
|
|
1302
1559
|
else if (typeof (type) === "string") {
|
|
@@ -1347,18 +1604,19 @@
|
|
|
1347
1604
|
}
|
|
1348
1605
|
const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
1349
1606
|
const first_byte = bytes[it.offset++];
|
|
1350
|
-
const metadata = ref
|
|
1607
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1351
1608
|
// "compressed" index + operation
|
|
1352
1609
|
const operation = (first_byte >> 6) << 6;
|
|
1353
1610
|
const index = first_byte % (operation || 255);
|
|
1354
1611
|
// skip early if field is not defined
|
|
1355
1612
|
const field = metadata[index];
|
|
1356
1613
|
if (field === undefined) {
|
|
1614
|
+
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
1357
1615
|
return DEFINITION_MISMATCH;
|
|
1358
1616
|
}
|
|
1359
|
-
const { value, previousValue } = decodeValue(decoder, operation, ref, index,
|
|
1617
|
+
const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
|
|
1360
1618
|
if (value !== null && value !== undefined) {
|
|
1361
|
-
ref[field] = value;
|
|
1619
|
+
ref[field.name] = value;
|
|
1362
1620
|
}
|
|
1363
1621
|
// add change
|
|
1364
1622
|
if (previousValue !== value) {
|
|
@@ -1366,7 +1624,7 @@
|
|
|
1366
1624
|
ref,
|
|
1367
1625
|
refId: decoder.currentRefId,
|
|
1368
1626
|
op: operation,
|
|
1369
|
-
field: field,
|
|
1627
|
+
field: field.name,
|
|
1370
1628
|
value,
|
|
1371
1629
|
previousValue,
|
|
1372
1630
|
});
|
|
@@ -1434,7 +1692,8 @@
|
|
|
1434
1692
|
};
|
|
1435
1693
|
const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
1436
1694
|
// "uncompressed" index + operation (array/map items)
|
|
1437
|
-
|
|
1695
|
+
let operation = bytes[it.offset++];
|
|
1696
|
+
let index;
|
|
1438
1697
|
if (operation === exports.OPERATION.CLEAR) {
|
|
1439
1698
|
//
|
|
1440
1699
|
// When decoding:
|
|
@@ -1445,11 +1704,15 @@
|
|
|
1445
1704
|
ref.clear();
|
|
1446
1705
|
return;
|
|
1447
1706
|
}
|
|
1707
|
+
else if (operation === exports.OPERATION.REVERSE) {
|
|
1708
|
+
ref.reverse();
|
|
1709
|
+
return;
|
|
1710
|
+
}
|
|
1448
1711
|
else if (operation === exports.OPERATION.DELETE_BY_REFID) {
|
|
1449
1712
|
// TODO: refactor here, try to follow same flow as below
|
|
1450
1713
|
const refId = number(bytes, it);
|
|
1451
|
-
const previousValue = decoder
|
|
1452
|
-
|
|
1714
|
+
const previousValue = decoder.root.refs.get(refId);
|
|
1715
|
+
index = ref.findIndex((value) => value === previousValue);
|
|
1453
1716
|
ref[$deleteByIndex](index);
|
|
1454
1717
|
allChanges.push({
|
|
1455
1718
|
ref,
|
|
@@ -1462,7 +1725,17 @@
|
|
|
1462
1725
|
});
|
|
1463
1726
|
return;
|
|
1464
1727
|
}
|
|
1465
|
-
|
|
1728
|
+
else if (operation === exports.OPERATION.ADD_BY_REFID) {
|
|
1729
|
+
const refId = number(bytes, it);
|
|
1730
|
+
const itemByRefId = decoder.root.refs.get(refId);
|
|
1731
|
+
// use existing index, or push new value
|
|
1732
|
+
index = (itemByRefId)
|
|
1733
|
+
? ref.findIndex((value) => value === itemByRefId)
|
|
1734
|
+
: ref.length;
|
|
1735
|
+
}
|
|
1736
|
+
else {
|
|
1737
|
+
index = number(bytes, it);
|
|
1738
|
+
}
|
|
1466
1739
|
const type = ref[$childType];
|
|
1467
1740
|
let dynamicIndex = index;
|
|
1468
1741
|
const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
|
|
@@ -1486,6 +1759,47 @@
|
|
|
1486
1759
|
}
|
|
1487
1760
|
};
|
|
1488
1761
|
|
|
1762
|
+
class EncodeSchemaError extends Error {
|
|
1763
|
+
}
|
|
1764
|
+
function assertType(value, type, klass, field) {
|
|
1765
|
+
let typeofTarget;
|
|
1766
|
+
let allowNull = false;
|
|
1767
|
+
switch (type) {
|
|
1768
|
+
case "number":
|
|
1769
|
+
case "int8":
|
|
1770
|
+
case "uint8":
|
|
1771
|
+
case "int16":
|
|
1772
|
+
case "uint16":
|
|
1773
|
+
case "int32":
|
|
1774
|
+
case "uint32":
|
|
1775
|
+
case "int64":
|
|
1776
|
+
case "uint64":
|
|
1777
|
+
case "float32":
|
|
1778
|
+
case "float64":
|
|
1779
|
+
typeofTarget = "number";
|
|
1780
|
+
if (isNaN(value)) {
|
|
1781
|
+
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
1782
|
+
}
|
|
1783
|
+
break;
|
|
1784
|
+
case "string":
|
|
1785
|
+
typeofTarget = "string";
|
|
1786
|
+
allowNull = true;
|
|
1787
|
+
break;
|
|
1788
|
+
case "boolean":
|
|
1789
|
+
// boolean is always encoded as true/false based on truthiness
|
|
1790
|
+
return;
|
|
1791
|
+
}
|
|
1792
|
+
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
1793
|
+
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
1794
|
+
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
1795
|
+
}
|
|
1796
|
+
}
|
|
1797
|
+
function assertInstanceType(value, type, instance, field) {
|
|
1798
|
+
if (!(value instanceof type)) {
|
|
1799
|
+
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
|
|
1800
|
+
}
|
|
1801
|
+
}
|
|
1802
|
+
|
|
1489
1803
|
var _a$4, _b$4;
|
|
1490
1804
|
const DEFAULT_SORT = (a, b) => {
|
|
1491
1805
|
const A = a.toString();
|
|
@@ -1536,6 +1850,7 @@
|
|
|
1536
1850
|
const proxy = new Proxy(this, {
|
|
1537
1851
|
get: (obj, prop) => {
|
|
1538
1852
|
if (typeof (prop) !== "symbol" &&
|
|
1853
|
+
// FIXME: d8 accuses this as low performance
|
|
1539
1854
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1540
1855
|
) {
|
|
1541
1856
|
return this.items[prop];
|
|
@@ -1551,8 +1866,9 @@
|
|
|
1551
1866
|
}
|
|
1552
1867
|
else {
|
|
1553
1868
|
if (setValue[$changes]) {
|
|
1869
|
+
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1554
1870
|
if (obj.items[key] !== undefined) {
|
|
1555
|
-
if (setValue[$changes]
|
|
1871
|
+
if (setValue[$changes].isNew) {
|
|
1556
1872
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
1557
1873
|
}
|
|
1558
1874
|
else {
|
|
@@ -1564,7 +1880,7 @@
|
|
|
1564
1880
|
}
|
|
1565
1881
|
}
|
|
1566
1882
|
}
|
|
1567
|
-
else if (setValue[$changes]
|
|
1883
|
+
else if (setValue[$changes].isNew) {
|
|
1568
1884
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
|
|
1569
1885
|
}
|
|
1570
1886
|
}
|
|
@@ -1597,7 +1913,10 @@
|
|
|
1597
1913
|
}
|
|
1598
1914
|
});
|
|
1599
1915
|
this[$changes] = new ChangeTree(proxy);
|
|
1600
|
-
this.
|
|
1916
|
+
this[$changes].indexes = {};
|
|
1917
|
+
if (items.length > 0) {
|
|
1918
|
+
this.push(...items);
|
|
1919
|
+
}
|
|
1601
1920
|
return proxy;
|
|
1602
1921
|
}
|
|
1603
1922
|
set length(newLength) {
|
|
@@ -1616,14 +1935,19 @@
|
|
|
1616
1935
|
}
|
|
1617
1936
|
push(...values) {
|
|
1618
1937
|
let length = this.tmpItems.length;
|
|
1619
|
-
|
|
1620
|
-
|
|
1938
|
+
const changeTree = this[$changes];
|
|
1939
|
+
// values.forEach((value, i) => {
|
|
1940
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1941
|
+
const value = values[i];
|
|
1621
1942
|
if (value === undefined || value === null) {
|
|
1943
|
+
// skip null values
|
|
1622
1944
|
return;
|
|
1623
1945
|
}
|
|
1624
|
-
|
|
1946
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
1947
|
+
assertInstanceType(value, this[$childType], this, i);
|
|
1948
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1949
|
+
}
|
|
1625
1950
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1626
|
-
// changeTree.indexes[length] = length;
|
|
1627
1951
|
this.items.push(value);
|
|
1628
1952
|
this.tmpItems.push(value);
|
|
1629
1953
|
//
|
|
@@ -1631,8 +1955,9 @@
|
|
|
1631
1955
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1632
1956
|
//
|
|
1633
1957
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1634
|
-
|
|
1635
|
-
|
|
1958
|
+
}
|
|
1959
|
+
// length++;
|
|
1960
|
+
// });
|
|
1636
1961
|
return length;
|
|
1637
1962
|
}
|
|
1638
1963
|
/**
|
|
@@ -1653,6 +1978,7 @@
|
|
|
1653
1978
|
}
|
|
1654
1979
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1655
1980
|
// this.tmpItems[index] = undefined;
|
|
1981
|
+
// this.tmpItems.pop();
|
|
1656
1982
|
this.deletedIndexes[index] = true;
|
|
1657
1983
|
return this.items.pop();
|
|
1658
1984
|
}
|
|
@@ -1717,9 +2043,12 @@
|
|
|
1717
2043
|
//
|
|
1718
2044
|
// TODO: do not use [$changes] at decoding time.
|
|
1719
2045
|
//
|
|
1720
|
-
changeTree.root
|
|
1721
|
-
|
|
1722
|
-
|
|
2046
|
+
const root = changeTree.root;
|
|
2047
|
+
if (root !== undefined) {
|
|
2048
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
2049
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
2050
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
2051
|
+
}
|
|
1723
2052
|
});
|
|
1724
2053
|
changeTree.discard(true);
|
|
1725
2054
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -1763,6 +2092,7 @@
|
|
|
1763
2092
|
const changeTree = this[$changes];
|
|
1764
2093
|
changeTree.delete(index);
|
|
1765
2094
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
2095
|
+
// this.deletedIndexes[index] = true;
|
|
1766
2096
|
return this.items.shift();
|
|
1767
2097
|
}
|
|
1768
2098
|
/**
|
|
@@ -1843,10 +2173,12 @@
|
|
|
1843
2173
|
changeTree.shiftChangeIndexes(items.length);
|
|
1844
2174
|
// new index
|
|
1845
2175
|
if (changeTree.isFiltered) {
|
|
1846
|
-
changeTree.filteredChanges
|
|
2176
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
2177
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1847
2178
|
}
|
|
1848
2179
|
else {
|
|
1849
|
-
changeTree.allChanges
|
|
2180
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2181
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1850
2182
|
}
|
|
1851
2183
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1852
2184
|
items.forEach((_, index) => {
|
|
@@ -1871,14 +2203,6 @@
|
|
|
1871
2203
|
lastIndexOf(searchElement, fromIndex = this.length - 1) {
|
|
1872
2204
|
return this.items.lastIndexOf(searchElement, fromIndex);
|
|
1873
2205
|
}
|
|
1874
|
-
/**
|
|
1875
|
-
* Determines whether all the members of an array satisfy the specified test.
|
|
1876
|
-
* @param callbackfn A function that accepts up to three arguments. The every method calls
|
|
1877
|
-
* the callbackfn function for each element in the array until the callbackfn returns a value
|
|
1878
|
-
* which is coercible to the Boolean value false, or until the end of the array.
|
|
1879
|
-
* @param thisArg An object to which the this keyword can refer in the callbackfn function.
|
|
1880
|
-
* If thisArg is omitted, undefined is used as the this value.
|
|
1881
|
-
*/
|
|
1882
2206
|
every(callbackfn, thisArg) {
|
|
1883
2207
|
return this.items.every(callbackfn, thisArg);
|
|
1884
2208
|
}
|
|
@@ -2157,6 +2481,7 @@
|
|
|
2157
2481
|
this.$items = new Map();
|
|
2158
2482
|
this.$indexes = new Map();
|
|
2159
2483
|
this[$changes] = new ChangeTree(this);
|
|
2484
|
+
this[$changes].indexes = {};
|
|
2160
2485
|
if (initialValues) {
|
|
2161
2486
|
if (initialValues instanceof Map ||
|
|
2162
2487
|
initialValues instanceof MapSchema) {
|
|
@@ -2183,6 +2508,9 @@
|
|
|
2183
2508
|
if (value === undefined || value === null) {
|
|
2184
2509
|
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
|
|
2185
2510
|
}
|
|
2511
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
2512
|
+
assertInstanceType(value, this[$childType], this, key);
|
|
2513
|
+
}
|
|
2186
2514
|
// Force "key" as string
|
|
2187
2515
|
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
|
|
2188
2516
|
key = key.toString();
|
|
@@ -2191,7 +2519,7 @@
|
|
|
2191
2519
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2192
2520
|
const index = (isReplace)
|
|
2193
2521
|
? changeTree.indexes[key]
|
|
2194
|
-
: changeTree.indexes[
|
|
2522
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2195
2523
|
let operation = (isReplace)
|
|
2196
2524
|
? exports.OPERATION.REPLACE
|
|
2197
2525
|
: exports.OPERATION.ADD;
|
|
@@ -2203,7 +2531,7 @@
|
|
|
2203
2531
|
if (!isReplace) {
|
|
2204
2532
|
this.$indexes.set(index, key);
|
|
2205
2533
|
changeTree.indexes[key] = index;
|
|
2206
|
-
changeTree.indexes[
|
|
2534
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2207
2535
|
}
|
|
2208
2536
|
else if (!isRef &&
|
|
2209
2537
|
this.$items.get(key) === value) {
|
|
@@ -2278,8 +2606,11 @@
|
|
|
2278
2606
|
}
|
|
2279
2607
|
[$onEncodeEnd]() {
|
|
2280
2608
|
const changeTree = this[$changes];
|
|
2281
|
-
const
|
|
2282
|
-
for (
|
|
2609
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2610
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2611
|
+
const key = keys[i];
|
|
2612
|
+
const fieldIndex = Number(key);
|
|
2613
|
+
const operation = changeTree.indexedOperations[key];
|
|
2283
2614
|
if (operation === exports.OPERATION.DELETE) {
|
|
2284
2615
|
const index = this[$getByIndex](fieldIndex);
|
|
2285
2616
|
delete changeTree.indexes[index];
|
|
@@ -2305,111 +2636,24 @@
|
|
|
2305
2636
|
// client-side
|
|
2306
2637
|
cloned = Object.assign(new MapSchema(), this);
|
|
2307
2638
|
}
|
|
2308
|
-
else {
|
|
2309
|
-
// server-side
|
|
2310
|
-
cloned = new MapSchema();
|
|
2311
|
-
this.forEach((value, key) => {
|
|
2312
|
-
if (value[$changes]) {
|
|
2313
|
-
cloned.set(key, value['clone']());
|
|
2314
|
-
}
|
|
2315
|
-
else {
|
|
2316
|
-
cloned.set(key, value);
|
|
2317
|
-
}
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
2320
|
-
return cloned;
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
registerType("map", { constructor: MapSchema });
|
|
2324
|
-
|
|
2325
|
-
const DEFAULT_VIEW_TAG = -1;
|
|
2326
|
-
class TypeContext {
|
|
2327
|
-
/**
|
|
2328
|
-
* For inheritance support
|
|
2329
|
-
* Keeps track of which classes extends which. (parent -> children)
|
|
2330
|
-
*/
|
|
2331
|
-
static { this.inheritedTypes = new Map(); }
|
|
2332
|
-
static register(target) {
|
|
2333
|
-
const parent = Object.getPrototypeOf(target);
|
|
2334
|
-
if (parent !== Schema) {
|
|
2335
|
-
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
2336
|
-
if (!inherits) {
|
|
2337
|
-
inherits = new Set();
|
|
2338
|
-
TypeContext.inheritedTypes.set(parent, inherits);
|
|
2339
|
-
}
|
|
2340
|
-
inherits.add(target);
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
constructor(rootClass) {
|
|
2344
|
-
this.types = {};
|
|
2345
|
-
this.schemas = new Map();
|
|
2346
|
-
this.hasFilters = false;
|
|
2347
|
-
if (rootClass) {
|
|
2348
|
-
this.discoverTypes(rootClass);
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
has(schema) {
|
|
2352
|
-
return this.schemas.has(schema);
|
|
2353
|
-
}
|
|
2354
|
-
get(typeid) {
|
|
2355
|
-
return this.types[typeid];
|
|
2356
|
-
}
|
|
2357
|
-
add(schema, typeid = this.schemas.size) {
|
|
2358
|
-
// skip if already registered
|
|
2359
|
-
if (this.schemas.has(schema)) {
|
|
2360
|
-
return false;
|
|
2361
|
-
}
|
|
2362
|
-
this.types[typeid] = schema;
|
|
2363
|
-
this.schemas.set(schema, typeid);
|
|
2364
|
-
return true;
|
|
2365
|
-
}
|
|
2366
|
-
getTypeId(klass) {
|
|
2367
|
-
return this.schemas.get(klass);
|
|
2368
|
-
}
|
|
2369
|
-
discoverTypes(klass) {
|
|
2370
|
-
if (!this.add(klass)) {
|
|
2371
|
-
return;
|
|
2372
|
-
}
|
|
2373
|
-
// add classes inherited from this base class
|
|
2374
|
-
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
2375
|
-
this.discoverTypes(child);
|
|
2376
|
-
});
|
|
2377
|
-
// skip if no fields are defined for this class.
|
|
2378
|
-
if (klass[Symbol.metadata] === undefined) {
|
|
2379
|
-
klass[Symbol.metadata] = {};
|
|
2380
|
-
}
|
|
2381
|
-
// const metadata = Metadata.getFor(klass);
|
|
2382
|
-
const metadata = klass[Symbol.metadata];
|
|
2383
|
-
// if any schema/field has filters, mark "context" as having filters.
|
|
2384
|
-
if (metadata[-2]) {
|
|
2385
|
-
this.hasFilters = true;
|
|
2386
|
-
}
|
|
2387
|
-
for (const field in metadata) {
|
|
2388
|
-
const fieldType = metadata[field].type;
|
|
2389
|
-
if (typeof (fieldType) === "string") {
|
|
2390
|
-
continue;
|
|
2391
|
-
}
|
|
2392
|
-
if (Array.isArray(fieldType)) {
|
|
2393
|
-
const type = fieldType[0];
|
|
2394
|
-
if (type === "string") {
|
|
2395
|
-
continue;
|
|
2396
|
-
}
|
|
2397
|
-
this.discoverTypes(type);
|
|
2398
|
-
}
|
|
2399
|
-
else if (typeof (fieldType) === "function") {
|
|
2400
|
-
this.discoverTypes(fieldType);
|
|
2401
|
-
}
|
|
2402
|
-
else {
|
|
2403
|
-
const type = Object.values(fieldType)[0];
|
|
2404
|
-
// skip primitive types
|
|
2405
|
-
if (typeof (type) === "string") {
|
|
2406
|
-
continue;
|
|
2639
|
+
else {
|
|
2640
|
+
// server-side
|
|
2641
|
+
cloned = new MapSchema();
|
|
2642
|
+
this.forEach((value, key) => {
|
|
2643
|
+
if (value[$changes]) {
|
|
2644
|
+
cloned.set(key, value['clone']());
|
|
2407
2645
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2646
|
+
else {
|
|
2647
|
+
cloned.set(key, value);
|
|
2648
|
+
}
|
|
2649
|
+
});
|
|
2410
2650
|
}
|
|
2651
|
+
return cloned;
|
|
2411
2652
|
}
|
|
2412
2653
|
}
|
|
2654
|
+
registerType("map", { constructor: MapSchema });
|
|
2655
|
+
|
|
2656
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
2413
2657
|
/**
|
|
2414
2658
|
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
2415
2659
|
*
|
|
@@ -2436,8 +2680,8 @@
|
|
|
2436
2680
|
// // detect index for this field, considering inheritance
|
|
2437
2681
|
// //
|
|
2438
2682
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2439
|
-
// let fieldIndex: number = context.metadata[
|
|
2440
|
-
// ?? (parent && parent[
|
|
2683
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2684
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2441
2685
|
// ?? -1; // no fields defined
|
|
2442
2686
|
// fieldIndex++;
|
|
2443
2687
|
// if (
|
|
@@ -2557,18 +2801,20 @@
|
|
|
2557
2801
|
const constructor = target.constructor;
|
|
2558
2802
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2559
2803
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2804
|
+
// TODO: use Metadata.initialize()
|
|
2560
2805
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
}
|
|
2806
|
+
// const fieldIndex = metadata[fieldName];
|
|
2807
|
+
// if (!metadata[fieldIndex]) {
|
|
2808
|
+
// //
|
|
2809
|
+
// // detect index for this field, considering inheritance
|
|
2810
|
+
// //
|
|
2811
|
+
// metadata[fieldIndex] = {
|
|
2812
|
+
// type: undefined,
|
|
2813
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2814
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2815
|
+
// ?? -1) + 1 // no fields defined
|
|
2816
|
+
// }
|
|
2817
|
+
// }
|
|
2572
2818
|
Metadata.setTag(metadata, fieldName, tag);
|
|
2573
2819
|
};
|
|
2574
2820
|
}
|
|
@@ -2582,17 +2828,17 @@
|
|
|
2582
2828
|
TypeContext.register(constructor);
|
|
2583
2829
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2584
2830
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2585
|
-
const metadata =
|
|
2586
|
-
let fieldIndex;
|
|
2831
|
+
const metadata = Metadata.initialize(constructor);
|
|
2832
|
+
let fieldIndex = metadata[field];
|
|
2587
2833
|
/**
|
|
2588
2834
|
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
2589
2835
|
*/
|
|
2590
|
-
if (metadata[
|
|
2591
|
-
if (metadata[
|
|
2836
|
+
if (metadata[fieldIndex] !== undefined) {
|
|
2837
|
+
if (metadata[fieldIndex].deprecated) {
|
|
2592
2838
|
// do not create accessors for deprecated properties.
|
|
2593
2839
|
return;
|
|
2594
2840
|
}
|
|
2595
|
-
else if (metadata[
|
|
2841
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
2596
2842
|
// trying to define same property multiple times across inheritance.
|
|
2597
2843
|
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
2598
2844
|
try {
|
|
@@ -2603,16 +2849,13 @@
|
|
|
2603
2849
|
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
2604
2850
|
}
|
|
2605
2851
|
}
|
|
2606
|
-
else {
|
|
2607
|
-
fieldIndex = metadata[field].index;
|
|
2608
|
-
}
|
|
2609
2852
|
}
|
|
2610
2853
|
else {
|
|
2611
2854
|
//
|
|
2612
2855
|
// detect index for this field, considering inheritance
|
|
2613
2856
|
//
|
|
2614
|
-
fieldIndex = metadata[
|
|
2615
|
-
?? (parentMetadata && parentMetadata[
|
|
2857
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2858
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2616
2859
|
?? -1; // no fields defined
|
|
2617
2860
|
fieldIndex++;
|
|
2618
2861
|
}
|
|
@@ -2631,15 +2874,15 @@
|
|
|
2631
2874
|
const childType = (complexTypeKlass)
|
|
2632
2875
|
? Object.values(type)[0]
|
|
2633
2876
|
: type;
|
|
2634
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass
|
|
2877
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
2635
2878
|
}
|
|
2636
2879
|
};
|
|
2637
2880
|
}
|
|
2638
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass
|
|
2881
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
2639
2882
|
return {
|
|
2640
2883
|
get: function () { return this[fieldCached]; },
|
|
2641
2884
|
set: function (value) {
|
|
2642
|
-
const previousValue = this[fieldCached]
|
|
2885
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2643
2886
|
// skip if value is the same as cached.
|
|
2644
2887
|
if (value === previousValue) {
|
|
2645
2888
|
return;
|
|
@@ -2657,22 +2900,27 @@
|
|
|
2657
2900
|
}
|
|
2658
2901
|
value[$childType] = type;
|
|
2659
2902
|
}
|
|
2903
|
+
else if (typeof (type) !== "string") {
|
|
2904
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
2905
|
+
}
|
|
2906
|
+
else {
|
|
2907
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
2908
|
+
}
|
|
2909
|
+
const changeTree = this[$changes];
|
|
2660
2910
|
//
|
|
2661
2911
|
// Replacing existing "ref", remove it from root.
|
|
2662
2912
|
// TODO: if there are other references to this instance, we should not remove it from root.
|
|
2663
2913
|
//
|
|
2664
2914
|
if (previousValue !== undefined && previousValue[$changes]) {
|
|
2665
|
-
|
|
2915
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
2666
2916
|
}
|
|
2667
2917
|
// flag the change for encoding.
|
|
2668
|
-
this.constructor[$track](
|
|
2918
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
2669
2919
|
//
|
|
2670
2920
|
// call setParent() recursively for this and its child
|
|
2671
2921
|
// structures.
|
|
2672
2922
|
//
|
|
2673
|
-
|
|
2674
|
-
value[$changes].setParent(this, this[$changes].root, metadata[field].index);
|
|
2675
|
-
}
|
|
2923
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
2676
2924
|
}
|
|
2677
2925
|
else if (previousValue !== undefined) {
|
|
2678
2926
|
//
|
|
@@ -2699,20 +2947,22 @@
|
|
|
2699
2947
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2700
2948
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2701
2949
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2950
|
+
const fieldIndex = metadata[field];
|
|
2951
|
+
// if (!metadata[field]) {
|
|
2952
|
+
// //
|
|
2953
|
+
// // detect index for this field, considering inheritance
|
|
2954
|
+
// //
|
|
2955
|
+
// metadata[field] = {
|
|
2956
|
+
// type: undefined,
|
|
2957
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2958
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2959
|
+
// ?? -1) + 1 // no fields defined
|
|
2960
|
+
// }
|
|
2961
|
+
// }
|
|
2962
|
+
metadata[fieldIndex].deprecated = true;
|
|
2714
2963
|
if (throws) {
|
|
2715
|
-
metadata[
|
|
2964
|
+
metadata[$descriptors] ??= {};
|
|
2965
|
+
metadata[$descriptors][field] = {
|
|
2716
2966
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
2717
2967
|
set: function (value) { },
|
|
2718
2968
|
enumerable: false,
|
|
@@ -2720,8 +2970,8 @@
|
|
|
2720
2970
|
};
|
|
2721
2971
|
}
|
|
2722
2972
|
// flag metadata[field] as non-enumerable
|
|
2723
|
-
Object.defineProperty(metadata,
|
|
2724
|
-
value: metadata[
|
|
2973
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
2974
|
+
value: metadata[fieldIndex],
|
|
2725
2975
|
enumerable: false,
|
|
2726
2976
|
configurable: true
|
|
2727
2977
|
});
|
|
@@ -2733,6 +2983,37 @@
|
|
|
2733
2983
|
}
|
|
2734
2984
|
return target;
|
|
2735
2985
|
}
|
|
2986
|
+
function schema(fields, name, inherits = Schema) {
|
|
2987
|
+
const defaultValues = {};
|
|
2988
|
+
const viewTagFields = {};
|
|
2989
|
+
for (let fieldName in fields) {
|
|
2990
|
+
const field = fields[fieldName];
|
|
2991
|
+
if (typeof (field) === "object") {
|
|
2992
|
+
if (field['default'] !== undefined) {
|
|
2993
|
+
defaultValues[fieldName] = field['default'];
|
|
2994
|
+
}
|
|
2995
|
+
if (field['view'] !== undefined) {
|
|
2996
|
+
viewTagFields[fieldName] = (typeof (field['view']) === "boolean")
|
|
2997
|
+
? DEFAULT_VIEW_TAG
|
|
2998
|
+
: field['view'];
|
|
2999
|
+
}
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
const klass = Metadata.setFields(class extends inherits {
|
|
3003
|
+
constructor(...args) {
|
|
3004
|
+
args[0] = Object.assign({}, defaultValues, args[0]);
|
|
3005
|
+
super(...args);
|
|
3006
|
+
}
|
|
3007
|
+
}, fields);
|
|
3008
|
+
for (let fieldName in viewTagFields) {
|
|
3009
|
+
view(viewTagFields[fieldName])(klass.prototype, fieldName);
|
|
3010
|
+
}
|
|
3011
|
+
if (name) {
|
|
3012
|
+
Object.defineProperty(klass, "name", { value: name });
|
|
3013
|
+
}
|
|
3014
|
+
klass.extends = (fields, name) => schema(fields, name, klass);
|
|
3015
|
+
return klass;
|
|
3016
|
+
}
|
|
2736
3017
|
|
|
2737
3018
|
function getIndent(level) {
|
|
2738
3019
|
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
@@ -2743,15 +3024,18 @@
|
|
|
2743
3024
|
ops: {},
|
|
2744
3025
|
refs: []
|
|
2745
3026
|
};
|
|
2746
|
-
$root.changes
|
|
3027
|
+
// for (const refId in $root.changes) {
|
|
3028
|
+
$root.changes.forEach(changeTree => {
|
|
3029
|
+
const changes = changeTree.indexedOperations;
|
|
2747
3030
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2748
|
-
|
|
3031
|
+
for (const index in changes) {
|
|
3032
|
+
const op = changes[index];
|
|
2749
3033
|
const opName = exports.OPERATION[op];
|
|
2750
3034
|
if (!dump.ops[opName]) {
|
|
2751
3035
|
dump.ops[opName] = 0;
|
|
2752
3036
|
}
|
|
2753
3037
|
dump.ops[exports.OPERATION[op]]++;
|
|
2754
|
-
}
|
|
3038
|
+
}
|
|
2755
3039
|
});
|
|
2756
3040
|
return dump;
|
|
2757
3041
|
}
|
|
@@ -2777,6 +3061,7 @@
|
|
|
2777
3061
|
class Schema {
|
|
2778
3062
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2779
3063
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
3064
|
+
// public [$changes]: ChangeTree;
|
|
2780
3065
|
/**
|
|
2781
3066
|
* Assign the property descriptors required to track changes on this instance.
|
|
2782
3067
|
* @param instance
|
|
@@ -2787,35 +3072,7 @@
|
|
|
2787
3072
|
enumerable: false,
|
|
2788
3073
|
writable: true
|
|
2789
3074
|
});
|
|
2790
|
-
|
|
2791
|
-
// Define property descriptors
|
|
2792
|
-
for (const field in metadata) {
|
|
2793
|
-
if (metadata[field].descriptor) {
|
|
2794
|
-
// for encoder
|
|
2795
|
-
Object.defineProperty(instance, `_${field}`, {
|
|
2796
|
-
value: undefined,
|
|
2797
|
-
writable: true,
|
|
2798
|
-
enumerable: false,
|
|
2799
|
-
configurable: true,
|
|
2800
|
-
});
|
|
2801
|
-
Object.defineProperty(instance, field, metadata[field].descriptor);
|
|
2802
|
-
}
|
|
2803
|
-
else {
|
|
2804
|
-
// for decoder
|
|
2805
|
-
Object.defineProperty(instance, field, {
|
|
2806
|
-
value: undefined,
|
|
2807
|
-
writable: true,
|
|
2808
|
-
enumerable: true,
|
|
2809
|
-
configurable: true,
|
|
2810
|
-
});
|
|
2811
|
-
}
|
|
2812
|
-
// Object.defineProperty(instance, field, {
|
|
2813
|
-
// ...instance.constructor[Symbol.metadata][field].descriptor
|
|
2814
|
-
// });
|
|
2815
|
-
// if (args[0]?.hasOwnProperty(field)) {
|
|
2816
|
-
// instance[field] = args[0][field];
|
|
2817
|
-
// }
|
|
2818
|
-
}
|
|
3075
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2819
3076
|
}
|
|
2820
3077
|
static is(type) {
|
|
2821
3078
|
return typeof (type[Symbol.metadata]) === "object";
|
|
@@ -2839,7 +3096,7 @@
|
|
|
2839
3096
|
*/
|
|
2840
3097
|
static [$filter](ref, index, view) {
|
|
2841
3098
|
const metadata = ref.constructor[Symbol.metadata];
|
|
2842
|
-
const tag = metadata[
|
|
3099
|
+
const tag = metadata[index]?.tag;
|
|
2843
3100
|
if (view === undefined) {
|
|
2844
3101
|
// shared pass/encode: encode if doesn't have a tag
|
|
2845
3102
|
return tag === undefined;
|
|
@@ -2860,12 +3117,16 @@
|
|
|
2860
3117
|
}
|
|
2861
3118
|
// allow inherited classes to have a constructor
|
|
2862
3119
|
constructor(...args) {
|
|
3120
|
+
//
|
|
3121
|
+
// inline
|
|
3122
|
+
// Schema.initialize(this);
|
|
3123
|
+
//
|
|
2863
3124
|
Schema.initialize(this);
|
|
2864
3125
|
//
|
|
2865
3126
|
// Assign initial values
|
|
2866
3127
|
//
|
|
2867
3128
|
if (args[0]) {
|
|
2868
|
-
|
|
3129
|
+
Object.assign(this, args[0]);
|
|
2869
3130
|
}
|
|
2870
3131
|
}
|
|
2871
3132
|
assign(props) {
|
|
@@ -2879,7 +3140,8 @@
|
|
|
2879
3140
|
* @param operation OPERATION to perform (detected automatically)
|
|
2880
3141
|
*/
|
|
2881
3142
|
setDirty(property, operation) {
|
|
2882
|
-
this
|
|
3143
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3144
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
2883
3145
|
}
|
|
2884
3146
|
clone() {
|
|
2885
3147
|
const cloned = new (this.constructor);
|
|
@@ -2888,7 +3150,9 @@
|
|
|
2888
3150
|
// TODO: clone all properties, not only annotated ones
|
|
2889
3151
|
//
|
|
2890
3152
|
// for (const field in this) {
|
|
2891
|
-
for (const
|
|
3153
|
+
for (const fieldIndex in metadata) {
|
|
3154
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
3155
|
+
const field = metadata[fieldIndex].name;
|
|
2892
3156
|
if (typeof (this[field]) === "object" &&
|
|
2893
3157
|
typeof (this[field]?.clone) === "function") {
|
|
2894
3158
|
// deep clone
|
|
@@ -2902,10 +3166,11 @@
|
|
|
2902
3166
|
return cloned;
|
|
2903
3167
|
}
|
|
2904
3168
|
toJSON() {
|
|
2905
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
2906
3169
|
const obj = {};
|
|
2907
|
-
|
|
2908
|
-
|
|
3170
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3171
|
+
for (const index in metadata) {
|
|
3172
|
+
const field = metadata[index];
|
|
3173
|
+
const fieldName = field.name;
|
|
2909
3174
|
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
2910
3175
|
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
2911
3176
|
? this[fieldName]['toJSON']()
|
|
@@ -2918,17 +3183,19 @@
|
|
|
2918
3183
|
this[$changes].discardAll();
|
|
2919
3184
|
}
|
|
2920
3185
|
[$getByIndex](index) {
|
|
2921
|
-
|
|
3186
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3187
|
+
return this[metadata[index].name];
|
|
2922
3188
|
}
|
|
2923
3189
|
[$deleteByIndex](index) {
|
|
2924
|
-
this
|
|
3190
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3191
|
+
this[metadata[index].name] = undefined;
|
|
2925
3192
|
}
|
|
2926
3193
|
static debugRefIds(instance, jsonContents = true, level = 0) {
|
|
2927
3194
|
const ref = instance;
|
|
2928
3195
|
const changeTree = ref[$changes];
|
|
2929
3196
|
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2930
3197
|
let output = "";
|
|
2931
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
3198
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
2932
3199
|
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
2933
3200
|
return output;
|
|
2934
3201
|
}
|
|
@@ -2946,30 +3213,40 @@
|
|
|
2946
3213
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
2947
3214
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
2948
3215
|
function dumpChangeSet(changeSet) {
|
|
2949
|
-
|
|
2950
|
-
.
|
|
2951
|
-
.forEach((
|
|
3216
|
+
changeSet.operations
|
|
3217
|
+
.filter(op => op)
|
|
3218
|
+
.forEach((index) => {
|
|
3219
|
+
const operation = changeTree.indexedOperations[index];
|
|
3220
|
+
console.log({ index, operation });
|
|
3221
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3222
|
+
});
|
|
2952
3223
|
}
|
|
2953
3224
|
dumpChangeSet(changeSet);
|
|
2954
3225
|
// display filtered changes
|
|
2955
|
-
if (!isEncodeAll &&
|
|
3226
|
+
if (!isEncodeAll &&
|
|
3227
|
+
changeTree.filteredChanges &&
|
|
3228
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
2956
3229
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
2957
3230
|
dumpChangeSet(changeTree.filteredChanges);
|
|
2958
3231
|
}
|
|
2959
3232
|
// display filtered changes
|
|
2960
|
-
if (isEncodeAll &&
|
|
3233
|
+
if (isEncodeAll &&
|
|
3234
|
+
changeTree.allFilteredChanges &&
|
|
3235
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
2961
3236
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
2962
3237
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
2963
3238
|
}
|
|
2964
3239
|
return output;
|
|
2965
3240
|
}
|
|
2966
|
-
static debugChangesDeep(ref) {
|
|
3241
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
2967
3242
|
let output = "";
|
|
2968
3243
|
const rootChangeTree = ref[$changes];
|
|
3244
|
+
const root = rootChangeTree.root;
|
|
2969
3245
|
const changeTrees = new Map();
|
|
2970
3246
|
let totalInstances = 0;
|
|
2971
3247
|
let totalOperations = 0;
|
|
2972
|
-
for (const [
|
|
3248
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3249
|
+
const changeTree = root.changeTrees[refId];
|
|
2973
3250
|
let includeChangeTree = false;
|
|
2974
3251
|
let parentChangeTrees = [];
|
|
2975
3252
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -2988,7 +3265,7 @@
|
|
|
2988
3265
|
}
|
|
2989
3266
|
if (includeChangeTree) {
|
|
2990
3267
|
totalInstances += 1;
|
|
2991
|
-
totalOperations += changes.
|
|
3268
|
+
totalOperations += Object.keys(changes).length;
|
|
2992
3269
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
2993
3270
|
}
|
|
2994
3271
|
}
|
|
@@ -3006,12 +3283,13 @@
|
|
|
3006
3283
|
visitedParents.add(parentChangeTree);
|
|
3007
3284
|
}
|
|
3008
3285
|
});
|
|
3009
|
-
const changes = changeTree.
|
|
3286
|
+
const changes = changeTree.indexedOperations;
|
|
3010
3287
|
const level = parentChangeTrees.length;
|
|
3011
3288
|
const indent = getIndent(level);
|
|
3012
3289
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3013
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3014
|
-
for (const
|
|
3290
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3291
|
+
for (const index in changes) {
|
|
3292
|
+
const operation = changes[index];
|
|
3015
3293
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3016
3294
|
}
|
|
3017
3295
|
}
|
|
@@ -3045,6 +3323,7 @@
|
|
|
3045
3323
|
this.$indexes = new Map();
|
|
3046
3324
|
this.$refId = 0;
|
|
3047
3325
|
this[$changes] = new ChangeTree(this);
|
|
3326
|
+
this[$changes].indexes = {};
|
|
3048
3327
|
if (initialValues) {
|
|
3049
3328
|
initialValues.forEach((v) => this.add(v));
|
|
3050
3329
|
}
|
|
@@ -3200,6 +3479,7 @@
|
|
|
3200
3479
|
this.$indexes = new Map();
|
|
3201
3480
|
this.$refId = 0;
|
|
3202
3481
|
this[$changes] = new ChangeTree(this);
|
|
3482
|
+
this[$changes].indexes = {};
|
|
3203
3483
|
if (initialValues) {
|
|
3204
3484
|
initialValues.forEach((v) => this.add(v));
|
|
3205
3485
|
}
|
|
@@ -3355,6 +3635,8 @@
|
|
|
3355
3635
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3356
3636
|
PERFORMANCE OF THIS SOFTWARE.
|
|
3357
3637
|
***************************************************************************** */
|
|
3638
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
3639
|
+
|
|
3358
3640
|
|
|
3359
3641
|
function __decorate(decorators, target, key, desc) {
|
|
3360
3642
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3368,37 +3650,135 @@
|
|
|
3368
3650
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3369
3651
|
};
|
|
3370
3652
|
|
|
3653
|
+
function spliceOne(arr, index) {
|
|
3654
|
+
// manually splice an array
|
|
3655
|
+
if (index === -1 || index >= arr.length) {
|
|
3656
|
+
return false;
|
|
3657
|
+
}
|
|
3658
|
+
const len = arr.length - 1;
|
|
3659
|
+
for (let i = index; i < len; i++) {
|
|
3660
|
+
arr[i] = arr[i + 1];
|
|
3661
|
+
}
|
|
3662
|
+
arr.length = len;
|
|
3663
|
+
return true;
|
|
3664
|
+
}
|
|
3665
|
+
|
|
3666
|
+
class Root {
|
|
3667
|
+
constructor(types) {
|
|
3668
|
+
this.types = types;
|
|
3669
|
+
this.nextUniqueId = 0;
|
|
3670
|
+
this.refCount = {};
|
|
3671
|
+
this.changeTrees = {};
|
|
3672
|
+
// all changes
|
|
3673
|
+
this.allChanges = [];
|
|
3674
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3675
|
+
// pending changes to be encoded
|
|
3676
|
+
this.changes = [];
|
|
3677
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3678
|
+
}
|
|
3679
|
+
getNextUniqueId() {
|
|
3680
|
+
return this.nextUniqueId++;
|
|
3681
|
+
}
|
|
3682
|
+
add(changeTree) {
|
|
3683
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3684
|
+
changeTree.ensureRefId();
|
|
3685
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3686
|
+
if (isNewChangeTree) {
|
|
3687
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3688
|
+
}
|
|
3689
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3690
|
+
if (previousRefCount === 0) {
|
|
3691
|
+
//
|
|
3692
|
+
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3693
|
+
// We need to re-add all changes to the `changes` map.
|
|
3694
|
+
//
|
|
3695
|
+
const ops = changeTree.allChanges.operations;
|
|
3696
|
+
let len = ops.length;
|
|
3697
|
+
while (len--) {
|
|
3698
|
+
changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
|
|
3699
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3700
|
+
}
|
|
3701
|
+
}
|
|
3702
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3703
|
+
return isNewChangeTree;
|
|
3704
|
+
}
|
|
3705
|
+
remove(changeTree) {
|
|
3706
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3707
|
+
if (refCount <= 0) {
|
|
3708
|
+
//
|
|
3709
|
+
// Only remove "root" reference if it's the last reference
|
|
3710
|
+
//
|
|
3711
|
+
changeTree.root = undefined;
|
|
3712
|
+
delete this.changeTrees[changeTree.refId];
|
|
3713
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3714
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3715
|
+
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3716
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3717
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3718
|
+
}
|
|
3719
|
+
this.refCount[changeTree.refId] = 0;
|
|
3720
|
+
}
|
|
3721
|
+
else {
|
|
3722
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3723
|
+
}
|
|
3724
|
+
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3725
|
+
return refCount;
|
|
3726
|
+
}
|
|
3727
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3728
|
+
const changeSet = this[changeSetName];
|
|
3729
|
+
const index = changeSet.indexOf(changeTree);
|
|
3730
|
+
if (index !== -1) {
|
|
3731
|
+
spliceOne(changeSet, index);
|
|
3732
|
+
// changeSet[index] = undefined;
|
|
3733
|
+
}
|
|
3734
|
+
}
|
|
3735
|
+
clear() {
|
|
3736
|
+
this.changes.length = 0;
|
|
3737
|
+
}
|
|
3738
|
+
}
|
|
3739
|
+
|
|
3371
3740
|
class Encoder {
|
|
3372
3741
|
static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
|
|
3373
|
-
constructor(
|
|
3742
|
+
constructor(state) {
|
|
3374
3743
|
this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
|
|
3375
|
-
this.setRoot(root);
|
|
3376
3744
|
//
|
|
3377
3745
|
// TODO: cache and restore "Context" based on root schema
|
|
3378
3746
|
// (to avoid creating a new context for every new room)
|
|
3379
3747
|
//
|
|
3380
|
-
this.context = new TypeContext(
|
|
3748
|
+
this.context = new TypeContext(state.constructor);
|
|
3749
|
+
this.root = new Root(this.context);
|
|
3750
|
+
this.setState(state);
|
|
3381
3751
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
3382
3752
|
// this.context.schemas.forEach((id, schema) => {
|
|
3383
3753
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3384
3754
|
// });
|
|
3385
3755
|
}
|
|
3386
|
-
|
|
3387
|
-
this.root = new Root();
|
|
3756
|
+
setState(state) {
|
|
3388
3757
|
this.state = state;
|
|
3389
|
-
state[$changes].setRoot(this.root);
|
|
3758
|
+
this.state[$changes].setRoot(this.root);
|
|
3390
3759
|
}
|
|
3391
|
-
encode(it = { offset: 0 }, view,
|
|
3392
|
-
|
|
3393
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
3760
|
+
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeSetName = "changes", isEncodeAll = changeSetName === "allChanges", initialOffset = it.offset // cache current offset in case we need to resize the buffer
|
|
3761
|
+
) {
|
|
3394
3762
|
const hasView = (view !== undefined);
|
|
3395
3763
|
const rootChangeTree = this.state[$changes];
|
|
3396
|
-
const
|
|
3397
|
-
|
|
3764
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3765
|
+
const changeTrees = this.root[changeSetName];
|
|
3766
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3767
|
+
const changeTree = changeTrees[i];
|
|
3768
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
3769
|
+
// if (changeTree === undefined) { continue; }
|
|
3770
|
+
const operations = changeTree[changeSetName];
|
|
3398
3771
|
const ref = changeTree.ref;
|
|
3399
|
-
const ctor = ref
|
|
3772
|
+
const ctor = ref.constructor;
|
|
3400
3773
|
const encoder = ctor[$encoder];
|
|
3401
3774
|
const filter = ctor[$filter];
|
|
3775
|
+
const metadata = ctor[Symbol.metadata];
|
|
3776
|
+
// try { throw new Error(); } catch (e) {
|
|
3777
|
+
// // only print if not coming from Reflection.ts
|
|
3778
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3779
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
3780
|
+
// }
|
|
3781
|
+
// }
|
|
3402
3782
|
if (hasView) {
|
|
3403
3783
|
if (!view.items.has(changeTree)) {
|
|
3404
3784
|
view.invisible.add(changeTree);
|
|
@@ -3409,12 +3789,18 @@
|
|
|
3409
3789
|
}
|
|
3410
3790
|
}
|
|
3411
3791
|
// skip root `refId` if it's the first change tree
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3792
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
3793
|
+
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
3794
|
+
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3795
|
+
number$1(buffer, changeTree.refId, it);
|
|
3415
3796
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3797
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3798
|
+
const fieldIndex = operations.operations[j];
|
|
3799
|
+
const operation = (fieldIndex < 0)
|
|
3800
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3801
|
+
: (isEncodeAll)
|
|
3802
|
+
? exports.OPERATION.ADD
|
|
3803
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3418
3804
|
//
|
|
3419
3805
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3420
3806
|
// they will be encoded per client, based on their view.
|
|
@@ -3422,93 +3808,127 @@
|
|
|
3422
3808
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3423
3809
|
// TODO: avoid checking if no view tags were defined
|
|
3424
3810
|
//
|
|
3425
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3426
|
-
// console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
|
|
3811
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3427
3812
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3428
3813
|
// view?.invisible.add(changeTree);
|
|
3429
3814
|
continue;
|
|
3430
3815
|
}
|
|
3431
|
-
//
|
|
3432
|
-
//
|
|
3433
|
-
//
|
|
3434
|
-
//
|
|
3435
|
-
//
|
|
3436
|
-
|
|
3816
|
+
// try { throw new Error(); } catch (e) {
|
|
3817
|
+
// // only print if not coming from Reflection.ts
|
|
3818
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3819
|
+
// console.log("WILL ENCODE", {
|
|
3820
|
+
// ref: changeTree.ref.constructor.name,
|
|
3821
|
+
// fieldIndex,
|
|
3822
|
+
// operation: OPERATION[operation],
|
|
3823
|
+
// });
|
|
3824
|
+
// }
|
|
3825
|
+
// }
|
|
3826
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
3827
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3828
|
+
}
|
|
3829
|
+
if (shouldDiscardChanges) {
|
|
3830
|
+
changeTree.discard();
|
|
3831
|
+
// Not a new instance anymore
|
|
3832
|
+
changeTree.isNew = false;
|
|
3437
3833
|
}
|
|
3438
3834
|
}
|
|
3439
|
-
if (it.offset >
|
|
3440
|
-
const newSize = getNextPowerOf2(
|
|
3441
|
-
console.warn(
|
|
3835
|
+
if (it.offset > buffer.byteLength) {
|
|
3836
|
+
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
3837
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
3838
|
+
|
|
3839
|
+
import { Encoder } from "@colyseus/schema";
|
|
3840
|
+
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
3841
|
+
`);
|
|
3442
3842
|
//
|
|
3443
3843
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
3444
3844
|
//
|
|
3445
|
-
|
|
3446
|
-
|
|
3845
|
+
buffer = Buffer.allocUnsafeSlow(newSize);
|
|
3846
|
+
// assign resized buffer to local sharedBuffer
|
|
3847
|
+
if (buffer === this.sharedBuffer) {
|
|
3848
|
+
this.sharedBuffer = buffer;
|
|
3849
|
+
}
|
|
3850
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3447
3851
|
}
|
|
3448
3852
|
else {
|
|
3449
|
-
//
|
|
3450
|
-
// only clear changes after making sure buffer resize is not required.
|
|
3451
|
-
//
|
|
3452
|
-
if (
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
}
|
|
3458
|
-
|
|
3459
|
-
return bytes.slice(0, it.offset);
|
|
3853
|
+
// //
|
|
3854
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
3855
|
+
// //
|
|
3856
|
+
// if (shouldClearChanges) {
|
|
3857
|
+
// //
|
|
3858
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
3859
|
+
// //
|
|
3860
|
+
// this.onEndEncode(changeTrees);
|
|
3861
|
+
// }
|
|
3862
|
+
return buffer.subarray(0, it.offset);
|
|
3460
3863
|
}
|
|
3461
3864
|
}
|
|
3462
|
-
encodeAll(it = { offset: 0 }) {
|
|
3463
|
-
// console.log(
|
|
3464
|
-
//
|
|
3465
|
-
|
|
3466
|
-
// });
|
|
3467
|
-
return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
|
|
3865
|
+
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3866
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
3867
|
+
// this.debugChanges("allChanges");
|
|
3868
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3468
3869
|
}
|
|
3469
3870
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3470
3871
|
const viewOffset = it.offset;
|
|
3471
|
-
// console.log(
|
|
3472
|
-
// this.
|
|
3872
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
3873
|
+
// this.debugChanges("allFilteredChanges");
|
|
3874
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
3473
3875
|
// try to encode "filtered" changes
|
|
3474
|
-
this.encode(it, view, bytes,
|
|
3876
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3475
3877
|
return Buffer.concat([
|
|
3476
|
-
bytes.
|
|
3477
|
-
bytes.
|
|
3878
|
+
bytes.subarray(0, sharedOffset),
|
|
3879
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3478
3880
|
]);
|
|
3479
3881
|
}
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3882
|
+
debugChanges(field) {
|
|
3883
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3884
|
+
? this.root[field]
|
|
3885
|
+
: field;
|
|
3886
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3887
|
+
const changeSet = changeTree[field];
|
|
3888
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3889
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3890
|
+
for (const index in changeSet) {
|
|
3891
|
+
const op = changeSet[index];
|
|
3892
|
+
console.log(" ->", {
|
|
3893
|
+
index,
|
|
3894
|
+
field: metadata?.[index],
|
|
3895
|
+
op: exports.OPERATION[op],
|
|
3896
|
+
});
|
|
3897
|
+
}
|
|
3898
|
+
});
|
|
3899
|
+
}
|
|
3490
3900
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3491
3901
|
const viewOffset = it.offset;
|
|
3492
|
-
//
|
|
3493
|
-
this.
|
|
3902
|
+
// console.log(`\nencodeView(), view.changes (${view.changes.size})`);
|
|
3903
|
+
// this.debugChanges(view.changes);
|
|
3904
|
+
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3905
|
+
// this.debugChanges("filteredChanges");
|
|
3494
3906
|
// encode visibility changes (add/remove for this view)
|
|
3495
|
-
const
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3907
|
+
const refIds = Object.keys(view.changes);
|
|
3908
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
3909
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3910
|
+
const refId = refIds[i];
|
|
3911
|
+
const changes = view.changes[refId];
|
|
3912
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3913
|
+
if (changeTree === undefined ||
|
|
3914
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3915
|
+
) {
|
|
3916
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3500
3917
|
continue;
|
|
3501
3918
|
}
|
|
3502
3919
|
const ref = changeTree.ref;
|
|
3503
|
-
const ctor = ref
|
|
3920
|
+
const ctor = ref.constructor;
|
|
3504
3921
|
const encoder = ctor[$encoder];
|
|
3922
|
+
const metadata = ctor[Symbol.metadata];
|
|
3505
3923
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3506
3924
|
number$1(bytes, changeTree.refId, it);
|
|
3507
|
-
const
|
|
3508
|
-
for (
|
|
3925
|
+
const keys = Object.keys(changes);
|
|
3926
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3927
|
+
const key = keys[i];
|
|
3928
|
+
const operation = changes[key];
|
|
3509
3929
|
// isEncodeAll = false
|
|
3510
3930
|
// hasView = true
|
|
3511
|
-
encoder(this, bytes, changeTree,
|
|
3931
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3512
3932
|
}
|
|
3513
3933
|
}
|
|
3514
3934
|
//
|
|
@@ -3516,51 +3936,64 @@
|
|
|
3516
3936
|
// (to allow re-using StateView's for multiple clients)
|
|
3517
3937
|
//
|
|
3518
3938
|
// clear "view" changes after encoding
|
|
3519
|
-
view.changes
|
|
3939
|
+
view.changes = {};
|
|
3940
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
3941
|
+
// try to encode "filtered" changes
|
|
3942
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3520
3943
|
return Buffer.concat([
|
|
3521
|
-
bytes.
|
|
3522
|
-
bytes.
|
|
3944
|
+
bytes.subarray(0, sharedOffset),
|
|
3945
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3523
3946
|
]);
|
|
3524
3947
|
}
|
|
3525
3948
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3949
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3950
|
+
// changeTree.endEncode();
|
|
3951
|
+
// });
|
|
3952
|
+
// for (const refId in changeTrees) {
|
|
3953
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3954
|
+
// changeTree.endEncode();
|
|
3955
|
+
// // changeTree.changes.clear();
|
|
3956
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3957
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3958
|
+
// // // Not a new instance anymore
|
|
3959
|
+
// // delete changeTree[$isNew];
|
|
3960
|
+
// }
|
|
3530
3961
|
}
|
|
3531
3962
|
discardChanges() {
|
|
3963
|
+
// console.log("DISCARD CHANGES!");
|
|
3532
3964
|
// discard shared changes
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3965
|
+
let length = this.root.changes.length;
|
|
3966
|
+
if (length > 0) {
|
|
3967
|
+
while (length--) {
|
|
3968
|
+
this.root.changes[length]?.endEncode();
|
|
3969
|
+
}
|
|
3970
|
+
this.root.changes.length = 0;
|
|
3536
3971
|
}
|
|
3537
3972
|
// discard filtered changes
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3973
|
+
length = this.root.filteredChanges.length;
|
|
3974
|
+
if (length > 0) {
|
|
3975
|
+
while (length--) {
|
|
3976
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3977
|
+
}
|
|
3978
|
+
this.root.filteredChanges.length = 0;
|
|
3541
3979
|
}
|
|
3542
3980
|
}
|
|
3543
3981
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
3544
3982
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
3545
3983
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
3984
|
+
if (targetTypeId === undefined) {
|
|
3985
|
+
console.warn(`@colyseus/schema WARNING: Class "${targetType.name}" is not registered on TypeRegistry - Please either tag the class with @entity or define a @type() field.`);
|
|
3986
|
+
return;
|
|
3987
|
+
}
|
|
3546
3988
|
if (baseTypeId !== targetTypeId) {
|
|
3547
3989
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
3548
3990
|
number$1(bytes, targetTypeId, it);
|
|
3549
3991
|
}
|
|
3550
3992
|
}
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
// manually splice an array
|
|
3555
|
-
if (index === -1 || index >= arr.length) {
|
|
3556
|
-
return false;
|
|
3557
|
-
}
|
|
3558
|
-
const len = arr.length - 1;
|
|
3559
|
-
for (let i = index; i < len; i++) {
|
|
3560
|
-
arr[i] = arr[i + 1];
|
|
3993
|
+
get hasChanges() {
|
|
3994
|
+
return (this.root.changes.length > 0 ||
|
|
3995
|
+
this.root.filteredChanges.length > 0);
|
|
3561
3996
|
}
|
|
3562
|
-
arr.length = len;
|
|
3563
|
-
return true;
|
|
3564
3997
|
}
|
|
3565
3998
|
|
|
3566
3999
|
class DecodingWarning extends Error {
|
|
@@ -3641,8 +4074,9 @@
|
|
|
3641
4074
|
// Ensure child schema instances have their references removed as well.
|
|
3642
4075
|
//
|
|
3643
4076
|
if (Metadata.isValidInstance(ref)) {
|
|
3644
|
-
const metadata = ref
|
|
3645
|
-
for (const
|
|
4077
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
4078
|
+
for (const index in metadata) {
|
|
4079
|
+
const field = metadata[index].name;
|
|
3646
4080
|
const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
|
|
3647
4081
|
if (childRefId) {
|
|
3648
4082
|
this.removeRef(childRefId);
|
|
@@ -3689,21 +4123,21 @@
|
|
|
3689
4123
|
class Decoder {
|
|
3690
4124
|
constructor(root, context) {
|
|
3691
4125
|
this.currentRefId = 0;
|
|
3692
|
-
this.
|
|
4126
|
+
this.setState(root);
|
|
3693
4127
|
this.context = context || new TypeContext(root.constructor);
|
|
3694
4128
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
3695
4129
|
// this.context.schemas.forEach((id, schema) => {
|
|
3696
4130
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3697
4131
|
// });
|
|
3698
4132
|
}
|
|
3699
|
-
|
|
4133
|
+
setState(root) {
|
|
3700
4134
|
this.state = root;
|
|
3701
|
-
this
|
|
3702
|
-
this
|
|
4135
|
+
this.root = new ReferenceTracker();
|
|
4136
|
+
this.root.addRef(0, root);
|
|
3703
4137
|
}
|
|
3704
4138
|
decode(bytes, it = { offset: 0 }, ref = this.state) {
|
|
3705
4139
|
const allChanges = [];
|
|
3706
|
-
const $root = this
|
|
4140
|
+
const $root = this.root;
|
|
3707
4141
|
const totalBytes = bytes.byteLength;
|
|
3708
4142
|
let decoder = ref['constructor'][$decoder];
|
|
3709
4143
|
this.currentRefId = 0;
|
|
@@ -3723,7 +4157,7 @@
|
|
|
3723
4157
|
}
|
|
3724
4158
|
ref[$onDecodeEnd]?.();
|
|
3725
4159
|
ref = nextRef;
|
|
3726
|
-
decoder = ref
|
|
4160
|
+
decoder = ref.constructor[$decoder];
|
|
3727
4161
|
continue;
|
|
3728
4162
|
}
|
|
3729
4163
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -3784,7 +4218,7 @@
|
|
|
3784
4218
|
previousValue: value
|
|
3785
4219
|
});
|
|
3786
4220
|
if (needRemoveRef) {
|
|
3787
|
-
this
|
|
4221
|
+
this.root.removeRef(this.root.refIds.get(value));
|
|
3788
4222
|
}
|
|
3789
4223
|
});
|
|
3790
4224
|
}
|
|
@@ -3824,14 +4258,27 @@
|
|
|
3824
4258
|
super(...arguments);
|
|
3825
4259
|
this.types = new ArraySchema();
|
|
3826
4260
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
4261
|
+
/**
|
|
4262
|
+
* Encodes the TypeContext of an Encoder into a buffer.
|
|
4263
|
+
*
|
|
4264
|
+
* @param encoder Encoder instance
|
|
4265
|
+
* @param it
|
|
4266
|
+
* @returns
|
|
4267
|
+
*/
|
|
4268
|
+
static encode(encoder, it = { offset: 0 }) {
|
|
4269
|
+
const context = encoder.context;
|
|
3831
4270
|
const reflection = new Reflection();
|
|
3832
|
-
const
|
|
4271
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
4272
|
+
// rootType is usually the first schema passed to the Encoder
|
|
4273
|
+
// (unless it inherits from another schema)
|
|
4274
|
+
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4275
|
+
if (rootType > 0) {
|
|
4276
|
+
reflection.rootType = rootType;
|
|
4277
|
+
}
|
|
3833
4278
|
const buildType = (currentType, metadata) => {
|
|
3834
|
-
for (const
|
|
4279
|
+
for (const fieldIndex in metadata) {
|
|
4280
|
+
const index = Number(fieldIndex);
|
|
4281
|
+
const fieldName = metadata[index].name;
|
|
3835
4282
|
// skip fields from parent classes
|
|
3836
4283
|
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
3837
4284
|
continue;
|
|
@@ -3839,7 +4286,7 @@
|
|
|
3839
4286
|
const field = new ReflectionField();
|
|
3840
4287
|
field.name = fieldName;
|
|
3841
4288
|
let fieldType;
|
|
3842
|
-
const type = metadata[
|
|
4289
|
+
const type = metadata[index].type;
|
|
3843
4290
|
if (typeof (type) === "string") {
|
|
3844
4291
|
fieldType = type;
|
|
3845
4292
|
}
|
|
@@ -3881,66 +4328,335 @@
|
|
|
3881
4328
|
}
|
|
3882
4329
|
buildType(type, klass[Symbol.metadata]);
|
|
3883
4330
|
}
|
|
3884
|
-
const
|
|
3885
|
-
const buf = encoder.encodeAll(it);
|
|
4331
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
3886
4332
|
return Buffer.from(buf, 0, it.offset);
|
|
3887
4333
|
}
|
|
4334
|
+
/**
|
|
4335
|
+
* Decodes the TypeContext from a buffer into a Decoder instance.
|
|
4336
|
+
*
|
|
4337
|
+
* @param bytes Reflection.encode() output
|
|
4338
|
+
* @param it
|
|
4339
|
+
* @returns Decoder instance
|
|
4340
|
+
*/
|
|
3888
4341
|
static decode(bytes, it) {
|
|
3889
4342
|
const reflection = new Reflection();
|
|
3890
4343
|
const reflectionDecoder = new Decoder(reflection);
|
|
3891
4344
|
reflectionDecoder.decode(bytes, it);
|
|
3892
|
-
const
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
const
|
|
4345
|
+
const typeContext = new TypeContext();
|
|
4346
|
+
// 1st pass, initialize metadata + inheritance
|
|
4347
|
+
reflection.types.forEach((reflectionType) => {
|
|
4348
|
+
const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
4349
|
+
const schema = class _ extends parentClass {
|
|
3896
4350
|
};
|
|
3897
|
-
// const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
|
|
3898
|
-
const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
|
|
3899
|
-
Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
3900
4351
|
// register for inheritance support
|
|
3901
4352
|
TypeContext.register(schema);
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
return types;
|
|
4353
|
+
// // for inheritance support
|
|
4354
|
+
// Metadata.initialize(schema);
|
|
4355
|
+
typeContext.add(schema, reflectionType.id);
|
|
3906
4356
|
}, {});
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
const metadata = schemaType[Symbol.metadata];
|
|
3910
|
-
const parentKlass = reflection.types[reflectionType.extendsId];
|
|
3911
|
-
const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
|
|
4357
|
+
// define fields
|
|
4358
|
+
const addFields = (metadata, reflectionType, parentFieldIndex) => {
|
|
3912
4359
|
reflectionType.fields.forEach((field, i) => {
|
|
3913
4360
|
const fieldIndex = parentFieldIndex + i;
|
|
3914
4361
|
if (field.referencedType !== undefined) {
|
|
3915
4362
|
let fieldType = field.type;
|
|
3916
|
-
let refType =
|
|
4363
|
+
let refType = typeContext.get(field.referencedType);
|
|
3917
4364
|
// map or array of primitive type (-1)
|
|
3918
4365
|
if (!refType) {
|
|
3919
4366
|
const typeInfo = field.type.split(":");
|
|
3920
4367
|
fieldType = typeInfo[0];
|
|
3921
|
-
refType = typeInfo[1];
|
|
4368
|
+
refType = typeInfo[1]; // string
|
|
3922
4369
|
}
|
|
3923
4370
|
if (fieldType === "ref") {
|
|
3924
|
-
// type(refType)(schemaType.prototype, field.name);
|
|
3925
4371
|
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
3926
4372
|
}
|
|
3927
4373
|
else {
|
|
3928
|
-
// type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
|
|
3929
4374
|
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
3930
4375
|
}
|
|
3931
4376
|
}
|
|
3932
4377
|
else {
|
|
3933
|
-
// type(field.type as PrimitiveType)(schemaType.prototype, field.name);
|
|
3934
4378
|
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
3935
4379
|
}
|
|
3936
4380
|
});
|
|
4381
|
+
};
|
|
4382
|
+
// 2nd pass, set fields
|
|
4383
|
+
reflection.types.forEach((reflectionType) => {
|
|
4384
|
+
const schema = typeContext.get(reflectionType.id);
|
|
4385
|
+
// for inheritance support
|
|
4386
|
+
const metadata = Metadata.initialize(schema);
|
|
4387
|
+
const inheritedTypes = [];
|
|
4388
|
+
let parentType = reflectionType;
|
|
4389
|
+
do {
|
|
4390
|
+
inheritedTypes.push(parentType);
|
|
4391
|
+
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4392
|
+
} while (parentType);
|
|
4393
|
+
let parentFieldIndex = 0;
|
|
4394
|
+
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4395
|
+
// add fields from all inherited classes
|
|
4396
|
+
// TODO: refactor this to avoid adding fields from parent classes
|
|
4397
|
+
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4398
|
+
parentFieldIndex += reflectionType.fields.length;
|
|
4399
|
+
});
|
|
3937
4400
|
});
|
|
3938
|
-
|
|
4401
|
+
const state = new (typeContext.get(reflection.rootType || 0))();
|
|
4402
|
+
return new Decoder(state, typeContext);
|
|
3939
4403
|
}
|
|
3940
4404
|
}
|
|
3941
4405
|
__decorate([
|
|
3942
4406
|
type([ReflectionType])
|
|
3943
4407
|
], Reflection.prototype, "types", void 0);
|
|
4408
|
+
__decorate([
|
|
4409
|
+
type("number")
|
|
4410
|
+
], Reflection.prototype, "rootType", void 0);
|
|
4411
|
+
|
|
4412
|
+
function getDecoderStateCallbacks(decoder) {
|
|
4413
|
+
const $root = decoder.root;
|
|
4414
|
+
const callbacks = $root.callbacks;
|
|
4415
|
+
const onAddCalls = new WeakMap();
|
|
4416
|
+
let currentOnAddCallback;
|
|
4417
|
+
decoder.triggerChanges = function (allChanges) {
|
|
4418
|
+
const uniqueRefIds = new Set();
|
|
4419
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
4420
|
+
const change = allChanges[i];
|
|
4421
|
+
const refId = change.refId;
|
|
4422
|
+
const ref = change.ref;
|
|
4423
|
+
const $callbacks = callbacks[refId];
|
|
4424
|
+
if (!$callbacks) {
|
|
4425
|
+
continue;
|
|
4426
|
+
}
|
|
4427
|
+
//
|
|
4428
|
+
// trigger onRemove on child structure.
|
|
4429
|
+
//
|
|
4430
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4431
|
+
change.previousValue instanceof Schema) {
|
|
4432
|
+
const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
|
|
4433
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4434
|
+
deleteCallbacks[i]();
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
if (ref instanceof Schema) {
|
|
4438
|
+
//
|
|
4439
|
+
// Handle schema instance
|
|
4440
|
+
//
|
|
4441
|
+
if (!uniqueRefIds.has(refId)) {
|
|
4442
|
+
// trigger onChange
|
|
4443
|
+
const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
|
|
4444
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4445
|
+
replaceCallbacks[i]();
|
|
4446
|
+
// try {
|
|
4447
|
+
// } catch (e) {
|
|
4448
|
+
// console.error(e);
|
|
4449
|
+
// }
|
|
4450
|
+
}
|
|
4451
|
+
}
|
|
4452
|
+
if ($callbacks.hasOwnProperty(change.field)) {
|
|
4453
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
4454
|
+
for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
|
|
4455
|
+
fieldCallbacks[i](change.value, change.previousValue);
|
|
4456
|
+
// try {
|
|
4457
|
+
// } catch (e) {
|
|
4458
|
+
// console.error(e);
|
|
4459
|
+
// }
|
|
4460
|
+
}
|
|
4461
|
+
}
|
|
4462
|
+
}
|
|
4463
|
+
else {
|
|
4464
|
+
//
|
|
4465
|
+
// Handle collection of items
|
|
4466
|
+
//
|
|
4467
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
4468
|
+
//
|
|
4469
|
+
// FIXME: `previousValue` should always be available.
|
|
4470
|
+
//
|
|
4471
|
+
if (change.previousValue !== undefined) {
|
|
4472
|
+
// triger onRemove
|
|
4473
|
+
const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
|
|
4474
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4475
|
+
deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
// Handle DELETE_AND_ADD operations
|
|
4479
|
+
if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
4480
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4481
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4482
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4483
|
+
}
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
|
|
4487
|
+
// triger onAdd
|
|
4488
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4489
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4490
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
// trigger onChange
|
|
4494
|
+
if (change.value !== change.previousValue) {
|
|
4495
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
4496
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4497
|
+
replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4498
|
+
}
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
uniqueRefIds.add(refId);
|
|
4502
|
+
}
|
|
4503
|
+
};
|
|
4504
|
+
function getProxy(metadataOrType, context) {
|
|
4505
|
+
let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
|
|
4506
|
+
let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
|
|
4507
|
+
(metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
|
|
4508
|
+
if (metadata && !isCollection) {
|
|
4509
|
+
const onAddListen = function (ref, prop, callback, immediate) {
|
|
4510
|
+
// immediate trigger
|
|
4511
|
+
if (immediate &&
|
|
4512
|
+
context.instance[prop] !== undefined &&
|
|
4513
|
+
!onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
|
|
4514
|
+
) {
|
|
4515
|
+
callback(context.instance[prop], undefined);
|
|
4516
|
+
}
|
|
4517
|
+
return $root.addCallback($root.refIds.get(ref), prop, callback);
|
|
4518
|
+
};
|
|
4519
|
+
/**
|
|
4520
|
+
* Schema instances
|
|
4521
|
+
*/
|
|
4522
|
+
return new Proxy({
|
|
4523
|
+
listen: function listen(prop, callback, immediate = true) {
|
|
4524
|
+
if (context.instance) {
|
|
4525
|
+
return onAddListen(context.instance, prop, callback, immediate);
|
|
4526
|
+
}
|
|
4527
|
+
else {
|
|
4528
|
+
// collection instance not received yet
|
|
4529
|
+
let detachCallback = () => { };
|
|
4530
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4531
|
+
detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4532
|
+
});
|
|
4533
|
+
return () => detachCallback();
|
|
4534
|
+
}
|
|
4535
|
+
},
|
|
4536
|
+
onChange: function onChange(callback) {
|
|
4537
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
|
|
4538
|
+
},
|
|
4539
|
+
//
|
|
4540
|
+
// TODO: refactor `bindTo()` implementation.
|
|
4541
|
+
// There is room for improvement.
|
|
4542
|
+
//
|
|
4543
|
+
bindTo: function bindTo(targetObject, properties) {
|
|
4544
|
+
if (!properties) {
|
|
4545
|
+
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4546
|
+
}
|
|
4547
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
|
|
4548
|
+
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4549
|
+
});
|
|
4550
|
+
}
|
|
4551
|
+
}, {
|
|
4552
|
+
get(target, prop) {
|
|
4553
|
+
const metadataField = metadata[metadata[prop]];
|
|
4554
|
+
if (metadataField) {
|
|
4555
|
+
const instance = context.instance?.[prop];
|
|
4556
|
+
const onInstanceAvailable = ((callback) => {
|
|
4557
|
+
const unbind = $(context.instance).listen(prop, (value, _) => {
|
|
4558
|
+
callback(value, false);
|
|
4559
|
+
// FIXME: by "unbinding" the callback here,
|
|
4560
|
+
// it will not support when the server
|
|
4561
|
+
// re-instantiates the instance.
|
|
4562
|
+
//
|
|
4563
|
+
unbind?.();
|
|
4564
|
+
}, false);
|
|
4565
|
+
// has existing value
|
|
4566
|
+
if ($root.refIds.get(instance) !== undefined) {
|
|
4567
|
+
callback(instance, true);
|
|
4568
|
+
}
|
|
4569
|
+
});
|
|
4570
|
+
return getProxy(metadataField.type, {
|
|
4571
|
+
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
4572
|
+
instance: ($root.refIds.get(instance) && instance),
|
|
4573
|
+
parentInstance: context.instance,
|
|
4574
|
+
onInstanceAvailable,
|
|
4575
|
+
});
|
|
4576
|
+
}
|
|
4577
|
+
else {
|
|
4578
|
+
// accessing the function
|
|
4579
|
+
return target[prop];
|
|
4580
|
+
}
|
|
4581
|
+
},
|
|
4582
|
+
has(target, prop) { return metadata[prop] !== undefined; },
|
|
4583
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4584
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4585
|
+
});
|
|
4586
|
+
}
|
|
4587
|
+
else {
|
|
4588
|
+
/**
|
|
4589
|
+
* Collection instances
|
|
4590
|
+
*/
|
|
4591
|
+
const onAdd = function (ref, callback, immediate) {
|
|
4592
|
+
// Trigger callback on existing items
|
|
4593
|
+
if (immediate) {
|
|
4594
|
+
ref.forEach((v, k) => callback(v, k));
|
|
4595
|
+
}
|
|
4596
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
|
|
4597
|
+
onAddCalls.set(callback, true);
|
|
4598
|
+
currentOnAddCallback = callback;
|
|
4599
|
+
callback(value, key);
|
|
4600
|
+
onAddCalls.delete(callback);
|
|
4601
|
+
currentOnAddCallback = undefined;
|
|
4602
|
+
});
|
|
4603
|
+
};
|
|
4604
|
+
const onRemove = function (ref, callback) {
|
|
4605
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
|
|
4606
|
+
};
|
|
4607
|
+
return new Proxy({
|
|
4608
|
+
onAdd: function (callback, immediate = true) {
|
|
4609
|
+
//
|
|
4610
|
+
// https://github.com/colyseus/schema/issues/147
|
|
4611
|
+
// If parent instance has "onAdd" registered, avoid triggering immediate callback.
|
|
4612
|
+
//
|
|
4613
|
+
if (context.instance) {
|
|
4614
|
+
return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
|
|
4615
|
+
}
|
|
4616
|
+
else if (context.onInstanceAvailable) {
|
|
4617
|
+
// collection instance not received yet
|
|
4618
|
+
let detachCallback = () => { };
|
|
4619
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4620
|
+
detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4621
|
+
});
|
|
4622
|
+
return () => detachCallback();
|
|
4623
|
+
}
|
|
4624
|
+
},
|
|
4625
|
+
onRemove: function (callback) {
|
|
4626
|
+
if (context.onInstanceAvailable) {
|
|
4627
|
+
// collection instance not received yet
|
|
4628
|
+
let detachCallback = () => { };
|
|
4629
|
+
context.onInstanceAvailable((ref) => {
|
|
4630
|
+
detachCallback = onRemove(ref, callback);
|
|
4631
|
+
});
|
|
4632
|
+
return () => detachCallback();
|
|
4633
|
+
}
|
|
4634
|
+
else if (context.instance) {
|
|
4635
|
+
return onRemove(context.instance, callback);
|
|
4636
|
+
}
|
|
4637
|
+
},
|
|
4638
|
+
}, {
|
|
4639
|
+
get(target, prop) {
|
|
4640
|
+
if (!target[prop]) {
|
|
4641
|
+
throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
|
|
4642
|
+
}
|
|
4643
|
+
return target[prop];
|
|
4644
|
+
},
|
|
4645
|
+
has(target, prop) { return target[prop] !== undefined; },
|
|
4646
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4647
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4648
|
+
});
|
|
4649
|
+
}
|
|
4650
|
+
}
|
|
4651
|
+
function $(instance) {
|
|
4652
|
+
return getProxy(undefined, { instance });
|
|
4653
|
+
}
|
|
4654
|
+
return $;
|
|
4655
|
+
}
|
|
4656
|
+
|
|
4657
|
+
function getRawChangesCallback(decoder, callback) {
|
|
4658
|
+
decoder.triggerChanges = callback;
|
|
4659
|
+
}
|
|
3944
4660
|
|
|
3945
4661
|
class StateView {
|
|
3946
4662
|
constructor() {
|
|
@@ -3956,31 +4672,32 @@
|
|
|
3956
4672
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
3957
4673
|
* (This is used to force encoding a property, even if it was not changed)
|
|
3958
4674
|
*/
|
|
3959
|
-
this.changes =
|
|
4675
|
+
this.changes = {};
|
|
3960
4676
|
}
|
|
3961
4677
|
// TODO: allow to set multiple tags at once
|
|
3962
|
-
add(obj, tag = DEFAULT_VIEW_TAG) {
|
|
4678
|
+
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
3963
4679
|
if (!obj[$changes]) {
|
|
3964
4680
|
console.warn("StateView#add(), invalid object:", obj);
|
|
3965
4681
|
return this;
|
|
3966
4682
|
}
|
|
3967
|
-
|
|
3968
|
-
this.items.add(changeTree);
|
|
3969
|
-
// Add children of this ChangeTree to this view
|
|
3970
|
-
changeTree.forEachChild((change, _) => this.add(change.ref, tag));
|
|
3971
|
-
// FIXME: ArraySchema/MapSchema does not have metadata
|
|
4683
|
+
// FIXME: ArraySchema/MapSchema do not have metadata
|
|
3972
4684
|
const metadata = obj.constructor[Symbol.metadata];
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
4685
|
+
const changeTree = obj[$changes];
|
|
4686
|
+
this.items.add(changeTree);
|
|
4687
|
+
// add parent ChangeTree's
|
|
4688
|
+
// - if it was invisible to this view
|
|
4689
|
+
// - if it were previously filtered out
|
|
4690
|
+
if (checkIncludeParent && changeTree.parent) {
|
|
4691
|
+
this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
|
|
4692
|
+
}
|
|
3976
4693
|
//
|
|
3977
4694
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
3978
4695
|
// be set (only the parent's changes are set)
|
|
3979
4696
|
//
|
|
3980
|
-
let changes = this.changes.
|
|
4697
|
+
let changes = this.changes[changeTree.refId];
|
|
3981
4698
|
if (changes === undefined) {
|
|
3982
|
-
changes =
|
|
3983
|
-
this.changes.
|
|
4699
|
+
changes = {};
|
|
4700
|
+
this.changes[changeTree.refId] = changes;
|
|
3984
4701
|
}
|
|
3985
4702
|
// set tag
|
|
3986
4703
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -3996,82 +4713,76 @@
|
|
|
3996
4713
|
tags = this.tags.get(changeTree);
|
|
3997
4714
|
}
|
|
3998
4715
|
tags.add(tag);
|
|
3999
|
-
// console.log("BY TAG:", tag);
|
|
4000
4716
|
// Ref: add tagged properties
|
|
4001
|
-
metadata?.[
|
|
4717
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4002
4718
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
4003
|
-
changes
|
|
4719
|
+
changes[index] = exports.OPERATION.ADD;
|
|
4004
4720
|
}
|
|
4005
4721
|
});
|
|
4006
4722
|
}
|
|
4007
4723
|
else {
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
// metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
|
|
4011
|
-
// if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
4012
|
-
// changes.set(index, OPERATION.ADD);
|
|
4013
|
-
// }
|
|
4014
|
-
// });
|
|
4015
|
-
const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4724
|
+
const isInvisible = this.invisible.has(changeTree);
|
|
4725
|
+
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4016
4726
|
? changeTree.allFilteredChanges
|
|
4017
4727
|
: changeTree.allChanges;
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4728
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4729
|
+
const index = changeSet.operations[i];
|
|
4730
|
+
if (index === undefined) {
|
|
4731
|
+
continue;
|
|
4732
|
+
} // skip "undefined" indexes
|
|
4733
|
+
const op = changeTree.indexedOperations[index];
|
|
4734
|
+
const tagAtIndex = metadata?.[index].tag;
|
|
4735
|
+
if ((isInvisible || // if "invisible", include all
|
|
4736
|
+
tagAtIndex === undefined || // "all change" with no tag
|
|
4737
|
+
tagAtIndex === tag // tagged property
|
|
4738
|
+
) &&
|
|
4739
|
+
op !== exports.OPERATION.DELETE) {
|
|
4740
|
+
changes[index] = op;
|
|
4024
4741
|
}
|
|
4025
4742
|
}
|
|
4026
4743
|
}
|
|
4027
|
-
//
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
(
|
|
4031
|
-
|
|
4032
|
-
|
|
4744
|
+
// Add children of this ChangeTree to this view
|
|
4745
|
+
changeTree.forEachChild((change, index) => {
|
|
4746
|
+
// Do not ADD children that don't have the same tag
|
|
4747
|
+
if (metadata && metadata[index].tag !== tag) {
|
|
4748
|
+
return;
|
|
4749
|
+
}
|
|
4750
|
+
this.add(change.ref, tag, false);
|
|
4751
|
+
});
|
|
4033
4752
|
return this;
|
|
4034
4753
|
}
|
|
4035
|
-
addParent(changeTree, tag) {
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4754
|
+
addParent(changeTree, parentIndex, tag) {
|
|
4755
|
+
// view must have all "changeTree" parent tree
|
|
4756
|
+
this.items.add(changeTree);
|
|
4757
|
+
// add parent's parent
|
|
4758
|
+
const parentChangeTree = changeTree.parent?.[$changes];
|
|
4759
|
+
if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
|
|
4760
|
+
this.addParent(parentChangeTree, changeTree.parentIndex, tag);
|
|
4039
4761
|
}
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
if (!this.invisible.has(parentChangeTree)) {
|
|
4043
|
-
// parent is already available, no need to add it!
|
|
4762
|
+
// parent is already available, no need to add it!
|
|
4763
|
+
if (!this.invisible.has(changeTree)) {
|
|
4044
4764
|
return;
|
|
4045
4765
|
}
|
|
4046
|
-
this.addParent(parentChangeTree, tag);
|
|
4047
4766
|
// add parent's tag properties
|
|
4048
|
-
if (
|
|
4049
|
-
let
|
|
4050
|
-
if (
|
|
4051
|
-
|
|
4052
|
-
this.changes.
|
|
4767
|
+
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4768
|
+
let changes = this.changes[changeTree.refId];
|
|
4769
|
+
if (changes === undefined) {
|
|
4770
|
+
changes = {};
|
|
4771
|
+
this.changes[changeTree.refId] = changes;
|
|
4053
4772
|
}
|
|
4054
|
-
// console.log("add parent change", {
|
|
4055
|
-
// parentIndex,
|
|
4056
|
-
// parentChanges,
|
|
4057
|
-
// parentChange: (
|
|
4058
|
-
// parentChangeTree.getChange(parentIndex) &&
|
|
4059
|
-
// OPERATION[parentChangeTree.getChange(parentIndex)]
|
|
4060
|
-
// ),
|
|
4061
|
-
// })
|
|
4062
4773
|
if (!this.tags) {
|
|
4063
4774
|
this.tags = new WeakMap();
|
|
4064
4775
|
}
|
|
4065
4776
|
let tags;
|
|
4066
|
-
if (!this.tags.has(
|
|
4777
|
+
if (!this.tags.has(changeTree)) {
|
|
4067
4778
|
tags = new Set();
|
|
4068
|
-
this.tags.set(
|
|
4779
|
+
this.tags.set(changeTree, tags);
|
|
4069
4780
|
}
|
|
4070
4781
|
else {
|
|
4071
|
-
tags = this.tags.get(
|
|
4782
|
+
tags = this.tags.get(changeTree);
|
|
4072
4783
|
}
|
|
4073
4784
|
tags.add(tag);
|
|
4074
|
-
|
|
4785
|
+
changes[parentIndex] = exports.OPERATION.ADD;
|
|
4075
4786
|
}
|
|
4076
4787
|
}
|
|
4077
4788
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4083,32 +4794,32 @@
|
|
|
4083
4794
|
this.items.delete(changeTree);
|
|
4084
4795
|
const ref = changeTree.ref;
|
|
4085
4796
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4086
|
-
let changes = this.changes.
|
|
4797
|
+
let changes = this.changes[changeTree.refId];
|
|
4087
4798
|
if (changes === undefined) {
|
|
4088
|
-
changes =
|
|
4089
|
-
this.changes.
|
|
4799
|
+
changes = {};
|
|
4800
|
+
this.changes[changeTree.refId] = changes;
|
|
4090
4801
|
}
|
|
4091
4802
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4092
4803
|
// parent is collection (Map/Array)
|
|
4093
4804
|
const parent = changeTree.parent;
|
|
4094
4805
|
if (!Metadata.isValidInstance(parent)) {
|
|
4095
4806
|
const parentChangeTree = parent[$changes];
|
|
4096
|
-
let changes = this.changes.
|
|
4807
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4097
4808
|
if (changes === undefined) {
|
|
4098
|
-
changes =
|
|
4099
|
-
this.changes.
|
|
4809
|
+
changes = {};
|
|
4810
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4100
4811
|
}
|
|
4101
4812
|
// DELETE / DELETE BY REF ID
|
|
4102
|
-
changes
|
|
4813
|
+
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
4103
4814
|
}
|
|
4104
4815
|
else {
|
|
4105
4816
|
// delete all "tagged" properties.
|
|
4106
|
-
metadata[
|
|
4817
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4107
4818
|
}
|
|
4108
4819
|
}
|
|
4109
4820
|
else {
|
|
4110
4821
|
// delete only tagged properties
|
|
4111
|
-
metadata[
|
|
4822
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4112
4823
|
}
|
|
4113
4824
|
// remove tag
|
|
4114
4825
|
if (this.tags && this.tags.has(changeTree)) {
|
|
@@ -4166,10 +4877,11 @@
|
|
|
4166
4877
|
exports.encode = encode;
|
|
4167
4878
|
exports.encodeKeyValueOperation = encodeArray;
|
|
4168
4879
|
exports.encodeSchemaOperation = encodeSchemaOperation;
|
|
4880
|
+
exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
|
|
4881
|
+
exports.getRawChangesCallback = getRawChangesCallback;
|
|
4169
4882
|
exports.registerType = registerType;
|
|
4883
|
+
exports.schema = schema;
|
|
4170
4884
|
exports.type = type;
|
|
4171
4885
|
exports.view = view;
|
|
4172
4886
|
|
|
4173
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4174
|
-
|
|
4175
4887
|
}));
|