mt-lang 0.2.0 → 0.2.1

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.
Files changed (189) hide show
  1. checksums.yaml +4 -4
  2. data/docs/index.html +4 -3
  3. data/lib/milk_tea/base.rb +1 -1
  4. data/std/asset_pack.mt +267 -0
  5. data/std/async/libuv_runtime.mt +539 -0
  6. data/std/async/mailbox.mt +191 -0
  7. data/std/async/runtime.mt +87 -0
  8. data/std/async.mt +87 -0
  9. data/std/base64.mt +185 -0
  10. data/std/behavior_tree.mt +396 -0
  11. data/std/binary.mt +314 -0
  12. data/std/binary_heap.mt +157 -0
  13. data/std/bitset.mt +239 -0
  14. data/std/box2d.mt +535 -0
  15. data/std/bytes.mt +46 -0
  16. data/std/c/box2d.mt +1254 -0
  17. data/std/c/cgltf.mt +647 -0
  18. data/std/c/cjson.mt +106 -0
  19. data/std/c/crypto.mt +11 -0
  20. data/std/c/crypto_support.h +51 -0
  21. data/std/c/ctype.mt +19 -0
  22. data/std/c/ctype_bindgen.h +29 -0
  23. data/std/c/curl.mt +1223 -0
  24. data/std/c/enet.mt +473 -0
  25. data/std/c/errno.mt +41 -0
  26. data/std/c/errno_bindgen.h +53 -0
  27. data/std/c/flecs.mt +2945 -0
  28. data/std/c/fs.linux.mt +43 -0
  29. data/std/c/fs.windows.mt +43 -0
  30. data/std/c/fs_support.h +954 -0
  31. data/std/c/gl.mt +2065 -0
  32. data/std/c/gl_registry_helpers.h +10748 -0
  33. data/std/c/glfw.mt +499 -0
  34. data/std/c/libc.mt +167 -0
  35. data/std/c/libuv.mt +1491 -0
  36. data/std/c/math.mt +22 -0
  37. data/std/c/math_bindgen.h +31 -0
  38. data/std/c/miniaudio.mt +3420 -0
  39. data/std/c/pcre2.mt +735 -0
  40. data/std/c/process.mt +58 -0
  41. data/std/c/process_support.h +1147 -0
  42. data/std/c/raygui.mt +1602 -0
  43. data/std/c/raylib.mt +1245 -0
  44. data/std/c/raymath.mt +163 -0
  45. data/std/c/rlgl.mt +434 -0
  46. data/std/c/rpng.mt +60 -0
  47. data/std/c/rres.mt +187 -0
  48. data/std/c/sdl3.mt +4132 -0
  49. data/std/c/sqlite3.mt +951 -0
  50. data/std/c/stb_image.mt +52 -0
  51. data/std/c/stb_image_resize2.mt +128 -0
  52. data/std/c/stb_image_write.mt +19 -0
  53. data/std/c/stb_rect_pack.mt +38 -0
  54. data/std/c/stb_truetype.mt +175 -0
  55. data/std/c/stb_vorbis.mt +31 -0
  56. data/std/c/stdio.mt +90 -0
  57. data/std/c/steamworks.h +5769 -0
  58. data/std/c/steamworks.mt +4771 -0
  59. data/std/c/string.mt +16 -0
  60. data/std/c/string_bindgen.h +36 -0
  61. data/std/c/sync.mt +33 -0
  62. data/std/c/sync_support.h +219 -0
  63. data/std/c/terminal.mt +24 -0
  64. data/std/c/terminal_support.h +408 -0
  65. data/std/c/time.mt +26 -0
  66. data/std/c/tls.mt +24 -0
  67. data/std/c/tls_support.h +626 -0
  68. data/std/c/tracy.mt +53 -0
  69. data/std/c/zlib.mt +20 -0
  70. data/std/c/zlib_support.h +239 -0
  71. data/std/c/zstd.mt +231 -0
  72. data/std/cell.mt +60 -0
  73. data/std/cgltf.mt +115 -0
  74. data/std/cjson.mt +90 -0
  75. data/std/cli.mt +768 -0
  76. data/std/cookie.mt +198 -0
  77. data/std/counter.mt +160 -0
  78. data/std/crypto.mt +74 -0
  79. data/std/cstring.mt +13 -0
  80. data/std/ctype.mt +52 -0
  81. data/std/curl/runtime.mt +261 -0
  82. data/std/curl.mt +38 -0
  83. data/std/deque.mt +348 -0
  84. data/std/encoding.mt +93 -0
  85. data/std/enet.mt +133 -0
  86. data/std/env.mt +41 -0
  87. data/std/errno.mt +47 -0
  88. data/std/flecs.mt +978 -0
  89. data/std/fmt.mt +271 -0
  90. data/std/fs.linux.mt +696 -0
  91. data/std/fs.windows.mt +711 -0
  92. data/std/fsm.mt +267 -0
  93. data/std/gl.mt +2062 -0
  94. data/std/glfw.mt +471 -0
  95. data/std/goap.mt +342 -0
  96. data/std/graph.mt +521 -0
  97. data/std/gzip.mt +77 -0
  98. data/std/hash.mt +429 -0
  99. data/std/http/server.mt +810 -0
  100. data/std/http.mt +1227 -0
  101. data/std/intern.mt +53 -0
  102. data/std/jobs.mt +288 -0
  103. data/std/json.mt +627 -0
  104. data/std/libc.mt +34 -0
  105. data/std/libuv.mt +554 -0
  106. data/std/linear_algebra.mt +243 -0
  107. data/std/linked_map.mt +372 -0
  108. data/std/linked_map_view.mt +49 -0
  109. data/std/linked_set.mt +117 -0
  110. data/std/log.mt +69 -0
  111. data/std/map.mt +418 -0
  112. data/std/math.mt +24 -0
  113. data/std/mem/arena.mt +137 -0
  114. data/std/mem/endian.mt +51 -0
  115. data/std/mem/heap.mt +298 -0
  116. data/std/mem/pool.mt +164 -0
  117. data/std/mem/stack.mt +47 -0
  118. data/std/mem/tracking.mt +119 -0
  119. data/std/miniaudio.mt +1299 -0
  120. data/std/multiset.mt +191 -0
  121. data/std/net/channel.mt +727 -0
  122. data/std/net/clock.mt +237 -0
  123. data/std/net/discovery.mt +277 -0
  124. data/std/net/lobby.mt +813 -0
  125. data/std/net/manager.mt +539 -0
  126. data/std/net/mux.mt +839 -0
  127. data/std/net/nat.mt +122 -0
  128. data/std/net/packet.mt +219 -0
  129. data/std/net/punch.mt +162 -0
  130. data/std/net/rpc.mt +119 -0
  131. data/std/net/session.mt +1157 -0
  132. data/std/net/stun.mt +252 -0
  133. data/std/net/sync.mt +183 -0
  134. data/std/net/turn.mt +459 -0
  135. data/std/net.mt +2944 -0
  136. data/std/oauth2.mt +408 -0
  137. data/std/option.mt +51 -0
  138. data/std/ordered_map.mt +497 -0
  139. data/std/ordered_set.mt +348 -0
  140. data/std/path.mt +368 -0
  141. data/std/pcre2/runtime.mt +35 -0
  142. data/std/pcre2.mt +78 -0
  143. data/std/priority_queue.mt +55 -0
  144. data/std/process.mt +837 -0
  145. data/std/queue.mt +55 -0
  146. data/std/random.mt +146 -0
  147. data/std/raygui.mt +99 -0
  148. data/std/raylib/debug_console.mt +71 -0
  149. data/std/raylib/easing.mt +388 -0
  150. data/std/raylib/packed_assets.mt +255 -0
  151. data/std/raylib/runtime.mt +30 -0
  152. data/std/raylib/tracy_gpu.mt +37 -0
  153. data/std/raylib.mt +1510 -0
  154. data/std/raymath.mt +153 -0
  155. data/std/result.mt +89 -0
  156. data/std/rlgl.mt +268 -0
  157. data/std/rpng.mt +46 -0
  158. data/std/rres.mt +36 -0
  159. data/std/sdl3/runtime.mt +54 -0
  160. data/std/sdl3.mt +1731 -0
  161. data/std/serialize.mt +68 -0
  162. data/std/set.mt +124 -0
  163. data/std/spatial.mt +176 -0
  164. data/std/sqlite3.mt +151 -0
  165. data/std/stack.mt +55 -0
  166. data/std/stb_image.mt +47 -0
  167. data/std/stb_image_resize2.mt +41 -0
  168. data/std/stb_image_write.mt +17 -0
  169. data/std/stb_rect_pack.mt +15 -0
  170. data/std/stb_truetype.mt +77 -0
  171. data/std/stb_vorbis.mt +16 -0
  172. data/std/stdio.mt +88 -0
  173. data/std/steamworks.mt +1542 -0
  174. data/std/str.mt +293 -0
  175. data/std/string.mt +234 -0
  176. data/std/sync.mt +194 -0
  177. data/std/tar.mt +704 -0
  178. data/std/terminal.mt +1002 -0
  179. data/std/testing.mt +266 -0
  180. data/std/thread.mt +120 -0
  181. data/std/time.mt +105 -0
  182. data/std/tls.mt +616 -0
  183. data/std/toml.mt +1310 -0
  184. data/std/tracy.mt +42 -0
  185. data/std/uri.mt +118 -0
  186. data/std/url.mt +372 -0
  187. data/std/vec.mt +433 -0
  188. data/std/zstd.mt +94 -0
  189. metadata +187 -2
