Route C# mesh clients through RUDP endpoints

This commit is contained in:
Meta Vens 2026-06-17 13:54:11 +01:00
commit 9621d310f0
3 changed files with 147 additions and 8 deletions

View file

@ -339,7 +339,8 @@ namespace GameCult.Mesh
public Action<Client>? ConfigureClient { get; set; }
/// <summary>
/// Gets or sets the schema-v0 client factory. Defaults to the C# LiteNetLib adapter.
/// Gets or sets the schema-v0 client factory.
/// Defaults to endpoint selection: rudp:// uses RUDP and cultnet:// uses LiteNetLib.
/// </summary>
public Func<ICultNetSchemaClient>? CreateClient { get; set; }
}
@ -375,7 +376,7 @@ namespace GameCult.Mesh
var completion = new TaskCompletionSource<CultMeshPeerExchangeResponseMessage>(
TaskCreationOptions.RunContinuationsAsynchronously);
using var client = CreateClient();
using var client = CreateClient(endpoint);
client.OnCultNet<CultMeshPeerExchangeResponseMessage>(response =>
{
if (string.Equals(response.MessageId, messageId, StringComparison.Ordinal))
@ -431,10 +432,10 @@ namespace GameCult.Mesh
return count;
}
private ICultNetSchemaClient CreateClient()
private ICultNetSchemaClient CreateClient(string endpoint)
{
return _options.CreateClient?.Invoke()
?? CultNetSchemaClients.CreateLiteNetLib(_options.Security, _options.ConfigureClient);
?? CultNetSchemaClients.CreateForEndpoint(endpoint, _options.Security, _options.ConfigureClient);
}
private async Task WaitForConnectionAsync(ICultNetSchemaClient client, string endpoint)

View file

@ -157,7 +157,8 @@ namespace GameCult.Mesh
public Action<Client>? ConfigureClient { get; set; }
/// <summary>
/// Gets or sets the schema-v0 client factory. Defaults to the C# LiteNetLib adapter.
/// Gets or sets the schema-v0 client factory.
/// Defaults to endpoint selection: rudp:// uses RUDP and cultnet:// uses LiteNetLib.
/// </summary>
public Func<ICultNetSchemaClient>? CreateClient { get; set; }
}
@ -190,7 +191,7 @@ namespace GameCult.Mesh
var completion = new TaskCompletionSource<CultMeshVerseCatalogResponseMessage>(
TaskCreationOptions.RunContinuationsAsynchronously);
using var client = CreateClient();
using var client = CreateClient(endpoint);
client.OnCultNet<CultMeshVerseCatalogResponseMessage>(response =>
{
if (string.Equals(response.MessageId, messageId, StringComparison.Ordinal))
@ -238,10 +239,10 @@ namespace GameCult.Mesh
return count;
}
private ICultNetSchemaClient CreateClient()
private ICultNetSchemaClient CreateClient(string endpoint)
{
return _options.CreateClient?.Invoke()
?? CultNetSchemaClients.CreateLiteNetLib(_options.Security, _options.ConfigureClient);
?? CultNetSchemaClients.CreateForEndpoint(endpoint, _options.Security, _options.ConfigureClient);
}
private async Task WaitForConnectionAsync(ICultNetSchemaClient client, string endpoint)

View file

@ -1836,6 +1836,143 @@ namespace GameCult.Networking.Tests
Assert.That(response.Schemas.Single().SchemaId, Is.EqualTo("rudp.schema.test"));
}
[Test]
public async Task CultMeshVerseDiscoveryClient_Fetches_Over_RudpEndpoint()
{
using var serverSocket = BindUdpSocket();
using var server = new CultNetRudpSocketTransportConnection(new CultNetRudpSocketTransportOptions
{
RuntimeId = "csharp-rudp-verse-discovery-server",
Mode = CultNetRudpSocketMode.Server,
Socket = serverSocket,
ConnectionId = 0x43554c54,
InitialSequence = 100,
TransportId = "schema-rudp",
ResendDelayMs = 25,
MaxFragmentBytes = 1024
});
var serverDone = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var serverThread = new Thread(() =>
{
try
{
while (!serverDone.Task.IsCompleted)
{
var request = server.ReceiveSchemaMessageOnce<CultMeshVerseCatalogRequestMessage>();
server.PollResends();
if (request == null)
{
Thread.Sleep(5);
continue;
}
server.SendSchemaMessage(new CultMeshVerseCatalogResponseMessage
{
MessageId = request.MessageId,
Verses =
[
new CultMeshVerseDescriptor(
"rudp-verse",
"RUDP Verse",
CultMeshVerseAuthorityModel.OperatorCluster,
new CultMeshVerseCompatibility("cultmesh.v0", "rules"),
discoveryEndpoints: ["rudp://127.0.0.1"]).ToMessage()
]
});
serverDone.TrySetResult();
return;
}
}
catch (Exception error)
{
serverDone.TrySetException(error);
}
})
{
IsBackground = true
};
serverThread.Start();
var client = new CultMeshVerseDiscoveryClient(new CultMeshVerseDiscoveryClientOptions
{
ConnectTimeout = TimeSpan.FromSeconds(2),
ResponseTimeout = TimeSpan.FromSeconds(2)
});
var response = await client.FetchAsync($"rudp://127.0.0.1:{((IPEndPoint)serverSocket.LocalEndPoint!).Port}");
await AwaitWithTimeout(serverDone.Task, TimeSpan.FromSeconds(2));
Assert.That(response.Verses.Single().VerseId, Is.EqualTo("rudp-verse"));
}
[Test]
public async Task CultMeshPeerExchangeClient_Fetches_Over_RudpEndpoint()
{
using var serverSocket = BindUdpSocket();
using var server = new CultNetRudpSocketTransportConnection(new CultNetRudpSocketTransportOptions
{
RuntimeId = "csharp-rudp-peer-exchange-server",
Mode = CultNetRudpSocketMode.Server,
Socket = serverSocket,
ConnectionId = 0x43554c54,
InitialSequence = 100,
TransportId = "schema-rudp",
ResendDelayMs = 25,
MaxFragmentBytes = 1024
});
var serverDone = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously);
var serverThread = new Thread(() =>
{
try
{
while (!serverDone.Task.IsCompleted)
{
var request = server.ReceiveSchemaMessageOnce<CultMeshPeerExchangeRequestMessage>();
server.PollResends();
if (request == null)
{
Thread.Sleep(5);
continue;
}
server.SendSchemaMessage(new CultMeshPeerExchangeResponseMessage
{
MessageId = request.MessageId,
Peers =
[
new CultMeshPeerCard(
"rudp-peer",
request.VerseId,
["rudp://127.0.0.1"],
roles: ["mesh-peer"]).ToMessage()
]
});
serverDone.TrySetResult();
return;
}
}
catch (Exception error)
{
serverDone.TrySetException(error);
}
})
{
IsBackground = true
};
serverThread.Start();
var client = new CultMeshPeerExchangeClient(new CultMeshPeerExchangeClientOptions
{
ConnectTimeout = TimeSpan.FromSeconds(2),
ResponseTimeout = TimeSpan.FromSeconds(2)
});
var response = await client.FetchAsync(
$"rudp://127.0.0.1:{((IPEndPoint)serverSocket.LocalEndPoint!).Port}",
new CultMeshPeerExchangeRequestMessage { VerseId = "local" });
await AwaitWithTimeout(serverDone.Task, TimeSpan.FromSeconds(2));
Assert.That(response.Peers.Single().PeerId, Is.EqualTo("rudp-peer"));
}
[Test]
public void CultMesh_CreateClient_Returns_CultNetClient()
{