diff --git a/src/GameCult.Mesh/CultMeshSessions.cs b/src/GameCult.Mesh/CultMeshSessions.cs index 87f18158..5011a00a 100644 --- a/src/GameCult.Mesh/CultMeshSessions.cs +++ b/src/GameCult.Mesh/CultMeshSessions.cs @@ -520,7 +520,9 @@ namespace GameCult.Mesh CultMeshSessionFailureReason.UnsupportedPath, "No streaming content connectors are configured. Register the TCP content connector or the explicit legacy RUDP connector."); - var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery(endpointId.Value)).ConfigureAwait(false); + var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery( + endpointId.Value, + new[] { endpointId.Value })).ConfigureAwait(false); var candidates = discovery.Candidates .SelectMany(candidate => candidate.Descriptor.DiscoveryEndpoints) .Distinct(StringComparer.Ordinal) @@ -646,7 +648,9 @@ namespace GameCult.Mesh CultMeshSessionFailureReason.UnsupportedPath, "No realtime state connectors are configured. Register a QUIC connector explicitly."); - var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery(endpointId.Value)).ConfigureAwait(false); + var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery( + endpointId.Value, + new[] { endpointId.Value })).ConfigureAwait(false); var routes = discovery.Candidates .SelectMany(candidate => candidate.Descriptor.DiscoveryEndpoints) .Distinct(StringComparer.Ordinal) @@ -733,7 +737,9 @@ namespace GameCult.Mesh private async Task ResolveConnectedPathAsync(CultMeshEndpointId endpointId, CultMeshProtocolId protocol) { - var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery(endpointId.Value)).ConfigureAwait(false); + var discovery = await _discovery.ResolveAsync(new CultMeshDiscoveryQuery( + endpointId.Value, + new[] { endpointId.Value })).ConfigureAwait(false); var candidates = discovery.Candidates.SelectMany(candidate => candidate.Descriptor.DiscoveryEndpoints) .Distinct(StringComparer.Ordinal) .Select(endpoint => new CultMeshTransportCandidate(endpoint)) diff --git a/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs b/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs index 57b8995a..c105ae06 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshBodySessionTests.cs @@ -28,7 +28,7 @@ public sealed class CultMeshBodySessionTests using var discovery = new CultMeshDiscoveryService(new[] { new RouteSource() }); using var sessions = new CultMeshSessionManager(discovery, new[] { connector }); var provider = new CultMeshSessionBodyProvider( - "aetheria.daemon", sessions, CultMeshEndpointId.Parse("service:aetheria.daemon")); + "aetheria.daemon", sessions, CultMeshEndpointId.Parse("aetheria")); var received = await provider.FetchAsync(descriptor); @@ -69,7 +69,7 @@ public sealed class CultMeshBodySessionTests using var sessions = new CultMeshSessionManager(discovery, new ICultMeshTransportConnector[] { new CultMeshSchemaTransportConnector() }); var provider = new CultMeshSessionBodyProvider("aetheria.daemon", sessions, - CultMeshEndpointId.Parse("service:aetheria.daemon"), + CultMeshEndpointId.Parse("aetheria"), new CultMeshSessionBodyProviderOptions { ResponseTimeout = TimeSpan.FromSeconds(10) }); var elapsed = Stopwatch.StartNew(); diff --git a/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs b/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs index 6bf5e88b..c1a4d04b 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshContentSessionTests.cs @@ -52,7 +52,7 @@ public sealed class CultMeshContentSessionTests var provider = new CultMeshSessionContentProvider( "aetheria.daemon", sessions, - CultMeshEndpointId.Parse("service:aetheria.daemon")); + CultMeshEndpointId.Parse("aetheria")); using var transferState = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshContentTransferStateDocument))); var transfer = new CultMeshContentTransferService( @@ -114,7 +114,7 @@ public sealed class CultMeshContentSessionTests var provider = new CultMeshSessionContentProvider( "aetheria.daemon", sessions, - CultMeshEndpointId.Parse("service:aetheria.daemon")); + CultMeshEndpointId.Parse("aetheria")); using var transferState = new CultCache(CultMesh.CreateCultCacheDocumentRegistry( typeof(CultMeshContentTransferStateDocument))); var transfer = new CultMeshContentTransferService( diff --git a/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs b/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs index b0aabd03..42e6d962 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshSessionManagerTests.cs @@ -13,6 +13,27 @@ namespace GameCult.Mesh.Tests; public sealed class CultMeshSessionManagerTests { + [Test] + public async Task Connect_ResolvesOnlyTheRequestedVerseIdentity() + { + var clock = new ManualSessionClock(); + using var discovery = new CultMeshDiscoveryService( + new[] { new MultiVerseRouteSource(clock) }, + new CultMeshDiscoveryServiceOptions { Clock = clock }); + CultMeshTransportCandidate? connected = null; + var connector = new FakeConnector((candidate, _) => + { + connected = candidate; + return Task.FromResult(new FakeSchemaClient()); + }); + using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); + + await manager.ConnectAsync(CultMeshEndpointId.Parse("aetheria"), CultMeshProtocols.Documents); + + connected.Should().NotBeNull(); + connected!.Endpoint.Should().Be("rudp://aetheria:3076"); + } + [Test] public async Task Connect_ReusesInflightAndEstablishedSession() { @@ -21,7 +42,7 @@ public sealed class CultMeshSessionManagerTests var release = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); var connector = new FakeConnector(async (_, _) => { await release.Task; return new FakeSchemaClient(); }); using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); - var endpoint = CultMeshEndpointId.Parse("odin:aetheria"); + var endpoint = CultMeshEndpointId.Parse("aetheria"); var first = manager.ConnectAsync(endpoint, CultMeshProtocols.Documents); var second = manager.ConnectAsync(endpoint, CultMeshProtocols.Documents); @@ -49,7 +70,7 @@ public sealed class CultMeshSessionManagerTests }); using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); - var session = await manager.ConnectAsync(CultMeshEndpointId.Parse("odin:aetheria"), CultMeshProtocols.Documents); + var session = await manager.ConnectAsync(CultMeshEndpointId.Parse("aetheria"), CultMeshProtocols.Documents); session.State.Path!.Endpoint.Should().Be("rudp://fast:3076"); slowRelease.TrySetResult(); await WaitUntilAsync(() => slowClient.DisposeCount == 1); @@ -70,7 +91,7 @@ public sealed class CultMeshSessionManagerTests }); using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); using var cancellation = new CancellationTokenSource(); - var endpoint = CultMeshEndpointId.Parse("odin:aetheria"); + var endpoint = CultMeshEndpointId.Parse("aetheria"); var cancelled = manager.ConnectAsync(endpoint, CultMeshProtocols.Documents, cancellation.Token); var survivor = manager.ConnectAsync(endpoint, CultMeshProtocols.Documents); @@ -97,7 +118,7 @@ public sealed class CultMeshSessionManagerTests return Task.FromResult(client); }); using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); - var endpoint = CultMeshEndpointId.Parse("odin:aetheria"); + var endpoint = CultMeshEndpointId.Parse("aetheria"); var first = await manager.ConnectAsync(endpoint, CultMeshProtocols.Documents); var states = new List(); using var stateWatch = first.WatchState().Subscribe(state => states.Add(state.Status)); @@ -129,7 +150,7 @@ public sealed class CultMeshSessionManagerTests using var manager = new CultMeshSessionManager(discovery, Array.Empty(), new CultMeshSessionManagerOptions { Clock = clock }); var error = Assert.ThrowsAsync(() => - manager.ConnectAsync(CultMeshEndpointId.Parse("odin:aetheria"), CultMeshProtocols.Documents)); + manager.ConnectAsync(CultMeshEndpointId.Parse("aetheria"), CultMeshProtocols.Documents)); error!.Failure.Reason.Should().Be(CultMeshSessionFailureReason.Transport); error.InnerException.Should().BeOfType() @@ -144,7 +165,7 @@ public sealed class CultMeshSessionManagerTests var client = new RespondingSchemaClient(); var connector = new FakeConnector((_, _) => Task.FromResult(client)); using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); - var endpoint = CultMeshEndpointId.Parse("odin:aetheria"); + var endpoint = CultMeshEndpointId.Parse("aetheria"); using var first = await CultMeshSnapshotSession.ConnectAsync(manager, endpoint, new CultMeshSnapshotRequestOptions()); using var second = await CultMeshSnapshotSession.ConnectAsync(manager, endpoint, new CultMeshSnapshotRequestOptions()); @@ -166,7 +187,7 @@ public sealed class CultMeshSessionManagerTests var connector = new FakeConnector((_, _) => Task.FromResult(client)); using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); var exchange = new CultMeshPeerExchangeClient(new CultMeshPeerExchangeClientOptions { Sessions = manager, Clock = clock }); - var endpoint = CultMeshEndpointId.Parse("odin:aetheria"); + var endpoint = CultMeshEndpointId.Parse("aetheria"); var first = await exchange.FetchAsync(endpoint, new CultMeshPeerExchangeRequestMessage { VerseId = "aetheria" }); var second = await exchange.FetchAsync(endpoint, new CultMeshPeerExchangeRequestMessage { VerseId = "aetheria" }); @@ -185,7 +206,7 @@ public sealed class CultMeshSessionManagerTests var client = new LeaseSchemaClient(); var connector = new FakeConnector((_, _) => Task.FromResult(client)); using var manager = new CultMeshSessionManager(discovery, new[] { connector }, new CultMeshSessionManagerOptions { Clock = clock }); - var session = await manager.ConnectAsync(CultMeshEndpointId.Parse("odin:aetheria"), CultMeshProtocols.Subscriptions); + var session = await manager.ConnectAsync(CultMeshEndpointId.Parse("aetheria"), CultMeshProtocols.Subscriptions); using var first = session.OpenSchemaClient(); using var second = session.OpenSchemaClient(); var firstCount = 0; @@ -237,6 +258,38 @@ public sealed class CultMeshSessionManagerTests }); } + private sealed class MultiVerseRouteSource : ICultMeshLookupSource + { + private readonly ManualSessionClock _clock; + public MultiVerseRouteSource(ManualSessionClock clock) => _clock = clock; + public string SourceId => "odin"; + + public Task> LookupAsync( + CultMeshDiscoveryQuery query, + CancellationToken cancellationToken = default) + { + var expires = _clock.UtcNow.AddMinutes(1); + return Task.FromResult>(new[] + { + Observation("norn", "rudp://norn:3076", expires), + Observation("aetheria", "rudp://aetheria:3076", expires) + }); + } + + private CultMeshDiscoveryObservation Observation(string verseId, string endpoint, DateTimeOffset expires) => + new( + new CultMeshVerseDescriptor( + verseId, + verseId, + CultMeshVerseAuthorityModel.OperatorCluster, + new CultMeshVerseCompatibility("cultmesh.v0", "rules"), + new[] { endpoint }), + SourceId, + _clock.UtcNow, + expires, + CultMeshDiscoveryTrust.Signed); + } + private sealed class FakeConnector : ICultMeshTransportConnector { private readonly Func> _connect; diff --git a/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs b/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs index a280c436..47eab18b 100644 --- a/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs +++ b/tests/GameCult.Mesh.Tests/CultMeshTransportModularityTests.cs @@ -39,7 +39,7 @@ public sealed class CultMeshTransportModularityTests }); var session = await sessions.ConnectAsync( - CultMeshEndpointId.Parse("service:aetheria.daemon"), + CultMeshEndpointId.Parse("aetheria"), CultMeshProtocols.Documents); var response = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); using var subscription = session.OnCultNet(message => response.TrySetResult(message)); @@ -77,7 +77,7 @@ public sealed class CultMeshTransportModularityTests new CultMeshTcpContentTransportConnector() }); - var session = await sessions.ConnectContentAsync(CultMeshEndpointId.Parse("service:aetheria.daemon")); + var session = await sessions.ConnectContentAsync(CultMeshEndpointId.Parse("aetheria")); using var destination = new MemoryStream(); await session.CopyChunkToAsync(artifact.Manifest.Chunks[0], destination); @@ -98,7 +98,7 @@ public sealed class CultMeshTransportModularityTests Array.Empty()); Func connect = async () => await sessions.ConnectContentAsync( - CultMeshEndpointId.Parse("service:aetheria.daemon")); + CultMeshEndpointId.Parse("aetheria")); await connect.Should().ThrowAsync() .WithMessage("*No streaming content connectors are configured*"); @@ -118,7 +118,7 @@ public sealed class CultMeshTransportModularityTests Array.Empty(), new ICultMeshRealtimeTransportConnector[] { connector }); var session = await sessions.ConnectRealtimeAsync( - CultMeshEndpointId.Parse("service:aetheria.daemon")); + CultMeshEndpointId.Parse("aetheria")); var sent = new CultMeshRealtimeFrame { ChannelId = "aetheria.entities", @@ -150,7 +150,7 @@ public sealed class CultMeshTransportModularityTests Array.Empty()); Func connect = async () => await sessions.ConnectRealtimeAsync( - CultMeshEndpointId.Parse("service:aetheria.daemon")); + CultMeshEndpointId.Parse("aetheria")); await connect.Should().ThrowAsync() .WithMessage("*No realtime state connectors are configured*"); diff --git a/unity/org.gamecult.cultlib/package.json b/unity/org.gamecult.cultlib/package.json index 6be0f3e1..979cabc8 100644 --- a/unity/org.gamecult.cultlib/package.json +++ b/unity/org.gamecult.cultlib/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.cultlib", "displayName": "CultLib", - "version": "1.0.45", + "version": "1.0.46", "unity": "2021.3", "description": "CultCache, CultNet, and CultMesh managed runtime assemblies for Unity.", "license": "MIT",