@colyseus/schema 3.0.0-alpha.34 → 3.0.0-alpha.36

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 (71) hide show
  1. package/bin/schema-debug +4 -3
  2. package/build/cjs/index.js +612 -408
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +612 -408
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +612 -408
  7. package/lib/Metadata.d.ts +6 -6
  8. package/lib/Metadata.js +48 -21
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Reflection.d.ts +17 -2
  11. package/lib/Reflection.js +19 -6
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.js +24 -17
  14. package/lib/Schema.js.map +1 -1
  15. package/lib/annotations.d.ts +1 -1
  16. package/lib/annotations.js +13 -16
  17. package/lib/annotations.js.map +1 -1
  18. package/lib/bench_encode.js +12 -5
  19. package/lib/bench_encode.js.map +1 -1
  20. package/lib/debug.js +1 -2
  21. package/lib/debug.js.map +1 -1
  22. package/lib/decoder/Decoder.js +1 -1
  23. package/lib/decoder/Decoder.js.map +1 -1
  24. package/lib/encoder/ChangeTree.d.ts +23 -7
  25. package/lib/encoder/ChangeTree.js +183 -106
  26. package/lib/encoder/ChangeTree.js.map +1 -1
  27. package/lib/encoder/EncodeOperation.d.ts +2 -1
  28. package/lib/encoder/EncodeOperation.js +2 -2
  29. package/lib/encoder/EncodeOperation.js.map +1 -1
  30. package/lib/encoder/Encoder.d.ts +3 -5
  31. package/lib/encoder/Encoder.js +97 -61
  32. package/lib/encoder/Encoder.js.map +1 -1
  33. package/lib/encoder/Root.d.ts +12 -7
  34. package/lib/encoder/Root.js +41 -20
  35. package/lib/encoder/Root.js.map +1 -1
  36. package/lib/encoder/StateView.d.ts +5 -5
  37. package/lib/encoder/StateView.js +29 -23
  38. package/lib/encoder/StateView.js.map +1 -1
  39. package/lib/encoding/encode.js +12 -9
  40. package/lib/encoding/encode.js.map +1 -1
  41. package/lib/types/TypeContext.js +2 -1
  42. package/lib/types/TypeContext.js.map +1 -1
  43. package/lib/types/custom/ArraySchema.js +27 -13
  44. package/lib/types/custom/ArraySchema.js.map +1 -1
  45. package/lib/types/custom/MapSchema.d.ts +3 -1
  46. package/lib/types/custom/MapSchema.js +7 -4
  47. package/lib/types/custom/MapSchema.js.map +1 -1
  48. package/lib/types/symbols.d.ts +8 -6
  49. package/lib/types/symbols.js +9 -7
  50. package/lib/types/symbols.js.map +1 -1
  51. package/lib/utils.js +6 -3
  52. package/lib/utils.js.map +1 -1
  53. package/package.json +1 -1
  54. package/src/Metadata.ts +51 -27
  55. package/src/Reflection.ts +21 -8
  56. package/src/Schema.ts +33 -25
  57. package/src/annotations.ts +14 -18
  58. package/src/bench_encode.ts +15 -6
  59. package/src/debug.ts +1 -2
  60. package/src/decoder/Decoder.ts +1 -1
  61. package/src/encoder/ChangeTree.ts +220 -115
  62. package/src/encoder/EncodeOperation.ts +5 -4
  63. package/src/encoder/Encoder.ts +115 -68
  64. package/src/encoder/Root.ts +41 -21
  65. package/src/encoder/StateView.ts +32 -28
  66. package/src/encoding/encode.ts +12 -9
  67. package/src/types/TypeContext.ts +2 -1
  68. package/src/types/custom/ArraySchema.ts +39 -17
  69. package/src/types/custom/MapSchema.ts +12 -5
  70. package/src/types/symbols.ts +10 -9
  71. package/src/utils.ts +7 -3
@@ -1,6 +1,6 @@
1
1
  import type { Schema } from "../Schema";
2
2
  import { TypeContext } from "../types/TypeContext";
3
- import { $changes, $encoder, $filter, $isNew, $onEncodeEnd } from "../types/symbols";
3
+ import { $changes, $encoder, $filter, $onEncodeEnd } from "../types/symbols";
4
4
 
5
5
  import * as encode from "../encoding/encode";
6
6
  import type { Iterator } from "../encoding/decode";
