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