@colyseus/schema 3.0.11 → 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.
@@ -59,20 +59,6 @@ export function decodeValue(
59
59
  //
60
60
  if (operation !== OPERATION.DELETE_AND_ADD) {
61
61
  ref[$deleteByIndex](index);
62
-
63
- // //
64
- // // FIXME: is this in the correct place?
65
- // // (This is sounding like a workaround just for ArraySchema, see
66
- // // "should splice and move" test on ArraySchema.test.ts)
67
- // //
68
- // allChanges.push({
69
- // ref,
70
- // refId: decoder.currentRefId,
71
- // op: OPERATION.DELETE,
72
- // field: index as unknown as string,
73
- // value: undefined,
74
- // previousValue,
75
- // });
76
62
  }
77
63
 
78
64
  value = undefined;
@@ -11,8 +11,6 @@ import { Root } from "./Root";
11
11
  import { Metadata } from "../Metadata";
12
12
  import type { EncodeOperation } from "./EncodeOperation";
13
13
  import type { DecodeOperation } from "../decoder/DecodeOperation";
14
- import { TypeContext } from "../types/TypeContext";
15
- import { ReferenceTracker } from "../decoder/ReferenceTracker";
16
14
 
17
15
  declare global {
18
16
  interface Object {
@@ -1,6 +1,6 @@
1
1
  import type { Schema } from "../Schema";
2
2
  import { TypeContext } from "../types/TypeContext";
3
- import { $changes, $encoder, $filter, $onEncodeEnd } from "../types/symbols";
3
+ import { $changes, $encoder, $filter } from "../types/symbols";
4
4
 
5
5
  import { encode } from "../encoding/encode";
6
6
  import type { Iterator } from "../encoding/decode";
@@ -58,14 +58,6 @@ export class Encoder<T extends Schema = any> {
58
58
  for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
59
59
  const changeTree = changeTrees[i];
60
60
 
61
- const operations = changeTree[changeSetName];
62
- const ref = changeTree.ref;
63
-
64
- const ctor = ref.constructor;
65
- const encoder = ctor[$encoder];
66
- const filter = ctor[$filter];
67
- const metadata = ctor[Symbol.metadata];
68
-
69
61
  if (hasView) {
70
62
  if (!view.items.has(changeTree)) {
71
63
  view.invisible.add(changeTree);
@@ -76,6 +68,18 @@ export class Encoder<T extends Schema = any> {
76
68
  }
77
69
  }
78
70
 
71
+ const operations = changeTree[changeSetName];
72
+ const ref = changeTree.ref;
73
+
74
+ // TODO: avoid iterating over change tree if no changes were made
75
+ const numChanges = operations.operations.length;
76
+ if (numChanges === 0) { continue; }
77
+
78
+ const ctor = ref.constructor;
79
+ const encoder = ctor[$encoder];
80
+ const filter = ctor[$filter];
81
+ const metadata = ctor[Symbol.metadata];
82
+
79
83
  // skip root `refId` if it's the first change tree
80
84
  // (unless it "hasView", which will need to revisit the root)
81
85
  if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
@@ -83,7 +87,7 @@ export class Encoder<T extends Schema = any> {
83
87
  encode.number(buffer, changeTree.refId, it);
84
88
  }
85
89
 
86
- for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
90
+ for (let j = 0; j < numChanges; j++) {
87
91
  const fieldIndex = operations.operations[j];
88
92
 
89
93
  const operation = (fieldIndex < 0)
@@ -199,17 +203,18 @@ export class Encoder<T extends Schema = any> {
199
203
  const viewOffset = it.offset;
200
204
 
201
205
  // encode visibility changes (add/remove for this view)
202
- const refIds = Object.keys(view.changes);
203
-
204
- for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
205
- const refId = refIds[i];
206
- const changes = view.changes[refId];
206
+ for (const [refId, changes] of view.changes) {
207
207
  const changeTree = this.root.changeTrees[refId];
208
208
 
209
- if (
210
- changeTree === undefined ||
211
- Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
212
- ) {
209
+ if (changeTree === undefined) {
210
+ // detached instance, remove from view and skip.
211
+ view.changes.delete(refId);
212
+ continue;
213
+ }
214
+
215
+ const keys = Object.keys(changes);
216
+ if (keys.length === 0) {
217
+ // FIXME: avoid having empty changes if no changes were made
213
218
  // console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
214
219
  continue;
215
220
  }
@@ -223,7 +228,6 @@ export class Encoder<T extends Schema = any> {
223
228
  bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
224
229
  encode.number(bytes, changeTree.refId, it);
225
230
 
226
- const keys = Object.keys(changes);
227
231
  for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
228
232
  const key = keys[i];
229
233
  const operation = changes[key];
@@ -239,7 +243,7 @@ export class Encoder<T extends Schema = any> {
239
243
  // (to allow re-using StateView's for multiple clients)
240
244
  //
241
245
  // clear "view" changes after encoding
242
- view.changes = {};
246
+ view.changes.clear();
243
247
 
244
248
  // try to encode "filtered" changes
245
249
  this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
@@ -250,26 +254,6 @@ export class Encoder<T extends Schema = any> {
250
254
  ]);
251
255
  }
252
256
 
253
- onEndEncode(changeTrees = this.root.changes) {
254
- // changeTrees.forEach(function(changeTree) {
255
- // changeTree.endEncode();
256
- // });
257
-
258
-
259
- // for (const refId in changeTrees) {
260
- // const changeTree = this.root.changeTrees[refId];
261
- // changeTree.endEncode();
262
-
263
- // // changeTree.changes.clear();
264
-
265
- // // // ArraySchema and MapSchema have a custom "encode end" method
266
- // // changeTree.ref[$onEncodeEnd]?.();
267
-
268
- // // // Not a new instance anymore
269
- // // delete changeTree[$isNew];
270
- // }
271
- }
272
-
273
257
  discardChanges() {
274
258
  // discard shared changes
275
259
  let length = this.root.changes.length;
@@ -100,6 +100,7 @@ export class Root {
100
100
  if (spliceOne(changeSet, changeSet.indexOf(changeTree))) {
101
101
  changeTree[changeSetName].queueRootIndex = -1;
102
102
  // changeSet[index] = undefined;
103
+ return true;
103
104
  }
104
105
  }
105
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);