@colyseus/schema 3.0.0-alpha.4 → 3.0.0-alpha.41
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/README.md +148 -62
- package/bin/schema-debug +94 -0
- package/build/cjs/index.js +2201 -1507
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +2198 -1506
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +2208 -1514
- package/lib/Metadata.d.ts +21 -9
- package/lib/Metadata.js +169 -32
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +19 -4
- package/lib/Reflection.js +66 -32
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +4 -4
- package/lib/Schema.js +44 -50
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +31 -34
- package/lib/annotations.js +110 -160
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.d.ts +1 -0
- package/lib/bench_encode.js +130 -0
- package/lib/bench_encode.js.map +1 -0
- package/lib/codegen/api.js +1 -2
- package/lib/codegen/api.js.map +1 -1
- package/lib/codegen/languages/cpp.js +1 -2
- package/lib/codegen/languages/cpp.js.map +1 -1
- package/lib/codegen/languages/csharp.js +2 -46
- package/lib/codegen/languages/csharp.js.map +1 -1
- package/lib/codegen/languages/haxe.js +1 -2
- package/lib/codegen/languages/haxe.js.map +1 -1
- package/lib/codegen/languages/java.js +1 -2
- package/lib/codegen/languages/java.js.map +1 -1
- package/lib/codegen/languages/js.js +1 -2
- package/lib/codegen/languages/js.js.map +1 -1
- package/lib/codegen/languages/lua.js +1 -2
- package/lib/codegen/languages/lua.js.map +1 -1
- package/lib/codegen/languages/ts.js +1 -2
- package/lib/codegen/languages/ts.js.map +1 -1
- package/lib/codegen/parser.js +85 -3
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +6 -3
- package/lib/codegen/types.js.map +1 -1
- package/lib/debug.d.ts +1 -0
- package/lib/debug.js +51 -0
- package/lib/debug.js.map +1 -0
- package/lib/decoder/DecodeOperation.d.ts +3 -4
- package/lib/decoder/DecodeOperation.js +37 -19
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +6 -7
- package/lib/decoder/Decoder.js +14 -14
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/ReferenceTracker.js +3 -2
- package/lib/decoder/ReferenceTracker.js.map +1 -1
- package/lib/decoder/strategy/RawChanges.js +1 -2
- package/lib/decoder/strategy/RawChanges.js.map +1 -1
- package/lib/decoder/strategy/StateCallbacks.d.ts +44 -11
- package/lib/decoder/strategy/StateCallbacks.js +75 -65
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +27 -21
- package/lib/encoder/ChangeTree.js +246 -186
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +3 -6
- package/lib/encoder/EncodeOperation.js +51 -65
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +9 -8
- package/lib/encoder/Encoder.js +168 -91
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +22 -0
- package/lib/encoder/Root.js +81 -0
- package/lib/encoder/Root.js.map +1 -0
- package/lib/encoder/StateView.d.ts +7 -7
- package/lib/encoder/StateView.js +70 -74
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/assert.d.ts +7 -6
- package/lib/encoding/assert.js +13 -5
- package/lib/encoding/assert.js.map +1 -1
- package/lib/encoding/decode.d.ts +35 -20
- package/lib/encoding/decode.js +43 -87
- package/lib/encoding/decode.js.map +1 -1
- package/lib/encoding/encode.d.ts +36 -17
- package/lib/encoding/encode.js +82 -68
- package/lib/encoding/encode.js.map +1 -1
- package/lib/encoding/spec.d.ts +4 -5
- package/lib/encoding/spec.js +1 -2
- package/lib/encoding/spec.js.map +1 -1
- package/lib/index.d.ts +10 -9
- package/lib/index.js +24 -17
- package/lib/index.js.map +1 -1
- package/lib/types/HelperTypes.d.ts +34 -2
- package/lib/types/HelperTypes.js.map +1 -1
- package/lib/types/TypeContext.d.ts +23 -0
- package/lib/types/TypeContext.js +111 -0
- package/lib/types/TypeContext.js.map +1 -0
- package/lib/types/custom/ArraySchema.d.ts +2 -2
- package/lib/types/custom/ArraySchema.js +33 -22
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/custom/CollectionSchema.js +1 -0
- package/lib/types/custom/CollectionSchema.js.map +1 -1
- package/lib/types/custom/MapSchema.d.ts +3 -1
- package/lib/types/custom/MapSchema.js +12 -4
- package/lib/types/custom/MapSchema.js.map +1 -1
- package/lib/types/custom/SetSchema.js +1 -0
- package/lib/types/custom/SetSchema.js.map +1 -1
- package/lib/types/registry.d.ts +8 -1
- package/lib/types/registry.js +23 -6
- package/lib/types/registry.js.map +1 -1
- package/lib/types/symbols.d.ts +8 -5
- package/lib/types/symbols.js +9 -6
- package/lib/types/symbols.js.map +1 -1
- package/lib/types/utils.js +1 -2
- package/lib/types/utils.js.map +1 -1
- package/lib/utils.js +9 -7
- package/lib/utils.js.map +1 -1
- package/package.json +7 -6
- package/src/Metadata.ts +190 -42
- package/src/Reflection.ts +77 -39
- package/src/Schema.ts +59 -64
- package/src/annotations.ts +156 -202
- package/src/bench_encode.ts +108 -0
- package/src/codegen/languages/csharp.ts +1 -47
- package/src/codegen/parser.ts +107 -0
- package/src/codegen/types.ts +1 -0
- package/src/debug.ts +55 -0
- package/src/decoder/DecodeOperation.ts +46 -18
- package/src/decoder/Decoder.ts +17 -15
- package/src/decoder/ReferenceTracker.ts +3 -2
- package/src/decoder/strategy/StateCallbacks.ts +153 -82
- package/src/encoder/ChangeTree.ts +286 -202
- package/src/encoder/EncodeOperation.ts +78 -78
- package/src/encoder/Encoder.ts +202 -97
- package/src/encoder/Root.ts +93 -0
- package/src/encoder/StateView.ts +76 -88
- package/src/encoding/assert.ts +17 -8
- package/src/encoding/decode.ts +62 -97
- package/src/encoding/encode.ts +99 -65
- package/src/encoding/spec.ts +3 -5
- package/src/index.ts +12 -20
- package/src/types/HelperTypes.ts +54 -2
- package/src/types/TypeContext.ts +133 -0
- package/src/types/custom/ArraySchema.ts +49 -19
- package/src/types/custom/CollectionSchema.ts +1 -0
- package/src/types/custom/MapSchema.ts +18 -5
- package/src/types/custom/SetSchema.ts +1 -0
- package/src/types/registry.ts +22 -3
- package/src/types/symbols.ts +10 -7
- package/src/utils.ts +7 -3
package/src/encoder/Encoder.ts
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
import type { Schema } from "../Schema";
|
|
2
|
-
import { TypeContext } from "../
|
|
3
|
-
import { $changes, $encoder, $filter } from "../types/symbols";
|
|
2
|
+
import { TypeContext } from "../types/TypeContext";
|
|
3
|
+
import { $changes, $encoder, $filter, $onEncodeEnd } from "../types/symbols";
|
|
4
4
|
|
|
5
|
-
import
|
|
5
|
+
import { encode } from "../encoding/encode";
|
|
6
6
|
import type { Iterator } from "../encoding/decode";
|
|
7
7
|
|
|
8
|
-
import { SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
|
|
9
|
-
import { Root } from "./
|
|
8
|
+
import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
|
|
9
|
+
import { Root } from "./Root";
|
|
10
10
|
import { getNextPowerOf2 } from "../utils";
|
|
11
|
+
|
|
11
12
|
import type { StateView } from "./StateView";
|
|
13
|
+
import type { Metadata } from "../Metadata";
|
|
12
14
|
|
|
13
15
|
export class Encoder<T extends Schema = any> {
|
|
14
16
|
static BUFFER_SIZE = 8 * 1024;// 8KB
|
|
@@ -19,14 +21,16 @@ export class Encoder<T extends Schema = any> {
|
|
|
19
21
|
|
|
20
22
|
root: Root;
|
|
21
23
|
|
|
22
|
-
constructor(
|
|
23
|
-
this.setRoot(root);
|
|
24
|
+
constructor(state: T) {
|
|
24
25
|
|
|
25
26
|
//
|
|
26
27
|
// TODO: cache and restore "Context" based on root schema
|
|
27
28
|
// (to avoid creating a new context for every new room)
|
|
28
29
|
//
|
|
29
|
-
this.context = new TypeContext(
|
|
30
|
+
this.context = new TypeContext(state.constructor as typeof Schema);
|
|
31
|
+
this.root = new Root(this.context);
|
|
32
|
+
|
|
33
|
+
this.setState(state);
|
|
30
34
|
|
|
31
35
|
// console.log(">>>>>>>>>>>>>>>> Encoder types");
|
|
32
36
|
// this.context.schemas.forEach((id, schema) => {
|
|
@@ -34,32 +38,45 @@ export class Encoder<T extends Schema = any> {
|
|
|
34
38
|
// });
|
|
35
39
|
}
|
|
36
40
|
|
|
37
|
-
protected
|
|
38
|
-
this.root = new Root();
|
|
41
|
+
protected setState(state: T) {
|
|
39
42
|
this.state = state;
|
|
40
|
-
state[$changes].setRoot(this.root);
|
|
43
|
+
this.state[$changes].setRoot(this.root);
|
|
41
44
|
}
|
|
42
45
|
|
|
43
46
|
encode(
|
|
44
47
|
it: Iterator = { offset: 0 },
|
|
45
48
|
view?: StateView,
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
buffer = this.sharedBuffer,
|
|
50
|
+
changeSetName: "changes" | "allChanges" | "filteredChanges" | "allFilteredChanges" = "changes",
|
|
51
|
+
isEncodeAll = changeSetName === "allChanges",
|
|
52
|
+
initialOffset = it.offset // cache current offset in case we need to resize the buffer
|
|
48
53
|
): Buffer {
|
|
49
|
-
const initialOffset = it.offset; // cache current offset in case we need to resize the buffer
|
|
50
|
-
|
|
51
|
-
const isEncodeAll = this.root.allChanges === changeTrees;
|
|
52
54
|
const hasView = (view !== undefined);
|
|
53
55
|
const rootChangeTree = this.state[$changes];
|
|
54
56
|
|
|
55
|
-
const
|
|
57
|
+
const shouldDiscardChanges = !isEncodeAll && !hasView;
|
|
58
|
+
const changeTrees = this.root[changeSetName];
|
|
56
59
|
|
|
57
|
-
for (
|
|
60
|
+
for (let i = 0, numChangeTrees = changeTrees.length; i < numChangeTrees; i++) {
|
|
61
|
+
const changeTree = changeTrees[i];
|
|
62
|
+
|
|
63
|
+
// // Root#removeChangeFromChangeSet() is now removing instead of setting to "undefined"
|
|
64
|
+
// if (changeTree === undefined) { continue; }
|
|
65
|
+
|
|
66
|
+
const operations = changeTree[changeSetName];
|
|
58
67
|
const ref = changeTree.ref;
|
|
59
68
|
|
|
60
|
-
const ctor = ref
|
|
69
|
+
const ctor = ref.constructor;
|
|
61
70
|
const encoder = ctor[$encoder];
|
|
62
71
|
const filter = ctor[$filter];
|
|
72
|
+
const metadata = ctor[Symbol.metadata];
|
|
73
|
+
|
|
74
|
+
// try { throw new Error(); } catch (e) {
|
|
75
|
+
// // only print if not coming from Reflection.ts
|
|
76
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
77
|
+
// console.log("ChangeTree:", { refId: changeTree.refId, ref: ref.constructor.name });
|
|
78
|
+
// }
|
|
79
|
+
// }
|
|
63
80
|
|
|
64
81
|
if (hasView) {
|
|
65
82
|
if (!view.items.has(changeTree)) {
|
|
@@ -72,14 +89,21 @@ export class Encoder<T extends Schema = any> {
|
|
|
72
89
|
}
|
|
73
90
|
|
|
74
91
|
// skip root `refId` if it's the first change tree
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
92
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
93
|
+
if (hasView || it.offset > initialOffset || changeTree !== rootChangeTree) {
|
|
94
|
+
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
95
|
+
encode.number(buffer, changeTree.refId, it);
|
|
78
96
|
}
|
|
79
97
|
|
|
80
|
-
|
|
98
|
+
for (let j = 0, numChanges = operations.operations.length; j < numChanges; j++) {
|
|
99
|
+
const fieldIndex = operations.operations[j];
|
|
100
|
+
|
|
101
|
+
const operation = (fieldIndex < 0)
|
|
102
|
+
? Math.abs(fieldIndex) // "pure" operation without fieldIndex (e.g. CLEAR, REVERSE, etc.)
|
|
103
|
+
: (isEncodeAll)
|
|
104
|
+
? OPERATION.ADD
|
|
105
|
+
: changeTree.indexedOperations[fieldIndex];
|
|
81
106
|
|
|
82
|
-
for (const [fieldIndex, operation] of changesIterator) {
|
|
83
107
|
//
|
|
84
108
|
// first pass (encodeAll), identify "filtered" operations without encoding them
|
|
85
109
|
// they will be encoded per client, based on their view.
|
|
@@ -87,116 +111,158 @@ export class Encoder<T extends Schema = any> {
|
|
|
87
111
|
// TODO: how can we optimize filtering out "encode all" operations?
|
|
88
112
|
// TODO: avoid checking if no view tags were defined
|
|
89
113
|
//
|
|
90
|
-
if (filter && !filter(ref, fieldIndex, view)) {
|
|
91
|
-
// console.log("SKIP FIELD:", { ref: changeTree.ref.constructor.name, fieldIndex, })
|
|
92
|
-
|
|
114
|
+
if (fieldIndex === undefined || operation === undefined || (filter && !filter(ref, fieldIndex, view))) {
|
|
93
115
|
// console.log("ADD AS INVISIBLE:", fieldIndex, changeTree.ref.constructor.name)
|
|
94
116
|
// view?.invisible.add(changeTree);
|
|
95
117
|
continue;
|
|
96
118
|
}
|
|
97
119
|
|
|
98
|
-
//
|
|
99
|
-
//
|
|
100
|
-
//
|
|
101
|
-
//
|
|
102
|
-
//
|
|
120
|
+
// try { throw new Error(); } catch (e) {
|
|
121
|
+
// // only print if not coming from Reflection.ts
|
|
122
|
+
// if (!e.stack.includes("src/Reflection.ts")) {
|
|
123
|
+
// console.log("WILL ENCODE", {
|
|
124
|
+
// ref: changeTree.ref.constructor.name,
|
|
125
|
+
// fieldIndex,
|
|
126
|
+
// operation: OPERATION[operation],
|
|
127
|
+
// });
|
|
128
|
+
// }
|
|
129
|
+
// }
|
|
130
|
+
|
|
131
|
+
// console.log("encode...", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, fieldIndex, operation });
|
|
132
|
+
|
|
133
|
+
encoder(this, buffer, changeTree, fieldIndex, operation, it, isEncodeAll, hasView, metadata);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (shouldDiscardChanges) {
|
|
137
|
+
changeTree.discard();
|
|
103
138
|
|
|
104
|
-
|
|
139
|
+
// Not a new instance anymore
|
|
140
|
+
changeTree.isNew = false;
|
|
105
141
|
}
|
|
106
142
|
}
|
|
107
143
|
|
|
108
|
-
if (it.offset >
|
|
109
|
-
const newSize = getNextPowerOf2(
|
|
110
|
-
console.warn(
|
|
144
|
+
if (it.offset > buffer.byteLength) {
|
|
145
|
+
const newSize = getNextPowerOf2(buffer.byteLength * 2);
|
|
146
|
+
console.warn(`@colyseus/schema buffer overflow. Encoded state is higher than default BUFFER_SIZE. Use the following to increase default BUFFER_SIZE:
|
|
147
|
+
|
|
148
|
+
import { Encoder } from "@colyseus/schema";
|
|
149
|
+
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
150
|
+
`);
|
|
111
151
|
|
|
112
152
|
//
|
|
113
153
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
114
154
|
//
|
|
115
|
-
|
|
116
|
-
return this.encode({ offset: initialOffset }, view);
|
|
155
|
+
buffer = Buffer.allocUnsafeSlow(newSize);
|
|
117
156
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
//
|
|
122
|
-
if (!isEncodeAll && !hasView) {
|
|
123
|
-
//
|
|
124
|
-
// FIXME: avoid iterating over change trees twice.
|
|
125
|
-
//
|
|
126
|
-
this.onEndEncode(changeTrees);
|
|
157
|
+
// assign resized buffer to local sharedBuffer
|
|
158
|
+
if (buffer === this.sharedBuffer) {
|
|
159
|
+
this.sharedBuffer = buffer;
|
|
127
160
|
}
|
|
128
161
|
|
|
129
|
-
|
|
130
|
-
|
|
162
|
+
return this.encode({ offset: initialOffset }, view, buffer, changeSetName, isEncodeAll);
|
|
163
|
+
|
|
164
|
+
} else {
|
|
165
|
+
// //
|
|
166
|
+
// // only clear changes after making sure buffer resize is not required.
|
|
167
|
+
// //
|
|
168
|
+
// if (shouldClearChanges) {
|
|
169
|
+
// //
|
|
170
|
+
// // FIXME: avoid iterating over change trees twice.
|
|
171
|
+
// //
|
|
172
|
+
// this.onEndEncode(changeTrees);
|
|
173
|
+
// }
|
|
174
|
+
|
|
175
|
+
return buffer.subarray(0, it.offset);
|
|
131
176
|
}
|
|
132
177
|
}
|
|
133
178
|
|
|
134
|
-
encodeAll(it: Iterator = { offset: 0 }) {
|
|
135
|
-
// console.log(
|
|
179
|
+
encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
|
|
180
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${(Object.keys(this.root.allChanges).length)})`);
|
|
181
|
+
// this.debugChanges("allChanges");
|
|
136
182
|
|
|
137
|
-
|
|
138
|
-
// console.log("->", item[0].refId, item[0].ref.toJSON());
|
|
139
|
-
// });
|
|
140
|
-
|
|
141
|
-
return this.encode(it, undefined, this.sharedBuffer, this.root.allChanges);
|
|
183
|
+
return this.encode(it, undefined, buffer, "allChanges", true);
|
|
142
184
|
}
|
|
143
185
|
|
|
144
186
|
encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
|
|
145
187
|
const viewOffset = it.offset;
|
|
146
188
|
|
|
147
|
-
// console.log(
|
|
148
|
-
// this.
|
|
189
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${(Object.keys(this.root.allFilteredChanges).length)})`);
|
|
190
|
+
// this.debugChanges("allFilteredChanges");
|
|
191
|
+
|
|
192
|
+
// console.log("\n\nENCODE ALL FOR VIEW...\n\n")
|
|
149
193
|
|
|
150
194
|
// try to encode "filtered" changes
|
|
151
|
-
this.encode(it, view, bytes,
|
|
195
|
+
this.encode(it, view, bytes, "allFilteredChanges", true, viewOffset);
|
|
152
196
|
|
|
153
197
|
return Buffer.concat([
|
|
154
|
-
bytes.
|
|
155
|
-
bytes.
|
|
198
|
+
bytes.subarray(0, sharedOffset),
|
|
199
|
+
bytes.subarray(viewOffset, it.offset)
|
|
156
200
|
]);
|
|
157
201
|
}
|
|
158
202
|
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
203
|
+
debugChanges(field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges") {
|
|
204
|
+
const rootChangeSet = (typeof (field) === "string")
|
|
205
|
+
? this.root[field]
|
|
206
|
+
: field;
|
|
207
|
+
|
|
208
|
+
rootChangeSet.forEach((changeTree) => {
|
|
209
|
+
const changeSet = changeTree[field];
|
|
210
|
+
|
|
211
|
+
const metadata: Metadata = changeTree.ref.constructor[Symbol.metadata];
|
|
212
|
+
console.log("->", { ref: changeTree.ref.constructor.name, refId: changeTree.refId, changes: Object.keys(changeSet).length });
|
|
213
|
+
for (const index in changeSet) {
|
|
214
|
+
const op = changeSet[index];
|
|
215
|
+
console.log(" ->", {
|
|
216
|
+
index,
|
|
217
|
+
field: metadata?.[index],
|
|
218
|
+
op: OPERATION[op],
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
}
|
|
170
223
|
|
|
171
224
|
encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
|
|
172
225
|
const viewOffset = it.offset;
|
|
173
226
|
|
|
174
|
-
//
|
|
175
|
-
this.
|
|
227
|
+
// console.log(`\nencodeView(), view.changes (${view.changes.size})`);
|
|
228
|
+
// this.debugChanges(view.changes);
|
|
229
|
+
|
|
230
|
+
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
231
|
+
// this.debugChanges("filteredChanges");
|
|
176
232
|
|
|
177
233
|
// encode visibility changes (add/remove for this view)
|
|
178
|
-
const
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
234
|
+
const refIds = Object.keys(view.changes);
|
|
235
|
+
// console.log("ENCODE VIEW:", refIds);
|
|
236
|
+
for (let i = 0, numRefIds = refIds.length; i < numRefIds; i++) {
|
|
237
|
+
const refId = refIds[i];
|
|
238
|
+
const changes = view.changes[refId];
|
|
239
|
+
const changeTree = this.root.changeTrees[refId];
|
|
240
|
+
|
|
241
|
+
if (
|
|
242
|
+
changeTree === undefined ||
|
|
243
|
+
Object.keys(changes).length === 0 // FIXME: avoid having empty changes if no changes were made
|
|
244
|
+
) {
|
|
245
|
+
// console.log("changes.size === 0, skip", changeTree.ref.constructor.name);
|
|
183
246
|
continue;
|
|
184
247
|
}
|
|
185
248
|
|
|
186
249
|
const ref = changeTree.ref;
|
|
187
250
|
|
|
188
|
-
const ctor = ref
|
|
251
|
+
const ctor = ref.constructor;
|
|
189
252
|
const encoder = ctor[$encoder];
|
|
253
|
+
const metadata = ctor[Symbol.metadata];
|
|
190
254
|
|
|
191
255
|
bytes[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
192
256
|
encode.number(bytes, changeTree.refId, it);
|
|
193
257
|
|
|
194
|
-
const
|
|
258
|
+
const keys = Object.keys(changes);
|
|
259
|
+
for (let i = 0, numChanges = keys.length; i < numChanges; i++) {
|
|
260
|
+
const key = keys[i];
|
|
261
|
+
const operation = changes[key];
|
|
195
262
|
|
|
196
|
-
for (const [fieldIndex, operation] of changesIterator) {
|
|
197
263
|
// isEncodeAll = false
|
|
198
264
|
// hasView = true
|
|
199
|
-
encoder(this, bytes, changeTree,
|
|
265
|
+
encoder(this, bytes, changeTree, Number(key), operation, it, false, true, metadata);
|
|
200
266
|
}
|
|
201
267
|
}
|
|
202
268
|
|
|
@@ -205,31 +271,58 @@ export class Encoder<T extends Schema = any> {
|
|
|
205
271
|
// (to allow re-using StateView's for multiple clients)
|
|
206
272
|
//
|
|
207
273
|
// clear "view" changes after encoding
|
|
208
|
-
view.changes
|
|
274
|
+
view.changes = {};
|
|
275
|
+
|
|
276
|
+
// console.log("FILTERED CHANGES:", this.root.filteredChanges);
|
|
277
|
+
|
|
278
|
+
// try to encode "filtered" changes
|
|
279
|
+
this.encode(it, view, bytes, "filteredChanges", false, viewOffset);
|
|
209
280
|
|
|
210
281
|
return Buffer.concat([
|
|
211
|
-
bytes.
|
|
212
|
-
bytes.
|
|
282
|
+
bytes.subarray(0, sharedOffset),
|
|
283
|
+
bytes.subarray(viewOffset, it.offset)
|
|
213
284
|
]);
|
|
214
285
|
}
|
|
215
286
|
|
|
216
287
|
onEndEncode(changeTrees = this.root.changes) {
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
288
|
+
// changeTrees.forEach(function(changeTree) {
|
|
289
|
+
// changeTree.endEncode();
|
|
290
|
+
// });
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
// for (const refId in changeTrees) {
|
|
294
|
+
// const changeTree = this.root.changeTrees[refId];
|
|
295
|
+
// changeTree.endEncode();
|
|
296
|
+
|
|
297
|
+
// // changeTree.changes.clear();
|
|
298
|
+
|
|
299
|
+
// // // ArraySchema and MapSchema have a custom "encode end" method
|
|
300
|
+
// // changeTree.ref[$onEncodeEnd]?.();
|
|
301
|
+
|
|
302
|
+
// // // Not a new instance anymore
|
|
303
|
+
// // delete changeTree[$isNew];
|
|
304
|
+
// }
|
|
221
305
|
}
|
|
222
306
|
|
|
223
307
|
discardChanges() {
|
|
308
|
+
// console.log("DISCARD CHANGES!");
|
|
309
|
+
|
|
224
310
|
// discard shared changes
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
311
|
+
let length = this.root.changes.length;
|
|
312
|
+
if (length > 0) {
|
|
313
|
+
while (length--) {
|
|
314
|
+
this.root.changes[length]?.endEncode();
|
|
315
|
+
}
|
|
316
|
+
this.root.changes.length = 0;
|
|
228
317
|
}
|
|
318
|
+
|
|
229
319
|
// discard filtered changes
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
320
|
+
length = this.root.filteredChanges.length;
|
|
321
|
+
if (length > 0) {
|
|
322
|
+
while (length--) {
|
|
323
|
+
this.root.filteredChanges[length]?.endEncode();
|
|
324
|
+
}
|
|
325
|
+
this.root.filteredChanges.length = 0;
|
|
233
326
|
}
|
|
234
327
|
}
|
|
235
328
|
|
|
@@ -237,9 +330,21 @@ export class Encoder<T extends Schema = any> {
|
|
|
237
330
|
const baseTypeId = this.context.getTypeId(baseType);
|
|
238
331
|
const targetTypeId = this.context.getTypeId(targetType);
|
|
239
332
|
|
|
333
|
+
if (targetTypeId === undefined) {
|
|
334
|
+
console.warn(`@colyseus/schema WARNING: Class "${targetType.name}" is not registered on TypeRegistry - Please either tag the class with @entity or define a @type() field.`);
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
|
|
240
338
|
if (baseTypeId !== targetTypeId) {
|
|
241
339
|
bytes[it.offset++] = TYPE_ID & 255;
|
|
242
340
|
encode.number(bytes, targetTypeId, it);
|
|
243
341
|
}
|
|
244
342
|
}
|
|
245
|
-
|
|
343
|
+
|
|
344
|
+
get hasChanges() {
|
|
345
|
+
return (
|
|
346
|
+
this.root.changes.length > 0 ||
|
|
347
|
+
this.root.filteredChanges.length > 0
|
|
348
|
+
);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { OPERATION } from "../encoding/spec";
|
|
2
|
+
import { TypeContext } from "../types/TypeContext";
|
|
3
|
+
import { spliceOne } from "../types/utils";
|
|
4
|
+
import { ChangeTree, setOperationAtIndex } from "./ChangeTree";
|
|
5
|
+
|
|
6
|
+
export class Root {
|
|
7
|
+
protected nextUniqueId: number = 0;
|
|
8
|
+
|
|
9
|
+
refCount: {[id: number]: number} = {};
|
|
10
|
+
changeTrees: {[refId: number]: ChangeTree} = {};
|
|
11
|
+
|
|
12
|
+
// all changes
|
|
13
|
+
allChanges: ChangeTree[] = [];
|
|
14
|
+
allFilteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
|
|
15
|
+
|
|
16
|
+
// pending changes to be encoded
|
|
17
|
+
changes: ChangeTree[] = [];
|
|
18
|
+
filteredChanges: ChangeTree[] = [];// TODO: do not initialize it if filters are not used
|
|
19
|
+
|
|
20
|
+
constructor(public types: TypeContext) { }
|
|
21
|
+
|
|
22
|
+
getNextUniqueId() {
|
|
23
|
+
return this.nextUniqueId++;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
add(changeTree: ChangeTree) {
|
|
27
|
+
// FIXME: move implementation of `ensureRefId` to `Root` class
|
|
28
|
+
changeTree.ensureRefId();
|
|
29
|
+
|
|
30
|
+
const isNewChangeTree = (this.changeTrees[changeTree.refId] === undefined);
|
|
31
|
+
if (isNewChangeTree) { this.changeTrees[changeTree.refId] = changeTree; }
|
|
32
|
+
|
|
33
|
+
const previousRefCount = this.refCount[changeTree.refId];
|
|
34
|
+
if (previousRefCount === 0) {
|
|
35
|
+
//
|
|
36
|
+
// When a ChangeTree is re-added, it means that it was previously removed.
|
|
37
|
+
// We need to re-add all changes to the `changes` map.
|
|
38
|
+
//
|
|
39
|
+
const ops = changeTree.allChanges.operations;
|
|
40
|
+
let len = ops.length;
|
|
41
|
+
while (len--) {
|
|
42
|
+
changeTree.indexedOperations[ops[len]] = OPERATION.ADD;
|
|
43
|
+
setOperationAtIndex(changeTree.changes, len);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
this.refCount[changeTree.refId] = (previousRefCount || 0) + 1;
|
|
48
|
+
|
|
49
|
+
return isNewChangeTree;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
remove(changeTree: ChangeTree) {
|
|
53
|
+
const refCount = (this.refCount[changeTree.refId]) - 1;
|
|
54
|
+
|
|
55
|
+
if (refCount <= 0) {
|
|
56
|
+
//
|
|
57
|
+
// Only remove "root" reference if it's the last reference
|
|
58
|
+
//
|
|
59
|
+
changeTree.root = undefined;
|
|
60
|
+
delete this.changeTrees[changeTree.refId];
|
|
61
|
+
|
|
62
|
+
this.removeChangeFromChangeSet("allChanges", changeTree);
|
|
63
|
+
this.removeChangeFromChangeSet("changes", changeTree);
|
|
64
|
+
|
|
65
|
+
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
66
|
+
this.removeChangeFromChangeSet("allFilteredChanges", changeTree);
|
|
67
|
+
this.removeChangeFromChangeSet("filteredChanges", changeTree);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
this.refCount[changeTree.refId] = 0;
|
|
71
|
+
|
|
72
|
+
} else {
|
|
73
|
+
this.refCount[changeTree.refId] = refCount;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
changeTree.forEachChild((child, _) => this.remove(child));
|
|
77
|
+
|
|
78
|
+
return refCount;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
removeChangeFromChangeSet(changeSetName: "allChanges" | "changes" | "filteredChanges" | "allFilteredChanges", changeTree: ChangeTree) {
|
|
82
|
+
const changeSet = this[changeSetName];
|
|
83
|
+
const index = changeSet.indexOf(changeTree);
|
|
84
|
+
if (index !== -1) {
|
|
85
|
+
spliceOne(changeSet, index);
|
|
86
|
+
// changeSet[index] = undefined;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
clear() {
|
|
91
|
+
this.changes.length = 0;
|
|
92
|
+
}
|
|
93
|
+
}
|