@colyseus/schema 3.0.0-alpha.5 → 3.0.0-alpha.50

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 (148) hide show
  1. package/README.md +148 -62
  2. package/bin/schema-debug +94 -0
  3. package/build/cjs/index.js +2234 -1510
  4. package/build/cjs/index.js.map +1 -1
  5. package/build/esm/index.mjs +2231 -1509
  6. package/build/esm/index.mjs.map +1 -1
  7. package/build/umd/index.js +2233 -1509
  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 -31
  13. package/lib/Reflection.js.map +1 -1
  14. package/lib/Schema.d.ts +12 -5
  15. package/lib/Schema.js +57 -56
  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 +4 -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 +23 -25
  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 +4 -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 +28 -20
  60. package/lib/encoder/ChangeTree.js +242 -188
  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 +134 -85
  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 +72 -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 +36 -19
  78. package/lib/encoding/decode.js +54 -84
  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 +29 -0
  92. package/lib/types/TypeContext.js +151 -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 +17 -15
  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 +19 -18
  115. package/src/Metadata.ts +190 -42
  116. package/src/Reflection.ts +76 -38
  117. package/src/Schema.ts +72 -70
  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/languages/haxe.ts +4 -0
  122. package/src/codegen/languages/lua.ts +19 -27
  123. package/src/codegen/parser.ts +107 -0
  124. package/src/codegen/types.ts +1 -0
  125. package/src/debug.ts +55 -0
  126. package/src/decoder/DecodeOperation.ts +46 -18
  127. package/src/decoder/Decoder.ts +17 -15
  128. package/src/decoder/ReferenceTracker.ts +5 -3
  129. package/src/decoder/strategy/StateCallbacks.ts +153 -82
  130. package/src/encoder/ChangeTree.ts +282 -209
  131. package/src/encoder/EncodeOperation.ts +78 -78
  132. package/src/encoder/Encoder.ts +161 -96
  133. package/src/encoder/Root.ts +93 -0
  134. package/src/encoder/StateView.ts +80 -88
  135. package/src/encoding/assert.ts +17 -8
  136. package/src/encoding/decode.ts +73 -93
  137. package/src/encoding/encode.ts +99 -65
  138. package/src/encoding/spec.ts +3 -5
  139. package/src/index.ts +12 -20
  140. package/src/types/HelperTypes.ts +54 -2
  141. package/src/types/TypeContext.ts +175 -0
  142. package/src/types/custom/ArraySchema.ts +49 -19
  143. package/src/types/custom/CollectionSchema.ts +1 -0
  144. package/src/types/custom/MapSchema.ts +30 -17
  145. package/src/types/custom/SetSchema.ts +1 -0
  146. package/src/types/registry.ts +22 -3
  147. package/src/types/symbols.ts +10 -7
  148. 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;
246
+ }
247
+ else {
248
+ throw new Error('String too long');
197
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;
252
- }
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;
260
- }
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);
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
+ }
272
269
  }
273
- this.ensureRefId();
274
- this.forEachChild((changeTree, atIndex) => {
275
- changeTree.setParent(this.ref, root, atIndex);
276
- });
270
+ bytes[it.offset++] = 0xcb;
271
+ float64$1(bytes, value, it);
272
+ return 9;
277
273
  }
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;
274
+ if (value >= 0) {
275
+ // positive fixnum
276
+ if (value < 0x80) {
277
+ bytes[it.offset++] = value & 255; // uint8
278
+ return 1;
422
279
  }
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,1154 @@ 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;
395
+ if ((byte & 0xf0) === 0xe0) {
396
+ string += String.fromCharCode(((byte & 0x0f) << 12) |
397
+ ((bytes[++i] & 0x3f) << 6) |
398
+ ((bytes[++i] & 0x3f) << 0));
399
+ continue;
582
400
  }
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);
601
- }
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
+ function stringCheck(bytes, it) {
542
+ const prefix = bytes[it.offset];
543
+ return (
544
+ // fixstr
545
+ (prefix < 0xc0 && prefix > 0xa0) ||
546
+ // str 8
547
+ prefix === 0xd9 ||
548
+ // str 16
549
+ prefix === 0xda ||
550
+ // str 32
551
+ prefix === 0xdb);
552
+ }
553
+ const decode = {
554
+ utf8Read,
555
+ int8,
556
+ uint8,
557
+ int16,
558
+ uint16,
559
+ int32,
560
+ uint32,
561
+ float32,
562
+ float64,
563
+ int64,
564
+ uint64,
565
+ bigint64,
566
+ biguint64,
567
+ boolean,
568
+ string,
569
+ number,
570
+ stringCheck,
571
+ };
789
572
 
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;
573
+ const registeredTypes = {};
574
+ const identifiers = new Map();
575
+ function registerType(identifier, definition) {
576
+ if (definition.constructor) {
577
+ identifiers.set(definition.constructor, identifier);
578
+ registeredTypes[identifier] = definition;
840
579
  }
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}`);
580
+ if (definition.encode) {
581
+ encode[identifier] = definition.encode;
844
582
  }
845
- }
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}`);
583
+ if (definition.decode) {
584
+ decode[identifier] = definition.decode;
849
585
  }
850
586
  }
