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