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