@colyseus/schema 3.0.0-alpha.4 → 3.0.0-alpha.41

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