@colyseus/schema 3.0.0-alpha.3 → 3.0.0-alpha.30
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 +131 -61
- package/build/cjs/index.js +644 -283
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +643 -282
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +644 -283
- package/lib/Metadata.d.ts +2 -0
- package/lib/Metadata.js +39 -0
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +2 -3
- package/lib/Reflection.js +31 -26
- package/lib/Reflection.js.map +1 -1
- package/lib/Schema.d.ts +2 -2
- package/lib/Schema.js +2 -2
- package/lib/Schema.js.map +1 -1
- package/lib/annotations.d.ts +0 -19
- package/lib/annotations.js +15 -101
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.d.ts +1 -0
- package/lib/bench_encode.js +120 -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 +1 -2
- 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 +2 -3
- package/lib/codegen/parser.js.map +1 -1
- package/lib/codegen/types.js +3 -3
- package/lib/codegen/types.js.map +1 -1
- package/lib/debug.d.ts +1 -0
- package/lib/debug.js +52 -0
- package/lib/debug.js.map +1 -0
- package/lib/decoder/DecodeOperation.d.ts +0 -1
- package/lib/decoder/DecodeOperation.js +23 -7
- package/lib/decoder/DecodeOperation.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +6 -7
- package/lib/decoder/Decoder.js +8 -8
- package/lib/decoder/Decoder.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 +72 -63
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +1 -12
- package/lib/encoder/ChangeTree.js +29 -58
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/EncodeOperation.d.ts +0 -1
- package/lib/encoder/EncodeOperation.js +29 -13
- package/lib/encoder/EncodeOperation.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +10 -8
- package/lib/encoder/Encoder.js +78 -53
- package/lib/encoder/Encoder.js.map +1 -1
- package/lib/encoder/Root.d.ts +17 -0
- package/lib/encoder/Root.js +44 -0
- package/lib/encoder/Root.js.map +1 -0
- package/lib/encoder/StateView.d.ts +2 -2
- package/lib/encoder/StateView.js +48 -58
- package/lib/encoder/StateView.js.map +1 -1
- package/lib/encoding/assert.js +3 -3
- package/lib/encoding/assert.js.map +1 -1
- package/lib/encoding/decode.js +21 -22
- package/lib/encoding/decode.js.map +1 -1
- package/lib/encoding/encode.d.ts +2 -2
- package/lib/encoding/encode.js +40 -39
- package/lib/encoding/encode.js.map +1 -1
- package/lib/encoding/spec.d.ts +2 -1
- package/lib/encoding/spec.js +1 -0
- package/lib/encoding/spec.js.map +1 -1
- package/lib/index.d.ts +5 -1
- package/lib/index.js +8 -4
- package/lib/index.js.map +1 -1
- package/lib/types/TypeContext.d.ts +23 -0
- package/lib/types/TypeContext.js +109 -0
- package/lib/types/TypeContext.js.map +1 -0
- package/lib/types/custom/ArraySchema.d.ts +2 -2
- package/lib/types/custom/ArraySchema.js +0 -9
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/lib/types/registry.js +3 -4
- package/lib/types/registry.js.map +1 -1
- package/lib/types/utils.js +1 -2
- package/lib/types/utils.js.map +1 -1
- package/lib/utils.js +3 -4
- package/lib/utils.js.map +1 -1
- package/package.json +5 -5
- package/src/Metadata.ts +47 -0
- package/src/Reflection.ts +34 -26
- package/src/Schema.ts +2 -2
- package/src/annotations.ts +7 -109
- package/src/bench_encode.ts +97 -0
- package/src/debug.ts +56 -0
- package/src/decoder/DecodeOperation.ts +30 -7
- package/src/decoder/Decoder.ts +13 -11
- package/src/decoder/strategy/StateCallbacks.ts +149 -79
- package/src/encoder/ChangeTree.ts +36 -66
- package/src/encoder/EncodeOperation.ts +29 -12
- package/src/encoder/Encoder.ts +95 -61
- package/src/encoder/Root.ts +51 -0
- package/src/encoder/StateView.ts +51 -67
- package/src/encoding/decode.ts +1 -2
- package/src/encoding/encode.ts +25 -22
- package/src/encoding/spec.ts +1 -0
- package/src/index.ts +8 -11
- package/src/types/TypeContext.ts +127 -0
- package/src/types/custom/ArraySchema.ts +2 -2
package/src/Metadata.ts
CHANGED
|
@@ -117,6 +117,53 @@ export const Metadata = {
|
|
|
117
117
|
return metadata[field].deprecated === true;
|
|
118
118
|
},
|
|
119
119
|
|
|
120
|
+
init(klass: any) {
|
|
121
|
+
//
|
|
122
|
+
// Used only to initialize an empty Schema (Encoder#constructor)
|
|
123
|
+
// TODO: remove/refactor this...
|
|
124
|
+
//
|
|
125
|
+
const metadata = {};
|
|
126
|
+
klass[Symbol.metadata] = metadata;
|
|
127
|
+
Object.defineProperty(metadata, -1, {
|
|
128
|
+
value: 0,
|
|
129
|
+
enumerable: false,
|
|
130
|
+
configurable: true,
|
|
131
|
+
});
|
|
132
|
+
},
|
|
133
|
+
|
|
134
|
+
initialize(constructor: any, parentMetadata?: any) {
|
|
135
|
+
let metadata: Metadata = constructor[Symbol.metadata] ?? Object.create(null);
|
|
136
|
+
|
|
137
|
+
// make sure inherited classes have their own metadata object.
|
|
138
|
+
if (constructor[Symbol.metadata] === parentMetadata) {
|
|
139
|
+
metadata = Object.create(null);
|
|
140
|
+
|
|
141
|
+
if (parentMetadata) {
|
|
142
|
+
// assign parent metadata to current
|
|
143
|
+
Object.assign(metadata, parentMetadata);
|
|
144
|
+
|
|
145
|
+
for (let i = 0; i <= parentMetadata[-1]; i++) {
|
|
146
|
+
Object.defineProperty(metadata, i, {
|
|
147
|
+
value: parentMetadata[i],
|
|
148
|
+
enumerable: false,
|
|
149
|
+
configurable: true,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
Object.defineProperty(metadata, -1, {
|
|
154
|
+
value: parentMetadata[-1],
|
|
155
|
+
enumerable: false,
|
|
156
|
+
configurable: true,
|
|
157
|
+
writable: true,
|
|
158
|
+
});
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
constructor[Symbol.metadata] = metadata;
|
|
163
|
+
|
|
164
|
+
return metadata;
|
|
165
|
+
},
|
|
166
|
+
|
|
120
167
|
isValidInstance(klass: any) {
|
|
121
168
|
return (
|
|
122
169
|
klass.constructor[Symbol.metadata] &&
|
package/src/Reflection.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
|
-
import { type, PrimitiveType, DefinitionType
|
|
1
|
+
import { type, PrimitiveType, DefinitionType } from "./annotations";
|
|
2
|
+
import { TypeContext } from "./types/TypeContext";
|
|
2
3
|
import { Metadata } from "./Metadata";
|
|
3
4
|
import { ArraySchema } from "./types/custom/ArraySchema";
|
|
4
5
|
import { Iterator } from "./encoding/decode";
|
|
@@ -24,10 +25,8 @@ export class ReflectionType extends Schema {
|
|
|
24
25
|
export class Reflection extends Schema {
|
|
25
26
|
@type([ ReflectionType ]) types: ArraySchema<ReflectionType> = new ArraySchema<ReflectionType>();
|
|
26
27
|
|
|
27
|
-
static encode
|
|
28
|
-
|
|
29
|
-
context = new TypeContext(instance.constructor as typeof Schema);
|
|
30
|
-
}
|
|
28
|
+
static encode(instance: Schema, context?: TypeContext, it: Iterator = { offset: 0 }) {
|
|
29
|
+
context ??= new TypeContext(instance.constructor as typeof Schema);
|
|
31
30
|
|
|
32
31
|
const reflection = new Reflection();
|
|
33
32
|
const encoder = new Encoder(reflection);
|
|
@@ -96,7 +95,6 @@ export class Reflection extends Schema {
|
|
|
96
95
|
buildType(type, klass[Symbol.metadata]);
|
|
97
96
|
}
|
|
98
97
|
|
|
99
|
-
const it = { offset: 0 };
|
|
100
98
|
const buf = encoder.encodeAll(it);
|
|
101
99
|
return Buffer.from(buf, 0, it.offset);
|
|
102
100
|
}
|
|
@@ -107,62 +105,72 @@ export class Reflection extends Schema {
|
|
|
107
105
|
const reflectionDecoder = new Decoder(reflection);
|
|
108
106
|
reflectionDecoder.decode(bytes, it);
|
|
109
107
|
|
|
110
|
-
const
|
|
108
|
+
const typeContext = new TypeContext();
|
|
111
109
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const
|
|
110
|
+
// 1st pass, initialize metadata + inheritance
|
|
111
|
+
reflection.types.forEach((reflectionType) => {
|
|
112
|
+
const parentClass: typeof Schema = typeContext.get(reflectionType.extendsId) ?? Schema;
|
|
113
|
+
const schema: typeof Schema = class _ extends parentClass {};
|
|
115
114
|
|
|
116
|
-
|
|
117
|
-
const _metadata = parentKlass && parentKlass[Symbol.metadata] || Object.create(null);
|
|
118
|
-
Object.defineProperty(schema, Symbol.metadata, { enumerable: true, configurable: true, writable: true, value: _metadata })
|
|
115
|
+
const parentMetadata = parentClass[Symbol.metadata];
|
|
119
116
|
|
|
120
117
|
// register for inheritance support
|
|
121
118
|
TypeContext.register(schema);
|
|
122
119
|
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
120
|
+
// for inheritance support
|
|
121
|
+
Metadata.initialize(schema, parentMetadata);
|
|
122
|
+
|
|
123
|
+
typeContext.add(schema, reflectionType.id);
|
|
127
124
|
}, {});
|
|
128
125
|
|
|
126
|
+
// 2nd pass, set fields
|
|
129
127
|
reflection.types.forEach((reflectionType) => {
|
|
130
|
-
const schemaType =
|
|
128
|
+
const schemaType = typeContext.get(reflectionType.id);
|
|
131
129
|
const metadata = schemaType[Symbol.metadata];
|
|
132
130
|
|
|
133
|
-
|
|
134
|
-
const parentFieldIndex =
|
|
131
|
+
// FIXME: use metadata[-1] to get field count
|
|
132
|
+
const parentFieldIndex = 0;
|
|
133
|
+
|
|
134
|
+
// console.log("--------------------");
|
|
135
|
+
// // console.log("reflectionType", reflectionType.toJSON());
|
|
136
|
+
// console.log("reflectionType.fields", reflectionType.fields.toJSON());
|
|
137
|
+
// console.log("parentFieldIndex", parentFieldIndex);
|
|
138
|
+
|
|
139
|
+
//
|
|
140
|
+
// FIXME: set fields using parentKlass as well
|
|
141
|
+
// currently the fields are duplicated on inherited classes
|
|
142
|
+
//
|
|
143
|
+
// // const parentKlass = reflection.types[reflectionType.extendsId];
|
|
144
|
+
// // parentKlass.fields
|
|
135
145
|
|
|
136
146
|
reflectionType.fields.forEach((field, i) => {
|
|
137
147
|
const fieldIndex = parentFieldIndex + i;
|
|
138
148
|
|
|
139
149
|
if (field.referencedType !== undefined) {
|
|
140
150
|
let fieldType = field.type;
|
|
141
|
-
let refType =
|
|
151
|
+
let refType: PrimitiveType = typeContext.get(field.referencedType);
|
|
142
152
|
|
|
143
153
|
// map or array of primitive type (-1)
|
|
144
154
|
if (!refType) {
|
|
145
155
|
const typeInfo = field.type.split(":");
|
|
146
156
|
fieldType = typeInfo[0];
|
|
147
|
-
refType = typeInfo[1];
|
|
157
|
+
refType = typeInfo[1] as PrimitiveType; // string
|
|
148
158
|
}
|
|
149
159
|
|
|
150
160
|
if (fieldType === "ref") {
|
|
151
|
-
// type(refType)(schemaType.prototype, field.name);
|
|
152
161
|
Metadata.addField(metadata, fieldIndex, field.name, refType);
|
|
153
162
|
|
|
154
163
|
} else {
|
|
155
|
-
// type({ [fieldType]: refType } as DefinitionType)(schemaType.prototype, field.name);
|
|
156
164
|
Metadata.addField(metadata, fieldIndex, field.name, { [fieldType]: refType } as DefinitionType);
|
|
157
165
|
}
|
|
158
166
|
|
|
159
167
|
} else {
|
|
160
|
-
// type(field.type as PrimitiveType)(schemaType.prototype, field.name);
|
|
161
168
|
Metadata.addField(metadata, fieldIndex, field.name, field.type as PrimitiveType);
|
|
162
169
|
}
|
|
163
170
|
});
|
|
164
171
|
});
|
|
165
172
|
|
|
166
|
-
|
|
173
|
+
// @ts-ignore
|
|
174
|
+
return new (typeContext.get(0))();
|
|
167
175
|
}
|
|
168
176
|
}
|
package/src/Schema.ts
CHANGED
|
@@ -248,7 +248,7 @@ export abstract class Schema {
|
|
|
248
248
|
return output;
|
|
249
249
|
}
|
|
250
250
|
|
|
251
|
-
static debugChangesDeep(ref: Ref) {
|
|
251
|
+
static debugChangesDeep(ref: Ref, changeSetName: "changes" | "allChanges" | "allFilteredChanges" | "filteredChanges" = "changes") {
|
|
252
252
|
let output = "";
|
|
253
253
|
|
|
254
254
|
const rootChangeTree = ref[$changes];
|
|
@@ -257,7 +257,7 @@ export abstract class Schema {
|
|
|
257
257
|
let totalInstances = 0;
|
|
258
258
|
let totalOperations = 0;
|
|
259
259
|
|
|
260
|
-
for (const [changeTree, changes] of (rootChangeTree.root.
|
|
260
|
+
for (const [changeTree, changes] of (rootChangeTree.root[changeSetName].entries())) {
|
|
261
261
|
let includeChangeTree = false;
|
|
262
262
|
let parentChangeTrees: ChangeTree[] = [];
|
|
263
263
|
let parentChangeTree = changeTree.parent?.[$changes];
|
package/src/annotations.ts
CHANGED
|
@@ -6,6 +6,7 @@ import { Metadata } from "./Metadata";
|
|
|
6
6
|
import { $changes, $childType, $track } from "./types/symbols";
|
|
7
7
|
import { TypeDefinition, getType } from "./types/registry";
|
|
8
8
|
import { OPERATION } from "./encoding/spec";
|
|
9
|
+
import { TypeContext } from "./types/TypeContext";
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
12
|
* Data types
|
|
@@ -42,113 +43,6 @@ export interface TypeOptions {
|
|
|
42
43
|
|
|
43
44
|
export const DEFAULT_VIEW_TAG = -1;
|
|
44
45
|
|
|
45
|
-
export class TypeContext {
|
|
46
|
-
types: {[id: number]: typeof Schema} = {};
|
|
47
|
-
schemas = new Map<typeof Schema, number>();
|
|
48
|
-
|
|
49
|
-
hasFilters: boolean = false;
|
|
50
|
-
|
|
51
|
-
/**
|
|
52
|
-
* For inheritance support
|
|
53
|
-
* Keeps track of which classes extends which. (parent -> children)
|
|
54
|
-
*/
|
|
55
|
-
static inheritedTypes = new Map<typeof Schema, Set<typeof Schema>>();
|
|
56
|
-
|
|
57
|
-
static register(target: typeof Schema) {
|
|
58
|
-
const parent = Object.getPrototypeOf(target);
|
|
59
|
-
if (parent !== Schema) {
|
|
60
|
-
let inherits = TypeContext.inheritedTypes.get(parent);
|
|
61
|
-
if (!inherits) {
|
|
62
|
-
inherits = new Set<typeof Schema>();
|
|
63
|
-
TypeContext.inheritedTypes.set(parent, inherits);
|
|
64
|
-
}
|
|
65
|
-
inherits.add(target);
|
|
66
|
-
}
|
|
67
|
-
}
|
|
68
|
-
|
|
69
|
-
constructor(rootClass?: typeof Schema) {
|
|
70
|
-
if (rootClass) {
|
|
71
|
-
this.discoverTypes(rootClass);
|
|
72
|
-
}
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
has(schema: typeof Schema) {
|
|
76
|
-
return this.schemas.has(schema);
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
get(typeid: number) {
|
|
80
|
-
return this.types[typeid];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
add(schema: typeof Schema, typeid: number = this.schemas.size) {
|
|
84
|
-
// skip if already registered
|
|
85
|
-
if (this.schemas.has(schema)) {
|
|
86
|
-
return false;
|
|
87
|
-
}
|
|
88
|
-
|
|
89
|
-
this.types[typeid] = schema;
|
|
90
|
-
this.schemas.set(schema, typeid);
|
|
91
|
-
return true;
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
getTypeId(klass: typeof Schema) {
|
|
95
|
-
return this.schemas.get(klass);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
private discoverTypes(klass: typeof Schema) {
|
|
99
|
-
if (!this.add(klass)) {
|
|
100
|
-
return;
|
|
101
|
-
}
|
|
102
|
-
|
|
103
|
-
// add classes inherited from this base class
|
|
104
|
-
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
105
|
-
this.discoverTypes(child);
|
|
106
|
-
});
|
|
107
|
-
|
|
108
|
-
// skip if no fields are defined for this class.
|
|
109
|
-
if (klass[Symbol.metadata] === undefined) {
|
|
110
|
-
klass[Symbol.metadata] = {};
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
// const metadata = Metadata.getFor(klass);
|
|
114
|
-
const metadata = klass[Symbol.metadata];
|
|
115
|
-
|
|
116
|
-
// if any schema/field has filters, mark "context" as having filters.
|
|
117
|
-
if (metadata[-2]) {
|
|
118
|
-
this.hasFilters = true;
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
for (const field in metadata) {
|
|
122
|
-
const fieldType = metadata[field].type;
|
|
123
|
-
|
|
124
|
-
if (typeof(fieldType) === "string") {
|
|
125
|
-
continue;
|
|
126
|
-
}
|
|
127
|
-
|
|
128
|
-
if (Array.isArray(fieldType)) {
|
|
129
|
-
const type = fieldType[0];
|
|
130
|
-
if (type === "string") {
|
|
131
|
-
continue;
|
|
132
|
-
}
|
|
133
|
-
this.discoverTypes(type as typeof Schema);
|
|
134
|
-
|
|
135
|
-
} else if (typeof(fieldType) === "function") {
|
|
136
|
-
this.discoverTypes(fieldType);
|
|
137
|
-
|
|
138
|
-
} else {
|
|
139
|
-
const type = Object.values(fieldType)[0];
|
|
140
|
-
|
|
141
|
-
// skip primitive types
|
|
142
|
-
if (typeof(type) === "string") {
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
this.discoverTypes(type as typeof Schema);
|
|
147
|
-
}
|
|
148
|
-
}
|
|
149
|
-
}
|
|
150
|
-
}
|
|
151
|
-
|
|
152
46
|
export function entity(constructor, context: ClassDecoratorContext) {
|
|
153
47
|
if (!constructor._definition) {
|
|
154
48
|
// for inheritance support
|
|
@@ -330,6 +224,8 @@ export function view<T> (tag: number = DEFAULT_VIEW_TAG) {
|
|
|
330
224
|
|
|
331
225
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
332
226
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
227
|
+
|
|
228
|
+
// TODO: use Metadata.initialize()
|
|
333
229
|
const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
334
230
|
|
|
335
231
|
if (!metadata[fieldName]) {
|
|
@@ -356,6 +252,8 @@ export function unreliable<T> (target: T, field: string) {
|
|
|
356
252
|
|
|
357
253
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
358
254
|
const parentMetadata = parentClass[Symbol.metadata];
|
|
255
|
+
|
|
256
|
+
// TODO: use Metadata.initialize()
|
|
359
257
|
const metadata: Metadata = (constructor[Symbol.metadata] ??= Object.assign({}, constructor[Symbol.metadata], parentMetadata ?? Object.create(null)));
|
|
360
258
|
|
|
361
259
|
if (!metadata[field]) {
|
|
@@ -389,8 +287,8 @@ export function type (
|
|
|
389
287
|
TypeContext.register(constructor);
|
|
390
288
|
|
|
391
289
|
const parentClass = Object.getPrototypeOf(constructor);
|
|
392
|
-
const parentMetadata = parentClass[Symbol.metadata];
|
|
393
|
-
const metadata
|
|
290
|
+
const parentMetadata = parentClass && parentClass[Symbol.metadata];
|
|
291
|
+
const metadata = Metadata.initialize(constructor, parentMetadata);
|
|
394
292
|
|
|
395
293
|
let fieldIndex: number;
|
|
396
294
|
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import { nanoid } from "nanoid";
|
|
2
|
+
import { Schema, type, MapSchema, ArraySchema, Encoder } from ".";
|
|
3
|
+
import * as benchmark from "benchmark";
|
|
4
|
+
|
|
5
|
+
const suite = new benchmark.Suite();
|
|
6
|
+
|
|
7
|
+
class Attribute extends Schema {
|
|
8
|
+
@type("string") name: string;
|
|
9
|
+
@type("number") value: number;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
class Item extends Schema {
|
|
13
|
+
@type("number") price: number;
|
|
14
|
+
@type([ Attribute ]) attributes = new ArraySchema<Attribute>();
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
class Position extends Schema {
|
|
18
|
+
@type("number") x: number;
|
|
19
|
+
@type("number") y: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
class Player extends Schema {
|
|
23
|
+
@type(Position) position = new Position();
|
|
24
|
+
@type({ map: Item }) items = new MapSchema<Item>();
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
class State extends Schema {
|
|
28
|
+
@type({ map: Player }) players = new MapSchema<Player>();
|
|
29
|
+
@type("string") currentTurn;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const state = new State();
|
|
33
|
+
|
|
34
|
+
for (let i = 0; i < 50; i++) {
|
|
35
|
+
const player = new Player();
|
|
36
|
+
state.players.set(`p-${nanoid()}`, player);
|
|
37
|
+
|
|
38
|
+
player.position.x = (i + 1) * 100;
|
|
39
|
+
player.position.y = (i + 1) * 100;
|
|
40
|
+
for (let j = 0; j < 10; j++) {
|
|
41
|
+
const item = new Item();
|
|
42
|
+
item.price = (i + 1) * 50;
|
|
43
|
+
for (let k = 0; k < 5; k++) {
|
|
44
|
+
const attr = new Attribute();
|
|
45
|
+
attr.name = `Attribute ${k}`;
|
|
46
|
+
attr.value = k;
|
|
47
|
+
item.attributes.push(attr);
|
|
48
|
+
|
|
49
|
+
}
|
|
50
|
+
player.items.set(`item-${j}`, item);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
Encoder.BUFFER_SIZE = 4096 * 4096;
|
|
56
|
+
const encoder = new Encoder(state);
|
|
57
|
+
|
|
58
|
+
// measure time to .encodeAll()
|
|
59
|
+
|
|
60
|
+
let now = Date.now();
|
|
61
|
+
for (let i = 0; i < 1000; i++) {
|
|
62
|
+
encoder.encodeAll();
|
|
63
|
+
}
|
|
64
|
+
console.log(Date.now() - now);
|
|
65
|
+
|
|
66
|
+
const allEncodes = Date.now();
|
|
67
|
+
for (let i = 0; i < 100; i++) {
|
|
68
|
+
now = Date.now();
|
|
69
|
+
for (let j = 0; j < 50; j++) {
|
|
70
|
+
const player = new Player();
|
|
71
|
+
state.players.set(`p-${nanoid()}`, player);
|
|
72
|
+
|
|
73
|
+
player.position.x = (j + 1) * 100;
|
|
74
|
+
player.position.y = (j + 1) * 100;
|
|
75
|
+
for (let k = 0; k < 10; k++) {
|
|
76
|
+
const item = new Item();
|
|
77
|
+
item.price = (j + 1) * 50;
|
|
78
|
+
for (let l = 0; l < 5; l++) {
|
|
79
|
+
const attr = new Attribute();
|
|
80
|
+
attr.name = `Attribute ${l}`;
|
|
81
|
+
attr.value = l;
|
|
82
|
+
item.attributes.push(attr);
|
|
83
|
+
|
|
84
|
+
}
|
|
85
|
+
player.items.set(`item-${k}`, item);
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
console.log("time to make changes:", Date.now() - now);
|
|
89
|
+
|
|
90
|
+
now = Date.now();
|
|
91
|
+
encoder.encode();
|
|
92
|
+
encoder.discardChanges();
|
|
93
|
+
console.log("time to encode:", Date.now() - now);
|
|
94
|
+
}
|
|
95
|
+
console.log("time for all encodes:", Date.now() - allEncodes);
|
|
96
|
+
|
|
97
|
+
console.log(Array.from(encoder.encodeAll()).length, "bytes");
|
package/src/debug.ts
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import * as fs from "fs";
|
|
2
|
+
import { Reflection, Decoder } from "./index";
|
|
3
|
+
|
|
4
|
+
const contents = fs.readFileSync("/Users/endel/Projects/colyseus/clients/bubbits/project/@bubbits/backend/schema-debug.txt", { encoding: "utf8" }).toString();
|
|
5
|
+
|
|
6
|
+
let isCommentBlock = false;
|
|
7
|
+
let lastComment = "";
|
|
8
|
+
|
|
9
|
+
let decoder: Decoder;
|
|
10
|
+
|
|
11
|
+
function getBuffer(line: string) {
|
|
12
|
+
const start = line.lastIndexOf(":");
|
|
13
|
+
const buffer = Buffer.from(new Uint8Array(line.substring(start + 1).split(",").map(n => Number(n))));
|
|
14
|
+
console.log(`(${buffer.byteLength}) ${Array.from(buffer).join(",")}`)
|
|
15
|
+
// console.log("");
|
|
16
|
+
// console.log("");
|
|
17
|
+
// console.log("> ", line);
|
|
18
|
+
// console.log("> substring:", line.substring(start + 1))
|
|
19
|
+
return buffer;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function decode(buffer: Buffer) {
|
|
23
|
+
try {
|
|
24
|
+
decoder.decode(buffer);
|
|
25
|
+
} catch (e) {
|
|
26
|
+
console.error(e);
|
|
27
|
+
console.log("Last log:\n\n")
|
|
28
|
+
console.log(lastComment);
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
contents.split("\n").forEach((line) => {
|
|
33
|
+
if (line.startsWith("#")) {
|
|
34
|
+
// reset last comment.
|
|
35
|
+
if (isCommentBlock === false) { lastComment = ""; }
|
|
36
|
+
|
|
37
|
+
isCommentBlock = true;
|
|
38
|
+
lastComment += line.substring(line.indexOf(":") + 1) + "\n";
|
|
39
|
+
return;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
isCommentBlock = false;
|
|
43
|
+
|
|
44
|
+
if (line.startsWith("handshake:") && !decoder) {
|
|
45
|
+
const state = Reflection.decode(getBuffer(line));
|
|
46
|
+
decoder = new Decoder(state);
|
|
47
|
+
|
|
48
|
+
} else if (line.startsWith("state:")) {
|
|
49
|
+
decode(getBuffer(line));
|
|
50
|
+
|
|
51
|
+
} else if (line.startsWith("patch:")) {
|
|
52
|
+
decode(getBuffer(line));
|
|
53
|
+
}
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
console.log(decoder.state.toJSON());
|
|
@@ -4,7 +4,7 @@ import { Schema } from "../Schema";
|
|
|
4
4
|
import type { Ref } from "../encoder/ChangeTree";
|
|
5
5
|
import type { Decoder } from "./Decoder";
|
|
6
6
|
import * as decode from "../encoding/decode";
|
|
7
|
-
import { $
|
|
7
|
+
import { $childType, $deleteByIndex, $getByIndex } from "../types/symbols";
|
|
8
8
|
|
|
9
9
|
import type { MapSchema } from "../types/custom/MapSchema";
|
|
10
10
|
import type { ArraySchema } from "../types/custom/ArraySchema";
|
|
@@ -43,7 +43,7 @@ export function decodeValue(
|
|
|
43
43
|
it: decode.Iterator,
|
|
44
44
|
allChanges: DataChange[],
|
|
45
45
|
) {
|
|
46
|
-
const $root = decoder
|
|
46
|
+
const $root = decoder.root;
|
|
47
47
|
const previousValue = ref[$getByIndex](index);
|
|
48
48
|
|
|
49
49
|
let value: any;
|
|
@@ -182,7 +182,10 @@ export const decodeSchemaOperation: DecodeOperation = function (
|
|
|
182
182
|
|
|
183
183
|
// skip early if field is not defined
|
|
184
184
|
const field = metadata[index];
|
|
185
|
-
if (field === undefined) {
|
|
185
|
+
if (field === undefined) {
|
|
186
|
+
console.warn("@colyseus/schema: field not defined at", { index, ref: ref.constructor.name, metadata });
|
|
187
|
+
return DEFINITION_MISMATCH;
|
|
188
|
+
}
|
|
186
189
|
|
|
187
190
|
const { value, previousValue } = decodeValue(
|
|
188
191
|
decoder,
|
|
@@ -251,6 +254,7 @@ export const decodeKeyValueOperation: DecodeOperation = function (
|
|
|
251
254
|
dynamicIndex = ref['getIndex'](index);
|
|
252
255
|
}
|
|
253
256
|
|
|
257
|
+
|
|
254
258
|
const { value, previousValue } = decodeValue(
|
|
255
259
|
decoder,
|
|
256
260
|
operation,
|
|
@@ -303,7 +307,10 @@ export const decodeArray: DecodeOperation = function (
|
|
|
303
307
|
allChanges: DataChange[]
|
|
304
308
|
) {
|
|
305
309
|
// "uncompressed" index + operation (array/map items)
|
|
306
|
-
|
|
310
|
+
let operation = bytes[it.offset++];
|
|
311
|
+
|
|
312
|
+
let isSchemaChild: boolean;
|
|
313
|
+
let index: number;
|
|
307
314
|
|
|
308
315
|
if (operation === OPERATION.CLEAR) {
|
|
309
316
|
//
|
|
@@ -318,8 +325,8 @@ export const decodeArray: DecodeOperation = function (
|
|
|
318
325
|
} else if (operation === OPERATION.DELETE_BY_REFID) {
|
|
319
326
|
// TODO: refactor here, try to follow same flow as below
|
|
320
327
|
const refId = decode.number(bytes, it);
|
|
321
|
-
const previousValue = decoder
|
|
322
|
-
|
|
328
|
+
const previousValue = decoder.root.refs.get(refId);
|
|
329
|
+
index = ref.findIndex((value) => value === previousValue);
|
|
323
330
|
ref[$deleteByIndex](index);
|
|
324
331
|
allChanges.push({
|
|
325
332
|
ref,
|
|
@@ -330,10 +337,26 @@ export const decodeArray: DecodeOperation = function (
|
|
|
330
337
|
value: undefined,
|
|
331
338
|
previousValue,
|
|
332
339
|
});
|
|
340
|
+
|
|
333
341
|
return;
|
|
342
|
+
|
|
343
|
+
} else if (operation === OPERATION.ADD_BY_REFID) {
|
|
344
|
+
isSchemaChild = true;
|
|
345
|
+
// operation = OPERATION.ADD;
|
|
346
|
+
|
|
347
|
+
const refId = decode.number(bytes, it);
|
|
348
|
+
const itemByRefId = decoder.root.refs.get(refId);
|
|
349
|
+
|
|
350
|
+
// use existing index, or push new value
|
|
351
|
+
index = (itemByRefId)
|
|
352
|
+
? ref.findIndex((value) => value === itemByRefId)
|
|
353
|
+
: ref.length;
|
|
354
|
+
|
|
355
|
+
} else {
|
|
356
|
+
isSchemaChild = false;
|
|
357
|
+
index = decode.number(bytes, it);
|
|
334
358
|
}
|
|
335
359
|
|
|
336
|
-
const index = decode.number(bytes, it);
|
|
337
360
|
const type = ref[$childType];
|
|
338
361
|
|
|
339
362
|
let dynamicIndex: number | string = index;
|
package/src/decoder/Decoder.ts
CHANGED
|
@@ -1,27 +1,28 @@
|
|
|
1
|
-
import { TypeContext } from "../
|
|
1
|
+
import { TypeContext } from "../types/TypeContext";
|
|
2
2
|
import { $changes, $childType, $decoder, $onDecodeEnd } from "../types/symbols";
|
|
3
3
|
import { Schema } from "../Schema";
|
|
4
4
|
|
|
5
5
|
import * as decode from "../encoding/decode";
|
|
6
6
|
import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
|
|
7
|
-
import { Ref } from "../encoder/ChangeTree";
|
|
8
|
-
import { Iterator } from "../encoding/decode";
|
|
7
|
+
import type { Ref } from "../encoder/ChangeTree";
|
|
8
|
+
import type { Iterator } from "../encoding/decode";
|
|
9
9
|
import { ReferenceTracker } from "./ReferenceTracker";
|
|
10
|
-
import { DEFINITION_MISMATCH, DataChange, DecodeOperation } from "./DecodeOperation";
|
|
10
|
+
import { DEFINITION_MISMATCH, type DataChange, type DecodeOperation } from "./DecodeOperation";
|
|
11
11
|
import { Collection } from "../types/HelperTypes";
|
|
12
12
|
|
|
13
13
|
export class Decoder<T extends Schema = any> {
|
|
14
14
|
context: TypeContext;
|
|
15
15
|
|
|
16
16
|
state: T;
|
|
17
|
-
|
|
17
|
+
root: ReferenceTracker;
|
|
18
18
|
|
|
19
19
|
currentRefId: number = 0;
|
|
20
20
|
|
|
21
21
|
triggerChanges?: (allChanges: DataChange[]) => void;
|
|
22
22
|
|
|
23
23
|
constructor(root: T, context?: TypeContext) {
|
|
24
|
-
this.
|
|
24
|
+
this.setState(root);
|
|
25
|
+
|
|
25
26
|
this.context = context || new TypeContext(root.constructor as typeof Schema);
|
|
26
27
|
|
|
27
28
|
// console.log(">>>>>>>>>>>>>>>> Decoder types");
|
|
@@ -30,10 +31,10 @@ export class Decoder<T extends Schema = any> {
|
|
|
30
31
|
// });
|
|
31
32
|
}
|
|
32
33
|
|
|
33
|
-
protected
|
|
34
|
+
protected setState(root: T) {
|
|
34
35
|
this.state = root;
|
|
35
|
-
this
|
|
36
|
-
this
|
|
36
|
+
this.root = new ReferenceTracker();
|
|
37
|
+
this.root.addRef(0, root);
|
|
37
38
|
}
|
|
38
39
|
|
|
39
40
|
decode(
|
|
@@ -43,7 +44,7 @@ export class Decoder<T extends Schema = any> {
|
|
|
43
44
|
) {
|
|
44
45
|
const allChanges: DataChange[] = [];
|
|
45
46
|
|
|
46
|
-
const $root = this
|
|
47
|
+
const $root = this.root;
|
|
47
48
|
const totalBytes = bytes.byteLength;
|
|
48
49
|
|
|
49
50
|
let decoder: DecodeOperation = ref['constructor'][$decoder];
|
|
@@ -66,6 +67,7 @@ export class Decoder<T extends Schema = any> {
|
|
|
66
67
|
if (!nextRef) { throw new Error(`"refId" not found: ${this.currentRefId}`); }
|
|
67
68
|
ref[$onDecodeEnd]?.()
|
|
68
69
|
ref = nextRef;
|
|
70
|
+
|
|
69
71
|
decoder = ref['constructor'][$decoder];
|
|
70
72
|
|
|
71
73
|
continue;
|
|
@@ -146,7 +148,7 @@ export class Decoder<T extends Schema = any> {
|
|
|
146
148
|
});
|
|
147
149
|
|
|
148
150
|
if (needRemoveRef) {
|
|
149
|
-
this
|
|
151
|
+
this.root.removeRef(this.root.refIds.get(value));
|
|
150
152
|
}
|
|
151
153
|
});
|
|
152
154
|
}
|