-
Notifications
You must be signed in to change notification settings - Fork 5
/
Copy pathmessages.nim
171 lines (135 loc) · 5.08 KB
/
messages.nim
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
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
## Message is a base unit for communication between systems.
import hashes
import tables
export tables
import macros
import locks
import typetraits
import msgpack4nim
export msgpack4nim # every module using messages packing must import msgpack4nim
type
Message* {.inheritable.} = object
## Message is an object with minimal required information to describe some event or command.
## Call `messages.register` on message subtype so that msgpack4nim knows how to (de)serialize it.
## Example:
## type CustomMessage = object of Message
## messages.register(CustomMessage)
PackProc = proc(message: ref Message): string {.closure.}
UnpackProc = proc(stream: MsgStream): ref Message {.closure.}
var packTable = initTable[
uint8,
tuple[
pack: PackProc,
unpack: UnpackProc,
],
]()
let packTablePtr = packTable.addr
var packTableLock: Lock
initLock(packTableLock)
# -- Message --
method `$`*(self: ref Message): string {.base.} = "Message"
method packId*(self: ref Message): uint8 {.base.} =
raise newException(LibraryError, "Trying to pack/unpack base Message type, probably forgot to call `register()` on custom message type")
proc msgpack*(message: ref Message): string {.gcsafe.} =
## General method which selects appropriate pack method from pack table according to real message runtime type.
# var packProc: PackProc
# withLock packTableLock:
# packProc = packTablePtr[][message.packId].pack
# result = packProc(message)
{.gcsafe.}: # im so sorry for this
withLock packTableLock:
try:
result = packTablePtr[][message.packId].pack(message)
except KeyError:
raise newException(LibraryError, "Unknown message type id: " & $message.packId)
proc msgunpack*(data: string): ref Message {.gcsafe.} =
## General method which selects appropriate unpack method from pack table according to real message runtime type.
var
packId: uint8
stream = MsgStream.init(data)
# stream.setPosition(0) # TODO: why was it needed?
stream.unpack(packId)
{.gcsafe.}:
withLock packTableLock:
try:
result = packTablePtr[][packId].unpack(stream)
except KeyError:
raise newException(LibraryError, "Unknown message type id: " & $packId)
template register*(MessageType: typedesc) =
## Template for registering pack/unpack procs for specific message type.
## Without registering, packing/unpacking won't store runtime type information.
var messageId: uint8
withLock packTableLock:
messageId = uint8(packTable.len) + 1
packTablePtr[][messageId] = (
# pack proc
proc(message: ref Message): string {.closure.} =
let packId = messageId
# var stream = MsgStream.init(sizeof(packId) + sizeof(MessageType))
var stream = MsgStream.init()
stream.pack packId
stream.pack (ref MessageType) message
result = stream.data,
# unpack proc
proc(stream: MsgStream): ref Message {.closure.} =
var temp: ref MessageType
stream.unpack(temp)
result = temp
)
method packId*(self: ref MessageType): uint8 = messageId
method `$`*(self: ref MessageType): string =
result = self[].type.name
for name, value in self[].fieldPairs:
result.add(" " & name & "=[" & $value & "]")
proc msgpack*(self: ref MessageType): string = ((ref Message)self).msgpack() # required for instant pack
when isMainModule:
import unittest
type
MessageA = object of Message
msg: string
MessageB = object of Message
counter: int8
data: string
is_correct: bool
method getData(x: ref Message): string {.base.} = ""
method getData(x: ref MessageA): string = x.msg
method getData(x: ref MessageB): string = $x.counter
register(MessageA)
register(MessageB)
suite "Messages test":
var
packed: string
unpacked: ref Message
test "Pack/unpack base Message type":
expect LibraryError:
packed = new(Message).msgpack()
test "Pack/unpack Message subtypes":
var message: ref Message
message = (ref MessageA)(msg: "some message")
packed = message.msgpack()
echo "MessageA packed as: " & stringify(packed)
unpacked = packed.msgunpack()
check:
packed.len == 15
unpacked.getData() == "some message"
message = (ref MessageB)(counter: 42, data: "some data string", is_correct: true)
packed = message.msgpack()
echo "MessageB packed as: " & stringify(packed)
unpacked = packed.msgunpack()
check:
packed.len == 21
unpacked.getData() == "42"
test "Instant pack":
let packedInstant = (ref MessageB)(counter: 42).msgpack()
echo "Instant packed as: " & stringify(packedInstant)
var msg: ref Message = (ref MessageB)(counter: 42)
let packedVariable = msg.msgpack()
echo "Var packed as: " & stringify(packedVariable)
check:
packedInstant == packedVariable
test "Inside a thread":
var thread: Thread[void]
thread.createThread(tp = proc() {.thread.} =
let packed = (ref MessageB)(counter: 42).msgpack()
discard packed.msgunpack()
)