@colyseus/schema 3.0.0-alpha.4 → 3.0.0-alpha.41
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 +2201 -1507
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +2198 -1506
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +2208 -1514
- package/lib/Metadata.d.ts +21 -9
- package/lib/Metadata.js +169 -32
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +19 -4
- package/lib/Reflection.js +66 -32
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +4 -4
- package/lib/Schema.js +44 -50
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +31 -34
- package/lib/annotations.js +110 -160
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.d.ts +1 -0
- package/lib/bench_encode.js +130 -0
- package/lib/bench_encode.js.map +1 -0
- package/lib/codegen/api.js +1 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/languages/cpp.js +1 -2
- package/lib/codegen/languages/cpp.js.map +1 -1
- package/lib/codegen/languages/csharp.js +2 -46
- package/lib/codegen/languages/csharp.js.map +1 -1
- package/lib/codegen/languages/haxe.js +1 -2
- package/lib/codegen/languages/haxe.js.map +1 -1
- package/lib/codegen/languages/java.js +1 -2
- package/lib/codegen/languages/java.js.map +1 -1
- package/lib/codegen/languages/js.js +1 -2
- package/lib/codegen/languages/js.js.map +1 -1
- package/lib/codegen/languages/lua.js +1 -2
- package/lib/codegen/languages/lua.js.map +1 -1
- package/lib/codegen/languages/ts.js +1 -2
- package/lib/codegen/languages/ts.js.map +1 -1
- package/lib/codegen/parser.js +85 -3
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +6 -3
- package/lib/codegen/types.js.map +1 -1
- package/lib/debug.d.ts +1 -0
- package/lib/debug.js +51 -0
- package/lib/debug.js.map +1 -0
- package/lib/decoder/DecodeOperation.d.ts +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 +3 -2
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/decoder/strategy/RawChanges.js +1 -2
- package/lib/decoder/strategy/RawChanges.js.map +1 -1
- package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
- package/lib/decoder/strategy/StateCallbacks.js +75 -65
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +27 -21
- package/lib/encoder/ChangeTree.js +246 -186
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +3 -6
- package/lib/encoder/EncodeOperation.js +51 -65
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +9 -8
- package/lib/encoder/Encoder.js +168 -91
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +22 -0
- package/lib/encoder/Root.js +81 -0
- package/lib/encoder/Root.js.map +1 -0
- package/lib/encoder/StateView.d.ts +7 -7
- package/lib/encoder/StateView.js +70 -74
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/assert.d.ts +7 -6
- package/lib/encoding/assert.js +13 -5
- package/lib/encoding/assert.js.map +1 -1
- package/lib/encoding/decode.d.ts +35 -20
- package/lib/encoding/decode.js +43 -87
- 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 +23 -0
- package/lib/types/TypeContext.js +111 -0
- package/lib/types/TypeContext.js.map +1 -0
- package/lib/types/custom/ArraySchema.d.ts +2 -2
- package/lib/types/custom/ArraySchema.js +33 -22
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.js +1 -0
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -1
- package/lib/types/custom/MapSchema.js +12 -4
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.js +1 -0
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/registry.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 +7 -6
- package/src/Metadata.ts +190 -42
- package/src/Reflection.ts +77 -39
- package/src/Schema.ts +59 -64
- package/src/annotations.ts +156 -202
- package/src/bench_encode.ts +108 -0
- package/src/codegen/languages/csharp.ts +1 -47
- 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 +3 -2
- package/src/decoder/strategy/StateCallbacks.ts +153 -82
- package/src/encoder/ChangeTree.ts +286 -202
- package/src/encoder/EncodeOperation.ts +78 -78
- package/src/encoder/Encoder.ts +202 -97
- package/src/encoder/Root.ts +93 -0
- package/src/encoder/StateView.ts +76 -88
- package/src/encoding/assert.ts +17 -8
- package/src/encoding/decode.ts +62 -97
- 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 +133 -0
- package/src/types/custom/ArraySchema.ts +49 -19
- package/src/types/custom/CollectionSchema.ts +1 -0
- package/src/types/custom/MapSchema.ts +18 -5
- package/src/types/custom/SetSchema.ts +1 -0
- package/src/types/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
|
-
|
|
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
|
+
}
|
|
254
273
|
}
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
274
|
+
bytes[it.offset++] = 0xcb;
|
|
275
|
+
float64$1(bytes, value, it);
|
|
276
|
+
return 9;
|
|
277
|
+
}
|
|
278
|
+
if (value >= 0) {
|
|
279
|
+
// positive fixnum
|
|
280
|
+
if (value < 0x80) {
|
|
281
|
+
bytes[it.offset++] = value & 255; // uint8
|
|
282
|
+
return 1;
|
|
262
283
|
}
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
269
|
-
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
270
|
-
this.root.allFilteredChanges.set(this, this.filteredChanges);
|
|
271
|
-
}
|
|
272
|
-
else {
|
|
273
|
-
this.root.allChanges.set(this, this.allChanges);
|
|
274
|
-
}
|
|
275
|
-
this.ensureRefId();
|
|
276
|
-
this.forEachChild((changeTree, atIndex) => {
|
|
277
|
-
changeTree.setParent(this.ref, root, atIndex);
|
|
278
|
-
});
|
|
279
|
-
}
|
|
280
|
-
forEachChild(callback) {
|
|
281
|
-
//
|
|
282
|
-
// assign same parent on child structures
|
|
283
|
-
//
|
|
284
|
-
if (Metadata.isValidInstance(this.ref)) {
|
|
285
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
286
|
-
// FIXME: need to iterate over parent metadata instead.
|
|
287
|
-
for (const field in metadata) {
|
|
288
|
-
const value = this.ref[field];
|
|
289
|
-
if (value && value[$changes]) {
|
|
290
|
-
callback(value[$changes], metadata[field].index);
|
|
291
|
-
}
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
else if (typeof (this.ref) === "object") {
|
|
295
|
-
// MapSchema / ArraySchema, etc.
|
|
296
|
-
this.ref.forEach((value, key) => {
|
|
297
|
-
if (Metadata.isValidInstance(value)) {
|
|
298
|
-
callback(value[$changes], this.ref[$changes].indexes[key]);
|
|
299
|
-
}
|
|
300
|
-
});
|
|
301
|
-
}
|
|
302
|
-
}
|
|
303
|
-
operation(op) {
|
|
304
|
-
this.changes.set(--this.currentOperationIndex, op);
|
|
305
|
-
this.root?.changes.set(this, this.changes);
|
|
306
|
-
}
|
|
307
|
-
change(index, operation = exports.OPERATION.ADD) {
|
|
308
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
309
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
310
|
-
const changeSet = (isFiltered)
|
|
311
|
-
? this.filteredChanges
|
|
312
|
-
: this.changes;
|
|
313
|
-
const previousOperation = changeSet.get(index);
|
|
314
|
-
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
315
|
-
const op = (!previousOperation)
|
|
316
|
-
? operation
|
|
317
|
-
: (previousOperation === exports.OPERATION.DELETE)
|
|
318
|
-
? exports.OPERATION.DELETE_AND_ADD
|
|
319
|
-
: operation;
|
|
320
|
-
changeSet.set(index, op);
|
|
321
|
-
}
|
|
322
|
-
//
|
|
323
|
-
// TODO: are DELETE operations being encoded as ADD here ??
|
|
324
|
-
//
|
|
325
|
-
if (isFiltered) {
|
|
326
|
-
this.allFilteredChanges.set(index, exports.OPERATION.ADD);
|
|
327
|
-
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
328
|
-
}
|
|
329
|
-
else {
|
|
330
|
-
this.allChanges.set(index, exports.OPERATION.ADD);
|
|
331
|
-
this.root?.changes.set(this, this.changes);
|
|
332
|
-
}
|
|
333
|
-
}
|
|
334
|
-
shiftChangeIndexes(shiftIndex) {
|
|
335
|
-
//
|
|
336
|
-
// Used only during:
|
|
337
|
-
//
|
|
338
|
-
// - ArraySchema#unshift()
|
|
339
|
-
//
|
|
340
|
-
const changeSet = (this.isFiltered)
|
|
341
|
-
? this.filteredChanges
|
|
342
|
-
: this.changes;
|
|
343
|
-
const changeSetEntries = Array.from(changeSet.entries());
|
|
344
|
-
changeSet.clear();
|
|
345
|
-
// Re-insert each entry with the shifted index
|
|
346
|
-
for (const [index, op] of changeSetEntries) {
|
|
347
|
-
changeSet.set(index + shiftIndex, op);
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
351
|
-
//
|
|
352
|
-
// Used only during:
|
|
353
|
-
//
|
|
354
|
-
// - ArraySchema#splice()
|
|
355
|
-
//
|
|
356
|
-
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
357
|
-
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
|
|
358
|
-
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
359
|
-
}
|
|
360
|
-
else {
|
|
361
|
-
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
362
|
-
}
|
|
363
|
-
}
|
|
364
|
-
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
|
|
365
|
-
Array.from(allChangeSet.entries()).forEach(([index, op]) => {
|
|
366
|
-
// console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
|
|
367
|
-
if (index >= startIndex) {
|
|
368
|
-
allChangeSet.delete(index);
|
|
369
|
-
allChangeSet.set(index + shiftIndex, op);
|
|
370
|
-
}
|
|
371
|
-
});
|
|
372
|
-
}
|
|
373
|
-
indexedOperation(index, operation, allChangesIndex = index) {
|
|
374
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
375
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
376
|
-
if (isFiltered) {
|
|
377
|
-
this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
|
|
378
|
-
this.filteredChanges.set(index, operation);
|
|
379
|
-
this.root?.filteredChanges.set(this, this.filteredChanges);
|
|
380
|
-
}
|
|
381
|
-
else {
|
|
382
|
-
this.allChanges.set(allChangesIndex, exports.OPERATION.ADD);
|
|
383
|
-
this.changes.set(index, operation);
|
|
384
|
-
this.root?.changes.set(this, this.changes);
|
|
385
|
-
}
|
|
386
|
-
}
|
|
387
|
-
getType(index) {
|
|
388
|
-
if (Metadata.isValidInstance(this.ref)) {
|
|
389
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
390
|
-
return metadata[metadata[index]].type;
|
|
391
|
-
}
|
|
392
|
-
else {
|
|
393
|
-
//
|
|
394
|
-
// Get the child type from parent structure.
|
|
395
|
-
// - ["string"] => "string"
|
|
396
|
-
// - { map: "string" } => "string"
|
|
397
|
-
// - { set: "string" } => "string"
|
|
398
|
-
//
|
|
399
|
-
return this.ref[$childType];
|
|
400
|
-
}
|
|
401
|
-
}
|
|
402
|
-
getChange(index) {
|
|
403
|
-
// TODO: optimize this. avoid checking against multiple instances
|
|
404
|
-
return this.changes.get(index) ?? this.filteredChanges.get(index);
|
|
405
|
-
}
|
|
406
|
-
//
|
|
407
|
-
// used during `.encode()`
|
|
408
|
-
//
|
|
409
|
-
getValue(index, isEncodeAll = false) {
|
|
410
|
-
//
|
|
411
|
-
// `isEncodeAll` param is only used by ArraySchema
|
|
412
|
-
//
|
|
413
|
-
return this.ref[$getByIndex](index, isEncodeAll);
|
|
414
|
-
}
|
|
415
|
-
delete(index, operation, allChangesIndex = index) {
|
|
416
|
-
if (index === undefined) {
|
|
417
|
-
try {
|
|
418
|
-
throw new Error(`@colyseus/schema ${this.ref.constructor.name}: trying to delete non-existing index '${index}'`);
|
|
419
|
-
}
|
|
420
|
-
catch (e) {
|
|
421
|
-
console.warn(e);
|
|
422
|
-
}
|
|
423
|
-
return;
|
|
424
|
-
}
|
|
425
|
-
const metadata = this.ref['constructor'][Symbol.metadata];
|
|
426
|
-
const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
|
|
427
|
-
const changeSet = (isFiltered)
|
|
428
|
-
? this.filteredChanges
|
|
429
|
-
: this.changes;
|
|
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,1107 @@
|
|
|
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;
|
|
577
|
-
}
|
|
578
|
-
else if (c < 0xd800 || c >= 0xe000) {
|
|
579
|
-
length += 3;
|
|
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;
|
|
580
393
|
}
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
394
|
+
if ((byte & 0xe0) === 0xc0) {
|
|
395
|
+
string += String.fromCharCode(((byte & 0x1f) << 6) |
|
|
396
|
+
(bytes[++i] & 0x3f));
|
|
397
|
+
continue;
|
|
584
398
|
}
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
for (var i = 0, l = str.length; i < l; i++) {
|
|
591
|
-
c = str.charCodeAt(i);
|
|
592
|
-
if (c < 0x80) {
|
|
593
|
-
view[it.offset++] = c;
|
|
594
|
-
}
|
|
595
|
-
else if (c < 0x800) {
|
|
596
|
-
view[it.offset++] = 0xc0 | (c >> 6);
|
|
597
|
-
view[it.offset++] = 0x80 | (c & 0x3f);
|
|
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);
|
|
399
|
+
if ((byte & 0xf0) === 0xe0) {
|
|
400
|
+
string += String.fromCharCode(((byte & 0x0f) << 12) |
|
|
401
|
+
((bytes[++i] & 0x3f) << 6) |
|
|
402
|
+
((bytes[++i] & 0x3f) << 0));
|
|
403
|
+
continue;
|
|
603
404
|
}
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
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
|
+
const decode = {
|
|
546
|
+
utf8Read,
|
|
547
|
+
int8,
|
|
548
|
+
uint8,
|
|
549
|
+
int16,
|
|
550
|
+
uint16,
|
|
551
|
+
int32,
|
|
552
|
+
uint32,
|
|
553
|
+
float32,
|
|
554
|
+
float64,
|
|
555
|
+
int64,
|
|
556
|
+
uint64,
|
|
557
|
+
bigint64,
|
|
558
|
+
biguint64,
|
|
559
|
+
boolean,
|
|
560
|
+
string,
|
|
561
|
+
number,
|
|
562
|
+
};
|
|
791
563
|
|
|
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;
|
|
564
|
+
const registeredTypes = {};
|
|
565
|
+
const identifiers = new Map();
|
|
566
|
+
function registerType(identifier, definition) {
|
|
567
|
+
if (definition.constructor) {
|
|
568
|
+
identifiers.set(definition.constructor, identifier);
|
|
569
|
+
registeredTypes[identifier] = definition;
|
|
842
570
|
}
|
|
843
|
-
if (
|
|
844
|
-
|
|
845
|
-
|
|
571
|
+
if (definition.encode) {
|
|
572
|
+
encode[identifier] = definition.encode;
|
|
573
|
+
}
|
|
574
|
+
if (definition.decode) {
|
|
575
|
+
decode[identifier] = definition.decode;
|
|
846
576
|
}
|
|
847
577
|
}
|
|
848
|
-
function
|
|
849
|
-
|
|
850
|
-
|
|
578
|
+
function getType(identifier) {
|
|
579
|
+
return registeredTypes[identifier];
|
|
580
|
+
}
|
|
581
|
+
function defineCustomTypes(types) {
|
|
582
|
+
for (const identifier in types) {
|
|
583
|
+
registerType(identifier, types[identifier]);
|
|
851
584
|
}
|
|
585
|
+
return (t) => type(t);
|
|
852
586
|
}
|
|
853
587
|
|
|
854
|
-
|
|
855
|
-
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
588
|
+
class TypeContext {
|
|
589
|
+
/**
|
|
590
|
+
* For inheritance support
|
|
591
|
+
* Keeps track of which classes extends which. (parent -> children)
|
|
592
|
+
*/
|
|
593
|
+
static { this.inheritedTypes = new Map(); }
|
|
594
|
+
static register(target) {
|
|
595
|
+
const parent = Object.getPrototypeOf(target);
|
|
596
|
+
if (parent !== Schema) {
|
|
597
|
+
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
598
|
+
if (!inherits) {
|
|
599
|
+
inherits = new Set();
|
|
600
|
+
TypeContext.inheritedTypes.set(parent, inherits);
|
|
601
|
+
}
|
|
602
|
+
inherits.add(target);
|
|
603
|
+
}
|
|
863
604
|
}
|
|
864
|
-
|
|
865
|
-
|
|
866
|
-
|
|
867
|
-
|
|
868
|
-
|
|
869
|
-
|
|
870
|
-
|
|
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);
|
|
605
|
+
constructor(rootClass) {
|
|
606
|
+
this.types = {};
|
|
607
|
+
this.schemas = new Map();
|
|
608
|
+
this.hasFilters = false;
|
|
609
|
+
this.parentFiltered = {};
|
|
610
|
+
if (rootClass) {
|
|
611
|
+
this.discoverTypes(rootClass);
|
|
877
612
|
}
|
|
878
613
|
}
|
|
879
|
-
|
|
880
|
-
|
|
881
|
-
// Primitive values
|
|
882
|
-
//
|
|
883
|
-
encodePrimitiveType(type, bytes, value, ref, field, it);
|
|
614
|
+
has(schema) {
|
|
615
|
+
return this.schemas.has(schema);
|
|
884
616
|
}
|
|
885
|
-
|
|
886
|
-
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
|
|
891
|
-
|
|
892
|
-
|
|
893
|
-
|
|
617
|
+
get(typeid) {
|
|
618
|
+
return this.types[typeid];
|
|
619
|
+
}
|
|
620
|
+
add(schema, typeid = this.schemas.size) {
|
|
621
|
+
// skip if already registered
|
|
622
|
+
if (this.schemas.has(schema)) {
|
|
623
|
+
return false;
|
|
624
|
+
}
|
|
625
|
+
this.types[typeid] = schema;
|
|
894
626
|
//
|
|
895
|
-
//
|
|
896
|
-
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
627
|
+
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
897
628
|
//
|
|
898
|
-
|
|
899
|
-
|
|
900
|
-
}
|
|
901
|
-
/**
|
|
902
|
-
* Used for Schema instances.
|
|
903
|
-
* @private
|
|
904
|
-
*/
|
|
905
|
-
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
906
|
-
const ref = changeTree.ref;
|
|
907
|
-
const metadata = ref['constructor'][Symbol.metadata];
|
|
908
|
-
const field = metadata[index];
|
|
909
|
-
const type = metadata[field].type;
|
|
910
|
-
const value = ref[field];
|
|
911
|
-
// "compress" field index + operation
|
|
912
|
-
bytes[it.offset++] = (index | operation) & 255;
|
|
913
|
-
// Do not encode value for DELETE operations
|
|
914
|
-
if (operation === exports.OPERATION.DELETE) {
|
|
915
|
-
return;
|
|
916
|
-
}
|
|
917
|
-
// TODO: inline this function call small performance gain
|
|
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;
|
|
937
|
-
}
|
|
938
|
-
//
|
|
939
|
-
// encode "alias" for dynamic fields (maps)
|
|
940
|
-
//
|
|
941
|
-
if ((operation & exports.OPERATION.ADD) == exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
942
|
-
if (typeof (ref['set']) === "function") {
|
|
943
|
-
//
|
|
944
|
-
// MapSchema dynamic key
|
|
945
|
-
//
|
|
946
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(field);
|
|
947
|
-
string$1(bytes, dynamicIndex, it);
|
|
629
|
+
if (schema[Symbol.metadata] === undefined) {
|
|
630
|
+
Metadata.initialize(schema);
|
|
948
631
|
}
|
|
632
|
+
this.schemas.set(schema, typeid);
|
|
633
|
+
return true;
|
|
949
634
|
}
|
|
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;
|
|
970
|
-
}
|
|
971
|
-
// encode operation
|
|
972
|
-
bytes[it.offset++] = operation & 255;
|
|
973
|
-
// custom operations
|
|
974
|
-
if (operation === exports.OPERATION.CLEAR) {
|
|
975
|
-
return;
|
|
976
|
-
}
|
|
977
|
-
// encode index
|
|
978
|
-
number$1(bytes, field, it);
|
|
979
|
-
// Do not encode value for DELETE operations
|
|
980
|
-
if (operation === exports.OPERATION.DELETE) {
|
|
981
|
-
return;
|
|
635
|
+
getTypeId(klass) {
|
|
636
|
+
return this.schemas.get(klass);
|
|
982
637
|
}
|
|
983
|
-
|
|
984
|
-
|
|
985
|
-
|
|
986
|
-
// ref: changeTree.ref.constructor.name,
|
|
987
|
-
// field,
|
|
988
|
-
// operation: OPERATION[operation],
|
|
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;
|
|
638
|
+
discoverTypes(klass, parentIndex, parentFieldViewTag) {
|
|
639
|
+
if (!this.add(klass)) {
|
|
640
|
+
return;
|
|
1025
641
|
}
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
642
|
+
// add classes inherited from this base class
|
|
643
|
+
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
644
|
+
this.discoverTypes(child, parentIndex, parentFieldViewTag);
|
|
645
|
+
});
|
|
646
|
+
// add parent classes
|
|
647
|
+
let parent = klass;
|
|
648
|
+
while ((parent = Object.getPrototypeOf(parent)) &&
|
|
649
|
+
parent !== Schema && // stop at root (Schema)
|
|
650
|
+
parent !== Function.prototype // stop at root (non-Schema)
|
|
651
|
+
) {
|
|
652
|
+
this.discoverTypes(parent);
|
|
653
|
+
}
|
|
654
|
+
const metadata = (klass[Symbol.metadata] ??= {});
|
|
655
|
+
// if any schema/field has filters, mark "context" as having filters.
|
|
656
|
+
if (metadata[$viewFieldIndexes]) {
|
|
657
|
+
this.hasFilters = true;
|
|
1030
658
|
}
|
|
1031
|
-
if (
|
|
1032
|
-
|
|
1033
|
-
((bytes[++i] & 0x3f) << 6) |
|
|
1034
|
-
((bytes[++i] & 0x3f) << 0));
|
|
1035
|
-
continue;
|
|
659
|
+
if (parentFieldViewTag !== undefined) {
|
|
660
|
+
this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
|
|
1036
661
|
}
|
|
1037
|
-
|
|
1038
|
-
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
|
|
1042
|
-
|
|
1043
|
-
|
|
1044
|
-
|
|
662
|
+
for (const fieldIndex in metadata) {
|
|
663
|
+
const index = fieldIndex;
|
|
664
|
+
const fieldType = metadata[index].type;
|
|
665
|
+
const viewTag = metadata[index].tag;
|
|
666
|
+
if (typeof (fieldType) === "string") {
|
|
667
|
+
continue;
|
|
668
|
+
}
|
|
669
|
+
if (Array.isArray(fieldType)) {
|
|
670
|
+
const type = fieldType[0];
|
|
671
|
+
// skip primitive types
|
|
672
|
+
if (type === "string") {
|
|
673
|
+
continue;
|
|
674
|
+
}
|
|
675
|
+
this.discoverTypes(type, index, viewTag);
|
|
676
|
+
}
|
|
677
|
+
else if (typeof (fieldType) === "function") {
|
|
678
|
+
this.discoverTypes(fieldType, viewTag);
|
|
1045
679
|
}
|
|
1046
680
|
else {
|
|
1047
|
-
|
|
681
|
+
const type = Object.values(fieldType)[0];
|
|
682
|
+
// skip primitive types
|
|
683
|
+
if (typeof (type) === "string") {
|
|
684
|
+
continue;
|
|
685
|
+
}
|
|
686
|
+
this.discoverTypes(type, index, viewTag);
|
|
1048
687
|
}
|
|
1049
|
-
continue;
|
|
1050
688
|
}
|
|
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
689
|
}
|
|
1055
|
-
it.offset += length;
|
|
1056
|
-
return string;
|
|
1057
|
-
}
|
|
1058
|
-
function int8(bytes, it) {
|
|
1059
|
-
return uint8(bytes, it) << 24 >> 24;
|
|
1060
690
|
}
|
|
1061
|
-
|
|
1062
|
-
|
|
691
|
+
|
|
692
|
+
function getNormalizedType(type) {
|
|
693
|
+
return (Array.isArray(type))
|
|
694
|
+
? { array: type[0] }
|
|
695
|
+
: (typeof (type['type']) !== "undefined")
|
|
696
|
+
? type['type']
|
|
697
|
+
: type;
|
|
1063
698
|
}
|
|
1064
|
-
|
|
1065
|
-
|
|
1066
|
-
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
699
|
+
const Metadata = {
|
|
700
|
+
addField(metadata, index, name, type, descriptor) {
|
|
701
|
+
if (index > 64) {
|
|
702
|
+
throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
|
|
703
|
+
}
|
|
704
|
+
metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
|
|
705
|
+
{
|
|
706
|
+
type: getNormalizedType(type),
|
|
707
|
+
index,
|
|
708
|
+
name,
|
|
709
|
+
});
|
|
710
|
+
// create "descriptors" map
|
|
711
|
+
Object.defineProperty(metadata, $descriptors, {
|
|
712
|
+
value: metadata[$descriptors] || {},
|
|
713
|
+
enumerable: false,
|
|
714
|
+
configurable: true,
|
|
715
|
+
});
|
|
716
|
+
if (descriptor) {
|
|
717
|
+
// for encoder
|
|
718
|
+
metadata[$descriptors][name] = descriptor;
|
|
719
|
+
metadata[$descriptors][`_${name}`] = {
|
|
720
|
+
value: undefined,
|
|
721
|
+
writable: true,
|
|
722
|
+
enumerable: false,
|
|
723
|
+
configurable: true,
|
|
724
|
+
};
|
|
725
|
+
}
|
|
726
|
+
else {
|
|
727
|
+
// for decoder
|
|
728
|
+
metadata[$descriptors][name] = {
|
|
729
|
+
value: undefined,
|
|
730
|
+
writable: true,
|
|
731
|
+
enumerable: true,
|
|
732
|
+
configurable: true,
|
|
733
|
+
};
|
|
734
|
+
}
|
|
735
|
+
// map -1 as last field index
|
|
736
|
+
Object.defineProperty(metadata, $numFields, {
|
|
737
|
+
value: index,
|
|
738
|
+
enumerable: false,
|
|
739
|
+
configurable: true
|
|
740
|
+
});
|
|
741
|
+
// map field name => index (non enumerable)
|
|
742
|
+
Object.defineProperty(metadata, name, {
|
|
743
|
+
value: index,
|
|
744
|
+
enumerable: false,
|
|
745
|
+
configurable: true,
|
|
746
|
+
});
|
|
747
|
+
// if child Ref/complex type, add to -4
|
|
748
|
+
if (typeof (metadata[index].type) !== "string") {
|
|
749
|
+
if (metadata[$refTypeFieldIndexes] === undefined) {
|
|
750
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
751
|
+
value: [],
|
|
752
|
+
enumerable: false,
|
|
753
|
+
configurable: true,
|
|
754
|
+
});
|
|
755
|
+
}
|
|
756
|
+
metadata[$refTypeFieldIndexes].push(index);
|
|
757
|
+
}
|
|
758
|
+
},
|
|
759
|
+
setTag(metadata, fieldName, tag) {
|
|
760
|
+
const index = metadata[fieldName];
|
|
761
|
+
const field = metadata[index];
|
|
762
|
+
// add 'tag' to the field
|
|
763
|
+
field.tag = tag;
|
|
764
|
+
if (!metadata[$viewFieldIndexes]) {
|
|
765
|
+
// -2: all field indexes with "view" tag
|
|
766
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
767
|
+
value: [],
|
|
768
|
+
enumerable: false,
|
|
769
|
+
configurable: true
|
|
770
|
+
});
|
|
771
|
+
// -3: field indexes by "view" tag
|
|
772
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
773
|
+
value: {},
|
|
774
|
+
enumerable: false,
|
|
775
|
+
configurable: true
|
|
776
|
+
});
|
|
777
|
+
}
|
|
778
|
+
metadata[$viewFieldIndexes].push(index);
|
|
779
|
+
if (!metadata[$fieldIndexesByViewTag][tag]) {
|
|
780
|
+
metadata[$fieldIndexesByViewTag][tag] = [];
|
|
781
|
+
}
|
|
782
|
+
metadata[$fieldIndexesByViewTag][tag].push(index);
|
|
783
|
+
},
|
|
784
|
+
setFields(target, fields) {
|
|
785
|
+
// for inheritance support
|
|
786
|
+
const constructor = target.prototype.constructor;
|
|
787
|
+
TypeContext.register(constructor);
|
|
788
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
789
|
+
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
790
|
+
const metadata = Metadata.initialize(constructor);
|
|
791
|
+
// Use Schema's methods if not defined in the class
|
|
792
|
+
if (!constructor[$track]) {
|
|
793
|
+
constructor[$track] = Schema[$track];
|
|
794
|
+
}
|
|
795
|
+
if (!constructor[$encoder]) {
|
|
796
|
+
constructor[$encoder] = Schema[$encoder];
|
|
797
|
+
}
|
|
798
|
+
if (!constructor[$decoder]) {
|
|
799
|
+
constructor[$decoder] = Schema[$decoder];
|
|
800
|
+
}
|
|
801
|
+
if (!constructor.prototype.toJSON) {
|
|
802
|
+
constructor.prototype.toJSON = Schema.prototype.toJSON;
|
|
803
|
+
}
|
|
804
|
+
//
|
|
805
|
+
// detect index for this field, considering inheritance
|
|
806
|
+
//
|
|
807
|
+
let fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
808
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
809
|
+
?? -1; // no fields defined
|
|
810
|
+
fieldIndex++;
|
|
811
|
+
for (const field in fields) {
|
|
812
|
+
const type = fields[field];
|
|
813
|
+
const normalizedType = getNormalizedType(type);
|
|
814
|
+
// FIXME: this code is duplicated from @type() annotation
|
|
815
|
+
const complexTypeKlass = (Array.isArray(type))
|
|
816
|
+
? getType("array")
|
|
817
|
+
: (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
|
|
818
|
+
const childType = (complexTypeKlass)
|
|
819
|
+
? Object.values(type)[0]
|
|
820
|
+
: normalizedType;
|
|
821
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
822
|
+
fieldIndex++;
|
|
823
|
+
}
|
|
824
|
+
return target;
|
|
825
|
+
},
|
|
826
|
+
isDeprecated(metadata, field) {
|
|
827
|
+
return metadata[field].deprecated === true;
|
|
828
|
+
},
|
|
829
|
+
init(klass) {
|
|
830
|
+
//
|
|
831
|
+
// Used only to initialize an empty Schema (Encoder#constructor)
|
|
832
|
+
// TODO: remove/refactor this...
|
|
833
|
+
//
|
|
834
|
+
const metadata = {};
|
|
835
|
+
klass[Symbol.metadata] = metadata;
|
|
836
|
+
Object.defineProperty(metadata, $numFields, {
|
|
837
|
+
value: 0,
|
|
838
|
+
enumerable: false,
|
|
839
|
+
configurable: true,
|
|
840
|
+
});
|
|
841
|
+
},
|
|
842
|
+
initialize(constructor) {
|
|
843
|
+
const parentClass = Object.getPrototypeOf(constructor);
|
|
844
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
845
|
+
let metadata = constructor[Symbol.metadata] ?? Object.create(null);
|
|
846
|
+
// make sure inherited classes have their own metadata object.
|
|
847
|
+
if (parentClass !== Schema && metadata === parentMetadata) {
|
|
848
|
+
metadata = Object.create(null);
|
|
849
|
+
if (parentMetadata) {
|
|
850
|
+
//
|
|
851
|
+
// assign parent metadata to current
|
|
852
|
+
//
|
|
853
|
+
Object.setPrototypeOf(metadata, parentMetadata);
|
|
854
|
+
// $numFields
|
|
855
|
+
Object.defineProperty(metadata, $numFields, {
|
|
856
|
+
value: parentMetadata[$numFields],
|
|
857
|
+
enumerable: false,
|
|
858
|
+
configurable: true,
|
|
859
|
+
writable: true,
|
|
860
|
+
});
|
|
861
|
+
// $viewFieldIndexes / $fieldIndexesByViewTag
|
|
862
|
+
if (parentMetadata[$viewFieldIndexes] !== undefined) {
|
|
863
|
+
Object.defineProperty(metadata, $viewFieldIndexes, {
|
|
864
|
+
value: [...parentMetadata[$viewFieldIndexes]],
|
|
865
|
+
enumerable: false,
|
|
866
|
+
configurable: true,
|
|
867
|
+
writable: true,
|
|
868
|
+
});
|
|
869
|
+
Object.defineProperty(metadata, $fieldIndexesByViewTag, {
|
|
870
|
+
value: { ...parentMetadata[$fieldIndexesByViewTag] },
|
|
871
|
+
enumerable: false,
|
|
872
|
+
configurable: true,
|
|
873
|
+
writable: true,
|
|
874
|
+
});
|
|
875
|
+
}
|
|
876
|
+
// $refTypeFieldIndexes
|
|
877
|
+
if (parentMetadata[$refTypeFieldIndexes] !== undefined) {
|
|
878
|
+
Object.defineProperty(metadata, $refTypeFieldIndexes, {
|
|
879
|
+
value: [...parentMetadata[$refTypeFieldIndexes]],
|
|
880
|
+
enumerable: false,
|
|
881
|
+
configurable: true,
|
|
882
|
+
writable: true,
|
|
883
|
+
});
|
|
884
|
+
}
|
|
885
|
+
// $descriptors
|
|
886
|
+
Object.defineProperty(metadata, $descriptors, {
|
|
887
|
+
value: { ...parentMetadata[$descriptors] },
|
|
888
|
+
enumerable: false,
|
|
889
|
+
configurable: true,
|
|
890
|
+
writable: true,
|
|
891
|
+
});
|
|
892
|
+
}
|
|
893
|
+
}
|
|
894
|
+
constructor[Symbol.metadata] = metadata;
|
|
895
|
+
return metadata;
|
|
896
|
+
},
|
|
897
|
+
isValidInstance(klass) {
|
|
898
|
+
return (klass.constructor[Symbol.metadata] &&
|
|
899
|
+
Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
|
|
900
|
+
},
|
|
901
|
+
getFields(klass) {
|
|
902
|
+
const metadata = klass[Symbol.metadata];
|
|
903
|
+
const fields = {};
|
|
904
|
+
for (let i = 0; i <= metadata[$numFields]; i++) {
|
|
905
|
+
fields[metadata[i].name] = metadata[i].type;
|
|
906
|
+
}
|
|
907
|
+
return fields;
|
|
908
|
+
}
|
|
909
|
+
};
|
|
910
|
+
|
|
911
|
+
function setOperationAtIndex(changeSet, index) {
|
|
912
|
+
const operationsIndex = changeSet.indexes[index];
|
|
913
|
+
if (operationsIndex === undefined) {
|
|
914
|
+
changeSet.indexes[index] = changeSet.operations.push(index) - 1;
|
|
915
|
+
}
|
|
916
|
+
else {
|
|
917
|
+
changeSet.operations[operationsIndex] = index;
|
|
918
|
+
}
|
|
1098
919
|
}
|
|
1099
|
-
function
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
920
|
+
function deleteOperationAtIndex(changeSet, index) {
|
|
921
|
+
const operationsIndex = changeSet.indexes[index];
|
|
922
|
+
if (operationsIndex !== undefined) {
|
|
923
|
+
changeSet.operations[operationsIndex] = undefined;
|
|
924
|
+
}
|
|
925
|
+
delete changeSet.indexes[index];
|
|
1103
926
|
}
|
|
1104
|
-
function
|
|
1105
|
-
|
|
927
|
+
function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
|
|
928
|
+
if (root && root[changeSet][queueRootIndex] !== changeTree) {
|
|
929
|
+
changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
|
|
930
|
+
}
|
|
1106
931
|
}
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
932
|
+
class ChangeTree {
|
|
933
|
+
constructor(ref) {
|
|
934
|
+
this.isFiltered = false;
|
|
935
|
+
this.isPartiallyFiltered = false;
|
|
936
|
+
this.indexedOperations = {};
|
|
937
|
+
//
|
|
938
|
+
// TODO:
|
|
939
|
+
// try storing the index + operation per item.
|
|
940
|
+
// example: 1024 & 1025 => ADD, 1026 => DELETE
|
|
941
|
+
//
|
|
942
|
+
// => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
|
|
943
|
+
//
|
|
944
|
+
this.changes = { indexes: {}, operations: [] };
|
|
945
|
+
this.allChanges = { indexes: {}, operations: [] };
|
|
946
|
+
/**
|
|
947
|
+
* Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
|
|
948
|
+
*/
|
|
949
|
+
this.isNew = true;
|
|
950
|
+
this.ref = ref;
|
|
951
|
+
//
|
|
952
|
+
// Does this structure have "filters" declared?
|
|
953
|
+
//
|
|
954
|
+
if (ref.constructor[Symbol.metadata]?.[$viewFieldIndexes]) {
|
|
955
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
956
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
957
|
+
}
|
|
1113
958
|
}
|
|
1114
|
-
|
|
1115
|
-
|
|
959
|
+
setRoot(root) {
|
|
960
|
+
this.root = root;
|
|
961
|
+
const isNewChangeTree = this.root.add(this);
|
|
962
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
963
|
+
if (this.root.types.hasFilters) {
|
|
964
|
+
//
|
|
965
|
+
// At Schema initialization, the "root" structure might not be available
|
|
966
|
+
// yet, as it only does once the "Encoder" has been set up.
|
|
967
|
+
//
|
|
968
|
+
// So the "parent" may be already set without a "root".
|
|
969
|
+
//
|
|
970
|
+
this.checkIsFiltered(metadata, this.parent, this.parentIndex);
|
|
971
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
972
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
973
|
+
if (isNewChangeTree) {
|
|
974
|
+
this.root.allFilteredChanges.push(this);
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
}
|
|
978
|
+
if (!this.isFiltered) {
|
|
979
|
+
enqueueChangeTree(root, this, 'changes');
|
|
980
|
+
if (isNewChangeTree) {
|
|
981
|
+
this.root.allChanges.push(this);
|
|
982
|
+
}
|
|
983
|
+
}
|
|
984
|
+
// Recursively set root on child structures
|
|
985
|
+
if (metadata) {
|
|
986
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
987
|
+
const field = metadata[index];
|
|
988
|
+
const value = this.ref[field.name];
|
|
989
|
+
value?.[$changes].setRoot(root);
|
|
990
|
+
});
|
|
991
|
+
}
|
|
992
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
993
|
+
// MapSchema / ArraySchema, etc.
|
|
994
|
+
this.ref.forEach((value, key) => {
|
|
995
|
+
value[$changes].setRoot(root);
|
|
996
|
+
});
|
|
997
|
+
}
|
|
1116
998
|
}
|
|
1117
|
-
|
|
1118
|
-
|
|
999
|
+
setParent(parent, root, parentIndex) {
|
|
1000
|
+
this.parent = parent;
|
|
1001
|
+
this.parentIndex = parentIndex;
|
|
1002
|
+
// avoid setting parents with empty `root`
|
|
1003
|
+
if (!root) {
|
|
1004
|
+
return;
|
|
1005
|
+
}
|
|
1006
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1007
|
+
// skip if parent is already set
|
|
1008
|
+
if (root !== this.root) {
|
|
1009
|
+
this.root = root;
|
|
1010
|
+
const isNewChangeTree = root.add(this);
|
|
1011
|
+
if (root.types.hasFilters) {
|
|
1012
|
+
this.checkIsFiltered(metadata, parent, parentIndex);
|
|
1013
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
1014
|
+
enqueueChangeTree(root, this, 'filteredChanges');
|
|
1015
|
+
if (isNewChangeTree) {
|
|
1016
|
+
this.root.allFilteredChanges.push(this);
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
}
|
|
1020
|
+
if (!this.isFiltered) {
|
|
1021
|
+
enqueueChangeTree(root, this, 'changes');
|
|
1022
|
+
if (isNewChangeTree) {
|
|
1023
|
+
this.root.allChanges.push(this);
|
|
1024
|
+
}
|
|
1025
|
+
}
|
|
1026
|
+
}
|
|
1027
|
+
else {
|
|
1028
|
+
root.add(this);
|
|
1029
|
+
}
|
|
1030
|
+
// assign same parent on child structures
|
|
1031
|
+
if (metadata) {
|
|
1032
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
1033
|
+
const field = metadata[index];
|
|
1034
|
+
const value = this.ref[field.name];
|
|
1035
|
+
value?.[$changes].setParent(this.ref, root, index);
|
|
1036
|
+
// try { throw new Error(); } catch (e) {
|
|
1037
|
+
// console.log(e.stack);
|
|
1038
|
+
// }
|
|
1039
|
+
});
|
|
1040
|
+
}
|
|
1041
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
1042
|
+
// MapSchema / ArraySchema, etc.
|
|
1043
|
+
this.ref.forEach((value, key) => {
|
|
1044
|
+
value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
|
|
1045
|
+
});
|
|
1046
|
+
}
|
|
1119
1047
|
}
|
|
1120
|
-
|
|
1121
|
-
|
|
1048
|
+
forEachChild(callback) {
|
|
1049
|
+
//
|
|
1050
|
+
// assign same parent on child structures
|
|
1051
|
+
//
|
|
1052
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1053
|
+
if (metadata) {
|
|
1054
|
+
metadata[$refTypeFieldIndexes]?.forEach((index) => {
|
|
1055
|
+
const field = metadata[index];
|
|
1056
|
+
const value = this.ref[field.name];
|
|
1057
|
+
if (value) {
|
|
1058
|
+
callback(value[$changes], index);
|
|
1059
|
+
}
|
|
1060
|
+
});
|
|
1061
|
+
}
|
|
1062
|
+
else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
|
|
1063
|
+
// MapSchema / ArraySchema, etc.
|
|
1064
|
+
this.ref.forEach((value, key) => {
|
|
1065
|
+
callback(value[$changes], this.indexes[key] ?? key);
|
|
1066
|
+
});
|
|
1067
|
+
}
|
|
1122
1068
|
}
|
|
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;
|
|
1069
|
+
operation(op) {
|
|
1070
|
+
// operations without index use negative values to represent them
|
|
1071
|
+
// this is checked during .encode() time.
|
|
1072
|
+
this.changes.operations.push(-op);
|
|
1073
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1142
1074
|
}
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1075
|
+
change(index, operation = exports.OPERATION.ADD) {
|
|
1076
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1077
|
+
const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
|
|
1078
|
+
const changeSet = (isFiltered)
|
|
1079
|
+
? this.filteredChanges
|
|
1080
|
+
: this.changes;
|
|
1081
|
+
const previousOperation = this.indexedOperations[index];
|
|
1082
|
+
if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
|
|
1083
|
+
const op = (!previousOperation)
|
|
1084
|
+
? operation
|
|
1085
|
+
: (previousOperation === exports.OPERATION.DELETE)
|
|
1086
|
+
? exports.OPERATION.DELETE_AND_ADD
|
|
1087
|
+
: operation;
|
|
1088
|
+
//
|
|
1089
|
+
// TODO: are DELETE operations being encoded as ADD here ??
|
|
1090
|
+
//
|
|
1091
|
+
this.indexedOperations[index] = op;
|
|
1092
|
+
}
|
|
1093
|
+
setOperationAtIndex(changeSet, index);
|
|
1094
|
+
if (isFiltered) {
|
|
1095
|
+
setOperationAtIndex(this.allFilteredChanges, index);
|
|
1096
|
+
if (this.root) {
|
|
1097
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1098
|
+
enqueueChangeTree(this.root, this, 'allFilteredChanges');
|
|
1099
|
+
}
|
|
1100
|
+
}
|
|
1101
|
+
else {
|
|
1102
|
+
setOperationAtIndex(this.allChanges, index);
|
|
1103
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1104
|
+
}
|
|
1146
1105
|
}
|
|
1147
|
-
|
|
1148
|
-
//
|
|
1149
|
-
|
|
1106
|
+
shiftChangeIndexes(shiftIndex) {
|
|
1107
|
+
//
|
|
1108
|
+
// Used only during:
|
|
1109
|
+
//
|
|
1110
|
+
// - ArraySchema#unshift()
|
|
1111
|
+
//
|
|
1112
|
+
const changeSet = (this.isFiltered)
|
|
1113
|
+
? this.filteredChanges
|
|
1114
|
+
: this.changes;
|
|
1115
|
+
const newIndexedOperations = {};
|
|
1116
|
+
const newIndexes = {};
|
|
1117
|
+
for (const index in this.indexedOperations) {
|
|
1118
|
+
newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
|
|
1119
|
+
newIndexes[Number(index) + shiftIndex] = changeSet[index];
|
|
1120
|
+
}
|
|
1121
|
+
this.indexedOperations = newIndexedOperations;
|
|
1122
|
+
changeSet.indexes = newIndexes;
|
|
1123
|
+
changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
|
|
1124
|
+
}
|
|
1125
|
+
shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
|
|
1126
|
+
//
|
|
1127
|
+
// Used only during:
|
|
1128
|
+
//
|
|
1129
|
+
// - ArraySchema#splice()
|
|
1130
|
+
//
|
|
1131
|
+
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
1132
|
+
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
|
|
1133
|
+
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
1134
|
+
}
|
|
1135
|
+
else {
|
|
1136
|
+
this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
_shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
|
|
1140
|
+
const newIndexes = {};
|
|
1141
|
+
for (const key in changeSet.indexes) {
|
|
1142
|
+
const index = changeSet.indexes[key];
|
|
1143
|
+
if (index > startIndex) {
|
|
1144
|
+
newIndexes[Number(key) + shiftIndex] = index;
|
|
1145
|
+
}
|
|
1146
|
+
else {
|
|
1147
|
+
newIndexes[key] = index;
|
|
1148
|
+
}
|
|
1149
|
+
}
|
|
1150
|
+
changeSet.indexes = newIndexes;
|
|
1151
|
+
for (let i = 0; i < changeSet.operations.length; i++) {
|
|
1152
|
+
const index = changeSet.operations[i];
|
|
1153
|
+
if (index > startIndex) {
|
|
1154
|
+
changeSet.operations[i] = index + shiftIndex;
|
|
1155
|
+
}
|
|
1156
|
+
}
|
|
1157
|
+
}
|
|
1158
|
+
indexedOperation(index, operation, allChangesIndex = index) {
|
|
1159
|
+
this.indexedOperations[index] = operation;
|
|
1160
|
+
if (this.filteredChanges) {
|
|
1161
|
+
setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
1162
|
+
setOperationAtIndex(this.filteredChanges, index);
|
|
1163
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1164
|
+
}
|
|
1165
|
+
else {
|
|
1166
|
+
setOperationAtIndex(this.allChanges, allChangesIndex);
|
|
1167
|
+
setOperationAtIndex(this.changes, index);
|
|
1168
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1169
|
+
}
|
|
1170
|
+
}
|
|
1171
|
+
getType(index) {
|
|
1172
|
+
if (Metadata.isValidInstance(this.ref)) {
|
|
1173
|
+
const metadata = this.ref.constructor[Symbol.metadata];
|
|
1174
|
+
return metadata[index].type;
|
|
1175
|
+
}
|
|
1176
|
+
else {
|
|
1177
|
+
//
|
|
1178
|
+
// Get the child type from parent structure.
|
|
1179
|
+
// - ["string"] => "string"
|
|
1180
|
+
// - { map: "string" } => "string"
|
|
1181
|
+
// - { set: "string" } => "string"
|
|
1182
|
+
//
|
|
1183
|
+
return this.ref[$childType];
|
|
1184
|
+
}
|
|
1185
|
+
}
|
|
1186
|
+
getChange(index) {
|
|
1187
|
+
return this.indexedOperations[index];
|
|
1188
|
+
}
|
|
1189
|
+
//
|
|
1190
|
+
// used during `.encode()`
|
|
1191
|
+
//
|
|
1192
|
+
getValue(index, isEncodeAll = false) {
|
|
1193
|
+
//
|
|
1194
|
+
// `isEncodeAll` param is only used by ArraySchema
|
|
1195
|
+
//
|
|
1196
|
+
return this.ref[$getByIndex](index, isEncodeAll);
|
|
1197
|
+
}
|
|
1198
|
+
delete(index, operation, allChangesIndex = index) {
|
|
1199
|
+
if (index === undefined) {
|
|
1200
|
+
try {
|
|
1201
|
+
throw new Error(`@colyseus/schema ${this.ref.constructor.name}: trying to delete non-existing index '${index}'`);
|
|
1202
|
+
}
|
|
1203
|
+
catch (e) {
|
|
1204
|
+
console.warn(e);
|
|
1205
|
+
}
|
|
1206
|
+
return;
|
|
1207
|
+
}
|
|
1208
|
+
const changeSet = (this.filteredChanges)
|
|
1209
|
+
? this.filteredChanges
|
|
1210
|
+
: this.changes;
|
|
1211
|
+
this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
|
|
1212
|
+
setOperationAtIndex(changeSet, index);
|
|
1213
|
+
const previousValue = this.getValue(index);
|
|
1214
|
+
// remove `root` reference
|
|
1215
|
+
if (previousValue && previousValue[$changes]) {
|
|
1216
|
+
//
|
|
1217
|
+
// FIXME: this.root is "undefined"
|
|
1218
|
+
//
|
|
1219
|
+
// This method is being called at decoding time when a DELETE operation is found.
|
|
1220
|
+
//
|
|
1221
|
+
// - This is due to using the concrete Schema class at decoding time.
|
|
1222
|
+
// - "Reflected" structures do not have this problem.
|
|
1223
|
+
//
|
|
1224
|
+
// (the property descriptors should NOT be used at decoding time. only at encoding time.)
|
|
1225
|
+
//
|
|
1226
|
+
this.root?.remove(previousValue[$changes]);
|
|
1227
|
+
}
|
|
1228
|
+
//
|
|
1229
|
+
// FIXME: this is looking a ugly and repeated
|
|
1230
|
+
//
|
|
1231
|
+
if (this.filteredChanges) {
|
|
1232
|
+
deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
|
|
1233
|
+
enqueueChangeTree(this.root, this, 'filteredChanges');
|
|
1234
|
+
}
|
|
1235
|
+
else {
|
|
1236
|
+
deleteOperationAtIndex(this.allChanges, allChangesIndex);
|
|
1237
|
+
enqueueChangeTree(this.root, this, 'changes');
|
|
1238
|
+
}
|
|
1239
|
+
}
|
|
1240
|
+
endEncode() {
|
|
1241
|
+
this.indexedOperations = {};
|
|
1242
|
+
// // clear changes
|
|
1243
|
+
// this.changes.indexes = {};
|
|
1244
|
+
// this.changes.operations.length = 0;
|
|
1245
|
+
// ArraySchema and MapSchema have a custom "encode end" method
|
|
1246
|
+
this.ref[$onEncodeEnd]?.();
|
|
1247
|
+
// Not a new instance anymore
|
|
1248
|
+
this.isNew = false;
|
|
1249
|
+
}
|
|
1250
|
+
discard(discardAll = false) {
|
|
1251
|
+
//
|
|
1252
|
+
// > MapSchema:
|
|
1253
|
+
// Remove cached key to ensure ADD operations is unsed instead of
|
|
1254
|
+
// REPLACE in case same key is used on next patches.
|
|
1255
|
+
//
|
|
1256
|
+
this.ref[$onEncodeEnd]?.();
|
|
1257
|
+
this.indexedOperations = {};
|
|
1258
|
+
this.changes.indexes = {};
|
|
1259
|
+
this.changes.operations.length = 0;
|
|
1260
|
+
this.changes.queueRootIndex = undefined;
|
|
1261
|
+
if (this.filteredChanges !== undefined) {
|
|
1262
|
+
this.filteredChanges.indexes = {};
|
|
1263
|
+
this.filteredChanges.operations.length = 0;
|
|
1264
|
+
this.filteredChanges.queueRootIndex = undefined;
|
|
1265
|
+
}
|
|
1266
|
+
if (discardAll) {
|
|
1267
|
+
this.allChanges.indexes = {};
|
|
1268
|
+
this.allChanges.operations.length = 0;
|
|
1269
|
+
if (this.allFilteredChanges !== undefined) {
|
|
1270
|
+
this.allFilteredChanges.indexes = {};
|
|
1271
|
+
this.allFilteredChanges.operations.length = 0;
|
|
1272
|
+
}
|
|
1273
|
+
// remove children references
|
|
1274
|
+
this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
|
|
1275
|
+
}
|
|
1276
|
+
}
|
|
1277
|
+
/**
|
|
1278
|
+
* Recursively discard all changes from this, and child structures.
|
|
1279
|
+
*/
|
|
1280
|
+
discardAll() {
|
|
1281
|
+
const keys = Object.keys(this.indexedOperations);
|
|
1282
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
1283
|
+
const value = this.getValue(Number(keys[i]));
|
|
1284
|
+
if (value && value[$changes]) {
|
|
1285
|
+
value[$changes].discardAll();
|
|
1286
|
+
}
|
|
1287
|
+
}
|
|
1288
|
+
this.discard();
|
|
1289
|
+
}
|
|
1290
|
+
ensureRefId() {
|
|
1291
|
+
// skip if refId is already set.
|
|
1292
|
+
if (this.refId !== undefined) {
|
|
1293
|
+
return;
|
|
1294
|
+
}
|
|
1295
|
+
this.refId = this.root.getNextUniqueId();
|
|
1296
|
+
}
|
|
1297
|
+
get changed() {
|
|
1298
|
+
return (Object.entries(this.indexedOperations).length > 0);
|
|
1299
|
+
}
|
|
1300
|
+
checkIsFiltered(metadata, parent, parentIndex) {
|
|
1301
|
+
// Detect if current structure has "filters" declared
|
|
1302
|
+
this.isPartiallyFiltered = metadata?.[$viewFieldIndexes] !== undefined;
|
|
1303
|
+
if (this.isPartiallyFiltered) {
|
|
1304
|
+
this.filteredChanges = this.filteredChanges || { indexes: {}, operations: [] };
|
|
1305
|
+
this.allFilteredChanges = this.allFilteredChanges || { indexes: {}, operations: [] };
|
|
1306
|
+
}
|
|
1307
|
+
// skip if parent is not set
|
|
1308
|
+
if (!parent) {
|
|
1309
|
+
return;
|
|
1310
|
+
}
|
|
1311
|
+
if (!Metadata.isValidInstance(parent)) {
|
|
1312
|
+
const parentChangeTree = parent[$changes];
|
|
1313
|
+
parent = parentChangeTree.parent;
|
|
1314
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
1315
|
+
}
|
|
1316
|
+
const parentMetadata = parent.constructor?.[Symbol.metadata];
|
|
1317
|
+
this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex);
|
|
1318
|
+
//
|
|
1319
|
+
// TODO: refactor this!
|
|
1320
|
+
//
|
|
1321
|
+
// swapping `changes` and `filteredChanges` is required here
|
|
1322
|
+
// because "isFiltered" may not be imedialely available on `change()`
|
|
1323
|
+
//
|
|
1324
|
+
if (this.isFiltered) {
|
|
1325
|
+
this.filteredChanges = { indexes: {}, operations: [] };
|
|
1326
|
+
this.allFilteredChanges = { indexes: {}, operations: [] };
|
|
1327
|
+
if (this.changes.operations.length > 0) {
|
|
1328
|
+
// swap changes reference
|
|
1329
|
+
const changes = this.changes;
|
|
1330
|
+
this.changes = this.filteredChanges;
|
|
1331
|
+
this.filteredChanges = changes;
|
|
1332
|
+
// swap "all changes" reference
|
|
1333
|
+
const allFilteredChanges = this.allFilteredChanges;
|
|
1334
|
+
this.allFilteredChanges = this.allChanges;
|
|
1335
|
+
this.allChanges = allFilteredChanges;
|
|
1336
|
+
// console.log("SWAP =>", {
|
|
1337
|
+
// "this.allFilteredChanges": this.allFilteredChanges,
|
|
1338
|
+
// "this.allChanges": this.allChanges
|
|
1339
|
+
// })
|
|
1340
|
+
}
|
|
1341
|
+
}
|
|
1342
|
+
}
|
|
1343
|
+
}
|
|
1344
|
+
|
|
1345
|
+
function encodeValue(encoder, bytes, type, value, operation, it) {
|
|
1346
|
+
if (typeof (type) === "string") {
|
|
1347
|
+
encode[type]?.(bytes, value, it);
|
|
1348
|
+
}
|
|
1349
|
+
else if (type[Symbol.metadata] !== undefined) {
|
|
1350
|
+
//
|
|
1351
|
+
// Encode refId for this instance.
|
|
1352
|
+
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1353
|
+
//
|
|
1354
|
+
encode.number(bytes, value[$changes].refId, it);
|
|
1355
|
+
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
1356
|
+
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
1357
|
+
encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
|
|
1358
|
+
}
|
|
1150
1359
|
}
|
|
1151
|
-
else
|
|
1152
|
-
//
|
|
1153
|
-
|
|
1360
|
+
else {
|
|
1361
|
+
//
|
|
1362
|
+
// Encode refId for this instance.
|
|
1363
|
+
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
1364
|
+
//
|
|
1365
|
+
encode.number(bytes, value[$changes].refId, it);
|
|
1154
1366
|
}
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1367
|
+
}
|
|
1368
|
+
/**
|
|
1369
|
+
* Used for Schema instances.
|
|
1370
|
+
* @private
|
|
1371
|
+
*/
|
|
1372
|
+
const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
|
|
1373
|
+
// "compress" field index + operation
|
|
1374
|
+
bytes[it.offset++] = (index | operation) & 255;
|
|
1375
|
+
// Do not encode value for DELETE operations
|
|
1376
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1377
|
+
return;
|
|
1158
1378
|
}
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1379
|
+
const ref = changeTree.ref;
|
|
1380
|
+
const field = metadata[index];
|
|
1381
|
+
// TODO: inline this function call small performance gain
|
|
1382
|
+
encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
|
|
1383
|
+
};
|
|
1384
|
+
/**
|
|
1385
|
+
* Used for collections (MapSchema, CollectionSchema, SetSchema)
|
|
1386
|
+
* @private
|
|
1387
|
+
*/
|
|
1388
|
+
const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
|
|
1389
|
+
// encode operation
|
|
1390
|
+
bytes[it.offset++] = operation & 255;
|
|
1391
|
+
// custom operations
|
|
1392
|
+
if (operation === exports.OPERATION.CLEAR) {
|
|
1393
|
+
return;
|
|
1162
1394
|
}
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1395
|
+
// encode index
|
|
1396
|
+
encode.number(bytes, index, it);
|
|
1397
|
+
// Do not encode value for DELETE operations
|
|
1398
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1399
|
+
return;
|
|
1166
1400
|
}
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1401
|
+
const ref = changeTree.ref;
|
|
1402
|
+
//
|
|
1403
|
+
// encode "alias" for dynamic fields (maps)
|
|
1404
|
+
//
|
|
1405
|
+
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
1406
|
+
if (typeof (ref['set']) === "function") {
|
|
1407
|
+
//
|
|
1408
|
+
// MapSchema dynamic key
|
|
1409
|
+
//
|
|
1410
|
+
const dynamicIndex = changeTree.ref['$indexes'].get(index);
|
|
1411
|
+
encode.string(bytes, dynamicIndex, it);
|
|
1412
|
+
}
|
|
1170
1413
|
}
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1414
|
+
const type = ref[$childType];
|
|
1415
|
+
const value = ref[$getByIndex](index);
|
|
1416
|
+
// try { throw new Error(); } catch (e) {
|
|
1417
|
+
// // only print if not coming from Reflection.ts
|
|
1418
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
1419
|
+
// console.log("encodeKeyValueOperation -> ", {
|
|
1420
|
+
// ref: changeTree.ref.constructor.name,
|
|
1421
|
+
// field,
|
|
1422
|
+
// operation: OPERATION[operation],
|
|
1423
|
+
// value: value?.toJSON(),
|
|
1424
|
+
// items: ref.toJSON(),
|
|
1425
|
+
// });
|
|
1426
|
+
// }
|
|
1427
|
+
// }
|
|
1428
|
+
// TODO: inline this function call small performance gain
|
|
1429
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
1430
|
+
};
|
|
1431
|
+
/**
|
|
1432
|
+
* Used for collections (MapSchema, ArraySchema, etc.)
|
|
1433
|
+
* @private
|
|
1434
|
+
*/
|
|
1435
|
+
const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
|
|
1436
|
+
const ref = changeTree.ref;
|
|
1437
|
+
const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
|
|
1438
|
+
let refOrIndex;
|
|
1439
|
+
if (useOperationByRefId) {
|
|
1440
|
+
refOrIndex = ref['tmpItems'][field][$changes].refId;
|
|
1441
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1442
|
+
operation = exports.OPERATION.DELETE_BY_REFID;
|
|
1443
|
+
}
|
|
1444
|
+
else if (operation === exports.OPERATION.ADD) {
|
|
1445
|
+
operation = exports.OPERATION.ADD_BY_REFID;
|
|
1446
|
+
}
|
|
1174
1447
|
}
|
|
1175
|
-
else
|
|
1176
|
-
|
|
1177
|
-
return int32(bytes, it);
|
|
1448
|
+
else {
|
|
1449
|
+
refOrIndex = field;
|
|
1178
1450
|
}
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1451
|
+
// encode operation
|
|
1452
|
+
bytes[it.offset++] = operation & 255;
|
|
1453
|
+
// custom operations
|
|
1454
|
+
if (operation === exports.OPERATION.CLEAR ||
|
|
1455
|
+
operation === exports.OPERATION.REVERSE) {
|
|
1456
|
+
return;
|
|
1182
1457
|
}
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1458
|
+
// encode index
|
|
1459
|
+
encode.number(bytes, refOrIndex, it);
|
|
1460
|
+
// Do not encode value for DELETE operations
|
|
1461
|
+
if (operation === exports.OPERATION.DELETE) {
|
|
1462
|
+
return;
|
|
1186
1463
|
}
|
|
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
|
-
});
|
|
1464
|
+
const type = changeTree.getType(field);
|
|
1465
|
+
const value = changeTree.getValue(field, isEncodeAll);
|
|
1466
|
+
// console.log("encodeArray -> ", {
|
|
1467
|
+
// ref: changeTree.ref.constructor.name,
|
|
1468
|
+
// field,
|
|
1469
|
+
// operation: OPERATION[operation],
|
|
1470
|
+
// value: value?.toJSON(),
|
|
1471
|
+
// items: ref.toJSON(),
|
|
1472
|
+
// });
|
|
1473
|
+
// TODO: inline this function call small performance gain
|
|
1474
|
+
encodeValue(encoder, bytes, type, value, operation, it);
|
|
1475
|
+
};
|
|
1248
1476
|
|
|
1249
1477
|
const DEFINITION_MISMATCH = -1;
|
|
1250
1478
|
function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
|
|
1251
|
-
const $root = decoder
|
|
1479
|
+
const $root = decoder.root;
|
|
1252
1480
|
const previousValue = ref[$getByIndex](index);
|
|
1253
1481
|
let value;
|
|
1254
1482
|
if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
@@ -1280,7 +1508,7 @@
|
|
|
1280
1508
|
}
|
|
1281
1509
|
if (operation === exports.OPERATION.DELETE) ;
|
|
1282
1510
|
else if (Schema.is(type)) {
|
|
1283
|
-
const refId = number(bytes, it);
|
|
1511
|
+
const refId = decode.number(bytes, it);
|
|
1284
1512
|
value = $root.refs.get(refId);
|
|
1285
1513
|
if (previousValue) {
|
|
1286
1514
|
const previousRefId = $root.refIds.get(previousValue);
|
|
@@ -1296,7 +1524,9 @@
|
|
|
1296
1524
|
if (!value) {
|
|
1297
1525
|
value = decoder.createInstanceOfType(childType);
|
|
1298
1526
|
}
|
|
1299
|
-
$root.addRef(refId, value, (value !== previousValue
|
|
1527
|
+
$root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed
|
|
1528
|
+
(operation === exports.OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
|
|
1529
|
+
));
|
|
1300
1530
|
}
|
|
1301
1531
|
}
|
|
1302
1532
|
else if (typeof (type) === "string") {
|
|
@@ -1307,7 +1537,7 @@
|
|
|
1307
1537
|
}
|
|
1308
1538
|
else {
|
|
1309
1539
|
const typeDef = getType(Object.keys(type)[0]);
|
|
1310
|
-
const refId = number(bytes, it);
|
|
1540
|
+
const refId = decode.number(bytes, it);
|
|
1311
1541
|
const valueRef = ($root.refs.has(refId))
|
|
1312
1542
|
? previousValue || $root.refs.get(refId)
|
|
1313
1543
|
: new typeDef.constructor();
|
|
@@ -1347,18 +1577,19 @@
|
|
|
1347
1577
|
}
|
|
1348
1578
|
const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
|
|
1349
1579
|
const first_byte = bytes[it.offset++];
|
|
1350
|
-
const metadata = ref
|
|
1580
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
1351
1581
|
// "compressed" index + operation
|
|
1352
1582
|
const operation = (first_byte >> 6) << 6;
|
|
1353
1583
|
const index = first_byte % (operation || 255);
|
|
1354
1584
|
// skip early if field is not defined
|
|
1355
1585
|
const field = metadata[index];
|
|
1356
1586
|
if (field === undefined) {
|
|
1587
|
+
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
1357
1588
|
return DEFINITION_MISMATCH;
|
|
1358
1589
|
}
|
|
1359
|
-
const { value, previousValue } = decodeValue(decoder, operation, ref, index,
|
|
1590
|
+
const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
|
|
1360
1591
|
if (value !== null && value !== undefined) {
|
|
1361
|
-
ref[field] = value;
|
|
1592
|
+
ref[field.name] = value;
|
|
1362
1593
|
}
|
|
1363
1594
|
// add change
|
|
1364
1595
|
if (previousValue !== value) {
|
|
@@ -1366,7 +1597,7 @@
|
|
|
1366
1597
|
ref,
|
|
1367
1598
|
refId: decoder.currentRefId,
|
|
1368
1599
|
op: operation,
|
|
1369
|
-
field: field,
|
|
1600
|
+
field: field.name,
|
|
1370
1601
|
value,
|
|
1371
1602
|
previousValue,
|
|
1372
1603
|
});
|
|
@@ -1385,12 +1616,12 @@
|
|
|
1385
1616
|
ref.clear();
|
|
1386
1617
|
return;
|
|
1387
1618
|
}
|
|
1388
|
-
const index = number(bytes, it);
|
|
1619
|
+
const index = decode.number(bytes, it);
|
|
1389
1620
|
const type = ref[$childType];
|
|
1390
1621
|
let dynamicIndex;
|
|
1391
1622
|
if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
1392
1623
|
if (typeof (ref['set']) === "function") {
|
|
1393
|
-
dynamicIndex = string(bytes, it); // MapSchema
|
|
1624
|
+
dynamicIndex = decode.string(bytes, it); // MapSchema
|
|
1394
1625
|
ref['setIndex'](index, dynamicIndex);
|
|
1395
1626
|
}
|
|
1396
1627
|
else {
|
|
@@ -1434,7 +1665,8 @@
|
|
|
1434
1665
|
};
|
|
1435
1666
|
const decodeArray = function (decoder, bytes, it, ref, allChanges) {
|
|
1436
1667
|
// "uncompressed" index + operation (array/map items)
|
|
1437
|
-
|
|
1668
|
+
let operation = bytes[it.offset++];
|
|
1669
|
+
let index;
|
|
1438
1670
|
if (operation === exports.OPERATION.CLEAR) {
|
|
1439
1671
|
//
|
|
1440
1672
|
// When decoding:
|
|
@@ -1445,11 +1677,15 @@
|
|
|
1445
1677
|
ref.clear();
|
|
1446
1678
|
return;
|
|
1447
1679
|
}
|
|
1680
|
+
else if (operation === exports.OPERATION.REVERSE) {
|
|
1681
|
+
ref.reverse();
|
|
1682
|
+
return;
|
|
1683
|
+
}
|
|
1448
1684
|
else if (operation === exports.OPERATION.DELETE_BY_REFID) {
|
|
1449
1685
|
// TODO: refactor here, try to follow same flow as below
|
|
1450
|
-
const refId = number(bytes, it);
|
|
1451
|
-
const previousValue = decoder
|
|
1452
|
-
|
|
1686
|
+
const refId = decode.number(bytes, it);
|
|
1687
|
+
const previousValue = decoder.root.refs.get(refId);
|
|
1688
|
+
index = ref.findIndex((value) => value === previousValue);
|
|
1453
1689
|
ref[$deleteByIndex](index);
|
|
1454
1690
|
allChanges.push({
|
|
1455
1691
|
ref,
|
|
@@ -1462,7 +1698,17 @@
|
|
|
1462
1698
|
});
|
|
1463
1699
|
return;
|
|
1464
1700
|
}
|
|
1465
|
-
|
|
1701
|
+
else if (operation === exports.OPERATION.ADD_BY_REFID) {
|
|
1702
|
+
const refId = decode.number(bytes, it);
|
|
1703
|
+
const itemByRefId = decoder.root.refs.get(refId);
|
|
1704
|
+
// use existing index, or push new value
|
|
1705
|
+
index = (itemByRefId)
|
|
1706
|
+
? ref.findIndex((value) => value === itemByRefId)
|
|
1707
|
+
: ref.length;
|
|
1708
|
+
}
|
|
1709
|
+
else {
|
|
1710
|
+
index = decode.number(bytes, it);
|
|
1711
|
+
}
|
|
1466
1712
|
const type = ref[$childType];
|
|
1467
1713
|
let dynamicIndex = index;
|
|
1468
1714
|
const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
|
|
@@ -1486,6 +1732,55 @@
|
|
|
1486
1732
|
}
|
|
1487
1733
|
};
|
|
1488
1734
|
|
|
1735
|
+
class EncodeSchemaError extends Error {
|
|
1736
|
+
}
|
|
1737
|
+
function assertType(value, type, klass, field) {
|
|
1738
|
+
let typeofTarget;
|
|
1739
|
+
let allowNull = false;
|
|
1740
|
+
switch (type) {
|
|
1741
|
+
case "number":
|
|
1742
|
+
case "int8":
|
|
1743
|
+
case "uint8":
|
|
1744
|
+
case "int16":
|
|
1745
|
+
case "uint16":
|
|
1746
|
+
case "int32":
|
|
1747
|
+
case "uint32":
|
|
1748
|
+
case "int64":
|
|
1749
|
+
case "uint64":
|
|
1750
|
+
case "float32":
|
|
1751
|
+
case "float64":
|
|
1752
|
+
typeofTarget = "number";
|
|
1753
|
+
if (isNaN(value)) {
|
|
1754
|
+
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
1755
|
+
}
|
|
1756
|
+
break;
|
|
1757
|
+
case "bigint64":
|
|
1758
|
+
case "biguint64":
|
|
1759
|
+
typeofTarget = "bigint";
|
|
1760
|
+
break;
|
|
1761
|
+
case "string":
|
|
1762
|
+
typeofTarget = "string";
|
|
1763
|
+
allowNull = true;
|
|
1764
|
+
break;
|
|
1765
|
+
case "boolean":
|
|
1766
|
+
// boolean is always encoded as true/false based on truthiness
|
|
1767
|
+
return;
|
|
1768
|
+
default:
|
|
1769
|
+
// skip assertion for custom types
|
|
1770
|
+
// TODO: allow custom types to define their own assertions
|
|
1771
|
+
return;
|
|
1772
|
+
}
|
|
1773
|
+
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
1774
|
+
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
1775
|
+
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
1776
|
+
}
|
|
1777
|
+
}
|
|
1778
|
+
function assertInstanceType(value, type, instance, field) {
|
|
1779
|
+
if (!(value instanceof type)) {
|
|
1780
|
+
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
|
|
1781
|
+
}
|
|
1782
|
+
}
|
|
1783
|
+
|
|
1489
1784
|
var _a$4, _b$4;
|
|
1490
1785
|
const DEFAULT_SORT = (a, b) => {
|
|
1491
1786
|
const A = a.toString();
|
|
@@ -1536,6 +1831,7 @@
|
|
|
1536
1831
|
const proxy = new Proxy(this, {
|
|
1537
1832
|
get: (obj, prop) => {
|
|
1538
1833
|
if (typeof (prop) !== "symbol" &&
|
|
1834
|
+
// FIXME: d8 accuses this as low performance
|
|
1539
1835
|
!isNaN(prop) // https://stackoverflow.com/a/175787/892698
|
|
1540
1836
|
) {
|
|
1541
1837
|
return this.items[prop];
|
|
@@ -1551,8 +1847,9 @@
|
|
|
1551
1847
|
}
|
|
1552
1848
|
else {
|
|
1553
1849
|
if (setValue[$changes]) {
|
|
1850
|
+
assertInstanceType(setValue, obj[$childType], obj, key);
|
|
1554
1851
|
if (obj.items[key] !== undefined) {
|
|
1555
|
-
if (setValue[$changes]
|
|
1852
|
+
if (setValue[$changes].isNew) {
|
|
1556
1853
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
|
|
1557
1854
|
}
|
|
1558
1855
|
else {
|
|
@@ -1564,7 +1861,7 @@
|
|
|
1564
1861
|
}
|
|
1565
1862
|
}
|
|
1566
1863
|
}
|
|
1567
|
-
else if (setValue[$changes]
|
|
1864
|
+
else if (setValue[$changes].isNew) {
|
|
1568
1865
|
this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
|
|
1569
1866
|
}
|
|
1570
1867
|
}
|
|
@@ -1597,7 +1894,10 @@
|
|
|
1597
1894
|
}
|
|
1598
1895
|
});
|
|
1599
1896
|
this[$changes] = new ChangeTree(proxy);
|
|
1600
|
-
this.
|
|
1897
|
+
this[$changes].indexes = {};
|
|
1898
|
+
if (items.length > 0) {
|
|
1899
|
+
this.push(...items);
|
|
1900
|
+
}
|
|
1601
1901
|
return proxy;
|
|
1602
1902
|
}
|
|
1603
1903
|
set length(newLength) {
|
|
@@ -1616,14 +1916,19 @@
|
|
|
1616
1916
|
}
|
|
1617
1917
|
push(...values) {
|
|
1618
1918
|
let length = this.tmpItems.length;
|
|
1619
|
-
|
|
1620
|
-
|
|
1919
|
+
const changeTree = this[$changes];
|
|
1920
|
+
// values.forEach((value, i) => {
|
|
1921
|
+
for (let i = 0, l = values.length; i < values.length; i++, length++) {
|
|
1922
|
+
const value = values[i];
|
|
1621
1923
|
if (value === undefined || value === null) {
|
|
1924
|
+
// skip null values
|
|
1622
1925
|
return;
|
|
1623
1926
|
}
|
|
1624
|
-
|
|
1927
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
1928
|
+
assertInstanceType(value, this[$childType], this, i);
|
|
1929
|
+
// TODO: move value[$changes]?.setParent() to this block.
|
|
1930
|
+
}
|
|
1625
1931
|
changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
|
|
1626
|
-
// changeTree.indexes[length] = length;
|
|
1627
1932
|
this.items.push(value);
|
|
1628
1933
|
this.tmpItems.push(value);
|
|
1629
1934
|
//
|
|
@@ -1631,8 +1936,9 @@
|
|
|
1631
1936
|
// (to avoid encoding "refId" operations before parent's "ADD" operation)
|
|
1632
1937
|
//
|
|
1633
1938
|
value[$changes]?.setParent(this, changeTree.root, length);
|
|
1634
|
-
|
|
1635
|
-
|
|
1939
|
+
}
|
|
1940
|
+
// length++;
|
|
1941
|
+
// });
|
|
1636
1942
|
return length;
|
|
1637
1943
|
}
|
|
1638
1944
|
/**
|
|
@@ -1653,6 +1959,7 @@
|
|
|
1653
1959
|
}
|
|
1654
1960
|
this[$changes].delete(index, undefined, this.items.length - 1);
|
|
1655
1961
|
// this.tmpItems[index] = undefined;
|
|
1962
|
+
// this.tmpItems.pop();
|
|
1656
1963
|
this.deletedIndexes[index] = true;
|
|
1657
1964
|
return this.items.pop();
|
|
1658
1965
|
}
|
|
@@ -1717,9 +2024,12 @@
|
|
|
1717
2024
|
//
|
|
1718
2025
|
// TODO: do not use [$changes] at decoding time.
|
|
1719
2026
|
//
|
|
1720
|
-
changeTree.root
|
|
1721
|
-
|
|
1722
|
-
|
|
2027
|
+
const root = changeTree.root;
|
|
2028
|
+
if (root !== undefined) {
|
|
2029
|
+
root.removeChangeFromChangeSet("changes", changeTree);
|
|
2030
|
+
root.removeChangeFromChangeSet("allChanges", changeTree);
|
|
2031
|
+
root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
2032
|
+
}
|
|
1723
2033
|
});
|
|
1724
2034
|
changeTree.discard(true);
|
|
1725
2035
|
changeTree.operation(exports.OPERATION.CLEAR);
|
|
@@ -1763,6 +2073,7 @@
|
|
|
1763
2073
|
const changeTree = this[$changes];
|
|
1764
2074
|
changeTree.delete(index);
|
|
1765
2075
|
changeTree.shiftAllChangeIndexes(-1, index);
|
|
2076
|
+
// this.deletedIndexes[index] = true;
|
|
1766
2077
|
return this.items.shift();
|
|
1767
2078
|
}
|
|
1768
2079
|
/**
|
|
@@ -1843,10 +2154,12 @@
|
|
|
1843
2154
|
changeTree.shiftChangeIndexes(items.length);
|
|
1844
2155
|
// new index
|
|
1845
2156
|
if (changeTree.isFiltered) {
|
|
1846
|
-
changeTree.filteredChanges
|
|
2157
|
+
setOperationAtIndex(changeTree.filteredChanges, this.items.length);
|
|
2158
|
+
// changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
|
|
1847
2159
|
}
|
|
1848
2160
|
else {
|
|
1849
|
-
changeTree.allChanges
|
|
2161
|
+
setOperationAtIndex(changeTree.allChanges, this.items.length);
|
|
2162
|
+
// changeTree.allChanges[this.items.length] = OPERATION.ADD;
|
|
1850
2163
|
}
|
|
1851
2164
|
// FIXME: should we use OPERATION.MOVE here instead?
|
|
1852
2165
|
items.forEach((_, index) => {
|
|
@@ -1871,14 +2184,6 @@
|
|
|
1871
2184
|
lastIndexOf(searchElement, fromIndex = this.length - 1) {
|
|
1872
2185
|
return this.items.lastIndexOf(searchElement, fromIndex);
|
|
1873
2186
|
}
|
|
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
2187
|
every(callbackfn, thisArg) {
|
|
1883
2188
|
return this.items.every(callbackfn, thisArg);
|
|
1884
2189
|
}
|
|
@@ -2157,6 +2462,7 @@
|
|
|
2157
2462
|
this.$items = new Map();
|
|
2158
2463
|
this.$indexes = new Map();
|
|
2159
2464
|
this[$changes] = new ChangeTree(this);
|
|
2465
|
+
this[$changes].indexes = {};
|
|
2160
2466
|
if (initialValues) {
|
|
2161
2467
|
if (initialValues instanceof Map ||
|
|
2162
2468
|
initialValues instanceof MapSchema) {
|
|
@@ -2183,6 +2489,9 @@
|
|
|
2183
2489
|
if (value === undefined || value === null) {
|
|
2184
2490
|
throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
|
|
2185
2491
|
}
|
|
2492
|
+
else if (typeof (value) === "object" && this[$childType]) {
|
|
2493
|
+
assertInstanceType(value, this[$childType], this, key);
|
|
2494
|
+
}
|
|
2186
2495
|
// Force "key" as string
|
|
2187
2496
|
// See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
|
|
2188
2497
|
key = key.toString();
|
|
@@ -2191,7 +2500,7 @@
|
|
|
2191
2500
|
const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
|
|
2192
2501
|
const index = (isReplace)
|
|
2193
2502
|
? changeTree.indexes[key]
|
|
2194
|
-
: changeTree.indexes[
|
|
2503
|
+
: changeTree.indexes[$numFields] ?? 0;
|
|
2195
2504
|
let operation = (isReplace)
|
|
2196
2505
|
? exports.OPERATION.REPLACE
|
|
2197
2506
|
: exports.OPERATION.ADD;
|
|
@@ -2203,7 +2512,7 @@
|
|
|
2203
2512
|
if (!isReplace) {
|
|
2204
2513
|
this.$indexes.set(index, key);
|
|
2205
2514
|
changeTree.indexes[key] = index;
|
|
2206
|
-
changeTree.indexes[
|
|
2515
|
+
changeTree.indexes[$numFields] = index + 1;
|
|
2207
2516
|
}
|
|
2208
2517
|
else if (!isRef &&
|
|
2209
2518
|
this.$items.get(key) === value) {
|
|
@@ -2278,8 +2587,11 @@
|
|
|
2278
2587
|
}
|
|
2279
2588
|
[$onEncodeEnd]() {
|
|
2280
2589
|
const changeTree = this[$changes];
|
|
2281
|
-
const
|
|
2282
|
-
for (
|
|
2590
|
+
const keys = Object.keys(changeTree.indexedOperations);
|
|
2591
|
+
for (let i = 0, len = keys.length; i < len; i++) {
|
|
2592
|
+
const key = keys[i];
|
|
2593
|
+
const fieldIndex = Number(key);
|
|
2594
|
+
const operation = changeTree.indexedOperations[key];
|
|
2283
2595
|
if (operation === exports.OPERATION.DELETE) {
|
|
2284
2596
|
const index = this[$getByIndex](fieldIndex);
|
|
2285
2597
|
delete changeTree.indexes[index];
|
|
@@ -2305,111 +2617,24 @@
|
|
|
2305
2617
|
// client-side
|
|
2306
2618
|
cloned = Object.assign(new MapSchema(), this);
|
|
2307
2619
|
}
|
|
2308
|
-
else {
|
|
2309
|
-
// 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;
|
|
2396
|
-
}
|
|
2397
|
-
this.discoverTypes(type);
|
|
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;
|
|
2620
|
+
else {
|
|
2621
|
+
// server-side
|
|
2622
|
+
cloned = new MapSchema();
|
|
2623
|
+
this.forEach((value, key) => {
|
|
2624
|
+
if (value[$changes]) {
|
|
2625
|
+
cloned.set(key, value['clone']());
|
|
2407
2626
|
}
|
|
2408
|
-
|
|
2409
|
-
|
|
2627
|
+
else {
|
|
2628
|
+
cloned.set(key, value);
|
|
2629
|
+
}
|
|
2630
|
+
});
|
|
2410
2631
|
}
|
|
2632
|
+
return cloned;
|
|
2411
2633
|
}
|
|
2412
2634
|
}
|
|
2635
|
+
registerType("map", { constructor: MapSchema });
|
|
2636
|
+
|
|
2637
|
+
const DEFAULT_VIEW_TAG = -1;
|
|
2413
2638
|
/**
|
|
2414
2639
|
* [See documentation](https://docs.colyseus.io/state/schema/)
|
|
2415
2640
|
*
|
|
@@ -2436,8 +2661,8 @@
|
|
|
2436
2661
|
// // detect index for this field, considering inheritance
|
|
2437
2662
|
// //
|
|
2438
2663
|
// const parent = Object.getPrototypeOf(context.metadata);
|
|
2439
|
-
// let fieldIndex: number = context.metadata[
|
|
2440
|
-
// ?? (parent && parent[
|
|
2664
|
+
// let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
|
|
2665
|
+
// ?? (parent && parent[$numFields]) // parent structure has fields defined
|
|
2441
2666
|
// ?? -1; // no fields defined
|
|
2442
2667
|
// fieldIndex++;
|
|
2443
2668
|
// if (
|
|
@@ -2557,18 +2782,20 @@
|
|
|
2557
2782
|
const constructor = target.constructor;
|
|
2558
2783
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2559
2784
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2785
|
+
// TODO: use Metadata.initialize()
|
|
2560
2786
|
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
|
-
}
|
|
2787
|
+
// const fieldIndex = metadata[fieldName];
|
|
2788
|
+
// if (!metadata[fieldIndex]) {
|
|
2789
|
+
// //
|
|
2790
|
+
// // detect index for this field, considering inheritance
|
|
2791
|
+
// //
|
|
2792
|
+
// metadata[fieldIndex] = {
|
|
2793
|
+
// type: undefined,
|
|
2794
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2795
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2796
|
+
// ?? -1) + 1 // no fields defined
|
|
2797
|
+
// }
|
|
2798
|
+
// }
|
|
2572
2799
|
Metadata.setTag(metadata, fieldName, tag);
|
|
2573
2800
|
};
|
|
2574
2801
|
}
|
|
@@ -2582,17 +2809,17 @@
|
|
|
2582
2809
|
TypeContext.register(constructor);
|
|
2583
2810
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2584
2811
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2585
|
-
const metadata =
|
|
2586
|
-
let fieldIndex;
|
|
2812
|
+
const metadata = Metadata.initialize(constructor);
|
|
2813
|
+
let fieldIndex = metadata[field];
|
|
2587
2814
|
/**
|
|
2588
2815
|
* skip if descriptor already exists for this field (`@deprecated()`)
|
|
2589
2816
|
*/
|
|
2590
|
-
if (metadata[
|
|
2591
|
-
if (metadata[
|
|
2817
|
+
if (metadata[fieldIndex] !== undefined) {
|
|
2818
|
+
if (metadata[fieldIndex].deprecated) {
|
|
2592
2819
|
// do not create accessors for deprecated properties.
|
|
2593
2820
|
return;
|
|
2594
2821
|
}
|
|
2595
|
-
else if (metadata[
|
|
2822
|
+
else if (metadata[fieldIndex].type !== undefined) {
|
|
2596
2823
|
// trying to define same property multiple times across inheritance.
|
|
2597
2824
|
// https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
|
|
2598
2825
|
try {
|
|
@@ -2603,16 +2830,13 @@
|
|
|
2603
2830
|
throw new Error(`${e.message} ${definitionAtLine}`);
|
|
2604
2831
|
}
|
|
2605
2832
|
}
|
|
2606
|
-
else {
|
|
2607
|
-
fieldIndex = metadata[field].index;
|
|
2608
|
-
}
|
|
2609
2833
|
}
|
|
2610
2834
|
else {
|
|
2611
2835
|
//
|
|
2612
2836
|
// detect index for this field, considering inheritance
|
|
2613
2837
|
//
|
|
2614
|
-
fieldIndex = metadata[
|
|
2615
|
-
?? (parentMetadata && parentMetadata[
|
|
2838
|
+
fieldIndex = metadata[$numFields] // current structure already has fields defined
|
|
2839
|
+
?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2616
2840
|
?? -1; // no fields defined
|
|
2617
2841
|
fieldIndex++;
|
|
2618
2842
|
}
|
|
@@ -2631,15 +2855,15 @@
|
|
|
2631
2855
|
const childType = (complexTypeKlass)
|
|
2632
2856
|
? Object.values(type)[0]
|
|
2633
2857
|
: type;
|
|
2634
|
-
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass
|
|
2858
|
+
Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
|
|
2635
2859
|
}
|
|
2636
2860
|
};
|
|
2637
2861
|
}
|
|
2638
|
-
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass
|
|
2862
|
+
function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
|
|
2639
2863
|
return {
|
|
2640
2864
|
get: function () { return this[fieldCached]; },
|
|
2641
2865
|
set: function (value) {
|
|
2642
|
-
const previousValue = this[fieldCached]
|
|
2866
|
+
const previousValue = this[fieldCached] ?? undefined;
|
|
2643
2867
|
// skip if value is the same as cached.
|
|
2644
2868
|
if (value === previousValue) {
|
|
2645
2869
|
return;
|
|
@@ -2657,22 +2881,27 @@
|
|
|
2657
2881
|
}
|
|
2658
2882
|
value[$childType] = type;
|
|
2659
2883
|
}
|
|
2884
|
+
else if (typeof (type) !== "string") {
|
|
2885
|
+
assertInstanceType(value, type, this, fieldCached.substring(1));
|
|
2886
|
+
}
|
|
2887
|
+
else {
|
|
2888
|
+
assertType(value, type, this, fieldCached.substring(1));
|
|
2889
|
+
}
|
|
2890
|
+
const changeTree = this[$changes];
|
|
2660
2891
|
//
|
|
2661
2892
|
// Replacing existing "ref", remove it from root.
|
|
2662
2893
|
// TODO: if there are other references to this instance, we should not remove it from root.
|
|
2663
2894
|
//
|
|
2664
2895
|
if (previousValue !== undefined && previousValue[$changes]) {
|
|
2665
|
-
|
|
2896
|
+
changeTree.root?.remove(previousValue[$changes]);
|
|
2666
2897
|
}
|
|
2667
2898
|
// flag the change for encoding.
|
|
2668
|
-
this.constructor[$track](
|
|
2899
|
+
this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
|
|
2669
2900
|
//
|
|
2670
2901
|
// call setParent() recursively for this and its child
|
|
2671
2902
|
// structures.
|
|
2672
2903
|
//
|
|
2673
|
-
|
|
2674
|
-
value[$changes].setParent(this, this[$changes].root, metadata[field].index);
|
|
2675
|
-
}
|
|
2904
|
+
value[$changes]?.setParent(this, changeTree.root, fieldIndex);
|
|
2676
2905
|
}
|
|
2677
2906
|
else if (previousValue !== undefined) {
|
|
2678
2907
|
//
|
|
@@ -2699,20 +2928,22 @@
|
|
|
2699
2928
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
2700
2929
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
2701
2930
|
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
|
-
|
|
2931
|
+
const fieldIndex = metadata[field];
|
|
2932
|
+
// if (!metadata[field]) {
|
|
2933
|
+
// //
|
|
2934
|
+
// // detect index for this field, considering inheritance
|
|
2935
|
+
// //
|
|
2936
|
+
// metadata[field] = {
|
|
2937
|
+
// type: undefined,
|
|
2938
|
+
// index: (metadata[$numFields] // current structure already has fields defined
|
|
2939
|
+
// ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
|
|
2940
|
+
// ?? -1) + 1 // no fields defined
|
|
2941
|
+
// }
|
|
2942
|
+
// }
|
|
2943
|
+
metadata[fieldIndex].deprecated = true;
|
|
2714
2944
|
if (throws) {
|
|
2715
|
-
metadata[
|
|
2945
|
+
metadata[$descriptors] ??= {};
|
|
2946
|
+
metadata[$descriptors][field] = {
|
|
2716
2947
|
get: function () { throw new Error(`${field} is deprecated.`); },
|
|
2717
2948
|
set: function (value) { },
|
|
2718
2949
|
enumerable: false,
|
|
@@ -2720,8 +2951,8 @@
|
|
|
2720
2951
|
};
|
|
2721
2952
|
}
|
|
2722
2953
|
// flag metadata[field] as non-enumerable
|
|
2723
|
-
Object.defineProperty(metadata,
|
|
2724
|
-
value: metadata[
|
|
2954
|
+
Object.defineProperty(metadata, fieldIndex, {
|
|
2955
|
+
value: metadata[fieldIndex],
|
|
2725
2956
|
enumerable: false,
|
|
2726
2957
|
configurable: true
|
|
2727
2958
|
});
|
|
@@ -2733,6 +2964,37 @@
|
|
|
2733
2964
|
}
|
|
2734
2965
|
return target;
|
|
2735
2966
|
}
|
|
2967
|
+
function schema(fields, name, inherits = Schema) {
|
|
2968
|
+
const defaultValues = {};
|
|
2969
|
+
const viewTagFields = {};
|
|
2970
|
+
for (let fieldName in fields) {
|
|
2971
|
+
const field = fields[fieldName];
|
|
2972
|
+
if (typeof (field) === "object") {
|
|
2973
|
+
if (field['default'] !== undefined) {
|
|
2974
|
+
defaultValues[fieldName] = field['default'];
|
|
2975
|
+
}
|
|
2976
|
+
if (field['view'] !== undefined) {
|
|
2977
|
+
viewTagFields[fieldName] = (typeof (field['view']) === "boolean")
|
|
2978
|
+
? DEFAULT_VIEW_TAG
|
|
2979
|
+
: field['view'];
|
|
2980
|
+
}
|
|
2981
|
+
}
|
|
2982
|
+
}
|
|
2983
|
+
const klass = Metadata.setFields(class extends inherits {
|
|
2984
|
+
constructor(...args) {
|
|
2985
|
+
args[0] = Object.assign({}, defaultValues, args[0]);
|
|
2986
|
+
super(...args);
|
|
2987
|
+
}
|
|
2988
|
+
}, fields);
|
|
2989
|
+
for (let fieldName in viewTagFields) {
|
|
2990
|
+
view(viewTagFields[fieldName])(klass.prototype, fieldName);
|
|
2991
|
+
}
|
|
2992
|
+
if (name) {
|
|
2993
|
+
Object.defineProperty(klass, "name", { value: name });
|
|
2994
|
+
}
|
|
2995
|
+
klass.extends = (fields, name) => schema(fields, name, klass);
|
|
2996
|
+
return klass;
|
|
2997
|
+
}
|
|
2736
2998
|
|
|
2737
2999
|
function getIndent(level) {
|
|
2738
3000
|
return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
|
|
@@ -2743,15 +3005,18 @@
|
|
|
2743
3005
|
ops: {},
|
|
2744
3006
|
refs: []
|
|
2745
3007
|
};
|
|
2746
|
-
$root.changes
|
|
3008
|
+
// for (const refId in $root.changes) {
|
|
3009
|
+
$root.changes.forEach(changeTree => {
|
|
3010
|
+
const changes = changeTree.indexedOperations;
|
|
2747
3011
|
dump.refs.push(`refId#${changeTree.refId}`);
|
|
2748
|
-
|
|
3012
|
+
for (const index in changes) {
|
|
3013
|
+
const op = changes[index];
|
|
2749
3014
|
const opName = exports.OPERATION[op];
|
|
2750
3015
|
if (!dump.ops[opName]) {
|
|
2751
3016
|
dump.ops[opName] = 0;
|
|
2752
3017
|
}
|
|
2753
3018
|
dump.ops[exports.OPERATION[op]]++;
|
|
2754
|
-
}
|
|
3019
|
+
}
|
|
2755
3020
|
});
|
|
2756
3021
|
return dump;
|
|
2757
3022
|
}
|
|
@@ -2777,6 +3042,7 @@
|
|
|
2777
3042
|
class Schema {
|
|
2778
3043
|
static { this[_a$2] = encodeSchemaOperation; }
|
|
2779
3044
|
static { this[_b$2] = decodeSchemaOperation; }
|
|
3045
|
+
// public [$changes]: ChangeTree;
|
|
2780
3046
|
/**
|
|
2781
3047
|
* Assign the property descriptors required to track changes on this instance.
|
|
2782
3048
|
* @param instance
|
|
@@ -2787,35 +3053,7 @@
|
|
|
2787
3053
|
enumerable: false,
|
|
2788
3054
|
writable: true
|
|
2789
3055
|
});
|
|
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
|
-
}
|
|
3056
|
+
Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
|
|
2819
3057
|
}
|
|
2820
3058
|
static is(type) {
|
|
2821
3059
|
return typeof (type[Symbol.metadata]) === "object";
|
|
@@ -2839,7 +3077,7 @@
|
|
|
2839
3077
|
*/
|
|
2840
3078
|
static [$filter](ref, index, view) {
|
|
2841
3079
|
const metadata = ref.constructor[Symbol.metadata];
|
|
2842
|
-
const tag = metadata[
|
|
3080
|
+
const tag = metadata[index]?.tag;
|
|
2843
3081
|
if (view === undefined) {
|
|
2844
3082
|
// shared pass/encode: encode if doesn't have a tag
|
|
2845
3083
|
return tag === undefined;
|
|
@@ -2860,12 +3098,16 @@
|
|
|
2860
3098
|
}
|
|
2861
3099
|
// allow inherited classes to have a constructor
|
|
2862
3100
|
constructor(...args) {
|
|
3101
|
+
//
|
|
3102
|
+
// inline
|
|
3103
|
+
// Schema.initialize(this);
|
|
3104
|
+
//
|
|
2863
3105
|
Schema.initialize(this);
|
|
2864
3106
|
//
|
|
2865
3107
|
// Assign initial values
|
|
2866
3108
|
//
|
|
2867
3109
|
if (args[0]) {
|
|
2868
|
-
|
|
3110
|
+
Object.assign(this, args[0]);
|
|
2869
3111
|
}
|
|
2870
3112
|
}
|
|
2871
3113
|
assign(props) {
|
|
@@ -2879,7 +3121,8 @@
|
|
|
2879
3121
|
* @param operation OPERATION to perform (detected automatically)
|
|
2880
3122
|
*/
|
|
2881
3123
|
setDirty(property, operation) {
|
|
2882
|
-
this
|
|
3124
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3125
|
+
this[$changes].change(metadata[metadata[property]].index, operation);
|
|
2883
3126
|
}
|
|
2884
3127
|
clone() {
|
|
2885
3128
|
const cloned = new (this.constructor);
|
|
@@ -2888,7 +3131,9 @@
|
|
|
2888
3131
|
// TODO: clone all properties, not only annotated ones
|
|
2889
3132
|
//
|
|
2890
3133
|
// for (const field in this) {
|
|
2891
|
-
for (const
|
|
3134
|
+
for (const fieldIndex in metadata) {
|
|
3135
|
+
// const field = metadata[metadata[fieldIndex]].name;
|
|
3136
|
+
const field = metadata[fieldIndex].name;
|
|
2892
3137
|
if (typeof (this[field]) === "object" &&
|
|
2893
3138
|
typeof (this[field]?.clone) === "function") {
|
|
2894
3139
|
// deep clone
|
|
@@ -2902,10 +3147,11 @@
|
|
|
2902
3147
|
return cloned;
|
|
2903
3148
|
}
|
|
2904
3149
|
toJSON() {
|
|
2905
|
-
const metadata = this.constructor[Symbol.metadata];
|
|
2906
3150
|
const obj = {};
|
|
2907
|
-
|
|
2908
|
-
|
|
3151
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3152
|
+
for (const index in metadata) {
|
|
3153
|
+
const field = metadata[index];
|
|
3154
|
+
const fieldName = field.name;
|
|
2909
3155
|
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
2910
3156
|
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
2911
3157
|
? this[fieldName]['toJSON']()
|
|
@@ -2918,17 +3164,19 @@
|
|
|
2918
3164
|
this[$changes].discardAll();
|
|
2919
3165
|
}
|
|
2920
3166
|
[$getByIndex](index) {
|
|
2921
|
-
|
|
3167
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3168
|
+
return this[metadata[index].name];
|
|
2922
3169
|
}
|
|
2923
3170
|
[$deleteByIndex](index) {
|
|
2924
|
-
this
|
|
3171
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
3172
|
+
this[metadata[index].name] = undefined;
|
|
2925
3173
|
}
|
|
2926
3174
|
static debugRefIds(instance, jsonContents = true, level = 0) {
|
|
2927
3175
|
const ref = instance;
|
|
2928
3176
|
const changeTree = ref[$changes];
|
|
2929
3177
|
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
2930
3178
|
let output = "";
|
|
2931
|
-
output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
|
|
3179
|
+
output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
|
|
2932
3180
|
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
2933
3181
|
return output;
|
|
2934
3182
|
}
|
|
@@ -2946,30 +3194,40 @@
|
|
|
2946
3194
|
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
2947
3195
|
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
2948
3196
|
function dumpChangeSet(changeSet) {
|
|
2949
|
-
|
|
2950
|
-
.
|
|
2951
|
-
.forEach((
|
|
3197
|
+
changeSet.operations
|
|
3198
|
+
.filter(op => op)
|
|
3199
|
+
.forEach((index) => {
|
|
3200
|
+
const operation = changeTree.indexedOperations[index];
|
|
3201
|
+
console.log({ index, operation });
|
|
3202
|
+
output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
|
|
3203
|
+
});
|
|
2952
3204
|
}
|
|
2953
3205
|
dumpChangeSet(changeSet);
|
|
2954
3206
|
// display filtered changes
|
|
2955
|
-
if (!isEncodeAll &&
|
|
3207
|
+
if (!isEncodeAll &&
|
|
3208
|
+
changeTree.filteredChanges &&
|
|
3209
|
+
(changeTree.filteredChanges.operations).filter(op => op).length > 0) {
|
|
2956
3210
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
2957
3211
|
dumpChangeSet(changeTree.filteredChanges);
|
|
2958
3212
|
}
|
|
2959
3213
|
// display filtered changes
|
|
2960
|
-
if (isEncodeAll &&
|
|
3214
|
+
if (isEncodeAll &&
|
|
3215
|
+
changeTree.allFilteredChanges &&
|
|
3216
|
+
(changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
|
|
2961
3217
|
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
2962
3218
|
dumpChangeSet(changeTree.allFilteredChanges);
|
|
2963
3219
|
}
|
|
2964
3220
|
return output;
|
|
2965
3221
|
}
|
|
2966
|
-
static debugChangesDeep(ref) {
|
|
3222
|
+
static debugChangesDeep(ref, changeSetName = "changes") {
|
|
2967
3223
|
let output = "";
|
|
2968
3224
|
const rootChangeTree = ref[$changes];
|
|
3225
|
+
const root = rootChangeTree.root;
|
|
2969
3226
|
const changeTrees = new Map();
|
|
2970
3227
|
let totalInstances = 0;
|
|
2971
3228
|
let totalOperations = 0;
|
|
2972
|
-
for (const [
|
|
3229
|
+
for (const [refId, changes] of Object.entries(root[changeSetName])) {
|
|
3230
|
+
const changeTree = root.changeTrees[refId];
|
|
2973
3231
|
let includeChangeTree = false;
|
|
2974
3232
|
let parentChangeTrees = [];
|
|
2975
3233
|
let parentChangeTree = changeTree.parent?.[$changes];
|
|
@@ -2988,7 +3246,7 @@
|
|
|
2988
3246
|
}
|
|
2989
3247
|
if (includeChangeTree) {
|
|
2990
3248
|
totalInstances += 1;
|
|
2991
|
-
totalOperations += changes.
|
|
3249
|
+
totalOperations += Object.keys(changes).length;
|
|
2992
3250
|
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
2993
3251
|
}
|
|
2994
3252
|
}
|
|
@@ -3006,12 +3264,13 @@
|
|
|
3006
3264
|
visitedParents.add(parentChangeTree);
|
|
3007
3265
|
}
|
|
3008
3266
|
});
|
|
3009
|
-
const changes = changeTree.
|
|
3267
|
+
const changes = changeTree.indexedOperations;
|
|
3010
3268
|
const level = parentChangeTrees.length;
|
|
3011
3269
|
const indent = getIndent(level);
|
|
3012
3270
|
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
3013
|
-
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.
|
|
3014
|
-
for (const
|
|
3271
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
|
|
3272
|
+
for (const index in changes) {
|
|
3273
|
+
const operation = changes[index];
|
|
3015
3274
|
output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
|
|
3016
3275
|
}
|
|
3017
3276
|
}
|
|
@@ -3045,6 +3304,7 @@
|
|
|
3045
3304
|
this.$indexes = new Map();
|
|
3046
3305
|
this.$refId = 0;
|
|
3047
3306
|
this[$changes] = new ChangeTree(this);
|
|
3307
|
+
this[$changes].indexes = {};
|
|
3048
3308
|
if (initialValues) {
|
|
3049
3309
|
initialValues.forEach((v) => this.add(v));
|
|
3050
3310
|
}
|
|
@@ -3200,6 +3460,7 @@
|
|
|
3200
3460
|
this.$indexes = new Map();
|
|
3201
3461
|
this.$refId = 0;
|
|
3202
3462
|
this[$changes] = new ChangeTree(this);
|
|
3463
|
+
this[$changes].indexes = {};
|
|
3203
3464
|
if (initialValues) {
|
|
3204
3465
|
initialValues.forEach((v) => this.add(v));
|
|
3205
3466
|
}
|
|
@@ -3355,6 +3616,8 @@
|
|
|
3355
3616
|
OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
|
|
3356
3617
|
PERFORMANCE OF THIS SOFTWARE.
|
|
3357
3618
|
***************************************************************************** */
|
|
3619
|
+
/* global Reflect, Promise, SuppressedError, Symbol */
|
|
3620
|
+
|
|
3358
3621
|
|
|
3359
3622
|
function __decorate(decorators, target, key, desc) {
|
|
3360
3623
|
var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
|
|
@@ -3368,37 +3631,135 @@
|
|
|
3368
3631
|
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
3369
3632
|
};
|
|
3370
3633
|
|
|
3634
|
+
function spliceOne(arr, index) {
|
|
3635
|
+
// manually splice an array
|
|
3636
|
+
if (index === -1 || index >= arr.length) {
|
|
3637
|
+
return false;
|
|
3638
|
+
}
|
|
3639
|
+
const len = arr.length - 1;
|
|
3640
|
+
for (let i = index; i < len; i++) {
|
|
3641
|
+
arr[i] = arr[i + 1];
|
|
3642
|
+
}
|
|
3643
|
+
arr.length = len;
|
|
3644
|
+
return true;
|
|
3645
|
+
}
|
|
3646
|
+
|
|
3647
|
+
class Root {
|
|
3648
|
+
constructor(types) {
|
|
3649
|
+
this.types = types;
|
|
3650
|
+
this.nextUniqueId = 0;
|
|
3651
|
+
this.refCount = {};
|
|
3652
|
+
this.changeTrees = {};
|
|
3653
|
+
// all changes
|
|
3654
|
+
this.allChanges = [];
|
|
3655
|
+
this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3656
|
+
// pending changes to be encoded
|
|
3657
|
+
this.changes = [];
|
|
3658
|
+
this.filteredChanges = []; // TODO: do not initialize it if filters are not used
|
|
3659
|
+
}
|
|
3660
|
+
getNextUniqueId() {
|
|
3661
|
+
return this.nextUniqueId++;
|
|
3662
|
+
}
|
|
3663
|
+
add(changeTree) {
|
|
3664
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
3665
|
+
changeTree.ensureRefId();
|
|
3666
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
3667
|
+
if (isNewChangeTree) {
|
|
3668
|
+
this.changeTrees[changeTree.refId] = changeTree;
|
|
3669
|
+
}
|
|
3670
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
3671
|
+
if (previousRefCount === 0) {
|
|
3672
|
+
//
|
|
3673
|
+
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
3674
|
+
// We need to re-add all changes to the `changes` map.
|
|
3675
|
+
//
|
|
3676
|
+
const ops = changeTree.allChanges.operations;
|
|
3677
|
+
let len = ops.length;
|
|
3678
|
+
while (len--) {
|
|
3679
|
+
changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
|
|
3680
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
3681
|
+
}
|
|
3682
|
+
}
|
|
3683
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
3684
|
+
return isNewChangeTree;
|
|
3685
|
+
}
|
|
3686
|
+
remove(changeTree) {
|
|
3687
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
3688
|
+
if (refCount <= 0) {
|
|
3689
|
+
//
|
|
3690
|
+
// Only remove "root" reference if it's the last reference
|
|
3691
|
+
//
|
|
3692
|
+
changeTree.root = undefined;
|
|
3693
|
+
delete this.changeTrees[changeTree.refId];
|
|
3694
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
3695
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
3696
|
+
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
3697
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
3698
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
3699
|
+
}
|
|
3700
|
+
this.refCount[changeTree.refId] = 0;
|
|
3701
|
+
}
|
|
3702
|
+
else {
|
|
3703
|
+
this.refCount[changeTree.refId] = refCount;
|
|
3704
|
+
}
|
|
3705
|
+
changeTree.forEachChild((child, _) => this.remove(child));
|
|
3706
|
+
return refCount;
|
|
3707
|
+
}
|
|
3708
|
+
removeChangeFromChangeSet(changeSetName, changeTree) {
|
|
3709
|
+
const changeSet = this[changeSetName];
|
|
3710
|
+
const index = changeSet.indexOf(changeTree);
|
|
3711
|
+
if (index !== -1) {
|
|
3712
|
+
spliceOne(changeSet, index);
|
|
3713
|
+
// changeSet[index] = undefined;
|
|
3714
|
+
}
|
|
3715
|
+
}
|
|
3716
|
+
clear() {
|
|
3717
|
+
this.changes.length = 0;
|
|
3718
|
+
}
|
|
3719
|
+
}
|
|
3720
|
+
|
|
3371
3721
|
class Encoder {
|
|
3372
3722
|
static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
|
|
3373
|
-
constructor(
|
|
3723
|
+
constructor(state) {
|
|
3374
3724
|
this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
|
|
3375
|
-
this.setRoot(root);
|
|
3376
3725
|
//
|
|
3377
3726
|
// TODO: cache and restore "Context" based on root schema
|
|
3378
3727
|
// (to avoid creating a new context for every new room)
|
|
3379
3728
|
//
|
|
3380
|
-
this.context = new TypeContext(
|
|
3729
|
+
this.context = new TypeContext(state.constructor);
|
|
3730
|
+
this.root = new Root(this.context);
|
|
3731
|
+
this.setState(state);
|
|
3381
3732
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
3382
3733
|
// this.context.schemas.forEach((id, schema) => {
|
|
3383
3734
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3384
3735
|
// });
|
|
3385
3736
|
}
|
|
3386
|
-
|
|
3387
|
-
this.root = new Root();
|
|
3737
|
+
setState(state) {
|
|
3388
3738
|
this.state = state;
|
|
3389
|
-
state[$changes].setRoot(this.root);
|
|
3739
|
+
this.state[$changes].setRoot(this.root);
|
|
3390
3740
|
}
|
|
3391
|
-
encode(it = { offset: 0 }, view,
|
|
3392
|
-
|
|
3393
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
3741
|
+
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
|
|
3742
|
+
) {
|
|
3394
3743
|
const hasView = (view !== undefined);
|
|
3395
3744
|
const rootChangeTree = this.state[$changes];
|
|
3396
|
-
const
|
|
3397
|
-
|
|
3745
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
3746
|
+
const changeTrees = this.root[changeSetName];
|
|
3747
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
3748
|
+
const changeTree = changeTrees[i];
|
|
3749
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
3750
|
+
// if (changeTree === undefined) { continue; }
|
|
3751
|
+
const operations = changeTree[changeSetName];
|
|
3398
3752
|
const ref = changeTree.ref;
|
|
3399
|
-
const ctor = ref
|
|
3753
|
+
const ctor = ref.constructor;
|
|
3400
3754
|
const encoder = ctor[$encoder];
|
|
3401
3755
|
const filter = ctor[$filter];
|
|
3756
|
+
const metadata = ctor[Symbol.metadata];
|
|
3757
|
+
// try { throw new Error(); } catch (e) {
|
|
3758
|
+
// // only print if not coming from Reflection.ts
|
|
3759
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3760
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
3761
|
+
// }
|
|
3762
|
+
// }
|
|
3402
3763
|
if (hasView) {
|
|
3403
3764
|
if (!view.items.has(changeTree)) {
|
|
3404
3765
|
view.invisible.add(changeTree);
|
|
@@ -3409,12 +3770,18 @@
|
|
|
3409
3770
|
}
|
|
3410
3771
|
}
|
|
3411
3772
|
// skip root `refId` if it's the first change tree
|
|
3412
|
-
|
|
3413
|
-
|
|
3414
|
-
|
|
3773
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
3774
|
+
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
3775
|
+
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3776
|
+
encode.number(buffer, changeTree.refId, it);
|
|
3415
3777
|
}
|
|
3416
|
-
|
|
3417
|
-
|
|
3778
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
3779
|
+
const fieldIndex = operations.operations[j];
|
|
3780
|
+
const operation = (fieldIndex < 0)
|
|
3781
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
3782
|
+
: (isEncodeAll)
|
|
3783
|
+
? exports.OPERATION.ADD
|
|
3784
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
3418
3785
|
//
|
|
3419
3786
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
3420
3787
|
// they will be encoded per client, based on their view.
|
|
@@ -3422,93 +3789,127 @@
|
|
|
3422
3789
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
3423
3790
|
// TODO: avoid checking if no view tags were defined
|
|
3424
3791
|
//
|
|
3425
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
3426
|
-
// console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
|
|
3792
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
3427
3793
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
3428
3794
|
// view?.invisible.add(changeTree);
|
|
3429
3795
|
continue;
|
|
3430
3796
|
}
|
|
3431
|
-
//
|
|
3432
|
-
//
|
|
3433
|
-
//
|
|
3434
|
-
//
|
|
3435
|
-
//
|
|
3436
|
-
|
|
3797
|
+
// try { throw new Error(); } catch (e) {
|
|
3798
|
+
// // only print if not coming from Reflection.ts
|
|
3799
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
3800
|
+
// console.log("WILL ENCODE", {
|
|
3801
|
+
// ref: changeTree.ref.constructor.name,
|
|
3802
|
+
// fieldIndex,
|
|
3803
|
+
// operation: OPERATION[operation],
|
|
3804
|
+
// });
|
|
3805
|
+
// }
|
|
3806
|
+
// }
|
|
3807
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
3808
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
3809
|
+
}
|
|
3810
|
+
if (shouldDiscardChanges) {
|
|
3811
|
+
changeTree.discard();
|
|
3812
|
+
// Not a new instance anymore
|
|
3813
|
+
changeTree.isNew = false;
|
|
3437
3814
|
}
|
|
3438
3815
|
}
|
|
3439
|
-
if (it.offset >
|
|
3440
|
-
const newSize = getNextPowerOf2(
|
|
3441
|
-
console.warn(
|
|
3816
|
+
if (it.offset > buffer.byteLength) {
|
|
3817
|
+
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
3818
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
3819
|
+
|
|
3820
|
+
import { Encoder } from "@colyseus/schema";
|
|
3821
|
+
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
3822
|
+
`);
|
|
3442
3823
|
//
|
|
3443
3824
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
3444
3825
|
//
|
|
3445
|
-
|
|
3446
|
-
|
|
3826
|
+
buffer = Buffer.allocUnsafeSlow(newSize);
|
|
3827
|
+
// assign resized buffer to local sharedBuffer
|
|
3828
|
+
if (buffer === this.sharedBuffer) {
|
|
3829
|
+
this.sharedBuffer = buffer;
|
|
3830
|
+
}
|
|
3831
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
3447
3832
|
}
|
|
3448
3833
|
else {
|
|
3449
|
-
//
|
|
3450
|
-
// only clear changes after making sure buffer resize is not required.
|
|
3451
|
-
//
|
|
3452
|
-
if (
|
|
3453
|
-
|
|
3454
|
-
|
|
3455
|
-
|
|
3456
|
-
|
|
3457
|
-
}
|
|
3458
|
-
|
|
3459
|
-
return bytes.slice(0, it.offset);
|
|
3834
|
+
// //
|
|
3835
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
3836
|
+
// //
|
|
3837
|
+
// if (shouldClearChanges) {
|
|
3838
|
+
// //
|
|
3839
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
3840
|
+
// //
|
|
3841
|
+
// this.onEndEncode(changeTrees);
|
|
3842
|
+
// }
|
|
3843
|
+
return buffer.subarray(0, it.offset);
|
|
3460
3844
|
}
|
|
3461
3845
|
}
|
|
3462
|
-
encodeAll(it = { offset: 0 }) {
|
|
3463
|
-
// console.log(
|
|
3464
|
-
//
|
|
3465
|
-
|
|
3466
|
-
// });
|
|
3467
|
-
return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
|
|
3846
|
+
encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
|
|
3847
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
3848
|
+
// this.debugChanges("allChanges");
|
|
3849
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
3468
3850
|
}
|
|
3469
3851
|
encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3470
3852
|
const viewOffset = it.offset;
|
|
3471
|
-
// console.log(
|
|
3472
|
-
// this.
|
|
3853
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
3854
|
+
// this.debugChanges("allFilteredChanges");
|
|
3855
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
3473
3856
|
// try to encode "filtered" changes
|
|
3474
|
-
this.encode(it, view, bytes,
|
|
3857
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
3475
3858
|
return Buffer.concat([
|
|
3476
|
-
bytes.
|
|
3477
|
-
bytes.
|
|
3859
|
+
bytes.subarray(0, sharedOffset),
|
|
3860
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3478
3861
|
]);
|
|
3479
3862
|
}
|
|
3480
|
-
|
|
3481
|
-
|
|
3482
|
-
|
|
3483
|
-
|
|
3484
|
-
|
|
3485
|
-
|
|
3486
|
-
|
|
3487
|
-
|
|
3488
|
-
|
|
3489
|
-
|
|
3863
|
+
debugChanges(field) {
|
|
3864
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
3865
|
+
? this.root[field]
|
|
3866
|
+
: field;
|
|
3867
|
+
rootChangeSet.forEach((changeTree) => {
|
|
3868
|
+
const changeSet = changeTree[field];
|
|
3869
|
+
const metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
3870
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
3871
|
+
for (const index in changeSet) {
|
|
3872
|
+
const op = changeSet[index];
|
|
3873
|
+
console.log(" ->", {
|
|
3874
|
+
index,
|
|
3875
|
+
field: metadata?.[index],
|
|
3876
|
+
op: exports.OPERATION[op],
|
|
3877
|
+
});
|
|
3878
|
+
}
|
|
3879
|
+
});
|
|
3880
|
+
}
|
|
3490
3881
|
encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
|
|
3491
3882
|
const viewOffset = it.offset;
|
|
3492
|
-
//
|
|
3493
|
-
this.
|
|
3883
|
+
// console.log(`\nencodeView(), view.changes (${view.changes.size})`);
|
|
3884
|
+
// this.debugChanges(view.changes);
|
|
3885
|
+
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
3886
|
+
// this.debugChanges("filteredChanges");
|
|
3494
3887
|
// encode visibility changes (add/remove for this view)
|
|
3495
|
-
const
|
|
3496
|
-
|
|
3497
|
-
|
|
3498
|
-
|
|
3499
|
-
|
|
3888
|
+
const refIds = Object.keys(view.changes);
|
|
3889
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
3890
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
3891
|
+
const refId = refIds[i];
|
|
3892
|
+
const changes = view.changes[refId];
|
|
3893
|
+
const changeTree = this.root.changeTrees[refId];
|
|
3894
|
+
if (changeTree === undefined ||
|
|
3895
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
3896
|
+
) {
|
|
3897
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
3500
3898
|
continue;
|
|
3501
3899
|
}
|
|
3502
3900
|
const ref = changeTree.ref;
|
|
3503
|
-
const ctor = ref
|
|
3901
|
+
const ctor = ref.constructor;
|
|
3504
3902
|
const encoder = ctor[$encoder];
|
|
3903
|
+
const metadata = ctor[Symbol.metadata];
|
|
3505
3904
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
3506
|
-
number
|
|
3507
|
-
const
|
|
3508
|
-
for (
|
|
3905
|
+
encode.number(bytes, changeTree.refId, it);
|
|
3906
|
+
const keys = Object.keys(changes);
|
|
3907
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
3908
|
+
const key = keys[i];
|
|
3909
|
+
const operation = changes[key];
|
|
3509
3910
|
// isEncodeAll = false
|
|
3510
3911
|
// hasView = true
|
|
3511
|
-
encoder(this, bytes, changeTree,
|
|
3912
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
3512
3913
|
}
|
|
3513
3914
|
}
|
|
3514
3915
|
//
|
|
@@ -3516,51 +3917,64 @@
|
|
|
3516
3917
|
// (to allow re-using StateView's for multiple clients)
|
|
3517
3918
|
//
|
|
3518
3919
|
// clear "view" changes after encoding
|
|
3519
|
-
view.changes
|
|
3920
|
+
view.changes = {};
|
|
3921
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
3922
|
+
// try to encode "filtered" changes
|
|
3923
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
3520
3924
|
return Buffer.concat([
|
|
3521
|
-
bytes.
|
|
3522
|
-
bytes.
|
|
3925
|
+
bytes.subarray(0, sharedOffset),
|
|
3926
|
+
bytes.subarray(viewOffset, it.offset)
|
|
3523
3927
|
]);
|
|
3524
3928
|
}
|
|
3525
3929
|
onEndEncode(changeTrees = this.root.changes) {
|
|
3526
|
-
|
|
3527
|
-
|
|
3528
|
-
|
|
3529
|
-
|
|
3930
|
+
// changeTrees.forEach(function(changeTree) {
|
|
3931
|
+
// changeTree.endEncode();
|
|
3932
|
+
// });
|
|
3933
|
+
// for (const refId in changeTrees) {
|
|
3934
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
3935
|
+
// changeTree.endEncode();
|
|
3936
|
+
// // changeTree.changes.clear();
|
|
3937
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
3938
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
3939
|
+
// // // Not a new instance anymore
|
|
3940
|
+
// // delete changeTree[$isNew];
|
|
3941
|
+
// }
|
|
3530
3942
|
}
|
|
3531
3943
|
discardChanges() {
|
|
3944
|
+
// console.log("DISCARD CHANGES!");
|
|
3532
3945
|
// discard shared changes
|
|
3533
|
-
|
|
3534
|
-
|
|
3535
|
-
|
|
3946
|
+
let length = this.root.changes.length;
|
|
3947
|
+
if (length > 0) {
|
|
3948
|
+
while (length--) {
|
|
3949
|
+
this.root.changes[length]?.endEncode();
|
|
3950
|
+
}
|
|
3951
|
+
this.root.changes.length = 0;
|
|
3536
3952
|
}
|
|
3537
3953
|
// discard filtered changes
|
|
3538
|
-
|
|
3539
|
-
|
|
3540
|
-
|
|
3954
|
+
length = this.root.filteredChanges.length;
|
|
3955
|
+
if (length > 0) {
|
|
3956
|
+
while (length--) {
|
|
3957
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
3958
|
+
}
|
|
3959
|
+
this.root.filteredChanges.length = 0;
|
|
3541
3960
|
}
|
|
3542
3961
|
}
|
|
3543
3962
|
tryEncodeTypeId(bytes, baseType, targetType, it) {
|
|
3544
3963
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
3545
3964
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
3965
|
+
if (targetTypeId === undefined) {
|
|
3966
|
+
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.`);
|
|
3967
|
+
return;
|
|
3968
|
+
}
|
|
3546
3969
|
if (baseTypeId !== targetTypeId) {
|
|
3547
3970
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
3548
|
-
number
|
|
3971
|
+
encode.number(bytes, targetTypeId, it);
|
|
3549
3972
|
}
|
|
3550
3973
|
}
|
|
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];
|
|
3974
|
+
get hasChanges() {
|
|
3975
|
+
return (this.root.changes.length > 0 ||
|
|
3976
|
+
this.root.filteredChanges.length > 0);
|
|
3561
3977
|
}
|
|
3562
|
-
arr.length = len;
|
|
3563
|
-
return true;
|
|
3564
3978
|
}
|
|
3565
3979
|
|
|
3566
3980
|
class DecodingWarning extends Error {
|
|
@@ -3641,8 +4055,9 @@
|
|
|
3641
4055
|
// Ensure child schema instances have their references removed as well.
|
|
3642
4056
|
//
|
|
3643
4057
|
if (Metadata.isValidInstance(ref)) {
|
|
3644
|
-
const metadata = ref
|
|
3645
|
-
for (const
|
|
4058
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
4059
|
+
for (const index in metadata) {
|
|
4060
|
+
const field = metadata[index].name;
|
|
3646
4061
|
const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
|
|
3647
4062
|
if (childRefId) {
|
|
3648
4063
|
this.removeRef(childRefId);
|
|
@@ -3689,21 +4104,21 @@
|
|
|
3689
4104
|
class Decoder {
|
|
3690
4105
|
constructor(root, context) {
|
|
3691
4106
|
this.currentRefId = 0;
|
|
3692
|
-
this.
|
|
4107
|
+
this.setState(root);
|
|
3693
4108
|
this.context = context || new TypeContext(root.constructor);
|
|
3694
4109
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
3695
4110
|
// this.context.schemas.forEach((id, schema) => {
|
|
3696
4111
|
// console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
|
|
3697
4112
|
// });
|
|
3698
4113
|
}
|
|
3699
|
-
|
|
4114
|
+
setState(root) {
|
|
3700
4115
|
this.state = root;
|
|
3701
|
-
this
|
|
3702
|
-
this
|
|
4116
|
+
this.root = new ReferenceTracker();
|
|
4117
|
+
this.root.addRef(0, root);
|
|
3703
4118
|
}
|
|
3704
4119
|
decode(bytes, it = { offset: 0 }, ref = this.state) {
|
|
3705
4120
|
const allChanges = [];
|
|
3706
|
-
const $root = this
|
|
4121
|
+
const $root = this.root;
|
|
3707
4122
|
const totalBytes = bytes.byteLength;
|
|
3708
4123
|
let decoder = ref['constructor'][$decoder];
|
|
3709
4124
|
this.currentRefId = 0;
|
|
@@ -3713,7 +4128,7 @@
|
|
|
3713
4128
|
//
|
|
3714
4129
|
if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
|
|
3715
4130
|
it.offset++;
|
|
3716
|
-
this.currentRefId = number(bytes, it);
|
|
4131
|
+
this.currentRefId = decode.number(bytes, it);
|
|
3717
4132
|
const nextRef = $root.refs.get(this.currentRefId);
|
|
3718
4133
|
//
|
|
3719
4134
|
// Trying to access a reference that haven't been decoded yet.
|
|
@@ -3723,7 +4138,7 @@
|
|
|
3723
4138
|
}
|
|
3724
4139
|
ref[$onDecodeEnd]?.();
|
|
3725
4140
|
ref = nextRef;
|
|
3726
|
-
decoder = ref
|
|
4141
|
+
decoder = ref.constructor[$decoder];
|
|
3727
4142
|
continue;
|
|
3728
4143
|
}
|
|
3729
4144
|
const result = decoder(this, bytes, it, ref, allChanges);
|
|
@@ -3735,9 +4150,9 @@
|
|
|
3735
4150
|
//
|
|
3736
4151
|
const nextIterator = { offset: it.offset };
|
|
3737
4152
|
while (it.offset < totalBytes) {
|
|
3738
|
-
if (
|
|
4153
|
+
if (bytes[it.offset] === SWITCH_TO_STRUCTURE) {
|
|
3739
4154
|
nextIterator.offset = it.offset + 1;
|
|
3740
|
-
if ($root.refs.has(number(bytes, nextIterator))) {
|
|
4155
|
+
if ($root.refs.has(decode.number(bytes, nextIterator))) {
|
|
3741
4156
|
break;
|
|
3742
4157
|
}
|
|
3743
4158
|
}
|
|
@@ -3758,7 +4173,7 @@
|
|
|
3758
4173
|
let type;
|
|
3759
4174
|
if (bytes[it.offset] === TYPE_ID) {
|
|
3760
4175
|
it.offset++;
|
|
3761
|
-
const type_id = number(bytes, it);
|
|
4176
|
+
const type_id = decode.number(bytes, it);
|
|
3762
4177
|
type = this.context.get(type_id);
|
|
3763
4178
|
}
|
|
3764
4179
|
return type || defaultType;
|
|
@@ -3784,7 +4199,7 @@
|
|
|
3784
4199
|
previousValue: value
|
|
3785
4200
|
});
|
|
3786
4201
|
if (needRemoveRef) {
|
|
3787
|
-
this
|
|
4202
|
+
this.root.removeRef(this.root.refIds.get(value));
|
|
3788
4203
|
}
|
|
3789
4204
|
});
|
|
3790
4205
|
}
|
|
@@ -3824,14 +4239,27 @@
|
|
|
3824
4239
|
super(...arguments);
|
|
3825
4240
|
this.types = new ArraySchema();
|
|
3826
4241
|
}
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
4242
|
+
/**
|
|
4243
|
+
* Encodes the TypeContext of an Encoder into a buffer.
|
|
4244
|
+
*
|
|
4245
|
+
* @param encoder Encoder instance
|
|
4246
|
+
* @param it
|
|
4247
|
+
* @returns
|
|
4248
|
+
*/
|
|
4249
|
+
static encode(encoder, it = { offset: 0 }) {
|
|
4250
|
+
const context = encoder.context;
|
|
3831
4251
|
const reflection = new Reflection();
|
|
3832
|
-
const
|
|
4252
|
+
const reflectionEncoder = new Encoder(reflection);
|
|
4253
|
+
// rootType is usually the first schema passed to the Encoder
|
|
4254
|
+
// (unless it inherits from another schema)
|
|
4255
|
+
const rootType = context.schemas.get(encoder.state.constructor);
|
|
4256
|
+
if (rootType > 0) {
|
|
4257
|
+
reflection.rootType = rootType;
|
|
4258
|
+
}
|
|
3833
4259
|
const buildType = (currentType, metadata) => {
|
|
3834
|
-
for (const
|
|
4260
|
+
for (const fieldIndex in metadata) {
|
|
4261
|
+
const index = Number(fieldIndex);
|
|
4262
|
+
const fieldName = metadata[index].name;
|
|
3835
4263
|
// skip fields from parent classes
|
|
3836
4264
|
if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
|
|
3837
4265
|
continue;
|
|
@@ -3839,7 +4267,7 @@
|
|
|
3839
4267
|
const field = new ReflectionField();
|
|
3840
4268
|
field.name = fieldName;
|
|
3841
4269
|
let fieldType;
|
|
3842
|
-
const type = metadata[
|
|
4270
|
+
const type = metadata[index].type;
|
|
3843
4271
|
if (typeof (type) === "string") {
|
|
3844
4272
|
fieldType = type;
|
|
3845
4273
|
}
|
|
@@ -3881,66 +4309,335 @@
|
|
|
3881
4309
|
}
|
|
3882
4310
|
buildType(type, klass[Symbol.metadata]);
|
|
3883
4311
|
}
|
|
3884
|
-
const
|
|
3885
|
-
const buf = encoder.encodeAll(it);
|
|
4312
|
+
const buf = reflectionEncoder.encodeAll(it);
|
|
3886
4313
|
return Buffer.from(buf, 0, it.offset);
|
|
3887
4314
|
}
|
|
4315
|
+
/**
|
|
4316
|
+
* Decodes the TypeContext from a buffer into a Decoder instance.
|
|
4317
|
+
*
|
|
4318
|
+
* @param bytes Reflection.encode() output
|
|
4319
|
+
* @param it
|
|
4320
|
+
* @returns Decoder instance
|
|
4321
|
+
*/
|
|
3888
4322
|
static decode(bytes, it) {
|
|
3889
4323
|
const reflection = new Reflection();
|
|
3890
4324
|
const reflectionDecoder = new Decoder(reflection);
|
|
3891
4325
|
reflectionDecoder.decode(bytes, it);
|
|
3892
|
-
const
|
|
3893
|
-
|
|
3894
|
-
|
|
3895
|
-
const
|
|
4326
|
+
const typeContext = new TypeContext();
|
|
4327
|
+
// 1st pass, initialize metadata + inheritance
|
|
4328
|
+
reflection.types.forEach((reflectionType) => {
|
|
4329
|
+
const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
4330
|
+
const schema = class _ extends parentClass {
|
|
3896
4331
|
};
|
|
3897
|
-
// const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
|
|
3898
|
-
const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
|
|
3899
|
-
Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
|
|
3900
4332
|
// register for inheritance support
|
|
3901
4333
|
TypeContext.register(schema);
|
|
3902
|
-
|
|
3903
|
-
|
|
3904
|
-
|
|
3905
|
-
return types;
|
|
4334
|
+
// // for inheritance support
|
|
4335
|
+
// Metadata.initialize(schema);
|
|
4336
|
+
typeContext.add(schema, reflectionType.id);
|
|
3906
4337
|
}, {});
|
|
3907
|
-
|
|
3908
|
-
|
|
3909
|
-
const metadata = schemaType[Symbol.metadata];
|
|
3910
|
-
const parentKlass = reflection.types[reflectionType.extendsId];
|
|
3911
|
-
const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
|
|
4338
|
+
// define fields
|
|
4339
|
+
const addFields = (metadata, reflectionType, parentFieldIndex) => {
|
|
3912
4340
|
reflectionType.fields.forEach((field, i) => {
|
|
3913
4341
|
const fieldIndex = parentFieldIndex + i;
|
|
3914
4342
|
if (field.referencedType !== undefined) {
|
|
3915
4343
|
let fieldType = field.type;
|
|
3916
|
-
let refType =
|
|
4344
|
+
let refType = typeContext.get(field.referencedType);
|
|
3917
4345
|
// map or array of primitive type (-1)
|
|
3918
4346
|
if (!refType) {
|
|
3919
4347
|
const typeInfo = field.type.split(":");
|
|
3920
4348
|
fieldType = typeInfo[0];
|
|
3921
|
-
refType = typeInfo[1];
|
|
4349
|
+
refType = typeInfo[1]; // string
|
|
3922
4350
|
}
|
|
3923
4351
|
if (fieldType === "ref") {
|
|
3924
|
-
// type(refType)(schemaType.prototype, field.name);
|
|
3925
4352
|
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
3926
4353
|
}
|
|
3927
4354
|
else {
|
|
3928
|
-
// type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
|
|
3929
4355
|
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
|
|
3930
4356
|
}
|
|
3931
4357
|
}
|
|
3932
4358
|
else {
|
|
3933
|
-
// type(field.type as PrimitiveType)(schemaType.prototype, field.name);
|
|
3934
4359
|
Metadata.addField(metadata, fieldIndex, field.name, field.type);
|
|
3935
4360
|
}
|
|
3936
4361
|
});
|
|
4362
|
+
};
|
|
4363
|
+
// 2nd pass, set fields
|
|
4364
|
+
reflection.types.forEach((reflectionType) => {
|
|
4365
|
+
const schema = typeContext.get(reflectionType.id);
|
|
4366
|
+
// for inheritance support
|
|
4367
|
+
const metadata = Metadata.initialize(schema);
|
|
4368
|
+
const inheritedTypes = [];
|
|
4369
|
+
let parentType = reflectionType;
|
|
4370
|
+
do {
|
|
4371
|
+
inheritedTypes.push(parentType);
|
|
4372
|
+
parentType = reflection.types.find((t) => t.id === parentType.extendsId);
|
|
4373
|
+
} while (parentType);
|
|
4374
|
+
let parentFieldIndex = 0;
|
|
4375
|
+
inheritedTypes.reverse().forEach((reflectionType) => {
|
|
4376
|
+
// add fields from all inherited classes
|
|
4377
|
+
// TODO: refactor this to avoid adding fields from parent classes
|
|
4378
|
+
addFields(metadata, reflectionType, parentFieldIndex);
|
|
4379
|
+
parentFieldIndex += reflectionType.fields.length;
|
|
4380
|
+
});
|
|
3937
4381
|
});
|
|
3938
|
-
|
|
4382
|
+
const state = new (typeContext.get(reflection.rootType || 0))();
|
|
4383
|
+
return new Decoder(state, typeContext);
|
|
3939
4384
|
}
|
|
3940
4385
|
}
|
|
3941
4386
|
__decorate([
|
|
3942
4387
|
type([ReflectionType])
|
|
3943
4388
|
], Reflection.prototype, "types", void 0);
|
|
4389
|
+
__decorate([
|
|
4390
|
+
type("number")
|
|
4391
|
+
], Reflection.prototype, "rootType", void 0);
|
|
4392
|
+
|
|
4393
|
+
function getDecoderStateCallbacks(decoder) {
|
|
4394
|
+
const $root = decoder.root;
|
|
4395
|
+
const callbacks = $root.callbacks;
|
|
4396
|
+
const onAddCalls = new WeakMap();
|
|
4397
|
+
let currentOnAddCallback;
|
|
4398
|
+
decoder.triggerChanges = function (allChanges) {
|
|
4399
|
+
const uniqueRefIds = new Set();
|
|
4400
|
+
for (let i = 0, l = allChanges.length; i < l; i++) {
|
|
4401
|
+
const change = allChanges[i];
|
|
4402
|
+
const refId = change.refId;
|
|
4403
|
+
const ref = change.ref;
|
|
4404
|
+
const $callbacks = callbacks[refId];
|
|
4405
|
+
if (!$callbacks) {
|
|
4406
|
+
continue;
|
|
4407
|
+
}
|
|
4408
|
+
//
|
|
4409
|
+
// trigger onRemove on child structure.
|
|
4410
|
+
//
|
|
4411
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
|
|
4412
|
+
change.previousValue instanceof Schema) {
|
|
4413
|
+
const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
|
|
4414
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4415
|
+
deleteCallbacks[i]();
|
|
4416
|
+
}
|
|
4417
|
+
}
|
|
4418
|
+
if (ref instanceof Schema) {
|
|
4419
|
+
//
|
|
4420
|
+
// Handle schema instance
|
|
4421
|
+
//
|
|
4422
|
+
if (!uniqueRefIds.has(refId)) {
|
|
4423
|
+
// trigger onChange
|
|
4424
|
+
const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
|
|
4425
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4426
|
+
replaceCallbacks[i]();
|
|
4427
|
+
// try {
|
|
4428
|
+
// } catch (e) {
|
|
4429
|
+
// console.error(e);
|
|
4430
|
+
// }
|
|
4431
|
+
}
|
|
4432
|
+
}
|
|
4433
|
+
if ($callbacks.hasOwnProperty(change.field)) {
|
|
4434
|
+
const fieldCallbacks = $callbacks[change.field];
|
|
4435
|
+
for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
|
|
4436
|
+
fieldCallbacks[i](change.value, change.previousValue);
|
|
4437
|
+
// try {
|
|
4438
|
+
// } catch (e) {
|
|
4439
|
+
// console.error(e);
|
|
4440
|
+
// }
|
|
4441
|
+
}
|
|
4442
|
+
}
|
|
4443
|
+
}
|
|
4444
|
+
else {
|
|
4445
|
+
//
|
|
4446
|
+
// Handle collection of items
|
|
4447
|
+
//
|
|
4448
|
+
if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
|
|
4449
|
+
//
|
|
4450
|
+
// FIXME: `previousValue` should always be available.
|
|
4451
|
+
//
|
|
4452
|
+
if (change.previousValue !== undefined) {
|
|
4453
|
+
// triger onRemove
|
|
4454
|
+
const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
|
|
4455
|
+
for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
|
|
4456
|
+
deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
|
|
4457
|
+
}
|
|
4458
|
+
}
|
|
4459
|
+
// Handle DELETE_AND_ADD operations
|
|
4460
|
+
if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
|
|
4461
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4462
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4463
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4464
|
+
}
|
|
4465
|
+
}
|
|
4466
|
+
}
|
|
4467
|
+
else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
|
|
4468
|
+
// triger onAdd
|
|
4469
|
+
const addCallbacks = $callbacks[exports.OPERATION.ADD];
|
|
4470
|
+
for (let i = addCallbacks?.length - 1; i >= 0; i--) {
|
|
4471
|
+
addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4472
|
+
}
|
|
4473
|
+
}
|
|
4474
|
+
// trigger onChange
|
|
4475
|
+
if (change.value !== change.previousValue) {
|
|
4476
|
+
const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
|
|
4477
|
+
for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
|
|
4478
|
+
replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
|
|
4479
|
+
}
|
|
4480
|
+
}
|
|
4481
|
+
}
|
|
4482
|
+
uniqueRefIds.add(refId);
|
|
4483
|
+
}
|
|
4484
|
+
};
|
|
4485
|
+
function getProxy(metadataOrType, context) {
|
|
4486
|
+
let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
|
|
4487
|
+
let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
|
|
4488
|
+
(metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
|
|
4489
|
+
if (metadata && !isCollection) {
|
|
4490
|
+
const onAddListen = function (ref, prop, callback, immediate) {
|
|
4491
|
+
// immediate trigger
|
|
4492
|
+
if (immediate &&
|
|
4493
|
+
context.instance[prop] !== undefined &&
|
|
4494
|
+
!onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
|
|
4495
|
+
) {
|
|
4496
|
+
callback(context.instance[prop], undefined);
|
|
4497
|
+
}
|
|
4498
|
+
return $root.addCallback($root.refIds.get(ref), prop, callback);
|
|
4499
|
+
};
|
|
4500
|
+
/**
|
|
4501
|
+
* Schema instances
|
|
4502
|
+
*/
|
|
4503
|
+
return new Proxy({
|
|
4504
|
+
listen: function listen(prop, callback, immediate = true) {
|
|
4505
|
+
if (context.instance) {
|
|
4506
|
+
return onAddListen(context.instance, prop, callback, immediate);
|
|
4507
|
+
}
|
|
4508
|
+
else {
|
|
4509
|
+
// collection instance not received yet
|
|
4510
|
+
let detachCallback = () => { };
|
|
4511
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4512
|
+
detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4513
|
+
});
|
|
4514
|
+
return () => detachCallback();
|
|
4515
|
+
}
|
|
4516
|
+
},
|
|
4517
|
+
onChange: function onChange(callback) {
|
|
4518
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
|
|
4519
|
+
},
|
|
4520
|
+
//
|
|
4521
|
+
// TODO: refactor `bindTo()` implementation.
|
|
4522
|
+
// There is room for improvement.
|
|
4523
|
+
//
|
|
4524
|
+
bindTo: function bindTo(targetObject, properties) {
|
|
4525
|
+
if (!properties) {
|
|
4526
|
+
properties = Object.keys(metadata).map((index) => metadata[index].name);
|
|
4527
|
+
}
|
|
4528
|
+
return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
|
|
4529
|
+
properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
|
|
4530
|
+
});
|
|
4531
|
+
}
|
|
4532
|
+
}, {
|
|
4533
|
+
get(target, prop) {
|
|
4534
|
+
const metadataField = metadata[metadata[prop]];
|
|
4535
|
+
if (metadataField) {
|
|
4536
|
+
const instance = context.instance?.[prop];
|
|
4537
|
+
const onInstanceAvailable = ((callback) => {
|
|
4538
|
+
const unbind = $(context.instance).listen(prop, (value, _) => {
|
|
4539
|
+
callback(value, false);
|
|
4540
|
+
// FIXME: by "unbinding" the callback here,
|
|
4541
|
+
// it will not support when the server
|
|
4542
|
+
// re-instantiates the instance.
|
|
4543
|
+
//
|
|
4544
|
+
unbind?.();
|
|
4545
|
+
}, false);
|
|
4546
|
+
// has existing value
|
|
4547
|
+
if ($root.refIds.get(instance) !== undefined) {
|
|
4548
|
+
callback(instance, true);
|
|
4549
|
+
}
|
|
4550
|
+
});
|
|
4551
|
+
return getProxy(metadataField.type, {
|
|
4552
|
+
// make sure refId is available, otherwise need to wait for the instance to be available.
|
|
4553
|
+
instance: ($root.refIds.get(instance) && instance),
|
|
4554
|
+
parentInstance: context.instance,
|
|
4555
|
+
onInstanceAvailable,
|
|
4556
|
+
});
|
|
4557
|
+
}
|
|
4558
|
+
else {
|
|
4559
|
+
// accessing the function
|
|
4560
|
+
return target[prop];
|
|
4561
|
+
}
|
|
4562
|
+
},
|
|
4563
|
+
has(target, prop) { return metadata[prop] !== undefined; },
|
|
4564
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4565
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4566
|
+
});
|
|
4567
|
+
}
|
|
4568
|
+
else {
|
|
4569
|
+
/**
|
|
4570
|
+
* Collection instances
|
|
4571
|
+
*/
|
|
4572
|
+
const onAdd = function (ref, callback, immediate) {
|
|
4573
|
+
// Trigger callback on existing items
|
|
4574
|
+
if (immediate) {
|
|
4575
|
+
ref.forEach((v, k) => callback(v, k));
|
|
4576
|
+
}
|
|
4577
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
|
|
4578
|
+
onAddCalls.set(callback, true);
|
|
4579
|
+
currentOnAddCallback = callback;
|
|
4580
|
+
callback(value, key);
|
|
4581
|
+
onAddCalls.delete(callback);
|
|
4582
|
+
currentOnAddCallback = undefined;
|
|
4583
|
+
});
|
|
4584
|
+
};
|
|
4585
|
+
const onRemove = function (ref, callback) {
|
|
4586
|
+
return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
|
|
4587
|
+
};
|
|
4588
|
+
return new Proxy({
|
|
4589
|
+
onAdd: function (callback, immediate = true) {
|
|
4590
|
+
//
|
|
4591
|
+
// https://github.com/colyseus/schema/issues/147
|
|
4592
|
+
// If parent instance has "onAdd" registered, avoid triggering immediate callback.
|
|
4593
|
+
//
|
|
4594
|
+
if (context.instance) {
|
|
4595
|
+
return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
|
|
4596
|
+
}
|
|
4597
|
+
else if (context.onInstanceAvailable) {
|
|
4598
|
+
// collection instance not received yet
|
|
4599
|
+
let detachCallback = () => { };
|
|
4600
|
+
context.onInstanceAvailable((ref, existing) => {
|
|
4601
|
+
detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
|
|
4602
|
+
});
|
|
4603
|
+
return () => detachCallback();
|
|
4604
|
+
}
|
|
4605
|
+
},
|
|
4606
|
+
onRemove: function (callback) {
|
|
4607
|
+
if (context.onInstanceAvailable) {
|
|
4608
|
+
// collection instance not received yet
|
|
4609
|
+
let detachCallback = () => { };
|
|
4610
|
+
context.onInstanceAvailable((ref) => {
|
|
4611
|
+
detachCallback = onRemove(ref, callback);
|
|
4612
|
+
});
|
|
4613
|
+
return () => detachCallback();
|
|
4614
|
+
}
|
|
4615
|
+
else if (context.instance) {
|
|
4616
|
+
return onRemove(context.instance, callback);
|
|
4617
|
+
}
|
|
4618
|
+
},
|
|
4619
|
+
}, {
|
|
4620
|
+
get(target, prop) {
|
|
4621
|
+
if (!target[prop]) {
|
|
4622
|
+
throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
|
|
4623
|
+
}
|
|
4624
|
+
return target[prop];
|
|
4625
|
+
},
|
|
4626
|
+
has(target, prop) { return target[prop] !== undefined; },
|
|
4627
|
+
set(_, _1, _2) { throw new Error("not allowed"); },
|
|
4628
|
+
deleteProperty(_, _1) { throw new Error("not allowed"); },
|
|
4629
|
+
});
|
|
4630
|
+
}
|
|
4631
|
+
}
|
|
4632
|
+
function $(instance) {
|
|
4633
|
+
return getProxy(undefined, { instance });
|
|
4634
|
+
}
|
|
4635
|
+
return $;
|
|
4636
|
+
}
|
|
4637
|
+
|
|
4638
|
+
function getRawChangesCallback(decoder, callback) {
|
|
4639
|
+
decoder.triggerChanges = callback;
|
|
4640
|
+
}
|
|
3944
4641
|
|
|
3945
4642
|
class StateView {
|
|
3946
4643
|
constructor() {
|
|
@@ -3956,31 +4653,32 @@
|
|
|
3956
4653
|
* Manual "ADD" operations for changes per ChangeTree, specific to this view.
|
|
3957
4654
|
* (This is used to force encoding a property, even if it was not changed)
|
|
3958
4655
|
*/
|
|
3959
|
-
this.changes =
|
|
4656
|
+
this.changes = {};
|
|
3960
4657
|
}
|
|
3961
4658
|
// TODO: allow to set multiple tags at once
|
|
3962
|
-
add(obj, tag = DEFAULT_VIEW_TAG) {
|
|
4659
|
+
add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
|
|
3963
4660
|
if (!obj[$changes]) {
|
|
3964
4661
|
console.warn("StateView#add(), invalid object:", obj);
|
|
3965
4662
|
return this;
|
|
3966
4663
|
}
|
|
3967
|
-
|
|
3968
|
-
this.items.add(changeTree);
|
|
3969
|
-
// Add children of this ChangeTree to this view
|
|
3970
|
-
changeTree.forEachChild((change, _) => this.add(change.ref, tag));
|
|
3971
|
-
// FIXME: ArraySchema/MapSchema does not have metadata
|
|
4664
|
+
// FIXME: ArraySchema/MapSchema do not have metadata
|
|
3972
4665
|
const metadata = obj.constructor[Symbol.metadata];
|
|
3973
|
-
|
|
3974
|
-
|
|
3975
|
-
|
|
4666
|
+
const changeTree = obj[$changes];
|
|
4667
|
+
this.items.add(changeTree);
|
|
4668
|
+
// add parent ChangeTree's
|
|
4669
|
+
// - if it was invisible to this view
|
|
4670
|
+
// - if it were previously filtered out
|
|
4671
|
+
if (checkIncludeParent && changeTree.parent) {
|
|
4672
|
+
this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
|
|
4673
|
+
}
|
|
3976
4674
|
//
|
|
3977
4675
|
// TODO: when adding an item of a MapSchema, the changes may not
|
|
3978
4676
|
// be set (only the parent's changes are set)
|
|
3979
4677
|
//
|
|
3980
|
-
let changes = this.changes.
|
|
4678
|
+
let changes = this.changes[changeTree.refId];
|
|
3981
4679
|
if (changes === undefined) {
|
|
3982
|
-
changes =
|
|
3983
|
-
this.changes.
|
|
4680
|
+
changes = {};
|
|
4681
|
+
this.changes[changeTree.refId] = changes;
|
|
3984
4682
|
}
|
|
3985
4683
|
// set tag
|
|
3986
4684
|
if (tag !== DEFAULT_VIEW_TAG) {
|
|
@@ -3996,82 +4694,76 @@
|
|
|
3996
4694
|
tags = this.tags.get(changeTree);
|
|
3997
4695
|
}
|
|
3998
4696
|
tags.add(tag);
|
|
3999
|
-
// console.log("BY TAG:", tag);
|
|
4000
4697
|
// Ref: add tagged properties
|
|
4001
|
-
metadata?.[
|
|
4698
|
+
metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
|
|
4002
4699
|
if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
|
|
4003
|
-
changes
|
|
4700
|
+
changes[index] = exports.OPERATION.ADD;
|
|
4004
4701
|
}
|
|
4005
4702
|
});
|
|
4006
4703
|
}
|
|
4007
4704
|
else {
|
|
4008
|
-
|
|
4009
|
-
|
|
4010
|
-
// metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
|
|
4011
|
-
// if (changeTree.getChange(index) !== OPERATION.DELETE) {
|
|
4012
|
-
// changes.set(index, OPERATION.ADD);
|
|
4013
|
-
// }
|
|
4014
|
-
// });
|
|
4015
|
-
const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4705
|
+
const isInvisible = this.invisible.has(changeTree);
|
|
4706
|
+
const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
|
|
4016
4707
|
? changeTree.allFilteredChanges
|
|
4017
4708
|
: changeTree.allChanges;
|
|
4018
|
-
|
|
4019
|
-
|
|
4020
|
-
|
|
4021
|
-
|
|
4022
|
-
|
|
4023
|
-
|
|
4709
|
+
for (let i = 0, len = changeSet.operations.length; i < len; i++) {
|
|
4710
|
+
const index = changeSet.operations[i];
|
|
4711
|
+
if (index === undefined) {
|
|
4712
|
+
continue;
|
|
4713
|
+
} // skip "undefined" indexes
|
|
4714
|
+
const op = changeTree.indexedOperations[index];
|
|
4715
|
+
const tagAtIndex = metadata?.[index].tag;
|
|
4716
|
+
if ((isInvisible || // if "invisible", include all
|
|
4717
|
+
tagAtIndex === undefined || // "all change" with no tag
|
|
4718
|
+
tagAtIndex === tag // tagged property
|
|
4719
|
+
) &&
|
|
4720
|
+
op !== exports.OPERATION.DELETE) {
|
|
4721
|
+
changes[index] = op;
|
|
4024
4722
|
}
|
|
4025
4723
|
}
|
|
4026
4724
|
}
|
|
4027
|
-
//
|
|
4028
|
-
|
|
4029
|
-
|
|
4030
|
-
(
|
|
4031
|
-
|
|
4032
|
-
|
|
4725
|
+
// Add children of this ChangeTree to this view
|
|
4726
|
+
changeTree.forEachChild((change, index) => {
|
|
4727
|
+
// Do not ADD children that don't have the same tag
|
|
4728
|
+
if (metadata && metadata[index].tag !== tag) {
|
|
4729
|
+
return;
|
|
4730
|
+
}
|
|
4731
|
+
this.add(change.ref, tag, false);
|
|
4732
|
+
});
|
|
4033
4733
|
return this;
|
|
4034
4734
|
}
|
|
4035
|
-
addParent(changeTree, tag) {
|
|
4036
|
-
|
|
4037
|
-
|
|
4038
|
-
|
|
4735
|
+
addParent(changeTree, parentIndex, tag) {
|
|
4736
|
+
// view must have all "changeTree" parent tree
|
|
4737
|
+
this.items.add(changeTree);
|
|
4738
|
+
// add parent's parent
|
|
4739
|
+
const parentChangeTree = changeTree.parent?.[$changes];
|
|
4740
|
+
if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
|
|
4741
|
+
this.addParent(parentChangeTree, changeTree.parentIndex, tag);
|
|
4039
4742
|
}
|
|
4040
|
-
|
|
4041
|
-
|
|
4042
|
-
if (!this.invisible.has(parentChangeTree)) {
|
|
4043
|
-
// parent is already available, no need to add it!
|
|
4743
|
+
// parent is already available, no need to add it!
|
|
4744
|
+
if (!this.invisible.has(changeTree)) {
|
|
4044
4745
|
return;
|
|
4045
4746
|
}
|
|
4046
|
-
this.addParent(parentChangeTree, tag);
|
|
4047
4747
|
// add parent's tag properties
|
|
4048
|
-
if (
|
|
4049
|
-
let
|
|
4050
|
-
if (
|
|
4051
|
-
|
|
4052
|
-
this.changes.
|
|
4748
|
+
if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
|
|
4749
|
+
let changes = this.changes[changeTree.refId];
|
|
4750
|
+
if (changes === undefined) {
|
|
4751
|
+
changes = {};
|
|
4752
|
+
this.changes[changeTree.refId] = changes;
|
|
4053
4753
|
}
|
|
4054
|
-
// console.log("add parent change", {
|
|
4055
|
-
// parentIndex,
|
|
4056
|
-
// parentChanges,
|
|
4057
|
-
// parentChange: (
|
|
4058
|
-
// parentChangeTree.getChange(parentIndex) &&
|
|
4059
|
-
// OPERATION[parentChangeTree.getChange(parentIndex)]
|
|
4060
|
-
// ),
|
|
4061
|
-
// })
|
|
4062
4754
|
if (!this.tags) {
|
|
4063
4755
|
this.tags = new WeakMap();
|
|
4064
4756
|
}
|
|
4065
4757
|
let tags;
|
|
4066
|
-
if (!this.tags.has(
|
|
4758
|
+
if (!this.tags.has(changeTree)) {
|
|
4067
4759
|
tags = new Set();
|
|
4068
|
-
this.tags.set(
|
|
4760
|
+
this.tags.set(changeTree, tags);
|
|
4069
4761
|
}
|
|
4070
4762
|
else {
|
|
4071
|
-
tags = this.tags.get(
|
|
4763
|
+
tags = this.tags.get(changeTree);
|
|
4072
4764
|
}
|
|
4073
4765
|
tags.add(tag);
|
|
4074
|
-
|
|
4766
|
+
changes[parentIndex] = exports.OPERATION.ADD;
|
|
4075
4767
|
}
|
|
4076
4768
|
}
|
|
4077
4769
|
remove(obj, tag = DEFAULT_VIEW_TAG) {
|
|
@@ -4083,32 +4775,32 @@
|
|
|
4083
4775
|
this.items.delete(changeTree);
|
|
4084
4776
|
const ref = changeTree.ref;
|
|
4085
4777
|
const metadata = ref.constructor[Symbol.metadata];
|
|
4086
|
-
let changes = this.changes.
|
|
4778
|
+
let changes = this.changes[changeTree.refId];
|
|
4087
4779
|
if (changes === undefined) {
|
|
4088
|
-
changes =
|
|
4089
|
-
this.changes.
|
|
4780
|
+
changes = {};
|
|
4781
|
+
this.changes[changeTree.refId] = changes;
|
|
4090
4782
|
}
|
|
4091
4783
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
4092
4784
|
// parent is collection (Map/Array)
|
|
4093
4785
|
const parent = changeTree.parent;
|
|
4094
4786
|
if (!Metadata.isValidInstance(parent)) {
|
|
4095
4787
|
const parentChangeTree = parent[$changes];
|
|
4096
|
-
let changes = this.changes.
|
|
4788
|
+
let changes = this.changes[parentChangeTree.refId];
|
|
4097
4789
|
if (changes === undefined) {
|
|
4098
|
-
changes =
|
|
4099
|
-
this.changes.
|
|
4790
|
+
changes = {};
|
|
4791
|
+
this.changes[parentChangeTree.refId] = changes;
|
|
4100
4792
|
}
|
|
4101
4793
|
// DELETE / DELETE BY REF ID
|
|
4102
|
-
changes
|
|
4794
|
+
changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
|
|
4103
4795
|
}
|
|
4104
4796
|
else {
|
|
4105
4797
|
// delete all "tagged" properties.
|
|
4106
|
-
metadata[
|
|
4798
|
+
metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4107
4799
|
}
|
|
4108
4800
|
}
|
|
4109
4801
|
else {
|
|
4110
4802
|
// delete only tagged properties
|
|
4111
|
-
metadata[
|
|
4803
|
+
metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
|
|
4112
4804
|
}
|
|
4113
4805
|
// remove tag
|
|
4114
4806
|
if (this.tags && this.tags.has(changeTree)) {
|
|
@@ -4160,16 +4852,18 @@
|
|
|
4160
4852
|
exports.decode = decode;
|
|
4161
4853
|
exports.decodeKeyValueOperation = decodeKeyValueOperation;
|
|
4162
4854
|
exports.decodeSchemaOperation = decodeSchemaOperation;
|
|
4855
|
+
exports.defineCustomTypes = defineCustomTypes;
|
|
4163
4856
|
exports.defineTypes = defineTypes;
|
|
4164
4857
|
exports.deprecated = deprecated;
|
|
4165
4858
|
exports.dumpChanges = dumpChanges;
|
|
4166
4859
|
exports.encode = encode;
|
|
4167
4860
|
exports.encodeKeyValueOperation = encodeArray;
|
|
4168
4861
|
exports.encodeSchemaOperation = encodeSchemaOperation;
|
|
4862
|
+
exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
|
|
4863
|
+
exports.getRawChangesCallback = getRawChangesCallback;
|
|
4169
4864
|
exports.registerType = registerType;
|
|
4865
|
+
exports.schema = schema;
|
|
4170
4866
|
exports.type = type;
|
|
4171
4867
|
exports.view = view;
|
|
4172
4868
|
|
|
4173
|
-
Object.defineProperty(exports, '__esModule', { value: true });
|
|
4174
|
-
|
|
4175
4869
|
}));
|