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