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