851
-
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}`);
861
- }
587
+ function getType(identifier) {
588
+ return registeredTypes[identifier];
862
589
  }
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);
875
- }
876
- }
877
- else if (typeof (type) === "string") {
878
- //
879
- // Primitive values
880
- //
881
- encodePrimitiveType(type, bytes, value, ref, field, it);
882
- }
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);
892
- //
893
- // Encode refId for this instance.
894
- // The actual instance is going to be encoded on next `changeTree` iteration.
895
- //
896
- number$1(bytes, value[$changes].refId, it);
590
+ function defineCustomTypes(types) {
591
+ for (const identifier in types) {
592
+ registerType(identifier, types[identifier]);
897
593
  }
594
+ return (t) => type(t);
898
595
  }
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;
596
+
597
+ class TypeContext {
598
+ /**
599
+ * For inheritance support
600
+ * Keeps track of which classes extends which. (parent -> children)
601
+ */
602
+ static { this.inheritedTypes = new Map(); }
603
+ static register(target) {
604
+ const parent = Object.getPrototypeOf(target);
605
+ if (parent !== Schema) {
606
+ let inherits = TypeContext.inheritedTypes.get(parent);
607
+ if (!inherits) {
608
+ inherits = new Set();
609
+ TypeContext.inheritedTypes.set(parent, inherits);
610
+ }
611
+ inherits.add(target);
612
+ }
935
613
  }
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") {
614
+ constructor(rootClass) {
615
+ this.types = {};
616
+ this.schemas = new Map();
617
+ this.hasFilters = false;
618
+ this.parentFiltered = {};
619
+ if (rootClass) {
941
620
  //
942
- // MapSchema dynamic key
621
+ // TODO:
622
+ // cache "discoverTypes" results for each rootClass
623
+ // to avoid re-discovering types for each new context/room
943
624
  //
944
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
945
- string$1(bytes, dynamicIndex, it);
625
+ this.discoverTypes(rootClass);
946
626
  }
947
627
  }
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;
628
+ has(schema) {
629
+ return this.schemas.has(schema);
968
630
  }
969
- // encode operation
970
- bytes[it.offset++] = operation & 255;
971
- // custom operations
972
- if (operation === exports.OPERATION.CLEAR) {
973
- return;
631
+ get(typeid) {
632
+ return this.types[typeid];
974
633
  }
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;
634
+ add(schema, typeid = this.schemas.size) {
635
+ // skip if already registered
636
+ if (this.schemas.has(schema)) {
637
+ return false;
638
+ }
639
+ this.types[typeid] = schema;
640
+ //
641
+ // Workaround to allow using an empty Schema (with no `@type()` fields)
642
+ //
643
+ if (schema[Symbol.metadata] === undefined) {
644
+ Metadata.initialize(schema);
645
+ }
646
+ this.schemas.set(schema, typeid);
647
+ return true;
980
648
  }
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;
649
+ getTypeId(klass) {
650
+ return this.schemas.get(klass);
651
+ }
652
+ discoverTypes(klass, parentType, parentIndex, parentHasViewTag) {
653
+ if (parentHasViewTag) {
654
+ this.registerFilteredByParent(klass, parentType, parentIndex);
1023
655
  }
1024
- if ((byte & 0xe0) === 0xc0) {
1025
- string += String.fromCharCode(((byte & 0x1f) << 6) |
1026
- (bytes[++i] & 0x3f));
1027
- continue;
656
+ // skip if already registered
657
+ if (!this.add(klass)) {
658
+ return;
1028
659
  }
1029
- if ((byte & 0xf0) === 0xe0) {
1030
- string += String.fromCharCode(((byte & 0x0f) << 12) |
1031
- ((bytes[++i] & 0x3f) << 6) |
1032
- ((bytes[++i] & 0x3f) << 0));
1033
- continue;
660
+ // add classes inherited from this base class
661
+ TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
662
+ this.discoverTypes(child, parentType, parentIndex, parentHasViewTag);
663
+ });
664
+ // add parent classes
665
+ let parent = klass;
666
+ while ((parent = Object.getPrototypeOf(parent)) &&
667
+ parent !== Schema && // stop at root (Schema)
668
+ parent !== Function.prototype // stop at root (non-Schema)
669
+ ) {
670
+ this.discoverTypes(parent);
671
+ }
672
+ const metadata = (klass[Symbol.metadata] ??= {});
673
+ // if any schema/field has filters, mark "context" as having filters.
674
+ if (metadata[$viewFieldIndexes]) {
675
+ this.hasFilters = true;
1034
676
  }
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);
677
+ for (const fieldIndex in metadata) {
678
+ const index = fieldIndex;
679
+ const fieldType = metadata[index].type;
680
+ const fieldHasViewTag = (metadata[index].tag !== undefined);
681
+ if (typeof (fieldType) === "string") {
682
+ continue;
683
+ }
684
+ if (Array.isArray(fieldType)) {
685
+ const type = fieldType[0];
686
+ // skip primitive types
687
+ if (type === "string") {
688
+ continue;
689
+ }
690
+ this.discoverTypes(type, klass, index, parentHasViewTag || fieldHasViewTag);
691
+ }
692
+ else if (typeof (fieldType) === "function") {
693
+ this.discoverTypes(fieldType, klass, index, parentHasViewTag || fieldHasViewTag);
1043
694
  }
1044
695
  else {
1045
- string += String.fromCharCode(chr);
696
+ const type = Object.values(fieldType)[0];
697
+ // skip primitive types
698
+ if (typeof (type) === "string") {
699
+ continue;
700
+ }
701
+ this.discoverTypes(type, klass, index, parentHasViewTag || fieldHasViewTag);
1046
702
  }
1047
- continue;
1048
703
  }
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
704
  }
1053
- it.offset += length;
1054
- return string;
1055
- }
1056
- function int8(bytes, it) {
1057
- return uint8(bytes, it) << 24 >> 24;
1058
- }
1059
- function uint8(bytes, it) {
1060
- return bytes[it.offset++];
1061
- }
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;
705
+ /**
706
+ * Keep track of which classes have filters applied.
707
+ * Format: `${typeid}-${parentTypeid}-${parentIndex}`
708
+ */
709
+ registerFilteredByParent(schema, parentType, parentIndex) {
710
+ const typeid = this.schemas.get(schema) ?? this.schemas.size;
711
+ let key = `${typeid}`;
712
+ if (parentType) {
713
+ key += `-${this.schemas.get(parentType)}`;
714
+ }
715
+ key += `-${parentIndex}`;
716
+ this.parentFiltered[key] = true;
717
+ }
718
+ debug() {
719
+ let parentFiltered = "";
720
+ for (const key in this.parentFiltered) {
721
+ const keys = key.split("-").map(Number);
722
+ const fieldIndex = keys.pop();
723
+ parentFiltered += `\n\t\t`;
724
+ parentFiltered += `${key}: ${keys.reverse().map((id, i) => {
725
+ const klass = this.types[id];
726
+ const metadata = klass[Symbol.metadata];
727
+ let txt = klass.name;
728
+ if (i === 0) {
729
+ txt += `[${metadata[fieldIndex].name}]`;
730
+ }
731
+ return `${txt}`;
732
+ }).join(" -> ")}`;
733
+ }
734
+ return `TypeContext ->\n` +
735
+ `\tSchema types: ${this.schemas.size}\n` +
736
+ `\thasFilters: ${this.hasFilters}\n` +
737
+ `\tparentFiltered:${parentFiltered}`;
738
+ }
1084
739
  }
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;
740
+
741
+ function getNormalizedType(type) {
742
+ return (Array.isArray(type))
743
+ ? { array: type[0] }
744
+ : (typeof (type['type']) !== "undefined")
745
+ ? type['type']
746
+ : type;
1089
747
  }
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];
748
+ const Metadata = {
749
+ addField(metadata, index, name, type, descriptor) {
750
+ if (index > 64) {
751
+ throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
752
+ }
753
+ metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
754
+ {
755
+ type: getNormalizedType(type),
756
+ index,
757
+ name,
758
+ });
759
+ // create "descriptors" map
760
+ Object.defineProperty(metadata, $descriptors, {
761
+ value: metadata[$descriptors] || {},
762
+ enumerable: false,
763
+ configurable: true,
764
+ });
765
+ if (descriptor) {
766
+ // for encoder
767
+ metadata[$descriptors][name] = descriptor;
768
+ metadata[$descriptors][`_${name}`] = {
769
+ value: undefined,
770
+ writable: true,
771
+ enumerable: false,
772
+ configurable: true,
773
+ };
774
+ }
775
+ else {
776
+ // for decoder
777
+ metadata[$descriptors][name] = {
778
+ value: undefined,
779
+ writable: true,
780
+ enumerable: true,
781
+ configurable: true,
782
+ };
783
+ }
784
+ // map -1 as last field index
785
+ Object.defineProperty(metadata, $numFields, {
786
+ value: index,
787
+ enumerable: false,
788
+ configurable: true
789
+ });
790
+ // map field name => index (non enumerable)
791
+ Object.defineProperty(metadata, name, {
792
+ value: index,
793
+ enumerable: false,
794
+ configurable: true,
795
+ });
796
+ // if child Ref/complex type, add to -4
797
+ if (typeof (metadata[index].type) !== "string") {
798
+ if (metadata[$refTypeFieldIndexes] === undefined) {
799
+ Object.defineProperty(metadata, $refTypeFieldIndexes, {
800
+ value: [],
801
+ enumerable: false,
802
+ configurable: true,
803
+ });
804
+ }
805
+ metadata[$refTypeFieldIndexes].push(index);
806
+ }
807
+ },
808
+ setTag(metadata, fieldName, tag) {
809
+ const index = metadata[fieldName];
810
+ const field = metadata[index];
811
+ // add 'tag' to the field
812
+ field.tag = tag;
813
+ if (!metadata[$viewFieldIndexes]) {
814
+ // -2: all field indexes with "view" tag
815
+ Object.defineProperty(metadata, $viewFieldIndexes, {
816
+ value: [],
817
+ enumerable: false,
818
+ configurable: true
819
+ });
820
+ // -3: field indexes by "view" tag
821
+ Object.defineProperty(metadata, $fieldIndexesByViewTag, {
822
+ value: {},
823
+ enumerable: false,
824
+ configurable: true
825
+ });
826
+ }
827
+ metadata[$viewFieldIndexes].push(index);
828
+ if (!metadata[$fieldIndexesByViewTag][tag]) {
829
+ metadata[$fieldIndexesByViewTag][tag] = [];
830
+ }
831
+ metadata[$fieldIndexesByViewTag][tag].push(index);
832
+ },
833
+ setFields(target, fields) {
834
+ // for inheritance support
835
+ const constructor = target.prototype.constructor;
836
+ TypeContext.register(constructor);
837
+ const parentClass = Object.getPrototypeOf(constructor);
838
+ const parentMetadata = parentClass && parentClass[Symbol.metadata];
839
+ const metadata = Metadata.initialize(constructor);
840
+ // Use Schema's methods if not defined in the class
841
+ if (!constructor[$track]) {
842
+ constructor[$track] = Schema[$track];
843
+ }
844
+ if (!constructor[$encoder]) {
845
+ constructor[$encoder] = Schema[$encoder];
846
+ }
847
+ if (!constructor[$decoder]) {
848
+ constructor[$decoder] = Schema[$decoder];
849
+ }
850
+ if (!constructor.prototype.toJSON) {
851
+ constructor.prototype.toJSON = Schema.prototype.toJSON;
852
+ }
853
+ //
854
+ // detect index for this field, considering inheritance
855
+ //
856
+ let fieldIndex = metadata[$numFields] // current structure already has fields defined
857
+ ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
858
+ ?? -1; // no fields defined
859
+ fieldIndex++;
860
+ for (const field in fields) {
861
+ const type = fields[field];
862
+ const normalizedType = getNormalizedType(type);
863
+ // FIXME: this code is duplicated from @type() annotation
864
+ const complexTypeKlass = (Array.isArray(type))
865
+ ? getType("array")
866
+ : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
867
+ const childType = (complexTypeKlass)
868
+ ? Object.values(type)[0]
869
+ : normalizedType;
870
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
871
+ fieldIndex++;
872
+ }
873
+ return target;
874
+ },
875
+ isDeprecated(metadata, field) {
876
+ return metadata[field].deprecated === true;
877
+ },
878
+ init(klass) {
879
+ //
880
+ // Used only to initialize an empty Schema (Encoder#constructor)
881
+ // TODO: remove/refactor this...
882
+ //
883
+ const metadata = {};
884
+ klass[Symbol.metadata] = metadata;
885
+ Object.defineProperty(metadata, $numFields, {
886
+ value: 0,
887
+ enumerable: false,
888
+ configurable: true,
889
+ });
890
+ },
891
+ initialize(constructor) {
892
+ const parentClass = Object.getPrototypeOf(constructor);
893
+ const parentMetadata = parentClass[Symbol.metadata];
894
+ let metadata = constructor[Symbol.metadata] ?? Object.create(null);
895
+ // make sure inherited classes have their own metadata object.
896
+ if (parentClass !== Schema && metadata === parentMetadata) {
897
+ metadata = Object.create(null);
898
+ if (parentMetadata) {
899
+ //
900
+ // assign parent metadata to current
901
+ //
902
+ Object.setPrototypeOf(metadata, parentMetadata);
903
+ // $numFields
904
+ Object.defineProperty(metadata, $numFields, {
905
+ value: parentMetadata[$numFields],
906
+ enumerable: false,
907
+ configurable: true,
908
+ writable: true,
909
+ });
910
+ // $viewFieldIndexes / $fieldIndexesByViewTag
911
+ if (parentMetadata[$viewFieldIndexes] !== undefined) {
912
+ Object.defineProperty(metadata, $viewFieldIndexes, {
913
+ value: [...parentMetadata[$viewFieldIndexes]],
914
+ enumerable: false,
915
+ configurable: true,
916
+ writable: true,
917
+ });
918
+ Object.defineProperty(metadata, $fieldIndexesByViewTag, {
919
+ value: { ...parentMetadata[$fieldIndexesByViewTag] },
920
+ enumerable: false,
921
+ configurable: true,
922
+ writable: true,
923
+ });
924
+ }
925
+ // $refTypeFieldIndexes
926
+ if (parentMetadata[$refTypeFieldIndexes] !== undefined) {
927
+ Object.defineProperty(metadata, $refTypeFieldIndexes, {
928
+ value: [...parentMetadata[$refTypeFieldIndexes]],
929
+ enumerable: false,
930
+ configurable: true,
931
+ writable: true,
932
+ });
933
+ }
934
+ // $descriptors
935
+ Object.defineProperty(metadata, $descriptors, {
936
+ value: { ...parentMetadata[$descriptors] },
937
+ enumerable: false,
938
+ configurable: true,
939
+ writable: true,
940
+ });
941
+ }
942
+ }
943
+ constructor[Symbol.metadata] = metadata;
944
+ return metadata;
945
+ },
946
+ isValidInstance(klass) {
947
+ return (klass.constructor[Symbol.metadata] &&
948
+ Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], $numFields));
949
+ },
950
+ getFields(klass) {
951
+ const metadata = klass[Symbol.metadata];
952
+ const fields = {};
953
+ for (let i = 0; i <= metadata[$numFields]; i++) {
954
+ fields[metadata[i].name] = metadata[i].type;
955
+ }
956
+ return fields;
957
+ }
958
+ };
959
+
960
+ function setOperationAtIndex(changeSet, index) {
961
+ const operationsIndex = changeSet.indexes[index];
962
+ if (operationsIndex === undefined) {
963
+ changeSet.indexes[index] = changeSet.operations.push(index) - 1;
964
+ }
965
+ else {
966
+ changeSet.operations[operationsIndex] = index;
967
+ }
1096
968
  }
1097
- function readFloat64(bytes, it) {
1098
- _int32[0 ] = int32(bytes, it);
1099
- _int32[1 ] = int32(bytes, it);
1100
- return _float64[0];
969
+ function deleteOperationAtIndex(changeSet, index) {
970
+ const operationsIndex = changeSet.indexes[index];
971
+ if (operationsIndex !== undefined) {
972
+ changeSet.operations[operationsIndex] = undefined;
973
+ }
974
+ delete changeSet.indexes[index];
1101
975
  }
1102
- function boolean(bytes, it) {
1103
- return uint8(bytes, it) > 0;
976
+ function enqueueChangeTree(root, changeTree, changeSet, queueRootIndex = changeTree[changeSet].queueRootIndex) {
977
+ if (root && root[changeSet][queueRootIndex] !== changeTree) {
978
+ changeTree[changeSet].queueRootIndex = root[changeSet].push(changeTree) - 1;
979
+ }
1104
980
  }
1105
- function string(bytes, it) {
1106
- const prefix = bytes[it.offset++];
1107
- let length;
1108
- if (prefix < 0xc0) {
1109
- // fixstr
1110
- length = prefix & 0x1f;
981
+ class ChangeTree {
982
+ constructor(ref) {
983
+ /**
984
+ * Whether this structure is parent of a filtered structure.
985
+ */
986
+ this.isFiltered = false;
987
+ this.indexedOperations = {};
988
+ //
989
+ // TODO:
990
+ // try storing the index + operation per item.
991
+ // example: 1024 & 1025 => ADD, 1026 => DELETE
992
+ //
993
+ // => https://chatgpt.com/share/67107d0c-bc20-8004-8583-83b17dd7c196
994
+ //
995
+ this.changes = { indexes: {}, operations: [] };
996
+ this.allChanges = { indexes: {}, operations: [] };
997
+ /**
998
+ * Is this a new instance? Used on ArraySchema to determine OPERATION.MOVE_AND_ADD operation.
999
+ */
1000
+ this.isNew = true;
1001
+ this.ref = ref;
1002
+ //
1003
+ // Does this structure have "filters" declared?
1004
+ //
1005
+ const metadata = ref.constructor[Symbol.metadata];
1006
+ if (metadata?.[$viewFieldIndexes]) {
1007
+ this.allFilteredChanges = { indexes: {}, operations: [] };
1008
+ this.filteredChanges = { indexes: {}, operations: [] };
1009
+ }
1111
1010
  }
1112
- else if (prefix === 0xd9) {
1113
- length = uint8(bytes, it);
1011
+ setRoot(root) {
1012
+ this.root = root;
1013
+ this.checkIsFiltered(this.parent, this.parentIndex);
1014
+ // Recursively set root on child structures
1015
+ const metadata = this.ref.constructor[Symbol.metadata];
1016
+ if (metadata) {
1017
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
1018
+ const field = metadata[index];
1019
+ const value = this.ref[field.name];
1020
+ value?.[$changes].setRoot(root);
1021
+ });
1022
+ }
1023
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
1024
+ // MapSchema / ArraySchema, etc.
1025
+ this.ref.forEach((value, key) => {
1026
+ value[$changes].setRoot(root);
1027
+ });
1028
+ }
1114
1029
  }
1115
- else if (prefix === 0xda) {
1116
- length = uint16(bytes, it);
1030
+ setParent(parent, root, parentIndex) {
1031
+ this.parent = parent;
1032
+ this.parentIndex = parentIndex;
1033
+ // avoid setting parents with empty `root`
1034
+ if (!root) {
1035
+ return;
1036
+ }
1037
+ // skip if parent is already set
1038
+ if (root !== this.root) {
1039
+ this.root = root;
1040
+ this.checkIsFiltered(parent, parentIndex);
1041
+ }
1042
+ else {
1043
+ root.add(this);
1044
+ }
1045
+ // assign same parent on child structures
1046
+ const metadata = this.ref.constructor[Symbol.metadata];
1047
+ if (metadata) {
1048
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
1049
+ const field = metadata[index];
1050
+ const value = this.ref[field.name];
1051
+ value?.[$changes].setParent(this.ref, root, index);
1052
+ });
1053
+ }
1054
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
1055
+ // MapSchema / ArraySchema, etc.
1056
+ this.ref.forEach((value, key) => {
1057
+ value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
1058
+ });
1059
+ }
1117
1060
  }
1118
- else if (prefix === 0xdb) {
1119
- length = uint32(bytes, it);
1061
+ forEachChild(callback) {
1062
+ //
1063
+ // assign same parent on child structures
1064
+ //
1065
+ const metadata = this.ref.constructor[Symbol.metadata];
1066
+ if (metadata) {
1067
+ metadata[$refTypeFieldIndexes]?.forEach((index) => {
1068
+ const field = metadata[index];
1069
+ const value = this.ref[field.name];
1070
+ if (value) {
1071
+ callback(value[$changes], index);
1072
+ }
1073
+ });
1074
+ }
1075
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
1076
+ // MapSchema / ArraySchema, etc.
1077
+ this.ref.forEach((value, key) => {
1078
+ callback(value[$changes], this.indexes[key] ?? key);
1079
+ });
1080
+ }
1120
1081
  }
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;
1082
+ operation(op) {
1083
+ // operations without index use negative values to represent them
1084
+ // this is checked during .encode() time.
1085
+ this.changes.operations.push(-op);
1086
+ enqueueChangeTree(this.root, this, 'changes');
1140
1087
  }
1141
- else if (prefix === 0xca) {
1142
- // float 32
1143
- return readFloat32(bytes, it);
1088
+ change(index, operation = exports.OPERATION.ADD) {
1089
+ const metadata = this.ref.constructor[Symbol.metadata];
1090
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
1091
+ const changeSet = (isFiltered)
1092
+ ? this.filteredChanges
1093
+ : this.changes;
1094
+ const previousOperation = this.indexedOperations[index];
1095
+ if (!previousOperation || previousOperation === exports.OPERATION.DELETE) {
1096
+ const op = (!previousOperation)
1097
+ ? operation
1098
+ : (previousOperation === exports.OPERATION.DELETE)
1099
+ ? exports.OPERATION.DELETE_AND_ADD
1100
+ : operation;
1101
+ //
1102
+ // TODO: are DELETE operations being encoded as ADD here ??
1103
+ //
1104
+ this.indexedOperations[index] = op;
1105
+ }
1106
+ setOperationAtIndex(changeSet, index);
1107
+ if (isFiltered) {
1108
+ setOperationAtIndex(this.allFilteredChanges, index);
1109
+ if (this.root) {
1110
+ enqueueChangeTree(this.root, this, 'filteredChanges');
1111
+ enqueueChangeTree(this.root, this, 'allFilteredChanges');
1112
+ }
1113
+ }
1114
+ else {
1115
+ setOperationAtIndex(this.allChanges, index);
1116
+ enqueueChangeTree(this.root, this, 'changes');
1117
+ }
1144
1118
  }
1145
- else if (prefix === 0xcb) {
1146
- // float 64
1147
- return readFloat64(bytes, it);
1119
+ shiftChangeIndexes(shiftIndex) {
1120
+ //
1121
+ // Used only during:
1122
+ //
1123
+ // - ArraySchema#unshift()
1124
+ //
1125
+ const changeSet = (this.isFiltered)
1126
+ ? this.filteredChanges
1127
+ : this.changes;
1128
+ const newIndexedOperations = {};
1129
+ const newIndexes = {};
1130
+ for (const index in this.indexedOperations) {
1131
+ newIndexedOperations[Number(index) + shiftIndex] = this.indexedOperations[index];
1132
+ newIndexes[Number(index) + shiftIndex] = changeSet[index];
1133
+ }
1134
+ this.indexedOperations = newIndexedOperations;
1135
+ changeSet.indexes = newIndexes;
1136
+ changeSet.operations = changeSet.operations.map((index) => index + shiftIndex);
1137
+ }
1138
+ shiftAllChangeIndexes(shiftIndex, startIndex = 0) {
1139
+ //
1140
+ // Used only during:
1141
+ //
1142
+ // - ArraySchema#splice()
1143
+ //
1144
+ if (this.filteredChanges !== undefined) {
1145
+ this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allFilteredChanges);
1146
+ this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
1147
+ }
1148
+ else {
1149
+ this._shiftAllChangeIndexes(shiftIndex, startIndex, this.allChanges);
1150
+ }
1151
+ }
1152
+ _shiftAllChangeIndexes(shiftIndex, startIndex = 0, changeSet) {
1153
+ const newIndexes = {};
1154
+ for (const key in changeSet.indexes) {
1155
+ const index = changeSet.indexes[key];
1156
+ if (index > startIndex) {
1157
+ newIndexes[Number(key) + shiftIndex] = index;
1158
+ }
1159
+ else {
1160
+ newIndexes[key] = index;
1161
+ }
1162
+ }
1163
+ changeSet.indexes = newIndexes;
1164
+ for (let i = 0; i < changeSet.operations.length; i++) {
1165
+ const index = changeSet.operations[i];
1166
+ if (index > startIndex) {
1167
+ changeSet.operations[i] = index + shiftIndex;
1168
+ }
1169
+ }
1170
+ }
1171
+ indexedOperation(index, operation, allChangesIndex = index) {
1172
+ this.indexedOperations[index] = operation;
1173
+ if (this.filteredChanges !== undefined) {
1174
+ setOperationAtIndex(this.allFilteredChanges, allChangesIndex);
1175
+ setOperationAtIndex(this.filteredChanges, index);
1176
+ enqueueChangeTree(this.root, this, 'filteredChanges');
1177
+ }
1178
+ else {
1179
+ setOperationAtIndex(this.allChanges, allChangesIndex);
1180
+ setOperationAtIndex(this.changes, index);
1181
+ enqueueChangeTree(this.root, this, 'changes');
1182
+ }
1183
+ }
1184
+ getType(index) {
1185
+ if (Metadata.isValidInstance(this.ref)) {
1186
+ const metadata = this.ref.constructor[Symbol.metadata];
1187
+ return metadata[index].type;
1188
+ }
1189
+ else {
1190
+ //
1191
+ // Get the child type from parent structure.
1192
+ // - ["string"] => "string"
1193
+ // - { map: "string" } => "string"
1194
+ // - { set: "string" } => "string"
1195
+ //
1196
+ return this.ref[$childType];
1197
+ }
1198
+ }
1199
+ getChange(index) {
1200
+ return this.indexedOperations[index];
1201
+ }
1202
+ //
1203
+ // used during `.encode()`
1204
+ //
1205
+ getValue(index, isEncodeAll = false) {
1206
+ //
1207
+ // `isEncodeAll` param is only used by ArraySchema
1208
+ //
1209
+ return this.ref[$getByIndex](index, isEncodeAll);
1210
+ }
1211
+ delete(index, operation, allChangesIndex = index) {
1212
+ if (index === undefined) {
1213
+ try {
1214
+ throw new Error(`@colyseus/schema ${this.ref.constructor.name}: trying to delete non-existing index '${index}'`);
1215
+ }
1216
+ catch (e) {
1217
+ console.warn(e);
1218
+ }
1219
+ return;
1220
+ }
1221
+ const changeSet = (this.filteredChanges !== undefined)
1222
+ ? this.filteredChanges
1223
+ : this.changes;
1224
+ this.indexedOperations[index] = operation ?? exports.OPERATION.DELETE;
1225
+ setOperationAtIndex(changeSet, index);
1226
+ const previousValue = this.getValue(index);
1227
+ // remove `root` reference
1228
+ if (previousValue && previousValue[$changes]) {
1229
+ //
1230
+ // FIXME: this.root is "undefined"
1231
+ //
1232
+ // This method is being called at decoding time when a DELETE operation is found.
1233
+ //
1234
+ // - This is due to using the concrete Schema class at decoding time.
1235
+ // - "Reflected" structures do not have this problem.
1236
+ //
1237
+ // (The property descriptors should NOT be used at decoding time. only at encoding time.)
1238
+ //
1239
+ this.root?.remove(previousValue[$changes]);
1240
+ }
1241
+ //
1242
+ // FIXME: this is looking a ugly and repeated
1243
+ //
1244
+ if (this.filteredChanges !== undefined) {
1245
+ deleteOperationAtIndex(this.allFilteredChanges, allChangesIndex);
1246
+ enqueueChangeTree(this.root, this, 'filteredChanges');
1247
+ }
1248
+ else {
1249
+ deleteOperationAtIndex(this.allChanges, allChangesIndex);
1250
+ enqueueChangeTree(this.root, this, 'changes');
1251
+ }
1252
+ }
1253
+ endEncode() {
1254
+ this.indexedOperations = {};
1255
+ // // clear changes
1256
+ // this.changes.indexes = {};
1257
+ // this.changes.operations.length = 0;
1258
+ // ArraySchema and MapSchema have a custom "encode end" method
1259
+ this.ref[$onEncodeEnd]?.();
1260
+ // Not a new instance anymore
1261
+ this.isNew = false;
1262
+ }
1263
+ discard(discardAll = false) {
1264
+ //
1265
+ // > MapSchema:
1266
+ // Remove cached key to ensure ADD operations is unsed instead of
1267
+ // REPLACE in case same key is used on next patches.
1268
+ //
1269
+ this.ref[$onEncodeEnd]?.();
1270
+ this.indexedOperations = {};
1271
+ this.changes.indexes = {};
1272
+ this.changes.operations.length = 0;
1273
+ this.changes.queueRootIndex = undefined;
1274
+ if (this.filteredChanges !== undefined) {
1275
+ this.filteredChanges.indexes = {};
1276
+ this.filteredChanges.operations.length = 0;
1277
+ this.filteredChanges.queueRootIndex = undefined;
1278
+ }
1279
+ if (discardAll) {
1280
+ this.allChanges.indexes = {};
1281
+ this.allChanges.operations.length = 0;
1282
+ if (this.allFilteredChanges !== undefined) {
1283
+ this.allFilteredChanges.indexes = {};
1284
+ this.allFilteredChanges.operations.length = 0;
1285
+ }
1286
+ // remove children references
1287
+ this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
1288
+ }
1289
+ }
1290
+ /**
1291
+ * Recursively discard all changes from this, and child structures.
1292
+ */
1293
+ discardAll() {
1294
+ const keys = Object.keys(this.indexedOperations);
1295
+ for (let i = 0, len = keys.length; i < len; i++) {
1296
+ const value = this.getValue(Number(keys[i]));
1297
+ if (value && value[$changes]) {
1298
+ value[$changes].discardAll();
1299
+ }
1300
+ }
1301
+ this.discard();
1302
+ }
1303
+ ensureRefId() {
1304
+ // skip if refId is already set.
1305
+ if (this.refId !== undefined) {
1306
+ return;
1307
+ }
1308
+ this.refId = this.root.getNextUniqueId();
1309
+ }
1310
+ get changed() {
1311
+ return (Object.entries(this.indexedOperations).length > 0);
1312
+ }
1313
+ checkIsFiltered(parent, parentIndex) {
1314
+ const isNewChangeTree = this.root.add(this);
1315
+ if (this.root.types.hasFilters) {
1316
+ //
1317
+ // At Schema initialization, the "root" structure might not be available
1318
+ // yet, as it only does once the "Encoder" has been set up.
1319
+ //
1320
+ // So the "parent" may be already set without a "root".
1321
+ //
1322
+ this._checkFilteredByParent(parent, parentIndex);
1323
+ if (this.filteredChanges !== undefined) {
1324
+ enqueueChangeTree(this.root, this, 'filteredChanges');
1325
+ if (isNewChangeTree) {
1326
+ this.root.allFilteredChanges.push(this);
1327
+ }
1328
+ }
1329
+ }
1330
+ if (!this.isFiltered) {
1331
+ enqueueChangeTree(this.root, this, 'changes');
1332
+ if (isNewChangeTree) {
1333
+ this.root.allChanges.push(this);
1334
+ }
1335
+ }
1336
+ }
1337
+ _checkFilteredByParent(parent, parentIndex) {
1338
+ // skip if parent is not set
1339
+ if (!parent) {
1340
+ return;
1341
+ }
1342
+ //
1343
+ // ArraySchema | MapSchema - get the child type
1344
+ // (if refType is typeof string, the parentFiltered[key] below will always be invalid)
1345
+ //
1346
+ const refType = Metadata.isValidInstance(this.ref)
1347
+ ? this.ref.constructor
1348
+ : this.ref[$childType];
1349
+ if (!Metadata.isValidInstance(parent)) {
1350
+ const parentChangeTree = parent[$changes];
1351
+ parent = parentChangeTree.parent;
1352
+ parentIndex = parentChangeTree.parentIndex;
1353
+ }
1354
+ const parentConstructor = parent.constructor;
1355
+ let key = `${this.root.types.getTypeId(refType)}`;
1356
+ if (parentConstructor) {
1357
+ key += `-${this.root.types.schemas.get(parentConstructor)}`;
1358
+ }
1359
+ key += `-${parentIndex}`;
1360
+ this.isFiltered = parent[$changes].isFiltered // in case parent is already filtered
1361
+ || this.root.types.parentFiltered[key];
1362
+ // const parentMetadata = parentConstructor?.[Symbol.metadata];
1363
+ // this.isFiltered = parentMetadata?.[$viewFieldIndexes]?.includes(parentIndex) || this.root.types.parentFiltered[key];
1364
+ //
1365
+ // TODO: refactor this!
1366
+ //
1367
+ // swapping `changes` and `filteredChanges` is required here
1368
+ // because "isFiltered" may not be imedialely available on `change()`
1369
+ // (this happens when instance is detached from root or parent)
1370
+ //
1371
+ if (this.isFiltered) {
1372
+ this.filteredChanges = { indexes: {}, operations: [] };
1373
+ this.allFilteredChanges = { indexes: {}, operations: [] };
1374
+ if (this.changes.operations.length > 0) {
1375
+ // swap changes reference
1376
+ const changes = this.changes;
1377
+ this.changes = this.filteredChanges;
1378
+ this.filteredChanges = changes;
1379
+ // swap "all changes" reference
1380
+ const allFilteredChanges = this.allFilteredChanges;
1381
+ this.allFilteredChanges = this.allChanges;
1382
+ this.allChanges = allFilteredChanges;
1383
+ }
1384
+ }
1385
+ }
1386
+ }
1387
+
1388
+ function encodeValue(encoder, bytes, type, value, operation, it) {
1389
+ if (typeof (type) === "string") {
1390
+ encode[type]?.(bytes, value, it);
1148
1391
  }
1149
- else if (prefix === 0xcc) {
1150
- // uint 8
1151
- return uint8(bytes, it);
1392
+ else if (type[Symbol.metadata] !== undefined) {
1393
+ //
1394
+ // Encode refId for this instance.
1395
+ // The actual instance is going to be encoded on next `changeTree` iteration.
1396
+ //
1397
+ encode.number(bytes, value[$changes].refId, it);
1398
+ // Try to encode inherited TYPE_ID if it's an ADD operation.
1399
+ if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
1400
+ encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
1401
+ }
1152
1402
  }
1153
- else if (prefix === 0xcd) {
1154
- // uint 16
1155
- return uint16(bytes, it);
1403
+ else {
1404
+ //
1405
+ // Encode refId for this instance.
1406
+ // The actual instance is going to be encoded on next `changeTree` iteration.
1407
+ //
1408
+ encode.number(bytes, value[$changes].refId, it);
1156
1409
  }
1157
- else if (prefix === 0xce) {
1158
- // uint 32
1159
- return uint32(bytes, it);
1410
+ }
1411
+ /**
1412
+ * Used for Schema instances.
1413
+ * @private
1414
+ */
1415
+ const encodeSchemaOperation = function (encoder, bytes, changeTree, index, operation, it, _, __, metadata) {
1416
+ // "compress" field index + operation
1417
+ bytes[it.offset++] = (index | operation) & 255;
1418
+ // Do not encode value for DELETE operations
1419
+ if (operation === exports.OPERATION.DELETE) {
1420
+ return;
1160
1421
  }
1161
- else if (prefix === 0xcf) {
1162
- // uint 64
1163
- return uint64(bytes, it);
1422
+ const ref = changeTree.ref;
1423
+ const field = metadata[index];
1424
+ // TODO: inline this function call small performance gain
1425
+ encodeValue(encoder, bytes, metadata[index].type, ref[field.name], operation, it);
1426
+ };
1427
+ /**
1428
+ * Used for collections (MapSchema, CollectionSchema, SetSchema)
1429
+ * @private
1430
+ */
1431
+ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
1432
+ // encode operation
1433
+ bytes[it.offset++] = operation & 255;
1434
+ // custom operations
1435
+ if (operation === exports.OPERATION.CLEAR) {
1436
+ return;
1164
1437
  }
1165
- else if (prefix === 0xd0) {
1166
- // int 8
1167
- return int8(bytes, it);
1438
+ // encode index
1439
+ encode.number(bytes, index, it);
1440
+ // Do not encode value for DELETE operations
1441
+ if (operation === exports.OPERATION.DELETE) {
1442
+ return;
1168
1443
  }
1169
- else if (prefix === 0xd1) {
1170
- // int 16
1171
- return int16(bytes, it);
1444
+ const ref = changeTree.ref;
1445
+ //
1446
+ // encode "alias" for dynamic fields (maps)
1447
+ //
1448
+ if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
1449
+ if (typeof (ref['set']) === "function") {
1450
+ //
1451
+ // MapSchema dynamic key
1452
+ //
1453
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
1454
+ encode.string(bytes, dynamicIndex, it);
1455
+ }
1172
1456
  }
1173
- else if (prefix === 0xd2) {
1174
- // int 32
1175
- return int32(bytes, it);
1457
+ const type = ref[$childType];
1458
+ const value = ref[$getByIndex](index);
1459
+ // try { throw new Error(); } catch (e) {
1460
+ // // only print if not coming from Reflection.ts
1461
+ // if (!e.stack.includes("src/Reflection.ts")) {
1462
+ // console.log("encodeKeyValueOperation -> ", {
1463
+ // ref: changeTree.ref.constructor.name,
1464
+ // field,
1465
+ // operation: OPERATION[operation],
1466
+ // value: value?.toJSON(),
1467
+ // items: ref.toJSON(),
1468
+ // });
1469
+ // }
1470
+ // }
1471
+ // TODO: inline this function call small performance gain
1472
+ encodeValue(encoder, bytes, type, value, operation, it);
1473
+ };
1474
+ /**
1475
+ * Used for collections (MapSchema, ArraySchema, etc.)
1476
+ * @private
1477
+ */
1478
+ const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
1479
+ const ref = changeTree.ref;
1480
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
1481
+ let refOrIndex;
1482
+ if (useOperationByRefId) {
1483
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
1484
+ if (operation === exports.OPERATION.DELETE) {
1485
+ operation = exports.OPERATION.DELETE_BY_REFID;
1486
+ }
1487
+ else if (operation === exports.OPERATION.ADD) {
1488
+ operation = exports.OPERATION.ADD_BY_REFID;
1489
+ }
1176
1490
  }
1177
- else if (prefix === 0xd3) {
1178
- // int 64
1179
- return int64(bytes, it);
1491
+ else {
1492
+ refOrIndex = field;
1180
1493
  }
1181
- else if (prefix > 0xdf) {
1182
- // negative fixint
1183
- return (0xff - prefix + 1) * -1;
1494
+ // encode operation
1495
+ bytes[it.offset++] = operation & 255;
1496
+ // custom operations
1497
+ if (operation === exports.OPERATION.CLEAR ||
1498
+ operation === exports.OPERATION.REVERSE) {
1499
+ return;
1184
1500
  }
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
- });
1501
+ // encode index
1502
+ encode.number(bytes, refOrIndex, it);
1503
+ // Do not encode value for DELETE operations
1504
+ if (operation === exports.OPERATION.DELETE) {
1505
+ return;
1506
+ }
1507
+ const type = changeTree.getType(field);
1508
+ const value = changeTree.getValue(field, isEncodeAll);
1509
+ // console.log("encodeArray -> ", {
1510
+ // ref: changeTree.ref.constructor.name,
1511
+ // field,
1512
+ // operation: OPERATION[operation],
1513
+ // value: value?.toJSON(),
1514
+ // items: ref.toJSON(),
1515
+ // });
1516
+ // TODO: inline this function call small performance gain
1517
+ encodeValue(encoder, bytes, type, value, operation, it);
1518
+ };
1246
1519
 
1247
1520
  const DEFINITION_MISMATCH = -1;
1248
1521
  function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
1249
- const $root = decoder.$root;
1522
+ const $root = decoder.root;
1250
1523
  const previousValue = ref[$getByIndex](index);
1251
1524
  let value;
1252
1525
  if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
@@ -1278,7 +1551,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1278
1551
  }
1279
1552
  if (operation === exports.OPERATION.DELETE) ;
1280
1553
  else if (Schema.is(type)) {
1281
- const refId = number(bytes, it);
1554
+ const refId = decode.number(bytes, it);
1282
1555
  value = $root.refs.get(refId);
1283
1556
  if (previousValue) {
1284
1557
  const previousRefId = $root.refIds.get(previousValue);
@@ -1294,7 +1567,9 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1294
1567
  if (!value) {
1295
1568
  value = decoder.createInstanceOfType(childType);
1296
1569
  }
1297
- $root.addRef(refId, value, (value !== previousValue));
1570
+ $root.addRef(refId, value, (value !== previousValue || // increment ref count if value has changed
1571
+ (operation === exports.OPERATION.DELETE_AND_ADD && value === previousValue) // increment ref count if the same instance is being added again
1572
+ ));
1298
1573
  }
1299
1574
  }
1300
1575
  else if (typeof (type) === "string") {
@@ -1305,7 +1580,7 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1305
1580
  }
1306
1581
  else {
1307
1582
  const typeDef = getType(Object.keys(type)[0]);
1308
- const refId = number(bytes, it);
1583
+ const refId = decode.number(bytes, it);
1309
1584
  const valueRef = ($root.refs.has(refId))
1310
1585
  ? previousValue || $root.refs.get(refId)
1311
1586
  : new typeDef.constructor();
@@ -1345,18 +1620,19 @@ function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges
1345
1620
  }
1346
1621
  const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1347
1622
  const first_byte = bytes[it.offset++];
1348
- const metadata = ref['constructor'][Symbol.metadata];
1623
+ const metadata = ref.constructor[Symbol.metadata];
1349
1624
  // "compressed" index + operation
1350
1625
  const operation = (first_byte >> 6) << 6;
1351
1626
  const index = first_byte % (operation || 255);
1352
1627
  // skip early if field is not defined
1353
1628
  const field = metadata[index];
1354
1629
  if (field === undefined) {
1630
+ console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1355
1631
  return DEFINITION_MISMATCH;
1356
1632
  }
1357
- const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
1633
+ const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
1358
1634
  if (value !== null && value !== undefined) {
1359
- ref[field] = value;
1635
+ ref[field.name] = value;
1360
1636
  }
1361
1637
  // add change
1362
1638
  if (previousValue !== value) {
@@ -1364,7 +1640,7 @@ const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1364
1640
  ref,
1365
1641
  refId: decoder.currentRefId,
1366
1642
  op: operation,
1367
- field: field,
1643
+ field: field.name,
1368
1644
  value,
1369
1645
  previousValue,
1370
1646
  });
@@ -1383,12 +1659,12 @@ const decodeKeyValueOperation = function (decoder, bytes, it, ref, allChanges) {
1383
1659
  ref.clear();
1384
1660
  return;
1385
1661
  }
1386
- const index = number(bytes, it);
1662
+ const index = decode.number(bytes, it);
1387
1663
  const type = ref[$childType];
1388
1664
  let dynamicIndex;
1389
1665
  if ((operation & exports.OPERATION.ADD) === exports.OPERATION.ADD) { // ADD or DELETE_AND_ADD
1390
1666
  if (typeof (ref['set']) === "function") {
1391
- dynamicIndex = string(bytes, it); // MapSchema
1667
+ dynamicIndex = decode.string(bytes, it); // MapSchema
1392
1668
  ref['setIndex'](index, dynamicIndex);
1393
1669
  }
1394
1670
  else {
@@ -1432,7 +1708,8 @@ const decodeKeyValueOperation = function (decoder, bytes, it, ref, allChanges) {
1432
1708
  };
1433
1709
  const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1434
1710
  // "uncompressed" index + operation (array/map items)
1435
- const operation = bytes[it.offset++];
1711
+ let operation = bytes[it.offset++];
1712
+ let index;
1436
1713
  if (operation === exports.OPERATION.CLEAR) {
1437
1714
  //
1438
1715
  // When decoding:
@@ -1443,11 +1720,15 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1443
1720
  ref.clear();
1444
1721
  return;
1445
1722
  }
1723
+ else if (operation === exports.OPERATION.REVERSE) {
1724
+ ref.reverse();
1725
+ return;
1726
+ }
1446
1727
  else if (operation === exports.OPERATION.DELETE_BY_REFID) {
1447
1728
  // 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);
1729
+ const refId = decode.number(bytes, it);
1730
+ const previousValue = decoder.root.refs.get(refId);
1731
+ index = ref.findIndex((value) => value === previousValue);
1451
1732
  ref[$deleteByIndex](index);
1452
1733
  allChanges.push({
1453
1734
  ref,
@@ -1460,7 +1741,17 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1460
1741
  });
1461
1742
  return;
1462
1743
  }
1463
- const index = number(bytes, it);
1744
+ else if (operation === exports.OPERATION.ADD_BY_REFID) {
1745
+ const refId = decode.number(bytes, it);
1746
+ const itemByRefId = decoder.root.refs.get(refId);
1747
+ // use existing index, or push new value
1748
+ index = (itemByRefId)
1749
+ ? ref.findIndex((value) => value === itemByRefId)
1750
+ : ref.length;
1751
+ }
1752
+ else {
1753
+ index = decode.number(bytes, it);
1754
+ }
1464
1755
  const type = ref[$childType];
1465
1756
  let dynamicIndex = index;
1466
1757
  const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
@@ -1484,6 +1775,55 @@ const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1484
1775
  }
1485
1776
  };
1486
1777
 
1778
+ class EncodeSchemaError extends Error {
1779
+ }
1780
+ function assertType(value, type, klass, field) {
1781
+ let typeofTarget;
1782
+ let allowNull = false;
1783
+ switch (type) {
1784
+ case "number":
1785
+ case "int8":
1786
+ case "uint8":
1787
+ case "int16":
1788
+ case "uint16":
1789
+ case "int32":
1790
+ case "uint32":
1791
+ case "int64":
1792
+ case "uint64":
1793
+ case "float32":
1794
+ case "float64":
1795
+ typeofTarget = "number";
1796
+ if (isNaN(value)) {
1797
+ console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1798
+ }
1799
+ break;
1800
+ case "bigint64":
1801
+ case "biguint64":
1802
+ typeofTarget = "bigint";
1803
+ break;
1804
+ case "string":
1805
+ typeofTarget = "string";
1806
+ allowNull = true;
1807
+ break;
1808
+ case "boolean":
1809
+ // boolean is always encoded as true/false based on truthiness
1810
+ return;
1811
+ default:
1812
+ // skip assertion for custom types
1813
+ // TODO: allow custom types to define their own assertions
1814
+ return;
1815
+ }
1816
+ if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1817
+ let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
1818
+ throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
1819
+ }
1820
+ }
1821
+ function assertInstanceType(value, type, instance, field) {
1822
+ if (!(value instanceof type)) {
1823
+ throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
1824
+ }
1825
+ }
1826
+
1487
1827
  var _a$4, _b$4;
1488
1828
  const DEFAULT_SORT = (a, b) => {
1489
1829
  const A = a.toString();
@@ -1534,6 +1874,7 @@ class ArraySchema {
1534
1874
  const proxy = new Proxy(this, {
1535
1875
  get: (obj, prop) => {
1536
1876
  if (typeof (prop) !== "symbol" &&
1877
+ // FIXME: d8 accuses this as low performance
1537
1878
  !isNaN(prop) // https://stackoverflow.com/a/175787/892698
1538
1879
  ) {
1539
1880
  return this.items[prop];
@@ -1549,8 +1890,9 @@ class ArraySchema {
1549
1890
  }
1550
1891
  else {
1551
1892
  if (setValue[$changes]) {
1893
+ assertInstanceType(setValue, obj[$childType], obj, key);
1552
1894
  if (obj.items[key] !== undefined) {
1553
- if (setValue[$changes][$isNew]) {
1895
+ if (setValue[$changes].isNew) {
1554
1896
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
1555
1897
  }
1556
1898
  else {
@@ -1562,7 +1904,7 @@ class ArraySchema {
1562
1904
  }
1563
1905
  }
1564
1906
  }
1565
- else if (setValue[$changes][$isNew]) {
1907
+ else if (setValue[$changes].isNew) {
1566
1908
  this[$changes].indexedOperation(Number(key), exports.OPERATION.ADD);
1567
1909
  }
1568
1910
  }
@@ -1595,7 +1937,10 @@ class ArraySchema {
1595
1937
  }
1596
1938
  });
1597
1939
  this[$changes] = new ChangeTree(proxy);
1598
- this.push.apply(this, items);
1940
+ this[$changes].indexes = {};
1941
+ if (items.length > 0) {
1942
+ this.push(...items);
1943
+ }
1599
1944
  return proxy;
1600
1945
  }
1601
1946
  set length(newLength) {
@@ -1614,14 +1959,19 @@ class ArraySchema {
1614
1959
  }
1615
1960
  push(...values) {
1616
1961
  let length = this.tmpItems.length;
1617
- values.forEach((value, i) => {
1618
- // skip null values
1962
+ const changeTree = this[$changes];
1963
+ // values.forEach((value, i) => {
1964
+ for (let i = 0, l = values.length; i < values.length; i++, length++) {
1965
+ const value = values[i];
1619
1966
  if (value === undefined || value === null) {
1967
+ // skip null values
1620
1968
  return;
1621
1969
  }
1622
- const changeTree = this[$changes];
1970
+ else if (typeof (value) === "object" && this[$childType]) {
1971
+ assertInstanceType(value, this[$childType], this, i);
1972
+ // TODO: move value[$changes]?.setParent() to this block.
1973
+ }
1623
1974
  changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
1624
- // changeTree.indexes[length] = length;
1625
1975
  this.items.push(value);
1626
1976
  this.tmpItems.push(value);
1627
1977
  //
@@ -1629,8 +1979,9 @@ class ArraySchema {
1629
1979
  // (to avoid encoding "refId" operations before parent's "ADD" operation)
1630
1980
  //
1631
1981
  value[$changes]?.setParent(this, changeTree.root, length);
1632
- length++;
1633
- });
1982
+ }
1983
+ // length++;
1984
+ // });
1634
1985
  return length;
1635
1986
  }
1636
1987
  /**
@@ -1651,6 +2002,7 @@ class ArraySchema {
1651
2002
  }
1652
2003
  this[$changes].delete(index, undefined, this.items.length - 1);
1653
2004
  // this.tmpItems[index] = undefined;
2005
+ // this.tmpItems.pop();
1654
2006
  this.deletedIndexes[index] = true;
1655
2007
  return this.items.pop();
1656
2008
  }
@@ -1715,9 +2067,12 @@ class ArraySchema {
1715
2067
  //
1716
2068
  // TODO: do not use [$changes] at decoding time.
1717
2069
  //
1718
- changeTree.root?.changes.delete(changeTree);
1719
- changeTree.root?.allChanges.delete(changeTree);
1720
- changeTree.root?.allFilteredChanges.delete(changeTree);
2070
+ const root = changeTree.root;
2071
+ if (root !== undefined) {
2072
+ root.removeChangeFromChangeSet("changes", changeTree);
2073
+ root.removeChangeFromChangeSet("allChanges", changeTree);
2074
+ root.removeChangeFromChangeSet("allFilteredChanges", changeTree);
2075
+ }
1721
2076
  });
1722
2077
  changeTree.discard(true);
1723
2078
  changeTree.operation(exports.OPERATION.CLEAR);
@@ -1761,6 +2116,7 @@ class ArraySchema {
1761
2116
  const changeTree = this[$changes];
1762
2117
  changeTree.delete(index);
1763
2118
  changeTree.shiftAllChangeIndexes(-1, index);
2119
+ // this.deletedIndexes[index] = true;
1764
2120
  return this.items.shift();
1765
2121
  }
1766
2122
  /**
@@ -1841,10 +2197,12 @@ class ArraySchema {
1841
2197
  changeTree.shiftChangeIndexes(items.length);
1842
2198
  // new index
1843
2199
  if (changeTree.isFiltered) {
1844
- changeTree.filteredChanges.set(this.items.length, exports.OPERATION.ADD);
2200
+ setOperationAtIndex(changeTree.filteredChanges, this.items.length);
2201
+ // changeTree.filteredChanges[this.items.length] = OPERATION.ADD;
1845
2202
  }
1846
2203
  else {
1847
- changeTree.allChanges.set(this.items.length, exports.OPERATION.ADD);
2204
+ setOperationAtIndex(changeTree.allChanges, this.items.length);
2205
+ // changeTree.allChanges[this.items.length] = OPERATION.ADD;
1848
2206
  }
1849
2207
  // FIXME: should we use OPERATION.MOVE here instead?
1850
2208
  items.forEach((_, index) => {
@@ -1869,14 +2227,6 @@ class ArraySchema {
1869
2227
  lastIndexOf(searchElement, fromIndex = this.length - 1) {
1870
2228
  return this.items.lastIndexOf(searchElement, fromIndex);
1871
2229
  }
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
2230
  every(callbackfn, thisArg) {
1881
2231
  return this.items.every(callbackfn, thisArg);
1882
2232
  }
@@ -2155,6 +2505,7 @@ class MapSchema {
2155
2505
  this.$items = new Map();
2156
2506
  this.$indexes = new Map();
2157
2507
  this[$changes] = new ChangeTree(this);
2508
+ this[$changes].indexes = {};
2158
2509
  if (initialValues) {
2159
2510
  if (initialValues instanceof Map ||
2160
2511
  initialValues instanceof MapSchema) {
@@ -2181,6 +2532,9 @@ class MapSchema {
2181
2532
  if (value === undefined || value === null) {
2182
2533
  throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
2183
2534
  }
2535
+ else if (typeof (value) === "object" && this[$childType]) {
2536
+ assertInstanceType(value, this[$childType], this, key);
2537
+ }
2184
2538
  // Force "key" as string
2185
2539
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2186
2540
  key = key.toString();
@@ -2189,7 +2543,7 @@ class MapSchema {
2189
2543
  const isReplace = typeof (changeTree.indexes[key]) !== "undefined";
2190
2544
  const index = (isReplace)
2191
2545
  ? changeTree.indexes[key]
2192
- : changeTree.indexes[-1] ?? 0;
2546
+ : changeTree.indexes[$numFields] ?? 0;
2193
2547
  let operation = (isReplace)
2194
2548
  ? exports.OPERATION.REPLACE
2195
2549
  : exports.OPERATION.ADD;
@@ -2201,7 +2555,7 @@ class MapSchema {
2201
2555
  if (!isReplace) {
2202
2556
  this.$indexes.set(index, key);
2203
2557
  changeTree.indexes[key] = index;
2204
- changeTree.indexes[-1] = index + 1;
2558
+ changeTree.indexes[$numFields] = index + 1;
2205
2559
  }
2206
2560
  else if (!isRef &&
2207
2561
  this.$items.get(key) === value) {
@@ -2276,8 +2630,11 @@ class MapSchema {
2276
2630
  }
2277
2631
  [$onEncodeEnd]() {
2278
2632
  const changeTree = this[$changes];
2279
- const changes = changeTree.changes.entries();
2280
- for (const [fieldIndex, operation] of changes) {
2633
+ const keys = Object.keys(changeTree.indexedOperations);
2634
+ for (let i = 0, len = keys.length; i < len; i++) {
2635
+ const key = keys[i];
2636
+ const fieldIndex = Number(key);
2637
+ const operation = changeTree.indexedOperations[key];
2281
2638
  if (operation === exports.OPERATION.DELETE) {
2282
2639
  const index = this[$getByIndex](fieldIndex);
2283
2640
  delete changeTree.indexes[index];
@@ -2304,110 +2661,23 @@ class MapSchema {
2304
2661
  cloned = Object.assign(new MapSchema(), this);
2305
2662
  }
2306
2663
  else {
2307
- // server-side
2308
- cloned = new MapSchema();
2309
- this.forEach((value, key) => {
2310
- if (value[$changes]) {
2311
- cloned.set(key, value['clone']());
2312
- }
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;
2664
+ // server-side
2665
+ cloned = new MapSchema();
2666
+ this.forEach((value, key) => {
2667
+ if (value[$changes]) {
2668
+ cloned.set(key, value['clone']());
2394
2669
  }
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;
2670
+ else {
2671
+ cloned.set(key, value);
2405
2672
  }
2406
- this.discoverTypes(type);
2407
- }
2673
+ });
2408
2674
  }
2675
+ return cloned;
2409
2676
  }
2410
2677
  }
2678
+ registerType("map", { constructor: MapSchema });
2679
+
2680
+ const DEFAULT_VIEW_TAG = -1;
2411
2681
  /**
2412
2682
  * [See documentation](https://docs.colyseus.io/state/schema/)
2413
2683
  *
@@ -2434,8 +2704,8 @@ class TypeContext {
2434
2704
  // // detect index for this field, considering inheritance
2435
2705
  // //
2436
2706
  // 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
2707
+ // let fieldIndex: number = context.metadata[$numFields] // current structure already has fields defined
2708
+ // ?? (parent && parent[$numFields]) // parent structure has fields defined
2439
2709
  // ?? -1; // no fields defined
2440
2710
  // fieldIndex++;
2441
2711
  // if (
@@ -2555,18 +2825,20 @@ function view(tag = DEFAULT_VIEW_TAG) {
2555
2825
  const constructor = target.constructor;
2556
2826
  const parentClass = Object.getPrototypeOf(constructor);
2557
2827
  const parentMetadata = parentClass[Symbol.metadata];
2828
+ // TODO: use Metadata.initialize()
2558
2829
  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
- }
2830
+ // const fieldIndex = metadata[fieldName];
2831
+ // if (!metadata[fieldIndex]) {
2832
+ // //
2833
+ // // detect index for this field, considering inheritance
2834
+ // //
2835
+ // metadata[fieldIndex] = {
2836
+ // type: undefined,
2837
+ // index: (metadata[$numFields] // current structure already has fields defined
2838
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
2839
+ // ?? -1) + 1 // no fields defined
2840
+ // }
2841
+ // }
2570
2842
  Metadata.setTag(metadata, fieldName, tag);
2571
2843
  };
2572
2844
  }
@@ -2580,17 +2852,17 @@ function type(type, options) {
2580
2852
  TypeContext.register(constructor);
2581
2853
  const parentClass = Object.getPrototypeOf(constructor);
2582
2854
  const parentMetadata = parentClass[Symbol.metadata];
2583
- const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2584
- let fieldIndex;
2855
+ const metadata = Metadata.initialize(constructor);
2856
+ let fieldIndex = metadata[field];
2585
2857
  /**
2586
2858
  * skip if descriptor already exists for this field (`@deprecated()`)
2587
2859
  */
2588
- if (metadata[field]) {
2589
- if (metadata[field].deprecated) {
2860
+ if (metadata[fieldIndex] !== undefined) {
2861
+ if (metadata[fieldIndex].deprecated) {
2590
2862
  // do not create accessors for deprecated properties.
2591
2863
  return;
2592
2864
  }
2593
- else if (metadata[field].descriptor !== undefined) {
2865
+ else if (metadata[fieldIndex].type !== undefined) {
2594
2866
  // trying to define same property multiple times across inheritance.
2595
2867
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
2596
2868
  try {
@@ -2601,16 +2873,13 @@ function type(type, options) {
2601
2873
  throw new Error(`${e.message} ${definitionAtLine}`);
2602
2874
  }
2603
2875
  }
2604
- else {
2605
- fieldIndex = metadata[field].index;
2606
- }
2607
2876
  }
2608
2877
  else {
2609
2878
  //
2610
2879
  // detect index for this field, considering inheritance
2611
2880
  //
2612
- fieldIndex = metadata[-1] // current structure already has fields defined
2613
- ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2881
+ fieldIndex = metadata[$numFields] // current structure already has fields defined
2882
+ ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
2614
2883
  ?? -1; // no fields defined
2615
2884
  fieldIndex++;
2616
2885
  }
@@ -2629,15 +2898,15 @@ function type(type, options) {
2629
2898
  const childType = (complexTypeKlass)
2630
2899
  ? Object.values(type)[0]
2631
2900
  : type;
2632
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field));
2901
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
2633
2902
  }
2634
2903
  };
2635
2904
  }
2636
- function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass, metadata, field) {
2905
+ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
2637
2906
  return {
2638
2907
  get: function () { return this[fieldCached]; },
2639
2908
  set: function (value) {
2640
- const previousValue = this[fieldCached] || undefined;
2909
+ const previousValue = this[fieldCached] ?? undefined;
2641
2910
  // skip if value is the same as cached.
2642
2911
  if (value === previousValue) {
2643
2912
  return;
@@ -2655,22 +2924,27 @@ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass,
2655
2924
  }
2656
2925
  value[$childType] = type;
2657
2926
  }
2927
+ else if (typeof (type) !== "string") {
2928
+ assertInstanceType(value, type, this, fieldCached.substring(1));
2929
+ }
2930
+ else {
2931
+ assertType(value, type, this, fieldCached.substring(1));
2932
+ }
2933
+ const changeTree = this[$changes];
2658
2934
  //
2659
2935
  // Replacing existing "ref", remove it from root.
2660
2936
  // TODO: if there are other references to this instance, we should not remove it from root.
2661
2937
  //
2662
2938
  if (previousValue !== undefined && previousValue[$changes]) {
2663
- this[$changes].root?.remove(previousValue[$changes]);
2939
+ changeTree.root?.remove(previousValue[$changes]);
2664
2940
  }
2665
2941
  // flag the change for encoding.
2666
- this.constructor[$track](this[$changes], fieldIndex, exports.OPERATION.ADD);
2942
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2667
2943
  //
2668
2944
  // call setParent() recursively for this and its child
2669
2945
  // structures.
2670
2946
  //
2671
- if (value[$changes]) {
2672
- value[$changes].setParent(this, this[$changes].root, metadata[field].index);
2673
- }
2947
+ value[$changes]?.setParent(this, changeTree.root, fieldIndex);
2674
2948
  }
2675
2949
  else if (previousValue !== undefined) {
2676
2950
  //
@@ -2697,20 +2971,22 @@ function deprecated(throws = true) {
2697
2971
  const parentClass = Object.getPrototypeOf(constructor);
2698
2972
  const parentMetadata = parentClass[Symbol.metadata];
2699
2973
  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;
2974
+ const fieldIndex = metadata[field];
2975
+ // if (!metadata[field]) {
2976
+ // //
2977
+ // // detect index for this field, considering inheritance
2978
+ // //
2979
+ // metadata[field] = {
2980
+ // type: undefined,
2981
+ // index: (metadata[$numFields] // current structure already has fields defined
2982
+ // ?? (parentMetadata && parentMetadata[$numFields]) // parent structure has fields defined
2983
+ // ?? -1) + 1 // no fields defined
2984
+ // }
2985
+ // }
2986
+ metadata[fieldIndex].deprecated = true;
2712
2987
  if (throws) {
2713
- metadata[field].descriptor = {
2988
+ metadata[$descriptors] ??= {};
2989
+ metadata[$descriptors][field] = {
2714
2990
  get: function () { throw new Error(`${field} is deprecated.`); },
2715
2991
  set: function (value) { },
2716
2992
  enumerable: false,
@@ -2718,8 +2994,8 @@ function deprecated(throws = true) {
2718
2994
  };
2719
2995
  }
2720
2996
  // flag metadata[field] as non-enumerable
2721
- Object.defineProperty(metadata, field, {
2722
- value: metadata[field],
2997
+ Object.defineProperty(metadata, fieldIndex, {
2998
+ value: metadata[fieldIndex],
2723
2999
  enumerable: false,
2724
3000
  configurable: true
2725
3001
  });
@@ -2731,6 +3007,37 @@ function defineTypes(target, fields, options) {
2731
3007
  }
2732
3008
  return target;
2733
3009
  }
3010
+ function schema(fields, name, inherits = Schema) {
3011
+ const defaultValues = {};
3012
+ const viewTagFields = {};
3013
+ for (let fieldName in fields) {
3014
+ const field = fields[fieldName];
3015
+ if (typeof (field) === "object") {
3016
+ if (field['default'] !== undefined) {
3017
+ defaultValues[fieldName] = field['default'];
3018
+ }
3019
+ if (field['view'] !== undefined) {
3020
+ viewTagFields[fieldName] = (typeof (field['view']) === "boolean")
3021
+ ? DEFAULT_VIEW_TAG
3022
+ : field['view'];
3023
+ }
3024
+ }
3025
+ }
3026
+ const klass = Metadata.setFields(class extends inherits {
3027
+ constructor(...args) {
3028
+ args[0] = Object.assign({}, defaultValues, args[0]);
3029
+ super(...args);
3030
+ }
3031
+ }, fields);
3032
+ for (let fieldName in viewTagFields) {
3033
+ view(viewTagFields[fieldName])(klass.prototype, fieldName);
3034
+ }
3035
+ if (name) {
3036
+ Object.defineProperty(klass, "name", { value: name });
3037
+ }
3038
+ klass.extends = (fields, name) => schema(fields, name, klass);
3039
+ return klass;
3040
+ }
2734
3041
 
2735
3042
  function getIndent(level) {
2736
3043
  return (new Array(level).fill(0)).map((_, i) => (i === level - 1) ? `└─ ` : ` `).join("");
@@ -2741,15 +3048,18 @@ function dumpChanges(schema) {
2741
3048
  ops: {},
2742
3049
  refs: []
2743
3050
  };
2744
- $root.changes.forEach((operations, changeTree) => {
3051
+ // for (const refId in $root.changes) {
3052
+ $root.changes.forEach(changeTree => {
3053
+ const changes = changeTree.indexedOperations;
2745
3054
  dump.refs.push(`refId#${changeTree.refId}`);
