@colyseus/schema 3.0.42 → 3.0.44

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 (52) hide show
  1. package/build/cjs/index.js +365 -219
  2. package/build/cjs/index.js.map +1 -1
  3. package/build/esm/index.mjs +365 -219
  4. package/build/esm/index.mjs.map +1 -1
  5. package/build/umd/index.js +365 -219
  6. package/lib/Schema.d.ts +4 -3
  7. package/lib/Schema.js +22 -4
  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 +8 -9
  16. package/lib/decoder/Decoder.js.map +1 -1
  17. package/lib/encoder/ChangeTree.d.ts +57 -7
  18. package/lib/encoder/ChangeTree.js +172 -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 +9 -8
  23. package/lib/encoder/Root.js +84 -27
  24. package/lib/encoder/Root.js.map +1 -1
  25. package/lib/encoder/StateView.d.ts +1 -1
  26. package/lib/encoder/StateView.js +28 -23
  27. package/lib/encoder/StateView.js.map +1 -1
  28. package/lib/types/custom/ArraySchema.js +7 -5
  29. package/lib/types/custom/ArraySchema.js.map +1 -1
  30. package/lib/types/custom/CollectionSchema.js +1 -1
  31. package/lib/types/custom/CollectionSchema.js.map +1 -1
  32. package/lib/types/custom/MapSchema.js +9 -4
  33. package/lib/types/custom/MapSchema.js.map +1 -1
  34. package/lib/types/symbols.d.ts +14 -14
  35. package/lib/types/symbols.js +14 -14
  36. package/lib/types/symbols.js.map +1 -1
  37. package/lib/utils.js +7 -3
  38. package/lib/utils.js.map +1 -1
  39. package/package.json +2 -1
  40. package/src/Schema.ts +23 -7
  41. package/src/bench_encode.ts +108 -0
  42. package/src/debug.ts +55 -0
  43. package/src/decoder/Decoder.ts +9 -13
  44. package/src/encoder/ChangeTree.ts +203 -116
  45. package/src/encoder/Encoder.ts +21 -19
  46. package/src/encoder/Root.ts +90 -29
  47. package/src/encoder/StateView.ts +34 -26
  48. package/src/types/custom/ArraySchema.ts +8 -6
  49. package/src/types/custom/CollectionSchema.ts +1 -1
  50. package/src/types/custom/MapSchema.ts +10 -4
  51. package/src/types/symbols.ts +15 -15
  52. package/src/utils.ts +9 -3
@@ -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, Ref } 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; }
@@ -46,11 +47,14 @@ export class Root {
46
47
 
47
48
  this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
48
49
 
50
+ // console.log("ADD", { refId: changeTree.refId, refCount: this.refCount[changeTree.refId] });
51
+
49
52
  return isNewChangeTree;
50
53
  }
51
54
 
