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