@colyseus/schema 3.0.0-alpha.9 → 3.0.1

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