feat(udp): Udp.* - polled IPv4 datagrams on macOS and Windows

Udp.open/port/send/recv/from_ip/from_port/close/resolve/local_ip/ip/ip_text, native
BSD sockets (udp.ll) and Winsock (udp_win.ll, -lws2_32), linked only when a program
uses Udp.*; example, docs and tests.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
Orkun ÇAKILKAYA 2026-09-17 11:16:14 +03:00
parent 6a7f1dd393
commit ebb1a00352
24 changed files with 68630 additions and 66360 deletions

View file

@ -0,0 +1,9 @@
---
id: udp
title: Udp
order: 9
---
Plain IPv4 datagrams, polled — the transport under a game's own netcode: peer-to-peer play, a LAN lobby, a STUN query. Open a socket with <a href="udp-open.html"><code>Udp.open</code></a>, send with <a href="udp-send.html"><code>Udp.send</code></a>, and each frame drain what arrived with <a href="udp-recv.html"><code>Udp.recv</code></a>, which returns <code>0</code> when nothing is waiting and never blocks. The sender of the last datagram read is <a href="udp-from_ip.html"><code>Udp.from_ip</code></a> / <a href="udp-from_port.html"><code>from_port</code></a>.
An address is an <code>int</code>: <code>a.b.c.d</code> is <code>(a &lt;&lt; 24) | (b &lt;&lt; 16) | (c &lt;&lt; 8) | d</code>, converted by <a href="udp-ip.html"><code>Udp.ip</code></a> and <a href="udp-ip_text.html"><code>Udp.ip_text</code></a>. Datagrams can be lost, repeated and reordered; the protocol a game builds on top says what to do about that. Like <code>Http.*</code> it is <strong>out-of-band</strong> and must never feed the deterministic lockstep/replay simulation directly. BSD sockets on macOS, Winsock on Windows.

View file

@ -0,0 +1,23 @@
---
id: udp-close
name: Udp.close
category: udp
kind: namespace-method
tokens: Udp.close
sig: Udp.close(socket) -> void
tip: Close a socket.
order: 6
ns: Udp
member: close
---
Closes the socket and frees its handle.
```ludic
program Demo {
entry {
let s = Udp.open(port: 0)
Udp.close(socket: s)
}
}
```

View file

@ -0,0 +1,23 @@
---
id: udp-from_ip
name: Udp.from_ip
category: udp
kind: namespace-method
tokens: Udp.from_ip
sig: Udp.from_ip(socket) -> int
tip: The sender of the last datagram read.
order: 4
ns: Udp
member: from_ip
---
The address the last datagram read on this socket came from - the one to answer.
```ludic
program Demo {
entry {
let s = Udp.open(port: 0)
print(Udp.from_ip(socket: s))
}
}
```

View file

@ -0,0 +1,23 @@
---
id: udp-from_port
name: Udp.from_port
category: udp
kind: namespace-method
tokens: Udp.from_port
sig: Udp.from_port(socket) -> int
tip: The sender's port.
order: 5
ns: Udp
member: from_port
---
The port the last datagram read on this socket came from.
```ludic
program Demo {
entry {
let s = Udp.open(port: 0)
print(Udp.from_port(socket: s))
}
}
```

View file

@ -0,0 +1,22 @@
---
id: udp-ip
name: Udp.ip
category: udp
kind: namespace-method
tokens: Udp.ip
sig: Udp.ip(text) -> int
tip: Parse a dotted address.
order: 9
ns: Udp
member: ip
---
Turns <code>"a.b.c.d"</code> into an address, or <code>0</code> when the text is not one.
```ludic
program Demo {
entry {
print(Udp.ip(text: "10.0.0.2"))
}
}
```

View file

@ -0,0 +1,22 @@
---
id: udp-ip_text
name: Udp.ip_text
category: udp
kind: namespace-method
tokens: Udp.ip_text
sig: Udp.ip_text(ip) -> string
tip: Format an address.
order: 10
ns: Udp
member: ip_text
---
Turns an address back into <code>"a.b.c.d"</code>.
```ludic
program Demo {
entry {
print(Udp.ip_text(ip: Udp.ip(text: "192.168.1.11")))
}
}
```

