diff --git a/src/GameCult.Mesh/CultMeshPeerExchange.cs b/src/GameCult.Mesh/CultMeshPeerExchange.cs index afffb776..67e294fd 100644 --- a/src/GameCult.Mesh/CultMeshPeerExchange.cs +++ b/src/GameCult.Mesh/CultMeshPeerExchange.cs @@ -339,7 +339,8 @@ namespace GameCult.Mesh public Action? ConfigureClient { get; set; } /// - /// 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. /// public Func? CreateClient { get; set; } } @@ -375,7 +376,7 @@ namespace GameCult.Mesh var completion = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); - using var client = CreateClient(); + using var client = CreateClient(endpoint); client.OnCultNet(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) diff --git a/src/GameCult.Mesh/CultMeshVerseDiscovery.cs b/src/GameCult.Mesh/CultMeshVerseDiscovery.cs index 28d90e35..9ada1691 100644 --- a/src/GameCult.Mesh/CultMeshVerseDiscovery.cs +++ b/src/GameCult.Mesh/CultMeshVerseDiscovery.cs @@ -157,7 +157,8 @@ namespace GameCult.Mesh public Action? ConfigureClient { get; set; } /// - /// 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. /// public Func? CreateClient { get; set; } } @@ -190,7 +191,7 @@ namespace GameCult.Mesh var completion = new TaskCompletionSource( TaskCreationOptions.RunContinuationsAsynchronously); - using var client = CreateClient(); + using var client = CreateClient(endpoint); client.OnCultNet(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) diff --git a/tests/GameCult.Networking.Tests/NetworkingTests.cs b/tests/GameCult.Networking.Tests/NetworkingTests.cs index e116a4aa..e6acd079 100644 --- a/tests/GameCult.Networking.Tests/NetworkingTests.cs +++ b/tests/GameCult.Networking.Tests/NetworkingTests.cs @@ -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(); + 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(); + 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() {