@colyseus/schema 3.0.0-alpha.3 → 3.0.0-alpha.31

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 (134) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +966 -563
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +965 -562
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +966 -563
  7. package/lib/Metadata.d.ts +15 -4
  8. package/lib/Metadata.js +86 -18
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Reflection.d.ts +2 -3
  11. package/lib/Reflection.js +24 -28
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.d.ts +2 -2
  14. package/lib/Schema.js +28 -41
  15. package/lib/Schema.js.map +1 -1
  16. package/lib/annotations.d.ts +1 -21
  17. package/lib/annotations.js +73 -153
  18. package/lib/annotations.js.map +1 -1
  19. package/lib/bench_encode.d.ts +1 -0
  20. package/lib/bench_encode.js +142 -0
  21. package/lib/bench_encode.js.map +1 -0
  22. package/lib/codegen/api.js +1 -2
  23. package/lib/codegen/api.js.map +1 -1
  24. package/lib/codegen/languages/cpp.js +1 -2
  25. package/lib/codegen/languages/cpp.js.map +1 -1
  26. package/lib/codegen/languages/csharp.js +1 -2
  27. package/lib/codegen/languages/csharp.js.map +1 -1
  28. package/lib/codegen/languages/haxe.js +1 -2
  29. package/lib/codegen/languages/haxe.js.map +1 -1
  30. package/lib/codegen/languages/java.js +1 -2
  31. package/lib/codegen/languages/java.js.map +1 -1
  32. package/lib/codegen/languages/js.js +1 -2
  33. package/lib/codegen/languages/js.js.map +1 -1
  34. package/lib/codegen/languages/lua.js +1 -2
  35. package/lib/codegen/languages/lua.js.map +1 -1
  36. package/lib/codegen/languages/ts.js +1 -2
  37. package/lib/codegen/languages/ts.js.map +1 -1
  38. package/lib/codegen/parser.js +2 -3
  39. package/lib/codegen/parser.js.map +1 -1
  40. package/lib/codegen/types.js +3 -3
  41. package/lib/codegen/types.js.map +1 -1
  42. package/lib/debug.d.ts +1 -0
  43. package/lib/debug.js +52 -0
  44. package/lib/debug.js.map +1 -0
  45. package/lib/decoder/DecodeOperation.d.ts +0 -1
  46. package/lib/decoder/DecodeOperation.js +23 -11
  47. package/lib/decoder/DecodeOperation.js.map +1 -1
  48. package/lib/decoder/Decoder.d.ts +6 -7
  49. package/lib/decoder/Decoder.js +8 -8
  50. package/lib/decoder/Decoder.js.map +1 -1
  51. package/lib/decoder/ReferenceTracker.js +3 -2
  52. package/lib/decoder/ReferenceTracker.js.map +1 -1
  53. package/lib/decoder/strategy/RawChanges.js +1 -2
  54. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  55. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  56. package/lib/decoder/strategy/StateCallbacks.js +75 -65
  57. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  58. package/lib/encoder/ChangeTree.d.ts +9 -19
  59. package/lib/encoder/ChangeTree.js +129 -145
  60. package/lib/encoder/ChangeTree.js.map +1 -1
  61. package/lib/encoder/EncodeOperation.d.ts +1 -5
  62. package/lib/encoder/EncodeOperation.js +74 -58
  63. package/lib/encoder/EncodeOperation.js.map +1 -1
  64. package/lib/encoder/Encoder.d.ts +10 -8
  65. package/lib/encoder/Encoder.js +89 -56
  66. package/lib/encoder/Encoder.js.map +1 -1
  67. package/lib/encoder/Root.d.ts +17 -0
  68. package/lib/encoder/Root.js +44 -0
  69. package/lib/encoder/Root.js.map +1 -0
  70. package/lib/encoder/StateView.d.ts +2 -2
  71. package/lib/encoder/StateView.js +49 -59
  72. package/lib/encoder/StateView.js.map +1 -1
  73. package/lib/encoding/assert.d.ts +2 -1
  74. package/lib/encoding/assert.js +5 -5
  75. package/lib/encoding/assert.js.map +1 -1
  76. package/lib/encoding/decode.js +21 -22
  77. package/lib/encoding/decode.js.map +1 -1
  78. package/lib/encoding/encode.d.ts +2 -2
  79. package/lib/encoding/encode.js +40 -39
  80. package/lib/encoding/encode.js.map +1 -1
  81. package/lib/encoding/spec.d.ts +2 -1
  82. package/lib/encoding/spec.js +1 -0
  83. package/lib/encoding/spec.js.map +1 -1
  84. package/lib/index.d.ts +6 -3
  85. package/lib/index.js +18 -13
  86. package/lib/index.js.map +1 -1
  87. package/lib/types/TypeContext.d.ts +23 -0
  88. package/lib/types/TypeContext.js +102 -0
  89. package/lib/types/TypeContext.js.map +1 -0
  90. package/lib/types/custom/ArraySchema.d.ts +2 -2
  91. package/lib/types/custom/ArraySchema.js +6 -9
  92. package/lib/types/custom/ArraySchema.js.map +1 -1
  93. package/lib/types/custom/CollectionSchema.js +1 -0
  94. package/lib/types/custom/CollectionSchema.js.map +1 -1
  95. package/lib/types/custom/MapSchema.js +5 -0
  96. package/lib/types/custom/MapSchema.js.map +1 -1
  97. package/lib/types/custom/SetSchema.js +1 -0
  98. package/lib/types/custom/SetSchema.js.map +1 -1
  99. package/lib/types/registry.js +3 -4
  100. package/lib/types/registry.js.map +1 -1
  101. package/lib/types/symbols.d.ts +1 -0
  102. package/lib/types/symbols.js +2 -1
  103. package/lib/types/symbols.js.map +1 -1
  104. package/lib/types/utils.js +1 -2
  105. package/lib/types/utils.js.map +1 -1
  106. package/lib/utils.js +3 -4
  107. package/lib/utils.js.map +1 -1
  108. package/package.json +5 -5
  109. package/src/Metadata.ts +104 -26
  110. package/src/Reflection.ts +26 -28
  111. package/src/Schema.ts +35 -47
  112. package/src/annotations.ts +82 -176
  113. package/src/bench_encode.ts +121 -0
  114. package/src/debug.ts +56 -0
  115. package/src/decoder/DecodeOperation.ts +28 -11
  116. package/src/decoder/Decoder.ts +13 -11
  117. package/src/decoder/ReferenceTracker.ts +3 -2
  118. package/src/decoder/strategy/StateCallbacks.ts +152 -81
  119. package/src/encoder/ChangeTree.ts +147 -166
  120. package/src/encoder/EncodeOperation.ts +93 -70
  121. package/src/encoder/Encoder.ts +111 -65
  122. package/src/encoder/Root.ts +51 -0
  123. package/src/encoder/StateView.ts +53 -69
  124. package/src/encoding/assert.ts +4 -3
  125. package/src/encoding/decode.ts +1 -2
  126. package/src/encoding/encode.ts +25 -22
  127. package/src/encoding/spec.ts +1 -0
  128. package/src/index.ts +8 -14
  129. package/src/types/TypeContext.ts +122 -0
  130. package/src/types/custom/ArraySchema.ts +10 -2
  131. package/src/types/custom/CollectionSchema.ts +1 -0
  132. package/src/types/custom/MapSchema.ts +6 -0
  133. package/src/types/custom/SetSchema.ts +1 -0
  134. package/src/types/symbols.ts +2 -0
@@ -29,6 +29,7 @@
29
29
  OPERATION[OPERATION["REVERSE"] = 15] = "REVERSE";
30
30
  OPERATION[OPERATION["MOVE"] = 32] = "MOVE";
31
31
  OPERATION[OPERATION["DELETE_BY_REFID"] = 33] = "DELETE_BY_REFID";
32
+ OPERATION[OPERATION["ADD_BY_REFID"] = 129] = "ADD_BY_REFID";
32
33
  })(exports.OPERATION || (exports.OPERATION = {}));
33
34
 
34
35
  Symbol.metadata ??= Symbol.for("Symbol.metadata");
@@ -39,6 +40,7 @@
39
40
  const $filter = Symbol("$filter");
40
41
  const $getByIndex = Symbol("$getByIndex");
41
42
  const $deleteByIndex = Symbol("$deleteByIndex");
43
+ const $descriptors = Symbol("$descriptors");
42
44
  /**
43
45
  * Used to hold ChangeTree instances whitin the structures
44
46
  */
@@ -74,34 +76,67 @@
74
76
  }
75
77
 
76
78
  const Metadata = {
77
- addField(metadata, index, field, type, descriptor) {
79
+ addField(metadata, index, name, type, descriptor) {
78
80
  if (index > 64) {
79
- throw new Error(`Can't define field '${field}'.\nSchema instances may only have up to 64 fields.`);
81
+ throw new Error(`Can't define field '${name}'.\nSchema instances may only have up to 64 fields.`);
80
82
  }
81
- metadata[field] = Object.assign(metadata[field] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
83
+ metadata[index] = Object.assign(metadata[index] || {}, // avoid overwriting previous field metadata (@owned / @deprecated)
82
84
  {
83
85
  type: (Array.isArray(type))
84
86
  ? { array: type[0] }
85
87
  : type,
86
88
  index,
87
- descriptor,
89
+ name,
88
90
  });
91
+ // create "descriptors" map
92
+ metadata[$descriptors] ??= {};
93
+ if (descriptor) {
94
+ // for encoder
95
+ metadata[$descriptors][name] = descriptor;
96
+ metadata[$descriptors][`_${name}`] = {
97
+ value: undefined,
98
+ writable: true,
99
+ enumerable: false,
100
+ configurable: true,
101
+ };
102
+ }
103
+ else {
104
+ // for decoder
105
+ metadata[$descriptors][name] = {
106
+ value: undefined,
107
+ writable: true,
108
+ enumerable: true,
109
+ configurable: true,
110
+ };
111
+ }
89
112
  // map -1 as last field index
90
113
  Object.defineProperty(metadata, -1, {
91
114
  value: index,
92
115
  enumerable: false,
93
116
  configurable: true
94
117
  });
95
- // map index => field name (non enumerable)
96
- Object.defineProperty(metadata, index, {
97
- value: field,
118
+ // map field name => index (non enumerable)
119
+ Object.defineProperty(metadata, name, {
120
+ value: index,
98
121
  enumerable: false,
99
122
  configurable: true,
100
123
  });
124
+ // if child Ref/complex type, add to -4
125
+ if (typeof (metadata[index].type) !== "string") {
126
+ if (metadata[-4] === undefined) {
127
+ Object.defineProperty(metadata, -4, {
128
+ value: [],
129
+ enumerable: false,
130
+ configurable: true,
131
+ });
132
+ }
133
+ metadata[-4].push(index);
134
+ }
101
135
  },
102
136
  setTag(metadata, fieldName, tag) {
137
+ const index = metadata[fieldName];
138
+ const field = metadata[index];
103
139
  // add 'tag' to the field
104
- const field = metadata[fieldName];
105
140
  field.tag = tag;
106
141
  if (!metadata[-2]) {
107
142
  // -2: all field indexes with "view" tag
@@ -117,20 +152,14 @@
117
152
  configurable: true
118
153
  });
119
154
  }
120
- metadata[-2].push(field.index);
155
+ metadata[-2].push(index);
121
156
  if (!metadata[-3][tag]) {
122
157
  metadata[-3][tag] = [];
123
158
  }
124
- metadata[-3][tag].push(field.index);
159
+ metadata[-3][tag].push(index);
125
160
  },
126
161
  setFields(target, fields) {
127
162
  const metadata = (target.prototype.constructor[Symbol.metadata] ??= {});
128
- // target[$track] = function (changeTree, index: number, operation: OPERATION = OPERATION.ADD) {
129
- // changeTree.change(index, operation, encodeSchemaOperation);
130
- // };
131
- // target[$encoder] = encodeSchemaOperation;
132
- // target[$decoder] = decodeSchemaOperation;
133
- // if (!target.prototype.toJSON) { target.prototype.toJSON = Schema.prototype.toJSON; }
134
163
  let index = 0;
135
164
  for (const field in fields) {
136
165
  const type = fields[field];
@@ -138,13 +167,53 @@
138
167
  const complexTypeKlass = (Array.isArray(type))
139
168
  ? getType("array")
140
169
  : (typeof (Object.keys(type)[0]) === "string") && getType(Object.keys(type)[0]);
141
- Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass, metadata, field));
170
+ Metadata.addField(metadata, index, field, type, getPropertyDescriptor(`_${field}`, index, type, complexTypeKlass));
142
171
  index++;
143
172
  }
144
173
  },
145
174
  isDeprecated(metadata, field) {
146
175
  return metadata[field].deprecated === true;
147
176
  },
177
+ init(klass) {
178
+ //
179
+ // Used only to initialize an empty Schema (Encoder#constructor)
180
+ // TODO: remove/refactor this...
181
+ //
182
+ const metadata = {};
183
+ klass[Symbol.metadata] = metadata;
184
+ Object.defineProperty(metadata, -1, {
185
+ value: 0,
186
+ enumerable: false,
187
+ configurable: true,
188
+ });
189
+ },
190
+ initialize(constructor, parentMetadata) {
191
+ let metadata = constructor[Symbol.metadata] ?? Object.create(null);
192
+ // make sure inherited classes have their own metadata object.
193
+ if (constructor[Symbol.metadata] === parentMetadata) {
194
+ metadata = Object.create(null);
195
+ if (parentMetadata) {
196
+ // assign parent metadata to current
197
+ Object.assign(metadata, parentMetadata);
198
+ for (let i = 0; i <= parentMetadata[-1]; i++) {
199
+ const fieldName = parentMetadata[i].name;
200
+ Object.defineProperty(metadata, fieldName, {
201
+ value: parentMetadata[fieldName],
202
+ enumerable: false,
203
+ configurable: true,
204
+ });
205
+ }
206
+ Object.defineProperty(metadata, -1, {
207
+ value: parentMetadata[-1],
208
+ enumerable: false,
209
+ configurable: true,
210
+ writable: true,
211
+ });
212
+ }
213
+ }
214
+ constructor[Symbol.metadata] = metadata;
215
+ return metadata;
216
+ },
148
217
  isValidInstance(klass) {
149
218
  return (klass.constructor[Symbol.metadata] &&
150
219
  Object.prototype.hasOwnProperty.call(klass.constructor[Symbol.metadata], -1));
@@ -153,97 +222,68 @@
153
222
  const metadata = klass[Symbol.metadata];
154
223
  const fields = {};
155
224
  for (let i = 0; i <= metadata[-1]; i++) {
156
- fields[metadata[i]] = metadata[metadata[i]].type;
225
+ fields[metadata[i].name] = metadata[i].type;
157
226
  }
158
227
  return fields;
159
228
  }
160
229
  };