52
- remove(changeTree: ChangeTree) {
55
+ remove(changeTree: ChangeTree, parent?: Ref) {
53
56
  const refCount = (this.refCount[changeTree.refId]) - 1;
57
+ // console.log("REMOVE", { refId: changeTree.refId, refCount });
54
58
 
55
59
  if (refCount <= 0) {
56
60
  //
@@ -69,7 +73,20 @@ export class Root {
69
73
 
70
74
  this.refCount[changeTree.refId] = 0;
71
75
 
72
- changeTree.forEachChild((child, _) => this.remove(child));
76
+ changeTree.forEachChild((child, _) => {
77
+ if (child.removeParent(changeTree.ref)) {
78
+ if ((
79
+ child.parentChain === undefined || // no parent, remove it
80
+ (child.parentChain && this.refCount[child.refId] > 1) // parent is still in use, but has more than one reference, remove it
81
+ )) {
82
+ this.remove(child, changeTree.ref);
83
+
84
+ } else if (child.parentChain) {
85
+ // re-assigning a child of the same root, move it to the end
86
+ this.moveToEndOfChanges(child);
87
+ }
88
+ }
89
+ });
73
90
 
74
91
  } else {
75
92
  this.refCount[changeTree.refId] = refCount;
@@ -83,35 +100,79 @@ export class Root {
83
100
  // containing instance is not available, the Decoder will throw
84
101
  // "refId not found" error.
85
102
  //
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
- }
103
+ this.moveToEndOfChanges(changeTree);
104
+ changeTree.forEachChild((child, _) => this.moveToEndOfChanges(child));
93
105
  }
94
106
 
95
107
  return refCount;
96
108
  }
97
109
 
98
- removeChangeFromChangeSet(changeSetName: "allChanges" | "changes" | "filteredChanges" | "allFilteredChanges", changeTree: ChangeTree) {
110
+ moveToEndOfChanges(changeTree: ChangeTree) {
111
+ if (changeTree.filteredChanges) {
112
+ this.moveToEndOfChangeTreeList("filteredChanges", changeTree);
113
+ this.moveToEndOfChangeTreeList("allFilteredChanges", changeTree);
114
+ } else {
115
+ this.moveToEndOfChangeTreeList("changes", changeTree);
116
+ this.moveToEndOfChangeTreeList("allChanges", changeTree);
117
+ }
118
+ }
119
+
120
+ moveToEndOfChangeTreeList(changeSetName: ChangeSetName, changeTree: ChangeTree): void {
99
121
  const changeSet = this[changeSetName];
100
- const changeSetIndex = changeSet.indexOf(changeTree);
122
+ const node = changeTree[changeSetName].queueRootNode;
123
+ if (!node || node === changeSet.tail) return;
101
124
 
102
- if (changeSetIndex !== -1) {
103
- changeTree[changeSetName].queueRootIndex = -1;
104
- changeSet[changeSetIndex] = undefined;
105
- return true;
125
+ // Remove from current position
126
+ if (node.prev) {
127
+ node.prev.next = node.next;
128
+ } else {
129
+ changeSet.next = node.next;
130
+ }
131
+
132
+ if (node.next) {
133
+ node.next.prev = node.prev;
134
+ } else {
135
+ changeSet.tail = node.prev;
136
+ }
137
+
138
+ // Add to end
139
+ node.prev = changeSet.tail;
140
+ node.next = undefined;
141
+
142
+ if (changeSet.tail) {
143
+ changeSet.tail.next = node;
144
+ } else {
145
+ changeSet.next = node;
106
146
  }
107
147
 
108
- // if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
109
- // changeTree[changeSetName].queueRootIndex = -1;
110
- // return true;
111
- // }
148
+ changeSet.tail = node;
112
149
  }
113
150
 
114
- clear() {
115
- this.changes.length = 0;
151
+ protected removeChangeFromChangeSet(changeSetName: ChangeSetName, changeTree: ChangeTree) {
152
+ const changeSet = this[changeSetName];
153
+ const node = changeTree[changeSetName].queueRootNode;
154
+
155
+ if (node && node.changeTree === changeTree) {
156
+ // Remove the node from the linked list
157
+ if (node.prev) {
158
+ node.prev.next = node.next;
159
+ } else {
160
+ changeSet.next = node.next;
161
+ }
162
+
163
+ if (node.next) {
164
+ node.next.prev = node.prev;
165
+ } else {
166
+ changeSet.tail = node.prev;
167
+ }
168
+
169
+ changeSet.length--;
170
+
171
+ // Clear ChangeTree reference
172
+ changeTree[changeSetName].queueRootNode = undefined;
173
+ return true;
174
+ }
175
+
176
+ return false;
116
177
  }
117
178
  }
@@ -43,13 +43,14 @@ export class StateView {
43
43
  // TODO: allow to set multiple tags at once
44
44
  add(obj: Ref, tag: number = DEFAULT_VIEW_TAG, checkIncludeParent: boolean = true) {
45
45
  const changeTree: ChangeTree = obj?.[$changes];
46
+ const parentChangeTree = changeTree.parent;
46
47
 
47
48
  if (!changeTree) {
48
49
  console.warn("StateView#add(), invalid object:", obj);
49
- return this;
50
+ return false;
50
51
 
51
52
  } else if (
52
- !changeTree.parent &&
53
+ !parentChangeTree &&
53
54
  changeTree.refId !== 0 // allow root object
54
55
  ) {
55
56
  /**
@@ -76,20 +77,38 @@ export class StateView {
76
77
  // add parent ChangeTree's
77
78
  // - if it was invisible to this view
78
79
  // - if it were previously filtered out
79
- if (checkIncludeParent && changeTree.parent) {
80
+ if (checkIncludeParent && parentChangeTree) {
80
81
  this.addParentOf(changeTree, tag);
81
82
  }
82
83
 
83
- //
84
- // TODO: when adding an item of a MapSchema, the changes may not
85
- // be set (only the parent's changes are set)
86
- //
87
84
  let changes = this.changes.get(changeTree.refId);
88
85
  if (changes === undefined) {
89
86
  changes = {};
87
+ // FIXME / OPTIMIZE: do not add if no changes are needed
90
88
  this.changes.set(changeTree.refId, changes);
91
89
  }
92
90
 
91
+ let isChildAdded = false;
92
+
93
+ //
94
+ // Add children of this ChangeTree first.
95
+ // If successful, we must link the current ChangeTree to the child.
96
+ //
97
+ changeTree.forEachChild((change, index) => {
98
+ // Do not ADD children that don't have the same tag
99
+ if (
100
+ metadata &&
101
+ metadata[index].tag !== undefined &&
102
+ metadata[index].tag !== tag
103
+ ) {
104
+ return;
105
+ }
106
+
107
+ if (this.add(change.ref, tag, false)) {
108
+ isChildAdded = true;
109
+ }
110
+ });
111
+
93
112
  // set tag
94
113
  if (tag !== DEFAULT_VIEW_TAG) {
95
114
  if (!this.tags) {
@@ -111,12 +130,14 @@ export class StateView {
111
130
  }
112
131
  });
113
132
 
114
- } else {
115
- const isInvisible = this.invisible.has(changeTree);
133
+ } else if (!changeTree.isNew || isChildAdded) {
134
+ // new structures will be added as part of .encode() call, no need to force it to .encodeView()
116
135
  const changeSet = (changeTree.filteredChanges !== undefined)
117
136
  ? changeTree.allFilteredChanges
118
137
  : changeTree.allChanges;
119
138
 
139
+ const isInvisible = this.invisible.has(changeTree);
140
+
120
141
  for (let i = 0, len = changeSet.operations.length; i < len; i++) {
121
142
  const index = changeSet.operations[i];
122
143
  if (index === undefined) { continue; } // skip "undefined" indexes
@@ -124,33 +145,20 @@ export class StateView {
124
145
  const op = changeTree.indexedOperations[index] ?? OPERATION.ADD;
125
146
  const tagAtIndex = metadata?.[index].tag;
126
147
  if (
127
- !changeTree.isNew && // new structures will be added as part of .encode() call, no need to force it to .encodeView()
148
+ op !== OPERATION.DELETE &&
128
149
  (
129
150
  isInvisible || // if "invisible", include all
130
151
  tagAtIndex === undefined || // "all change" with no tag
131
152
  tagAtIndex === tag // tagged property
132
- ) &&
133
- op !== OPERATION.DELETE
153
+ )
134
154
  ) {
135
155
  changes[index] = op;
156
+ isChildAdded = true; // FIXME: assign only once
136
157
  }
137
158
  }
138
159
  }
139
160
 
140
- // Add children of this ChangeTree to this view
141
- changeTree.forEachChild((change, index) => {
142
- // Do not ADD children that don't have the same tag
143
- if (
144
- metadata &&
145
- metadata[index].tag !== undefined &&
146
- metadata[index].tag !== tag
147
- ) {
148
- return;
149
- }
150
- this.add(change.ref, tag, false);
151
- });
152
-
153
- return this;
161
+ return isChildAdded;
154
162
  }
155
163
 
156
164
  protected addParentOf(childChangeTree: ChangeTree, tag: number) {
@@ -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";
@@ -120,7 +120,7 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
120
120
 
121
121
  if (previousValue !== undefined) {
122
122
  // remove root reference from previous value
123
- previousValue[$changes].root?.remove(previousValue[$changes]);
123
+ previousValue[$changes].root?.remove(previousValue[$changes], obj);
124
124
  }
125
125
 
126
126
  } else {
@@ -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);
@@ -307,7 +310,7 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
307
310
 
308
311
  // remove children references
309
312
  changeTree.forEachChild((childChangeTree, _) => {
310
- changeTree.root?.remove(childChangeTree);
313
+ changeTree.root?.remove(childChangeTree, this);
311
314
  });
312
315
 
313
316
  changeTree.discard(true);
@@ -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]);
@@ -118,7 +118,7 @@ export class CollectionSchema<V=any> implements Collection<K, V>{
118
118
 
119
119
  // remove children references
120
120
  changeTree.forEachChild((childChangeTree, _) => {
121
- changeTree.root?.remove(childChangeTree);
121
+ changeTree.root?.remove(childChangeTree, this);
122
122
  });
123
123
 
124
124
  // clear previous indexes
@@ -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 (
@@ -108,7 +114,7 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
108
114
 
109
115
  // remove reference from previous value
110
116
  if (previousValue !== undefined) {
111
- previousValue[$changes].root?.remove(previousValue[$changes]);
117
+ previousValue[$changes].root?.remove(previousValue[$changes], this);
112
118
  }
113
119
  }
114
120
 
@@ -157,7 +163,7 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
157
163
 
158
164
  // remove children references
159
165
  changeTree.forEachChild((childChangeTree, _) => {
160
- changeTree.root?.remove(childChangeTree);
166
+ changeTree.root?.remove(childChangeTree, this);
161
167
  });
162
168
 
163
169
  // clear previous indexes
@@ -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";
39
- export const $fieldIndexesByViewTag = "$__fieldIndexesByViewTag";
35
+ export const $descriptors = "~descriptors";
36
+ export const $numFields = "~__numFields";
37
+ export const $refTypeFieldIndexes = "~__refTypeFieldIndexes";
38
+ export const $viewFieldIndexes = "~__viewFieldIndexes";
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
  }