@colyseus/schema 3.0.42 → 3.0.43

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 (45) hide show
  1. package/build/cjs/index.js +327 -185
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +327 -185
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +327 -185
  6. package/lib/Schema.d.ts +2 -1
  7. package/lib/Schema.js +21 -3
  8. package/lib/Schema.js.map +1 -1
  9. package/lib/bench_encode.d.ts +1 -0
  10. package/lib/bench_encode.js +130 -0
  11. package/lib/bench_encode.js.map +1 -0
  12. package/lib/debug.d.ts +1 -0
  13. package/lib/debug.js +51 -0
  14. package/lib/debug.js.map +1 -0
  15. package/lib/decoder/Decoder.js +7 -8
  16. package/lib/decoder/Decoder.js.map +1 -1
  17. package/lib/encoder/ChangeTree.d.ts +57 -7
  18. package/lib/encoder/ChangeTree.js +171 -106
  19. package/lib/encoder/ChangeTree.js.map +1 -1
  20. package/lib/encoder/Encoder.js +19 -20
  21. package/lib/encoder/Encoder.js.map +1 -1
  22. package/lib/encoder/Root.d.ts +8 -7
  23. package/lib/encoder/Root.js +81 -26
  24. package/lib/encoder/Root.js.map +1 -1
  25. package/lib/types/custom/ArraySchema.js +5 -3
  26. package/lib/types/custom/ArraySchema.js.map +1 -1
  27. package/lib/types/custom/MapSchema.js +7 -2
  28. package/lib/types/custom/MapSchema.js.map +1 -1
  29. package/lib/types/symbols.d.ts +14 -14
  30. package/lib/types/symbols.js +14 -14
  31. package/lib/types/symbols.js.map +1 -1
  32. package/lib/utils.js +7 -3
  33. package/lib/utils.js.map +1 -1
  34. package/package.json +1 -1
  35. package/src/Schema.ts +21 -5
  36. package/src/bench_encode.ts +108 -0
  37. package/src/debug.ts +55 -0
  38. package/src/decoder/Decoder.ts +8 -12
  39. package/src/encoder/ChangeTree.ts +201 -115
  40. package/src/encoder/Encoder.ts +21 -19
  41. package/src/encoder/Root.ts +87 -28
  42. package/src/types/custom/ArraySchema.ts +6 -4
  43. package/src/types/custom/MapSchema.ts +8 -2
  44. package/src/types/symbols.ts +14 -14
  45. package/src/utils.ts +9 -3
@@ -10,7 +10,8 @@ import { Root } from "./Root";
10
10
 
11
11
  import type { StateView } from "./StateView";
12
12
  import type { Metadata } from "../Metadata";
13
- import type { ChangeSetName, ChangeTree } from "./ChangeTree";
13
+ import type { ChangeSetName, ChangeTree, ChangeTreeList, ChangeTreeNode } from "./ChangeTree";
14
+ import { createChangeTreeList } from "./ChangeTree";
14
15
 
