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

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 (116) hide show
  1. package/README.md +131 -61
  2. package/build/cjs/index.js +644 -283
  3. package/build/cjs/index.js.map +1 -1
  4. package/build/esm/index.mjs +643 -282
  5. package/build/esm/index.mjs.map +1 -1
  6. package/build/umd/index.js +644 -283
  7. package/lib/Metadata.d.ts +2 -0
  8. package/lib/Metadata.js +39 -0
  9. package/lib/Metadata.js.map +1 -1
  10. package/lib/Reflection.d.ts +2 -3
  11. package/lib/Reflection.js +31 -26
  12. package/lib/Reflection.js.map +1 -1
  13. package/lib/Schema.d.ts +2 -2
  14. package/lib/Schema.js +2 -2
  15. package/lib/Schema.js.map +1 -1
  16. package/lib/annotations.d.ts +0 -19
  17. package/lib/annotations.js +15 -101
  18. package/lib/annotations.js.map +1 -1
  19. package/lib/bench_encode.d.ts +1 -0
  20. package/lib/bench_encode.js +120 -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 -7
  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/strategy/RawChanges.js +1 -2
  52. package/lib/decoder/strategy/RawChanges.js.map +1 -1
  53. package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
  54. package/lib/decoder/strategy/StateCallbacks.js +72 -63
  55. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  56. package/lib/encoder/ChangeTree.d.ts +1 -12
  57. package/lib/encoder/ChangeTree.js +29 -58
  58. package/lib/encoder/ChangeTree.js.map +1 -1
  59. package/lib/encoder/EncodeOperation.d.ts +0 -1
  60. package/lib/encoder/EncodeOperation.js +29 -13
  61. package/lib/encoder/EncodeOperation.js.map +1 -1
  62. package/lib/encoder/Encoder.d.ts +10 -8
  63. package/lib/encoder/Encoder.js +78 -53
  64. package/lib/encoder/Encoder.js.map +1 -1
  65. package/lib/encoder/Root.d.ts +17 -0
  66. package/lib/encoder/Root.js +44 -0
  67. package/lib/encoder/Root.js.map +1 -0
  68. package/lib/encoder/StateView.d.ts +2 -2
  69. package/lib/encoder/StateView.js +48 -58
  70. package/lib/encoder/StateView.js.map +1 -1
  71. package/lib/encoding/assert.js +3 -3
  72. package/lib/encoding/assert.js.map +1 -1
  73. package/lib/encoding/decode.js +21 -22
  74. package/lib/encoding/decode.js.map +1 -1
  75. package/lib/encoding/encode.d.ts +2 -2
  76. package/lib/encoding/encode.js +40 -39
  77. package/lib/encoding/encode.js.map +1 -1
  78. package/lib/encoding/spec.d.ts +2 -1
  79. package/lib/encoding/spec.js +1 -0
  80. package/lib/encoding/spec.js.map +1 -1
  81. package/lib/index.d.ts +5 -1
  82. package/lib/index.js +8 -4
  83. package/lib/index.js.map +1 -1
  84. package/lib/types/TypeContext.d.ts +23 -0
  85. package/lib/types/TypeContext.js +109 -0
  86. package/lib/types/TypeContext.js.map +1 -0
  87. package/lib/types/custom/ArraySchema.d.ts +2 -2
  88. package/lib/types/custom/ArraySchema.js +0 -9
  89. package/lib/types/custom/ArraySchema.js.map +1 -1
  90. package/lib/types/registry.js +3 -4
  91. package/lib/types/registry.js.map +1 -1
  92. package/lib/types/utils.js +1 -2
  93. package/lib/types/utils.js.map +1 -1
  94. package/lib/utils.js +3 -4
  95. package/lib/utils.js.map +1 -1
  96. package/package.json +5 -5
  97. package/src/Metadata.ts +47 -0
  98. package/src/Reflection.ts +34 -26
  99. package/src/Schema.ts +2 -2
  100. package/src/annotations.ts +7 -109
  101. package/src/bench_encode.ts +97 -0
  102. package/src/debug.ts +56 -0
  103. package/src/decoder/DecodeOperation.ts +30 -7
  104. package/src/decoder/Decoder.ts +13 -11
  105. package/src/decoder/strategy/StateCallbacks.ts +149 -79
  106. package/src/encoder/ChangeTree.ts +36 -66
  107. package/src/encoder/EncodeOperation.ts +29 -12
  108. package/src/encoder/Encoder.ts +95 -61
  109. package/src/encoder/Root.ts +51 -0
  110. package/src/encoder/StateView.ts +51 -67
  111. package/src/encoding/decode.ts +1 -2
  112. package/src/encoding/encode.ts +25 -22
  113. package/src/encoding/spec.ts +1 -0
  114. package/src/index.ts +8 -11
  115. package/src/types/TypeContext.ts +127 -0
  116. package/src/types/custom/ArraySchema.ts +2 -2