161
230
 
162
231
  var _a$5;
163
- class Root {
164
- constructor() {
165
- this.nextUniqueId = 0;
166
- this.refCount = new WeakMap();
167
- // all changes
168
- this.allChanges = new Map();
169
- this.allFilteredChanges = new Map();
170
- // pending changes to be encoded
171
- this.changes = new Map();
172
- this.filteredChanges = new Map();
173
- }
174
- getNextUniqueId() {
175
- return this.nextUniqueId++;
176
- }
177
- add(changeTree) {
178
- const refCount = this.refCount.get(changeTree) || 0;
179
- this.refCount.set(changeTree, refCount + 1);
180
- }
181
- remove(changeTree) {
182
- const refCount = this.refCount.get(changeTree);
183
- if (refCount <= 1) {
184
- this.allChanges.delete(changeTree);
185
- this.changes.delete(changeTree);
186
- if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
187
- this.allFilteredChanges.delete(changeTree);
188
- this.filteredChanges.delete(changeTree);
189
- }
190
- this.refCount.delete(changeTree);
191
- }
192
- else {
193
- this.refCount.set(changeTree, refCount - 1);
194
- }
195
- changeTree.forEachChild((child, _) => this.remove(child));
196
- }
197
- clear() {
198
- this.changes.clear();
199
- }
200
- }
201
232
  class ChangeTree {
202
233
  static { _a$5 = $isNew; }
203
- ;
204
234
  constructor(ref) {
205
- this.indexes = {}; // TODO: remove this, only used by MapSchema/SetSchema/CollectionSchema (`encodeKeyValueOperation`)
235
+ this.isFiltered = false;
236
+ this.isPartiallyFiltered = false;
206
237
  this.currentOperationIndex = 0;
207
- this.allChanges = new Map();
208
- this.allFilteredChanges = new Map();
209
238
  this.changes = new Map();
210
- this.filteredChanges = new Map();
239
+ this.allChanges = new Map();
211
240
  this[_a$5] = true;
212
241
  this.ref = ref;
242
+ //
243
+ // Does this structure have "filters" declared?
244
+ //
245
+ if (ref.constructor[Symbol.metadata]?.[-2]) {
246
+ this.allFilteredChanges = new Map();
247
+ this.filteredChanges = new Map();
248
+ }
213
249
  }
214
250
  setRoot(root) {
215
251
  this.root = root;
216
252
  this.root.add(this);
217
- //
218
- // At Schema initialization, the "root" structure might not be available
219
- // yet, as it only does once the "Encoder" has been set up.
220
- //
221
- // So the "parent" may be already set without a "root".
222
- //
223
- this.checkIsFiltered(this.parent, this.parentIndex);
224
- // unique refId for the ChangeTree.
225
- this.ensureRefId();
253
+ const metadata = this.ref.constructor[Symbol.metadata];
254
+ if (this.root.types.hasFilters) {
255
+ //
256
+ // At Schema initialization, the "root" structure might not be available
257
+ // yet, as it only does once the "Encoder" has been set up.
258
+ //
259
+ // So the "parent" may be already set without a "root".
260
+ //
261
+ this.checkIsFiltered(metadata, this.parent, this.parentIndex);
262
+ if (this.isFiltered || this.isPartiallyFiltered) {
263
+ this.root.allFilteredChanges.set(this, this.allFilteredChanges);
264
+ this.root.filteredChanges.set(this, this.filteredChanges);
265
+ }
266
+ }
226
267
  if (!this.isFiltered) {
227
268
  this.root.changes.set(this, this.changes);
269
+ this.root.allChanges.set(this, this.allChanges);
228
270
  }
229
- if (this.isFiltered || this.isPartiallyFiltered) {
230
- this.root.allFilteredChanges.set(this, this.allFilteredChanges);
231
- this.root.filteredChanges.set(this, this.filteredChanges);
232
- // } else {
233
- // this.root.allChanges.set(this, this.allChanges);
271
+ this.ensureRefId();
272
+ if (metadata) {
273
+ metadata[-4]?.forEach((index) => {
274
+ const field = metadata[index];
275
+ const value = this.ref[field.name];
276
+ if (value) {
277
+ value[$changes].setRoot(root);
278
+ }
279
+ });
234
280
  }
235
- if (!this.isFiltered) {
236
- this.root.allChanges.set(this, this.allChanges);
281
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
282
+ // MapSchema / ArraySchema, etc.
283
+ this.ref.forEach((value, key) => {
284
+ value[$changes].setRoot(root);
285
+ });
237
286
  }
238
- this.forEachChild((changeTree, _) => {
239
- changeTree.setRoot(root);
240
- });
241
- // this.allChanges.forEach((_, index) => {
242
- // const childRef = this.ref[$getByIndex](index);
243
- // if (childRef && childRef[$changes]) {
244
- // childRef[$changes].setRoot(root);
245
- // }
246
- // });
247
287
  }
248
288
  setParent(parent, root, parentIndex) {
249
289
  this.parent = parent;
@@ -253,50 +293,60 @@
253
293
  return;
254
294
  }
255
295
  root.add(this);
296
+ const metadata = this.ref.constructor[Symbol.metadata];
256
297
  // skip if parent is already set
257
- if (root === this.root) {
258
- this.forEachChild((changeTree, atIndex) => {
259
- changeTree.setParent(this.ref, root, atIndex);
260
- });
261
- return;
262
- }
263
- this.root = root;
264
- this.checkIsFiltered(parent, parentIndex);
265
- if (!this.isFiltered) {
266
- this.root.changes.set(this, this.changes);
298
+ if (root !== this.root) {
299
+ this.root = root;
300
+ if (root.types.hasFilters) {
301
+ this.checkIsFiltered(metadata, parent, parentIndex);
302
+ if (this.isFiltered || this.isPartiallyFiltered) {
303
+ this.root.filteredChanges.set(this, this.filteredChanges);
304
+ this.root.allFilteredChanges.set(this, this.filteredChanges);
305
+ }
306
+ }
307
+ if (!this.isFiltered) {
308
+ this.root.changes.set(this, this.changes);
309
+ this.root.allChanges.set(this, this.allChanges);
310
+ }
311
+ this.ensureRefId();
267
312
  }
268
- if (this.isFiltered || this.isPartiallyFiltered) {
269
- this.root.filteredChanges.set(this, this.filteredChanges);
270
- this.root.allFilteredChanges.set(this, this.filteredChanges);
313
+ // assign same parent on child structures
314
+ if (metadata) {
315
+ metadata[-4]?.forEach((index) => {
316
+ const field = metadata[index];
317
+ const value = this.ref[field.name];
318
+ value?.[$changes].setParent(this.ref, root, index);
319
+ // console.log(this.ref.constructor.name, field.name, value);
320
+ // try { throw new Error(); } catch (e) {
321
+ // console.log(e.stack);
322
+ // }
323
+ });
271
324
  }
272
- else {
273
- this.root.allChanges.set(this, this.allChanges);
325
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
326
+ // MapSchema / ArraySchema, etc.
327
+ this.ref.forEach((value, key) => {
328
+ value[$changes].setParent(this.ref, root, this.indexes[key] ?? key);
329
+ });
274
330
  }
275
- this.ensureRefId();
276
- this.forEachChild((changeTree, atIndex) => {
277
- changeTree.setParent(this.ref, root, atIndex);
278
- });
279
331
  }
280
332
  forEachChild(callback) {
281
333
  //
282
334
  // assign same parent on child structures
283
335
  //
284
- if (Metadata.isValidInstance(this.ref)) {
285
- const metadata = this.ref['constructor'][Symbol.metadata];
286
- // FIXME: need to iterate over parent metadata instead.
287
- for (const field in metadata) {
288
- const value = this.ref[field];
289
- if (value && value[$changes]) {
290
- callback(value[$changes], metadata[field].index);
336
+ const metadata = this.ref.constructor[Symbol.metadata];
337
+ if (metadata) {
338
+ metadata[-4]?.forEach((index) => {
339
+ const field = metadata[index];
340
+ const value = this.ref[field.name];
341
+ if (value) {
342
+ callback(value[$changes], index);
291
343
  }
292
- }
344
+ });
293
345
  }
294
- else if (typeof (this.ref) === "object") {
346
+ else if (this.ref[$childType] && typeof (this.ref[$childType]) !== "string") {
295
347
  // MapSchema / ArraySchema, etc.
296
348
  this.ref.forEach((value, key) => {
297
- if (Metadata.isValidInstance(value)) {
298
- callback(value[$changes], this.ref[$changes].indexes[key]);
299
- }
349
+ callback(value[$changes], this.indexes[key] ?? key);
300
350
  });
301
351
  }
302
352
  }
@@ -305,8 +355,8 @@
305
355
  this.root?.changes.set(this, this.changes);
306
356
  }
307
357
  change(index, operation = exports.OPERATION.ADD) {
308
- const metadata = this.ref['constructor'][Symbol.metadata];
309
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
358
+ const metadata = this.ref.constructor[Symbol.metadata];
359
+ const isFiltered = this.isFiltered || (metadata?.[index]?.tag !== undefined);
310
360
  const changeSet = (isFiltered)
311
361
  ? this.filteredChanges
312
362
  : this.changes;
@@ -317,14 +367,15 @@
317
367
  : (previousOperation === exports.OPERATION.DELETE)
318
368
  ? exports.OPERATION.DELETE_AND_ADD
319
369
  : operation;
370
+ //
371
+ // TODO: are DELETE operations being encoded as ADD here ??
372
+ //
320
373
  changeSet.set(index, op);
321
374
  }
322
- //
323
- // TODO: are DELETE operations being encoded as ADD here ??
324
- //
325
375
  if (isFiltered) {
326
376
  this.allFilteredChanges.set(index, exports.OPERATION.ADD);
327
377
  this.root?.filteredChanges.set(this, this.filteredChanges);
378
+ this.root?.allFilteredChanges.set(this, this.allFilteredChanges);
328
379
  }
329
380
  else {
330
381
  this.allChanges.set(index, exports.OPERATION.ADD);
@@ -363,7 +414,6 @@
363
414
  }
364
415
  _shiftAllChangeIndexes(shiftIndex, startIndex = 0, allChangeSet) {
365
416
  Array.from(allChangeSet.entries()).forEach(([index, op]) => {
366
- // console.log('shiftAllChangeIndexes', index >= startIndex, { index, op, shiftIndex, startIndex })
367
417
  if (index >= startIndex) {
368
418
  allChangeSet.delete(index);
369
419
  allChangeSet.set(index + shiftIndex, op);
@@ -371,9 +421,7 @@
371
421
  });
372
422
  }
373
423
  indexedOperation(index, operation, allChangesIndex = index) {
374
- const metadata = this.ref['constructor'][Symbol.metadata];
375
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
376
- if (isFiltered) {
424
+ if (this.filteredChanges !== undefined) {
377
425
  this.allFilteredChanges.set(allChangesIndex, exports.OPERATION.ADD);
378
426
  this.filteredChanges.set(index, operation);
379
427
  this.root?.filteredChanges.set(this, this.filteredChanges);
@@ -386,8 +434,8 @@
386
434
  }
387
435
  getType(index) {
388
436
  if (Metadata.isValidInstance(this.ref)) {
389
- const metadata = this.ref['constructor'][Symbol.metadata];
390
- return metadata[metadata[index]].type;
437
+ const metadata = this.ref.constructor[Symbol.metadata];
438
+ return metadata[index].type;
391
439
  }
392
440
  else {
393
441
  //
@@ -401,7 +449,7 @@
401
449
  }
402
450
  getChange(index) {
403
451
  // TODO: optimize this. avoid checking against multiple instances
404
- return this.changes.get(index) ?? this.filteredChanges.get(index);
452
+ return this.changes.get(index) ?? this.filteredChanges?.get(index);
405
453
  }
406
454
  //
407
455
  // used during `.encode()`
@@ -422,9 +470,7 @@
422
470
  }
423
471
  return;
424
472
  }
425
- const metadata = this.ref['constructor'][Symbol.metadata];
426
- const isFiltered = this.isFiltered || (metadata && metadata[metadata[index]].tag !== undefined);
427
- const changeSet = (isFiltered)
473
+ const changeSet = (this.filteredChanges)
428
474
  ? this.filteredChanges
429
475
  : this.changes;
430
476
  const previousValue = this.getValue(index);
@@ -447,7 +493,7 @@
447
493
  //
448
494
  // FIXME: this is looking a bit ugly (and repeated from `.change()`)
449
495
  //
450
- if (isFiltered) {
496
+ if (this.filteredChanges) {
451
497
  this.root?.filteredChanges.set(this, this.filteredChanges);
452
498
  this.allFilteredChanges.delete(allChangesIndex);
453
499
  }
@@ -458,6 +504,7 @@
458
504
  }
459
505
  endEncode() {
460
506
  this.changes.clear();
507
+ // ArraySchema and MapSchema have a custom "encode end" method
461
508
  this.ref[$onEncodeEnd]?.();
462
509
  // Not a new instance anymore
463
510
  delete this[$isNew];
@@ -470,12 +517,12 @@
470
517
  //
471
518
  this.ref[$onEncodeEnd]?.();
472
519
  this.changes.clear();
473
- this.filteredChanges.clear();
520
+ this.filteredChanges?.clear();
474
521
  // reset operation index
475
522
  this.currentOperationIndex = 0;
476
523
  if (discardAll) {
477
524
  this.allChanges.clear();
478
- this.allFilteredChanges.clear();
525
+ this.allFilteredChanges?.clear();
479
526
  // remove children references
480
527
  this.forEachChild((changeTree, _) => this.root?.remove(changeTree));
481
528
  }
@@ -502,33 +549,41 @@
502
549
  get changed() {
503
550
  return this.changes.size > 0;
504
551
  }
505
- checkIsFiltered(parent, parentIndex) {
552
+ checkIsFiltered(metadata, parent, parentIndex) {
506
553
  // Detect if current structure has "filters" declared
507
- this.isPartiallyFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-2];
508
- // TODO: support "partially filtered", where the instance is visible, but only a field is not.
509
- // Detect if parent has "filters" declared
510
- while (parent && !this.isFiltered) {
511
- const metadata = parent['constructor'][Symbol.metadata];
512
- const fieldName = metadata?.[parentIndex];
513
- const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
514
- this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
515
- parent = parent[$changes].parent;
554
+ this.isPartiallyFiltered = metadata?.[-2] !== undefined;
555
+ if (this.isPartiallyFiltered) {
556
+ this.filteredChanges = this.filteredChanges || new Map();
557
+ this.allFilteredChanges = this.allFilteredChanges || new Map();
516
558
  }
517
- //
518
- // TODO: refactor this!
519
- //
520
- // swapping `changes` and `filteredChanges` is required here
521
- // because "isFiltered" may not be imedialely available on `change()`
522
- //
523
- if (this.isFiltered && this.changes.size > 0) {
524
- // swap changes reference
525
- const changes = this.changes;
526
- this.changes = this.filteredChanges;
527
- this.filteredChanges = changes;
528
- // swap "all changes" reference
529
- const allFilteredChanges = this.allFilteredChanges;
530
- this.allFilteredChanges = this.allChanges;
531
- this.allChanges = allFilteredChanges;
559
+ if (parent) {
560
+ if (!Metadata.isValidInstance(parent)) {
561
+ const parentChangeTree = parent[$changes];
562
+ parent = parentChangeTree.parent;
563
+ parentIndex = parentChangeTree.parentIndex;
564
+ }
565
+ const parentMetadata = parent?.constructor?.[Symbol.metadata];
566
+ this.isFiltered = (parent && parentMetadata?.[-2]?.includes(parentIndex));
567
+ //
568
+ // TODO: refactor this!
569
+ //
570
+ // swapping `changes` and `filteredChanges` is required here
571
+ // because "isFiltered" may not be imedialely available on `change()`
572
+ //
573
+ if (this.isFiltered) {
574
+ this.filteredChanges = new Map();
575
+ this.allFilteredChanges = new Map();
576
+ if (this.changes.size > 0) {
577
+ // swap changes reference
578
+ const changes = this.changes;
579
+ this.changes = this.filteredChanges;
580
+ this.filteredChanges = changes;
581
+ // swap "all changes" reference
582
+ const allFilteredChanges = this.allFilteredChanges;
583
+ this.allFilteredChanges = this.allChanges;
584
+ this.allChanges = allFilteredChanges;
585
+ }
586
+ }
532
587
  }
533
588
  }
534
589
  }
@@ -565,26 +620,29 @@
565
620
  textEncoder = new TextEncoder();
566
621
  }
567
622
  catch (e) { }
568
- function utf8Length(str) {
569
- var c = 0, length = 0;
570
- for (var i = 0, l = str.length; i < l; i++) {
571
- c = str.charCodeAt(i);
572
- if (c < 0x80) {
573
- length += 1;
574
- }
575
- else if (c < 0x800) {
576
- length += 2;
577
- }
578
- else if (c < 0xd800 || c >= 0xe000) {
579
- length += 3;
580
- }
581
- else {
582
- i++;
583
- length += 4;
623
+ const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
624
+ const utf8Length = (hasBufferByteLength)
625
+ ? Buffer.byteLength // node
626
+ : function (str, _) {
627
+ var c = 0, length = 0;
628
+ for (var i = 0, l = str.length; i < l; i++) {
629
+ c = str.charCodeAt(i);
630
+ if (c < 0x80) {
631
+ length += 1;
632
+ }
633
+ else if (c < 0x800) {
634
+ length += 2;
635
+ }
636
+ else if (c < 0xd800 || c >= 0xe000) {
637
+ length += 3;
638
+ }
639
+ else {
640
+ i++;
641
+ length += 4;
642
+ }
584
643
  }
585
- }
586
- return length;
587
- }
644
+ return length;
645
+ };
588
646
  function utf8Write(view, str, it) {
589
647
  var c = 0;
590
648
  for (var i = 0, l = str.length; i < l; i++) {
@@ -679,8 +737,7 @@
679
737
  if (!value) {
680
738
  value = "";
681
739
  }
682
- // let length = utf8Length(value);
683
- let length = Buffer.byteLength(value, "utf8");
740
+ let length = utf8Length(value, "utf8");
684
741
  let size = 0;
685
742
  // fixstr
686
743
  if (length < 0x20) {
@@ -791,81 +848,40 @@
791
848
 
792
849
  var encode = /*#__PURE__*/Object.freeze({
793
850
  __proto__: null,
794
- utf8Length: utf8Length,
795
- utf8Write: utf8Write,
796
- int8: int8$1,
797
- uint8: uint8$1,
851
+ boolean: boolean$1,
852
+ float32: float32$1,
853
+ float64: float64$1,
798
854
  int16: int16$1,
799
- uint16: uint16$1,
800
855
  int32: int32$1,
801
- uint32: uint32$1,
802
856
  int64: int64$1,
857
+ int8: int8$1,
858
+ number: number$1,
859
+ string: string$1,
860
+ uint16: uint16$1,
861
+ uint32: uint32$1,
803
862
  uint64: uint64$1,
804
- float32: float32$1,
805
- float64: float64$1,
863
+ uint8: uint8$1,
864
+ utf8Length: utf8Length,
865
+ utf8Write: utf8Write,
806
866
  writeFloat32: writeFloat32,
807
- writeFloat64: writeFloat64,
808
- boolean: boolean$1,
809
- string: string$1,
810
- number: number$1
867
+ writeFloat64: writeFloat64
811
868
  });
812
869
 
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;
842
- }
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}`);
846
- }
847
- }
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
- }
852
- }
853
-
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}`);
870
+ function encodeValue(encoder, bytes,
871
+ // ref: Ref,
872
+ type, value,
873
+ // field: string | number,
874
+ operation, it) {
875
+ if (typeof (type) === "string") {
876
+ //
877
+ // Primitive values
878
+ //
879
+ // assertType(value, type as string, ref as Schema, field);
880
+ encode[type]?.(bytes, value, it);
863
881
  }
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);
882
+ else if (type[Symbol.metadata] !== undefined) {
883
+ // // TODO: move this to the `@type()` annotation
884
+ // assertInstanceType(value, type as typeof Schema, ref as Schema, field);
869
885
  //
870
886
  // Encode refId for this instance.
871
887
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -876,21 +892,15 @@
876
892
  encoder.tryEncodeTypeId(bytes, type, value.constructor, it);
877
893
  }
878
894
  }
879
- else if (typeof (type) === "string") {
880
- //
881
- // Primitive values
882
- //
883
- encodePrimitiveType(type, bytes, value, ref, field, it);
884
- }
885
895
  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);
896
+ // //
897
+ // // Custom type (MapSchema, ArraySchema, etc)
898
+ // //
899
+ // const definition = getType(Object.keys(type)[0]);
900
+ // //
901
+ // // ensure a ArraySchema has been provided
902
+ // //
903
+ // assertInstanceType(ref[field], definition.constructor, ref as Schema, field);
894
904
  //
895
905
  // Encode refId for this instance.
896
906
  // The actual instance is going to be encoded on next `changeTree` iteration.
@@ -903,26 +913,27 @@
903
913
  * @private
904
914
  */
905
915
  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
916
  // "compress" field index + operation
912
917
  bytes[it.offset++] = (index | operation) & 255;
913
918
  // Do not encode value for DELETE operations
914
919
  if (operation === exports.OPERATION.DELETE) {
915
920
  return;
916
921
  }
922
+ const ref = changeTree.ref;
923
+ const metadata = ref['constructor'][Symbol.metadata];
924
+ const field = metadata[index];
917
925
  // TODO: inline this function call small performance gain
918
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
926
+ encodeValue(encoder, bytes,
927
+ // ref,
928
+ metadata[index].type, ref[field.name],
929
+ // index,
930
+ operation, it);
919
931
  };
920
932
  /**
921
933
  * Used for collections (MapSchema, CollectionSchema, SetSchema)
922
934
  * @private
923
935
  */
924
- const encodeKeyValueOperation = function (encoder, bytes, changeTree, field, operation, it) {
925
- const ref = changeTree.ref;
936
+ const encodeKeyValueOperation = function (encoder, bytes, changeTree, index, operation, it) {
926
937
  // encode operation
927
938
  bytes[it.offset++] = operation & 255;
928
939
  // custom operations
@@ -930,11 +941,12 @@
930
941
  return;
931
942
  }
932
943
  // encode index
933
- number$1(bytes, field, it);
944
+ number$1(bytes, index, it);
934
945
  // Do not encode value for DELETE operations
935
946
  if (operation === exports.OPERATION.DELETE) {
936
947
  return;
937
948
  }
949
+ const ref = changeTree.ref;
938
950
  //
939
951
  // encode "alias" for dynamic fields (maps)
940
952
  //
@@ -943,14 +955,30 @@
943
955
  //
944
956
  // MapSchema dynamic key
945
957
  //
946
- const dynamicIndex = changeTree.ref['$indexes'].get(field);
958
+ const dynamicIndex = changeTree.ref['$indexes'].get(index);
947
959
  string$1(bytes, dynamicIndex, it);
948
960
  }
949
961
  }
950
- const type = changeTree.getType(field);
951
- const value = changeTree.getValue(field);
962
+ const type = changeTree.getType(index);
963
+ const value = changeTree.getValue(index);
964
+ // try { throw new Error(); } catch (e) {
965
+ // // only print if not coming from Reflection.ts
966
+ // if (!e.stack.includes("src/Reflection.ts")) {
967
+ // console.log("encodeKeyValueOperation -> ", {
968
+ // ref: changeTree.ref.constructor.name,
969
+ // field,
970
+ // operation: OPERATION[operation],
971
+ // value: value?.toJSON(),
972
+ // items: ref.toJSON(),
973
+ // });
974
+ // }
975
+ // }
952
976
  // TODO: inline this function call small performance gain
953
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
977
+ encodeValue(encoder, bytes,
978
+ // ref,
979
+ type, value,
980
+ // index,
981
+ operation, it);
954
982
  };
955
983
  /**
956
984
  * Used for collections (MapSchema, ArraySchema, etc.)
@@ -958,15 +986,19 @@
958
986
  */
959
987
  const encodeArray = function (encoder, bytes, changeTree, field, operation, it, isEncodeAll, hasView) {
960
988
  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;
989
+ const useOperationByRefId = hasView && changeTree.isFiltered && (typeof (changeTree.getType(field)) !== "string");
990
+ let refOrIndex;
991
+ if (useOperationByRefId) {
992
+ refOrIndex = ref['tmpItems'][field][$changes].refId;
993
+ if (operation === exports.OPERATION.DELETE) {
994
+ operation = exports.OPERATION.DELETE_BY_REFID;
995
+ }
996
+ else if (operation === exports.OPERATION.ADD) {
997
+ operation = exports.OPERATION.ADD_BY_REFID;
998
+ }
999
+ }
1000
+ else {
1001
+ refOrIndex = field;
970
1002
  }
971
1003
  // encode operation
972
1004
  bytes[it.offset++] = operation & 255;
@@ -975,7 +1007,7 @@
975
1007
  return;
976
1008
  }
977
1009
  // encode index
978
- number$1(bytes, field, it);
1010
+ number$1(bytes, refOrIndex, it);
979
1011
  // Do not encode value for DELETE operations
980
1012
  if (operation === exports.OPERATION.DELETE) {
981
1013
  return;
@@ -990,7 +1022,11 @@
990
1022
  // items: ref.toJSON(),
991
1023
  // });
992
1024
  // TODO: inline this function call small performance gain
993
- encodeValue(encoder, bytes, ref, type, value, field, operation, it);
1025
+ encodeValue(encoder, bytes,
1026
+ // ref,
1027
+ type, value,
1028
+ // field,
1029
+ operation, it);
994
1030
  };
995
1031
 
996
1032
  /**
@@ -1016,7 +1052,6 @@
1016
1052
  * SOFTWARE
1017
1053
  */
1018
1054
  function utf8Read(bytes, it, length) {
1019
- it.offset += length;
1020
1055
  var string = '', chr = 0;
1021
1056
  for (var i = it.offset, end = it.offset + length; i < end; i++) {
1022
1057
  var byte = bytes[i];
@@ -1053,6 +1088,7 @@
1053
1088
  // (do not throw error to avoid server/client from crashing due to hack attemps)
1054
1089
  // throw new Error('Invalid byte ' + byte.toString(16));
1055
1090
  }
1091
+ it.offset += length;
1056
1092
  return string;
1057
1093
  }
1058
1094
  function int8(bytes, it) {
@@ -1224,31 +1260,31 @@
1224
1260
 
1225
1261
  var decode = /*#__PURE__*/Object.freeze({
1226
1262
  __proto__: null,
1227
- utf8Read: utf8Read,
1228
- int8: int8,
1229
- uint8: uint8,
1230
- int16: int16,
1231
- uint16: uint16,
1232
- int32: int32,
1233
- uint32: uint32,
1263
+ arrayCheck: arrayCheck,
1264
+ boolean: boolean,
1234
1265
  float32: float32,
1235
1266
  float64: float64,
1267
+ int16: int16,
1268
+ int32: int32,
1236
1269
  int64: int64,
1237
- uint64: uint64,
1270
+ int8: int8,
1271
+ number: number,
1272
+ numberCheck: numberCheck,
1238
1273
  readFloat32: readFloat32,
1239
1274
  readFloat64: readFloat64,
1240
- boolean: boolean,
1241
1275
  string: string,
1242
1276
  stringCheck: stringCheck,
1243
- number: number,
1244
- numberCheck: numberCheck,
1245
- arrayCheck: arrayCheck,
1246
- switchStructureCheck: switchStructureCheck
1277
+ switchStructureCheck: switchStructureCheck,
1278
+ uint16: uint16,
1279
+ uint32: uint32,
1280
+ uint64: uint64,
1281
+ uint8: uint8,
1282
+ utf8Read: utf8Read
1247
1283
  });
1248
1284
 
1249
1285
  const DEFINITION_MISMATCH = -1;
1250
1286
  function decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges) {
1251
- const $root = decoder.$root;
1287
+ const $root = decoder.root;
1252
1288
  const previousValue = ref[$getByIndex](index);
1253
1289
  let value;
1254
1290
  if ((operation & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
@@ -1347,18 +1383,19 @@
1347
1383
  }
1348
1384
  const decodeSchemaOperation = function (decoder, bytes, it, ref, allChanges) {
1349
1385
  const first_byte = bytes[it.offset++];
1350
- const metadata = ref['constructor'][Symbol.metadata];
1386
+ const metadata = ref.constructor[Symbol.metadata];
1351
1387
  // "compressed" index + operation
1352
1388
  const operation = (first_byte >> 6) << 6;
1353
1389
  const index = first_byte % (operation || 255);
1354
1390
  // skip early if field is not defined
1355
1391
  const field = metadata[index];
1356
1392
  if (field === undefined) {
1393
+ console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
1357
1394
  return DEFINITION_MISMATCH;
1358
1395
  }
1359
- const { value, previousValue } = decodeValue(decoder, operation, ref, index, metadata[field].type, bytes, it, allChanges);
1396
+ const { value, previousValue } = decodeValue(decoder, operation, ref, index, field.type, bytes, it, allChanges);
1360
1397
  if (value !== null && value !== undefined) {
1361
- ref[field] = value;
1398
+ ref[field.name] = value;
1362
1399
  }
1363
1400
  // add change
1364
1401
  if (previousValue !== value) {
@@ -1366,7 +1403,7 @@
1366
1403
  ref,
1367
1404
  refId: decoder.currentRefId,
1368
1405
  op: operation,
1369
- field: field,
1406
+ field: field.name,
1370
1407
  value,
1371
1408
  previousValue,
1372
1409
  });
@@ -1434,7 +1471,8 @@
1434
1471
  };
1435
1472
  const decodeArray = function (decoder, bytes, it, ref, allChanges) {
1436
1473
  // "uncompressed" index + operation (array/map items)
1437
- const operation = bytes[it.offset++];
1474
+ let operation = bytes[it.offset++];
1475
+ let index;
1438
1476
  if (operation === exports.OPERATION.CLEAR) {
1439
1477
  //
1440
1478
  // When decoding:
@@ -1448,8 +1486,8 @@
1448
1486
  else if (operation === exports.OPERATION.DELETE_BY_REFID) {
1449
1487
  // TODO: refactor here, try to follow same flow as below
1450
1488
  const refId = number(bytes, it);
1451
- const previousValue = decoder.$root.refs.get(refId);
1452
- const index = ref.findIndex((value) => value === previousValue);
1489
+ const previousValue = decoder.root.refs.get(refId);
1490
+ index = ref.findIndex((value) => value === previousValue);
1453
1491
  ref[$deleteByIndex](index);
1454
1492
  allChanges.push({
1455
1493
  ref,
@@ -1462,7 +1500,17 @@
1462
1500
  });
1463
1501
  return;
1464
1502
  }
1465
- const index = number(bytes, it);
1503
+ else if (operation === exports.OPERATION.ADD_BY_REFID) {
1504
+ const refId = number(bytes, it);
1505
+ const itemByRefId = decoder.root.refs.get(refId);
1506
+ // use existing index, or push new value
1507
+ index = (itemByRefId)
1508
+ ? ref.findIndex((value) => value === itemByRefId)
1509
+ : ref.length;
1510
+ }
1511
+ else {
1512
+ index = number(bytes, it);
1513
+ }
1466
1514
  const type = ref[$childType];
1467
1515
  let dynamicIndex = index;
1468
1516
  const { value, previousValue } = decodeValue(decoder, operation, ref, index, type, bytes, it, allChanges);
@@ -1486,6 +1534,47 @@
1486
1534
  }
1487
1535
  };
1488
1536
 
1537
+ class EncodeSchemaError extends Error {
1538
+ }
1539
+ function assertType(value, type, klass, field) {
1540
+ let typeofTarget;
1541
+ let allowNull = false;
1542
+ switch (type) {
1543
+ case "number":
1544
+ case "int8":
1545
+ case "uint8":
1546
+ case "int16":
1547
+ case "uint16":
1548
+ case "int32":
1549
+ case "uint32":
1550
+ case "int64":
1551
+ case "uint64":
1552
+ case "float32":
1553
+ case "float64":
1554
+ typeofTarget = "number";
1555
+ if (isNaN(value)) {
1556
+ console.log(`trying to encode "NaN" in ${klass.constructor.name}#${field}`);
1557
+ }
1558
+ break;
1559
+ case "string":
1560
+ typeofTarget = "string";
1561
+ allowNull = true;
1562
+ break;
1563
+ case "boolean":
1564
+ // boolean is always encoded as true/false based on truthiness
1565
+ return;
1566
+ }
1567
+ if (typeof (value) !== typeofTarget && (!allowNull || (allowNull && value !== null))) {
1568
+ let foundValue = `'${JSON.stringify(value)}'${(value && value.constructor && ` (${value.constructor.name})`) || ''}`;
1569
+ throw new EncodeSchemaError(`a '${typeofTarget}' was expected, but ${foundValue} was provided in ${klass.constructor.name}#${field}`);
1570
+ }
1571
+ }
1572
+ function assertInstanceType(value, type, instance, field) {
1573
+ if (!(value instanceof type)) {
1574
+ throw new EncodeSchemaError(`a '${type.name}' was expected, but '${value && value.constructor.name}' was provided in ${instance.constructor.name}#${field}`);
1575
+ }
1576
+ }
1577
+
1489
1578
  var _a$4, _b$4;
1490
1579
  const DEFAULT_SORT = (a, b) => {
1491
1580
  const A = a.toString();
@@ -1551,6 +1640,7 @@
1551
1640
  }
1552
1641
  else {
1553
1642
  if (setValue[$changes]) {
1643
+ assertInstanceType(setValue, obj[$childType], obj, key);
1554
1644
  if (obj.items[key] !== undefined) {
1555
1645
  if (setValue[$changes][$isNew]) {
1556
1646
  this[$changes].indexedOperation(Number(key), exports.OPERATION.MOVE_AND_ADD);
@@ -1597,6 +1687,7 @@
1597
1687
  }
1598
1688
  });
1599
1689
  this[$changes] = new ChangeTree(proxy);
1690
+ this[$changes].indexes = {};
1600
1691
  this.push.apply(this, items);
1601
1692
  return proxy;
1602
1693
  }
@@ -1621,9 +1712,11 @@
1621
1712
  if (value === undefined || value === null) {
1622
1713
  return;
1623
1714
  }
1715
+ else if (typeof (value) === "object" && this[$childType]) {
1716
+ assertInstanceType(value, this[$childType], this, i);
1717
+ }
1624
1718
  const changeTree = this[$changes];
1625
1719
  changeTree.indexedOperation(length, exports.OPERATION.ADD, this.items.length);
1626
- // changeTree.indexes[length] = length;
1627
1720
  this.items.push(value);
1628
1721
  this.tmpItems.push(value);
1629
1722
  //
@@ -1871,14 +1964,6 @@
1871
1964
  lastIndexOf(searchElement, fromIndex = this.length - 1) {
1872
1965
  return this.items.lastIndexOf(searchElement, fromIndex);
1873
1966
  }
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
1967
  every(callbackfn, thisArg) {
1883
1968
  return this.items.every(callbackfn, thisArg);
1884
1969
  }
@@ -2157,6 +2242,7 @@
2157
2242
  this.$items = new Map();
2158
2243
  this.$indexes = new Map();
2159
2244
  this[$changes] = new ChangeTree(this);
2245
+ this[$changes].indexes = {};
2160
2246
  if (initialValues) {
2161
2247
  if (initialValues instanceof Map ||
2162
2248
  initialValues instanceof MapSchema) {
@@ -2183,6 +2269,9 @@
2183
2269
  if (value === undefined || value === null) {
2184
2270
  throw new Error(`MapSchema#set('${key}', ${value}): trying to set ${value} value on '${key}'.`);
2185
2271
  }
2272
+ else if (typeof (value) === "object" && this[$childType]) {
2273
+ assertInstanceType(value, this[$childType], this, key);
2274
+ }
2186
2275
  // Force "key" as string
2187
2276
  // See: https://github.com/colyseus/colyseus/issues/561#issuecomment-1646733468
2188
2277
  key = key.toString();
@@ -2322,7 +2411,6 @@
2322
2411
  }
2323
2412
  registerType("map", { constructor: MapSchema });
2324
2413
 
2325
- const DEFAULT_VIEW_TAG = -1;
2326
2414
  class TypeContext {
2327
2415
  /**
2328
2416
  * For inheritance support
@@ -2344,6 +2432,7 @@
2344
2432
  this.types = {};
2345
2433
  this.schemas = new Map();
2346
2434
  this.hasFilters = false;
2435
+ this.parentFiltered = {};
2347
2436
  if (rootClass) {
2348
2437
  this.discoverTypes(rootClass);
2349
2438
  }
@@ -2360,44 +2449,51 @@
2360
2449
  return false;
2361
2450
  }
2362
2451
  this.types[typeid] = schema;
2452
+ //
2453
+ // Workaround to allow using an empty Schema (with no `@type()` fields)
2454
+ //
2455
+ if (schema[Symbol.metadata] === undefined) {
2456
+ Metadata.init(schema);
2457
+ }
2363
2458
  this.schemas.set(schema, typeid);
2364
2459
  return true;
2365
2460
  }
2366
2461
  getTypeId(klass) {
2367
2462
  return this.schemas.get(klass);
2368
2463
  }
2369
- discoverTypes(klass) {
2464
+ discoverTypes(klass, parentIndex, parentFieldViewTag) {
2370
2465
  if (!this.add(klass)) {
2371
2466
  return;
2372
2467
  }
2373
2468
  // add classes inherited from this base class
2374
2469
  TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
2375
- this.discoverTypes(child);
2470
+ this.discoverTypes(child, parentIndex, parentFieldViewTag);
2376
2471
  });
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];
2472
+ const metadata = (klass[Symbol.metadata] ??= {});
2383
2473
  // if any schema/field has filters, mark "context" as having filters.
2384
2474
  if (metadata[-2]) {
2385
2475
  this.hasFilters = true;
2386
2476
  }
2387
- for (const field in metadata) {
2388
- const fieldType = metadata[field].type;
2477
+ if (parentFieldViewTag !== undefined) {
2478
+ this.parentFiltered[`${this.schemas.get(klass)}-${parentIndex}`] = true;
2479
+ }
2480
+ for (const fieldIndex in metadata) {
2481
+ const index = fieldIndex;
2482
+ const fieldType = metadata[index].type;
2483
+ const viewTag = metadata[index].tag;
2389
2484
  if (typeof (fieldType) === "string") {
2390
2485
  continue;
2391
2486
  }
2392
2487
  if (Array.isArray(fieldType)) {
2393
2488
  const type = fieldType[0];
2489
+ // skip primitive types
2394
2490
  if (type === "string") {
2395
2491
  continue;
2396
2492
  }
2397
- this.discoverTypes(type);
2493
+ this.discoverTypes(type, index, viewTag);
2398
2494
  }
2399
2495
  else if (typeof (fieldType) === "function") {
2400
- this.discoverTypes(fieldType);
2496
+ this.discoverTypes(fieldType, viewTag);
2401
2497
  }
2402
2498
  else {
2403
2499
  const type = Object.values(fieldType)[0];
@@ -2405,11 +2501,13 @@
2405
2501
  if (typeof (type) === "string") {
2406
2502
  continue;
2407
2503
  }
2408
- this.discoverTypes(type);
2504
+ this.discoverTypes(type, index, viewTag);
2409
2505
  }
2410
2506
  }
2411
2507
  }
2412
2508
  }
2509
+
2510
+ const DEFAULT_VIEW_TAG = -1;
2413
2511
  /**
2414
2512
  * [See documentation](https://docs.colyseus.io/state/schema/)
2415
2513
  *
@@ -2557,18 +2655,20 @@
2557
2655
  const constructor = target.constructor;
2558
2656
  const parentClass = Object.getPrototypeOf(constructor);
2559
2657
  const parentMetadata = parentClass[Symbol.metadata];
2658
+ // TODO: use Metadata.initialize()
2560
2659
  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
- }
2660
+ // const fieldIndex = metadata[fieldName];
2661
+ // if (!metadata[fieldIndex]) {
2662
+ // //
2663
+ // // detect index for this field, considering inheritance
2664
+ // //
2665
+ // metadata[fieldIndex] = {
2666
+ // type: undefined,
2667
+ // index: (metadata[-1] // current structure already has fields defined
2668
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2669
+ // ?? -1) + 1 // no fields defined
2670
+ // }
2671
+ // }
2572
2672
  Metadata.setTag(metadata, fieldName, tag);
2573
2673
  };
2574
2674
  }
@@ -2581,18 +2681,18 @@
2581
2681
  // for inheritance support
2582
2682
  TypeContext.register(constructor);
2583
2683
  const parentClass = Object.getPrototypeOf(constructor);
2584
- const parentMetadata = parentClass[Symbol.metadata];
2585
- const metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
2586
- let fieldIndex;
2684
+ const parentMetadata = parentClass && parentClass[Symbol.metadata];
2685
+ const metadata = Metadata.initialize(constructor, parentMetadata);
2686
+ let fieldIndex = metadata[field];
2587
2687
  /**
2588
2688
  * skip if descriptor already exists for this field (`@deprecated()`)
2589
2689
  */
2590
- if (metadata[field]) {
2591
- if (metadata[field].deprecated) {
2690
+ if (metadata[fieldIndex]) {
2691
+ if (metadata[fieldIndex].deprecated) {
2592
2692
  // do not create accessors for deprecated properties.
2593
2693
  return;
2594
2694
  }
2595
- else if (metadata[field].descriptor !== undefined) {
2695
+ else if (metadata[fieldIndex].type !== undefined) {
2596
2696
  // trying to define same property multiple times across inheritance.
2597
2697
  // https://github.com/colyseus/colyseus-unity3d/issues/131#issuecomment-814308572
2598
2698
  try {
@@ -2603,9 +2703,6 @@
2603
2703
  throw new Error(`${e.message} ${definitionAtLine}`);
2604
2704
  }
2605
2705
  }
2606
- else {
2607
- fieldIndex = metadata[field].index;
2608
- }
2609
2706
  }
2610
2707
  else {
2611
2708
  //
@@ -2631,11 +2728,11 @@
2631
2728
  const childType = (complexTypeKlass)
2632
2729
  ? Object.values(type)[0]
2633
2730
  : type;
2634
- Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass, metadata, field));
2731
+ Metadata.addField(metadata, fieldIndex, field, type, getPropertyDescriptor(`_${field}`, fieldIndex, childType, complexTypeKlass));
2635
2732
  }
2636
2733
  };
2637
2734
  }
2638
- function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass, metadata, field) {
2735
+ function getPropertyDescriptor(fieldCached, fieldIndex, type, complexTypeKlass) {
2639
2736
  return {
2640
2737
  get: function () { return this[fieldCached]; },
2641
2738
  set: function (value) {
@@ -2657,22 +2754,27 @@
2657
2754
  }
2658
2755
  value[$childType] = type;
2659
2756
  }
2757
+ else if (typeof (type) !== "string") {
2758
+ assertInstanceType(value, type, this, fieldCached.substring(1));
2759
+ }
2760
+ else {
2761
+ assertType(value, type, this, fieldCached.substring(1));
2762
+ }
2763
+ const changeTree = this[$changes];
2660
2764
  //
2661
2765
  // Replacing existing "ref", remove it from root.
2662
2766
  // TODO: if there are other references to this instance, we should not remove it from root.
2663
2767
  //
2664
2768
  if (previousValue !== undefined && previousValue[$changes]) {
2665
- this[$changes].root?.remove(previousValue[$changes]);
2769
+ changeTree.root?.remove(previousValue[$changes]);
2666
2770
  }
2667
2771
  // flag the change for encoding.
2668
- this.constructor[$track](this[$changes], fieldIndex, exports.OPERATION.ADD);
2772
+ this.constructor[$track](changeTree, fieldIndex, exports.OPERATION.ADD);
2669
2773
  //
2670
2774
  // call setParent() recursively for this and its child
2671
2775
  // structures.
2672
2776
  //
2673
- if (value[$changes]) {
2674
- value[$changes].setParent(this, this[$changes].root, metadata[field].index);
2675
- }
2777
+ value[$changes]?.setParent(this, changeTree.root, fieldIndex);
2676
2778
  }
2677
2779
  else if (previousValue !== undefined) {
2678
2780
  //
@@ -2699,20 +2801,22 @@
2699
2801
  const parentClass = Object.getPrototypeOf(constructor);
2700
2802
  const parentMetadata = parentClass[Symbol.metadata];
2701
2803
  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;
2804
+ const fieldIndex = metadata[field];
2805
+ // if (!metadata[field]) {
2806
+ // //
2807
+ // // detect index for this field, considering inheritance
2808
+ // //
2809
+ // metadata[field] = {
2810
+ // type: undefined,
2811
+ // index: (metadata[-1] // current structure already has fields defined
2812
+ // ?? (parentMetadata && parentMetadata[-1]) // parent structure has fields defined
2813
+ // ?? -1) + 1 // no fields defined
2814
+ // }
2815
+ // }
2816
+ metadata[fieldIndex].deprecated = true;
2714
2817
  if (throws) {
2715
- metadata[field].descriptor = {
2818
+ metadata[$descriptors] ??= {};
2819
+ metadata[$descriptors][field] = {
2716
2820
  get: function () { throw new Error(`${field} is deprecated.`); },
2717
2821
  set: function (value) { },
2718
2822
  enumerable: false,
@@ -2720,8 +2824,8 @@
2720
2824
  };
2721
2825
  }
2722
2826
  // flag metadata[field] as non-enumerable
2723
- Object.defineProperty(metadata, field, {
2724
- value: metadata[field],
2827
+ Object.defineProperty(metadata, fieldIndex, {
2828
+ value: metadata[fieldIndex],
2725
2829
  enumerable: false,
2726
2830
  configurable: true
2727
2831
  });
@@ -2787,35 +2891,7 @@
2787
2891
  enumerable: false,
2788
2892
  writable: true
2789
2893
  });
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
- }
2894
+ Object.defineProperties(instance, instance.constructor[Symbol.metadata]?.[$descriptors] || {});
2819
2895
  }
2820
2896
  static is(type) {
2821
2897
  return typeof (type[Symbol.metadata]) === "object";
@@ -2839,7 +2915,7 @@
2839
2915
  */
2840
2916
  static [$filter](ref, index, view) {
2841
2917
  const metadata = ref.constructor[Symbol.metadata];
2842
- const tag = metadata[metadata[index]].tag;
2918
+ const tag = metadata[index]?.tag;
2843
2919
  if (view === undefined) {
2844
2920
  // shared pass/encode: encode if doesn't have a tag
2845
2921
  return tag === undefined;
@@ -2860,12 +2936,21 @@
2860
2936
  }
2861
2937
  // allow inherited classes to have a constructor
2862
2938
  constructor(...args) {
2863
- Schema.initialize(this);
2939
+ //
2940
+ // inline
2941
+ // Schema.initialize(this);
2942
+ //
2943
+ Object.defineProperty(this, $changes, {
2944
+ value: new ChangeTree(this),
2945
+ enumerable: false,
2946
+ writable: true
2947
+ });
2948
+ Object.defineProperties(this, this.constructor[Symbol.metadata]?.[$descriptors] || {});
2864
2949
  //
2865
2950
  // Assign initial values
2866
2951
  //
2867
2952
  if (args[0]) {
2868
- this.assign(args[0]);
2953
+ Object.assign(this, args[0]);
2869
2954
  }
2870
2955
  }
2871
2956
  assign(props) {
@@ -2879,7 +2964,8 @@
2879
2964
  * @param operation OPERATION to perform (detected automatically)
2880
2965
  */
2881
2966
  setDirty(property, operation) {
2882
- this[$changes].change(this.constructor[Symbol.metadata][property].index, operation);
2967
+ const metadata = this.constructor[Symbol.metadata];
2968
+ this[$changes].change(metadata[metadata[property]].index, operation);
2883
2969
  }
2884
2970
  clone() {
2885
2971
  const cloned = new (this.constructor);
@@ -2888,7 +2974,9 @@
2888
2974
  // TODO: clone all properties, not only annotated ones
2889
2975
  //
2890
2976
  // for (const field in this) {
2891
- for (const field in metadata) {
2977
+ for (const fieldIndex in metadata) {
2978
+ // const field = metadata[metadata[fieldIndex]].name;
2979
+ const field = metadata[fieldIndex].name;
2892
2980
  if (typeof (this[field]) === "object" &&
2893
2981
  typeof (this[field]?.clone) === "function") {
2894
2982
  // deep clone
@@ -2902,10 +2990,11 @@
2902
2990
  return cloned;
2903
2991
  }
2904
2992
  toJSON() {
2905
- const metadata = this.constructor[Symbol.metadata];
2906
2993
  const obj = {};
2907
- for (const fieldName in metadata) {
2908
- const field = metadata[fieldName];
2994
+ const metadata = this.constructor[Symbol.metadata];
2995
+ for (const index in metadata) {
2996
+ const field = metadata[index];
2997
+ const fieldName = field.name;
2909
2998
  if (!field.deprecated && this[fieldName] !== null && typeof (this[fieldName]) !== "undefined") {
2910
2999
  obj[fieldName] = (typeof (this[fieldName]['toJSON']) === "function")
2911
3000
  ? this[fieldName]['toJSON']()
@@ -2918,10 +3007,12 @@
2918
3007
  this[$changes].discardAll();
2919
3008
  }
2920
3009
  [$getByIndex](index) {
2921
- return this[this.constructor[Symbol.metadata][index]];
3010
+ const metadata = this.constructor[Symbol.metadata];
3011
+ return this[metadata[index].name];
2922
3012
  }
2923
3013
  [$deleteByIndex](index) {
2924
- this[this.constructor[Symbol.metadata][index]] = undefined;
3014
+ const metadata = this.constructor[Symbol.metadata];
3015
+ this[metadata[index].name] = undefined;
2925
3016
  }
2926
3017
  static debugRefIds(instance, jsonContents = true, level = 0) {
2927
3018
  const ref = instance;
@@ -2963,13 +3054,13 @@
2963
3054
  }
2964
3055
  return output;
2965
3056
  }
2966
- static debugChangesDeep(ref) {
3057
+ static debugChangesDeep(ref, changeSetName = "changes") {
2967
3058
  let output = "";
2968
3059
  const rootChangeTree = ref[$changes];
2969
3060
  const changeTrees = new Map();
2970
3061
  let totalInstances = 0;
2971
3062
  let totalOperations = 0;
2972
- for (const [changeTree, changes] of (rootChangeTree.root.changes.entries())) {
3063
+ for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
2973
3064
  let includeChangeTree = false;
2974
3065
  let parentChangeTrees = [];
2975
3066
  let parentChangeTree = changeTree.parent?.[$changes];
@@ -3045,6 +3136,7 @@
3045
3136
  this.$indexes = new Map();
3046
3137
  this.$refId = 0;
3047
3138
  this[$changes] = new ChangeTree(this);
3139
+ this[$changes].indexes = {};
3048
3140
  if (initialValues) {
3049
3141
  initialValues.forEach((v) => this.add(v));
3050
3142
  }
@@ -3200,6 +3292,7 @@
3200
3292
  this.$indexes = new Map();
3201
3293
  this.$refId = 0;
3202
3294
  this[$changes] = new ChangeTree(this);
3295
+ this[$changes].indexes = {};
3203
3296
  if (initialValues) {
3204
3297
  initialValues.forEach((v) => this.add(v));
3205
3298
  }
@@ -3355,6 +3448,8 @@
3355
3448
  OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
3356
3449
  PERFORMANCE OF THIS SOFTWARE.
3357
3450
  ***************************************************************************** */
3451
+ /* global Reflect, Promise, SuppressedError, Symbol */
3452
+
3358
3453
 
3359
3454
  function __decorate(decorators, target, key, desc) {
3360
3455
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
@@ -3368,37 +3463,82 @@
3368
3463
  return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
3369
3464
  };
3370
3465
 
3466
+ class Root {
3467
+ constructor(types) {
3468
+ this.types = types;
3469
+ this.nextUniqueId = 0;
3470
+ this.refCount = new WeakMap();
3471
+ // all changes
3472
+ this.allChanges = new Map();
3473
+ this.allFilteredChanges = new Map();
3474
+ // pending changes to be encoded
3475
+ this.changes = new Map();
3476
+ this.filteredChanges = new Map();
3477
+ }
3478
+ getNextUniqueId() {
3479
+ return this.nextUniqueId++;
3480
+ }
3481
+ add(changeTree) {
3482
+ const refCount = this.refCount.get(changeTree) || 0;
3483
+ this.refCount.set(changeTree, refCount + 1);
3484
+ }
3485
+ remove(changeTree) {
3486
+ const refCount = this.refCount.get(changeTree);
3487
+ if (refCount <= 1) {
3488
+ this.allChanges.delete(changeTree);
3489
+ this.changes.delete(changeTree);
3490
+ if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
3491
+ this.allFilteredChanges.delete(changeTree);
3492
+ this.filteredChanges.delete(changeTree);
3493
+ }
3494
+ this.refCount.delete(changeTree);
3495
+ }
3496
+ else {
3497
+ this.refCount.set(changeTree, refCount - 1);
3498
+ }
3499
+ changeTree.forEachChild((child, _) => this.remove(child));
3500
+ }
3501
+ clear() {
3502
+ this.changes.clear();
3503
+ }
3504
+ }
3505
+
3371
3506
  class Encoder {
3372
3507
  static { this.BUFFER_SIZE = 8 * 1024; } // 8KB
3373
- constructor(root) {
3508
+ constructor(state) {
3374
3509
  this.sharedBuffer = Buffer.allocUnsafeSlow(Encoder.BUFFER_SIZE);
3375
- this.setRoot(root);
3376
3510
  //
3377
3511
  // TODO: cache and restore "Context" based on root schema
3378
3512
  // (to avoid creating a new context for every new room)
3379
3513
  //
3380
- this.context = new TypeContext(root.constructor);
3514
+ this.context = new TypeContext(state.constructor);
3515
+ this.root = new Root(this.context);
3516
+ this.setState(state);
3381
3517
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
3382
3518
  // this.context.schemas.forEach((id, schema) => {
3383
3519
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3384
3520
  // });
3385
3521
  }
3386
- setRoot(state) {
3387
- this.root = new Root();
3522
+ setState(state) {
3388
3523
  this.state = state;
3389
- state[$changes].setRoot(this.root);
3524
+ this.state[$changes].setRoot(this.root);
3390
3525
  }
3391
- encode(it = { offset: 0 }, view, bytes = this.sharedBuffer, changeTrees = this.root.changes) {
3392
- const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
3393
- const isEncodeAll = this.root.allChanges === changeTrees;
3526
+ encode(it = { offset: 0 }, view, buffer = this.sharedBuffer, changeTrees = this.root.changes, isEncodeAll = this.root.allChanges === changeTrees, initialOffset = it.offset // cache current offset in case we need to resize the buffer
3527
+ ) {
3394
3528
  const hasView = (view !== undefined);
3395
3529
  const rootChangeTree = this.state[$changes];
3396
- const changeTreesIterator = changeTrees.entries();
3397
- for (const [changeTree, changes] of changeTreesIterator) {
3530
+ const shouldClearChanges = !isEncodeAll && !hasView;
3531
+ for (const [changeTree, changes] of changeTrees.entries()) {
3398
3532
  const ref = changeTree.ref;
3399
3533
  const ctor = ref['constructor'];
3400
3534
  const encoder = ctor[$encoder];
3401
3535
  const filter = ctor[$filter];
3536
+ // try { throw new Error(); } catch (e) {
3537
+ // // only print if not coming from Reflection.ts
3538
+ // if (!e.stack.includes("src/Reflection.ts")) {
3539
+ // console.log("ChangeTree:", { ref: ref.constructor.name, });
3540
+ // }
3541
+ // }
3402
3542
  if (hasView) {
3403
3543
  if (!view.items.has(changeTree)) {
3404
3544
  view.invisible.add(changeTree);
@@ -3409,9 +3549,10 @@
3409
3549
  }
3410
3550
  }
3411
3551
  // skip root `refId` if it's the first change tree
3412
- if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
3413
- bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3414
- number$1(bytes, changeTree.refId, it);
3552
+ // (unless it "hasView", which will need to revisit the root)
3553
+ if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
3554
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
3555
+ number$1(buffer, changeTree.refId, it);
3415
3556
  }
3416
3557
  const changesIterator = changes.entries();
3417
3558
  for (const [fieldIndex, operation] of changesIterator) {
@@ -3423,74 +3564,94 @@
3423
3564
  // TODO: avoid checking if no view tags were defined
3424
3565
  //
3425
3566
  if (filter && !filter(ref, fieldIndex, view)) {
3426
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
3427
3567
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
3428
3568
  // view?.invisible.add(changeTree);
3429
3569
  continue;
3430
3570
  }
3431
- // console.log("WILL ENCODE", {
3432
- // ref: changeTree.ref.constructor.name,
3433
- // fieldIndex,
3434
- // operation: OPERATION[operation],
3435
- // });
3436
- encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3571
+ // try { throw new Error(); } catch (e) {
3572
+ // // only print if not coming from Reflection.ts
3573
+ // if (!e.stack.includes("src/Reflection.ts")) {
3574
+ // console.log("WILL ENCODE", {
3575
+ // ref: changeTree.ref.constructor.name,
3576
+ // fieldIndex,
3577
+ // operation: OPERATION[operation],
3578
+ // });
3579
+ // }
3580
+ // }
3581
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
3437
3582
  }
3583
+ // if (shouldClearChanges) {
3584
+ // changeTree.endEncode();
3585
+ // }
3438
3586
  }
3439
- if (it.offset > bytes.byteLength) {
3440
- const newSize = getNextPowerOf2(this.sharedBuffer.byteLength * 2);
3441
- console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + bytes.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
3587
+ if (it.offset > buffer.byteLength) {
3588
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
3589
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
3590
+
3591
+ import { Encoder } from "@colyseus/schema";
3592
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
3593
+ `);
3442
3594
  //
3443
3595
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
3444
3596
  //
3445
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
3446
- return this.encode({ offset: initialOffset }, view);
3597
+ buffer = Buffer.allocUnsafeSlow(newSize);
3598
+ // assign resized buffer to local sharedBuffer
3599
+ if (buffer === this.sharedBuffer) {
3600
+ this.sharedBuffer = buffer;
3601
+ }
3602
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
3447
3603
  }
3448
3604
  else {
3449
3605
  //
3450
3606
  // only clear changes after making sure buffer resize is not required.
3451
3607
  //
3452
- if (!isEncodeAll && !hasView) {
3608
+ if (shouldClearChanges) {
3453
3609
  //
3454
3610
  // FIXME: avoid iterating over change trees twice.
3455
3611
  //
3456
3612
  this.onEndEncode(changeTrees);
3457
3613
  }
3458
- // return bytes;
3459
- return bytes.slice(0, it.offset);
3614
+ return buffer.subarray(0, it.offset);
3460
3615
  }
3461
3616
  }
3462
- encodeAll(it = { offset: 0 }) {
3463
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
3464
- // Array.from(this.$root.allChanges.entries()).map((item) => {
3465
- // console.log("->", item[0].refId, item[0].ref.toJSON());
3466
- // });
3467
- return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
3617
+ encodeAll(it = { offset: 0 }, buffer = this.sharedBuffer) {
3618
+ // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
3619
+ // this.debugChanges("allChanges");
3620
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
3468
3621
  }
3469
3622
  encodeAllView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3470
3623
  const viewOffset = it.offset;
3471
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
3472
- // this.debugAllFilteredChanges();
3624
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
3625
+ // this.debugChanges("allFilteredChanges");
3473
3626
  // try to encode "filtered" changes
3474
- this.encode(it, view, bytes, this.root.allFilteredChanges);
3627
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
3475
3628
  return Buffer.concat([
3476
- bytes.slice(0, sharedOffset),
3477
- bytes.slice(viewOffset, it.offset)
3629
+ bytes.subarray(0, sharedOffset),
3630
+ bytes.subarray(viewOffset, it.offset)
3478
3631
  ]);
3479
3632
  }
3480
- // debugAllFilteredChanges() {
3481
- // Array.from(this.$root.allFilteredChanges.entries()).map((item) => {
3482
- // console.log("->", { refId: item[0].refId }, item[0].ref.toJSON());
3483
- // if (Array.isArray(item[0].ref.toJSON())) {
3484
- // item[1].forEach((op, key) => {
3485
- // console.log(" ->", { key, op: OPERATION[op] });
3486
- // })
3487
- // }
3488
- // });
3489
- // }
3633
+ debugChanges(field) {
3634
+ const changeSet = (typeof (field) === "string")
3635
+ ? this.root[field]
3636
+ : field;
3637
+ Array.from(changeSet.entries()).map((item) => {
3638
+ const metadata = item[0].ref.constructor[Symbol.metadata];
3639
+ console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
3640
+ item[1].forEach((op, index) => {
3641
+ console.log(" ->", {
3642
+ index,
3643
+ field: metadata?.[index],
3644
+ op: exports.OPERATION[op],
3645
+ });
3646
+ });
3647
+ });
3648
+ }
3490
3649
  encodeView(view, sharedOffset, it, bytes = this.sharedBuffer) {
3491
3650
  const viewOffset = it.offset;
3492
- // try to encode "filtered" changes
3493
- this.encode(it, view, bytes, this.root.filteredChanges);
3651
+ // console.log(`\nencodeView(), view.changes (${view.changes.size})`);
3652
+ // this.debugChanges(view.changes);
3653
+ // console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
3654
+ // this.debugChanges("filteredChanges");
3494
3655
  // encode visibility changes (add/remove for this view)
3495
3656
  const viewChangesIterator = view.changes.entries();
3496
3657
  for (const [changeTree, changes] of viewChangesIterator) {
@@ -3517,15 +3678,22 @@
3517
3678
  //
3518
3679
  // clear "view" changes after encoding
3519
3680
  view.changes.clear();
3681
+ // try to encode "filtered" changes
3682
+ this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
3520
3683
  return Buffer.concat([
3521
- bytes.slice(0, sharedOffset),
3522
- bytes.slice(viewOffset, it.offset)
3684
+ bytes.subarray(0, sharedOffset),
3685
+ bytes.subarray(viewOffset, it.offset)
3523
3686
  ]);
3524
3687
  }
3525
3688
  onEndEncode(changeTrees = this.root.changes) {
3526
3689
  const changeTreesIterator = changeTrees.entries();
3527
3690
  for (const [changeTree, _] of changeTreesIterator) {
3528
3691
  changeTree.endEncode();
3692
+ // changeTree.changes.clear();
3693
+ // // ArraySchema and MapSchema have a custom "encode end" method
3694
+ // changeTree.ref[$onEncodeEnd]?.();
3695
+ // // Not a new instance anymore
3696
+ // delete changeTree[$isNew];
3529
3697
  }
3530
3698
  }
3531
3699
  discardChanges() {
@@ -3641,8 +3809,9 @@
3641
3809
  // Ensure child schema instances have their references removed as well.
3642
3810
  //
3643
3811
  if (Metadata.isValidInstance(ref)) {
3644
- const metadata = ref['constructor'][Symbol.metadata];
3645
- for (const field in metadata) {
3812
+ const metadata = ref.constructor[Symbol.metadata];
3813
+ for (const index in metadata) {
3814
+ const field = metadata[index].name;
3646
3815
  const childRefId = typeof (ref[field]) === "object" && this.refIds.get(ref[field]);
3647
3816
  if (childRefId) {
3648
3817
  this.removeRef(childRefId);
@@ -3689,21 +3858,21 @@
3689
3858
  class Decoder {
3690
3859
  constructor(root, context) {
3691
3860
  this.currentRefId = 0;
3692
- this.setRoot(root);
3861
+ this.setState(root);
3693
3862
  this.context = context || new TypeContext(root.constructor);
3694
3863
  // console.log(">>>>>>>>>>>>>>>> Decoder types");
3695
3864
  // this.context.schemas.forEach((id, schema) => {
3696
3865
  // console.log("type:", id, schema.name, Object.keys(schema[Symbol.metadata]));
3697
3866
  // });
3698
3867
  }
3699
- setRoot(root) {
3868
+ setState(root) {
3700
3869
  this.state = root;
3701
- this.$root = new ReferenceTracker();
3702
- this.$root.addRef(0, root);
3870
+ this.root = new ReferenceTracker();
3871
+ this.root.addRef(0, root);
3703
3872
  }
3704
3873
  decode(bytes, it = { offset: 0 }, ref = this.state) {
3705
3874
  const allChanges = [];
3706
- const $root = this.$root;
3875
+ const $root = this.root;
3707
3876
  const totalBytes = bytes.byteLength;
3708
3877
  let decoder = ref['constructor'][$decoder];
3709
3878
  this.currentRefId = 0;
@@ -3784,7 +3953,7 @@
3784
3953
  previousValue: value
3785
3954
  });
3786
3955
  if (needRemoveRef) {
3787
- this.$root.removeRef(this.$root.refIds.get(value));
3956
+ this.root.removeRef(this.root.refIds.get(value));
3788
3957
  }
3789
3958
  });
3790
3959
  }
@@ -3824,14 +3993,14 @@
3824
3993
  super(...arguments);
3825
3994
  this.types = new ArraySchema();
3826
3995
  }
3827
- static encode(instance, context) {
3828
- if (!context) {
3829
- context = new TypeContext(instance.constructor);
3830
- }
3996
+ static encode(instance, context, it = { offset: 0 }) {
3997
+ context ??= new TypeContext(instance.constructor);
3831
3998
  const reflection = new Reflection();
3832
3999
  const encoder = new Encoder(reflection);
3833
4000
  const buildType = (currentType, metadata) => {
3834
- for (const fieldName in metadata) {
4001
+ for (const fieldIndex in metadata) {
4002
+ const index = Number(fieldIndex);
4003
+ const fieldName = metadata[index].name;
3835
4004
  // skip fields from parent classes
3836
4005
  if (!Object.prototype.hasOwnProperty.call(metadata, fieldName)) {
3837
4006
  continue;
@@ -3839,7 +4008,7 @@
3839
4008
  const field = new ReflectionField();
3840
4009
  field.name = fieldName;
3841
4010
  let fieldType;
3842
- const type = metadata[fieldName].type;
4011
+ const type = metadata[index].type;
3843
4012
  if (typeof (type) === "string") {
3844
4013
  fieldType = type;
3845
4014
  }
@@ -3881,7 +4050,6 @@
3881
4050
  }
3882
4051
  buildType(type, klass[Symbol.metadata]);
3883
4052
  }
3884
- const it = { offset: 0 };
3885
4053
  const buf = encoder.encodeAll(it);
3886
4054
  return Buffer.from(buf, 0, it.offset);
3887
4055
  }
@@ -3889,59 +4057,304 @@
3889
4057
  const reflection = new Reflection();
3890
4058
  const reflectionDecoder = new Decoder(reflection);
3891
4059
  reflectionDecoder.decode(bytes, it);
3892
- const context = new TypeContext();
3893
- const schemaTypes = reflection.types.reduce((types, reflectionType) => {
3894
- const parentKlass = types[reflectionType.extendsId] || Schema;
3895
- const schema = class _ extends parentKlass {
4060
+ const typeContext = new TypeContext();
4061
+ // 1st pass, initialize metadata + inheritance
4062
+ reflection.types.forEach((reflectionType) => {
4063
+ const parentClass = typeContext.get(reflectionType.extendsId) ?? Schema;
4064
+ const schema = class _ extends parentClass {
3896
4065
  };
3897
- // const _metadata = Object.create(_classSuper[Symbol.metadata] ?? null);
3898
- const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
3899
- Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata });
4066
+ const parentMetadata = parentClass[Symbol.metadata];
3900
4067
  // register for inheritance support
3901
4068
  TypeContext.register(schema);
3902
- const typeid = reflectionType.id;
3903
- types[typeid] = schema;
3904
- context.add(schema, typeid);
3905
- return types;
4069
+ // for inheritance support
4070
+ Metadata.initialize(schema, parentMetadata);
4071
+ typeContext.add(schema, reflectionType.id);
3906
4072
  }, {});
4073
+ // 2nd pass, set fields
3907
4074
  reflection.types.forEach((reflectionType) => {
3908
- const schemaType = schemaTypes[reflectionType.id];
4075
+ const schemaType = typeContext.get(reflectionType.id);
3909
4076
  const metadata = schemaType[Symbol.metadata];
3910
- const parentKlass = reflection.types[reflectionType.extendsId];
3911
- const parentFieldIndex = parentKlass && parentKlass.fields.length || 0;
4077
+ const parentFieldIndex = 0;
3912
4078
  reflectionType.fields.forEach((field, i) => {
3913
4079
  const fieldIndex = parentFieldIndex + i;
3914
4080
  if (field.referencedType !== undefined) {
3915
4081
  let fieldType = field.type;
3916
- let refType = schemaTypes[field.referencedType];
4082
+ let refType = typeContext.get(field.referencedType);
3917
4083
  // map or array of primitive type (-1)
3918
4084
  if (!refType) {
3919
4085
  const typeInfo = field.type.split(":");
3920
4086
  fieldType = typeInfo[0];
3921
- refType = typeInfo[1];
4087
+ refType = typeInfo[1]; // string
3922
4088
  }
3923
4089
  if (fieldType === "ref") {
3924
- // type(refType)(schemaType.prototype, field.name);
3925
4090
  Metadata.addField(metadata, fieldIndex, field.name, refType);
3926
4091
  }
3927
4092
  else {
3928
- // type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
3929
4093
  Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType });
3930
4094
  }
3931
4095
  }
3932
4096
  else {
3933
- // type(field.type as PrimitiveType)(schemaType.prototype, field.name);
3934
4097
  Metadata.addField(metadata, fieldIndex, field.name, field.type);
3935
4098
  }
3936
4099
  });
3937
4100
  });
3938
- return new (schemaTypes[0])();
4101
+ // @ts-ignore
4102
+ return new (typeContext.get(0))();
3939
4103
  }
3940
4104
  }
3941
4105
  __decorate([
3942
4106
  type([ReflectionType])
3943
4107
  ], Reflection.prototype, "types", void 0);
3944
4108
 
4109
+ function getDecoderStateCallbacks(decoder) {
4110
+ const $root = decoder.root;
4111
+ const callbacks = $root.callbacks;
4112
+ const onAddCalls = new WeakMap();
4113
+ let currentOnAddCallback;
4114
+ decoder.triggerChanges = function (allChanges) {
4115
+ const uniqueRefIds = new Set();
4116
+ for (let i = 0, l = allChanges.length; i < l; i++) {
4117
+ const change = allChanges[i];
4118
+ const refId = change.refId;
4119
+ const ref = change.ref;
4120
+ const $callbacks = callbacks[refId];
4121
+ if (!$callbacks) {
4122
+ continue;
4123
+ }
4124
+ //
4125
+ // trigger onRemove on child structure.
4126
+ //
4127
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE &&
4128
+ change.previousValue instanceof Schema) {
4129
+ const deleteCallbacks = callbacks[$root.refIds.get(change.previousValue)]?.[exports.OPERATION.DELETE];
4130
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4131
+ deleteCallbacks[i]();
4132
+ }
4133
+ }
4134
+ if (ref instanceof Schema) {
4135
+ //
4136
+ // Handle schema instance
4137
+ //
4138
+ if (!uniqueRefIds.has(refId)) {
4139
+ // trigger onChange
4140
+ const replaceCallbacks = $callbacks?.[exports.OPERATION.REPLACE];
4141
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4142
+ replaceCallbacks[i]();
4143
+ // try {
4144
+ // } catch (e) {
4145
+ // console.error(e);
4146
+ // }
4147
+ }
4148
+ }
4149
+ if ($callbacks.hasOwnProperty(change.field)) {
4150
+ const fieldCallbacks = $callbacks[change.field];
4151
+ for (let i = fieldCallbacks?.length - 1; i >= 0; i--) {
4152
+ fieldCallbacks[i](change.value, change.previousValue);
4153
+ // try {
4154
+ // } catch (e) {
4155
+ // console.error(e);
4156
+ // }
4157
+ }
4158
+ }
4159
+ }
4160
+ else {
4161
+ //
4162
+ // Handle collection of items
4163
+ //
4164
+ if ((change.op & exports.OPERATION.DELETE) === exports.OPERATION.DELETE) {
4165
+ //
4166
+ // FIXME: `previousValue` should always be available.
4167
+ //
4168
+ if (change.previousValue !== undefined) {
4169
+ // triger onRemove
4170
+ const deleteCallbacks = $callbacks[exports.OPERATION.DELETE];
4171
+ for (let i = deleteCallbacks?.length - 1; i >= 0; i--) {
4172
+ deleteCallbacks[i](change.previousValue, change.dynamicIndex ?? change.field);
4173
+ }
4174
+ }
4175
+ // Handle DELETE_AND_ADD operations
4176
+ if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD) {
4177
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4178
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4179
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4180
+ }
4181
+ }
4182
+ }
4183
+ else if ((change.op & exports.OPERATION.ADD) === exports.OPERATION.ADD && change.previousValue === undefined) {
4184
+ // triger onAdd
4185
+ const addCallbacks = $callbacks[exports.OPERATION.ADD];
4186
+ for (let i = addCallbacks?.length - 1; i >= 0; i--) {
4187
+ addCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4188
+ }
4189
+ }
4190
+ // trigger onChange
4191
+ if (change.value !== change.previousValue) {
4192
+ const replaceCallbacks = $callbacks[exports.OPERATION.REPLACE];
4193
+ for (let i = replaceCallbacks?.length - 1; i >= 0; i--) {
4194
+ replaceCallbacks[i](change.value, change.dynamicIndex ?? change.field);
4195
+ }
4196
+ }
4197
+ }
4198
+ uniqueRefIds.add(refId);
4199
+ }
4200
+ };
4201
+ function getProxy(metadataOrType, context) {
4202
+ let metadata = context.instance?.constructor[Symbol.metadata] || metadataOrType;
4203
+ let isCollection = ((context.instance && typeof (context.instance['forEach']) === "function") ||
4204
+ (metadataOrType && typeof (metadataOrType[Symbol.metadata]) === "undefined"));
4205
+ if (metadata && !isCollection) {
4206
+ const onAddListen = function (ref, prop, callback, immediate) {
4207
+ // immediate trigger
4208
+ if (immediate &&
4209
+ context.instance[prop] !== undefined &&
4210
+ !onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
4211
+ ) {
4212
+ callback(context.instance[prop], undefined);
4213
+ }
4214
+ return $root.addCallback($root.refIds.get(ref), prop, callback);
4215
+ };
4216
+ /**
4217
+ * Schema instances
4218
+ */
4219
+ return new Proxy({
4220
+ listen: function listen(prop, callback, immediate = true) {
4221
+ if (context.instance) {
4222
+ return onAddListen(context.instance, prop, callback, immediate);
4223
+ }
4224
+ else {
4225
+ // collection instance not received yet
4226
+ let detachCallback = () => { };
4227
+ context.onInstanceAvailable((ref, existing) => {
4228
+ detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4229
+ });
4230
+ return () => detachCallback();
4231
+ }
4232
+ },
4233
+ onChange: function onChange(callback) {
4234
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, callback);
4235
+ },
4236
+ //
4237
+ // TODO: refactor `bindTo()` implementation.
4238
+ // There is room for improvement.
4239
+ //
4240
+ bindTo: function bindTo(targetObject, properties) {
4241
+ if (!properties) {
4242
+ properties = Object.keys(metadata).map((index) => metadata[index].name);
4243
+ }
4244
+ return $root.addCallback($root.refIds.get(context.instance), exports.OPERATION.REPLACE, () => {
4245
+ properties.forEach((prop) => targetObject[prop] = context.instance[prop]);
4246
+ });
4247
+ }
4248
+ }, {
4249
+ get(target, prop) {
4250
+ const metadataField = metadata[metadata[prop]];
4251
+ if (metadataField) {
4252
+ const instance = context.instance?.[prop];
4253
+ const onInstanceAvailable = ((callback) => {
4254
+ const unbind = $(context.instance).listen(prop, (value, _) => {
4255
+ callback(value, false);
4256
+ // FIXME: by "unbinding" the callback here,
4257
+ // it will not support when the server
4258
+ // re-instantiates the instance.
4259
+ //
4260
+ unbind?.();
4261
+ }, false);
4262
+ // has existing value
4263
+ if ($root.refIds.get(instance) !== undefined) {
4264
+ callback(instance, true);
4265
+ }
4266
+ });
4267
+ return getProxy(metadataField.type, {
4268
+ // make sure refId is available, otherwise need to wait for the instance to be available.
4269
+ instance: ($root.refIds.get(instance) && instance),
4270
+ parentInstance: context.instance,
4271
+ onInstanceAvailable,
4272
+ });
4273
+ }
4274
+ else {
4275
+ // accessing the function
4276
+ return target[prop];
4277
+ }
4278
+ },
4279
+ has(target, prop) { return metadata[prop] !== undefined; },
4280
+ set(_, _1, _2) { throw new Error("not allowed"); },
4281
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4282
+ });
4283
+ }
4284
+ else {
4285
+ /**
4286
+ * Collection instances
4287
+ */
4288
+ const onAdd = function (ref, callback, immediate) {
4289
+ // Trigger callback on existing items
4290
+ if (immediate) {
4291
+ ref.forEach((v, k) => callback(v, k));
4292
+ }
4293
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.ADD, (value, key) => {
4294
+ onAddCalls.set(callback, true);
4295
+ currentOnAddCallback = callback;
4296
+ callback(value, key);
4297
+ onAddCalls.delete(callback);
4298
+ currentOnAddCallback = undefined;
4299
+ });
4300
+ };
4301
+ const onRemove = function (ref, callback) {
4302
+ return $root.addCallback($root.refIds.get(ref), exports.OPERATION.DELETE, callback);
4303
+ };
4304
+ return new Proxy({
4305
+ onAdd: function (callback, immediate = true) {
4306
+ //
4307
+ // https://github.com/colyseus/schema/issues/147
4308
+ // If parent instance has "onAdd" registered, avoid triggering immediate callback.
4309
+ //
4310
+ if (context.instance) {
4311
+ return onAdd(context.instance, callback, immediate && !onAddCalls.has(currentOnAddCallback));
4312
+ }
4313
+ else if (context.onInstanceAvailable) {
4314
+ // collection instance not received yet
4315
+ let detachCallback = () => { };
4316
+ context.onInstanceAvailable((ref, existing) => {
4317
+ detachCallback = onAdd(ref, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback));
4318
+ });
4319
+ return () => detachCallback();
4320
+ }
4321
+ },
4322
+ onRemove: function (callback) {
4323
+ if (context.onInstanceAvailable) {
4324
+ // collection instance not received yet
4325
+ let detachCallback = () => { };
4326
+ context.onInstanceAvailable((ref) => {
4327
+ detachCallback = onRemove(ref, callback);
4328
+ });
4329
+ return () => detachCallback();
4330
+ }
4331
+ else if (context.instance) {
4332
+ return onRemove(context.instance, callback);
4333
+ }
4334
+ },
4335
+ }, {
4336
+ get(target, prop) {
4337
+ if (!target[prop]) {
4338
+ throw new Error(`Can't access '${prop}' through callback proxy. access the instance directly.`);
4339
+ }
4340
+ return target[prop];
4341
+ },
4342
+ has(target, prop) { return target[prop] !== undefined; },
4343
+ set(_, _1, _2) { throw new Error("not allowed"); },
4344
+ deleteProperty(_, _1) { throw new Error("not allowed"); },
4345
+ });
4346
+ }
4347
+ }
4348
+ function $(instance) {
4349
+ return getProxy(undefined, { instance });
4350
+ }
4351
+ return $;
4352
+ }
4353
+
4354
+ function getRawChangesCallback(decoder, callback) {
4355
+ decoder.triggerChanges = callback;
4356
+ }
4357
+
3945
4358
  class StateView {
3946
4359
  constructor() {
3947
4360
  /**
@@ -3959,20 +4372,21 @@
3959
4372
  this.changes = new Map();
3960
4373
  }
3961
4374
  // TODO: allow to set multiple tags at once
3962
- add(obj, tag = DEFAULT_VIEW_TAG) {
4375
+ add(obj, tag = DEFAULT_VIEW_TAG, checkIncludeParent = true) {
3963
4376
  if (!obj[$changes]) {
3964
4377
  console.warn("StateView#add(), invalid object:", obj);
3965
4378
  return this;
3966
4379
  }
3967
- let changeTree = obj[$changes];
3968
- this.items.add(changeTree);
3969
- // Add children of this ChangeTree to this view
3970
- changeTree.forEachChild((change, _) => this.add(change.ref, tag));
3971
- // FIXME: ArraySchema/MapSchema does not have metadata
4380
+ // FIXME: ArraySchema/MapSchema do not have metadata
3972
4381
  const metadata = obj.constructor[Symbol.metadata];
3973
- // add parent ChangeTree's, if they are invisible to this view
3974
- // TODO: REFACTOR addParent()
3975
- this.addParent(changeTree, tag);
4382
+ const changeTree = obj[$changes];
4383
+ this.items.add(changeTree);
4384
+ // add parent ChangeTree's
4385
+ // - if it was invisible to this view
4386
+ // - if it were previously filtered out
4387
+ if (checkIncludeParent && changeTree.parent) {
4388
+ this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
4389
+ }
3976
4390
  //
3977
4391
  // TODO: when adding an item of a MapSchema, the changes may not
3978
4392
  // be set (only the parent's changes are set)
@@ -3996,7 +4410,6 @@
3996
4410
  tags = this.tags.get(changeTree);
3997
4411
  }
3998
4412
  tags.add(tag);
3999
- // console.log("BY TAG:", tag);
4000
4413
  // Ref: add tagged properties
4001
4414
  metadata?.[-3]?.[tag]?.forEach((index) => {
4002
4415
  if (changeTree.getChange(index) !== exports.OPERATION.DELETE) {
@@ -4005,73 +4418,63 @@
4005
4418
  });
4006
4419
  }
4007
4420
  else {
4008
- // console.log("DEFAULT TAG", changeTree.allChanges);
4009
- // // add default tag properties
4010
- // metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
4011
- // if (changeTree.getChange(index) !== OPERATION.DELETE) {
4012
- // changes.set(index, OPERATION.ADD);
4013
- // }
4014
- // });
4015
- const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4421
+ const isInvisible = this.invisible.has(changeTree);
4422
+ const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
4016
4423
  ? changeTree.allFilteredChanges
4017
4424
  : changeTree.allChanges;
4018
- const it = allChangesSet.keys();
4019
- const isInvisible = this.invisible.has(changeTree);
4020
- for (const index of it) {
4021
- if ((isInvisible || metadata?.[metadata?.[index]].tag === tag) &&
4022
- changeTree.getChange(index) !== exports.OPERATION.DELETE) {
4023
- changes.set(index, exports.OPERATION.ADD);
4425
+ changeSet.forEach((op, index) => {
4426
+ const tagAtIndex = metadata?.[index].tag;
4427
+ if ((isInvisible || // if "invisible", include all
4428
+ tagAtIndex === undefined || // "all change" with no tag
4429
+ tagAtIndex === tag // tagged property
4430
+ ) &&
4431
+ op !== exports.OPERATION.DELETE) {
4432
+ changes.set(index, op);
4024
4433
  }
4025
- }
4026
- }
4027
- // TODO: avoid unnecessary iteration here
4028
- while (changeTree.parent &&
4029
- (changeTree = changeTree.parent[$changes]) &&
4030
- (changeTree.isFiltered || changeTree.isPartiallyFiltered)) {
4031
- this.items.add(changeTree);
4434
+ });
4032
4435
  }
4436
+ // Add children of this ChangeTree to this view
4437
+ changeTree.forEachChild((change, index) => {
4438
+ // Do not ADD children that don't have the same tag
4439
+ if (metadata && metadata[index].tag !== tag) {
4440
+ return;
4441
+ }
4442
+ this.add(change.ref, tag, false);
4443
+ });
4033
4444
  return this;
4034
4445
  }
4035
- addParent(changeTree, tag) {
4036
- const parentRef = changeTree.parent;
4037
- if (!parentRef) {
4038
- return;
4446
+ addParent(changeTree, parentIndex, tag) {
4447
+ // view must have all "changeTree" parent tree
4448
+ this.items.add(changeTree);
4449
+ // add parent's parent
4450
+ const parentChangeTree = changeTree.parent?.[$changes];
4451
+ if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
4452
+ this.addParent(parentChangeTree, changeTree.parentIndex, tag);
4039
4453
  }
4040
- const parentChangeTree = parentRef[$changes];
4041
- const parentIndex = changeTree.parentIndex;
4042
- if (!this.invisible.has(parentChangeTree)) {
4043
- // parent is already available, no need to add it!
4454
+ // parent is already available, no need to add it!
4455
+ if (!this.invisible.has(changeTree)) {
4044
4456
  return;
4045
4457
  }
4046
- this.addParent(parentChangeTree, tag);
4047
4458
  // add parent's tag properties
4048
- if (parentChangeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4049
- let parentChanges = this.changes.get(parentChangeTree);
4050
- if (parentChanges === undefined) {
4051
- parentChanges = new Map();
4052
- this.changes.set(parentChangeTree, parentChanges);
4459
+ if (changeTree.getChange(parentIndex) !== exports.OPERATION.DELETE) {
4460
+ let changes = this.changes.get(changeTree);
4461
+ if (changes === undefined) {
4462
+ changes = new Map();
4463
+ this.changes.set(changeTree, changes);
4053
4464
  }
4054
- // console.log("add parent change", {
4055
- // parentIndex,
4056
- // parentChanges,
4057
- // parentChange: (
4058
- // parentChangeTree.getChange(parentIndex) &&
4059
- // OPERATION[parentChangeTree.getChange(parentIndex)]
4060
- // ),
4061
- // })
4062
4465
  if (!this.tags) {
4063
4466
  this.tags = new WeakMap();
4064
4467
  }
4065
4468
  let tags;
4066
- if (!this.tags.has(parentChangeTree)) {
4469
+ if (!this.tags.has(changeTree)) {
4067
4470
  tags = new Set();
4068
- this.tags.set(parentChangeTree, tags);
4471
+ this.tags.set(changeTree, tags);
4069
4472
  }
4070
4473
  else {
4071
- tags = this.tags.get(parentChangeTree);
4474
+ tags = this.tags.get(changeTree);
4072
4475
  }
4073
4476
  tags.add(tag);
4074
- parentChanges.set(parentIndex, exports.OPERATION.ADD);
4477
+ changes.set(parentIndex, exports.OPERATION.ADD);
4075
4478
  }
4076
4479
  }
4077
4480
  remove(obj, tag = DEFAULT_VIEW_TAG) {
@@ -4166,10 +4569,10 @@
4166
4569
  exports.encode = encode;
4167
4570
  exports.encodeKeyValueOperation = encodeArray;
4168
4571
  exports.encodeSchemaOperation = encodeSchemaOperation;
4572
+ exports.getDecoderStateCallbacks = getDecoderStateCallbacks;
4573
+ exports.getRawChangesCallback = getRawChangesCallback;
4169
4574
  exports.registerType = registerType;
4170
4575
  exports.type = type;
4171
4576
  exports.view = view;
4172
4577
 
4173
- Object.defineProperty(exports, '__esModule', { value: true });
4174
-
4175
4578
  }));