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