Add Kotlin RUDP endpoint client sugar

This commit is contained in:
Meta Vens 2026-06-16 04:39:22 +01:00
commit fa985255da
3 changed files with 89 additions and 9 deletions

View file

@ -242,9 +242,11 @@ Current progress:
max in-flight frame pressure, and latest-frame cursors.
- Kotlin now has a branded `CultMesh` facade matching the C#/TypeScript/Python
entrypoint pattern for local nodes, Verse catalogs, peer catalogs, authority
lease catalogs, stream catalogs, and RUDP client/server construction. The
facade delegates to the same owners as the top-level helpers instead of
creating a second authority path.
lease catalogs, stream catalogs, and RUDP client/server construction. Kotlin
also parses advertised `rudp://host:port` endpoints and can build a RUDP
client directly from a peer card contact hint. The facade delegates to the
same owners as the top-level helpers instead of creating a second authority
path.
- The remaining parity work is to move the remaining TCP/LiteNetLib/WebSocket
service bodies behind their shared transport ports, then broaden RUDP interop
into automatic reconnect orchestration and the wider schema-message matrix.

View file

@ -145,11 +145,16 @@ CultMesh.createRudpServer(
connectionId = 0x10203040,
tuning = CultNetRudpSocketTuning(maxFragmentBytes = 1024),
).use { server ->
CultMesh.createRudpClient(
val peer = CultMeshPeerCard(
peerId = "kotlin-server",
verseId = "local",
endpoints = listOf("rudp://127.0.0.1:${server.localPort}"),
roles = listOf("schema"),
)
CultMesh.createRudpClientForPeer(
runtimeId = "kotlin-client",
connectionId = 0x10203040,
remoteHost = "127.0.0.1",
remotePort = server.localPort,
peer = peer,
).use { client ->
client.connect("join")
check(pumpRudpPairUntilConnected(client, server))
@ -167,6 +172,10 @@ that already has its own receive loop, use `connectAndWait(...)`; for two
same-process transports, use `pumpRudpPairUntilConnected(...)` to drive both
sides through the handshake.
For lower-level code, `CultMesh.parseRudpEndpoint("rudp://127.0.0.1:4100")`
returns the host/port pair, and `CultMesh.createRudpClient(...)` accepts either
that parsed endpoint or the endpoint string directly.
Kotlin also exposes the shared reconnect-policy helper for peers that need a
portable backoff document without inventing a JVM-only delay dialect:

View file

@ -312,6 +312,54 @@ object CultMesh {
bindPort = bindPort,
tuning = tuning,
)
fun parseRudpEndpoint(endpoint: String): CultNetRudpEndpoint = cultNetRudpEndpoint(endpoint)
fun createRudpClient(
runtimeId: String,
connectionId: Long,
endpoint: CultNetRudpEndpoint,
bindHost: String = "127.0.0.1",
bindPort: Int = 0,
tuning: CultNetRudpSocketTuning = CultNetRudpSocketTuning(),
): CultNetRudpSocketTransportConnection = createRudpClient(
runtimeId = runtimeId,
connectionId = connectionId,
remoteHost = endpoint.host,
remotePort = endpoint.port,
bindHost = bindHost,
bindPort = bindPort,
tuning = tuning,
)
fun createRudpClient(
runtimeId: String,
connectionId: Long,
endpoint: String,
bindHost: String = "127.0.0.1",
bindPort: Int = 0,
tuning: CultNetRudpSocketTuning = CultNetRudpSocketTuning(),
): CultNetRudpSocketTransportConnection = createRudpClient(
runtimeId = runtimeId,
connectionId = connectionId,
endpoint = parseRudpEndpoint(endpoint),
bindHost = bindHost,
bindPort = bindPort,
tuning = tuning,
)
fun createRudpClientForPeer(
runtimeId: String,
connectionId: Long,
peer: CultMeshPeerCard,
bindHost: String = "127.0.0.1",
bindPort: Int = 0,
tuning: CultNetRudpSocketTuning = CultNetRudpSocketTuning(),
): CultNetRudpSocketTransportConnection {
val endpoint = peer.endpoints.firstOrNull { it.startsWith("rudp://", ignoreCase = true) }
?: throw IOException("Peer ${peer.peerId} does not advertise a RUDP endpoint")
return createRudpClient(runtimeId, connectionId, endpoint, bindHost, bindPort, tuning)
}
}
data class CultNetFrame(val opcode: Int, val payload: ByteArray)
@ -325,6 +373,10 @@ data class CultNetTransportStats(
data class CultNetTransportFrame(val channelId: String, val payload: ByteArray)
data class CultNetRudpEndpoint(val host: String, val port: Int) {
val uri: String get() = "rudp://$host:$port"
}
data class CultNetReconnectPolicy(
val schemaVersion: String = "cultnet.reconnect_policy.v0",
val policyId: String = "default",
@ -2021,6 +2073,15 @@ data class CultNetRudpSocketTuning(
val maxPendingReliablePackets: Int? = null,
)
fun cultNetRudpEndpoint(endpoint: String): CultNetRudpEndpoint {
val uri = URI(endpoint)
if (!uri.scheme.equals("rudp", ignoreCase = true)) throw IOException("RUDP endpoint must use the rudp scheme")
val host = uri.host ?: throw IOException("RUDP endpoint must include a host")
val port = uri.port
if (port <= 0 || port > 65535) throw IOException("RUDP endpoint must include a valid port")
return CultNetRudpEndpoint(host, port)
}
fun cultNetRudpServer(
runtimeId: String,
connectionId: Long,
@ -3193,11 +3254,19 @@ private fun rudpSocketTransportErgonomicFactoriesCarrySchemaFrames() {
connectionId = connectionId,
tuning = CultNetRudpSocketTuning(resendDelayMs = 25, maxFragmentBytes = 8, maxPendingReliablePackets = 16),
).use { server ->
CultMesh.createRudpClient(
val endpoint = CultMesh.parseRudpEndpoint("rudp://127.0.0.1:${server.localPort}")
check(endpoint.host == "127.0.0.1")
check(endpoint.port == server.localPort)
val peer = CultMeshPeerCard(
peerId = "kotlin-rudp-sugar-peer",
verseId = "local",
endpoints = listOf(endpoint.uri),
roles = listOf("schema"),
)
CultMesh.createRudpClientForPeer(
runtimeId = "kotlin-rudp-sugar-client",
connectionId = connectionId,
remoteHost = "127.0.0.1",
remotePort = server.localPort,
peer = peer,
tuning = CultNetRudpSocketTuning(resendDelayMs = 25, maxFragmentBytes = 8, maxPendingReliablePackets = 16),
).use { client ->
client.connect("join")