data/std/net/lobby.mt ADDED
@@ -0,0 +1,813 @@
1
+ import std.async as aio
2
+ import std.binary as bin
3
+ import std.bytes as bytes
4
+ import std.deque as deque
5
+ import std.net as net
6
+ import std.net.mux as mux
7
+ import std.net.session as sess
8
+ import std.str as text
9
+ import std.string as string
10
+ import std.vec as vec
11
+
12
+ const lobby_channel: ubyte = 0
13
+ const type_join_request: ushort = 0x0001
14
+ const type_join_accept: ushort = 0x0002
15
+ const type_join_reject: ushort = 0x0003
16
+ const type_player_joined: ushort = 0x0004
17
+ const type_player_left: ushort = 0x0005
18
+ const type_lobby_info: ushort = 0x0006
19
+ const type_discover_request: ushort = 0x0007
20
+ const type_discover_response: ushort = 0x0008
21
+
22
+ const reject_reason_full: ubyte = 1
23
+ const reject_reason_banned: ubyte = 2
24
+ const reject_reason_invalid: ubyte = 3
25
+
26
+ public struct LobbyInfo:
27
+ name: string.String
28
+ player_count: ubyte
29
+ max_players: ubyte
30
+ player_names: vec.Vec[string.String]
31
+ game_data: bytes.Bytes
32
+
33
+ public struct LobbyEvent:
34
+ kind: LobbyEventKind
35
+ player_id: uint
36
+ player_name: string.String
37
+ slot: ubyte
38
+ reason: ubyte
39
+
40
+ public enum LobbyEventKind: ubyte
41
+ player_joined = 0
42
+ player_left = 1
43
+ lobby_info_updated = 2
44
+ joined = 3
45
+ join_rejected = 4
46
+
47
+ public struct LobbyHost:
48
+ mux: mux.MuxedSession
49
+ info: LobbyInfo
50
+ slots: vec.Vec[PlayerSlot]
51
+ event_queue: deque.Deque[LobbyEvent]
52
+
53
+ struct PlayerSlot:
54
+ player_id: uint
55
+ player_name: string.String
56
+ occupied: bool
57
+
58
+ public struct LobbyClient:
59
+ mux: mux.MuxedConnection
60
+ assigned_slot: ubyte
61
+ assigned_id: uint
62
+ lobby_info: LobbyInfo
63
+ event_queue: deque.Deque[LobbyEvent]
64
+ pending_join: bool
65
+ pending_name: string.String
66
+
67
+
68
+ extending LobbyInfo:
69
+ public editable function release() -> void:
70
+ this.name.release()
71
+ var i: ptr_uint = 0
72
+ while i < this.player_names.len():
73
+ let p = this.player_names.get(i) else:
74
+ return
75
+ unsafe: read(p).release()
76
+ i += 1
77
+ this.player_names.release()
78
+ this.game_data.release()
79
+
80
+
81
+ extending LobbyEvent:
82
+ public editable function release() -> void:
83
+ this.player_name.release()
84
+
85
+
86
+ extending LobbyHost:
87
+ public editable function release() -> void:
88
+ release_lobby_queue(ref_of(this.event_queue))
89
+ this.event_queue.release()
90
+ release_slots(ref_of(this.slots))
91
+ this.slots.release()
92
+ this.info.release()
93
+ this.mux.release()
94
+
95
+
96
+ public function local_address() -> Result[net.SocketAddress, net.Error]:
97
+ return this.mux.session.local_address()
98
+
99
+
100
+ public async editable function tick(frame: uint) -> Result[bool, net.Error]:
101
+ let mux_result = await this.mux.tick(frame)
102
+ await drain_lobby_host(ref_of(this))
103
+ match mux_result:
104
+ Result.failure as p:
105
+ return Result[bool, net.Error].failure(error = p.error)
106
+ Result.success as p:
107
+ return Result[bool, net.Error].success(value = p.value)
108
+
109
+
110
+ public editable function try_recv() -> Option[LobbyEvent]:
111
+ return this.event_queue.pop_front()
112
+
113
+
114
+ public function info() -> LobbyInfo:
115
+ return this.info
116
+
117
+
118
+ public async editable function kick_player(player_id: uint, reason: ubyte) -> Result[bool, net.Error]:
119
+ match await this.mux.kick(player_id, reason):
120
+ Result.failure as p:
121
+ return Result[bool, net.Error].failure(error = p.error)
122
+ Result.success as p:
123
+ return Result[bool, net.Error].success(value = p.value)
124
+
125
+
126
+ extending LobbyClient:
127
+ public editable function release() -> void:
128
+ release_lobby_queue(ref_of(this.event_queue))
129
+ this.event_queue.release()
130
+ this.lobby_info.release()
131
+ this.pending_name.release()
132
+ this.mux.release()
133
+
134
+
135
+ public function state() -> sess.ConnectionState:
136
+ return this.mux.state()
137
+
138
+
139
+ public function assigned_slot() -> ubyte:
140
+ return this.assigned_slot
141
+
142
+
143
+ public function assigned_id() -> uint:
144
+ return this.assigned_id
145
+
146
+
147
+ public function lobby_info() -> LobbyInfo:
148
+ return this.lobby_info
149
+
150
+
151
+ public async editable function tick(frame: uint) -> Result[bool, net.Error]:
152
+ let mux_result = await this.mux.tick(frame)
153
+ await drain_lobby_client(ref_of(this))
154
+ match mux_result:
155
+ Result.failure as p:
156
+ return Result[bool, net.Error].failure(error = p.error)
157
+ Result.success as p:
158
+ return Result[bool, net.Error].success(value = p.value)
159
+
160
+
161
+ public editable function try_recv() -> Option[LobbyEvent]:
162
+ return this.event_queue.pop_front()
163
+
164
+
165
+ public async editable function leave() -> Result[bool, net.Error]:
166
+ match await this.mux.disconnect():
167
+ Result.failure as p:
168
+ return Result[bool, net.Error].failure(error = p.error)
169
+ Result.success as p:
170
+ return Result[bool, net.Error].success(value = p.value)
171
+
172
+
173
+ public function create_lobby_on(
174
+ runtime: aio.Runtime,
175
+ local_address: net.SocketAddress,
176
+ info: LobbyInfo,
177
+ config: mux.MuxedConfig
178
+ ) -> Result[LobbyHost, net.Error]:
179
+ match mux.mux_listen_on(runtime, local_address, config):
180
+ Result.failure as p:
181
+ return Result[LobbyHost, net.Error].failure(error = p.error)
182
+ Result.success as p:
183
+ var slots = vec.Vec[PlayerSlot].create()
184
+ var i: ubyte = 0
185
+ while i < info.max_players:
186
+ slots.push(PlayerSlot(player_id = 0u, player_name = string.String.create(), occupied = false))
187
+ i += 1
188
+ let host = LobbyHost(
189
+ mux = p.value,
190
+ info = info,
191
+ slots = slots,
192
+ event_queue = deque.Deque[LobbyEvent].create()
193
+ )
194
+ return Result[LobbyHost, net.Error].success(value = host)
195
+
196
+
197
+ public function create_lobby(
198
+ local_address: net.SocketAddress,
199
+ info: LobbyInfo,
200
+ config: mux.MuxedConfig
201
+ ) -> Result[LobbyHost, net.Error]:
202
+ return create_lobby_on(aio.current_runtime(), local_address, info, config)
203
+
204
+
205
+ public async function join_lobby_on(
206
+ runtime: aio.Runtime,
207
+ local_address: net.SocketAddress,
208
+ remote_address: net.SocketAddress,
209
+ player_name: str,
210
+ config: mux.MuxedConfig
211
+ ) -> Result[LobbyClient, net.Error]:
212
+ match mux.mux_connect_on(runtime, local_address, remote_address, config):
213
+ Result.failure as p:
214
+ return Result[LobbyClient, net.Error].failure(error = p.error)
215
+ Result.success as p:
216
+ var client = LobbyClient(
217
+ mux = p.value,
218
+ assigned_slot = 0,
219
+ assigned_id = 0,
220
+ lobby_info = LobbyInfo(
221
+ name = string.String.create(),
222
+ player_count = 0,
223
+ max_players = 0,
224
+ player_names = vec.Vec[string.String].create(),
225
+ game_data = bytes.Bytes.empty()
226
+ ),
227
+ event_queue = deque.Deque[LobbyEvent].create(),
228
+ pending_join = true,
229
+ pending_name = string.String.from_str(player_name)
230
+ )
231
+ match await client.mux.connect_to_peer():
232
+ Result.failure as pe:
233
+ return Result[LobbyClient, net.Error].failure(error = pe.error)
234
+ Result.success:
235
+ pass
236
+ return Result[LobbyClient, net.Error].success(value = client)
237
+
238
+
239
+ public async function join_lobby(
240
+ local_address: net.SocketAddress,
241
+ remote_address: net.SocketAddress,
242
+ player_name: str,
243
+ config: mux.MuxedConfig
244
+ ) -> Result[LobbyClient, net.Error]:
245
+ return await join_lobby_on(aio.current_runtime(), local_address, remote_address, player_name, config)
246
+
247
+
248
+ public async function discover_lobbies_on(
249
+ runtime: aio.Runtime,
250
+ local_address: net.SocketAddress,
251
+ remote_address: net.SocketAddress,
252
+ config: mux.MuxedConfig
253
+ ) -> Result[vec.Vec[LobbyInfo], net.Error]:
254
+ match mux.mux_connect_on(runtime, local_address, remote_address, config):
255
+ Result.failure as p:
256
+ return Result[vec.Vec[LobbyInfo], net.Error].failure(error = p.error)
257
+ Result.success as p:
258
+ var conn = p.value
259
+ defer conn.release()
260
+ match await conn.connect_to_peer():
261
+ Result.failure as pe:
262
+ return Result[vec.Vec[LobbyInfo], net.Error].failure(error = pe.error)
263
+ Result.success:
264
+ pass
265
+
266
+ let empty = bytes.Bytes.empty()
267
+ let _ = await conn.mux_send(lobby_channel, type_discover_request, empty.as_span(), mux.flag_reliable)
268
+
269
+ var frame: uint = 0
270
+ var result = vec.Vec[LobbyInfo].create()
271
+ while frame < 120:
272
+ await conn.tick(frame)
273
+
274
+ var drain_rounds: uint = 0
275
+ while drain_rounds < 5:
276
+ let msg_opt = conn.try_recv()
277
+ match msg_opt:
278
+ Option.some as mp:
279
+ var msg = mp.value
280
+ if msg.channel_id == lobby_channel and msg.type_id == type_discover_response:
281
+ let info_result = decode_lobby_info_payload(msg.payload.as_span())
282
+ msg.release()
283
+ match info_result:
284
+ Result.success as ip:
285
+ result.push(ip.value)
286
+ Result.failure:
287
+ pass
288
+ var disconnect_frame: uint = 0
289
+ while disconnect_frame < 30:
290
+ await conn.tick(frame + disconnect_frame)
291
+ disconnect_frame += 1
292
+ return Result[vec.Vec[LobbyInfo], net.Error].success(value = result)
293
+ msg.release()
294
+ Option.none:
295
+ pass
296
+ drain_rounds += 1
297
+ frame += 1
298
+
299
+ return Result[vec.Vec[LobbyInfo], net.Error].success(value = result)
300
+
301
+
302
+ public async function discover_lobbies(
303
+ local_address: net.SocketAddress,
304
+ remote_address: net.SocketAddress,
305
+ config: mux.MuxedConfig
306
+ ) -> Result[vec.Vec[LobbyInfo], net.Error]:
307
+ return await discover_lobbies_on(aio.current_runtime(), local_address, remote_address, config)
308
+
309
+
310
+ function encode_lobby_info_payload(w: ref[bin.Writer], info: ref[LobbyInfo]) -> void:
311
+ w.write_ubyte(info.player_count)
312
+ w.write_ubyte(info.max_players)
313
+ w.write_str(info.name.as_str())
314
+
315
+
316
+ function decode_lobby_info_payload(data: span[ubyte]) -> Result[LobbyInfo, net.Error]:
317
+ if data.len < 2:
318
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
319
+ code = -1,
320
+ message = string.String.from_str("lobby info payload truncated")
321
+ ))
322
+ var r = bin.reader(data)
323
+ var player_count: ubyte = 0
324
+ match r.read_ubyte():
325
+ Result.failure:
326
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
327
+ code = -1,
328
+ message = string.String.from_str("lobby info malformed")
329
+ ))
330
+ Result.success as pc:
331
+ player_count = pc.value
332
+ var max_players: ubyte = 0
333
+ match r.read_ubyte():
334
+ Result.failure:
335
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
336
+ code = -1,
337
+ message = string.String.from_str("lobby info malformed")
338
+ ))
339
+ Result.success as mp:
340
+ max_players = mp.value
341
+ match r.read_str():
342
+ Result.failure:
343
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
344
+ code = -1,
345
+ message = string.String.from_str("lobby info malformed")
346
+ ))
347
+ Result.success as sp:
348
+ return Result[LobbyInfo, net.Error].success(value = LobbyInfo(
349
+ name = sp.value,
350
+ player_count = player_count,
351
+ max_players = max_players,
352
+ player_names = vec.Vec[string.String].create(),
353
+ game_data = bytes.Bytes.empty()
354
+ ))
355
+
356
+
357
+ function encode_string(value: str) -> bytes.Bytes:
358
+ var w = bin.Writer.with_capacity(4 + value.len)
359
+ w.write_str(value)
360
+ return w.finish()
361
+
362
+
363
+ function decode_string(data: span[ubyte]) -> Result[string.String, net.Error]:
364
+ var r = bin.reader(data)
365
+ match r.read_str():
366
+ Result.failure:
367
+ return Result[string.String, net.Error].failure(error = net.Error(
368
+ code = -1,
369
+ message = string.String.from_str("lobby malformed string")
370
+ ))
371
+ Result.success as p:
372
+ return Result[string.String, net.Error].success(value = p.value)
373
+
374
+
375
+ async function drain_lobby_host(host: ref[LobbyHost]) -> void:
376
+ while true:
377
+ let ev = host.mux.try_recv()
378
+ match ev:
379
+ Option.none:
380
+ break
381
+ Option.some as p:
382
+ var msg = p.value
383
+ await process_lobby_msg_host(host, ref_of(msg))
384
+
385
+
386
+ async function drain_lobby_client(client: ref[LobbyClient]) -> void:
387
+ if client.pending_join:
388
+ let conn_state = client.mux.state()
389
+ if conn_state == sess.ConnectionState.connected:
390
+ client.pending_join = false
391
+ var encoded = encode_string(client.pending_name.as_str())
392
+ defer encoded.release()
393
+ let _ = await client.mux.mux_send(lobby_channel, type_join_request, encoded.as_span(), mux.flag_reliable)
394
+
395
+ while true:
396
+ let ev = client.mux.try_recv()
397
+ match ev:
398
+ Option.none:
399
+ break
400
+ Option.some as p:
401
+ var msg = p.value
402
+ process_lobby_msg_client(client, ref_of(msg))
403
+
404
+
405
+ async function process_lobby_msg_host(host: ref[LobbyHost], msg: ref[mux.MuxedMessage]) -> void:
406
+ if msg.channel_id != lobby_channel:
407
+ msg.release()
408
+ return
409
+
410
+ if msg.type_id == type_join_request:
411
+ await handle_join_request(host, msg)
412
+ return
413
+
414
+ if msg.type_id == type_discover_request:
415
+ await handle_discover_request(host, msg)
416
+ return
417
+
418
+ msg.release()
419
+
420
+
421
+ function process_lobby_msg_client(client: ref[LobbyClient], msg: ref[mux.MuxedMessage]) -> void:
422
+ if msg.channel_id != lobby_channel:
423
+ msg.release()
424
+ return
425
+
426
+ if msg.type_id == type_join_accept:
427
+ handle_join_accept(client, msg)
428
+ return
429
+
430
+ if msg.type_id == type_join_reject:
431
+ handle_join_reject_client(client, msg)
432
+ return
433
+
434
+ if msg.type_id == type_player_joined:
435
+ handle_player_joined_client(client, msg)
436
+ return
437
+
438
+ if msg.type_id == type_player_left:
439
+ handle_player_left_client(client, msg)
440
+ return
441
+
442
+ if msg.type_id == type_lobby_info:
443
+ handle_lobby_info_client(client, msg)
444
+ return
445
+
446
+ msg.release()
447
+
448
+
449
+ async function handle_join_request(host: ref[LobbyHost], msg: ref[mux.MuxedMessage]) -> void:
450
+ let data = msg.payload.as_span()
451
+ let name_result = decode_string(data)
452
+ match name_result:
453
+ Result.failure:
454
+ msg.release()
455
+ return
456
+ Result.success as name_payload:
457
+ var player_name = name_payload.value
458
+ defer player_name.release()
459
+
460
+ var first_free: int = -1
461
+ var slot_index: ubyte = 0
462
+ while slot_index < host.info.max_players:
463
+ let slot_ptr = host.slots.get(ptr_uint<-slot_index) else:
464
+ break
465
+ if not unsafe: read(slot_ptr).occupied:
466
+ first_free = slot_index
467
+ break
468
+ slot_index += 1
469
+
470
+ if first_free < 0:
471
+ var reject_data = encode_join_reject(reject_reason_full)
472
+ defer reject_data.release()
473
+ let _ = await host.mux.mux_send(
474
+ msg.peer_id,
475
+ lobby_channel,
476
+ type_join_reject,
477
+ reject_data.as_span(),
478
+ mux.flag_reliable
479
+ )
480
+ msg.release()
481
+ return
482
+
483
+ let slot = ubyte<-first_free
484
+ let slot_ptr = host.slots.get(ptr_uint<-slot) else:
485
+ msg.release()
486
+ return
487
+ unsafe: read(slot_ptr).player_id = msg.peer_id
488
+ unsafe: read(slot_ptr).player_name = string.String.from_str(player_name.as_str())
489
+ unsafe: read(slot_ptr).occupied = true
490
+
491
+ var accept_data = encode_join_accept(msg.peer_id, slot)
492
+ defer accept_data.release()
493
+ let _ = await host.mux.mux_send(
494
+ msg.peer_id,
495
+ lobby_channel,
496
+ type_join_accept,
497
+ accept_data.as_span(),
498
+ mux.flag_reliable
499
+ )
500
+
501
+ let other_id = msg.peer_id
502
+ msg.release()
503
+
504
+ await broadcast_player_joined(host, other_id, player_name.as_str(), slot)
505
+
506
+ host.event_queue.push_back(LobbyEvent(
507
+ kind = LobbyEventKind.player_joined,
508
+ player_id = other_id,
509
+ player_name = string.String.from_str(player_name.as_str()),
510
+ slot = slot,
511
+ reason = 0ub
512
+ ))
513
+
514
+
515
+ async function handle_discover_request(host: ref[LobbyHost], msg: ref[mux.MuxedMessage]) -> void:
516
+ var w = bin.Writer.with_capacity(256)
517
+ encode_lobby_info_payload(ref_of(w), host.info)
518
+ var payload = w.finish()
519
+ defer payload.release()
520
+ let _ = await host.mux.mux_send(
521
+ msg.peer_id,
522
+ lobby_channel,
523
+ type_discover_response,
524
+ payload.as_span(),
525
+ mux.flag_reliable
526
+ )
527
+ msg.release()
528
+
529
+
530
+ function handle_join_accept(client: ref[LobbyClient], msg: ref[mux.MuxedMessage]) -> void:
531
+ let data = msg.payload.as_span()
532
+ if data.len < 5:
533
+ msg.release()
534
+ return
535
+ var r = bin.reader(data)
536
+ match r.read_uint():
537
+ Result.failure:
538
+ msg.release()
539
+ return
540
+ Result.success as id_payload:
541
+ match r.read_ubyte():
542
+ Result.failure:
543
+ msg.release()
544
+ return
545
+ Result.success as slot_payload:
546
+ client.assigned_id = id_payload.value
547
+ client.assigned_slot = slot_payload.value
548
+ msg.release()
549
+ client.event_queue.push_back(LobbyEvent(
550
+ kind = LobbyEventKind.joined,
551
+ player_id = id_payload.value,
552
+ player_name = string.String.create(),
553
+ slot = slot_payload.value,
554
+ reason = 0ub
555
+ ))
556
+
557
+
558
+ function handle_join_reject_client(client: ref[LobbyClient], msg: ref[mux.MuxedMessage]) -> void:
559
+ let data = msg.payload.as_span()
560
+ var reason: ubyte = 0
561
+ if data.len > 0:
562
+ reason = unsafe: read(data.data)
563
+ msg.release()
564
+ client.event_queue.push_back(LobbyEvent(
565
+ kind = LobbyEventKind.join_rejected,
566
+ player_id = 0u,
567
+ player_name = string.String.create(),
568
+ slot = 0ub,
569
+ reason = reason
570
+ ))
571
+
572
+
573
+ function handle_player_joined_client(client: ref[LobbyClient], msg: ref[mux.MuxedMessage]) -> void:
574
+ let data = msg.payload.as_span()
575
+ if data.len < 5:
576
+ msg.release()
577
+ return
578
+ var r = bin.reader(data)
579
+ match r.read_uint():
580
+ Result.failure:
581
+ msg.release()
582
+ return
583
+ Result.success as id_payload:
584
+ match r.read_ubyte():
585
+ Result.failure:
586
+ msg.release()
587
+ return
588
+ Result.success as slot_payload:
589
+ let name_data = unsafe: span[ubyte](data = data.data + 5, len = data.len - 5)
590
+ match decode_string(name_data):
591
+ Result.failure:
592
+ msg.release()
593
+ return
594
+ Result.success as name_payload:
595
+ let name = name_payload.value
596
+ msg.release()
597
+ client.event_queue.push_back(LobbyEvent(
598
+ kind = LobbyEventKind.player_joined,
599
+ player_id = id_payload.value,
600
+ player_name = name,
601
+ slot = slot_payload.value,
602
+ reason = 0ub
603
+ ))
604
+
605
+
606
+ function handle_player_left_client(client: ref[LobbyClient], msg: ref[mux.MuxedMessage]) -> void:
607
+ let data = msg.payload.as_span()
608
+ if data.len < 5:
609
+ msg.release()
610
+ return
611
+ var r = bin.reader(data)
612
+ match r.read_uint():
613
+ Result.failure:
614
+ msg.release()
615
+ return
616
+ Result.success as id_payload:
617
+ var reason: ubyte = 0
618
+ if data.len > 4:
619
+ reason = unsafe: read(data.data + 4)
620
+ msg.release()
621
+ client.event_queue.push_back(LobbyEvent(
622
+ kind = LobbyEventKind.player_left,
623
+ player_id = id_payload.value,
624
+ player_name = string.String.create(),
625
+ slot = 0ub,
626
+ reason = reason
627
+ ))
628
+
629
+
630
+ function handle_lobby_info_client(client: ref[LobbyClient], msg: ref[mux.MuxedMessage]) -> void:
631
+ msg.release()
632
+ client.event_queue.push_back(LobbyEvent(
633
+ kind = LobbyEventKind.lobby_info_updated,
634
+ player_id = 0u,
635
+ player_name = string.String.create(),
636
+ slot = 0ub,
637
+ reason = 0ub
638
+ ))
639
+
640
+
641
+ async function broadcast_player_joined(
642
+ host: ref[LobbyHost],
643
+ new_player_id: uint,
644
+ new_player_name: str,
645
+ slot: ubyte
646
+ ) -> void:
647
+ var w = bin.Writer.with_capacity(5 + new_player_name.len)
648
+ w.write_uint(new_player_id)
649
+ w.write_ubyte(slot)
650
+ w.write_uint(uint<-new_player_name.len)
651
+ w.write_bytes(text.as_byte_span(new_player_name))
652
+ var payload = w.finish()
653
+ defer payload.release()
654
+
655
+ var peer_index: ptr_uint = 0
656
+ let peer_count = host.mux.peer_count()
657
+ while peer_index < peer_count:
658
+ let peer_id = uint<-peer_index + 1
659
+ if peer_id != new_player_id:
660
+ let _ = await host.mux.mux_send(
661
+ peer_id,
662
+ lobby_channel,
663
+ type_player_joined,
664
+ payload.as_span(),
665
+ mux.flag_reliable
666
+ )
667
+ peer_index += 1
668
+
669
+
670
+ function encode_join_accept(player_id: uint, slot: ubyte) -> bytes.Bytes:
671
+ var w = bin.Writer.with_capacity(5)
672
+ w.write_uint(player_id)
673
+ w.write_ubyte(slot)
674
+ return w.finish()
675
+
676
+
677
+ function encode_join_reject(reason: ubyte) -> bytes.Bytes:
678
+ var w = bin.Writer.with_capacity(1)
679
+ w.write_ubyte(reason)
680
+ return w.finish()
681
+
682
+
683
+ function release_lobby_queue(queue: ref[deque.Deque[LobbyEvent]]) -> void:
684
+ while true:
685
+ let ev = queue.pop_front()
686
+ match ev:
687
+ Option.none:
688
+ break
689
+ Option.some as p:
690
+ var e = p.value
691
+ e.release()
692
+
693
+
694
+ function release_slots(slots: ref[vec.Vec[PlayerSlot]]) -> void:
695
+ var index: ptr_uint = 0
696
+ while index < slots.len():
697
+ let slot_ptr = slots.get(index) else:
698
+ return
699
+ unsafe: read(slot_ptr).player_name.release()
700
+ index += 1
701
+
702
+ const beacon_magic: array[ubyte, 8] = array[ubyte, 8](0x4D, 0x54, 0x4C, 0x42, 0x59, 0x00, 0x00, 0x00)
703
+
704
+ const beacon_probe_len: ptr_uint = 8
705
+ const beacon_recv_timeout: uint = 60
706
+
707
+
708
+ public function build_beacon_probe() -> bytes.Bytes:
709
+ var w = bin.Writer.with_capacity(beacon_probe_len)
710
+ w.write_bytes(beacon_magic.as_span())
711
+ return w.finish()
712
+
713
+
714
+ public function is_beacon_probe(data: span[ubyte]) -> bool:
715
+ if data.len < beacon_probe_len:
716
+ return false
717
+ return data[0] == beacon_magic[0] and data[1] == beacon_magic[1] and data[2] == beacon_magic[2] and data[3] == beacon_magic[3]
718
+
719
+
720
+ public function build_beacon_response(info: ref[LobbyInfo]) -> bytes.Bytes:
721
+ var w = bin.Writer.with_capacity(16)
722
+ w.write_bytes(beacon_magic.as_span())
723
+ encode_lobby_info_payload(ref_of(w), info)
724
+ return w.finish()
725
+
726
+
727
+ public function parse_beacon_response(data: span[ubyte]) -> Result[LobbyInfo, net.Error]:
728
+ if data.len < 10z:
729
+ return Result[LobbyInfo, net.Error].failure(
730
+ error = net.Error(code = -1, message = string.String.from_str("beacon response too short"))
731
+ )
732
+ var r = bin.reader(data)
733
+ match r.read_bytes(beacon_probe_len):
734
+ Result.failure:
735
+ return Result[LobbyInfo, net.Error].failure(
736
+ error = net.Error(code = -1, message = string.String.from_str("beacon response malformed"))
737
+ )
738
+ Result.success as bp:
739
+ bp.value.release()
740
+ return decode_lobby_info_payload_offset(data, beacon_probe_len)
741
+
742
+
743
+ function decode_lobby_info_payload_offset(data: span[ubyte], offset: ptr_uint) -> Result[LobbyInfo, net.Error]:
744
+ if data.len < offset + 2z:
745
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
746
+ code = -1,
747
+ message = string.String.from_str("lobby info payload truncated")
748
+ ))
749
+ var r = bin.reader(data)
750
+ match r.read_bytes(offset):
751
+ Result.failure:
752
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
753
+ code = -1,
754
+ message = string.String.from_str("lobby info malformed")
755
+ ))
756
+ Result.success:
757
+ pass
758
+ var player_count: ubyte = 0
759
+ match r.read_ubyte():
760
+ Result.failure:
761
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
762
+ code = -1,
763
+ message = string.String.from_str("lobby info malformed")
764
+ ))
765
+ Result.success as pc:
766
+ player_count = pc.value
767
+ var max_players: ubyte = 0
768
+ match r.read_ubyte():
769
+ Result.failure:
770
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
771
+ code = -1,
772
+ message = string.String.from_str("lobby info malformed")
773
+ ))
774
+ Result.success as mp:
775
+ max_players = mp.value
776
+ match r.read_str():
777
+ Result.failure:
778
+ return Result[LobbyInfo, net.Error].failure(error = net.Error(
779
+ code = -1,
780
+ message = string.String.from_str("lobby info malformed")
781
+ ))
782
+ Result.success as sp:
783
+ return Result[LobbyInfo, net.Error].success(value = LobbyInfo(
784
+ name = sp.value,
785
+ player_count = player_count,
786
+ max_players = max_players,
787
+ player_names = vec.Vec[string.String].create(),
788
+ game_data = bytes.Bytes.empty()
789
+ ))
790
+
791
+
792
+ public async function respond_to_beacon(
793
+ socket: net.UdpSocket,
794
+ info: ref[LobbyInfo]
795
+ ) -> void:
796
+ let recv_task = socket.recv_from(1500)
797
+ var frame: uint = 0
798
+ while frame < beacon_recv_timeout:
799
+ if aio.completed(recv_task):
800
+ let recv_result = aio.result(recv_task)
801
+ match recv_result:
802
+ Result.success as dp:
803
+ var datagram = dp.value
804
+ defer datagram.data.release()
805
+ defer datagram.source.release()
806
+ if is_beacon_probe(datagram.data.as_span()):
807
+ var response = build_beacon_response(info)
808
+ defer response.release()
809
+ let _ = await socket.send_to(response.as_span(), datagram.source)
810
+ Result.failure:
811
+ pass
812
+ await aio.sleep(100)
813
+ frame += 1