@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/esm/index.mjs
CHANGED
|
@@ -23,6 +23,7 @@ var OPERATION;
|
|
|
23
23
|
OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
|
|
24
24
|
OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
|
|
25
25
|
OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
|
|
26
|
+
OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
|
|
26
27
|
})(OPERATION || (OPERATION = {}));
|
|
27
28
|
|
|
28
29
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
@@ -42,11 +43,6 @@ const $changes = Symbol('$changes');
|
|
|
42
43
|
* (MapSchema, ArraySchema, etc.)
|
|
43
44
|
*/
|
|
44
45
|
const $childType = Symbol('$childType');
|
|
45
|
-
/**
|
|
46
|
-
* Special ChangeTree property to identify new instances
|
|
47
|
-
* (Once they're encoded, they're not new anymore)
|
|
48
|
-
*/
|
|
49
|
-
const $isNew = Symbol("$isNew");
|
|
50
46
|
/**
|
|
51
47
|
* Optional "discard" method for custom types (ArraySchema)
|
|
52
48
|
* (Discards changes for next serialization)
|
|
@@ -56,6 +52,14 @@ const $onEncodeEnd = Symbol('$onEncodeEnd');
|
|
|
56
52
|
* When decoding, this method is called after the instance is fully decoded
|
|
57
53
|
*/
|
|
58
54
|
const $onDecodeEnd = Symbol("$onDecodeEnd");
|
|
55
|
+
/**
|
|
56
|
+
* Metadata
|
|
57
|
+
*/
|
|
58
|
+
const $descriptors = Symbol("$descriptors");
|
|
59
|
+
const $numFields = "$__numFields";
|
|
60
|
+
const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
|
|
61
|
+
const $viewFieldIndexes = "$__viewFieldIndexes";
|
|
62
|
+
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
59
63
|
|
|
60
64
|
const registeredTypes = {};
|
|
61
65
|
const identifiers = new Map();
|
|
@@ -67,177 +71,416 @@ function getType(identifier) {
|
|
|
67
71
|
return registeredTypes[identifier];
|
|
68
72
|
}
|
|
69
73
|
|
|
74
|
+
class TypeContext {
|
|
75
|
+
/**
|
|
76
|
+
* For inheritance support
|
|
77
|
+
* Keeps track of which classes extends which. (parent -> children)
|
|
78
|
+
*/
|
|
79
|
+
static { this.inheritedTypes = new Map(); }
|
|
80
|
+
static register(target) {
|
|
81
|
+
const parent = Object.getPrototypeOf(target);
|
|
82
|
+
if (parent !== Schema) {
|
|
83
|
+
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
84
|
+
if (!inherits) {
|
|
85
|
+
inherits = new Set();
|
|
86
|
+
TypeContext.inheritedTypes.set(parent, inherits);
|
|
87
|
+
}
|
|
88
|
+
inherits.add(target);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
constructor(rootClass) {
|
|
92
|
+
this.types = {};
|
|
93
|
+
this.schemas = new Map();
|
|
94
|
+
this.hasFilters = false;
|
|
95
|
+
this.parentFiltered = {};
|
|
96
|
+
if (rootClass) {
|
|
97
|
+
this.discoverTypes(rootClass);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
has(schema) {
|
|
101
|
+
return this.schemas.has(schema);
|
|
102
|
+
}
|
|
103
|
+
get(typeid) {
|
|
104
|
+
return this.types[typeid];
|
|
105
|
+
}
|
|
106
|
+
add(schema, typeid = this.schemas.size) {
|
|
107
|
+
// skip if already registered
|
|
108
|
+
if (this.schemas.has(schema)) {
|
|
109
|
+
return false;
|
|
110
|
+
}
|
|
111
|
+
this.types[typeid] = schema;
|
|
112
|
+
//
|
|
113
|
+
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
114
|
+
//
|
|
115
|
+
if (schema[Symbol.metadata] === undefined) {
|
|
116
|
+
Metadata.initialize(schema);
|
|
117
|
+
}
|
|
118
|
+
this.schemas.set(schema, typeid);
|
|
119
|
+
return true;
|
|
120
|
+
}
|
|
121
|
+
getTypeId(klass) {
|
|
122
|
+
return this.schemas.get(klass);
|
|
123
|
+
}
|
|
124
|
+
discoverTypes(klass, parentIndex, parentFieldViewTag) {
|
|
125
|
+
if (!this.add(klass)) {
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
// add classes inherited from this base class
|
|
129
|
+
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
130
|
+
this.discoverTypes(child, parentIndex, parentFieldViewTag);
|
|
131
|
+
});
|
|
132
|
+
// add parent classes
|
|
133
|
+
let parent = klass;
|
|
134
|
+
while ((parent = Object.getPrototypeOf(parent)) &&
|
|
135
|
+
parent !== Schema && // stop at root (Schema)
|
|
136
|
+
parent !== Function.prototype // stop at root (non-Schema)
|
|
137
|
+
) {
|
|
138
|
+
this.discoverTypes(parent);
|
|
139
|
+
}
|
|
140
|
+
const metadata = (klass[Symbol.metadata] ??= {});
|
|
141
|
+
// if any schema/field has filters, mark "context" as having filters.
|
|
142
|
+
if (metadata[$viewFieldIndexes]) {
|
|
143
|
+
this.hasFilters = true;
|
|
144
|
+
}
|
|
145
|
+
if (parentFieldViewTag !== undefined) {
|
|
146
|
+
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
147
|
+
}
|
|
148
|
+
for (const fieldIndex in metadata) {
|
|
149
|
+
const index = fieldIndex;
|
|
150
|
+
const fieldType = metadata[index].type;
|
|
151
|
+
const viewTag = metadata[index].tag;
|
|
152
|
+
if (typeof (fieldType) === "string") {
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
if (Array.isArray(fieldType)) {
|
|
156
|
+
const type = fieldType[0];
|
|
157
|
+
// skip primitive types
|
|
158
|
+
if (type === "string") {
|
|
159
|
+
continue;
|
|
160
|
+
}
|
|
161
|
+
this.discoverTypes(type, index, viewTag);
|
|
162
|
+
}
|
|
163
|
+
else if (typeof (fieldType) === "function") {
|
|
164
|
+
this.discoverTypes(fieldType, viewTag);
|
|
165
|
+
}
|
|
166
|
+
else {
|
|
167
|
+
const type = Object.values(fieldType)[0];
|
|
168
|
+
// skip primitive types
|
|
169
|
+
if (typeof (type) === "string") {
|
|
170
|
+
continue;
|
|
171
|
+
}
|
|
172
|
+
this.discoverTypes(type, index, viewTag);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
function getNormalizedType(type) {
|
|
179
|
+
return (Array.isArray(type))
|
|
180
|
+
? { array: type[0] }
|
|
181
|
+
: (typeof (type['type']) !== "undefined")
|
|
182
|
+
? type['type']
|
|
183
|
+
: type;
|
|
184
|
+
}
|
|
70
185
|
const Metadata = {
|
|
71
|
-
addField(metadata, index,
|
|
186
|
+
addField(metadata, index, name, type, descriptor) {
|
|
72
187
|
if (index > 64) {
|
|
73
|
-
throw new Error(`Can't define field '${
|
|
188
|
+
throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
|
|
74
189
|
}
|
|
75
|
-
metadata[
|
|
190
|
+
metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
|
|
76
191
|
{
|
|
77
|
-
type: (
|
|
78
|
-
? { array: type[0] }
|
|
79
|
-
: type,
|
|
192
|
+
type: getNormalizedType(type),
|
|
80
193
|
index,
|
|
81
|
-
|
|
194
|
+
name,
|
|
82
195
|
});
|
|
196
|
+
// create "descriptors" map
|
|
197
|
+
Object.defineProperty(metadata, $descriptors, {
|
|
198
|
+
value: metadata[$descriptors] || {},
|
|
199
|
+
enumerable: false,
|
|
200
|
+
configurable: true,
|
|
201
|
+
});
|
|
202
|
+
if (descriptor) {
|
|
203
|
+
// for encoder
|
|
204
|
+
metadata[$descriptors][name] = descriptor;
|
|
205
|
+
metadata[$descriptors][`_${name}`] = {
|
|
206
|
+
value: undefined,
|
|
207
|
+
writable: true,
|
|
208
|
+
enumerable: false,
|
|
209
|
+
configurable: true,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
else {
|
|
213
|
+
// for decoder
|
|
214
|
+
metadata[$descriptors][name] = {
|
|
215
|
+
value: undefined,
|
|
216
|
+
writable: true,
|
|
217
|
+
enumerable: true,
|
|
218
|
+
configurable: true,
|
|
219
|
+
};
|
|
220
|
+
}
|
|
83
221
|
// map -1 as last field index
|
|
84
|
-
Object.defineProperty(metadata,
|
|
222
|
+
Object.defineProperty(metadata, $numFields, {
|
|
85
223
|
value: index,
|
|
86
224
|
enumerable: false,
|
|
87
225
|
configurable: true
|
|
88
226
|
});
|
|
89
|
-
// map
|
|
90
|
-
Object.defineProperty(metadata,
|
|
91
|
-
value:
|
|
227
|
+
// map field name => index (non enumerable)
|
|
228
|
+
Object.defineProperty(metadata, name, {
|
|
229
|
+
value: index,
|
|
92
230
|
enumerable: false,
|
|
93
231
|
configurable: true,
|
|
94
232
|
});
|
|
233
|
+
// if child Ref/complex type, add to -4
|
|
234
|
+
if (typeof (metadata[index].type) !== "string") {
|
|
235
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
236
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
237
|
+
value: [],
|
|
238
|
+
enumerable: false,
|
|
239
|
+
configurable: true,
|
|
240
|
+
});
|
|
241
|
+
}
|
|
242
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
243
|
+
}
|
|
95
244
|
},
|
|
96
245
|
setTag(metadata, fieldName, tag) {
|
|
246
|
+
const index = metadata[fieldName];
|
|
247
|
+
const field = metadata[index];
|
|
97
248
|
// add 'tag' to the field
|
|
98
|
-
const field = metadata[fieldName];
|
|
99
249
|
field.tag = tag;
|
|
100
|
-
if (!metadata[
|
|
250
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
101
251
|
// -2: all field indexes with "view" tag
|
|
102
|
-
Object.defineProperty(metadata,
|
|
252
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
103
253
|
value: [],
|
|
104
254
|
enumerable: false,
|
|
105
255
|
configurable: true
|
|
106
256
|
});
|
|
107
257
|
// -3: field indexes by "view" tag
|
|
108
|
-
Object.defineProperty(metadata,
|
|
258
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
109
259
|
value: {},
|
|
110
260
|
enumerable: false,
|
|
111
261
|
configurable: true
|
|
112
262
|
});
|
|
113
263
|
}
|
|
114
|
-
metadata[
|
|
115
|
-
if (!metadata[
|
|
116
|
-
metadata[
|
|
264
|
+
metadata[$viewFieldIndexes].push(index);
|
|
265
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
266
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
117
267
|
}
|
|
118
|
-
metadata[
|
|
268
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
119
269
|
},
|
|
120
270
|
setFields(target, fields) {
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
// if
|
|
128
|
-
|
|
271
|
+
// for inheritance support
|
|
272
|
+
const constructor = target.prototype.constructor;
|
|
273
|
+
TypeContext.register(constructor);
|
|
274
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
275
|
+
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
276
|
+
const metadata = Metadata.initialize(constructor);
|
|
277
|
+
// Use Schema's methods if not defined in the class
|
|
278
|
+
if (!constructor[$track]) {
|
|
279
|
+
constructor[$track] = Schema[$track];
|
|
280
|
+
}
|
|
281
|
+
if (!constructor[$encoder]) {
|
|
282
|
+
constructor[$encoder] = Schema[$encoder];
|
|
283
|
+
}
|
|
284
|
+
if (!constructor[$decoder]) {
|
|
285
|
+
constructor[$decoder] = Schema[$decoder];
|
|
286
|
+
}
|
|
287
|
+
if (!constructor.prototype.toJSON) {
|
|
288
|
+
constructor.prototype.toJSON = Schema.prototype.toJSON;
|
|
289
|
+
}
|
|
290
|
+
//
|
|
291
|
+
// detect index for this field, considering inheritance
|
|
292
|
+
//
|
|
293
|
+
let fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
294
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
295
|
+
?? -1; // no fields defined
|
|
296
|
+
fieldIndex++;
|
|
129
297
|
for (const field in fields) {
|
|
130
298
|
const type = fields[field];
|
|
299
|
+
const normalizedType = getNormalizedType(type);
|
|
131
300
|
// FIXME: this code is duplicated from @type() annotation
|
|
132
301
|
const complexTypeKlass = (Array.isArray(type))
|
|
133
302
|
? getType("array")
|
|
134
303
|
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
135
|
-
|
|
136
|
-
|
|
304
|
+
const childType = (complexTypeKlass)
|
|
305
|
+
? Object.values(type)[0]
|
|
306
|
+
: normalizedType;
|
|
307
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
308
|
+
fieldIndex++;
|
|
137
309
|
}
|
|
310
|
+
return target;
|
|
138
311
|
},
|
|
139
312
|
isDeprecated(metadata, field) {
|
|
140
313
|
return metadata[field].deprecated === true;
|
|
141
314
|
},
|
|
315
|
+
init(klass) {
|
|
316
|
+
//
|
|
317
|
+
// Used only to initialize an empty Schema (Encoder#constructor)
|
|
318
|
+
// TODO: remove/refactor this...
|
|
319
|
+
//
|
|
320
|
+
const metadata = {};
|
|
321
|
+
klass[Symbol.metadata] = metadata;
|
|
322
|
+
Object.defineProperty(metadata, $numFields, {
|
|
323
|
+
value: 0,
|
|
324
|
+
enumerable: false,
|
|
325
|
+
configurable: true,
|
|
326
|
+
});
|
|
327
|
+
},
|
|
328
|
+
initialize(constructor) {
|
|
329
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
330
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
331
|
+
let metadata = constructor[Symbol.metadata] ?? Object.create(null);
|
|
332
|
+
// make sure inherited classes have their own metadata object.
|
|
333
|
+
if (parentClass !== Schema && metadata === parentMetadata) {
|
|
334
|
+
metadata = Object.create(null);
|
|
335
|
+
if (parentMetadata) {
|
|
336
|
+
//
|
|
337
|
+
// assign parent metadata to current
|
|
338
|
+
//
|
|
339
|
+
Object.setPrototypeOf(metadata, parentMetadata);
|
|
340
|
+
// $numFields
|
|
341
|
+
Object.defineProperty(metadata, $numFields, {
|
|
342
|
+
value: parentMetadata[$numFields],
|
|
343
|
+
enumerable: false,
|
|
344
|
+
configurable: true,
|
|
345
|
+
writable: true,
|
|
346
|
+
});
|
|
347
|
+
// $viewFieldIndexes / $fieldIndexesByViewTag
|
|
348
|
+
if (parentMetadata[$viewFieldIndexes] !== undefined) {
|
|
349
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
350
|
+
value: [...parentMetadata[$viewFieldIndexes]],
|
|
351
|
+
enumerable: false,
|
|
352
|
+
configurable: true,
|
|
353
|
+
writable: true,
|
|
354
|
+
});
|
|
355
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
356
|
+
value: { ...parentMetadata[$fieldIndexesByViewTag] },
|
|
357
|
+
enumerable: false,
|
|
358
|
+
configurable: true,
|
|
359
|
+
writable: true,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
// $refTypeFieldIndexes
|
|
363
|
+
if (parentMetadata[$refTypeFieldIndexes] !== undefined) {
|
|
364
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
365
|
+
value: [...parentMetadata[$refTypeFieldIndexes]],
|
|
366
|
+
enumerable: false,
|
|
367
|
+
configurable: true,
|
|
368
|
+
writable: true,
|
|
369
|
+
});
|
|
370
|
+
}
|
|
371
|
+
// $descriptors
|
|
372
|
+
Object.defineProperty(metadata, $descriptors, {
|
|
373
|
+
value: { ...parentMetadata[$descriptors] },
|
|
374
|
+
enumerable: false,
|
|
375
|
+
configurable: true,
|
|
376
|
+
writable: true,
|
|
377
|
+
});
|
|
378
|
+
}
|
|
379
|
+
}
|
|
380
|
+
constructor[Symbol.metadata] = metadata;
|
|
381
|
+
return metadata;
|
|
382
|
+
},
|
|
142
383
|
isValidInstance(klass) {
|
|
143
384
|
return (klass.constructor[Symbol.metadata] &&
|
|
144
|
-
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata],
|
|
385
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
145
386
|
},
|
|
146
387
|
getFields(klass) {
|
|
147
388
|
const metadata = klass[Symbol.metadata];
|
|
148
389
|
const fields = {};
|
|
149
|
-
for (let i = 0; i <= metadata[
|
|
150
|
-
fields[metadata[i]] = metadata[
|
|
390
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
391
|
+
fields[metadata[i].name] = metadata[i].type;
|
|
151
392
|
}
|
|
152
393
|
return fields;
|
|
153
394
|
}
|
|
154
395
|
};
|
|
155
396
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
this.refCount = new WeakMap();
|
|
161
|
-
// all changes
|
|
162
|
-
this.allChanges = new Map();
|
|
163
|
-
this.allFilteredChanges = new Map();
|
|
164
|
-
// pending changes to be encoded
|
|
165
|
-
this.changes = new Map();
|
|
166
|
-
this.filteredChanges = new Map();
|
|
167
|
-
}
|
|
168
|
-
getNextUniqueId() {
|
|
169
|
-
return this.nextUniqueId++;
|
|
397
|
+
function setOperationAtIndex(changeSet, index) {
|
|
398
|
+
const operationsIndex = changeSet.indexes[index];
|
|
399
|
+
if (operationsIndex === undefined) {
|
|
400
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
170
401
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
this.refCount.set(changeTree, refCount + 1);
|
|
402
|
+
else {
|
|
403
|
+
changeSet.operations[operationsIndex] = index;
|
|
174
404
|
}
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
181
|
-
this.allFilteredChanges.delete(changeTree);
|
|
182
|
-
this.filteredChanges.delete(changeTree);
|
|
183
|
-
}
|
|
184
|
-
this.refCount.delete(changeTree);
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
this.refCount.set(changeTree, refCount - 1);
|
|
188
|
-
}
|
|
189
|
-
changeTree.forEachChild((child, _) => this.remove(child));
|
|
405
|
+
}
|
|
406
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
407
|
+
const operationsIndex = changeSet.indexes[index];
|
|
408
|
+
if (operationsIndex !== undefined) {
|
|
409
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
190
410
|
}
|
|
191
|
-
|
|
192
|
-
|
|
411
|
+
delete changeSet.indexes[index];
|
|
412
|
+
}
|
|
413
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
414
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
415
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
193
416
|
}
|
|
194
417
|
}
|
|
195
418
|
class ChangeTree {
|
|
196
|
-
static { _a$5 = $isNew; }
|
|
197
|
-
;
|
|
198
419
|
constructor(ref) {
|
|
199
|
-
this.
|
|
200
|
-
this.
|
|
201
|
-
this.
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
420
|
+
this.isFiltered = false;
|
|
421
|
+
this.isPartiallyFiltered = false;
|
|
422
|
+
this.indexedOperations = {};
|
|
423
|
+
//
|
|
424
|
+
// TODO:
|
|
425
|
+
// try storing the index + operation per item.
|
|
426
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
427
|
+
//
|
|
428
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
429
|
+
//
|
|
430
|
+
this.changes = { indexes: {}, operations: [] };
|
|
431
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
432
|
+
/**
|
|
433
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
434
|
+
*/
|
|
435
|
+
this.isNew = true;
|
|
206
436
|
this.ref = ref;
|
|
437
|
+
//
|
|
438
|
+
// Does this structure have "filters" declared?
|
|
439
|
+
//
|
|
440
|
+
if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
|
|
441
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
442
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
443
|
+
}
|
|
207
444
|
}
|
|
208
445
|
setRoot(root) {
|
|
209
446
|
this.root = root;
|
|
210
|
-
this.root.add(this);
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
447
|
+
const isNewChangeTree = this.root.add(this);
|
|
448
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
449
|
+
if (this.root.types.hasFilters) {
|
|
450
|
+
//
|
|
451
|
+
// At Schema initialization, the "root" structure might not be available
|
|
452
|
+
// yet, as it only does once the "Encoder" has been set up.
|
|
453
|
+
//
|
|
454
|
+
// So the "parent" may be already set without a "root".
|
|
455
|
+
//
|
|
456
|
+
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
457
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
458
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
459
|
+
if (isNewChangeTree) {
|
|
460
|
+
this.root.allFilteredChanges.push(this);
|
|
461
|
+
}
|
|
462
|
+
}
|
|
463
|
+
}
|
|
220
464
|
if (!this.isFiltered) {
|
|
221
|
-
|
|
465
|
+
enqueueChangeTree(root, this, 'changes');
|
|
466
|
+
if (isNewChangeTree) {
|
|
467
|
+
this.root.allChanges.push(this);
|
|
468
|
+
}
|
|
222
469
|
}
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
470
|
+
// Recursively set root on child structures
|
|
471
|
+
if (metadata) {
|
|
472
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
473
|
+
const field = metadata[index];
|
|
474
|
+
const value = this.ref[field.name];
|
|
475
|
+
value?.[$changes].setRoot(root);
|
|
476
|
+
});
|
|
228
477
|
}
|
|
229
|
-
if (
|
|
230
|
-
|
|
478
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
479
|
+
// MapSchema / ArraySchema, etc.
|
|
480
|
+
this.ref.forEach((value, key) => {
|
|
481
|
+
value[$changes].setRoot(root);
|
|
482
|
+
});
|
|
231
483
|
}
|
|
232
|
-
this.forEachChild((changeTree, _) => {
|
|
233
|
-
changeTree.setRoot(root);
|
|
234
|
-
});
|
|
235
|
-
// this.allChanges.forEach((_, index) => {
|
|
236
|
-
// const childRef = this.ref[$getByIndex](index);
|
|
237
|
-
// if (childRef && childRef[$changes]) {
|
|
238
|
-
// childRef[$changes].setRoot(root);
|
|
239
|
-
// }
|
|
240
|
-
// });
|
|
241
484
|
}
|
|
242
485
|
setParent(parent, root, parentIndex) {
|
|
243
486
|
this.parent = parent;
|
|
@@ -246,83 +489,104 @@ class ChangeTree {
|
|
|
246
489
|
if (!root) {
|
|
247
490
|
return;
|
|
248
491
|
}
|
|
249
|
-
|
|
492
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
250
493
|
// skip if parent is already set
|
|
251
|
-
if (root
|
|
252
|
-
this.
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
494
|
+
if (root !== this.root) {
|
|
495
|
+
this.root = root;
|
|
496
|
+
const isNewChangeTree = root.add(this);
|
|
497
|
+
if (root.types.hasFilters) {
|
|
498
|
+
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
499
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
500
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
501
|
+
if (isNewChangeTree) {
|
|
502
|
+
this.root.allFilteredChanges.push(this);
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
}
|
|
506
|
+
if (!this.isFiltered) {
|
|
507
|
+
enqueueChangeTree(root, this, 'changes');
|
|
508
|
+
if (isNewChangeTree) {
|
|
509
|
+
this.root.allChanges.push(this);
|
|
510
|
+
}
|
|
511
|
+
}
|
|
256
512
|
}
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
if (!this.isFiltered) {
|
|
260
|
-
this.root.changes.set(this, this.changes);
|
|
513
|
+
else {
|
|
514
|
+
root.add(this);
|
|
261
515
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
516
|
+
// assign same parent on child structures
|
|
517
|
+
if (metadata) {
|
|
518
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
519
|
+
const field = metadata[index];
|
|
520
|
+
const value = this.ref[field.name];
|
|
521
|
+
value?.[$changes].setParent(this.ref, root, index);
|
|
522
|
+
// try { throw new Error(); } catch (e) {
|
|
523
|
+
// console.log(e.stack);
|
|
524
|
+
// }
|
|
525
|
+
});
|
|
265
526
|
}
|
|
266
|
-
else {
|
|
267
|
-
|
|
527
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
528
|
+
// MapSchema / ArraySchema, etc.
|
|
529
|
+
this.ref.forEach((value, key) => {
|
|
530
|
+
value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
|
|
531
|
+
});
|
|
268
532
|
}
|
|
269
|
-
this.ensureRefId();
|
|
270
|
-
this.forEachChild((changeTree, atIndex) => {
|
|
271
|
-
changeTree.setParent(this.ref, root, atIndex);
|
|
272
|
-
});
|
|
273
533
|
}
|
|
274
534
|
forEachChild(callback) {
|
|
275
535
|
//
|
|
276
536
|
// assign same parent on child structures
|
|
277
537
|
//
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
const value = this.ref[field];
|
|
283
|
-
if (value
|
|
284
|
-
callback(value[$changes],
|
|
538
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
539
|
+
if (metadata) {
|
|
540
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
541
|
+
const field = metadata[index];
|
|
542
|
+
const value = this.ref[field.name];
|
|
543
|
+
if (value) {
|
|
544
|
+
callback(value[$changes], index);
|
|
285
545
|
}
|
|
286
|
-
}
|
|
546
|
+
});
|
|
287
547
|
}
|
|
288
|
-
else if (typeof (this.ref)
|
|
548
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
289
549
|
// MapSchema / ArraySchema, etc.
|
|
290
550
|
this.ref.forEach((value, key) => {
|
|
291
|
-
|
|
292
|
-
callback(value[$changes], this.ref[$changes].indexes[key]);
|
|
293
|
-
}
|
|
551
|
+
callback(value[$changes], this.indexes[key] ?? key);
|
|
294
552
|
});
|
|
295
553
|
}
|
|
296
554
|
}
|
|
297
555
|
operation(op) {
|
|
298
|
-
|
|
299
|
-
this.
|
|
556
|
+
// operations without index use negative values to represent them
|
|
557
|
+
// this is checked during .encode() time.
|
|
558
|
+
this.changes.operations.push(-op);
|
|
559
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
300
560
|
}
|
|
301
561
|
change(index, operation = OPERATION.ADD) {
|
|
302
|
-
const metadata = this.ref
|
|
303
|
-
const isFiltered = this.isFiltered || (metadata
|
|
562
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
563
|
+
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
304
564
|
const changeSet = (isFiltered)
|
|
305
565
|
? this.filteredChanges
|
|
306
566
|
: this.changes;
|
|
307
|
-
const previousOperation =
|
|
567
|
+
const previousOperation = this.indexedOperations[index];
|
|
308
568
|
if (!previousOperation || previousOperation === OPERATION.DELETE) {
|
|
309
569
|
const op = (!previousOperation)
|
|
310
570
|
? operation
|
|
311
571
|
: (previousOperation === OPERATION.DELETE)
|
|
312
572
|
? OPERATION.DELETE_AND_ADD
|
|
313
573
|
: operation;
|
|
314
|
-
|
|
574
|
+
//
|
|
575
|
+
// TODO: are DELETE operations being encoded as ADD here ??
|
|
576
|
+
//
|
|
577
|
+
this.indexedOperations[index] = op;
|
|
315
578
|
}
|
|
316
|
-
|
|
317
|
-
// TODO: are DELETE operations being encoded as ADD here ??
|
|
318
|
-
//
|
|
579
|
+
setOperationAtIndex(changeSet, index);
|
|
319
580
|
if (isFiltered) {
|
|
320
|
-
this.allFilteredChanges
|
|
321
|
-
this.root
|
|
581
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
582
|
+
if (this.root) {
|
|
583
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
584
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
585
|
+
}
|
|
322
586
|
}
|
|
323
587
|
else {
|
|
324
|
-
this.allChanges
|
|
325
|
-
this.root
|
|
588
|
+
setOperationAtIndex(this.allChanges, index);
|
|
589
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
326
590
|
}
|
|
327
591
|
}
|
|
328
592
|
shiftChangeIndexes(shiftIndex) {
|
|
@@ -334,12 +598,15 @@ class ChangeTree {
|
|
|
334
598
|
const changeSet = (this.isFiltered)
|
|
335
599
|
? this.filteredChanges
|
|
336
600
|
: this.changes;
|
|
337
|
-
const
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
601
|
+
const newIndexedOperations = {};
|
|
602
|
+
const newIndexes = {};
|
|
603
|
+
for (const index in this.indexedOperations) {
|
|
604
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
605
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
342
606
|
}
|
|
607
|
+
this.indexedOperations = newIndexedOperations;
|
|
608
|
+
changeSet.indexes = newIndexes;
|
|
609
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
343
610
|
}
|
|
344
611
|
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
345
612
|
//
|
|
@@ -355,33 +622,42 @@ class ChangeTree {
|
|
|
355
622
|
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
356
623
|
}
|
|
357
624
|
}
|
|
358
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
625
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
626
|
+
const newIndexes = {};
|
|
627
|
+
for (const key in changeSet.indexes) {
|
|
628
|
+
const index = changeSet.indexes[key];
|
|
629
|
+
if (index > startIndex) {
|
|
630
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
364
631
|
}
|
|
365
|
-
|
|
632
|
+
else {
|
|
633
|
+
newIndexes[key] = index;
|
|
634
|
+
}
|
|
635
|
+
}
|
|
636
|
+
changeSet.indexes = newIndexes;
|
|
637
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
638
|
+
const index = changeSet.operations[i];
|
|
639
|
+
if (index > startIndex) {
|
|
640
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
641
|
+
}
|
|
642
|
+
}
|
|
366
643
|
}
|
|
367
644
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
this.
|
|
372
|
-
this.
|
|
373
|
-
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
645
|
+
this.indexedOperations[index] = operation;
|
|
646
|
+
if (this.filteredChanges) {
|
|
647
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
648
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
649
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
374
650
|
}
|
|
375
651
|
else {
|
|
376
|
-
this.allChanges
|
|
377
|
-
this.changes
|
|
378
|
-
this.root
|
|
652
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
653
|
+
setOperationAtIndex(this.changes, index);
|
|
654
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
379
655
|
}
|
|
380
656
|
}
|
|
381
657
|
getType(index) {
|
|
382
658
|
if (Metadata.isValidInstance(this.ref)) {
|
|
383
|
-
const metadata = this.ref
|
|
384
|
-
return metadata[
|
|
659
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
660
|
+
return metadata[index].type;
|
|
385
661
|
}
|
|
386
662
|
else {
|
|
387
663
|
//
|
|
@@ -394,8 +670,7 @@ class ChangeTree {
|
|
|
394
670
|
}
|
|
395
671
|
}
|
|
396
672
|
getChange(index) {
|
|
397
|
-
|
|
398
|
-
return this.changes.get(index) ?? this.filteredChanges.get(index);
|
|
673
|
+
return this.indexedOperations[index];
|
|
399
674
|
}
|
|
400
675
|
//
|
|
401
676
|
// used during `.encode()`
|
|
@@ -416,16 +691,14 @@ class ChangeTree {
|
|
|
416
691
|
}
|
|
417
692
|
return;
|
|
418
693
|
}
|
|
419
|
-
const
|
|
420
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
421
|
-
const changeSet = (isFiltered)
|
|
694
|
+
const changeSet = (this.filteredChanges)
|
|
422
695
|
? this.filteredChanges
|
|
423
696
|
: this.changes;
|
|
697
|
+
this.indexedOperations[index] = operation ?? OPERATION.DELETE;
|
|
698
|
+
setOperationAtIndex(changeSet, index);
|
|
424
699
|
const previousValue = this.getValue(index);
|
|
425
|
-
changeSet.set(index, operation ?? OPERATION.DELETE);
|
|
426
700
|
// remove `root` reference
|
|
427
701
|
if (previousValue && previousValue[$changes]) {
|
|
428
|
-
previousValue[$changes].root = undefined;
|
|
429
702
|
//
|
|
430
703
|
// FIXME: this.root is "undefined"
|
|
431
704
|
//
|
|
@@ -439,22 +712,26 @@ class ChangeTree {
|
|
|
439
712
|
this.root?.remove(previousValue[$changes]);
|
|
440
713
|
}
|
|
441
714
|
//
|
|
442
|
-
// FIXME: this is looking a
|
|
715
|
+
// FIXME: this is looking a ugly and repeated
|
|
443
716
|
//
|
|
444
|
-
if (
|
|
445
|
-
this.
|
|
446
|
-
this.
|
|
717
|
+
if (this.filteredChanges) {
|
|
718
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
719
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
447
720
|
}
|
|
448
721
|
else {
|
|
449
|
-
this.
|
|
450
|
-
this.
|
|
722
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
723
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
451
724
|
}
|
|
452
725
|
}
|
|
453
726
|
endEncode() {
|
|
454
|
-
this.
|
|
727
|
+
this.indexedOperations = {};
|
|
728
|
+
// // clear changes
|
|
729
|
+
// this.changes.indexes = {};
|
|
730
|
+
// this.changes.operations.length = 0;
|
|
731
|
+
// ArraySchema and MapSchema have a custom "encode end" method
|
|
455
732
|
this.ref[$onEncodeEnd]?.();
|
|
456
733
|
// Not a new instance anymore
|
|
457
|
-
|
|
734
|
+
this.isNew = false;
|
|
458
735
|
}
|
|
459
736
|
discard(discardAll = false) {
|
|
460
737
|
//
|
|
@@ -463,13 +740,22 @@ class ChangeTree {
|
|
|
463
740
|
// REPLACE in case same key is used on next patches.
|
|
464
741
|
//
|
|
465
742
|
this.ref[$onEncodeEnd]?.();
|
|
466
|
-
this.
|
|
467
|
-
this.
|
|
468
|
-
|
|
469
|
-
this.
|
|
743
|
+
this.indexedOperations = {};
|
|
744
|
+
this.changes.indexes = {};
|
|
745
|
+
this.changes.operations.length = 0;
|
|
746
|
+
this.changes.queueRootIndex = undefined;
|
|
747
|
+
if (this.filteredChanges !== undefined) {
|
|
748
|
+
this.filteredChanges.indexes = {};
|
|
749
|
+
this.filteredChanges.operations.length = 0;
|
|
750
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
751
|
+
}
|
|
470
752
|
if (discardAll) {
|
|
471
|
-
this.allChanges.
|
|
472
|
-
this.
|
|
753
|
+
this.allChanges.indexes = {};
|
|
754
|
+
this.allChanges.operations.length = 0;
|
|
755
|
+
if (this.allFilteredChanges !== undefined) {
|
|
756
|
+
this.allFilteredChanges.indexes = {};
|
|
757
|
+
this.allFilteredChanges.operations.length = 0;
|
|
758
|
+
}
|
|
473
759
|
// remove children references
|
|
474
760
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
475
761
|
}
|
|
@@ -478,12 +764,13 @@ class ChangeTree {
|
|
|
478
764
|
* Recursively discard all changes from this, and child structures.
|
|
479
765
|
*/
|
|
480
766
|
discardAll() {
|
|
481
|
-
this.
|
|
482
|
-
|
|
767
|
+
const keys = Object.keys(this.indexedOperations);
|
|
768
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
769
|
+
const value = this.getValue(Number(keys[i]));
|
|
483
770
|
if (value && value[$changes]) {
|
|
484
771
|
value[$changes].discardAll();
|
|
485
772
|
}
|
|
486
|
-
}
|
|
773
|
+
}
|
|
487
774
|
this.discard();
|
|
488
775
|
}
|
|
489
776
|
ensureRefId() {
|
|
@@ -494,35 +781,49 @@ class ChangeTree {
|
|
|
494
781
|
this.refId = this.root.getNextUniqueId();
|
|
495
782
|
}
|
|
496
783
|
get changed() {
|
|
497
|
-
return this.
|
|
784
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
498
785
|
}
|
|
499
|
-
checkIsFiltered(parent, parentIndex) {
|
|
786
|
+
checkIsFiltered(metadata, parent, parentIndex) {
|
|
500
787
|
// Detect if current structure has "filters" declared
|
|
501
|
-
this.isPartiallyFiltered =
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
const metadata = parent['constructor'][Symbol.metadata];
|
|
506
|
-
const fieldName = metadata?.[parentIndex];
|
|
507
|
-
const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
|
|
508
|
-
this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
|
|
509
|
-
parent = parent[$changes].parent;
|
|
788
|
+
this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
|
|
789
|
+
if (this.isPartiallyFiltered) {
|
|
790
|
+
this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
|
|
791
|
+
this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
|
|
510
792
|
}
|
|
793
|
+
// skip if parent is not set
|
|
794
|
+
if (!parent) {
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
798
|
+
const parentChangeTree = parent[$changes];
|
|
799
|
+
parent = parentChangeTree.parent;
|
|
800
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
801
|
+
}
|
|
802
|
+
const parentMetadata = parent.constructor?.[Symbol.metadata];
|
|
803
|
+
this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
511
804
|
//
|
|
512
805
|
// TODO: refactor this!
|
|
513
806
|
//
|
|
514
807
|
// swapping `changes` and `filteredChanges` is required here
|
|
515
808
|
// because "isFiltered" may not be imedialely available on `change()`
|
|
516
809
|
//
|
|
517
|
-
if (this.isFiltered
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
this.changes
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
810
|
+
if (this.isFiltered) {
|
|
811
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
812
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
813
|
+
if (this.changes.operations.length > 0) {
|
|
814
|
+
// swap changes reference
|
|
815
|
+
const changes = this.changes;
|
|
816
|
+
this.changes = this.filteredChanges;
|
|
817
|
+
this.filteredChanges = changes;
|
|
818
|
+
// swap "all changes" reference
|
|
819
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
820
|
+
this.allFilteredChanges = this.allChanges;
|
|
821
|
+
this.allChanges = allFilteredChanges;
|
|
822
|
+
// console.log("SWAP =>", {
|
|
823
|
+
// "this.allFilteredChanges": this.allFilteredChanges,
|
|
824
|
+
// "this.allChanges": this.allChanges
|
|
825
|
+
// })
|
|
826
|
+
}
|
|
526
827
|
}
|
|
527
828
|
}
|
|
528
829
|
}
|
|
@@ -559,26 +860,29 @@ try {
|
|
|
559
860
|
textEncoder = new TextEncoder();
|
|
560
861
|
}
|
|
561
862
|
catch (e) { }
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
863
|
+
const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
|
|
864
|
+
const utf8Length = (hasBufferByteLength)
|
|
865
|
+
? Buffer.byteLength // node
|
|
866
|
+
: function (str, _) {
|
|
867
|
+
var c = 0, length = 0;
|
|
868
|
+
for (var i = 0, l = str.length; i < l; i++) {
|
|
869
|
+
c = str.charCodeAt(i);
|
|
870
|
+
if (c < 0x80) {
|
|
871
|
+
length += 1;
|
|
872
|
+
}
|
|
873
|
+
else if (c < 0x800) {
|
|
874
|
+
length += 2;
|
|
875
|
+
}
|
|
876
|
+
else if (c < 0xd800 || c >= 0xe000) {
|
|
877
|
+
length += 3;
|
|
878
|
+
}
|
|
879
|
+
else {
|
|
880
|
+
i++;
|
|
881
|
+
length += 4;
|
|
882
|
+
}
|
|
578
883
|
}
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
}
|
|
884
|
+
return length;
|
|
885
|
+
};
|
|
582
886
|
function utf8Write(view, str, it) {
|
|
583
887
|
var c = 0;
|
|
584
888
|
for (var i = 0, l = str.length; i < l; i++) {
|
|
@@ -587,21 +891,24 @@ function utf8Write(view, str, it) {
|
|
|
587
891
|
view[it.offset++] = c;
|
|
588
892
|
}
|
|
589
893
|
else if (c < 0x800) {
|
|
590
|
-
view[it.offset
|
|
591
|
-
view[it.offset
|
|
894
|
+
view[it.offset] = 0xc0 | (c >> 6);
|
|
895
|
+
view[it.offset + 1] = 0x80 | (c & 0x3f);
|
|
896
|
+
it.offset += 2;
|
|
592
897
|
}
|
|
593
898
|
else if (c < 0xd800 || c >= 0xe000) {
|
|
594
|
-
view[it.offset
|
|
595
|
-
view[it.offset
|
|
596
|
-
view[it.offset
|
|
899
|
+
view[it.offset] = 0xe0 | (c >> 12);
|
|
900
|
+
view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
|
|
901
|
+
view[it.offset + 2] = 0x80 | (c & 0x3f);
|
|
902
|
+
it.offset += 3;
|
|
597
903
|
}
|
|
598
904
|
else {
|
|
599
905
|
i++;
|
|
600
906
|
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
601
|
-
view[it.offset
|
|
602
|
-
view[it.offset
|
|
603
|
-
view[it.offset
|
|
604
|
-
view[it.offset
|
|
907
|
+
view[it.offset] = 0xf0 | (c >> 18);
|
|
908
|
+
view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
|
|
909
|
+
view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
|
|
910
|
+
view[it.offset + 3] = 0x80 | (c & 0x3f);
|
|
911
|
+
it.offset += 4;
|
|
605
912
|
}
|
|
606
913
|
}
|
|
607
914
|
}
|
|
@@ -673,8 +980,7 @@ function string$1(bytes, value, it) {
|
|
|
673
980
|
if (!value) {
|
|
674
981
|
value = "";
|
|
675
982
|
}
|
|
676
|
-
|
|
677
|
-
let length = Buffer.byteLength(value, "utf8");
|
|
983
|
+
let length = utf8Length(value, "utf8");
|
|
678
984
|
let size = 0;
|
|
679
985
|
// fixstr
|
|
680
986
|
if (length < 0x20) {
|
|
@@ -785,81 +1091,30 @@ function number$1(bytes, value, it) {
|
|
|
785
1091
|
|
|
786
1092
|
var encode = /*#__PURE__*/Object.freeze({
|
|
787
1093
|
__proto__: null,
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
791
|
-
uint8: uint8$1,
|
|
1094
|
+
boolean: boolean$1,
|
|
1095
|
+
float32: float32$1,
|
|
1096
|
+
float64: float64$1,
|
|
792
1097
|
int16: int16$1,
|
|
793
|
-
uint16: uint16$1,
|
|
794
1098
|
int32: int32$1,
|
|
795
|
-
uint32: uint32$1,
|
|
796
1099
|
int64: int64$1,
|
|
1100
|
+
int8: int8$1,
|
|
1101
|
+
number: number$1,
|
|
1102
|
+
string: string$1,
|
|
1103
|
+
uint16: uint16$1,
|
|
1104
|
+
uint32: uint32$1,
|
|
797
1105
|
uint64: uint64$1,
|
|
798
|
-
|
|
799
|
-
|
|
1106
|
+
uint8: uint8$1,
|
|
1107
|
+
utf8Length: utf8Length,
|
|
1108
|
+
utf8Write: utf8Write,
|
|
800
1109
|
writeFloat32: writeFloat32,
|
|
801
|
-
writeFloat64: writeFloat64
|
|
802
|
-
boolean: boolean$1,
|
|
803
|
-
string: string$1,
|
|
804
|
-
number: number$1
|
|
1110
|
+
writeFloat64: writeFloat64
|
|
805
1111
|
});
|
|
806
1112
|
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
let typeofTarget;
|
|
811
|
-
let allowNull = false;
|
|
812
|
-
switch (type) {
|
|
813
|
-
case "number":
|
|
814
|
-
case "int8":
|
|
815
|
-
case "uint8":
|
|
816
|
-
case "int16":
|
|
817
|
-
case "uint16":
|
|
818
|
-
case "int32":
|
|
819
|
-
case "uint32":
|
|
820
|
-
case "int64":
|
|
821
|
-
case "uint64":
|
|
822
|
-
case "float32":
|
|
823
|
-
case "float64":
|
|
824
|
-
typeofTarget = "number";
|
|
825
|
-
if (isNaN(value)) {
|
|
826
|
-
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
827
|
-
}
|
|
828
|
-
break;
|
|
829
|
-
case "string":
|
|
830
|
-
typeofTarget = "string";
|
|
831
|
-
allowNull = true;
|
|
832
|
-
break;
|
|
833
|
-
case "boolean":
|
|
834
|
-
// boolean is always encoded as true/false based on truthiness
|
|
835
|
-
return;
|
|
836
|
-
}
|
|
837
|
-
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
838
|
-
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
839
|
-
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
840
|
-
}
|
|
841
|
-
}
|
|
842
|
-
function assertInstanceType(value, type, klass, field) {
|
|
843
|
-
if (!(value instanceof type)) {
|
|
844
|
-
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
|
|
845
|
-
}
|
|
846
|
-
}
|
|
847
|
-
|
|
848
|
-
function encodePrimitiveType(type, bytes, value, klass, field, it) {
|
|
849
|
-
assertType(value, type, klass, field);
|
|
850
|
-
const encodeFunc = encode[type];
|
|
851
|
-
if (encodeFunc) {
|
|
852
|
-
encodeFunc(bytes, value, it);
|
|
853
|
-
// encodeFunc(bytes, value);
|
|
1113
|
+
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
1114
|
+
if (typeof (type) === "string") {
|
|
1115
|
+
encode[type]?.(bytes, value, it);
|
|
854
1116
|
}
|
|
855
|
-
else {
|
|
856
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
857
|
-
}
|
|
858
|
-
}
|
|
859
|
-
function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
|
|
860
|
-
if (type[Symbol.metadata] !== undefined) {
|
|
861
|
-
// TODO: move this to the `@type()` annotation
|
|
862
|
-
assertInstanceType(value, type, ref, field);
|
|
1117
|
+
else if (type[Symbol.metadata] !== undefined) {
|
|
863
1118
|
//
|
|
864
1119
|
// Encode refId for this instance.
|
|
865
1120
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -870,21 +1125,7 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
|
|
|
870
1125
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
871
1126
|
}
|
|
872
1127
|
}
|
|
873
|
-
else if (typeof (type) === "string") {
|
|
874
|
-
//
|
|
875
|
-
// Primitive values
|
|
876
|
-
//
|
|
877
|
-
encodePrimitiveType(type, bytes, value, ref, field, it);
|
|
878
|
-
}
|
|
879
1128
|
else {
|
|
880
|
-
//
|
|
881
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
882
|
-
//
|
|
883
|
-
const definition = getType(Object.keys(type)[0]);
|
|
884
|
-
//
|
|
885
|
-
// ensure a ArraySchema has been provided
|
|
886
|
-
//
|
|
887
|
-
assertInstanceType(ref[field], definition.constructor, ref, field);
|
|
888
1129
|
//
|
|
889
1130
|
// Encode refId for this instance.
|
|
890
1131
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -896,27 +1137,23 @@ function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
|
|
|
896
1137
|
* Used for Schema instances.
|
|
897
1138
|
* @private
|
|
898
1139
|
*/
|
|
899
|
-
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
900
|
-
const ref = changeTree.ref;
|
|
901
|
-
const metadata = ref['constructor'][Symbol.metadata];
|
|
902
|
-
const field = metadata[index];
|
|
903
|
-
const type = metadata[field].type;
|
|
904
|
-
const value = ref[field];
|
|
1140
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
905
1141
|
// "compress" field index + operation
|
|
906
1142
|
bytes[it.offset++] = (index | operation) & 255;
|
|
907
1143
|
// Do not encode value for DELETE operations
|
|
908
1144
|
if (operation === OPERATION.DELETE) {
|
|
909
1145
|
return;
|
|
910
1146
|
}
|
|
1147
|
+
const ref = changeTree.ref;
|
|
1148
|
+
const field = metadata[index];
|
|
911
1149
|
// TODO: inline this function call small performance gain
|
|
912
|
-
encodeValue(encoder, bytes,
|
|
1150
|
+
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
913
1151
|
};
|
|
914
1152
|
/**
|
|
915
1153
|
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
916
1154
|
* @private
|
|
917
1155
|
*/
|
|
918
|
-
const encodeKeyValueOperation = function (encoder, bytes, changeTree,
|
|
919
|
-
const ref = changeTree.ref;
|
|
1156
|
+
const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
920
1157
|
// encode operation
|
|
921
1158
|
bytes[it.offset++] = operation & 255;
|
|
922
1159
|
// custom operations
|
|
@@ -924,27 +1161,40 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
|
|
|
924
1161
|
return;
|
|
925
1162
|
}
|
|
926
1163
|
// encode index
|
|
927
|
-
number$1(bytes,
|
|
1164
|
+
number$1(bytes, index, it);
|
|
928
1165
|
// Do not encode value for DELETE operations
|
|
929
1166
|
if (operation === OPERATION.DELETE) {
|
|
930
1167
|
return;
|
|
931
1168
|
}
|
|
1169
|
+
const ref = changeTree.ref;
|
|
932
1170
|
//
|
|
933
1171
|
// encode "alias" for dynamic fields (maps)
|
|
934
1172
|
//
|
|
935
|
-
if ((operation & OPERATION.ADD)
|
|
1173
|
+
if ((operation & OPERATION.ADD) === OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
936
1174
|
if (typeof (ref['set']) === "function") {
|
|
937
1175
|
//
|
|
938
1176
|
// MapSchema dynamic key
|
|
939
1177
|
//
|
|
940
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(
|
|
1178
|
+
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
941
1179
|
string$1(bytes, dynamicIndex, it);
|
|
942
1180
|
}
|
|
943
1181
|
}
|
|
944
|
-
const type =
|
|
945
|
-
const value =
|
|
1182
|
+
const type = ref[$childType];
|
|
1183
|
+
const value = ref[$getByIndex](index);
|
|
1184
|
+
// try { throw new Error(); } catch (e) {
|
|
1185
|
+
// // only print if not coming from Reflection.ts
|
|
1186
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
1187
|
+
// console.log("encodeKeyValueOperation -> ", {
|
|
1188
|
+
// ref: changeTree.ref.constructor.name,
|
|
1189
|
+
// field,
|
|
1190
|
+
// operation: OPERATION[operation],
|
|
1191
|
+
// value: value?.toJSON(),
|
|
1192
|
+
// items: ref.toJSON(),
|
|
1193
|
+
// });
|
|
1194
|
+
// }
|
|
1195
|
+
// }
|
|
946
1196
|
// TODO: inline this function call small performance gain
|
|
947
|
-
encodeValue(encoder, bytes,
|
|
1197
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
948
1198
|
};
|
|
949
1199
|
/**
|
|
950
1200
|
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
@@ -952,24 +1202,29 @@ const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, ope
|
|
|
952
1202
|
*/
|
|
953
1203
|
const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
|
|
954
1204
|
const ref = changeTree.ref;
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
1205
|
+
const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
|
|
1206
|
+
let refOrIndex;
|
|
1207
|
+
if (useOperationByRefId) {
|
|
1208
|
+
refOrIndex = ref['tmpItems'][field][$changes].refId;
|
|
1209
|
+
if (operation === OPERATION.DELETE) {
|
|
1210
|
+
operation = OPERATION.DELETE_BY_REFID;
|
|
1211
|
+
}
|
|
1212
|
+
else if (operation === OPERATION.ADD) {
|
|
1213
|
+
operation = OPERATION.ADD_BY_REFID;
|
|
1214
|
+
}
|
|
1215
|
+
}
|
|
1216
|
+
else {
|
|
1217
|
+
refOrIndex = field;
|
|
964
1218
|
}
|
|
965
1219
|
// encode operation
|
|
966
1220
|
bytes[it.offset++] = operation & 255;
|
|
967
1221
|
// custom operations
|
|
968
|
-
if (operation === OPERATION.CLEAR
|
|
1222
|
+
if (operation === OPERATION.CLEAR ||
|
|
1223
|
+
operation === OPERATION.REVERSE) {
|
|
969
1224
|
return;
|
|
970
1225
|
}
|
|
971
1226
|
// encode index
|
|
972
|
-
number$1(bytes,
|
|
1227
|
+
number$1(bytes, refOrIndex, it);
|
|
973
1228
|
// Do not encode value for DELETE operations
|
|
974
1229
|
if (operation === OPERATION.DELETE) {
|
|
975
1230
|
return;
|
|
@@ -984,7 +1239,7 @@ const encodeArray = function (encoder, bytes, changeTree, field, operation, it,
|
|
|
984
1239
|
// items: ref.toJSON(),
|
|
985
1240
|
// });
|
|
986
1241
|
// TODO: inline this function call small performance gain
|
|
987
|
-
encodeValue(encoder, bytes,
|
|
1242
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
988
1243
|
};
|
|
989
1244
|
|
|
990
1245
|
/**
|
|
@@ -1218,31 +1473,31 @@ function switchStructureCheck(bytes, it) {
|
|
|
1218
1473
|
|
|
1219
1474
|
var decode = /*#__PURE__*/Object.freeze({
|
|
1220
1475
|
__proto__: null,
|
|
1221
|
-
|
|
1222
|
-
|
|
1223
|
-
uint8: uint8,
|
|
1224
|
-
int16: int16,
|
|
1225
|
-
uint16: uint16,
|
|
1226
|
-
int32: int32,
|
|
1227
|
-
uint32: uint32,
|
|
1476
|
+
arrayCheck: arrayCheck,
|
|
1477
|
+
boolean: boolean,
|
|
1228
1478
|
float32: float32,
|
|
1229
1479
|
float64: float64,
|
|
1480
|
+
int16: int16,
|
|
1481
|
+
int32: int32,
|
|
1230
1482
|
int64: int64,
|
|
1231
|
-
|
|
1483
|
+
int8: int8,
|
|
1484
|
+
number: number,
|
|
1485
|
+
numberCheck: numberCheck,
|
|
1232
1486
|
readFloat32: readFloat32,
|
|
1233
1487
|
readFloat64: readFloat64,
|
|
1234
|
-
boolean: boolean,
|
|
1235
1488
|
string: string,
|
|
1236
1489
|
stringCheck: stringCheck,
|
|
1237
|
-
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1490
|
+
switchStructureCheck: switchStructureCheck,
|
|
1491
|
+
uint16: uint16,
|
|
1492
|
+
uint32: uint32,
|
|
1493
|
+
uint64: uint64,
|
|
1494
|
+
uint8: uint8,
|
|
1495
|
+
utf8Read: utf8Read
|
|
1241
1496
|
});
|
|
1242
1497
|
|
|
1243
1498
|
const DEFINITION_MISMATCH = -1;
|
|
1244
1499
|
function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
|
|
1245
|
-
const $root = decoder
|
|
1500
|
+
const $root = decoder.root;
|
|
1246
1501
|
const previousValue = ref[$getByIndex](index);
|
|
1247
1502
|
let value;
|
|
1248
1503
|
if ((operation & OPERATION.DELETE) === OPERATION.DELETE) {
|
|
@@ -1290,7 +1545,9 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1290
1545
|
if (!value) {
|
|
1291
1546
|
value = decoder.createInstanceOfType(childType);
|
|
1292
1547
|
}
|
|
1293
|
-
$root.addRef(refId, value, (value !== previousValue
|
|
1548
|
+
$root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed
|
|
1549
|
+
(operation === OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
|
|
1550
|
+
));
|
|
1294
1551
|
}
|
|
1295
1552
|
}
|
|
1296
1553
|
else if (typeof (type) === "string") {
|
|
@@ -1341,18 +1598,19 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
|
|
|
1341
1598
|
}
|
|
1342
1599
|
const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
1343
1600
|
const first_byte = bytes[it.offset++];
|
|
1344
|
-
const metadata = ref
|
|
1601
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1345
1602
|
// "compressed" index + operation
|
|
1346
1603
|
const operation = (first_byte >> 6) << 6;
|
|
1347
1604
|
const index = first_byte % (operation || 255);
|
|
1348
1605
|
// skip early if field is not defined
|
|
1349
1606
|
const field = metadata[index];
|
|
1350
1607
|
if (field === undefined) {
|
|
1608
|
+
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
1351
1609
|
return DEFINITION_MISMATCH;
|
|
1352
1610
|
}
|
|
1353
|
-
const { value, previousValue } = decodeValue(decoder, operation, ref, index,
|
|
1611
|
+
const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
|
|
1354
1612
|
if (value !== null && value !== undefined) {
|
|
1355
|
-
ref[field] = value;
|
|
1613
|
+
ref[field.name] = value;
|
|
1356
1614
|
}
|
|
1357
1615
|
// add change
|
|
1358
1616
|
if (previousValue !== value) {
|
|
@@ -1360,7 +1618,7 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1360
1618
|
ref,
|
|
1361
1619
|
refId: decoder.currentRefId,
|
|
1362
1620
|
op: operation,
|
|
1363
|
-
field: field,
|
|
1621
|
+
field: field.name,
|
|
1364
1622
|
value,
|
|
1365
1623
|
previousValue,
|
|
1366
1624
|
});
|
|
@@ -1428,7 +1686,8 @@ const decodeKeyValueOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1428
1686
|
};
|
|
1429
1687
|
const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
1430
1688
|
// "uncompressed" index + operation (array/map items)
|
|
1431
|
-
|
|
1689
|
+
let operation = bytes[it.offset++];
|
|
1690
|
+
let index;
|
|
1432
1691
|
if (operation === OPERATION.CLEAR) {
|
|
1433
1692
|
//
|
|
1434
1693
|
// When decoding:
|
|
@@ -1439,11 +1698,15 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1439
1698
|
ref.clear();
|
|
1440
1699
|
return;
|
|
1441
1700
|
}
|
|
1701
|
+
else if (operation === OPERATION.REVERSE) {
|
|
1702
|
+
ref.reverse();
|
|
1703
|
+
return;
|
|
1704
|
+
}
|
|
1442
1705
|
else if (operation === OPERATION.DELETE_BY_REFID) {
|
|
1443
1706
|
// TODO: refactor here, try to follow same flow as below
|
|
1444
1707
|
const refId = number(bytes, it);
|
|
1445
|
-
const previousValue = decoder
|
|
1446
|
-
|
|
1708
|
+
const previousValue = decoder.root.refs.get(refId);
|
|
1709
|
+
index = ref.findIndex((value) => value === previousValue);
|
|
1447
1710
|
ref[$deleteByIndex](index);
|
|
1448
1711
|
allChanges.push({
|
|
1449
1712
|
ref,
|
|
@@ -1456,7 +1719,17 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1456
1719
|
});
|
|
1457
1720
|
return;
|
|
1458
1721
|
}
|
|
1459
|
-
|
|
1722
|
+
else if (operation === OPERATION.ADD_BY_REFID) {
|
|
1723
|
+
const refId = number(bytes, it);
|
|
1724
|
+
const itemByRefId = decoder.root.refs.get(refId);
|
|
1725
|
+
// use existing index, or push new value
|
|
1726
|
+
index = (itemByRefId)
|
|
1727
|
+
? ref.findIndex((value) => value === itemByRefId)
|
|
1728
|
+
: ref.length;
|
|
1729
|
+
}
|
|
1730
|
+
else {
|
|
1731
|
+
index = number(bytes, it);
|
|
1732
|
+
}
|
|
1460
1733
|
const type = ref[$childType];
|
|
1461
1734
|
let dynamicIndex = index;
|
|
1462
1735
|
const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
|
|
@@ -1480,6 +1753,47 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
|
1480
1753
|
}
|
|
1481
1754
|
};
|
|
1482
1755
|
|
|
1756
|
+
class EncodeSchemaError extends Error {
|
|
1757
|
+
}
|
|
1758
|
+
function assertType(value, type, klass, field) {
|
|
1759
|
+
let typeofTarget;
|
|
1760
|
+
let allowNull = false;
|
|
1761
|
+
switch (type) {
|
|
1762
|
+
case "number":
|
|
1763
|
+
case "int8":
|
|
1764
|
+
case "uint8":
|
|
1765
|
+
case "int16":
|
|
1766
|
+
case "uint16":
|
|
1767
|
+
case "int32":
|
|
1768
|
+
case "uint32":
|
|
1769
|
+
case "int64":
|
|
1770
|
+
case "uint64":
|
|
1771
|
+
case "float32":
|
|
1772
|
+
case "float64":
|
|
1773
|
+
typeofTarget = "number";
|
|
1774
|
+
if (isNaN(value)) {
|
|
1775
|
+
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
1776
|
+
}
|
|
1777
|
+
break;
|
|
1778
|
+
case "string":
|
|
1779
|
+
typeofTarget = "string";
|
|
1780
|
+
allowNull = true;
|
|
1781
|
+
break;
|
|
1782
|
+
case "boolean":
|
|
1783
|
+
// boolean is always encoded as true/false based on truthiness
|
|
1784
|
+
return;
|
|
1785
|
+
}
|
|
1786
|
+
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
1787
|
+
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
1788
|
+
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
1789
|
+
}
|
|
1790
|
+
}
|
|
1791
|
+
function assertInstanceType(value, type, instance, field) {
|
|
1792
|
+
if (!(value instanceof type)) {
|
|
1793
|
+
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
|
|
1794
|
+
}
|
|
1795
|
+
}
|
|
1796
|
+
|
|
1483
1797
|
var _a$4, _b$4;
|
|
1484
1798
|
const DEFAULT_SORT = (a, b) => {
|
|
1485
1799
|
const A = a.toString();
|
|
@@ -1530,6 +1844,7 @@ class ArraySchema {
|
|
|
1530
1844
|
const proxy = new Proxy(this, {
|
|
1531
1845
|
get: (obj, prop) => {
|
|
1532
1846
|
if (typeof (prop) !== "symbol" &&
|
|
1847
|
+
// FIXME: d8 accuses this as low performance
|
|
1533
1848
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1534
1849
|
) {
|
|
1535
1850
|
return this.items[prop];
|
|
@@ -1545,8 +1860,9 @@ class ArraySchema {
|
|
|
1545
1860
|
}
|
|
1546
1861
|
else {
|
|
1547
1862
|
if (setValue[$changes]) {
|
|
1863
|
+
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1548
1864
|
if (obj.items[key] !== undefined) {
|
|
1549
|
-
if (setValue[$changes]
|
|
1865
|
+
if (setValue[$changes].isNew) {
|
|
1550
1866
|
this[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
|
|
1551
1867
|
}
|
|
1552
1868
|
else {
|
|
@@ -1558,7 +1874,7 @@ class ArraySchema {
|
|
|
1558
1874
|
}
|
|
1559
1875
|
}
|
|
1560
1876
|
}
|
|
1561
|
-
else if (setValue[$changes]
|
|
1877
|
+
else if (setValue[$changes].isNew) {
|
|
1562
1878
|
this[$changes].indexedOperation(Number(key), OPERATION.ADD);
|
|
1563
1879
|
}
|
|
1564
1880
|
}
|
|
@@ -1591,7 +1907,10 @@ class ArraySchema {
|
|
|
1591
1907
|
}
|
|
1592
1908
|
});
|
|
1593
1909
|
this[$changes] = new ChangeTree(proxy);
|
|
1594
|
-
this.
|
|
1910
|
+
this[$changes].indexes = {};
|
|
1911
|
+
if (items.length > 0) {
|
|
1912
|
+
this.push(...items);
|
|
1913
|
+
}
|
|
1595
1914
|
return proxy;
|
|
1596
1915
|
}
|
|
1597
1916
|
set length(newLength) {
|
|
@@ -1610,14 +1929,19 @@ class ArraySchema {
|
|
|
1610
1929
|
}
|
|
1611
1930
|
push(...values) {
|
|
1612
1931
|
let length = this.tmpItems.length;
|
|
1613
|
-
|
|
1614
|
-
|
|
1932
|
+
const changeTree = this[$changes];
|
|
1933
|
+
// values.forEach((value, i) => {
|
|
1934
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1935
|
+
const value = values[i];
|
|
1615
1936
|
if (value === undefined || value === null) {
|
|
1937
|
+
// skip null values
|
|
1616
1938
|
return;
|
|
1617
1939
|
}
|
|
1618
|
-
|
|
1940
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
1941
|
+
assertInstanceType(value, this[$childType], this, i);
|
|
1942
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1943
|
+
}
|
|
1619
1944
|
changeTree.indexedOperation(length, OPERATION.ADD, this.items.length);
|
|
1620
|
-
// changeTree.indexes[length] = length;
|
|
1621
1945
|
this.items.push(value);
|
|
1622
1946
|
this.tmpItems.push(value);
|
|
1623
1947
|
//
|
|
@@ -1625,8 +1949,9 @@ class ArraySchema {
|
|
|
1625
1949
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1626
1950
|
//
|
|
1627
1951
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1628
|
-
|
|
1629
|
-
|
|
1952
|
+
}
|
|
1953
|
+
// length++;
|
|
1954
|
+
// });
|
|
1630
1955
|
return length;
|
|
1631
1956
|
}
|
|
1632
1957
|
/**
|
|
@@ -1647,6 +1972,7 @@ class ArraySchema {
|
|
|
1647
1972
|
}
|
|
1648
1973
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1649
1974
|
// this.tmpItems[index] = undefined;
|
|
1975
|
+
// this.tmpItems.pop();
|
|
1650
1976
|
this.deletedIndexes[index] = true;
|
|
1651
1977
|
return this.items.pop();
|
|
1652
1978
|
}
|
|
@@ -1711,9 +2037,12 @@ class ArraySchema {
|
|
|
1711
2037
|
//
|
|
1712
2038
|
// TODO: do not use [$changes] at decoding time.
|
|
1713
2039
|
//
|
|
1714
|
-
changeTree.root
|
|
1715
|
-
|
|
1716
|
-
|
|
2040
|
+
const root = changeTree.root;
|
|
2041
|
+
if (root !== undefined) {
|
|
2042
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
2043
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
2044
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
2045
|
+
}
|
|
1717
2046
|
});
|
|
1718
2047
|
changeTree.discard(true);
|
|
1719
2048
|
changeTree.operation(OPERATION.CLEAR);
|
|
@@ -1757,6 +2086,7 @@ class ArraySchema {
|
|
|
1757
2086
|
const changeTree = this[$changes];
|
|
1758
2087
|
changeTree.delete(index);
|
|
1759
2088
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
2089
|
+
// this.deletedIndexes[index] = true;
|
|
1760
2090
|
return this.items.shift();
|
|
1761
2091
|
}
|
|
1762
2092
|
/**
|
|
@@ -1837,10 +2167,12 @@ class ArraySchema {
|
|
|
1837
2167
|
changeTree.shiftChangeIndexes(items.length);
|
|
1838
2168
|
// new index
|
|
1839
2169
|
if (changeTree.isFiltered) {
|
|
1840
|
-
changeTree.filteredChanges
|
|
2170
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
2171
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1841
2172
|
}
|
|
1842
2173
|
else {
|
|
1843
|
-
changeTree.allChanges
|
|
2174
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2175
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1844
2176
|
}
|
|
1845
2177
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1846
2178
|
items.forEach((_, index) => {
|
|
@@ -1865,14 +2197,6 @@ class ArraySchema {
|
|
|
1865
2197
|
lastIndexOf(searchElement, fromIndex = this.length - 1) {
|
|
1866
2198
|
return this.items.lastIndexOf(searchElement, fromIndex);
|
|
1867
2199
|
}
|
|
1868
|
-
/**
|
|
1869
|
-
* Determines whether all the members of an array satisfy the specified test.
|
|
1870
|
-
* @param callbackfn A function that accepts up to three arguments. The every method calls
|
|
1871
|
-
* the callbackfn function for each element in the array until the callbackfn returns a value
|
|
1872
|
-
* which is coercible to the Boolean value false, or until the end of the array.
|
|
1873
|
-
* @param thisArg An object to which the this keyword can refer in the callbackfn function.
|
|
1874
|
-
* If thisArg is omitted, undefined is used as the this value.
|
|
1875
|
-
*/
|
|
1876
2200
|
every(callbackfn, thisArg) {
|
|
1877
2201
|
return this.items.every(callbackfn, thisArg);
|
|
1878
2202
|
}
|
|
@@ -2151,6 +2475,7 @@ class MapSchema {
|
|
|
2151
2475
|
this.$items = new Map();
|
|
2152
2476
|
this.$indexes = new Map();
|
|
2153
2477
|
this[$changes] = new ChangeTree(this);
|
|
2478
|
+
this[$changes].indexes = {};
|
|
2154
2479
|
if (initialValues) {
|
|
2155
2480
|
if (initialValues instanceof Map ||
|
|
2156
2481
|
initialValues instanceof MapSchema) {
|
|
@@ -2177,6 +2502,9 @@ class MapSchema {
|
|
|
2177
2502
|
if (value === undefined || value === null) {
|
|
2178
2503
|
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
|
|
2179
2504
|
}
|
|
2505
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
2506
|
+
assertInstanceType(value, this[$childType], this, key);
|
|
2507
|
+
}
|
|
2180
2508
|
// Force "key" as string
|
|
2181
2509
|
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
|
|
2182
2510
|
key = key.toString();
|
|
@@ -2185,7 +2513,7 @@ class MapSchema {
|
|
|
2185
2513
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2186
2514
|
const index = (isReplace)
|
|
2187
2515
|
? changeTree.indexes[key]
|
|
2188
|
-
: changeTree.indexes[
|
|
2516
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2189
2517
|
let operation = (isReplace)
|
|
2190
2518
|
? OPERATION.REPLACE
|
|
2191
2519
|
: OPERATION.ADD;
|
|
@@ -2197,7 +2525,7 @@ class MapSchema {
|
|
|
2197
2525
|
if (!isReplace) {
|
|
2198
2526
|
this.$indexes.set(index, key);
|
|
2199
2527
|
changeTree.indexes[key] = index;
|
|
2200
|
-
changeTree.indexes[
|
|
2528
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2201
2529
|
}
|
|
2202
2530
|
else if (!isRef &&
|
|
2203
2531
|
this.$items.get(key) === value) {
|
|
@@ -2272,8 +2600,11 @@ class MapSchema {
|
|
|
2272
2600
|
}
|
|
2273
2601
|
[$onEncodeEnd]() {
|
|
2274
2602
|
const changeTree = this[$changes];
|
|
2275
|
-
const
|
|
2276
|
-
for (
|
|
2603
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2604
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2605
|
+
const key = keys[i];
|
|
2606
|
+
const fieldIndex = Number(key);
|
|
2607
|
+
const operation = changeTree.indexedOperations[key];
|
|
2277
2608
|
if (operation === OPERATION.DELETE) {
|
|
2278
2609
|
const index = this[$getByIndex](fieldIndex);
|
|
2279
2610
|
delete changeTree.indexes[index];
|
|
@@ -2306,104 +2637,17 @@ class MapSchema {
|
|
|
2306
2637
|
if (value[$changes]) {
|
|
2307
2638
|
cloned.set(key, value['clone']());
|
|
2308
2639
|
}
|
|
2309
|
-
else {
|
|
2310
|
-
cloned.set(key, value);
|
|
2311
|
-
}
|
|
2312
|
-
});
|
|
2313
|
-
}
|
|
2314
|
-
return cloned;
|
|
2315
|
-
}
|
|
2316
|
-
}
|
|
2317
|
-
registerType("map", { constructor: MapSchema });
|
|
2318
|
-
|
|
2319
|
-
const DEFAULT_VIEW_TAG = -1;
|
|
2320
|
-
class TypeContext {
|
|
2321
|
-
/**
|
|
2322
|
-
* For inheritance support
|
|
2323
|
-
* Keeps track of which classes extends which. (parent -> children)
|
|
2324
|
-
*/
|
|
2325
|
-
static { this.inheritedTypes = new Map(); }
|
|
2326
|
-
static register(target) {
|
|
2327
|
-
const parent = Object.getPrototypeOf(target);
|
|
2328
|
-
if (parent !== Schema) {
|
|
2329
|
-
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
2330
|
-
if (!inherits) {
|
|
2331
|
-
inherits = new Set();
|
|
2332
|
-
TypeContext.inheritedTypes.set(parent, inherits);
|
|
2333
|
-
}
|
|
2334
|
-
inherits.add(target);
|
|
2335
|
-
}
|
|
2336
|
-
}
|
|
2337
|
-
constructor(rootClass) {
|
|
2338
|
-
this.types = {};
|
|
2339
|
-
this.schemas = new Map();
|
|
2340
|
-
this.hasFilters = false;
|
|
2341
|
-
if (rootClass) {
|
|
2342
|
-
this.discoverTypes(rootClass);
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
has(schema) {
|
|
2346
|
-
return this.schemas.has(schema);
|
|
2347
|
-
}
|
|
2348
|
-
get(typeid) {
|
|
2349
|
-
return this.types[typeid];
|
|
2350
|
-
}
|
|
2351
|
-
add(schema, typeid = this.schemas.size) {
|
|
2352
|
-
// skip if already registered
|
|
2353
|
-
if (this.schemas.has(schema)) {
|
|
2354
|
-
return false;
|
|
2355
|
-
}
|
|
2356
|
-
this.types[typeid] = schema;
|
|
2357
|
-
this.schemas.set(schema, typeid);
|
|
2358
|
-
return true;
|
|
2359
|
-
}
|
|
2360
|
-
getTypeId(klass) {
|
|
2361
|
-
return this.schemas.get(klass);
|
|
2362
|
-
}
|
|
2363
|
-
discoverTypes(klass) {
|
|
2364
|
-
if (!this.add(klass)) {
|
|
2365
|
-
return;
|
|
2366
|
-
}
|
|
2367
|
-
// add classes inherited from this base class
|
|
2368
|
-
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
2369
|
-
this.discoverTypes(child);
|
|
2370
|
-
});
|
|
2371
|
-
// skip if no fields are defined for this class.
|
|
2372
|
-
if (klass[Symbol.metadata] === undefined) {
|
|
2373
|
-
klass[Symbol.metadata] = {};
|
|
2374
|
-
}
|
|
2375
|
-
// const metadata = Metadata.getFor(klass);
|
|
2376
|
-
const metadata = klass[Symbol.metadata];
|
|
2377
|
-
// if any schema/field has filters, mark "context" as having filters.
|
|
2378
|
-
if (metadata[-2]) {
|
|
2379
|
-
this.hasFilters = true;
|
|
2380
|
-
}
|
|
2381
|
-
for (const field in metadata) {
|
|
2382
|
-
const fieldType = metadata[field].type;
|
|
2383
|
-
if (typeof (fieldType) === "string") {
|
|
2384
|
-
continue;
|
|
2385
|
-
}
|
|
2386
|
-
if (Array.isArray(fieldType)) {
|
|
2387
|
-
const type = fieldType[0];
|
|
2388
|
-
if (type === "string") {
|
|
2389
|
-
continue;
|
|
2390
|
-
}
|
|
2391
|
-
this.discoverTypes(type);
|
|
2392
|
-
}
|
|
2393
|
-
else if (typeof (fieldType) === "function") {
|
|
2394
|
-
this.discoverTypes(fieldType);
|
|
2395
|
-
}
|
|
2396
|
-
else {
|
|
2397
|
-
const type = Object.values(fieldType)[0];
|
|
2398
|
-
// skip primitive types
|
|
2399
|
-
if (typeof (type) === "string") {
|
|
2400
|
-
continue;
|
|
2640
|
+
else {
|
|
2641
|
+
cloned.set(key, value);
|
|
2401
2642
|
}
|
|
2402
|
-
|
|
2403
|
-
}
|
|
2643
|
+
});
|
|
2404
2644
|
}
|
|
2645
|
+
return cloned;
|
|
2405
2646
|
}
|
|
2406
2647
|
}
|
|
2648
|
+
registerType("map", { constructor: MapSchema });
|
|
2649
|
+
|
|
2650
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
2407
2651
|
/**
|
|
2408
2652
|
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
2409
2653
|
*
|
|
@@ -2430,8 +2674,8 @@ class TypeContext {
|
|
|
2430
2674
|
// // detect index for this field, considering inheritance
|
|
2431
2675
|
// //
|
|
2432
2676
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2433
|
-
// let fieldIndex: number = context.metadata[
|
|
2434
|
-
// ?? (parent && parent[
|
|
2677
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2678
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2435
2679
|
// ?? -1; // no fields defined
|
|
2436
2680
|
// fieldIndex++;
|
|
2437
2681
|
// if (
|
|
@@ -2551,18 +2795,20 @@ function view(tag = DEFAULT_VIEW_TAG) {
|
|
|
2551
2795
|
const constructor = target.constructor;
|
|
2552
2796
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2553
2797
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2798
|
+
// TODO: use Metadata.initialize()
|
|
2554
2799
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2555
|
-
|
|
2556
|
-
|
|
2557
|
-
|
|
2558
|
-
|
|
2559
|
-
|
|
2560
|
-
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
}
|
|
2800
|
+
// const fieldIndex = metadata[fieldName];
|
|
2801
|
+
// if (!metadata[fieldIndex]) {
|
|
2802
|
+
// //
|
|
2803
|
+
// // detect index for this field, considering inheritance
|
|
2804
|
+
// //
|
|
2805
|
+
// metadata[fieldIndex] = {
|
|
2806
|
+
// type: undefined,
|
|
2807
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2808
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2809
|
+
// ?? -1) + 1 // no fields defined
|
|
2810
|
+
// }
|
|
2811
|
+
// }
|
|
2566
2812
|
Metadata.setTag(metadata, fieldName, tag);
|
|
2567
2813
|
};
|
|
2568
2814
|
}
|
|
@@ -2576,17 +2822,17 @@ function type(type, options) {
|
|
|
2576
2822
|
TypeContext.register(constructor);
|
|
2577
2823
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2578
2824
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2579
|
-
const metadata =
|
|
2580
|
-
let fieldIndex;
|
|
2825
|
+
const metadata = Metadata.initialize(constructor);
|
|
2826
|
+
let fieldIndex = metadata[field];
|
|
2581
2827
|
/**
|
|
2582
2828
|
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
2583
2829
|
*/
|
|
2584
|
-
if (metadata[
|
|
2585
|
-
if (metadata[
|
|
2830
|
+
if (metadata[fieldIndex] !== undefined) {
|
|
2831
|
+
if (metadata[fieldIndex].deprecated) {
|
|
2586
2832
|
// do not create accessors for deprecated properties.
|
|
2587
2833
|
return;
|
|
2588
2834
|
}
|
|
2589
|
-
else if (metadata[
|
|
2835
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
2590
2836
|
// trying to define same property multiple times across inheritance.
|
|
2591
2837
|
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
2592
2838
|
try {
|
|
@@ -2597,16 +2843,13 @@ function type(type, options) {
|
|
|
2597
2843
|
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
2598
2844
|
}
|
|
2599
2845
|
}
|
|
2600
|
-
else {
|
|
2601
|
-
fieldIndex = metadata[field].index;
|
|
2602
|
-
}
|
|
2603
2846
|
}
|
|
2604
2847
|
else {
|
|
2605
2848
|
//
|
|
2606
2849
|
// detect index for this field, considering inheritance
|
|
2607
2850
|
//
|
|
2608
|
-
fieldIndex = metadata[
|
|
2609
|
-
?? (parentMetadata && parentMetadata[
|
|
2851
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2852
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2610
2853
|
?? -1; // no fields defined
|
|
2611
2854
|
fieldIndex++;
|
|
2612
2855
|
}
|
|
@@ -2625,15 +2868,15 @@ function type(type, options) {
|
|
|
2625
2868
|
const childType = (complexTypeKlass)
|
|
2626
2869
|
? Object.values(type)[0]
|
|
2627
2870
|
: type;
|
|
2628
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass
|
|
2871
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
2629
2872
|
}
|
|
2630
2873
|
};
|
|
2631
2874
|
}
|
|
2632
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass
|
|
2875
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
2633
2876
|
return {
|
|
2634
2877
|
get: function () { return this[fieldCached]; },
|
|
2635
2878
|
set: function (value) {
|
|
2636
|
-
const previousValue = this[fieldCached]
|
|
2879
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2637
2880
|
// skip if value is the same as cached.
|
|
2638
2881
|
if (value === previousValue) {
|
|
2639
2882
|
return;
|
|
@@ -2651,22 +2894,27 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass,
|
|
|
2651
2894
|
}
|
|
2652
2895
|
value[$childType] = type;
|
|
2653
2896
|
}
|
|
2897
|
+
else if (typeof (type) !== "string") {
|
|
2898
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
2899
|
+
}
|
|
2900
|
+
else {
|
|
2901
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
2902
|
+
}
|
|
2903
|
+
const changeTree = this[$changes];
|
|
2654
2904
|
//
|
|
2655
2905
|
// Replacing existing "ref", remove it from root.
|
|
2656
2906
|
// TODO: if there are other references to this instance, we should not remove it from root.
|
|
2657
2907
|
//
|
|
2658
2908
|
if (previousValue !== undefined && previousValue[$changes]) {
|
|
2659
|
-
|
|
2909
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
2660
2910
|
}
|
|
2661
2911
|
// flag the change for encoding.
|
|
2662
|
-
this.constructor[$track](
|
|
2912
|
+
this.constructor[$track](changeTree, fieldIndex, OPERATION.ADD);
|
|
2663
2913
|
//
|
|
2664
2914
|
// call setParent() recursively for this and its child
|
|
2665
2915
|
// structures.
|
|
2666
2916
|
//
|
|
2667
|
-
|
|
2668
|
-
value[$changes].setParent(this, this[$changes].root, metadata[field].index);
|
|
2669
|
-
}
|
|
2917
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
2670
2918
|
}
|
|
2671
2919
|
else if (previousValue !== undefined) {
|
|
2672
2920
|
//
|
|
@@ -2693,20 +2941,22 @@ function deprecated(throws = true) {
|
|
|
2693
2941
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2694
2942
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2695
2943
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2696
|
-
|
|
2697
|
-
|
|
2698
|
-
|
|
2699
|
-
|
|
2700
|
-
|
|
2701
|
-
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
}
|
|
2707
|
-
|
|
2944
|
+
const fieldIndex = metadata[field];
|
|
2945
|
+
// if (!metadata[field]) {
|
|
2946
|
+
// //
|
|
2947
|
+
// // detect index for this field, considering inheritance
|
|
2948
|
+
// //
|
|
2949
|
+
// metadata[field] = {
|
|
2950
|
+
// type: undefined,
|
|
2951
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2952
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2953
|
+
// ?? -1) + 1 // no fields defined
|
|
2954
|
+
// }
|
|
2955
|
+
// }
|
|
2956
|
+
metadata[fieldIndex].deprecated = true;
|
|
2708
2957
|
if (throws) {
|
|
2709
|
-
metadata[
|
|
2958
|
+
metadata[$descriptors] ??= {};
|
|
2959
|
+
metadata[$descriptors][field] = {
|
|
2710
2960
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
2711
2961
|
set: function (value) { },
|
|
2712
2962
|
enumerable: false,
|
|
@@ -2714,8 +2964,8 @@ function deprecated(throws = true) {
|
|
|
2714
2964
|
};
|
|
2715
2965
|
}
|
|
2716
2966
|
// flag metadata[field] as non-enumerable
|
|
2717
|
-
Object.defineProperty(metadata,
|
|
2718
|
-
value: metadata[
|
|
2967
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
2968
|
+
value: metadata[fieldIndex],
|
|
2719
2969
|
enumerable: false,
|
|
2720
2970
|
configurable: true
|
|
2721
2971
|
});
|
|
@@ -2727,6 +2977,37 @@ function defineTypes(target, fields, options) {
|
|
|
2727
2977
|
}
|
|
2728
2978
|
return target;
|
|
2729
2979
|
}
|
|
2980
|
+
function schema(fields, name, inherits = Schema) {
|
|
2981
|
+
const defaultValues = {};
|
|
2982
|
+
const viewTagFields = {};
|
|
2983
|
+
for (let fieldName in fields) {
|
|
2984
|
+
const field = fields[fieldName];
|
|
2985
|
+
if (typeof (field) === "object") {
|
|
2986
|
+
if (field['default'] !== undefined) {
|
|
2987
|
+
defaultValues[fieldName] = field['default'];
|
|
2988
|
+
}
|
|
2989
|
+
if (field['view'] !== undefined) {
|
|
2990
|
+
viewTagFields[fieldName] = (typeof (field['view']) === "boolean")
|
|
2991
|
+
? DEFAULT_VIEW_TAG
|
|
2992
|
+
: field['view'];
|
|
2993
|
+
}
|
|
2994
|
+
}
|
|
2995
|
+
}
|
|
2996
|
+
const klass = Metadata.setFields(class extends inherits {
|
|
2997
|
+
constructor(...args) {
|
|
2998
|
+
args[0] = Object.assign({}, defaultValues, args[0]);
|
|
2999
|
+
super(...args);
|
|
3000
|
+
}
|
|
3001
|
+
}, fields);
|
|
3002
|
+
for (let fieldName in viewTagFields) {
|
|
3003
|
+
view(viewTagFields[fieldName])(klass.prototype, fieldName);
|
|
3004
|
+
}
|
|
3005
|
+
if (name) {
|
|
3006
|
+
Object.defineProperty(klass, "name", { value: name });
|
|
3007
|
+
}
|
|
3008
|
+
klass.extends = (fields, name) => schema(fields, name, klass);
|
|
3009
|
+
return klass;
|
|
3010
|
+
}
|
|
2730
3011
|
|
|
2731
3012
|
function getIndent(level) {
|
|
2732
3013
|
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
@@ -2737,15 +3018,18 @@ function dumpChanges(schema) {
|
|
|
2737
3018
|
ops: {},
|
|
2738
3019
|
refs: []
|
|
2739
3020
|
};
|
|
2740
|
-
$root.changes
|
|
3021
|
+
// for (const refId in $root.changes) {
|
|
3022
|
+
$root.changes.forEach(changeTree => {
|
|
3023
|
+
const changes = changeTree.indexedOperations;
|
|
2741
3024
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2742
|
-
|
|
3025
|
+
for (const index in changes) {
|
|
3026
|
+
const op = changes[index];
|
|
2743
3027
|
const opName = OPERATION[op];
|
|
2744
3028
|
if (!dump.ops[opName]) {
|
|
2745
3029
|
dump.ops[opName] = 0;
|
|
2746
3030
|
}
|
|
2747
3031
|
dump.ops[OPERATION[op]]++;
|
|
2748
|
-
}
|
|
3032
|
+
}
|
|
2749
3033
|
});
|
|
2750
3034
|
return dump;
|
|
2751
3035
|
}
|
|
@@ -2771,6 +3055,7 @@ var _a$2, _b$2;
|
|
|
2771
3055
|
class Schema {
|
|
2772
3056
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2773
3057
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
3058
|
+
// public [$changes]: ChangeTree;
|
|
2774
3059
|
/**
|
|
2775
3060
|
* Assign the property descriptors required to track changes on this instance.
|
|
2776
3061
|
* @param instance
|
|
@@ -2781,35 +3066,7 @@ class Schema {
|
|
|
2781
3066
|
enumerable: false,
|
|
2782
3067
|
writable: true
|
|
2783
3068
|
});
|
|
2784
|
-
|
|
2785
|
-
// Define property descriptors
|
|
2786
|
-
for (const field in metadata) {
|
|
2787
|
-
if (metadata[field].descriptor) {
|
|
2788
|
-
// for encoder
|
|
2789
|
-
Object.defineProperty(instance, `_${field}`, {
|
|
2790
|
-
value: undefined,
|
|
2791
|
-
writable: true,
|
|
2792
|
-
enumerable: false,
|
|
2793
|
-
configurable: true,
|
|
2794
|
-
});
|
|
2795
|
-
Object.defineProperty(instance, field, metadata[field].descriptor);
|
|
2796
|
-
}
|
|
2797
|
-
else {
|
|
2798
|
-
// for decoder
|
|
2799
|
-
Object.defineProperty(instance, field, {
|
|
2800
|
-
value: undefined,
|
|
2801
|
-
writable: true,
|
|
2802
|
-
enumerable: true,
|
|
2803
|
-
configurable: true,
|
|
2804
|
-
});
|
|
2805
|
-
}
|
|
2806
|
-
// Object.defineProperty(instance, field, {
|
|
2807
|
-
// ...instance.constructor[Symbol.metadata][field].descriptor
|
|
2808
|
-
// });
|
|
2809
|
-
// if (args[0]?.hasOwnProperty(field)) {
|
|
2810
|
-
// instance[field] = args[0][field];
|
|
2811
|
-
// }
|
|
2812
|
-
}
|
|
3069
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2813
3070
|
}
|
|
2814
3071
|
static is(type) {
|
|
2815
3072
|
return typeof (type[Symbol.metadata]) === "object";
|
|
@@ -2833,7 +3090,7 @@ class Schema {
|
|
|
2833
3090
|
*/
|
|
2834
3091
|
static [$filter](ref, index, view) {
|
|
2835
3092
|
const metadata = ref.constructor[Symbol.metadata];
|
|
2836
|
-
const tag = metadata[
|
|
3093
|
+
const tag = metadata[index]?.tag;
|
|
2837
3094
|
if (view === undefined) {
|
|
2838
3095
|
// shared pass/encode: encode if doesn't have a tag
|
|
2839
3096
|
return tag === undefined;
|
|
@@ -2854,12 +3111,16 @@ class Schema {
|
|
|
2854
3111
|
}
|
|
2855
3112
|
// allow inherited classes to have a constructor
|
|
2856
3113
|
constructor(...args) {
|
|
3114
|
+
//
|
|
3115
|
+
// inline
|
|
3116
|
+
// Schema.initialize(this);
|
|
3117
|
+
//
|
|
2857
3118
|
Schema.initialize(this);
|
|
2858
3119
|
//
|
|
2859
3120
|
// Assign initial values
|
|
2860
3121
|
//
|
|
2861
3122
|
if (args[0]) {
|
|
2862
|
-
|
|
3123
|
+
Object.assign(this, args[0]);
|
|
2863
3124
|
}
|
|
2864
3125
|
}
|
|
2865
3126
|
assign(props) {
|
|
@@ -2873,7 +3134,8 @@ class Schema {
|
|
|
2873
3134
|
* @param operation OPERATION to perform (detected automatically)
|
|
2874
3135
|
*/
|
|
2875
3136
|
setDirty(property, operation) {
|
|
2876
|
-
this
|
|
3137
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3138
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
2877
3139
|
}
|
|
2878
3140
|
clone() {
|
|
2879
3141
|
const cloned = new (this.constructor);
|
|
@@ -2882,7 +3144,9 @@ class Schema {
|
|
|
2882
3144
|
// TODO: clone all properties, not only annotated ones
|
|
2883
3145
|
//
|
|
2884
3146
|
// for (const field in this) {
|
|
2885
|
-
for (const
|
|
3147
|
+
for (const fieldIndex in metadata) {
|
|
3148
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
3149
|
+
const field = metadata[fieldIndex].name;
|
|
2886
3150
|
if (typeof (this[field]) === "object" &&
|
|
2887
3151
|
typeof (this[field]?.clone) === "function") {
|
|
2888
3152
|
// deep clone
|
|
@@ -2896,10 +3160,11 @@ class Schema {
|
|
|
2896
3160
|
return cloned;
|
|
2897
3161
|
}
|
|
2898
3162
|
toJSON() {
|
|
2899
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
2900
3163
|
const obj = {};
|
|
2901
|
-
|
|
2902
|
-
|
|
3164
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3165
|
+
for (const index in metadata) {
|
|
3166
|
+
const field = metadata[index];
|
|
3167
|
+
const fieldName = field.name;
|
|
2903
3168
|
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
2904
3169
|
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
2905
3170
|
? this[fieldName]['toJSON']()
|
|
@@ -2912,17 +3177,19 @@ class Schema {
|
|
|
2912
3177
|
this[$changes].discardAll();
|
|
2913
3178
|
}
|
|
2914
3179
|
[$getByIndex](index) {
|
|
2915
|
-
|
|
3180
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3181
|
+
return this[metadata[index].name];
|
|
2916
3182
|
}
|
|
2917
3183
|
[$deleteByIndex](index) {
|
|
2918
|
-
this
|
|
3184
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3185
|
+
this[metadata[index].name] = undefined;
|
|
2919
3186
|
}
|
|
2920
3187
|
static debugRefIds(instance, jsonContents = true, level = 0) {
|
|
2921
3188
|
const ref = instance;
|
|
2922
3189
|
const changeTree = ref[$changes];
|
|
2923
3190
|
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2924
3191
|
let output = "";
|
|
2925
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
3192
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
2926
3193
|
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
2927
3194
|
return output;
|
|
2928
3195
|
}
|
|
@@ -2940,30 +3207,40 @@ class Schema {
|
|
|
2940
3207
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
2941
3208
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
2942
3209
|
function dumpChangeSet(changeSet) {
|
|
2943
|
-
|
|
2944
|
-
.
|
|
2945
|
-
.forEach((
|
|
3210
|
+
changeSet.operations
|
|
3211
|
+
.filter(op => op)
|
|
3212
|
+
.forEach((index) => {
|
|
3213
|
+
const operation = changeTree.indexedOperations[index];
|
|
3214
|
+
console.log({ index, operation });
|
|
3215
|
+
output += `- [${index}]: ${OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3216
|
+
});
|
|
2946
3217
|
}
|
|
2947
3218
|
dumpChangeSet(changeSet);
|
|
2948
3219
|
// display filtered changes
|
|
2949
|
-
if (!isEncodeAll &&
|
|
3220
|
+
if (!isEncodeAll &&
|
|
3221
|
+
changeTree.filteredChanges &&
|
|
3222
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
2950
3223
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
2951
3224
|
dumpChangeSet(changeTree.filteredChanges);
|
|
2952
3225
|
}
|
|
2953
3226
|
// display filtered changes
|
|
2954
|
-
if (isEncodeAll &&
|
|
3227
|
+
if (isEncodeAll &&
|
|
3228
|
+
changeTree.allFilteredChanges &&
|
|
3229
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
2955
3230
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
2956
3231
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
2957
3232
|
}
|
|
2958
3233
|
return output;
|
|
2959
3234
|
}
|
|
2960
|
-
static debugChangesDeep(ref) {
|
|
3235
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
2961
3236
|
let output = "";
|
|
2962
3237
|
const rootChangeTree = ref[$changes];
|
|
3238
|
+
const root = rootChangeTree.root;
|
|
2963
3239
|
const changeTrees = new Map();
|
|
2964
3240
|
let totalInstances = 0;
|
|
2965
3241
|
let totalOperations = 0;
|
|
2966
|
-
for (const [
|
|
3242
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3243
|
+
const changeTree = root.changeTrees[refId];
|
|
2967
3244
|
let includeChangeTree = false;
|
|
2968
3245
|
let parentChangeTrees = [];
|
|
2969
3246
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -2982,7 +3259,7 @@ class Schema {
|
|
|
2982
3259
|
}
|
|
2983
3260
|
if (includeChangeTree) {
|
|
2984
3261
|
totalInstances += 1;
|
|
2985
|
-
totalOperations += changes.
|
|
3262
|
+
totalOperations += Object.keys(changes).length;
|
|
2986
3263
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
2987
3264
|
}
|
|
2988
3265
|
}
|
|
@@ -3000,12 +3277,13 @@ class Schema {
|
|
|
3000
3277
|
visitedParents.add(parentChangeTree);
|
|
3001
3278
|
}
|
|
3002
3279
|
});
|
|
3003
|
-
const changes = changeTree.
|
|
3280
|
+
const changes = changeTree.indexedOperations;
|
|
3004
3281
|
const level = parentChangeTrees.length;
|
|
3005
3282
|
const indent = getIndent(level);
|
|
3006
3283
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3007
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3008
|
-
for (const
|
|
3284
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3285
|
+
for (const index in changes) {
|
|
3286
|
+
const operation = changes[index];
|
|
3009
3287
|
output += `${getIndent(level + 1)}${OPERATION[operation]}: ${index}\n`;
|
|
3010
3288
|
}
|
|
3011
3289
|
}
|
|
@@ -3039,6 +3317,7 @@ class CollectionSchema {
|
|
|
3039
3317
|
this.$indexes = new Map();
|
|
3040
3318
|
this.$refId = 0;
|
|
3041
3319
|
this[$changes] = new ChangeTree(this);
|
|
3320
|
+
this[$changes].indexes = {};
|
|
3042
3321
|
if (initialValues) {
|
|
3043
3322
|
initialValues.forEach((v) => this.add(v));
|
|
3044
3323
|
}
|
|
@@ -3194,6 +3473,7 @@ class SetSchema {
|
|
|
3194
3473
|
this.$indexes = new Map();
|
|
3195
3474
|
this.$refId = 0;
|
|
3196
3475
|
this[$changes] = new ChangeTree(this);
|
|
3476
|
+
this[$changes].indexes = {};
|
|
3197
3477
|
if (initialValues) {
|
|
3198
3478
|
initialValues.forEach((v) => this.add(v));
|
|
3199
3479
|
}
|
|
@@ -3349,6 +3629,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
|
|
|
3349
3629
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3350
3630
|
PERFORMANCE OF THIS SOFTWARE.
|
|
3351
3631
|
***************************************************************************** */
|
|
3632
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
3633
|
+
|
|
3352
3634
|
|
|
3353
3635
|
function __decorate(decorators, target, key, desc) {
|
|
3354
3636
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3362,37 +3644,135 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
|
|
|
3362
3644
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3363
3645
|
};
|
|
3364
3646
|
|
|
3647
|
+
function spliceOne(arr, index) {
|
|
3648
|
+
// manually splice an array
|
|
3649
|
+
if (index === -1 || index >= arr.length) {
|
|
3650
|
+
return false;
|
|
3651
|
+
}
|
|
3652
|
+
const len = arr.length - 1;
|
|
3653
|
+
for (let i = index; i < len; i++) {
|
|
3654
|
+
arr[i] = arr[i + 1];
|
|
3655
|
+
}
|
|
3656
|
+
arr.length = len;
|
|
3657
|
+
return true;
|
|
3658
|
+
}
|
|
3659
|
+
|
|
3660
|
+
class Root {
|
|
3661
|
+
constructor(types) {
|
|
3662
|
+
this.types = types;
|
|
3663
|
+
this.nextUniqueId = 0;
|
|
3664
|
+
this.refCount = {};
|
|
3665
|
+
this.changeTrees = {};
|
|
3666
|
+
// all changes
|
|
3667
|
+
this.allChanges = [];
|
|
3668
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3669
|
+
// pending changes to be encoded
|
|
3670
|
+
this.changes = [];
|
|
3671
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3672
|
+
}
|
|
3673
|
+
getNextUniqueId() {
|
|
3674
|
+
return this.nextUniqueId++;
|
|
3675
|
+
}
|
|
3676
|
+
add(changeTree) {
|
|
3677
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3678
|
+
changeTree.ensureRefId();
|
|
3679
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3680
|
+
if (isNewChangeTree) {
|
|
3681
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3682
|
+
}
|
|
3683
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3684
|
+
if (previousRefCount === 0) {
|
|
3685
|
+
//
|
|
3686
|
+
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3687
|
+
// We need to re-add all changes to the `changes` map.
|
|
3688
|
+
//
|
|
3689
|
+
const ops = changeTree.allChanges.operations;
|
|
3690
|
+
let len = ops.length;
|
|
3691
|
+
while (len--) {
|
|
3692
|
+
changeTree.indexedOperations[ops[len]] = OPERATION.ADD;
|
|
3693
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3694
|
+
}
|
|
3695
|
+
}
|
|
3696
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3697
|
+
return isNewChangeTree;
|
|
3698
|
+
}
|
|
3699
|
+
remove(changeTree) {
|
|
3700
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3701
|
+
if (refCount <= 0) {
|
|
3702
|
+
//
|
|
3703
|
+
// Only remove "root" reference if it's the last reference
|
|
3704
|
+
//
|
|
3705
|
+
changeTree.root = undefined;
|
|
3706
|
+
delete this.changeTrees[changeTree.refId];
|
|
3707
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3708
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3709
|
+
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3710
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3711
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3712
|
+
}
|
|
3713
|
+
this.refCount[changeTree.refId] = 0;
|
|
3714
|
+
}
|
|
3715
|
+
else {
|
|
3716
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3717
|
+
}
|
|
3718
|
+
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3719
|
+
return refCount;
|
|
3720
|
+
}
|
|
3721
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3722
|
+
const changeSet = this[changeSetName];
|
|
3723
|
+
const index = changeSet.indexOf(changeTree);
|
|
3724
|
+
if (index !== -1) {
|
|
3725
|
+
spliceOne(changeSet, index);
|
|
3726
|
+
// changeSet[index] = undefined;
|
|
3727
|
+
}
|
|
3728
|
+
}
|
|
3729
|
+
clear() {
|
|
3730
|
+
this.changes.length = 0;
|
|
3731
|
+
}
|
|
3732
|
+
}
|
|
3733
|
+
|
|
3365
3734
|
class Encoder {
|
|
3366
3735
|
static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
|
|
3367
|
-
constructor(
|
|
3736
|
+
constructor(state) {
|
|
3368
3737
|
this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
|
|
3369
|
-
this.setRoot(root);
|
|
3370
3738
|
//
|
|
3371
3739
|
// TODO: cache and restore "Context" based on root schema
|
|
3372
3740
|
// (to avoid creating a new context for every new room)
|
|
3373
3741
|
//
|
|
3374
|
-
this.context = new TypeContext(
|
|
3742
|
+
this.context = new TypeContext(state.constructor);
|
|
3743
|
+
this.root = new Root(this.context);
|
|
3744
|
+
this.setState(state);
|
|
3375
3745
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
3376
3746
|
// this.context.schemas.forEach((id, schema) => {
|
|
3377
3747
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3378
3748
|
// });
|
|
3379
3749
|
}
|
|
3380
|
-
|
|
3381
|
-
this.root = new Root();
|
|
3750
|
+
setState(state) {
|
|
3382
3751
|
this.state = state;
|
|
3383
|
-
state[$changes].setRoot(this.root);
|
|
3752
|
+
this.state[$changes].setRoot(this.root);
|
|
3384
3753
|
}
|
|
3385
|
-
encode(it = { offset: 0 }, view,
|
|
3386
|
-
|
|
3387
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
3754
|
+
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
|
|
3755
|
+
) {
|
|
3388
3756
|
const hasView = (view !== undefined);
|
|
3389
3757
|
const rootChangeTree = this.state[$changes];
|
|
3390
|
-
const
|
|
3391
|
-
|
|
3758
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3759
|
+
const changeTrees = this.root[changeSetName];
|
|
3760
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3761
|
+
const changeTree = changeTrees[i];
|
|
3762
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
3763
|
+
// if (changeTree === undefined) { continue; }
|
|
3764
|
+
const operations = changeTree[changeSetName];
|
|
3392
3765
|
const ref = changeTree.ref;
|
|
3393
|
-
const ctor = ref
|
|
3766
|
+
const ctor = ref.constructor;
|
|
3394
3767
|
const encoder = ctor[$encoder];
|
|
3395
3768
|
const filter = ctor[$filter];
|
|
3769
|
+
const metadata = ctor[Symbol.metadata];
|
|
3770
|
+
// try { throw new Error(); } catch (e) {
|
|
3771
|
+
// // only print if not coming from Reflection.ts
|
|
3772
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3773
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
3774
|
+
// }
|
|
3775
|
+
// }
|
|
3396
3776
|
if (hasView) {
|
|
3397
3777
|
if (!view.items.has(changeTree)) {
|
|
3398
3778
|
view.invisible.add(changeTree);
|
|
@@ -3403,12 +3783,18 @@ class Encoder {
|
|
|
3403
3783
|
}
|
|
3404
3784
|
}
|
|
3405
3785
|
// skip root `refId` if it's the first change tree
|
|
3406
|
-
|
|
3407
|
-
|
|
3408
|
-
|
|
3786
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
3787
|
+
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
3788
|
+
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3789
|
+
number$1(buffer, changeTree.refId, it);
|
|
3409
3790
|
}
|
|
3410
|
-
|
|
3411
|
-
|
|
3791
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3792
|
+
const fieldIndex = operations.operations[j];
|
|
3793
|
+
const operation = (fieldIndex < 0)
|
|
3794
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3795
|
+
: (isEncodeAll)
|
|
3796
|
+
? OPERATION.ADD
|
|
3797
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3412
3798
|
//
|
|
3413
3799
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3414
3800
|
// they will be encoded per client, based on their view.
|
|
@@ -3416,93 +3802,127 @@ class Encoder {
|
|
|
3416
3802
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3417
3803
|
// TODO: avoid checking if no view tags were defined
|
|
3418
3804
|
//
|
|
3419
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3420
|
-
// console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
|
|
3805
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3421
3806
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3422
3807
|
// view?.invisible.add(changeTree);
|
|
3423
3808
|
continue;
|
|
3424
3809
|
}
|
|
3425
|
-
//
|
|
3426
|
-
//
|
|
3427
|
-
//
|
|
3428
|
-
//
|
|
3429
|
-
//
|
|
3430
|
-
|
|
3810
|
+
// try { throw new Error(); } catch (e) {
|
|
3811
|
+
// // only print if not coming from Reflection.ts
|
|
3812
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3813
|
+
// console.log("WILL ENCODE", {
|
|
3814
|
+
// ref: changeTree.ref.constructor.name,
|
|
3815
|
+
// fieldIndex,
|
|
3816
|
+
// operation: OPERATION[operation],
|
|
3817
|
+
// });
|
|
3818
|
+
// }
|
|
3819
|
+
// }
|
|
3820
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
3821
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3822
|
+
}
|
|
3823
|
+
if (shouldDiscardChanges) {
|
|
3824
|
+
changeTree.discard();
|
|
3825
|
+
// Not a new instance anymore
|
|
3826
|
+
changeTree.isNew = false;
|
|
3431
3827
|
}
|
|
3432
3828
|
}
|
|
3433
|
-
if (it.offset >
|
|
3434
|
-
const newSize = getNextPowerOf2(
|
|
3435
|
-
console.warn(
|
|
3829
|
+
if (it.offset > buffer.byteLength) {
|
|
3830
|
+
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
3831
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
3832
|
+
|
|
3833
|
+
import { Encoder } from "@colyseus/schema";
|
|
3834
|
+
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
3835
|
+
`);
|
|
3436
3836
|
//
|
|
3437
3837
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
3438
3838
|
//
|
|
3439
|
-
|
|
3440
|
-
|
|
3839
|
+
buffer = Buffer.allocUnsafeSlow(newSize);
|
|
3840
|
+
// assign resized buffer to local sharedBuffer
|
|
3841
|
+
if (buffer === this.sharedBuffer) {
|
|
3842
|
+
this.sharedBuffer = buffer;
|
|
3843
|
+
}
|
|
3844
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3441
3845
|
}
|
|
3442
3846
|
else {
|
|
3443
|
-
//
|
|
3444
|
-
// only clear changes after making sure buffer resize is not required.
|
|
3445
|
-
//
|
|
3446
|
-
if (
|
|
3447
|
-
|
|
3448
|
-
|
|
3449
|
-
|
|
3450
|
-
|
|
3451
|
-
}
|
|
3452
|
-
|
|
3453
|
-
return bytes.slice(0, it.offset);
|
|
3847
|
+
// //
|
|
3848
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
3849
|
+
// //
|
|
3850
|
+
// if (shouldClearChanges) {
|
|
3851
|
+
// //
|
|
3852
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
3853
|
+
// //
|
|
3854
|
+
// this.onEndEncode(changeTrees);
|
|
3855
|
+
// }
|
|
3856
|
+
return buffer.subarray(0, it.offset);
|
|
3454
3857
|
}
|
|
3455
3858
|
}
|
|
3456
|
-
encodeAll(it = { offset: 0 }) {
|
|
3457
|
-
// console.log(
|
|
3458
|
-
//
|
|
3459
|
-
|
|
3460
|
-
// });
|
|
3461
|
-
return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
|
|
3859
|
+
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3860
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
3861
|
+
// this.debugChanges("allChanges");
|
|
3862
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3462
3863
|
}
|
|
3463
3864
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3464
3865
|
const viewOffset = it.offset;
|
|
3465
|
-
// console.log(
|
|
3466
|
-
// this.
|
|
3866
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
3867
|
+
// this.debugChanges("allFilteredChanges");
|
|
3868
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
3467
3869
|
// try to encode "filtered" changes
|
|
3468
|
-
this.encode(it, view, bytes,
|
|
3870
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3469
3871
|
return Buffer.concat([
|
|
3470
|
-
bytes.
|
|
3471
|
-
bytes.
|
|
3872
|
+
bytes.subarray(0, sharedOffset),
|
|
3873
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3472
3874
|
]);
|
|
3473
3875
|
}
|
|
3474
|
-
|
|
3475
|
-
|
|
3476
|
-
|
|
3477
|
-
|
|
3478
|
-
|
|
3479
|
-
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3876
|
+
debugChanges(field) {
|
|
3877
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3878
|
+
? this.root[field]
|
|
3879
|
+
: field;
|
|
3880
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3881
|
+
const changeSet = changeTree[field];
|
|
3882
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3883
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3884
|
+
for (const index in changeSet) {
|
|
3885
|
+
const op = changeSet[index];
|
|
3886
|
+
console.log(" ->", {
|
|
3887
|
+
index,
|
|
3888
|
+
field: metadata?.[index],
|
|
3889
|
+
op: OPERATION[op],
|
|
3890
|
+
});
|
|
3891
|
+
}
|
|
3892
|
+
});
|
|
3893
|
+
}
|
|
3484
3894
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3485
3895
|
const viewOffset = it.offset;
|
|
3486
|
-
//
|
|
3487
|
-
this.
|
|
3896
|
+
// console.log(`\nencodeView(), view.changes (${view.changes.size})`);
|
|
3897
|
+
// this.debugChanges(view.changes);
|
|
3898
|
+
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3899
|
+
// this.debugChanges("filteredChanges");
|
|
3488
3900
|
// encode visibility changes (add/remove for this view)
|
|
3489
|
-
const
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3901
|
+
const refIds = Object.keys(view.changes);
|
|
3902
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
3903
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3904
|
+
const refId = refIds[i];
|
|
3905
|
+
const changes = view.changes[refId];
|
|
3906
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3907
|
+
if (changeTree === undefined ||
|
|
3908
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3909
|
+
) {
|
|
3910
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3494
3911
|
continue;
|
|
3495
3912
|
}
|
|
3496
3913
|
const ref = changeTree.ref;
|
|
3497
|
-
const ctor = ref
|
|
3914
|
+
const ctor = ref.constructor;
|
|
3498
3915
|
const encoder = ctor[$encoder];
|
|
3916
|
+
const metadata = ctor[Symbol.metadata];
|
|
3499
3917
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3500
3918
|
number$1(bytes, changeTree.refId, it);
|
|
3501
|
-
const
|
|
3502
|
-
for (
|
|
3919
|
+
const keys = Object.keys(changes);
|
|
3920
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3921
|
+
const key = keys[i];
|
|
3922
|
+
const operation = changes[key];
|
|
3503
3923
|
// isEncodeAll = false
|
|
3504
3924
|
// hasView = true
|
|
3505
|
-
encoder(this, bytes, changeTree,
|
|
3925
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3506
3926
|
}
|
|
3507
3927
|
}
|
|
3508
3928
|
//
|
|
@@ -3510,51 +3930,64 @@ class Encoder {
|
|
|
3510
3930
|
// (to allow re-using StateView's for multiple clients)
|
|
3511
3931
|
//
|
|
3512
3932
|
// clear "view" changes after encoding
|
|
3513
|
-
view.changes
|
|
3933
|
+
view.changes = {};
|
|
3934
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
3935
|
+
// try to encode "filtered" changes
|
|
3936
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3514
3937
|
return Buffer.concat([
|
|
3515
|
-
bytes.
|
|
3516
|
-
bytes.
|
|
3938
|
+
bytes.subarray(0, sharedOffset),
|
|
3939
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3517
3940
|
]);
|
|
3518
3941
|
}
|
|
3519
3942
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3520
|
-
|
|
3521
|
-
|
|
3522
|
-
|
|
3523
|
-
|
|
3943
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3944
|
+
// changeTree.endEncode();
|
|
3945
|
+
// });
|
|
3946
|
+
// for (const refId in changeTrees) {
|
|
3947
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3948
|
+
// changeTree.endEncode();
|
|
3949
|
+
// // changeTree.changes.clear();
|
|
3950
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3951
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3952
|
+
// // // Not a new instance anymore
|
|
3953
|
+
// // delete changeTree[$isNew];
|
|
3954
|
+
// }
|
|
3524
3955
|
}
|
|
3525
3956
|
discardChanges() {
|
|
3957
|
+
// console.log("DISCARD CHANGES!");
|
|
3526
3958
|
// discard shared changes
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3959
|
+
let length = this.root.changes.length;
|
|
3960
|
+
if (length > 0) {
|
|
3961
|
+
while (length--) {
|
|
3962
|
+
this.root.changes[length]?.endEncode();
|
|
3963
|
+
}
|
|
3964
|
+
this.root.changes.length = 0;
|
|
3530
3965
|
}
|
|
3531
3966
|
// discard filtered changes
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3967
|
+
length = this.root.filteredChanges.length;
|
|
3968
|
+
if (length > 0) {
|
|
3969
|
+
while (length--) {
|
|
3970
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3971
|
+
}
|
|
3972
|
+
this.root.filteredChanges.length = 0;
|
|
3535
3973
|
}
|
|
3536
3974
|
}
|
|
3537
3975
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
3538
3976
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
3539
3977
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
3978
|
+
if (targetTypeId === undefined) {
|
|
3979
|
+
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.`);
|
|
3980
|
+
return;
|
|
3981
|
+
}
|
|
3540
3982
|
if (baseTypeId !== targetTypeId) {
|
|
3541
3983
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
3542
3984
|
number$1(bytes, targetTypeId, it);
|
|
3543
3985
|
}
|
|
3544
3986
|
}
|
|
3545
|
-
|
|
3546
|
-
|
|
3547
|
-
|
|
3548
|
-
// manually splice an array
|
|
3549
|
-
if (index === -1 || index >= arr.length) {
|
|
3550
|
-
return false;
|
|
3551
|
-
}
|
|
3552
|
-
const len = arr.length - 1;
|
|
3553
|
-
for (let i = index; i < len; i++) {
|
|
3554
|
-
arr[i] = arr[i + 1];
|
|
3987
|
+
get hasChanges() {
|
|
3988
|
+
return (this.root.changes.length > 0 ||
|
|
3989
|
+
this.root.filteredChanges.length > 0);
|
|
3555
3990
|
}
|
|
3556
|
-
arr.length = len;
|
|
3557
|
-
return true;
|
|
3558
3991
|
}
|
|
3559
3992
|
|
|
3560
3993
|
class DecodingWarning extends Error {
|
|
@@ -3635,8 +4068,9 @@ class ReferenceTracker {
|
|
|
3635
4068
|
// Ensure child schema instances have their references removed as well.
|
|
3636
4069
|
//
|
|
3637
4070
|
if (Metadata.isValidInstance(ref)) {
|
|
3638
|
-
const metadata = ref
|
|
3639
|
-
for (const
|
|
4071
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
4072
|
+
for (const index in metadata) {
|
|
4073
|
+
const field = metadata[index].name;
|
|
3640
4074
|
const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
|
|
3641
4075
|
if (childRefId) {
|
|
3642
4076
|
this.removeRef(childRefId);
|
|
@@ -3683,21 +4117,21 @@ class ReferenceTracker {
|
|
|
3683
4117
|
class Decoder {
|
|
3684
4118
|
constructor(root, context) {
|
|
3685
4119
|
this.currentRefId = 0;
|
|
3686
|
-
this.
|
|
4120
|
+
this.setState(root);
|
|
3687
4121
|
this.context = context || new TypeContext(root.constructor);
|
|
3688
4122
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
3689
4123
|
// this.context.schemas.forEach((id, schema) => {
|
|
3690
4124
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3691
4125
|
// });
|
|
3692
4126
|
}
|
|
3693
|
-
|
|
4127
|
+
setState(root) {
|
|
3694
4128
|
this.state = root;
|
|
3695
|
-
this
|
|
3696
|
-
this
|
|
4129
|
+
this.root = new ReferenceTracker();
|
|
4130
|
+
this.root.addRef(0, root);
|
|
3697
4131
|
}
|
|
3698
4132
|
decode(bytes, it = { offset: 0 }, ref = this.state) {
|
|
3699
4133
|
const allChanges = [];
|
|
3700
|
-
const $root = this
|
|
4134
|
+
const $root = this.root;
|
|
3701
4135
|
const totalBytes = bytes.byteLength;
|
|
3702
4136
|
let decoder = ref['constructor'][$decoder];
|
|
3703
4137
|
this.currentRefId = 0;
|
|
@@ -3717,7 +4151,7 @@ class Decoder {
|
|
|
3717
4151
|
}
|
|
3718
4152
|
ref[$onDecodeEnd]?.();
|
|
3719
4153
|
ref = nextRef;
|
|
3720
|
-
decoder = ref
|
|
4154
|
+
decoder = ref.constructor[$decoder];
|
|
3721
4155
|
continue;
|
|
3722
4156
|
}
|
|
3723
4157
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -3778,7 +4212,7 @@ class Decoder {
|
|
|
3778
4212
|
previousValue: value
|
|
3779
4213
|
});
|
|
3780
4214
|
if (needRemoveRef) {
|
|
3781
|
-
this
|
|
4215
|
+
this.root.removeRef(this.root.refIds.get(value));
|
|
3782
4216
|
}
|
|
3783
4217
|
});
|
|
3784
4218
|
}
|
|
@@ -3818,14 +4252,27 @@ class Reflection extends Schema {
|
|
|
3818
4252
|
super(...arguments);
|
|
3819
4253
|
this.types = new ArraySchema();
|
|
3820
4254
|
}
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
4255
|
+
/**
|
|
4256
|
+
* Encodes the TypeContext of an Encoder into a buffer.
|
|
4257
|
+
*
|
|
4258
|
+
* @param encoder Encoder instance
|
|
4259
|
+
* @param it
|
|
4260
|
+
* @returns
|
|
4261
|
+
*/
|
|
4262
|
+
static encode(encoder, it = { offset: 0 }) {
|
|
4263
|
+
const context = encoder.context;
|
|
3825
4264
|
const reflection = new Reflection();
|
|
3826
|
-
const
|
|
4265
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
4266
|
+
// rootType is usually the first schema passed to the Encoder
|
|
4267
|
+
// (unless it inherits from another schema)
|
|
4268
|
+
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4269
|
+
if (rootType > 0) {
|
|
4270
|
+
reflection.rootType = rootType;
|
|
4271
|
+
}
|
|
3827
4272
|
const buildType = (currentType, metadata) => {
|
|
3828
|
-
for (const
|
|
4273
|
+
for (const fieldIndex in metadata) {
|
|
4274
|
+
const index = Number(fieldIndex);
|
|
4275
|
+
const fieldName = metadata[index].name;
|
|
3829
4276
|
// skip fields from parent classes
|
|
3830
4277
|
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
3831
4278
|
continue;
|
|
@@ -3833,7 +4280,7 @@ class Reflection extends Schema {
|
|
|
3833
4280
|
const field = new ReflectionField();
|
|
3834
4281
|
field.name = fieldName;
|
|
3835
4282
|
let fieldType;
|
|
3836
|
-
const type = metadata[
|
|
4283
|
+
const type = metadata[index].type;
|
|
3837
4284
|
if (typeof (type) === "string") {
|
|
3838
4285
|
fieldType = type;
|
|
3839
4286
|
}
|
|
@@ -3875,66 +4322,335 @@ class Reflection extends Schema {
|
|
|
3875
4322
|
}
|
|
3876
4323
|
buildType(type, klass[Symbol.metadata]);
|
|
3877
4324
|
}
|
|
3878
|
-
const
|
|
3879
|
-
const buf = encoder.encodeAll(it);
|
|
4325
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
3880
4326
|
return Buffer.from(buf, 0, it.offset);
|
|
3881
4327
|
}
|
|
4328
|
+
/**
|
|
4329
|
+
* Decodes the TypeContext from a buffer into a Decoder instance.
|
|
4330
|
+
*
|
|
4331
|
+
* @param bytes Reflection.encode() output
|
|
4332
|
+
* @param it
|
|
4333
|
+
* @returns Decoder instance
|
|
4334
|
+
*/
|
|
3882
4335
|
static decode(bytes, it) {
|
|
3883
4336
|
const reflection = new Reflection();
|
|
3884
4337
|
const reflectionDecoder = new Decoder(reflection);
|
|
3885
4338
|
reflectionDecoder.decode(bytes, it);
|
|
3886
|
-
const
|
|
3887
|
-
|
|
3888
|
-
|
|
3889
|
-
const
|
|
4339
|
+
const typeContext = new TypeContext();
|
|
4340
|
+
// 1st pass, initialize metadata + inheritance
|
|
4341
|
+
reflection.types.forEach((reflectionType) => {
|
|
4342
|
+
const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
4343
|
+
const schema = class _ extends parentClass {
|
|
3890
4344
|
};
|
|
3891
|
-
// const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
|
|
3892
|
-
const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
|
|
3893
|
-
Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
3894
4345
|
// register for inheritance support
|
|
3895
4346
|
TypeContext.register(schema);
|
|
3896
|
-
|
|
3897
|
-
|
|
3898
|
-
|
|
3899
|
-
return types;
|
|
4347
|
+
// // for inheritance support
|
|
4348
|
+
// Metadata.initialize(schema);
|
|
4349
|
+
typeContext.add(schema, reflectionType.id);
|
|
3900
4350
|
}, {});
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
const metadata = schemaType[Symbol.metadata];
|
|
3904
|
-
const parentKlass = reflection.types[reflectionType.extendsId];
|
|
3905
|
-
const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
|
|
4351
|
+
// define fields
|
|
4352
|
+
const addFields = (metadata, reflectionType, parentFieldIndex) => {
|
|
3906
4353
|
reflectionType.fields.forEach((field, i) => {
|
|
3907
4354
|
const fieldIndex = parentFieldIndex + i;
|
|
3908
4355
|
if (field.referencedType !== undefined) {
|
|
3909
4356
|
let fieldType = field.type;
|
|
3910
|
-
let refType =
|
|
4357
|
+
let refType = typeContext.get(field.referencedType);
|
|
3911
4358
|
// map or array of primitive type (-1)
|
|
3912
4359
|
if (!refType) {
|
|
3913
4360
|
const typeInfo = field.type.split(":");
|
|
3914
4361
|
fieldType = typeInfo[0];
|
|
3915
|
-
refType = typeInfo[1];
|
|
4362
|
+
refType = typeInfo[1]; // string
|
|
3916
4363
|
}
|
|
3917
4364
|
if (fieldType === "ref") {
|
|
3918
|
-
// type(refType)(schemaType.prototype, field.name);
|
|
3919
4365
|
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
3920
4366
|
}
|
|
3921
4367
|
else {
|
|
3922
|
-
// type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
|
|
3923
4368
|
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
3924
4369
|
}
|
|
3925
4370
|
}
|
|
3926
4371
|
else {
|
|
3927
|
-
// type(field.type as PrimitiveType)(schemaType.prototype, field.name);
|
|
3928
4372
|
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
3929
4373
|
}
|
|
3930
4374
|
});
|
|
4375
|
+
};
|
|
4376
|
+
// 2nd pass, set fields
|
|
4377
|
+
reflection.types.forEach((reflectionType) => {
|
|
4378
|
+
const schema = typeContext.get(reflectionType.id);
|
|
4379
|
+
// for inheritance support
|
|
4380
|
+
const metadata = Metadata.initialize(schema);
|
|
4381
|
+
const inheritedTypes = [];
|
|
4382
|
+
let parentType = reflectionType;
|
|
4383
|
+
do {
|
|
4384
|
+
inheritedTypes.push(parentType);
|
|
4385
|
+
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4386
|
+
} while (parentType);
|
|
4387
|
+
let parentFieldIndex = 0;
|
|
4388
|
+
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4389
|
+
// add fields from all inherited classes
|
|
4390
|
+
// TODO: refactor this to avoid adding fields from parent classes
|
|
4391
|
+
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4392
|
+
parentFieldIndex += reflectionType.fields.length;
|
|
4393
|
+
});
|
|
3931
4394
|
});
|
|
3932
|
-
|
|
4395
|
+
const state = new (typeContext.get(reflection.rootType || 0))();
|
|
4396
|
+
return new Decoder(state, typeContext);
|
|
3933
4397
|
}
|
|
3934
4398
|
}
|
|
3935
4399
|
__decorate([
|
|
3936
4400
|
type([ReflectionType])
|
|
3937
4401
|
], Reflection.prototype, "types", void 0);
|
|
4402
|
+
__decorate([
|
|
4403
|
+
type("number")
|
|
4404
|
+
], Reflection.prototype, "rootType", void 0);
|
|
4405
|
+
|
|
4406
|
+
function getDecoderStateCallbacks(decoder) {
|
|
4407
|
+
const $root = decoder.root;
|
|
4408
|
+
const callbacks = $root.callbacks;
|
|
4409
|
+
const onAddCalls = new WeakMap();
|
|
4410
|
+
let currentOnAddCallback;
|
|
4411
|
+
decoder.triggerChanges = function (allChanges) {
|
|
4412
|
+
const uniqueRefIds = new Set();
|
|
4413
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
4414
|
+
const change = allChanges[i];
|
|
4415
|
+
const refId = change.refId;
|
|
4416
|
+
const ref = change.ref;
|
|
4417
|
+
const $callbacks = callbacks[refId];
|
|
4418
|
+
if (!$callbacks) {
|
|
4419
|
+
continue;
|
|
4420
|
+
}
|
|
4421
|
+
//
|
|
4422
|
+
// trigger onRemove on child structure.
|
|
4423
|
+
//
|
|
4424
|
+
if ((change.op & OPERATION.DELETE) === OPERATION.DELETE &&
|
|
4425
|
+
change.previousValue instanceof Schema) {
|
|
4426
|
+
const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[OPERATION.DELETE];
|
|
4427
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4428
|
+
deleteCallbacks[i]();
|
|
4429
|
+
}
|
|
4430
|
+
}
|
|
4431
|
+
if (ref instanceof Schema) {
|
|
4432
|
+
//
|
|
4433
|
+
// Handle schema instance
|
|
4434
|
+
//
|
|
4435
|
+
if (!uniqueRefIds.has(refId)) {
|
|
4436
|
+
// trigger onChange
|
|
4437
|
+
const replaceCallbacks = $callbacks?.[OPERATION.REPLACE];
|
|
4438
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4439
|
+
replaceCallbacks[i]();
|
|
4440
|
+
// try {
|
|
4441
|
+
// } catch (e) {
|
|
4442
|
+
// console.error(e);
|
|
4443
|
+
// }
|
|
4444
|
+
}
|
|
4445
|
+
}
|
|
4446
|
+
if ($callbacks.hasOwnProperty(change.field)) {
|
|
4447
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
4448
|
+
for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
|
|
4449
|
+
fieldCallbacks[i](change.value, change.previousValue);
|
|
4450
|
+
// try {
|
|
4451
|
+
// } catch (e) {
|
|
4452
|
+
// console.error(e);
|
|
4453
|
+
// }
|
|
4454
|
+
}
|
|
4455
|
+
}
|
|
4456
|
+
}
|
|
4457
|
+
else {
|
|
4458
|
+
//
|
|
4459
|
+
// Handle collection of items
|
|
4460
|
+
//
|
|
4461
|
+
if ((change.op & OPERATION.DELETE) === OPERATION.DELETE) {
|
|
4462
|
+
//
|
|
4463
|
+
// FIXME: `previousValue` should always be available.
|
|
4464
|
+
//
|
|
4465
|
+
if (change.previousValue !== undefined) {
|
|
4466
|
+
// triger onRemove
|
|
4467
|
+
const deleteCallbacks = $callbacks[OPERATION.DELETE];
|
|
4468
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4469
|
+
deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
|
|
4470
|
+
}
|
|
4471
|
+
}
|
|
4472
|
+
// Handle DELETE_AND_ADD operations
|
|
4473
|
+
if ((change.op & OPERATION.ADD) === OPERATION.ADD) {
|
|
4474
|
+
const addCallbacks = $callbacks[OPERATION.ADD];
|
|
4475
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4476
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4477
|
+
}
|
|
4478
|
+
}
|
|
4479
|
+
}
|
|
4480
|
+
else if ((change.op & OPERATION.ADD) === OPERATION.ADD && change.previousValue === undefined) {
|
|
4481
|
+
// triger onAdd
|
|
4482
|
+
const addCallbacks = $callbacks[OPERATION.ADD];
|
|
4483
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4484
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4485
|
+
}
|
|
4486
|
+
}
|
|
4487
|
+
// trigger onChange
|
|
4488
|
+
if (change.value !== change.previousValue) {
|
|
4489
|
+
const replaceCallbacks = $callbacks[OPERATION.REPLACE];
|
|
4490
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4491
|
+
replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
}
|
|
4495
|
+
uniqueRefIds.add(refId);
|
|
4496
|
+
}
|
|
4497
|
+
};
|
|
4498
|
+
function getProxy(metadataOrType, context) {
|
|
4499
|
+
let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
|
|
4500
|
+
let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
|
|
4501
|
+
(metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
|
|
4502
|
+
if (metadata && !isCollection) {
|
|
4503
|
+
const onAddListen = function (ref, prop, callback, immediate) {
|
|
4504
|
+
// immediate trigger
|
|
4505
|
+
if (immediate &&
|
|
4506
|
+
context.instance[prop] !== undefined &&
|
|
4507
|
+
!onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
|
|
4508
|
+
) {
|
|
4509
|
+
callback(context.instance[prop], undefined);
|
|
4510
|
+
}
|
|
4511
|
+
return $root.addCallback($root.refIds.get(ref), prop, callback);
|
|
4512
|
+
};
|
|
4513
|
+
/**
|
|
4514
|
+
* Schema instances
|
|
4515
|
+
*/
|
|
4516
|
+
return new Proxy({
|
|
4517
|
+
listen: function listen(prop, callback, immediate = true) {
|
|
4518
|
+
if (context.instance) {
|
|
4519
|
+
return onAddListen(context.instance, prop, callback, immediate);
|
|
4520
|
+
}
|
|
4521
|
+
else {
|
|
4522
|
+
// collection instance not received yet
|
|
4523
|
+
let detachCallback = () => { };
|
|
4524
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4525
|
+
detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4526
|
+
});
|
|
4527
|
+
return () => detachCallback();
|
|
4528
|
+
}
|
|
4529
|
+
},
|
|
4530
|
+
onChange: function onChange(callback) {
|
|
4531
|
+
return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, callback);
|
|
4532
|
+
},
|
|
4533
|
+
//
|
|
4534
|
+
// TODO: refactor `bindTo()` implementation.
|
|
4535
|
+
// There is room for improvement.
|
|
4536
|
+
//
|
|
4537
|
+
bindTo: function bindTo(targetObject, properties) {
|
|
4538
|
+
if (!properties) {
|
|
4539
|
+
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4540
|
+
}
|
|
4541
|
+
return $root.addCallback($root.refIds.get(context.instance), OPERATION.REPLACE, () => {
|
|
4542
|
+
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4543
|
+
});
|
|
4544
|
+
}
|
|
4545
|
+
}, {
|
|
4546
|
+
get(target, prop) {
|
|
4547
|
+
const metadataField = metadata[metadata[prop]];
|
|
4548
|
+
if (metadataField) {
|
|
4549
|
+
const instance = context.instance?.[prop];
|
|
4550
|
+
const onInstanceAvailable = ((callback) => {
|
|
4551
|
+
const unbind = $(context.instance).listen(prop, (value, _) => {
|
|
4552
|
+
callback(value, false);
|
|
4553
|
+
// FIXME: by "unbinding" the callback here,
|
|
4554
|
+
// it will not support when the server
|
|
4555
|
+
// re-instantiates the instance.
|
|
4556
|
+
//
|
|
4557
|
+
unbind?.();
|
|
4558
|
+
}, false);
|
|
4559
|
+
// has existing value
|
|
4560
|
+
if ($root.refIds.get(instance) !== undefined) {
|
|
4561
|
+
callback(instance, true);
|
|
4562
|
+
}
|
|
4563
|
+
});
|
|
4564
|
+
return getProxy(metadataField.type, {
|
|
4565
|
+
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
4566
|
+
instance: ($root.refIds.get(instance) && instance),
|
|
4567
|
+
parentInstance: context.instance,
|
|
4568
|
+
onInstanceAvailable,
|
|
4569
|
+
});
|
|
4570
|
+
}
|
|
4571
|
+
else {
|
|
4572
|
+
// accessing the function
|
|
4573
|
+
return target[prop];
|
|
4574
|
+
}
|
|
4575
|
+
},
|
|
4576
|
+
has(target, prop) { return metadata[prop] !== undefined; },
|
|
4577
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4578
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4579
|
+
});
|
|
4580
|
+
}
|
|
4581
|
+
else {
|
|
4582
|
+
/**
|
|
4583
|
+
* Collection instances
|
|
4584
|
+
*/
|
|
4585
|
+
const onAdd = function (ref, callback, immediate) {
|
|
4586
|
+
// Trigger callback on existing items
|
|
4587
|
+
if (immediate) {
|
|
4588
|
+
ref.forEach((v, k) => callback(v, k));
|
|
4589
|
+
}
|
|
4590
|
+
return $root.addCallback($root.refIds.get(ref), OPERATION.ADD, (value, key) => {
|
|
4591
|
+
onAddCalls.set(callback, true);
|
|
4592
|
+
currentOnAddCallback = callback;
|
|
4593
|
+
callback(value, key);
|
|
4594
|
+
onAddCalls.delete(callback);
|
|
4595
|
+
currentOnAddCallback = undefined;
|
|
4596
|
+
});
|
|
4597
|
+
};
|
|
4598
|
+
const onRemove = function (ref, callback) {
|
|
4599
|
+
return $root.addCallback($root.refIds.get(ref), OPERATION.DELETE, callback);
|
|
4600
|
+
};
|
|
4601
|
+
return new Proxy({
|
|
4602
|
+
onAdd: function (callback, immediate = true) {
|
|
4603
|
+
//
|
|
4604
|
+
// https://github.com/colyseus/schema/issues/147
|
|
4605
|
+
// If parent instance has "onAdd" registered, avoid triggering immediate callback.
|
|
4606
|
+
//
|
|
4607
|
+
if (context.instance) {
|
|
4608
|
+
return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
|
|
4609
|
+
}
|
|
4610
|
+
else if (context.onInstanceAvailable) {
|
|
4611
|
+
// collection instance not received yet
|
|
4612
|
+
let detachCallback = () => { };
|
|
4613
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4614
|
+
detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4615
|
+
});
|
|
4616
|
+
return () => detachCallback();
|
|
4617
|
+
}
|
|
4618
|
+
},
|
|
4619
|
+
onRemove: function (callback) {
|
|
4620
|
+
if (context.onInstanceAvailable) {
|
|
4621
|
+
// collection instance not received yet
|
|
4622
|
+
let detachCallback = () => { };
|
|
4623
|
+
context.onInstanceAvailable((ref) => {
|
|
4624
|
+
detachCallback = onRemove(ref, callback);
|
|
4625
|
+
});
|
|
4626
|
+
return () => detachCallback();
|
|
4627
|
+
}
|
|
4628
|
+
else if (context.instance) {
|
|
4629
|
+
return onRemove(context.instance, callback);
|
|
4630
|
+
}
|
|
4631
|
+
},
|
|
4632
|
+
}, {
|
|
4633
|
+
get(target, prop) {
|
|
4634
|
+
if (!target[prop]) {
|
|
4635
|
+
throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
|
|
4636
|
+
}
|
|
4637
|
+
return target[prop];
|
|
4638
|
+
},
|
|
4639
|
+
has(target, prop) { return target[prop] !== undefined; },
|
|
4640
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4641
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4642
|
+
});
|
|
4643
|
+
}
|
|
4644
|
+
}
|
|
4645
|
+
function $(instance) {
|
|
4646
|
+
return getProxy(undefined, { instance });
|
|
4647
|
+
}
|
|
4648
|
+
return $;
|
|
4649
|
+
}
|
|
4650
|
+
|
|
4651
|
+
function getRawChangesCallback(decoder, callback) {
|
|
4652
|
+
decoder.triggerChanges = callback;
|
|
4653
|
+
}
|
|
3938
4654
|
|
|
3939
4655
|
class StateView {
|
|
3940
4656
|
constructor() {
|
|
@@ -3950,31 +4666,32 @@ class StateView {
|
|
|
3950
4666
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
3951
4667
|
* (This is used to force encoding a property, even if it was not changed)
|
|
3952
4668
|
*/
|
|
3953
|
-
this.changes =
|
|
4669
|
+
this.changes = {};
|
|
3954
4670
|
}
|
|
3955
4671
|
// TODO: allow to set multiple tags at once
|
|
3956
|
-
add(obj, tag = DEFAULT_VIEW_TAG) {
|
|
4672
|
+
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
3957
4673
|
if (!obj[$changes]) {
|
|
3958
4674
|
console.warn("StateView#add(), invalid object:", obj);
|
|
3959
4675
|
return this;
|
|
3960
4676
|
}
|
|
3961
|
-
|
|
3962
|
-
this.items.add(changeTree);
|
|
3963
|
-
// Add children of this ChangeTree to this view
|
|
3964
|
-
changeTree.forEachChild((change, _) => this.add(change.ref, tag));
|
|
3965
|
-
// FIXME: ArraySchema/MapSchema does not have metadata
|
|
4677
|
+
// FIXME: ArraySchema/MapSchema do not have metadata
|
|
3966
4678
|
const metadata = obj.constructor[Symbol.metadata];
|
|
3967
|
-
|
|
3968
|
-
|
|
3969
|
-
|
|
4679
|
+
const changeTree = obj[$changes];
|
|
4680
|
+
this.items.add(changeTree);
|
|
4681
|
+
// add parent ChangeTree's
|
|
4682
|
+
// - if it was invisible to this view
|
|
4683
|
+
// - if it were previously filtered out
|
|
4684
|
+
if (checkIncludeParent && changeTree.parent) {
|
|
4685
|
+
this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
|
|
4686
|
+
}
|
|
3970
4687
|
//
|
|
3971
4688
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
3972
4689
|
// be set (only the parent's changes are set)
|
|
3973
4690
|
//
|
|
3974
|
-
let changes = this.changes.
|
|
4691
|
+
let changes = this.changes[changeTree.refId];
|
|
3975
4692
|
if (changes === undefined) {
|
|
3976
|
-
changes =
|
|
3977
|
-
this.changes.
|
|
4693
|
+
changes = {};
|
|
4694
|
+
this.changes[changeTree.refId] = changes;
|
|
3978
4695
|
}
|
|
3979
4696
|
// set tag
|
|
3980
4697
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -3990,82 +4707,76 @@ class StateView {
|
|
|
3990
4707
|
tags = this.tags.get(changeTree);
|
|
3991
4708
|
}
|
|
3992
4709
|
tags.add(tag);
|
|
3993
|
-
// console.log("BY TAG:", tag);
|
|
3994
4710
|
// Ref: add tagged properties
|
|
3995
|
-
metadata?.[
|
|
4711
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
3996
4712
|
if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
3997
|
-
changes
|
|
4713
|
+
changes[index] = OPERATION.ADD;
|
|
3998
4714
|
}
|
|
3999
4715
|
});
|
|
4000
4716
|
}
|
|
4001
4717
|
else {
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
// metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
|
|
4005
|
-
// if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
4006
|
-
// changes.set(index, OPERATION.ADD);
|
|
4007
|
-
// }
|
|
4008
|
-
// });
|
|
4009
|
-
const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4718
|
+
const isInvisible = this.invisible.has(changeTree);
|
|
4719
|
+
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4010
4720
|
? changeTree.allFilteredChanges
|
|
4011
4721
|
: changeTree.allChanges;
|
|
4012
|
-
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4722
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4723
|
+
const index = changeSet.operations[i];
|
|
4724
|
+
if (index === undefined) {
|
|
4725
|
+
continue;
|
|
4726
|
+
} // skip "undefined" indexes
|
|
4727
|
+
const op = changeTree.indexedOperations[index];
|
|
4728
|
+
const tagAtIndex = metadata?.[index].tag;
|
|
4729
|
+
if ((isInvisible || // if "invisible", include all
|
|
4730
|
+
tagAtIndex === undefined || // "all change" with no tag
|
|
4731
|
+
tagAtIndex === tag // tagged property
|
|
4732
|
+
) &&
|
|
4733
|
+
op !== OPERATION.DELETE) {
|
|
4734
|
+
changes[index] = op;
|
|
4018
4735
|
}
|
|
4019
4736
|
}
|
|
4020
4737
|
}
|
|
4021
|
-
//
|
|
4022
|
-
|
|
4023
|
-
|
|
4024
|
-
(
|
|
4025
|
-
|
|
4026
|
-
|
|
4738
|
+
// Add children of this ChangeTree to this view
|
|
4739
|
+
changeTree.forEachChild((change, index) => {
|
|
4740
|
+
// Do not ADD children that don't have the same tag
|
|
4741
|
+
if (metadata && metadata[index].tag !== tag) {
|
|
4742
|
+
return;
|
|
4743
|
+
}
|
|
4744
|
+
this.add(change.ref, tag, false);
|
|
4745
|
+
});
|
|
4027
4746
|
return this;
|
|
4028
4747
|
}
|
|
4029
|
-
addParent(changeTree, tag) {
|
|
4030
|
-
|
|
4031
|
-
|
|
4032
|
-
|
|
4748
|
+
addParent(changeTree, parentIndex, tag) {
|
|
4749
|
+
// view must have all "changeTree" parent tree
|
|
4750
|
+
this.items.add(changeTree);
|
|
4751
|
+
// add parent's parent
|
|
4752
|
+
const parentChangeTree = changeTree.parent?.[$changes];
|
|
4753
|
+
if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
|
|
4754
|
+
this.addParent(parentChangeTree, changeTree.parentIndex, tag);
|
|
4033
4755
|
}
|
|
4034
|
-
|
|
4035
|
-
|
|
4036
|
-
if (!this.invisible.has(parentChangeTree)) {
|
|
4037
|
-
// parent is already available, no need to add it!
|
|
4756
|
+
// parent is already available, no need to add it!
|
|
4757
|
+
if (!this.invisible.has(changeTree)) {
|
|
4038
4758
|
return;
|
|
4039
4759
|
}
|
|
4040
|
-
this.addParent(parentChangeTree, tag);
|
|
4041
4760
|
// add parent's tag properties
|
|
4042
|
-
if (
|
|
4043
|
-
let
|
|
4044
|
-
if (
|
|
4045
|
-
|
|
4046
|
-
this.changes.
|
|
4761
|
+
if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
|
|
4762
|
+
let changes = this.changes[changeTree.refId];
|
|
4763
|
+
if (changes === undefined) {
|
|
4764
|
+
changes = {};
|
|
4765
|
+
this.changes[changeTree.refId] = changes;
|
|
4047
4766
|
}
|
|
4048
|
-
// console.log("add parent change", {
|
|
4049
|
-
// parentIndex,
|
|
4050
|
-
// parentChanges,
|
|
4051
|
-
// parentChange: (
|
|
4052
|
-
// parentChangeTree.getChange(parentIndex) &&
|
|
4053
|
-
// OPERATION[parentChangeTree.getChange(parentIndex)]
|
|
4054
|
-
// ),
|
|
4055
|
-
// })
|
|
4056
4767
|
if (!this.tags) {
|
|
4057
4768
|
this.tags = new WeakMap();
|
|
4058
4769
|
}
|
|
4059
4770
|
let tags;
|
|
4060
|
-
if (!this.tags.has(
|
|
4771
|
+
if (!this.tags.has(changeTree)) {
|
|
4061
4772
|
tags = new Set();
|
|
4062
|
-
this.tags.set(
|
|
4773
|
+
this.tags.set(changeTree, tags);
|
|
4063
4774
|
}
|
|
4064
4775
|
else {
|
|
4065
|
-
tags = this.tags.get(
|
|
4776
|
+
tags = this.tags.get(changeTree);
|
|
4066
4777
|
}
|
|
4067
4778
|
tags.add(tag);
|
|
4068
|
-
|
|
4779
|
+
changes[parentIndex] = OPERATION.ADD;
|
|
4069
4780
|
}
|
|
4070
4781
|
}
|
|
4071
4782
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4077,32 +4788,32 @@ class StateView {
|
|
|
4077
4788
|
this.items.delete(changeTree);
|
|
4078
4789
|
const ref = changeTree.ref;
|
|
4079
4790
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4080
|
-
let changes = this.changes.
|
|
4791
|
+
let changes = this.changes[changeTree.refId];
|
|
4081
4792
|
if (changes === undefined) {
|
|
4082
|
-
changes =
|
|
4083
|
-
this.changes.
|
|
4793
|
+
changes = {};
|
|
4794
|
+
this.changes[changeTree.refId] = changes;
|
|
4084
4795
|
}
|
|
4085
4796
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4086
4797
|
// parent is collection (Map/Array)
|
|
4087
4798
|
const parent = changeTree.parent;
|
|
4088
4799
|
if (!Metadata.isValidInstance(parent)) {
|
|
4089
4800
|
const parentChangeTree = parent[$changes];
|
|
4090
|
-
let changes = this.changes.
|
|
4801
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4091
4802
|
if (changes === undefined) {
|
|
4092
|
-
changes =
|
|
4093
|
-
this.changes.
|
|
4803
|
+
changes = {};
|
|
4804
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4094
4805
|
}
|
|
4095
4806
|
// DELETE / DELETE BY REF ID
|
|
4096
|
-
changes
|
|
4807
|
+
changes[changeTree.parentIndex] = OPERATION.DELETE;
|
|
4097
4808
|
}
|
|
4098
4809
|
else {
|
|
4099
4810
|
// delete all "tagged" properties.
|
|
4100
|
-
metadata[
|
|
4811
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = OPERATION.DELETE);
|
|
4101
4812
|
}
|
|
4102
4813
|
}
|
|
4103
4814
|
else {
|
|
4104
4815
|
// delete only tagged properties
|
|
4105
|
-
metadata[
|
|
4816
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = OPERATION.DELETE);
|
|
4106
4817
|
}
|
|
4107
4818
|
// remove tag
|
|
4108
4819
|
if (this.tags && this.tags.has(changeTree)) {
|
|
@@ -4129,5 +4840,5 @@ registerType("array", { constructor: ArraySchema });
|
|
|
4129
4840
|
registerType("set", { constructor: SetSchema });
|
|
4130
4841
|
registerType("collection", { constructor: CollectionSchema, });
|
|
4131
4842
|
|
|
4132
|
-
export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, registerType, type, view };
|
|
4843
|
+
export { $changes, $childType, $decoder, $deleteByIndex, $encoder, $filter, $getByIndex, $track, ArraySchema, ChangeTree, CollectionSchema, Decoder, Encoder, MapSchema, Metadata, OPERATION, Reflection, ReflectionField, ReflectionType, Schema, SetSchema, StateView, TypeContext, decode, decodeKeyValueOperation, decodeSchemaOperation, defineTypes, deprecated, dumpChanges, encode, encodeArray as encodeKeyValueOperation, encodeSchemaOperation, getDecoderStateCallbacks, getRawChangesCallback, registerType, schema, type, view };
|
|
4133
4844
|
//# sourceMappingURL=index.mjs.map
|