@colyseus/schema 3.0.11 → 3.0.13
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.
- package/build/cjs/index.js +99 -108
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +99 -108
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +99 -108
- package/lib/Metadata.js +1 -2
- package/lib/Metadata.js.map +1 -1
- package/lib/annotations.js +4 -3
- package/lib/annotations.js.map +1 -1
- package/lib/decoder/DecodeOperation.js +0 -13
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +1 -1
- package/lib/encoder/ChangeTree.js +1 -0
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +0 -1
- package/lib/encoder/Encoder.js +27 -33
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +1 -1
- package/lib/encoder/Root.js +1 -0
- package/lib/encoder/Root.js.map +1 -1
- package/lib/encoder/StateView.d.ts +4 -4
- package/lib/encoder/StateView.js +33 -21
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/types/custom/ArraySchema.js +5 -1
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -0
- package/lib/types/custom/MapSchema.js +28 -35
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/package.json +1 -1
- package/src/Metadata.ts +1 -2
- package/src/annotations.ts +4 -4
- package/src/decoder/DecodeOperation.ts +0 -14
- package/src/encoder/ChangeTree.ts +2 -2
- package/src/encoder/Encoder.ts +31 -44
- package/src/encoder/Root.ts +1 -0
- package/src/encoder/StateView.ts +34 -22
- package/src/types/custom/ArraySchema.ts +8 -1
- package/src/types/custom/MapSchema.ts +29 -43
package/src/encoder/Encoder.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import type { Schema } from "../Schema";
|
|
2
2
|
import { TypeContext } from "../types/TypeContext";
|
|
3
|
-
import { $changes, $encoder, $filter, $
|
|
3
|
+
import { $changes, $encoder, $filter, $getByIndex } from "../types/symbols";
|
|
4
4
|
|
|
5
5
|
import { encode } from "../encoding/encode";
|
|
6
6
|
import type { Iterator } from "../encoding/decode";
|
|
@@ -10,6 +10,7 @@ import { Root } from "./Root";
|
|
|
10
10
|
|
|
11
11
|
import type { StateView } from "./StateView";
|
|
12
12
|
import type { Metadata } from "../Metadata";
|
|
13
|
+
import type { ChangeTree } from "./ChangeTree";
|
|
13
14
|
|
|
14
15
|
export class Encoder<T extends Schema = any> {
|
|
15
16
|
static BUFFER_SIZE = (typeof(Buffer) !== "undefined") && Buffer.poolSize || 8 * 1024; // 8KB
|
|
@@ -58,14 +59,6 @@ export class Encoder<T extends Schema = any> {
|
|
|
58
59
|
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
59
60
|
const changeTree = changeTrees[i];
|
|
60
61
|
|
|
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
62
|
if (hasView) {
|
|
70
63
|
if (!view.items.has(changeTree)) {
|
|
71
64
|
view.invisible.add(changeTree);
|
|
@@ -76,6 +69,18 @@ export class Encoder<T extends Schema = any> {
|
|
|
76
69
|
}
|
|
77
70
|
}
|
|
78
71
|
|
|
72
|
+
const operations = changeTree[changeSetName];
|
|
73
|
+
const ref = changeTree.ref;
|
|
74
|
+
|
|
75
|
+
// TODO: avoid iterating over change tree if no changes were made
|
|
76
|
+
const numChanges = operations.operations.length;
|
|
77
|
+
if (numChanges === 0) { continue; }
|
|
78
|
+
|
|
79
|
+
const ctor = ref.constructor;
|
|
80
|
+
const encoder = ctor[$encoder];
|
|
81
|
+
const filter = ctor[$filter];
|
|
82
|
+
const metadata = ctor[Symbol.metadata];
|
|
83
|
+
|
|
79
84
|
// skip root `refId` if it's the first change tree
|
|
80
85
|
// (unless it "hasView", which will need to revisit the root)
|
|
81
86
|
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
@@ -83,7 +88,7 @@ export class Encoder<T extends Schema = any> {
|
|
|
83
88
|
encode.number(buffer, changeTree.refId, it);
|
|
84
89
|
}
|
|
85
90
|
|
|
86
|
-
for (let j = 0
|
|
91
|
+
for (let j = 0; j < numChanges; j++) {
|
|
87
92
|
const fieldIndex = operations.operations[j];
|
|
88
93
|
|
|
89
94
|
const operation = (fieldIndex < 0)
|
|
@@ -199,17 +204,18 @@ export class Encoder<T extends Schema = any> {
|
|
|
199
204
|
const viewOffset = it.offset;
|
|
200
205
|
|
|
201
206
|
// encode visibility changes (add/remove for this view)
|
|
202
|
-
const
|
|
207
|
+
for (const [refId, changes] of view.changes) {
|
|
208
|
+
const changeTree: ChangeTree = this.root.changeTrees[refId];
|
|
203
209
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
210
|
+
if (changeTree === undefined) {
|
|
211
|
+
// detached instance, remove from view and skip.
|
|
212
|
+
view.changes.delete(refId);
|
|
213
|
+
continue;
|
|
214
|
+
}
|
|
208
215
|
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
) {
|
|
216
|
+
const keys = Object.keys(changes);
|
|
217
|
+
if (keys.length === 0) {
|
|
218
|
+
// FIXME: avoid having empty changes if no changes were made
|
|
213
219
|
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
214
220
|
continue;
|
|
215
221
|
}
|
|
@@ -223,14 +229,15 @@ export class Encoder<T extends Schema = any> {
|
|
|
223
229
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
224
230
|
encode.number(bytes, changeTree.refId, it);
|
|
225
231
|
|
|
226
|
-
const keys = Object.keys(changes);
|
|
227
232
|
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
228
|
-
const
|
|
229
|
-
|
|
233
|
+
const index = Number(keys[i]);
|
|
234
|
+
// workaround when using view.add() on item that has been deleted from state (see test "adding to view item that has been removed from state")
|
|
235
|
+
const value = changeTree.ref[$getByIndex](index);
|
|
236
|
+
const operation = (value !== undefined && changes[index]) || OPERATION.DELETE;
|
|
230
237
|
|
|
231
238
|
// isEncodeAll = false
|
|
232
239
|
// hasView = true
|
|
233
|
-
encoder(this, bytes, changeTree,
|
|
240
|
+
encoder(this, bytes, changeTree, index, operation, it, false, true, metadata);
|
|
234
241
|
}
|
|
235
242
|
}
|
|
236
243
|
|
|
@@ -239,7 +246,7 @@ export class Encoder<T extends Schema = any> {
|
|
|
239
246
|
// (to allow re-using StateView's for multiple clients)
|
|
240
247
|
//
|
|
241
248
|
// clear "view" changes after encoding
|
|
242
|
-
view.changes
|
|
249
|
+
view.changes.clear();
|
|
243
250
|
|
|
244
251
|
// try to encode "filtered" changes
|
|
245
252
|
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
@@ -250,26 +257,6 @@ export class Encoder<T extends Schema = any> {
|
|
|
250
257
|
]);
|
|
251
258
|
}
|
|
252
259
|
|
|
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
260
|
discardChanges() {
|
|
274
261
|
// discard shared changes
|
|
275
262
|
let length = this.root.changes.length;
|
package/src/encoder/Root.ts
CHANGED
package/src/encoder/StateView.ts
CHANGED
|
@@ -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
|
-
|
|
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.
|
|
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
|
|
54
|
+
let changes = this.changes.get(changeTree.refId);
|
|
54
55
|
if (changes === undefined) {
|
|
55
56
|
changes = {};
|
|
56
|
-
this.changes
|
|
57
|
+
this.changes.set(changeTree.refId, changes);
|
|
57
58
|
}
|
|
58
59
|
|
|
59
60
|
// set tag
|
|
@@ -118,28 +119,30 @@ export class StateView {
|
|
|
118
119
|
return this;
|
|
119
120
|
}
|
|
120
121
|
|
|
121
|
-
protected
|
|
122
|
-
|
|
123
|
-
|
|
122
|
+
protected addParentOf(childChangeTree: ChangeTree, tag: number) {
|
|
123
|
+
const changeTree = childChangeTree.parent[$changes];
|
|
124
|
+
const parentIndex = childChangeTree.parentIndex;
|
|
124
125
|
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
this.addParent(parentChangeTree, changeTree.parentIndex, tag);
|
|
129
|
-
}
|
|
126
|
+
if (!this.items.has(changeTree)) {
|
|
127
|
+
// view must have all "changeTree" parent tree
|
|
128
|
+
this.items.add(changeTree);
|
|
130
129
|
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
130
|
+
// add parent's parent
|
|
131
|
+
const parentChangeTree: ChangeTree = changeTree.parent?.[$changes];
|
|
132
|
+
if (parentChangeTree && (parentChangeTree.filteredChanges !== undefined)) {
|
|
133
|
+
this.addParentOf(changeTree, tag);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// parent is already available, no need to add it!
|
|
137
|
+
if (!this.invisible.has(changeTree)) { return; }
|
|
134
138
|
}
|
|
135
139
|
|
|
136
140
|
// add parent's tag properties
|
|
137
141
|
if (changeTree.getChange(parentIndex) !== OPERATION.DELETE) {
|
|
138
|
-
|
|
139
|
-
let changes = this.changes[changeTree.refId];
|
|
142
|
+
let changes = this.changes.get(changeTree.refId);
|
|
140
143
|
if (changes === undefined) {
|
|
141
144
|
changes = {};
|
|
142
|
-
this.changes
|
|
145
|
+
this.changes.set(changeTree.refId, changes);
|
|
143
146
|
}
|
|
144
147
|
|
|
145
148
|
if (!this.tags) {
|
|
@@ -171,10 +174,10 @@ export class StateView {
|
|
|
171
174
|
const ref = changeTree.ref;
|
|
172
175
|
const metadata: Metadata = ref.constructor[Symbol.metadata];
|
|
173
176
|
|
|
174
|
-
let changes = this.changes
|
|
177
|
+
let changes = this.changes.get(changeTree.refId);
|
|
175
178
|
if (changes === undefined) {
|
|
176
179
|
changes = {};
|
|
177
|
-
this.changes
|
|
180
|
+
this.changes.set(changeTree.refId, changes);
|
|
178
181
|
}
|
|
179
182
|
|
|
180
183
|
if (tag === DEFAULT_VIEW_TAG) {
|
|
@@ -182,10 +185,10 @@ export class StateView {
|
|
|
182
185
|
const parent = changeTree.parent;
|
|
183
186
|
if (!Metadata.isValidInstance(parent)) {
|
|
184
187
|
const parentChangeTree = parent[$changes];
|
|
185
|
-
let changes = this.changes
|
|
188
|
+
let changes = this.changes.get(parentChangeTree.refId);
|
|
186
189
|
if (changes === undefined) {
|
|
187
190
|
changes = {};
|
|
188
|
-
this.changes
|
|
191
|
+
this.changes.set(parentChangeTree.refId, changes);
|
|
189
192
|
}
|
|
190
193
|
// DELETE / DELETE BY REF ID
|
|
191
194
|
changes[changeTree.parentIndex] = OPERATION.DELETE;
|
|
@@ -222,4 +225,13 @@ export class StateView {
|
|
|
222
225
|
|
|
223
226
|
return this;
|
|
224
227
|
}
|
|
228
|
+
|
|
229
|
+
has(obj: Ref) {
|
|
230
|
+
return this.items.has(obj[$changes]);
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
hasTag(ob: Ref, tag: number = DEFAULT_VIEW_TAG) {
|
|
234
|
+
const tags = this.tags?.get(ob[$changes]);
|
|
235
|
+
return tags?.has(tag) ?? false;
|
|
236
|
+
}
|
|
225
237
|
}
|
|
@@ -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
|
-
|
|
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
|
}
|
|
@@ -14,6 +14,7 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
|
|
|
14
14
|
|
|
15
15
|
protected $items: Map<K, V> = new Map<K, V>();
|
|
16
16
|
protected $indexes: Map<number, K> = new Map<number, K>();
|
|
17
|
+
protected deletedItems: { [field: string]: V } = {};
|
|
17
18
|
|
|
18
19
|
protected [$changes]: ChangeTree;
|
|
19
20
|
|
|
@@ -31,9 +32,9 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
|
|
|
31
32
|
*/
|
|
32
33
|
static [$filter] (ref: MapSchema, index: number, view: StateView) {
|
|
33
34
|
return (
|
|
34
|
-
!view
|
|
35
|
+
!view ||
|
|
35
36
|
typeof (ref[$childType]) === "string" ||
|
|
36
|
-
view.items.has(ref[$getByIndex](index)[$changes])
|
|
37
|
+
view.items.has((ref[$getByIndex](index) ?? ref.deletedItems[index])[$changes])
|
|
37
38
|
);
|
|
38
39
|
}
|
|
39
40
|
|
|
@@ -86,41 +87,38 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
|
|
|
86
87
|
key = key.toString() as K;
|
|
87
88
|
|
|
88
89
|
const changeTree = this[$changes];
|
|
90
|
+
const isRef = (value[$changes]) !== undefined;
|
|
89
91
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
+
let index: number;
|
|
93
|
+
let operation: OPERATION;
|
|
92
94
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
95
|
+
// IS REPLACE?
|
|
96
|
+
if (typeof(changeTree.indexes[key]) !== "undefined") {
|
|
97
|
+
index = changeTree.indexes[key];
|
|
98
|
+
operation = OPERATION.REPLACE;
|
|
96
99
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
+
const previousValue = this.$items.get(key);
|
|
101
|
+
if (previousValue === value) {
|
|
102
|
+
// if value is the same, avoid re-encoding it.
|
|
103
|
+
return;
|
|
100
104
|
|
|
101
|
-
|
|
105
|
+
} else if (isRef) {
|
|
106
|
+
// if is schema, force ADD operation if value differ from previous one.
|
|
107
|
+
operation = OPERATION.DELETE_AND_ADD;
|
|
108
|
+
|
|
109
|
+
// remove reference from previous value
|
|
110
|
+
if (previousValue !== undefined) {
|
|
111
|
+
previousValue[$changes].root?.remove(previousValue[$changes]);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
} else {
|
|
116
|
+
index = changeTree.indexes[$numFields] ?? 0;
|
|
117
|
+
operation = OPERATION.ADD;
|
|
102
118
|
|
|
103
|
-
//
|
|
104
|
-
// (encoding)
|
|
105
|
-
// set a unique id to relate directly with this key/value.
|
|
106
|
-
//
|
|
107
|
-
if (!isReplace) {
|
|
108
119
|
this.$indexes.set(index, key);
|
|
109
120
|
changeTree.indexes[key] = index;
|
|
110
121
|
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
122
|
}
|
|
125
123
|
|
|
126
124
|
this.$items.set(key, value);
|
|
@@ -145,7 +143,7 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
|
|
|
145
143
|
delete(key: K) {
|
|
146
144
|
const index = this[$changes].indexes[key];
|
|
147
145
|
|
|
148
|
-
this[$changes].delete(index)
|
|
146
|
+
this.deletedItems[index] = this[$changes].delete(index);;
|
|
149
147
|
|
|
150
148
|
return this.$items.delete(key);
|
|
151
149
|
}
|
|
@@ -209,19 +207,7 @@ export class MapSchema<V=any, K extends string = string> implements Map<K, V>, C
|
|
|
209
207
|
}
|
|
210
208
|
|
|
211
209
|
protected [$onEncodeEnd]() {
|
|
212
|
-
|
|
213
|
-
const keys = Object.keys(changeTree.indexedOperations);
|
|
214
|
-
|
|
215
|
-
for (let i = 0, len = keys.length; i < len; i++) {
|
|
216
|
-
const key = keys[i];
|
|
217
|
-
const fieldIndex = Number(key);
|
|
218
|
-
const operation = changeTree.indexedOperations[key];
|
|
219
|
-
|
|
220
|
-
if (operation === OPERATION.DELETE) {
|
|
221
|
-
const index = this[$getByIndex](fieldIndex) as string;
|
|
222
|
-
delete changeTree.indexes[index];
|
|
223
|
-
}
|
|
224
|
-
}
|
|
210
|
+
this.deletedItems = {};
|
|
225
211
|
}
|
|
226
212
|
|
|
227
213
|
toJSON() {
|