@@ -11,7 +11,6 @@ import { getNextPowerOf2 } from "../utils";
11
11
 
12
12
  import type { StateView } from "./StateView";
13
13
  import type { Metadata } from "../Metadata";
14
- import type { ChangeTree } from "./ChangeTree";
15
14
 
16
15
  export class Encoder<T extends Schema = any> {
17
16
  static BUFFER_SIZE = 8 * 1024;// 8KB
@@ -48,26 +47,34 @@ export class Encoder<T extends Schema = any> {
48
47
  it: Iterator = { offset: 0 },
49
48
  view?: StateView,
50
49
  buffer = this.sharedBuffer,
51
- changeTrees = this.root.changes,
52
- isEncodeAll = this.root.allChanges === changeTrees,
50
+ changeSetName: "changes" | "allChanges" | "filteredChanges" | "allFilteredChanges" = "changes",
51
+ isEncodeAll = changeSetName === "allChanges",
53
52
  initialOffset = it.offset // cache current offset in case we need to resize the buffer
54
53
  ): Buffer {
55
54
  const hasView = (view !== undefined);
56
55
  const rootChangeTree = this.state[$changes];
57
56
 
58
- const shouldClearChanges = !isEncodeAll && !hasView;
57
+ const shouldDiscardChanges = !isEncodeAll && !hasView;
58
+ const changeTrees = this.root[changeSetName];
59
59
 
60
- for (const [changeTree, changes] of changeTrees.entries()) {
60
+ for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
61
+ const changeTree = changeTrees[i];
62
+
63
+ // // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
64
+ // if (changeTree === undefined) { continue; }
65
+
66
+ const operations = changeTree[changeSetName];
61
67
  const ref = changeTree.ref;
62
68
 
63
69
  const ctor = ref.constructor;
64
70
  const encoder = ctor[$encoder];
65
71
  const filter = ctor[$filter];
72
+ const metadata = ctor[Symbol.metadata];
66
73
 
67
74
  // try { throw new Error(); } catch (e) {
68
75
  // // only print if not coming from Reflection.ts
69
76
  // if (!e.stack.includes("src/Reflection.ts")) {
70
- // console.log("ChangeTree:", { ref: ref.constructor.name, });
77
+ // console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
71
78
  // }
72
79
  // }
73
80
 
@@ -88,7 +95,15 @@ export class Encoder<T extends Schema = any> {
88
95
  encode.number(buffer, changeTree.refId, it);
89
96
  }
90
97
 
91
- for (const [fieldIndex, operation] of changes.entries()) {
98
+ for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
99
+ const fieldIndex = operations.operations[j];
100
+
101
+ const operation = (fieldIndex < 0)
102
+ ? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
103
+ : (isEncodeAll)
104
+ ? OPERATION.ADD
105
+ : changeTree.indexedOperations[fieldIndex];
106
+
92
107
  //
93
108
  // first pass (encodeAll), identify "filtered" operations without encoding them
94
109
  // they will be encoded per client, based on their view.
@@ -96,7 +111,7 @@ export class Encoder<T extends Schema = any> {
96
111
  // TODO: how can we optimize filtering out "encode all" operations?
97
112
  // TODO: avoid checking if no view tags were defined
98
113
  //
99
- if (filter && !filter(ref, fieldIndex, view)) {
114
+ if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
100
115
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
101
116
  // view?.invisible.add(changeTree);
102
117
  continue;
@@ -113,19 +128,17 @@ export class Encoder<T extends Schema = any> {
113
128
  // }
114
129
  // }
115
130
 
116
- encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
117
- }
131
+ // console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
118
132
 
119
- // if (shouldClearChanges) {
120
- // // changeTree.endEncode();
121
- // changeTree.changes.clear();
133
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
134
+ }
122
135
 
123
- // // ArraySchema and MapSchema have a custom "encode end" method
124
- // changeTree.ref[$onEncodeEnd]?.();
136
+ if (shouldDiscardChanges) {
137
+ changeTree.discard();
125
138
 
126
- // // Not a new instance anymore
127
- // delete changeTree[$isNew];
128
- // }
139
+ // Not a new instance anymore
140
+ changeTree.isNew = false;
141
+ }
129
142
  }
130
143
 
131
144
  if (it.offset > buffer.byteLength) {
@@ -146,38 +159,40 @@ export class Encoder<T extends Schema = any> {
146
159
  this.sharedBuffer = buffer;
147
160
  }
148
161
 
149
- return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
162
+ return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
150
163
 
151
164
  } else {
152
- //
153
- // only clear changes after making sure buffer resize is not required.
154
- //
155
- if (shouldClearChanges) {
156
- //
157
- // FIXME: avoid iterating over change trees twice.
158
- //
159
- this.onEndEncode(changeTrees);
160
- }
165
+ // //
166
+ // // only clear changes after making sure buffer resize is not required.
167
+ // //
168
+ // if (shouldClearChanges) {
169
+ // //
170
+ // // FIXME: avoid iterating over change trees twice.
171
+ // //
172
+ // this.onEndEncode(changeTrees);
173
+ // }
161
174
 
162
175
  return buffer.subarray(0, it.offset);
163
176
  }
164
177
  }
165
178
 
166
179
  encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
167
- // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
180
+ // console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
168
181
  // this.debugChanges("allChanges");
169
182
 
170
- return this.encode(it, undefined, buffer, this.root.allChanges, true);
183
+ return this.encode(it, undefined, buffer, "allChanges", true);
171
184
  }
172
185
 
173
186
  encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
174
187
  const viewOffset = it.offset;
175
188
 
176
- // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
189
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
177
190
  // this.debugChanges("allFilteredChanges");
178
191
 
192
+ // console.log("\n\nENCODE ALL FOR VIEW...\n\n")
193
+
179
194
  // try to encode "filtered" changes
180
- this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
195
+ this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
181
196
 
182
197
  return Buffer.concat([
183
198
  bytes.subarray(0, sharedOffset),
@@ -185,23 +200,24 @@ export class Encoder<T extends Schema = any> {
185
200
  ]);
186
201
  }
187
202
 
188
- debugChanges(
189
- field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges" | Map<ChangeTree, Map<number, OPERATION>>
190
- ) {
191
- const changeSet = (typeof (field) === "string")
203
+ debugChanges(field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges") {
204
+ const rootChangeSet = (typeof (field) === "string")
192
205
  ? this.root[field]
193
206
  : field;
194
207
 
195
- Array.from(changeSet.entries()).map((item) => {
196
- const metadata: Metadata = item[0].ref.constructor[Symbol.metadata];
197
- console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
198
- item[1].forEach((op, index) => {
208
+ rootChangeSet.forEach((changeTree) => {
209
+ const changeSet = changeTree[field];
210
+
211
+ const metadata: Metadata = changeTree.ref.constructor[Symbol.metadata];
212
+ console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
213
+ for (const index in changeSet) {
214
+ const op = changeSet[index];
199
215
  console.log(" ->", {
200
216
  index,
201
217
  field: metadata?.[index],
202
218
  op: OPERATION[op],
203
219
  });
204
- });
220
+ }
205
221
  });
206
222
  }
207
223
 
@@ -215,28 +231,38 @@ export class Encoder<T extends Schema = any> {
215
231
  // this.debugChanges("filteredChanges");
216
232
 
217
233
  // encode visibility changes (add/remove for this view)
218
- const viewChangesIterator = view.changes.entries();
219
- for (const [changeTree, changes] of viewChangesIterator) {
220
- if (changes.size === 0) {
221
- // FIXME: avoid having empty changes if no changes were made
222
- // console.log("changes.size === 0", changeTree.ref.constructor.name);
234
+ const refIds = Object.keys(view.changes);
235
+ // console.log("ENCODE VIEW:", refIds);
236
+ for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
237
+ const refId = refIds[i];
238
+ const changes = view.changes[refId];
239
+ const changeTree = this.root.changeTrees[refId];
240
+
241
+ if (
242
+ changeTree === undefined ||
243
+ Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
244
+ ) {
245
+ // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
223
246
  continue;
224
247
  }
225
248
 
226
249
  const ref = changeTree.ref;
227
250
 
228
- const ctor = ref['constructor'];
251
+ const ctor = ref.constructor;
229
252
  const encoder = ctor[$encoder];
253
+ const metadata = ctor[Symbol.metadata];
230
254
 
231
255
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
232
256
  encode.number(bytes, changeTree.refId, it);
233
257
 
234
- const changesIterator = changes.entries();
258
+ const keys = Object.keys(changes);
259
+ for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
260
+ const key = keys[i];
261
+ const operation = changes[key];
235
262
 
236
- for (const [fieldIndex, operation] of changesIterator) {
237
263
  // isEncodeAll = false
238
264
  // hasView = true
239
- encoder(this, bytes, changeTree, fieldIndex, operation, it, false, true);
265
+ encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
240
266
  }
241
267
  }
242
268
 
@@ -245,10 +271,12 @@ export class Encoder<T extends Schema = any> {
245
271
  // (to allow re-using StateView's for multiple clients)
246
272
  //
247
273
  // clear "view" changes after encoding
248
- view.changes.clear();
274
+ view.changes = {};
275
+
276
+ // console.log("FILTERED CHANGES:", this.root.filteredChanges);
249
277
 
250
278
  // try to encode "filtered" changes
251
- this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
279
+ this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
252
280
 
253
281
  return Buffer.concat([
254
282
  bytes.subarray(0, sharedOffset),
@@ -257,30 +285,44 @@ export class Encoder<T extends Schema = any> {
257
285
  }
258
286
 
259
287
  onEndEncode(changeTrees = this.root.changes) {
260
- const changeTreesIterator = changeTrees.entries();
261
- for (const [changeTree, _] of changeTreesIterator) {
262
- changeTree.endEncode();
263
- // changeTree.changes.clear();
288
+ // changeTrees.forEach(function(changeTree) {
289
+ // changeTree.endEncode();
290
+ // });
264
291
 
265
- // // ArraySchema and MapSchema have a custom "encode end" method
266
- // changeTree.ref[$onEncodeEnd]?.();
267
292
 
268
- // // Not a new instance anymore
269
- // delete changeTree[$isNew];
293
+ // for (const refId in changeTrees) {
294
+ // const changeTree = this.root.changeTrees[refId];
295
+ // changeTree.endEncode();
270
296
 
271
- }
297
+ // // changeTree.changes.clear();
298
+
299
+ // // // ArraySchema and MapSchema have a custom "encode end" method
300
+ // // changeTree.ref[$onEncodeEnd]?.();
301
+
302
+ // // // Not a new instance anymore
303
+ // // delete changeTree[$isNew];
304
+ // }
272
305
  }
273
306
 
274
307
  discardChanges() {
308
+ // console.log("DISCARD CHANGES!");
309
+
275
310
  // discard shared changes
276
- if (this.root.changes.size > 0) {
277
- this.onEndEncode(this.root.changes);
278
- this.root.changes.clear();
311
+ let length = this.root.changes.length;
312
+ if (length > 0) {
313
+ while (length--) {
314
+ this.root.changes[length]?.endEncode();
315
+ }
316
+ this.root.changes.length = 0;
279
317
  }
318
+
280
319
  // discard filtered changes
281
- if (this.root.filteredChanges.size > 0) {
282
- this.onEndEncode(this.root.filteredChanges);
283
- this.root.filteredChanges.clear();
320
+ length = this.root.filteredChanges.length;
321
+ if (length > 0) {
322
+ while (length--) {
323
+ this.root.filteredChanges[length]?.endEncode();
324
+ }
325
+ this.root.filteredChanges.length = 0;
284
326
  }
285
327
  }
286
328
 
@@ -288,6 +330,11 @@ export class Encoder<T extends Schema = any> {
288
330
  const baseTypeId = this.context.getTypeId(baseType);
289
331
  const targetTypeId = this.context.getTypeId(targetType);
290
332
 
333
+ if (targetTypeId === undefined) {
334
+ console.warn(`@colyseus/schema WARNING: Class "${targetType.name}" is not registered on TypeRegistry - Please either tag the class with @entity or define a @type() field.`);
335
+ return;
336
+ }
337
+
291
338
  if (baseTypeId !== targetTypeId) {
292
339
  bytes[it.offset++] = TYPE_ID & 255;
293
340
  encode.number(bytes, targetTypeId, it);
@@ -1,18 +1,21 @@
1
1
  import { OPERATION } from "../encoding/spec";
2
2
  import { TypeContext } from "../types/TypeContext";
3
- import { ChangeTree } from "./ChangeTree";
3
+ import { spliceOne } from "../types/utils";
4
+ import { ChangeTree, setOperationAtIndex } from "./ChangeTree";
4
5
 
5
6
  export class Root {
6
7
  protected nextUniqueId: number = 0;
7
- refCount = new WeakMap<ChangeTree, number>();
8
+
9
+ refCount: {[id: number]: number} = {};
10
+ changeTrees: {[refId: number]: ChangeTree} = {};
8
11
 
9
12
  // all changes
10
- allChanges = new Map<ChangeTree, Map<number, OPERATION>>();
11
- allFilteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
13
+ allChanges: ChangeTree[] = [];
14
+ allFilteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
12
15
 
13
16
  // pending changes to be encoded
14
- changes = new Map<ChangeTree, Map<number, OPERATION>>();
15
- filteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
17
+ changes: ChangeTree[] = [];
18
+ filteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
16
19
 
17
20
  constructor(public types: TypeContext) { }
18
21
 
@@ -21,45 +24,53 @@ export class Root {
21
24
  }
22
25
 
23
26
  add(changeTree: ChangeTree) {
24
- const previousRefCount = this.refCount.get(changeTree);
27
+ // FIXME: move implementation of `ensureRefId` to `Root` class
28
+ changeTree.ensureRefId();
29
+
30
+ const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
31
+ if (isNewChangeTree) { this.changeTrees[changeTree.refId] = changeTree; }
25
32
 
33
+ const previousRefCount = this.refCount[changeTree.refId];
26
34
  if (previousRefCount === 0) {
27
35
  //
28
36
  // When a ChangeTree is re-added, it means that it was previously removed.
29
37
  // We need to re-add all changes to the `changes` map.
30
38
  //
31
- changeTree.allChanges.forEach((operation, index) => {
32
- changeTree.changes.set(index, operation);
33
- });
39
+ const ops = changeTree.allChanges.operations;
40
+ let len = ops.length;
41
+ while (len--) {
42
+ changeTree.indexedOperations[ops[len]] = OPERATION.ADD;
43
+ setOperationAtIndex(changeTree.changes, len);
44
+ }
34
45
  }
35
46
 
36
- const refCount = (previousRefCount || 0) + 1;
37
- this.refCount.set(changeTree, refCount);
47
+ this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
38
48
 
39
- return refCount;
49
+ return isNewChangeTree;
40
50
  }
41
51
 
42
52
  remove(changeTree: ChangeTree) {
43
- const refCount = (this.refCount.get(changeTree)) - 1;
53
+ const refCount = (this.refCount[changeTree.refId]) - 1;
44
54
 
45
55
  if (refCount <= 0) {
46
56
  //
47
57
  // Only remove "root" reference if it's the last reference
48
58
  //
49
59
  changeTree.root = undefined;
60
+ delete this.changeTrees[changeTree.refId];
50
61
 
51
- this.allChanges.delete(changeTree);
52
- this.changes.delete(changeTree);
62
+ this.removeChangeFromChangeSet("allChanges", changeTree);
63
+ this.removeChangeFromChangeSet("changes", changeTree);
53
64
 
54
65
  if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
55
- this.allFilteredChanges.delete(changeTree);
56
- this.filteredChanges.delete(changeTree);
66
+ this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
67
+ this.removeChangeFromChangeSet("filteredChanges", changeTree);
57
68
  }
58
69
 
59
- this.refCount.set(changeTree, 0);
70
+ this.refCount[changeTree.refId] = 0;
60
71
 
61
72
  } else {
62
- this.refCount.set(changeTree, refCount);
73
+ this.refCount[changeTree.refId] = refCount;
63
74
  }
64
75
 
65
76
  changeTree.forEachChild((child, _) => this.remove(child));
@@ -67,7 +78,16 @@ export class Root {
67
78
  return refCount;
68
79
  }
69
80
 
81
+ removeChangeFromChangeSet(changeSetName: "allChanges" | "changes" | "filteredChanges" | "allFilteredChanges", changeTree: ChangeTree) {
82
+ const changeSet = this[changeSetName];
83
+ const index = changeSet.indexOf(changeTree);
84
+ if (index !== -1) {
85
+ spliceOne(changeSet, index);
86
+ // changeSet[index] = undefined;
87
+ }
88
+ }
89
+
70
90
  clear() {
71
- this.changes.clear();
91
+ this.changes.length = 0;
72
92
  }
73
93
  }
@@ -1,11 +1,11 @@
1
- import { ChangeTree, Ref } from "./ChangeTree";
2
- import { $changes } from "../types/symbols";
1
+ import { ChangeSet, ChangeTree, IndexedOperations, Ref } from "./ChangeTree";
2
+ import { $changes, $fieldIndexesByViewTag, $viewFieldIndexes } from "../types/symbols";
3
3
  import { DEFAULT_VIEW_TAG } from "../annotations";
4
4
  import { OPERATION } from "../encoding/spec";
5
5
  import { Metadata } from "../Metadata";
6
- import type { Schema } from "../Schema";
7
6
 
8
- export function createView(root: Schema) {
7
+ export function createView() {
8
+ return new StateView();
9
9
  }
10
10
 
11
11
  export class StateView {
@@ -25,7 +25,7 @@ export class StateView {
25
25
  * Manual "ADD" operations for changes per ChangeTree, specific to this view.
26
26
  * (This is used to force encoding a property, even if it was not changed)
27
27
  */
28
- changes = new Map<ChangeTree, Map<number, OPERATION>>();
28
+ changes: { [refId: number]: IndexedOperations } = {};
29
29
 
30
30
  // TODO: allow to set multiple tags at once
31
31
  add(obj: Ref, tag: number = DEFAULT_VIEW_TAG, checkIncludeParent: boolean = true) {
@@ -50,10 +50,10 @@ export class StateView {
50
50
  // TODO: when adding an item of a MapSchema, the changes may not
51
51
  // be set (only the parent's changes are set)
52
52
  //
53
- let changes = this.changes.get(changeTree);
53
+ let changes = this.changes[changeTree.refId];
54
54
  if (changes === undefined) {
55
- changes = new Map<number, OPERATION>();
56
- this.changes.set(changeTree, changes)
55
+ changes = {};
56
+ this.changes[changeTree.refId] = changes;
57
57
  }
58
58
 
59
59
  // set tag
@@ -71,9 +71,9 @@ export class StateView {
71
71
  tags.add(tag);
72
72
 
73
73
  // Ref: add tagged properties
74
- metadata?.[-3]?.[tag]?.forEach((index) => {
74
+ metadata?.[$fieldIndexesByViewTag]?.[tag]?.forEach((index) => {
75
75
  if (changeTree.getChange(index) !== OPERATION.DELETE) {
76
- changes.set(index, OPERATION.ADD)
76
+ changes[index] = OPERATION.ADD;
77
77
  }
78
78
  });
79
79
 
@@ -83,7 +83,11 @@ export class StateView {
83
83
  ? changeTree.allFilteredChanges
84
84
  : changeTree.allChanges;
85
85
 
86
- changeSet.forEach((op, index) => {
86
+ for (let i = 0, len = changeSet.operations.length; i < len; i++) {
87
+ const index = changeSet.operations[i];
88
+ if (index === undefined) { continue; } // skip "undefined" indexes
89
+
90
+ const op = changeTree.indexedOperations[index];
87
91
  const tagAtIndex = metadata?.[index].tag;
88
92
  if (
89
93
  (
@@ -93,9 +97,9 @@ export class StateView {
93
97
  ) &&
94
98
  op !== OPERATION.DELETE
95
99
  ) {
96
- changes.set(index, op);
100
+ changes[index] = op;
97
101
  }
98
- });
102
+ }
99
103
  }
100
104
 
101
105
  // Add children of this ChangeTree to this view
@@ -128,10 +132,10 @@ export class StateView {
128
132
  // add parent's tag properties
129
133
  if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
130
134
 
131
- let changes = this.changes.get(changeTree);
135
+ let changes = this.changes[changeTree.refId];
132
136
  if (changes === undefined) {
133
- changes = new Map<number, OPERATION>();
134
- this.changes.set(changeTree, changes);
137
+ changes = {};
138
+ this.changes[changeTree.refId] = changes;
135
139
  }
136
140
 
137
141
  if (!this.tags) {
@@ -147,7 +151,7 @@ export class StateView {
147
151
  }
148
152
  tags.add(tag);
149
153
 
150
- changes.set(parentIndex, OPERATION.ADD);
154
+ changes[parentIndex] = OPERATION.ADD;
151
155
  }
152
156
  }
153
157
 
@@ -163,10 +167,10 @@ export class StateView {
163
167
  const ref = changeTree.ref;
164
168
  const metadata: Metadata = ref.constructor[Symbol.metadata];
165
169
 
166
- let changes = this.changes.get(changeTree);
170
+ let changes = this.changes[changeTree.refId];
167
171
  if (changes === undefined) {
168
- changes = new Map<number, OPERATION>();
169
- this.changes.set(changeTree, changes);
172
+ changes = {};
173
+ this.changes[changeTree.refId] = changes;
170
174
  }
171
175
 
172
176
  if (tag === DEFAULT_VIEW_TAG) {
@@ -174,25 +178,25 @@ export class StateView {
174
178
  const parent = changeTree.parent;
175
179
  if (!Metadata.isValidInstance(parent)) {
176
180
  const parentChangeTree = parent[$changes];
177
- let changes = this.changes.get(parentChangeTree);
181
+ let changes = this.changes[parentChangeTree.refId];
178
182
  if (changes === undefined) {
179
- changes = new Map<number, OPERATION>();
180
- this.changes.set(parentChangeTree, changes)
183
+ changes = {};
184
+ this.changes[parentChangeTree.refId] = changes;
181
185
  }
182
186
  // DELETE / DELETE BY REF ID
183
- changes.set(changeTree.parentIndex, OPERATION.DELETE);
187
+ changes[changeTree.parentIndex] = OPERATION.DELETE;
184
188
 
185
189
  } else {
186
190
  // delete all "tagged" properties.
187
- metadata[-2].forEach((index) =>
188
- changes.set(index, OPERATION.DELETE));
191
+ metadata[$viewFieldIndexes].forEach((index) =>
192
+ changes[index] = OPERATION.DELETE);
189
193
  }
190
194
 
191
195
 
192
196
  } else {
193
197
  // delete only tagged properties
194
- metadata[-3][tag].forEach((index) =>
195
- changes.set(index, OPERATION.DELETE));
198
+ metadata[$fieldIndexesByViewTag][tag].forEach((index) =>
199
+ changes[index] = OPERATION.DELETE);
196
200
  }
197
201
 
198
202
  // remove tag
@@ -68,21 +68,24 @@ export function utf8Write(view: BufferLike, str: string, it: Iterator) {
68
68
  view[it.offset++] = c;
69
69
  }
70
70
  else if (c < 0x800) {
71
- view[it.offset++] = 0xc0 | (c >> 6);
72
- view[it.offset++] = 0x80 | (c & 0x3f);
71
+ view[it.offset] = 0xc0 | (c >> 6);
72
+ view[it.offset + 1] = 0x80 | (c & 0x3f);
73
+ it.offset += 2;
73
74
  }
74
75
  else if (c < 0xd800 || c >= 0xe000) {
75
- view[it.offset++] = 0xe0 | (c >> 12);
76
- view[it.offset++] = 0x80 | (c >> 6 & 0x3f);
77
- view[it.offset++] = 0x80 | (c & 0x3f);
76
+ view[it.offset] = 0xe0 | (c >> 12);
77
+ view[it.offset+1] = 0x80 | (c >> 6 & 0x3f);
78
+ view[it.offset+2] = 0x80 | (c & 0x3f);
79
+ it.offset += 3;
78
80
  }
79
81
  else {
80
82
  i++;
81
83
  c = 0x10000 + (((c & 0x3ff) << 10) | (str.charCodeAt(i) & 0x3ff));
82
- view[it.offset++] = 0xf0 | (c >> 18);
83
- view[it.offset++] = 0x80 | (c >> 12 & 0x3f);
84
- view[it.offset++] = 0x80 | (c >> 6 & 0x3f);
85
- view[it.offset++] = 0x80 | (c & 0x3f);
84
+ view[it.offset] = 0xf0 | (c >> 18);
85
+ view[it.offset+1] = 0x80 | (c >> 12 & 0x3f);
86
+ view[it.offset+2] = 0x80 | (c >> 6 & 0x3f);
87
+ view[it.offset+3] = 0x80 | (c & 0x3f);
88
+ it.offset += 4;
86
89
  }
87
90
  }
88
91
  }
@@ -1,5 +1,6 @@
1
1
  import { Metadata } from "../Metadata";
2
2
  import { Schema } from "../Schema";
3
+ import { $viewFieldIndexes } from "./symbols";
3
4
 
4
5
  export class TypeContext {
5
6
  types: { [id: number]: typeof Schema; } = {};
@@ -76,7 +77,7 @@ export class TypeContext {
76
77
  const metadata: Metadata = (klass[Symbol.metadata] ??= {});
77
78
 
78
79
  // if any schema/field has filters, mark "context" as having filters.
79
- if (metadata[-2]) {
80
+ if (metadata[$viewFieldIndexes]) {
80
81
  this.hasFilters = true;
81
82
  }
82
83