View file

@ -0,0 +1,22 @@
---
id: udp-local_ip
name: Udp.local_ip
category: udp
kind: namespace-method
tokens: Udp.local_ip
sig: Udp.local_ip() -> int
tip: This machine's address.
order: 8
ns: Udp
member: local_ip
---
The address this machine reaches the internet from - the one a player on the same network would dial. No packet is sent; <code>0</code> when there is no route.
```ludic
program Demo {
entry {
print(Udp.ip_text(ip: Udp.local_ip()))
}
}
```

View file

@ -0,0 +1,23 @@
---
id: udp-open
name: Udp.open
category: udp
kind: namespace-method
tokens: Udp.open
sig: Udp.open(port) -> int
tip: Open a socket on a port.
order: 0
ns: Udp
member: open
---
Opens a non-blocking IPv4 socket bound to <code>port</code> on every interface and returns its handle, or <code>0</code> when it could not. Port <code>0</code> lets the system pick a free one; <a href="udp-port.html"><code>Udp.port</code></a> says which.
```ludic
program Demo {
entry {
let s = Udp.open(port: 0)
print(s > 0)
}
}
```

View file

@ -0,0 +1,23 @@
---
id: udp-port
name: Udp.port
category: udp
kind: namespace-method
tokens: Udp.port
sig: Udp.port(socket) -> int
tip: The port a socket is bound to.
order: 1
ns: Udp
member: port
---
Returns the port the socket is bound to - the one the system chose when it was opened on port <code>0</code>.
```ludic
program Demo {
entry {
let s = Udp.open(port: 0)
print(Udp.port(socket: s) > 0)
}
}
```

View file

@ -0,0 +1,24 @@
---
id: udp-recv
name: Udp.recv
category: udp
kind: namespace-method
tokens: Udp.recv
sig: Udp.recv(socket, bytes, cap) -> int
tip: Read the next waiting datagram.
order: 3
ns: Udp
member: recv
---
Copies the next waiting datagram, up to <code>cap</code> bytes, into <code>bytes</code> and returns its length, or <code>0</code> when nothing is waiting. It never blocks: call it in a loop each frame until it returns <code>0</code>. The sender is <a href="udp-from_ip.html"><code>Udp.from_ip</code></a> / <a href="udp-from_port.html"><code>from_port</code></a>.
```ludic
program Demo {
entry {
let s = Udp.open(port: 0)
let buf = Memory.bytes(64)
print(Udp.recv(socket: s, bytes: buf, cap: 64))
}
}
```

View file

@ -0,0 +1,22 @@
---
id: udp-resolve
name: Udp.resolve
category: udp
kind: namespace-method
tokens: Udp.resolve
sig: Udp.resolve(name) -> int
tip: Look a host name up.
order: 7
ns: Udp
member: resolve
---
Returns the first IPv4 address of a host name, or <code>0</code>. It waits for the system's resolver, so call it outside the frame loop.
```ludic
program Demo {
entry {
print(Udp.ip_text(ip: Udp.resolve(name: "localhost")))
}
}
```

View file

@ -0,0 +1,25 @@
---
id: udp-send
name: Udp.send
category: udp
kind: namespace-method
tokens: Udp.send
sig: Udp.send(socket, ip, port, bytes, len) -> int
tip: Send one datagram.
order: 2
ns: Udp
member: send
---
Sends the first <code>len</code> bytes of <code>bytes</code> to <code>ip</code>:<code>port</code> as one datagram and returns the number of bytes sent, or <code>-1</code>.
```ludic
program Demo {
entry {
let s = Udp.open(port: 0)
let buf = Memory.bytes(4)
Memory.poke(buf, 0, 1)
print(Udp.send(socket: s, ip: Udp.ip(text: "127.0.0.1"), port: Udp.port(socket: s), bytes: buf, len: 1))
}
}
```