@colyseus/schema 2.0.31 → 3.0.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/cjs/index.js +3614 -2634
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +3324 -2445
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +3614 -2634
- package/lib/Decoder.d.ts +16 -0
- package/lib/Decoder.js +182 -0
- package/lib/Decoder.js.map +1 -0
- package/lib/Encoder.d.ts +13 -0
- package/lib/Encoder.js +79 -0
- package/lib/Encoder.js.map +1 -0
- package/lib/Metadata.d.ts +36 -0
- package/lib/Metadata.js +91 -0
- package/lib/Metadata.js.map +1 -0
- package/lib/Reflection.d.ts +7 -5
- package/lib/Reflection.js +62 -58
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +39 -51
- package/lib/Schema.js +189 -731
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +26 -45
- package/lib/annotations.js +363 -194
- package/lib/annotations.js.map +1 -1
- package/lib/changes/ChangeSet.d.ts +12 -0
- package/lib/changes/ChangeSet.js +35 -0
- package/lib/changes/ChangeSet.js.map +1 -0
- package/lib/changes/DecodeOperation.d.ts +15 -0
- package/lib/changes/DecodeOperation.js +186 -0
- package/lib/changes/DecodeOperation.js.map +1 -0
- package/lib/changes/EncodeOperation.d.ts +18 -0
- package/lib/changes/EncodeOperation.js +130 -0
- package/lib/changes/EncodeOperation.js.map +1 -0
- package/lib/changes/consts.d.ts +14 -0
- package/lib/changes/consts.js +18 -0
- package/lib/changes/consts.js.map +1 -0
- package/lib/decoder/DecodeOperation.d.ts +24 -0
- package/lib/decoder/DecodeOperation.js +256 -0
- package/lib/decoder/DecodeOperation.js.map +1 -0
- package/lib/decoder/Decoder.d.ts +21 -0
- package/lib/decoder/Decoder.js +114 -0
- package/lib/decoder/Decoder.js.map +1 -0
- package/lib/decoder/ReferenceTracker.d.ts +26 -0
- package/lib/decoder/ReferenceTracker.js +131 -0
- package/lib/decoder/ReferenceTracker.js.map +1 -0
- package/lib/decoder/strategy/RawChanges.d.ts +3 -0
- package/lib/decoder/strategy/RawChanges.js +8 -0
- package/lib/decoder/strategy/RawChanges.js.map +1 -0
- package/lib/decoder/strategy/StateCallbacks.d.ts +20 -0
- package/lib/decoder/strategy/StateCallbacks.js +240 -0
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -0
- package/lib/decoding/decode.d.ts +48 -0
- package/lib/decoding/decode.js +267 -0
- package/lib/decoding/decode.js.map +1 -0
- package/lib/ecs.d.ts +11 -0
- package/lib/ecs.js +160 -0
- package/lib/ecs.js.map +1 -0
- package/lib/encoder/ChangeTree.d.ts +72 -0
- package/lib/encoder/ChangeTree.js +384 -0
- package/lib/encoder/ChangeTree.js.map +1 -0
- package/lib/encoder/EncodeOperation.d.ts +25 -0
- package/lib/encoder/EncodeOperation.js +156 -0
- package/lib/encoder/EncodeOperation.js.map +1 -0
- package/lib/encoder/Encoder.d.ts +23 -0
- package/lib/encoder/Encoder.js +192 -0
- package/lib/encoder/Encoder.js.map +1 -0
- package/lib/encoder/StateView.d.ts +21 -0
- package/lib/encoder/StateView.js +196 -0
- package/lib/encoder/StateView.js.map +1 -0
- package/lib/encoding/assert.d.ts +9 -0
- package/lib/encoding/assert.js +47 -0
- package/lib/encoding/assert.js.map +1 -0
- package/lib/encoding/decode.js +1 -1
- package/lib/encoding/decode.js.map +1 -1
- package/lib/encoding/encode.d.ts +17 -16
- package/lib/encoding/encode.js +88 -81
- package/lib/encoding/encode.js.map +1 -1
- package/lib/encoding/spec.d.ts +25 -0
- package/lib/encoding/spec.js +30 -0
- package/lib/encoding/spec.js.map +1 -0
- package/lib/index.d.ts +18 -10
- package/lib/index.js +39 -17
- package/lib/index.js.map +1 -1
- package/lib/symbol.shim.d.ts +6 -0
- package/lib/symbol.shim.js +4 -0
- package/lib/symbol.shim.js.map +1 -0
- package/lib/types/ArraySchema.js +0 -7
- package/lib/types/ArraySchema.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +10 -2
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/custom/ArraySchema.d.ts +245 -0
- package/lib/types/custom/ArraySchema.js +659 -0
- package/lib/types/custom/ArraySchema.js.map +1 -0
- package/lib/types/custom/CollectionSchema.d.ts +42 -0
- package/lib/types/custom/CollectionSchema.js +165 -0
- package/lib/types/custom/CollectionSchema.js.map +1 -0
- package/lib/types/custom/MapSchema.d.ts +43 -0
- package/lib/types/custom/MapSchema.js +200 -0
- package/lib/types/custom/MapSchema.js.map +1 -0
- package/lib/types/custom/SetSchema.d.ts +39 -0
- package/lib/types/custom/SetSchema.js +177 -0
- package/lib/types/custom/SetSchema.js.map +1 -0
- package/lib/types/registry.d.ts +6 -0
- package/lib/types/registry.js +19 -0
- package/lib/types/registry.js.map +1 -0
- package/lib/types/symbols.d.ts +29 -0
- package/lib/types/symbols.js +33 -0
- package/lib/types/symbols.js.map +1 -0
- package/lib/types/utils.d.ts +0 -8
- package/lib/types/utils.js +1 -33
- package/lib/types/utils.js.map +1 -1
- package/lib/usage.d.ts +1 -0
- package/lib/usage.js +22 -0
- package/lib/usage.js.map +1 -0
- package/lib/utils.d.ts +13 -2
- package/lib/utils.js +36 -15
- package/lib/utils.js.map +1 -1
- package/lib/v3.d.ts +1 -0
- package/lib/v3.js +427 -0
- package/lib/v3.js.map +1 -0
- package/lib/v3_bench.d.ts +1 -0
- package/lib/v3_bench.js +130 -0
- package/lib/v3_bench.js.map +1 -0
- package/lib/v3_experiment.d.ts +1 -0
- package/lib/v3_experiment.js +407 -0
- package/lib/v3_experiment.js.map +1 -0
- package/package.json +5 -5
- package/src/Metadata.ts +135 -0
- package/src/Reflection.ts +75 -66
- package/src/Schema.ts +213 -931
- package/src/annotations.ts +430 -243
- package/src/decoder/DecodeOperation.ts +372 -0
- package/src/decoder/Decoder.ts +155 -0
- package/src/decoder/ReferenceTracker.ts +151 -0
- package/src/decoder/strategy/RawChanges.ts +9 -0
- package/src/decoder/strategy/StateCallbacks.ts +326 -0
- package/src/encoder/ChangeTree.ts +492 -0
- package/src/encoder/EncodeOperation.ts +237 -0
- package/src/encoder/Encoder.ts +246 -0
- package/src/encoder/StateView.ts +229 -0
- package/src/encoding/assert.ts +58 -0
- package/src/encoding/decode.ts +1 -1
- package/src/encoding/encode.ts +88 -82
- package/src/encoding/spec.ts +29 -0
- package/src/index.ts +22 -19
- package/src/symbol.shim.ts +12 -0
- package/src/types/HelperTypes.ts +16 -2
- package/src/types/{ArraySchema.ts → custom/ArraySchema.ts} +345 -251
- package/src/types/{CollectionSchema.ts → custom/CollectionSchema.ts} +56 -46
- package/src/types/{MapSchema.ts → custom/MapSchema.ts} +88 -115
- package/src/types/{SetSchema.ts → custom/SetSchema.ts} +58 -47
- package/src/types/{typeRegistry.ts → registry.ts} +6 -6
- package/src/types/symbols.ts +36 -0
- package/src/types/utils.ts +0 -46
- package/src/utils.ts +50 -21
- package/src/v3_bench.ts +107 -0
- package/src/changes/ChangeTree.ts +0 -295
- package/src/changes/ReferenceTracker.ts +0 -91
- package/src/filters/index.ts +0 -23
- package/src/spec.ts +0 -49
package/lib/Schema.js
CHANGED
|
@@ -1,114 +1,104 @@
|
|
|
1
1
|
"use strict";
|
|
2
|
+
var _a, _b;
|
|
2
3
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
4
|
exports.Schema = void 0;
|
|
4
|
-
const spec_1 = require("./spec");
|
|
5
|
+
const spec_1 = require("./encoding/spec");
|
|
5
6
|
const annotations_1 = require("./annotations");
|
|
6
|
-
const
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const
|
|
11
|
-
const SetSchema_1 = require("./types/SetSchema");
|
|
12
|
-
const ChangeTree_1 = require("./changes/ChangeTree");
|
|
13
|
-
const filters_1 = require("./filters");
|
|
14
|
-
const typeRegistry_1 = require("./types/typeRegistry");
|
|
15
|
-
const ReferenceTracker_1 = require("./changes/ReferenceTracker");
|
|
16
|
-
const utils_1 = require("./types/utils");
|
|
17
|
-
class EncodeSchemaError extends Error {
|
|
18
|
-
}
|
|
19
|
-
function assertType(value, type, klass, field) {
|
|
20
|
-
let typeofTarget;
|
|
21
|
-
let allowNull = false;
|
|
22
|
-
switch (type) {
|
|
23
|
-
case "number":
|
|
24
|
-
case "int8":
|
|
25
|
-
case "uint8":
|
|
26
|
-
case "int16":
|
|
27
|
-
case "uint16":
|
|
28
|
-
case "int32":
|
|
29
|
-
case "uint32":
|
|
30
|
-
case "int64":
|
|
31
|
-
case "uint64":
|
|
32
|
-
case "float32":
|
|
33
|
-
case "float64":
|
|
34
|
-
typeofTarget = "number";
|
|
35
|
-
if (isNaN(value)) {
|
|
36
|
-
console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
|
|
37
|
-
}
|
|
38
|
-
break;
|
|
39
|
-
case "string":
|
|
40
|
-
typeofTarget = "string";
|
|
41
|
-
allowNull = true;
|
|
42
|
-
break;
|
|
43
|
-
case "boolean":
|
|
44
|
-
// boolean is always encoded as true/false based on truthiness
|
|
45
|
-
return;
|
|
46
|
-
}
|
|
47
|
-
if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
|
|
48
|
-
let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
|
|
49
|
-
throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
function assertInstanceType(value, type, klass, field) {
|
|
53
|
-
if (!(value instanceof type)) {
|
|
54
|
-
throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value.constructor.name}' was provided in ${klass.constructor.name}#${field}`);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function encodePrimitiveType(type, bytes, value, klass, field) {
|
|
58
|
-
assertType(value, type, klass, field);
|
|
59
|
-
const encodeFunc = encode[type];
|
|
60
|
-
if (encodeFunc) {
|
|
61
|
-
encodeFunc(bytes, value);
|
|
62
|
-
}
|
|
63
|
-
else {
|
|
64
|
-
throw new EncodeSchemaError(`a '${type}' was expected, but ${value} was provided in ${klass.constructor.name}#${field}`);
|
|
65
|
-
}
|
|
66
|
-
}
|
|
67
|
-
function decodePrimitiveType(type, bytes, it) {
|
|
68
|
-
return decode[type](bytes, it);
|
|
69
|
-
}
|
|
7
|
+
const ChangeTree_1 = require("./encoder/ChangeTree");
|
|
8
|
+
const symbols_1 = require("./types/symbols");
|
|
9
|
+
const EncodeOperation_1 = require("./encoder/EncodeOperation");
|
|
10
|
+
const DecodeOperation_1 = require("./decoder/DecodeOperation");
|
|
11
|
+
const utils_1 = require("./utils");
|
|
70
12
|
/**
|
|
71
13
|
* Schema encoder / decoder
|
|
72
14
|
*/
|
|
73
15
|
class Schema {
|
|
74
|
-
static { this
|
|
75
|
-
static
|
|
76
|
-
|
|
16
|
+
static { this[_a] = EncodeOperation_1.encodeSchemaOperation; }
|
|
17
|
+
static { this[_b] = DecodeOperation_1.decodeSchemaOperation; }
|
|
18
|
+
/**
|
|
19
|
+
* Assign the property descriptors required to track changes on this instance.
|
|
20
|
+
* @param instance
|
|
21
|
+
*/
|
|
22
|
+
static initialize(instance) {
|
|
23
|
+
Object.defineProperty(instance, symbols_1.$changes, {
|
|
24
|
+
value: new ChangeTree_1.ChangeTree(instance),
|
|
25
|
+
enumerable: false,
|
|
26
|
+
writable: true
|
|
27
|
+
});
|
|
28
|
+
const metadata = instance.constructor[Symbol.metadata];
|
|
29
|
+
// Define property descriptors
|
|
30
|
+
for (const field in metadata) {
|
|
31
|
+
if (metadata[field].descriptor) {
|
|
32
|
+
// for encoder
|
|
33
|
+
Object.defineProperty(instance, `_${field}`, {
|
|
34
|
+
value: undefined,
|
|
35
|
+
writable: true,
|
|
36
|
+
enumerable: false,
|
|
37
|
+
configurable: true,
|
|
38
|
+
});
|
|
39
|
+
Object.defineProperty(instance, field, metadata[field].descriptor);
|
|
40
|
+
}
|
|
41
|
+
else {
|
|
42
|
+
// for decoder
|
|
43
|
+
Object.defineProperty(instance, field, {
|
|
44
|
+
value: undefined,
|
|
45
|
+
writable: true,
|
|
46
|
+
enumerable: true,
|
|
47
|
+
configurable: true,
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
// Object.defineProperty(instance, field, {
|
|
51
|
+
// ...instance.constructor[Symbol.metadata][field].descriptor
|
|
52
|
+
// });
|
|
53
|
+
// if (args[0]?.hasOwnProperty(field)) {
|
|
54
|
+
// instance[field] = args[0][field];
|
|
55
|
+
// }
|
|
56
|
+
}
|
|
77
57
|
}
|
|
78
58
|
static is(type) {
|
|
79
|
-
return (type[
|
|
80
|
-
|
|
59
|
+
return typeof (type[Symbol.metadata]) === "object";
|
|
60
|
+
// const metadata = type[Symbol.metadata];
|
|
61
|
+
// return metadata && Object.prototype.hasOwnProperty.call(metadata, -1);
|
|
81
62
|
}
|
|
82
|
-
|
|
83
|
-
|
|
63
|
+
/**
|
|
64
|
+
* Track property changes
|
|
65
|
+
*/
|
|
66
|
+
static [(_a = symbols_1.$encoder, _b = symbols_1.$decoder, symbols_1.$track)](changeTree, index, operation = spec_1.OPERATION.ADD) {
|
|
67
|
+
changeTree.change(index, operation);
|
|
84
68
|
}
|
|
85
|
-
|
|
86
|
-
|
|
69
|
+
/**
|
|
70
|
+
* Determine if a property must be filtered.
|
|
71
|
+
* - If returns false, the property is NOT going to be encoded.
|
|
72
|
+
* - If returns true, the property is going to be encoded.
|
|
73
|
+
*
|
|
74
|
+
* Encoding with "filters" happens in two steps:
|
|
75
|
+
* - First, the encoder iterates over all "not owned" properties and encodes them.
|
|
76
|
+
* - Then, the encoder iterates over all "owned" properties per instance and encodes them.
|
|
77
|
+
*/
|
|
78
|
+
static [symbols_1.$filter](ref, index, view) {
|
|
79
|
+
const metadata = ref.constructor[Symbol.metadata];
|
|
80
|
+
const tag = metadata[metadata[index]].tag;
|
|
81
|
+
if (view === undefined) {
|
|
82
|
+
// shared pass/encode: encode if doesn't have a tag
|
|
83
|
+
return tag === undefined;
|
|
84
|
+
}
|
|
85
|
+
else if (tag === undefined) {
|
|
86
|
+
// view pass: no tag
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
else if (tag === annotations_1.DEFAULT_VIEW_TAG) {
|
|
90
|
+
// view pass: default tag
|
|
91
|
+
return view.items.has(ref[symbols_1.$changes]);
|
|
92
|
+
}
|
|
93
|
+
else {
|
|
94
|
+
// view pass: custom tag
|
|
95
|
+
const tags = view.tags?.get(ref[symbols_1.$changes]);
|
|
96
|
+
return tags && tags.has(tag);
|
|
97
|
+
}
|
|
87
98
|
}
|
|
88
99
|
// allow inherited classes to have a constructor
|
|
89
100
|
constructor(...args) {
|
|
90
|
-
|
|
91
|
-
Object.defineProperties(this, {
|
|
92
|
-
$changes: {
|
|
93
|
-
value: new ChangeTree_1.ChangeTree(this, undefined, new ReferenceTracker_1.ReferenceTracker()),
|
|
94
|
-
enumerable: false,
|
|
95
|
-
writable: true
|
|
96
|
-
},
|
|
97
|
-
// $listeners: {
|
|
98
|
-
// value: undefined,
|
|
99
|
-
// enumerable: false,
|
|
100
|
-
// writable: true
|
|
101
|
-
// },
|
|
102
|
-
$callbacks: {
|
|
103
|
-
value: undefined,
|
|
104
|
-
enumerable: false,
|
|
105
|
-
writable: true
|
|
106
|
-
},
|
|
107
|
-
});
|
|
108
|
-
const descriptors = this._definition.descriptors;
|
|
109
|
-
if (descriptors) {
|
|
110
|
-
Object.defineProperties(this, descriptors);
|
|
111
|
-
}
|
|
101
|
+
Schema.initialize(this);
|
|
112
102
|
//
|
|
113
103
|
// Assign initial values
|
|
114
104
|
//
|
|
@@ -120,7 +110,6 @@ class Schema {
|
|
|
120
110
|
Object.assign(this, props);
|
|
121
111
|
return this;
|
|
122
112
|
}
|
|
123
|
-
get _definition() { return this.constructor._definition; }
|
|
124
113
|
/**
|
|
125
114
|
* (Server-side): Flag a property to be encoded for the next patch.
|
|
126
115
|
* @param instance Schema instance
|
|
@@ -128,551 +117,16 @@ class Schema {
|
|
|
128
117
|
* @param operation OPERATION to perform (detected automatically)
|
|
129
118
|
*/
|
|
130
119
|
setDirty(property, operation) {
|
|
131
|
-
this.$changes.change(property, operation);
|
|
132
|
-
}
|
|
133
|
-
/**
|
|
134
|
-
* Client-side: listen for changes on property.
|
|
135
|
-
* @param prop the property name
|
|
136
|
-
* @param callback callback to be triggered on property change
|
|
137
|
-
* @param immediate trigger immediatelly if property has been already set.
|
|
138
|
-
*/
|
|
139
|
-
listen(prop, callback, immediate = true) {
|
|
140
|
-
if (!this.$callbacks) {
|
|
141
|
-
this.$callbacks = {};
|
|
142
|
-
}
|
|
143
|
-
if (!this.$callbacks[prop]) {
|
|
144
|
-
this.$callbacks[prop] = [];
|
|
145
|
-
}
|
|
146
|
-
this.$callbacks[prop].push(callback);
|
|
147
|
-
if (immediate && this[prop] !== undefined) {
|
|
148
|
-
callback(this[prop], undefined);
|
|
149
|
-
}
|
|
150
|
-
// return un-register callback.
|
|
151
|
-
return () => (0, utils_1.spliceOne)(this.$callbacks[prop], this.$callbacks[prop].indexOf(callback));
|
|
152
|
-
}
|
|
153
|
-
decode(bytes, it = { offset: 0 }, ref = this) {
|
|
154
|
-
const allChanges = [];
|
|
155
|
-
const $root = this.$changes.root;
|
|
156
|
-
const totalBytes = bytes.length;
|
|
157
|
-
let refId = 0;
|
|
158
|
-
$root.refs.set(refId, this);
|
|
159
|
-
while (it.offset < totalBytes) {
|
|
160
|
-
let byte = bytes[it.offset++];
|
|
161
|
-
if (byte == spec_1.SWITCH_TO_STRUCTURE) {
|
|
162
|
-
refId = decode.number(bytes, it);
|
|
163
|
-
const nextRef = $root.refs.get(refId);
|
|
164
|
-
//
|
|
165
|
-
// Trying to access a reference that haven't been decoded yet.
|
|
166
|
-
//
|
|
167
|
-
if (!nextRef) {
|
|
168
|
-
throw new Error(`"refId" not found: ${refId}`);
|
|
169
|
-
}
|
|
170
|
-
ref = nextRef;
|
|
171
|
-
continue;
|
|
172
|
-
}
|
|
173
|
-
const changeTree = ref['$changes'];
|
|
174
|
-
const isSchema = (ref['_definition'] !== undefined);
|
|
175
|
-
const operation = (isSchema)
|
|
176
|
-
? (byte >> 6) << 6 // "compressed" index + operation
|
|
177
|
-
: byte; // "uncompressed" index + operation (array/map items)
|
|
178
|
-
if (operation === spec_1.OPERATION.CLEAR) {
|
|
179
|
-
//
|
|
180
|
-
// TODO: refactor me!
|
|
181
|
-
// The `.clear()` method is calling `$root.removeRef(refId)` for
|
|
182
|
-
// each item inside this collection
|
|
183
|
-
//
|
|
184
|
-
ref.clear(allChanges);
|
|
185
|
-
continue;
|
|
186
|
-
}
|
|
187
|
-
const fieldIndex = (isSchema)
|
|
188
|
-
? byte % (operation || 255) // if "REPLACE" operation (0), use 255
|
|
189
|
-
: decode.number(bytes, it);
|
|
190
|
-
const fieldName = (isSchema)
|
|
191
|
-
? (ref['_definition'].fieldsByIndex[fieldIndex])
|
|
192
|
-
: "";
|
|
193
|
-
let type = changeTree.getType(fieldIndex);
|
|
194
|
-
let value;
|
|
195
|
-
let previousValue;
|
|
196
|
-
let dynamicIndex;
|
|
197
|
-
if (!isSchema) {
|
|
198
|
-
previousValue = ref['getByIndex'](fieldIndex);
|
|
199
|
-
if ((operation & spec_1.OPERATION.ADD) === spec_1.OPERATION.ADD) { // ADD or DELETE_AND_ADD
|
|
200
|
-
dynamicIndex = (ref instanceof MapSchema_1.MapSchema)
|
|
201
|
-
? decode.string(bytes, it)
|
|
202
|
-
: fieldIndex;
|
|
203
|
-
ref['setIndex'](fieldIndex, dynamicIndex);
|
|
204
|
-
}
|
|
205
|
-
else {
|
|
206
|
-
// here
|
|
207
|
-
dynamicIndex = ref['getIndex'](fieldIndex);
|
|
208
|
-
}
|
|
209
|
-
}
|
|
210
|
-
else {
|
|
211
|
-
previousValue = ref[`_${fieldName}`];
|
|
212
|
-
}
|
|
213
|
-
//
|
|
214
|
-
// Delete operations
|
|
215
|
-
//
|
|
216
|
-
if ((operation & spec_1.OPERATION.DELETE) === spec_1.OPERATION.DELETE) {
|
|
217
|
-
if (operation !== spec_1.OPERATION.DELETE_AND_ADD) {
|
|
218
|
-
ref['deleteByIndex'](fieldIndex);
|
|
219
|
-
}
|
|
220
|
-
// Flag `refId` for garbage collection.
|
|
221
|
-
if (previousValue && previousValue['$changes']) {
|
|
222
|
-
$root.removeRef(previousValue['$changes'].refId);
|
|
223
|
-
}
|
|
224
|
-
value = null;
|
|
225
|
-
}
|
|
226
|
-
if (fieldName === undefined) {
|
|
227
|
-
console.warn("@colyseus/schema: definition mismatch");
|
|
228
|
-
//
|
|
229
|
-
// keep skipping next bytes until reaches a known structure
|
|
230
|
-
// by local decoder.
|
|
231
|
-
//
|
|
232
|
-
const nextIterator = { offset: it.offset };
|
|
233
|
-
while (it.offset < totalBytes) {
|
|
234
|
-
if (decode.switchStructureCheck(bytes, it)) {
|
|
235
|
-
nextIterator.offset = it.offset + 1;
|
|
236
|
-
if ($root.refs.has(decode.number(bytes, nextIterator))) {
|
|
237
|
-
break;
|
|
238
|
-
}
|
|
239
|
-
}
|
|
240
|
-
it.offset++;
|
|
241
|
-
}
|
|
242
|
-
continue;
|
|
243
|
-
}
|
|
244
|
-
else if (operation === spec_1.OPERATION.DELETE) {
|
|
245
|
-
//
|
|
246
|
-
// FIXME: refactor me.
|
|
247
|
-
// Don't do anything.
|
|
248
|
-
//
|
|
249
|
-
}
|
|
250
|
-
else if (Schema.is(type)) {
|
|
251
|
-
const refId = decode.number(bytes, it);
|
|
252
|
-
value = $root.refs.get(refId);
|
|
253
|
-
if (operation !== spec_1.OPERATION.REPLACE) {
|
|
254
|
-
const childType = this.getSchemaType(bytes, it, type);
|
|
255
|
-
if (!value) {
|
|
256
|
-
value = this.createTypeInstance(childType);
|
|
257
|
-
value.$changes.refId = refId;
|
|
258
|
-
if (previousValue) {
|
|
259
|
-
value.$callbacks = previousValue.$callbacks;
|
|
260
|
-
// value.$listeners = previousValue.$listeners;
|
|
261
|
-
if (previousValue['$changes'].refId &&
|
|
262
|
-
refId !== previousValue['$changes'].refId) {
|
|
263
|
-
$root.removeRef(previousValue['$changes'].refId);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
$root.addRef(refId, value, (value !== previousValue));
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
|
-
else if (typeof (type) === "string") {
|
|
271
|
-
//
|
|
272
|
-
// primitive value (number, string, boolean, etc)
|
|
273
|
-
//
|
|
274
|
-
value = decodePrimitiveType(type, bytes, it);
|
|
275
|
-
}
|
|
276
|
-
else {
|
|
277
|
-
const typeDef = (0, typeRegistry_1.getType)(Object.keys(type)[0]);
|
|
278
|
-
const refId = decode.number(bytes, it);
|
|
279
|
-
const valueRef = ($root.refs.has(refId))
|
|
280
|
-
? previousValue || $root.refs.get(refId)
|
|
281
|
-
: new typeDef.constructor();
|
|
282
|
-
value = valueRef.clone(true);
|
|
283
|
-
value.$changes.refId = refId;
|
|
284
|
-
// preserve schema callbacks
|
|
285
|
-
if (previousValue) {
|
|
286
|
-
value['$callbacks'] = previousValue['$callbacks'];
|
|
287
|
-
if (previousValue['$changes'].refId &&
|
|
288
|
-
refId !== previousValue['$changes'].refId) {
|
|
289
|
-
$root.removeRef(previousValue['$changes'].refId);
|
|
290
|
-
//
|
|
291
|
-
// Trigger onRemove if structure has been replaced.
|
|
292
|
-
//
|
|
293
|
-
const entries = previousValue.entries();
|
|
294
|
-
let iter;
|
|
295
|
-
while ((iter = entries.next()) && !iter.done) {
|
|
296
|
-
const [key, value] = iter.value;
|
|
297
|
-
allChanges.push({
|
|
298
|
-
refId,
|
|
299
|
-
op: spec_1.OPERATION.DELETE,
|
|
300
|
-
field: key,
|
|
301
|
-
value: undefined,
|
|
302
|
-
previousValue: value,
|
|
303
|
-
});
|
|
304
|
-
}
|
|
305
|
-
}
|
|
306
|
-
}
|
|
307
|
-
$root.addRef(refId, value, (valueRef !== previousValue));
|
|
308
|
-
}
|
|
309
|
-
if (value !== null &&
|
|
310
|
-
value !== undefined) {
|
|
311
|
-
if (value['$changes']) {
|
|
312
|
-
value['$changes'].setParent(changeTree.ref, changeTree.root, fieldIndex);
|
|
313
|
-
}
|
|
314
|
-
if (ref instanceof Schema) {
|
|
315
|
-
ref[fieldName] = value;
|
|
316
|
-
// ref[`_${fieldName}`] = value;
|
|
317
|
-
}
|
|
318
|
-
else if (ref instanceof MapSchema_1.MapSchema) {
|
|
319
|
-
// const key = ref['$indexes'].get(field);
|
|
320
|
-
const key = dynamicIndex;
|
|
321
|
-
// ref.set(key, value);
|
|
322
|
-
ref['$items'].set(key, value);
|
|
323
|
-
ref['$changes'].allChanges.add(fieldIndex);
|
|
324
|
-
}
|
|
325
|
-
else if (ref instanceof ArraySchema_1.ArraySchema) {
|
|
326
|
-
// const key = ref['$indexes'][field];
|
|
327
|
-
// console.log("SETTING FOR ArraySchema =>", { field, key, value });
|
|
328
|
-
// ref[key] = value;
|
|
329
|
-
ref.setAt(fieldIndex, value);
|
|
330
|
-
}
|
|
331
|
-
else if (ref instanceof CollectionSchema_1.CollectionSchema) {
|
|
332
|
-
const index = ref.add(value);
|
|
333
|
-
ref['setIndex'](fieldIndex, index);
|
|
334
|
-
}
|
|
335
|
-
else if (ref instanceof SetSchema_1.SetSchema) {
|
|
336
|
-
const index = ref.add(value);
|
|
337
|
-
if (index !== false) {
|
|
338
|
-
ref['setIndex'](fieldIndex, index);
|
|
339
|
-
}
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
if (previousValue !== value) {
|
|
343
|
-
allChanges.push({
|
|
344
|
-
refId,
|
|
345
|
-
op: operation,
|
|
346
|
-
field: fieldName,
|
|
347
|
-
dynamicIndex,
|
|
348
|
-
value,
|
|
349
|
-
previousValue,
|
|
350
|
-
});
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
this._triggerChanges(allChanges);
|
|
354
|
-
// drop references of unused schemas
|
|
355
|
-
$root.garbageCollectDeletedRefs();
|
|
356
|
-
return allChanges;
|
|
357
|
-
}
|
|
358
|
-
encode(encodeAll = false, bytes = [], useFilters = false) {
|
|
359
|
-
const rootChangeTree = this.$changes;
|
|
360
|
-
const refIdsVisited = new WeakSet();
|
|
361
|
-
const changeTrees = [rootChangeTree];
|
|
362
|
-
let numChangeTrees = 1;
|
|
363
|
-
for (let i = 0; i < numChangeTrees; i++) {
|
|
364
|
-
const changeTree = changeTrees[i];
|
|
365
|
-
const ref = changeTree.ref;
|
|
366
|
-
const isSchema = (ref instanceof Schema);
|
|
367
|
-
// Generate unique refId for the ChangeTree.
|
|
368
|
-
changeTree.ensureRefId();
|
|
369
|
-
// mark this ChangeTree as visited.
|
|
370
|
-
refIdsVisited.add(changeTree);
|
|
371
|
-
// root `refId` is skipped.
|
|
372
|
-
if (changeTree !== rootChangeTree &&
|
|
373
|
-
(changeTree.changed || encodeAll)) {
|
|
374
|
-
encode.uint8(bytes, spec_1.SWITCH_TO_STRUCTURE);
|
|
375
|
-
encode.number(bytes, changeTree.refId);
|
|
376
|
-
}
|
|
377
|
-
const changes = (encodeAll)
|
|
378
|
-
? Array.from(changeTree.allChanges)
|
|
379
|
-
: Array.from(changeTree.changes.values());
|
|
380
|
-
for (let j = 0, cl = changes.length; j < cl; j++) {
|
|
381
|
-
const operation = (encodeAll)
|
|
382
|
-
? { op: spec_1.OPERATION.ADD, index: changes[j] }
|
|
383
|
-
: changes[j];
|
|
384
|
-
const fieldIndex = operation.index;
|
|
385
|
-
const field = (isSchema)
|
|
386
|
-
? ref['_definition'].fieldsByIndex && ref['_definition'].fieldsByIndex[fieldIndex]
|
|
387
|
-
: fieldIndex;
|
|
388
|
-
// cache begin index if `useFilters`
|
|
389
|
-
const beginIndex = bytes.length;
|
|
390
|
-
// encode field index + operation
|
|
391
|
-
if (operation.op !== spec_1.OPERATION.TOUCH) {
|
|
392
|
-
if (isSchema) {
|
|
393
|
-
//
|
|
394
|
-
// Compress `fieldIndex` + `operation` into a single byte.
|
|
395
|
-
// This adds a limitaion of 64 fields per Schema structure
|
|
396
|
-
//
|
|
397
|
-
encode.uint8(bytes, (fieldIndex | operation.op));
|
|
398
|
-
}
|
|
399
|
-
else {
|
|
400
|
-
encode.uint8(bytes, operation.op);
|
|
401
|
-
// custom operations
|
|
402
|
-
if (operation.op === spec_1.OPERATION.CLEAR) {
|
|
403
|
-
continue;
|
|
404
|
-
}
|
|
405
|
-
// indexed operations
|
|
406
|
-
encode.number(bytes, fieldIndex);
|
|
407
|
-
}
|
|
408
|
-
}
|
|
409
|
-
//
|
|
410
|
-
// encode "alias" for dynamic fields (maps)
|
|
411
|
-
//
|
|
412
|
-
if (!isSchema &&
|
|
413
|
-
(operation.op & spec_1.OPERATION.ADD) == spec_1.OPERATION.ADD // ADD or DELETE_AND_ADD
|
|
414
|
-
) {
|
|
415
|
-
if (ref instanceof MapSchema_1.MapSchema) {
|
|
416
|
-
//
|
|
417
|
-
// MapSchema dynamic key
|
|
418
|
-
//
|
|
419
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(fieldIndex);
|
|
420
|
-
encode.string(bytes, dynamicIndex);
|
|
421
|
-
}
|
|
422
|
-
}
|
|
423
|
-
if (operation.op === spec_1.OPERATION.DELETE) {
|
|
424
|
-
//
|
|
425
|
-
// TODO: delete from filter cache data.
|
|
426
|
-
//
|
|
427
|
-
// if (useFilters) {
|
|
428
|
-
// delete changeTree.caches[fieldIndex];
|
|
429
|
-
// }
|
|
430
|
-
continue;
|
|
431
|
-
}
|
|
432
|
-
// const type = changeTree.childType || ref._schema[field];
|
|
433
|
-
const type = changeTree.getType(fieldIndex);
|
|
434
|
-
// const type = changeTree.getType(fieldIndex);
|
|
435
|
-
const value = changeTree.getValue(fieldIndex);
|
|
436
|
-
// Enqueue ChangeTree to be visited
|
|
437
|
-
if (value &&
|
|
438
|
-
value['$changes'] &&
|
|
439
|
-
!refIdsVisited.has(value['$changes'])) {
|
|
440
|
-
changeTrees.push(value['$changes']);
|
|
441
|
-
value['$changes'].ensureRefId();
|
|
442
|
-
numChangeTrees++;
|
|
443
|
-
}
|
|
444
|
-
if (operation.op === spec_1.OPERATION.TOUCH) {
|
|
445
|
-
continue;
|
|
446
|
-
}
|
|
447
|
-
if (Schema.is(type)) {
|
|
448
|
-
assertInstanceType(value, type, ref, field);
|
|
449
|
-
//
|
|
450
|
-
// Encode refId for this instance.
|
|
451
|
-
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
452
|
-
//
|
|
453
|
-
encode.number(bytes, value.$changes.refId);
|
|
454
|
-
// Try to encode inherited TYPE_ID if it's an ADD operation.
|
|
455
|
-
if ((operation.op & spec_1.OPERATION.ADD) === spec_1.OPERATION.ADD) {
|
|
456
|
-
this.tryEncodeTypeId(bytes, type, value.constructor);
|
|
457
|
-
}
|
|
458
|
-
}
|
|
459
|
-
else if (typeof (type) === "string") {
|
|
460
|
-
//
|
|
461
|
-
// Primitive values
|
|
462
|
-
//
|
|
463
|
-
encodePrimitiveType(type, bytes, value, ref, field);
|
|
464
|
-
}
|
|
465
|
-
else {
|
|
466
|
-
//
|
|
467
|
-
// Custom type (MapSchema, ArraySchema, etc)
|
|
468
|
-
//
|
|
469
|
-
const definition = (0, typeRegistry_1.getType)(Object.keys(type)[0]);
|
|
470
|
-
//
|
|
471
|
-
// ensure a ArraySchema has been provided
|
|
472
|
-
//
|
|
473
|
-
assertInstanceType(ref[`_${field}`], definition.constructor, ref, field);
|
|
474
|
-
//
|
|
475
|
-
// Encode refId for this instance.
|
|
476
|
-
// The actual instance is going to be encoded on next `changeTree` iteration.
|
|
477
|
-
//
|
|
478
|
-
encode.number(bytes, value.$changes.refId);
|
|
479
|
-
}
|
|
480
|
-
if (useFilters) {
|
|
481
|
-
// cache begin / end index
|
|
482
|
-
changeTree.cache(fieldIndex, bytes.slice(beginIndex));
|
|
483
|
-
}
|
|
484
|
-
}
|
|
485
|
-
if (!encodeAll && !useFilters) {
|
|
486
|
-
changeTree.discard();
|
|
487
|
-
}
|
|
488
|
-
}
|
|
489
|
-
return bytes;
|
|
490
|
-
}
|
|
491
|
-
encodeAll(useFilters) {
|
|
492
|
-
return this.encode(true, [], useFilters);
|
|
493
|
-
}
|
|
494
|
-
applyFilters(client, encodeAll = false) {
|
|
495
|
-
const root = this;
|
|
496
|
-
const refIdsDissallowed = new Set();
|
|
497
|
-
const $filterState = filters_1.ClientState.get(client);
|
|
498
|
-
const changeTrees = [this.$changes];
|
|
499
|
-
let numChangeTrees = 1;
|
|
500
|
-
let filteredBytes = [];
|
|
501
|
-
for (let i = 0; i < numChangeTrees; i++) {
|
|
502
|
-
const changeTree = changeTrees[i];
|
|
503
|
-
if (refIdsDissallowed.has(changeTree.refId)) {
|
|
504
|
-
// console.log("REFID IS NOT ALLOWED. SKIP.", { refId: changeTree.refId })
|
|
505
|
-
continue;
|
|
506
|
-
}
|
|
507
|
-
const ref = changeTree.ref;
|
|
508
|
-
const isSchema = ref instanceof Schema;
|
|
509
|
-
encode.uint8(filteredBytes, spec_1.SWITCH_TO_STRUCTURE);
|
|
510
|
-
encode.number(filteredBytes, changeTree.refId);
|
|
511
|
-
const clientHasRefId = $filterState.refIds.has(changeTree);
|
|
512
|
-
const isEncodeAll = (encodeAll || !clientHasRefId);
|
|
513
|
-
// console.log("REF:", ref.constructor.name);
|
|
514
|
-
// console.log("Encode all?", isEncodeAll);
|
|
515
|
-
//
|
|
516
|
-
// include `changeTree` on list of known refIds by this client.
|
|
517
|
-
//
|
|
518
|
-
$filterState.addRefId(changeTree);
|
|
519
|
-
const containerIndexes = $filterState.containerIndexes.get(changeTree);
|
|
520
|
-
const changes = (isEncodeAll)
|
|
521
|
-
? Array.from(changeTree.allChanges)
|
|
522
|
-
: Array.from(changeTree.changes.values());
|
|
523
|
-
//
|
|
524
|
-
// WORKAROUND: tries to re-evaluate previously not included @filter() attributes
|
|
525
|
-
// - see "DELETE a field of Schema" test case.
|
|
526
|
-
//
|
|
527
|
-
if (!encodeAll &&
|
|
528
|
-
isSchema &&
|
|
529
|
-
ref._definition.indexesWithFilters) {
|
|
530
|
-
const indexesWithFilters = ref._definition.indexesWithFilters;
|
|
531
|
-
indexesWithFilters.forEach(indexWithFilter => {
|
|
532
|
-
if (!containerIndexes.has(indexWithFilter) &&
|
|
533
|
-
changeTree.allChanges.has(indexWithFilter)) {
|
|
534
|
-
if (isEncodeAll) {
|
|
535
|
-
changes.push(indexWithFilter);
|
|
536
|
-
}
|
|
537
|
-
else {
|
|
538
|
-
changes.push({ op: spec_1.OPERATION.ADD, index: indexWithFilter, });
|
|
539
|
-
}
|
|
540
|
-
}
|
|
541
|
-
});
|
|
542
|
-
}
|
|
543
|
-
for (let j = 0, cl = changes.length; j < cl; j++) {
|
|
544
|
-
const change = (isEncodeAll)
|
|
545
|
-
? { op: spec_1.OPERATION.ADD, index: changes[j] }
|
|
546
|
-
: changes[j];
|
|
547
|
-
// custom operations
|
|
548
|
-
if (change.op === spec_1.OPERATION.CLEAR) {
|
|
549
|
-
encode.uint8(filteredBytes, change.op);
|
|
550
|
-
continue;
|
|
551
|
-
}
|
|
552
|
-
const fieldIndex = change.index;
|
|
553
|
-
//
|
|
554
|
-
// Deleting fields: encode the operation + field index
|
|
555
|
-
//
|
|
556
|
-
if (change.op === spec_1.OPERATION.DELETE) {
|
|
557
|
-
//
|
|
558
|
-
// DELETE operations also need to go through filtering.
|
|
559
|
-
//
|
|
560
|
-
// TODO: cache the previous value so we can access the value (primitive or `refId`)
|
|
561
|
-
// (check against `$filterState.refIds`)
|
|
562
|
-
//
|
|
563
|
-
if (isSchema) {
|
|
564
|
-
encode.uint8(filteredBytes, change.op | fieldIndex);
|
|
565
|
-
}
|
|
566
|
-
else {
|
|
567
|
-
encode.uint8(filteredBytes, change.op);
|
|
568
|
-
encode.number(filteredBytes, fieldIndex);
|
|
569
|
-
}
|
|
570
|
-
continue;
|
|
571
|
-
}
|
|
572
|
-
// indexed operation
|
|
573
|
-
const value = changeTree.getValue(fieldIndex);
|
|
574
|
-
const type = changeTree.getType(fieldIndex);
|
|
575
|
-
if (isSchema) {
|
|
576
|
-
// Is a Schema!
|
|
577
|
-
const filter = (ref._definition.filters &&
|
|
578
|
-
ref._definition.filters[fieldIndex]);
|
|
579
|
-
if (filter && !filter.call(ref, client, value, root)) {
|
|
580
|
-
if (value && value['$changes']) {
|
|
581
|
-
refIdsDissallowed.add(value['$changes'].refId);
|
|
582
|
-
;
|
|
583
|
-
}
|
|
584
|
-
continue;
|
|
585
|
-
}
|
|
586
|
-
}
|
|
587
|
-
else {
|
|
588
|
-
// Is a collection! (map, array, etc.)
|
|
589
|
-
const parent = changeTree.parent;
|
|
590
|
-
const filter = changeTree.getChildrenFilter();
|
|
591
|
-
if (filter && !filter.call(parent, client, ref['$indexes'].get(fieldIndex), value, root)) {
|
|
592
|
-
if (value && value['$changes']) {
|
|
593
|
-
refIdsDissallowed.add(value['$changes'].refId);
|
|
594
|
-
}
|
|
595
|
-
continue;
|
|
596
|
-
}
|
|
597
|
-
}
|
|
598
|
-
// visit child ChangeTree on further iteration.
|
|
599
|
-
if (value['$changes']) {
|
|
600
|
-
changeTrees.push(value['$changes']);
|
|
601
|
-
numChangeTrees++;
|
|
602
|
-
}
|
|
603
|
-
//
|
|
604
|
-
// Copy cached bytes
|
|
605
|
-
//
|
|
606
|
-
if (change.op !== spec_1.OPERATION.TOUCH) {
|
|
607
|
-
//
|
|
608
|
-
// TODO: refactor me!
|
|
609
|
-
//
|
|
610
|
-
if (change.op === spec_1.OPERATION.ADD || isSchema) {
|
|
611
|
-
//
|
|
612
|
-
// use cached bytes directly if is from Schema type.
|
|
613
|
-
//
|
|
614
|
-
filteredBytes.push.apply(filteredBytes, changeTree.caches[fieldIndex] ?? []);
|
|
615
|
-
containerIndexes.add(fieldIndex);
|
|
616
|
-
}
|
|
617
|
-
else {
|
|
618
|
-
if (containerIndexes.has(fieldIndex)) {
|
|
619
|
-
//
|
|
620
|
-
// use cached bytes if already has the field
|
|
621
|
-
//
|
|
622
|
-
filteredBytes.push.apply(filteredBytes, changeTree.caches[fieldIndex] ?? []);
|
|
623
|
-
}
|
|
624
|
-
else {
|
|
625
|
-
//
|
|
626
|
-
// force ADD operation if field is not known by this client.
|
|
627
|
-
//
|
|
628
|
-
containerIndexes.add(fieldIndex);
|
|
629
|
-
encode.uint8(filteredBytes, spec_1.OPERATION.ADD);
|
|
630
|
-
encode.number(filteredBytes, fieldIndex);
|
|
631
|
-
if (ref instanceof MapSchema_1.MapSchema) {
|
|
632
|
-
//
|
|
633
|
-
// MapSchema dynamic key
|
|
634
|
-
//
|
|
635
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(fieldIndex);
|
|
636
|
-
encode.string(filteredBytes, dynamicIndex);
|
|
637
|
-
}
|
|
638
|
-
if (value['$changes']) {
|
|
639
|
-
encode.number(filteredBytes, value['$changes'].refId);
|
|
640
|
-
}
|
|
641
|
-
else {
|
|
642
|
-
// "encodePrimitiveType" without type checking.
|
|
643
|
-
// the type checking has been done on the first .encode() call.
|
|
644
|
-
encode[type](filteredBytes, value);
|
|
645
|
-
}
|
|
646
|
-
}
|
|
647
|
-
}
|
|
648
|
-
}
|
|
649
|
-
else if (value['$changes'] && !isSchema) {
|
|
650
|
-
//
|
|
651
|
-
// TODO:
|
|
652
|
-
// - track ADD/REPLACE/DELETE instances on `$filterState`
|
|
653
|
-
// - do NOT always encode dynamicIndex for MapSchema.
|
|
654
|
-
// (If client already has that key, only the first index is necessary.)
|
|
655
|
-
//
|
|
656
|
-
encode.uint8(filteredBytes, spec_1.OPERATION.ADD);
|
|
657
|
-
encode.number(filteredBytes, fieldIndex);
|
|
658
|
-
if (ref instanceof MapSchema_1.MapSchema) {
|
|
659
|
-
//
|
|
660
|
-
// MapSchema dynamic key
|
|
661
|
-
//
|
|
662
|
-
const dynamicIndex = changeTree.ref['$indexes'].get(fieldIndex);
|
|
663
|
-
encode.string(filteredBytes, dynamicIndex);
|
|
664
|
-
}
|
|
665
|
-
encode.number(filteredBytes, value['$changes'].refId);
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
;
|
|
669
|
-
}
|
|
670
|
-
return filteredBytes;
|
|
120
|
+
this[symbols_1.$changes].change(this.constructor[Symbol.metadata][property].index, operation);
|
|
671
121
|
}
|
|
672
122
|
clone() {
|
|
673
123
|
const cloned = new (this.constructor);
|
|
674
|
-
const
|
|
675
|
-
|
|
124
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
125
|
+
//
|
|
126
|
+
// TODO: clone all properties, not only annotated ones
|
|
127
|
+
//
|
|
128
|
+
// for (const field in this) {
|
|
129
|
+
for (const field in metadata) {
|
|
676
130
|
if (typeof (this[field]) === "object" &&
|
|
677
131
|
typeof (this[field]?.clone) === "function") {
|
|
678
132
|
// deep clone
|
|
@@ -686,116 +140,120 @@ class Schema {
|
|
|
686
140
|
return cloned;
|
|
687
141
|
}
|
|
688
142
|
toJSON() {
|
|
689
|
-
const
|
|
690
|
-
const deprecated = this._definition.deprecated;
|
|
143
|
+
const metadata = this.constructor[Symbol.metadata];
|
|
691
144
|
const obj = {};
|
|
692
|
-
for (
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
145
|
+
for (const fieldName in metadata) {
|
|
146
|
+
const field = metadata[fieldName];
|
|
147
|
+
if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
|
|
148
|
+
obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
|
|
149
|
+
? this[fieldName]['toJSON']()
|
|
150
|
+
: this[fieldName];
|
|
697
151
|
}
|
|
698
152
|
}
|
|
699
153
|
return obj;
|
|
700
154
|
}
|
|
701
155
|
discardAllChanges() {
|
|
702
|
-
this.$changes.discardAll();
|
|
156
|
+
this[symbols_1.$changes].discardAll();
|
|
703
157
|
}
|
|
704
|
-
getByIndex(index) {
|
|
705
|
-
return this[this.
|
|
158
|
+
[symbols_1.$getByIndex](index) {
|
|
159
|
+
return this[this.constructor[Symbol.metadata][index]];
|
|
706
160
|
}
|
|
707
|
-
deleteByIndex(index) {
|
|
708
|
-
this[this.
|
|
161
|
+
[symbols_1.$deleteByIndex](index) {
|
|
162
|
+
this[this.constructor[Symbol.metadata][index]] = undefined;
|
|
709
163
|
}
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
164
|
+
static debugRefIds(instance, jsonContents = true, level = 0) {
|
|
165
|
+
const ref = instance;
|
|
166
|
+
const changeTree = ref[symbols_1.$changes];
|
|
167
|
+
const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
|
|
168
|
+
let output = "";
|
|
169
|
+
output += `${(0, utils_1.getIndent)(level)}${ref.constructor.name} (${ref[symbols_1.$changes].refId})${contents}\n`;
|
|
170
|
+
changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
|
|
171
|
+
return output;
|
|
715
172
|
}
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
173
|
+
/**
|
|
174
|
+
* Return a string representation of the changes on a Schema instance.
|
|
175
|
+
* The list of changes is cleared after each encode.
|
|
176
|
+
*
|
|
177
|
+
* @param instance Schema instance
|
|
178
|
+
* @param isEncodeAll Return "full encode" instead of current change set.
|
|
179
|
+
* @returns
|
|
180
|
+
*/
|
|
181
|
+
static debugChanges(instance, isEncodeAll = false) {
|
|
182
|
+
const changeTree = instance[symbols_1.$changes];
|
|
183
|
+
const changeSet = (isEncodeAll) ? changeTree.allChanges : changeTree.changes;
|
|
184
|
+
const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
|
|
185
|
+
let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
|
|
186
|
+
function dumpChangeSet(changeSet) {
|
|
187
|
+
Array.from(changeSet)
|
|
188
|
+
.sort((a, b) => a[0] - b[0])
|
|
189
|
+
.forEach(([index, operation]) => output += `- [${index}]: ${spec_1.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(index, isEncodeAll))})\n`);
|
|
721
190
|
}
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
191
|
+
dumpChangeSet(changeSet);
|
|
192
|
+
// display filtered changes
|
|
193
|
+
if (!isEncodeAll && changeTree.filteredChanges?.size > 0) {
|
|
194
|
+
output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
|
|
195
|
+
dumpChangeSet(changeTree.filteredChanges);
|
|
196
|
+
}
|
|
197
|
+
// display filtered changes
|
|
198
|
+
if (isEncodeAll && changeTree.allFilteredChanges?.size > 0) {
|
|
199
|
+
output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
|
|
200
|
+
dumpChangeSet(changeTree.allFilteredChanges);
|
|
201
|
+
}
|
|
202
|
+
return output;
|
|
729
203
|
}
|
|
730
|
-
|
|
731
|
-
|
|
732
|
-
const
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
change.previousValue instanceof Schema) {
|
|
743
|
-
change.previousValue['$callbacks']?.[spec_1.OPERATION.DELETE]?.forEach(callback => callback());
|
|
744
|
-
}
|
|
745
|
-
// no callbacks defined, skip this structure!
|
|
746
|
-
if (!$callbacks) {
|
|
747
|
-
continue;
|
|
748
|
-
}
|
|
749
|
-
if (ref instanceof Schema) {
|
|
750
|
-
if (!uniqueRefIds.has(refId)) {
|
|
751
|
-
try {
|
|
752
|
-
// trigger onChange
|
|
753
|
-
$callbacks?.[spec_1.OPERATION.REPLACE]?.forEach(callback => callback());
|
|
754
|
-
}
|
|
755
|
-
catch (e) {
|
|
756
|
-
Schema.onError(e);
|
|
757
|
-
}
|
|
758
|
-
}
|
|
759
|
-
try {
|
|
760
|
-
if ($callbacks.hasOwnProperty(change.field)) {
|
|
761
|
-
$callbacks[change.field]?.forEach((callback) => callback(change.value, change.previousValue));
|
|
762
|
-
}
|
|
763
|
-
}
|
|
764
|
-
catch (e) {
|
|
765
|
-
Schema.onError(e);
|
|
766
|
-
}
|
|
204
|
+
static debugChangesDeep(ref) {
|
|
205
|
+
let output = "";
|
|
206
|
+
const rootChangeTree = ref[symbols_1.$changes];
|
|
207
|
+
const changeTrees = new Map();
|
|
208
|
+
let totalInstances = 0;
|
|
209
|
+
let totalOperations = 0;
|
|
210
|
+
for (const [changeTree, changes] of (rootChangeTree.root.changes.entries())) {
|
|
211
|
+
let includeChangeTree = false;
|
|
212
|
+
let parentChangeTrees = [];
|
|
213
|
+
let parentChangeTree = changeTree.parent?.[symbols_1.$changes];
|
|
214
|
+
if (changeTree === rootChangeTree) {
|
|
215
|
+
includeChangeTree = true;
|
|
767
216
|
}
|
|
768
217
|
else {
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
else if (change.op === spec_1.OPERATION.DELETE) {
|
|
775
|
-
//
|
|
776
|
-
// FIXME: `previousValue` should always be available.
|
|
777
|
-
// ADD + DELETE operations are still encoding DELETE operation.
|
|
778
|
-
//
|
|
779
|
-
if (change.previousValue !== undefined) {
|
|
780
|
-
// triger onRemove
|
|
781
|
-
$callbacks[spec_1.OPERATION.DELETE]?.forEach(callback => callback(change.previousValue, change.dynamicIndex ?? change.field));
|
|
782
|
-
}
|
|
783
|
-
}
|
|
784
|
-
else if (change.op === spec_1.OPERATION.DELETE_AND_ADD) {
|
|
785
|
-
// triger onRemove
|
|
786
|
-
if (change.previousValue !== undefined) {
|
|
787
|
-
$callbacks[spec_1.OPERATION.DELETE]?.forEach(callback => callback(change.previousValue, change.dynamicIndex ?? change.field));
|
|
218
|
+
while (parentChangeTree !== undefined) {
|
|
219
|
+
parentChangeTrees.push(parentChangeTree);
|
|
220
|
+
if (parentChangeTree.ref === ref) {
|
|
221
|
+
includeChangeTree = true;
|
|
222
|
+
break;
|
|
788
223
|
}
|
|
789
|
-
|
|
790
|
-
$callbacks[spec_1.OPERATION.ADD]?.forEach(callback => callback(change.value, change.dynamicIndex ?? change.field));
|
|
791
|
-
}
|
|
792
|
-
// trigger onChange
|
|
793
|
-
if (change.value !== change.previousValue) {
|
|
794
|
-
$callbacks[spec_1.OPERATION.REPLACE]?.forEach(callback => callback(change.value, change.dynamicIndex ?? change.field));
|
|
224
|
+
parentChangeTree = parentChangeTree.parent?.[symbols_1.$changes];
|
|
795
225
|
}
|
|
796
226
|
}
|
|
797
|
-
|
|
227
|
+
if (includeChangeTree) {
|
|
228
|
+
totalInstances += 1;
|
|
229
|
+
totalOperations += changes.size;
|
|
230
|
+
changeTrees.set(changeTree, parentChangeTrees.reverse());
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
output += "---\n";
|
|
234
|
+
output += `root refId: ${rootChangeTree.refId}\n`;
|
|
235
|
+
output += `Total instances: ${totalInstances}\n`;
|
|
236
|
+
output += `Total changes: ${totalOperations}\n`;
|
|
237
|
+
output += "---\n";
|
|
238
|
+
// based on root.changes, display a tree of changes that has the "ref" instance as parent
|
|
239
|
+
const visitedParents = new WeakSet();
|
|
240
|
+
for (const [changeTree, parentChangeTrees] of changeTrees.entries()) {
|
|
241
|
+
parentChangeTrees.forEach((parentChangeTree, level) => {
|
|
242
|
+
if (!visitedParents.has(parentChangeTree)) {
|
|
243
|
+
output += `${(0, utils_1.getIndent)(level)}${parentChangeTree.ref.constructor.name} (refId: ${parentChangeTree.refId})\n`;
|
|
244
|
+
visitedParents.add(parentChangeTree);
|
|
245
|
+
}
|
|
246
|
+
});
|
|
247
|
+
const changes = changeTree.changes;
|
|
248
|
+
const level = parentChangeTrees.length;
|
|
249
|
+
const indent = (0, utils_1.getIndent)(level);
|
|
250
|
+
const parentIndex = (level > 0) ? `(${changeTree.parentIndex}) ` : "";
|
|
251
|
+
output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${changes.size}\n`;
|
|
252
|
+
for (const [index, operation] of changes) {
|
|
253
|
+
output += `${(0, utils_1.getIndent)(level + 1)}${spec_1.OPERATION[operation]}: ${index}\n`;
|
|
254
|
+
}
|
|
798
255
|
}
|
|
256
|
+
return `${output}`;
|
|
799
257
|
}
|
|
800
258
|
}
|
|
801
259
|
exports.Schema = Schema;
|