Carry CultMesh Verse and provider identity separately
This commit is contained in:
parent
83f9d2e0cc
commit
f67632233a
7 changed files with 87 additions and 65 deletions
|
|
@ -310,7 +310,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(provider.Selection.SurfaceId, Is.Not.Empty);
|
||||
var quicProof = ReceiveAdvertisedRealtimeFrameAsync(
|
||||
rendezvousEndpoint,
|
||||
provider.Selection.VerseId);
|
||||
provider.Selection.VerseId,
|
||||
provider.Selection.AuthorityRuntimeId);
|
||||
var quicDeadline = Time.realtimeSinceStartup + 15f;
|
||||
while (!quicProof.IsCompleted && Time.realtimeSinceStartup < quicDeadline)
|
||||
yield return new WaitForSecondsRealtime(0.05f);
|
||||
|
|
@ -1200,7 +1201,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
|
||||
private static async Task<CultMeshRealtimeFrame> ReceiveAdvertisedRealtimeFrameAsync(
|
||||
string rendezvousEndpoint,
|
||||
string verseId)
|
||||
string verseId,
|
||||
string authorityRuntimeId)
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
|
||||
using var client = new CultMeshClient(new CultMeshClientOptions
|
||||
|
|
@ -1211,7 +1213,9 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
new CultMeshNativeQuicRealtimeTransportConnector()
|
||||
}
|
||||
});
|
||||
using var session = await client.ConnectRealtimeAsync(verseId, timeout.Token);
|
||||
using var session = await client.ConnectRealtimeAsync(
|
||||
new CultMeshSessionTarget(verseId, authorityRuntimeId),
|
||||
timeout.Token);
|
||||
return await session.ReceiveAsync(timeout.Token);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -310,7 +310,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
Assert.That(provider.Selection.SurfaceId, Is.Not.Empty);
|
||||
var quicProof = ReceiveAdvertisedRealtimeFrameAsync(
|
||||
rendezvousEndpoint,
|
||||
provider.Selection.VerseId);
|
||||
provider.Selection.VerseId,
|
||||
provider.Selection.AuthorityRuntimeId);
|
||||
var quicDeadline = Time.realtimeSinceStartup + 15f;
|
||||
while (!quicProof.IsCompleted && Time.realtimeSinceStartup < quicDeadline)
|
||||
yield return new WaitForSecondsRealtime(0.05f);
|
||||
|
|
@ -1200,7 +1201,8 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
|
||||
private static async Task<CultMeshRealtimeFrame> ReceiveAdvertisedRealtimeFrameAsync(
|
||||
string rendezvousEndpoint,
|
||||
string verseId)
|
||||
string verseId,
|
||||
string authorityRuntimeId)
|
||||
{
|
||||
using var timeout = new CancellationTokenSource(TimeSpan.FromSeconds(15));
|
||||
using var client = new CultMeshClient(new CultMeshClientOptions
|
||||
|
|
@ -1211,7 +1213,9 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
|
|||
new CultMeshNativeQuicRealtimeTransportConnector()
|
||||
}
|
||||
});
|
||||
using var session = await client.ConnectRealtimeAsync(verseId, timeout.Token);
|
||||
using var session = await client.ConnectRealtimeAsync(
|
||||
new CultMeshSessionTarget(verseId, authorityRuntimeId),
|
||||
timeout.Token);
|
||||
return await session.ReceiveAsync(timeout.Token);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
private readonly string _cachePath;
|
||||
private readonly string _rendezvousEndpoint;
|
||||
private readonly string _endpointId;
|
||||
private readonly CultMeshSessionTarget _target;
|
||||
private readonly string _providerId;
|
||||
private readonly string _surfaceId;
|
||||
private readonly string _runtimeId;
|
||||
|
|
@ -83,7 +83,8 @@ namespace GameCult.Eve.UnityScene
|
|||
public EveUnityCultMeshLiveProviderTransport(
|
||||
string cachePath,
|
||||
string rendezvousEndpoint,
|
||||
string endpointId,
|
||||
string verseId,
|
||||
string authorityRuntimeId,
|
||||
string providerId,
|
||||
string surfaceId,
|
||||
string runtimeId = "eve-unity",
|
||||
|
|
@ -95,9 +96,7 @@ namespace GameCult.Eve.UnityScene
|
|||
_rendezvousEndpoint = string.IsNullOrWhiteSpace(rendezvousEndpoint)
|
||||
? throw new ArgumentException("CultMesh rendezvous endpoint must be non-empty.", nameof(rendezvousEndpoint))
|
||||
: rendezvousEndpoint.Trim();
|
||||
_endpointId = string.IsNullOrWhiteSpace(endpointId)
|
||||
? throw new ArgumentException("CultMesh stable endpoint identity must be non-empty.", nameof(endpointId))
|
||||
: endpointId.Trim();
|
||||
_target = new CultMeshSessionTarget(verseId, authorityRuntimeId);
|
||||
_providerId = string.IsNullOrWhiteSpace(providerId)
|
||||
? throw new ArgumentException("Provider id must be non-empty.", nameof(providerId))
|
||||
: providerId.Trim();
|
||||
|
|
@ -189,7 +188,7 @@ namespace GameCult.Eve.UnityScene
|
|||
catch (Exception error) when (error is IOException || error is SocketException || error is TimeoutException)
|
||||
{
|
||||
throw new InvalidOperationException(
|
||||
$"CultMesh provider '{_providerId}' bootstrap through stable endpoint '{_endpointId}' failed.",
|
||||
$"CultMesh provider '{_providerId}' bootstrap through target '{_target}' failed.",
|
||||
error);
|
||||
}
|
||||
}
|
||||
|
|
@ -361,7 +360,7 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
var recordKey = ChildRecordKey(interaction.CommandRecordRef, commandId);
|
||||
_meshClient!.SubmitDocumentAsync(
|
||||
_endpointId,
|
||||
_target,
|
||||
recordKey,
|
||||
request,
|
||||
_runtimeId,
|
||||
|
|
@ -466,7 +465,7 @@ namespace GameCult.Eve.UnityScene
|
|||
if (!forceRefresh && _advertisement != null && _advertisedSurface != null)
|
||||
return;
|
||||
using var advertisements = _meshClient!
|
||||
.LeaseCollectionAsync<EveProviderAdvertisementDocument>(_endpointId)
|
||||
.LeaseCollectionAsync<EveProviderAdvertisementDocument>(_target)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
_advertisement = advertisements.Handle.LatestAsync().GetAwaiter().GetResult().FirstOrDefault(document =>
|
||||
|
|
@ -490,7 +489,7 @@ namespace GameCult.Eve.UnityScene
|
|||
var mappedRoot = _cachePath;
|
||||
var networkBodies = _meshClient!.BodyProvider(
|
||||
_advertisement.ProviderId,
|
||||
_advertisement.ServiceId,
|
||||
_target,
|
||||
new CultMeshSessionBodyProviderOptions { ResponseTimeout = TimeSpan.FromSeconds(2) });
|
||||
return new CultMeshBodyPublicationResolver(new CultMeshBodyTransportService(
|
||||
new ICultMeshBodyTransportAdapter[]
|
||||
|
|
@ -604,19 +603,20 @@ namespace GameCult.Eve.UnityScene
|
|||
throw new InvalidOperationException(
|
||||
$"Eve provider '{_providerId}' does not advertise the Verse identity required for realtime state.");
|
||||
_realtimeLifetime = new CancellationTokenSource();
|
||||
_realtimePump = Task.Run(() => PumpRealtimeEntityStateAsync(
|
||||
_advertisement.VerseId,
|
||||
_realtimeLifetime.Token));
|
||||
if (!string.Equals(_advertisement.VerseId, _target.VerseId, StringComparison.Ordinal))
|
||||
throw new InvalidOperationException(
|
||||
$"Eve provider '{_providerId}' advertisement Verse '{_advertisement.VerseId}' does not match selected Verse '{_target.VerseId}'.");
|
||||
_realtimePump = Task.Run(() => PumpRealtimeEntityStateAsync(_realtimeLifetime.Token));
|
||||
}
|
||||
|
||||
private async Task PumpRealtimeEntityStateAsync(string verseId, CancellationToken cancellationToken)
|
||||
private async Task PumpRealtimeEntityStateAsync(CancellationToken cancellationToken)
|
||||
{
|
||||
try
|
||||
{
|
||||
_realtimeSession = await _meshClient!
|
||||
.ConnectRealtimeAsync(verseId, cancellationToken)
|
||||
.ConnectRealtimeAsync(_target, cancellationToken)
|
||||
.ConfigureAwait(false);
|
||||
TraceHotState($"realtime state connected transport={_realtimeSession.TransportId} verse={verseId}");
|
||||
TraceHotState($"realtime state connected transport={_realtimeSession.TransportId} target={_target}");
|
||||
while (!cancellationToken.IsCancellationRequested)
|
||||
{
|
||||
var frame = await _realtimeSession.ReceiveAsync(cancellationToken).ConfigureAwait(false);
|
||||
|
|
@ -823,7 +823,7 @@ namespace GameCult.Eve.UnityScene
|
|||
private void RefreshSurface()
|
||||
{
|
||||
var surface = _meshClient!
|
||||
.ReadAsync<EveSurfaceDocument>(_endpointId, _advertisedSurface!.RecordRef)
|
||||
.ReadAsync<EveSurfaceDocument>(_target, _advertisedSurface!.RecordRef)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
PublishBaseSurface(surface);
|
||||
|
|
@ -1010,7 +1010,7 @@ namespace GameCult.Eve.UnityScene
|
|||
bool publishInitial = true)
|
||||
where TDocument : class
|
||||
{
|
||||
var lease = _meshClient!.LeaseDocumentAsync<TDocument>(_endpointId, recordKey)
|
||||
var lease = _meshClient!.LeaseDocumentAsync<TDocument>(_target, recordKey)
|
||||
.GetAwaiter().GetResult();
|
||||
_documentLeases.Add(lease);
|
||||
if (publishInitial) publish(lease.Handle.Latest());
|
||||
|
|
@ -1022,7 +1022,7 @@ namespace GameCult.Eve.UnityScene
|
|||
bool includeInitialSnapshot = true)
|
||||
where TDocument : class
|
||||
{
|
||||
var lease = _meshClient!.LeaseCollectionAsync<TDocument>(_endpointId, includeInitialSnapshot)
|
||||
var lease = _meshClient!.LeaseCollectionAsync<TDocument>(_target, includeInitialSnapshot)
|
||||
.GetAwaiter().GetResult();
|
||||
_documentLeases.Add(lease);
|
||||
if (includeInitialSnapshot)
|
||||
|
|
@ -1068,7 +1068,7 @@ namespace GameCult.Eve.UnityScene
|
|||
return;
|
||||
|
||||
var catalog = _meshClient!
|
||||
.ReadAsync<EveAssetCatalogDocument>(_endpointId, interaction.AssetManifestRecordRef)
|
||||
.ReadAsync<EveAssetCatalogDocument>(_target, interaction.AssetManifestRecordRef)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
PublishAssetCatalog(catalog);
|
||||
|
|
@ -1157,7 +1157,7 @@ namespace GameCult.Eve.UnityScene
|
|||
EnsureBundleLoaded(dependency);
|
||||
|
||||
var groupElapsed = Stopwatch.StartNew();
|
||||
var descriptor = _meshClient!.ReadAsync<CultMeshCdnArtifactManifest>(_endpointId, uri)
|
||||
var descriptor = _meshClient!.ReadAsync<CultMeshCdnArtifactManifest>(_target, uri)
|
||||
.GetAwaiter()
|
||||
.GetResult();
|
||||
TraceStartup("asset-manifest", groupElapsed);
|
||||
|
|
@ -1280,7 +1280,7 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
_meshClient!.ContentProvider(
|
||||
_advertisement.ProviderId,
|
||||
_advertisement.ServiceId)
|
||||
_target)
|
||||
},
|
||||
new CultMeshContentTransferOptions(cacheRoot),
|
||||
_assetBodyMappings);
|
||||
|
|
|
|||
|
|
@ -250,7 +250,8 @@ namespace GameCult.Eve.UnityScene
|
|||
_transport = new EveUnityCultMeshLiveProviderTransport(
|
||||
resolvedCachePath,
|
||||
Selection.RendezvousEndpoint,
|
||||
Selection.EndpointId,
|
||||
Selection.VerseId,
|
||||
Selection.AuthorityRuntimeId,
|
||||
Selection.ProviderId,
|
||||
Selection.SurfaceId,
|
||||
runtimeId);
|
||||
|
|
|
|||
|
|
@ -14,23 +14,23 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
public EveUnityCultMeshProviderSelection(
|
||||
string rendezvousEndpoint,
|
||||
string endpointId,
|
||||
string verseId,
|
||||
string authorityRuntimeId,
|
||||
string providerId,
|
||||
string surfaceId,
|
||||
string surfaceKind)
|
||||
{
|
||||
RendezvousEndpoint = rendezvousEndpoint ?? "";
|
||||
EndpointId = endpointId ?? "";
|
||||
VerseId = verseId ?? "";
|
||||
AuthorityRuntimeId = authorityRuntimeId ?? "";
|
||||
ProviderId = providerId ?? "";
|
||||
SurfaceId = surfaceId ?? "";
|
||||
SurfaceKind = surfaceKind ?? "";
|
||||
}
|
||||
|
||||
public string RendezvousEndpoint { get; }
|
||||
public string EndpointId { get; }
|
||||
public string VerseId { get; }
|
||||
public string AuthorityRuntimeId { get; }
|
||||
public string ProviderId { get; }
|
||||
public string SurfaceId { get; }
|
||||
public string SurfaceKind { get; }
|
||||
|
|
@ -95,41 +95,48 @@ namespace GameCult.Eve.UnityScene
|
|||
});
|
||||
foreach (var candidate in candidates)
|
||||
{
|
||||
try
|
||||
foreach (var authorityRuntimeId in (candidate.AuthorityRuntimeIds ?? Array.Empty<string>())
|
||||
.Where(id => !string.IsNullOrWhiteSpace(id))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.OrderBy(id => id, StringComparer.Ordinal))
|
||||
{
|
||||
using var advertisementsLease = await mesh
|
||||
.LeaseCollectionAsync<EveProviderAdvertisementDocument>(candidate.VerseId)
|
||||
.ConfigureAwait(false);
|
||||
var advertisements = await advertisementsLease.Handle.LatestAsync().ConfigureAwait(false);
|
||||
observed.AddRange(advertisements.Select(document =>
|
||||
$"{candidate.VerseId}: {document.ProviderId}[{string.Join(",", document.Surfaces.Select(surface => $"{surface.SurfaceId}:{surface.SurfaceKind}"))}]"));
|
||||
var advertisement = advertisements
|
||||
.Where(document => string.IsNullOrWhiteSpace(providerId) ||
|
||||
string.Equals(document.ProviderId, providerId, StringComparison.Ordinal))
|
||||
.Select(document => new
|
||||
{
|
||||
Document = document,
|
||||
Surface = document.Surfaces.FirstOrDefault(surface =>
|
||||
(string.IsNullOrWhiteSpace(surfaceId) ||
|
||||
string.Equals(surface.SurfaceId, surfaceId, StringComparison.Ordinal)) &&
|
||||
(string.IsNullOrWhiteSpace(surfaceKind) ||
|
||||
string.Equals(surface.SurfaceKind, surfaceKind, StringComparison.Ordinal)))
|
||||
})
|
||||
.FirstOrDefault(match => match.Surface != null);
|
||||
if (advertisement?.Surface == null)
|
||||
continue;
|
||||
try
|
||||
{
|
||||
var target = new CultMeshSessionTarget(candidate.VerseId, authorityRuntimeId);
|
||||
using var advertisementsLease = await mesh
|
||||
.LeaseCollectionAsync<EveProviderAdvertisementDocument>(target)
|
||||
.ConfigureAwait(false);
|
||||
var advertisements = await advertisementsLease.Handle.LatestAsync().ConfigureAwait(false);
|
||||
observed.AddRange(advertisements.Select(document =>
|
||||
$"{target}: {document.ProviderId}[{string.Join(",", document.Surfaces.Select(surface => $"{surface.SurfaceId}:{surface.SurfaceKind}"))}]"));
|
||||
var advertisement = advertisements
|
||||
.Where(document => string.IsNullOrWhiteSpace(providerId) ||
|
||||
string.Equals(document.ProviderId, providerId, StringComparison.Ordinal))
|
||||
.Select(document => new
|
||||
{
|
||||
Document = document,
|
||||
Surface = document.Surfaces.FirstOrDefault(surface =>
|
||||
(string.IsNullOrWhiteSpace(surfaceId) ||
|
||||
string.Equals(surface.SurfaceId, surfaceId, StringComparison.Ordinal)) &&
|
||||
(string.IsNullOrWhiteSpace(surfaceKind) ||
|
||||
string.Equals(surface.SurfaceKind, surfaceKind, StringComparison.Ordinal)))
|
||||
})
|
||||
.FirstOrDefault(match => match.Surface != null);
|
||||
if (advertisement?.Surface == null)
|
||||
continue;
|
||||
|
||||
return new EveUnityCultMeshProviderSelection(
|
||||
rendezvousEndpoint,
|
||||
candidate.VerseId,
|
||||
candidate.VerseId,
|
||||
advertisement.Document.ProviderId,
|
||||
advertisement.Surface.SurfaceId,
|
||||
advertisement.Surface.SurfaceKind);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
failures.Add($"{candidate.VerseId}: {error.Message}");
|
||||
return new EveUnityCultMeshProviderSelection(
|
||||
rendezvousEndpoint,
|
||||
candidate.VerseId,
|
||||
authorityRuntimeId,
|
||||
advertisement.Document.ProviderId,
|
||||
advertisement.Surface.SurfaceId,
|
||||
advertisement.Surface.SurfaceKind);
|
||||
}
|
||||
catch (Exception error)
|
||||
{
|
||||
failures.Add($"{candidate.VerseId}/{authorityRuntimeId}: {error.Message}");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -26,8 +26,8 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
"interactive-world");
|
||||
|
||||
Assert.That(selection.RendezvousEndpoint, Is.EqualTo("cultnet+tcp://odin.example:3075"));
|
||||
Assert.That(selection.EndpointId, Is.EqualTo("aetheria.public"));
|
||||
Assert.That(selection.VerseId, Is.EqualTo("aetheria.public"));
|
||||
Assert.That(selection.AuthorityRuntimeId, Is.EqualTo("aetheria.public"));
|
||||
Assert.That(typeof(EveUnityCultMeshProviderSelection).GetProperty("Endpoint"), Is.Null);
|
||||
}
|
||||
|
||||
|
|
@ -180,6 +180,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
"cultnet+tcp://127.0.0.1:1",
|
||||
"provider",
|
||||
"provider",
|
||||
"provider",
|
||||
"surface");
|
||||
var variant = new EveAssetVariant(
|
||||
"unity-scene",
|
||||
|
|
|
|||
|
|
@ -32,6 +32,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
"cultnet://127.0.0.1:3075",
|
||||
"aetheria",
|
||||
"aetheria",
|
||||
"aetheria",
|
||||
"aetheria.pilot");
|
||||
});
|
||||
}
|
||||
|
|
@ -67,6 +68,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
"cultnet://127.0.0.1:3075",
|
||||
"aetheria",
|
||||
"aetheria",
|
||||
"aetheria",
|
||||
"aetheria.pilot");
|
||||
var view = EntityLeaseDocument();
|
||||
var publication = BodyPublication(view, view.Sequence + 1);
|
||||
|
|
@ -102,6 +104,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
"cultnet://127.0.0.1:3075",
|
||||
"aetheria",
|
||||
"aetheria",
|
||||
"aetheria",
|
||||
"aetheria.pilot");
|
||||
var view = EntityLeaseDocument();
|
||||
var publication = BodyPublication(view, view.Sequence);
|
||||
|
|
@ -131,6 +134,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
"cultnet://127.0.0.1:3075",
|
||||
"provider",
|
||||
"provider",
|
||||
"provider",
|
||||
"provider.pilot");
|
||||
var enqueue = typeof(EveUnityCultMeshLiveProviderTransport)
|
||||
.GetMethod("EnqueueRealtimeEntityFrame", BindingFlags.NonPublic | BindingFlags.Instance)!;
|
||||
|
|
@ -161,6 +165,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
"cultnet://127.0.0.1:3075",
|
||||
"provider",
|
||||
"provider",
|
||||
"provider",
|
||||
"provider.pilot");
|
||||
var view = EntityLeaseDocument();
|
||||
view.Sequence = 0;
|
||||
|
|
|
|||
Reference in a new issue