@colyseus/schema 3.0.0-alpha.27 → 3.0.0-alpha.29

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 (50) hide show
  1. package/build/cjs/index.js +114 -88
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +114 -88
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +114 -88
  6. package/lib/Metadata.d.ts +3 -0
  7. package/lib/Metadata.js.map +1 -1
  8. package/lib/Reflection.d.ts +1 -1
  9. package/lib/Reflection.js +4 -3
  10. package/lib/Reflection.js.map +1 -1
  11. package/lib/annotations.d.ts +0 -19
  12. package/lib/annotations.js +4 -106
  13. package/lib/annotations.js.map +1 -1
  14. package/lib/bench_encode.js +54 -27
  15. package/lib/bench_encode.js.map +1 -1
  16. package/lib/decoder/Decoder.d.ts +1 -1
  17. package/lib/decoder/Decoder.js +2 -2
  18. package/lib/decoder/Decoder.js.map +1 -1
  19. package/lib/decoder/strategy/StateCallbacks.js +5 -4
  20. package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
  21. package/lib/encoder/ChangeTree.d.ts +1 -12
  22. package/lib/encoder/ChangeTree.js +24 -51
  23. package/lib/encoder/ChangeTree.js.map +1 -1
  24. package/lib/encoder/Encoder.d.ts +6 -5
  25. package/lib/encoder/Encoder.js +28 -19
  26. package/lib/encoder/Encoder.js.map +1 -1
  27. package/lib/encoder/Root.d.ts +17 -0
  28. package/lib/encoder/Root.js +44 -0
  29. package/lib/encoder/Root.js.map +1 -0
  30. package/lib/index.d.ts +2 -1
  31. package/lib/index.js +3 -3
  32. package/lib/index.js.map +1 -1
  33. package/lib/types/TypeContext.d.ts +23 -0
  34. package/lib/types/TypeContext.js +109 -0
  35. package/lib/types/TypeContext.js.map +1 -0
  36. package/lib/types/custom/ArraySchema.js +0 -1
  37. package/lib/types/custom/ArraySchema.js.map +1 -1
  38. package/package.json +1 -1
  39. package/src/Metadata.ts +1 -0
  40. package/src/Reflection.ts +2 -1
  41. package/src/annotations.ts +1 -126
  42. package/src/bench_encode.ts +47 -16
  43. package/src/decoder/Decoder.ts +1 -1
  44. package/src/decoder/strategy/StateCallbacks.ts +5 -4
  45. package/src/encoder/ChangeTree.ts +30 -59
  46. package/src/encoder/Encoder.ts +36 -23
  47. package/src/encoder/Root.ts +51 -0
  48. package/src/index.ts +3 -11
  49. package/src/types/TypeContext.ts +127 -0
  50. package/src/types/custom/ArraySchema.ts +0 -1
@@ -6,6 +6,7 @@ import { Metadata } from "./Metadata";
6
6
  import { $changes, $childType, $track } from "./types/symbols";
7
7
  import { TypeDefinition, getType } from "./types/registry";
8
8
  import { OPERATION } from "./encoding/spec";
9
+ import { TypeContext } from "./types/TypeContext";
9
10
 
