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