15
16
  export class Encoder<T extends Schema = any> {
16
17
  static BUFFER_SIZE = (typeof(Buffer) !== "undefined") && Buffer.poolSize || 8 * 1024; // 8KB
@@ -54,11 +55,11 @@ export class Encoder<T extends Schema = any> {
54
55
  ): Buffer {
55
56
  const hasView = (view !== undefined);
56
57
  const rootChangeTree = this.state[$changes];
57
- const changeTrees = this.root[changeSetName];
58
58
 
59
- for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
60
- const changeTree = changeTrees[i];
61
- if (!changeTree) { continue; }
59
+ let current: ChangeTreeList | ChangeTreeNode = this.root[changeSetName];
60
+
61
+ while (current = current.next) {
62
+ const changeTree = current.changeTree;
62
63
 
63
64
  if (hasView) {
64
65
  if (!view.isChangeTreeVisible(changeTree)) {
@@ -166,7 +167,9 @@ export class Encoder<T extends Schema = any> {
166
167
  ? this.root[field]
167
168
  : field;
168
169
 
169
- rootChangeSet.forEach((changeTree) => {
170
+ let current = rootChangeSet.next;
171
+ while (current) {
172
+ const changeTree = current.changeTree;
170
173
  const changeSet = changeTree[field];
171
174
 
172
175
  const metadata: Metadata = changeTree.ref.constructor[Symbol.metadata];
@@ -179,7 +182,8 @@ export class Encoder<T extends Schema = any> {
179
182
  op: OPERATION[op],
180
183
  });
181
184
  }
182
- });
185
+ current = current.next;
186
+ }
183
187
  }
184
188
 
185
189
  encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
@@ -242,22 +246,20 @@ export class Encoder<T extends Schema = any> {
242
246
 
243
247
  discardChanges() {
244
248
  // discard shared changes
245
- let length = this.root.changes.length;
246
- if (length > 0) {
247
- while (length--) {
248
- this.root.changes[length]?.endEncode('changes');
249
- }
250
- this.root.changes.length = 0;
249
+ let current = this.root.changes.next;
250
+ while (current) {
251
+ current.changeTree.endEncode('changes');
252
+ current = current.next;
251
253
  }
254
+ this.root.changes = createChangeTreeList();
252
255
 
253
256
  // discard filtered changes
254
- length = this.root.filteredChanges.length;
255
- if (length > 0) {
256
- while (length--) {
257
- this.root.filteredChanges[length]?.endEncode('filteredChanges');
258
- }
259
- this.root.filteredChanges.length = 0;
257
+ current = this.root.filteredChanges.next;
258
+ while (current) {
259
+ current.changeTree.endEncode('filteredChanges');
260
+ current = current.next;
260
261
  }
262
+ this.root.filteredChanges = createChangeTreeList();
261
263
  }
262
264
 
263
265
  tryEncodeTypeId (bytes: Buffer, baseType: typeof Schema, targetType: typeof Schema, it: Iterator) {
@@ -1,7 +1,6 @@
1
1
  import { OPERATION } from "../encoding/spec";
2
2
  import { TypeContext } from "../types/TypeContext";
3
- import { spliceOne } from "../types/utils";
4
- import { ChangeTree, enqueueChangeTree, setOperationAtIndex } from "./ChangeTree";
3
+ import { ChangeTree, setOperationAtIndex, ChangeTreeList, createChangeTreeList, ChangeSetName } from "./ChangeTree";
5
4
 
6
5
  export class Root {
7
6
  protected nextUniqueId: number = 0;
@@ -10,12 +9,12 @@ export class Root {
10
9
  changeTrees: {[refId: number]: ChangeTree} = {};
11
10
 
12
11
  // all changes
13
- allChanges: ChangeTree[] = [];
14
- allFilteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
12
+ allChanges: ChangeTreeList = createChangeTreeList();
13
+ allFilteredChanges: ChangeTreeList = createChangeTreeList();// TODO: do not initialize it if filters are not used
15
14
 
16
15
  // pending changes to be encoded
17
- changes: ChangeTree[] = [];
18
- filteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
16
+ changes: ChangeTreeList = createChangeTreeList();
17
+ filteredChanges: ChangeTreeList = createChangeTreeList();// TODO: do not initialize it if filters are not used
19
18
 
20
19
  constructor(public types: TypeContext) { }
21
20
 
@@ -24,8 +23,10 @@ export class Root {
24
23
  }
25
24
 
26
25
  add(changeTree: ChangeTree) {
27
- // FIXME: move implementation of `ensureRefId` to `Root` class
28
- changeTree.ensureRefId();
26
+ // Assign unique `refId` to changeTree if it doesn't have one yet.
27
+ if (changeTree.refId === undefined) {
28
+ changeTree.refId = this.getNextUniqueId();
29
+ }
29
30
 
30
31
  const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
31
32
  if (isNewChangeTree) { this.changeTrees[changeTree.refId] = changeTree; }
@@ -53,6 +54,7 @@ export class Root {
53
54
  const refCount = (this.refCount[changeTree.refId]) - 1;
54
55
 
55
56
  if (refCount <= 0) {
57
+
56
58
  //
57
59
  // Only remove "root" reference if it's the last reference
58
60
  //
@@ -69,7 +71,20 @@ export class Root {
69
71
 
70
72
  this.refCount[changeTree.refId] = 0;
71
73
 
72
- changeTree.forEachChild((child, _) => this.remove(child));
74
+ changeTree.forEachChild((child, _) => {
75
+ if (child.removeParent(changeTree.ref)) {
76
+ if ((
77
+ child.parentChain === undefined || // no parent, remove it
78
+ (child.parentChain && this.refCount[child.refId] > 1) // parent is still in use, but has more than one reference, remove it
79
+ )) {
80
+ this.remove(child);
81
+
82
+ } else if (child.parentChain) {
83
+ // re-assigning a child of the same root, move it to the end
84
+ this.moveToEndOfChanges(child);
85
+ }
86
+ }
87
+ });
73
88
 
74
89
  } else {
75
90
  this.refCount[changeTree.refId] = refCount;
@@ -83,35 +98,79 @@ export class Root {
83
98
  // containing instance is not available, the Decoder will throw
84
99
  // "refId not found" error.
85
100
  //
86
- if (changeTree.filteredChanges !== undefined) {
87
- this.removeChangeFromChangeSet("filteredChanges", changeTree);
88
- enqueueChangeTree(this, changeTree, "filteredChanges");
89
- } else {
90
- this.removeChangeFromChangeSet("changes", changeTree);
91
- enqueueChangeTree(this, changeTree, "changes");
92
- }
101
+ this.moveToEndOfChanges(changeTree);
102
+ changeTree.forEachChild((child, _) => this.moveToEndOfChanges(child));
93
103
  }
94
104
 
95
105
  return refCount;
96
106
  }
97
107
 
98
- removeChangeFromChangeSet(changeSetName: "allChanges" | "changes" | "filteredChanges" | "allFilteredChanges", changeTree: ChangeTree) {
108
+ moveToEndOfChanges(changeTree: ChangeTree) {
109
+ if (changeTree.filteredChanges) {
110
+ this.moveToEndOfChangeTreeList("filteredChanges", changeTree);
111
+ this.moveToEndOfChangeTreeList("allFilteredChanges", changeTree);
112
+ } else {
113
+ this.moveToEndOfChangeTreeList("changes", changeTree);
114
+ this.moveToEndOfChangeTreeList("allChanges", changeTree);
115
+ }
116
+ }
117
+
118
+ moveToEndOfChangeTreeList(changeSetName: ChangeSetName, changeTree: ChangeTree): void {
99
119
  const changeSet = this[changeSetName];
100
- const changeSetIndex = changeSet.indexOf(changeTree);
120
+ const node = changeTree[changeSetName].queueRootNode;
121
+ if (!node || node === changeSet.tail) return;
101
122
 
102
- if (changeSetIndex !== -1) {
103
- changeTree[changeSetName].queueRootIndex = -1;
104
- changeSet[changeSetIndex] = undefined;
105
- return true;
123
+ // Remove from current position
124
+ if (node.prev) {
125
+ node.prev.next = node.next;
126
+ } else {
127
+ changeSet.next = node.next;
128
+ }
129
+
130
+ if (node.next) {
131
+ node.next.prev = node.prev;
132
+ } else {
133
+ changeSet.tail = node.prev;
134
+ }
135
+
136
+ // Add to end
137
+ node.prev = changeSet.tail;
138
+ node.next = undefined;
139
+
140
+ if (changeSet.tail) {
141
+ changeSet.tail.next = node;
142
+ } else {
143
+ changeSet.next = node;
106
144
  }
107
145
 
108
- // if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
109
- // changeTree[changeSetName].queueRootIndex = -1;
110
- // return true;
111
- // }
146
+ changeSet.tail = node;
112
147
  }
113
148
 
114
- clear() {
115
- this.changes.length = 0;
149
+ protected removeChangeFromChangeSet(changeSetName: ChangeSetName, changeTree: ChangeTree) {
150
+ const changeSet = this[changeSetName];
151
+ const node = changeTree[changeSetName].queueRootNode;
152
+
153
+ if (node && node.changeTree === changeTree) {
154
+ // Remove the node from the linked list
155
+ if (node.prev) {
156
+ node.prev.next = node.next;
157
+ } else {
158
+ changeSet.next = node.next;
159
+ }
160
+
161
+ if (node.next) {
162
+ node.next.prev = node.prev;
163
+ } else {
164
+ changeSet.tail = node.prev;
165
+ }
166
+
167
+ changeSet.length--;
168
+
169
+ // Clear ChangeTree reference
170
+ changeTree[changeSetName].queueRootNode = undefined;
171
+ return true;
172
+ }
173
+
174
+ return false;
116
175
  }
117
176
  }
@@ -1,6 +1,6 @@
1
1
  import { $changes, $childType, $decoder, $deleteByIndex, $onEncodeEnd, $encoder, $filter, $getByIndex, $onDecodeEnd } from "../symbols";
2
2
  import type { Schema } from "../../Schema";
3
- import { ChangeTree, debugChangeSet, deleteOperationAtIndex, enqueueChangeTree, setOperationAtIndex } from "../../encoder/ChangeTree";
3
+ import { ChangeTree, enqueueChangeTree, setOperationAtIndex } from "../../encoder/ChangeTree";
4
4
  import { OPERATION } from "../../encoding/spec";
5
5
  import { registerType } from "../registry";
6
6
  import { Collection } from "../HelperTypes";
@@ -156,8 +156,11 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
156
156
  }
157
157
  });
158
158
 
159
- this[$changes] = new ChangeTree(proxy);
160
- this[$changes].indexes = {};
159
+ Object.defineProperty(this, $changes, {
160
+ value: new ChangeTree(proxy),
161
+ enumerable: false,
162
+ writable: true,
163
+ });
161
164
 
162
165
  if (items.length > 0) {
163
166
  this.push(...items);
@@ -351,7 +354,6 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
351
354
  shift(): V | undefined {
352
355
  if (this.items.length === 0) { return undefined; }
353
356
 
354
- // const index = Number(Object.keys(changeTree.indexes)[0]);
355
357
  const changeTree = this[$changes];
356
358
 
357
359
  const index = this.tmpItems.findIndex(item => item === this.items[0]);
@@ -43,8 +43,14 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
43
43
  }
44
44
 
45
45
  constructor (initialValues?: Map<K, V> | Record<K, V>) {
46
- this[$changes] = new ChangeTree(this);
47
- this[$changes].indexes = {};
46
+ const changeTree = new ChangeTree(this);
47
+ changeTree.indexes = {};
48
+
49
+ Object.defineProperty(this, $changes, {
50
+ value: changeTree,
51
+ enumerable: false,
52
+ writable: true,
53
+ });
48
54
 
49
55
  if (initialValues) {
50
56
  if (
@@ -1,39 +1,39 @@
1
- export const $track = Symbol("$track");
2
- export const $encoder = Symbol("$encoder");
3
- export const $decoder = Symbol("$decoder");
1
+ export const $track = "~track";
2
+ export const $encoder = "~encoder";
3
+ export const $decoder = "~decoder";
4
4
 
5
- export const $filter = Symbol("$filter");
5
+ export const $filter = "~filter";
6
6
 
7
- export const $getByIndex = Symbol("$getByIndex");
8
- export const $deleteByIndex = Symbol("$deleteByIndex");
7
+ export const $getByIndex = "~getByIndex";
8
+ export const $deleteByIndex = "~deleteByIndex";
9
9
 
10
10
  /**
11
11
  * Used to hold ChangeTree instances whitin the structures
12
12
  */
13
- export const $changes = Symbol('$changes');
13
+ export const $changes = '~changes';
14
14
 
15
15
  /**
16
16
  * Used to keep track of the type of the child elements of a collection
17
17
  * (MapSchema, ArraySchema, etc.)
18
18
  */
19
- export const $childType = Symbol('$childType');
19
+ export const $childType = '~childType';
20
20
 
21
21
  /**
22
22
  * Optional "discard" method for custom types (ArraySchema)
23
23
  * (Discards changes for next serialization)
24
24
  */
25
- export const $onEncodeEnd = Symbol('$onEncodeEnd');
25
+ export const $onEncodeEnd = '~onEncodeEnd';
26
26
 
27
27
  /**
28
28
  * When decoding, this method is called after the instance is fully decoded
29
29
  */
30
- export const $onDecodeEnd = Symbol("$onDecodeEnd");
30
+ export const $onDecodeEnd = "~onDecodeEnd";
31
31
 
32
32
  /**
33
33
  * Metadata
34
34
  */
35
- export const $descriptors = Symbol("$descriptors");
36
- export const $numFields = "$__numFields";
37
- export const $refTypeFieldIndexes = "$__refTypeFieldIndexes";
38
- export const $viewFieldIndexes = "$__viewFieldIndexes";
35
+ export const $descriptors = "~descriptors";
36
+ export const $numFields = "~__numFields";
37
+ export const $refTypeFieldIndexes = "~__refTypeFieldIndexes";
38
+ export const $viewFieldIndexes = "~__viewFieldIndexes";
39
39
  export const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
package/src/utils.ts CHANGED
@@ -28,9 +28,14 @@ export function dumpChanges(schema: Schema) {
28
28
  };
29
29
 
30
30
  // for (const refId in $root.changes) {
31
- $root.changes.forEach(changeTree => {
31
+ let current = $root.changes.next;
32
+ while (current) {
33
+ const changeTree = current.changeTree;
32
34
  // skip if ChangeTree is undefined
33
- if (changeTree === undefined) { return; }
35
+ if (changeTree === undefined) {
36
+ current = current.next;
37
+ continue;
38
+ }
34
39
 
35
40
  const changes = changeTree.indexedOperations;
36
41
 
@@ -41,7 +46,8 @@ export function dumpChanges(schema: Schema) {
41
46
  if (!dump.ops[opName]) { dump.ops[opName] = 0; }
42
47
  dump.ops[OPERATION[op]]++;
43
48
  }
44
- });
49
+ current = current.next;
50
+ }
45
51
 
46
52
  return dump;
47
53
  }