@colyseus/schema 3.0.0-alpha.3 → 3.0.0-alpha.31
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +131 -61
- package/build/cjs/index.js +966 -563
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +965 -562
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +966 -563
- package/lib/Metadata.d.ts +15 -4
- package/lib/Metadata.js +86 -18
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +2 -3
- package/lib/Reflection.js +24 -28
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +2 -2
- package/lib/Schema.js +28 -41
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +1 -21
- package/lib/annotations.js +73 -153
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.d.ts +1 -0
- package/lib/bench_encode.js +142 -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 +2 -3
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +3 -3
- package/lib/codegen/types.js.map +1 -1
- package/lib/debug.d.ts +1 -0
- package/lib/debug.js +52 -0
- package/lib/debug.js.map +1 -0
- package/lib/decoder/DecodeOperation.d.ts +0 -1
- package/lib/decoder/DecodeOperation.js +23 -11
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +6 -7
- package/lib/decoder/Decoder.js +8 -8
- 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 +9 -19
- package/lib/encoder/ChangeTree.js +129 -145
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +1 -5
- package/lib/encoder/EncodeOperation.js +74 -58
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +10 -8
- package/lib/encoder/Encoder.js +89 -56
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +17 -0
- package/lib/encoder/Root.js +44 -0
- package/lib/encoder/Root.js.map +1 -0
- package/lib/encoder/StateView.d.ts +2 -2
- package/lib/encoder/StateView.js +49 -59
- 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 +21 -22
- package/lib/encoding/decode.js.map +1 -1
- package/lib/encoding/encode.d.ts +2 -2
- package/lib/encoding/encode.js +40 -39
- 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 +18 -13
- package/lib/index.js.map +1 -1
- package/lib/types/TypeContext.d.ts +23 -0
- package/lib/types/TypeContext.js +102 -0
- package/lib/types/TypeContext.js.map +1 -0
- package/lib/types/custom/ArraySchema.d.ts +2 -2
- package/lib/types/custom/ArraySchema.js +6 -9
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.js +1 -0
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.js +5 -0
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.js +1 -0
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/registry.js +3 -4
- package/lib/types/registry.js.map +1 -1
- package/lib/types/symbols.d.ts +1 -0
- package/lib/types/symbols.js +2 -1
- package/lib/types/symbols.js.map +1 -1
- package/lib/types/utils.js +1 -2
- package/lib/types/utils.js.map +1 -1
- package/lib/utils.js +3 -4
- package/lib/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/Metadata.ts +104 -26
- package/src/Reflection.ts +26 -28
- package/src/Schema.ts +35 -47
- package/src/annotations.ts +82 -176
- package/src/bench_encode.ts +121 -0
- package/src/debug.ts +56 -0
- package/src/decoder/DecodeOperation.ts +28 -11
- package/src/decoder/Decoder.ts +13 -11
- package/src/decoder/ReferenceTracker.ts +3 -2
- package/src/decoder/strategy/StateCallbacks.ts +152 -81
- package/src/encoder/ChangeTree.ts +147 -166
- package/src/encoder/EncodeOperation.ts +93 -70
- package/src/encoder/Encoder.ts +111 -65
- package/src/encoder/Root.ts +51 -0
- package/src/encoder/StateView.ts +53 -69
- package/src/encoding/assert.ts +4 -3
- package/src/encoding/decode.ts +1 -2
- package/src/encoding/encode.ts +25 -22
- package/src/encoding/spec.ts +1 -0
- package/src/index.ts +8 -14
- package/src/types/TypeContext.ts +122 -0
- package/src/types/custom/ArraySchema.ts +10 -2
- package/src/types/custom/CollectionSchema.ts +1 -0
- package/src/types/custom/MapSchema.ts +6 -0
- package/src/types/custom/SetSchema.ts +1 -0
- package/src/types/symbols.ts +2 -0
package/build/umd/index.js
CHANGED
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
|
|
30
30
|
OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
|
|
31
31
|
OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
|
|
32
|
+
OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
|
|
32
33
|
})(exports.OPERATION || (exports.OPERATION = {}));
|
|
33
34
|
|
|
34
35
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
@@ -39,6 +40,7 @@
|
|
|
39
40
|
const $filter = Symbol("$filter");
|
|
40
41
|
const $getByIndex = Symbol("$getByIndex");
|
|
41
42
|
const $deleteByIndex = Symbol("$deleteByIndex");
|
|
43
|
+
const $descriptors = Symbol("$descriptors");
|
|
42
44
|
/**
|
|
43
45
|
* Used to hold ChangeTree instances whitin the structures
|
|
44
46
|
*/
|
|
@@ -74,34 +76,67 @@
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
78
|
const Metadata = {
|
|
77
|
-
addField(metadata, index,
|
|
79
|
+
addField(metadata, index, name, type, descriptor) {
|
|
78
80
|
if (index > 64) {
|
|
79
|
-
throw new Error(`Can't define field '${
|
|
81
|
+
throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
|
|
80
82
|
}
|
|
81
|
-
metadata[
|
|
83
|
+
metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
|
|
82
84
|
{
|
|
83
85
|
type: (Array.isArray(type))
|
|
84
86
|
? { array: type[0] }
|
|
85
87
|
: type,
|
|
86
88
|
index,
|
|
87
|
-
|
|
89
|
+
name,
|
|
88
90
|
});
|
|
91
|
+
// create "descriptors" map
|
|
92
|
+
metadata[$descriptors] ??= {};
|
|
93
|
+
if (descriptor) {
|
|
94
|
+
// for encoder
|
|
95
|
+
metadata[$descriptors][name] = descriptor;
|
|
96
|
+
metadata[$descriptors][`_${name}`] = {
|
|
97
|
+
value: undefined,
|
|
98
|
+
writable: true,
|
|
99
|
+
enumerable: false,
|
|
100
|
+
configurable: true,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// for decoder
|
|
105
|
+
metadata[$descriptors][name] = {
|
|
106
|
+
value: undefined,
|
|
107
|
+
writable: true,
|
|
108
|
+
enumerable: true,
|
|
109
|
+
configurable: true,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
89
112
|
// map -1 as last field index
|
|
90
113
|
Object.defineProperty(metadata, -1, {
|
|
91
114
|
value: index,
|
|
92
115
|
enumerable: false,
|
|
93
116
|
configurable: true
|
|
94
117
|
});
|
|
95
|
-
// map
|
|
96
|
-
Object.defineProperty(metadata,
|
|
97
|
-
value:
|
|
118
|
+
// map field name => index (non enumerable)
|
|
119
|
+
Object.defineProperty(metadata, name, {
|
|
120
|
+
value: index,
|
|
98
121
|
enumerable: false,
|
|
99
122
|
configurable: true,
|
|
100
123
|
});
|
|
124
|
+
// if child Ref/complex type, add to -4
|
|
125
|
+
if (typeof (metadata[index].type) !== "string") {
|
|
126
|
+
if (metadata[-4] === undefined) {
|
|
127
|
+
Object.defineProperty(metadata, -4, {
|
|
128
|
+
value: [],
|
|
129
|
+
enumerable: false,
|
|
130
|
+
configurable: true,
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
metadata[-4].push(index);
|
|
134
|
+
}
|
|
101
135
|
},
|
|
102
136
|
setTag(metadata, fieldName, tag) {
|
|
137
|
+
const index = metadata[fieldName];
|
|
138
|
+
const field = metadata[index];
|
|
103
139
|
// add 'tag' to the field
|
|
104
|
-
const field = metadata[fieldName];
|
|
105
140
|
field.tag = tag;
|
|
106
141
|
if (!metadata[-2]) {
|
|
107
142
|
// -2: all field indexes with "view" tag
|
|
@@ -117,20 +152,14 @@
|
|
|
117
152
|
configurable: true
|
|
118
153
|
});
|
|
119
154
|
}
|
|
120
|
-
metadata[-2].push(
|
|
155
|
+
metadata[-2].push(index);
|
|
121
156
|
if (!metadata[-3][tag]) {
|
|
122
157
|
metadata[-3][tag] = [];
|
|
123
158
|
}
|
|
124
|
-
metadata[-3][tag].push(
|
|
159
|
+
metadata[-3][tag].push(index);
|
|
125
160
|
},
|
|
126
161
|
setFields(target, fields) {
|
|
127
162
|
const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
|
|
128
|
-
// target[$track] = function (changeTree, index: number, operation: OPERATION = OPERATION.ADD) {
|
|
129
|
-
// changeTree.change(index, operation, encodeSchemaOperation);
|
|
130
|
-
// };
|
|
131
|
-
// target[$encoder] = encodeSchemaOperation;
|
|
132
|
-
// target[$decoder] = decodeSchemaOperation;
|
|
133
|
-
// if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
|
|
134
163
|
let index = 0;
|
|
135
164
|
for (const field in fields) {
|
|
136
165
|
const type = fields[field];
|
|
@@ -138,13 +167,53 @@
|
|
|
138
167
|
const complexTypeKlass = (Array.isArray(type))
|
|
139
168
|
? getType("array")
|
|
140
169
|
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
141
|
-
Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass
|
|
170
|
+
Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
|
|
142
171
|
index++;
|
|
143
172
|
}
|
|
144
173
|
},
|
|
145
174
|
isDeprecated(metadata, field) {
|
|
146
175
|
return metadata[field].deprecated === true;
|
|
147
176
|
},
|
|
177
|
+
init(klass) {
|
|
178
|
+
//
|
|
179
|
+
// Used only to initialize an empty Schema (Encoder#constructor)
|
|
180
|
+
// TODO: remove/refactor this...
|
|
181
|
+
//
|
|
182
|
+
const metadata = {};
|
|
183
|
+
klass[Symbol.metadata] = metadata;
|
|
184
|
+
Object.defineProperty(metadata, -1, {
|
|
185
|
+
value: 0,
|
|
186
|
+
enumerable: false,
|
|
187
|
+
configurable: true,
|
|
188
|
+
});
|
|
189
|
+
},
|
|
190
|
+
initialize(constructor, parentMetadata) {
|
|
191
|
+
let metadata = constructor[Symbol.metadata] ?? Object.create(null);
|
|
192
|
+
// make sure inherited classes have their own metadata object.
|
|
193
|
+
if (constructor[Symbol.metadata] === parentMetadata) {
|
|
194
|
+
metadata = Object.create(null);
|
|
195
|
+
if (parentMetadata) {
|
|
196
|
+
// assign parent metadata to current
|
|
197
|
+
Object.assign(metadata, parentMetadata);
|
|
198
|
+
for (let i = 0; i <= parentMetadata[-1]; i++) {
|
|
199
|
+
const fieldName = parentMetadata[i].name;
|
|
200
|
+
Object.defineProperty(metadata, fieldName, {
|
|
201
|
+
value: parentMetadata[fieldName],
|
|
202
|
+
enumerable: false,
|
|
203
|
+
configurable: true,
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
Object.defineProperty(metadata, -1, {
|
|
207
|
+
value: parentMetadata[-1],
|
|
208
|
+
enumerable: false,
|
|
209
|
+
configurable: true,
|
|
210
|
+
writable: true,
|
|
211
|
+
});
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
constructor[Symbol.metadata] = metadata;
|
|
215
|
+
return metadata;
|
|
216
|
+
},
|
|
148
217
|
isValidInstance(klass) {
|
|
149
218
|
return (klass.constructor[Symbol.metadata] &&
|
|
150
219
|
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], -1));
|
|
@@ -153,97 +222,68 @@
|
|
|
153
222
|
const metadata = klass[Symbol.metadata];
|
|
154
223
|
const fields = {};
|
|
155
224
|
for (let i = 0; i <= metadata[-1]; i++) {
|
|
156
|
-
fields[metadata[i]] = metadata[
|
|
225
|
+
fields[metadata[i].name] = metadata[i].type;
|
|
157
226
|
}
|
|
158
227
|
return fields;
|
|
159
228
|
}
|
|
160
229
|
};
|
|
161
230
|
|
|
162
231
|
var _a$5;
|
|
163
|
-
class Root {
|
|
164
|
-
constructor() {
|
|
165
|
-
this.nextUniqueId = 0;
|
|
166
|
-
this.refCount = new WeakMap();
|
|
167
|
-
// all changes
|
|
168
|
-
this.allChanges = new Map();
|
|
169
|
-
this.allFilteredChanges = new Map();
|
|
170
|
-
// pending changes to be encoded
|
|
171
|
-
this.changes = new Map();
|
|
172
|
-
this.filteredChanges = new Map();
|
|
173
|
-
}
|
|
174
|
-
getNextUniqueId() {
|
|
175
|
-
return this.nextUniqueId++;
|
|
176
|
-
}
|
|
177
|
-
add(changeTree) {
|
|
178
|
-
const refCount = this.refCount.get(changeTree) || 0;
|
|
179
|
-
this.refCount.set(changeTree, refCount + 1);
|
|
180
|
-
}
|
|
181
|
-
remove(changeTree) {
|
|
182
|
-
const refCount = this.refCount.get(changeTree);
|
|
183
|
-
if (refCount <= 1) {
|
|
184
|
-
this.allChanges.delete(changeTree);
|
|
185
|
-
this.changes.delete(changeTree);
|
|
186
|
-
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
187
|
-
this.allFilteredChanges.delete(changeTree);
|
|
188
|
-
this.filteredChanges.delete(changeTree);
|
|
189
|
-
}
|
|
190
|
-
this.refCount.delete(changeTree);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
this.refCount.set(changeTree, refCount - 1);
|
|
194
|
-
}
|
|
195
|
-
changeTree.forEachChild((child, _) => this.remove(child));
|
|
196
|
-
}
|
|
197
|
-
clear() {
|
|
198
|
-
this.changes.clear();
|
|
199
|
-
}
|
|
200
|
-
}
|
|
201
232
|
class ChangeTree {
|
|
202
233
|
static { _a$5 = $isNew; }
|
|
203
|
-
;
|
|
204
234
|
constructor(ref) {
|
|
205
|
-
this.
|
|
235
|
+
this.isFiltered = false;
|
|
236
|
+
this.isPartiallyFiltered = false;
|
|
206
237
|
this.currentOperationIndex = 0;
|
|
207
|
-
this.allChanges = new Map();
|
|
208
|
-
this.allFilteredChanges = new Map();
|
|
209
238
|
this.changes = new Map();
|
|
210
|
-
this.
|
|
239
|
+
this.allChanges = new Map();
|
|
211
240
|
this[_a$5] = true;
|
|
212
241
|
this.ref = ref;
|
|
242
|
+
//
|
|
243
|
+
// Does this structure have "filters" declared?
|
|
244
|
+
//
|
|
245
|
+
if (ref.constructor[Symbol.metadata]?.[-2]) {
|
|
246
|
+
this.allFilteredChanges = new Map();
|
|
247
|
+
this.filteredChanges = new Map();
|
|
248
|
+
}
|
|
213
249
|
}
|
|
214
250
|
setRoot(root) {
|
|
215
251
|
this.root = root;
|
|
216
252
|
this.root.add(this);
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
253
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
254
|
+
if (this.root.types.hasFilters) {
|
|
255
|
+
//
|
|
256
|
+
// At Schema initialization, the "root" structure might not be available
|
|
257
|
+
// yet, as it only does once the "Encoder" has been set up.
|
|
258
|
+
//
|
|
259
|
+
// So the "parent" may be already set without a "root".
|
|
260
|
+
//
|
|
261
|
+
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
262
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
263
|
+
this.root.allFilteredChanges.set(this, this.allFilteredChanges);
|
|
264
|
+
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
265
|
+
}
|
|
266
|
+
}
|
|
226
267
|
if (!this.isFiltered) {
|
|
227
268
|
this.root.changes.set(this, this.changes);
|
|
269
|
+
this.root.allChanges.set(this, this.allChanges);
|
|
228
270
|
}
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
271
|
+
this.ensureRefId();
|
|
272
|
+
if (metadata) {
|
|
273
|
+
metadata[-4]?.forEach((index) => {
|
|
274
|
+
const field = metadata[index];
|
|
275
|
+
const value = this.ref[field.name];
|
|
276
|
+
if (value) {
|
|
277
|
+
value[$changes].setRoot(root);
|
|
278
|
+
}
|
|
279
|
+
});
|
|
234
280
|
}
|
|
235
|
-
if (
|
|
236
|
-
|
|
281
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
282
|
+
// MapSchema / ArraySchema, etc.
|
|
283
|
+
this.ref.forEach((value, key) => {
|
|
284
|
+
value[$changes].setRoot(root);
|
|
285
|
+
});
|
|
237
286
|
}
|
|
238
|
-
this.forEachChild((changeTree, _) => {
|
|
239
|
-
changeTree.setRoot(root);
|
|
240
|
-
});
|
|
241
|
-
// this.allChanges.forEach((_, index) => {
|
|
242
|
-
// const childRef = this.ref[$getByIndex](index);
|
|
243
|
-
// if (childRef && childRef[$changes]) {
|
|
244
|
-
// childRef[$changes].setRoot(root);
|
|
245
|
-
// }
|
|
246
|
-
// });
|
|
247
287
|
}
|
|
248
288
|
setParent(parent, root, parentIndex) {
|
|
249
289
|
this.parent = parent;
|
|
@@ -253,50 +293,60 @@
|
|
|
253
293
|
return;
|
|
254
294
|
}
|
|
255
295
|
root.add(this);
|
|
296
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
256
297
|
// skip if parent is already set
|
|
257
|
-
if (root
|
|
258
|
-
this.
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
298
|
+
if (root !== this.root) {
|
|
299
|
+
this.root = root;
|
|
300
|
+
if (root.types.hasFilters) {
|
|
301
|
+
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
302
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
303
|
+
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
304
|
+
this.root.allFilteredChanges.set(this, this.filteredChanges);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
if (!this.isFiltered) {
|
|
308
|
+
this.root.changes.set(this, this.changes);
|
|
309
|
+
this.root.allChanges.set(this, this.allChanges);
|
|
310
|
+
}
|
|
311
|
+
this.ensureRefId();
|
|
267
312
|
}
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
313
|
+
// assign same parent on child structures
|
|
314
|
+
if (metadata) {
|
|
315
|
+
metadata[-4]?.forEach((index) => {
|
|
316
|
+
const field = metadata[index];
|
|
317
|
+
const value = this.ref[field.name];
|
|
318
|
+
value?.[$changes].setParent(this.ref, root, index);
|
|
319
|
+
// console.log(this.ref.constructor.name, field.name, value);
|
|
320
|
+
// try { throw new Error(); } catch (e) {
|
|
321
|
+
// console.log(e.stack);
|
|
322
|
+
// }
|
|
323
|
+
});
|
|
271
324
|
}
|
|
272
|
-
else {
|
|
273
|
-
|
|
325
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
326
|
+
// MapSchema / ArraySchema, etc.
|
|
327
|
+
this.ref.forEach((value, key) => {
|
|
328
|
+
value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
|
|
329
|
+
});
|
|
274
330
|
}
|
|
275
|
-
this.ensureRefId();
|
|
276
|
-
this.forEachChild((changeTree, atIndex) => {
|
|
277
|
-
changeTree.setParent(this.ref, root, atIndex);
|
|
278
|
-
});
|
|
279
331
|
}
|
|
280
332
|
forEachChild(callback) {
|
|
281
333
|
//
|
|
282
334
|
// assign same parent on child structures
|
|
283
335
|
//
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
const value = this.ref[field];
|
|
289
|
-
if (value
|
|
290
|
-
callback(value[$changes],
|
|
336
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
337
|
+
if (metadata) {
|
|
338
|
+
metadata[-4]?.forEach((index) => {
|
|
339
|
+
const field = metadata[index];
|
|
340
|
+
const value = this.ref[field.name];
|
|
341
|
+
if (value) {
|
|
342
|
+
callback(value[$changes], index);
|
|
291
343
|
}
|
|
292
|
-
}
|
|
344
|
+
});
|
|
293
345
|
}
|
|
294
|
-
else if (typeof (this.ref)
|
|
346
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
295
347
|
// MapSchema / ArraySchema, etc.
|
|
296
348
|
this.ref.forEach((value, key) => {
|
|
297
|
-
|
|
298
|
-
callback(value[$changes], this.ref[$changes].indexes[key]);
|
|
299
|
-
}
|
|
349
|
+
callback(value[$changes], this.indexes[key] ?? key);
|
|
300
350
|
});
|
|
301
351
|
}
|
|
302
352
|
}
|
|
@@ -305,8 +355,8 @@
|
|
|
305
355
|
this.root?.changes.set(this, this.changes);
|
|
306
356
|
}
|
|
307
357
|
change(index, operation = exports.OPERATION.ADD) {
|
|
308
|
-
const metadata = this.ref
|
|
309
|
-
const isFiltered = this.isFiltered || (metadata
|
|
358
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
359
|
+
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
310
360
|
const changeSet = (isFiltered)
|
|
311
361
|
? this.filteredChanges
|
|
312
362
|
: this.changes;
|
|
@@ -317,14 +367,15 @@
|
|
|
317
367
|
: (previousOperation === exports.OPERATION.DELETE)
|
|
318
368
|
? exports.OPERATION.DELETE_AND_ADD
|
|
319
369
|
: operation;
|
|
370
|
+
//
|
|
371
|
+
// TODO: are DELETE operations being encoded as ADD here ??
|
|
372
|
+
//
|
|
320
373
|
changeSet.set(index, op);
|
|
321
374
|
}
|
|
322
|
-
//
|
|
323
|
-
// TODO: are DELETE operations being encoded as ADD here ??
|
|
324
|
-
//
|
|
325
375
|
if (isFiltered) {
|
|
326
376
|
this.allFilteredChanges.set(index, exports.OPERATION.ADD);
|
|
327
377
|
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
378
|
+
this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
|
|
328
379
|
}
|
|
329
380
|
else {
|
|
330
381
|
this.allChanges.set(index, exports.OPERATION.ADD);
|
|
@@ -363,7 +414,6 @@
|
|
|
363
414
|
}
|
|
364
415
|
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
|
|
365
416
|
Array.from(allChangeSet.entries()).forEach(([index, op]) => {
|
|
366
|
-
// console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
|
|
367
417
|
if (index >= startIndex) {
|
|
368
418
|
allChangeSet.delete(index);
|
|
369
419
|
allChangeSet.set(index + shiftIndex, op);
|
|
@@ -371,9 +421,7 @@
|
|
|
371
421
|
});
|
|
372
422
|
}
|
|
373
423
|
indexedOperation(index, operation, allChangesIndex = index) {
|
|
374
|
-
|
|
375
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
376
|
-
if (isFiltered) {
|
|
424
|
+
if (this.filteredChanges !== undefined) {
|
|
377
425
|
this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
|
|
378
426
|
this.filteredChanges.set(index, operation);
|
|
379
427
|
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
@@ -386,8 +434,8 @@
|
|
|
386
434
|
}
|
|
387
435
|
getType(index) {
|
|
388
436
|
if (Metadata.isValidInstance(this.ref)) {
|
|
389
|
-
const metadata = this.ref
|
|
390
|
-
return metadata[
|
|
437
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
438
|
+
return metadata[index].type;
|
|
391
439
|
}
|
|
392
440
|
else {
|
|
393
441
|
//
|
|
@@ -401,7 +449,7 @@
|
|
|
401
449
|
}
|
|
402
450
|
getChange(index) {
|
|
403
451
|
// TODO: optimize this. avoid checking against multiple instances
|
|
404
|
-
return this.changes.get(index) ?? this.filteredChanges
|
|
452
|
+
return this.changes.get(index) ?? this.filteredChanges?.get(index);
|
|
405
453
|
}
|
|
406
454
|
//
|
|
407
455
|
// used during `.encode()`
|
|
@@ -422,9 +470,7 @@
|
|
|
422
470
|
}
|
|
423
471
|
return;
|
|
424
472
|
}
|
|
425
|
-
const
|
|
426
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
427
|
-
const changeSet = (isFiltered)
|
|
473
|
+
const changeSet = (this.filteredChanges)
|
|
428
474
|
? this.filteredChanges
|
|
429
475
|
: this.changes;
|
|
430
476
|
const previousValue = this.getValue(index);
|
|
@@ -447,7 +493,7 @@
|
|
|
447
493
|
//
|
|
448
494
|
// FIXME: this is looking a bit ugly (and repeated from `.change()`)
|
|
449
495
|
//
|
|
450
|
-
if (
|
|
496
|
+
if (this.filteredChanges) {
|
|
451
497
|
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
452
498
|
this.allFilteredChanges.delete(allChangesIndex);
|
|
453
499
|
}
|
|
@@ -458,6 +504,7 @@
|
|
|
458
504
|
}
|
|
459
505
|
endEncode() {
|
|
460
506
|
this.changes.clear();
|
|
507
|
+
// ArraySchema and MapSchema have a custom "encode end" method
|
|
461
508
|
this.ref[$onEncodeEnd]?.();
|
|
462
509
|
// Not a new instance anymore
|
|
463
510
|
delete this[$isNew];
|
|
@@ -470,12 +517,12 @@
|
|
|
470
517
|
//
|
|
471
518
|
this.ref[$onEncodeEnd]?.();
|
|
472
519
|
this.changes.clear();
|
|
473
|
-
this.filteredChanges
|
|
520
|
+
this.filteredChanges?.clear();
|
|
474
521
|
// reset operation index
|
|
475
522
|
this.currentOperationIndex = 0;
|
|
476
523
|
if (discardAll) {
|
|
477
524
|
this.allChanges.clear();
|
|
478
|
-
this.allFilteredChanges
|
|
525
|
+
this.allFilteredChanges?.clear();
|
|
479
526
|
// remove children references
|
|
480
527
|
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
481
528
|
}
|
|
@@ -502,33 +549,41 @@
|
|
|
502
549
|
get changed() {
|
|
503
550
|
return this.changes.size > 0;
|
|
504
551
|
}
|
|
505
|
-
checkIsFiltered(parent, parentIndex) {
|
|
552
|
+
checkIsFiltered(metadata, parent, parentIndex) {
|
|
506
553
|
// Detect if current structure has "filters" declared
|
|
507
|
-
this.isPartiallyFiltered =
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
const metadata = parent['constructor'][Symbol.metadata];
|
|
512
|
-
const fieldName = metadata?.[parentIndex];
|
|
513
|
-
const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
|
|
514
|
-
this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
|
|
515
|
-
parent = parent[$changes].parent;
|
|
554
|
+
this.isPartiallyFiltered = metadata?.[-2] !== undefined;
|
|
555
|
+
if (this.isPartiallyFiltered) {
|
|
556
|
+
this.filteredChanges = this.filteredChanges || new Map();
|
|
557
|
+
this.allFilteredChanges = this.allFilteredChanges || new Map();
|
|
516
558
|
}
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
//
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
this.
|
|
559
|
+
if (parent) {
|
|
560
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
561
|
+
const parentChangeTree = parent[$changes];
|
|
562
|
+
parent = parentChangeTree.parent;
|
|
563
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
564
|
+
}
|
|
565
|
+
const parentMetadata = parent?.constructor?.[Symbol.metadata];
|
|
566
|
+
this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
|
|
567
|
+
//
|
|
568
|
+
// TODO: refactor this!
|
|
569
|
+
//
|
|
570
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
571
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
572
|
+
//
|
|
573
|
+
if (this.isFiltered) {
|
|
574
|
+
this.filteredChanges = new Map();
|
|
575
|
+
this.allFilteredChanges = new Map();
|
|
576
|
+
if (this.changes.size > 0) {
|
|
577
|
+
// swap changes reference
|
|
578
|
+
const changes = this.changes;
|
|
579
|
+
this.changes = this.filteredChanges;
|
|
580
|
+
this.filteredChanges = changes;
|
|
581
|
+
// swap "all changes" reference
|
|
582
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
583
|
+
this.allFilteredChanges = this.allChanges;
|
|
584
|
+
this.allChanges = allFilteredChanges;
|
|
585
|
+
}
|
|
586
|
+
}
|
|
532
587
|
}
|
|
533
588
|
}
|
|
534
589
|
}
|
|
@@ -565,26 +620,29 @@
|
|
|
565
620
|
textEncoder = new TextEncoder();
|
|
566
621
|
}
|
|
567
622
|
catch (e) { }
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
623
|
+
const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
|
|
624
|
+
const utf8Length = (hasBufferByteLength)
|
|
625
|
+
? Buffer.byteLength // node
|
|
626
|
+
: function (str, _) {
|
|
627
|
+
var c = 0, length = 0;
|
|
628
|
+
for (var i = 0, l = str.length; i < l; i++) {
|
|
629
|
+
c = str.charCodeAt(i);
|
|
630
|
+
if (c < 0x80) {
|
|
631
|
+
length += 1;
|
|
632
|
+
}
|
|
633
|
+
else if (c < 0x800) {
|
|
634
|
+
length += 2;
|
|
635
|
+
}
|
|
636
|
+
else if (c < 0xd800 || c >= 0xe000) {
|
|
637
|
+
length += 3;
|
|
638
|
+
}
|
|
639
|
+
else {
|
|
640
|
+
i++;
|
|
641
|
+
length += 4;
|
|
642
|
+
}
|
|
584
643
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
}
|
|
644
|
+
return length;
|
|
645
|
+
};
|
|
588
646
|
function utf8Write(view, str, it) {
|
|
589
647
|
var c = 0;
|
|
590
648
|
for (var i = 0, l = str.length; i < l; i++) {
|
|
@@ -679,8 +737,7 @@
|
|
|
679
737
|
if (!value) {
|
|
680
738
|
value = "";
|
|
681
739
|
}
|
|
682
|
-
|
|
683
|
-
let length = Buffer.byteLength(value, "utf8");
|
|
740
|
+
let length = utf8Length(value, "utf8");
|
|
684
741
|
let size = 0;
|
|
685
742
|
// fixstr
|
|
686
743
|
if (length < 0x20) {
|
|
@@ -791,81 +848,40 @@
|
|
|
791
848
|
|
|
792
849
|
var encode = /*#__PURE__*/Object.freeze({
|
|
793
850
|
__proto__: null,
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
uint8: uint8$1,
|
|
851
|
+
boolean: boolean$1,
|
|
852
|
+
float32: float32$1,
|
|
853
|
+
float64: float64$1,
|
|
798
854
|
int16: int16$1,
|
|
799
|
-
uint16: uint16$1,
|
|
800
855
|
int32: int32$1,
|
|
801
|
-
uint32: uint32$1,
|
|
802
856
|
int64: int64$1,
|
|
857
|
+
int8: int8$1,
|
|
858
|
+
number: number$1,
|
|
859
|
+
string: string$1,
|
|
860
|
+
uint16: uint16$1,
|
|
861
|
+
uint32: uint32$1,
|
|
803
862
|
uint64: uint64$1,
|
|
804
|
-
|
|
805
|
-
|
|
863
|
+
uint8: uint8$1,
|
|
864
|
+
utf8Length: utf8Length,
|
|
865
|
+
utf8Write: utf8Write,
|
|
806
866
|
writeFloat32: writeFloat32,
|
|
807
|
-
writeFloat64: writeFloat64
|
|
808
|
-
boolean: boolean$1,
|
|
809
|
-
string: string$1,
|
|
810
|
-
number: number$1
|
|
867
|
+
writeFloat64: writeFloat64
|
|
811
868
|
});
|
|
812
869
|
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
816
|
-
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
821
|
-
|
|
822
|
-
|
|
823
|
-
|
|
824
|
-
case "int32":
|
|
825
|
-
case "uint32":
|
|
826
|
-
case "int64":
|
|
827
|
-
case "uint64":
|
|
828
|
-
case "float32":
|
|
829
|
-
case "float64":
|
|
830
|
-
typeofTarget = "number";
|
|
831
|
-
if (isNaN(value)) {
|
|
832
|
-
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
833
|
-
}
|
|
834
|
-
break;
|
|
835
|
-
case "string":
|
|
836
|
-
typeofTarget = "string";
|
|
837
|
-
allowNull = true;
|
|
838
|
-
break;
|
|
839
|
-
case "boolean":
|
|
840
|
-
// boolean is always encoded as true/false based on truthiness
|
|
841
|
-
return;
|
|
842
|
-
}
|
|
843
|
-
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
844
|
-
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
845
|
-
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
846
|
-
}
|
|
847
|
-
}
|
|
848
|
-
function assertInstanceType(value, type, klass, field) {
|
|
849
|
-
if (!(value instanceof type)) {
|
|
850
|
-
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
|
|
851
|
-
}
|
|
852
|
-
}
|
|
853
|
-
|
|
854
|
-
function encodePrimitiveType(type, bytes, value, klass, field, it) {
|
|
855
|
-
assertType(value, type, klass, field);
|
|
856
|
-
const encodeFunc = encode[type];
|
|
857
|
-
if (encodeFunc) {
|
|
858
|
-
encodeFunc(bytes, value, it);
|
|
859
|
-
// encodeFunc(bytes, value);
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
870
|
+
function encodeValue(encoder, bytes,
|
|
871
|
+
// ref: Ref,
|
|
872
|
+
type, value,
|
|
873
|
+
// field: string | number,
|
|
874
|
+
operation, it) {
|
|
875
|
+
if (typeof (type) === "string") {
|
|
876
|
+
//
|
|
877
|
+
// Primitive values
|
|
878
|
+
//
|
|
879
|
+
// assertType(value, type as string, ref as Schema, field);
|
|
880
|
+
encode[type]?.(bytes, value, it);
|
|
863
881
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
// TODO: move this to the `@type()` annotation
|
|
868
|
-
assertInstanceType(value, type, ref, field);
|
|
882
|
+
else if (type[Symbol.metadata] !== undefined) {
|
|
883
|
+
// // TODO: move this to the `@type()` annotation
|
|
884
|
+
// assertInstanceType(value, type as typeof Schema, ref as Schema, field);
|
|
869
885
|
//
|
|
870
886
|
// Encode refId for this instance.
|
|
871
887
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -876,21 +892,15 @@
|
|
|
876
892
|
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
877
893
|
}
|
|
878
894
|
}
|
|
879
|
-
else if (typeof (type) === "string") {
|
|
880
|
-
//
|
|
881
|
-
// Primitive values
|
|
882
|
-
//
|
|
883
|
-
encodePrimitiveType(type, bytes, value, ref, field, it);
|
|
884
|
-
}
|
|
885
895
|
else {
|
|
886
|
-
//
|
|
887
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
888
|
-
//
|
|
889
|
-
const definition = getType(Object.keys(type)[0]);
|
|
890
|
-
//
|
|
891
|
-
// ensure a ArraySchema has been provided
|
|
892
|
-
//
|
|
893
|
-
assertInstanceType(ref[field], definition.constructor, ref, field);
|
|
896
|
+
// //
|
|
897
|
+
// // Custom type (MapSchema, ArraySchema, etc)
|
|
898
|
+
// //
|
|
899
|
+
// const definition = getType(Object.keys(type)[0]);
|
|
900
|
+
// //
|
|
901
|
+
// // ensure a ArraySchema has been provided
|
|
902
|
+
// //
|
|
903
|
+
// assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
|
|
894
904
|
//
|
|
895
905
|
// Encode refId for this instance.
|
|
896
906
|
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
@@ -903,26 +913,27 @@
|
|
|
903
913
|
* @private
|
|
904
914
|
*/
|
|
905
915
|
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
906
|
-
const ref = changeTree.ref;
|
|
907
|
-
const metadata = ref['constructor'][Symbol.metadata];
|
|
908
|
-
const field = metadata[index];
|
|
909
|
-
const type = metadata[field].type;
|
|
910
|
-
const value = ref[field];
|
|
911
916
|
// "compress" field index + operation
|
|
912
917
|
bytes[it.offset++] = (index | operation) & 255;
|
|
913
918
|
// Do not encode value for DELETE operations
|
|
914
919
|
if (operation === exports.OPERATION.DELETE) {
|
|
915
920
|
return;
|
|
916
921
|
}
|
|
922
|
+
const ref = changeTree.ref;
|
|
923
|
+
const metadata = ref['constructor'][Symbol.metadata];
|
|
924
|
+
const field = metadata[index];
|
|
917
925
|
// TODO: inline this function call small performance gain
|
|
918
|
-
encodeValue(encoder, bytes,
|
|
926
|
+
encodeValue(encoder, bytes,
|
|
927
|
+
// ref,
|
|
928
|
+
metadata[index].type, ref[field.name],
|
|
929
|
+
// index,
|
|
930
|
+
operation, it);
|
|
919
931
|
};
|
|
920
932
|
/**
|
|
921
933
|
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
922
934
|
* @private
|
|
923
935
|
*/
|
|
924
|
-
const encodeKeyValueOperation = function (encoder, bytes, changeTree,
|
|
925
|
-
const ref = changeTree.ref;
|
|
936
|
+
const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
926
937
|
// encode operation
|
|
927
938
|
bytes[it.offset++] = operation & 255;
|
|
928
939
|
// custom operations
|
|
@@ -930,11 +941,12 @@
|
|
|
930
941
|
return;
|
|
931
942
|
}
|
|
932
943
|
// encode index
|
|
933
|
-
number$1(bytes,
|
|
944
|
+
number$1(bytes, index, it);
|
|
934
945
|
// Do not encode value for DELETE operations
|
|
935
946
|
if (operation === exports.OPERATION.DELETE) {
|
|
936
947
|
return;
|
|
937
948
|
}
|
|
949
|
+
const ref = changeTree.ref;
|
|
938
950
|
//
|
|
939
951
|
// encode "alias" for dynamic fields (maps)
|
|
940
952
|
//
|
|
@@ -943,14 +955,30 @@
|
|
|
943
955
|
//
|
|
944
956
|
// MapSchema dynamic key
|
|
945
957
|
//
|
|
946
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(
|
|
958
|
+
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
947
959
|
string$1(bytes, dynamicIndex, it);
|
|
948
960
|
}
|
|
949
961
|
}
|
|
950
|
-
const type = changeTree.getType(
|
|
951
|
-
const value = changeTree.getValue(
|
|
962
|
+
const type = changeTree.getType(index);
|
|
963
|
+
const value = changeTree.getValue(index);
|
|
964
|
+
// try { throw new Error(); } catch (e) {
|
|
965
|
+
// // only print if not coming from Reflection.ts
|
|
966
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
967
|
+
// console.log("encodeKeyValueOperation -> ", {
|
|
968
|
+
// ref: changeTree.ref.constructor.name,
|
|
969
|
+
// field,
|
|
970
|
+
// operation: OPERATION[operation],
|
|
971
|
+
// value: value?.toJSON(),
|
|
972
|
+
// items: ref.toJSON(),
|
|
973
|
+
// });
|
|
974
|
+
// }
|
|
975
|
+
// }
|
|
952
976
|
// TODO: inline this function call small performance gain
|
|
953
|
-
encodeValue(encoder, bytes,
|
|
977
|
+
encodeValue(encoder, bytes,
|
|
978
|
+
// ref,
|
|
979
|
+
type, value,
|
|
980
|
+
// index,
|
|
981
|
+
operation, it);
|
|
954
982
|
};
|
|
955
983
|
/**
|
|
956
984
|
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
@@ -958,15 +986,19 @@
|
|
|
958
986
|
*/
|
|
959
987
|
const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
|
|
960
988
|
const ref = changeTree.ref;
|
|
961
|
-
|
|
962
|
-
|
|
963
|
-
|
|
964
|
-
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
|
|
989
|
+
const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
|
|
990
|
+
let refOrIndex;
|
|
991
|
+
if (useOperationByRefId) {
|
|
992
|
+
refOrIndex = ref['tmpItems'][field][$changes].refId;
|
|
993
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
994
|
+
operation = exports.OPERATION.DELETE_BY_REFID;
|
|
995
|
+
}
|
|
996
|
+
else if (operation === exports.OPERATION.ADD) {
|
|
997
|
+
operation = exports.OPERATION.ADD_BY_REFID;
|
|
998
|
+
}
|
|
999
|
+
}
|
|
1000
|
+
else {
|
|
1001
|
+
refOrIndex = field;
|
|
970
1002
|
}
|
|
971
1003
|
// encode operation
|
|
972
1004
|
bytes[it.offset++] = operation & 255;
|
|
@@ -975,7 +1007,7 @@
|
|
|
975
1007
|
return;
|
|
976
1008
|
}
|
|
977
1009
|
// encode index
|
|
978
|
-
number$1(bytes,
|
|
1010
|
+
number$1(bytes, refOrIndex, it);
|
|
979
1011
|
// Do not encode value for DELETE operations
|
|
980
1012
|
if (operation === exports.OPERATION.DELETE) {
|
|
981
1013
|
return;
|
|
@@ -990,7 +1022,11 @@
|
|
|
990
1022
|
// items: ref.toJSON(),
|
|
991
1023
|
// });
|
|
992
1024
|
// TODO: inline this function call small performance gain
|
|
993
|
-
encodeValue(encoder, bytes,
|
|
1025
|
+
encodeValue(encoder, bytes,
|
|
1026
|
+
// ref,
|
|
1027
|
+
type, value,
|
|
1028
|
+
// field,
|
|
1029
|
+
operation, it);
|
|
994
1030
|
};
|
|
995
1031
|
|
|
996
1032
|
/**
|
|
@@ -1016,7 +1052,6 @@
|
|
|
1016
1052
|
* SOFTWARE
|
|
1017
1053
|
*/
|
|
1018
1054
|
function utf8Read(bytes, it, length) {
|
|
1019
|
-
it.offset += length;
|
|
1020
1055
|
var string = '', chr = 0;
|
|
1021
1056
|
for (var i = it.offset, end = it.offset + length; i < end; i++) {
|
|
1022
1057
|
var byte = bytes[i];
|
|
@@ -1053,6 +1088,7 @@
|
|
|
1053
1088
|
// (do not throw error to avoid server/client from crashing due to hack attemps)
|
|
1054
1089
|
// throw new Error('Invalid byte ' + byte.toString(16));
|
|
1055
1090
|
}
|
|
1091
|
+
it.offset += length;
|
|
1056
1092
|
return string;
|
|
1057
1093
|
}
|
|
1058
1094
|
function int8(bytes, it) {
|
|
@@ -1224,31 +1260,31 @@
|
|
|
1224
1260
|
|
|
1225
1261
|
var decode = /*#__PURE__*/Object.freeze({
|
|
1226
1262
|
__proto__: null,
|
|
1227
|
-
|
|
1228
|
-
|
|
1229
|
-
uint8: uint8,
|
|
1230
|
-
int16: int16,
|
|
1231
|
-
uint16: uint16,
|
|
1232
|
-
int32: int32,
|
|
1233
|
-
uint32: uint32,
|
|
1263
|
+
arrayCheck: arrayCheck,
|
|
1264
|
+
boolean: boolean,
|
|
1234
1265
|
float32: float32,
|
|
1235
1266
|
float64: float64,
|
|
1267
|
+
int16: int16,
|
|
1268
|
+
int32: int32,
|
|
1236
1269
|
int64: int64,
|
|
1237
|
-
|
|
1270
|
+
int8: int8,
|
|
1271
|
+
number: number,
|
|
1272
|
+
numberCheck: numberCheck,
|
|
1238
1273
|
readFloat32: readFloat32,
|
|
1239
1274
|
readFloat64: readFloat64,
|
|
1240
|
-
boolean: boolean,
|
|
1241
1275
|
string: string,
|
|
1242
1276
|
stringCheck: stringCheck,
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1277
|
+
switchStructureCheck: switchStructureCheck,
|
|
1278
|
+
uint16: uint16,
|
|
1279
|
+
uint32: uint32,
|
|
1280
|
+
uint64: uint64,
|
|
1281
|
+
uint8: uint8,
|
|
1282
|
+
utf8Read: utf8Read
|
|
1247
1283
|
});
|
|
1248
1284
|
|
|
1249
1285
|
const DEFINITION_MISMATCH = -1;
|
|
1250
1286
|
function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
|
|
1251
|
-
const $root = decoder
|
|
1287
|
+
const $root = decoder.root;
|
|
1252
1288
|
const previousValue = ref[$getByIndex](index);
|
|
1253
1289
|
let value;
|
|
1254
1290
|
if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
@@ -1347,18 +1383,19 @@
|
|
|
1347
1383
|
}
|
|
1348
1384
|
const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
1349
1385
|
const first_byte = bytes[it.offset++];
|
|
1350
|
-
const metadata = ref
|
|
1386
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1351
1387
|
// "compressed" index + operation
|
|
1352
1388
|
const operation = (first_byte >> 6) << 6;
|
|
1353
1389
|
const index = first_byte % (operation || 255);
|
|
1354
1390
|
// skip early if field is not defined
|
|
1355
1391
|
const field = metadata[index];
|
|
1356
1392
|
if (field === undefined) {
|
|
1393
|
+
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
1357
1394
|
return DEFINITION_MISMATCH;
|
|
1358
1395
|
}
|
|
1359
|
-
const { value, previousValue } = decodeValue(decoder, operation, ref, index,
|
|
1396
|
+
const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
|
|
1360
1397
|
if (value !== null && value !== undefined) {
|
|
1361
|
-
ref[field] = value;
|
|
1398
|
+
ref[field.name] = value;
|
|
1362
1399
|
}
|
|
1363
1400
|
// add change
|
|
1364
1401
|
if (previousValue !== value) {
|
|
@@ -1366,7 +1403,7 @@
|
|
|
1366
1403
|
ref,
|
|
1367
1404
|
refId: decoder.currentRefId,
|
|
1368
1405
|
op: operation,
|
|
1369
|
-
field: field,
|
|
1406
|
+
field: field.name,
|
|
1370
1407
|
value,
|
|
1371
1408
|
previousValue,
|
|
1372
1409
|
});
|
|
@@ -1434,7 +1471,8 @@
|
|
|
1434
1471
|
};
|
|
1435
1472
|
const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
1436
1473
|
// "uncompressed" index + operation (array/map items)
|
|
1437
|
-
|
|
1474
|
+
let operation = bytes[it.offset++];
|
|
1475
|
+
let index;
|
|
1438
1476
|
if (operation === exports.OPERATION.CLEAR) {
|
|
1439
1477
|
//
|
|
1440
1478
|
// When decoding:
|
|
@@ -1448,8 +1486,8 @@
|
|
|
1448
1486
|
else if (operation === exports.OPERATION.DELETE_BY_REFID) {
|
|
1449
1487
|
// TODO: refactor here, try to follow same flow as below
|
|
1450
1488
|
const refId = number(bytes, it);
|
|
1451
|
-
const previousValue = decoder
|
|
1452
|
-
|
|
1489
|
+
const previousValue = decoder.root.refs.get(refId);
|
|
1490
|
+
index = ref.findIndex((value) => value === previousValue);
|
|
1453
1491
|
ref[$deleteByIndex](index);
|
|
1454
1492
|
allChanges.push({
|
|
1455
1493
|
ref,
|
|
@@ -1462,7 +1500,17 @@
|
|
|
1462
1500
|
});
|
|
1463
1501
|
return;
|
|
1464
1502
|
}
|
|
1465
|
-
|
|
1503
|
+
else if (operation === exports.OPERATION.ADD_BY_REFID) {
|
|
1504
|
+
const refId = number(bytes, it);
|
|
1505
|
+
const itemByRefId = decoder.root.refs.get(refId);
|
|
1506
|
+
// use existing index, or push new value
|
|
1507
|
+
index = (itemByRefId)
|
|
1508
|
+
? ref.findIndex((value) => value === itemByRefId)
|
|
1509
|
+
: ref.length;
|
|
1510
|
+
}
|
|
1511
|
+
else {
|
|
1512
|
+
index = number(bytes, it);
|
|
1513
|
+
}
|
|
1466
1514
|
const type = ref[$childType];
|
|
1467
1515
|
let dynamicIndex = index;
|
|
1468
1516
|
const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
|
|
@@ -1486,6 +1534,47 @@
|
|
|
1486
1534
|
}
|
|
1487
1535
|
};
|
|
1488
1536
|
|
|
1537
|
+
class EncodeSchemaError extends Error {
|
|
1538
|
+
}
|
|
1539
|
+
function assertType(value, type, klass, field) {
|
|
1540
|
+
let typeofTarget;
|
|
1541
|
+
let allowNull = false;
|
|
1542
|
+
switch (type) {
|
|
1543
|
+
case "number":
|
|
1544
|
+
case "int8":
|
|
1545
|
+
case "uint8":
|
|
1546
|
+
case "int16":
|
|
1547
|
+
case "uint16":
|
|
1548
|
+
case "int32":
|
|
1549
|
+
case "uint32":
|
|
1550
|
+
case "int64":
|
|
1551
|
+
case "uint64":
|
|
1552
|
+
case "float32":
|
|
1553
|
+
case "float64":
|
|
1554
|
+
typeofTarget = "number";
|
|
1555
|
+
if (isNaN(value)) {
|
|
1556
|
+
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
1557
|
+
}
|
|
1558
|
+
break;
|
|
1559
|
+
case "string":
|
|
1560
|
+
typeofTarget = "string";
|
|
1561
|
+
allowNull = true;
|
|
1562
|
+
break;
|
|
1563
|
+
case "boolean":
|
|
1564
|
+
// boolean is always encoded as true/false based on truthiness
|
|
1565
|
+
return;
|
|
1566
|
+
}
|
|
1567
|
+
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
1568
|
+
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
1569
|
+
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
1570
|
+
}
|
|
1571
|
+
}
|
|
1572
|
+
function assertInstanceType(value, type, instance, field) {
|
|
1573
|
+
if (!(value instanceof type)) {
|
|
1574
|
+
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
|
|
1575
|
+
}
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1489
1578
|
var _a$4, _b$4;
|
|
1490
1579
|
const DEFAULT_SORT = (a, b) => {
|
|
1491
1580
|
const A = a.toString();
|
|
@@ -1551,6 +1640,7 @@
|
|
|
1551
1640
|
}
|
|
1552
1641
|
else {
|
|
1553
1642
|
if (setValue[$changes]) {
|
|
1643
|
+
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1554
1644
|
if (obj.items[key] !== undefined) {
|
|
1555
1645
|
if (setValue[$changes][$isNew]) {
|
|
1556
1646
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
@@ -1597,6 +1687,7 @@
|
|
|
1597
1687
|
}
|
|
1598
1688
|
});
|
|
1599
1689
|
this[$changes] = new ChangeTree(proxy);
|
|
1690
|
+
this[$changes].indexes = {};
|
|
1600
1691
|
this.push.apply(this, items);
|
|
1601
1692
|
return proxy;
|
|
1602
1693
|
}
|
|
@@ -1621,9 +1712,11 @@
|
|
|
1621
1712
|
if (value === undefined || value === null) {
|
|
1622
1713
|
return;
|
|
1623
1714
|
}
|
|
1715
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
1716
|
+
assertInstanceType(value, this[$childType], this, i);
|
|
1717
|
+
}
|
|
1624
1718
|
const changeTree = this[$changes];
|
|
1625
1719
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1626
|
-
// changeTree.indexes[length] = length;
|
|
1627
1720
|
this.items.push(value);
|
|
1628
1721
|
this.tmpItems.push(value);
|
|
1629
1722
|
//
|
|
@@ -1871,14 +1964,6 @@
|
|
|
1871
1964
|
lastIndexOf(searchElement, fromIndex = this.length - 1) {
|
|
1872
1965
|
return this.items.lastIndexOf(searchElement, fromIndex);
|
|
1873
1966
|
}
|
|
1874
|
-
/**
|
|
1875
|
-
* Determines whether all the members of an array satisfy the specified test.
|
|
1876
|
-
* @param callbackfn A function that accepts up to three arguments. The every method calls
|
|
1877
|
-
* the callbackfn function for each element in the array until the callbackfn returns a value
|
|
1878
|
-
* which is coercible to the Boolean value false, or until the end of the array.
|
|
1879
|
-
* @param thisArg An object to which the this keyword can refer in the callbackfn function.
|
|
1880
|
-
* If thisArg is omitted, undefined is used as the this value.
|
|
1881
|
-
*/
|
|
1882
1967
|
every(callbackfn, thisArg) {
|
|
1883
1968
|
return this.items.every(callbackfn, thisArg);
|
|
1884
1969
|
}
|
|
@@ -2157,6 +2242,7 @@
|
|
|
2157
2242
|
this.$items = new Map();
|
|
2158
2243
|
this.$indexes = new Map();
|
|
2159
2244
|
this[$changes] = new ChangeTree(this);
|
|
2245
|
+
this[$changes].indexes = {};
|
|
2160
2246
|
if (initialValues) {
|
|
2161
2247
|
if (initialValues instanceof Map ||
|
|
2162
2248
|
initialValues instanceof MapSchema) {
|
|
@@ -2183,6 +2269,9 @@
|
|
|
2183
2269
|
if (value === undefined || value === null) {
|
|
2184
2270
|
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
|
|
2185
2271
|
}
|
|
2272
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
2273
|
+
assertInstanceType(value, this[$childType], this, key);
|
|
2274
|
+
}
|
|
2186
2275
|
// Force "key" as string
|
|
2187
2276
|
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
|
|
2188
2277
|
key = key.toString();
|
|
@@ -2322,7 +2411,6 @@
|
|
|
2322
2411
|
}
|
|
2323
2412
|
registerType("map", { constructor: MapSchema });
|
|
2324
2413
|
|
|
2325
|
-
const DEFAULT_VIEW_TAG = -1;
|
|
2326
2414
|
class TypeContext {
|
|
2327
2415
|
/**
|
|
2328
2416
|
* For inheritance support
|
|
@@ -2344,6 +2432,7 @@
|
|
|
2344
2432
|
this.types = {};
|
|
2345
2433
|
this.schemas = new Map();
|
|
2346
2434
|
this.hasFilters = false;
|
|
2435
|
+
this.parentFiltered = {};
|
|
2347
2436
|
if (rootClass) {
|
|
2348
2437
|
this.discoverTypes(rootClass);
|
|
2349
2438
|
}
|
|
@@ -2360,44 +2449,51 @@
|
|
|
2360
2449
|
return false;
|
|
2361
2450
|
}
|
|
2362
2451
|
this.types[typeid] = schema;
|
|
2452
|
+
//
|
|
2453
|
+
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
2454
|
+
//
|
|
2455
|
+
if (schema[Symbol.metadata] === undefined) {
|
|
2456
|
+
Metadata.init(schema);
|
|
2457
|
+
}
|
|
2363
2458
|
this.schemas.set(schema, typeid);
|
|
2364
2459
|
return true;
|
|
2365
2460
|
}
|
|
2366
2461
|
getTypeId(klass) {
|
|
2367
2462
|
return this.schemas.get(klass);
|
|
2368
2463
|
}
|
|
2369
|
-
discoverTypes(klass) {
|
|
2464
|
+
discoverTypes(klass, parentIndex, parentFieldViewTag) {
|
|
2370
2465
|
if (!this.add(klass)) {
|
|
2371
2466
|
return;
|
|
2372
2467
|
}
|
|
2373
2468
|
// add classes inherited from this base class
|
|
2374
2469
|
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
2375
|
-
this.discoverTypes(child);
|
|
2470
|
+
this.discoverTypes(child, parentIndex, parentFieldViewTag);
|
|
2376
2471
|
});
|
|
2377
|
-
|
|
2378
|
-
if (klass[Symbol.metadata] === undefined) {
|
|
2379
|
-
klass[Symbol.metadata] = {};
|
|
2380
|
-
}
|
|
2381
|
-
// const metadata = Metadata.getFor(klass);
|
|
2382
|
-
const metadata = klass[Symbol.metadata];
|
|
2472
|
+
const metadata = (klass[Symbol.metadata] ??= {});
|
|
2383
2473
|
// if any schema/field has filters, mark "context" as having filters.
|
|
2384
2474
|
if (metadata[-2]) {
|
|
2385
2475
|
this.hasFilters = true;
|
|
2386
2476
|
}
|
|
2387
|
-
|
|
2388
|
-
|
|
2477
|
+
if (parentFieldViewTag !== undefined) {
|
|
2478
|
+
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
2479
|
+
}
|
|
2480
|
+
for (const fieldIndex in metadata) {
|
|
2481
|
+
const index = fieldIndex;
|
|
2482
|
+
const fieldType = metadata[index].type;
|
|
2483
|
+
const viewTag = metadata[index].tag;
|
|
2389
2484
|
if (typeof (fieldType) === "string") {
|
|
2390
2485
|
continue;
|
|
2391
2486
|
}
|
|
2392
2487
|
if (Array.isArray(fieldType)) {
|
|
2393
2488
|
const type = fieldType[0];
|
|
2489
|
+
// skip primitive types
|
|
2394
2490
|
if (type === "string") {
|
|
2395
2491
|
continue;
|
|
2396
2492
|
}
|
|
2397
|
-
this.discoverTypes(type);
|
|
2493
|
+
this.discoverTypes(type, index, viewTag);
|
|
2398
2494
|
}
|
|
2399
2495
|
else if (typeof (fieldType) === "function") {
|
|
2400
|
-
this.discoverTypes(fieldType);
|
|
2496
|
+
this.discoverTypes(fieldType, viewTag);
|
|
2401
2497
|
}
|
|
2402
2498
|
else {
|
|
2403
2499
|
const type = Object.values(fieldType)[0];
|
|
@@ -2405,11 +2501,13 @@
|
|
|
2405
2501
|
if (typeof (type) === "string") {
|
|
2406
2502
|
continue;
|
|
2407
2503
|
}
|
|
2408
|
-
this.discoverTypes(type);
|
|
2504
|
+
this.discoverTypes(type, index, viewTag);
|
|
2409
2505
|
}
|
|
2410
2506
|
}
|
|
2411
2507
|
}
|
|
2412
2508
|
}
|
|
2509
|
+
|
|
2510
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
2413
2511
|
/**
|
|
2414
2512
|
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
2415
2513
|
*
|
|
@@ -2557,18 +2655,20 @@
|
|
|
2557
2655
|
const constructor = target.constructor;
|
|
2558
2656
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2559
2657
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2658
|
+
// TODO: use Metadata.initialize()
|
|
2560
2659
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
}
|
|
2660
|
+
// const fieldIndex = metadata[fieldName];
|
|
2661
|
+
// if (!metadata[fieldIndex]) {
|
|
2662
|
+
// //
|
|
2663
|
+
// // detect index for this field, considering inheritance
|
|
2664
|
+
// //
|
|
2665
|
+
// metadata[fieldIndex] = {
|
|
2666
|
+
// type: undefined,
|
|
2667
|
+
// index: (metadata[-1] // current structure already has fields defined
|
|
2668
|
+
// ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
|
|
2669
|
+
// ?? -1) + 1 // no fields defined
|
|
2670
|
+
// }
|
|
2671
|
+
// }
|
|
2572
2672
|
Metadata.setTag(metadata, fieldName, tag);
|
|
2573
2673
|
};
|
|
2574
2674
|
}
|
|
@@ -2581,18 +2681,18 @@
|
|
|
2581
2681
|
// for inheritance support
|
|
2582
2682
|
TypeContext.register(constructor);
|
|
2583
2683
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2584
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
2585
|
-
const metadata =
|
|
2586
|
-
let fieldIndex;
|
|
2684
|
+
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
2685
|
+
const metadata = Metadata.initialize(constructor, parentMetadata);
|
|
2686
|
+
let fieldIndex = metadata[field];
|
|
2587
2687
|
/**
|
|
2588
2688
|
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
2589
2689
|
*/
|
|
2590
|
-
if (metadata[
|
|
2591
|
-
if (metadata[
|
|
2690
|
+
if (metadata[fieldIndex]) {
|
|
2691
|
+
if (metadata[fieldIndex].deprecated) {
|
|
2592
2692
|
// do not create accessors for deprecated properties.
|
|
2593
2693
|
return;
|
|
2594
2694
|
}
|
|
2595
|
-
else if (metadata[
|
|
2695
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
2596
2696
|
// trying to define same property multiple times across inheritance.
|
|
2597
2697
|
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
2598
2698
|
try {
|
|
@@ -2603,9 +2703,6 @@
|
|
|
2603
2703
|
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
2604
2704
|
}
|
|
2605
2705
|
}
|
|
2606
|
-
else {
|
|
2607
|
-
fieldIndex = metadata[field].index;
|
|
2608
|
-
}
|
|
2609
2706
|
}
|
|
2610
2707
|
else {
|
|
2611
2708
|
//
|
|
@@ -2631,11 +2728,11 @@
|
|
|
2631
2728
|
const childType = (complexTypeKlass)
|
|
2632
2729
|
? Object.values(type)[0]
|
|
2633
2730
|
: type;
|
|
2634
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass
|
|
2731
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
2635
2732
|
}
|
|
2636
2733
|
};
|
|
2637
2734
|
}
|
|
2638
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass
|
|
2735
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
2639
2736
|
return {
|
|
2640
2737
|
get: function () { return this[fieldCached]; },
|
|
2641
2738
|
set: function (value) {
|
|
@@ -2657,22 +2754,27 @@
|
|
|
2657
2754
|
}
|
|
2658
2755
|
value[$childType] = type;
|
|
2659
2756
|
}
|
|
2757
|
+
else if (typeof (type) !== "string") {
|
|
2758
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
2759
|
+
}
|
|
2760
|
+
else {
|
|
2761
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
2762
|
+
}
|
|
2763
|
+
const changeTree = this[$changes];
|
|
2660
2764
|
//
|
|
2661
2765
|
// Replacing existing "ref", remove it from root.
|
|
2662
2766
|
// TODO: if there are other references to this instance, we should not remove it from root.
|
|
2663
2767
|
//
|
|
2664
2768
|
if (previousValue !== undefined && previousValue[$changes]) {
|
|
2665
|
-
|
|
2769
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
2666
2770
|
}
|
|
2667
2771
|
// flag the change for encoding.
|
|
2668
|
-
this.constructor[$track](
|
|
2772
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
2669
2773
|
//
|
|
2670
2774
|
// call setParent() recursively for this and its child
|
|
2671
2775
|
// structures.
|
|
2672
2776
|
//
|
|
2673
|
-
|
|
2674
|
-
value[$changes].setParent(this, this[$changes].root, metadata[field].index);
|
|
2675
|
-
}
|
|
2777
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
2676
2778
|
}
|
|
2677
2779
|
else if (previousValue !== undefined) {
|
|
2678
2780
|
//
|
|
@@ -2699,20 +2801,22 @@
|
|
|
2699
2801
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2700
2802
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2701
2803
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
}
|
|
2713
|
-
|
|
2804
|
+
const fieldIndex = metadata[field];
|
|
2805
|
+
// if (!metadata[field]) {
|
|
2806
|
+
// //
|
|
2807
|
+
// // detect index for this field, considering inheritance
|
|
2808
|
+
// //
|
|
2809
|
+
// metadata[field] = {
|
|
2810
|
+
// type: undefined,
|
|
2811
|
+
// index: (metadata[-1] // current structure already has fields defined
|
|
2812
|
+
// ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
|
|
2813
|
+
// ?? -1) + 1 // no fields defined
|
|
2814
|
+
// }
|
|
2815
|
+
// }
|
|
2816
|
+
metadata[fieldIndex].deprecated = true;
|
|
2714
2817
|
if (throws) {
|
|
2715
|
-
metadata[
|
|
2818
|
+
metadata[$descriptors] ??= {};
|
|
2819
|
+
metadata[$descriptors][field] = {
|
|
2716
2820
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
2717
2821
|
set: function (value) { },
|
|
2718
2822
|
enumerable: false,
|
|
@@ -2720,8 +2824,8 @@
|
|
|
2720
2824
|
};
|
|
2721
2825
|
}
|
|
2722
2826
|
// flag metadata[field] as non-enumerable
|
|
2723
|
-
Object.defineProperty(metadata,
|
|
2724
|
-
value: metadata[
|
|
2827
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
2828
|
+
value: metadata[fieldIndex],
|
|
2725
2829
|
enumerable: false,
|
|
2726
2830
|
configurable: true
|
|
2727
2831
|
});
|
|
@@ -2787,35 +2891,7 @@
|
|
|
2787
2891
|
enumerable: false,
|
|
2788
2892
|
writable: true
|
|
2789
2893
|
});
|
|
2790
|
-
|
|
2791
|
-
// Define property descriptors
|
|
2792
|
-
for (const field in metadata) {
|
|
2793
|
-
if (metadata[field].descriptor) {
|
|
2794
|
-
// for encoder
|
|
2795
|
-
Object.defineProperty(instance, `_${field}`, {
|
|
2796
|
-
value: undefined,
|
|
2797
|
-
writable: true,
|
|
2798
|
-
enumerable: false,
|
|
2799
|
-
configurable: true,
|
|
2800
|
-
});
|
|
2801
|
-
Object.defineProperty(instance, field, metadata[field].descriptor);
|
|
2802
|
-
}
|
|
2803
|
-
else {
|
|
2804
|
-
// for decoder
|
|
2805
|
-
Object.defineProperty(instance, field, {
|
|
2806
|
-
value: undefined,
|
|
2807
|
-
writable: true,
|
|
2808
|
-
enumerable: true,
|
|
2809
|
-
configurable: true,
|
|
2810
|
-
});
|
|
2811
|
-
}
|
|
2812
|
-
// Object.defineProperty(instance, field, {
|
|
2813
|
-
// ...instance.constructor[Symbol.metadata][field].descriptor
|
|
2814
|
-
// });
|
|
2815
|
-
// if (args[0]?.hasOwnProperty(field)) {
|
|
2816
|
-
// instance[field] = args[0][field];
|
|
2817
|
-
// }
|
|
2818
|
-
}
|
|
2894
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2819
2895
|
}
|
|
2820
2896
|
static is(type) {
|
|
2821
2897
|
return typeof (type[Symbol.metadata]) === "object";
|
|
@@ -2839,7 +2915,7 @@
|
|
|
2839
2915
|
*/
|
|
2840
2916
|
static [$filter](ref, index, view) {
|
|
2841
2917
|
const metadata = ref.constructor[Symbol.metadata];
|
|
2842
|
-
const tag = metadata[
|
|
2918
|
+
const tag = metadata[index]?.tag;
|
|
2843
2919
|
if (view === undefined) {
|
|
2844
2920
|
// shared pass/encode: encode if doesn't have a tag
|
|
2845
2921
|
return tag === undefined;
|
|
@@ -2860,12 +2936,21 @@
|
|
|
2860
2936
|
}
|
|
2861
2937
|
// allow inherited classes to have a constructor
|
|
2862
2938
|
constructor(...args) {
|
|
2863
|
-
|
|
2939
|
+
//
|
|
2940
|
+
// inline
|
|
2941
|
+
// Schema.initialize(this);
|
|
2942
|
+
//
|
|
2943
|
+
Object.defineProperty(this, $changes, {
|
|
2944
|
+
value: new ChangeTree(this),
|
|
2945
|
+
enumerable: false,
|
|
2946
|
+
writable: true
|
|
2947
|
+
});
|
|
2948
|
+
Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2864
2949
|
//
|
|
2865
2950
|
// Assign initial values
|
|
2866
2951
|
//
|
|
2867
2952
|
if (args[0]) {
|
|
2868
|
-
|
|
2953
|
+
Object.assign(this, args[0]);
|
|
2869
2954
|
}
|
|
2870
2955
|
}
|
|
2871
2956
|
assign(props) {
|
|
@@ -2879,7 +2964,8 @@
|
|
|
2879
2964
|
* @param operation OPERATION to perform (detected automatically)
|
|
2880
2965
|
*/
|
|
2881
2966
|
setDirty(property, operation) {
|
|
2882
|
-
this
|
|
2967
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
2968
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
2883
2969
|
}
|
|
2884
2970
|
clone() {
|
|
2885
2971
|
const cloned = new (this.constructor);
|
|
@@ -2888,7 +2974,9 @@
|
|
|
2888
2974
|
// TODO: clone all properties, not only annotated ones
|
|
2889
2975
|
//
|
|
2890
2976
|
// for (const field in this) {
|
|
2891
|
-
for (const
|
|
2977
|
+
for (const fieldIndex in metadata) {
|
|
2978
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
2979
|
+
const field = metadata[fieldIndex].name;
|
|
2892
2980
|
if (typeof (this[field]) === "object" &&
|
|
2893
2981
|
typeof (this[field]?.clone) === "function") {
|
|
2894
2982
|
// deep clone
|
|
@@ -2902,10 +2990,11 @@
|
|
|
2902
2990
|
return cloned;
|
|
2903
2991
|
}
|
|
2904
2992
|
toJSON() {
|
|
2905
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
2906
2993
|
const obj = {};
|
|
2907
|
-
|
|
2908
|
-
|
|
2994
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
2995
|
+
for (const index in metadata) {
|
|
2996
|
+
const field = metadata[index];
|
|
2997
|
+
const fieldName = field.name;
|
|
2909
2998
|
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
2910
2999
|
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
2911
3000
|
? this[fieldName]['toJSON']()
|
|
@@ -2918,10 +3007,12 @@
|
|
|
2918
3007
|
this[$changes].discardAll();
|
|
2919
3008
|
}
|
|
2920
3009
|
[$getByIndex](index) {
|
|
2921
|
-
|
|
3010
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3011
|
+
return this[metadata[index].name];
|
|
2922
3012
|
}
|
|
2923
3013
|
[$deleteByIndex](index) {
|
|
2924
|
-
this
|
|
3014
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3015
|
+
this[metadata[index].name] = undefined;
|
|
2925
3016
|
}
|
|
2926
3017
|
static debugRefIds(instance, jsonContents = true, level = 0) {
|
|
2927
3018
|
const ref = instance;
|
|
@@ -2963,13 +3054,13 @@
|
|
|
2963
3054
|
}
|
|
2964
3055
|
return output;
|
|
2965
3056
|
}
|
|
2966
|
-
static debugChangesDeep(ref) {
|
|
3057
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
2967
3058
|
let output = "";
|
|
2968
3059
|
const rootChangeTree = ref[$changes];
|
|
2969
3060
|
const changeTrees = new Map();
|
|
2970
3061
|
let totalInstances = 0;
|
|
2971
3062
|
let totalOperations = 0;
|
|
2972
|
-
for (const [changeTree, changes] of (rootChangeTree.root.
|
|
3063
|
+
for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
|
|
2973
3064
|
let includeChangeTree = false;
|
|
2974
3065
|
let parentChangeTrees = [];
|
|
2975
3066
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -3045,6 +3136,7 @@
|
|
|
3045
3136
|
this.$indexes = new Map();
|
|
3046
3137
|
this.$refId = 0;
|
|
3047
3138
|
this[$changes] = new ChangeTree(this);
|
|
3139
|
+
this[$changes].indexes = {};
|
|
3048
3140
|
if (initialValues) {
|
|
3049
3141
|
initialValues.forEach((v) => this.add(v));
|
|
3050
3142
|
}
|
|
@@ -3200,6 +3292,7 @@
|
|
|
3200
3292
|
this.$indexes = new Map();
|
|
3201
3293
|
this.$refId = 0;
|
|
3202
3294
|
this[$changes] = new ChangeTree(this);
|
|
3295
|
+
this[$changes].indexes = {};
|
|
3203
3296
|
if (initialValues) {
|
|
3204
3297
|
initialValues.forEach((v) => this.add(v));
|
|
3205
3298
|
}
|
|
@@ -3355,6 +3448,8 @@
|
|
|
3355
3448
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3356
3449
|
PERFORMANCE OF THIS SOFTWARE.
|
|
3357
3450
|
***************************************************************************** */
|
|
3451
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
3452
|
+
|
|
3358
3453
|
|
|
3359
3454
|
function __decorate(decorators, target, key, desc) {
|
|
3360
3455
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3368,37 +3463,82 @@
|
|
|
3368
3463
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3369
3464
|
};
|
|
3370
3465
|
|
|
3466
|
+
class Root {
|
|
3467
|
+
constructor(types) {
|
|
3468
|
+
this.types = types;
|
|
3469
|
+
this.nextUniqueId = 0;
|
|
3470
|
+
this.refCount = new WeakMap();
|
|
3471
|
+
// all changes
|
|
3472
|
+
this.allChanges = new Map();
|
|
3473
|
+
this.allFilteredChanges = new Map();
|
|
3474
|
+
// pending changes to be encoded
|
|
3475
|
+
this.changes = new Map();
|
|
3476
|
+
this.filteredChanges = new Map();
|
|
3477
|
+
}
|
|
3478
|
+
getNextUniqueId() {
|
|
3479
|
+
return this.nextUniqueId++;
|
|
3480
|
+
}
|
|
3481
|
+
add(changeTree) {
|
|
3482
|
+
const refCount = this.refCount.get(changeTree) || 0;
|
|
3483
|
+
this.refCount.set(changeTree, refCount + 1);
|
|
3484
|
+
}
|
|
3485
|
+
remove(changeTree) {
|
|
3486
|
+
const refCount = this.refCount.get(changeTree);
|
|
3487
|
+
if (refCount <= 1) {
|
|
3488
|
+
this.allChanges.delete(changeTree);
|
|
3489
|
+
this.changes.delete(changeTree);
|
|
3490
|
+
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3491
|
+
this.allFilteredChanges.delete(changeTree);
|
|
3492
|
+
this.filteredChanges.delete(changeTree);
|
|
3493
|
+
}
|
|
3494
|
+
this.refCount.delete(changeTree);
|
|
3495
|
+
}
|
|
3496
|
+
else {
|
|
3497
|
+
this.refCount.set(changeTree, refCount - 1);
|
|
3498
|
+
}
|
|
3499
|
+
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3500
|
+
}
|
|
3501
|
+
clear() {
|
|
3502
|
+
this.changes.clear();
|
|
3503
|
+
}
|
|
3504
|
+
}
|
|
3505
|
+
|
|
3371
3506
|
class Encoder {
|
|
3372
3507
|
static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
|
|
3373
|
-
constructor(
|
|
3508
|
+
constructor(state) {
|
|
3374
3509
|
this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
|
|
3375
|
-
this.setRoot(root);
|
|
3376
3510
|
//
|
|
3377
3511
|
// TODO: cache and restore "Context" based on root schema
|
|
3378
3512
|
// (to avoid creating a new context for every new room)
|
|
3379
3513
|
//
|
|
3380
|
-
this.context = new TypeContext(
|
|
3514
|
+
this.context = new TypeContext(state.constructor);
|
|
3515
|
+
this.root = new Root(this.context);
|
|
3516
|
+
this.setState(state);
|
|
3381
3517
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
3382
3518
|
// this.context.schemas.forEach((id, schema) => {
|
|
3383
3519
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3384
3520
|
// });
|
|
3385
3521
|
}
|
|
3386
|
-
|
|
3387
|
-
this.root = new Root();
|
|
3522
|
+
setState(state) {
|
|
3388
3523
|
this.state = state;
|
|
3389
|
-
state[$changes].setRoot(this.root);
|
|
3524
|
+
this.state[$changes].setRoot(this.root);
|
|
3390
3525
|
}
|
|
3391
|
-
encode(it = { offset: 0 }, view,
|
|
3392
|
-
|
|
3393
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
3526
|
+
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees, initialOffset = it.offset // cache current offset in case we need to resize the buffer
|
|
3527
|
+
) {
|
|
3394
3528
|
const hasView = (view !== undefined);
|
|
3395
3529
|
const rootChangeTree = this.state[$changes];
|
|
3396
|
-
const
|
|
3397
|
-
for (const [changeTree, changes] of
|
|
3530
|
+
const shouldClearChanges = !isEncodeAll && !hasView;
|
|
3531
|
+
for (const [changeTree, changes] of changeTrees.entries()) {
|
|
3398
3532
|
const ref = changeTree.ref;
|
|
3399
3533
|
const ctor = ref['constructor'];
|
|
3400
3534
|
const encoder = ctor[$encoder];
|
|
3401
3535
|
const filter = ctor[$filter];
|
|
3536
|
+
// try { throw new Error(); } catch (e) {
|
|
3537
|
+
// // only print if not coming from Reflection.ts
|
|
3538
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3539
|
+
// console.log("ChangeTree:", { ref: ref.constructor.name, });
|
|
3540
|
+
// }
|
|
3541
|
+
// }
|
|
3402
3542
|
if (hasView) {
|
|
3403
3543
|
if (!view.items.has(changeTree)) {
|
|
3404
3544
|
view.invisible.add(changeTree);
|
|
@@ -3409,9 +3549,10 @@
|
|
|
3409
3549
|
}
|
|
3410
3550
|
}
|
|
3411
3551
|
// skip root `refId` if it's the first change tree
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3552
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
3553
|
+
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
3554
|
+
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3555
|
+
number$1(buffer, changeTree.refId, it);
|
|
3415
3556
|
}
|
|
3416
3557
|
const changesIterator = changes.entries();
|
|
3417
3558
|
for (const [fieldIndex, operation] of changesIterator) {
|
|
@@ -3423,74 +3564,94 @@
|
|
|
3423
3564
|
// TODO: avoid checking if no view tags were defined
|
|
3424
3565
|
//
|
|
3425
3566
|
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3426
|
-
// console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
|
|
3427
3567
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3428
3568
|
// view?.invisible.add(changeTree);
|
|
3429
3569
|
continue;
|
|
3430
3570
|
}
|
|
3431
|
-
//
|
|
3432
|
-
//
|
|
3433
|
-
//
|
|
3434
|
-
//
|
|
3435
|
-
//
|
|
3436
|
-
|
|
3571
|
+
// try { throw new Error(); } catch (e) {
|
|
3572
|
+
// // only print if not coming from Reflection.ts
|
|
3573
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3574
|
+
// console.log("WILL ENCODE", {
|
|
3575
|
+
// ref: changeTree.ref.constructor.name,
|
|
3576
|
+
// fieldIndex,
|
|
3577
|
+
// operation: OPERATION[operation],
|
|
3578
|
+
// });
|
|
3579
|
+
// }
|
|
3580
|
+
// }
|
|
3581
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
|
|
3437
3582
|
}
|
|
3583
|
+
// if (shouldClearChanges) {
|
|
3584
|
+
// changeTree.endEncode();
|
|
3585
|
+
// }
|
|
3438
3586
|
}
|
|
3439
|
-
if (it.offset >
|
|
3440
|
-
const newSize = getNextPowerOf2(
|
|
3441
|
-
console.warn(
|
|
3587
|
+
if (it.offset > buffer.byteLength) {
|
|
3588
|
+
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
3589
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
3590
|
+
|
|
3591
|
+
import { Encoder } from "@colyseus/schema";
|
|
3592
|
+
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
3593
|
+
`);
|
|
3442
3594
|
//
|
|
3443
3595
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
3444
3596
|
//
|
|
3445
|
-
|
|
3446
|
-
|
|
3597
|
+
buffer = Buffer.allocUnsafeSlow(newSize);
|
|
3598
|
+
// assign resized buffer to local sharedBuffer
|
|
3599
|
+
if (buffer === this.sharedBuffer) {
|
|
3600
|
+
this.sharedBuffer = buffer;
|
|
3601
|
+
}
|
|
3602
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
|
|
3447
3603
|
}
|
|
3448
3604
|
else {
|
|
3449
3605
|
//
|
|
3450
3606
|
// only clear changes after making sure buffer resize is not required.
|
|
3451
3607
|
//
|
|
3452
|
-
if (
|
|
3608
|
+
if (shouldClearChanges) {
|
|
3453
3609
|
//
|
|
3454
3610
|
// FIXME: avoid iterating over change trees twice.
|
|
3455
3611
|
//
|
|
3456
3612
|
this.onEndEncode(changeTrees);
|
|
3457
3613
|
}
|
|
3458
|
-
|
|
3459
|
-
return bytes.slice(0, it.offset);
|
|
3614
|
+
return buffer.subarray(0, it.offset);
|
|
3460
3615
|
}
|
|
3461
3616
|
}
|
|
3462
|
-
encodeAll(it = { offset: 0 }) {
|
|
3463
|
-
// console.log(
|
|
3464
|
-
//
|
|
3465
|
-
|
|
3466
|
-
// });
|
|
3467
|
-
return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
|
|
3617
|
+
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3618
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
|
|
3619
|
+
// this.debugChanges("allChanges");
|
|
3620
|
+
return this.encode(it, undefined, buffer, this.root.allChanges, true);
|
|
3468
3621
|
}
|
|
3469
3622
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3470
3623
|
const viewOffset = it.offset;
|
|
3471
|
-
// console.log(
|
|
3472
|
-
// this.
|
|
3624
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
|
|
3625
|
+
// this.debugChanges("allFilteredChanges");
|
|
3473
3626
|
// try to encode "filtered" changes
|
|
3474
|
-
this.encode(it, view, bytes, this.root.allFilteredChanges);
|
|
3627
|
+
this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
|
|
3475
3628
|
return Buffer.concat([
|
|
3476
|
-
bytes.
|
|
3477
|
-
bytes.
|
|
3629
|
+
bytes.subarray(0, sharedOffset),
|
|
3630
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3478
3631
|
]);
|
|
3479
3632
|
}
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3633
|
+
debugChanges(field) {
|
|
3634
|
+
const changeSet = (typeof (field) === "string")
|
|
3635
|
+
? this.root[field]
|
|
3636
|
+
: field;
|
|
3637
|
+
Array.from(changeSet.entries()).map((item) => {
|
|
3638
|
+
const metadata = item[0].ref.constructor[Symbol.metadata];
|
|
3639
|
+
console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
|
|
3640
|
+
item[1].forEach((op, index) => {
|
|
3641
|
+
console.log(" ->", {
|
|
3642
|
+
index,
|
|
3643
|
+
field: metadata?.[index],
|
|
3644
|
+
op: exports.OPERATION[op],
|
|
3645
|
+
});
|
|
3646
|
+
});
|
|
3647
|
+
});
|
|
3648
|
+
}
|
|
3490
3649
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3491
3650
|
const viewOffset = it.offset;
|
|
3492
|
-
//
|
|
3493
|
-
this.
|
|
3651
|
+
// console.log(`\nencodeView(), view.changes (${view.changes.size})`);
|
|
3652
|
+
// this.debugChanges(view.changes);
|
|
3653
|
+
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3654
|
+
// this.debugChanges("filteredChanges");
|
|
3494
3655
|
// encode visibility changes (add/remove for this view)
|
|
3495
3656
|
const viewChangesIterator = view.changes.entries();
|
|
3496
3657
|
for (const [changeTree, changes] of viewChangesIterator) {
|
|
@@ -3517,15 +3678,22 @@
|
|
|
3517
3678
|
//
|
|
3518
3679
|
// clear "view" changes after encoding
|
|
3519
3680
|
view.changes.clear();
|
|
3681
|
+
// try to encode "filtered" changes
|
|
3682
|
+
this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
|
|
3520
3683
|
return Buffer.concat([
|
|
3521
|
-
bytes.
|
|
3522
|
-
bytes.
|
|
3684
|
+
bytes.subarray(0, sharedOffset),
|
|
3685
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3523
3686
|
]);
|
|
3524
3687
|
}
|
|
3525
3688
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3526
3689
|
const changeTreesIterator = changeTrees.entries();
|
|
3527
3690
|
for (const [changeTree, _] of changeTreesIterator) {
|
|
3528
3691
|
changeTree.endEncode();
|
|
3692
|
+
// changeTree.changes.clear();
|
|
3693
|
+
// // ArraySchema and MapSchema have a custom "encode end" method
|
|
3694
|
+
// changeTree.ref[$onEncodeEnd]?.();
|
|
3695
|
+
// // Not a new instance anymore
|
|
3696
|
+
// delete changeTree[$isNew];
|
|
3529
3697
|
}
|
|
3530
3698
|
}
|
|
3531
3699
|
discardChanges() {
|
|
@@ -3641,8 +3809,9 @@
|
|
|
3641
3809
|
// Ensure child schema instances have their references removed as well.
|
|
3642
3810
|
//
|
|
3643
3811
|
if (Metadata.isValidInstance(ref)) {
|
|
3644
|
-
const metadata = ref
|
|
3645
|
-
for (const
|
|
3812
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
3813
|
+
for (const index in metadata) {
|
|
3814
|
+
const field = metadata[index].name;
|
|
3646
3815
|
const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
|
|
3647
3816
|
if (childRefId) {
|
|
3648
3817
|
this.removeRef(childRefId);
|
|
@@ -3689,21 +3858,21 @@
|
|
|
3689
3858
|
class Decoder {
|
|
3690
3859
|
constructor(root, context) {
|
|
3691
3860
|
this.currentRefId = 0;
|
|
3692
|
-
this.
|
|
3861
|
+
this.setState(root);
|
|
3693
3862
|
this.context = context || new TypeContext(root.constructor);
|
|
3694
3863
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
3695
3864
|
// this.context.schemas.forEach((id, schema) => {
|
|
3696
3865
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3697
3866
|
// });
|
|
3698
3867
|
}
|
|
3699
|
-
|
|
3868
|
+
setState(root) {
|
|
3700
3869
|
this.state = root;
|
|
3701
|
-
this
|
|
3702
|
-
this
|
|
3870
|
+
this.root = new ReferenceTracker();
|
|
3871
|
+
this.root.addRef(0, root);
|
|
3703
3872
|
}
|
|
3704
3873
|
decode(bytes, it = { offset: 0 }, ref = this.state) {
|
|
3705
3874
|
const allChanges = [];
|
|
3706
|
-
const $root = this
|
|
3875
|
+
const $root = this.root;
|
|
3707
3876
|
const totalBytes = bytes.byteLength;
|
|
3708
3877
|
let decoder = ref['constructor'][$decoder];
|
|
3709
3878
|
this.currentRefId = 0;
|
|
@@ -3784,7 +3953,7 @@
|
|
|
3784
3953
|
previousValue: value
|
|
3785
3954
|
});
|
|
3786
3955
|
if (needRemoveRef) {
|
|
3787
|
-
this
|
|
3956
|
+
this.root.removeRef(this.root.refIds.get(value));
|
|
3788
3957
|
}
|
|
3789
3958
|
});
|
|
3790
3959
|
}
|
|
@@ -3824,14 +3993,14 @@
|
|
|
3824
3993
|
super(...arguments);
|
|
3825
3994
|
this.types = new ArraySchema();
|
|
3826
3995
|
}
|
|
3827
|
-
static encode(instance, context) {
|
|
3828
|
-
|
|
3829
|
-
context = new TypeContext(instance.constructor);
|
|
3830
|
-
}
|
|
3996
|
+
static encode(instance, context, it = { offset: 0 }) {
|
|
3997
|
+
context ??= new TypeContext(instance.constructor);
|
|
3831
3998
|
const reflection = new Reflection();
|
|
3832
3999
|
const encoder = new Encoder(reflection);
|
|
3833
4000
|
const buildType = (currentType, metadata) => {
|
|
3834
|
-
for (const
|
|
4001
|
+
for (const fieldIndex in metadata) {
|
|
4002
|
+
const index = Number(fieldIndex);
|
|
4003
|
+
const fieldName = metadata[index].name;
|
|
3835
4004
|
// skip fields from parent classes
|
|
3836
4005
|
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
3837
4006
|
continue;
|
|
@@ -3839,7 +4008,7 @@
|
|
|
3839
4008
|
const field = new ReflectionField();
|
|
3840
4009
|
field.name = fieldName;
|
|
3841
4010
|
let fieldType;
|
|
3842
|
-
const type = metadata[
|
|
4011
|
+
const type = metadata[index].type;
|
|
3843
4012
|
if (typeof (type) === "string") {
|
|
3844
4013
|
fieldType = type;
|
|
3845
4014
|
}
|
|
@@ -3881,7 +4050,6 @@
|
|
|
3881
4050
|
}
|
|
3882
4051
|
buildType(type, klass[Symbol.metadata]);
|
|
3883
4052
|
}
|
|
3884
|
-
const it = { offset: 0 };
|
|
3885
4053
|
const buf = encoder.encodeAll(it);
|
|
3886
4054
|
return Buffer.from(buf, 0, it.offset);
|
|
3887
4055
|
}
|
|
@@ -3889,59 +4057,304 @@
|
|
|
3889
4057
|
const reflection = new Reflection();
|
|
3890
4058
|
const reflectionDecoder = new Decoder(reflection);
|
|
3891
4059
|
reflectionDecoder.decode(bytes, it);
|
|
3892
|
-
const
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
const
|
|
4060
|
+
const typeContext = new TypeContext();
|
|
4061
|
+
// 1st pass, initialize metadata + inheritance
|
|
4062
|
+
reflection.types.forEach((reflectionType) => {
|
|
4063
|
+
const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
4064
|
+
const schema = class _ extends parentClass {
|
|
3896
4065
|
};
|
|
3897
|
-
|
|
3898
|
-
const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
|
|
3899
|
-
Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
4066
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
3900
4067
|
// register for inheritance support
|
|
3901
4068
|
TypeContext.register(schema);
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
return types;
|
|
4069
|
+
// for inheritance support
|
|
4070
|
+
Metadata.initialize(schema, parentMetadata);
|
|
4071
|
+
typeContext.add(schema, reflectionType.id);
|
|
3906
4072
|
}, {});
|
|
4073
|
+
// 2nd pass, set fields
|
|
3907
4074
|
reflection.types.forEach((reflectionType) => {
|
|
3908
|
-
const schemaType =
|
|
4075
|
+
const schemaType = typeContext.get(reflectionType.id);
|
|
3909
4076
|
const metadata = schemaType[Symbol.metadata];
|
|
3910
|
-
const
|
|
3911
|
-
const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
|
|
4077
|
+
const parentFieldIndex = 0;
|
|
3912
4078
|
reflectionType.fields.forEach((field, i) => {
|
|
3913
4079
|
const fieldIndex = parentFieldIndex + i;
|
|
3914
4080
|
if (field.referencedType !== undefined) {
|
|
3915
4081
|
let fieldType = field.type;
|
|
3916
|
-
let refType =
|
|
4082
|
+
let refType = typeContext.get(field.referencedType);
|
|
3917
4083
|
// map or array of primitive type (-1)
|
|
3918
4084
|
if (!refType) {
|
|
3919
4085
|
const typeInfo = field.type.split(":");
|
|
3920
4086
|
fieldType = typeInfo[0];
|
|
3921
|
-
refType = typeInfo[1];
|
|
4087
|
+
refType = typeInfo[1]; // string
|
|
3922
4088
|
}
|
|
3923
4089
|
if (fieldType === "ref") {
|
|
3924
|
-
// type(refType)(schemaType.prototype, field.name);
|
|
3925
4090
|
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
3926
4091
|
}
|
|
3927
4092
|
else {
|
|
3928
|
-
// type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
|
|
3929
4093
|
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
3930
4094
|
}
|
|
3931
4095
|
}
|
|
3932
4096
|
else {
|
|
3933
|
-
// type(field.type as PrimitiveType)(schemaType.prototype, field.name);
|
|
3934
4097
|
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
3935
4098
|
}
|
|
3936
4099
|
});
|
|
3937
4100
|
});
|
|
3938
|
-
|
|
4101
|
+
// @ts-ignore
|
|
4102
|
+
return new (typeContext.get(0))();
|
|
3939
4103
|
}
|
|
3940
4104
|
}
|
|
3941
4105
|
__decorate([
|
|
3942
4106
|
type([ReflectionType])
|
|
3943
4107
|
], Reflection.prototype, "types", void 0);
|
|
3944
4108
|
|
|
4109
|
+
function getDecoderStateCallbacks(decoder) {
|
|
4110
|
+
const $root = decoder.root;
|
|
4111
|
+
const callbacks = $root.callbacks;
|
|
4112
|
+
const onAddCalls = new WeakMap();
|
|
4113
|
+
let currentOnAddCallback;
|
|
4114
|
+
decoder.triggerChanges = function (allChanges) {
|
|
4115
|
+
const uniqueRefIds = new Set();
|
|
4116
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
4117
|
+
const change = allChanges[i];
|
|
4118
|
+
const refId = change.refId;
|
|
4119
|
+
const ref = change.ref;
|
|
4120
|
+
const $callbacks = callbacks[refId];
|
|
4121
|
+
if (!$callbacks) {
|
|
4122
|
+
continue;
|
|
4123
|
+
}
|
|
4124
|
+
//
|
|
4125
|
+
// trigger onRemove on child structure.
|
|
4126
|
+
//
|
|
4127
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4128
|
+
change.previousValue instanceof Schema) {
|
|
4129
|
+
const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
|
|
4130
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4131
|
+
deleteCallbacks[i]();
|
|
4132
|
+
}
|
|
4133
|
+
}
|
|
4134
|
+
if (ref instanceof Schema) {
|
|
4135
|
+
//
|
|
4136
|
+
// Handle schema instance
|
|
4137
|
+
//
|
|
4138
|
+
if (!uniqueRefIds.has(refId)) {
|
|
4139
|
+
// trigger onChange
|
|
4140
|
+
const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
|
|
4141
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4142
|
+
replaceCallbacks[i]();
|
|
4143
|
+
// try {
|
|
4144
|
+
// } catch (e) {
|
|
4145
|
+
// console.error(e);
|
|
4146
|
+
// }
|
|
4147
|
+
}
|
|
4148
|
+
}
|
|
4149
|
+
if ($callbacks.hasOwnProperty(change.field)) {
|
|
4150
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
4151
|
+
for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
|
|
4152
|
+
fieldCallbacks[i](change.value, change.previousValue);
|
|
4153
|
+
// try {
|
|
4154
|
+
// } catch (e) {
|
|
4155
|
+
// console.error(e);
|
|
4156
|
+
// }
|
|
4157
|
+
}
|
|
4158
|
+
}
|
|
4159
|
+
}
|
|
4160
|
+
else {
|
|
4161
|
+
//
|
|
4162
|
+
// Handle collection of items
|
|
4163
|
+
//
|
|
4164
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
4165
|
+
//
|
|
4166
|
+
// FIXME: `previousValue` should always be available.
|
|
4167
|
+
//
|
|
4168
|
+
if (change.previousValue !== undefined) {
|
|
4169
|
+
// triger onRemove
|
|
4170
|
+
const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
|
|
4171
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4172
|
+
deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
|
|
4173
|
+
}
|
|
4174
|
+
}
|
|
4175
|
+
// Handle DELETE_AND_ADD operations
|
|
4176
|
+
if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
4177
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4178
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4179
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4180
|
+
}
|
|
4181
|
+
}
|
|
4182
|
+
}
|
|
4183
|
+
else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
|
|
4184
|
+
// triger onAdd
|
|
4185
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4186
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4187
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4188
|
+
}
|
|
4189
|
+
}
|
|
4190
|
+
// trigger onChange
|
|
4191
|
+
if (change.value !== change.previousValue) {
|
|
4192
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
4193
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4194
|
+
replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4195
|
+
}
|
|
4196
|
+
}
|
|
4197
|
+
}
|
|
4198
|
+
uniqueRefIds.add(refId);
|
|
4199
|
+
}
|
|
4200
|
+
};
|
|
4201
|
+
function getProxy(metadataOrType, context) {
|
|
4202
|
+
let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
|
|
4203
|
+
let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
|
|
4204
|
+
(metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
|
|
4205
|
+
if (metadata && !isCollection) {
|
|
4206
|
+
const onAddListen = function (ref, prop, callback, immediate) {
|
|
4207
|
+
// immediate trigger
|
|
4208
|
+
if (immediate &&
|
|
4209
|
+
context.instance[prop] !== undefined &&
|
|
4210
|
+
!onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
|
|
4211
|
+
) {
|
|
4212
|
+
callback(context.instance[prop], undefined);
|
|
4213
|
+
}
|
|
4214
|
+
return $root.addCallback($root.refIds.get(ref), prop, callback);
|
|
4215
|
+
};
|
|
4216
|
+
/**
|
|
4217
|
+
* Schema instances
|
|
4218
|
+
*/
|
|
4219
|
+
return new Proxy({
|
|
4220
|
+
listen: function listen(prop, callback, immediate = true) {
|
|
4221
|
+
if (context.instance) {
|
|
4222
|
+
return onAddListen(context.instance, prop, callback, immediate);
|
|
4223
|
+
}
|
|
4224
|
+
else {
|
|
4225
|
+
// collection instance not received yet
|
|
4226
|
+
let detachCallback = () => { };
|
|
4227
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4228
|
+
detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4229
|
+
});
|
|
4230
|
+
return () => detachCallback();
|
|
4231
|
+
}
|
|
4232
|
+
},
|
|
4233
|
+
onChange: function onChange(callback) {
|
|
4234
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
|
|
4235
|
+
},
|
|
4236
|
+
//
|
|
4237
|
+
// TODO: refactor `bindTo()` implementation.
|
|
4238
|
+
// There is room for improvement.
|
|
4239
|
+
//
|
|
4240
|
+
bindTo: function bindTo(targetObject, properties) {
|
|
4241
|
+
if (!properties) {
|
|
4242
|
+
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4243
|
+
}
|
|
4244
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
|
|
4245
|
+
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4246
|
+
});
|
|
4247
|
+
}
|
|
4248
|
+
}, {
|
|
4249
|
+
get(target, prop) {
|
|
4250
|
+
const metadataField = metadata[metadata[prop]];
|
|
4251
|
+
if (metadataField) {
|
|
4252
|
+
const instance = context.instance?.[prop];
|
|
4253
|
+
const onInstanceAvailable = ((callback) => {
|
|
4254
|
+
const unbind = $(context.instance).listen(prop, (value, _) => {
|
|
4255
|
+
callback(value, false);
|
|
4256
|
+
// FIXME: by "unbinding" the callback here,
|
|
4257
|
+
// it will not support when the server
|
|
4258
|
+
// re-instantiates the instance.
|
|
4259
|
+
//
|
|
4260
|
+
unbind?.();
|
|
4261
|
+
}, false);
|
|
4262
|
+
// has existing value
|
|
4263
|
+
if ($root.refIds.get(instance) !== undefined) {
|
|
4264
|
+
callback(instance, true);
|
|
4265
|
+
}
|
|
4266
|
+
});
|
|
4267
|
+
return getProxy(metadataField.type, {
|
|
4268
|
+
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
4269
|
+
instance: ($root.refIds.get(instance) && instance),
|
|
4270
|
+
parentInstance: context.instance,
|
|
4271
|
+
onInstanceAvailable,
|
|
4272
|
+
});
|
|
4273
|
+
}
|
|
4274
|
+
else {
|
|
4275
|
+
// accessing the function
|
|
4276
|
+
return target[prop];
|
|
4277
|
+
}
|
|
4278
|
+
},
|
|
4279
|
+
has(target, prop) { return metadata[prop] !== undefined; },
|
|
4280
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4281
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4282
|
+
});
|
|
4283
|
+
}
|
|
4284
|
+
else {
|
|
4285
|
+
/**
|
|
4286
|
+
* Collection instances
|
|
4287
|
+
*/
|
|
4288
|
+
const onAdd = function (ref, callback, immediate) {
|
|
4289
|
+
// Trigger callback on existing items
|
|
4290
|
+
if (immediate) {
|
|
4291
|
+
ref.forEach((v, k) => callback(v, k));
|
|
4292
|
+
}
|
|
4293
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
|
|
4294
|
+
onAddCalls.set(callback, true);
|
|
4295
|
+
currentOnAddCallback = callback;
|
|
4296
|
+
callback(value, key);
|
|
4297
|
+
onAddCalls.delete(callback);
|
|
4298
|
+
currentOnAddCallback = undefined;
|
|
4299
|
+
});
|
|
4300
|
+
};
|
|
4301
|
+
const onRemove = function (ref, callback) {
|
|
4302
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
|
|
4303
|
+
};
|
|
4304
|
+
return new Proxy({
|
|
4305
|
+
onAdd: function (callback, immediate = true) {
|
|
4306
|
+
//
|
|
4307
|
+
// https://github.com/colyseus/schema/issues/147
|
|
4308
|
+
// If parent instance has "onAdd" registered, avoid triggering immediate callback.
|
|
4309
|
+
//
|
|
4310
|
+
if (context.instance) {
|
|
4311
|
+
return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
|
|
4312
|
+
}
|
|
4313
|
+
else if (context.onInstanceAvailable) {
|
|
4314
|
+
// collection instance not received yet
|
|
4315
|
+
let detachCallback = () => { };
|
|
4316
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4317
|
+
detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4318
|
+
});
|
|
4319
|
+
return () => detachCallback();
|
|
4320
|
+
}
|
|
4321
|
+
},
|
|
4322
|
+
onRemove: function (callback) {
|
|
4323
|
+
if (context.onInstanceAvailable) {
|
|
4324
|
+
// collection instance not received yet
|
|
4325
|
+
let detachCallback = () => { };
|
|
4326
|
+
context.onInstanceAvailable((ref) => {
|
|
4327
|
+
detachCallback = onRemove(ref, callback);
|
|
4328
|
+
});
|
|
4329
|
+
return () => detachCallback();
|
|
4330
|
+
}
|
|
4331
|
+
else if (context.instance) {
|
|
4332
|
+
return onRemove(context.instance, callback);
|
|
4333
|
+
}
|
|
4334
|
+
},
|
|
4335
|
+
}, {
|
|
4336
|
+
get(target, prop) {
|
|
4337
|
+
if (!target[prop]) {
|
|
4338
|
+
throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
|
|
4339
|
+
}
|
|
4340
|
+
return target[prop];
|
|
4341
|
+
},
|
|
4342
|
+
has(target, prop) { return target[prop] !== undefined; },
|
|
4343
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4344
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4345
|
+
});
|
|
4346
|
+
}
|
|
4347
|
+
}
|
|
4348
|
+
function $(instance) {
|
|
4349
|
+
return getProxy(undefined, { instance });
|
|
4350
|
+
}
|
|
4351
|
+
return $;
|
|
4352
|
+
}
|
|
4353
|
+
|
|
4354
|
+
function getRawChangesCallback(decoder, callback) {
|
|
4355
|
+
decoder.triggerChanges = callback;
|
|
4356
|
+
}
|
|
4357
|
+
|
|
3945
4358
|
class StateView {
|
|
3946
4359
|
constructor() {
|
|
3947
4360
|
/**
|
|
@@ -3959,20 +4372,21 @@
|
|
|
3959
4372
|
this.changes = new Map();
|
|
3960
4373
|
}
|
|
3961
4374
|
// TODO: allow to set multiple tags at once
|
|
3962
|
-
add(obj, tag = DEFAULT_VIEW_TAG) {
|
|
4375
|
+
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
3963
4376
|
if (!obj[$changes]) {
|
|
3964
4377
|
console.warn("StateView#add(), invalid object:", obj);
|
|
3965
4378
|
return this;
|
|
3966
4379
|
}
|
|
3967
|
-
|
|
3968
|
-
this.items.add(changeTree);
|
|
3969
|
-
// Add children of this ChangeTree to this view
|
|
3970
|
-
changeTree.forEachChild((change, _) => this.add(change.ref, tag));
|
|
3971
|
-
// FIXME: ArraySchema/MapSchema does not have metadata
|
|
4380
|
+
// FIXME: ArraySchema/MapSchema do not have metadata
|
|
3972
4381
|
const metadata = obj.constructor[Symbol.metadata];
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
4382
|
+
const changeTree = obj[$changes];
|
|
4383
|
+
this.items.add(changeTree);
|
|
4384
|
+
// add parent ChangeTree's
|
|
4385
|
+
// - if it was invisible to this view
|
|
4386
|
+
// - if it were previously filtered out
|
|
4387
|
+
if (checkIncludeParent && changeTree.parent) {
|
|
4388
|
+
this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
|
|
4389
|
+
}
|
|
3976
4390
|
//
|
|
3977
4391
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
3978
4392
|
// be set (only the parent's changes are set)
|
|
@@ -3996,7 +4410,6 @@
|
|
|
3996
4410
|
tags = this.tags.get(changeTree);
|
|
3997
4411
|
}
|
|
3998
4412
|
tags.add(tag);
|
|
3999
|
-
// console.log("BY TAG:", tag);
|
|
4000
4413
|
// Ref: add tagged properties
|
|
4001
4414
|
metadata?.[-3]?.[tag]?.forEach((index) => {
|
|
4002
4415
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
@@ -4005,73 +4418,63 @@
|
|
|
4005
4418
|
});
|
|
4006
4419
|
}
|
|
4007
4420
|
else {
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
// metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
|
|
4011
|
-
// if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
4012
|
-
// changes.set(index, OPERATION.ADD);
|
|
4013
|
-
// }
|
|
4014
|
-
// });
|
|
4015
|
-
const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4421
|
+
const isInvisible = this.invisible.has(changeTree);
|
|
4422
|
+
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4016
4423
|
? changeTree.allFilteredChanges
|
|
4017
4424
|
: changeTree.allChanges;
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4425
|
+
changeSet.forEach((op, index) => {
|
|
4426
|
+
const tagAtIndex = metadata?.[index].tag;
|
|
4427
|
+
if ((isInvisible || // if "invisible", include all
|
|
4428
|
+
tagAtIndex === undefined || // "all change" with no tag
|
|
4429
|
+
tagAtIndex === tag // tagged property
|
|
4430
|
+
) &&
|
|
4431
|
+
op !== exports.OPERATION.DELETE) {
|
|
4432
|
+
changes.set(index, op);
|
|
4024
4433
|
}
|
|
4025
|
-
}
|
|
4026
|
-
}
|
|
4027
|
-
// TODO: avoid unnecessary iteration here
|
|
4028
|
-
while (changeTree.parent &&
|
|
4029
|
-
(changeTree = changeTree.parent[$changes]) &&
|
|
4030
|
-
(changeTree.isFiltered || changeTree.isPartiallyFiltered)) {
|
|
4031
|
-
this.items.add(changeTree);
|
|
4434
|
+
});
|
|
4032
4435
|
}
|
|
4436
|
+
// Add children of this ChangeTree to this view
|
|
4437
|
+
changeTree.forEachChild((change, index) => {
|
|
4438
|
+
// Do not ADD children that don't have the same tag
|
|
4439
|
+
if (metadata && metadata[index].tag !== tag) {
|
|
4440
|
+
return;
|
|
4441
|
+
}
|
|
4442
|
+
this.add(change.ref, tag, false);
|
|
4443
|
+
});
|
|
4033
4444
|
return this;
|
|
4034
4445
|
}
|
|
4035
|
-
addParent(changeTree, tag) {
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4446
|
+
addParent(changeTree, parentIndex, tag) {
|
|
4447
|
+
// view must have all "changeTree" parent tree
|
|
4448
|
+
this.items.add(changeTree);
|
|
4449
|
+
// add parent's parent
|
|
4450
|
+
const parentChangeTree = changeTree.parent?.[$changes];
|
|
4451
|
+
if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
|
|
4452
|
+
this.addParent(parentChangeTree, changeTree.parentIndex, tag);
|
|
4039
4453
|
}
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
if (!this.invisible.has(parentChangeTree)) {
|
|
4043
|
-
// parent is already available, no need to add it!
|
|
4454
|
+
// parent is already available, no need to add it!
|
|
4455
|
+
if (!this.invisible.has(changeTree)) {
|
|
4044
4456
|
return;
|
|
4045
4457
|
}
|
|
4046
|
-
this.addParent(parentChangeTree, tag);
|
|
4047
4458
|
// add parent's tag properties
|
|
4048
|
-
if (
|
|
4049
|
-
let
|
|
4050
|
-
if (
|
|
4051
|
-
|
|
4052
|
-
this.changes.set(
|
|
4459
|
+
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4460
|
+
let changes = this.changes.get(changeTree);
|
|
4461
|
+
if (changes === undefined) {
|
|
4462
|
+
changes = new Map();
|
|
4463
|
+
this.changes.set(changeTree, changes);
|
|
4053
4464
|
}
|
|
4054
|
-
// console.log("add parent change", {
|
|
4055
|
-
// parentIndex,
|
|
4056
|
-
// parentChanges,
|
|
4057
|
-
// parentChange: (
|
|
4058
|
-
// parentChangeTree.getChange(parentIndex) &&
|
|
4059
|
-
// OPERATION[parentChangeTree.getChange(parentIndex)]
|
|
4060
|
-
// ),
|
|
4061
|
-
// })
|
|
4062
4465
|
if (!this.tags) {
|
|
4063
4466
|
this.tags = new WeakMap();
|
|
4064
4467
|
}
|
|
4065
4468
|
let tags;
|
|
4066
|
-
if (!this.tags.has(
|
|
4469
|
+
if (!this.tags.has(changeTree)) {
|
|
4067
4470
|
tags = new Set();
|
|
4068
|
-
this.tags.set(
|
|
4471
|
+
this.tags.set(changeTree, tags);
|
|
4069
4472
|
}
|
|
4070
4473
|
else {
|
|
4071
|
-
tags = this.tags.get(
|
|
4474
|
+
tags = this.tags.get(changeTree);
|
|
4072
4475
|
}
|
|
4073
4476
|
tags.add(tag);
|
|
4074
|
-
|
|
4477
|
+
changes.set(parentIndex, exports.OPERATION.ADD);
|
|
4075
4478
|
}
|
|
4076
4479
|
}
|
|
4077
4480
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4166,10 +4569,10 @@
|
|
|
4166
4569
|
exports.encode = encode;
|
|
4167
4570
|
exports.encodeKeyValueOperation = encodeArray;
|
|
4168
4571
|
exports.encodeSchemaOperation = encodeSchemaOperation;
|
|
4572
|
+
exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
|
|
4573
|
+
exports.getRawChangesCallback = getRawChangesCallback;
|
|
4169
4574
|
exports.registerType = registerType;
|
|
4170
4575
|
exports.type = type;
|
|
4171
4576
|
exports.view = view;
|
|
4172
4577
|
|
|
4173
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4174
|
-
|
|
4175
4578
|
}));
|