@@ -1,14 +1,17 @@
1
1
  import type { Schema } from "../Schema";
2
- import { TypeContext } from "../annotations";
2
+ import { TypeContext } from "../types/TypeContext";
3
3
  import { $changes, $encoder, $filter } from "../types/symbols";
4
4
 
5
5
  import * as encode from "../encoding/encode";
6
6
  import type { Iterator } from "../encoding/decode";
7
7
 
8
- import { SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
9
- import { Root } from "./ChangeTree";
8
+ import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
9
+ import { Root } from "./Root";
10
10
  import { getNextPowerOf2 } from "../utils";
11
+
11
12
  import type { StateView } from "./StateView";
13
+ import type { Metadata } from "../Metadata";
14
+ import type { ChangeTree } from "./ChangeTree";
12
15
 
13
16
  export class Encoder<T extends Schema = any> {
14
17
  static BUFFER_SIZE = 8 * 1024;// 8KB
@@ -19,14 +22,16 @@ export class Encoder<T extends Schema = any> {
19
22
 
20
23
  root: Root;
21
24
 
22
- constructor(root: T) {
23
- this.setRoot(root);
25
+ constructor(state: T) {
24
26
 
25
27
  //
26
28
  // TODO: cache and restore "Context" based on root schema
27
29
  // (to avoid creating a new context for every new room)
28
30
  //
29
- this.context = new TypeContext(root.constructor as typeof Schema);
31
+ this.context = new TypeContext(state.constructor as typeof Schema);
32
+ this.root = new Root(this.context);
33
+
34
+ this.setState(state);
30
35
 
31
36
  // console.log(">>>>>>>>>>>>>>>> Encoder types");
32
37
  // this.context.schemas.forEach((id, schema) => {
@@ -34,21 +39,19 @@ export class Encoder<T extends Schema = any> {
34
39
  // });
35
40
  }
36
41
 
37
- protected setRoot(state: T) {
38
- this.root = new Root();
42
+ protected setState(state: T) {
39
43
  this.state = state;
40
- state[$changes].setRoot(this.root);
44
+ this.state[$changes].setRoot(this.root);
41
45
  }
42
46
 
43
47
  encode(
44
48
  it: Iterator = { offset: 0 },
45
49
  view?: StateView,
46
- bytes = this.sharedBuffer,
47
- changeTrees = this.root.changes
50
+ buffer = this.sharedBuffer,
51
+ changeTrees = this.root.changes,
52
+ isEncodeAll = this.root.allChanges === changeTrees,
53
+ initialOffset = it.offset // cache current offset in case we need to resize the buffer
48
54
  ): Buffer {
49
- const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
50
-
51
- const isEncodeAll = this.root.allChanges === changeTrees;
52
55
  const hasView = (view !== undefined);
53
56
  const rootChangeTree = this.state[$changes];
54
57
 
@@ -61,6 +64,13 @@ export class Encoder<T extends Schema = any> {
61
64
  const encoder = ctor[$encoder];
62
65
  const filter = ctor[$filter];
63
66
 
67
+ // try { throw new Error(); } catch (e) {
68
+ // // only print if not coming from Reflection.ts
69
+ // if (!e.stack.includes("src/Reflection.ts")) {
70
+ // console.log("ChangeTree:", { ref: ref.constructor.name, });
71
+ // }
72
+ // }
73
+
64
74
  if (hasView) {
65
75
  if (!view.items.has(changeTree)) {
66
76
  view.invisible.add(changeTree);
@@ -72,9 +82,10 @@ export class Encoder<T extends Schema = any> {
72
82
  }
73
83
 
74
84
  // skip root `refId` if it's the first change tree
75
- if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
76
- bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
77
- encode.number(bytes, changeTree.refId, it);
85
+ // (unless it "hasView", which will need to revisit the root)
86
+ if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
87
+ buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
88
+ encode.number(buffer, changeTree.refId, it);
78
89
  }
79
90
 
80
91
  const changesIterator = changes.entries();
@@ -88,32 +99,45 @@ export class Encoder<T extends Schema = any> {
88
99
  // TODO: avoid checking if no view tags were defined
89
100
  //
90
101
  if (filter && !filter(ref, fieldIndex, view)) {
91
- // console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
92
-
93
102
  // console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
94
103
  // view?.invisible.add(changeTree);
95
104
  continue;
96
105
  }
97
106
 
98
- // console.log("WILL ENCODE", {
99
- // ref: changeTree.ref.constructor.name,
100
- // fieldIndex,
101
- // operation: OPERATION[operation],
102
- // });
103
-
104
- encoder(this, bytes, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
107
+ // try { throw new Error(); } catch (e) {
108
+ // // only print if not coming from Reflection.ts
109
+ // if (!e.stack.includes("src/Reflection.ts")) {
110
+ // console.log("WILL ENCODE", {
111
+ // ref: changeTree.ref.constructor.name,
112
+ // fieldIndex,
113
+ // operation: OPERATION[operation],
114
+ // });
115
+ // }
116
+ // }
117
+
118
+ encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView);
105
119
  }
106
120
  }
107
121
 
108
- if (it.offset > bytes.byteLength) {
109
- const newSize = getNextPowerOf2(this.sharedBuffer.byteLength * 2);
110
- console.warn("@colyseus/schema encode buffer overflow. Current buffer size: " + bytes.byteLength + ", encoding offset: " + it.offset + ", new size: " + newSize);
122
+ if (it.offset > buffer.byteLength) {
123
+ const newSize = getNextPowerOf2(buffer.byteLength * 2);
124
+ console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
125
+
126
+ import { Encoder } from "@colyseus/schema";
127
+ Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
128
+ `);
111
129
 
112
130
  //
113
131
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
114
132
  //
115
- this.sharedBuffer = Buffer.allocUnsafeSlow(newSize);
116
- return this.encode({ offset: initialOffset }, view);
133
+ buffer = Buffer.allocUnsafeSlow(newSize);
134
+
135
+ // assign resized buffer to local sharedBuffer
136
+ if (buffer === this.sharedBuffer) {
137
+ this.sharedBuffer = buffer;
138
+ }
139
+
140
+ return this.encode({ offset: initialOffset }, view, buffer, changeTrees, isEncodeAll);
117
141
 
118
142
  } else {
119
143
  //
@@ -126,53 +150,60 @@ export class Encoder<T extends Schema = any> {
126
150
  this.onEndEncode(changeTrees);
127
151
  }
128
152
 
129
- // return bytes;
130
- return bytes.slice(0, it.offset);
153
+ return buffer.subarray(0, it.offset);
131
154
  }
132
155
  }
133
156
 
134
- encodeAll(it: Iterator = { offset: 0 }) {
135
- // console.log(`encodeAll(), this.$root.allChanges (${this.$root.allChanges.size})`);
136
-
137
- // Array.from(this.$root.allChanges.entries()).map((item) => {
138
- // console.log("->", item[0].refId, item[0].ref.toJSON());
139
- // });
157
+ encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
158
+ // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
159
+ // this.debugChanges("allChanges");
140
160
 
141
- return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
161
+ return this.encode(it, undefined, buffer, this.root.allChanges, true);
142
162
  }
143
163
 
144
164
  encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
145
165
  const viewOffset = it.offset;
146
166
 
147
- // console.log(`encodeAllView(), this.$root.allFilteredChanges (${this.$root.allFilteredChanges.size})`);
148
- // this.debugAllFilteredChanges();
167
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
168
+ // this.debugChanges("allFilteredChanges");
149
169
 
150
170
  // try to encode "filtered" changes
151
- this.encode(it, view, bytes, this.root.allFilteredChanges);
171
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
152
172
 
153
173
  return Buffer.concat([
154
- bytes.slice(0, sharedOffset),
155
- bytes.slice(viewOffset, it.offset)
174
+ bytes.subarray(0, sharedOffset),
175
+ bytes.subarray(viewOffset, it.offset)
156
176
  ]);
157
177
  }
158
178
 
159
-
160
- // debugAllFilteredChanges() {
161
- // Array.from(this.$root.allFilteredChanges.entries()).map((item) => {
162
- // console.log("->", { refId: item[0].refId }, item[0].ref.toJSON());
163
- // if (Array.isArray(item[0].ref.toJSON())) {
164
- // item[1].forEach((op, key) => {
165
- // console.log(" ->", { key, op: OPERATION[op] });
166
- // })
167
- // }
168
- // });
169
- // }
179
+ debugChanges(
180
+ field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges" | Map<ChangeTree, Map<number, OPERATION>>
181
+ ) {
182
+ const changeSet = (typeof (field) === "string")
183
+ ? this.root[field]
184
+ : field;
185
+
186
+ Array.from(changeSet.entries()).map((item) => {
187
+ const metadata: Metadata = item[0].ref.constructor[Symbol.metadata];
188
+ console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
189
+ item[1].forEach((op, index) => {
190
+ console.log(" ->", {
191
+ index,
192
+ field: metadata?.[index],
193
+ op: OPERATION[op],
194
+ });
195
+ });
196
+ });
197
+ }
170
198
 
171
199
  encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
172
200
  const viewOffset = it.offset;
173
201
 
174
- // try to encode "filtered" changes
175
- this.encode(it, view, bytes, this.root.filteredChanges);
202
+ // console.log(`\nencodeView(), view.changes (${view.changes.size})`);
203
+ // this.debugChanges(view.changes);
204
+
205
+ // console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
206
+ // this.debugChanges("filteredChanges");
176
207
 
177
208
  // encode visibility changes (add/remove for this view)
178
209
  const viewChangesIterator = view.changes.entries();
@@ -207,9 +238,12 @@ export class Encoder<T extends Schema = any> {
207
238
  // clear "view" changes after encoding
208
239
  view.changes.clear();
209
240
 
241
+ // try to encode "filtered" changes
242
+ this.encode(it, view, bytes, this.root.filteredChanges, false, viewOffset);
243
+
210
244
  return Buffer.concat([
211
- bytes.slice(0, sharedOffset),
212
- bytes.slice(viewOffset, it.offset)
245
+ bytes.subarray(0, sharedOffset),
246
+ bytes.subarray(viewOffset, it.offset)
213
247
  ]);
214
248
  }
215
249
 
@@ -242,4 +276,4 @@ export class Encoder<T extends Schema = any> {
242
276
  encode.number(bytes, targetTypeId, it);
243
277
  }
244
278
  }
245
- }
279
+ }
@@ -0,0 +1,51 @@
1
+ import { OPERATION } from "../encoding/spec";
2
+ import { TypeContext } from "../types/TypeContext";
3
+ import { ChangeTree } from "./ChangeTree";
4
+
5
+ export class Root {
6
+ protected nextUniqueId: number = 0;
7
+ refCount = new WeakMap<ChangeTree, number>();
8
+
9
+ // all changes
10
+ allChanges = new Map<ChangeTree, Map<number, OPERATION>>();
11
+ allFilteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
12
+
13
+ // pending changes to be encoded
14
+ changes = new Map<ChangeTree, Map<number, OPERATION>>();
15
+ filteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
16
+
17
+ constructor(public types: TypeContext) { }
18
+
19
+ getNextUniqueId() {
20
+ return this.nextUniqueId++;
21
+ }
22
+
23
+ add(changeTree: ChangeTree) {
24
+ const refCount = this.refCount.get(changeTree) || 0;
25
+ this.refCount.set(changeTree, refCount + 1);
26
+ }
27
+
28
+ remove(changeTree: ChangeTree) {
29
+ const refCount = this.refCount.get(changeTree);
30
+ if (refCount <= 1) {
31
+ this.allChanges.delete(changeTree);
32
+ this.changes.delete(changeTree);
33
+
34
+ if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
35
+ this.allFilteredChanges.delete(changeTree);
36
+ this.filteredChanges.delete(changeTree);
37
+ }
38
+
39
+ this.refCount.delete(changeTree);
40
+
41
+ } else {
42
+ this.refCount.set(changeTree, refCount - 1);
43
+ }
44
+
45
+ changeTree.forEachChild((child, _) => this.remove(child));
46
+ }
47
+
48
+ clear() {
49
+ this.changes.clear();
50
+ }
51
+ }
@@ -28,25 +28,23 @@ export class StateView {
28
28
  changes = new Map<ChangeTree, Map<number, OPERATION>>();
29
29
 
30
30
  // TODO: allow to set multiple tags at once
31
- add(obj: Ref, tag: number = DEFAULT_VIEW_TAG) {
31
+ add(obj: Ref, tag: number = DEFAULT_VIEW_TAG, checkIncludeParent: boolean = true) {
32
32
  if (!obj[$changes]) {
33
33
  console.warn("StateView#add(), invalid object:", obj);
34
34
  return this;
35
35
  }
36
36
 
37
- let changeTree: ChangeTree = obj[$changes];
38
- this.items.add(changeTree);
39
-
40
- // Add children of this ChangeTree to this view
41
- changeTree.forEachChild((change, _) =>
42
- this.add(change.ref, tag));
43
-
44
37
  // FIXME: ArraySchema/MapSchema does not have metadata
45
38
  const metadata: Metadata = obj.constructor[Symbol.metadata];
39
+ const changeTree: ChangeTree = obj[$changes];
40
+ this.items.add(changeTree);
46
41
 
47
- // add parent ChangeTree's, if they are invisible to this view
48
- // TODO: REFACTOR addParent()
49
- this.addParent(changeTree, tag);
42
+ // add parent ChangeTree's
43
+ // - if it was invisible to this view
44
+ // - if it were previously filtered out
45
+ if (checkIncludeParent && changeTree.parent) {
46
+ this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
47
+ }
50
48
 
51
49
  //
52
50
  // TODO: when adding an item of a MapSchema, the changes may not
@@ -72,8 +70,6 @@ export class StateView {
72
70
  }
73
71
  tags.add(tag);
74
72
 
75
- // console.log("BY TAG:", tag);
76
-
77
73
  // Ref: add tagged properties
78
74
  metadata?.[-3]?.[tag]?.forEach((index) => {
79
75
  if (changeTree.getChange(index) !== OPERATION.DELETE) {
@@ -82,89 +78,77 @@ export class StateView {
82
78
  });
83
79
 
84
80
  } else {
85
-
86
- // console.log("DEFAULT TAG", changeTree.allChanges);
87
-
88
- // // add default tag properties
89
- // metadata?.[-3]?.[DEFAULT_VIEW_TAG]?.forEach((index) => {
90
- // if (changeTree.getChange(index) !== OPERATION.DELETE) {
91
- // changes.set(index, OPERATION.ADD);
92
- // }
93
- // });
94
-
95
- const allChangesSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
81
+ const isInvisible = this.invisible.has(changeTree);
82
+ const changeSet = (changeTree.isFiltered || changeTree.isPartiallyFiltered)
96
83
  ? changeTree.allFilteredChanges
97
84
  : changeTree.allChanges;
98
- const it = allChangesSet.keys();
99
- const isInvisible = this.invisible.has(changeTree);
100
85
 
101
- for (const index of it) {
86
+ changeSet.forEach((op, index) => {
87
+ const tagAtIndex = metadata?.[metadata?.[index]].tag;
102
88
  if (
103
- (isInvisible || metadata?.[metadata?.[index]].tag === tag) &&
104
- changeTree.getChange(index) !== OPERATION.DELETE
89
+ (
90
+ isInvisible || // if "invisible", include all
91
+ tagAtIndex === undefined || // "all change" with no tag
92
+ tagAtIndex === tag // tagged property
93
+ ) &&
94
+ op !== OPERATION.DELETE
105
95
  ) {
106
- changes.set(index, OPERATION.ADD);
96
+ changes.set(index, op);
107
97
  }
108
- }
98
+ });
109
99
  }
110
100
 
111
- // TODO: avoid unnecessary iteration here
112
- while (
113
- changeTree.parent &&
114
- (changeTree = changeTree.parent[$changes]) &&
115
- (changeTree.isFiltered || changeTree.isPartiallyFiltered)
116
- ) {
117
- this.items.add(changeTree);
118
- }
101
+ // Add children of this ChangeTree to this view
102
+ changeTree.forEachChild((change, index) => {
103
+ // Do not ADD children that don't have the same tag
104
+ if (metadata && metadata[metadata[index]].tag !== tag) {
105
+ return;
106
+ }
107
+ this.add(change.ref, tag, false);
108
+ });
119
109
 
120
110
  return this;
121
111
  }
122
112
 
123
- protected addParent(changeTree: ChangeTree, tag: number) {
124
- const parentRef = changeTree.parent;
125
- if (!parentRef) { return; }
113
+ protected addParent(changeTree: ChangeTree, parentIndex: number, tag: number) {
114
+ // view must have all "changeTree" parent tree
115
+ this.items.add(changeTree);
126
116
 
127
- const parentChangeTree = parentRef[$changes];
128
- const parentIndex = changeTree.parentIndex;
117
+ // add parent's parent
118
+ const parentChangeTree = changeTree.parent?.[$changes];
119
+ if (parentChangeTree && (parentChangeTree.isFiltered || parentChangeTree.isPartiallyFiltered)) {
120
+ this.addParent(parentChangeTree, changeTree.parentIndex, tag);
121
+ }
129
122
 
130
- if (!this.invisible.has(parentChangeTree)) {
131
- // parent is already available, no need to add it!
123
+ // parent is already available, no need to add it!
124
+ if (!this.invisible.has(changeTree)) {
132
125
  return;
133
126
  }
134
127
 
135
- this.addParent(parentChangeTree, tag);
136
-
137
128
  // add parent's tag properties
138
- if (parentChangeTree.getChange(parentIndex) !== OPERATION.DELETE) {
129
+ if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
139
130
 
140
- let parentChanges = this.changes.get(parentChangeTree);
141
- if (parentChanges === undefined) {
142
- parentChanges = new Map<number, OPERATION>();
143
- this.changes.set(parentChangeTree, parentChanges);
131
+ let changes = this.changes.get(changeTree);
132
+ if (changes === undefined) {
133
+ changes = new Map<number, OPERATION>();
134
+ this.changes.set(changeTree, changes);
144
135
  }
145
136
 
146
- // console.log("add parent change", {
147
- // parentIndex,
148
- // parentChanges,
149
- // parentChange: (
150
- // parentChangeTree.getChange(parentIndex) &&
151
- // OPERATION[parentChangeTree.getChange(parentIndex)]
152
- // ),
153
- // })
137
+ if (!this.tags) {
138
+ this.tags = new WeakMap<ChangeTree, Set<number>>();
139
+ }
154
140
 
155
- if (!this.tags) { this.tags = new WeakMap<ChangeTree, Set<number>>(); }
156
141
  let tags: Set<number>;
157
- if (!this.tags.has(parentChangeTree)) {
142
+ if (!this.tags.has(changeTree)) {
158
143
  tags = new Set<number>();
159
- this.tags.set(parentChangeTree, tags);
144
+ this.tags.set(changeTree, tags);
160
145
  } else {
161
- tags = this.tags.get(parentChangeTree);
146
+ tags = this.tags.get(changeTree);
162
147
  }
163
148
  tags.add(tag);
164
149
 
165
- parentChanges.set(parentIndex, OPERATION.ADD);
150
+ changes.set(parentIndex, OPERATION.ADD);
166
151
  }
167
-
168
152
  }
169
153
 
170
154
  remove(obj: Ref, tag: number = DEFAULT_VIEW_TAG) {
@@ -32,8 +32,6 @@ import type { BufferLike } from "./encode";
32
32
  export interface Iterator { offset: number; }
33
33
 
34
34
  export function utf8Read(bytes: BufferLike, it: Iterator, length: number) {
35
- it.offset += length;
36
-
37
35
  var string = '', chr = 0;
38
36
  for (var i = it.offset, end = it.offset + length; i < end; i++) {
39
37
  var byte = bytes[i];
@@ -74,6 +72,7 @@ export function utf8Read(bytes: BufferLike, it: Iterator, length: number) {
74
72
  // (do not throw error to avoid server/client from crashing due to hack attemps)
75
73
  // throw new Error('Invalid byte ' + byte.toString(16));
76
74
  }
75
+ it.offset += length;
77
76
  return string;
78
77
  }
79
78
 
@@ -35,28 +35,32 @@ let textEncoder: TextEncoder;
35
35
  // @ts-ignore
36
36
  try { textEncoder = new TextEncoder(); } catch (e) { }
37
37
 
38
- export function utf8Length(str) {
39
- var c = 0, length = 0;
40
- for (var i = 0, l = str.length; i < l; i++) {
41
- c = str.charCodeAt(i);
42
- if (c < 0x80) {
43
- length += 1;
44
- }
45
- else if (c < 0x800) {
46
- length += 2;
47
- }
48
- else if (c < 0xd800 || c >= 0xe000) {
49
- length += 3;
50
- }
51
- else {
52
- i++;
53
- length += 4;
38
+ const hasBufferByteLength = (typeof Buffer !== 'undefined' && Buffer.byteLength);
39
+
40
+ export const utf8Length = (hasBufferByteLength)
41
+ ? Buffer.byteLength // node
42
+ : function (str: string, _?: any) {
43
+ var c = 0, length = 0;
44
+ for (var i = 0, l = str.length; i < l; i++) {
45
+ c = str.charCodeAt(i);
46
+ if (c < 0x80) {
47
+ length += 1;
48
+ }
49
+ else if (c < 0x800) {
50
+ length += 2;
51
+ }
52
+ else if (c < 0xd800 || c >= 0xe000) {
53
+ length += 3;
54
+ }
55
+ else {
56
+ i++;
57
+ length += 4;
58
+ }
59
+ }
60
+ return length;
54
61
  }
55
- }
56
- return length;
57
- }
58
62
 
59
- export function utf8Write(view, str, it) {
63
+ export function utf8Write(view: BufferLike, str: string, it: Iterator) {
60
64
  var c = 0;
61
65
  for (var i = 0, l = str.length; i < l; i++) {
62
66
  c = str.charCodeAt(i);
@@ -166,8 +170,7 @@ export function string(bytes: BufferLike, value: string, it: Iterator) {
166
170
  // encode `null` strings as empty.
167
171
  if (!value) { value = ""; }
168
172
 
169
- // let length = utf8Length(value);
170
- let length = Buffer.byteLength(value, "utf8");
173
+ let length = utf8Length(value, "utf8");
171
174
  let size = 0;
172
175
 
173
176
  // fixstr
@@ -25,5 +25,6 @@ export enum OPERATION {
25
25
  REVERSE = 15,
26
26
  MOVE = 32,
27
27
  DELETE_BY_REFID = 33, // This operation is only used at ENCODING time. During DECODING, DELETE_BY_REFID is converted to DELETE
28
+ ADD_BY_REFID = 129,
28
29
 
29
30
  }
package/src/index.ts CHANGED
@@ -4,6 +4,8 @@ export type { DataChange } from "./decoder/DecodeOperation";
4
4
  import { $track, $encoder, $decoder, $filter, $getByIndex, $deleteByIndex, $changes, $childType } from "./types/symbols";
5
5
  export { $track, $encoder, $decoder, $filter, $getByIndex, $deleteByIndex, $changes, $childType };
6
6
 
7
+ export type { ToJSON } from "./types/HelperTypes";
8
+
7
9
  import { MapSchema } from "./types/custom/MapSchema"
8
10
  export { MapSchema };
9
11
 
@@ -40,22 +42,17 @@ export {
40
42
  ReflectionField,
41
43
  } from "./Reflection";
42
44
 
45
+ // Annotations, Metadata and TypeContext
43
46
  export { Metadata } from "./Metadata";
44
-
45
- export {
46
- // Annotations
47
- type,
48
- deprecated,
49
- defineTypes,
50
- view,
51
-
52
- // Internals
53
- TypeContext,
54
- } from "./annotations";
47
+ export { type, deprecated, defineTypes, view, } from "./annotations";
48
+ export { TypeContext } from "./types/TypeContext";
55
49
 
56
50
  // Annotation types
57
51
  export type { DefinitionType, PrimitiveType, Definition, } from "./annotations";
58
52
 
53
+ export { getDecoderStateCallbacks, CallbackProxy, GetCallbackProxy } from "./decoder/strategy/StateCallbacks";
54
+ export { getRawChangesCallback } from "./decoder/strategy/RawChanges";
55
+
59
56
  export { Encoder } from "./encoder/Encoder";
60
57
  export { encodeSchemaOperation, encodeArray as encodeKeyValueOperation } from "./encoder/EncodeOperation";
61
58
  export { ChangeTree, Ref } from "./encoder/ChangeTree";