2746
- operations.forEach((op, index) => {
3055
+ for (const index in changes) {
3056
+ const op = changes[index];
2747
3057
  const opName = exports.OPERATION[op];
2748
3058
  if (!dump.ops[opName]) {
2749
3059
  dump.ops[opName] = 0;
2750
3060
  }
2751
3061
  dump.ops[exports.OPERATION[op]]++;
2752
- });
3062
+ }
2753
3063
  });
2754
3064
  return dump;
2755
3065
  }
@@ -2775,6 +3085,7 @@ var _a$2, _b$2;
2775
3085
  class Schema {
2776
3086
  static { this[_a$2] = encodeSchemaOperation; }
2777
3087
  static { this[_b$2] = decodeSchemaOperation; }
3088
+ // public [$changes]: ChangeTree;
2778
3089
  /**
2779
3090
  * Assign the property descriptors required to track changes on this instance.
2780
3091
  * @param instance
@@ -2785,35 +3096,7 @@ class Schema {
2785
3096
  enumerable: false,
2786
3097
  writable: true
2787
3098
  });
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
- }
3099
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
2817
3100
  }
2818
3101
  static is(type) {
2819
3102
  return typeof (type[Symbol.metadata]) === "object";
@@ -2837,7 +3120,7 @@ class Schema {
2837
3120
  */
2838
3121
  static [$filter](ref, index, view) {
2839
3122
  const metadata = ref.constructor[Symbol.metadata];
2840
- const tag = metadata[metadata[index]].tag;
3123
+ const tag = metadata[index]?.tag;
2841
3124
  if (view === undefined) {
2842
3125
  // shared pass/encode: encode if doesn't have a tag
2843
3126
  return tag === undefined;
@@ -2858,12 +3141,16 @@ class Schema {
2858
3141
  }
2859
3142
  // allow inherited classes to have a constructor
2860
3143
  constructor(...args) {
3144
+ //
3145
+ // inline
3146
+ // Schema.initialize(this);
3147
+ //
2861
3148
  Schema.initialize(this);
2862
3149
  //
2863
3150
  // Assign initial values
2864
3151
  //
2865
3152
  if (args[0]) {
2866
- this.assign(args[0]);
3153
+ Object.assign(this, args[0]);
2867
3154
  }
2868
3155
  }
2869
3156
  assign(props) {
@@ -2877,7 +3164,8 @@ class Schema {
2877
3164
  * @param operation OPERATION to perform (detected automatically)
2878
3165
  */
2879
3166
  setDirty(property, operation) {
2880
- this[$changes].change(this.constructor[Symbol.metadata][property].index, operation);
3167
+ const metadata = this.constructor[Symbol.metadata];
3168
+ this[$changes].change(metadata[metadata[property]].index, operation);
2881
3169
  }
2882
3170
  clone() {
2883
3171
  const cloned = new (this.constructor);
@@ -2886,7 +3174,9 @@ class Schema {
2886
3174
  // TODO: clone all properties, not only annotated ones
2887
3175
  //
2888
3176
  // for (const field in this) {
2889
- for (const field in metadata) {
3177
+ for (const fieldIndex in metadata) {
3178
+ // const field = metadata[metadata[fieldIndex]].name;
3179
+ const field = metadata[fieldIndex].name;
2890
3180
  if (typeof (this[field]) === "object" &&
2891
3181
  typeof (this[field]?.clone) === "function") {
2892
3182
  // deep clone
@@ -2900,10 +3190,11 @@ class Schema {
2900
3190
  return cloned;
2901
3191
  }
2902
3192
  toJSON() {
2903
- const metadata = this.constructor[Symbol.metadata];
2904
3193
  const obj = {};
2905
- for (const fieldName in metadata) {
2906
- const field = metadata[fieldName];
3194
+ const metadata = this.constructor[Symbol.metadata];
3195
+ for (const index in metadata) {
3196
+ const field = metadata[index];
3197
+ const fieldName = field.name;
2907
3198
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
2908
3199
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
2909
3200
  ? this[fieldName]['toJSON']()
@@ -2916,18 +3207,27 @@ class Schema {
2916
3207
  this[$changes].discardAll();
2917
3208
  }
2918
3209
  [$getByIndex](index) {
2919
- return this[this.constructor[Symbol.metadata][index]];
3210
+ const metadata = this.constructor[Symbol.metadata];
3211
+ return this[metadata[index].name];
2920
3212
  }
2921
3213
  [$deleteByIndex](index) {
2922
- this[this.constructor[Symbol.metadata][index]] = undefined;
3214
+ const metadata = this.constructor[Symbol.metadata];
3215
+ this[metadata[index].name] = undefined;
2923
3216
  }
2924
- static debugRefIds(instance, jsonContents = true, level = 0) {
3217
+ /**
3218
+ * Inspect the `refId` of all Schema instances in the tree. Optionally display the contents of the instance.
3219
+ *
3220
+ * @param instance Schema instance
3221
+ * @param showContents display JSON contents of the instance
3222
+ * @returns
3223
+ */
3224
+ static debugRefIds(instance, showContents = false, level = 0) {
2925
3225
  const ref = instance;
2926
3226
  const changeTree = ref[$changes];
2927
- const contents = (jsonContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
3227
+ const contents = (showContents) ? ` - ${JSON.stringify(ref.toJSON())}` : "";
2928
3228
  let output = "";
2929
- output += `${getIndent(level)}${ref.constructor.name} (${ref[$changes].refId})${contents}\n`;
2930
- changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, jsonContents, level + 1));
3229
+ output += `${getIndent(level)}${ref.constructor.name} (refId: ${ref[$changes].refId})${contents}\n`;
3230
+ changeTree.forEachChild((childChangeTree) => output += this.debugRefIds(childChangeTree.ref, showContents, level + 1));
2931
3231
  return output;
2932
3232
  }
2933
3233
  /**
@@ -2944,30 +3244,40 @@ class Schema {
2944
3244
  const changeSetName = (isEncodeAll) ? "allChanges" : "changes";
2945
3245
  let output = `${instance.constructor.name} (${changeTree.refId}) -> .${changeSetName}:\n`;
2946
3246
  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`);
3247
+ changeSet.operations
3248
+ .filter(op => op)
3249
+ .forEach((index) => {
3250
+ const operation = changeTree.indexedOperations[index];
3251
+ console.log({ index, operation });
3252
+ output += `- [${index}]: ${exports.OPERATION[operation]} (${JSON.stringify(changeTree.getValue(Number(index), isEncodeAll))})\n`;
3253
+ });
2950
3254
  }
2951
3255
  dumpChangeSet(changeSet);
2952
3256
  // display filtered changes
2953
- if (!isEncodeAll && changeTree.filteredChanges?.size > 0) {
3257
+ if (!isEncodeAll &&
3258
+ changeTree.filteredChanges &&
3259
+ (changeTree.filteredChanges.operations).filter(op => op).length > 0) {
2954
3260
  output += `${instance.constructor.name} (${changeTree.refId}) -> .filteredChanges:\n`;
2955
3261
  dumpChangeSet(changeTree.filteredChanges);
2956
3262
  }
2957
3263
  // display filtered changes
2958
- if (isEncodeAll && changeTree.allFilteredChanges?.size > 0) {
3264
+ if (isEncodeAll &&
3265
+ changeTree.allFilteredChanges &&
3266
+ (changeTree.allFilteredChanges.operations).filter(op => op).length > 0) {
2959
3267
  output += `${instance.constructor.name} (${changeTree.refId}) -> .allFilteredChanges:\n`;
2960
3268
  dumpChangeSet(changeTree.allFilteredChanges);
2961
3269
  }
2962
3270
  return output;
2963
3271
  }
2964
- static debugChangesDeep(ref) {
3272
+ static debugChangesDeep(ref, changeSetName = "changes") {
2965
3273
  let output = "";
2966
3274
  const rootChangeTree = ref[$changes];
3275
+ const root = rootChangeTree.root;
2967
3276
  const changeTrees = new Map();
2968
- let totalInstances = 0;
3277
+ const instanceRefIds = [];
2969
3278
  let totalOperations = 0;
2970
- for (const [changeTree, changes] of (rootChangeTree.root.changes.entries())) {
3279
+ for (const [refId, changes] of Object.entries(root[changeSetName])) {
3280
+ const changeTree = root.changeTrees[refId];
2971
3281
  let includeChangeTree = false;
2972
3282
  let parentChangeTrees = [];
2973
3283
  let parentChangeTree = changeTree.parent?.[$changes];
@@ -2985,14 +3295,14 @@ class Schema {
2985
3295
  }
2986
3296
  }
2987
3297
  if (includeChangeTree) {
2988
- totalInstances += 1;
2989
- totalOperations += changes.size;
3298
+ instanceRefIds.push(changeTree.refId);
3299
+ totalOperations += Object.keys(changes).length;
2990
3300
  changeTrees.set(changeTree, parentChangeTrees.reverse());
2991
3301
  }
2992
3302
  }
2993
3303
  output += "---\n";
2994
3304
  output += `root refId: ${rootChangeTree.refId}\n`;
2995
- output += `Total instances: ${totalInstances}\n`;
3305
+ output += `Total instances: ${instanceRefIds.length} (refIds: ${instanceRefIds.join(", ")})\n`;
2996
3306
  output += `Total changes: ${totalOperations}\n`;
2997
3307
  output += "---\n";
2998
3308
  // based on root.changes, display a tree of changes that has the "ref" instance as parent
@@ -3004,12 +3314,13 @@ class Schema {
3004
3314
  visitedParents.add(parentChangeTree);
3005
3315
  }
3006
3316
  });
3007
- const changes = changeTree.changes;
3317
+ const changes = changeTree.indexedOperations;
3008
3318
  const level = parentChangeTrees.length;
3009
3319
  const indent = getIndent(level);
3010
3320
  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) {
3321
+ output += `${indent}${parentIndex}${changeTree.ref.constructor.name} (refId: ${changeTree.refId}) - changes: ${Object.keys(changes).length}\n`;
3322
+ for (const index in changes) {
3323
+ const operation = changes[index];
3013
3324
  output += `${getIndent(level + 1)}${exports.OPERATION[operation]}: ${index}\n`;
3014
3325
  }
3015
3326
  }
@@ -3043,6 +3354,7 @@ class CollectionSchema {
3043
3354
  this.$indexes = new Map();
3044
3355
  this.$refId = 0;
3045
3356
  this[$changes] = new ChangeTree(this);
3357
+ this[$changes].indexes = {};
3046
3358
  if (initialValues) {
3047
3359
  initialValues.forEach((v) => this.add(v));
3048
3360
  }
@@ -3198,6 +3510,7 @@ class SetSchema {
3198
3510
  this.$indexes = new Map();
3199
3511
  this.$refId = 0;
3200
3512
  this[$changes] = new ChangeTree(this);
3513
+ this[$changes].indexes = {};
3201
3514
  if (initialValues) {
3202
3515
  initialValues.forEach((v) => this.add(v));
3203
3516
  }
@@ -3353,6 +3666,8 @@ LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
3353
3666
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3354
3667
  PERFORMANCE OF THIS SOFTWARE.
3355
3668
  ***************************************************************************** */
3669
+ /* global Reflect, Promise, SuppressedError, Symbol */
3670
+
3356
3671
 
3357
3672
  function __decorate(decorators, target, key, desc) {
3358
3673
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -3366,37 +3681,127 @@ typeof SuppressedError === "function" ? SuppressedError : function (error, suppr
3366
3681
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3367
3682
  };
3368
3683
 
3684
+ function spliceOne(arr, index) {
3685
+ // manually splice an array
3686
+ if (index === -1 || index >= arr.length) {
3687
+ return false;
3688
+ }
3689
+ const len = arr.length - 1;
3690
+ for (let i = index; i < len; i++) {
3691
+ arr[i] = arr[i + 1];
3692
+ }
3693
+ arr.length = len;
3694
+ return true;
3695
+ }
3696
+
3697
+ class Root {
3698
+ constructor(types) {
3699
+ this.types = types;
3700
+ this.nextUniqueId = 0;
3701
+ this.refCount = {};
3702
+ this.changeTrees = {};
3703
+ // all changes
3704
+ this.allChanges = [];
3705
+ this.allFilteredChanges = []; // TODO: do not initialize it if filters are not used
3706
+ // pending changes to be encoded
3707
+ this.changes = [];
3708
+ this.filteredChanges = []; // TODO: do not initialize it if filters are not used
3709
+ }
3710
+ getNextUniqueId() {
3711
+ return this.nextUniqueId++;
3712
+ }
3713
+ add(changeTree) {
3714
+ // FIXME: move implementation of `ensureRefId` to `Root` class
3715
+ changeTree.ensureRefId();
3716
+ const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
3717
+ if (isNewChangeTree) {
3718
+ this.changeTrees[changeTree.refId] = changeTree;
3719
+ }
3720
+ const previousRefCount = this.refCount[changeTree.refId];
3721
+ if (previousRefCount === 0) {
3722
+ //
3723
+ // When a ChangeTree is re-added, it means that it was previously removed.
3724
+ // We need to re-add all changes to the `changes` map.
3725
+ //
3726
+ const ops = changeTree.allChanges.operations;
3727
+ let len = ops.length;
3728
+ while (len--) {
3729
+ changeTree.indexedOperations[ops[len]] = exports.OPERATION.ADD;
3730
+ setOperationAtIndex(changeTree.changes, len);
3731
+ }
3732
+ }
3733
+ this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
3734
+ return isNewChangeTree;
3735
+ }
3736
+ remove(changeTree) {
3737
+ const refCount = (this.refCount[changeTree.refId]) - 1;
3738
+ if (refCount <= 0) {
3739
+ //
3740
+ // Only remove "root" reference if it's the last reference
3741
+ //
3742
+ changeTree.root = undefined;
3743
+ delete this.changeTrees[changeTree.refId];
3744
+ this.removeChangeFromChangeSet("allChanges", changeTree);
3745
+ this.removeChangeFromChangeSet("changes", changeTree);
3746
+ if (changeTree.filteredChanges) {
3747
+ this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
3748
+ this.removeChangeFromChangeSet("filteredChanges", changeTree);
3749
+ }
3750
+ this.refCount[changeTree.refId] = 0;
3751
+ }
3752
+ else {
3753
+ this.refCount[changeTree.refId] = refCount;
3754
+ }
3755
+ changeTree.forEachChild((child, _) => this.remove(child));
3756
+ return refCount;
3757
+ }
3758
+ removeChangeFromChangeSet(changeSetName, changeTree) {
3759
+ const changeSet = this[changeSetName];
3760
+ const index = changeSet.indexOf(changeTree);
3761
+ if (index !== -1) {
3762
+ spliceOne(changeSet, index);
3763
+ // changeSet[index] = undefined;
3764
+ }
3765
+ }
3766
+ clear() {
3767
+ this.changes.length = 0;
3768
+ }
3769
+ }
3770
+
3369
3771
  class Encoder {
3370
3772
  static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
3371
- constructor(root) {
3773
+ constructor(state) {
3372
3774
  this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
3373
- this.setRoot(root);
3374
3775
  //
3375
3776
  // TODO: cache and restore "Context" based on root schema
3376
3777
  // (to avoid creating a new context for every new room)
3377
3778
  //
3378
- this.context = new TypeContext(root.constructor);
3779
+ this.context = new TypeContext(state.constructor);
3780
+ this.root = new Root(this.context);
3781
+ this.setState(state);
3379
3782
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
3380
3783
  // this.context.schemas.forEach((id, schema) => {
3381
3784
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3382
3785
  // });
3383
3786
  }
3384
- setRoot(state) {
3385
- this.root = new Root();
3787
+ setState(state) {
3386
3788
  this.state = state;
3387
- state[$changes].setRoot(this.root);
3789
+ this.state[$changes].setRoot(this.root);
3388
3790
  }
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;
3791
+ 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
3792
+ ) {
3392
3793
  const hasView = (view !== undefined);
3393
3794
  const rootChangeTree = this.state[$changes];
3394
- const changeTreesIterator = changeTrees.entries();
3395
- for (const [changeTree, changes] of changeTreesIterator) {
3795
+ const shouldDiscardChanges = !isEncodeAll && !hasView;
3796
+ const changeTrees = this.root[changeSetName];
3797
+ for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
3798
+ const changeTree = changeTrees[i];
3799
+ const operations = changeTree[changeSetName];
3396
3800
  const ref = changeTree.ref;
3397
- const ctor = ref['constructor'];
3801
+ const ctor = ref.constructor;
3398
3802
  const encoder = ctor[$encoder];
3399
3803
  const filter = ctor[$filter];
3804
+ const metadata = ctor[Symbol.metadata];
3400
3805
  if (hasView) {
3401
3806
  if (!view.items.has(changeTree)) {
3402
3807
  view.invisible.add(changeTree);
@@ -3407,12 +3812,18 @@ class Encoder {
3407
3812
  }
3408
3813
  }
3409
3814
  // 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);
3815
+ // (unless it "hasView", which will need to revisit the root)
3816
+ if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3817
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3818
+ encode.number(buffer, changeTree.refId, it);
3413
3819
  }
3414
- const changesIterator = changes.entries();
3415
- for (const [fieldIndex, operation] of changesIterator) {
3820
+ for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
3821
+ const fieldIndex = operations.operations[j];
3822
+ const operation = (fieldIndex < 0)
3823
+ ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
3824
+ : (isEncodeAll)
3825
+ ? exports.OPERATION.ADD
3826
+ : changeTree.indexedOperations[fieldIndex];
3416
3827
  //
3417
3828
  // first pass (encodeAll), identify "filtered" operations without encoding them
3418
3829
  // they will be encoded per client, based on their view.
@@ -3420,93 +3831,109 @@ class Encoder {
3420
3831
  // TODO: how can we optimize filtering out "encode all" operations?
3421
3832
  // TODO: avoid checking if no view tags were defined
3422
3833
  //
3423
- if (filter && !filter(ref, fieldIndex, view)) {
3424
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
3834
+ if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
3425
3835
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
3426
3836
  // view?.invisible.add(changeTree);
3427
3837
  continue;
3428
3838
  }
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);
3839
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
3435
3840
  }
3841
+ // if (shouldDiscardChanges) {
3842
+ // changeTree.discard();
3843
+ // changeTree.isNew = false; // Not a new instance anymore
3844
+ // }
3436
3845
  }
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);
3846
+ if (it.offset > buffer.byteLength) {
3847
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
3848
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
3849
+
3850
+ import { Encoder } from "@colyseus/schema";
3851
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
3852
+ `);
3440
3853
  //
3441
3854
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
3442
3855
  //
3443
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
3444
- return this.encode({ offset: initialOffset }, view);
3856
+ buffer = Buffer.alloc(newSize);
3857
+ // assign resized buffer to local sharedBuffer
3858
+ if (buffer === this.sharedBuffer) {
3859
+ this.sharedBuffer = buffer;
3860
+ }
3861
+ return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
3445
3862
  }
3446
3863
  else {
3447
3864
  //
3448
3865
  // only clear changes after making sure buffer resize is not required.
3449
3866
  //
3450
- if (!isEncodeAll && !hasView) {
3867
+ if (shouldDiscardChanges) {
3451
3868
  //
3452
- // FIXME: avoid iterating over change trees twice.
3869
+ // TODO: avoid iterating over change trees twice.
3453
3870
  //
3454
- this.onEndEncode(changeTrees);
3871
+ for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
3872
+ const changeTree = changeTrees[i];
3873
+ changeTree.discard();
3874
+ changeTree.isNew = false; // Not a new instance anymore
3875
+ }
3455
3876
  }
3456
- // return bytes;
3457
- return bytes.slice(0, it.offset);
3877
+ return buffer.subarray(0, it.offset);
3458
3878
  }
3459
3879
  }
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);
3880
+ encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3881
+ return this.encode(it, undefined, buffer, "allChanges", true);
3466
3882
  }
3467
3883
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3468
3884
  const viewOffset = it.offset;
3469
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
3470
- // this.debugAllFilteredChanges();
3471
3885
  // try to encode "filtered" changes
3472
- this.encode(it, view, bytes, this.root.allFilteredChanges);
3886
+ this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
3473
3887
  return Buffer.concat([
3474
- bytes.slice(0, sharedOffset),
3475
- bytes.slice(viewOffset, it.offset)
3888
+ bytes.subarray(0, sharedOffset),
3889
+ bytes.subarray(viewOffset, it.offset)
3476
3890
  ]);
3477
3891
  }
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
- // }
3892
+ debugChanges(field) {
3893
+ const rootChangeSet = (typeof (field) === "string")
3894
+ ? this.root[field]
3895
+ : field;
3896
+ rootChangeSet.forEach((changeTree) => {
3897
+ const changeSet = changeTree[field];
3898
+ const metadata = changeTree.ref.constructor[Symbol.metadata];
3899
+ console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
3900
+ for (const index in changeSet) {
3901
+ const op = changeSet[index];
3902
+ console.log(" ->", {
3903
+ index,
3904
+ field: metadata?.[index],
3905
+ op: exports.OPERATION[op],
3906
+ });
3907
+ }
3908
+ });
3909
+ }
3488
3910
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3489
3911
  const viewOffset = it.offset;
3490
- // try to encode "filtered" changes
3491
- this.encode(it, view, bytes, this.root.filteredChanges);
3492
3912
  // 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);
3913
+ const refIds = Object.keys(view.changes);
3914
+ for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
3915
+ const refId = refIds[i];
3916
+ const changes = view.changes[refId];
3917
+ const changeTree = this.root.changeTrees[refId];
3918
+ if (changeTree === undefined ||
3919
+ Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
3920
+ ) {
3921
+ // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
3498
3922
  continue;
3499
3923
  }
3500
3924
  const ref = changeTree.ref;
3501
- const ctor = ref['constructor'];
3925
+ const ctor = ref.constructor;
3502
3926
  const encoder = ctor[$encoder];
3927
+ const metadata = ctor[Symbol.metadata];
3503
3928
  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) {
3929
+ encode.number(bytes, changeTree.refId, it);
3930
+ const keys = Object.keys(changes);
3931
+ for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
3932
+ const key = keys[i];
3933
+ const operation = changes[key];
3507
3934
  // isEncodeAll = false
3508
3935
  // hasView = true
3509
- encoder(this, bytes, changeTree, fieldIndex, operation, it, false, true);
3936
+ encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
3510
3937
  }
3511
3938
  }
3512
3939
  //
@@ -3514,51 +3941,62 @@ class Encoder {
3514
3941
  // (to allow re-using StateView's for multiple clients)
3515
3942
  //
3516
3943
  // clear "view" changes after encoding
3517
- view.changes.clear();
3944
+ view.changes = {};
3945
+ // try to encode "filtered" changes
3946
+ this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
3518
3947
  return Buffer.concat([
3519
- bytes.slice(0, sharedOffset),
3520
- bytes.slice(viewOffset, it.offset)
3948
+ bytes.subarray(0, sharedOffset),
3949
+ bytes.subarray(viewOffset, it.offset)
3521
3950
  ]);
3522
3951
  }
3523
3952
  onEndEncode(changeTrees = this.root.changes) {
3524
- const changeTreesIterator = changeTrees.entries();
3525
- for (const [changeTree, _] of changeTreesIterator) {
3526
- changeTree.endEncode();
3527
- }
3953
+ // changeTrees.forEach(function(changeTree) {
3954
+ // changeTree.endEncode();
3955
+ // });
3956
+ // for (const refId in changeTrees) {
3957
+ // const changeTree = this.root.changeTrees[refId];
3958
+ // changeTree.endEncode();
3959
+ // // changeTree.changes.clear();
3960
+ // // // ArraySchema and MapSchema have a custom "encode end" method
3961
+ // // changeTree.ref[$onEncodeEnd]?.();
3962
+ // // // Not a new instance anymore
3963
+ // // delete changeTree[$isNew];
3964
+ // }
3528
3965
  }
3529
3966
  discardChanges() {
3530
3967
  // discard shared changes
3531
- if (this.root.changes.size > 0) {
3532
- this.onEndEncode(this.root.changes);
3533
- this.root.changes.clear();
3968
+ let length = this.root.changes.length;
3969
+ if (length > 0) {
3970
+ while (length--) {
3971
+ this.root.changes[length]?.endEncode();
3972
+ }
3973
+ this.root.changes.length = 0;
3534
3974
  }
3535
3975
  // discard filtered changes
3536
- if (this.root.filteredChanges.size > 0) {
3537
- this.onEndEncode(this.root.filteredChanges);
3538
- this.root.filteredChanges.clear();
3976
+ length = this.root.filteredChanges.length;
3977
+ if (length > 0) {
3978
+ while (length--) {
3979
+ this.root.filteredChanges[length]?.endEncode();
3980
+ }
3981
+ this.root.filteredChanges.length = 0;
3539
3982
  }
3540
3983
  }
3541
3984
  tryEncodeTypeId(bytes, baseType, targetType, it) {
3542
3985
  const baseTypeId = this.context.getTypeId(baseType);
3543
3986
  const targetTypeId = this.context.getTypeId(targetType);
3987
+ if (targetTypeId === undefined) {
3988
+ 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.`);
3989
+ return;
3990
+ }
3544
3991
  if (baseTypeId !== targetTypeId) {
3545
3992
  bytes[it.offset++] = TYPE_ID & 255;
3546
- number$1(bytes, targetTypeId, it);
3993
+ encode.number(bytes, targetTypeId, it);
3547
3994
  }
3548
3995
  }
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];
3996
+ get hasChanges() {
3997
+ return (this.root.changes.length > 0 ||
3998
+ this.root.filteredChanges.length > 0);
3559
3999
  }
3560
- arr.length = len;
3561
- return true;
3562
4000
  }
3563
4001
 
3564
4002
  class DecodingWarning extends Error {
@@ -3623,6 +4061,7 @@ class ReferenceTracker {
3623
4061
  clearRefs() {
3624
4062
  this.refs.clear();
3625
4063
  this.deletedRefs.clear();
4064
+ this.callbacks = {};
3626
4065
  this.refCounts = {};
3627
4066
  }
3628
4067
  // for decoding
@@ -3639,8 +4078,9 @@ class ReferenceTracker {
3639
4078
  // Ensure child schema instances have their references removed as well.
3640
4079
  //
3641
4080
  if (Metadata.isValidInstance(ref)) {
3642
- const metadata = ref['constructor'][Symbol.metadata];
3643
- for (const field in metadata) {
4081
+ const metadata = ref.constructor[Symbol.metadata];
4082
+ for (const index in metadata) {
4083
+ const field = metadata[index].name;
3644
4084
  const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
3645
4085
  if (childRefId) {
3646
4086
  this.removeRef(childRefId);
@@ -3687,21 +4127,21 @@ class ReferenceTracker {
3687
4127
  class Decoder {
3688
4128
  constructor(root, context) {
3689
4129
  this.currentRefId = 0;
3690
- this.setRoot(root);
4130
+ this.setState(root);
3691
4131
  this.context = context || new TypeContext(root.constructor);
3692
4132
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
3693
4133
  // this.context.schemas.forEach((id, schema) => {
3694
4134
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3695
4135
  // });
3696
4136
  }
3697
- setRoot(root) {
4137
+ setState(root) {
3698
4138
  this.state = root;
3699
- this.$root = new ReferenceTracker();
3700
- this.$root.addRef(0, root);
4139
+ this.root = new ReferenceTracker();
4140
+ this.root.addRef(0, root);
3701
4141
  }
3702
4142
  decode(bytes, it = { offset: 0 }, ref = this.state) {
3703
4143
  const allChanges = [];
3704
- const $root = this.$root;
4144
+ const $root = this.root;
3705
4145
  const totalBytes = bytes.byteLength;
3706
4146
  let decoder = ref['constructor'][$decoder];
3707
4147
  this.currentRefId = 0;
@@ -3711,7 +4151,7 @@ class Decoder {
3711
4151
  //
3712
4152
  if (bytes[it.offset] == SWITCH_TO_STRUCTURE) {
3713
4153
  it.offset++;
3714
- this.currentRefId = number(bytes, it);
4154
+ this.currentRefId = decode.number(bytes, it);
3715
4155
  const nextRef = $root.refs.get(this.currentRefId);
3716
4156
  //
3717
4157
  // Trying to access a reference that haven't been decoded yet.
@@ -3721,7 +4161,7 @@ class Decoder {
3721
4161
  }
3722
4162
  ref[$onDecodeEnd]?.();
3723
4163
  ref = nextRef;
3724
- decoder = ref['constructor'][$decoder];
4164
+ decoder = ref.constructor[$decoder];
3725
4165
  continue;
3726
4166
  }
3727
4167
  const result = decoder(this, bytes, it, ref, allChanges);
@@ -3733,9 +4173,9 @@ class Decoder {
3733
4173
  //
3734
4174
  const nextIterator = { offset: it.offset };
3735
4175
  while (it.offset < totalBytes) {
3736
- if (switchStructureCheck(bytes, it)) {
4176
+ if (bytes[it.offset] === SWITCH_TO_STRUCTURE) {
3737
4177
  nextIterator.offset = it.offset + 1;
3738
- if ($root.refs.has(number(bytes, nextIterator))) {
4178
+ if ($root.refs.has(decode.number(bytes, nextIterator))) {
3739
4179
  break;
3740
4180
  }
3741
4181
  }
@@ -3756,7 +4196,7 @@ class Decoder {
3756
4196
  let type;
3757
4197
  if (bytes[it.offset] === TYPE_ID) {
3758
4198
  it.offset++;
3759
- const type_id = number(bytes, it);
4199
+ const type_id = decode.number(bytes, it);
3760
4200
  type = this.context.get(type_id);
3761
4201
  }
3762
4202
  return type || defaultType;
@@ -3782,7 +4222,7 @@ class Decoder {
3782
4222
  previousValue: value
3783
4223
  });
3784
4224
  if (needRemoveRef) {
3785
- this.$root.removeRef(this.$root.refIds.get(value));
4225
+ this.root.removeRef(this.root.refIds.get(value));
3786
4226
  }
3787
4227
  });
3788
4228
  }
@@ -3822,14 +4262,27 @@ class Reflection extends Schema {
3822
4262
  super(...arguments);
3823
4263
  this.types = new ArraySchema();
3824
4264
  }
3825
- static encode(instance, context, it = { offset: 0 }) {
3826
- if (!context) {
3827
- context = new TypeContext(instance.constructor);
3828
- }
4265
+ /**
4266
+ * Encodes the TypeContext of an Encoder into a buffer.
4267
+ *
4268
+ * @param encoder Encoder instance
4269
+ * @param it
4270
+ * @returns
4271
+ */
4272
+ static encode(encoder, it = { offset: 0 }) {
4273
+ const context = encoder.context;
3829
4274
  const reflection = new Reflection();
3830
- const encoder = new Encoder(reflection);
4275
+ const reflectionEncoder = new Encoder(reflection);
4276
+ // rootType is usually the first schema passed to the Encoder
4277
+ // (unless it inherits from another schema)
4278
+ const rootType = context.schemas.get(encoder.state.constructor);
4279
+ if (rootType > 0) {
4280
+ reflection.rootType = rootType;
4281
+ }
3831
4282
  const buildType = (currentType, metadata) => {
3832
- for (const fieldName in metadata) {
4283
+ for (const fieldIndex in metadata) {
4284
+ const index = Number(fieldIndex);
4285
+ const fieldName = metadata[index].name;
3833
4286
  // skip fields from parent classes
3834
4287
  if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
3835
4288
  continue;
@@ -3837,7 +4290,7 @@ class Reflection extends Schema {
3837
4290
  const field = new ReflectionField();
3838
4291
  field.name = fieldName;
3839
4292
  let fieldType;
3840
- const type = metadata[fieldName].type;
4293
+ const type = metadata[index].type;
3841
4294
  if (typeof (type) === "string") {
3842
4295
  fieldType = type;
3843
4296
  }
@@ -3879,65 +4332,335 @@ class Reflection extends Schema {
3879
4332
  }
3880
4333
  buildType(type, klass[Symbol.metadata]);
3881
4334
  }
3882
- const buf = encoder.encodeAll(it);
4335
+ const buf = reflectionEncoder.encodeAll(it);
3883
4336
  return Buffer.from(buf, 0, it.offset);
3884
4337
  }
4338
+ /**
4339
+ * Decodes the TypeContext from a buffer into a Decoder instance.
4340
+ *
4341
+ * @param bytes Reflection.encode() output
4342
+ * @param it
4343
+ * @returns Decoder instance
4344
+ */
3885
4345
  static decode(bytes, it) {
3886
4346
  const reflection = new Reflection();
3887
4347
  const reflectionDecoder = new Decoder(reflection);
3888
4348
  reflectionDecoder.decode(bytes, it);
3889
- const context = new TypeContext();
3890
- const schemaTypes = reflection.types.reduce((types, reflectionType) => {
3891
- const parentKlass = types[reflectionType.extendsId] || Schema;
3892
- const schema = class _ extends parentKlass {
4349
+ const typeContext = new TypeContext();
4350
+ // 1st pass, initialize metadata + inheritance
4351
+ reflection.types.forEach((reflectionType) => {
4352
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4353
+ const schema = class _ extends parentClass {
3893
4354
  };
3894
- // const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
3895
- const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
3896
- Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
3897
4355
  // register for inheritance support
3898
4356
  TypeContext.register(schema);
3899
- const typeid = reflectionType.id;
3900
- types[typeid] = schema;
3901
- context.add(schema, typeid);
3902
- return types;
4357
+ // // for inheritance support
4358
+ // Metadata.initialize(schema);
4359
+ typeContext.add(schema, reflectionType.id);
3903
4360
  }, {});
3904
- reflection.types.forEach((reflectionType) => {
3905
- const schemaType = schemaTypes[reflectionType.id];
3906
- const metadata = schemaType[Symbol.metadata];
3907
- const parentKlass = reflection.types[reflectionType.extendsId];
3908
- const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
4361
+ // define fields
4362
+ const addFields = (metadata, reflectionType, parentFieldIndex) => {
3909
4363
  reflectionType.fields.forEach((field, i) => {
3910
4364
  const fieldIndex = parentFieldIndex + i;
3911
4365
  if (field.referencedType !== undefined) {
3912
4366
  let fieldType = field.type;
3913
- let refType = schemaTypes[field.referencedType];
4367
+ let refType = typeContext.get(field.referencedType);
3914
4368
  // map or array of primitive type (-1)
3915
4369
  if (!refType) {
3916
4370
  const typeInfo = field.type.split(":");
3917
4371
  fieldType = typeInfo[0];
3918
- refType = typeInfo[1];
4372
+ refType = typeInfo[1]; // string
3919
4373
  }
3920
4374
  if (fieldType === "ref") {
3921
- // type(refType)(schemaType.prototype, field.name);
3922
4375
  Metadata.addField(metadata, fieldIndex, field.name, refType);
3923
4376
  }
3924
4377
  else {
3925
- // type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
3926
4378
  Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
3927
4379
  }
3928
4380
  }
3929
4381
  else {
3930
- // type(field.type as PrimitiveType)(schemaType.prototype, field.name);
3931
4382
  Metadata.addField(metadata, fieldIndex, field.name, field.type);
3932
4383
  }
3933
4384
  });
4385
+ };
4386
+ // 2nd pass, set fields
4387
+ reflection.types.forEach((reflectionType) => {
4388
+ const schema = typeContext.get(reflectionType.id);
4389
+ // for inheritance support
4390
+ const metadata = Metadata.initialize(schema);
4391
+ const inheritedTypes = [];
4392
+ let parentType = reflectionType;
4393
+ do {
4394
+ inheritedTypes.push(parentType);
4395
+ parentType = reflection.types.find((t) => t.id === parentType.extendsId);
4396
+ } while (parentType);
4397
+ let parentFieldIndex = 0;
4398
+ inheritedTypes.reverse().forEach((reflectionType) => {
4399
+ // add fields from all inherited classes
4400
+ // TODO: refactor this to avoid adding fields from parent classes
4401
+ addFields(metadata, reflectionType, parentFieldIndex);
4402
+ parentFieldIndex += reflectionType.fields.length;
4403
+ });
3934
4404
  });
3935
- return new (schemaTypes[0])();
4405
+ const state = new (typeContext.get(reflection.rootType || 0))();
4406
+ return new Decoder(state, typeContext);
3936
4407
  }
3937
4408
  }
3938
4409
  __decorate([
3939
4410
  type([ReflectionType])
3940
4411
  ], Reflection.prototype, "types", void 0);
4412
+ __decorate([
4413
+ type("number")
4414
+ ], Reflection.prototype, "rootType", void 0);
4415
+
4416
+ function getDecoderStateCallbacks(decoder) {
4417
+ const $root = decoder.root;
4418
+ const callbacks = $root.callbacks;
4419
+ const onAddCalls = new WeakMap();
4420
+ let currentOnAddCallback;
4421
+ decoder.triggerChanges = function (allChanges) {
4422
+ const uniqueRefIds = new Set();
4423
+ for (let i = 0, l = allChanges.length; i < l; i++) {
4424
+ const change = allChanges[i];
4425
+ const refId = change.refId;
4426
+ const ref = change.ref;
4427
+ const $callbacks = callbacks[refId];
4428
+ if (!$callbacks) {
4429
+ continue;
4430
+ }
4431
+ //
4432
+ // trigger onRemove on child structure.
4433
+ //
4434
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
4435
+ change.previousValue instanceof Schema) {
4436
+ const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
4437
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4438
+ deleteCallbacks[i]();
4439
+ }
4440
+ }
4441
+ if (ref instanceof Schema) {
4442
+ //
4443
+ // Handle schema instance
4444
+ //
4445
+ if (!uniqueRefIds.has(refId)) {
4446
+ // trigger onChange
4447
+ const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
4448
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4449
+ replaceCallbacks[i]();
4450
+ // try {
4451
+ // } catch (e) {
4452
+ // console.error(e);
4453
+ // }
4454
+ }
4455
+ }
4456
+ if ($callbacks.hasOwnProperty(change.field)) {
4457
+ const fieldCallbacks = $callbacks[change.field];
4458
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
4459
+ fieldCallbacks[i](change.value, change.previousValue);
4460
+ // try {
4461
+ // } catch (e) {
4462
+ // console.error(e);
4463
+ // }
4464
+ }
4465
+ }
4466
+ }
4467
+ else {
4468
+ //
4469
+ // Handle collection of items
4470
+ //
4471
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
4472
+ //
4473
+ // FIXME: `previousValue` should always be available.
4474
+ //
4475
+ if (change.previousValue !== undefined) {
4476
+ // triger onRemove
4477
+ const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
4478
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4479
+ deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
4480
+ }
4481
+ }
4482
+ // Handle DELETE_AND_ADD operations
4483
+ if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
4484
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4485
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4486
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4487
+ }
4488
+ }
4489
+ }
4490
+ else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
4491
+ // triger onAdd
4492
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4493
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4494
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4495
+ }
4496
+ }
4497
+ // trigger onChange
4498
+ if (change.value !== change.previousValue) {
4499
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
4500
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4501
+ replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4502
+ }
4503
+ }
4504
+ }
4505
+ uniqueRefIds.add(refId);
4506
+ }
4507
+ };
4508
+ function getProxy(metadataOrType, context) {
4509
+ let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
4510
+ let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4511
+ (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4512
+ if (metadata && !isCollection) {
4513
+ const onAddListen = function (ref, prop, callback, immediate) {
4514
+ // immediate trigger
4515
+ if (immediate &&
4516
+ context.instance[prop] !== undefined &&
4517
+ !onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
4518
+ ) {
4519
+ callback(context.instance[prop], undefined);
4520
+ }
4521
+ return $root.addCallback($root.refIds.get(ref), prop, callback);
4522
+ };
4523
+ /**
4524
+ * Schema instances
4525
+ */
4526
+ return new Proxy({
4527
+ listen: function listen(prop, callback, immediate = true) {
4528
+ if (context.instance) {
4529
+ return onAddListen(context.instance, prop, callback, immediate);
4530
+ }
4531
+ else {
4532
+ // collection instance not received yet
4533
+ let detachCallback = () => { };
4534
+ context.onInstanceAvailable((ref, existing) => {
4535
+ detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4536
+ });
4537
+ return () => detachCallback();
4538
+ }
4539
+ },
4540
+ onChange: function onChange(callback) {
4541
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
4542
+ },
4543
+ //
4544
+ // TODO: refactor `bindTo()` implementation.
4545
+ // There is room for improvement.
4546
+ //
4547
+ bindTo: function bindTo(targetObject, properties) {
4548
+ if (!properties) {
4549
+ properties = Object.keys(metadata).map((index) => metadata[index].name);
4550
+ }
4551
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4552
+ properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4553
+ });
4554
+ }
4555
+ }, {
4556
+ get(target, prop) {
4557
+ const metadataField = metadata[metadata[prop]];
4558
+ if (metadataField) {
4559
+ const instance = context.instance?.[prop];
4560
+ const onInstanceAvailable = ((callback) => {
4561
+ const unbind = $(context.instance).listen(prop, (value, _) => {
4562
+ callback(value, false);
4563
+ // FIXME: by "unbinding" the callback here,
4564
+ // it will not support when the server
4565
+ // re-instantiates the instance.
4566
+ //
4567
+ unbind?.();
4568
+ }, false);
4569
+ // has existing value
4570
+ if ($root.refIds.get(instance) !== undefined) {
4571
+ callback(instance, true);
4572
+ }
4573
+ });
4574
+ return getProxy(metadataField.type, {
4575
+ // make sure refId is available, otherwise need to wait for the instance to be available.
4576
+ instance: ($root.refIds.get(instance) && instance),
4577
+ parentInstance: context.instance,
4578
+ onInstanceAvailable,
4579
+ });
4580
+ }
4581
+ else {
4582
+ // accessing the function
4583
+ return target[prop];
4584
+ }
4585
+ },
4586
+ has(target, prop) { return metadata[prop] !== undefined; },
4587
+ set(_, _1, _2) { throw new Error("not allowed"); },
4588
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4589
+ });
4590
+ }
4591
+ else {
4592
+ /**
4593
+ * Collection instances
4594
+ */
4595
+ const onAdd = function (ref, callback, immediate) {
4596
+ // Trigger callback on existing items
4597
+ if (immediate) {
4598
+ ref.forEach((v, k) => callback(v, k));
4599
+ }
4600
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
4601
+ onAddCalls.set(callback, true);
4602
+ currentOnAddCallback = callback;
4603
+ callback(value, key);
4604
+ onAddCalls.delete(callback);
4605
+ currentOnAddCallback = undefined;
4606
+ });
4607
+ };
4608
+ const onRemove = function (ref, callback) {
4609
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
4610
+ };
4611
+ return new Proxy({
4612
+ onAdd: function (callback, immediate = true) {
4613
+ //
4614
+ // https://github.com/colyseus/schema/issues/147
4615
+ // If parent instance has "onAdd" registered, avoid triggering immediate callback.
4616
+ //
4617
+ if (context.instance) {
4618
+ return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
4619
+ }
4620
+ else if (context.onInstanceAvailable) {
4621
+ // collection instance not received yet
4622
+ let detachCallback = () => { };
4623
+ context.onInstanceAvailable((ref, existing) => {
4624
+ detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4625
+ });
4626
+ return () => detachCallback();
4627
+ }
4628
+ },
4629
+ onRemove: function (callback) {
4630
+ if (context.onInstanceAvailable) {
4631
+ // collection instance not received yet
4632
+ let detachCallback = () => { };
4633
+ context.onInstanceAvailable((ref) => {
4634
+ detachCallback = onRemove(ref, callback);
4635
+ });
4636
+ return () => detachCallback();
4637
+ }
4638
+ else if (context.instance) {
4639
+ return onRemove(context.instance, callback);
4640
+ }
4641
+ },
4642
+ }, {
4643
+ get(target, prop) {
4644
+ if (!target[prop]) {
4645
+ throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
4646
+ }
4647
+ return target[prop];
4648
+ },
4649
+ has(target, prop) { return target[prop] !== undefined; },
4650
+ set(_, _1, _2) { throw new Error("not allowed"); },
4651
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4652
+ });
4653
+ }
4654
+ }
4655
+ function $(instance) {
4656
+ return getProxy(undefined, { instance });
4657
+ }
4658
+ return $;
4659
+ }
4660
+
4661
+ function getRawChangesCallback(decoder, callback) {
4662
+ decoder.triggerChanges = callback;
4663
+ }
3941
4664
 
3942
4665
  class StateView {
3943
4666
  constructor() {
@@ -3953,31 +4676,32 @@ class StateView {
3953
4676
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
3954
4677
  * (This is used to force encoding a property, even if it was not changed)
3955
4678
  */
3956
- this.changes = new Map();
4679
+ this.changes = {};
3957
4680
  }
3958
4681
  // TODO: allow to set multiple tags at once
3959
- add(obj, tag = DEFAULT_VIEW_TAG) {
4682
+ add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
3960
4683
  if (!obj[$changes]) {
3961
4684
  console.warn("StateView#add(), invalid object:", obj);
3962
4685
  return this;
3963
4686
  }
3964
- let changeTree = obj[$changes];
3965
- this.items.add(changeTree);
3966
- // Add children of this ChangeTree to this view
3967
- changeTree.forEachChild((change, _) => this.add(change.ref, tag));
3968
- // FIXME: ArraySchema/MapSchema does not have metadata
4687
+ // FIXME: ArraySchema/MapSchema do not have metadata
3969
4688
  const metadata = obj.constructor[Symbol.metadata];
3970
- // add parent ChangeTree's, if they are invisible to this view
3971
- // TODO: REFACTOR addParent()
3972
- this.addParent(changeTree, tag);
4689
+ const changeTree = obj[$changes];
4690
+ this.items.add(changeTree);
4691
+ // add parent ChangeTree's
4692
+ // - if it was invisible to this view
4693
+ // - if it were previously filtered out
4694
+ if (checkIncludeParent && changeTree.parent) {
4695
+ this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4696
+ }
3973
4697
  //
3974
4698
  // TODO: when adding an item of a MapSchema, the changes may not
3975
4699
  // be set (only the parent's changes are set)
3976
4700
  //
3977
- let changes = this.changes.get(changeTree);
4701
+ let changes = this.changes[changeTree.refId];
3978
4702
  if (changes === undefined) {
3979
- changes = new Map();
3980
- this.changes.set(changeTree, changes);
4703
+ changes = {};
4704
+ this.changes[changeTree.refId] = changes;
3981
4705
  }
3982
4706
  // set tag
3983
4707
  if (tag !== DEFAULT_VIEW_TAG) {
@@ -3993,82 +4717,78 @@ class StateView {
3993
4717
  tags = this.tags.get(changeTree);
3994
4718
  }
3995
4719
  tags.add(tag);
3996
- // console.log("BY TAG:", tag);
3997
4720
  // Ref: add tagged properties
3998
- metadata?.[-3]?.[tag]?.forEach((index) => {
4721
+ metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
3999
4722
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
4000
- changes.set(index, exports.OPERATION.ADD);
4723
+ changes[index] = exports.OPERATION.ADD;
4001
4724
  }
4002
4725
  });
4003
4726
  }
4004
4727
  else {
4005
- // console.log("DEFAULT TAG", changeTree.allChanges);
4006
- // // add default tag properties
4007
- // metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
4008
- // if (changeTree.getChange(index) !== OPERATION.DELETE) {
4009
- // changes.set(index, OPERATION.ADD);
4010
- // }
4011
- // });
4012
- const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4728
+ const isInvisible = this.invisible.has(changeTree);
4729
+ const changeSet = (changeTree.filteredChanges !== undefined)
4013
4730
  ? changeTree.allFilteredChanges
4014
4731
  : changeTree.allChanges;
4015
- const it = allChangesSet.keys();
4016
- const isInvisible = this.invisible.has(changeTree);
4017
- for (const index of it) {
4018
- if ((isInvisible || metadata?.[metadata?.[index]].tag === tag) &&
4019
- changeTree.getChange(index) !== exports.OPERATION.DELETE) {
4020
- changes.set(index, exports.OPERATION.ADD);
4732
+ for (let i = 0, len = changeSet.operations.length; i < len; i++) {
4733
+ const index = changeSet.operations[i];
4734
+ if (index === undefined) {
4735
+ continue;
4736
+ } // skip "undefined" indexes
4737
+ const op = changeTree.indexedOperations[index] ?? exports.OPERATION.ADD;
4738
+ const tagAtIndex = metadata?.[index].tag;
4739
+ if ((isInvisible || // if "invisible", include all
4740
+ tagAtIndex === undefined || // "all change" with no tag
4741
+ tagAtIndex === tag // tagged property
4742
+ ) &&
4743
+ op !== exports.OPERATION.DELETE) {
4744
+ changes[index] = op;
4021
4745
  }
4022
4746
  }
4023
4747
  }
4024
- // TODO: avoid unnecessary iteration here
4025
- while (changeTree.parent &&
4026
- (changeTree = changeTree.parent[$changes]) &&
4027
- (changeTree.isFiltered || changeTree.isPartiallyFiltered)) {
4028
- this.items.add(changeTree);
4029
- }
4748
+ // Add children of this ChangeTree to this view
4749
+ changeTree.forEachChild((change, index) => {
4750
+ // Do not ADD children that don't have the same tag
4751
+ if (metadata &&
4752
+ metadata[index].tag !== undefined &&
4753
+ metadata[index].tag !== tag) {
4754
+ return;
4755
+ }
4756
+ this.add(change.ref, tag, false);
4757
+ });
4030
4758
  return this;
4031
4759
  }
4032
- addParent(changeTree, tag) {
4033
- const parentRef = changeTree.parent;
4034
- if (!parentRef) {
4035
- return;
4760
+ addParent(changeTree, parentIndex, tag) {
4761
+ // view must have all "changeTree" parent tree
4762
+ this.items.add(changeTree);
4763
+ // add parent's parent
4764
+ const parentChangeTree = changeTree.parent?.[$changes];
4765
+ if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
4766
+ this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4036
4767
  }
4037
- const parentChangeTree = parentRef[$changes];
4038
- const parentIndex = changeTree.parentIndex;
4039
- if (!this.invisible.has(parentChangeTree)) {
4040
- // parent is already available, no need to add it!
4768
+ // parent is already available, no need to add it!
4769
+ if (!this.invisible.has(changeTree)) {
4041
4770
  return;
4042
4771
  }
4043
- this.addParent(parentChangeTree, tag);
4044
4772
  // add parent's tag properties
4045
- if (parentChangeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4046
- let parentChanges = this.changes.get(parentChangeTree);
4047
- if (parentChanges === undefined) {
4048
- parentChanges = new Map();
4049
- this.changes.set(parentChangeTree, parentChanges);
4773
+ if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4774
+ let changes = this.changes[changeTree.refId];
4775
+ if (changes === undefined) {
4776
+ changes = {};
4777
+ this.changes[changeTree.refId] = changes;
4050
4778
  }
4051
- // console.log("add parent change", {
4052
- // parentIndex,
4053
- // parentChanges,
4054
- // parentChange: (
4055
- // parentChangeTree.getChange(parentIndex) &&
4056
- // OPERATION[parentChangeTree.getChange(parentIndex)]
4057
- // ),
4058
- // })
4059
4779
  if (!this.tags) {
4060
4780
  this.tags = new WeakMap();
4061
4781
  }
4062
4782
  let tags;
4063
- if (!this.tags.has(parentChangeTree)) {
4783
+ if (!this.tags.has(changeTree)) {
4064
4784
  tags = new Set();
4065
- this.tags.set(parentChangeTree, tags);
4785
+ this.tags.set(changeTree, tags);
4066
4786
  }
4067
4787
  else {
4068
- tags = this.tags.get(parentChangeTree);
4788
+ tags = this.tags.get(changeTree);
4069
4789
  }
4070
4790
  tags.add(tag);
4071
- parentChanges.set(parentIndex, exports.OPERATION.ADD);
4791
+ changes[parentIndex] = exports.OPERATION.ADD;
4072
4792
  }
4073
4793
  }
4074
4794
  remove(obj, tag = DEFAULT_VIEW_TAG) {
@@ -4080,32 +4800,32 @@ class StateView {
4080
4800
  this.items.delete(changeTree);
4081
4801
  const ref = changeTree.ref;
4082
4802
  const metadata = ref.constructor[Symbol.metadata];
4083
- let changes = this.changes.get(changeTree);
4803
+ let changes = this.changes[changeTree.refId];
4084
4804
  if (changes === undefined) {
4085
- changes = new Map();
4086
- this.changes.set(changeTree, changes);
4805
+ changes = {};
4806
+ this.changes[changeTree.refId] = changes;
4087
4807
  }
4088
4808
  if (tag === DEFAULT_VIEW_TAG) {
4089
4809
  // parent is collection (Map/Array)
4090
4810
  const parent = changeTree.parent;
4091
4811
  if (!Metadata.isValidInstance(parent)) {
4092
4812
  const parentChangeTree = parent[$changes];
4093
- let changes = this.changes.get(parentChangeTree);
4813
+ let changes = this.changes[parentChangeTree.refId];
4094
4814
  if (changes === undefined) {
4095
- changes = new Map();
4096
- this.changes.set(parentChangeTree, changes);
4815
+ changes = {};
4816
+ this.changes[parentChangeTree.refId] = changes;
4097
4817
  }
4098
4818
  // DELETE / DELETE BY REF ID
4099
- changes.set(changeTree.parentIndex, exports.OPERATION.DELETE);
4819
+ changes[changeTree.parentIndex] = exports.OPERATION.DELETE;
4100
4820
  }
4101
4821
  else {
4102
4822
  // delete all "tagged" properties.
4103
- metadata[-2].forEach((index) => changes.set(index, exports.OPERATION.DELETE));
4823
+ metadata[$viewFieldIndexes].forEach((index) => changes[index] = exports.OPERATION.DELETE);
4104
4824
  }
4105
4825
  }
4106
4826
  else {
4107
4827
  // delete only tagged properties
4108
- metadata[-3][tag].forEach((index) => changes.set(index, exports.OPERATION.DELETE));
4828
+ metadata[$fieldIndexesByViewTag][tag].forEach((index) => changes[index] = exports.OPERATION.DELETE);
4109
4829
  }
4110
4830
  // remove tag
4111
4831
  if (this.tags && this.tags.has(changeTree)) {
@@ -4157,13 +4877,17 @@ exports.TypeContext = TypeContext;
4157
4877
  exports.decode = decode;
4158
4878
  exports.decodeKeyValueOperation = decodeKeyValueOperation;
4159
4879
  exports.decodeSchemaOperation = decodeSchemaOperation;
4880
+ exports.defineCustomTypes = defineCustomTypes;
4160
4881
  exports.defineTypes = defineTypes;
4161
4882
  exports.deprecated = deprecated;
4162
4883
  exports.dumpChanges = dumpChanges;
4163
4884
  exports.encode = encode;
4164
4885
  exports.encodeKeyValueOperation = encodeArray;
4165
4886
  exports.encodeSchemaOperation = encodeSchemaOperation;
4887
+ exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
4888
+ exports.getRawChangesCallback = getRawChangesCallback;
4166
4889
  exports.registerType = registerType;
4890
+ exports.schema = schema;
4167
4891
  exports.type = type;
4168
4892
  exports.view = view;
4169
4893
  //# sourceMappingURL=index.js.map