@colyseus/schema 3.0.0-alpha.5 → 3.0.0-alpha.50
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 +2234 -1510
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +2231 -1509
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +2233 -1509
- 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 +2 -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 +37 -19
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +6 -7
- package/lib/decoder/Decoder.js +14 -14
- 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 +75 -65
- 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 +9 -8
- package/lib/encoder/Encoder.js +134 -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 -17
- package/lib/encoding/encode.js +82 -68
- 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.js +1 -0
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +17 -15
- package/lib/types/custom/MapSchema.js +12 -4
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.js +1 -0
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/registry.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 +1 -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 +46 -18
- package/src/decoder/Decoder.ts +17 -15
- package/src/decoder/ReferenceTracker.ts +5 -3
- package/src/decoder/strategy/StateCallbacks.ts +153 -82
- package/src/encoder/ChangeTree.ts +282 -209
- package/src/encoder/EncodeOperation.ts +78 -78
- package/src/encoder/Encoder.ts +161 -96
- 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 +99 -65
- 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
|
-
this.root.add(this);
|
|
217
|
-
//
|
|
218
|
-
// At Schema initialization, the "root" structure might not be available
|
|
219
|
-
// yet, as it only does once the "Encoder" has been set up.
|
|
220
|
-
//
|
|
221
|
-
// So the "parent" may be already set without a "root".
|
|
222
|
-
//
|
|
223
|
-
this.checkIsFiltered(this.parent, this.parentIndex);
|
|
224
|
-
// unique refId for the ChangeTree.
|
|
225
|
-
this.ensureRefId();
|
|
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);
|
|
237
|
-
}
|
|
238
|
-
this.forEachChild((changeTree, _) => {
|
|
239
|
-
changeTree.setRoot(root);
|
|
240
|
-
});
|
|
241
|
-
// this.allChanges.forEach((_, index) => {
|
|
242
|
-
// const childRef = this.ref[$getByIndex](index);
|
|
243
|
-
// if (childRef && childRef[$changes]) {
|
|
244
|
-
// childRef[$changes].setRoot(root);
|
|
245
|
-
// }
|
|
246
|
-
// });
|
|
261
|
+
else if (!isFinite(value)) {
|
|
262
|
+
return number$1(bytes, (value > 0) ? Number.MAX_SAFE_INTEGER : -Number.MAX_SAFE_INTEGER, it);
|
|
247
263
|
}
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
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);
|
|
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
|
+
}
|
|
274
273
|
}
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
});
|
|
274
|
+
bytes[it.offset++] = 0xcb;
|
|
275
|
+
float64$1(bytes, value, it);
|
|
276
|
+
return 9;
|
|
279
277
|
}
|
|
280
|
-
|
|
281
|
-
//
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
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 >= -0x20) {
|
|
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 >= -0x80) {
|
|
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 >= -0x8000) {
|
|
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 >= -0x80000000) {
|
|
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,700 +376,1154 @@
|
|
|
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
|
-
if (c < 0x80) {
|
|
573
|
-
length += 1;
|
|
574
|
-
}
|
|
575
|
-
else if (c < 0x800) {
|
|
576
|
-
length += 2;
|
|
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;
|
|
577
393
|
}
|
|
578
|
-
|
|
579
|
-
|
|
394
|
+
if ((byte & 0xe0) === 0xc0) {
|
|
395
|
+
string += String.fromCharCode(((byte & 0x1f) << 6) |
|
|
396
|
+
(bytes[++i] & 0x3f));
|
|
397
|
+
continue;
|
|
580
398
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
399
|
+
if ((byte & 0xf0) === 0xe0) {
|
|
400
|
+
string += String.fromCharCode(((byte & 0x0f) << 12) |
|
|
401
|
+
((bytes[++i] & 0x3f) << 6) |
|
|
402
|
+
((bytes[++i] & 0x3f) << 0));
|
|
403
|
+
continue;
|
|
584
404
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
}
|
|
599
|
-
else if (c < 0xd800 || c >= 0xe000) {
|
|
600
|
-
view[it.offset++] = 0xe0 | (c >> 12);
|
|
601
|
-
view[it.offset++] = 0x80 | (c >> 6 & 0x3f);
|
|
602
|
-
view[it.offset++] = 0x80 | (c & 0x3f);
|
|
603
|
-
}
|
|
604
|
-
else {
|
|
605
|
-
i++;
|
|
606
|
-
c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
|
|
607
|
-
view[it.offset++] = 0xf0 | (c >> 18);
|
|
608
|
-
view[it.offset++] = 0x80 | (c >> 12 & 0x3f);
|
|
609
|
-
view[it.offset++] = 0x80 | (c >> 6 & 0x3f);
|
|
610
|
-
view[it.offset++] = 0x80 | (c & 0x3f);
|
|
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;
|
|
611
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));
|
|
612
422
|
}
|
|
423
|
+
it.offset += length;
|
|
424
|
+
return string;
|
|
613
425
|
}
|
|
614
|
-
function int8
|
|
615
|
-
bytes
|
|
426
|
+
function int8(bytes, it) {
|
|
427
|
+
return uint8(bytes, it) << 24 >> 24;
|
|
616
428
|
}
|
|
617
|
-
function uint8
|
|
618
|
-
bytes[it.offset++]
|
|
429
|
+
function uint8(bytes, it) {
|
|
430
|
+
return bytes[it.offset++];
|
|
619
431
|
}
|
|
620
|
-
function int16
|
|
621
|
-
bytes
|
|
622
|
-
bytes[it.offset++] = (value >> 8) & 255;
|
|
432
|
+
function int16(bytes, it) {
|
|
433
|
+
return uint16(bytes, it) << 16 >> 16;
|
|
623
434
|
}
|
|
624
|
-
function uint16
|
|
625
|
-
bytes[it.offset++]
|
|
626
|
-
bytes[it.offset++] = (value >> 8) & 255;
|
|
435
|
+
function uint16(bytes, it) {
|
|
436
|
+
return bytes[it.offset++] | bytes[it.offset++] << 8;
|
|
627
437
|
}
|
|
628
|
-
function int32
|
|
629
|
-
bytes[it.offset++]
|
|
630
|
-
bytes[it.offset++] = (value >> 8) & 255;
|
|
631
|
-
bytes[it.offset++] = (value >> 16) & 255;
|
|
632
|
-
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;
|
|
633
440
|
}
|
|
634
|
-
function uint32
|
|
635
|
-
|
|
636
|
-
const b3 = value >> 16;
|
|
637
|
-
const b2 = value >> 8;
|
|
638
|
-
const b1 = value;
|
|
639
|
-
bytes[it.offset++] = b1 & 255;
|
|
640
|
-
bytes[it.offset++] = b2 & 255;
|
|
641
|
-
bytes[it.offset++] = b3 & 255;
|
|
642
|
-
bytes[it.offset++] = b4 & 255;
|
|
441
|
+
function uint32(bytes, it) {
|
|
442
|
+
return int32(bytes, it) >>> 0;
|
|
643
443
|
}
|
|
644
|
-
function
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
uint32$1(bytes, low, it);
|
|
648
|
-
uint32$1(bytes, high, it);
|
|
444
|
+
function float32(bytes, it) {
|
|
445
|
+
_int32[0] = int32(bytes, it);
|
|
446
|
+
return _float32[0];
|
|
649
447
|
}
|
|
650
|
-
function
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
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];
|
|
655
452
|
}
|
|
656
|
-
function
|
|
657
|
-
|
|
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;
|
|
658
457
|
}
|
|
659
|
-
function
|
|
660
|
-
|
|
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;
|
|
661
462
|
}
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
_float32$1[0] = value;
|
|
667
|
-
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];
|
|
668
467
|
}
|
|
669
|
-
function
|
|
670
|
-
|
|
671
|
-
int32
|
|
672
|
-
|
|
468
|
+
function biguint64(bytes, it) {
|
|
469
|
+
_int32[0] = int32(bytes, it);
|
|
470
|
+
_int32[1] = int32(bytes, it);
|
|
471
|
+
return _uint64[0];
|
|
673
472
|
}
|
|
674
|
-
function boolean
|
|
675
|
-
bytes
|
|
473
|
+
function boolean(bytes, it) {
|
|
474
|
+
return uint8(bytes, it) > 0;
|
|
676
475
|
}
|
|
677
|
-
function string
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
let length = Buffer.byteLength(value, "utf8");
|
|
684
|
-
let size = 0;
|
|
685
|
-
// fixstr
|
|
686
|
-
if (length < 0x20) {
|
|
687
|
-
bytes[it.offset++] = length | 0xa0;
|
|
688
|
-
size = 1;
|
|
689
|
-
}
|
|
690
|
-
// str 8
|
|
691
|
-
else if (length < 0x100) {
|
|
692
|
-
bytes[it.offset++] = 0xd9;
|
|
693
|
-
bytes[it.offset++] = length % 255;
|
|
694
|
-
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;
|
|
695
482
|
}
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
bytes[it.offset++] = 0xda;
|
|
699
|
-
uint16$1(bytes, length, it);
|
|
700
|
-
size = 3;
|
|
483
|
+
else if (prefix === 0xd9) {
|
|
484
|
+
length = uint8(bytes, it);
|
|
701
485
|
}
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
bytes[it.offset++] = 0xdb;
|
|
705
|
-
uint32$1(bytes, length, it);
|
|
706
|
-
size = 5;
|
|
486
|
+
else if (prefix === 0xda) {
|
|
487
|
+
length = uint16(bytes, it);
|
|
707
488
|
}
|
|
708
|
-
else {
|
|
709
|
-
|
|
489
|
+
else if (prefix === 0xdb) {
|
|
490
|
+
length = uint32(bytes, it);
|
|
710
491
|
}
|
|
711
|
-
|
|
712
|
-
return size + length;
|
|
492
|
+
return utf8Read(bytes, it, length);
|
|
713
493
|
}
|
|
714
|
-
function number
|
|
715
|
-
|
|
716
|
-
|
|
494
|
+
function number(bytes, it) {
|
|
495
|
+
const prefix = bytes[it.offset++];
|
|
496
|
+
if (prefix < 0x80) {
|
|
497
|
+
// positive fixint
|
|
498
|
+
return prefix;
|
|
717
499
|
}
|
|
718
|
-
else if (
|
|
719
|
-
|
|
500
|
+
else if (prefix === 0xca) {
|
|
501
|
+
// float 32
|
|
502
|
+
return float32(bytes, it);
|
|
720
503
|
}
|
|
721
|
-
else if (
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
return 9;
|
|
725
|
-
// TODO: encode float 32?
|
|
726
|
-
// is it possible to differentiate between float32 / float64 here?
|
|
727
|
-
// // float 32
|
|
728
|
-
// bytes.push(0xca);
|
|
729
|
-
// writeFloat32(bytes, value);
|
|
730
|
-
// return 5;
|
|
504
|
+
else if (prefix === 0xcb) {
|
|
505
|
+
// float 64
|
|
506
|
+
return float64(bytes, it);
|
|
731
507
|
}
|
|
732
|
-
if (
|
|
733
|
-
// positive fixnum
|
|
734
|
-
if (value < 0x80) {
|
|
735
|
-
bytes[it.offset++] = value & 255; // uint8
|
|
736
|
-
return 1;
|
|
737
|
-
}
|
|
508
|
+
else if (prefix === 0xcc) {
|
|
738
509
|
// uint 8
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
return 2;
|
|
743
|
-
}
|
|
510
|
+
return uint8(bytes, it);
|
|
511
|
+
}
|
|
512
|
+
else if (prefix === 0xcd) {
|
|
744
513
|
// uint 16
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
return 3;
|
|
749
|
-
}
|
|
514
|
+
return uint16(bytes, it);
|
|
515
|
+
}
|
|
516
|
+
else if (prefix === 0xce) {
|
|
750
517
|
// uint 32
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
return 5;
|
|
755
|
-
}
|
|
518
|
+
return uint32(bytes, it);
|
|
519
|
+
}
|
|
520
|
+
else if (prefix === 0xcf) {
|
|
756
521
|
// uint 64
|
|
757
|
-
bytes
|
|
758
|
-
uint64$1(bytes, value, it);
|
|
759
|
-
return 9;
|
|
522
|
+
return uint64(bytes, it);
|
|
760
523
|
}
|
|
761
|
-
else {
|
|
762
|
-
// negative fixnum
|
|
763
|
-
if (value >= -0x20) {
|
|
764
|
-
bytes[it.offset++] = 0xe0 | (value + 0x20);
|
|
765
|
-
return 1;
|
|
766
|
-
}
|
|
524
|
+
else if (prefix === 0xd0) {
|
|
767
525
|
// int 8
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
return 2;
|
|
772
|
-
}
|
|
526
|
+
return int8(bytes, it);
|
|
527
|
+
}
|
|
528
|
+
else if (prefix === 0xd1) {
|
|
773
529
|
// int 16
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
return 3;
|
|
778
|
-
}
|
|
530
|
+
return int16(bytes, it);
|
|
531
|
+
}
|
|
532
|
+
else if (prefix === 0xd2) {
|
|
779
533
|
// int 32
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
return 5;
|
|
784
|
-
}
|
|
534
|
+
return int32(bytes, it);
|
|
535
|
+
}
|
|
536
|
+
else if (prefix === 0xd3) {
|
|
785
537
|
// int 64
|
|
786
|
-
bytes
|
|
787
|
-
|
|
788
|
-
|
|
538
|
+
return int64(bytes, it);
|
|
539
|
+
}
|
|
540
|
+
else if (prefix > 0xdf) {
|
|
541
|
+
// negative fixint
|
|
542
|
+
return (0xff - prefix + 1) * -1;
|
|
789
543
|
}
|
|
790
544
|
}
|
|
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);
|
|
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
|
+
};
|
|
791
576
|
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
int16: int16$1,
|
|
799
|
-
uint16: uint16$1,
|
|
800
|
-
int32: int32$1,
|
|
801
|
-
uint32: uint32$1,
|
|
802
|
-
int64: int64$1,
|
|
803
|
-
uint64: uint64$1,
|
|
804
|
-
float32: float32$1,
|
|
805
|
-
float64: float64$1,
|
|
806
|
-
writeFloat32: writeFloat32,
|
|
807
|
-
writeFloat64: writeFloat64,
|
|
808
|
-
boolean: boolean$1,
|
|
809
|
-
string: string$1,
|
|
810
|
-
number: number$1
|
|
811
|
-
});
|
|
812
|
-
|
|
813
|
-
class EncodeSchemaError extends Error {
|
|
814
|
-
}
|
|
815
|
-
function assertType(value, type, klass, field) {
|
|
816
|
-
let typeofTarget;
|
|
817
|
-
let allowNull = false;
|
|
818
|
-
switch (type) {
|
|
819
|
-
case "number":
|
|
820
|
-
case "int8":
|
|
821
|
-
case "uint8":
|
|
822
|
-
case "int16":
|
|
823
|
-
case "uint16":
|
|
824
|
-
case "int32":
|
|
825
|
-
case "uint32":
|
|
826
|
-
case "int64":
|
|
827
|
-
case "uint64":
|
|
828
|
-
case "float32":
|
|
829
|
-
case "float64":
|
|
830
|
-
typeofTarget = "number";
|
|
831
|
-
if (isNaN(value)) {
|
|
832
|
-
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
833
|
-
}
|
|
834
|
-
break;
|
|
835
|
-
case "string":
|
|
836
|
-
typeofTarget = "string";
|
|
837
|
-
allowNull = true;
|
|
838
|
-
break;
|
|
839
|
-
case "boolean":
|
|
840
|
-
// boolean is always encoded as true/false based on truthiness
|
|
841
|
-
return;
|
|
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;
|
|
842
583
|
}
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
584
|
+
if (definition.encode) {
|
|
585
|
+
encode[identifier] = definition.encode;
|
|
846
586
|
}
|
|
847
|
-
|
|
848
|
-
|
|
849
|
-
if (!(value instanceof type)) {
|
|
850
|
-
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
|
|
587
|
+
if (definition.decode) {
|
|
588
|
+
decode[identifier] = definition.decode;
|
|
851
589
|
}
|
|
852
590
|
}
|
|
853
|
-
|
|
854
|
-
|
|
855
|
-
assertType(value, type, klass, field);
|
|
856
|
-
const encodeFunc = encode[type];
|
|
857
|
-
if (encodeFunc) {
|
|
858
|
-
encodeFunc(bytes, value, it);
|
|
859
|
-
// encodeFunc(bytes, value);
|
|
860
|
-
}
|
|
861
|
-
else {
|
|
862
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
863
|
-
}
|
|
591
|
+
function getType(identifier) {
|
|
592
|
+
return registeredTypes[identifier];
|
|
864
593
|
}
|
|
865
|
-
function
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
assertInstanceType(value, type, ref, field);
|
|
869
|
-
//
|
|
870
|
-
// Encode refId for this instance.
|
|
871
|
-
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
872
|
-
//
|
|
873
|
-
number$1(bytes, value[$changes].refId, it);
|
|
874
|
-
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
875
|
-
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
876
|
-
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
877
|
-
}
|
|
878
|
-
}
|
|
879
|
-
else if (typeof (type) === "string") {
|
|
880
|
-
//
|
|
881
|
-
// Primitive values
|
|
882
|
-
//
|
|
883
|
-
encodePrimitiveType(type, bytes, value, ref, field, it);
|
|
884
|
-
}
|
|
885
|
-
else {
|
|
886
|
-
//
|
|
887
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
888
|
-
//
|
|
889
|
-
const definition = getType(Object.keys(type)[0]);
|
|
890
|
-
//
|
|
891
|
-
// ensure a ArraySchema has been provided
|
|
892
|
-
//
|
|
893
|
-
assertInstanceType(ref[field], definition.constructor, ref, field);
|
|
894
|
-
//
|
|
895
|
-
// Encode refId for this instance.
|
|
896
|
-
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
897
|
-
//
|
|
898
|
-
number$1(bytes, value[$changes].refId, it);
|
|
594
|
+
function defineCustomTypes(types) {
|
|
595
|
+
for (const identifier in types) {
|
|
596
|
+
registerType(identifier, types[identifier]);
|
|
899
597
|
}
|
|
598
|
+
return (t) => type(t);
|
|
900
599
|
}
|
|
901
|
-
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
905
|
-
|
|
906
|
-
|
|
907
|
-
|
|
908
|
-
|
|
909
|
-
|
|
910
|
-
|
|
911
|
-
|
|
912
|
-
|
|
913
|
-
|
|
914
|
-
|
|
915
|
-
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
encodeValue(encoder, bytes, ref, type, value, field, operation, it);
|
|
919
|
-
};
|
|
920
|
-
/**
|
|
921
|
-
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
922
|
-
* @private
|
|
923
|
-
*/
|
|
924
|
-
const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
|
|
925
|
-
const ref = changeTree.ref;
|
|
926
|
-
// encode operation
|
|
927
|
-
bytes[it.offset++] = operation & 255;
|
|
928
|
-
// custom operations
|
|
929
|
-
if (operation === exports.OPERATION.CLEAR) {
|
|
930
|
-
return;
|
|
931
|
-
}
|
|
932
|
-
// encode index
|
|
933
|
-
number$1(bytes, field, it);
|
|
934
|
-
// Do not encode value for DELETE operations
|
|
935
|
-
if (operation === exports.OPERATION.DELETE) {
|
|
936
|
-
return;
|
|
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
|
+
}
|
|
937
617
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
618
|
+
constructor(rootClass) {
|
|
619
|
+
this.types = {};
|
|
620
|
+
this.schemas = new Map();
|
|
621
|
+
this.hasFilters = false;
|
|
622
|
+
this.parentFiltered = {};
|
|
623
|
+
if (rootClass) {
|
|
943
624
|
//
|
|
944
|
-
//
|
|
625
|
+
// TODO:
|
|
626
|
+
// cache "discoverTypes" results for each rootClass
|
|
627
|
+
// to avoid re-discovering types for each new context/room
|
|
945
628
|
//
|
|
946
|
-
|
|
947
|
-
string$1(bytes, dynamicIndex, it);
|
|
629
|
+
this.discoverTypes(rootClass);
|
|
948
630
|
}
|
|
949
631
|
}
|
|
950
|
-
|
|
951
|
-
|
|
952
|
-
// TODO: inline this function call small performance gain
|
|
953
|
-
encodeValue(encoder, bytes, ref, type, value, field, operation, it);
|
|
954
|
-
};
|
|
955
|
-
/**
|
|
956
|
-
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
957
|
-
* @private
|
|
958
|
-
*/
|
|
959
|
-
const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
|
|
960
|
-
const ref = changeTree.ref;
|
|
961
|
-
if (hasView &&
|
|
962
|
-
operation === exports.OPERATION.DELETE &&
|
|
963
|
-
typeof (changeTree.getType(field)) !== "string") {
|
|
964
|
-
// encode delete by refId (array of schemas)
|
|
965
|
-
bytes[it.offset++] = exports.OPERATION.DELETE_BY_REFID;
|
|
966
|
-
const value = ref['tmpItems'][field];
|
|
967
|
-
const refId = value[$changes].refId;
|
|
968
|
-
number$1(bytes, refId, it);
|
|
969
|
-
return;
|
|
632
|
+
has(schema) {
|
|
633
|
+
return this.schemas.has(schema);
|
|
970
634
|
}
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
// custom operations
|
|
974
|
-
if (operation === exports.OPERATION.CLEAR) {
|
|
975
|
-
return;
|
|
635
|
+
get(typeid) {
|
|
636
|
+
return this.types[typeid];
|
|
976
637
|
}
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
|
|
980
|
-
|
|
981
|
-
|
|
638
|
+
add(schema, typeid = this.schemas.size) {
|
|
639
|
+
// skip if already registered
|
|
640
|
+
if (this.schemas.has(schema)) {
|
|
641
|
+
return false;
|
|
642
|
+
}
|
|
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);
|
|
649
|
+
}
|
|
650
|
+
this.schemas.set(schema, typeid);
|
|
651
|
+
return true;
|
|
982
652
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
// value: value?.toJSON(),
|
|
990
|
-
// items: ref.toJSON(),
|
|
991
|
-
// });
|
|
992
|
-
// TODO: inline this function call small performance gain
|
|
993
|
-
encodeValue(encoder, bytes, ref, type, value, field, operation, it);
|
|
994
|
-
};
|
|
995
|
-
|
|
996
|
-
/**
|
|
997
|
-
* Copyright (c) 2018 Endel Dreyer
|
|
998
|
-
* Copyright (c) 2014 Ion Drive Software Ltd.
|
|
999
|
-
*
|
|
1000
|
-
* Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
1001
|
-
* of this software and associated documentation files (the "Software"), to deal
|
|
1002
|
-
* in the Software without restriction, including without limitation the rights
|
|
1003
|
-
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
1004
|
-
* copies of the Software, and to permit persons to whom the Software is
|
|
1005
|
-
* furnished to do so, subject to the following conditions:
|
|
1006
|
-
*
|
|
1007
|
-
* The above copyright notice and this permission notice shall be included in all
|
|
1008
|
-
* copies or substantial portions of the Software.
|
|
1009
|
-
*
|
|
1010
|
-
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
1011
|
-
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
1012
|
-
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
1013
|
-
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
1014
|
-
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
1015
|
-
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
1016
|
-
* SOFTWARE
|
|
1017
|
-
*/
|
|
1018
|
-
function utf8Read(bytes, it, length) {
|
|
1019
|
-
var string = '', chr = 0;
|
|
1020
|
-
for (var i = it.offset, end = it.offset + length; i < end; i++) {
|
|
1021
|
-
var byte = bytes[i];
|
|
1022
|
-
if ((byte & 0x80) === 0x00) {
|
|
1023
|
-
string += String.fromCharCode(byte);
|
|
1024
|
-
continue;
|
|
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);
|
|
1025
659
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
continue;
|
|
660
|
+
// skip if already registered
|
|
661
|
+
if (!this.add(klass)) {
|
|
662
|
+
return;
|
|
1030
663
|
}
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
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;
|
|
1036
680
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
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);
|
|
1045
698
|
}
|
|
1046
699
|
else {
|
|
1047
|
-
|
|
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);
|
|
1048
706
|
}
|
|
1049
|
-
continue;
|
|
1050
707
|
}
|
|
1051
|
-
console.error('Invalid byte ' + byte.toString(16));
|
|
1052
|
-
// (do not throw error to avoid server/client from crashing due to hack attemps)
|
|
1053
|
-
// throw new Error('Invalid byte ' + byte.toString(16));
|
|
1054
708
|
}
|
|
1055
|
-
|
|
1056
|
-
|
|
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
|
-
|
|
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
|
+
}
|
|
1086
743
|
}
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
744
|
+
|
|
745
|
+
function getNormalizedType(type) {
|
|
746
|
+
return (Array.isArray(type))
|
|
747
|
+
? { array: type[0] }
|
|
748
|
+
: (typeof (type['type']) !== "undefined")
|
|
749
|
+
? type['type']
|
|
750
|
+
: type;
|
|
1091
751
|
}
|
|
1092
|
-
const
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
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
|
+
}
|
|
1098
972
|
}
|
|
1099
|
-
function
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
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];
|
|
1103
979
|
}
|
|
1104
|
-
function
|
|
1105
|
-
|
|
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
|
+
}
|
|
1106
984
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
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
|
+
}
|
|
1113
1014
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
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
|
+
}
|
|
1116
1033
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
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
|
+
}
|
|
1119
1064
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
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
|
+
}
|
|
1122
1085
|
}
|
|
1123
|
-
|
|
1124
|
-
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
|
|
1128
|
-
// fixstr
|
|
1129
|
-
(prefix < 0xc0 && prefix > 0xa0) ||
|
|
1130
|
-
// str 8
|
|
1131
|
-
prefix === 0xd9 ||
|
|
1132
|
-
// str 16
|
|
1133
|
-
prefix === 0xda ||
|
|
1134
|
-
// str 32
|
|
1135
|
-
prefix === 0xdb);
|
|
1136
|
-
}
|
|
1137
|
-
function number(bytes, it) {
|
|
1138
|
-
const prefix = bytes[it.offset++];
|
|
1139
|
-
if (prefix < 0x80) {
|
|
1140
|
-
// positive fixint
|
|
1141
|
-
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');
|
|
1142
1091
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
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);
|
|
1146
1395
|
}
|
|
1147
|
-
else if (
|
|
1148
|
-
//
|
|
1149
|
-
|
|
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
|
+
}
|
|
1150
1406
|
}
|
|
1151
|
-
else
|
|
1152
|
-
//
|
|
1153
|
-
|
|
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);
|
|
1154
1413
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
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;
|
|
1158
1425
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
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;
|
|
1162
1441
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
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;
|
|
1166
1447
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
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
|
+
}
|
|
1170
1460
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
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
|
+
}
|
|
1174
1494
|
}
|
|
1175
|
-
else
|
|
1176
|
-
|
|
1177
|
-
return int32(bytes, it);
|
|
1495
|
+
else {
|
|
1496
|
+
refOrIndex = field;
|
|
1178
1497
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
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;
|
|
1182
1504
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
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;
|
|
1186
1510
|
}
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
//
|
|
1191
|
-
//
|
|
1192
|
-
//
|
|
1193
|
-
//
|
|
1194
|
-
//
|
|
1195
|
-
//
|
|
1196
|
-
//
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
// int 32 - 0xd2
|
|
1200
|
-
// int 64 - 0xd3
|
|
1201
|
-
return (prefix < 0x80 ||
|
|
1202
|
-
(prefix >= 0xca && prefix <= 0xd3));
|
|
1203
|
-
}
|
|
1204
|
-
function arrayCheck(bytes, it) {
|
|
1205
|
-
return bytes[it.offset] < 0xa0;
|
|
1206
|
-
// const prefix = bytes[it.offset] ;
|
|
1207
|
-
// if (prefix < 0xa0) {
|
|
1208
|
-
// return prefix;
|
|
1209
|
-
// // array
|
|
1210
|
-
// } else if (prefix === 0xdc) {
|
|
1211
|
-
// it.offset += 2;
|
|
1212
|
-
// } else if (0xdd) {
|
|
1213
|
-
// it.offset += 4;
|
|
1214
|
-
// }
|
|
1215
|
-
// return prefix;
|
|
1216
|
-
}
|
|
1217
|
-
function switchStructureCheck(bytes, it) {
|
|
1218
|
-
return (
|
|
1219
|
-
// previous byte should be `SWITCH_TO_STRUCTURE`
|
|
1220
|
-
bytes[it.offset - 1] === SWITCH_TO_STRUCTURE &&
|
|
1221
|
-
// next byte should be a number
|
|
1222
|
-
(bytes[it.offset] < 0x80 || (bytes[it.offset] >= 0xca && bytes[it.offset] <= 0xd3)));
|
|
1223
|
-
}
|
|
1224
|
-
|
|
1225
|
-
var decode = /*#__PURE__*/Object.freeze({
|
|
1226
|
-
__proto__: null,
|
|
1227
|
-
utf8Read: utf8Read,
|
|
1228
|
-
int8: int8,
|
|
1229
|
-
uint8: uint8,
|
|
1230
|
-
int16: int16,
|
|
1231
|
-
uint16: uint16,
|
|
1232
|
-
int32: int32,
|
|
1233
|
-
uint32: uint32,
|
|
1234
|
-
float32: float32,
|
|
1235
|
-
float64: float64,
|
|
1236
|
-
int64: int64,
|
|
1237
|
-
uint64: uint64,
|
|
1238
|
-
readFloat32: readFloat32,
|
|
1239
|
-
readFloat64: readFloat64,
|
|
1240
|
-
boolean: boolean,
|
|
1241
|
-
string: string,
|
|
1242
|
-
stringCheck: stringCheck,
|
|
1243
|
-
number: number,
|
|
1244
|
-
numberCheck: numberCheck,
|
|
1245
|
-
arrayCheck: arrayCheck,
|
|
1246
|
-
switchStructureCheck: switchStructureCheck
|
|
1247
|
-
});
|
|
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
|
+
};
|
|
1248
1523
|
|
|
1249
1524
|
const DEFINITION_MISMATCH = -1;
|
|
1250
1525
|
function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
|
|
1251
|
-
const $root = decoder
|
|
1526
|
+
const $root = decoder.root;
|
|
1252
1527
|
const previousValue = ref[$getByIndex](index);
|
|
1253
1528
|
let value;
|
|
1254
1529
|
if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
@@ -1280,7 +1555,7 @@
|
|
|
1280
1555
|
}
|
|
1281
1556
|
if (operation === exports.OPERATION.DELETE) ;
|
|
1282
1557
|
else if (Schema.is(type)) {
|
|
1283
|
-
const refId = number(bytes, it);
|
|
1558
|
+
const refId = decode.number(bytes, it);
|
|
1284
1559
|
value = $root.refs.get(refId);
|
|
1285
1560
|
if (previousValue) {
|
|
1286
1561
|
const previousRefId = $root.refIds.get(previousValue);
|
|
@@ -1296,7 +1571,9 @@
|
|
|
1296
1571
|
if (!value) {
|
|
1297
1572
|
value = decoder.createInstanceOfType(childType);
|
|
1298
1573
|
}
|
|
1299
|
-
$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
|
+
));
|
|
1300
1577
|
}
|
|
1301
1578
|
}
|
|
1302
1579
|
else if (typeof (type) === "string") {
|
|
@@ -1307,7 +1584,7 @@
|
|
|
1307
1584
|
}
|
|
1308
1585
|
else {
|
|
1309
1586
|
const typeDef = getType(Object.keys(type)[0]);
|
|
1310
|
-
const refId = number(bytes, it);
|
|
1587
|
+
const refId = decode.number(bytes, it);
|
|
1311
1588
|
const valueRef = ($root.refs.has(refId))
|
|
1312
1589
|
? previousValue || $root.refs.get(refId)
|
|
1313
1590
|
: new typeDef.constructor();
|
|
@@ -1347,18 +1624,19 @@
|
|
|
1347
1624
|
}
|
|
1348
1625
|
const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
1349
1626
|
const first_byte = bytes[it.offset++];
|
|
1350
|
-
const metadata = ref
|
|
1627
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1351
1628
|
// "compressed" index + operation
|
|
1352
1629
|
const operation = (first_byte >> 6) << 6;
|
|
1353
1630
|
const index = first_byte % (operation || 255);
|
|
1354
1631
|
// skip early if field is not defined
|
|
1355
1632
|
const field = metadata[index];
|
|
1356
1633
|
if (field === undefined) {
|
|
1634
|
+
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
1357
1635
|
return DEFINITION_MISMATCH;
|
|
1358
1636
|
}
|
|
1359
|
-
const { value, previousValue } = decodeValue(decoder, operation, ref, index,
|
|
1637
|
+
const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
|
|
1360
1638
|
if (value !== null && value !== undefined) {
|
|
1361
|
-
ref[field] = value;
|
|
1639
|
+
ref[field.name] = value;
|
|
1362
1640
|
}
|
|
1363
1641
|
// add change
|
|
1364
1642
|
if (previousValue !== value) {
|
|
@@ -1366,7 +1644,7 @@
|
|
|
1366
1644
|
ref,
|
|
1367
1645
|
refId: decoder.currentRefId,
|
|
1368
1646
|
op: operation,
|
|
1369
|
-
field: field,
|
|
1647
|
+
field: field.name,
|
|
1370
1648
|
value,
|
|
1371
1649
|
previousValue,
|
|
1372
1650
|
});
|
|
@@ -1385,12 +1663,12 @@
|
|
|
1385
1663
|
ref.clear();
|
|
1386
1664
|
return;
|
|
1387
1665
|
}
|
|
1388
|
-
const index = number(bytes, it);
|
|
1666
|
+
const index = decode.number(bytes, it);
|
|
1389
1667
|
const type = ref[$childType];
|
|
1390
1668
|
let dynamicIndex;
|
|
1391
1669
|
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
1392
1670
|
if (typeof (ref['set']) === "function") {
|
|
1393
|
-
dynamicIndex = string(bytes, it); // MapSchema
|
|
1671
|
+
dynamicIndex = decode.string(bytes, it); // MapSchema
|
|
1394
1672
|
ref['setIndex'](index, dynamicIndex);
|
|
1395
1673
|
}
|
|
1396
1674
|
else {
|
|
@@ -1434,7 +1712,8 @@
|
|
|
1434
1712
|
};
|
|
1435
1713
|
const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
1436
1714
|
// "uncompressed" index + operation (array/map items)
|
|
1437
|
-
|
|
1715
|
+
let operation = bytes[it.offset++];
|
|
1716
|
+
let index;
|
|
1438
1717
|
if (operation === exports.OPERATION.CLEAR) {
|
|
1439
1718
|
//
|
|
1440
1719
|
// When decoding:
|
|
@@ -1445,11 +1724,15 @@
|
|
|
1445
1724
|
ref.clear();
|
|
1446
1725
|
return;
|
|
1447
1726
|
}
|
|
1727
|
+
else if (operation === exports.OPERATION.REVERSE) {
|
|
1728
|
+
ref.reverse();
|
|
1729
|
+
return;
|
|
1730
|
+
}
|
|
1448
1731
|
else if (operation === exports.OPERATION.DELETE_BY_REFID) {
|
|
1449
1732
|
// TODO: refactor here, try to follow same flow as below
|
|
1450
|
-
const refId = number(bytes, it);
|
|
1451
|
-
const previousValue = decoder
|
|
1452
|
-
|
|
1733
|
+
const refId = decode.number(bytes, it);
|
|
1734
|
+
const previousValue = decoder.root.refs.get(refId);
|
|
1735
|
+
index = ref.findIndex((value) => value === previousValue);
|
|
1453
1736
|
ref[$deleteByIndex](index);
|
|
1454
1737
|
allChanges.push({
|
|
1455
1738
|
ref,
|
|
@@ -1462,7 +1745,17 @@
|
|
|
1462
1745
|
});
|
|
1463
1746
|
return;
|
|
1464
1747
|
}
|
|
1465
|
-
|
|
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
|
+
}
|
|
1466
1759
|
const type = ref[$childType];
|
|
1467
1760
|
let dynamicIndex = index;
|
|
1468
1761
|
const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
|
|
@@ -1486,6 +1779,55 @@
|
|
|
1486
1779
|
}
|
|
1487
1780
|
};
|
|
1488
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
|
+
|
|
1489
1831
|
var _a$4, _b$4;
|
|
1490
1832
|
const DEFAULT_SORT = (a, b) => {
|
|
1491
1833
|
const A = a.toString();
|
|
@@ -1536,6 +1878,7 @@
|
|
|
1536
1878
|
const proxy = new Proxy(this, {
|
|
1537
1879
|
get: (obj, prop) => {
|
|
1538
1880
|
if (typeof (prop) !== "symbol" &&
|
|
1881
|
+
// FIXME: d8 accuses this as low performance
|
|
1539
1882
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1540
1883
|
) {
|
|
1541
1884
|
return this.items[prop];
|
|
@@ -1551,8 +1894,9 @@
|
|
|
1551
1894
|
}
|
|
1552
1895
|
else {
|
|
1553
1896
|
if (setValue[$changes]) {
|
|
1897
|
+
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1554
1898
|
if (obj.items[key] !== undefined) {
|
|
1555
|
-
if (setValue[$changes]
|
|
1899
|
+
if (setValue[$changes].isNew) {
|
|
1556
1900
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
1557
1901
|
}
|
|
1558
1902
|
else {
|
|
@@ -1564,7 +1908,7 @@
|
|
|
1564
1908
|
}
|
|
1565
1909
|
}
|
|
1566
1910
|
}
|
|
1567
|
-
else if (setValue[$changes]
|
|
1911
|
+
else if (setValue[$changes].isNew) {
|
|
1568
1912
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
|
|
1569
1913
|
}
|
|
1570
1914
|
}
|
|
@@ -1597,7 +1941,10 @@
|
|
|
1597
1941
|
}
|
|
1598
1942
|
});
|
|
1599
1943
|
this[$changes] = new ChangeTree(proxy);
|
|
1600
|
-
this.
|
|
1944
|
+
this[$changes].indexes = {};
|
|
1945
|
+
if (items.length > 0) {
|
|
1946
|
+
this.push(...items);
|
|
1947
|
+
}
|
|
1601
1948
|
return proxy;
|
|
1602
1949
|
}
|
|
1603
1950
|
set length(newLength) {
|
|
@@ -1616,14 +1963,19 @@
|
|
|
1616
1963
|
}
|
|
1617
1964
|
push(...values) {
|
|
1618
1965
|
let length = this.tmpItems.length;
|
|
1619
|
-
|
|
1620
|
-
|
|
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];
|
|
1621
1970
|
if (value === undefined || value === null) {
|
|
1971
|
+
// skip null values
|
|
1622
1972
|
return;
|
|
1623
1973
|
}
|
|
1624
|
-
|
|
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
|
+
}
|
|
1625
1978
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1626
|
-
// changeTree.indexes[length] = length;
|
|
1627
1979
|
this.items.push(value);
|
|
1628
1980
|
this.tmpItems.push(value);
|
|
1629
1981
|
//
|
|
@@ -1631,8 +1983,9 @@
|
|
|
1631
1983
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1632
1984
|
//
|
|
1633
1985
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1634
|
-
|
|
1635
|
-
|
|
1986
|
+
}
|
|
1987
|
+
// length++;
|
|
1988
|
+
// });
|
|
1636
1989
|
return length;
|
|
1637
1990
|
}
|
|
1638
1991
|
/**
|
|
@@ -1653,6 +2006,7 @@
|
|
|
1653
2006
|
}
|
|
1654
2007
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1655
2008
|
// this.tmpItems[index] = undefined;
|
|
2009
|
+
// this.tmpItems.pop();
|
|
1656
2010
|
this.deletedIndexes[index] = true;
|
|
1657
2011
|
return this.items.pop();
|
|
1658
2012
|
}
|
|
@@ -1717,9 +2071,12 @@
|
|
|
1717
2071
|
//
|
|
1718
2072
|
// TODO: do not use [$changes] at decoding time.
|
|
1719
2073
|
//
|
|
1720
|
-
changeTree.root
|
|
1721
|
-
|
|
1722
|
-
|
|
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
|
+
}
|
|
1723
2080
|
});
|
|
1724
2081
|
changeTree.discard(true);
|
|
1725
2082
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -1763,6 +2120,7 @@
|
|
|
1763
2120
|
const changeTree = this[$changes];
|
|
1764
2121
|
changeTree.delete(index);
|
|
1765
2122
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
2123
|
+
// this.deletedIndexes[index] = true;
|
|
1766
2124
|
return this.items.shift();
|
|
1767
2125
|
}
|
|
1768
2126
|
/**
|
|
@@ -1843,10 +2201,12 @@
|
|
|
1843
2201
|
changeTree.shiftChangeIndexes(items.length);
|
|
1844
2202
|
// new index
|
|
1845
2203
|
if (changeTree.isFiltered) {
|
|
1846
|
-
changeTree.filteredChanges
|
|
2204
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
2205
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1847
2206
|
}
|
|
1848
2207
|
else {
|
|
1849
|
-
changeTree.allChanges
|
|
2208
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2209
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1850
2210
|
}
|
|
1851
2211
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1852
2212
|
items.forEach((_, index) => {
|
|
@@ -1871,14 +2231,6 @@
|
|
|
1871
2231
|
lastIndexOf(searchElement, fromIndex = this.length - 1) {
|
|
1872
2232
|
return this.items.lastIndexOf(searchElement, fromIndex);
|
|
1873
2233
|
}
|
|
1874
|
-
/**
|
|
1875
|
-
* Determines whether all the members of an array satisfy the specified test.
|
|
1876
|
-
* @param callbackfn A function that accepts up to three arguments. The every method calls
|
|
1877
|
-
* the callbackfn function for each element in the array until the callbackfn returns a value
|
|
1878
|
-
* which is coercible to the Boolean value false, or until the end of the array.
|
|
1879
|
-
* @param thisArg An object to which the this keyword can refer in the callbackfn function.
|
|
1880
|
-
* If thisArg is omitted, undefined is used as the this value.
|
|
1881
|
-
*/
|
|
1882
2234
|
every(callbackfn, thisArg) {
|
|
1883
2235
|
return this.items.every(callbackfn, thisArg);
|
|
1884
2236
|
}
|
|
@@ -2157,6 +2509,7 @@
|
|
|
2157
2509
|
this.$items = new Map();
|
|
2158
2510
|
this.$indexes = new Map();
|
|
2159
2511
|
this[$changes] = new ChangeTree(this);
|
|
2512
|
+
this[$changes].indexes = {};
|
|
2160
2513
|
if (initialValues) {
|
|
2161
2514
|
if (initialValues instanceof Map ||
|
|
2162
2515
|
initialValues instanceof MapSchema) {
|
|
@@ -2183,6 +2536,9 @@
|
|
|
2183
2536
|
if (value === undefined || value === null) {
|
|
2184
2537
|
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
|
|
2185
2538
|
}
|
|
2539
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
2540
|
+
assertInstanceType(value, this[$childType], this, key);
|
|
2541
|
+
}
|
|
2186
2542
|
// Force "key" as string
|
|
2187
2543
|
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
|
|
2188
2544
|
key = key.toString();
|
|
@@ -2191,7 +2547,7 @@
|
|
|
2191
2547
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2192
2548
|
const index = (isReplace)
|
|
2193
2549
|
? changeTree.indexes[key]
|
|
2194
|
-
: changeTree.indexes[
|
|
2550
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2195
2551
|
let operation = (isReplace)
|
|
2196
2552
|
? exports.OPERATION.REPLACE
|
|
2197
2553
|
: exports.OPERATION.ADD;
|
|
@@ -2203,7 +2559,7 @@
|
|
|
2203
2559
|
if (!isReplace) {
|
|
2204
2560
|
this.$indexes.set(index, key);
|
|
2205
2561
|
changeTree.indexes[key] = index;
|
|
2206
|
-
changeTree.indexes[
|
|
2562
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2207
2563
|
}
|
|
2208
2564
|
else if (!isRef &&
|
|
2209
2565
|
this.$items.get(key) === value) {
|
|
@@ -2278,8 +2634,11 @@
|
|
|
2278
2634
|
}
|
|
2279
2635
|
[$onEncodeEnd]() {
|
|
2280
2636
|
const changeTree = this[$changes];
|
|
2281
|
-
const
|
|
2282
|
-
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];
|
|
2283
2642
|
if (operation === exports.OPERATION.DELETE) {
|
|
2284
2643
|
const index = this[$getByIndex](fieldIndex);
|
|
2285
2644
|
delete changeTree.indexes[index];
|
|
@@ -2307,109 +2666,22 @@
|
|
|
2307
2666
|
}
|
|
2308
2667
|
else {
|
|
2309
2668
|
// server-side
|
|
2310
|
-
cloned = new MapSchema();
|
|
2311
|
-
this.forEach((value, key) => {
|
|
2312
|
-
if (value[$changes]) {
|
|
2313
|
-
cloned.set(key, value['clone']());
|
|
2314
|
-
}
|
|
2315
|
-
else {
|
|
2316
|
-
cloned.set(key, value);
|
|
2317
|
-
}
|
|
2318
|
-
});
|
|
2319
|
-
}
|
|
2320
|
-
return cloned;
|
|
2321
|
-
}
|
|
2322
|
-
}
|
|
2323
|
-
registerType("map", { constructor: MapSchema });
|
|
2324
|
-
|
|
2325
|
-
const DEFAULT_VIEW_TAG = -1;
|
|
2326
|
-
class TypeContext {
|
|
2327
|
-
/**
|
|
2328
|
-
* For inheritance support
|
|
2329
|
-
* Keeps track of which classes extends which. (parent -> children)
|
|
2330
|
-
*/
|
|
2331
|
-
static { this.inheritedTypes = new Map(); }
|
|
2332
|
-
static register(target) {
|
|
2333
|
-
const parent = Object.getPrototypeOf(target);
|
|
2334
|
-
if (parent !== Schema) {
|
|
2335
|
-
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
2336
|
-
if (!inherits) {
|
|
2337
|
-
inherits = new Set();
|
|
2338
|
-
TypeContext.inheritedTypes.set(parent, inherits);
|
|
2339
|
-
}
|
|
2340
|
-
inherits.add(target);
|
|
2341
|
-
}
|
|
2342
|
-
}
|
|
2343
|
-
constructor(rootClass) {
|
|
2344
|
-
this.types = {};
|
|
2345
|
-
this.schemas = new Map();
|
|
2346
|
-
this.hasFilters = false;
|
|
2347
|
-
if (rootClass) {
|
|
2348
|
-
this.discoverTypes(rootClass);
|
|
2349
|
-
}
|
|
2350
|
-
}
|
|
2351
|
-
has(schema) {
|
|
2352
|
-
return this.schemas.has(schema);
|
|
2353
|
-
}
|
|
2354
|
-
get(typeid) {
|
|
2355
|
-
return this.types[typeid];
|
|
2356
|
-
}
|
|
2357
|
-
add(schema, typeid = this.schemas.size) {
|
|
2358
|
-
// skip if already registered
|
|
2359
|
-
if (this.schemas.has(schema)) {
|
|
2360
|
-
return false;
|
|
2361
|
-
}
|
|
2362
|
-
this.types[typeid] = schema;
|
|
2363
|
-
this.schemas.set(schema, typeid);
|
|
2364
|
-
return true;
|
|
2365
|
-
}
|
|
2366
|
-
getTypeId(klass) {
|
|
2367
|
-
return this.schemas.get(klass);
|
|
2368
|
-
}
|
|
2369
|
-
discoverTypes(klass) {
|
|
2370
|
-
if (!this.add(klass)) {
|
|
2371
|
-
return;
|
|
2372
|
-
}
|
|
2373
|
-
// add classes inherited from this base class
|
|
2374
|
-
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
2375
|
-
this.discoverTypes(child);
|
|
2376
|
-
});
|
|
2377
|
-
// skip if no fields are defined for this class.
|
|
2378
|
-
if (klass[Symbol.metadata] === undefined) {
|
|
2379
|
-
klass[Symbol.metadata] = {};
|
|
2380
|
-
}
|
|
2381
|
-
// const metadata = Metadata.getFor(klass);
|
|
2382
|
-
const metadata = klass[Symbol.metadata];
|
|
2383
|
-
// if any schema/field has filters, mark "context" as having filters.
|
|
2384
|
-
if (metadata[-2]) {
|
|
2385
|
-
this.hasFilters = true;
|
|
2386
|
-
}
|
|
2387
|
-
for (const field in metadata) {
|
|
2388
|
-
const fieldType = metadata[field].type;
|
|
2389
|
-
if (typeof (fieldType) === "string") {
|
|
2390
|
-
continue;
|
|
2391
|
-
}
|
|
2392
|
-
if (Array.isArray(fieldType)) {
|
|
2393
|
-
const type = fieldType[0];
|
|
2394
|
-
if (type === "string") {
|
|
2395
|
-
continue;
|
|
2669
|
+
cloned = new MapSchema();
|
|
2670
|
+
this.forEach((value, key) => {
|
|
2671
|
+
if (value[$changes]) {
|
|
2672
|
+
cloned.set(key, value['clone']());
|
|
2396
2673
|
}
|
|
2397
|
-
|
|
2398
|
-
|
|
2399
|
-
else if (typeof (fieldType) === "function") {
|
|
2400
|
-
this.discoverTypes(fieldType);
|
|
2401
|
-
}
|
|
2402
|
-
else {
|
|
2403
|
-
const type = Object.values(fieldType)[0];
|
|
2404
|
-
// skip primitive types
|
|
2405
|
-
if (typeof (type) === "string") {
|
|
2406
|
-
continue;
|
|
2674
|
+
else {
|
|
2675
|
+
cloned.set(key, value);
|
|
2407
2676
|
}
|
|
2408
|
-
|
|
2409
|
-
}
|
|
2677
|
+
});
|
|
2410
2678
|
}
|
|
2679
|
+
return cloned;
|
|
2411
2680
|
}
|
|
2412
2681
|
}
|
|
2682
|
+
registerType("map", { constructor: MapSchema });
|
|
2683
|
+
|
|
2684
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
2413
2685
|
/**
|
|
2414
2686
|
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
2415
2687
|
*
|
|
@@ -2436,8 +2708,8 @@
|
|
|
2436
2708
|
// // detect index for this field, considering inheritance
|
|
2437
2709
|
// //
|
|
2438
2710
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2439
|
-
// let fieldIndex: number = context.metadata[
|
|
2440
|
-
// ?? (parent && parent[
|
|
2711
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2712
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2441
2713
|
// ?? -1; // no fields defined
|
|
2442
2714
|
// fieldIndex++;
|
|
2443
2715
|
// if (
|
|
@@ -2557,18 +2829,20 @@
|
|
|
2557
2829
|
const constructor = target.constructor;
|
|
2558
2830
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2559
2831
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2832
|
+
// TODO: use Metadata.initialize()
|
|
2560
2833
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2561
|
-
|
|
2562
|
-
|
|
2563
|
-
|
|
2564
|
-
|
|
2565
|
-
|
|
2566
|
-
|
|
2567
|
-
|
|
2568
|
-
|
|
2569
|
-
|
|
2570
|
-
|
|
2571
|
-
}
|
|
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
|
+
// }
|
|
2572
2846
|
Metadata.setTag(metadata, fieldName, tag);
|
|
2573
2847
|
};
|
|
2574
2848
|
}
|
|
@@ -2582,17 +2856,17 @@
|
|
|
2582
2856
|
TypeContext.register(constructor);
|
|
2583
2857
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2584
2858
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2585
|
-
const metadata =
|
|
2586
|
-
let fieldIndex;
|
|
2859
|
+
const metadata = Metadata.initialize(constructor);
|
|
2860
|
+
let fieldIndex = metadata[field];
|
|
2587
2861
|
/**
|
|
2588
2862
|
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
2589
2863
|
*/
|
|
2590
|
-
if (metadata[
|
|
2591
|
-
if (metadata[
|
|
2864
|
+
if (metadata[fieldIndex] !== undefined) {
|
|
2865
|
+
if (metadata[fieldIndex].deprecated) {
|
|
2592
2866
|
// do not create accessors for deprecated properties.
|
|
2593
2867
|
return;
|
|
2594
2868
|
}
|
|
2595
|
-
else if (metadata[
|
|
2869
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
2596
2870
|
// trying to define same property multiple times across inheritance.
|
|
2597
2871
|
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
2598
2872
|
try {
|
|
@@ -2603,16 +2877,13 @@
|
|
|
2603
2877
|
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
2604
2878
|
}
|
|
2605
2879
|
}
|
|
2606
|
-
else {
|
|
2607
|
-
fieldIndex = metadata[field].index;
|
|
2608
|
-
}
|
|
2609
2880
|
}
|
|
2610
2881
|
else {
|
|
2611
2882
|
//
|
|
2612
2883
|
// detect index for this field, considering inheritance
|
|
2613
2884
|
//
|
|
2614
|
-
fieldIndex = metadata[
|
|
2615
|
-
?? (parentMetadata && parentMetadata[
|
|
2885
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2886
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2616
2887
|
?? -1; // no fields defined
|
|
2617
2888
|
fieldIndex++;
|
|
2618
2889
|
}
|
|
@@ -2631,15 +2902,15 @@
|
|
|
2631
2902
|
const childType = (complexTypeKlass)
|
|
2632
2903
|
? Object.values(type)[0]
|
|
2633
2904
|
: type;
|
|
2634
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass
|
|
2905
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
2635
2906
|
}
|
|
2636
2907
|
};
|
|
2637
2908
|
}
|
|
2638
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass
|
|
2909
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
2639
2910
|
return {
|
|
2640
2911
|
get: function () { return this[fieldCached]; },
|
|
2641
2912
|
set: function (value) {
|
|
2642
|
-
const previousValue = this[fieldCached]
|
|
2913
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2643
2914
|
// skip if value is the same as cached.
|
|
2644
2915
|
if (value === previousValue) {
|
|
2645
2916
|
return;
|
|
@@ -2657,22 +2928,27 @@
|
|
|
2657
2928
|
}
|
|
2658
2929
|
value[$childType] = type;
|
|
2659
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];
|
|
2660
2938
|
//
|
|
2661
2939
|
// Replacing existing "ref", remove it from root.
|
|
2662
2940
|
// TODO: if there are other references to this instance, we should not remove it from root.
|
|
2663
2941
|
//
|
|
2664
2942
|
if (previousValue !== undefined && previousValue[$changes]) {
|
|
2665
|
-
|
|
2943
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
2666
2944
|
}
|
|
2667
2945
|
// flag the change for encoding.
|
|
2668
|
-
this.constructor[$track](
|
|
2946
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
2669
2947
|
//
|
|
2670
2948
|
// call setParent() recursively for this and its child
|
|
2671
2949
|
// structures.
|
|
2672
2950
|
//
|
|
2673
|
-
|
|
2674
|
-
value[$changes].setParent(this, this[$changes].root, metadata[field].index);
|
|
2675
|
-
}
|
|
2951
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
2676
2952
|
}
|
|
2677
2953
|
else if (previousValue !== undefined) {
|
|
2678
2954
|
//
|
|
@@ -2699,20 +2975,22 @@
|
|
|
2699
2975
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2700
2976
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2701
2977
|
const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
2702
|
-
|
|
2703
|
-
|
|
2704
|
-
|
|
2705
|
-
|
|
2706
|
-
|
|
2707
|
-
|
|
2708
|
-
|
|
2709
|
-
|
|
2710
|
-
|
|
2711
|
-
|
|
2712
|
-
}
|
|
2713
|
-
|
|
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;
|
|
2714
2991
|
if (throws) {
|
|
2715
|
-
metadata[
|
|
2992
|
+
metadata[$descriptors] ??= {};
|
|
2993
|
+
metadata[$descriptors][field] = {
|
|
2716
2994
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
2717
2995
|
set: function (value) { },
|
|
2718
2996
|
enumerable: false,
|
|
@@ -2720,8 +2998,8 @@
|
|
|
2720
2998
|
};
|
|
2721
2999
|
}
|
|
2722
3000
|
// flag metadata[field] as non-enumerable
|
|
2723
|
-
Object.defineProperty(metadata,
|
|
2724
|
-
value: metadata[
|
|
3001
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
3002
|
+
value: metadata[fieldIndex],
|
|
2725
3003
|
enumerable: false,
|
|
2726
3004
|
configurable: true
|
|
2727
3005
|
});
|
|
@@ -2733,6 +3011,37 @@
|
|
|
2733
3011
|
}
|
|
2734
3012
|
return target;
|
|
2735
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
|
+
}
|
|
2736
3045
|
|
|
2737
3046
|
function getIndent(level) {
|
|
2738
3047
|
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
@@ -2743,15 +3052,18 @@
|
|
|
2743
3052
|
ops: {},
|
|
2744
3053
|
refs: []
|
|
2745
3054
|
};
|
|
2746
|
-
$root.changes
|
|
3055
|
+
// for (const refId in $root.changes) {
|
|
3056
|
+
$root.changes.forEach(changeTree => {
|
|
3057
|
+
const changes = changeTree.indexedOperations;
|
|
2747
3058
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2748
|
-
|
|
3059
|
+
for (const index in changes) {
|
|
3060
|
+
const op = changes[index];
|
|
2749
3061
|
const opName = exports.OPERATION[op];
|
|
2750
3062
|
if (!dump.ops[opName]) {
|
|
2751
3063
|
dump.ops[opName] = 0;
|
|
2752
3064
|
}
|
|
2753
3065
|
dump.ops[exports.OPERATION[op]]++;
|
|
2754
|
-
}
|
|
3066
|
+
}
|
|
2755
3067
|
});
|
|
2756
3068
|
return dump;
|
|
2757
3069
|
}
|
|
@@ -2777,6 +3089,7 @@
|
|
|
2777
3089
|
class Schema {
|
|
2778
3090
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2779
3091
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
3092
|
+
// public [$changes]: ChangeTree;
|
|
2780
3093
|
/**
|
|
2781
3094
|
* Assign the property descriptors required to track changes on this instance.
|
|
2782
3095
|
* @param instance
|
|
@@ -2787,35 +3100,7 @@
|
|
|
2787
3100
|
enumerable: false,
|
|
2788
3101
|
writable: true
|
|
2789
3102
|
});
|
|
2790
|
-
|
|
2791
|
-
// Define property descriptors
|
|
2792
|
-
for (const field in metadata) {
|
|
2793
|
-
if (metadata[field].descriptor) {
|
|
2794
|
-
// for encoder
|
|
2795
|
-
Object.defineProperty(instance, `_${field}`, {
|
|
2796
|
-
value: undefined,
|
|
2797
|
-
writable: true,
|
|
2798
|
-
enumerable: false,
|
|
2799
|
-
configurable: true,
|
|
2800
|
-
});
|
|
2801
|
-
Object.defineProperty(instance, field, metadata[field].descriptor);
|
|
2802
|
-
}
|
|
2803
|
-
else {
|
|
2804
|
-
// for decoder
|
|
2805
|
-
Object.defineProperty(instance, field, {
|
|
2806
|
-
value: undefined,
|
|
2807
|
-
writable: true,
|
|
2808
|
-
enumerable: true,
|
|
2809
|
-
configurable: true,
|
|
2810
|
-
});
|
|
2811
|
-
}
|
|
2812
|
-
// Object.defineProperty(instance, field, {
|
|
2813
|
-
// ...instance.constructor[Symbol.metadata][field].descriptor
|
|
2814
|
-
// });
|
|
2815
|
-
// if (args[0]?.hasOwnProperty(field)) {
|
|
2816
|
-
// instance[field] = args[0][field];
|
|
2817
|
-
// }
|
|
2818
|
-
}
|
|
3103
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2819
3104
|
}
|
|
2820
3105
|
static is(type) {
|
|
2821
3106
|
return typeof (type[Symbol.metadata]) === "object";
|
|
@@ -2839,7 +3124,7 @@
|
|
|
2839
3124
|
*/
|
|
2840
3125
|
static [$filter](ref, index, view) {
|
|
2841
3126
|
const metadata = ref.constructor[Symbol.metadata];
|
|
2842
|
-
const tag = metadata[
|
|
3127
|
+
const tag = metadata[index]?.tag;
|
|
2843
3128
|
if (view === undefined) {
|
|
2844
3129
|
// shared pass/encode: encode if doesn't have a tag
|
|
2845
3130
|
return tag === undefined;
|
|
@@ -2860,12 +3145,16 @@
|
|
|
2860
3145
|
}
|
|
2861
3146
|
// allow inherited classes to have a constructor
|
|
2862
3147
|
constructor(...args) {
|
|
3148
|
+
//
|
|
3149
|
+
// inline
|
|
3150
|
+
// Schema.initialize(this);
|
|
3151
|
+
//
|
|
2863
3152
|
Schema.initialize(this);
|
|
2864
3153
|
//
|
|
2865
3154
|
// Assign initial values
|
|
2866
3155
|
//
|
|
2867
3156
|
if (args[0]) {
|
|
2868
|
-
|
|
3157
|
+
Object.assign(this, args[0]);
|
|
2869
3158
|
}
|
|
2870
3159
|
}
|
|
2871
3160
|
assign(props) {
|
|
@@ -2879,7 +3168,8 @@
|
|
|
2879
3168
|
* @param operation OPERATION to perform (detected automatically)
|
|
2880
3169
|
*/
|
|
2881
3170
|
setDirty(property, operation) {
|
|
2882
|
-
this
|
|
3171
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3172
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
2883
3173
|
}
|
|
2884
3174
|
clone() {
|
|
2885
3175
|
const cloned = new (this.constructor);
|
|
@@ -2888,7 +3178,9 @@
|
|
|
2888
3178
|
// TODO: clone all properties, not only annotated ones
|
|
2889
3179
|
//
|
|
2890
3180
|
// for (const field in this) {
|
|
2891
|
-
for (const
|
|
3181
|
+
for (const fieldIndex in metadata) {
|
|
3182
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
3183
|
+
const field = metadata[fieldIndex].name;
|
|
2892
3184
|
if (typeof (this[field]) === "object" &&
|
|
2893
3185
|
typeof (this[field]?.clone) === "function") {
|
|
2894
3186
|
// deep clone
|
|
@@ -2902,10 +3194,11 @@
|
|
|
2902
3194
|
return cloned;
|
|
2903
3195
|
}
|
|
2904
3196
|
toJSON() {
|
|
2905
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
2906
3197
|
const obj = {};
|
|
2907
|
-
|
|
2908
|
-
|
|
3198
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3199
|
+
for (const index in metadata) {
|
|
3200
|
+
const field = metadata[index];
|
|
3201
|
+
const fieldName = field.name;
|
|
2909
3202
|
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
2910
3203
|
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
2911
3204
|
? this[fieldName]['toJSON']()
|
|
@@ -2918,18 +3211,27 @@
|
|
|
2918
3211
|
this[$changes].discardAll();
|
|
2919
3212
|
}
|
|
2920
3213
|
[$getByIndex](index) {
|
|
2921
|
-
|
|
3214
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3215
|
+
return this[metadata[index].name];
|
|
2922
3216
|
}
|
|
2923
3217
|
[$deleteByIndex](index) {
|
|
2924
|
-
this
|
|
3218
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3219
|
+
this[metadata[index].name] = undefined;
|
|
2925
3220
|
}
|
|
2926
|
-
|
|
3221
|
+
/**
|
|
3222
|
+
* Inspect the `refId` of all Schema instances in the tree. Optionally display the contents of the instance.
|
|
3223
|
+
*
|
|
3224
|
+
* @param instance Schema instance
|
|
3225
|
+
* @param showContents display JSON contents of the instance
|
|
3226
|
+
* @returns
|
|
3227
|
+
*/
|
|
3228
|
+
static debugRefIds(instance, showContents = false, level = 0) {
|
|
2927
3229
|
const ref = instance;
|
|
2928
3230
|
const changeTree = ref[$changes];
|
|
2929
|
-
const contents = (
|
|
3231
|
+
const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2930
3232
|
let output = "";
|
|
2931
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
2932
|
-
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref,
|
|
3233
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
3234
|
+
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, showContents, level + 1));
|
|
2933
3235
|
return output;
|
|
2934
3236
|
}
|
|
2935
3237
|
/**
|
|
@@ -2946,30 +3248,40 @@
|
|
|
2946
3248
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
2947
3249
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
2948
3250
|
function dumpChangeSet(changeSet) {
|
|
2949
|
-
|
|
2950
|
-
.
|
|
2951
|
-
.forEach((
|
|
3251
|
+
changeSet.operations
|
|
3252
|
+
.filter(op => op)
|
|
3253
|
+
.forEach((index) => {
|
|
3254
|
+
const operation = changeTree.indexedOperations[index];
|
|
3255
|
+
console.log({ index, operation });
|
|
3256
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3257
|
+
});
|
|
2952
3258
|
}
|
|
2953
3259
|
dumpChangeSet(changeSet);
|
|
2954
3260
|
// display filtered changes
|
|
2955
|
-
if (!isEncodeAll &&
|
|
3261
|
+
if (!isEncodeAll &&
|
|
3262
|
+
changeTree.filteredChanges &&
|
|
3263
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
2956
3264
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
2957
3265
|
dumpChangeSet(changeTree.filteredChanges);
|
|
2958
3266
|
}
|
|
2959
3267
|
// display filtered changes
|
|
2960
|
-
if (isEncodeAll &&
|
|
3268
|
+
if (isEncodeAll &&
|
|
3269
|
+
changeTree.allFilteredChanges &&
|
|
3270
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
2961
3271
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
2962
3272
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
2963
3273
|
}
|
|
2964
3274
|
return output;
|
|
2965
3275
|
}
|
|
2966
|
-
static debugChangesDeep(ref) {
|
|
3276
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
2967
3277
|
let output = "";
|
|
2968
3278
|
const rootChangeTree = ref[$changes];
|
|
3279
|
+
const root = rootChangeTree.root;
|
|
2969
3280
|
const changeTrees = new Map();
|
|
2970
|
-
|
|
3281
|
+
const instanceRefIds = [];
|
|
2971
3282
|
let totalOperations = 0;
|
|
2972
|
-
for (const [
|
|
3283
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3284
|
+
const changeTree = root.changeTrees[refId];
|
|
2973
3285
|
let includeChangeTree = false;
|
|
2974
3286
|
let parentChangeTrees = [];
|
|
2975
3287
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -2987,14 +3299,14 @@
|
|
|
2987
3299
|
}
|
|
2988
3300
|
}
|
|
2989
3301
|
if (includeChangeTree) {
|
|
2990
|
-
|
|
2991
|
-
totalOperations += changes.
|
|
3302
|
+
instanceRefIds.push(changeTree.refId);
|
|
3303
|
+
totalOperations += Object.keys(changes).length;
|
|
2992
3304
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
2993
3305
|
}
|
|
2994
3306
|
}
|
|
2995
3307
|
output += "---\n";
|
|
2996
3308
|
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
2997
|
-
output += `Total instances: ${
|
|
3309
|
+
output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
|
|
2998
3310
|
output += `Total changes: ${totalOperations}\n`;
|
|
2999
3311
|
output += "---\n";
|
|
3000
3312
|
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
@@ -3006,12 +3318,13 @@
|
|
|
3006
3318
|
visitedParents.add(parentChangeTree);
|
|
3007
3319
|
}
|
|
3008
3320
|
});
|
|
3009
|
-
const changes = changeTree.
|
|
3321
|
+
const changes = changeTree.indexedOperations;
|
|
3010
3322
|
const level = parentChangeTrees.length;
|
|
3011
3323
|
const indent = getIndent(level);
|
|
3012
3324
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3013
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3014
|
-
for (const
|
|
3325
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3326
|
+
for (const index in changes) {
|
|
3327
|
+
const operation = changes[index];
|
|
3015
3328
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3016
3329
|
}
|
|
3017
3330
|
}
|
|
@@ -3045,6 +3358,7 @@
|
|
|
3045
3358
|
this.$indexes = new Map();
|
|
3046
3359
|
this.$refId = 0;
|
|
3047
3360
|
this[$changes] = new ChangeTree(this);
|
|
3361
|
+
this[$changes].indexes = {};
|
|
3048
3362
|
if (initialValues) {
|
|
3049
3363
|
initialValues.forEach((v) => this.add(v));
|
|
3050
3364
|
}
|
|
@@ -3200,6 +3514,7 @@
|
|
|
3200
3514
|
this.$indexes = new Map();
|
|
3201
3515
|
this.$refId = 0;
|
|
3202
3516
|
this[$changes] = new ChangeTree(this);
|
|
3517
|
+
this[$changes].indexes = {};
|
|
3203
3518
|
if (initialValues) {
|
|
3204
3519
|
initialValues.forEach((v) => this.add(v));
|
|
3205
3520
|
}
|
|
@@ -3355,6 +3670,8 @@
|
|
|
3355
3670
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3356
3671
|
PERFORMANCE OF THIS SOFTWARE.
|
|
3357
3672
|
***************************************************************************** */
|
|
3673
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
3674
|
+
|
|
3358
3675
|
|
|
3359
3676
|
function __decorate(decorators, target, key, desc) {
|
|
3360
3677
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3368,37 +3685,127 @@
|
|
|
3368
3685
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3369
3686
|
};
|
|
3370
3687
|
|
|
3688
|
+
function spliceOne(arr, index) {
|
|
3689
|
+
// manually splice an array
|
|
3690
|
+
if (index === -1 || index >= arr.length) {
|
|
3691
|
+
return false;
|
|
3692
|
+
}
|
|
3693
|
+
const len = arr.length - 1;
|
|
3694
|
+
for (let i = index; i < len; i++) {
|
|
3695
|
+
arr[i] = arr[i + 1];
|
|
3696
|
+
}
|
|
3697
|
+
arr.length = len;
|
|
3698
|
+
return true;
|
|
3699
|
+
}
|
|
3700
|
+
|
|
3701
|
+
class Root {
|
|
3702
|
+
constructor(types) {
|
|
3703
|
+
this.types = types;
|
|
3704
|
+
this.nextUniqueId = 0;
|
|
3705
|
+
this.refCount = {};
|
|
3706
|
+
this.changeTrees = {};
|
|
3707
|
+
// all changes
|
|
3708
|
+
this.allChanges = [];
|
|
3709
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3710
|
+
// pending changes to be encoded
|
|
3711
|
+
this.changes = [];
|
|
3712
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3713
|
+
}
|
|
3714
|
+
getNextUniqueId() {
|
|
3715
|
+
return this.nextUniqueId++;
|
|
3716
|
+
}
|
|
3717
|
+
add(changeTree) {
|
|
3718
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3719
|
+
changeTree.ensureRefId();
|
|
3720
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3721
|
+
if (isNewChangeTree) {
|
|
3722
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3723
|
+
}
|
|
3724
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3725
|
+
if (previousRefCount === 0) {
|
|
3726
|
+
//
|
|
3727
|
+
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3728
|
+
// We need to re-add all changes to the `changes` map.
|
|
3729
|
+
//
|
|
3730
|
+
const ops = changeTree.allChanges.operations;
|
|
3731
|
+
let len = ops.length;
|
|
3732
|
+
while (len--) {
|
|
3733
|
+
changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
|
|
3734
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3735
|
+
}
|
|
3736
|
+
}
|
|
3737
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3738
|
+
return isNewChangeTree;
|
|
3739
|
+
}
|
|
3740
|
+
remove(changeTree) {
|
|
3741
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3742
|
+
if (refCount <= 0) {
|
|
3743
|
+
//
|
|
3744
|
+
// Only remove "root" reference if it's the last reference
|
|
3745
|
+
//
|
|
3746
|
+
changeTree.root = undefined;
|
|
3747
|
+
delete this.changeTrees[changeTree.refId];
|
|
3748
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3749
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3750
|
+
if (changeTree.filteredChanges) {
|
|
3751
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3752
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3753
|
+
}
|
|
3754
|
+
this.refCount[changeTree.refId] = 0;
|
|
3755
|
+
}
|
|
3756
|
+
else {
|
|
3757
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3758
|
+
}
|
|
3759
|
+
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3760
|
+
return refCount;
|
|
3761
|
+
}
|
|
3762
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3763
|
+
const changeSet = this[changeSetName];
|
|
3764
|
+
const index = changeSet.indexOf(changeTree);
|
|
3765
|
+
if (index !== -1) {
|
|
3766
|
+
spliceOne(changeSet, index);
|
|
3767
|
+
// changeSet[index] = undefined;
|
|
3768
|
+
}
|
|
3769
|
+
}
|
|
3770
|
+
clear() {
|
|
3771
|
+
this.changes.length = 0;
|
|
3772
|
+
}
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3371
3775
|
class Encoder {
|
|
3372
3776
|
static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
|
|
3373
|
-
constructor(
|
|
3777
|
+
constructor(state) {
|
|
3374
3778
|
this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
|
|
3375
|
-
this.setRoot(root);
|
|
3376
3779
|
//
|
|
3377
3780
|
// TODO: cache and restore "Context" based on root schema
|
|
3378
3781
|
// (to avoid creating a new context for every new room)
|
|
3379
3782
|
//
|
|
3380
|
-
this.context = new TypeContext(
|
|
3783
|
+
this.context = new TypeContext(state.constructor);
|
|
3784
|
+
this.root = new Root(this.context);
|
|
3785
|
+
this.setState(state);
|
|
3381
3786
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
3382
3787
|
// this.context.schemas.forEach((id, schema) => {
|
|
3383
3788
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3384
3789
|
// });
|
|
3385
3790
|
}
|
|
3386
|
-
|
|
3387
|
-
this.root = new Root();
|
|
3791
|
+
setState(state) {
|
|
3388
3792
|
this.state = state;
|
|
3389
|
-
state[$changes].setRoot(this.root);
|
|
3793
|
+
this.state[$changes].setRoot(this.root);
|
|
3390
3794
|
}
|
|
3391
|
-
encode(it = { offset: 0 }, view,
|
|
3392
|
-
|
|
3393
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
3795
|
+
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
|
|
3796
|
+
) {
|
|
3394
3797
|
const hasView = (view !== undefined);
|
|
3395
3798
|
const rootChangeTree = this.state[$changes];
|
|
3396
|
-
const
|
|
3397
|
-
|
|
3799
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3800
|
+
const changeTrees = this.root[changeSetName];
|
|
3801
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3802
|
+
const changeTree = changeTrees[i];
|
|
3803
|
+
const operations = changeTree[changeSetName];
|
|
3398
3804
|
const ref = changeTree.ref;
|
|
3399
|
-
const ctor = ref
|
|
3805
|
+
const ctor = ref.constructor;
|
|
3400
3806
|
const encoder = ctor[$encoder];
|
|
3401
3807
|
const filter = ctor[$filter];
|
|
3808
|
+
const metadata = ctor[Symbol.metadata];
|
|
3402
3809
|
if (hasView) {
|
|
3403
3810
|
if (!view.items.has(changeTree)) {
|
|
3404
3811
|
view.invisible.add(changeTree);
|
|
@@ -3409,12 +3816,18 @@
|
|
|
3409
3816
|
}
|
|
3410
3817
|
}
|
|
3411
3818
|
// skip root `refId` if it's the first change tree
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3819
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
3820
|
+
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
3821
|
+
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3822
|
+
encode.number(buffer, changeTree.refId, it);
|
|
3415
3823
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3824
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3825
|
+
const fieldIndex = operations.operations[j];
|
|
3826
|
+
const operation = (fieldIndex < 0)
|
|
3827
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3828
|
+
: (isEncodeAll)
|
|
3829
|
+
? exports.OPERATION.ADD
|
|
3830
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3418
3831
|
//
|
|
3419
3832
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3420
3833
|
// they will be encoded per client, based on their view.
|
|
@@ -3422,93 +3835,109 @@
|
|
|
3422
3835
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3423
3836
|
// TODO: avoid checking if no view tags were defined
|
|
3424
3837
|
//
|
|
3425
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3426
|
-
// console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
|
|
3838
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3427
3839
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3428
3840
|
// view?.invisible.add(changeTree);
|
|
3429
3841
|
continue;
|
|
3430
3842
|
}
|
|
3431
|
-
|
|
3432
|
-
// ref: changeTree.ref.constructor.name,
|
|
3433
|
-
// fieldIndex,
|
|
3434
|
-
// operation: OPERATION[operation],
|
|
3435
|
-
// });
|
|
3436
|
-
encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
|
|
3843
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3437
3844
|
}
|
|
3845
|
+
// if (shouldDiscardChanges) {
|
|
3846
|
+
// changeTree.discard();
|
|
3847
|
+
// changeTree.isNew = false; // Not a new instance anymore
|
|
3848
|
+
// }
|
|
3438
3849
|
}
|
|
3439
|
-
if (it.offset >
|
|
3440
|
-
const newSize = getNextPowerOf2(
|
|
3441
|
-
console.warn(
|
|
3850
|
+
if (it.offset > buffer.byteLength) {
|
|
3851
|
+
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
3852
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
3853
|
+
|
|
3854
|
+
import { Encoder } from "@colyseus/schema";
|
|
3855
|
+
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
3856
|
+
`);
|
|
3442
3857
|
//
|
|
3443
3858
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
3444
3859
|
//
|
|
3445
|
-
|
|
3446
|
-
|
|
3860
|
+
buffer = Buffer.alloc(newSize);
|
|
3861
|
+
// assign resized buffer to local sharedBuffer
|
|
3862
|
+
if (buffer === this.sharedBuffer) {
|
|
3863
|
+
this.sharedBuffer = buffer;
|
|
3864
|
+
}
|
|
3865
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3447
3866
|
}
|
|
3448
3867
|
else {
|
|
3449
3868
|
//
|
|
3450
3869
|
// only clear changes after making sure buffer resize is not required.
|
|
3451
3870
|
//
|
|
3452
|
-
if (
|
|
3871
|
+
if (shouldDiscardChanges) {
|
|
3453
3872
|
//
|
|
3454
|
-
//
|
|
3873
|
+
// TODO: avoid iterating over change trees twice.
|
|
3455
3874
|
//
|
|
3456
|
-
|
|
3875
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3876
|
+
const changeTree = changeTrees[i];
|
|
3877
|
+
changeTree.discard();
|
|
3878
|
+
changeTree.isNew = false; // Not a new instance anymore
|
|
3879
|
+
}
|
|
3457
3880
|
}
|
|
3458
|
-
|
|
3459
|
-
return bytes.slice(0, it.offset);
|
|
3881
|
+
return buffer.subarray(0, it.offset);
|
|
3460
3882
|
}
|
|
3461
3883
|
}
|
|
3462
|
-
encodeAll(it = { offset: 0 }) {
|
|
3463
|
-
|
|
3464
|
-
// Array.from(this.$root.allChanges.entries()).map((item) => {
|
|
3465
|
-
// console.log("->", item[0].refId, item[0].ref.toJSON());
|
|
3466
|
-
// });
|
|
3467
|
-
return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
|
|
3884
|
+
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3885
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3468
3886
|
}
|
|
3469
3887
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3470
3888
|
const viewOffset = it.offset;
|
|
3471
|
-
// console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
|
|
3472
|
-
// this.debugAllFilteredChanges();
|
|
3473
3889
|
// try to encode "filtered" changes
|
|
3474
|
-
this.encode(it, view, bytes,
|
|
3890
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3475
3891
|
return Buffer.concat([
|
|
3476
|
-
bytes.
|
|
3477
|
-
bytes.
|
|
3892
|
+
bytes.subarray(0, sharedOffset),
|
|
3893
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3478
3894
|
]);
|
|
3479
3895
|
}
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3896
|
+
debugChanges(field) {
|
|
3897
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3898
|
+
? this.root[field]
|
|
3899
|
+
: field;
|
|
3900
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3901
|
+
const changeSet = changeTree[field];
|
|
3902
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3903
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3904
|
+
for (const index in changeSet) {
|
|
3905
|
+
const op = changeSet[index];
|
|
3906
|
+
console.log(" ->", {
|
|
3907
|
+
index,
|
|
3908
|
+
field: metadata?.[index],
|
|
3909
|
+
op: exports.OPERATION[op],
|
|
3910
|
+
});
|
|
3911
|
+
}
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3490
3914
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3491
3915
|
const viewOffset = it.offset;
|
|
3492
|
-
// try to encode "filtered" changes
|
|
3493
|
-
this.encode(it, view, bytes, this.root.filteredChanges);
|
|
3494
3916
|
// encode visibility changes (add/remove for this view)
|
|
3495
|
-
const
|
|
3496
|
-
for (
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3917
|
+
const refIds = Object.keys(view.changes);
|
|
3918
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3919
|
+
const refId = refIds[i];
|
|
3920
|
+
const changes = view.changes[refId];
|
|
3921
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3922
|
+
if (changeTree === undefined ||
|
|
3923
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3924
|
+
) {
|
|
3925
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3500
3926
|
continue;
|
|
3501
3927
|
}
|
|
3502
3928
|
const ref = changeTree.ref;
|
|
3503
|
-
const ctor = ref
|
|
3929
|
+
const ctor = ref.constructor;
|
|
3504
3930
|
const encoder = ctor[$encoder];
|
|
3931
|
+
const metadata = ctor[Symbol.metadata];
|
|
3505
3932
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3506
|
-
number
|
|
3507
|
-
const
|
|
3508
|
-
for (
|
|
3933
|
+
encode.number(bytes, changeTree.refId, it);
|
|
3934
|
+
const keys = Object.keys(changes);
|
|
3935
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3936
|
+
const key = keys[i];
|
|
3937
|
+
const operation = changes[key];
|
|
3509
3938
|
// isEncodeAll = false
|
|
3510
3939
|
// hasView = true
|
|
3511
|
-
encoder(this, bytes, changeTree,
|
|
3940
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3512
3941
|
}
|
|
3513
3942
|
}
|
|
3514
3943
|
//
|
|
@@ -3516,51 +3945,62 @@
|
|
|
3516
3945
|
// (to allow re-using StateView's for multiple clients)
|
|
3517
3946
|
//
|
|
3518
3947
|
// clear "view" changes after encoding
|
|
3519
|
-
view.changes
|
|
3948
|
+
view.changes = {};
|
|
3949
|
+
// try to encode "filtered" changes
|
|
3950
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3520
3951
|
return Buffer.concat([
|
|
3521
|
-
bytes.
|
|
3522
|
-
bytes.
|
|
3952
|
+
bytes.subarray(0, sharedOffset),
|
|
3953
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3523
3954
|
]);
|
|
3524
3955
|
}
|
|
3525
3956
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3957
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3958
|
+
// changeTree.endEncode();
|
|
3959
|
+
// });
|
|
3960
|
+
// for (const refId in changeTrees) {
|
|
3961
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3962
|
+
// changeTree.endEncode();
|
|
3963
|
+
// // changeTree.changes.clear();
|
|
3964
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3965
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3966
|
+
// // // Not a new instance anymore
|
|
3967
|
+
// // delete changeTree[$isNew];
|
|
3968
|
+
// }
|
|
3530
3969
|
}
|
|
3531
3970
|
discardChanges() {
|
|
3532
3971
|
// discard shared changes
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3972
|
+
let length = this.root.changes.length;
|
|
3973
|
+
if (length > 0) {
|
|
3974
|
+
while (length--) {
|
|
3975
|
+
this.root.changes[length]?.endEncode();
|
|
3976
|
+
}
|
|
3977
|
+
this.root.changes.length = 0;
|
|
3536
3978
|
}
|
|
3537
3979
|
// discard filtered changes
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3980
|
+
length = this.root.filteredChanges.length;
|
|
3981
|
+
if (length > 0) {
|
|
3982
|
+
while (length--) {
|
|
3983
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3984
|
+
}
|
|
3985
|
+
this.root.filteredChanges.length = 0;
|
|
3541
3986
|
}
|
|
3542
3987
|
}
|
|
3543
3988
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
3544
3989
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
3545
3990
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
3991
|
+
if (targetTypeId === undefined) {
|
|
3992
|
+
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.`);
|
|
3993
|
+
return;
|
|
3994
|
+
}
|
|
3546
3995
|
if (baseTypeId !== targetTypeId) {
|
|
3547
3996
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
3548
|
-
number
|
|
3997
|
+
encode.number(bytes, targetTypeId, it);
|
|
3549
3998
|
}
|
|
3550
3999
|
}
|
|
3551
|
-
|
|
3552
|
-
|
|
3553
|
-
|
|
3554
|
-
// manually splice an array
|
|
3555
|
-
if (index === -1 || index >= arr.length) {
|
|
3556
|
-
return false;
|
|
3557
|
-
}
|
|
3558
|
-
const len = arr.length - 1;
|
|
3559
|
-
for (let i = index; i < len; i++) {
|
|
3560
|
-
arr[i] = arr[i + 1];
|
|
4000
|
+
get hasChanges() {
|
|
4001
|
+
return (this.root.changes.length > 0 ||
|
|
4002
|
+
this.root.filteredChanges.length > 0);
|
|
3561
4003
|
}
|
|
3562
|
-
arr.length = len;
|
|
3563
|
-
return true;
|
|
3564
4004
|
}
|
|
3565
4005
|
|
|
3566
4006
|
class DecodingWarning extends Error {
|
|
@@ -3625,6 +4065,7 @@
|
|
|
3625
4065
|
clearRefs() {
|
|
3626
4066
|
this.refs.clear();
|
|
3627
4067
|
this.deletedRefs.clear();
|
|
4068
|
+
this.callbacks = {};
|
|
3628
4069
|
this.refCounts = {};
|
|
3629
4070
|
}
|
|
3630
4071
|
// for decoding
|
|
@@ -3641,8 +4082,9 @@
|
|
|
3641
4082
|
// Ensure child schema instances have their references removed as well.
|
|
3642
4083
|
//
|
|
3643
4084
|
if (Metadata.isValidInstance(ref)) {
|
|
3644
|
-
const metadata = ref
|
|
3645
|
-
for (const
|
|
4085
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
4086
|
+
for (const index in metadata) {
|
|
4087
|
+
const field = metadata[index].name;
|
|
3646
4088
|
const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
|
|
3647
4089
|
if (childRefId) {
|
|
3648
4090
|
this.removeRef(childRefId);
|
|
@@ -3689,21 +4131,21 @@
|
|
|
3689
4131
|
class Decoder {
|
|
3690
4132
|
constructor(root, context) {
|
|
3691
4133
|
this.currentRefId = 0;
|
|
3692
|
-
this.
|
|
4134
|
+
this.setState(root);
|
|
3693
4135
|
this.context = context || new TypeContext(root.constructor);
|
|
3694
4136
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
3695
4137
|
// this.context.schemas.forEach((id, schema) => {
|
|
3696
4138
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3697
4139
|
// });
|
|
3698
4140
|
}
|
|
3699
|
-
|
|
4141
|
+
setState(root) {
|
|
3700
4142
|
this.state = root;
|
|
3701
|
-
this
|
|
3702
|
-
this
|
|
4143
|
+
this.root = new ReferenceTracker();
|
|
4144
|
+
this.root.addRef(0, root);
|
|
3703
4145
|
}
|
|
3704
4146
|
decode(bytes, it = { offset: 0 }, ref = this.state) {
|
|
3705
4147
|
const allChanges = [];
|
|
3706
|
-
const $root = this
|
|
4148
|
+
const $root = this.root;
|
|
3707
4149
|
const totalBytes = bytes.byteLength;
|
|
3708
4150
|
let decoder = ref['constructor'][$decoder];
|
|
3709
4151
|
this.currentRefId = 0;
|
|
@@ -3713,7 +4155,7 @@
|
|
|
3713
4155
|
//
|
|
3714
4156
|
if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
|
|
3715
4157
|
it.offset++;
|
|
3716
|
-
this.currentRefId = number(bytes, it);
|
|
4158
|
+
this.currentRefId = decode.number(bytes, it);
|
|
3717
4159
|
const nextRef = $root.refs.get(this.currentRefId);
|
|
3718
4160
|
//
|
|
3719
4161
|
// Trying to access a reference that haven't been decoded yet.
|
|
@@ -3723,7 +4165,7 @@
|
|
|
3723
4165
|
}
|
|
3724
4166
|
ref[$onDecodeEnd]?.();
|
|
3725
4167
|
ref = nextRef;
|
|
3726
|
-
decoder = ref
|
|
4168
|
+
decoder = ref.constructor[$decoder];
|
|
3727
4169
|
continue;
|
|
3728
4170
|
}
|
|
3729
4171
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -3735,9 +4177,9 @@
|
|
|
3735
4177
|
//
|
|
3736
4178
|
const nextIterator = { offset: it.offset };
|
|
3737
4179
|
while (it.offset < totalBytes) {
|
|
3738
|
-
if (
|
|
4180
|
+
if (bytes[it.offset] === SWITCH_TO_STRUCTURE) {
|
|
3739
4181
|
nextIterator.offset = it.offset + 1;
|
|
3740
|
-
if ($root.refs.has(number(bytes, nextIterator))) {
|
|
4182
|
+
if ($root.refs.has(decode.number(bytes, nextIterator))) {
|
|
3741
4183
|
break;
|
|
3742
4184
|
}
|
|
3743
4185
|
}
|
|
@@ -3758,7 +4200,7 @@
|
|
|
3758
4200
|
let type;
|
|
3759
4201
|
if (bytes[it.offset] === TYPE_ID) {
|
|
3760
4202
|
it.offset++;
|
|
3761
|
-
const type_id = number(bytes, it);
|
|
4203
|
+
const type_id = decode.number(bytes, it);
|
|
3762
4204
|
type = this.context.get(type_id);
|
|
3763
4205
|
}
|
|
3764
4206
|
return type || defaultType;
|
|
@@ -3784,7 +4226,7 @@
|
|
|
3784
4226
|
previousValue: value
|
|
3785
4227
|
});
|
|
3786
4228
|
if (needRemoveRef) {
|
|
3787
|
-
this
|
|
4229
|
+
this.root.removeRef(this.root.refIds.get(value));
|
|
3788
4230
|
}
|
|
3789
4231
|
});
|
|
3790
4232
|
}
|
|
@@ -3824,14 +4266,27 @@
|
|
|
3824
4266
|
super(...arguments);
|
|
3825
4267
|
this.types = new ArraySchema();
|
|
3826
4268
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
4269
|
+
/**
|
|
4270
|
+
* Encodes the TypeContext of an Encoder into a buffer.
|
|
4271
|
+
*
|
|
4272
|
+
* @param encoder Encoder instance
|
|
4273
|
+
* @param it
|
|
4274
|
+
* @returns
|
|
4275
|
+
*/
|
|
4276
|
+
static encode(encoder, it = { offset: 0 }) {
|
|
4277
|
+
const context = encoder.context;
|
|
3831
4278
|
const reflection = new Reflection();
|
|
3832
|
-
const
|
|
4279
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
4280
|
+
// rootType is usually the first schema passed to the Encoder
|
|
4281
|
+
// (unless it inherits from another schema)
|
|
4282
|
+
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4283
|
+
if (rootType > 0) {
|
|
4284
|
+
reflection.rootType = rootType;
|
|
4285
|
+
}
|
|
3833
4286
|
const buildType = (currentType, metadata) => {
|
|
3834
|
-
for (const
|
|
4287
|
+
for (const fieldIndex in metadata) {
|
|
4288
|
+
const index = Number(fieldIndex);
|
|
4289
|
+
const fieldName = metadata[index].name;
|
|
3835
4290
|
// skip fields from parent classes
|
|
3836
4291
|
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
3837
4292
|
continue;
|
|
@@ -3839,7 +4294,7 @@
|
|
|
3839
4294
|
const field = new ReflectionField();
|
|
3840
4295
|
field.name = fieldName;
|
|
3841
4296
|
let fieldType;
|
|
3842
|
-
const type = metadata[
|
|
4297
|
+
const type = metadata[index].type;
|
|
3843
4298
|
if (typeof (type) === "string") {
|
|
3844
4299
|
fieldType = type;
|
|
3845
4300
|
}
|
|
@@ -3881,65 +4336,335 @@
|
|
|
3881
4336
|
}
|
|
3882
4337
|
buildType(type, klass[Symbol.metadata]);
|
|
3883
4338
|
}
|
|
3884
|
-
const buf =
|
|
4339
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
3885
4340
|
return Buffer.from(buf, 0, it.offset);
|
|
3886
4341
|
}
|
|
4342
|
+
/**
|
|
4343
|
+
* Decodes the TypeContext from a buffer into a Decoder instance.
|
|
4344
|
+
*
|
|
4345
|
+
* @param bytes Reflection.encode() output
|
|
4346
|
+
* @param it
|
|
4347
|
+
* @returns Decoder instance
|
|
4348
|
+
*/
|
|
3887
4349
|
static decode(bytes, it) {
|
|
3888
4350
|
const reflection = new Reflection();
|
|
3889
4351
|
const reflectionDecoder = new Decoder(reflection);
|
|
3890
4352
|
reflectionDecoder.decode(bytes, it);
|
|
3891
|
-
const
|
|
3892
|
-
|
|
3893
|
-
|
|
3894
|
-
const
|
|
4353
|
+
const typeContext = new TypeContext();
|
|
4354
|
+
// 1st pass, initialize metadata + inheritance
|
|
4355
|
+
reflection.types.forEach((reflectionType) => {
|
|
4356
|
+
const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
4357
|
+
const schema = class _ extends parentClass {
|
|
3895
4358
|
};
|
|
3896
|
-
// const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
|
|
3897
|
-
const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
|
|
3898
|
-
Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
3899
4359
|
// register for inheritance support
|
|
3900
4360
|
TypeContext.register(schema);
|
|
3901
|
-
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
return types;
|
|
4361
|
+
// // for inheritance support
|
|
4362
|
+
// Metadata.initialize(schema);
|
|
4363
|
+
typeContext.add(schema, reflectionType.id);
|
|
3905
4364
|
}, {});
|
|
3906
|
-
|
|
3907
|
-
|
|
3908
|
-
const metadata = schemaType[Symbol.metadata];
|
|
3909
|
-
const parentKlass = reflection.types[reflectionType.extendsId];
|
|
3910
|
-
const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
|
|
4365
|
+
// define fields
|
|
4366
|
+
const addFields = (metadata, reflectionType, parentFieldIndex) => {
|
|
3911
4367
|
reflectionType.fields.forEach((field, i) => {
|
|
3912
4368
|
const fieldIndex = parentFieldIndex + i;
|
|
3913
4369
|
if (field.referencedType !== undefined) {
|
|
3914
4370
|
let fieldType = field.type;
|
|
3915
|
-
let refType =
|
|
4371
|
+
let refType = typeContext.get(field.referencedType);
|
|
3916
4372
|
// map or array of primitive type (-1)
|
|
3917
4373
|
if (!refType) {
|
|
3918
4374
|
const typeInfo = field.type.split(":");
|
|
3919
4375
|
fieldType = typeInfo[0];
|
|
3920
|
-
refType = typeInfo[1];
|
|
4376
|
+
refType = typeInfo[1]; // string
|
|
3921
4377
|
}
|
|
3922
4378
|
if (fieldType === "ref") {
|
|
3923
|
-
// type(refType)(schemaType.prototype, field.name);
|
|
3924
4379
|
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
3925
4380
|
}
|
|
3926
4381
|
else {
|
|
3927
|
-
// type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
|
|
3928
4382
|
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
3929
4383
|
}
|
|
3930
4384
|
}
|
|
3931
4385
|
else {
|
|
3932
|
-
// type(field.type as PrimitiveType)(schemaType.prototype, field.name);
|
|
3933
4386
|
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
3934
4387
|
}
|
|
3935
4388
|
});
|
|
4389
|
+
};
|
|
4390
|
+
// 2nd pass, set fields
|
|
4391
|
+
reflection.types.forEach((reflectionType) => {
|
|
4392
|
+
const schema = typeContext.get(reflectionType.id);
|
|
4393
|
+
// for inheritance support
|
|
4394
|
+
const metadata = Metadata.initialize(schema);
|
|
4395
|
+
const inheritedTypes = [];
|
|
4396
|
+
let parentType = reflectionType;
|
|
4397
|
+
do {
|
|
4398
|
+
inheritedTypes.push(parentType);
|
|
4399
|
+
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4400
|
+
} while (parentType);
|
|
4401
|
+
let parentFieldIndex = 0;
|
|
4402
|
+
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4403
|
+
// add fields from all inherited classes
|
|
4404
|
+
// TODO: refactor this to avoid adding fields from parent classes
|
|
4405
|
+
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4406
|
+
parentFieldIndex += reflectionType.fields.length;
|
|
4407
|
+
});
|
|
3936
4408
|
});
|
|
3937
|
-
|
|
4409
|
+
const state = new (typeContext.get(reflection.rootType || 0))();
|
|
4410
|
+
return new Decoder(state, typeContext);
|
|
3938
4411
|
}
|
|
3939
4412
|
}
|
|
3940
4413
|
__decorate([
|
|
3941
4414
|
type([ReflectionType])
|
|
3942
4415
|
], Reflection.prototype, "types", void 0);
|
|
4416
|
+
__decorate([
|
|
4417
|
+
type("number")
|
|
4418
|
+
], Reflection.prototype, "rootType", void 0);
|
|
4419
|
+
|
|
4420
|
+
function getDecoderStateCallbacks(decoder) {
|
|
4421
|
+
const $root = decoder.root;
|
|
4422
|
+
const callbacks = $root.callbacks;
|
|
4423
|
+
const onAddCalls = new WeakMap();
|
|
4424
|
+
let currentOnAddCallback;
|
|
4425
|
+
decoder.triggerChanges = function (allChanges) {
|
|
4426
|
+
const uniqueRefIds = new Set();
|
|
4427
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
4428
|
+
const change = allChanges[i];
|
|
4429
|
+
const refId = change.refId;
|
|
4430
|
+
const ref = change.ref;
|
|
4431
|
+
const $callbacks = callbacks[refId];
|
|
4432
|
+
if (!$callbacks) {
|
|
4433
|
+
continue;
|
|
4434
|
+
}
|
|
4435
|
+
//
|
|
4436
|
+
// trigger onRemove on child structure.
|
|
4437
|
+
//
|
|
4438
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4439
|
+
change.previousValue instanceof Schema) {
|
|
4440
|
+
const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
|
|
4441
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4442
|
+
deleteCallbacks[i]();
|
|
4443
|
+
}
|
|
4444
|
+
}
|
|
4445
|
+
if (ref instanceof Schema) {
|
|
4446
|
+
//
|
|
4447
|
+
// Handle schema instance
|
|
4448
|
+
//
|
|
4449
|
+
if (!uniqueRefIds.has(refId)) {
|
|
4450
|
+
// trigger onChange
|
|
4451
|
+
const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
|
|
4452
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4453
|
+
replaceCallbacks[i]();
|
|
4454
|
+
// try {
|
|
4455
|
+
// } catch (e) {
|
|
4456
|
+
// console.error(e);
|
|
4457
|
+
// }
|
|
4458
|
+
}
|
|
4459
|
+
}
|
|
4460
|
+
if ($callbacks.hasOwnProperty(change.field)) {
|
|
4461
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
4462
|
+
for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
|
|
4463
|
+
fieldCallbacks[i](change.value, change.previousValue);
|
|
4464
|
+
// try {
|
|
4465
|
+
// } catch (e) {
|
|
4466
|
+
// console.error(e);
|
|
4467
|
+
// }
|
|
4468
|
+
}
|
|
4469
|
+
}
|
|
4470
|
+
}
|
|
4471
|
+
else {
|
|
4472
|
+
//
|
|
4473
|
+
// Handle collection of items
|
|
4474
|
+
//
|
|
4475
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
4476
|
+
//
|
|
4477
|
+
// FIXME: `previousValue` should always be available.
|
|
4478
|
+
//
|
|
4479
|
+
if (change.previousValue !== undefined) {
|
|
4480
|
+
// triger onRemove
|
|
4481
|
+
const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
|
|
4482
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4483
|
+
deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
|
|
4484
|
+
}
|
|
4485
|
+
}
|
|
4486
|
+
// Handle DELETE_AND_ADD operations
|
|
4487
|
+
if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
4488
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4489
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4490
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4491
|
+
}
|
|
4492
|
+
}
|
|
4493
|
+
}
|
|
4494
|
+
else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
|
|
4495
|
+
// triger onAdd
|
|
4496
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4497
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4498
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
// trigger onChange
|
|
4502
|
+
if (change.value !== change.previousValue) {
|
|
4503
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
4504
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4505
|
+
replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4506
|
+
}
|
|
4507
|
+
}
|
|
4508
|
+
}
|
|
4509
|
+
uniqueRefIds.add(refId);
|
|
4510
|
+
}
|
|
4511
|
+
};
|
|
4512
|
+
function getProxy(metadataOrType, context) {
|
|
4513
|
+
let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
|
|
4514
|
+
let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
|
|
4515
|
+
(metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
|
|
4516
|
+
if (metadata && !isCollection) {
|
|
4517
|
+
const onAddListen = function (ref, prop, callback, immediate) {
|
|
4518
|
+
// immediate trigger
|
|
4519
|
+
if (immediate &&
|
|
4520
|
+
context.instance[prop] !== undefined &&
|
|
4521
|
+
!onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
|
|
4522
|
+
) {
|
|
4523
|
+
callback(context.instance[prop], undefined);
|
|
4524
|
+
}
|
|
4525
|
+
return $root.addCallback($root.refIds.get(ref), prop, callback);
|
|
4526
|
+
};
|
|
4527
|
+
/**
|
|
4528
|
+
* Schema instances
|
|
4529
|
+
*/
|
|
4530
|
+
return new Proxy({
|
|
4531
|
+
listen: function listen(prop, callback, immediate = true) {
|
|
4532
|
+
if (context.instance) {
|
|
4533
|
+
return onAddListen(context.instance, prop, callback, immediate);
|
|
4534
|
+
}
|
|
4535
|
+
else {
|
|
4536
|
+
// collection instance not received yet
|
|
4537
|
+
let detachCallback = () => { };
|
|
4538
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4539
|
+
detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4540
|
+
});
|
|
4541
|
+
return () => detachCallback();
|
|
4542
|
+
}
|
|
4543
|
+
},
|
|
4544
|
+
onChange: function onChange(callback) {
|
|
4545
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
|
|
4546
|
+
},
|
|
4547
|
+
//
|
|
4548
|
+
// TODO: refactor `bindTo()` implementation.
|
|
4549
|
+
// There is room for improvement.
|
|
4550
|
+
//
|
|
4551
|
+
bindTo: function bindTo(targetObject, properties) {
|
|
4552
|
+
if (!properties) {
|
|
4553
|
+
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4554
|
+
}
|
|
4555
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
|
|
4556
|
+
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4557
|
+
});
|
|
4558
|
+
}
|
|
4559
|
+
}, {
|
|
4560
|
+
get(target, prop) {
|
|
4561
|
+
const metadataField = metadata[metadata[prop]];
|
|
4562
|
+
if (metadataField) {
|
|
4563
|
+
const instance = context.instance?.[prop];
|
|
4564
|
+
const onInstanceAvailable = ((callback) => {
|
|
4565
|
+
const unbind = $(context.instance).listen(prop, (value, _) => {
|
|
4566
|
+
callback(value, false);
|
|
4567
|
+
// FIXME: by "unbinding" the callback here,
|
|
4568
|
+
// it will not support when the server
|
|
4569
|
+
// re-instantiates the instance.
|
|
4570
|
+
//
|
|
4571
|
+
unbind?.();
|
|
4572
|
+
}, false);
|
|
4573
|
+
// has existing value
|
|
4574
|
+
if ($root.refIds.get(instance) !== undefined) {
|
|
4575
|
+
callback(instance, true);
|
|
4576
|
+
}
|
|
4577
|
+
});
|
|
4578
|
+
return getProxy(metadataField.type, {
|
|
4579
|
+
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
4580
|
+
instance: ($root.refIds.get(instance) && instance),
|
|
4581
|
+
parentInstance: context.instance,
|
|
4582
|
+
onInstanceAvailable,
|
|
4583
|
+
});
|
|
4584
|
+
}
|
|
4585
|
+
else {
|
|
4586
|
+
// accessing the function
|
|
4587
|
+
return target[prop];
|
|
4588
|
+
}
|
|
4589
|
+
},
|
|
4590
|
+
has(target, prop) { return metadata[prop] !== undefined; },
|
|
4591
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4592
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4593
|
+
});
|
|
4594
|
+
}
|
|
4595
|
+
else {
|
|
4596
|
+
/**
|
|
4597
|
+
* Collection instances
|
|
4598
|
+
*/
|
|
4599
|
+
const onAdd = function (ref, callback, immediate) {
|
|
4600
|
+
// Trigger callback on existing items
|
|
4601
|
+
if (immediate) {
|
|
4602
|
+
ref.forEach((v, k) => callback(v, k));
|
|
4603
|
+
}
|
|
4604
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
|
|
4605
|
+
onAddCalls.set(callback, true);
|
|
4606
|
+
currentOnAddCallback = callback;
|
|
4607
|
+
callback(value, key);
|
|
4608
|
+
onAddCalls.delete(callback);
|
|
4609
|
+
currentOnAddCallback = undefined;
|
|
4610
|
+
});
|
|
4611
|
+
};
|
|
4612
|
+
const onRemove = function (ref, callback) {
|
|
4613
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
|
|
4614
|
+
};
|
|
4615
|
+
return new Proxy({
|
|
4616
|
+
onAdd: function (callback, immediate = true) {
|
|
4617
|
+
//
|
|
4618
|
+
// https://github.com/colyseus/schema/issues/147
|
|
4619
|
+
// If parent instance has "onAdd" registered, avoid triggering immediate callback.
|
|
4620
|
+
//
|
|
4621
|
+
if (context.instance) {
|
|
4622
|
+
return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
|
|
4623
|
+
}
|
|
4624
|
+
else if (context.onInstanceAvailable) {
|
|
4625
|
+
// collection instance not received yet
|
|
4626
|
+
let detachCallback = () => { };
|
|
4627
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4628
|
+
detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4629
|
+
});
|
|
4630
|
+
return () => detachCallback();
|
|
4631
|
+
}
|
|
4632
|
+
},
|
|
4633
|
+
onRemove: function (callback) {
|
|
4634
|
+
if (context.onInstanceAvailable) {
|
|
4635
|
+
// collection instance not received yet
|
|
4636
|
+
let detachCallback = () => { };
|
|
4637
|
+
context.onInstanceAvailable((ref) => {
|
|
4638
|
+
detachCallback = onRemove(ref, callback);
|
|
4639
|
+
});
|
|
4640
|
+
return () => detachCallback();
|
|
4641
|
+
}
|
|
4642
|
+
else if (context.instance) {
|
|
4643
|
+
return onRemove(context.instance, callback);
|
|
4644
|
+
}
|
|
4645
|
+
},
|
|
4646
|
+
}, {
|
|
4647
|
+
get(target, prop) {
|
|
4648
|
+
if (!target[prop]) {
|
|
4649
|
+
throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
|
|
4650
|
+
}
|
|
4651
|
+
return target[prop];
|
|
4652
|
+
},
|
|
4653
|
+
has(target, prop) { return target[prop] !== undefined; },
|
|
4654
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4655
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4656
|
+
});
|
|
4657
|
+
}
|
|
4658
|
+
}
|
|
4659
|
+
function $(instance) {
|
|
4660
|
+
return getProxy(undefined, { instance });
|
|
4661
|
+
}
|
|
4662
|
+
return $;
|
|
4663
|
+
}
|
|
4664
|
+
|
|
4665
|
+
function getRawChangesCallback(decoder, callback) {
|
|
4666
|
+
decoder.triggerChanges = callback;
|
|
4667
|
+
}
|
|
3943
4668
|
|
|
3944
4669
|
class StateView {
|
|
3945
4670
|
constructor() {
|
|
@@ -3955,31 +4680,32 @@
|
|
|
3955
4680
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
3956
4681
|
* (This is used to force encoding a property, even if it was not changed)
|
|
3957
4682
|
*/
|
|
3958
|
-
this.changes =
|
|
4683
|
+
this.changes = {};
|
|
3959
4684
|
}
|
|
3960
4685
|
// TODO: allow to set multiple tags at once
|
|
3961
|
-
add(obj, tag = DEFAULT_VIEW_TAG) {
|
|
4686
|
+
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
3962
4687
|
if (!obj[$changes]) {
|
|
3963
4688
|
console.warn("StateView#add(), invalid object:", obj);
|
|
3964
4689
|
return this;
|
|
3965
4690
|
}
|
|
3966
|
-
|
|
3967
|
-
this.items.add(changeTree);
|
|
3968
|
-
// Add children of this ChangeTree to this view
|
|
3969
|
-
changeTree.forEachChild((change, _) => this.add(change.ref, tag));
|
|
3970
|
-
// FIXME: ArraySchema/MapSchema does not have metadata
|
|
4691
|
+
// FIXME: ArraySchema/MapSchema do not have metadata
|
|
3971
4692
|
const metadata = obj.constructor[Symbol.metadata];
|
|
3972
|
-
|
|
3973
|
-
|
|
3974
|
-
|
|
4693
|
+
const changeTree = obj[$changes];
|
|
4694
|
+
this.items.add(changeTree);
|
|
4695
|
+
// add parent ChangeTree's
|
|
4696
|
+
// - if it was invisible to this view
|
|
4697
|
+
// - if it were previously filtered out
|
|
4698
|
+
if (checkIncludeParent && changeTree.parent) {
|
|
4699
|
+
this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
|
|
4700
|
+
}
|
|
3975
4701
|
//
|
|
3976
4702
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
3977
4703
|
// be set (only the parent's changes are set)
|
|
3978
4704
|
//
|
|
3979
|
-
let changes = this.changes.
|
|
4705
|
+
let changes = this.changes[changeTree.refId];
|
|
3980
4706
|
if (changes === undefined) {
|
|
3981
|
-
changes =
|
|
3982
|
-
this.changes.
|
|
4707
|
+
changes = {};
|
|
4708
|
+
this.changes[changeTree.refId] = changes;
|
|
3983
4709
|
}
|
|
3984
4710
|
// set tag
|
|
3985
4711
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -3995,82 +4721,78 @@
|
|
|
3995
4721
|
tags = this.tags.get(changeTree);
|
|
3996
4722
|
}
|
|
3997
4723
|
tags.add(tag);
|
|
3998
|
-
// console.log("BY TAG:", tag);
|
|
3999
4724
|
// Ref: add tagged properties
|
|
4000
|
-
metadata?.[
|
|
4725
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4001
4726
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
4002
|
-
changes
|
|
4727
|
+
changes[index] = exports.OPERATION.ADD;
|
|
4003
4728
|
}
|
|
4004
4729
|
});
|
|
4005
4730
|
}
|
|
4006
4731
|
else {
|
|
4007
|
-
|
|
4008
|
-
|
|
4009
|
-
// metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
|
|
4010
|
-
// if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
4011
|
-
// changes.set(index, OPERATION.ADD);
|
|
4012
|
-
// }
|
|
4013
|
-
// });
|
|
4014
|
-
const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4732
|
+
const isInvisible = this.invisible.has(changeTree);
|
|
4733
|
+
const changeSet = (changeTree.filteredChanges !== undefined)
|
|
4015
4734
|
? changeTree.allFilteredChanges
|
|
4016
4735
|
: changeTree.allChanges;
|
|
4017
|
-
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4736
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4737
|
+
const index = changeSet.operations[i];
|
|
4738
|
+
if (index === undefined) {
|
|
4739
|
+
continue;
|
|
4740
|
+
} // skip "undefined" indexes
|
|
4741
|
+
const op = changeTree.indexedOperations[index] ?? exports.OPERATION.ADD;
|
|
4742
|
+
const tagAtIndex = metadata?.[index].tag;
|
|
4743
|
+
if ((isInvisible || // if "invisible", include all
|
|
4744
|
+
tagAtIndex === undefined || // "all change" with no tag
|
|
4745
|
+
tagAtIndex === tag // tagged property
|
|
4746
|
+
) &&
|
|
4747
|
+
op !== exports.OPERATION.DELETE) {
|
|
4748
|
+
changes[index] = op;
|
|
4023
4749
|
}
|
|
4024
4750
|
}
|
|
4025
4751
|
}
|
|
4026
|
-
//
|
|
4027
|
-
|
|
4028
|
-
|
|
4029
|
-
(
|
|
4030
|
-
|
|
4031
|
-
|
|
4752
|
+
// Add children of this ChangeTree to this view
|
|
4753
|
+
changeTree.forEachChild((change, index) => {
|
|
4754
|
+
// Do not ADD children that don't have the same tag
|
|
4755
|
+
if (metadata &&
|
|
4756
|
+
metadata[index].tag !== undefined &&
|
|
4757
|
+
metadata[index].tag !== tag) {
|
|
4758
|
+
return;
|
|
4759
|
+
}
|
|
4760
|
+
this.add(change.ref, tag, false);
|
|
4761
|
+
});
|
|
4032
4762
|
return this;
|
|
4033
4763
|
}
|
|
4034
|
-
addParent(changeTree, tag) {
|
|
4035
|
-
|
|
4036
|
-
|
|
4037
|
-
|
|
4764
|
+
addParent(changeTree, parentIndex, tag) {
|
|
4765
|
+
// view must have all "changeTree" parent tree
|
|
4766
|
+
this.items.add(changeTree);
|
|
4767
|
+
// add parent's parent
|
|
4768
|
+
const parentChangeTree = changeTree.parent?.[$changes];
|
|
4769
|
+
if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
|
|
4770
|
+
this.addParent(parentChangeTree, changeTree.parentIndex, tag);
|
|
4038
4771
|
}
|
|
4039
|
-
|
|
4040
|
-
|
|
4041
|
-
if (!this.invisible.has(parentChangeTree)) {
|
|
4042
|
-
// parent is already available, no need to add it!
|
|
4772
|
+
// parent is already available, no need to add it!
|
|
4773
|
+
if (!this.invisible.has(changeTree)) {
|
|
4043
4774
|
return;
|
|
4044
4775
|
}
|
|
4045
|
-
this.addParent(parentChangeTree, tag);
|
|
4046
4776
|
// add parent's tag properties
|
|
4047
|
-
if (
|
|
4048
|
-
let
|
|
4049
|
-
if (
|
|
4050
|
-
|
|
4051
|
-
this.changes.
|
|
4777
|
+
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4778
|
+
let changes = this.changes[changeTree.refId];
|
|
4779
|
+
if (changes === undefined) {
|
|
4780
|
+
changes = {};
|
|
4781
|
+
this.changes[changeTree.refId] = changes;
|
|
4052
4782
|
}
|
|
4053
|
-
// console.log("add parent change", {
|
|
4054
|
-
// parentIndex,
|
|
4055
|
-
// parentChanges,
|
|
4056
|
-
// parentChange: (
|
|
4057
|
-
// parentChangeTree.getChange(parentIndex) &&
|
|
4058
|
-
// OPERATION[parentChangeTree.getChange(parentIndex)]
|
|
4059
|
-
// ),
|
|
4060
|
-
// })
|
|
4061
4783
|
if (!this.tags) {
|
|
4062
4784
|
this.tags = new WeakMap();
|
|
4063
4785
|
}
|
|
4064
4786
|
let tags;
|
|
4065
|
-
if (!this.tags.has(
|
|
4787
|
+
if (!this.tags.has(changeTree)) {
|
|
4066
4788
|
tags = new Set();
|
|
4067
|
-
this.tags.set(
|
|
4789
|
+
this.tags.set(changeTree, tags);
|
|
4068
4790
|
}
|
|
4069
4791
|
else {
|
|
4070
|
-
tags = this.tags.get(
|
|
4792
|
+
tags = this.tags.get(changeTree);
|
|
4071
4793
|
}
|
|
4072
4794
|
tags.add(tag);
|
|
4073
|
-
|
|
4795
|
+
changes[parentIndex] = exports.OPERATION.ADD;
|
|
4074
4796
|
}
|
|
4075
4797
|
}
|
|
4076
4798
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4082,32 +4804,32 @@
|
|
|
4082
4804
|
this.items.delete(changeTree);
|
|
4083
4805
|
const ref = changeTree.ref;
|
|
4084
4806
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4085
|
-
let changes = this.changes.
|
|
4807
|
+
let changes = this.changes[changeTree.refId];
|
|
4086
4808
|
if (changes === undefined) {
|
|
4087
|
-
changes =
|
|
4088
|
-
this.changes.
|
|
4809
|
+
changes = {};
|
|
4810
|
+
this.changes[changeTree.refId] = changes;
|
|
4089
4811
|
}
|
|
4090
4812
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4091
4813
|
// parent is collection (Map/Array)
|
|
4092
4814
|
const parent = changeTree.parent;
|
|
4093
4815
|
if (!Metadata.isValidInstance(parent)) {
|
|
4094
4816
|
const parentChangeTree = parent[$changes];
|
|
4095
|
-
let changes = this.changes.
|
|
4817
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4096
4818
|
if (changes === undefined) {
|
|
4097
|
-
changes =
|
|
4098
|
-
this.changes.
|
|
4819
|
+
changes = {};
|
|
4820
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4099
4821
|
}
|
|
4100
4822
|
// DELETE / DELETE BY REF ID
|
|
4101
|
-
changes
|
|
4823
|
+
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
4102
4824
|
}
|
|
4103
4825
|
else {
|
|
4104
4826
|
// delete all "tagged" properties.
|
|
4105
|
-
metadata[
|
|
4827
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4106
4828
|
}
|
|
4107
4829
|
}
|
|
4108
4830
|
else {
|
|
4109
4831
|
// delete only tagged properties
|
|
4110
|
-
metadata[
|
|
4832
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4111
4833
|
}
|
|
4112
4834
|
// remove tag
|
|
4113
4835
|
if (this.tags && this.tags.has(changeTree)) {
|
|
@@ -4159,16 +4881,18 @@
|
|
|
4159
4881
|
exports.decode = decode;
|
|
4160
4882
|
exports.decodeKeyValueOperation = decodeKeyValueOperation;
|
|
4161
4883
|
exports.decodeSchemaOperation = decodeSchemaOperation;
|
|
4884
|
+
exports.defineCustomTypes = defineCustomTypes;
|
|
4162
4885
|
exports.defineTypes = defineTypes;
|
|
4163
4886
|
exports.deprecated = deprecated;
|
|
4164
4887
|
exports.dumpChanges = dumpChanges;
|
|
4165
4888
|
exports.encode = encode;
|
|
4166
4889
|
exports.encodeKeyValueOperation = encodeArray;
|
|
4167
4890
|
exports.encodeSchemaOperation = encodeSchemaOperation;
|
|
4891
|
+
exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
|
|
4892
|
+
exports.getRawChangesCallback = getRawChangesCallback;
|
|
4168
4893
|
exports.registerType = registerType;
|
|
4894
|
+
exports.schema = schema;
|
|
4169
4895
|
exports.type = type;
|
|
4170
4896
|
exports.view = view;
|
|
4171
4897
|
|
|
4172
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4173
|
-
|
|
4174
4898
|
}));
|