@colyseus/schema 3.0.0-alpha.8 → 3.0.0
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 +148 -62
- package/bin/schema-debug +94 -0
- package/build/cjs/index.js +2227 -1519
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +2228 -1522
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +2230 -1522
- package/lib/Metadata.d.ts +21 -9
- package/lib/Metadata.js +169 -32
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +19 -4
- package/lib/Reflection.js +66 -31
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +12 -5
- package/lib/Schema.js +57 -56
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +31 -34
- package/lib/annotations.js +110 -160
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.d.ts +1 -0
- package/lib/bench_encode.js +130 -0
- package/lib/bench_encode.js.map +1 -0
- package/lib/codegen/api.js +1 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/languages/cpp.js +1 -2
- package/lib/codegen/languages/cpp.js.map +1 -1
- package/lib/codegen/languages/csharp.js +9 -46
- package/lib/codegen/languages/csharp.js.map +1 -1
- package/lib/codegen/languages/haxe.js +4 -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 +23 -25
- package/lib/codegen/languages/lua.js.map +1 -1
- package/lib/codegen/languages/ts.js +1 -2
- package/lib/codegen/languages/ts.js.map +1 -1
- package/lib/codegen/parser.js +85 -3
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +6 -3
- package/lib/codegen/types.js.map +1 -1
- package/lib/debug.d.ts +1 -0
- package/lib/debug.js +51 -0
- package/lib/debug.js.map +1 -0
- package/lib/decoder/DecodeOperation.d.ts +3 -4
- package/lib/decoder/DecodeOperation.js +35 -17
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +5 -6
- package/lib/decoder/Decoder.js +10 -10
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/ReferenceTracker.js +4 -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 +74 -64
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +28 -20
- package/lib/encoder/ChangeTree.js +242 -188
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +3 -6
- package/lib/encoder/EncodeOperation.js +51 -65
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +8 -7
- package/lib/encoder/Encoder.js +133 -85
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +22 -0
- package/lib/encoder/Root.js +81 -0
- package/lib/encoder/Root.js.map +1 -0
- package/lib/encoder/StateView.d.ts +7 -7
- package/lib/encoder/StateView.js +72 -74
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/assert.d.ts +7 -6
- package/lib/encoding/assert.js +13 -5
- package/lib/encoding/assert.js.map +1 -1
- package/lib/encoding/decode.d.ts +36 -19
- package/lib/encoding/decode.js +54 -84
- package/lib/encoding/decode.js.map +1 -1
- package/lib/encoding/encode.d.ts +36 -18
- package/lib/encoding/encode.js +61 -48
- package/lib/encoding/encode.js.map +1 -1
- package/lib/encoding/spec.d.ts +4 -5
- package/lib/encoding/spec.js +1 -2
- package/lib/encoding/spec.js.map +1 -1
- package/lib/index.d.ts +10 -9
- package/lib/index.js +24 -17
- package/lib/index.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +34 -2
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/TypeContext.d.ts +29 -0
- package/lib/types/TypeContext.js +151 -0
- package/lib/types/TypeContext.js.map +1 -0
- package/lib/types/custom/ArraySchema.d.ts +2 -2
- package/lib/types/custom/ArraySchema.js +33 -22
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.d.ts +2 -2
- package/lib/types/custom/CollectionSchema.js +1 -0
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +18 -16
- package/lib/types/custom/MapSchema.js +12 -4
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.d.ts +2 -2
- package/lib/types/custom/SetSchema.js +1 -0
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/registry.d.ts +8 -1
- package/lib/types/registry.js +23 -6
- package/lib/types/registry.js.map +1 -1
- package/lib/types/symbols.d.ts +8 -5
- package/lib/types/symbols.js +9 -6
- package/lib/types/symbols.js.map +1 -1
- package/lib/types/utils.js +1 -2
- package/lib/types/utils.js.map +1 -1
- package/lib/utils.js +9 -7
- package/lib/utils.js.map +1 -1
- package/package.json +19 -18
- package/src/Metadata.ts +190 -42
- package/src/Reflection.ts +76 -38
- package/src/Schema.ts +72 -70
- package/src/annotations.ts +156 -202
- package/src/bench_encode.ts +108 -0
- package/src/codegen/languages/csharp.ts +8 -47
- package/src/codegen/languages/haxe.ts +4 -0
- package/src/codegen/languages/lua.ts +19 -27
- package/src/codegen/parser.ts +107 -0
- package/src/codegen/types.ts +1 -0
- package/src/debug.ts +55 -0
- package/src/decoder/DecodeOperation.ts +43 -15
- package/src/decoder/Decoder.ts +12 -10
- package/src/decoder/ReferenceTracker.ts +5 -3
- package/src/decoder/strategy/StateCallbacks.ts +152 -81
- package/src/encoder/ChangeTree.ts +282 -209
- package/src/encoder/EncodeOperation.ts +78 -78
- package/src/encoder/Encoder.ts +157 -93
- package/src/encoder/Root.ts +93 -0
- package/src/encoder/StateView.ts +80 -88
- package/src/encoding/assert.ts +17 -8
- package/src/encoding/decode.ts +73 -93
- package/src/encoding/encode.ts +76 -45
- package/src/encoding/spec.ts +3 -5
- package/src/index.ts +12 -20
- package/src/types/HelperTypes.ts +54 -2
- package/src/types/TypeContext.ts +175 -0
- package/src/types/custom/ArraySchema.ts +49 -19
- package/src/types/custom/CollectionSchema.ts +1 -0
- package/src/types/custom/MapSchema.ts +30 -17
- package/src/types/custom/SetSchema.ts +1 -0
- package/src/types/registry.ts +22 -3
- package/src/types/symbols.ts +10 -7
- package/src/utils.ts +7 -3
package/build/umd/index.js
CHANGED
|
@@ -24,11 +24,10 @@
|
|
|
24
24
|
/**
|
|
25
25
|
* ArraySchema operations
|
|
26
26
|
*/
|
|
27
|
-
OPERATION[OPERATION["PUSH"] = 11] = "PUSH";
|
|
28
|
-
OPERATION[OPERATION["UNSHIFT"] = 12] = "UNSHIFT";
|
|
29
27
|
OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
|
|
30
28
|
OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
|
|
31
29
|
OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
|
|
30
|
+
OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
|
|
32
31
|
})(exports.OPERATION || (exports.OPERATION = {}));
|
|
33
32
|
|
|
34
33
|
Symbol.metadata ??= Symbol.for("Symbol.metadata");
|
|
@@ -48,11 +47,6 @@
|
|
|
48
47
|
* (MapSchema, ArraySchema, etc.)
|
|
49
48
|
*/
|
|
50
49
|
const $childType = Symbol('$childType');
|
|
51
|
-
/**
|
|
52
|
-
* Special ChangeTree property to identify new instances
|
|
53
|
-
* (Once they're encoded, they're not new anymore)
|
|
54
|
-
*/
|
|
55
|
-
const $isNew = Symbol("$isNew");
|
|
56
50
|
/**
|
|
57
51
|
* Optional "discard" method for custom types (ArraySchema)
|
|
58
52
|
* (Discards changes for next serialization)
|
|
@@ -62,476 +56,303 @@
|
|
|
62
56
|
* When decoding, this method is called after the instance is fully decoded
|
|
63
57
|
*/
|
|
64
58
|
const $onDecodeEnd = Symbol("$onDecodeEnd");
|
|
59
|
+
/**
|
|
60
|
+
* Metadata
|
|
61
|
+
*/
|
|
62
|
+
const $descriptors = Symbol("$descriptors");
|
|
63
|
+
const $numFields = "$__numFields";
|
|
64
|
+
const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
|
|
65
|
+
const $viewFieldIndexes = "$__viewFieldIndexes";
|
|
66
|
+
const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
|
|
65
67
|
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
68
|
+
/**
|
|
69
|
+
* Copyright (c) 2018 Endel Dreyer
|
|
70
|
+
* Copyright (c) 2014 Ion Drive Software Ltd.
|
|
71
|
+
*
|
|
72
|
+
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
73
|
+
* of this software and associated documentation files (the "Software"), to deal
|
|
74
|
+
* in the Software without restriction, including without limitation the rights
|
|
75
|
+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
76
|
+
* copies of the Software, and to permit persons to whom the Software is
|
|
77
|
+
* furnished to do so, subject to the following conditions:
|
|
78
|
+
*
|
|
79
|
+
* The above copyright notice and this permission notice shall be included in all
|
|
80
|
+
* copies or substantial portions of the Software.
|
|
81
|
+
*
|
|
82
|
+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
83
|
+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
84
|
+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
85
|
+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
86
|
+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
87
|
+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
88
|
+
* SOFTWARE
|
|
89
|
+
*/
|
|
90
|
+
/**
|
|
91
|
+
* msgpack implementation highly based on notepack.io
|
|
92
|
+
* https://github.com/darrachequesne/notepack
|
|
93
|
+
*/
|
|
94
|
+
let textEncoder;
|
|
95
|
+
// @ts-ignore
|
|
96
|
+
try {
|
|
97
|
+
textEncoder = new TextEncoder();
|
|
74
98
|
}
|
|
75
|
-
|
|
76
|
-
const
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
99
|
+
catch (e) { }
|
|
100
|
+
const _convoBuffer$1 = new ArrayBuffer(8);
|
|
101
|
+
const _int32$1 = new Int32Array(_convoBuffer$1);
|
|
102
|
+
const _float32$1 = new Float32Array(_convoBuffer$1);
|
|
103
|
+
const _float64$1 = new Float64Array(_convoBuffer$1);
|
|
104
|
+
const _int64$1 = new BigInt64Array(_convoBuffer$1);
|
|
105
|
+
const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
|
|
106
|
+
const utf8Length = (hasBufferByteLength)
|
|
107
|
+
? Buffer.byteLength // node
|
|
108
|
+
: function (str, _) {
|
|
109
|
+
var c = 0, length = 0;
|
|
110
|
+
for (var i = 0, l = str.length; i < l; i++) {
|
|
111
|
+
c = str.charCodeAt(i);
|
|
112
|
+
if (c < 0x80) {
|
|
113
|
+
length += 1;
|
|
114
|
+
}
|
|
115
|
+
else if (c < 0x800) {
|
|
116
|
+
length += 2;
|
|
117
|
+
}
|
|
118
|
+
else if (c < 0xd800 || c >= 0xe000) {
|
|
119
|
+
length += 3;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
i++;
|
|
123
|
+
length += 4;
|
|
124
|
+
}
|
|
80
125
|
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
// map -1 as last field index
|
|
90
|
-
Object.defineProperty(metadata, -1, {
|
|
91
|
-
value: index,
|
|
92
|
-
enumerable: false,
|
|
93
|
-
configurable: true
|
|
94
|
-
});
|
|
95
|
-
// map index => field name (non enumerable)
|
|
96
|
-
Object.defineProperty(metadata, index, {
|
|
97
|
-
value: field,
|
|
98
|
-
enumerable: false,
|
|
99
|
-
configurable: true,
|
|
100
|
-
});
|
|
101
|
-
},
|
|
102
|
-
setTag(metadata, fieldName, tag) {
|
|
103
|
-
// add 'tag' to the field
|
|
104
|
-
const field = metadata[fieldName];
|
|
105
|
-
field.tag = tag;
|
|
106
|
-
if (!metadata[-2]) {
|
|
107
|
-
// -2: all field indexes with "view" tag
|
|
108
|
-
Object.defineProperty(metadata, -2, {
|
|
109
|
-
value: [],
|
|
110
|
-
enumerable: false,
|
|
111
|
-
configurable: true
|
|
112
|
-
});
|
|
113
|
-
// -3: field indexes by "view" tag
|
|
114
|
-
Object.defineProperty(metadata, -3, {
|
|
115
|
-
value: {},
|
|
116
|
-
enumerable: false,
|
|
117
|
-
configurable: true
|
|
118
|
-
});
|
|
126
|
+
return length;
|
|
127
|
+
};
|
|
128
|
+
function utf8Write(view, str, it) {
|
|
129
|
+
var c = 0;
|
|
130
|
+
for (var i = 0, l = str.length; i < l; i++) {
|
|
131
|
+
c = str.charCodeAt(i);
|
|
132
|
+
if (c < 0x80) {
|
|
133
|
+
view[it.offset++] = c;
|
|
119
134
|
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
135
|
+
else if (c < 0x800) {
|
|
136
|
+
view[it.offset] = 0xc0 | (c >> 6);
|
|
137
|
+
view[it.offset + 1] = 0x80 | (c & 0x3f);
|
|
138
|
+
it.offset += 2;
|
|
123
139
|
}
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
// changeTree.change(index, operation, encodeSchemaOperation);
|
|
130
|
-
// };
|
|
131
|
-
// target[$encoder] = encodeSchemaOperation;
|
|
132
|
-
// target[$decoder] = decodeSchemaOperation;
|
|
133
|
-
// if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
|
|
134
|
-
let index = 0;
|
|
135
|
-
for (const field in fields) {
|
|
136
|
-
const type = fields[field];
|
|
137
|
-
// FIXME: this code is duplicated from @type() annotation
|
|
138
|
-
const complexTypeKlass = (Array.isArray(type))
|
|
139
|
-
? getType("array")
|
|
140
|
-
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
141
|
-
Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass, metadata, field));
|
|
142
|
-
index++;
|
|
140
|
+
else if (c < 0xd800 || c >= 0xe000) {
|
|
141
|
+
view[it.offset] = 0xe0 | (c >> 12);
|
|
142
|
+
view[it.offset + 1] = 0x80 | (c >> 6 & 0x3f);
|
|
143
|
+
view[it.offset + 2] = 0x80 | (c & 0x3f);
|
|
144
|
+
it.offset += 3;
|
|
143
145
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
getFields(klass) {
|
|
153
|
-
const metadata = klass[Symbol.metadata];
|
|
154
|
-
const fields = {};
|
|
155
|
-
for (let i = 0; i <= metadata[-1]; i++) {
|
|
156
|
-
fields[metadata[i]] = metadata[metadata[i]].type;
|
|
146
|
+
else {
|
|
147
|
+
i++;
|
|
148
|
+
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
149
|
+
view[it.offset] = 0xf0 | (c >> 18);
|
|
150
|
+
view[it.offset + 1] = 0x80 | (c >> 12 & 0x3f);
|
|
151
|
+
view[it.offset + 2] = 0x80 | (c >> 6 & 0x3f);
|
|
152
|
+
view[it.offset + 3] = 0x80 | (c & 0x3f);
|
|
153
|
+
it.offset += 4;
|
|
157
154
|
}
|
|
158
|
-
return fields;
|
|
159
155
|
}
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
156
|
+
}
|
|
157
|
+
function int8$1(bytes, value, it) {
|
|
158
|
+
bytes[it.offset++] = value & 255;
|
|
159
|
+
}
|
|
160
|
+
function uint8$1(bytes, value, it) {
|
|
161
|
+
bytes[it.offset++] = value & 255;
|
|
162
|
+
}
|
|
163
|
+
function int16$1(bytes, value, it) {
|
|
164
|
+
bytes[it.offset++] = value & 255;
|
|
165
|
+
bytes[it.offset++] = (value >> 8) & 255;
|
|
166
|
+
}
|
|
167
|
+
function uint16$1(bytes, value, it) {
|
|
168
|
+
bytes[it.offset++] = value & 255;
|
|
169
|
+
bytes[it.offset++] = (value >> 8) & 255;
|
|
170
|
+
}
|
|
171
|
+
function int32$1(bytes, value, it) {
|
|
172
|
+
bytes[it.offset++] = value & 255;
|
|
173
|
+
bytes[it.offset++] = (value >> 8) & 255;
|
|
174
|
+
bytes[it.offset++] = (value >> 16) & 255;
|
|
175
|
+
bytes[it.offset++] = (value >> 24) & 255;
|
|
176
|
+
}
|
|
177
|
+
function uint32$1(bytes, value, it) {
|
|
178
|
+
const b4 = value >> 24;
|
|
179
|
+
const b3 = value >> 16;
|
|
180
|
+
const b2 = value >> 8;
|
|
181
|
+
const b1 = value;
|
|
182
|
+
bytes[it.offset++] = b1 & 255;
|
|
183
|
+
bytes[it.offset++] = b2 & 255;
|
|
184
|
+
bytes[it.offset++] = b3 & 255;
|
|
185
|
+
bytes[it.offset++] = b4 & 255;
|
|
186
|
+
}
|
|
187
|
+
function int64$1(bytes, value, it) {
|
|
188
|
+
const high = Math.floor(value / Math.pow(2, 32));
|
|
189
|
+
const low = value >>> 0;
|
|
190
|
+
uint32$1(bytes, low, it);
|
|
191
|
+
uint32$1(bytes, high, it);
|
|
192
|
+
}
|
|
193
|
+
function uint64$1(bytes, value, it) {
|
|
194
|
+
const high = (value / Math.pow(2, 32)) >> 0;
|
|
195
|
+
const low = value >>> 0;
|
|
196
|
+
uint32$1(bytes, low, it);
|
|
197
|
+
uint32$1(bytes, high, it);
|
|
198
|
+
}
|
|
199
|
+
function bigint64$1(bytes, value, it) {
|
|
200
|
+
_int64$1[0] = BigInt.asIntN(64, value);
|
|
201
|
+
int32$1(bytes, _int32$1[0], it);
|
|
202
|
+
int32$1(bytes, _int32$1[1], it);
|
|
203
|
+
}
|
|
204
|
+
function biguint64$1(bytes, value, it) {
|
|
205
|
+
_int64$1[0] = BigInt.asIntN(64, value);
|
|
206
|
+
int32$1(bytes, _int32$1[0], it);
|
|
207
|
+
int32$1(bytes, _int32$1[1], it);
|
|
208
|
+
}
|
|
209
|
+
function float32$1(bytes, value, it) {
|
|
210
|
+
_float32$1[0] = value;
|
|
211
|
+
int32$1(bytes, _int32$1[0], it);
|
|
212
|
+
}
|
|
213
|
+
function float64$1(bytes, value, it) {
|
|
214
|
+
_float64$1[0] = value;
|
|
215
|
+
int32$1(bytes, _int32$1[0 ], it);
|
|
216
|
+
int32$1(bytes, _int32$1[1 ], it);
|
|
217
|
+
}
|
|
218
|
+
function boolean$1(bytes, value, it) {
|
|
219
|
+
bytes[it.offset++] = value ? 1 : 0; // uint8
|
|
220
|
+
}
|
|
221
|
+
function string$1(bytes, value, it) {
|
|
222
|
+
// encode `null` strings as empty.
|
|
223
|
+
if (!value) {
|
|
224
|
+
value = "";
|
|
173
225
|
}
|
|
174
|
-
|
|
175
|
-
|
|
226
|
+
let length = utf8Length(value, "utf8");
|
|
227
|
+
let size = 0;
|
|
228
|
+
// fixstr
|
|
229
|
+
if (length < 0x20) {
|
|
230
|
+
bytes[it.offset++] = length | 0xa0;
|
|
231
|
+
size = 1;
|
|
176
232
|
}
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
233
|
+
// str 8
|
|
234
|
+
else if (length < 0x100) {
|
|
235
|
+
bytes[it.offset++] = 0xd9;
|
|
236
|
+
bytes[it.offset++] = length % 255;
|
|
237
|
+
size = 2;
|
|
180
238
|
}
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
187
|
-
this.allFilteredChanges.delete(changeTree);
|
|
188
|
-
this.filteredChanges.delete(changeTree);
|
|
189
|
-
}
|
|
190
|
-
this.refCount.delete(changeTree);
|
|
191
|
-
}
|
|
192
|
-
else {
|
|
193
|
-
this.refCount.set(changeTree, refCount - 1);
|
|
194
|
-
}
|
|
195
|
-
changeTree.forEachChild((child, _) => this.remove(child));
|
|
239
|
+
// str 16
|
|
240
|
+
else if (length < 0x10000) {
|
|
241
|
+
bytes[it.offset++] = 0xda;
|
|
242
|
+
uint16$1(bytes, length, it);
|
|
243
|
+
size = 3;
|
|
196
244
|
}
|
|
197
|
-
|
|
198
|
-
|
|
245
|
+
// str 32
|
|
246
|
+
else if (length < 0x100000000) {
|
|
247
|
+
bytes[it.offset++] = 0xdb;
|
|
248
|
+
uint32$1(bytes, length, it);
|
|
249
|
+
size = 5;
|
|
250
|
+
}
|
|
251
|
+
else {
|
|
252
|
+
throw new Error('String too long');
|
|
199
253
|
}
|
|
254
|
+
utf8Write(bytes, value, it);
|
|
255
|
+
return size + length;
|
|
200
256
|
}
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
constructor(ref) {
|
|
205
|
-
this.indexes = {}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
|
|
206
|
-
this.currentOperationIndex = 0;
|
|
207
|
-
this.allChanges = new Map();
|
|
208
|
-
this.allFilteredChanges = new Map();
|
|
209
|
-
this.changes = new Map();
|
|
210
|
-
this.filteredChanges = new Map();
|
|
211
|
-
this[_a$5] = true;
|
|
212
|
-
this.ref = ref;
|
|
257
|
+
function number$1(bytes, value, it) {
|
|
258
|
+
if (isNaN(value)) {
|
|
259
|
+
return number$1(bytes, 0, it);
|
|
213
260
|
}
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
if (!this.isFiltered) {
|
|
227
|
-
this.root.changes.set(this, this.changes);
|
|
228
|
-
}
|
|
229
|
-
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
230
|
-
this.root.allFilteredChanges.set(this, this.allFilteredChanges);
|
|
231
|
-
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
232
|
-
// } else {
|
|
233
|
-
// this.root.allChanges.set(this, this.allChanges);
|
|
234
|
-
}
|
|
235
|
-
if (!this.isFiltered) {
|
|
236
|
-
this.root.allChanges.set(this, this.allChanges);
|
|
261
|
+
else if (!isFinite(value)) {
|
|
262
|
+
return number$1(bytes, (value > 0) ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER, it);
|
|
263
|
+
}
|
|
264
|
+
else if (value !== (value | 0)) {
|
|
265
|
+
if (Math.abs(value) <= 3.4028235e+38) { // range check
|
|
266
|
+
_float32$1[0] = value;
|
|
267
|
+
if (Math.abs(Math.abs(_float32$1[0]) - Math.abs(value)) < 1e-4) { // precision check; adjust 1e-n (n = precision) to in-/decrease acceptable precision loss
|
|
268
|
+
// now we know value is in range for f32 and has acceptable precision for f32
|
|
269
|
+
bytes[it.offset++] = 0xca;
|
|
270
|
+
float32$1(bytes, value, it);
|
|
271
|
+
return 5;
|
|
272
|
+
}
|
|
237
273
|
}
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
// this.allChanges.forEach((_, index) => {
|
|
242
|
-
// const childRef = this.ref[$getByIndex](index);
|
|
243
|
-
// if (childRef && childRef[$changes]) {
|
|
244
|
-
// childRef[$changes].setRoot(root);
|
|
245
|
-
// }
|
|
246
|
-
// });
|
|
274
|
+
bytes[it.offset++] = 0xcb;
|
|
275
|
+
float64$1(bytes, value, it);
|
|
276
|
+
return 9;
|
|
247
277
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
return;
|
|
254
|
-
}
|
|
255
|
-
root.add(this);
|
|
256
|
-
// skip if parent is already set
|
|
257
|
-
if (root === this.root) {
|
|
258
|
-
this.forEachChild((changeTree, atIndex) => {
|
|
259
|
-
changeTree.setParent(this.ref, root, atIndex);
|
|
260
|
-
});
|
|
261
|
-
return;
|
|
262
|
-
}
|
|
263
|
-
this.root = root;
|
|
264
|
-
this.checkIsFiltered(parent, parentIndex);
|
|
265
|
-
if (!this.isFiltered) {
|
|
266
|
-
this.root.changes.set(this, this.changes);
|
|
267
|
-
}
|
|
268
|
-
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
269
|
-
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
270
|
-
this.root.allFilteredChanges.set(this, this.filteredChanges);
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
this.root.allChanges.set(this, this.allChanges);
|
|
274
|
-
}
|
|
275
|
-
this.ensureRefId();
|
|
276
|
-
this.forEachChild((changeTree, atIndex) => {
|
|
277
|
-
changeTree.setParent(this.ref, root, atIndex);
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
forEachChild(callback) {
|
|
281
|
-
//
|
|
282
|
-
// assign same parent on child structures
|
|
283
|
-
//
|
|
284
|
-
if (Metadata.isValidInstance(this.ref)) {
|
|
285
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
286
|
-
// FIXME: need to iterate over parent metadata instead.
|
|
287
|
-
for (const field in metadata) {
|
|
288
|
-
const value = this.ref[field];
|
|
289
|
-
if (value && value[$changes]) {
|
|
290
|
-
callback(value[$changes], metadata[field].index);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else if (typeof (this.ref) === "object") {
|
|
295
|
-
// MapSchema / ArraySchema, etc.
|
|
296
|
-
this.ref.forEach((value, key) => {
|
|
297
|
-
if (Metadata.isValidInstance(value)) {
|
|
298
|
-
callback(value[$changes], this.ref[$changes].indexes[key]);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
operation(op) {
|
|
304
|
-
this.changes.set(--this.currentOperationIndex, op);
|
|
305
|
-
this.root?.changes.set(this, this.changes);
|
|
306
|
-
}
|
|
307
|
-
change(index, operation = exports.OPERATION.ADD) {
|
|
308
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
309
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
310
|
-
const changeSet = (isFiltered)
|
|
311
|
-
? this.filteredChanges
|
|
312
|
-
: this.changes;
|
|
313
|
-
const previousOperation = changeSet.get(index);
|
|
314
|
-
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
315
|
-
const op = (!previousOperation)
|
|
316
|
-
? operation
|
|
317
|
-
: (previousOperation === exports.OPERATION.DELETE)
|
|
318
|
-
? exports.OPERATION.DELETE_AND_ADD
|
|
319
|
-
: operation;
|
|
320
|
-
changeSet.set(index, op);
|
|
321
|
-
}
|
|
322
|
-
//
|
|
323
|
-
// TODO: are DELETE operations being encoded as ADD here ??
|
|
324
|
-
//
|
|
325
|
-
if (isFiltered) {
|
|
326
|
-
this.allFilteredChanges.set(index, exports.OPERATION.ADD);
|
|
327
|
-
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
this.allChanges.set(index, exports.OPERATION.ADD);
|
|
331
|
-
this.root?.changes.set(this, this.changes);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
shiftChangeIndexes(shiftIndex) {
|
|
335
|
-
//
|
|
336
|
-
// Used only during:
|
|
337
|
-
//
|
|
338
|
-
// - ArraySchema#unshift()
|
|
339
|
-
//
|
|
340
|
-
const changeSet = (this.isFiltered)
|
|
341
|
-
? this.filteredChanges
|
|
342
|
-
: this.changes;
|
|
343
|
-
const changeSetEntries = Array.from(changeSet.entries());
|
|
344
|
-
changeSet.clear();
|
|
345
|
-
// Re-insert each entry with the shifted index
|
|
346
|
-
for (const [index, op] of changeSetEntries) {
|
|
347
|
-
changeSet.set(index + shiftIndex, op);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
351
|
-
//
|
|
352
|
-
// Used only during:
|
|
353
|
-
//
|
|
354
|
-
// - ArraySchema#splice()
|
|
355
|
-
//
|
|
356
|
-
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
357
|
-
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
|
|
358
|
-
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
|
|
365
|
-
Array.from(allChangeSet.entries()).forEach(([index, op]) => {
|
|
366
|
-
// console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
|
|
367
|
-
if (index >= startIndex) {
|
|
368
|
-
allChangeSet.delete(index);
|
|
369
|
-
allChangeSet.set(index + shiftIndex, op);
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
indexedOperation(index, operation, allChangesIndex = index) {
|
|
374
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
375
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
376
|
-
if (isFiltered) {
|
|
377
|
-
this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
|
|
378
|
-
this.filteredChanges.set(index, operation);
|
|
379
|
-
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
this.allChanges.set(allChangesIndex, exports.OPERATION.ADD);
|
|
383
|
-
this.changes.set(index, operation);
|
|
384
|
-
this.root?.changes.set(this, this.changes);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
getType(index) {
|
|
388
|
-
if (Metadata.isValidInstance(this.ref)) {
|
|
389
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
390
|
-
return metadata[metadata[index]].type;
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
//
|
|
394
|
-
// Get the child type from parent structure.
|
|
395
|
-
// - ["string"] => "string"
|
|
396
|
-
// - { map: "string" } => "string"
|
|
397
|
-
// - { set: "string" } => "string"
|
|
398
|
-
//
|
|
399
|
-
return this.ref[$childType];
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
getChange(index) {
|
|
403
|
-
// TODO: optimize this. avoid checking against multiple instances
|
|
404
|
-
return this.changes.get(index) ?? this.filteredChanges.get(index);
|
|
405
|
-
}
|
|
406
|
-
//
|
|
407
|
-
// used during `.encode()`
|
|
408
|
-
//
|
|
409
|
-
getValue(index, isEncodeAll = false) {
|
|
410
|
-
//
|
|
411
|
-
// `isEncodeAll` param is only used by ArraySchema
|
|
412
|
-
//
|
|
413
|
-
return this.ref[$getByIndex](index, isEncodeAll);
|
|
414
|
-
}
|
|
415
|
-
delete(index, operation, allChangesIndex = index) {
|
|
416
|
-
if (index === undefined) {
|
|
417
|
-
try {
|
|
418
|
-
throw new Error(`@colyseus/schema ${this.ref.constructor.name}: trying to delete non-existing index '${index}'`);
|
|
419
|
-
}
|
|
420
|
-
catch (e) {
|
|
421
|
-
console.warn(e);
|
|
422
|
-
}
|
|
423
|
-
return;
|
|
278
|
+
if (value >= 0) {
|
|
279
|
+
// positive fixnum
|
|
280
|
+
if (value < 0x80) {
|
|
281
|
+
bytes[it.offset++] = value & 255; // uint8
|
|
282
|
+
return 1;
|
|
424
283
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
const previousValue = this.getValue(index);
|
|
431
|
-
changeSet.set(index, operation ?? exports.OPERATION.DELETE);
|
|
432
|
-
// remove `root` reference
|
|
433
|
-
if (previousValue && previousValue[$changes]) {
|
|
434
|
-
previousValue[$changes].root = undefined;
|
|
435
|
-
//
|
|
436
|
-
// FIXME: this.root is "undefined"
|
|
437
|
-
//
|
|
438
|
-
// This method is being called at decoding time when a DELETE operation is found.
|
|
439
|
-
//
|
|
440
|
-
// - This is due to using the concrete Schema class at decoding time.
|
|
441
|
-
// - "Reflected" structures do not have this problem.
|
|
442
|
-
//
|
|
443
|
-
// (the property descriptors should NOT be used at decoding time. only at encoding time.)
|
|
444
|
-
//
|
|
445
|
-
this.root?.remove(previousValue[$changes]);
|
|
284
|
+
// uint 8
|
|
285
|
+
if (value < 0x100) {
|
|
286
|
+
bytes[it.offset++] = 0xcc;
|
|
287
|
+
bytes[it.offset++] = value & 255; // uint8
|
|
288
|
+
return 2;
|
|
446
289
|
}
|
|
447
|
-
//
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
this.allFilteredChanges.delete(allChangesIndex);
|
|
290
|
+
// uint 16
|
|
291
|
+
if (value < 0x10000) {
|
|
292
|
+
bytes[it.offset++] = 0xcd;
|
|
293
|
+
uint16$1(bytes, value, it);
|
|
294
|
+
return 3;
|
|
453
295
|
}
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
296
|
+
// uint 32
|
|
297
|
+
if (value < 0x100000000) {
|
|
298
|
+
bytes[it.offset++] = 0xce;
|
|
299
|
+
uint32$1(bytes, value, it);
|
|
300
|
+
return 5;
|
|
457
301
|
}
|
|
302
|
+
// uint 64
|
|
303
|
+
bytes[it.offset++] = 0xcf;
|
|
304
|
+
uint64$1(bytes, value, it);
|
|
305
|
+
return 9;
|
|
458
306
|
}
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
}
|
|
465
|
-
discard(discardAll = false) {
|
|
466
|
-
//
|
|
467
|
-
// > MapSchema:
|
|
468
|
-
// Remove cached key to ensure ADD operations is unsed instead of
|
|
469
|
-
// REPLACE in case same key is used on next patches.
|
|
470
|
-
//
|
|
471
|
-
this.ref[$onEncodeEnd]?.();
|
|
472
|
-
this.changes.clear();
|
|
473
|
-
this.filteredChanges.clear();
|
|
474
|
-
// reset operation index
|
|
475
|
-
this.currentOperationIndex = 0;
|
|
476
|
-
if (discardAll) {
|
|
477
|
-
this.allChanges.clear();
|
|
478
|
-
this.allFilteredChanges.clear();
|
|
479
|
-
// remove children references
|
|
480
|
-
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
307
|
+
else {
|
|
308
|
+
// negative fixnum
|
|
309
|
+
if (value >= -32) {
|
|
310
|
+
bytes[it.offset++] = 0xe0 | (value + 0x20);
|
|
311
|
+
return 1;
|
|
481
312
|
}
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
this.changes.forEach((_, fieldIndex) => {
|
|
488
|
-
const value = this.getValue(fieldIndex);
|
|
489
|
-
if (value && value[$changes]) {
|
|
490
|
-
value[$changes].discardAll();
|
|
491
|
-
}
|
|
492
|
-
});
|
|
493
|
-
this.discard();
|
|
494
|
-
}
|
|
495
|
-
ensureRefId() {
|
|
496
|
-
// skip if refId is already set.
|
|
497
|
-
if (this.refId !== undefined) {
|
|
498
|
-
return;
|
|
313
|
+
// int 8
|
|
314
|
+
if (value >= -128) {
|
|
315
|
+
bytes[it.offset++] = 0xd0;
|
|
316
|
+
int8$1(bytes, value, it);
|
|
317
|
+
return 2;
|
|
499
318
|
}
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
checkIsFiltered(parent, parentIndex) {
|
|
506
|
-
// Detect if current structure has "filters" declared
|
|
507
|
-
this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
|
|
508
|
-
// TODO: support "partially filtered", where the instance is visible, but only a field is not.
|
|
509
|
-
// Detect if parent has "filters" declared
|
|
510
|
-
while (parent && !this.isFiltered) {
|
|
511
|
-
const metadata = parent['constructor'][Symbol.metadata];
|
|
512
|
-
const fieldName = metadata?.[parentIndex];
|
|
513
|
-
const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
|
|
514
|
-
this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
|
|
515
|
-
parent = parent[$changes].parent;
|
|
319
|
+
// int 16
|
|
320
|
+
if (value >= -32768) {
|
|
321
|
+
bytes[it.offset++] = 0xd1;
|
|
322
|
+
int16$1(bytes, value, it);
|
|
323
|
+
return 3;
|
|
516
324
|
}
|
|
517
|
-
//
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
//
|
|
523
|
-
if (this.isFiltered && this.changes.size > 0) {
|
|
524
|
-
// swap changes reference
|
|
525
|
-
const changes = this.changes;
|
|
526
|
-
this.changes = this.filteredChanges;
|
|
527
|
-
this.filteredChanges = changes;
|
|
528
|
-
// swap "all changes" reference
|
|
529
|
-
const allFilteredChanges = this.allFilteredChanges;
|
|
530
|
-
this.allFilteredChanges = this.allChanges;
|
|
531
|
-
this.allChanges = allFilteredChanges;
|
|
325
|
+
// int 32
|
|
326
|
+
if (value >= -2147483648) {
|
|
327
|
+
bytes[it.offset++] = 0xd2;
|
|
328
|
+
int32$1(bytes, value, it);
|
|
329
|
+
return 5;
|
|
532
330
|
}
|
|
331
|
+
// int 64
|
|
332
|
+
bytes[it.offset++] = 0xd3;
|
|
333
|
+
int64$1(bytes, value, it);
|
|
334
|
+
return 9;
|
|
533
335
|
}
|
|
534
336
|
}
|
|
337
|
+
const encode = {
|
|
338
|
+
int8: int8$1,
|
|
339
|
+
uint8: uint8$1,
|
|
340
|
+
int16: int16$1,
|
|
341
|
+
uint16: uint16$1,
|
|
342
|
+
int32: int32$1,
|
|
343
|
+
uint32: uint32$1,
|
|
344
|
+
int64: int64$1,
|
|
345
|
+
uint64: uint64$1,
|
|
346
|
+
bigint64: bigint64$1,
|
|
347
|
+
biguint64: biguint64$1,
|
|
348
|
+
float32: float32$1,
|
|
349
|
+
float64: float64$1,
|
|
350
|
+
boolean: boolean$1,
|
|
351
|
+
string: string$1,
|
|
352
|
+
number: number$1,
|
|
353
|
+
utf8Write,
|
|
354
|
+
utf8Length,
|
|
355
|
+
};
|
|
535
356
|
|
|
536
357
|
/**
|
|
537
358
|
* Copyright (c) 2018 Endel Dreyer
|
|
@@ -555,698 +376,1150 @@
|
|
|
555
376
|
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
556
377
|
* SOFTWARE
|
|
557
378
|
*/
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
var c = 0, length = 0;
|
|
573
|
-
for (var i = 0, l = str.length; i < l; i++) {
|
|
574
|
-
c = str.charCodeAt(i);
|
|
575
|
-
if (c < 0x80) {
|
|
576
|
-
length += 1;
|
|
577
|
-
}
|
|
578
|
-
else if (c < 0x800) {
|
|
579
|
-
length += 2;
|
|
580
|
-
}
|
|
581
|
-
else if (c < 0xd800 || c >= 0xe000) {
|
|
582
|
-
length += 3;
|
|
583
|
-
}
|
|
584
|
-
else {
|
|
585
|
-
i++;
|
|
586
|
-
length += 4;
|
|
587
|
-
}
|
|
588
|
-
}
|
|
589
|
-
return length;
|
|
590
|
-
};
|
|
591
|
-
function utf8Write(view, str, it) {
|
|
592
|
-
var c = 0;
|
|
593
|
-
for (var i = 0, l = str.length; i < l; i++) {
|
|
594
|
-
c = str.charCodeAt(i);
|
|
595
|
-
if (c < 0x80) {
|
|
596
|
-
view[it.offset++] = c;
|
|
379
|
+
// force little endian to facilitate decoding on multiple implementations
|
|
380
|
+
const _convoBuffer = new ArrayBuffer(8);
|
|
381
|
+
const _int32 = new Int32Array(_convoBuffer);
|
|
382
|
+
const _float32 = new Float32Array(_convoBuffer);
|
|
383
|
+
const _float64 = new Float64Array(_convoBuffer);
|
|
384
|
+
const _uint64 = new BigUint64Array(_convoBuffer);
|
|
385
|
+
const _int64 = new BigInt64Array(_convoBuffer);
|
|
386
|
+
function utf8Read(bytes, it, length) {
|
|
387
|
+
var string = '', chr = 0;
|
|
388
|
+
for (var i = it.offset, end = it.offset + length; i < end; i++) {
|
|
389
|
+
var byte = bytes[i];
|
|
390
|
+
if ((byte & 0x80) === 0x00) {
|
|
391
|
+
string += String.fromCharCode(byte);
|
|
392
|
+
continue;
|
|
597
393
|
}
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
394
|
+
if ((byte & 0xe0) === 0xc0) {
|
|
395
|
+
string += String.fromCharCode(((byte & 0x1f) << 6) |
|
|
396
|
+
(bytes[++i] & 0x3f));
|
|
397
|
+
continue;
|
|
601
398
|
}
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
399
|
+
if ((byte & 0xf0) === 0xe0) {
|
|
400
|
+
string += String.fromCharCode(((byte & 0x0f) << 12) |
|
|
401
|
+
((bytes[++i] & 0x3f) << 6) |
|
|
402
|
+
((bytes[++i] & 0x3f) << 0));
|
|
403
|
+
continue;
|
|
606
404
|
}
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
405
|
+
if ((byte & 0xf8) === 0xf0) {
|
|
406
|
+
chr = ((byte & 0x07) << 18) |
|
|
407
|
+
((bytes[++i] & 0x3f) << 12) |
|
|
408
|
+
((bytes[++i] & 0x3f) << 6) |
|
|
409
|
+
((bytes[++i] & 0x3f) << 0);
|
|
410
|
+
if (chr >= 0x010000) { // surrogate pair
|
|
411
|
+
chr -= 0x010000;
|
|
412
|
+
string += String.fromCharCode((chr >>> 10) + 0xD800, (chr & 0x3FF) + 0xDC00);
|
|
413
|
+
}
|
|
414
|
+
else {
|
|
415
|
+
string += String.fromCharCode(chr);
|
|
416
|
+
}
|
|
417
|
+
continue;
|
|
614
418
|
}
|
|
419
|
+
console.error('Invalid byte ' + byte.toString(16));
|
|
420
|
+
// (do not throw error to avoid server/client from crashing due to hack attemps)
|
|
421
|
+
// throw new Error('Invalid byte ' + byte.toString(16));
|
|
615
422
|
}
|
|
423
|
+
it.offset += length;
|
|
424
|
+
return string;
|
|
616
425
|
}
|
|
617
|
-
function int8
|
|
618
|
-
bytes
|
|
426
|
+
function int8(bytes, it) {
|
|
427
|
+
return uint8(bytes, it) << 24 >> 24;
|
|
619
428
|
}
|
|
620
|
-
function uint8
|
|
621
|
-
bytes[it.offset++]
|
|
429
|
+
function uint8(bytes, it) {
|
|
430
|
+
return bytes[it.offset++];
|
|
622
431
|
}
|
|
623
|
-
function int16
|
|
624
|
-
bytes
|
|
625
|
-
bytes[it.offset++] = (value >> 8) & 255;
|
|
432
|
+
function int16(bytes, it) {
|
|
433
|
+
return uint16(bytes, it) << 16 >> 16;
|
|
626
434
|
}
|
|
627
|
-
function uint16
|
|
628
|
-
bytes[it.offset++]
|
|
629
|
-
bytes[it.offset++] = (value >> 8) & 255;
|
|
435
|
+
function uint16(bytes, it) {
|
|
436
|
+
return bytes[it.offset++] | bytes[it.offset++] << 8;
|
|
630
437
|
}
|
|
631
|
-
function int32
|
|
632
|
-
bytes[it.offset++]
|
|
633
|
-
bytes[it.offset++] = (value >> 8) & 255;
|
|
634
|
-
bytes[it.offset++] = (value >> 16) & 255;
|
|
635
|
-
bytes[it.offset++] = (value >> 24) & 255;
|
|
438
|
+
function int32(bytes, it) {
|
|
439
|
+
return bytes[it.offset++] | bytes[it.offset++] << 8 | bytes[it.offset++] << 16 | bytes[it.offset++] << 24;
|
|
636
440
|
}
|
|
637
|
-
function uint32
|
|
638
|
-
|
|
639
|
-
const b3 = value >> 16;
|
|
640
|
-
const b2 = value >> 8;
|
|
641
|
-
const b1 = value;
|
|
642
|
-
bytes[it.offset++] = b1 & 255;
|
|
643
|
-
bytes[it.offset++] = b2 & 255;
|
|
644
|
-
bytes[it.offset++] = b3 & 255;
|
|
645
|
-
bytes[it.offset++] = b4 & 255;
|
|
441
|
+
function uint32(bytes, it) {
|
|
442
|
+
return int32(bytes, it) >>> 0;
|
|
646
443
|
}
|
|
647
|
-
function
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
uint32$1(bytes, low, it);
|
|
651
|
-
uint32$1(bytes, high, it);
|
|
444
|
+
function float32(bytes, it) {
|
|
445
|
+
_int32[0] = int32(bytes, it);
|
|
446
|
+
return _float32[0];
|
|
652
447
|
}
|
|
653
|
-
function
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
uint32$1(bytes, high, it);
|
|
448
|
+
function float64(bytes, it) {
|
|
449
|
+
_int32[0 ] = int32(bytes, it);
|
|
450
|
+
_int32[1 ] = int32(bytes, it);
|
|
451
|
+
return _float64[0];
|
|
658
452
|
}
|
|
659
|
-
function
|
|
660
|
-
|
|
453
|
+
function int64(bytes, it) {
|
|
454
|
+
const low = uint32(bytes, it);
|
|
455
|
+
const high = int32(bytes, it) * Math.pow(2, 32);
|
|
456
|
+
return high + low;
|
|
661
457
|
}
|
|
662
|
-
function
|
|
663
|
-
|
|
458
|
+
function uint64(bytes, it) {
|
|
459
|
+
const low = uint32(bytes, it);
|
|
460
|
+
const high = uint32(bytes, it) * Math.pow(2, 32);
|
|
461
|
+
return high + low;
|
|
664
462
|
}
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
_float32$1[0] = value;
|
|
670
|
-
int32$1(bytes, _int32$1[0], it);
|
|
463
|
+
function bigint64(bytes, it) {
|
|
464
|
+
_int32[0] = int32(bytes, it);
|
|
465
|
+
_int32[1] = int32(bytes, it);
|
|
466
|
+
return _int64[0];
|
|
671
467
|
}
|
|
672
|
-
function
|
|
673
|
-
|
|
674
|
-
int32
|
|
675
|
-
|
|
468
|
+
function biguint64(bytes, it) {
|
|
469
|
+
_int32[0] = int32(bytes, it);
|
|
470
|
+
_int32[1] = int32(bytes, it);
|
|
471
|
+
return _uint64[0];
|
|
676
472
|
}
|
|
677
|
-
function boolean
|
|
678
|
-
bytes
|
|
473
|
+
function boolean(bytes, it) {
|
|
474
|
+
return uint8(bytes, it) > 0;
|
|
679
475
|
}
|
|
680
|
-
function string
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
let size = 0;
|
|
687
|
-
// fixstr
|
|
688
|
-
if (length < 0x20) {
|
|
689
|
-
bytes[it.offset++] = length | 0xa0;
|
|
690
|
-
size = 1;
|
|
691
|
-
}
|
|
692
|
-
// str 8
|
|
693
|
-
else if (length < 0x100) {
|
|
694
|
-
bytes[it.offset++] = 0xd9;
|
|
695
|
-
bytes[it.offset++] = length % 255;
|
|
696
|
-
size = 2;
|
|
476
|
+
function string(bytes, it) {
|
|
477
|
+
const prefix = bytes[it.offset++];
|
|
478
|
+
let length;
|
|
479
|
+
if (prefix < 0xc0) {
|
|
480
|
+
// fixstr
|
|
481
|
+
length = prefix & 0x1f;
|
|
697
482
|
}
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
bytes[it.offset++] = 0xda;
|
|
701
|
-
uint16$1(bytes, length, it);
|
|
702
|
-
size = 3;
|
|
483
|
+
else if (prefix === 0xd9) {
|
|
484
|
+
length = uint8(bytes, it);
|
|
703
485
|
}
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
bytes[it.offset++] = 0xdb;
|
|
707
|
-
uint32$1(bytes, length, it);
|
|
708
|
-
size = 5;
|
|
486
|
+
else if (prefix === 0xda) {
|
|
487
|
+
length = uint16(bytes, it);
|
|
709
488
|
}
|
|
710
|
-
else {
|
|
711
|
-
|
|
489
|
+
else if (prefix === 0xdb) {
|
|
490
|
+
length = uint32(bytes, it);
|
|
712
491
|
}
|
|
713
|
-
|
|
714
|
-
return size + length;
|
|
492
|
+
return utf8Read(bytes, it, length);
|
|
715
493
|
}
|
|
716
|
-
function number
|
|
717
|
-
|
|
718
|
-
|
|
494
|
+
function number(bytes, it) {
|
|
495
|
+
const prefix = bytes[it.offset++];
|
|
496
|
+
if (prefix < 0x80) {
|
|
497
|
+
// positive fixint
|
|
498
|
+
return prefix;
|
|
719
499
|
}
|
|
720
|
-
else if (
|
|
721
|
-
|
|
500
|
+
else if (prefix === 0xca) {
|
|
501
|
+
// float 32
|
|
502
|
+
return float32(bytes, it);
|
|
722
503
|
}
|
|
723
|
-
else if (
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
return 9;
|
|
727
|
-
// TODO: encode float 32?
|
|
728
|
-
// is it possible to differentiate between float32 / float64 here?
|
|
729
|
-
// // float 32
|
|
730
|
-
// bytes.push(0xca);
|
|
731
|
-
// writeFloat32(bytes, value);
|
|
732
|
-
// return 5;
|
|
504
|
+
else if (prefix === 0xcb) {
|
|
505
|
+
// float 64
|
|
506
|
+
return float64(bytes, it);
|
|
733
507
|
}
|
|
734
|
-
if (
|
|
735
|
-
// positive fixnum
|
|
736
|
-
if (value < 0x80) {
|
|
737
|
-
bytes[it.offset++] = value & 255; // uint8
|
|
738
|
-
return 1;
|
|
739
|
-
}
|
|
508
|
+
else if (prefix === 0xcc) {
|
|
740
509
|
// uint 8
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
return 2;
|
|
745
|
-
}
|
|
510
|
+
return uint8(bytes, it);
|
|
511
|
+
}
|
|
512
|
+
else if (prefix === 0xcd) {
|
|
746
513
|
// uint 16
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
return 3;
|
|
751
|
-
}
|
|
514
|
+
return uint16(bytes, it);
|
|
515
|
+
}
|
|
516
|
+
else if (prefix === 0xce) {
|
|
752
517
|
// uint 32
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
return 5;
|
|
757
|
-
}
|
|
518
|
+
return uint32(bytes, it);
|
|
519
|
+
}
|
|
520
|
+
else if (prefix === 0xcf) {
|
|
758
521
|
// uint 64
|
|
759
|
-
bytes
|
|
760
|
-
uint64$1(bytes, value, it);
|
|
761
|
-
return 9;
|
|
522
|
+
return uint64(bytes, it);
|
|
762
523
|
}
|
|
763
|
-
else {
|
|
764
|
-
// negative fixnum
|
|
765
|
-
if (value >= -0x20) {
|
|
766
|
-
bytes[it.offset++] = 0xe0 | (value + 0x20);
|
|
767
|
-
return 1;
|
|
768
|
-
}
|
|
524
|
+
else if (prefix === 0xd0) {
|
|
769
525
|
// int 8
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
return 2;
|
|
774
|
-
}
|
|
526
|
+
return int8(bytes, it);
|
|
527
|
+
}
|
|
528
|
+
else if (prefix === 0xd1) {
|
|
775
529
|
// int 16
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
return 3;
|
|
780
|
-
}
|
|
530
|
+
return int16(bytes, it);
|
|
531
|
+
}
|
|
532
|
+
else if (prefix === 0xd2) {
|
|
781
533
|
// int 32
|
|
782
|
-
|
|
783
|
-
bytes[it.offset++] = 0xd2;
|
|
784
|
-
int32$1(bytes, value, it);
|
|
785
|
-
return 5;
|
|
786
|
-
}
|
|
787
|
-
// int 64
|
|
788
|
-
bytes[it.offset++] = 0xd3;
|
|
789
|
-
int64$1(bytes, value, it);
|
|
790
|
-
return 9;
|
|
534
|
+
return int32(bytes, it);
|
|
791
535
|
}
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
__proto__: null,
|
|
796
|
-
utf8Length: utf8Length,
|
|
797
|
-
utf8Write: utf8Write,
|
|
798
|
-
int8: int8$1,
|
|
799
|
-
uint8: uint8$1,
|
|
800
|
-
int16: int16$1,
|
|
801
|
-
uint16: uint16$1,
|
|
802
|
-
int32: int32$1,
|
|
803
|
-
uint32: uint32$1,
|
|
804
|
-
int64: int64$1,
|
|
805
|
-
uint64: uint64$1,
|
|
806
|
-
float32: float32$1,
|
|
807
|
-
float64: float64$1,
|
|
808
|
-
writeFloat32: writeFloat32,
|
|
809
|
-
writeFloat64: writeFloat64,
|
|
810
|
-
boolean: boolean$1,
|
|
811
|
-
string: string$1,
|
|
812
|
-
number: number$1
|
|
813
|
-
});
|
|
814
|
-
|
|
815
|
-
class EncodeSchemaError extends Error {
|
|
816
|
-
}
|
|
817
|
-
function assertType(value, type, klass, field) {
|
|
818
|
-
let typeofTarget;
|
|
819
|
-
let allowNull = false;
|
|
820
|
-
switch (type) {
|
|
821
|
-
case "number":
|
|
822
|
-
case "int8":
|
|
823
|
-
case "uint8":
|
|
824
|
-
case "int16":
|
|
825
|
-
case "uint16":
|
|
826
|
-
case "int32":
|
|
827
|
-
case "uint32":
|
|
828
|
-
case "int64":
|
|
829
|
-
case "uint64":
|
|
830
|
-
case "float32":
|
|
831
|
-
case "float64":
|
|
832
|
-
typeofTarget = "number";
|
|
833
|
-
if (isNaN(value)) {
|
|
834
|
-
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
835
|
-
}
|
|
836
|
-
break;
|
|
837
|
-
case "string":
|
|
838
|
-
typeofTarget = "string";
|
|
839
|
-
allowNull = true;
|
|
840
|
-
break;
|
|
841
|
-
case "boolean":
|
|
842
|
-
// boolean is always encoded as true/false based on truthiness
|
|
843
|
-
return;
|
|
536
|
+
else if (prefix === 0xd3) {
|
|
537
|
+
// int 64
|
|
538
|
+
return int64(bytes, it);
|
|
844
539
|
}
|
|
845
|
-
if (
|
|
846
|
-
|
|
847
|
-
|
|
540
|
+
else if (prefix > 0xdf) {
|
|
541
|
+
// negative fixint
|
|
542
|
+
return (0xff - prefix + 1) * -1;
|
|
848
543
|
}
|
|
849
544
|
}
|
|
850
|
-
function
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
545
|
+
function stringCheck(bytes, it) {
|
|
546
|
+
const prefix = bytes[it.offset];
|
|
547
|
+
return (
|
|
548
|
+
// fixstr
|
|
549
|
+
(prefix < 0xc0 && prefix > 0xa0) ||
|
|
550
|
+
// str 8
|
|
551
|
+
prefix === 0xd9 ||
|
|
552
|
+
// str 16
|
|
553
|
+
prefix === 0xda ||
|
|
554
|
+
// str 32
|
|
555
|
+
prefix === 0xdb);
|
|
854
556
|
}
|
|
557
|
+
const decode = {
|
|
558
|
+
utf8Read,
|
|
559
|
+
int8,
|
|
560
|
+
uint8,
|
|
561
|
+
int16,
|
|
562
|
+
uint16,
|
|
563
|
+
int32,
|
|
564
|
+
uint32,
|
|
565
|
+
float32,
|
|
566
|
+
float64,
|
|
567
|
+
int64,
|
|
568
|
+
uint64,
|
|
569
|
+
bigint64,
|
|
570
|
+
biguint64,
|
|
571
|
+
boolean,
|
|
572
|
+
string,
|
|
573
|
+
number,
|
|
574
|
+
stringCheck,
|
|
575
|
+
};
|
|
855
576
|
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
if (
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
}
|
|
863
|
-
else {
|
|
864
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
865
|
-
}
|
|
866
|
-
}
|
|
867
|
-
function encodeValue(encoder, bytes, ref, type, value, field, operation, it) {
|
|
868
|
-
if (type[Symbol.metadata] !== undefined) {
|
|
869
|
-
// TODO: move this to the `@type()` annotation
|
|
870
|
-
assertInstanceType(value, type, ref, field);
|
|
871
|
-
//
|
|
872
|
-
// Encode refId for this instance.
|
|
873
|
-
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
874
|
-
//
|
|
875
|
-
number$1(bytes, value[$changes].refId, it);
|
|
876
|
-
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
877
|
-
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
878
|
-
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
879
|
-
}
|
|
577
|
+
const registeredTypes = {};
|
|
578
|
+
const identifiers = new Map();
|
|
579
|
+
function registerType(identifier, definition) {
|
|
580
|
+
if (definition.constructor) {
|
|
581
|
+
identifiers.set(definition.constructor, identifier);
|
|
582
|
+
registeredTypes[identifier] = definition;
|
|
880
583
|
}
|
|
881
|
-
|
|
882
|
-
|
|
883
|
-
// Primitive values
|
|
884
|
-
//
|
|
885
|
-
encodePrimitiveType(type, bytes, value, ref, field, it);
|
|
584
|
+
if (definition.encode) {
|
|
585
|
+
encode[identifier] = definition.encode;
|
|
886
586
|
}
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
890
|
-
//
|
|
891
|
-
const definition = getType(Object.keys(type)[0]);
|
|
892
|
-
//
|
|
893
|
-
// ensure a ArraySchema has been provided
|
|
894
|
-
//
|
|
895
|
-
assertInstanceType(ref[field], definition.constructor, ref, field);
|
|
896
|
-
//
|
|
897
|
-
// Encode refId for this instance.
|
|
898
|
-
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
899
|
-
//
|
|
900
|
-
number$1(bytes, value[$changes].refId, it);
|
|
587
|
+
if (definition.decode) {
|
|
588
|
+
decode[identifier] = definition.decode;
|
|
901
589
|
}
|
|
902
590
|
}
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
const metadata = ref['constructor'][Symbol.metadata];
|
|
910
|
-
const field = metadata[index];
|
|
911
|
-
const type = metadata[field].type;
|
|
912
|
-
const value = ref[field];
|
|
913
|
-
// "compress" field index + operation
|
|
914
|
-
bytes[it.offset++] = (index | operation) & 255;
|
|
915
|
-
// Do not encode value for DELETE operations
|
|
916
|
-
if (operation === exports.OPERATION.DELETE) {
|
|
917
|
-
return;
|
|
918
|
-
}
|
|
919
|
-
// TODO: inline this function call small performance gain
|
|
920
|
-
encodeValue(encoder, bytes, ref, type, value, field, operation, it);
|
|
921
|
-
};
|
|
922
|
-
/**
|
|
923
|
-
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
924
|
-
* @private
|
|
925
|
-
*/
|
|
926
|
-
const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
|
|
927
|
-
const ref = changeTree.ref;
|
|
928
|
-
// encode operation
|
|
929
|
-
bytes[it.offset++] = operation & 255;
|
|
930
|
-
// custom operations
|
|
931
|
-
if (operation === exports.OPERATION.CLEAR) {
|
|
932
|
-
return;
|
|
591
|
+
function getType(identifier) {
|
|
592
|
+
return registeredTypes[identifier];
|
|
593
|
+
}
|
|
594
|
+
function defineCustomTypes(types) {
|
|
595
|
+
for (const identifier in types) {
|
|
596
|
+
registerType(identifier, types[identifier]);
|
|
933
597
|
}
|
|
934
|
-
|
|
935
|
-
|
|
936
|
-
|
|
937
|
-
|
|
938
|
-
|
|
598
|
+
return (t) => type(t);
|
|
599
|
+
}
|
|
600
|
+
|
|
601
|
+
class TypeContext {
|
|
602
|
+
/**
|
|
603
|
+
* For inheritance support
|
|
604
|
+
* Keeps track of which classes extends which. (parent -> children)
|
|
605
|
+
*/
|
|
606
|
+
static { this.inheritedTypes = new Map(); }
|
|
607
|
+
static register(target) {
|
|
608
|
+
const parent = Object.getPrototypeOf(target);
|
|
609
|
+
if (parent !== Schema) {
|
|
610
|
+
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
611
|
+
if (!inherits) {
|
|
612
|
+
inherits = new Set();
|
|
613
|
+
TypeContext.inheritedTypes.set(parent, inherits);
|
|
614
|
+
}
|
|
615
|
+
inherits.add(target);
|
|
616
|
+
}
|
|
939
617
|
}
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
618
|
+
constructor(rootClass) {
|
|
619
|
+
this.types = {};
|
|
620
|
+
this.schemas = new Map();
|
|
621
|
+
this.hasFilters = false;
|
|
622
|
+
this.parentFiltered = {};
|
|
623
|
+
if (rootClass) {
|
|
945
624
|
//
|
|
946
|
-
//
|
|
625
|
+
// TODO:
|
|
626
|
+
// cache "discoverTypes" results for each rootClass
|
|
627
|
+
// to avoid re-discovering types for each new context/room
|
|
947
628
|
//
|
|
948
|
-
|
|
949
|
-
string$1(bytes, dynamicIndex, it);
|
|
629
|
+
this.discoverTypes(rootClass);
|
|
950
630
|
}
|
|
951
631
|
}
|
|
952
|
-
|
|
953
|
-
|
|
954
|
-
// TODO: inline this function call small performance gain
|
|
955
|
-
encodeValue(encoder, bytes, ref, type, value, field, operation, it);
|
|
956
|
-
};
|
|
957
|
-
/**
|
|
958
|
-
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
959
|
-
* @private
|
|
960
|
-
*/
|
|
961
|
-
const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
|
|
962
|
-
const ref = changeTree.ref;
|
|
963
|
-
if (hasView &&
|
|
964
|
-
operation === exports.OPERATION.DELETE &&
|
|
965
|
-
typeof (changeTree.getType(field)) !== "string") {
|
|
966
|
-
// encode delete by refId (array of schemas)
|
|
967
|
-
bytes[it.offset++] = exports.OPERATION.DELETE_BY_REFID;
|
|
968
|
-
const value = ref['tmpItems'][field];
|
|
969
|
-
const refId = value[$changes].refId;
|
|
970
|
-
number$1(bytes, refId, it);
|
|
971
|
-
return;
|
|
972
|
-
}
|
|
973
|
-
// encode operation
|
|
974
|
-
bytes[it.offset++] = operation & 255;
|
|
975
|
-
// custom operations
|
|
976
|
-
if (operation === exports.OPERATION.CLEAR) {
|
|
977
|
-
return;
|
|
632
|
+
has(schema) {
|
|
633
|
+
return this.schemas.has(schema);
|
|
978
634
|
}
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
// Do not encode value for DELETE operations
|
|
982
|
-
if (operation === exports.OPERATION.DELETE) {
|
|
983
|
-
return;
|
|
635
|
+
get(typeid) {
|
|
636
|
+
return this.types[typeid];
|
|
984
637
|
}
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
// field,
|
|
990
|
-
// operation: OPERATION[operation],
|
|
991
|
-
// value: value?.toJSON(),
|
|
992
|
-
// items: ref.toJSON(),
|
|
993
|
-
// });
|
|
994
|
-
// TODO: inline this function call small performance gain
|
|
995
|
-
encodeValue(encoder, bytes, ref, type, value, field, operation, it);
|
|
996
|
-
};
|
|
997
|
-
|
|
998
|
-
/**
|
|
999
|
-
* Copyright (c) 2018 Endel Dreyer
|
|
1000
|
-
* Copyright (c) 2014 Ion Drive Software Ltd.
|
|
1001
|
-
*
|
|
1002
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1003
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
1004
|
-
* in the Software without restriction, including without limitation the rights
|
|
1005
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1006
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
1007
|
-
* furnished to do so, subject to the following conditions:
|
|
1008
|
-
*
|
|
1009
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
1010
|
-
* copies or substantial portions of the Software.
|
|
1011
|
-
*
|
|
1012
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1013
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1014
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1015
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1016
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1017
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1018
|
-
* SOFTWARE
|
|
1019
|
-
*/
|
|
1020
|
-
function utf8Read(bytes, it, length) {
|
|
1021
|
-
var string = '', chr = 0;
|
|
1022
|
-
for (var i = it.offset, end = it.offset + length; i < end; i++) {
|
|
1023
|
-
var byte = bytes[i];
|
|
1024
|
-
if ((byte & 0x80) === 0x00) {
|
|
1025
|
-
string += String.fromCharCode(byte);
|
|
1026
|
-
continue;
|
|
638
|
+
add(schema, typeid = this.schemas.size) {
|
|
639
|
+
// skip if already registered
|
|
640
|
+
if (this.schemas.has(schema)) {
|
|
641
|
+
return false;
|
|
1027
642
|
}
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
643
|
+
this.types[typeid] = schema;
|
|
644
|
+
//
|
|
645
|
+
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
646
|
+
//
|
|
647
|
+
if (schema[Symbol.metadata] === undefined) {
|
|
648
|
+
Metadata.initialize(schema);
|
|
1032
649
|
}
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
650
|
+
this.schemas.set(schema, typeid);
|
|
651
|
+
return true;
|
|
652
|
+
}
|
|
653
|
+
getTypeId(klass) {
|
|
654
|
+
return this.schemas.get(klass);
|
|
655
|
+
}
|
|
656
|
+
discoverTypes(klass, parentType, parentIndex, parentHasViewTag) {
|
|
657
|
+
if (parentHasViewTag) {
|
|
658
|
+
this.registerFilteredByParent(klass, parentType, parentIndex);
|
|
1038
659
|
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
1045
|
-
|
|
1046
|
-
|
|
1047
|
-
|
|
1048
|
-
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
660
|
+
// skip if already registered
|
|
661
|
+
if (!this.add(klass)) {
|
|
662
|
+
return;
|
|
663
|
+
}
|
|
664
|
+
// add classes inherited from this base class
|
|
665
|
+
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
666
|
+
this.discoverTypes(child, parentType, parentIndex, parentHasViewTag);
|
|
667
|
+
});
|
|
668
|
+
// add parent classes
|
|
669
|
+
let parent = klass;
|
|
670
|
+
while ((parent = Object.getPrototypeOf(parent)) &&
|
|
671
|
+
parent !== Schema && // stop at root (Schema)
|
|
672
|
+
parent !== Function.prototype // stop at root (non-Schema)
|
|
673
|
+
) {
|
|
674
|
+
this.discoverTypes(parent);
|
|
675
|
+
}
|
|
676
|
+
const metadata = (klass[Symbol.metadata] ??= {});
|
|
677
|
+
// if any schema/field has filters, mark "context" as having filters.
|
|
678
|
+
if (metadata[$viewFieldIndexes]) {
|
|
679
|
+
this.hasFilters = true;
|
|
680
|
+
}
|
|
681
|
+
for (const fieldIndex in metadata) {
|
|
682
|
+
const index = fieldIndex;
|
|
683
|
+
const fieldType = metadata[index].type;
|
|
684
|
+
const fieldHasViewTag = (metadata[index].tag !== undefined);
|
|
685
|
+
if (typeof (fieldType) === "string") {
|
|
686
|
+
continue;
|
|
687
|
+
}
|
|
688
|
+
if (Array.isArray(fieldType)) {
|
|
689
|
+
const type = fieldType[0];
|
|
690
|
+
// skip primitive types
|
|
691
|
+
if (type === "string") {
|
|
692
|
+
continue;
|
|
693
|
+
}
|
|
694
|
+
this.discoverTypes(type, klass, index, parentHasViewTag || fieldHasViewTag);
|
|
695
|
+
}
|
|
696
|
+
else if (typeof (fieldType) === "function") {
|
|
697
|
+
this.discoverTypes(fieldType, klass, index, parentHasViewTag || fieldHasViewTag);
|
|
698
|
+
}
|
|
699
|
+
else {
|
|
700
|
+
const type = Object.values(fieldType)[0];
|
|
701
|
+
// skip primitive types
|
|
702
|
+
if (typeof (type) === "string") {
|
|
703
|
+
continue;
|
|
704
|
+
}
|
|
705
|
+
this.discoverTypes(type, klass, index, parentHasViewTag || fieldHasViewTag);
|
|
706
|
+
}
|
|
1052
707
|
}
|
|
1053
|
-
console.error('Invalid byte ' + byte.toString(16));
|
|
1054
|
-
// (do not throw error to avoid server/client from crashing due to hack attemps)
|
|
1055
|
-
// throw new Error('Invalid byte ' + byte.toString(16));
|
|
1056
708
|
}
|
|
1057
|
-
|
|
1058
|
-
|
|
1059
|
-
|
|
1060
|
-
|
|
1061
|
-
|
|
1062
|
-
|
|
1063
|
-
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
709
|
+
/**
|
|
710
|
+
* Keep track of which classes have filters applied.
|
|
711
|
+
* Format: `${typeid}-${parentTypeid}-${parentIndex}`
|
|
712
|
+
*/
|
|
713
|
+
registerFilteredByParent(schema, parentType, parentIndex) {
|
|
714
|
+
const typeid = this.schemas.get(schema) ?? this.schemas.size;
|
|
715
|
+
let key = `${typeid}`;
|
|
716
|
+
if (parentType) {
|
|
717
|
+
key += `-${this.schemas.get(parentType)}`;
|
|
718
|
+
}
|
|
719
|
+
key += `-${parentIndex}`;
|
|
720
|
+
this.parentFiltered[key] = true;
|
|
721
|
+
}
|
|
722
|
+
debug() {
|
|
723
|
+
let parentFiltered = "";
|
|
724
|
+
for (const key in this.parentFiltered) {
|
|
725
|
+
const keys = key.split("-").map(Number);
|
|
726
|
+
const fieldIndex = keys.pop();
|
|
727
|
+
parentFiltered += `\n\t\t`;
|
|
728
|
+
parentFiltered += `${key}: ${keys.reverse().map((id, i) => {
|
|
729
|
+
const klass = this.types[id];
|
|
730
|
+
const metadata = klass[Symbol.metadata];
|
|
731
|
+
let txt = klass.name;
|
|
732
|
+
if (i === 0) {
|
|
733
|
+
txt += `[${metadata[fieldIndex].name}]`;
|
|
734
|
+
}
|
|
735
|
+
return `${txt}`;
|
|
736
|
+
}).join(" -> ")}`;
|
|
737
|
+
}
|
|
738
|
+
return `TypeContext ->\n` +
|
|
739
|
+
`\tSchema types: ${this.schemas.size}\n` +
|
|
740
|
+
`\thasFilters: ${this.hasFilters}\n` +
|
|
741
|
+
`\tparentFiltered:${parentFiltered}`;
|
|
742
|
+
}
|
|
1088
743
|
}
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
744
|
+
|
|
745
|
+
function getNormalizedType(type) {
|
|
746
|
+
return (Array.isArray(type))
|
|
747
|
+
? { array: type[0] }
|
|
748
|
+
: (typeof (type['type']) !== "undefined")
|
|
749
|
+
? type['type']
|
|
750
|
+
: type;
|
|
1093
751
|
}
|
|
1094
|
-
const
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
752
|
+
const Metadata = {
|
|
753
|
+
addField(metadata, index, name, type, descriptor) {
|
|
754
|
+
if (index > 64) {
|
|
755
|
+
throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
|
|
756
|
+
}
|
|
757
|
+
metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
|
|
758
|
+
{
|
|
759
|
+
type: getNormalizedType(type),
|
|
760
|
+
index,
|
|
761
|
+
name,
|
|
762
|
+
});
|
|
763
|
+
// create "descriptors" map
|
|
764
|
+
Object.defineProperty(metadata, $descriptors, {
|
|
765
|
+
value: metadata[$descriptors] || {},
|
|
766
|
+
enumerable: false,
|
|
767
|
+
configurable: true,
|
|
768
|
+
});
|
|
769
|
+
if (descriptor) {
|
|
770
|
+
// for encoder
|
|
771
|
+
metadata[$descriptors][name] = descriptor;
|
|
772
|
+
metadata[$descriptors][`_${name}`] = {
|
|
773
|
+
value: undefined,
|
|
774
|
+
writable: true,
|
|
775
|
+
enumerable: false,
|
|
776
|
+
configurable: true,
|
|
777
|
+
};
|
|
778
|
+
}
|
|
779
|
+
else {
|
|
780
|
+
// for decoder
|
|
781
|
+
metadata[$descriptors][name] = {
|
|
782
|
+
value: undefined,
|
|
783
|
+
writable: true,
|
|
784
|
+
enumerable: true,
|
|
785
|
+
configurable: true,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
// map -1 as last field index
|
|
789
|
+
Object.defineProperty(metadata, $numFields, {
|
|
790
|
+
value: index,
|
|
791
|
+
enumerable: false,
|
|
792
|
+
configurable: true
|
|
793
|
+
});
|
|
794
|
+
// map field name => index (non enumerable)
|
|
795
|
+
Object.defineProperty(metadata, name, {
|
|
796
|
+
value: index,
|
|
797
|
+
enumerable: false,
|
|
798
|
+
configurable: true,
|
|
799
|
+
});
|
|
800
|
+
// if child Ref/complex type, add to -4
|
|
801
|
+
if (typeof (metadata[index].type) !== "string") {
|
|
802
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
803
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
804
|
+
value: [],
|
|
805
|
+
enumerable: false,
|
|
806
|
+
configurable: true,
|
|
807
|
+
});
|
|
808
|
+
}
|
|
809
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
810
|
+
}
|
|
811
|
+
},
|
|
812
|
+
setTag(metadata, fieldName, tag) {
|
|
813
|
+
const index = metadata[fieldName];
|
|
814
|
+
const field = metadata[index];
|
|
815
|
+
// add 'tag' to the field
|
|
816
|
+
field.tag = tag;
|
|
817
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
818
|
+
// -2: all field indexes with "view" tag
|
|
819
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
820
|
+
value: [],
|
|
821
|
+
enumerable: false,
|
|
822
|
+
configurable: true
|
|
823
|
+
});
|
|
824
|
+
// -3: field indexes by "view" tag
|
|
825
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
826
|
+
value: {},
|
|
827
|
+
enumerable: false,
|
|
828
|
+
configurable: true
|
|
829
|
+
});
|
|
830
|
+
}
|
|
831
|
+
metadata[$viewFieldIndexes].push(index);
|
|
832
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
833
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
834
|
+
}
|
|
835
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
836
|
+
},
|
|
837
|
+
setFields(target, fields) {
|
|
838
|
+
// for inheritance support
|
|
839
|
+
const constructor = target.prototype.constructor;
|
|
840
|
+
TypeContext.register(constructor);
|
|
841
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
842
|
+
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
843
|
+
const metadata = Metadata.initialize(constructor);
|
|
844
|
+
// Use Schema's methods if not defined in the class
|
|
845
|
+
if (!constructor[$track]) {
|
|
846
|
+
constructor[$track] = Schema[$track];
|
|
847
|
+
}
|
|
848
|
+
if (!constructor[$encoder]) {
|
|
849
|
+
constructor[$encoder] = Schema[$encoder];
|
|
850
|
+
}
|
|
851
|
+
if (!constructor[$decoder]) {
|
|
852
|
+
constructor[$decoder] = Schema[$decoder];
|
|
853
|
+
}
|
|
854
|
+
if (!constructor.prototype.toJSON) {
|
|
855
|
+
constructor.prototype.toJSON = Schema.prototype.toJSON;
|
|
856
|
+
}
|
|
857
|
+
//
|
|
858
|
+
// detect index for this field, considering inheritance
|
|
859
|
+
//
|
|
860
|
+
let fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
861
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
862
|
+
?? -1; // no fields defined
|
|
863
|
+
fieldIndex++;
|
|
864
|
+
for (const field in fields) {
|
|
865
|
+
const type = fields[field];
|
|
866
|
+
const normalizedType = getNormalizedType(type);
|
|
867
|
+
// FIXME: this code is duplicated from @type() annotation
|
|
868
|
+
const complexTypeKlass = (Array.isArray(type))
|
|
869
|
+
? getType("array")
|
|
870
|
+
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
871
|
+
const childType = (complexTypeKlass)
|
|
872
|
+
? Object.values(type)[0]
|
|
873
|
+
: normalizedType;
|
|
874
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
875
|
+
fieldIndex++;
|
|
876
|
+
}
|
|
877
|
+
return target;
|
|
878
|
+
},
|
|
879
|
+
isDeprecated(metadata, field) {
|
|
880
|
+
return metadata[field].deprecated === true;
|
|
881
|
+
},
|
|
882
|
+
init(klass) {
|
|
883
|
+
//
|
|
884
|
+
// Used only to initialize an empty Schema (Encoder#constructor)
|
|
885
|
+
// TODO: remove/refactor this...
|
|
886
|
+
//
|
|
887
|
+
const metadata = {};
|
|
888
|
+
klass[Symbol.metadata] = metadata;
|
|
889
|
+
Object.defineProperty(metadata, $numFields, {
|
|
890
|
+
value: 0,
|
|
891
|
+
enumerable: false,
|
|
892
|
+
configurable: true,
|
|
893
|
+
});
|
|
894
|
+
},
|
|
895
|
+
initialize(constructor) {
|
|
896
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
897
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
898
|
+
let metadata = constructor[Symbol.metadata] ?? Object.create(null);
|
|
899
|
+
// make sure inherited classes have their own metadata object.
|
|
900
|
+
if (parentClass !== Schema && metadata === parentMetadata) {
|
|
901
|
+
metadata = Object.create(null);
|
|
902
|
+
if (parentMetadata) {
|
|
903
|
+
//
|
|
904
|
+
// assign parent metadata to current
|
|
905
|
+
//
|
|
906
|
+
Object.setPrototypeOf(metadata, parentMetadata);
|
|
907
|
+
// $numFields
|
|
908
|
+
Object.defineProperty(metadata, $numFields, {
|
|
909
|
+
value: parentMetadata[$numFields],
|
|
910
|
+
enumerable: false,
|
|
911
|
+
configurable: true,
|
|
912
|
+
writable: true,
|
|
913
|
+
});
|
|
914
|
+
// $viewFieldIndexes / $fieldIndexesByViewTag
|
|
915
|
+
if (parentMetadata[$viewFieldIndexes] !== undefined) {
|
|
916
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
917
|
+
value: [...parentMetadata[$viewFieldIndexes]],
|
|
918
|
+
enumerable: false,
|
|
919
|
+
configurable: true,
|
|
920
|
+
writable: true,
|
|
921
|
+
});
|
|
922
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
923
|
+
value: { ...parentMetadata[$fieldIndexesByViewTag] },
|
|
924
|
+
enumerable: false,
|
|
925
|
+
configurable: true,
|
|
926
|
+
writable: true,
|
|
927
|
+
});
|
|
928
|
+
}
|
|
929
|
+
// $refTypeFieldIndexes
|
|
930
|
+
if (parentMetadata[$refTypeFieldIndexes] !== undefined) {
|
|
931
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
932
|
+
value: [...parentMetadata[$refTypeFieldIndexes]],
|
|
933
|
+
enumerable: false,
|
|
934
|
+
configurable: true,
|
|
935
|
+
writable: true,
|
|
936
|
+
});
|
|
937
|
+
}
|
|
938
|
+
// $descriptors
|
|
939
|
+
Object.defineProperty(metadata, $descriptors, {
|
|
940
|
+
value: { ...parentMetadata[$descriptors] },
|
|
941
|
+
enumerable: false,
|
|
942
|
+
configurable: true,
|
|
943
|
+
writable: true,
|
|
944
|
+
});
|
|
945
|
+
}
|
|
946
|
+
}
|
|
947
|
+
constructor[Symbol.metadata] = metadata;
|
|
948
|
+
return metadata;
|
|
949
|
+
},
|
|
950
|
+
isValidInstance(klass) {
|
|
951
|
+
return (klass.constructor[Symbol.metadata] &&
|
|
952
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
953
|
+
},
|
|
954
|
+
getFields(klass) {
|
|
955
|
+
const metadata = klass[Symbol.metadata];
|
|
956
|
+
const fields = {};
|
|
957
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
958
|
+
fields[metadata[i].name] = metadata[i].type;
|
|
959
|
+
}
|
|
960
|
+
return fields;
|
|
961
|
+
}
|
|
962
|
+
};
|
|
963
|
+
|
|
964
|
+
function setOperationAtIndex(changeSet, index) {
|
|
965
|
+
const operationsIndex = changeSet.indexes[index];
|
|
966
|
+
if (operationsIndex === undefined) {
|
|
967
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
968
|
+
}
|
|
969
|
+
else {
|
|
970
|
+
changeSet.operations[operationsIndex] = index;
|
|
971
|
+
}
|
|
1100
972
|
}
|
|
1101
|
-
function
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
973
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
974
|
+
const operationsIndex = changeSet.indexes[index];
|
|
975
|
+
if (operationsIndex !== undefined) {
|
|
976
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
977
|
+
}
|
|
978
|
+
delete changeSet.indexes[index];
|
|
1105
979
|
}
|
|
1106
|
-
function
|
|
1107
|
-
|
|
980
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
981
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
982
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
983
|
+
}
|
|
1108
984
|
}
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
985
|
+
class ChangeTree {
|
|
986
|
+
constructor(ref) {
|
|
987
|
+
/**
|
|
988
|
+
* Whether this structure is parent of a filtered structure.
|
|
989
|
+
*/
|
|
990
|
+
this.isFiltered = false;
|
|
991
|
+
this.indexedOperations = {};
|
|
992
|
+
//
|
|
993
|
+
// TODO:
|
|
994
|
+
// try storing the index + operation per item.
|
|
995
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
996
|
+
//
|
|
997
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
998
|
+
//
|
|
999
|
+
this.changes = { indexes: {}, operations: [] };
|
|
1000
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
1001
|
+
/**
|
|
1002
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
1003
|
+
*/
|
|
1004
|
+
this.isNew = true;
|
|
1005
|
+
this.ref = ref;
|
|
1006
|
+
//
|
|
1007
|
+
// Does this structure have "filters" declared?
|
|
1008
|
+
//
|
|
1009
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1010
|
+
if (metadata?.[$viewFieldIndexes]) {
|
|
1011
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
1012
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
1013
|
+
}
|
|
1115
1014
|
}
|
|
1116
|
-
|
|
1117
|
-
|
|
1015
|
+
setRoot(root) {
|
|
1016
|
+
this.root = root;
|
|
1017
|
+
this.checkIsFiltered(this.parent, this.parentIndex);
|
|
1018
|
+
// Recursively set root on child structures
|
|
1019
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1020
|
+
if (metadata) {
|
|
1021
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
1022
|
+
const field = metadata[index];
|
|
1023
|
+
const value = this.ref[field.name];
|
|
1024
|
+
value?.[$changes].setRoot(root);
|
|
1025
|
+
});
|
|
1026
|
+
}
|
|
1027
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
1028
|
+
// MapSchema / ArraySchema, etc.
|
|
1029
|
+
this.ref.forEach((value, key) => {
|
|
1030
|
+
value[$changes].setRoot(root);
|
|
1031
|
+
});
|
|
1032
|
+
}
|
|
1118
1033
|
}
|
|
1119
|
-
|
|
1120
|
-
|
|
1034
|
+
setParent(parent, root, parentIndex) {
|
|
1035
|
+
this.parent = parent;
|
|
1036
|
+
this.parentIndex = parentIndex;
|
|
1037
|
+
// avoid setting parents with empty `root`
|
|
1038
|
+
if (!root) {
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
// skip if parent is already set
|
|
1042
|
+
if (root !== this.root) {
|
|
1043
|
+
this.root = root;
|
|
1044
|
+
this.checkIsFiltered(parent, parentIndex);
|
|
1045
|
+
}
|
|
1046
|
+
else {
|
|
1047
|
+
root.add(this);
|
|
1048
|
+
}
|
|
1049
|
+
// assign same parent on child structures
|
|
1050
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1051
|
+
if (metadata) {
|
|
1052
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
1053
|
+
const field = metadata[index];
|
|
1054
|
+
const value = this.ref[field.name];
|
|
1055
|
+
value?.[$changes].setParent(this.ref, root, index);
|
|
1056
|
+
});
|
|
1057
|
+
}
|
|
1058
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
1059
|
+
// MapSchema / ArraySchema, etc.
|
|
1060
|
+
this.ref.forEach((value, key) => {
|
|
1061
|
+
value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
|
|
1062
|
+
});
|
|
1063
|
+
}
|
|
1121
1064
|
}
|
|
1122
|
-
|
|
1123
|
-
|
|
1065
|
+
forEachChild(callback) {
|
|
1066
|
+
//
|
|
1067
|
+
// assign same parent on child structures
|
|
1068
|
+
//
|
|
1069
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1070
|
+
if (metadata) {
|
|
1071
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
1072
|
+
const field = metadata[index];
|
|
1073
|
+
const value = this.ref[field.name];
|
|
1074
|
+
if (value) {
|
|
1075
|
+
callback(value[$changes], index);
|
|
1076
|
+
}
|
|
1077
|
+
});
|
|
1078
|
+
}
|
|
1079
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
1080
|
+
// MapSchema / ArraySchema, etc.
|
|
1081
|
+
this.ref.forEach((value, key) => {
|
|
1082
|
+
callback(value[$changes], this.indexes[key] ?? key);
|
|
1083
|
+
});
|
|
1084
|
+
}
|
|
1124
1085
|
}
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
|
|
1130
|
-
// fixstr
|
|
1131
|
-
(prefix < 0xc0 && prefix > 0xa0) ||
|
|
1132
|
-
// str 8
|
|
1133
|
-
prefix === 0xd9 ||
|
|
1134
|
-
// str 16
|
|
1135
|
-
prefix === 0xda ||
|
|
1136
|
-
// str 32
|
|
1137
|
-
prefix === 0xdb);
|
|
1138
|
-
}
|
|
1139
|
-
function number(bytes, it) {
|
|
1140
|
-
const prefix = bytes[it.offset++];
|
|
1141
|
-
if (prefix < 0x80) {
|
|
1142
|
-
// positive fixint
|
|
1143
|
-
return prefix;
|
|
1086
|
+
operation(op) {
|
|
1087
|
+
// operations without index use negative values to represent them
|
|
1088
|
+
// this is checked during .encode() time.
|
|
1089
|
+
this.changes.operations.push(-op);
|
|
1090
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1144
1091
|
}
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1092
|
+
change(index, operation = exports.OPERATION.ADD) {
|
|
1093
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1094
|
+
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
1095
|
+
const changeSet = (isFiltered)
|
|
1096
|
+
? this.filteredChanges
|
|
1097
|
+
: this.changes;
|
|
1098
|
+
const previousOperation = this.indexedOperations[index];
|
|
1099
|
+
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
1100
|
+
const op = (!previousOperation)
|
|
1101
|
+
? operation
|
|
1102
|
+
: (previousOperation === exports.OPERATION.DELETE)
|
|
1103
|
+
? exports.OPERATION.DELETE_AND_ADD
|
|
1104
|
+
: operation;
|
|
1105
|
+
//
|
|
1106
|
+
// TODO: are DELETE operations being encoded as ADD here ??
|
|
1107
|
+
//
|
|
1108
|
+
this.indexedOperations[index] = op;
|
|
1109
|
+
}
|
|
1110
|
+
setOperationAtIndex(changeSet, index);
|
|
1111
|
+
if (isFiltered) {
|
|
1112
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
1113
|
+
if (this.root) {
|
|
1114
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1115
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
1116
|
+
}
|
|
1117
|
+
}
|
|
1118
|
+
else {
|
|
1119
|
+
setOperationAtIndex(this.allChanges, index);
|
|
1120
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1121
|
+
}
|
|
1122
|
+
}
|
|
1123
|
+
shiftChangeIndexes(shiftIndex) {
|
|
1124
|
+
//
|
|
1125
|
+
// Used only during:
|
|
1126
|
+
//
|
|
1127
|
+
// - ArraySchema#unshift()
|
|
1128
|
+
//
|
|
1129
|
+
const changeSet = (this.isFiltered)
|
|
1130
|
+
? this.filteredChanges
|
|
1131
|
+
: this.changes;
|
|
1132
|
+
const newIndexedOperations = {};
|
|
1133
|
+
const newIndexes = {};
|
|
1134
|
+
for (const index in this.indexedOperations) {
|
|
1135
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
1136
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
1137
|
+
}
|
|
1138
|
+
this.indexedOperations = newIndexedOperations;
|
|
1139
|
+
changeSet.indexes = newIndexes;
|
|
1140
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
1141
|
+
}
|
|
1142
|
+
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
1143
|
+
//
|
|
1144
|
+
// Used only during:
|
|
1145
|
+
//
|
|
1146
|
+
// - ArraySchema#splice()
|
|
1147
|
+
//
|
|
1148
|
+
if (this.filteredChanges !== undefined) {
|
|
1149
|
+
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
|
|
1150
|
+
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
1151
|
+
}
|
|
1152
|
+
else {
|
|
1153
|
+
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
1154
|
+
}
|
|
1155
|
+
}
|
|
1156
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
1157
|
+
const newIndexes = {};
|
|
1158
|
+
for (const key in changeSet.indexes) {
|
|
1159
|
+
const index = changeSet.indexes[key];
|
|
1160
|
+
if (index > startIndex) {
|
|
1161
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
1162
|
+
}
|
|
1163
|
+
else {
|
|
1164
|
+
newIndexes[key] = index;
|
|
1165
|
+
}
|
|
1166
|
+
}
|
|
1167
|
+
changeSet.indexes = newIndexes;
|
|
1168
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
1169
|
+
const index = changeSet.operations[i];
|
|
1170
|
+
if (index > startIndex) {
|
|
1171
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
1172
|
+
}
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
indexedOperation(index, operation, allChangesIndex = index) {
|
|
1176
|
+
this.indexedOperations[index] = operation;
|
|
1177
|
+
if (this.filteredChanges !== undefined) {
|
|
1178
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
1179
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
1180
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1181
|
+
}
|
|
1182
|
+
else {
|
|
1183
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
1184
|
+
setOperationAtIndex(this.changes, index);
|
|
1185
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1186
|
+
}
|
|
1187
|
+
}
|
|
1188
|
+
getType(index) {
|
|
1189
|
+
if (Metadata.isValidInstance(this.ref)) {
|
|
1190
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1191
|
+
return metadata[index].type;
|
|
1192
|
+
}
|
|
1193
|
+
else {
|
|
1194
|
+
//
|
|
1195
|
+
// Get the child type from parent structure.
|
|
1196
|
+
// - ["string"] => "string"
|
|
1197
|
+
// - { map: "string" } => "string"
|
|
1198
|
+
// - { set: "string" } => "string"
|
|
1199
|
+
//
|
|
1200
|
+
return this.ref[$childType];
|
|
1201
|
+
}
|
|
1202
|
+
}
|
|
1203
|
+
getChange(index) {
|
|
1204
|
+
return this.indexedOperations[index];
|
|
1205
|
+
}
|
|
1206
|
+
//
|
|
1207
|
+
// used during `.encode()`
|
|
1208
|
+
//
|
|
1209
|
+
getValue(index, isEncodeAll = false) {
|
|
1210
|
+
//
|
|
1211
|
+
// `isEncodeAll` param is only used by ArraySchema
|
|
1212
|
+
//
|
|
1213
|
+
return this.ref[$getByIndex](index, isEncodeAll);
|
|
1214
|
+
}
|
|
1215
|
+
delete(index, operation, allChangesIndex = index) {
|
|
1216
|
+
if (index === undefined) {
|
|
1217
|
+
try {
|
|
1218
|
+
throw new Error(`@colyseus/schema ${this.ref.constructor.name}: trying to delete non-existing index '${index}'`);
|
|
1219
|
+
}
|
|
1220
|
+
catch (e) {
|
|
1221
|
+
console.warn(e);
|
|
1222
|
+
}
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
const changeSet = (this.filteredChanges !== undefined)
|
|
1226
|
+
? this.filteredChanges
|
|
1227
|
+
: this.changes;
|
|
1228
|
+
this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
|
|
1229
|
+
setOperationAtIndex(changeSet, index);
|
|
1230
|
+
const previousValue = this.getValue(index);
|
|
1231
|
+
// remove `root` reference
|
|
1232
|
+
if (previousValue && previousValue[$changes]) {
|
|
1233
|
+
//
|
|
1234
|
+
// FIXME: this.root is "undefined"
|
|
1235
|
+
//
|
|
1236
|
+
// This method is being called at decoding time when a DELETE operation is found.
|
|
1237
|
+
//
|
|
1238
|
+
// - This is due to using the concrete Schema class at decoding time.
|
|
1239
|
+
// - "Reflected" structures do not have this problem.
|
|
1240
|
+
//
|
|
1241
|
+
// (The property descriptors should NOT be used at decoding time. only at encoding time.)
|
|
1242
|
+
//
|
|
1243
|
+
this.root?.remove(previousValue[$changes]);
|
|
1244
|
+
}
|
|
1245
|
+
//
|
|
1246
|
+
// FIXME: this is looking a ugly and repeated
|
|
1247
|
+
//
|
|
1248
|
+
if (this.filteredChanges !== undefined) {
|
|
1249
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
1250
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1251
|
+
}
|
|
1252
|
+
else {
|
|
1253
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
1254
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1255
|
+
}
|
|
1256
|
+
}
|
|
1257
|
+
endEncode() {
|
|
1258
|
+
this.indexedOperations = {};
|
|
1259
|
+
// // clear changes
|
|
1260
|
+
// this.changes.indexes = {};
|
|
1261
|
+
// this.changes.operations.length = 0;
|
|
1262
|
+
// ArraySchema and MapSchema have a custom "encode end" method
|
|
1263
|
+
this.ref[$onEncodeEnd]?.();
|
|
1264
|
+
// Not a new instance anymore
|
|
1265
|
+
this.isNew = false;
|
|
1266
|
+
}
|
|
1267
|
+
discard(discardAll = false) {
|
|
1268
|
+
//
|
|
1269
|
+
// > MapSchema:
|
|
1270
|
+
// Remove cached key to ensure ADD operations is unsed instead of
|
|
1271
|
+
// REPLACE in case same key is used on next patches.
|
|
1272
|
+
//
|
|
1273
|
+
this.ref[$onEncodeEnd]?.();
|
|
1274
|
+
this.indexedOperations = {};
|
|
1275
|
+
this.changes.indexes = {};
|
|
1276
|
+
this.changes.operations.length = 0;
|
|
1277
|
+
this.changes.queueRootIndex = undefined;
|
|
1278
|
+
if (this.filteredChanges !== undefined) {
|
|
1279
|
+
this.filteredChanges.indexes = {};
|
|
1280
|
+
this.filteredChanges.operations.length = 0;
|
|
1281
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
1282
|
+
}
|
|
1283
|
+
if (discardAll) {
|
|
1284
|
+
this.allChanges.indexes = {};
|
|
1285
|
+
this.allChanges.operations.length = 0;
|
|
1286
|
+
if (this.allFilteredChanges !== undefined) {
|
|
1287
|
+
this.allFilteredChanges.indexes = {};
|
|
1288
|
+
this.allFilteredChanges.operations.length = 0;
|
|
1289
|
+
}
|
|
1290
|
+
// remove children references
|
|
1291
|
+
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
1292
|
+
}
|
|
1293
|
+
}
|
|
1294
|
+
/**
|
|
1295
|
+
* Recursively discard all changes from this, and child structures.
|
|
1296
|
+
*/
|
|
1297
|
+
discardAll() {
|
|
1298
|
+
const keys = Object.keys(this.indexedOperations);
|
|
1299
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
1300
|
+
const value = this.getValue(Number(keys[i]));
|
|
1301
|
+
if (value && value[$changes]) {
|
|
1302
|
+
value[$changes].discardAll();
|
|
1303
|
+
}
|
|
1304
|
+
}
|
|
1305
|
+
this.discard();
|
|
1306
|
+
}
|
|
1307
|
+
ensureRefId() {
|
|
1308
|
+
// skip if refId is already set.
|
|
1309
|
+
if (this.refId !== undefined) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
this.refId = this.root.getNextUniqueId();
|
|
1313
|
+
}
|
|
1314
|
+
get changed() {
|
|
1315
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
1316
|
+
}
|
|
1317
|
+
checkIsFiltered(parent, parentIndex) {
|
|
1318
|
+
const isNewChangeTree = this.root.add(this);
|
|
1319
|
+
if (this.root.types.hasFilters) {
|
|
1320
|
+
//
|
|
1321
|
+
// At Schema initialization, the "root" structure might not be available
|
|
1322
|
+
// yet, as it only does once the "Encoder" has been set up.
|
|
1323
|
+
//
|
|
1324
|
+
// So the "parent" may be already set without a "root".
|
|
1325
|
+
//
|
|
1326
|
+
this._checkFilteredByParent(parent, parentIndex);
|
|
1327
|
+
if (this.filteredChanges !== undefined) {
|
|
1328
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1329
|
+
if (isNewChangeTree) {
|
|
1330
|
+
this.root.allFilteredChanges.push(this);
|
|
1331
|
+
}
|
|
1332
|
+
}
|
|
1333
|
+
}
|
|
1334
|
+
if (!this.isFiltered) {
|
|
1335
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1336
|
+
if (isNewChangeTree) {
|
|
1337
|
+
this.root.allChanges.push(this);
|
|
1338
|
+
}
|
|
1339
|
+
}
|
|
1340
|
+
}
|
|
1341
|
+
_checkFilteredByParent(parent, parentIndex) {
|
|
1342
|
+
// skip if parent is not set
|
|
1343
|
+
if (!parent) {
|
|
1344
|
+
return;
|
|
1345
|
+
}
|
|
1346
|
+
//
|
|
1347
|
+
// ArraySchema | MapSchema - get the child type
|
|
1348
|
+
// (if refType is typeof string, the parentFiltered[key] below will always be invalid)
|
|
1349
|
+
//
|
|
1350
|
+
const refType = Metadata.isValidInstance(this.ref)
|
|
1351
|
+
? this.ref.constructor
|
|
1352
|
+
: this.ref[$childType];
|
|
1353
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
1354
|
+
const parentChangeTree = parent[$changes];
|
|
1355
|
+
parent = parentChangeTree.parent;
|
|
1356
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
1357
|
+
}
|
|
1358
|
+
const parentConstructor = parent.constructor;
|
|
1359
|
+
let key = `${this.root.types.getTypeId(refType)}`;
|
|
1360
|
+
if (parentConstructor) {
|
|
1361
|
+
key += `-${this.root.types.schemas.get(parentConstructor)}`;
|
|
1362
|
+
}
|
|
1363
|
+
key += `-${parentIndex}`;
|
|
1364
|
+
this.isFiltered = parent[$changes].isFiltered // in case parent is already filtered
|
|
1365
|
+
|| this.root.types.parentFiltered[key];
|
|
1366
|
+
// const parentMetadata = parentConstructor?.[Symbol.metadata];
|
|
1367
|
+
// this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex) || this.root.types.parentFiltered[key];
|
|
1368
|
+
//
|
|
1369
|
+
// TODO: refactor this!
|
|
1370
|
+
//
|
|
1371
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
1372
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
1373
|
+
// (this happens when instance is detached from root or parent)
|
|
1374
|
+
//
|
|
1375
|
+
if (this.isFiltered) {
|
|
1376
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
1377
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
1378
|
+
if (this.changes.operations.length > 0) {
|
|
1379
|
+
// swap changes reference
|
|
1380
|
+
const changes = this.changes;
|
|
1381
|
+
this.changes = this.filteredChanges;
|
|
1382
|
+
this.filteredChanges = changes;
|
|
1383
|
+
// swap "all changes" reference
|
|
1384
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
1385
|
+
this.allFilteredChanges = this.allChanges;
|
|
1386
|
+
this.allChanges = allFilteredChanges;
|
|
1387
|
+
}
|
|
1388
|
+
}
|
|
1389
|
+
}
|
|
1390
|
+
}
|
|
1391
|
+
|
|
1392
|
+
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
1393
|
+
if (typeof (type) === "string") {
|
|
1394
|
+
encode[type]?.(bytes, value, it);
|
|
1148
1395
|
}
|
|
1149
|
-
else if (
|
|
1150
|
-
//
|
|
1151
|
-
|
|
1396
|
+
else if (type[Symbol.metadata] !== undefined) {
|
|
1397
|
+
//
|
|
1398
|
+
// Encode refId for this instance.
|
|
1399
|
+
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1400
|
+
//
|
|
1401
|
+
encode.number(bytes, value[$changes].refId, it);
|
|
1402
|
+
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
1403
|
+
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
1404
|
+
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
1405
|
+
}
|
|
1152
1406
|
}
|
|
1153
|
-
else
|
|
1154
|
-
//
|
|
1155
|
-
|
|
1407
|
+
else {
|
|
1408
|
+
//
|
|
1409
|
+
// Encode refId for this instance.
|
|
1410
|
+
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1411
|
+
//
|
|
1412
|
+
encode.number(bytes, value[$changes].refId, it);
|
|
1156
1413
|
}
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1414
|
+
}
|
|
1415
|
+
/**
|
|
1416
|
+
* Used for Schema instances.
|
|
1417
|
+
* @private
|
|
1418
|
+
*/
|
|
1419
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
1420
|
+
// "compress" field index + operation
|
|
1421
|
+
bytes[it.offset++] = (index | operation) & 255;
|
|
1422
|
+
// Do not encode value for DELETE operations
|
|
1423
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1424
|
+
return;
|
|
1160
1425
|
}
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1426
|
+
const ref = changeTree.ref;
|
|
1427
|
+
const field = metadata[index];
|
|
1428
|
+
// TODO: inline this function call small performance gain
|
|
1429
|
+
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
1430
|
+
};
|
|
1431
|
+
/**
|
|
1432
|
+
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
1433
|
+
* @private
|
|
1434
|
+
*/
|
|
1435
|
+
const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
1436
|
+
// encode operation
|
|
1437
|
+
bytes[it.offset++] = operation & 255;
|
|
1438
|
+
// custom operations
|
|
1439
|
+
if (operation === exports.OPERATION.CLEAR) {
|
|
1440
|
+
return;
|
|
1164
1441
|
}
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1442
|
+
// encode index
|
|
1443
|
+
encode.number(bytes, index, it);
|
|
1444
|
+
// Do not encode value for DELETE operations
|
|
1445
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1446
|
+
return;
|
|
1168
1447
|
}
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1448
|
+
const ref = changeTree.ref;
|
|
1449
|
+
//
|
|
1450
|
+
// encode "alias" for dynamic fields (maps)
|
|
1451
|
+
//
|
|
1452
|
+
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
1453
|
+
if (typeof (ref['set']) === "function") {
|
|
1454
|
+
//
|
|
1455
|
+
// MapSchema dynamic key
|
|
1456
|
+
//
|
|
1457
|
+
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
1458
|
+
encode.string(bytes, dynamicIndex, it);
|
|
1459
|
+
}
|
|
1172
1460
|
}
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1461
|
+
const type = ref[$childType];
|
|
1462
|
+
const value = ref[$getByIndex](index);
|
|
1463
|
+
// try { throw new Error(); } catch (e) {
|
|
1464
|
+
// // only print if not coming from Reflection.ts
|
|
1465
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
1466
|
+
// console.log("encodeKeyValueOperation -> ", {
|
|
1467
|
+
// ref: changeTree.ref.constructor.name,
|
|
1468
|
+
// field,
|
|
1469
|
+
// operation: OPERATION[operation],
|
|
1470
|
+
// value: value?.toJSON(),
|
|
1471
|
+
// items: ref.toJSON(),
|
|
1472
|
+
// });
|
|
1473
|
+
// }
|
|
1474
|
+
// }
|
|
1475
|
+
// TODO: inline this function call small performance gain
|
|
1476
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
1477
|
+
};
|
|
1478
|
+
/**
|
|
1479
|
+
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
1480
|
+
* @private
|
|
1481
|
+
*/
|
|
1482
|
+
const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
|
|
1483
|
+
const ref = changeTree.ref;
|
|
1484
|
+
const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
|
|
1485
|
+
let refOrIndex;
|
|
1486
|
+
if (useOperationByRefId) {
|
|
1487
|
+
refOrIndex = ref['tmpItems'][field][$changes].refId;
|
|
1488
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1489
|
+
operation = exports.OPERATION.DELETE_BY_REFID;
|
|
1490
|
+
}
|
|
1491
|
+
else if (operation === exports.OPERATION.ADD) {
|
|
1492
|
+
operation = exports.OPERATION.ADD_BY_REFID;
|
|
1493
|
+
}
|
|
1176
1494
|
}
|
|
1177
|
-
else
|
|
1178
|
-
|
|
1179
|
-
return int32(bytes, it);
|
|
1495
|
+
else {
|
|
1496
|
+
refOrIndex = field;
|
|
1180
1497
|
}
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1498
|
+
// encode operation
|
|
1499
|
+
bytes[it.offset++] = operation & 255;
|
|
1500
|
+
// custom operations
|
|
1501
|
+
if (operation === exports.OPERATION.CLEAR ||
|
|
1502
|
+
operation === exports.OPERATION.REVERSE) {
|
|
1503
|
+
return;
|
|
1184
1504
|
}
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1505
|
+
// encode index
|
|
1506
|
+
encode.number(bytes, refOrIndex, it);
|
|
1507
|
+
// Do not encode value for DELETE operations
|
|
1508
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1509
|
+
return;
|
|
1188
1510
|
}
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
//
|
|
1193
|
-
//
|
|
1194
|
-
//
|
|
1195
|
-
//
|
|
1196
|
-
//
|
|
1197
|
-
//
|
|
1198
|
-
//
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
// int 32 - 0xd2
|
|
1202
|
-
// int 64 - 0xd3
|
|
1203
|
-
return (prefix < 0x80 ||
|
|
1204
|
-
(prefix >= 0xca && prefix <= 0xd3));
|
|
1205
|
-
}
|
|
1206
|
-
function arrayCheck(bytes, it) {
|
|
1207
|
-
return bytes[it.offset] < 0xa0;
|
|
1208
|
-
// const prefix = bytes[it.offset] ;
|
|
1209
|
-
// if (prefix < 0xa0) {
|
|
1210
|
-
// return prefix;
|
|
1211
|
-
// // array
|
|
1212
|
-
// } else if (prefix === 0xdc) {
|
|
1213
|
-
// it.offset += 2;
|
|
1214
|
-
// } else if (0xdd) {
|
|
1215
|
-
// it.offset += 4;
|
|
1216
|
-
// }
|
|
1217
|
-
// return prefix;
|
|
1218
|
-
}
|
|
1219
|
-
function switchStructureCheck(bytes, it) {
|
|
1220
|
-
return (
|
|
1221
|
-
// previous byte should be `SWITCH_TO_STRUCTURE`
|
|
1222
|
-
bytes[it.offset - 1] === SWITCH_TO_STRUCTURE &&
|
|
1223
|
-
// next byte should be a number
|
|
1224
|
-
(bytes[it.offset] < 0x80 || (bytes[it.offset] >= 0xca && bytes[it.offset] <= 0xd3)));
|
|
1225
|
-
}
|
|
1226
|
-
|
|
1227
|
-
var decode = /*#__PURE__*/Object.freeze({
|
|
1228
|
-
__proto__: null,
|
|
1229
|
-
utf8Read: utf8Read,
|
|
1230
|
-
int8: int8,
|
|
1231
|
-
uint8: uint8,
|
|
1232
|
-
int16: int16,
|
|
1233
|
-
uint16: uint16,
|
|
1234
|
-
int32: int32,
|
|
1235
|
-
uint32: uint32,
|
|
1236
|
-
float32: float32,
|
|
1237
|
-
float64: float64,
|
|
1238
|
-
int64: int64,
|
|
1239
|
-
uint64: uint64,
|
|
1240
|
-
readFloat32: readFloat32,
|
|
1241
|
-
readFloat64: readFloat64,
|
|
1242
|
-
boolean: boolean,
|
|
1243
|
-
string: string,
|
|
1244
|
-
stringCheck: stringCheck,
|
|
1245
|
-
number: number,
|
|
1246
|
-
numberCheck: numberCheck,
|
|
1247
|
-
arrayCheck: arrayCheck,
|
|
1248
|
-
switchStructureCheck: switchStructureCheck
|
|
1249
|
-
});
|
|
1511
|
+
const type = changeTree.getType(field);
|
|
1512
|
+
const value = changeTree.getValue(field, isEncodeAll);
|
|
1513
|
+
// console.log("encodeArray -> ", {
|
|
1514
|
+
// ref: changeTree.ref.constructor.name,
|
|
1515
|
+
// field,
|
|
1516
|
+
// operation: OPERATION[operation],
|
|
1517
|
+
// value: value?.toJSON(),
|
|
1518
|
+
// items: ref.toJSON(),
|
|
1519
|
+
// });
|
|
1520
|
+
// TODO: inline this function call small performance gain
|
|
1521
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
1522
|
+
};
|
|
1250
1523
|
|
|
1251
1524
|
const DEFINITION_MISMATCH = -1;
|
|
1252
1525
|
function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
|
|
@@ -1282,7 +1555,7 @@
|
|
|
1282
1555
|
}
|
|
1283
1556
|
if (operation === exports.OPERATION.DELETE) ;
|
|
1284
1557
|
else if (Schema.is(type)) {
|
|
1285
|
-
const refId = number(bytes, it);
|
|
1558
|
+
const refId = decode.number(bytes, it);
|
|
1286
1559
|
value = $root.refs.get(refId);
|
|
1287
1560
|
if (previousValue) {
|
|
1288
1561
|
const previousRefId = $root.refIds.get(previousValue);
|
|
@@ -1298,7 +1571,9 @@
|
|
|
1298
1571
|
if (!value) {
|
|
1299
1572
|
value = decoder.createInstanceOfType(childType);
|
|
1300
1573
|
}
|
|
1301
|
-
$root.addRef(refId, value, (value !== previousValue
|
|
1574
|
+
$root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed
|
|
1575
|
+
(operation === exports.OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
|
|
1576
|
+
));
|
|
1302
1577
|
}
|
|
1303
1578
|
}
|
|
1304
1579
|
else if (typeof (type) === "string") {
|
|
@@ -1309,7 +1584,7 @@
|
|
|
1309
1584
|
}
|
|
1310
1585
|
else {
|
|
1311
1586
|
const typeDef = getType(Object.keys(type)[0]);
|
|
1312
|
-
const refId = number(bytes, it);
|
|
1587
|
+
const refId = decode.number(bytes, it);
|
|
1313
1588
|
const valueRef = ($root.refs.has(refId))
|
|
1314
1589
|
? previousValue || $root.refs.get(refId)
|
|
1315
1590
|
: new typeDef.constructor();
|
|
@@ -1349,18 +1624,19 @@
|
|
|
1349
1624
|
}
|
|
1350
1625
|
const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
1351
1626
|
const first_byte = bytes[it.offset++];
|
|
1352
|
-
const metadata = ref
|
|
1627
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1353
1628
|
// "compressed" index + operation
|
|
1354
1629
|
const operation = (first_byte >> 6) << 6;
|
|
1355
1630
|
const index = first_byte % (operation || 255);
|
|
1356
1631
|
// skip early if field is not defined
|
|
1357
1632
|
const field = metadata[index];
|
|
1358
1633
|
if (field === undefined) {
|
|
1634
|
+
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
1359
1635
|
return DEFINITION_MISMATCH;
|
|
1360
1636
|
}
|
|
1361
|
-
const { value, previousValue } = decodeValue(decoder, operation, ref, index,
|
|
1637
|
+
const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
|
|
1362
1638
|
if (value !== null && value !== undefined) {
|
|
1363
|
-
ref[field] = value;
|
|
1639
|
+
ref[field.name] = value;
|
|
1364
1640
|
}
|
|
1365
1641
|
// add change
|
|
1366
1642
|
if (previousValue !== value) {
|
|
@@ -1368,7 +1644,7 @@
|
|
|
1368
1644
|
ref,
|
|
1369
1645
|
refId: decoder.currentRefId,
|
|
1370
1646
|
op: operation,
|
|
1371
|
-
field: field,
|
|
1647
|
+
field: field.name,
|
|
1372
1648
|
value,
|
|
1373
1649
|
previousValue,
|
|
1374
1650
|
});
|
|
@@ -1387,12 +1663,12 @@
|
|
|
1387
1663
|
ref.clear();
|
|
1388
1664
|
return;
|
|
1389
1665
|
}
|
|
1390
|
-
const index = number(bytes, it);
|
|
1666
|
+
const index = decode.number(bytes, it);
|
|
1391
1667
|
const type = ref[$childType];
|
|
1392
1668
|
let dynamicIndex;
|
|
1393
1669
|
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
1394
1670
|
if (typeof (ref['set']) === "function") {
|
|
1395
|
-
dynamicIndex = string(bytes, it); // MapSchema
|
|
1671
|
+
dynamicIndex = decode.string(bytes, it); // MapSchema
|
|
1396
1672
|
ref['setIndex'](index, dynamicIndex);
|
|
1397
1673
|
}
|
|
1398
1674
|
else {
|
|
@@ -1436,7 +1712,8 @@
|
|
|
1436
1712
|
};
|
|
1437
1713
|
const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
1438
1714
|
// "uncompressed" index + operation (array/map items)
|
|
1439
|
-
|
|
1715
|
+
let operation = bytes[it.offset++];
|
|
1716
|
+
let index;
|
|
1440
1717
|
if (operation === exports.OPERATION.CLEAR) {
|
|
1441
1718
|
//
|
|
1442
1719
|
// When decoding:
|
|
@@ -1447,11 +1724,15 @@
|
|
|
1447
1724
|
ref.clear();
|
|
1448
1725
|
return;
|
|
1449
1726
|
}
|
|
1727
|
+
else if (operation === exports.OPERATION.REVERSE) {
|
|
1728
|
+
ref.reverse();
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1450
1731
|
else if (operation === exports.OPERATION.DELETE_BY_REFID) {
|
|
1451
1732
|
// TODO: refactor here, try to follow same flow as below
|
|
1452
|
-
const refId = number(bytes, it);
|
|
1733
|
+
const refId = decode.number(bytes, it);
|
|
1453
1734
|
const previousValue = decoder.root.refs.get(refId);
|
|
1454
|
-
|
|
1735
|
+
index = ref.findIndex((value) => value === previousValue);
|
|
1455
1736
|
ref[$deleteByIndex](index);
|
|
1456
1737
|
allChanges.push({
|
|
1457
1738
|
ref,
|
|
@@ -1464,7 +1745,17 @@
|
|
|
1464
1745
|
});
|
|
1465
1746
|
return;
|
|
1466
1747
|
}
|
|
1467
|
-
|
|
1748
|
+
else if (operation === exports.OPERATION.ADD_BY_REFID) {
|
|
1749
|
+
const refId = decode.number(bytes, it);
|
|
1750
|
+
const itemByRefId = decoder.root.refs.get(refId);
|
|
1751
|
+
// use existing index, or push new value
|
|
1752
|
+
index = (itemByRefId)
|
|
1753
|
+
? ref.findIndex((value) => value === itemByRefId)
|
|
1754
|
+
: ref.length;
|
|
1755
|
+
}
|
|
1756
|
+
else {
|
|
1757
|
+
index = decode.number(bytes, it);
|
|
1758
|
+
}
|
|
1468
1759
|
const type = ref[$childType];
|
|
1469
1760
|
let dynamicIndex = index;
|
|
1470
1761
|
const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
|
|
@@ -1488,6 +1779,55 @@
|
|
|
1488
1779
|
}
|
|
1489
1780
|
};
|
|
1490
1781
|
|
|
1782
|
+
class EncodeSchemaError extends Error {
|
|
1783
|
+
}
|
|
1784
|
+
function assertType(value, type, klass, field) {
|
|
1785
|
+
let typeofTarget;
|
|
1786
|
+
let allowNull = false;
|
|
1787
|
+
switch (type) {
|
|
1788
|
+
case "number":
|
|
1789
|
+
case "int8":
|
|
1790
|
+
case "uint8":
|
|
1791
|
+
case "int16":
|
|
1792
|
+
case "uint16":
|
|
1793
|
+
case "int32":
|
|
1794
|
+
case "uint32":
|
|
1795
|
+
case "int64":
|
|
1796
|
+
case "uint64":
|
|
1797
|
+
case "float32":
|
|
1798
|
+
case "float64":
|
|
1799
|
+
typeofTarget = "number";
|
|
1800
|
+
if (isNaN(value)) {
|
|
1801
|
+
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
1802
|
+
}
|
|
1803
|
+
break;
|
|
1804
|
+
case "bigint64":
|
|
1805
|
+
case "biguint64":
|
|
1806
|
+
typeofTarget = "bigint";
|
|
1807
|
+
break;
|
|
1808
|
+
case "string":
|
|
1809
|
+
typeofTarget = "string";
|
|
1810
|
+
allowNull = true;
|
|
1811
|
+
break;
|
|
1812
|
+
case "boolean":
|
|
1813
|
+
// boolean is always encoded as true/false based on truthiness
|
|
1814
|
+
return;
|
|
1815
|
+
default:
|
|
1816
|
+
// skip assertion for custom types
|
|
1817
|
+
// TODO: allow custom types to define their own assertions
|
|
1818
|
+
return;
|
|
1819
|
+
}
|
|
1820
|
+
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
1821
|
+
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
1822
|
+
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
1823
|
+
}
|
|
1824
|
+
}
|
|
1825
|
+
function assertInstanceType(value, type, instance, field) {
|
|
1826
|
+
if (!(value instanceof type)) {
|
|
1827
|
+
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
|
|
1828
|
+
}
|
|
1829
|
+
}
|
|
1830
|
+
|
|
1491
1831
|
var _a$4, _b$4;
|
|
1492
1832
|
const DEFAULT_SORT = (a, b) => {
|
|
1493
1833
|
const A = a.toString();
|
|
@@ -1538,6 +1878,7 @@
|
|
|
1538
1878
|
const proxy = new Proxy(this, {
|
|
1539
1879
|
get: (obj, prop) => {
|
|
1540
1880
|
if (typeof (prop) !== "symbol" &&
|
|
1881
|
+
// FIXME: d8 accuses this as low performance
|
|
1541
1882
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1542
1883
|
) {
|
|
1543
1884
|
return this.items[prop];
|
|
@@ -1553,8 +1894,9 @@
|
|
|
1553
1894
|
}
|
|
1554
1895
|
else {
|
|
1555
1896
|
if (setValue[$changes]) {
|
|
1897
|
+
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1556
1898
|
if (obj.items[key] !== undefined) {
|
|
1557
|
-
if (setValue[$changes]
|
|
1899
|
+
if (setValue[$changes].isNew) {
|
|
1558
1900
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
1559
1901
|
}
|
|
1560
1902
|
else {
|
|
@@ -1566,7 +1908,7 @@
|
|
|
1566
1908
|
}
|
|
1567
1909
|
}
|
|
1568
1910
|
}
|
|
1569
|
-
else if (setValue[$changes]
|
|
1911
|
+
else if (setValue[$changes].isNew) {
|
|
1570
1912
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
|
|
1571
1913
|
}
|
|
1572
1914
|
}
|
|
@@ -1599,7 +1941,10 @@
|
|
|
1599
1941
|
}
|
|
1600
1942
|
});
|
|
1601
1943
|
this[$changes] = new ChangeTree(proxy);
|
|
1602
|
-
this.
|
|
1944
|
+
this[$changes].indexes = {};
|
|
1945
|
+
if (items.length > 0) {
|
|
1946
|
+
this.push(...items);
|
|
1947
|
+
}
|
|
1603
1948
|
return proxy;
|
|
1604
1949
|
}
|
|
1605
1950
|
set length(newLength) {
|
|
@@ -1618,14 +1963,19 @@
|
|
|
1618
1963
|
}
|
|
1619
1964
|
push(...values) {
|
|
1620
1965
|
let length = this.tmpItems.length;
|
|
1621
|
-
|
|
1622
|
-
|
|
1966
|
+
const changeTree = this[$changes];
|
|
1967
|
+
// values.forEach((value, i) => {
|
|
1968
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1969
|
+
const value = values[i];
|
|
1623
1970
|
if (value === undefined || value === null) {
|
|
1971
|
+
// skip null values
|
|
1624
1972
|
return;
|
|
1625
1973
|
}
|
|
1626
|
-
|
|
1974
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
1975
|
+
assertInstanceType(value, this[$childType], this, i);
|
|
1976
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1977
|
+
}
|
|
1627
1978
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1628
|
-
// changeTree.indexes[length] = length;
|
|
1629
1979
|
this.items.push(value);
|
|
1630
1980
|
this.tmpItems.push(value);
|
|
1631
1981
|
//
|
|
@@ -1633,8 +1983,9 @@
|
|
|
1633
1983
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1634
1984
|
//
|
|
1635
1985
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1636
|
-
|
|
1637
|
-
|
|
1986
|
+
}
|
|
1987
|
+
// length++;
|
|
1988
|
+
// });
|
|
1638
1989
|
return length;
|
|
1639
1990
|
}
|
|
1640
1991
|
/**
|
|
@@ -1655,6 +2006,7 @@
|
|
|
1655
2006
|
}
|
|
1656
2007
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1657
2008
|
// this.tmpItems[index] = undefined;
|
|
2009
|
+
// this.tmpItems.pop();
|
|
1658
2010
|
this.deletedIndexes[index] = true;
|
|
1659
2011
|
return this.items.pop();
|
|
1660
2012
|
}
|
|
@@ -1719,9 +2071,12 @@
|
|
|
1719
2071
|
//
|
|
1720
2072
|
// TODO: do not use [$changes] at decoding time.
|
|
1721
2073
|
//
|
|
1722
|
-
changeTree.root
|
|
1723
|
-
|
|
1724
|
-
|
|
2074
|
+
const root = changeTree.root;
|
|
2075
|
+
if (root !== undefined) {
|
|
2076
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
2077
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
2078
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
2079
|
+
}
|
|
1725
2080
|
});
|
|
1726
2081
|
changeTree.discard(true);
|
|
1727
2082
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -1765,6 +2120,7 @@
|
|
|
1765
2120
|
const changeTree = this[$changes];
|
|
1766
2121
|
changeTree.delete(index);
|
|
1767
2122
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
2123
|
+
// this.deletedIndexes[index] = true;
|
|
1768
2124
|
return this.items.shift();
|
|
1769
2125
|
}
|
|
1770
2126
|
/**
|
|
@@ -1845,10 +2201,12 @@
|
|
|
1845
2201
|
changeTree.shiftChangeIndexes(items.length);
|
|
1846
2202
|
// new index
|
|
1847
2203
|
if (changeTree.isFiltered) {
|
|
1848
|
-
changeTree.filteredChanges
|
|
2204
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
2205
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1849
2206
|
}
|
|
1850
2207
|
else {
|
|
1851
|
-
changeTree.allChanges
|
|
2208
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2209
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1852
2210
|
}
|
|
1853
2211
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1854
2212
|
items.forEach((_, index) => {
|
|
@@ -1873,14 +2231,6 @@
|
|
|
1873
2231
|
lastIndexOf(searchElement, fromIndex = this.length - 1) {
|
|
1874
2232
|
return this.items.lastIndexOf(searchElement, fromIndex);
|
|
1875
2233
|
}
|
|
1876
|
-
/**
|
|
1877
|
-
* Determines whether all the members of an array satisfy the specified test.
|
|
1878
|
-
* @param callbackfn A function that accepts up to three arguments. The every method calls
|
|
1879
|
-
* the callbackfn function for each element in the array until the callbackfn returns a value
|
|
1880
|
-
* which is coercible to the Boolean value false, or until the end of the array.
|
|
1881
|
-
* @param thisArg An object to which the this keyword can refer in the callbackfn function.
|
|
1882
|
-
* If thisArg is omitted, undefined is used as the this value.
|
|
1883
|
-
*/
|
|
1884
2234
|
every(callbackfn, thisArg) {
|
|
1885
2235
|
return this.items.every(callbackfn, thisArg);
|
|
1886
2236
|
}
|
|
@@ -2159,6 +2509,7 @@
|
|
|
2159
2509
|
this.$items = new Map();
|
|
2160
2510
|
this.$indexes = new Map();
|
|
2161
2511
|
this[$changes] = new ChangeTree(this);
|
|
2512
|
+
this[$changes].indexes = {};
|
|
2162
2513
|
if (initialValues) {
|
|
2163
2514
|
if (initialValues instanceof Map ||
|
|
2164
2515
|
initialValues instanceof MapSchema) {
|
|
@@ -2185,6 +2536,9 @@
|
|
|
2185
2536
|
if (value === undefined || value === null) {
|
|
2186
2537
|
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
|
|
2187
2538
|
}
|
|
2539
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
2540
|
+
assertInstanceType(value, this[$childType], this, key);
|
|
2541
|
+
}
|
|
2188
2542
|
// Force "key" as string
|
|
2189
2543
|
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
|
|
2190
2544
|
key = key.toString();
|
|
@@ -2193,7 +2547,7 @@
|
|
|
2193
2547
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2194
2548
|
const index = (isReplace)
|
|
2195
2549
|
? changeTree.indexes[key]
|
|
2196
|
-
: changeTree.indexes[
|
|
2550
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2197
2551
|
let operation = (isReplace)
|
|
2198
2552
|
? exports.OPERATION.REPLACE
|
|
2199
2553
|
: exports.OPERATION.ADD;
|
|
@@ -2205,7 +2559,7 @@
|
|
|
2205
2559
|
if (!isReplace) {
|
|
2206
2560
|
this.$indexes.set(index, key);
|
|
2207
2561
|
changeTree.indexes[key] = index;
|
|
2208
|
-
changeTree.indexes[
|
|
2562
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2209
2563
|
}
|
|
2210
2564
|
else if (!isRef &&
|
|
2211
2565
|
this.$items.get(key) === value) {
|
|
@@ -2280,8 +2634,11 @@
|
|
|
2280
2634
|
}
|
|
2281
2635
|
[$onEncodeEnd]() {
|
|
2282
2636
|
const changeTree = this[$changes];
|
|
2283
|
-
const
|
|
2284
|
-
for (
|
|
2637
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2638
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2639
|
+
const key = keys[i];
|
|
2640
|
+
const fieldIndex = Number(key);
|
|
2641
|
+
const operation = changeTree.indexedOperations[key];
|
|
2285
2642
|
if (operation === exports.OPERATION.DELETE) {
|
|
2286
2643
|
const index = this[$getByIndex](fieldIndex);
|
|
2287
2644
|
delete changeTree.indexes[index];
|
|
@@ -2310,108 +2667,21 @@
|
|
|
2310
2667
|
else {
|
|
2311
2668
|
// server-side
|
|
2312
2669
|
cloned = new MapSchema();
|
|
2313
|
-
this.forEach((value, key) => {
|
|
2314
|
-
if (value[$changes]) {
|
|
2315
|
-
cloned.set(key, value['clone']());
|
|
2316
|
-
}
|
|
2317
|
-
else {
|
|
2318
|
-
cloned.set(key, value);
|
|
2319
|
-
}
|
|
2320
|
-
});
|
|
2321
|
-
}
|
|
2322
|
-
return cloned;
|
|
2323
|
-
}
|
|
2324
|
-
}
|
|
2325
|
-
registerType("map", { constructor: MapSchema });
|
|
2326
|
-
|
|
2327
|
-
const DEFAULT_VIEW_TAG = -1;
|
|
2328
|
-
class TypeContext {
|
|
2329
|
-
/**
|
|
2330
|
-
* For inheritance support
|
|
2331
|
-
* Keeps track of which classes extends which. (parent -> children)
|
|
2332
|
-
*/
|
|
2333
|
-
static { this.inheritedTypes = new Map(); }
|
|
2334
|
-
static register(target) {
|
|
2335
|
-
const parent = Object.getPrototypeOf(target);
|
|
2336
|
-
if (parent !== Schema) {
|
|
2337
|
-
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
2338
|
-
if (!inherits) {
|
|
2339
|
-
inherits = new Set();
|
|
2340
|
-
TypeContext.inheritedTypes.set(parent, inherits);
|
|
2341
|
-
}
|
|
2342
|
-
inherits.add(target);
|
|
2343
|
-
}
|
|
2344
|
-
}
|
|
2345
|
-
constructor(rootClass) {
|
|
2346
|
-
this.types = {};
|
|
2347
|
-
this.schemas = new Map();
|
|
2348
|
-
this.hasFilters = false;
|
|
2349
|
-
if (rootClass) {
|
|
2350
|
-
this.discoverTypes(rootClass);
|
|
2351
|
-
}
|
|
2352
|
-
}
|
|
2353
|
-
has(schema) {
|
|
2354
|
-
return this.schemas.has(schema);
|
|
2355
|
-
}
|
|
2356
|
-
get(typeid) {
|
|
2357
|
-
return this.types[typeid];
|
|
2358
|
-
}
|
|
2359
|
-
add(schema, typeid = this.schemas.size) {
|
|
2360
|
-
// skip if already registered
|
|
2361
|
-
if (this.schemas.has(schema)) {
|
|
2362
|
-
return false;
|
|
2363
|
-
}
|
|
2364
|
-
this.types[typeid] = schema;
|
|
2365
|
-
this.schemas.set(schema, typeid);
|
|
2366
|
-
return true;
|
|
2367
|
-
}
|
|
2368
|
-
getTypeId(klass) {
|
|
2369
|
-
return this.schemas.get(klass);
|
|
2370
|
-
}
|
|
2371
|
-
discoverTypes(klass) {
|
|
2372
|
-
if (!this.add(klass)) {
|
|
2373
|
-
return;
|
|
2374
|
-
}
|
|
2375
|
-
// add classes inherited from this base class
|
|
2376
|
-
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
2377
|
-
this.discoverTypes(child);
|
|
2378
|
-
});
|
|
2379
|
-
// skip if no fields are defined for this class.
|
|
2380
|
-
if (klass[Symbol.metadata] === undefined) {
|
|
2381
|
-
klass[Symbol.metadata] = {};
|
|
2382
|
-
}
|
|
2383
|
-
// const metadata = Metadata.getFor(klass);
|
|
2384
|
-
const metadata = klass[Symbol.metadata];
|
|
2385
|
-
// if any schema/field has filters, mark "context" as having filters.
|
|
2386
|
-
if (metadata[-2]) {
|
|
2387
|
-
this.hasFilters = true;
|
|
2388
|
-
}
|
|
2389
|
-
for (const field in metadata) {
|
|
2390
|
-
const fieldType = metadata[field].type;
|
|
2391
|
-
if (typeof (fieldType) === "string") {
|
|
2392
|
-
continue;
|
|
2393
|
-
}
|
|
2394
|
-
if (Array.isArray(fieldType)) {
|
|
2395
|
-
const type = fieldType[0];
|
|
2396
|
-
if (type === "string") {
|
|
2397
|
-
continue;
|
|
2670
|
+
this.forEach((value, key) => {
|
|
2671
|
+
if (value[$changes]) {
|
|
2672
|
+
cloned.set(key, value['clone']());
|
|
2398
2673
|
}
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
else if (typeof (fieldType) === "function") {
|
|
2402
|
-
this.discoverTypes(fieldType);
|
|
2403
|
-
}
|
|
2404
|
-
else {
|
|
2405
|
-
const type = Object.values(fieldType)[0];
|
|
2406
|
-
// skip primitive types
|
|
2407
|
-
if (typeof (type) === "string") {
|
|
2408
|
-
continue;
|
|
2674
|
+
else {
|
|
2675
|
+
cloned.set(key, value);
|
|
2409
2676
|
}
|
|
2410
|
-
|
|
2411
|
-
}
|
|
2677
|
+
});
|
|
2412
2678
|
}
|
|
2679
|
+
return cloned;
|
|
2413
2680
|
}
|
|
2414
2681
|
}
|
|
2682
|
+
registerType("map", { constructor: MapSchema });
|
|
2683
|
+
|
|
2684
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
2415
2685
|
/**
|
|
2416
2686
|
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
2417
2687
|
*
|
|
@@ -2438,8 +2708,8 @@
|
|
|
2438
2708
|
// // detect index for this field, considering inheritance
|
|
2439
2709
|
// //
|
|
2440
2710
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2441
|
-
// let fieldIndex: number = context.metadata[
|
|
2442
|
-
// ?? (parent && parent[
|
|
2711
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2712
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2443
2713
|
// ?? -1; // no fields defined
|
|
2444
2714
|
// fieldIndex++;
|
|
2445
2715
|
// if (
|
|
@@ -2559,18 +2829,20 @@
|
|
|
2559
2829
|
const constructor = target.constructor;
|
|
2560
2830
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2561
2831
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2832
|
+
// TODO: use Metadata.initialize()
|
|
2562
2833
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
|
|
2572
|
-
|
|
2573
|
-
}
|
|
2834
|
+
// const fieldIndex = metadata[fieldName];
|
|
2835
|
+
// if (!metadata[fieldIndex]) {
|
|
2836
|
+
// //
|
|
2837
|
+
// // detect index for this field, considering inheritance
|
|
2838
|
+
// //
|
|
2839
|
+
// metadata[fieldIndex] = {
|
|
2840
|
+
// type: undefined,
|
|
2841
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2842
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2843
|
+
// ?? -1) + 1 // no fields defined
|
|
2844
|
+
// }
|
|
2845
|
+
// }
|
|
2574
2846
|
Metadata.setTag(metadata, fieldName, tag);
|
|
2575
2847
|
};
|
|
2576
2848
|
}
|
|
@@ -2584,17 +2856,17 @@
|
|
|
2584
2856
|
TypeContext.register(constructor);
|
|
2585
2857
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2586
2858
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2587
|
-
const metadata =
|
|
2588
|
-
let fieldIndex;
|
|
2859
|
+
const metadata = Metadata.initialize(constructor);
|
|
2860
|
+
let fieldIndex = metadata[field];
|
|
2589
2861
|
/**
|
|
2590
2862
|
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
2591
2863
|
*/
|
|
2592
|
-
if (metadata[
|
|
2593
|
-
if (metadata[
|
|
2864
|
+
if (metadata[fieldIndex] !== undefined) {
|
|
2865
|
+
if (metadata[fieldIndex].deprecated) {
|
|
2594
2866
|
// do not create accessors for deprecated properties.
|
|
2595
2867
|
return;
|
|
2596
2868
|
}
|
|
2597
|
-
else if (metadata[
|
|
2869
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
2598
2870
|
// trying to define same property multiple times across inheritance.
|
|
2599
2871
|
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
2600
2872
|
try {
|
|
@@ -2605,16 +2877,13 @@
|
|
|
2605
2877
|
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
2606
2878
|
}
|
|
2607
2879
|
}
|
|
2608
|
-
else {
|
|
2609
|
-
fieldIndex = metadata[field].index;
|
|
2610
|
-
}
|
|
2611
2880
|
}
|
|
2612
2881
|
else {
|
|
2613
2882
|
//
|
|
2614
2883
|
// detect index for this field, considering inheritance
|
|
2615
2884
|
//
|
|
2616
|
-
fieldIndex = metadata[
|
|
2617
|
-
?? (parentMetadata && parentMetadata[
|
|
2885
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2886
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2618
2887
|
?? -1; // no fields defined
|
|
2619
2888
|
fieldIndex++;
|
|
2620
2889
|
}
|
|
@@ -2633,15 +2902,15 @@
|
|
|
2633
2902
|
const childType = (complexTypeKlass)
|
|
2634
2903
|
? Object.values(type)[0]
|
|
2635
2904
|
: type;
|
|
2636
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass
|
|
2905
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
2637
2906
|
}
|
|
2638
2907
|
};
|
|
2639
2908
|
}
|
|
2640
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass
|
|
2909
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
2641
2910
|
return {
|
|
2642
2911
|
get: function () { return this[fieldCached]; },
|
|
2643
2912
|
set: function (value) {
|
|
2644
|
-
const previousValue = this[fieldCached]
|
|
2913
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2645
2914
|
// skip if value is the same as cached.
|
|
2646
2915
|
if (value === previousValue) {
|
|
2647
2916
|
return;
|
|
@@ -2659,22 +2928,27 @@
|
|
|
2659
2928
|
}
|
|
2660
2929
|
value[$childType] = type;
|
|
2661
2930
|
}
|
|
2931
|
+
else if (typeof (type) !== "string") {
|
|
2932
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
2933
|
+
}
|
|
2934
|
+
else {
|
|
2935
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
2936
|
+
}
|
|
2937
|
+
const changeTree = this[$changes];
|
|
2662
2938
|
//
|
|
2663
2939
|
// Replacing existing "ref", remove it from root.
|
|
2664
2940
|
// TODO: if there are other references to this instance, we should not remove it from root.
|
|
2665
2941
|
//
|
|
2666
2942
|
if (previousValue !== undefined && previousValue[$changes]) {
|
|
2667
|
-
|
|
2943
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
2668
2944
|
}
|
|
2669
2945
|
// flag the change for encoding.
|
|
2670
|
-
this.constructor[$track](
|
|
2946
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
2671
2947
|
//
|
|
2672
2948
|
// call setParent() recursively for this and its child
|
|
2673
2949
|
// structures.
|
|
2674
2950
|
//
|
|
2675
|
-
|
|
2676
|
-
value[$changes].setParent(this, this[$changes].root, metadata[field].index);
|
|
2677
|
-
}
|
|
2951
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
2678
2952
|
}
|
|
2679
2953
|
else if (previousValue !== undefined) {
|
|
2680
2954
|
//
|
|
@@ -2701,20 +2975,22 @@
|
|
|
2701
2975
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2702
2976
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2703
2977
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
|
|
2713
|
-
|
|
2714
|
-
}
|
|
2715
|
-
|
|
2978
|
+
const fieldIndex = metadata[field];
|
|
2979
|
+
// if (!metadata[field]) {
|
|
2980
|
+
// //
|
|
2981
|
+
// // detect index for this field, considering inheritance
|
|
2982
|
+
// //
|
|
2983
|
+
// metadata[field] = {
|
|
2984
|
+
// type: undefined,
|
|
2985
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2986
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2987
|
+
// ?? -1) + 1 // no fields defined
|
|
2988
|
+
// }
|
|
2989
|
+
// }
|
|
2990
|
+
metadata[fieldIndex].deprecated = true;
|
|
2716
2991
|
if (throws) {
|
|
2717
|
-
metadata[
|
|
2992
|
+
metadata[$descriptors] ??= {};
|
|
2993
|
+
metadata[$descriptors][field] = {
|
|
2718
2994
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
2719
2995
|
set: function (value) { },
|
|
2720
2996
|
enumerable: false,
|
|
@@ -2722,8 +2998,8 @@
|
|
|
2722
2998
|
};
|
|
2723
2999
|
}
|
|
2724
3000
|
// flag metadata[field] as non-enumerable
|
|
2725
|
-
Object.defineProperty(metadata,
|
|
2726
|
-
value: metadata[
|
|
3001
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
3002
|
+
value: metadata[fieldIndex],
|
|
2727
3003
|
enumerable: false,
|
|
2728
3004
|
configurable: true
|
|
2729
3005
|
});
|
|
@@ -2735,6 +3011,37 @@
|
|
|
2735
3011
|
}
|
|
2736
3012
|
return target;
|
|
2737
3013
|
}
|
|
3014
|
+
function schema(fields, name, inherits = Schema) {
|
|
3015
|
+
const defaultValues = {};
|
|
3016
|
+
const viewTagFields = {};
|
|
3017
|
+
for (let fieldName in fields) {
|
|
3018
|
+
const field = fields[fieldName];
|
|
3019
|
+
if (typeof (field) === "object") {
|
|
3020
|
+
if (field['default'] !== undefined) {
|
|
3021
|
+
defaultValues[fieldName] = field['default'];
|
|
3022
|
+
}
|
|
3023
|
+
if (field['view'] !== undefined) {
|
|
3024
|
+
viewTagFields[fieldName] = (typeof (field['view']) === "boolean")
|
|
3025
|
+
? DEFAULT_VIEW_TAG
|
|
3026
|
+
: field['view'];
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
}
|
|
3030
|
+
const klass = Metadata.setFields(class extends inherits {
|
|
3031
|
+
constructor(...args) {
|
|
3032
|
+
args[0] = Object.assign({}, defaultValues, args[0]);
|
|
3033
|
+
super(...args);
|
|
3034
|
+
}
|
|
3035
|
+
}, fields);
|
|
3036
|
+
for (let fieldName in viewTagFields) {
|
|
3037
|
+
view(viewTagFields[fieldName])(klass.prototype, fieldName);
|
|
3038
|
+
}
|
|
3039
|
+
if (name) {
|
|
3040
|
+
Object.defineProperty(klass, "name", { value: name });
|
|
3041
|
+
}
|
|
3042
|
+
klass.extends = (fields, name) => schema(fields, name, klass);
|
|
3043
|
+
return klass;
|
|
3044
|
+
}
|
|
2738
3045
|
|
|
2739
3046
|
function getIndent(level) {
|
|
2740
3047
|
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
@@ -2745,32 +3052,21 @@
|
|
|
2745
3052
|
ops: {},
|
|
2746
3053
|
refs: []
|
|
2747
3054
|
};
|
|
2748
|
-
$root.changes
|
|
3055
|
+
// for (const refId in $root.changes) {
|
|
3056
|
+
$root.changes.forEach(changeTree => {
|
|
3057
|
+
const changes = changeTree.indexedOperations;
|
|
2749
3058
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2750
|
-
|
|
3059
|
+
for (const index in changes) {
|
|
3060
|
+
const op = changes[index];
|
|
2751
3061
|
const opName = exports.OPERATION[op];
|
|
2752
3062
|
if (!dump.ops[opName]) {
|
|
2753
3063
|
dump.ops[opName] = 0;
|
|
2754
3064
|
}
|
|
2755
3065
|
dump.ops[exports.OPERATION[op]]++;
|
|
2756
|
-
}
|
|
3066
|
+
}
|
|
2757
3067
|
});
|
|
2758
3068
|
return dump;
|
|
2759
3069
|
}
|
|
2760
|
-
function getNextPowerOf2(number) {
|
|
2761
|
-
// If number is already a power of 2, return it
|
|
2762
|
-
if ((number & (number - 1)) === 0) {
|
|
2763
|
-
return number;
|
|
2764
|
-
}
|
|
2765
|
-
// Find the position of the most significant bit
|
|
2766
|
-
let msbPosition = 0;
|
|
2767
|
-
while (number > 0) {
|
|
2768
|
-
number >>= 1;
|
|
2769
|
-
msbPosition++;
|
|
2770
|
-
}
|
|
2771
|
-
// Return the next power of 2
|
|
2772
|
-
return 1 << msbPosition;
|
|
2773
|
-
}
|
|
2774
3070
|
|
|
2775
3071
|
var _a$2, _b$2;
|
|
2776
3072
|
/**
|
|
@@ -2779,6 +3075,7 @@
|
|
|
2779
3075
|
class Schema {
|
|
2780
3076
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2781
3077
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
3078
|
+
// public [$changes]: ChangeTree;
|
|
2782
3079
|
/**
|
|
2783
3080
|
* Assign the property descriptors required to track changes on this instance.
|
|
2784
3081
|
* @param instance
|
|
@@ -2789,35 +3086,7 @@
|
|
|
2789
3086
|
enumerable: false,
|
|
2790
3087
|
writable: true
|
|
2791
3088
|
});
|
|
2792
|
-
|
|
2793
|
-
// Define property descriptors
|
|
2794
|
-
for (const field in metadata) {
|
|
2795
|
-
if (metadata[field].descriptor) {
|
|
2796
|
-
// for encoder
|
|
2797
|
-
Object.defineProperty(instance, `_${field}`, {
|
|
2798
|
-
value: undefined,
|
|
2799
|
-
writable: true,
|
|
2800
|
-
enumerable: false,
|
|
2801
|
-
configurable: true,
|
|
2802
|
-
});
|
|
2803
|
-
Object.defineProperty(instance, field, metadata[field].descriptor);
|
|
2804
|
-
}
|
|
2805
|
-
else {
|
|
2806
|
-
// for decoder
|
|
2807
|
-
Object.defineProperty(instance, field, {
|
|
2808
|
-
value: undefined,
|
|
2809
|
-
writable: true,
|
|
2810
|
-
enumerable: true,
|
|
2811
|
-
configurable: true,
|
|
2812
|
-
});
|
|
2813
|
-
}
|
|
2814
|
-
// Object.defineProperty(instance, field, {
|
|
2815
|
-
// ...instance.constructor[Symbol.metadata][field].descriptor
|
|
2816
|
-
// });
|
|
2817
|
-
// if (args[0]?.hasOwnProperty(field)) {
|
|
2818
|
-
// instance[field] = args[0][field];
|
|
2819
|
-
// }
|
|
2820
|
-
}
|
|
3089
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2821
3090
|
}
|
|
2822
3091
|
static is(type) {
|
|
2823
3092
|
return typeof (type[Symbol.metadata]) === "object";
|
|
@@ -2841,7 +3110,7 @@
|
|
|
2841
3110
|
*/
|
|
2842
3111
|
static [$filter](ref, index, view) {
|
|
2843
3112
|
const metadata = ref.constructor[Symbol.metadata];
|
|
2844
|
-
const tag = metadata[
|
|
3113
|
+
const tag = metadata[index]?.tag;
|
|
2845
3114
|
if (view === undefined) {
|
|
2846
3115
|
// shared pass/encode: encode if doesn't have a tag
|
|
2847
3116
|
return tag === undefined;
|
|
@@ -2862,12 +3131,16 @@
|
|
|
2862
3131
|
}
|
|
2863
3132
|
// allow inherited classes to have a constructor
|
|
2864
3133
|
constructor(...args) {
|
|
3134
|
+
//
|
|
3135
|
+
// inline
|
|
3136
|
+
// Schema.initialize(this);
|
|
3137
|
+
//
|
|
2865
3138
|
Schema.initialize(this);
|
|
2866
3139
|
//
|
|
2867
3140
|
// Assign initial values
|
|
2868
3141
|
//
|
|
2869
3142
|
if (args[0]) {
|
|
2870
|
-
|
|
3143
|
+
Object.assign(this, args[0]);
|
|
2871
3144
|
}
|
|
2872
3145
|
}
|
|
2873
3146
|
assign(props) {
|
|
@@ -2881,7 +3154,8 @@
|
|
|
2881
3154
|
* @param operation OPERATION to perform (detected automatically)
|
|
2882
3155
|
*/
|
|
2883
3156
|
setDirty(property, operation) {
|
|
2884
|
-
this
|
|
3157
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3158
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
2885
3159
|
}
|
|
2886
3160
|
clone() {
|
|
2887
3161
|
const cloned = new (this.constructor);
|
|
@@ -2890,7 +3164,9 @@
|
|
|
2890
3164
|
// TODO: clone all properties, not only annotated ones
|
|
2891
3165
|
//
|
|
2892
3166
|
// for (const field in this) {
|
|
2893
|
-
for (const
|
|
3167
|
+
for (const fieldIndex in metadata) {
|
|
3168
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
3169
|
+
const field = metadata[fieldIndex].name;
|
|
2894
3170
|
if (typeof (this[field]) === "object" &&
|
|
2895
3171
|
typeof (this[field]?.clone) === "function") {
|
|
2896
3172
|
// deep clone
|
|
@@ -2904,10 +3180,11 @@
|
|
|
2904
3180
|
return cloned;
|
|
2905
3181
|
}
|
|
2906
3182
|
toJSON() {
|
|
2907
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
2908
3183
|
const obj = {};
|
|
2909
|
-
|
|
2910
|
-
|
|
3184
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3185
|
+
for (const index in metadata) {
|
|
3186
|
+
const field = metadata[index];
|
|
3187
|
+
const fieldName = field.name;
|
|
2911
3188
|
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
2912
3189
|
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
2913
3190
|
? this[fieldName]['toJSON']()
|
|
@@ -2920,18 +3197,27 @@
|
|
|
2920
3197
|
this[$changes].discardAll();
|
|
2921
3198
|
}
|
|
2922
3199
|
[$getByIndex](index) {
|
|
2923
|
-
|
|
3200
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3201
|
+
return this[metadata[index].name];
|
|
2924
3202
|
}
|
|
2925
3203
|
[$deleteByIndex](index) {
|
|
2926
|
-
this
|
|
3204
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3205
|
+
this[metadata[index].name] = undefined;
|
|
2927
3206
|
}
|
|
2928
|
-
|
|
3207
|
+
/**
|
|
3208
|
+
* Inspect the `refId` of all Schema instances in the tree. Optionally display the contents of the instance.
|
|
3209
|
+
*
|
|
3210
|
+
* @param instance Schema instance
|
|
3211
|
+
* @param showContents display JSON contents of the instance
|
|
3212
|
+
* @returns
|
|
3213
|
+
*/
|
|
3214
|
+
static debugRefIds(instance, showContents = false, level = 0) {
|
|
2929
3215
|
const ref = instance;
|
|
2930
3216
|
const changeTree = ref[$changes];
|
|
2931
|
-
const contents = (
|
|
3217
|
+
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2932
3218
|
let output = "";
|
|
2933
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
2934
|
-
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref,
|
|
3219
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
3220
|
+
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, showContents, level + 1));
|
|
2935
3221
|
return output;
|
|
2936
3222
|
}
|
|
2937
3223
|
/**
|
|
@@ -2948,30 +3234,40 @@
|
|
|
2948
3234
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
2949
3235
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
2950
3236
|
function dumpChangeSet(changeSet) {
|
|
2951
|
-
|
|
2952
|
-
.
|
|
2953
|
-
.forEach((
|
|
3237
|
+
changeSet.operations
|
|
3238
|
+
.filter(op => op)
|
|
3239
|
+
.forEach((index) => {
|
|
3240
|
+
const operation = changeTree.indexedOperations[index];
|
|
3241
|
+
console.log({ index, operation });
|
|
3242
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3243
|
+
});
|
|
2954
3244
|
}
|
|
2955
3245
|
dumpChangeSet(changeSet);
|
|
2956
3246
|
// display filtered changes
|
|
2957
|
-
if (!isEncodeAll &&
|
|
3247
|
+
if (!isEncodeAll &&
|
|
3248
|
+
changeTree.filteredChanges &&
|
|
3249
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
2958
3250
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
2959
3251
|
dumpChangeSet(changeTree.filteredChanges);
|
|
2960
3252
|
}
|
|
2961
3253
|
// display filtered changes
|
|
2962
|
-
if (isEncodeAll &&
|
|
3254
|
+
if (isEncodeAll &&
|
|
3255
|
+
changeTree.allFilteredChanges &&
|
|
3256
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
2963
3257
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
2964
3258
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
2965
3259
|
}
|
|
2966
3260
|
return output;
|
|
2967
3261
|
}
|
|
2968
|
-
static debugChangesDeep(ref) {
|
|
3262
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
2969
3263
|
let output = "";
|
|
2970
3264
|
const rootChangeTree = ref[$changes];
|
|
3265
|
+
const root = rootChangeTree.root;
|
|
2971
3266
|
const changeTrees = new Map();
|
|
2972
|
-
|
|
3267
|
+
const instanceRefIds = [];
|
|
2973
3268
|
let totalOperations = 0;
|
|
2974
|
-
for (const [
|
|
3269
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3270
|
+
const changeTree = root.changeTrees[refId];
|
|
2975
3271
|
let includeChangeTree = false;
|
|
2976
3272
|
let parentChangeTrees = [];
|
|
2977
3273
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -2989,14 +3285,14 @@
|
|
|
2989
3285
|
}
|
|
2990
3286
|
}
|
|
2991
3287
|
if (includeChangeTree) {
|
|
2992
|
-
|
|
2993
|
-
totalOperations += changes.
|
|
3288
|
+
instanceRefIds.push(changeTree.refId);
|
|
3289
|
+
totalOperations += Object.keys(changes).length;
|
|
2994
3290
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
2995
3291
|
}
|
|
2996
3292
|
}
|
|
2997
3293
|
output += "---\n";
|
|
2998
3294
|
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
2999
|
-
output += `Total instances: ${
|
|
3295
|
+
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
3000
3296
|
output += `Total changes: ${totalOperations}\n`;
|
|
3001
3297
|
output += "---\n";
|
|
3002
3298
|
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
@@ -3008,12 +3304,13 @@
|
|
|
3008
3304
|
visitedParents.add(parentChangeTree);
|
|
3009
3305
|
}
|
|
3010
3306
|
});
|
|
3011
|
-
const changes = changeTree.
|
|
3307
|
+
const changes = changeTree.indexedOperations;
|
|
3012
3308
|
const level = parentChangeTrees.length;
|
|
3013
3309
|
const indent = getIndent(level);
|
|
3014
3310
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3015
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3016
|
-
for (const
|
|
3311
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3312
|
+
for (const index in changes) {
|
|
3313
|
+
const operation = changes[index];
|
|
3017
3314
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3018
3315
|
}
|
|
3019
3316
|
}
|
|
@@ -3047,6 +3344,7 @@
|
|
|
3047
3344
|
this.$indexes = new Map();
|
|
3048
3345
|
this.$refId = 0;
|
|
3049
3346
|
this[$changes] = new ChangeTree(this);
|
|
3347
|
+
this[$changes].indexes = {};
|
|
3050
3348
|
if (initialValues) {
|
|
3051
3349
|
initialValues.forEach((v) => this.add(v));
|
|
3052
3350
|
}
|
|
@@ -3202,6 +3500,7 @@
|
|
|
3202
3500
|
this.$indexes = new Map();
|
|
3203
3501
|
this.$refId = 0;
|
|
3204
3502
|
this[$changes] = new ChangeTree(this);
|
|
3503
|
+
this[$changes].indexes = {};
|
|
3205
3504
|
if (initialValues) {
|
|
3206
3505
|
initialValues.forEach((v) => this.add(v));
|
|
3207
3506
|
}
|
|
@@ -3357,6 +3656,8 @@
|
|
|
3357
3656
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3358
3657
|
PERFORMANCE OF THIS SOFTWARE.
|
|
3359
3658
|
***************************************************************************** */
|
|
3659
|
+
/* global Reflect, Promise, SuppressedError, Symbol, Iterator */
|
|
3660
|
+
|
|
3360
3661
|
|
|
3361
3662
|
function __decorate(decorators, target, key, desc) {
|
|
3362
3663
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3370,37 +3671,127 @@
|
|
|
3370
3671
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3371
3672
|
};
|
|
3372
3673
|
|
|
3674
|
+
function spliceOne(arr, index) {
|
|
3675
|
+
// manually splice an array
|
|
3676
|
+
if (index === -1 || index >= arr.length) {
|
|
3677
|
+
return false;
|
|
3678
|
+
}
|
|
3679
|
+
const len = arr.length - 1;
|
|
3680
|
+
for (let i = index; i < len; i++) {
|
|
3681
|
+
arr[i] = arr[i + 1];
|
|
3682
|
+
}
|
|
3683
|
+
arr.length = len;
|
|
3684
|
+
return true;
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
class Root {
|
|
3688
|
+
constructor(types) {
|
|
3689
|
+
this.types = types;
|
|
3690
|
+
this.nextUniqueId = 0;
|
|
3691
|
+
this.refCount = {};
|
|
3692
|
+
this.changeTrees = {};
|
|
3693
|
+
// all changes
|
|
3694
|
+
this.allChanges = [];
|
|
3695
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3696
|
+
// pending changes to be encoded
|
|
3697
|
+
this.changes = [];
|
|
3698
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3699
|
+
}
|
|
3700
|
+
getNextUniqueId() {
|
|
3701
|
+
return this.nextUniqueId++;
|
|
3702
|
+
}
|
|
3703
|
+
add(changeTree) {
|
|
3704
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3705
|
+
changeTree.ensureRefId();
|
|
3706
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3707
|
+
if (isNewChangeTree) {
|
|
3708
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3709
|
+
}
|
|
3710
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3711
|
+
if (previousRefCount === 0) {
|
|
3712
|
+
//
|
|
3713
|
+
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3714
|
+
// We need to re-add all changes to the `changes` map.
|
|
3715
|
+
//
|
|
3716
|
+
const ops = changeTree.allChanges.operations;
|
|
3717
|
+
let len = ops.length;
|
|
3718
|
+
while (len--) {
|
|
3719
|
+
changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
|
|
3720
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3721
|
+
}
|
|
3722
|
+
}
|
|
3723
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3724
|
+
return isNewChangeTree;
|
|
3725
|
+
}
|
|
3726
|
+
remove(changeTree) {
|
|
3727
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3728
|
+
if (refCount <= 0) {
|
|
3729
|
+
//
|
|
3730
|
+
// Only remove "root" reference if it's the last reference
|
|
3731
|
+
//
|
|
3732
|
+
changeTree.root = undefined;
|
|
3733
|
+
delete this.changeTrees[changeTree.refId];
|
|
3734
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3735
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3736
|
+
if (changeTree.filteredChanges) {
|
|
3737
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3738
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3739
|
+
}
|
|
3740
|
+
this.refCount[changeTree.refId] = 0;
|
|
3741
|
+
}
|
|
3742
|
+
else {
|
|
3743
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3744
|
+
}
|
|
3745
|
+
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3746
|
+
return refCount;
|
|
3747
|
+
}
|
|
3748
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3749
|
+
const changeSet = this[changeSetName];
|
|
3750
|
+
const index = changeSet.indexOf(changeTree);
|
|
3751
|
+
if (index !== -1) {
|
|
3752
|
+
spliceOne(changeSet, index);
|
|
3753
|
+
// changeSet[index] = undefined;
|
|
3754
|
+
}
|
|
3755
|
+
}
|
|
3756
|
+
clear() {
|
|
3757
|
+
this.changes.length = 0;
|
|
3758
|
+
}
|
|
3759
|
+
}
|
|
3760
|
+
|
|
3373
3761
|
class Encoder {
|
|
3374
|
-
static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
|
|
3375
|
-
constructor(
|
|
3376
|
-
this.sharedBuffer = Buffer.
|
|
3377
|
-
this.setRoot(root);
|
|
3762
|
+
static { this.BUFFER_SIZE = Buffer.poolSize ?? 8 * 1024; } // 8KB
|
|
3763
|
+
constructor(state) {
|
|
3764
|
+
this.sharedBuffer = Buffer.allocUnsafe(Encoder.BUFFER_SIZE);
|
|
3378
3765
|
//
|
|
3379
3766
|
// TODO: cache and restore "Context" based on root schema
|
|
3380
3767
|
// (to avoid creating a new context for every new room)
|
|
3381
3768
|
//
|
|
3382
|
-
this.context = new TypeContext(
|
|
3769
|
+
this.context = new TypeContext(state.constructor);
|
|
3770
|
+
this.root = new Root(this.context);
|
|
3771
|
+
this.setState(state);
|
|
3383
3772
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
3384
3773
|
// this.context.schemas.forEach((id, schema) => {
|
|
3385
3774
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3386
3775
|
// });
|
|
3387
3776
|
}
|
|
3388
|
-
|
|
3389
|
-
this.root = new Root();
|
|
3777
|
+
setState(state) {
|
|
3390
3778
|
this.state = state;
|
|
3391
|
-
state[$changes].setRoot(this.root);
|
|
3779
|
+
this.state[$changes].setRoot(this.root);
|
|
3392
3780
|
}
|
|
3393
|
-
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer,
|
|
3394
|
-
|
|
3395
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
3781
|
+
encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeSetName = "changes", isEncodeAll = changeSetName === "allChanges", initialOffset = it.offset // cache current offset in case we need to resize the buffer
|
|
3782
|
+
) {
|
|
3396
3783
|
const hasView = (view !== undefined);
|
|
3397
3784
|
const rootChangeTree = this.state[$changes];
|
|
3398
|
-
const
|
|
3399
|
-
|
|
3785
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3786
|
+
const changeTrees = this.root[changeSetName];
|
|
3787
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3788
|
+
const changeTree = changeTrees[i];
|
|
3789
|
+
const operations = changeTree[changeSetName];
|
|
3400
3790
|
const ref = changeTree.ref;
|
|
3401
|
-
const ctor = ref
|
|
3791
|
+
const ctor = ref.constructor;
|
|
3402
3792
|
const encoder = ctor[$encoder];
|
|
3403
3793
|
const filter = ctor[$filter];
|
|
3794
|
+
const metadata = ctor[Symbol.metadata];
|
|
3404
3795
|
if (hasView) {
|
|
3405
3796
|
if (!view.items.has(changeTree)) {
|
|
3406
3797
|
view.invisible.add(changeTree);
|
|
@@ -3411,12 +3802,18 @@
|
|
|
3411
3802
|
}
|
|
3412
3803
|
}
|
|
3413
3804
|
// skip root `refId` if it's the first change tree
|
|
3414
|
-
|
|
3805
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
3806
|
+
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
3415
3807
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3416
|
-
number
|
|
3808
|
+
encode.number(buffer, changeTree.refId, it);
|
|
3417
3809
|
}
|
|
3418
|
-
|
|
3419
|
-
|
|
3810
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3811
|
+
const fieldIndex = operations.operations[j];
|
|
3812
|
+
const operation = (fieldIndex < 0)
|
|
3813
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3814
|
+
: (isEncodeAll)
|
|
3815
|
+
? exports.OPERATION.ADD
|
|
3816
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3420
3817
|
//
|
|
3421
3818
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3422
3819
|
// they will be encoded per client, based on their view.
|
|
@@ -3424,97 +3821,113 @@
|
|
|
3424
3821
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3425
3822
|
// TODO: avoid checking if no view tags were defined
|
|
3426
3823
|
//
|
|
3427
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3428
|
-
// console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
|
|
3824
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3429
3825
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3430
3826
|
// view?.invisible.add(changeTree);
|
|
3431
3827
|
continue;
|
|
3432
3828
|
}
|
|
3433
|
-
|
|
3434
|
-
// ref: changeTree.ref.constructor.name,
|
|
3435
|
-
// fieldIndex,
|
|
3436
|
-
// operation: OPERATION[operation],
|
|
3437
|
-
// });
|
|
3438
|
-
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
|
|
3829
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3439
3830
|
}
|
|
3831
|
+
// if (shouldDiscardChanges) {
|
|
3832
|
+
// changeTree.discard();
|
|
3833
|
+
// changeTree.isNew = false; // Not a new instance anymore
|
|
3834
|
+
// }
|
|
3440
3835
|
}
|
|
3441
3836
|
if (it.offset > buffer.byteLength) {
|
|
3442
|
-
|
|
3443
|
-
|
|
3837
|
+
// we can assume that n + 1 poolSize will suffice given that we are likely done with encoding at this point
|
|
3838
|
+
// multiples of poolSize are faster to allocate than arbitrary sizes
|
|
3839
|
+
// if we are on an older platform that doesn't implement pooling use 8kb as poolSize (that's the default for node)
|
|
3840
|
+
const newSize = Math.ceil(it.offset / (Buffer.poolSize ?? 8 * 1024)) * (Buffer.poolSize ?? 8 * 1024);
|
|
3841
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
3842
|
+
|
|
3843
|
+
import { Encoder } from "@colyseus/schema";
|
|
3844
|
+
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
3845
|
+
`);
|
|
3444
3846
|
//
|
|
3445
3847
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
3848
|
+
// -> No we probably can't unless we catch the need for resize before encoding which is likely more computationally expensive than resizing on demand
|
|
3446
3849
|
//
|
|
3447
|
-
buffer = Buffer.
|
|
3850
|
+
buffer = Buffer.alloc(newSize, buffer); // fill with buffer here to memcpy previous encoding steps beyond the initialOffset
|
|
3448
3851
|
// assign resized buffer to local sharedBuffer
|
|
3449
3852
|
if (buffer === this.sharedBuffer) {
|
|
3450
3853
|
this.sharedBuffer = buffer;
|
|
3451
3854
|
}
|
|
3452
|
-
return this.encode({ offset: initialOffset }, view, buffer);
|
|
3855
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3453
3856
|
}
|
|
3454
3857
|
else {
|
|
3455
3858
|
//
|
|
3456
3859
|
// only clear changes after making sure buffer resize is not required.
|
|
3457
3860
|
//
|
|
3458
|
-
if (
|
|
3861
|
+
if (shouldDiscardChanges) {
|
|
3459
3862
|
//
|
|
3460
|
-
//
|
|
3863
|
+
// TODO: avoid iterating over change trees twice.
|
|
3461
3864
|
//
|
|
3462
|
-
|
|
3865
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3866
|
+
const changeTree = changeTrees[i];
|
|
3867
|
+
changeTree.discard();
|
|
3868
|
+
changeTree.isNew = false; // Not a new instance anymore
|
|
3869
|
+
}
|
|
3463
3870
|
}
|
|
3464
|
-
|
|
3465
|
-
return buffer.slice(0, it.offset);
|
|
3871
|
+
return buffer.subarray(0, it.offset);
|
|
3466
3872
|
}
|
|
3467
3873
|
}
|
|
3468
3874
|
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3469
|
-
|
|
3470
|
-
// Array.from(this.$root.allChanges.entries()).map((item) => {
|
|
3471
|
-
// console.log("->", item[0].refId, item[0].ref.toJSON());
|
|
3472
|
-
// });
|
|
3473
|
-
return this.encode(it, undefined, buffer, this.root.allChanges);
|
|
3875
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3474
3876
|
}
|
|
3475
3877
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3476
3878
|
const viewOffset = it.offset;
|
|
3477
|
-
// console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
|
|
3478
|
-
// this.debugAllFilteredChanges();
|
|
3479
3879
|
// try to encode "filtered" changes
|
|
3480
|
-
this.encode(it, view, bytes,
|
|
3880
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3481
3881
|
return Buffer.concat([
|
|
3482
|
-
bytes.
|
|
3483
|
-
bytes.
|
|
3882
|
+
bytes.subarray(0, sharedOffset),
|
|
3883
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3484
3884
|
]);
|
|
3485
3885
|
}
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3490
|
-
|
|
3491
|
-
|
|
3492
|
-
|
|
3493
|
-
|
|
3494
|
-
|
|
3495
|
-
|
|
3886
|
+
debugChanges(field) {
|
|
3887
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3888
|
+
? this.root[field]
|
|
3889
|
+
: field;
|
|
3890
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3891
|
+
const changeSet = changeTree[field];
|
|
3892
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3893
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3894
|
+
for (const index in changeSet) {
|
|
3895
|
+
const op = changeSet[index];
|
|
3896
|
+
console.log(" ->", {
|
|
3897
|
+
index,
|
|
3898
|
+
field: metadata?.[index],
|
|
3899
|
+
op: exports.OPERATION[op],
|
|
3900
|
+
});
|
|
3901
|
+
}
|
|
3902
|
+
});
|
|
3903
|
+
}
|
|
3496
3904
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3497
3905
|
const viewOffset = it.offset;
|
|
3498
|
-
// try to encode "filtered" changes
|
|
3499
|
-
this.encode(it, view, bytes, this.root.filteredChanges);
|
|
3500
3906
|
// encode visibility changes (add/remove for this view)
|
|
3501
|
-
const
|
|
3502
|
-
for (
|
|
3503
|
-
|
|
3504
|
-
|
|
3505
|
-
|
|
3907
|
+
const refIds = Object.keys(view.changes);
|
|
3908
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3909
|
+
const refId = refIds[i];
|
|
3910
|
+
const changes = view.changes[refId];
|
|
3911
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3912
|
+
if (changeTree === undefined ||
|
|
3913
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3914
|
+
) {
|
|
3915
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3506
3916
|
continue;
|
|
3507
3917
|
}
|
|
3508
3918
|
const ref = changeTree.ref;
|
|
3509
|
-
const ctor = ref
|
|
3919
|
+
const ctor = ref.constructor;
|
|
3510
3920
|
const encoder = ctor[$encoder];
|
|
3921
|
+
const metadata = ctor[Symbol.metadata];
|
|
3511
3922
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3512
|
-
number
|
|
3513
|
-
const
|
|
3514
|
-
for (
|
|
3923
|
+
encode.number(bytes, changeTree.refId, it);
|
|
3924
|
+
const keys = Object.keys(changes);
|
|
3925
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3926
|
+
const key = keys[i];
|
|
3927
|
+
const operation = changes[key];
|
|
3515
3928
|
// isEncodeAll = false
|
|
3516
3929
|
// hasView = true
|
|
3517
|
-
encoder(this, bytes, changeTree,
|
|
3930
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3518
3931
|
}
|
|
3519
3932
|
}
|
|
3520
3933
|
//
|
|
@@ -3522,51 +3935,62 @@
|
|
|
3522
3935
|
// (to allow re-using StateView's for multiple clients)
|
|
3523
3936
|
//
|
|
3524
3937
|
// clear "view" changes after encoding
|
|
3525
|
-
view.changes
|
|
3938
|
+
view.changes = {};
|
|
3939
|
+
// try to encode "filtered" changes
|
|
3940
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3526
3941
|
return Buffer.concat([
|
|
3527
|
-
bytes.
|
|
3528
|
-
bytes.
|
|
3942
|
+
bytes.subarray(0, sharedOffset),
|
|
3943
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3529
3944
|
]);
|
|
3530
3945
|
}
|
|
3531
3946
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3532
|
-
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3947
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3948
|
+
// changeTree.endEncode();
|
|
3949
|
+
// });
|
|
3950
|
+
// for (const refId in changeTrees) {
|
|
3951
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3952
|
+
// changeTree.endEncode();
|
|
3953
|
+
// // changeTree.changes.clear();
|
|
3954
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3955
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3956
|
+
// // // Not a new instance anymore
|
|
3957
|
+
// // delete changeTree[$isNew];
|
|
3958
|
+
// }
|
|
3536
3959
|
}
|
|
3537
3960
|
discardChanges() {
|
|
3538
3961
|
// discard shared changes
|
|
3539
|
-
|
|
3540
|
-
|
|
3541
|
-
|
|
3962
|
+
let length = this.root.changes.length;
|
|
3963
|
+
if (length > 0) {
|
|
3964
|
+
while (length--) {
|
|
3965
|
+
this.root.changes[length]?.endEncode();
|
|
3966
|
+
}
|
|
3967
|
+
this.root.changes.length = 0;
|
|
3542
3968
|
}
|
|
3543
3969
|
// discard filtered changes
|
|
3544
|
-
|
|
3545
|
-
|
|
3546
|
-
|
|
3970
|
+
length = this.root.filteredChanges.length;
|
|
3971
|
+
if (length > 0) {
|
|
3972
|
+
while (length--) {
|
|
3973
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3974
|
+
}
|
|
3975
|
+
this.root.filteredChanges.length = 0;
|
|
3547
3976
|
}
|
|
3548
3977
|
}
|
|
3549
3978
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
3550
3979
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
3551
3980
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
3981
|
+
if (targetTypeId === undefined) {
|
|
3982
|
+
console.warn(`@colyseus/schema WARNING: Class "${targetType.name}" is not registered on TypeRegistry - Please either tag the class with @entity or define a @type() field.`);
|
|
3983
|
+
return;
|
|
3984
|
+
}
|
|
3552
3985
|
if (baseTypeId !== targetTypeId) {
|
|
3553
3986
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
3554
|
-
number
|
|
3987
|
+
encode.number(bytes, targetTypeId, it);
|
|
3555
3988
|
}
|
|
3556
3989
|
}
|
|
3557
|
-
|
|
3558
|
-
|
|
3559
|
-
|
|
3560
|
-
// manually splice an array
|
|
3561
|
-
if (index === -1 || index >= arr.length) {
|
|
3562
|
-
return false;
|
|
3563
|
-
}
|
|
3564
|
-
const len = arr.length - 1;
|
|
3565
|
-
for (let i = index; i < len; i++) {
|
|
3566
|
-
arr[i] = arr[i + 1];
|
|
3990
|
+
get hasChanges() {
|
|
3991
|
+
return (this.root.changes.length > 0 ||
|
|
3992
|
+
this.root.filteredChanges.length > 0);
|
|
3567
3993
|
}
|
|
3568
|
-
arr.length = len;
|
|
3569
|
-
return true;
|
|
3570
3994
|
}
|
|
3571
3995
|
|
|
3572
3996
|
class DecodingWarning extends Error {
|
|
@@ -3631,6 +4055,7 @@
|
|
|
3631
4055
|
clearRefs() {
|
|
3632
4056
|
this.refs.clear();
|
|
3633
4057
|
this.deletedRefs.clear();
|
|
4058
|
+
this.callbacks = {};
|
|
3634
4059
|
this.refCounts = {};
|
|
3635
4060
|
}
|
|
3636
4061
|
// for decoding
|
|
@@ -3647,8 +4072,9 @@
|
|
|
3647
4072
|
// Ensure child schema instances have their references removed as well.
|
|
3648
4073
|
//
|
|
3649
4074
|
if (Metadata.isValidInstance(ref)) {
|
|
3650
|
-
const metadata = ref
|
|
3651
|
-
for (const
|
|
4075
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
4076
|
+
for (const index in metadata) {
|
|
4077
|
+
const field = metadata[index].name;
|
|
3652
4078
|
const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
|
|
3653
4079
|
if (childRefId) {
|
|
3654
4080
|
this.removeRef(childRefId);
|
|
@@ -3695,14 +4121,14 @@
|
|
|
3695
4121
|
class Decoder {
|
|
3696
4122
|
constructor(root, context) {
|
|
3697
4123
|
this.currentRefId = 0;
|
|
3698
|
-
this.
|
|
4124
|
+
this.setState(root);
|
|
3699
4125
|
this.context = context || new TypeContext(root.constructor);
|
|
3700
4126
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
3701
4127
|
// this.context.schemas.forEach((id, schema) => {
|
|
3702
4128
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3703
4129
|
// });
|
|
3704
4130
|
}
|
|
3705
|
-
|
|
4131
|
+
setState(root) {
|
|
3706
4132
|
this.state = root;
|
|
3707
4133
|
this.root = new ReferenceTracker();
|
|
3708
4134
|
this.root.addRef(0, root);
|
|
@@ -3719,7 +4145,7 @@
|
|
|
3719
4145
|
//
|
|
3720
4146
|
if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
|
|
3721
4147
|
it.offset++;
|
|
3722
|
-
this.currentRefId = number(bytes, it);
|
|
4148
|
+
this.currentRefId = decode.number(bytes, it);
|
|
3723
4149
|
const nextRef = $root.refs.get(this.currentRefId);
|
|
3724
4150
|
//
|
|
3725
4151
|
// Trying to access a reference that haven't been decoded yet.
|
|
@@ -3729,7 +4155,7 @@
|
|
|
3729
4155
|
}
|
|
3730
4156
|
ref[$onDecodeEnd]?.();
|
|
3731
4157
|
ref = nextRef;
|
|
3732
|
-
decoder = ref
|
|
4158
|
+
decoder = ref.constructor[$decoder];
|
|
3733
4159
|
continue;
|
|
3734
4160
|
}
|
|
3735
4161
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -3741,9 +4167,9 @@
|
|
|
3741
4167
|
//
|
|
3742
4168
|
const nextIterator = { offset: it.offset };
|
|
3743
4169
|
while (it.offset < totalBytes) {
|
|
3744
|
-
if (
|
|
4170
|
+
if (bytes[it.offset] === SWITCH_TO_STRUCTURE) {
|
|
3745
4171
|
nextIterator.offset = it.offset + 1;
|
|
3746
|
-
if ($root.refs.has(number(bytes, nextIterator))) {
|
|
4172
|
+
if ($root.refs.has(decode.number(bytes, nextIterator))) {
|
|
3747
4173
|
break;
|
|
3748
4174
|
}
|
|
3749
4175
|
}
|
|
@@ -3764,7 +4190,7 @@
|
|
|
3764
4190
|
let type;
|
|
3765
4191
|
if (bytes[it.offset] === TYPE_ID) {
|
|
3766
4192
|
it.offset++;
|
|
3767
|
-
const type_id = number(bytes, it);
|
|
4193
|
+
const type_id = decode.number(bytes, it);
|
|
3768
4194
|
type = this.context.get(type_id);
|
|
3769
4195
|
}
|
|
3770
4196
|
return type || defaultType;
|
|
@@ -3830,14 +4256,27 @@
|
|
|
3830
4256
|
super(...arguments);
|
|
3831
4257
|
this.types = new ArraySchema();
|
|
3832
4258
|
}
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
4259
|
+
/**
|
|
4260
|
+
* Encodes the TypeContext of an Encoder into a buffer.
|
|
4261
|
+
*
|
|
4262
|
+
* @param encoder Encoder instance
|
|
4263
|
+
* @param it
|
|
4264
|
+
* @returns
|
|
4265
|
+
*/
|
|
4266
|
+
static encode(encoder, it = { offset: 0 }) {
|
|
4267
|
+
const context = encoder.context;
|
|
3837
4268
|
const reflection = new Reflection();
|
|
3838
|
-
const
|
|
4269
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
4270
|
+
// rootType is usually the first schema passed to the Encoder
|
|
4271
|
+
// (unless it inherits from another schema)
|
|
4272
|
+
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4273
|
+
if (rootType > 0) {
|
|
4274
|
+
reflection.rootType = rootType;
|
|
4275
|
+
}
|
|
3839
4276
|
const buildType = (currentType, metadata) => {
|
|
3840
|
-
for (const
|
|
4277
|
+
for (const fieldIndex in metadata) {
|
|
4278
|
+
const index = Number(fieldIndex);
|
|
4279
|
+
const fieldName = metadata[index].name;
|
|
3841
4280
|
// skip fields from parent classes
|
|
3842
4281
|
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
3843
4282
|
continue;
|
|
@@ -3845,7 +4284,7 @@
|
|
|
3845
4284
|
const field = new ReflectionField();
|
|
3846
4285
|
field.name = fieldName;
|
|
3847
4286
|
let fieldType;
|
|
3848
|
-
const type = metadata[
|
|
4287
|
+
const type = metadata[index].type;
|
|
3849
4288
|
if (typeof (type) === "string") {
|
|
3850
4289
|
fieldType = type;
|
|
3851
4290
|
}
|
|
@@ -3887,65 +4326,335 @@
|
|
|
3887
4326
|
}
|
|
3888
4327
|
buildType(type, klass[Symbol.metadata]);
|
|
3889
4328
|
}
|
|
3890
|
-
const buf =
|
|
4329
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
3891
4330
|
return Buffer.from(buf, 0, it.offset);
|
|
3892
4331
|
}
|
|
4332
|
+
/**
|
|
4333
|
+
* Decodes the TypeContext from a buffer into a Decoder instance.
|
|
4334
|
+
*
|
|
4335
|
+
* @param bytes Reflection.encode() output
|
|
4336
|
+
* @param it
|
|
4337
|
+
* @returns Decoder instance
|
|
4338
|
+
*/
|
|
3893
4339
|
static decode(bytes, it) {
|
|
3894
4340
|
const reflection = new Reflection();
|
|
3895
4341
|
const reflectionDecoder = new Decoder(reflection);
|
|
3896
4342
|
reflectionDecoder.decode(bytes, it);
|
|
3897
|
-
const
|
|
3898
|
-
|
|
3899
|
-
|
|
3900
|
-
const
|
|
4343
|
+
const typeContext = new TypeContext();
|
|
4344
|
+
// 1st pass, initialize metadata + inheritance
|
|
4345
|
+
reflection.types.forEach((reflectionType) => {
|
|
4346
|
+
const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
4347
|
+
const schema = class _ extends parentClass {
|
|
3901
4348
|
};
|
|
3902
|
-
// const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
|
|
3903
|
-
const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
|
|
3904
|
-
Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
3905
4349
|
// register for inheritance support
|
|
3906
4350
|
TypeContext.register(schema);
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
|
|
3910
|
-
return types;
|
|
4351
|
+
// // for inheritance support
|
|
4352
|
+
// Metadata.initialize(schema);
|
|
4353
|
+
typeContext.add(schema, reflectionType.id);
|
|
3911
4354
|
}, {});
|
|
3912
|
-
|
|
3913
|
-
|
|
3914
|
-
const metadata = schemaType[Symbol.metadata];
|
|
3915
|
-
const parentKlass = reflection.types[reflectionType.extendsId];
|
|
3916
|
-
const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
|
|
4355
|
+
// define fields
|
|
4356
|
+
const addFields = (metadata, reflectionType, parentFieldIndex) => {
|
|
3917
4357
|
reflectionType.fields.forEach((field, i) => {
|
|
3918
4358
|
const fieldIndex = parentFieldIndex + i;
|
|
3919
4359
|
if (field.referencedType !== undefined) {
|
|
3920
4360
|
let fieldType = field.type;
|
|
3921
|
-
let refType =
|
|
4361
|
+
let refType = typeContext.get(field.referencedType);
|
|
3922
4362
|
// map or array of primitive type (-1)
|
|
3923
4363
|
if (!refType) {
|
|
3924
4364
|
const typeInfo = field.type.split(":");
|
|
3925
4365
|
fieldType = typeInfo[0];
|
|
3926
|
-
refType = typeInfo[1];
|
|
4366
|
+
refType = typeInfo[1]; // string
|
|
3927
4367
|
}
|
|
3928
4368
|
if (fieldType === "ref") {
|
|
3929
|
-
// type(refType)(schemaType.prototype, field.name);
|
|
3930
4369
|
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
3931
4370
|
}
|
|
3932
4371
|
else {
|
|
3933
|
-
// type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
|
|
3934
4372
|
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
3935
4373
|
}
|
|
3936
4374
|
}
|
|
3937
4375
|
else {
|
|
3938
|
-
// type(field.type as PrimitiveType)(schemaType.prototype, field.name);
|
|
3939
4376
|
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
3940
4377
|
}
|
|
3941
4378
|
});
|
|
4379
|
+
};
|
|
4380
|
+
// 2nd pass, set fields
|
|
4381
|
+
reflection.types.forEach((reflectionType) => {
|
|
4382
|
+
const schema = typeContext.get(reflectionType.id);
|
|
4383
|
+
// for inheritance support
|
|
4384
|
+
const metadata = Metadata.initialize(schema);
|
|
4385
|
+
const inheritedTypes = [];
|
|
4386
|
+
let parentType = reflectionType;
|
|
4387
|
+
do {
|
|
4388
|
+
inheritedTypes.push(parentType);
|
|
4389
|
+
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4390
|
+
} while (parentType);
|
|
4391
|
+
let parentFieldIndex = 0;
|
|
4392
|
+
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4393
|
+
// add fields from all inherited classes
|
|
4394
|
+
// TODO: refactor this to avoid adding fields from parent classes
|
|
4395
|
+
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4396
|
+
parentFieldIndex += reflectionType.fields.length;
|
|
4397
|
+
});
|
|
3942
4398
|
});
|
|
3943
|
-
|
|
4399
|
+
const state = new (typeContext.get(reflection.rootType || 0))();
|
|
4400
|
+
return new Decoder(state, typeContext);
|
|
3944
4401
|
}
|
|
3945
4402
|
}
|
|
3946
4403
|
__decorate([
|
|
3947
4404
|
type([ReflectionType])
|
|
3948
4405
|
], Reflection.prototype, "types", void 0);
|
|
4406
|
+
__decorate([
|
|
4407
|
+
type("number")
|
|
4408
|
+
], Reflection.prototype, "rootType", void 0);
|
|
4409
|
+
|
|
4410
|
+
function getDecoderStateCallbacks(decoder) {
|
|
4411
|
+
const $root = decoder.root;
|
|
4412
|
+
const callbacks = $root.callbacks;
|
|
4413
|
+
const onAddCalls = new WeakMap();
|
|
4414
|
+
let currentOnAddCallback;
|
|
4415
|
+
decoder.triggerChanges = function (allChanges) {
|
|
4416
|
+
const uniqueRefIds = new Set();
|
|
4417
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
4418
|
+
const change = allChanges[i];
|
|
4419
|
+
const refId = change.refId;
|
|
4420
|
+
const ref = change.ref;
|
|
4421
|
+
const $callbacks = callbacks[refId];
|
|
4422
|
+
if (!$callbacks) {
|
|
4423
|
+
continue;
|
|
4424
|
+
}
|
|
4425
|
+
//
|
|
4426
|
+
// trigger onRemove on child structure.
|
|
4427
|
+
//
|
|
4428
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4429
|
+
change.previousValue instanceof Schema) {
|
|
4430
|
+
const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
|
|
4431
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4432
|
+
deleteCallbacks[i]();
|
|
4433
|
+
}
|
|
4434
|
+
}
|
|
4435
|
+
if (ref instanceof Schema) {
|
|
4436
|
+
//
|
|
4437
|
+
// Handle schema instance
|
|
4438
|
+
//
|
|
4439
|
+
if (!uniqueRefIds.has(refId)) {
|
|
4440
|
+
// trigger onChange
|
|
4441
|
+
const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
|
|
4442
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4443
|
+
replaceCallbacks[i]();
|
|
4444
|
+
// try {
|
|
4445
|
+
// } catch (e) {
|
|
4446
|
+
// console.error(e);
|
|
4447
|
+
// }
|
|
4448
|
+
}
|
|
4449
|
+
}
|
|
4450
|
+
if ($callbacks.hasOwnProperty(change.field)) {
|
|
4451
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
4452
|
+
for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
|
|
4453
|
+
fieldCallbacks[i](change.value, change.previousValue);
|
|
4454
|
+
// try {
|
|
4455
|
+
// } catch (e) {
|
|
4456
|
+
// console.error(e);
|
|
4457
|
+
// }
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
}
|
|
4461
|
+
else {
|
|
4462
|
+
//
|
|
4463
|
+
// Handle collection of items
|
|
4464
|
+
//
|
|
4465
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
4466
|
+
//
|
|
4467
|
+
// FIXME: `previousValue` should always be available.
|
|
4468
|
+
//
|
|
4469
|
+
if (change.previousValue !== undefined) {
|
|
4470
|
+
// triger onRemove
|
|
4471
|
+
const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
|
|
4472
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4473
|
+
deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
|
|
4474
|
+
}
|
|
4475
|
+
}
|
|
4476
|
+
// Handle DELETE_AND_ADD operations
|
|
4477
|
+
if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
4478
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4479
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4480
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4481
|
+
}
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
|
|
4485
|
+
// triger onAdd
|
|
4486
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4487
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4488
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4489
|
+
}
|
|
4490
|
+
}
|
|
4491
|
+
// trigger onChange
|
|
4492
|
+
if (change.value !== change.previousValue) {
|
|
4493
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
4494
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4495
|
+
replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4496
|
+
}
|
|
4497
|
+
}
|
|
4498
|
+
}
|
|
4499
|
+
uniqueRefIds.add(refId);
|
|
4500
|
+
}
|
|
4501
|
+
};
|
|
4502
|
+
function getProxy(metadataOrType, context) {
|
|
4503
|
+
let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
|
|
4504
|
+
let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
|
|
4505
|
+
(metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
|
|
4506
|
+
if (metadata && !isCollection) {
|
|
4507
|
+
const onAddListen = function (ref, prop, callback, immediate) {
|
|
4508
|
+
// immediate trigger
|
|
4509
|
+
if (immediate &&
|
|
4510
|
+
context.instance[prop] !== undefined &&
|
|
4511
|
+
!onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
|
|
4512
|
+
) {
|
|
4513
|
+
callback(context.instance[prop], undefined);
|
|
4514
|
+
}
|
|
4515
|
+
return $root.addCallback($root.refIds.get(ref), prop, callback);
|
|
4516
|
+
};
|
|
4517
|
+
/**
|
|
4518
|
+
* Schema instances
|
|
4519
|
+
*/
|
|
4520
|
+
return new Proxy({
|
|
4521
|
+
listen: function listen(prop, callback, immediate = true) {
|
|
4522
|
+
if (context.instance) {
|
|
4523
|
+
return onAddListen(context.instance, prop, callback, immediate);
|
|
4524
|
+
}
|
|
4525
|
+
else {
|
|
4526
|
+
// collection instance not received yet
|
|
4527
|
+
let detachCallback = () => { };
|
|
4528
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4529
|
+
detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4530
|
+
});
|
|
4531
|
+
return () => detachCallback();
|
|
4532
|
+
}
|
|
4533
|
+
},
|
|
4534
|
+
onChange: function onChange(callback) {
|
|
4535
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
|
|
4536
|
+
},
|
|
4537
|
+
//
|
|
4538
|
+
// TODO: refactor `bindTo()` implementation.
|
|
4539
|
+
// There is room for improvement.
|
|
4540
|
+
//
|
|
4541
|
+
bindTo: function bindTo(targetObject, properties) {
|
|
4542
|
+
if (!properties) {
|
|
4543
|
+
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4544
|
+
}
|
|
4545
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
|
|
4546
|
+
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4547
|
+
});
|
|
4548
|
+
}
|
|
4549
|
+
}, {
|
|
4550
|
+
get(target, prop) {
|
|
4551
|
+
const metadataField = metadata[metadata[prop]];
|
|
4552
|
+
if (metadataField) {
|
|
4553
|
+
const instance = context.instance?.[prop];
|
|
4554
|
+
const onInstanceAvailable = ((callback) => {
|
|
4555
|
+
const unbind = $(context.instance).listen(prop, (value, _) => {
|
|
4556
|
+
callback(value, false);
|
|
4557
|
+
// FIXME: by "unbinding" the callback here,
|
|
4558
|
+
// it will not support when the server
|
|
4559
|
+
// re-instantiates the instance.
|
|
4560
|
+
//
|
|
4561
|
+
unbind?.();
|
|
4562
|
+
}, false);
|
|
4563
|
+
// has existing value
|
|
4564
|
+
if ($root.refIds.get(instance) !== undefined) {
|
|
4565
|
+
callback(instance, true);
|
|
4566
|
+
}
|
|
4567
|
+
});
|
|
4568
|
+
return getProxy(metadataField.type, {
|
|
4569
|
+
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
4570
|
+
instance: ($root.refIds.get(instance) && instance),
|
|
4571
|
+
parentInstance: context.instance,
|
|
4572
|
+
onInstanceAvailable,
|
|
4573
|
+
});
|
|
4574
|
+
}
|
|
4575
|
+
else {
|
|
4576
|
+
// accessing the function
|
|
4577
|
+
return target[prop];
|
|
4578
|
+
}
|
|
4579
|
+
},
|
|
4580
|
+
has(target, prop) { return metadata[prop] !== undefined; },
|
|
4581
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4582
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4583
|
+
});
|
|
4584
|
+
}
|
|
4585
|
+
else {
|
|
4586
|
+
/**
|
|
4587
|
+
* Collection instances
|
|
4588
|
+
*/
|
|
4589
|
+
const onAdd = function (ref, callback, immediate) {
|
|
4590
|
+
// Trigger callback on existing items
|
|
4591
|
+
if (immediate) {
|
|
4592
|
+
ref.forEach((v, k) => callback(v, k));
|
|
4593
|
+
}
|
|
4594
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
|
|
4595
|
+
onAddCalls.set(callback, true);
|
|
4596
|
+
currentOnAddCallback = callback;
|
|
4597
|
+
callback(value, key);
|
|
4598
|
+
onAddCalls.delete(callback);
|
|
4599
|
+
currentOnAddCallback = undefined;
|
|
4600
|
+
});
|
|
4601
|
+
};
|
|
4602
|
+
const onRemove = function (ref, callback) {
|
|
4603
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
|
|
4604
|
+
};
|
|
4605
|
+
return new Proxy({
|
|
4606
|
+
onAdd: function (callback, immediate = true) {
|
|
4607
|
+
//
|
|
4608
|
+
// https://github.com/colyseus/schema/issues/147
|
|
4609
|
+
// If parent instance has "onAdd" registered, avoid triggering immediate callback.
|
|
4610
|
+
//
|
|
4611
|
+
if (context.instance) {
|
|
4612
|
+
return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
|
|
4613
|
+
}
|
|
4614
|
+
else if (context.onInstanceAvailable) {
|
|
4615
|
+
// collection instance not received yet
|
|
4616
|
+
let detachCallback = () => { };
|
|
4617
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4618
|
+
detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4619
|
+
});
|
|
4620
|
+
return () => detachCallback();
|
|
4621
|
+
}
|
|
4622
|
+
},
|
|
4623
|
+
onRemove: function (callback) {
|
|
4624
|
+
if (context.onInstanceAvailable) {
|
|
4625
|
+
// collection instance not received yet
|
|
4626
|
+
let detachCallback = () => { };
|
|
4627
|
+
context.onInstanceAvailable((ref) => {
|
|
4628
|
+
detachCallback = onRemove(ref, callback);
|
|
4629
|
+
});
|
|
4630
|
+
return () => detachCallback();
|
|
4631
|
+
}
|
|
4632
|
+
else if (context.instance) {
|
|
4633
|
+
return onRemove(context.instance, callback);
|
|
4634
|
+
}
|
|
4635
|
+
},
|
|
4636
|
+
}, {
|
|
4637
|
+
get(target, prop) {
|
|
4638
|
+
if (!target[prop]) {
|
|
4639
|
+
throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
|
|
4640
|
+
}
|
|
4641
|
+
return target[prop];
|
|
4642
|
+
},
|
|
4643
|
+
has(target, prop) { return target[prop] !== undefined; },
|
|
4644
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4645
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4646
|
+
});
|
|
4647
|
+
}
|
|
4648
|
+
}
|
|
4649
|
+
function $(instance) {
|
|
4650
|
+
return getProxy(undefined, { instance });
|
|
4651
|
+
}
|
|
4652
|
+
return $;
|
|
4653
|
+
}
|
|
4654
|
+
|
|
4655
|
+
function getRawChangesCallback(decoder, callback) {
|
|
4656
|
+
decoder.triggerChanges = callback;
|
|
4657
|
+
}
|
|
3949
4658
|
|
|
3950
4659
|
class StateView {
|
|
3951
4660
|
constructor() {
|
|
@@ -3961,31 +4670,32 @@
|
|
|
3961
4670
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
3962
4671
|
* (This is used to force encoding a property, even if it was not changed)
|
|
3963
4672
|
*/
|
|
3964
|
-
this.changes =
|
|
4673
|
+
this.changes = {};
|
|
3965
4674
|
}
|
|
3966
4675
|
// TODO: allow to set multiple tags at once
|
|
3967
|
-
add(obj, tag = DEFAULT_VIEW_TAG) {
|
|
4676
|
+
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
3968
4677
|
if (!obj[$changes]) {
|
|
3969
4678
|
console.warn("StateView#add(), invalid object:", obj);
|
|
3970
4679
|
return this;
|
|
3971
4680
|
}
|
|
3972
|
-
|
|
3973
|
-
this.items.add(changeTree);
|
|
3974
|
-
// Add children of this ChangeTree to this view
|
|
3975
|
-
changeTree.forEachChild((change, _) => this.add(change.ref, tag));
|
|
3976
|
-
// FIXME: ArraySchema/MapSchema does not have metadata
|
|
4681
|
+
// FIXME: ArraySchema/MapSchema do not have metadata
|
|
3977
4682
|
const metadata = obj.constructor[Symbol.metadata];
|
|
3978
|
-
|
|
3979
|
-
|
|
3980
|
-
|
|
4683
|
+
const changeTree = obj[$changes];
|
|
4684
|
+
this.items.add(changeTree);
|
|
4685
|
+
// add parent ChangeTree's
|
|
4686
|
+
// - if it was invisible to this view
|
|
4687
|
+
// - if it were previously filtered out
|
|
4688
|
+
if (checkIncludeParent && changeTree.parent) {
|
|
4689
|
+
this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
|
|
4690
|
+
}
|
|
3981
4691
|
//
|
|
3982
4692
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
3983
4693
|
// be set (only the parent's changes are set)
|
|
3984
4694
|
//
|
|
3985
|
-
let changes = this.changes.
|
|
4695
|
+
let changes = this.changes[changeTree.refId];
|
|
3986
4696
|
if (changes === undefined) {
|
|
3987
|
-
changes =
|
|
3988
|
-
this.changes.
|
|
4697
|
+
changes = {};
|
|
4698
|
+
this.changes[changeTree.refId] = changes;
|
|
3989
4699
|
}
|
|
3990
4700
|
// set tag
|
|
3991
4701
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -4001,82 +4711,78 @@
|
|
|
4001
4711
|
tags = this.tags.get(changeTree);
|
|
4002
4712
|
}
|
|
4003
4713
|
tags.add(tag);
|
|
4004
|
-
// console.log("BY TAG:", tag);
|
|
4005
4714
|
// Ref: add tagged properties
|
|
4006
|
-
metadata?.[
|
|
4715
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4007
4716
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
4008
|
-
changes
|
|
4717
|
+
changes[index] = exports.OPERATION.ADD;
|
|
4009
4718
|
}
|
|
4010
4719
|
});
|
|
4011
4720
|
}
|
|
4012
4721
|
else {
|
|
4013
|
-
|
|
4014
|
-
|
|
4015
|
-
// metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
|
|
4016
|
-
// if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
4017
|
-
// changes.set(index, OPERATION.ADD);
|
|
4018
|
-
// }
|
|
4019
|
-
// });
|
|
4020
|
-
const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4722
|
+
const isInvisible = this.invisible.has(changeTree);
|
|
4723
|
+
const changeSet = (changeTree.filteredChanges !== undefined)
|
|
4021
4724
|
? changeTree.allFilteredChanges
|
|
4022
4725
|
: changeTree.allChanges;
|
|
4023
|
-
|
|
4024
|
-
|
|
4025
|
-
|
|
4026
|
-
|
|
4027
|
-
|
|
4028
|
-
|
|
4726
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4727
|
+
const index = changeSet.operations[i];
|
|
4728
|
+
if (index === undefined) {
|
|
4729
|
+
continue;
|
|
4730
|
+
} // skip "undefined" indexes
|
|
4731
|
+
const op = changeTree.indexedOperations[index] ?? exports.OPERATION.ADD;
|
|
4732
|
+
const tagAtIndex = metadata?.[index].tag;
|
|
4733
|
+
if ((isInvisible || // if "invisible", include all
|
|
4734
|
+
tagAtIndex === undefined || // "all change" with no tag
|
|
4735
|
+
tagAtIndex === tag // tagged property
|
|
4736
|
+
) &&
|
|
4737
|
+
op !== exports.OPERATION.DELETE) {
|
|
4738
|
+
changes[index] = op;
|
|
4029
4739
|
}
|
|
4030
4740
|
}
|
|
4031
4741
|
}
|
|
4032
|
-
//
|
|
4033
|
-
|
|
4034
|
-
|
|
4035
|
-
(
|
|
4036
|
-
|
|
4037
|
-
|
|
4742
|
+
// Add children of this ChangeTree to this view
|
|
4743
|
+
changeTree.forEachChild((change, index) => {
|
|
4744
|
+
// Do not ADD children that don't have the same tag
|
|
4745
|
+
if (metadata &&
|
|
4746
|
+
metadata[index].tag !== undefined &&
|
|
4747
|
+
metadata[index].tag !== tag) {
|
|
4748
|
+
return;
|
|
4749
|
+
}
|
|
4750
|
+
this.add(change.ref, tag, false);
|
|
4751
|
+
});
|
|
4038
4752
|
return this;
|
|
4039
4753
|
}
|
|
4040
|
-
addParent(changeTree, tag) {
|
|
4041
|
-
|
|
4042
|
-
|
|
4043
|
-
|
|
4754
|
+
addParent(changeTree, parentIndex, tag) {
|
|
4755
|
+
// view must have all "changeTree" parent tree
|
|
4756
|
+
this.items.add(changeTree);
|
|
4757
|
+
// add parent's parent
|
|
4758
|
+
const parentChangeTree = changeTree.parent?.[$changes];
|
|
4759
|
+
if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
|
|
4760
|
+
this.addParent(parentChangeTree, changeTree.parentIndex, tag);
|
|
4044
4761
|
}
|
|
4045
|
-
|
|
4046
|
-
|
|
4047
|
-
if (!this.invisible.has(parentChangeTree)) {
|
|
4048
|
-
// parent is already available, no need to add it!
|
|
4762
|
+
// parent is already available, no need to add it!
|
|
4763
|
+
if (!this.invisible.has(changeTree)) {
|
|
4049
4764
|
return;
|
|
4050
4765
|
}
|
|
4051
|
-
this.addParent(parentChangeTree, tag);
|
|
4052
4766
|
// add parent's tag properties
|
|
4053
|
-
if (
|
|
4054
|
-
let
|
|
4055
|
-
if (
|
|
4056
|
-
|
|
4057
|
-
this.changes.
|
|
4767
|
+
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4768
|
+
let changes = this.changes[changeTree.refId];
|
|
4769
|
+
if (changes === undefined) {
|
|
4770
|
+
changes = {};
|
|
4771
|
+
this.changes[changeTree.refId] = changes;
|
|
4058
4772
|
}
|
|
4059
|
-
// console.log("add parent change", {
|
|
4060
|
-
// parentIndex,
|
|
4061
|
-
// parentChanges,
|
|
4062
|
-
// parentChange: (
|
|
4063
|
-
// parentChangeTree.getChange(parentIndex) &&
|
|
4064
|
-
// OPERATION[parentChangeTree.getChange(parentIndex)]
|
|
4065
|
-
// ),
|
|
4066
|
-
// })
|
|
4067
4773
|
if (!this.tags) {
|
|
4068
4774
|
this.tags = new WeakMap();
|
|
4069
4775
|
}
|
|
4070
4776
|
let tags;
|
|
4071
|
-
if (!this.tags.has(
|
|
4777
|
+
if (!this.tags.has(changeTree)) {
|
|
4072
4778
|
tags = new Set();
|
|
4073
|
-
this.tags.set(
|
|
4779
|
+
this.tags.set(changeTree, tags);
|
|
4074
4780
|
}
|
|
4075
4781
|
else {
|
|
4076
|
-
tags = this.tags.get(
|
|
4782
|
+
tags = this.tags.get(changeTree);
|
|
4077
4783
|
}
|
|
4078
4784
|
tags.add(tag);
|
|
4079
|
-
|
|
4785
|
+
changes[parentIndex] = exports.OPERATION.ADD;
|
|
4080
4786
|
}
|
|
4081
4787
|
}
|
|
4082
4788
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4088,32 +4794,32 @@
|
|
|
4088
4794
|
this.items.delete(changeTree);
|
|
4089
4795
|
const ref = changeTree.ref;
|
|
4090
4796
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4091
|
-
let changes = this.changes.
|
|
4797
|
+
let changes = this.changes[changeTree.refId];
|
|
4092
4798
|
if (changes === undefined) {
|
|
4093
|
-
changes =
|
|
4094
|
-
this.changes.
|
|
4799
|
+
changes = {};
|
|
4800
|
+
this.changes[changeTree.refId] = changes;
|
|
4095
4801
|
}
|
|
4096
4802
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4097
4803
|
// parent is collection (Map/Array)
|
|
4098
4804
|
const parent = changeTree.parent;
|
|
4099
4805
|
if (!Metadata.isValidInstance(parent)) {
|
|
4100
4806
|
const parentChangeTree = parent[$changes];
|
|
4101
|
-
let changes = this.changes.
|
|
4807
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4102
4808
|
if (changes === undefined) {
|
|
4103
|
-
changes =
|
|
4104
|
-
this.changes.
|
|
4809
|
+
changes = {};
|
|
4810
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4105
4811
|
}
|
|
4106
4812
|
// DELETE / DELETE BY REF ID
|
|
4107
|
-
changes
|
|
4813
|
+
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
4108
4814
|
}
|
|
4109
4815
|
else {
|
|
4110
4816
|
// delete all "tagged" properties.
|
|
4111
|
-
metadata[
|
|
4817
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4112
4818
|
}
|
|
4113
4819
|
}
|
|
4114
4820
|
else {
|
|
4115
4821
|
// delete only tagged properties
|
|
4116
|
-
metadata[
|
|
4822
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4117
4823
|
}
|
|
4118
4824
|
// remove tag
|
|
4119
4825
|
if (this.tags && this.tags.has(changeTree)) {
|
|
@@ -4165,16 +4871,18 @@
|
|
|
4165
4871
|
exports.decode = decode;
|
|
4166
4872
|
exports.decodeKeyValueOperation = decodeKeyValueOperation;
|
|
4167
4873
|
exports.decodeSchemaOperation = decodeSchemaOperation;
|
|
4874
|
+
exports.defineCustomTypes = defineCustomTypes;
|
|
4168
4875
|
exports.defineTypes = defineTypes;
|
|
4169
4876
|
exports.deprecated = deprecated;
|
|
4170
4877
|
exports.dumpChanges = dumpChanges;
|
|
4171
4878
|
exports.encode = encode;
|
|
4172
4879
|
exports.encodeKeyValueOperation = encodeArray;
|
|
4173
4880
|
exports.encodeSchemaOperation = encodeSchemaOperation;
|
|
4881
|
+
exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
|
|
4882
|
+
exports.getRawChangesCallback = getRawChangesCallback;
|
|
4174
4883
|
exports.registerType = registerType;
|
|
4884
|
+
exports.schema = schema;
|
|
4175
4885
|
exports.type = type;
|
|
4176
4886
|
exports.view = view;
|
|
4177
4887
|
|
|
4178
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4179
|
-
|
|
4180
4888
|
}));
|