@colyseus/schema 3.0.10 → 3.0.12

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.
@@ -1,7 +1,7 @@
1
1
  import { OPERATION } from "../encoding/spec";
2
2
  import { TypeContext } from "../types/TypeContext";
3
3
  import { spliceOne } from "../types/utils";
4
- import { ChangeTree, setOperationAtIndex } from "./ChangeTree";
4
+ import { ChangeTree, enqueueChangeTree, setOperationAtIndex } from "./ChangeTree";
5
5
 
6
6
  export class Root {
7
7
  protected nextUniqueId: number = 0;
@@ -71,6 +71,23 @@ export class Root {
71
71
 
72
72
  } else {
73
73
  this.refCount[changeTree.refId] = refCount;
74
+
75
+ //
76
+ // When losing a reference to an instance, it is best to move the
77
+ // ChangeTree to the end of the encoding queue.
78
+ //
79
+ // This way, at decoding time, the instance that contains the
80
+ // ChangeTree will be available before the ChangeTree itself. If the
81
+ // containing instance is not available, the Decoder will throw
82
+ // "refId not found" error.
83
+ //
84
+ if (changeTree.filteredChanges !== undefined) {
85
+ this.removeChangeFromChangeSet("filteredChanges", changeTree);
86
+ enqueueChangeTree(this, changeTree, "filteredChanges");
87
+ } else {
88
+ this.removeChangeFromChangeSet("changes", changeTree);
89
+ enqueueChangeTree(this, changeTree, "changes");
90
+ }
74
91
  }
75
92
 
76
93
  changeTree.forEachChild((child, _) => this.remove(child));
@@ -80,10 +97,10 @@ export class Root {
80
97
 
81
98
  removeChangeFromChangeSet(changeSetName: "allChanges" | "changes" | "filteredChanges" | "allFilteredChanges", changeTree: ChangeTree) {
82
99
  const changeSet = this[changeSetName];
83
- const index = changeSet.indexOf(changeTree);
84
- if (index !== -1) {
85
- spliceOne(changeSet, index);
100
+ if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
101
+ changeTree[changeSetName].queueRootIndex = -1;
86
102
  // changeSet[index] = undefined;
103
+ return true;
87
104
  }
88
105
  }
89
106
 
@@ -25,7 +25,8 @@ 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: { [refId: number]: IndexedOperations } = {};
28
+ // TODO: use map here!? may fix encode ordering issue
29
+ changes = new Map<number, IndexedOperations>();
29
30
 
30
31
  // TODO: allow to set multiple tags at once
31
32
  add(obj: Ref, tag: number = DEFAULT_VIEW_TAG, checkIncludeParent: boolean = true) {
@@ -43,17 +44,17 @@ export class StateView {
43
44
  // - if it was invisible to this view
44
45
  // - if it were previously filtered out
45
46
  if (checkIncludeParent && changeTree.parent) {
46
- this.addParent(changeTree.parent[$changes], changeTree.parentIndex, tag);
47
+ this.addParentOf(changeTree, tag);
47
48
  }
48
49
 
49
50
  //
50
51
  // TODO: when adding an item of a MapSchema, the changes may not
51
52
  // be set (only the parent's changes are set)
52
53
  //
53
- let changes = this.changes[changeTree.refId];
54
+ let changes = this.changes.get(changeTree.refId);
54
55
  if (changes === undefined) {
55
56
  changes = {};
56
- this.changes[changeTree.refId] = changes;
57
+ this.changes.set(changeTree.refId, changes);
57
58
  }
58
59
 
59
60
  // set tag
@@ -118,28 +119,34 @@ export class StateView {
118
119
  return this;
119
120
  }
120
121
 
121
- protected addParent(changeTree: ChangeTree, parentIndex: number, tag: number) {
122
+ protected addParentOf(childChangeTree: ChangeTree, tag: number) {
123
+ const changeTree = childChangeTree.parent[$changes];
124
+ const parentIndex = childChangeTree.parentIndex;
125
+
122
126
  // view must have all "changeTree" parent tree
123
127
  this.items.add(changeTree);
124
128
 
125
129
  // add parent's parent
126
130
  const parentChangeTree: ChangeTree = changeTree.parent?.[$changes];
127
131
  if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
128
- this.addParent(parentChangeTree, changeTree.parentIndex, tag);
132
+ this.addParentOf(changeTree, tag);
129
133
  }
130
134
 
131
- // parent is already available, no need to add it!
132
- if (!this.invisible.has(changeTree)) {
135
+ if (
136
+ // parent is already available, no need to add it!
137
+ !this.invisible.has(changeTree) &&
138
+ // item is being replaced, no need to add parent
139
+ changeTree.indexedOperations[parentIndex] !== OPERATION.DELETE_AND_ADD
140
+ ) {
133
141
  return;
134
142
  }
135
143
 
136
144
  // add parent's tag properties
137
145
  if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
138
-
139
- let changes = this.changes[changeTree.refId];
146
+ let changes = this.changes.get(changeTree.refId);
140
147
  if (changes === undefined) {
141
148
  changes = {};
142
- this.changes[changeTree.refId] = changes;
149
+ this.changes.set(changeTree.refId, changes);
143
150
  }
144
151
 
145
152
  if (!this.tags) {
@@ -171,10 +178,10 @@ export class StateView {
171
178
  const ref = changeTree.ref;
172
179
  const metadata: Metadata = ref.constructor[Symbol.metadata];
173
180
 
174
- let changes = this.changes[changeTree.refId];
181
+ let changes = this.changes.get(changeTree.refId);
175
182
  if (changes === undefined) {
176
183
  changes = {};
177
- this.changes[changeTree.refId] = changes;
184
+ this.changes.set(changeTree.refId, changes);
178
185
  }
179
186
 
180
187
  if (tag === DEFAULT_VIEW_TAG) {
@@ -182,10 +189,10 @@ export class StateView {
182
189
  const parent = changeTree.parent;
183
190
  if (!Metadata.isValidInstance(parent)) {
184
191
  const parentChangeTree = parent[$changes];
185
- let changes = this.changes[parentChangeTree.refId];
192
+ let changes = this.changes.get(parentChangeTree.refId);
186
193
  if (changes === undefined) {
187
194
  changes = {};
188
- this.changes[parentChangeTree.refId] = changes;
195
+ this.changes.set(parentChangeTree.refId, changes);
189
196
  }
190
197
  // DELETE / DELETE BY REF ID
191
198
  changes[changeTree.parentIndex] = OPERATION.DELETE;
@@ -92,7 +92,8 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
92
92
  if (setValue[$changes]) {
93
93
  assertInstanceType(setValue, obj[$childType] as typeof Schema, obj, key);
94
94
 
95
- if (obj.items[key as unknown as number] !== undefined) {
95
+ const previousValue = obj.items[key as unknown as number];
96
+ if (previousValue !== undefined) {
96
97
  if (setValue[$changes].isNew) {
97
98
  this[$changes].indexedOperation(Number(key), OPERATION.MOVE_AND_ADD);
98
99
 
@@ -103,10 +104,16 @@ export class ArraySchema<V = any> implements Array<V>, Collection<number, V> {
103
104
  this[$changes].indexedOperation(Number(key), OPERATION.MOVE);
104
105
  }
105
106
  }
107
+
108
+ // remove root reference from previous value
109
+ previousValue[$changes].root?.remove(previousValue[$changes]);
110
+
106
111
  } else if (setValue[$changes].isNew) {
107
112
  this[$changes].indexedOperation(Number(key), OPERATION.ADD);
108
113
  }
109
114
 
115
+ setValue[$changes].setParent(this, obj[$changes].root, key);
116
+
110
117
  } else {
111
118
  obj.$changeAt(Number(key), setValue);
112
119
  }
@@ -86,41 +86,38 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
86
86
  key = key.toString() as K;
87
87
 
88
88
  const changeTree = this[$changes];
89
+ const isRef = (value[$changes]) !== undefined;
89
90
 
90
- // get "index" for this value.
91
- const isReplace = typeof(changeTree.indexes[key]) !== "undefined";
91
+ let index: number;
92
+ let operation: OPERATION;
92
93
 
93
- const index = (isReplace)
94
- ? changeTree.indexes[key]
95
- : changeTree.indexes[$numFields] ?? 0;
94
+ // IS REPLACE?
95
+ if (typeof(changeTree.indexes[key]) !== "undefined") {
96
+ index = changeTree.indexes[key];
97
+ operation = OPERATION.REPLACE;
96
98
 
97
- let operation: OPERATION = (isReplace)
98
- ? OPERATION.REPLACE
99
- : OPERATION.ADD;
99
+ const previousValue = this.$items.get(key);
100
+ if (previousValue === value) {
101
+ // if value is the same, avoid re-encoding it.
102
+ return;
100
103
 
101
- const isRef = (value[$changes]) !== undefined;
104
+ } else if (isRef) {
105
+ // if is schema, force ADD operation if value differ from previous one.
106
+ operation = OPERATION.DELETE_AND_ADD;
107
+
108
+ // remove reference from previous value
109
+ if (previousValue !== undefined) {
110
+ previousValue[$changes].root?.remove(previousValue[$changes]);
111
+ }
112
+ }
113
+
114
+ } else {
115
+ index = changeTree.indexes[$numFields] ?? 0;
116
+ operation = OPERATION.ADD;
102
117
 
103
- //
104
- // (encoding)
105
- // set a unique id to relate directly with this key/value.
106
- //
107
- if (!isReplace) {
108
118
  this.$indexes.set(index, key);
109
119
  changeTree.indexes[key] = index;
110
120
  changeTree.indexes[$numFields] = index + 1;
111
-
112
- } else if (
113
- !isRef &&
114
- this.$items.get(key) === value
115
- ) {
116
- // if value is the same, avoid re-encoding it.
117
- return;
118
-
119
- } else if (
120
- isRef && // if is schema, force ADD operation if value differ from previous one.
121
- this.$items.get(key) !== value
122
- ) {
123
- operation = OPERATION.ADD;
124
121
  }
125
122
 
126
123
  this.$items.set(key, value);