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.
- checksums.yaml +4 -4
- data/docs/index.html +4 -3
- data/lib/milk_tea/base.rb +1 -1
- data/std/asset_pack.mt +267 -0
- data/std/async/libuv_runtime.mt +539 -0
- data/std/async/mailbox.mt +191 -0
- data/std/async/runtime.mt +87 -0
- data/std/async.mt +87 -0
- data/std/base64.mt +185 -0
- data/std/behavior_tree.mt +396 -0
- data/std/binary.mt +314 -0
- data/std/binary_heap.mt +157 -0
- data/std/bitset.mt +239 -0
- data/std/box2d.mt +535 -0
- data/std/bytes.mt +46 -0
- data/std/c/box2d.mt +1254 -0
- data/std/c/cgltf.mt +647 -0
- data/std/c/cjson.mt +106 -0
- data/std/c/crypto.mt +11 -0
- data/std/c/crypto_support.h +51 -0
- data/std/c/ctype.mt +19 -0
- data/std/c/ctype_bindgen.h +29 -0
- data/std/c/curl.mt +1223 -0
- data/std/c/enet.mt +473 -0
- data/std/c/errno.mt +41 -0
- data/std/c/errno_bindgen.h +53 -0
- data/std/c/flecs.mt +2945 -0
- data/std/c/fs.linux.mt +43 -0
- data/std/c/fs.windows.mt +43 -0
- data/std/c/fs_support.h +954 -0
- data/std/c/gl.mt +2065 -0
- data/std/c/gl_registry_helpers.h +10748 -0
- data/std/c/glfw.mt +499 -0
- data/std/c/libc.mt +167 -0
- data/std/c/libuv.mt +1491 -0
- data/std/c/math.mt +22 -0
- data/std/c/math_bindgen.h +31 -0
- data/std/c/miniaudio.mt +3420 -0
- data/std/c/pcre2.mt +735 -0
- data/std/c/process.mt +58 -0
- data/std/c/process_support.h +1147 -0
- data/std/c/raygui.mt +1602 -0
- data/std/c/raylib.mt +1245 -0
- data/std/c/raymath.mt +163 -0
- data/std/c/rlgl.mt +434 -0
- data/std/c/rpng.mt +60 -0
- data/std/c/rres.mt +187 -0
- data/std/c/sdl3.mt +4132 -0
- data/std/c/sqlite3.mt +951 -0
- data/std/c/stb_image.mt +52 -0
- data/std/c/stb_image_resize2.mt +128 -0
- data/std/c/stb_image_write.mt +19 -0
- data/std/c/stb_rect_pack.mt +38 -0
- data/std/c/stb_truetype.mt +175 -0
- data/std/c/stb_vorbis.mt +31 -0
- data/std/c/stdio.mt +90 -0
- data/std/c/steamworks.h +5769 -0
- data/std/c/steamworks.mt +4771 -0
- data/std/c/string.mt +16 -0
- data/std/c/string_bindgen.h +36 -0
- data/std/c/sync.mt +33 -0
- data/std/c/sync_support.h +219 -0
- data/std/c/terminal.mt +24 -0
- data/std/c/terminal_support.h +408 -0
- data/std/c/time.mt +26 -0
- data/std/c/tls.mt +24 -0
- data/std/c/tls_support.h +626 -0
- data/std/c/tracy.mt +53 -0
- data/std/c/zlib.mt +20 -0
- data/std/c/zlib_support.h +239 -0
- data/std/c/zstd.mt +231 -0
- data/std/cell.mt +60 -0
- data/std/cgltf.mt +115 -0
- data/std/cjson.mt +90 -0
- data/std/cli.mt +768 -0
- data/std/cookie.mt +198 -0
- data/std/counter.mt +160 -0
- data/std/crypto.mt +74 -0
- data/std/cstring.mt +13 -0
- data/std/ctype.mt +52 -0
- data/std/curl/runtime.mt +261 -0
- data/std/curl.mt +38 -0
- data/std/deque.mt +348 -0
- data/std/encoding.mt +93 -0
- data/std/enet.mt +133 -0
- data/std/env.mt +41 -0
- data/std/errno.mt +47 -0
- data/std/flecs.mt +978 -0
- data/std/fmt.mt +271 -0
- data/std/fs.linux.mt +696 -0
- data/std/fs.windows.mt +711 -0
- data/std/fsm.mt +267 -0
- data/std/gl.mt +2062 -0
- data/std/glfw.mt +471 -0
- data/std/goap.mt +342 -0
- data/std/graph.mt +521 -0
- data/std/gzip.mt +77 -0
- data/std/hash.mt +429 -0
- data/std/http/server.mt +810 -0
- data/std/http.mt +1227 -0
- data/std/intern.mt +53 -0
- data/std/jobs.mt +288 -0
- data/std/json.mt +627 -0
- data/std/libc.mt +34 -0
- data/std/libuv.mt +554 -0
- data/std/linear_algebra.mt +243 -0
- data/std/linked_map.mt +372 -0
- data/std/linked_map_view.mt +49 -0
- data/std/linked_set.mt +117 -0
- data/std/log.mt +69 -0
- data/std/map.mt +418 -0
- data/std/math.mt +24 -0
- data/std/mem/arena.mt +137 -0
- data/std/mem/endian.mt +51 -0
- data/std/mem/heap.mt +298 -0
- data/std/mem/pool.mt +164 -0
- data/std/mem/stack.mt +47 -0
- data/std/mem/tracking.mt +119 -0
- data/std/miniaudio.mt +1299 -0
- data/std/multiset.mt +191 -0
- data/std/net/channel.mt +727 -0
- data/std/net/clock.mt +237 -0
- data/std/net/discovery.mt +277 -0
- data/std/net/lobby.mt +813 -0
- data/std/net/manager.mt +539 -0
- data/std/net/mux.mt +839 -0
- data/std/net/nat.mt +122 -0
- data/std/net/packet.mt +219 -0
- data/std/net/punch.mt +162 -0
- data/std/net/rpc.mt +119 -0
- data/std/net/session.mt +1157 -0
- data/std/net/stun.mt +252 -0
- data/std/net/sync.mt +183 -0
- data/std/net/turn.mt +459 -0
- data/std/net.mt +2944 -0
- data/std/oauth2.mt +408 -0
- data/std/option.mt +51 -0
- data/std/ordered_map.mt +497 -0
- data/std/ordered_set.mt +348 -0
- data/std/path.mt +368 -0
- data/std/pcre2/runtime.mt +35 -0
- data/std/pcre2.mt +78 -0
- data/std/priority_queue.mt +55 -0
- data/std/process.mt +837 -0
- data/std/queue.mt +55 -0
- data/std/random.mt +146 -0
- data/std/raygui.mt +99 -0
- data/std/raylib/debug_console.mt +71 -0
- data/std/raylib/easing.mt +388 -0
- data/std/raylib/packed_assets.mt +255 -0
- data/std/raylib/runtime.mt +30 -0
- data/std/raylib/tracy_gpu.mt +37 -0
- data/std/raylib.mt +1510 -0
- data/std/raymath.mt +153 -0
- data/std/result.mt +89 -0
- data/std/rlgl.mt +268 -0
- data/std/rpng.mt +46 -0
- data/std/rres.mt +36 -0
- data/std/sdl3/runtime.mt +54 -0
- data/std/sdl3.mt +1731 -0
- data/std/serialize.mt +68 -0
- data/std/set.mt +124 -0
- data/std/spatial.mt +176 -0
- data/std/sqlite3.mt +151 -0
- data/std/stack.mt +55 -0
- data/std/stb_image.mt +47 -0
- data/std/stb_image_resize2.mt +41 -0
- data/std/stb_image_write.mt +17 -0
- data/std/stb_rect_pack.mt +15 -0
- data/std/stb_truetype.mt +77 -0
- data/std/stb_vorbis.mt +16 -0
- data/std/stdio.mt +88 -0
- data/std/steamworks.mt +1542 -0
- data/std/str.mt +293 -0
- data/std/string.mt +234 -0
- data/std/sync.mt +194 -0
- data/std/tar.mt +704 -0
- data/std/terminal.mt +1002 -0
- data/std/testing.mt +266 -0
- data/std/thread.mt +120 -0
- data/std/time.mt +105 -0
- data/std/tls.mt +616 -0
- data/std/toml.mt +1310 -0
- data/std/tracy.mt +42 -0
- data/std/uri.mt +118 -0
- data/std/url.mt +372 -0
- data/std/vec.mt +433 -0
- data/std/zstd.mt +94 -0
- metadata +187 -2
data/std/net/clock.mt
ADDED
|
@@ -0,0 +1,237 @@
|
|
|
1
|
+
import std.async as aio
|
|
2
|
+
import std.binary as bin
|
|
3
|
+
import std.bytes as bytes
|
|
4
|
+
import std.libuv as libuv
|
|
5
|
+
import std.net as net
|
|
6
|
+
import std.string as string
|
|
7
|
+
|
|
8
|
+
const clock_magic: array[ubyte, 8] = array[ubyte, 8](
|
|
9
|
+
0x4Dub, 0x54ub, 0x43ub, 0x4Cub,
|
|
10
|
+
0x4Fub, 0x43ub, 0x4Bub, 0x00ub
|
|
11
|
+
)
|
|
12
|
+
|
|
13
|
+
const sync_request: ubyte = 0x01
|
|
14
|
+
const sync_response: ubyte = 0x02
|
|
15
|
+
|
|
16
|
+
const header_bytes: ptr_uint = 9
|
|
17
|
+
const request_bytes: ptr_uint = 17
|
|
18
|
+
const response_bytes: ptr_uint = 25
|
|
19
|
+
|
|
20
|
+
const err_send_failed: int = -1
|
|
21
|
+
const err_recv_failed: int = -2
|
|
22
|
+
const err_timeout: int = -3
|
|
23
|
+
const err_bad_packet: int = -4
|
|
24
|
+
|
|
25
|
+
public struct ClockSync:
|
|
26
|
+
offset_us: int
|
|
27
|
+
rtt_us: ptr_uint
|
|
28
|
+
|
|
29
|
+
public struct TickClock:
|
|
30
|
+
tick: uint
|
|
31
|
+
rate: uint
|
|
32
|
+
epoch: ulong
|
|
33
|
+
|
|
34
|
+
public struct Error:
|
|
35
|
+
code: int
|
|
36
|
+
message: string.String
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
extending Error:
|
|
40
|
+
public editable function release() -> void:
|
|
41
|
+
this.message.release()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
function clock_error(code: int, msg: str) -> Error:
|
|
45
|
+
return Error(code = code, message = string.String.from_str(msg))
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
public function monotonic_ns() -> ulong:
|
|
49
|
+
return libuv.hrtime()
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
public function monotonic_us() -> ulong:
|
|
53
|
+
return libuv.hrtime() / 1000ul
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
public function build_request(t1: ulong) -> bytes.Bytes:
|
|
57
|
+
var w = bin.Writer.with_capacity(request_bytes)
|
|
58
|
+
var i: ptr_uint = 0
|
|
59
|
+
while i < 8z:
|
|
60
|
+
w.write_ubyte(clock_magic[i])
|
|
61
|
+
i += 1
|
|
62
|
+
w.write_ubyte(sync_request)
|
|
63
|
+
w.write_ulong(t1)
|
|
64
|
+
return w.finish()
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
public function build_response(t1_client: ulong, t2_server: ulong) -> bytes.Bytes:
|
|
68
|
+
var w = bin.Writer.with_capacity(response_bytes)
|
|
69
|
+
var i: ptr_uint = 0
|
|
70
|
+
while i < 8z:
|
|
71
|
+
w.write_ubyte(clock_magic[i])
|
|
72
|
+
i += 1
|
|
73
|
+
w.write_ubyte(sync_response)
|
|
74
|
+
w.write_ulong(t1_client)
|
|
75
|
+
w.write_ulong(t2_server)
|
|
76
|
+
return w.finish()
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
function is_clock_packet(data: span[ubyte]) -> bool:
|
|
80
|
+
if data.len < header_bytes:
|
|
81
|
+
return false
|
|
82
|
+
var i: ptr_uint = 0
|
|
83
|
+
while i < 8z:
|
|
84
|
+
if data[i] != clock_magic[i]:
|
|
85
|
+
return false
|
|
86
|
+
i += 1
|
|
87
|
+
return true
|
|
88
|
+
|
|
89
|
+
|
|
90
|
+
public function parse_request(data: span[ubyte]) -> Result[ulong, Error]:
|
|
91
|
+
if data.len < request_bytes or data[8z] != sync_request:
|
|
92
|
+
return Result[ulong, Error].failure(error = clock_error(err_bad_packet, "not a clock sync request"))
|
|
93
|
+
var r = bin.reader(data)
|
|
94
|
+
var _bp = r.read_bytes(9z).map_error(proc(_: bin.Error) -> Error: clock_error(err_bad_packet, "malformed request"))?
|
|
95
|
+
_bp.release()
|
|
96
|
+
return r.read_ulong().map_error(proc(_: bin.Error) -> Error: clock_error(err_bad_packet, "malformed timestamp"))
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
public function parse_response(data: span[ubyte]) -> Result[ClockSync, Error]:
|
|
100
|
+
if data.len < response_bytes or data[8z] != sync_response:
|
|
101
|
+
return Result[ClockSync, Error].failure(error = clock_error(err_bad_packet, "not a clock sync response"))
|
|
102
|
+
var r = bin.reader(data)
|
|
103
|
+
match r.read_bytes(9z):
|
|
104
|
+
Result.failure:
|
|
105
|
+
return Result[ClockSync, Error].failure(error = clock_error(err_bad_packet, "malformed response"))
|
|
106
|
+
Result.success as bp:
|
|
107
|
+
bp.value.release()
|
|
108
|
+
match r.read_ulong():
|
|
109
|
+
Result.failure:
|
|
110
|
+
return Result[ClockSync, Error].failure(error = clock_error(err_bad_packet, "malformed t1"))
|
|
111
|
+
Result.success as t1_payload:
|
|
112
|
+
let t1_client = t1_payload.value
|
|
113
|
+
match r.read_ulong():
|
|
114
|
+
Result.failure:
|
|
115
|
+
return Result[ClockSync, Error].failure(error = clock_error(err_bad_packet, "malformed t2"))
|
|
116
|
+
Result.success as t2_payload:
|
|
117
|
+
let t2_svr = t2_payload.value
|
|
118
|
+
let t4 = libuv.hrtime()
|
|
119
|
+
let rtt = t4 - t1_client
|
|
120
|
+
let half_rtt = rtt / 2ul
|
|
121
|
+
if t2_svr > t1_client + half_rtt:
|
|
122
|
+
let offset_ns = t2_svr - t1_client - half_rtt
|
|
123
|
+
return Result[ClockSync, Error].success(
|
|
124
|
+
value = ClockSync(
|
|
125
|
+
offset_us = int<-(offset_ns / 1000ul),
|
|
126
|
+
rtt_us = ptr_uint<-(rtt / 1000ul)
|
|
127
|
+
)
|
|
128
|
+
)
|
|
129
|
+
return Result[ClockSync, Error].success(
|
|
130
|
+
value = ClockSync(offset_us = 0, rtt_us = ptr_uint<-(rtt / 1000ul))
|
|
131
|
+
)
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
public async function measure_offset(
|
|
135
|
+
socket: net.UdpSocket,
|
|
136
|
+
peer: net.SocketAddress
|
|
137
|
+
) -> Result[ClockSync, Error]:
|
|
138
|
+
let t1 = libuv.hrtime()
|
|
139
|
+
var request = build_request(t1)
|
|
140
|
+
defer request.release()
|
|
141
|
+
(await socket.send_to(request.as_span(), peer)).map_error(proc(_: net.Error) -> Error:
|
|
142
|
+
clock_error(err_send_failed, "failed to send sync request")
|
|
143
|
+
)?
|
|
144
|
+
|
|
145
|
+
var frame: uint = 0
|
|
146
|
+
let recv_task = socket.recv_from(512)
|
|
147
|
+
while frame < 40:
|
|
148
|
+
if aio.completed(recv_task):
|
|
149
|
+
let recv_result = aio.result(recv_task)
|
|
150
|
+
match recv_result:
|
|
151
|
+
Result.failure:
|
|
152
|
+
return Result[ClockSync, Error].failure(
|
|
153
|
+
error = clock_error(err_recv_failed, "recv failed")
|
|
154
|
+
)
|
|
155
|
+
Result.success as dp:
|
|
156
|
+
var datagram = dp.value
|
|
157
|
+
defer datagram.data.release()
|
|
158
|
+
defer datagram.source.release()
|
|
159
|
+
if is_clock_packet(datagram.data.as_span()):
|
|
160
|
+
let parse_result = parse_response(datagram.data.as_span())
|
|
161
|
+
match parse_result:
|
|
162
|
+
Result.failure:
|
|
163
|
+
continue
|
|
164
|
+
Result.success as sp:
|
|
165
|
+
return Result[ClockSync, Error].success(value = sp.value)
|
|
166
|
+
await aio.sleep(50)
|
|
167
|
+
frame += 1
|
|
168
|
+
|
|
169
|
+
return Result[ClockSync, Error].failure(
|
|
170
|
+
error = clock_error(err_timeout, "clock sync request timed out")
|
|
171
|
+
)
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
public async function respond_to_sync(
|
|
175
|
+
socket: net.UdpSocket
|
|
176
|
+
) -> void:
|
|
177
|
+
var recv_task = socket.recv_from(512)
|
|
178
|
+
var frame: uint = 0
|
|
179
|
+
while frame < 60:
|
|
180
|
+
if aio.completed(recv_task):
|
|
181
|
+
let recv_result = aio.result(recv_task)
|
|
182
|
+
match recv_result:
|
|
183
|
+
Result.success as dp:
|
|
184
|
+
var datagram = dp.value
|
|
185
|
+
defer datagram.data.release()
|
|
186
|
+
defer datagram.source.release()
|
|
187
|
+
if is_clock_packet(datagram.data.as_span()):
|
|
188
|
+
let parse_result = parse_request(datagram.data.as_span())
|
|
189
|
+
match parse_result:
|
|
190
|
+
Result.success as tp:
|
|
191
|
+
let t2 = libuv.hrtime()
|
|
192
|
+
var resp = build_response(tp.value, t2)
|
|
193
|
+
defer resp.release()
|
|
194
|
+
let _ = await socket.send_to(resp.as_span(), datagram.source)
|
|
195
|
+
Result.failure:
|
|
196
|
+
pass
|
|
197
|
+
Result.failure:
|
|
198
|
+
pass
|
|
199
|
+
recv_task = socket.recv_from(512)
|
|
200
|
+
await aio.sleep(100)
|
|
201
|
+
frame += 1
|
|
202
|
+
|
|
203
|
+
|
|
204
|
+
public function tick_clock_new(rate: uint) -> TickClock:
|
|
205
|
+
return TickClock(tick = 0u, rate = rate, epoch = monotonic_ns())
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
public function tick_clock_from_seed(rate: uint, seed_tick: uint, epoch_ns: ulong) -> TickClock:
|
|
209
|
+
return TickClock(tick = seed_tick, rate = rate, epoch = epoch_ns)
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
extending TickClock:
|
|
213
|
+
public editable function advance() -> void:
|
|
214
|
+
this.tick += 1
|
|
215
|
+
|
|
216
|
+
|
|
217
|
+
public function elapsed_ticks() -> uint:
|
|
218
|
+
let now = monotonic_ns()
|
|
219
|
+
let elapsed_ns = now - this.epoch
|
|
220
|
+
let tick_duration_ns = (1000000000ul / ulong<-this.rate)
|
|
221
|
+
if tick_duration_ns == 0ul:
|
|
222
|
+
return 0u
|
|
223
|
+
return uint<-(elapsed_ns / tick_duration_ns)
|
|
224
|
+
|
|
225
|
+
|
|
226
|
+
public function time_to_next_tick() -> ptr_uint:
|
|
227
|
+
let now = monotonic_ns()
|
|
228
|
+
let elapsed_ns = now - this.epoch
|
|
229
|
+
let tick_ns = (1000000000ul / (ulong<-this.rate))
|
|
230
|
+
let into_current = elapsed_ns % tick_ns
|
|
231
|
+
let remaining = tick_ns - into_current
|
|
232
|
+
return (remaining / 1000ul)
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
public function tick_ready() -> bool:
|
|
236
|
+
let elapsed = this.elapsed_ticks()
|
|
237
|
+
return elapsed > this.tick
|
|
@@ -0,0 +1,277 @@
|
|
|
1
|
+
import std.async as aio
|
|
2
|
+
import std.binary as bin
|
|
3
|
+
import std.bytes as bytes
|
|
4
|
+
import std.net as net
|
|
5
|
+
import std.string as string
|
|
6
|
+
import std.vec as vec
|
|
7
|
+
|
|
8
|
+
const discovery_magic: array[ubyte, 4] = array[ubyte, 4](
|
|
9
|
+
ubyte<-0x4D, ubyte<-0x54, ubyte<-0x44, ubyte<-0x53
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const probe_bytes: ptr_uint = 4
|
|
13
|
+
const min_response_bytes: ptr_uint = 11
|
|
14
|
+
const default_discovery_port_offset: int = 1
|
|
15
|
+
|
|
16
|
+
public struct ServerInfo:
|
|
17
|
+
address: net.SocketAddress
|
|
18
|
+
game_port: int
|
|
19
|
+
player_count: ubyte
|
|
20
|
+
max_players: ubyte
|
|
21
|
+
game_name: string.String
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
extending ServerInfo:
|
|
25
|
+
public editable function release() -> void:
|
|
26
|
+
this.address.release()
|
|
27
|
+
this.game_name.release()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
public function build_probe() -> bytes.Bytes:
|
|
31
|
+
var w = bin.Writer.with_capacity(probe_bytes)
|
|
32
|
+
w.write_ubyte(discovery_magic[0])
|
|
33
|
+
w.write_ubyte(discovery_magic[1])
|
|
34
|
+
w.write_ubyte(discovery_magic[2])
|
|
35
|
+
w.write_ubyte(discovery_magic[3])
|
|
36
|
+
return w.finish()
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
public function build_response(
|
|
40
|
+
game_port: int,
|
|
41
|
+
player_count: ubyte,
|
|
42
|
+
max_players: ubyte,
|
|
43
|
+
game_name: str
|
|
44
|
+
) -> bytes.Bytes:
|
|
45
|
+
var w = bin.Writer.with_capacity(probe_bytes + 6z + game_name.len)
|
|
46
|
+
w.write_ubyte(discovery_magic[0])
|
|
47
|
+
w.write_ubyte(discovery_magic[1])
|
|
48
|
+
w.write_ubyte(discovery_magic[2])
|
|
49
|
+
w.write_ubyte(discovery_magic[3])
|
|
50
|
+
w.write_ushort(ushort<-game_port)
|
|
51
|
+
w.write_ubyte(player_count)
|
|
52
|
+
w.write_ubyte(max_players)
|
|
53
|
+
w.write_str(game_name)
|
|
54
|
+
return w.finish()
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
function is_probe(data: span[ubyte]) -> bool:
|
|
58
|
+
if data.len < probe_bytes:
|
|
59
|
+
return false
|
|
60
|
+
return (
|
|
61
|
+
data[0] == discovery_magic[0]
|
|
62
|
+
and data[1] == discovery_magic[1]
|
|
63
|
+
and data[2] == discovery_magic[2]
|
|
64
|
+
and data[3] == discovery_magic[3]
|
|
65
|
+
)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
function discovery_error(message: str) -> net.Error:
|
|
69
|
+
return net.net_error(message)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
function parse_response(data: span[ubyte]) -> Result[ServerInfo, net.Error]:
|
|
73
|
+
if data.len < min_response_bytes:
|
|
74
|
+
return Result[ServerInfo, net.Error].failure(
|
|
75
|
+
error = discovery_error("discovery response too short")
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
if (
|
|
79
|
+
data[0] != discovery_magic[0]
|
|
80
|
+
or data[1] != discovery_magic[1]
|
|
81
|
+
or data[2] != discovery_magic[2]
|
|
82
|
+
or data[3] != discovery_magic[3]
|
|
83
|
+
):
|
|
84
|
+
return Result[ServerInfo, net.Error].failure(
|
|
85
|
+
error = discovery_error("discovery response bad magic")
|
|
86
|
+
)
|
|
87
|
+
|
|
88
|
+
var r = bin.reader(data)
|
|
89
|
+
|
|
90
|
+
var _bp = r.read_bytes(probe_bytes).map_error(proc(_: bin.Error) -> net.Error:
|
|
91
|
+
discovery_error("discovery response malformed header")
|
|
92
|
+
)?
|
|
93
|
+
_bp.release()
|
|
94
|
+
|
|
95
|
+
var game_port: int = 0
|
|
96
|
+
game_port = r.read_ushort().map_error(proc(_: bin.Error) -> net.Error:
|
|
97
|
+
discovery_error("discovery response malformed port")
|
|
98
|
+
)?
|
|
99
|
+
|
|
100
|
+
var player_count: ubyte = 0
|
|
101
|
+
player_count = r.read_ubyte().map_error(proc(_: bin.Error) -> net.Error:
|
|
102
|
+
discovery_error("discovery response malformed player_count")
|
|
103
|
+
)?
|
|
104
|
+
|
|
105
|
+
var max_players: ubyte = 0
|
|
106
|
+
max_players = r.read_ubyte().map_error(proc(_: bin.Error) -> net.Error:
|
|
107
|
+
discovery_error("discovery response malformed max_players")
|
|
108
|
+
)?
|
|
109
|
+
|
|
110
|
+
let game_name = r.read_str().map_error(proc(_: bin.Error) -> net.Error:
|
|
111
|
+
discovery_error("discovery response malformed name")
|
|
112
|
+
)?
|
|
113
|
+
return Result[ServerInfo, net.Error].success(value = ServerInfo(
|
|
114
|
+
address = zero[net.SocketAddress],
|
|
115
|
+
game_port = game_port,
|
|
116
|
+
player_count = player_count,
|
|
117
|
+
max_players = max_players,
|
|
118
|
+
game_name = game_name
|
|
119
|
+
))
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
public async function announce(
|
|
123
|
+
game_port: int,
|
|
124
|
+
max_players: ubyte,
|
|
125
|
+
game_name: str
|
|
126
|
+
) -> void:
|
|
127
|
+
let discovery_port = game_port + default_discovery_port_offset
|
|
128
|
+
match net.ipv4("0.0.0.0", discovery_port):
|
|
129
|
+
Result.failure:
|
|
130
|
+
return
|
|
131
|
+
Result.success as addr_p:
|
|
132
|
+
var announce_addr = addr_p.value
|
|
133
|
+
defer announce_addr.release()
|
|
134
|
+
|
|
135
|
+
match net.udp_bind(announce_addr):
|
|
136
|
+
Result.failure:
|
|
137
|
+
return
|
|
138
|
+
Result.success as bp:
|
|
139
|
+
var socket = bp.value
|
|
140
|
+
defer socket.release()
|
|
141
|
+
|
|
142
|
+
var response = build_response(game_port, ubyte<-0, max_players, game_name)
|
|
143
|
+
defer response.release()
|
|
144
|
+
|
|
145
|
+
while true:
|
|
146
|
+
let recv_task = socket.recv_from(1500)
|
|
147
|
+
var frame: uint = 0
|
|
148
|
+
while frame < 120:
|
|
149
|
+
if aio.completed(recv_task):
|
|
150
|
+
let recv_result = aio.result(recv_task)
|
|
151
|
+
match recv_result:
|
|
152
|
+
Result.success as dp:
|
|
153
|
+
var datagram = dp.value
|
|
154
|
+
if is_probe(datagram.data.as_span()):
|
|
155
|
+
let _ = await socket.send_to(
|
|
156
|
+
response.as_span(),
|
|
157
|
+
datagram.source
|
|
158
|
+
)
|
|
159
|
+
datagram.data.release()
|
|
160
|
+
datagram.source.release()
|
|
161
|
+
break
|
|
162
|
+
Result.failure:
|
|
163
|
+
break
|
|
164
|
+
await aio.sleep(100)
|
|
165
|
+
frame += 1
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
public async function discover_on(
|
|
169
|
+
runtime: aio.Runtime,
|
|
170
|
+
game_port: int,
|
|
171
|
+
timeout_frames: uint
|
|
172
|
+
) -> Result[vec.Vec[ServerInfo], net.Error]:
|
|
173
|
+
match net.ipv4("0.0.0.0", 0):
|
|
174
|
+
Result.failure:
|
|
175
|
+
return Result[vec.Vec[ServerInfo], net.Error].failure(
|
|
176
|
+
error = discovery_error("failed to create local address")
|
|
177
|
+
)
|
|
178
|
+
Result.success as la:
|
|
179
|
+
var local_addr = la.value
|
|
180
|
+
defer local_addr.release()
|
|
181
|
+
|
|
182
|
+
match net.udp_bind_on(runtime, local_addr):
|
|
183
|
+
Result.failure:
|
|
184
|
+
return Result[vec.Vec[ServerInfo], net.Error].failure(
|
|
185
|
+
error = discovery_error("failed to bind discovery socket")
|
|
186
|
+
)
|
|
187
|
+
Result.success as bp:
|
|
188
|
+
var socket = bp.value
|
|
189
|
+
defer socket.release()
|
|
190
|
+
|
|
191
|
+
match socket.set_broadcast(true):
|
|
192
|
+
0:
|
|
193
|
+
pass
|
|
194
|
+
_:
|
|
195
|
+
return Result[vec.Vec[ServerInfo], net.Error].failure(
|
|
196
|
+
error = discovery_error("failed to enable broadcast")
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
let broadcast_addr_result = net.ipv4(
|
|
200
|
+
"255.255.255.255",
|
|
201
|
+
game_port + default_discovery_port_offset
|
|
202
|
+
)
|
|
203
|
+
match broadcast_addr_result:
|
|
204
|
+
Result.failure:
|
|
205
|
+
return Result[vec.Vec[ServerInfo], net.Error].failure(
|
|
206
|
+
error = discovery_error("failed to create broadcast address")
|
|
207
|
+
)
|
|
208
|
+
Result.success as broad_addr:
|
|
209
|
+
var broadcast_addr = broad_addr.value
|
|
210
|
+
defer broadcast_addr.release()
|
|
211
|
+
|
|
212
|
+
var probe = build_probe()
|
|
213
|
+
defer probe.release()
|
|
214
|
+
|
|
215
|
+
var results = vec.Vec[ServerInfo].create()
|
|
216
|
+
|
|
217
|
+
var burst: uint = 0
|
|
218
|
+
while burst < 3:
|
|
219
|
+
let _ = await socket.send_to(probe.as_span(), broadcast_addr)
|
|
220
|
+
burst += 1
|
|
221
|
+
|
|
222
|
+
var recv_task = socket.recv_from(1500)
|
|
223
|
+
var frame: uint = 0
|
|
224
|
+
while frame < timeout_frames:
|
|
225
|
+
if aio.completed(recv_task):
|
|
226
|
+
let recv_result = aio.result(recv_task)
|
|
227
|
+
match recv_result:
|
|
228
|
+
Result.success as dp:
|
|
229
|
+
var datagram = dp.value
|
|
230
|
+
let parse_result = parse_response(
|
|
231
|
+
datagram.data.as_span()
|
|
232
|
+
)
|
|
233
|
+
datagram.data.release()
|
|
234
|
+
match parse_result:
|
|
235
|
+
Result.success as sp:
|
|
236
|
+
var info = sp.value
|
|
237
|
+
info.address = datagram.source
|
|
238
|
+
datagram.source = zero[net.SocketAddress]
|
|
239
|
+
if not already_discovered(
|
|
240
|
+
ref_of(results),
|
|
241
|
+
ref_of(info)
|
|
242
|
+
):
|
|
243
|
+
results.push(info)
|
|
244
|
+
else:
|
|
245
|
+
info.release()
|
|
246
|
+
Result.failure:
|
|
247
|
+
datagram.source.release()
|
|
248
|
+
Result.failure:
|
|
249
|
+
break
|
|
250
|
+
recv_task = socket.recv_from(1500)
|
|
251
|
+
await aio.sleep(50)
|
|
252
|
+
frame += 1
|
|
253
|
+
|
|
254
|
+
return Result[vec.Vec[ServerInfo], net.Error].success(
|
|
255
|
+
value = results
|
|
256
|
+
)
|
|
257
|
+
|
|
258
|
+
|
|
259
|
+
public async function discover(
|
|
260
|
+
game_port: int,
|
|
261
|
+
timeout_frames: uint
|
|
262
|
+
) -> Result[vec.Vec[ServerInfo], net.Error]:
|
|
263
|
+
return await discover_on(aio.current_runtime(), game_port, timeout_frames)
|
|
264
|
+
|
|
265
|
+
|
|
266
|
+
function already_discovered(
|
|
267
|
+
results: ref[vec.Vec[ServerInfo]],
|
|
268
|
+
info: ref[ServerInfo]
|
|
269
|
+
) -> bool:
|
|
270
|
+
var index: ptr_uint = 0
|
|
271
|
+
while index < results.len():
|
|
272
|
+
let existing_ptr = results.get(index) else:
|
|
273
|
+
break
|
|
274
|
+
if unsafe: read(existing_ptr).address.equal(info.address):
|
|
275
|
+
return true
|
|
276
|
+
index += 1
|
|
277
|
+
return false
|