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