@colyseus/schema 3.0.0-alpha.27 → 3.0.0-alpha.29
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 +114 -88
- package/build/cjs/index.js.map +1 -1
- package/build/esm/index.mjs +114 -88
- package/build/esm/index.mjs.map +1 -1
- package/build/umd/index.js +114 -88
- package/lib/Metadata.d.ts +3 -0
- package/lib/Metadata.js.map +1 -1
- package/lib/Reflection.d.ts +1 -1
- package/lib/Reflection.js +4 -3
- package/lib/Reflection.js.map +1 -1
- package/lib/annotations.d.ts +0 -19
- package/lib/annotations.js +4 -106
- package/lib/annotations.js.map +1 -1
- package/lib/bench_encode.js +54 -27
- package/lib/bench_encode.js.map +1 -1
- package/lib/decoder/Decoder.d.ts +1 -1
- package/lib/decoder/Decoder.js +2 -2
- package/lib/decoder/Decoder.js.map +1 -1
- package/lib/decoder/strategy/StateCallbacks.js +5 -4
- package/lib/decoder/strategy/StateCallbacks.js.map +1 -1
- package/lib/encoder/ChangeTree.d.ts +1 -12
- package/lib/encoder/ChangeTree.js +24 -51
- package/lib/encoder/ChangeTree.js.map +1 -1
- package/lib/encoder/Encoder.d.ts +6 -5
- package/lib/encoder/Encoder.js +28 -19
- 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/index.d.ts +2 -1
- package/lib/index.js +3 -3
- 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.js +0 -1
- package/lib/types/custom/ArraySchema.js.map +1 -1
- package/package.json +1 -1
- package/src/Metadata.ts +1 -0
- package/src/Reflection.ts +2 -1
- package/src/annotations.ts +1 -126
- package/src/bench_encode.ts +47 -16
- package/src/decoder/Decoder.ts +1 -1
- package/src/decoder/strategy/StateCallbacks.ts +5 -4
- package/src/encoder/ChangeTree.ts +30 -59
- package/src/encoder/Encoder.ts +36 -23
- package/src/encoder/Root.ts +51 -0
- package/src/index.ts +3 -11
- package/src/types/TypeContext.ts +127 -0
- package/src/types/custom/ArraySchema.ts +0 -1
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,132 +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
|
-
|
|
91
|
-
//
|
|
92
|
-
// Workaround to allow using an empty Schema (with no `@type()` fields)
|
|
93
|
-
//
|
|
94
|
-
if (schema[Symbol.metadata] === undefined) {
|
|
95
|
-
Metadata.init(schema);
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
this.schemas.set(schema, typeid);
|
|
99
|
-
return true;
|
|
100
|
-
}
|
|
101
|
-
|
|
102
|
-
getTypeId(klass: typeof Schema) {
|
|
103
|
-
return this.schemas.get(klass);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
private discoverTypes(klass: typeof Schema, parentFieldViewTag?: number) {
|
|
107
|
-
if (!this.add(klass)) {
|
|
108
|
-
return;
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
// add classes inherited from this base class
|
|
112
|
-
TypeContext.inheritedTypes.get(klass)?.forEach((child) => {
|
|
113
|
-
this.discoverTypes(child, parentFieldViewTag);
|
|
114
|
-
});
|
|
115
|
-
|
|
116
|
-
// skip if no fields are defined for this class.
|
|
117
|
-
if (klass[Symbol.metadata] === undefined) {
|
|
118
|
-
klass[Symbol.metadata] = {};
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
// const metadata = Metadata.getFor(klass);
|
|
122
|
-
const metadata = klass[Symbol.metadata];
|
|
123
|
-
|
|
124
|
-
// if any schema/field has filters, mark "context" as having filters.
|
|
125
|
-
if (metadata[-2]) {
|
|
126
|
-
this.hasFilters = true;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
for (const field in metadata) {
|
|
130
|
-
//
|
|
131
|
-
// Modify the field's metadata to include the parent field's view tag
|
|
132
|
-
//
|
|
133
|
-
if (
|
|
134
|
-
parentFieldViewTag !== undefined &&
|
|
135
|
-
metadata[field].tag === undefined
|
|
136
|
-
) {
|
|
137
|
-
metadata[field].tag = parentFieldViewTag;
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
const fieldType = metadata[field].type;
|
|
141
|
-
const viewTag = metadata[field].tag;
|
|
142
|
-
|
|
143
|
-
if (typeof(fieldType) === "string") {
|
|
144
|
-
continue;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
if (Array.isArray(fieldType)) {
|
|
148
|
-
const type = fieldType[0];
|
|
149
|
-
if (type === "string") {
|
|
150
|
-
continue;
|
|
151
|
-
}
|
|
152
|
-
this.discoverTypes(type as typeof Schema, viewTag);
|
|
153
|
-
|
|
154
|
-
} else if (typeof(fieldType) === "function") {
|
|
155
|
-
this.discoverTypes(fieldType, viewTag);
|
|
156
|
-
|
|
157
|
-
} else {
|
|
158
|
-
const type = Object.values(fieldType)[0];
|
|
159
|
-
|
|
160
|
-
// skip primitive types
|
|
161
|
-
if (typeof(type) === "string") {
|
|
162
|
-
continue;
|
|
163
|
-
}
|
|
164
|
-
|
|
165
|
-
this.discoverTypes(type as typeof Schema, viewTag);
|
|
166
|
-
}
|
|
167
|
-
}
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
|
|
171
46
|
export function entity(constructor, context: ClassDecoratorContext) {
|
|
172
47
|
if (!constructor._definition) {
|
|
173
48
|
// for inheritance support
|
package/src/bench_encode.ts
CHANGED
|
@@ -4,44 +4,44 @@ import * as benchmark from "benchmark";
|
|
|
4
4
|
|
|
5
5
|
const suite = new benchmark.Suite();
|
|
6
6
|
|
|
7
|
-
class
|
|
7
|
+
class Attribute extends Schema {
|
|
8
8
|
@type("string") name: string;
|
|
9
9
|
@type("number") value: number;
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
-
class
|
|
12
|
+
class Item extends Schema {
|
|
13
13
|
@type("number") price: number;
|
|
14
|
-
@type([
|
|
14
|
+
@type([ Attribute ]) attributes = new ArraySchema<Attribute>();
|
|
15
15
|
}
|
|
16
16
|
|
|
17
|
-
class
|
|
17
|
+
class Position extends Schema {
|
|
18
18
|
@type("number") x: number;
|
|
19
19
|
@type("number") y: number;
|
|
20
20
|
}
|
|
21
21
|
|
|
22
|
-
class
|
|
23
|
-
@type(
|
|
24
|
-
@type({ map:
|
|
22
|
+
class Player extends Schema {
|
|
23
|
+
@type(Position) position = new Position();
|
|
24
|
+
@type({ map: Item }) items = new MapSchema<Item>();
|
|
25
25
|
}
|
|
26
26
|
|
|
27
|
-
class
|
|
28
|
-
@type({ map:
|
|
27
|
+
class State extends Schema {
|
|
28
|
+
@type({ map: Player }) players = new MapSchema<Player>();
|
|
29
29
|
@type("string") currentTurn;
|
|
30
30
|
}
|
|
31
31
|
|
|
32
|
-
const state = new
|
|
32
|
+
const state = new State();
|
|
33
33
|
|
|
34
34
|
for (let i = 0; i < 50; i++) {
|
|
35
|
-
const player = new
|
|
35
|
+
const player = new Player();
|
|
36
36
|
state.players.set(`p-${nanoid()}`, player);
|
|
37
37
|
|
|
38
38
|
player.position.x = (i + 1) * 100;
|
|
39
39
|
player.position.y = (i + 1) * 100;
|
|
40
40
|
for (let j = 0; j < 10; j++) {
|
|
41
|
-
const item = new
|
|
41
|
+
const item = new Item();
|
|
42
42
|
item.price = (i + 1) * 50;
|
|
43
43
|
for (let k = 0; k < 5; k++) {
|
|
44
|
-
const attr = new
|
|
44
|
+
const attr = new Attribute();
|
|
45
45
|
attr.name = `Attribute ${k}`;
|
|
46
46
|
attr.value = k;
|
|
47
47
|
item.attributes.push(attr);
|
|
@@ -52,15 +52,46 @@ for (let i = 0; i < 50; i++) {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
|
|
55
|
-
Encoder.BUFFER_SIZE =
|
|
55
|
+
Encoder.BUFFER_SIZE = 4096 * 4096;
|
|
56
56
|
const encoder = new Encoder(state);
|
|
57
57
|
|
|
58
58
|
// measure time to .encodeAll()
|
|
59
59
|
|
|
60
|
-
|
|
60
|
+
let now = Date.now();
|
|
61
61
|
for (let i = 0; i < 1000; i++) {
|
|
62
62
|
encoder.encodeAll();
|
|
63
63
|
}
|
|
64
64
|
console.log(Date.now() - now);
|
|
65
65
|
|
|
66
|
-
|
|
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/decoder/Decoder.ts
CHANGED
|
@@ -214,7 +214,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
214
214
|
|
|
215
215
|
if (metadata && !isCollection) {
|
|
216
216
|
|
|
217
|
-
const
|
|
217
|
+
const onAddListen = function (
|
|
218
218
|
ref: Ref,
|
|
219
219
|
prop: string,
|
|
220
220
|
callback: (value: any, previousValue: any) => void, immediate: boolean
|
|
@@ -223,7 +223,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
223
223
|
if (
|
|
224
224
|
immediate &&
|
|
225
225
|
context.instance[prop] !== undefined &&
|
|
226
|
-
!onAddCalls.has(
|
|
226
|
+
!onAddCalls.has(currentOnAddCallback) // Workaround for https://github.com/colyseus/schema/issues/147
|
|
227
227
|
) {
|
|
228
228
|
callback(context.instance[prop], undefined);
|
|
229
229
|
}
|
|
@@ -236,14 +236,14 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
236
236
|
return new Proxy({
|
|
237
237
|
listen: function listen(prop: string, callback: (value: any, previousValue: any) => void, immediate: boolean = true) {
|
|
238
238
|
if (context.instance) {
|
|
239
|
-
return
|
|
239
|
+
return onAddListen(context.instance, prop, callback, immediate);
|
|
240
240
|
|
|
241
241
|
} else {
|
|
242
242
|
// collection instance not received yet
|
|
243
243
|
let detachCallback = () => {};
|
|
244
244
|
|
|
245
245
|
context.onInstanceAvailable((ref: Ref, existing: boolean) => {
|
|
246
|
-
detachCallback =
|
|
246
|
+
detachCallback = onAddListen(ref, prop, callback, immediate && existing && !onAddCalls.has(currentOnAddCallback))
|
|
247
247
|
});
|
|
248
248
|
|
|
249
249
|
return () => detachCallback();
|
|
@@ -330,6 +330,7 @@ export function getDecoderStateCallbacks<T extends Schema>(decoder: Decoder<T>):
|
|
|
330
330
|
currentOnAddCallback = callback;
|
|
331
331
|
callback(value, key);
|
|
332
332
|
onAddCalls.delete(callback)
|
|
333
|
+
currentOnAddCallback = undefined;
|
|
333
334
|
});
|
|
334
335
|
};
|
|
335
336
|
|
|
@@ -7,6 +7,7 @@ import type { ArraySchema } from "../types/custom/ArraySchema";
|
|
|
7
7
|
import type { CollectionSchema } from "../types/custom/CollectionSchema";
|
|
8
8
|
import type { SetSchema } from "../types/custom/SetSchema";
|
|
9
9
|
|
|
10
|
+
import { Root } from "./Root";
|
|
10
11
|
import { Metadata } from "../Metadata";
|
|
11
12
|
import type { EncodeOperation } from "./EncodeOperation";
|
|
12
13
|
import type { DecodeOperation } from "../decoder/DecodeOperation";
|
|
@@ -26,53 +27,6 @@ export type Ref = Schema
|
|
|
26
27
|
| CollectionSchema
|
|
27
28
|
| SetSchema;
|
|
28
29
|
|
|
29
|
-
export class Root {
|
|
30
|
-
protected nextUniqueId: number = 0;
|
|
31
|
-
refCount = new WeakMap<ChangeTree, number>();
|
|
32
|
-
|
|
33
|
-
// all changes
|
|
34
|
-
allChanges = new Map<ChangeTree, Map<number, OPERATION>>();
|
|
35
|
-
allFilteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
|
|
36
|
-
|
|
37
|
-
// pending changes to be encoded
|
|
38
|
-
changes = new Map<ChangeTree, Map<number, OPERATION>>();
|
|
39
|
-
filteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
|
|
40
|
-
|
|
41
|
-
getNextUniqueId() {
|
|
42
|
-
return this.nextUniqueId++;
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
add (changeTree: ChangeTree) {
|
|
46
|
-
const refCount = this.refCount.get(changeTree) || 0;
|
|
47
|
-
this.refCount.set(changeTree, refCount + 1);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
remove(changeTree: ChangeTree) {
|
|
51
|
-
const refCount = this.refCount.get(changeTree);
|
|
52
|
-
if (refCount <= 1) {
|
|
53
|
-
this.allChanges.delete(changeTree);
|
|
54
|
-
this.changes.delete(changeTree);
|
|
55
|
-
|
|
56
|
-
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
57
|
-
this.allFilteredChanges.delete(changeTree);
|
|
58
|
-
this.filteredChanges.delete(changeTree);
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
this.refCount.delete(changeTree);
|
|
62
|
-
|
|
63
|
-
} else {
|
|
64
|
-
this.refCount.set(changeTree, refCount - 1);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
changeTree.forEachChild((child, _) =>
|
|
68
|
-
this.remove(child));
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
clear() {
|
|
72
|
-
this.changes.clear();
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
|
|
76
30
|
export class ChangeTree<T extends Ref=any> {
|
|
77
31
|
ref: T;
|
|
78
32
|
refId: number;
|
|
@@ -122,9 +76,6 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
122
76
|
if (this.isFiltered || this.isPartiallyFiltered) {
|
|
123
77
|
this.root.allFilteredChanges.set(this, this.allFilteredChanges);
|
|
124
78
|
this.root.filteredChanges.set(this, this.filteredChanges);
|
|
125
|
-
|
|
126
|
-
// } else {
|
|
127
|
-
// this.root.allChanges.set(this, this.allChanges);
|
|
128
79
|
}
|
|
129
80
|
|
|
130
81
|
if (!this.isFiltered) {
|
|
@@ -453,19 +404,39 @@ export class ChangeTree<T extends Ref=any> {
|
|
|
453
404
|
// Detect if current structure has "filters" declared
|
|
454
405
|
this.isPartiallyFiltered = (this.ref['constructor']?.[Symbol.metadata]?.[-2] !== undefined);
|
|
455
406
|
|
|
456
|
-
|
|
407
|
+
if (parent && !Metadata.isValidInstance(parent)) {
|
|
408
|
+
const parentChangeTree = parent[$changes];
|
|
409
|
+
parent = parentChangeTree.parent;
|
|
410
|
+
parentIndex = parentChangeTree.parentIndex;
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
const parentMetadata = parent?.['constructor']?.[Symbol.metadata];
|
|
457
414
|
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
415
|
+
this.isFiltered = (
|
|
416
|
+
parent &&
|
|
417
|
+
parentMetadata?.[-2]?.includes(parentIndex)
|
|
418
|
+
);
|
|
461
419
|
|
|
462
|
-
|
|
463
|
-
const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
|
|
420
|
+
// this.isFiltered = this.ref['constructor']?.[Symbol.metadata]?.[-4];
|
|
464
421
|
|
|
465
|
-
|
|
422
|
+
// // Detect if parent has "filters" declared
|
|
423
|
+
// while (parent && !this.isFiltered) {
|
|
424
|
+
// const metadata: Metadata = parent['constructor'][Symbol.metadata];
|
|
425
|
+
// // this.isFiltered = metadata?.[-4];
|
|
466
426
|
|
|
467
|
-
|
|
468
|
-
|
|
427
|
+
// const fieldName = metadata?.[parentIndex];
|
|
428
|
+
// const isParentOwned = metadata?.[fieldName]?.tag !== undefined;
|
|
429
|
+
// this.isFiltered = isParentOwned || parent[$changes].isFiltered; // metadata?.[-2]
|
|
430
|
+
|
|
431
|
+
// parent = parent[$changes].parent;
|
|
432
|
+
// };
|
|
433
|
+
|
|
434
|
+
// console.log("ChangeTree.checkIsFiltered", {
|
|
435
|
+
// parent: parent?.constructor.name,
|
|
436
|
+
// ref: this.ref.constructor.name,
|
|
437
|
+
// isFiltered: this.isFiltered,
|
|
438
|
+
// isPartiallyFiltered: this.isPartiallyFiltered,
|
|
439
|
+
// });
|
|
469
440
|
|
|
470
441
|
//
|
|
471
442
|
// TODO: refactor this!
|
package/src/encoder/Encoder.ts
CHANGED
|
@@ -1,15 +1,17 @@
|
|
|
1
1
|
import type { Schema } from "../Schema";
|
|
2
|
-
import { TypeContext } from "../
|
|
2
|
+
import { TypeContext } from "../types/TypeContext";
|
|
3
3
|
import { $changes, $encoder, $filter } from "../types/symbols";
|
|
4
4
|
|
|
5
5
|
import * as encode from "../encoding/encode";
|
|
6
6
|
import type { Iterator } from "../encoding/decode";
|
|
7
7
|
|
|
8
8
|
import { OPERATION, SWITCH_TO_STRUCTURE, TYPE_ID } from '../encoding/spec';
|
|
9
|
-
import { Root } from "./
|
|
9
|
+
import { Root } from "./Root";
|
|
10
10
|
import { getNextPowerOf2 } from "../utils";
|
|
11
|
+
|
|
11
12
|
import type { StateView } from "./StateView";
|
|
12
|
-
import { Metadata } from "../Metadata";
|
|
13
|
+
import type { Metadata } from "../Metadata";
|
|
14
|
+
import type { ChangeTree } from "./ChangeTree";
|
|
13
15
|
|
|
14
16
|
export class Encoder<T extends Schema = any> {
|
|
15
17
|
static BUFFER_SIZE = 8 * 1024;// 8KB
|
|
@@ -21,13 +23,13 @@ export class Encoder<T extends Schema = any> {
|
|
|
21
23
|
root: Root;
|
|
22
24
|
|
|
23
25
|
constructor(state: T) {
|
|
24
|
-
this.root = new Root();
|
|
25
26
|
|
|
26
27
|
//
|
|
27
28
|
// TODO: cache and restore "Context" based on root schema
|
|
28
29
|
// (to avoid creating a new context for every new room)
|
|
29
30
|
//
|
|
30
31
|
this.context = new TypeContext(state.constructor as typeof Schema);
|
|
32
|
+
this.root = new Root(this.context);
|
|
31
33
|
|
|
32
34
|
this.setState(state);
|
|
33
35
|
|
|
@@ -80,7 +82,8 @@ export class Encoder<T extends Schema = any> {
|
|
|
80
82
|
}
|
|
81
83
|
|
|
82
84
|
// skip root `refId` if it's the first change tree
|
|
83
|
-
|
|
85
|
+
// (unless it "hasView", which will need to revisit the root)
|
|
86
|
+
if (hasView || changeTree !== rootChangeTree) {
|
|
84
87
|
buffer[it.offset++] = SWITCH_TO_STRUCTURE & 255;
|
|
85
88
|
encode.number(buffer, changeTree.refId, it);
|
|
86
89
|
}
|
|
@@ -124,7 +127,6 @@ export class Encoder<T extends Schema = any> {
|
|
|
124
127
|
Encoder.BUFFER_SIZE = ${Math.round(newSize / 1024)} * 1024; // ${Math.round(newSize / 1024)} KB
|
|
125
128
|
`);
|
|
126
129
|
|
|
127
|
-
|
|
128
130
|
//
|
|
129
131
|
// resize buffer and re-encode (TODO: can we avoid re-encoding here?)
|
|
130
132
|
//
|
|
@@ -153,11 +155,8 @@ export class Encoder<T extends Schema = any> {
|
|
|
153
155
|
}
|
|
154
156
|
|
|
155
157
|
encodeAll(it: Iterator = { offset: 0 }, buffer: Buffer = this.sharedBuffer) {
|
|
156
|
-
// console.log(
|
|
157
|
-
|
|
158
|
-
// Array.from(this.root.allChanges.entries()).map((item) => {
|
|
159
|
-
// console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
|
|
160
|
-
// });
|
|
158
|
+
// console.log(`\nencodeAll(), this.root.allChanges (${this.root.allChanges.size})`);
|
|
159
|
+
// this.debugChanges("allChanges");
|
|
161
160
|
|
|
162
161
|
return this.encode(it, undefined, buffer, this.root.allChanges, true);
|
|
163
162
|
}
|
|
@@ -165,11 +164,11 @@ export class Encoder<T extends Schema = any> {
|
|
|
165
164
|
encodeAllView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
|
|
166
165
|
const viewOffset = it.offset;
|
|
167
166
|
|
|
168
|
-
// console.log(
|
|
169
|
-
// this.
|
|
167
|
+
// console.log(`\nencodeAllView(), this.root.allFilteredChanges (${this.root.allFilteredChanges.size})`);
|
|
168
|
+
// this.debugChanges("allFilteredChanges");
|
|
170
169
|
|
|
171
170
|
// try to encode "filtered" changes
|
|
172
|
-
this.encode(it, view, bytes, this.root.allFilteredChanges, true);
|
|
171
|
+
this.encode(it, view, bytes, this.root.allFilteredChanges, true, viewOffset);
|
|
173
172
|
|
|
174
173
|
return Buffer.concat([
|
|
175
174
|
bytes.subarray(0, sharedOffset),
|
|
@@ -177,21 +176,35 @@ export class Encoder<T extends Schema = any> {
|
|
|
177
176
|
]);
|
|
178
177
|
}
|
|
179
178
|
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
179
|
+
debugChanges(
|
|
180
|
+
field: "changes" | "allFilteredChanges" | "allChanges" | "filteredChanges" | Map<ChangeTree, Map<number, OPERATION>>
|
|
181
|
+
) {
|
|
182
|
+
const changeSet = (typeof (field) === "string")
|
|
183
|
+
? this.root[field]
|
|
184
|
+
: field;
|
|
185
|
+
|
|
186
|
+
Array.from(changeSet.entries()).map((item) => {
|
|
187
|
+
const metadata: Metadata = item[0].ref.constructor[Symbol.metadata];
|
|
188
|
+
console.log("->", { ref: item[0].ref.constructor.name, refId: item[0].refId, changes: item[1].size });
|
|
189
|
+
item[1].forEach((op, index) => {
|
|
190
|
+
console.log(" ->", {
|
|
191
|
+
index,
|
|
192
|
+
field: metadata?.[index],
|
|
193
|
+
op: OPERATION[op],
|
|
194
|
+
});
|
|
195
|
+
});
|
|
189
196
|
});
|
|
190
197
|
}
|
|
191
198
|
|
|
192
199
|
encodeView(view: StateView, sharedOffset: number, it: Iterator, bytes = this.sharedBuffer) {
|
|
193
200
|
const viewOffset = it.offset;
|
|
194
201
|
|
|
202
|
+
// console.log(`\nencodeView(), view.changes (${view.changes.size})`);
|
|
203
|
+
// this.debugChanges(view.changes);
|
|
204
|
+
|
|
205
|
+
// console.log(`\nencodeView(), this.root.filteredChanges (${this.root.filteredChanges.size})`);
|
|
206
|
+
// this.debugChanges("filteredChanges");
|
|
207
|
+
|
|
195
208
|
// encode visibility changes (add/remove for this view)
|
|
196
209
|
const viewChangesIterator = view.changes.entries();
|
|
197
210
|
for (const [changeTree, changes] of viewChangesIterator) {
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
import { OPERATION } from "../encoding/spec";
|
|
2
|
+
import { TypeContext } from "../types/TypeContext";
|
|
3
|
+
import { ChangeTree } from "./ChangeTree";
|
|
4
|
+
|
|
5
|
+
export class Root {
|
|
6
|
+
protected nextUniqueId: number = 0;
|
|
7
|
+
refCount = new WeakMap<ChangeTree, number>();
|
|
8
|
+
|
|
9
|
+
// all changes
|
|
10
|
+
allChanges = new Map<ChangeTree, Map<number, OPERATION>>();
|
|
11
|
+
allFilteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
|
|
12
|
+
|
|
13
|
+
// pending changes to be encoded
|
|
14
|
+
changes = new Map<ChangeTree, Map<number, OPERATION>>();
|
|
15
|
+
filteredChanges = new Map<ChangeTree, Map<number, OPERATION>>();
|
|
16
|
+
|
|
17
|
+
constructor(public types: TypeContext) { }
|
|
18
|
+
|
|
19
|
+
getNextUniqueId() {
|
|
20
|
+
return this.nextUniqueId++;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
add(changeTree: ChangeTree) {
|
|
24
|
+
const refCount = this.refCount.get(changeTree) || 0;
|
|
25
|
+
this.refCount.set(changeTree, refCount + 1);
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
remove(changeTree: ChangeTree) {
|
|
29
|
+
const refCount = this.refCount.get(changeTree);
|
|
30
|
+
if (refCount <= 1) {
|
|
31
|
+
this.allChanges.delete(changeTree);
|
|
32
|
+
this.changes.delete(changeTree);
|
|
33
|
+
|
|
34
|
+
if (changeTree.isFiltered || changeTree.isPartiallyFiltered) {
|
|
35
|
+
this.allFilteredChanges.delete(changeTree);
|
|
36
|
+
this.filteredChanges.delete(changeTree);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
this.refCount.delete(changeTree);
|
|
40
|
+
|
|
41
|
+
} else {
|
|
42
|
+
this.refCount.set(changeTree, refCount - 1);
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
changeTree.forEachChild((child, _) => this.remove(child));
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
clear() {
|
|
49
|
+
this.changes.clear();
|
|
50
|
+
}
|
|
51
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -42,18 +42,10 @@ export {
|
|
|
42
42
|
ReflectionField,
|
|
43
43
|
} from "./Reflection";
|
|
44
44
|
|
|
45
|
+
// Annotations, Metadata and TypeContext
|
|
45
46
|
export { Metadata } from "./Metadata";
|
|
46
|
-
|
|
47
|
-
export {
|
|
48
|
-
// Annotations
|
|
49
|
-
type,
|
|
50
|
-
deprecated,
|
|
51
|
-
defineTypes,
|
|
52
|
-
view,
|
|
53
|
-
|
|
54
|
-
// Internals
|
|
55
|
-
TypeContext,
|
|
56
|
-
} from "./annotations";
|
|
47
|
+
export { type, deprecated, defineTypes, view, } from "./annotations";
|
|
48
|
+
export { TypeContext } from "./types/TypeContext";
|
|
57
49
|
|
|
58
50
|
// Annotation types
|
|
59
51
|
export type { DefinitionType, PrimitiveType, Definition, } from "./annotations";
|