10
11
  /**
11
12
  * Data types
@@ -42,132 +43,6 @@ export interface TypeOptions {
42
43
 
43
44
  export const DEFAULT_VIEW_TAG = -1;
44
45
 
45
- export class TypeContext {
46
- types: {[id: number]: typeof Schema} = {};
47
- schemas = new Map<typeof Schema, number>();
48
-
49
- hasFilters: boolean = false;
50
-
51
- /**
52
- * For inheritance support
53
- * Keeps track of which classes extends which. (parent -> children)
54
- */
55
- static inheritedTypes = new Map<typeof Schema, Set<typeof Schema>>();
56
-
57
- static register(target: typeof Schema) {
58
- const parent = Object.getPrototypeOf(target);
59
- if (parent !== Schema) {
60
- let inherits = TypeContext.inheritedTypes.get(parent);
61
- if (!inherits) {
62
- inherits = new Set<typeof Schema>();
63
- TypeContext.inheritedTypes.set(parent, inherits);
64
- }
65
- inherits.add(target);
66
- }
67
- }
68
-
69
- constructor(rootClass?: typeof Schema) {
70
- if (rootClass) {
71
- this.discoverTypes(rootClass);
72
- }
73
- }
74
-
75
- has(schema: typeof Schema) {
76
- return this.schemas.has(schema);
77
- }
78
-
79
- get(typeid: number) {
80
- return this.types[typeid];
81
- }
82
-
83
- add(schema: typeof Schema, typeid: number = this.schemas.size) {
84
- // skip if already registered
85
- if (this.schemas.has(schema)) {
86
- return false;
87
- }
88
-
89
- this.types[typeid] = schema;
90
-
91
- //
92
- // Workaround to allow using an empty Schema (with no `@type()` fields)
93
- //
94
- if (schema[Symbol.metadata] === undefined) {
95
- Metadata.init(schema);
96
- }
97
-
98
- this.schemas.set(schema, typeid);
99
- return true;
100
- }
101
-
102
- getTypeId(klass: typeof Schema) {
103
- return this.schemas.get(klass);
104
- }
105
-
106
- private discoverTypes(klass: typeof Schema, parentFieldViewTag?: number) {
107
- if (!this.add(klass)) {
108
- return;
109
- }
110
-
111
- // add classes inherited from this base class
112
- TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
113
- this.discoverTypes(child, parentFieldViewTag);
114
- });
115
-
116
- // skip if no fields are defined for this class.
117
- if (klass[Symbol.metadata] === undefined) {
118
- klass[Symbol.metadata] = {};
119
- }
120
-
121
- // const metadata = Metadata.getFor(klass);
122
- const metadata = klass[Symbol.metadata];
123
-
124
- // if any schema/field has filters, mark "context" as having filters.
125
- if (metadata[-2]) {
126
- this.hasFilters = true;
127
- }
128
-
129
- for (const field in metadata) {
130
- //
131
- // Modify the field's metadata to include the parent field's view tag
132
- //
133
- if (
134
- parentFieldViewTag !== undefined &&
135
- metadata[field].tag === undefined
136
- ) {
137
- metadata[field].tag = parentFieldViewTag;
138
- }
139
-
140
- const fieldType = metadata[field].type;
141
- const viewTag = metadata[field].tag;
142
-
143
- if (typeof(fieldType) === "string") {
144
- continue;
145
- }
146
-
147
- if (Array.isArray(fieldType)) {
148
- const type = fieldType[0];
149
- if (type === "string") {
150
- continue;
151
- }
152
- this.discoverTypes(type as typeof Schema, viewTag);
153
-
154
- } else if (typeof(fieldType) === "function") {
155
- this.discoverTypes(fieldType, viewTag);
156
-
157
- } else {
158
- const type = Object.values(fieldType)[0];
159
-
160
- // skip primitive types
161
- if (typeof(type) === "string") {
162
- continue;
163
- }
164
-
165
- this.discoverTypes(type as typeof Schema, viewTag);
166
- }
167
- }
168
- }
169
- }
170
-
171
46
  export function entity(constructor, context: ClassDecoratorContext) {
172
47
  if (!constructor._definition) {
173
48
  // for inheritance support
@@ -4,44 +4,44 @@ import * as benchmark from "benchmark";
4
4
 
5
5
  const suite = new benchmark.Suite();
6
6
 
7
- class AttributeNew extends Schema {
7
+ class Attribute extends Schema {
8
8
  @type("string") name: string;
9
9
  @type("number") value: number;
10
10
  }
11
11
 
12
- class ItemNew extends Schema {
12
+ class Item extends Schema {
13
13
  @type("number") price: number;
14
- @type([ AttributeNew ]) attributes = new ArraySchema<AttributeNew>();
14
+ @type([ Attribute ]) attributes = new ArraySchema<Attribute>();
15
15
  }
16
16
 
17
- class PositionNew extends Schema {
17
+ class Position extends Schema {
18
18
  @type("number") x: number;
19
19
  @type("number") y: number;
20
20
  }
21
21
 
22
- class PlayerNew extends Schema {
23
- @type(PositionNew) position = new PositionNew();
24
- @type({ map: ItemNew }) items = new MapSchema<ItemNew>();
22
+ class Player extends Schema {
23
+ @type(Position) position = new Position();
24
+ @type({ map: Item }) items = new MapSchema<Item>();
25
25
  }
26
26
 
27
- class StateNew extends Schema {
28
- @type({ map: PlayerNew }) players = new MapSchema<PlayerNew>();
27
+ class State extends Schema {
28
+ @type({ map: Player }) players = new MapSchema<Player>();
29
29
  @type("string") currentTurn;
30
30
  }
31
31
 
32
- const state = new StateNew();
32
+ const state = new State();
33
33
 
34
34
  for (let i = 0; i < 50; i++) {
35
- const player = new PlayerNew();
35
+ const player = new Player();
36
36
  state.players.set(`p-${nanoid()}`, player);
37
37
 
38
38
  player.position.x = (i + 1) * 100;
39
39
  player.position.y = (i + 1) * 100;
40
40
  for (let j = 0; j < 10; j++) {
41
- const item = new ItemNew();
41
+ const item = new Item();
42
42
  item.price = (i + 1) * 50;
43
43
  for (let k = 0; k < 5; k++) {
44
- const attr = new AttributeNew();
44
+ const attr = new Attribute();
45
45
  attr.name = `Attribute ${k}`;
46
46
  attr.value = k;
47
47
  item.attributes.push(attr);
@@ -52,15 +52,46 @@ for (let i = 0; i < 50; i++) {
52
52
  }
53
53
 
54
54
 
55
- Encoder.BUFFER_SIZE = 1024 * 128;
55
+ Encoder.BUFFER_SIZE = 4096 * 4096;
56
56
  const encoder = new Encoder(state);
57
57
 
58
58
  // measure time to .encodeAll()
59
59
 
60
- const now = Date.now();
60
+ let now = Date.now();
61
61
  for (let i = 0; i < 1000; i++) {
62
62
  encoder.encodeAll();
63
63
  }
64
64
  console.log(Date.now() - now);
65
65
 
66
- console.log(Array.from(encoder.encodeAll()).join(","));
66
+ const allEncodes = Date.now();
67
+ for (let i = 0; i < 100; i++) {
68
+ now = Date.now();
69
+ for (let j = 0; j < 50; j++) {
70
+ const player = new Player();
71
+ state.players.set(`p-${nanoid()}`, player);
72
+
73
+ player.position.x = (j + 1) * 100;
74
+ player.position.y = (j + 1) * 100;
75
+ for (let k = 0; k < 10; k++) {
76
+ const item = new Item();
77
+ item.price = (j + 1) * 50;
78
+ for (let l = 0; l < 5; l++) {
79
+ const attr = new Attribute();
80
+ attr.name = `Attribute ${l}`;
81
+ attr.value = l;
82
+ item.attributes.push(attr);
83
+
84
+ }
85
+ player.items.set(`item-${k}`, item);
86
+ }
87
+ }
88
+ console.log("time to make changes:", Date.now() - now);
89
+
90
+ now = Date.now();
91
+ encoder.encode();
92
+ encoder.discardChanges();
93
+ console.log("time to encode:", Date.now() - now);
94
+ }
95
+ console.log("time for all encodes:", Date.now() - allEncodes);
96
+
97
+ console.log(Array.from(encoder.encodeAll()).length, "bytes");
@@ -1,4 +1,4 @@
1
- import { TypeContext } from "../annotations";
1
+ import { TypeContext } from "../types/TypeContext";
2
2
  import { $changes, $childType, $decoder, $onDecodeEnd } from "../types/symbols";
3
3
  import { Schema } from "../Schema";
4
4
 
@@ -214,7 +214,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
214
214
 
215
215
  if (metadata && !isCollection) {
216
216
 
217
- const onAdd = function (
217
+ const onAddListen = function (
218
218
  ref: Ref,
219
219
  prop: string,
220
220
  callback: (value: any, previousValue: any) => void, immediate: boolean
@@ -223,7 +223,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
223
223
  if (
224
224
  immediate &&
225
225
  context.instance[prop] !== undefined &&
226
- !onAddCalls.has(callback) // Workaround for https://github.com/colyseus/schema/issues/147
226
+ !onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
227
227
  ) {
228
228
  callback(context.instance[prop], undefined);
229
229
  }
@@ -236,14 +236,14 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
236
236
  return new Proxy({
237
237
  listen: function listen(prop: string, callback: (value: any, previousValue: any) => void, immediate: boolean = true) {
238
238
  if (context.instance) {
239
- return onAdd(context.instance, prop, callback, immediate);
239
+ return onAddListen(context.instance, prop, callback, immediate);
240
240
 
241
241
  } else {
242
242
  // collection instance not received yet
243
243
  let detachCallback = () => {};
244
244
 
245
245
  context.onInstanceAvailable((ref: Ref, existing: boolean) => {
246
- detachCallback = onAdd(ref, prop, callback, immediate && existing)
246
+ detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback))
247
247
  });
248
248
 
249
249
  return () => detachCallback();
@@ -330,6 +330,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
330
330
  currentOnAddCallback = callback;
331
331
  callback(value, key);
332
332
  onAddCalls.delete(callback)
333
+ currentOnAddCallback = undefined;
333
334
  });
334
335
  };
335
336
 
@@ -7,6 +7,7 @@ import type { ArraySchema } from "../types/custom/ArraySchema";
7
7
  import type { CollectionSchema } from "../types/custom/CollectionSchema";
8
8
  import type { SetSchema } from "../types/custom/SetSchema";
9
9
 
10
+ import { Root } from "./Root";
10
11
  import { Metadata } from "../Metadata";
11
12
  import type { EncodeOperation } from "./EncodeOperation";
12
13
  import type { DecodeOperation } from "../decoder/DecodeOperation";
@@ -26,53 +27,6 @@ export type Ref = Schema
26
27
  | CollectionSchema
27
28
  | SetSchema;
28
29
 
29
- export class Root {
30
- protected nextUniqueId: number = 0;
31
- refCount = new WeakMap<ChangeTree, number>();
32
-
33
- // all changes
34
- allChanges = new Map<ChangeTree, Map<number, OPERATION>>();
35
- allFilteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
36
-
37
- // pending changes to be encoded
38
- changes = new Map<ChangeTree, Map<number, OPERATION>>();
39
- filteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
40
-
41
- getNextUniqueId() {
42
- return this.nextUniqueId++;
43
- }
44
-
45
- add (changeTree: ChangeTree) {
46
- const refCount = this.refCount.get(changeTree) || 0;
47
- this.refCount.set(changeTree, refCount + 1);
48
- }
49
-
50
- remove(changeTree: ChangeTree) {
51
- const refCount = this.refCount.get(changeTree);
52
- if (refCount <= 1) {
53
- this.allChanges.delete(changeTree);
54
- this.changes.delete(changeTree);
55
-
56
- if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
57
- this.allFilteredChanges.delete(changeTree);
58
- this.filteredChanges.delete(changeTree);
59
- }
60
-
61
- this.refCount.delete(changeTree);
62
-
63
- } else {
64
- this.refCount.set(changeTree, refCount - 1);
65
- }
66
-
67
- changeTree.forEachChild((child, _) =>
68
- this.remove(child));
69
- }
70
-
71
- clear() {
72
- this.changes.clear();
73
- }
74
- }
75
-
76
30
  export class ChangeTree<T extends Ref=any> {
77
31
  ref: T;
78
32
  refId: number;
@@ -122,9 +76,6 @@ export class ChangeTree<T extends Ref=any> {
122
76
  if (this.isFiltered || this.isPartiallyFiltered) {
123
77
  this.root.allFilteredChanges.set(this, this.allFilteredChanges);
124
78
  this.root.filteredChanges.set(this, this.filteredChanges);
125
-
126
- // } else {
127
- // this.root.allChanges.set(this, this.allChanges);
128
79
  }
129
80
 
130
81
  if (!this.isFiltered) {
@@ -453,19 +404,39 @@ export class ChangeTree<T extends Ref=any> {
453
404
  // Detect if current structure has "filters" declared
454
405
  this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
455
406
 
456
- // TODO: support "partially filtered", where the instance is visible, but only a field is not.
407
+ if (parent && !Metadata.isValidInstance(parent)) {
408
+ const parentChangeTree = parent[$changes];
409
+ parent = parentChangeTree.parent;
410
+ parentIndex = parentChangeTree.parentIndex;
411
+ }
412
+
413
+ const parentMetadata = parent?.['constructor']?.[Symbol.metadata];
457
414
 
458
- // Detect if parent has "filters" declared
459
- while (parent && !this.isFiltered) {
460
- const metadata: Metadata = parent['constructor'][Symbol.metadata];
415
+ this.isFiltered = (
416
+ parent &&
417
+ parentMetadata?.[-2]?.includes(parentIndex)
418
+ );
461
419
 
462
- const fieldName = metadata?.[parentIndex];
463
- const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
420
+ // this.isFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-4];
464
421
 
465
- this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
422
+ // // Detect if parent has "filters" declared
423
+ // while (parent && !this.isFiltered) {
424
+ // const metadata: Metadata = parent['constructor'][Symbol.metadata];
425
+ // // this.isFiltered = metadata?.[-4];
466
426
 
467
- parent = parent[$changes].parent;
468
- };
427
+ // const fieldName = metadata?.[parentIndex];
428
+ // const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
429
+ // this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
430
+
431
+ // parent = parent[$changes].parent;
432
+ // };
433
+
434
+ // console.log("ChangeTree.checkIsFiltered", {
435
+ // parent: parent?.constructor.name,
436
+ // ref: this.ref.constructor.name,
437
+ // isFiltered: this.isFiltered,
438
+ // isPartiallyFiltered: this.isPartiallyFiltered,
439
+ // });
469
440
 
470
441
  //
471
442
  // TODO: refactor this!
@@ -1,15 +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
8
  import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
9
- import { Root } from "./ChangeTree";
9
+ import { Root } from "./Root";
10
10
  import { getNextPowerOf2 } from "../utils";
11
+
11
12
  import type { StateView } from "./StateView";
12
- import { Metadata } from "../Metadata";
13
+ import type { Metadata } from "../Metadata";
14
+ import type { ChangeTree } from "./ChangeTree";
13
15
 
14
16
  export class Encoder<T extends Schema = any> {
15
17
  static BUFFER_SIZE = 8 * 1024;// 8KB
@@ -21,13 +23,13 @@ export class Encoder<T extends Schema = any> {
21
23
  root: Root;
22
24
 
23
25
  constructor(state: T) {
24
- this.root = new Root();
25
26
 
26
27
  //
27
28
  // TODO: cache and restore "Context" based on root schema
28
29
  // (to avoid creating a new context for every new room)
29
30
  //
30
31
  this.context = new TypeContext(state.constructor as typeof Schema);
32
+ this.root = new Root(this.context);
31
33
 
32
34
  this.setState(state);
33
35
 
@@ -80,7 +82,8 @@ export class Encoder<T extends Schema = any> {
80
82
  }
81
83
 
82
84
  // skip root `refId` if it's the first change tree
83
- if (it.offset !== initialOffset || changeTree !== rootChangeTree) {
85
+ // (unless it "hasView", which will need to revisit the root)
86
+ if (hasView || changeTree !== rootChangeTree) {
84
87
  buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
85
88
  encode.number(buffer, changeTree.refId, it);
86
89
  }
@@ -124,7 +127,6 @@ export class Encoder<T extends Schema = any> {
124
127
  Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
125
128
  `);
126
129
 
127
-
128
130
  //
129
131
  // resize buffer and re-encode (TODO: can we avoid re-encoding here?)
130
132
  //
@@ -153,11 +155,8 @@ export class Encoder<T extends Schema = any> {
153
155
  }
154
156
 
155
157
  encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
156
- // console.log(`encodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
157
-
158
- // Array.from(this.root.allChanges.entries()).map((item) => {
159
- // console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
160
- // });
158
+ // console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
159
+ // this.debugChanges("allChanges");
161
160
 
162
161
  return this.encode(it, undefined, buffer, this.root.allChanges, true);
163
162
  }
@@ -165,11 +164,11 @@ export class Encoder<T extends Schema = any> {
165
164
  encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
166
165
  const viewOffset = it.offset;
167
166
 
168
- // console.log(`encodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
169
- // this.debugAllFilteredChanges();
167
+ // console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
168
+ // this.debugChanges("allFilteredChanges");
170
169
 
171
170
  // try to encode "filtered" changes
172
- this.encode(it, view, bytes, this.root.allFilteredChanges, true);
171
+ this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
173
172
 
174
173
  return Buffer.concat([
175
174
  bytes.subarray(0, sharedOffset),
@@ -177,21 +176,35 @@ export class Encoder<T extends Schema = any> {
177
176
  ]);
178
177
  }
179
178
 
180
-
181
- debugAllFilteredChanges() {
182
- Array.from(this.root.allFilteredChanges.entries()).map((item) => {
183
- console.log("->", { refId: item[0].refId, changes: item[1].size }, item[0].ref.toJSON());
184
- if (Array.isArray(item[0].ref.toJSON())) {
185
- item[1].forEach((op, key) => {
186
- console.log(" ->", { key, op: OPERATION[op] });
187
- })
188
- }
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
+ });
189
196
  });
190
197
  }
191
198
 
192
199
  encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
193
200
  const viewOffset = it.offset;
194
201
 
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");
207
+
195
208
  // encode visibility changes (add/remove for this view)
196
209
  const viewChangesIterator = view.changes.entries();
197
210
  for (const [changeTree, changes] of viewChangesIterator) {
@@ -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
+ }
package/src/index.ts CHANGED
@@ -42,18 +42,10 @@ export {
42
42
  ReflectionField,
43
43
  } from "./Reflection";
44
44
 
45
+ // Annotations, Metadata and TypeContext
45
46
  export { Metadata } from "./Metadata";
46
-
47
- export {
48
- // Annotations
49
- type,
50
- deprecated,
51
- defineTypes,
52
- view,
53
-
54
- // Internals
55
- TypeContext,
56
- } from "./annotations";
47
+ export { type, deprecated, defineTypes, view, } from "./annotations";
48
+ export { TypeContext } from "./types/TypeContext";
57
49
 
58
50
  // Annotation types
59
51
  export type { DefinitionType, PrimitiveType, Definition, } from "./annotations";