-
Notifications
You must be signed in to change notification settings - Fork 29
/
Copy pathpacket.ts
91 lines (83 loc) · 2.82 KB
/
packet.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
import type { State } from './channel'
import { Coder } from './coders'
interface FieldRecord { name: string; type: Coder<any> }
export type Side = 'server' | 'client'
export interface PacketRegistryEntry {
readonly id: number
readonly name: string
readonly state: State
readonly side: Side
readonly coder: Coder<any>
}
export type FieldType<T> = (type: Coder<T>) => (target: any, key: string) => void
export type PacketType = (side: Side, id: number, state: State) => (constructor: (...args: any[]) => any) => void
export const PacketMetadata = Symbol('PacketMetadata')
export const PacketFieldsMetadata = Symbol('PacketFieldsMetadata')
/**
* Get a packet registry entry for a class
* @param clazz The class object
*/
export function getPacketRegistryEntry(clazz: new (...args: any) => any): PacketRegistryEntry {
return clazz.prototype[PacketMetadata]
}
/**
* Annotate the field type in your packet. Assign a coder for serialization/deserialization.
* This will generate a list of `FieldType` in your class prototype.
*
* @param type The coder to serialize/deserialize the field.
* @see "coders.ts"
*/
export function Field<T>(type: Coder<T>) {
return (target: any, key: string) => {
const container = target
if (!container[PacketFieldsMetadata]) {
container[PacketFieldsMetadata] = []
}
container[PacketFieldsMetadata].push({ name: key, type })
}
}
/**
* Decoarte for you packet class.
* This will generate a `PacketRegistryEntry` in your class prototype.
*
* @param side The side of your packet
* @param id The id of your packet
* @param state The state of you packet should be
*/
export function Packet(side: Side, id: number, state: State, name = '') {
return (constructor: new (...args: any[]) => any) => {
const container = constructor.prototype
const fields: FieldRecord[] = container[PacketFieldsMetadata] || []
container[PacketMetadata] = {
id,
name: name || constructor.name,
side,
state,
coder: {
encode(buffer, value) {
fields.forEach((cod) => {
cod.type.encode(buffer, value[cod.name])
})
},
decode(buffer) {
const value = newCall(constructor)
fields.forEach((cod) => {
try {
value[cod.name] = cod.type.decode(buffer)
} catch (e) {
console.error(new Error(`Exception during reciving packet [${id}]${constructor.name}`))
console.error(e)
}
})
return value
},
},
} as PacketRegistryEntry
}
}
// https://stackoverflow.com/questions/1606797/use-of-apply-with-new-operator-is-this-possible
function newCall(Cls: any) {
// tslint:disable-next-line: new-parens
// eslint-disable-next-line prefer-rest-params
return new (Function.prototype.bind.apply(Cls, arguments as any))()
}