Lower EveUnity through the CultMesh client

This commit is contained in:
Meta Vens 2026-07-12 21:28:39 +01:00
commit e836884f16
5 changed files with 85 additions and 403 deletions

View file

@ -1,84 +0,0 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GameCult.Mesh;
#nullable enable
namespace GameCult.Eve.UnityScene
{
public sealed class EveUnityCultMeshConnectionContext : IDisposable
{
private readonly CultMeshDiscoveryService _discovery;
internal EveUnityCultMeshConnectionContext(
CultMeshEndpointId endpointId,
CultMeshDiscoveryService discovery,
CultMeshSessionManager sessions)
{
EndpointId = endpointId ?? throw new ArgumentNullException(nameof(endpointId));
_discovery = discovery ?? throw new ArgumentNullException(nameof(discovery));
Sessions = sessions ?? throw new ArgumentNullException(nameof(sessions));
}
public CultMeshEndpointId EndpointId { get; }
public CultMeshSessionManager Sessions { get; }
public Task<CultMeshSession> OpenDocumentsAsync(CancellationToken cancellationToken = default) =>
Sessions.ConnectAsync(EndpointId, CultMeshProtocols.Documents, cancellationToken);
public void Dispose()
{
Sessions.Dispose();
_discovery.Dispose();
}
internal static EveUnityCultMeshConnectionContext Create(
string endpointId,
IEnumerable<CultMeshVerseDescriptor> routes)
{
var identity = CultMeshEndpointId.Parse(endpointId);
var discovery = new CultMeshDiscoveryService(new[] { new FixedRouteSource(routes) });
return new EveUnityCultMeshConnectionContext(
identity,
discovery,
new CultMeshSessionManager(discovery, new ICultMeshTransportConnector[]
{
new CultMeshSchemaTransportConnector()
}));
}
private sealed class FixedRouteSource : ICultMeshLookupSource
{
private readonly CultMeshVerseDescriptor[] _routes;
public FixedRouteSource(IEnumerable<CultMeshVerseDescriptor> routes)
{
_routes = routes?.ToArray() ?? throw new ArgumentNullException(nameof(routes));
}
public string SourceId => "eve-unity-rendezvous";
public Task<IReadOnlyList<CultMeshDiscoveryObservation>> LookupAsync(
CultMeshDiscoveryQuery query,
CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var now = DateTimeOffset.UtcNow;
IReadOnlyList<CultMeshDiscoveryObservation> observations = _routes
.Where(route => string.Equals(route.VerseId, query.EndpointId, StringComparison.Ordinal))
.Select(route => new CultMeshDiscoveryObservation(
route,
SourceId,
now,
now.AddMinutes(5),
CultMeshDiscoveryTrust.Unsigned,
"CultMesh rendezvous catalog"))
.ToArray();
return Task.FromResult(observations);
}
}
}
}

View file

@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: e2ece888321b9854fb4424a65448f703

View file

@ -34,12 +34,12 @@ namespace GameCult.Eve.UnityScene
typeof(CultMeshCdnArtifactChunk)
};
private readonly string _replicaPath;
private readonly string _endpoint;
private readonly EveUnityCultMeshProviderSelection _selection;
private readonly CultMeshClient _mesh;
private readonly string _endpointId;
private readonly string _providerId;
private readonly string _surfaceId;
private readonly string _runtimeId;
private readonly EveUnityCultMeshConnectionContext? _connection;
private CultMeshSession? _documentSession;
private readonly HashSet<string> _pendingCommandIds = new HashSet<string>(StringComparer.Ordinal);
private readonly HashSet<string> _publishedReceiptIds = new HashSet<string>(StringComparer.Ordinal);
@ -48,36 +48,24 @@ namespace GameCult.Eve.UnityScene
private readonly List<AssetBundle> _assetBundles = new List<AssetBundle>();
private readonly Dictionary<string, int> _cameraCullingMasks = new Dictionary<string, int>(StringComparer.Ordinal);
private readonly ConcurrentQueue<object> _liveDocuments = new ConcurrentQueue<object>();
private CultMeshNode? _node;
private CultMeshSnapshotSession? _snapshot;
private CultNetDocumentRegistry? _networkRegistry;
private CultNetShardDescriptor? _replicaShard;
private CultNetDatabaseSubscriptionClient? _subscriptions;
private readonly List<IDisposable> _liveWatches = new List<IDisposable>();
private bool _liveSubscriptionsOpen;
private EveProviderAdvertisementDocument? _advertisement;
private EveAdvertisedSurface? _advertisedSurface;
public EveUnityCultMeshLiveProviderTransport(
string replicaPath,
string endpoint,
string providerId,
string surfaceId,
string runtimeId = "eve-unity",
EveUnityCultMeshConnectionContext? connection = null)
EveUnityCultMeshProviderSelection selection,
string runtimeId = "eve-unity")
{
_replicaPath = string.IsNullOrWhiteSpace(replicaPath)
? throw new ArgumentException("Replica path must be non-empty.", nameof(replicaPath))
: Path.GetFullPath(replicaPath);
_endpoint = string.IsNullOrWhiteSpace(endpoint)
? throw new ArgumentException("CultMesh endpoint must be non-empty.", nameof(endpoint))
: endpoint.Trim();
_providerId = string.IsNullOrWhiteSpace(providerId)
? throw new ArgumentException("Provider id must be non-empty.", nameof(providerId))
: providerId.Trim();
_surfaceId = string.IsNullOrWhiteSpace(surfaceId)
? throw new ArgumentException("Surface id must be non-empty.", nameof(surfaceId))
: surfaceId.Trim();
_ = replicaPath;
_selection = selection ?? throw new ArgumentNullException(nameof(selection));
_mesh = selection.Mesh;
_endpointId = selection.EndpointId;
_providerId = selection.ProviderId;
_surfaceId = selection.SurfaceId;
_runtimeId = string.IsNullOrWhiteSpace(runtimeId) ? "eve-unity" : runtimeId.Trim();
_connection = connection;
CurrentSurfaceDocument = EmptySurfaceDocument();
CurrentAssetManifestDocument = EmptyAssetManifest();
}
@ -159,10 +147,9 @@ namespace GameCult.Eve.UnityScene
SourceRuntimeId = _runtimeId,
SourceRole = "eve-unity"
});
new CultNetSchemaWriteForwarder(new CultNetSchemaWriteForwarderOptions())
.ForwardPutAsync(_replicaShard!, message)
.GetAwaiter()
.GetResult();
message.ShardId = RemoteShardId;
message.ShardEpoch = 1;
_documentSession!.SendCultNet(message);
_pendingCommandIds.Add(commandId);
}
@ -172,15 +159,11 @@ namespace GameCult.Eve.UnityScene
_assetBundles.Clear();
_prefabs.Clear();
_nativeAssets.Clear();
_node?.Dispose();
_snapshot?.Dispose();
_subscriptions?.Dispose();
_connection?.Dispose();
_node = null;
_snapshot = null;
_subscriptions = null;
foreach (var watch in _liveWatches) watch.Dispose();
_liveWatches.Clear();
_mesh.Dispose();
_documentSession = null;
_networkRegistry = null;
_replicaShard = null;
_pendingCommandIds.Clear();
_publishedReceiptIds.Clear();
}
@ -206,103 +189,24 @@ namespace GameCult.Eve.UnityScene
private void EnsureOpen()
{
if (_node != null)
if (_documentSession != null)
return;
var cacheRegistry = CultMesh.CreateCultCacheDocumentRegistry(WireDocumentTypes);
_networkRegistry = CultMesh.CreateCultNetDocumentRegistry(WireDocumentTypes, cacheRegistry);
_replicaShard = new CultNetShardDescriptor(
RemoteShardId,
_providerId,
epoch: 1,
isPrimary: false,
schemaIds: WireDocumentTypes.Select(type => cacheRegistry.GetRequired(type).SchemaId),
primaryEndpoints: new[] { _endpoint });
_node = CultMesh.CreateNodeAsync(
_replicaPath,
new CultMeshNodeOptions
{
StartServer = false,
EnableDurableShardLogs = true,
CacheOptions = new CultCacheOpenOptions
{
Registry = cacheRegistry,
PullOnOpen = false,
StoreFlushOnDispose = true,
UseDirectoryStore = true
},
DatabaseOptions = new CultNetDatabaseOptions
{
RuntimeId = _runtimeId,
Shards = new[] { _replicaShard },
DocumentRegistry = _networkRegistry
}
})
.GetAwaiter()
.GetResult();
if (_connection != null)
{
_documentSession = _connection.OpenDocumentsAsync().GetAwaiter().GetResult();
_snapshot = CultMeshSnapshotSession.ConnectAsync(
_connection.Sessions,
_connection.EndpointId,
new CultMeshSnapshotRequestOptions
{
ShardId = RemoteShardId,
ShardEpoch = 1,
ConnectTimeout = TimeSpan.FromSeconds(5),
ResponseTimeout = TimeSpan.FromSeconds(10),
MessageIdPrefix = "eve-unity",
RudpRuntimeId = _runtimeId,
RudpMaxFragmentBytes = 1024
},
_networkRegistry)
.GetAwaiter().GetResult();
return;
}
_snapshot = CultMesh.SnapshotSession(
_endpoint,
new CultMeshSnapshotRequestOptions
{
ShardId = RemoteShardId,
ShardEpoch = 1,
ConnectTimeout = TimeSpan.FromSeconds(5),
ResponseTimeout = TimeSpan.FromSeconds(10),
MessageIdPrefix = "eve-unity",
RudpRuntimeId = _runtimeId,
RudpMaxFragmentBytes = 1024
},
_networkRegistry);
_documentSession = _mesh.ConnectAsync(_endpointId, CultMeshProtocols.Documents)
.GetAwaiter().GetResult();
}
private void ResolveAdvertisement()
{
var documents = _snapshot!
.FetchDocumentsAsync<EveProviderAdvertisementDocument>()
.GetAwaiter()
.GetResult();
_advertisement = documents.FirstOrDefault(document =>
string.Equals(document.ProviderId, _providerId, StringComparison.Ordinal))
?? throw new InvalidOperationException($"Provider '{_providerId}' did not publish an Eve advertisement.");
_advertisedSurface = _advertisement.Surfaces.FirstOrDefault(surface =>
string.Equals(surface.SurfaceId, _surfaceId, StringComparison.Ordinal))
?? throw new InvalidOperationException($"Provider '{_providerId}' did not advertise surface '{_surfaceId}'.");
if (!string.Equals(_advertisedSurface.Transport, "cultmesh-record", StringComparison.Ordinal))
throw new InvalidOperationException("EveUnity CultMesh transport requires a cultmesh-record surface advertisement.");
if (string.IsNullOrWhiteSpace(_advertisedSurface.RecordRef))
throw new InvalidOperationException("The provider surface advertisement does not publish a record reference.");
_advertisement = _selection.Surface.Provider;
_advertisedSurface = _selection.Surface.Advertisement;
}
private void RefreshSurface()
{
var surface = _snapshot!
.FetchDocumentsAsync<EveSurfaceDocument>(
recordKeys: new[] { _advertisedSurface!.RecordRef },
schemaIds: new[] { EveSurfaceDocument.SchemaId })
.GetAwaiter()
.GetResult()
.FirstOrDefault()
?? throw new InvalidOperationException("The advertised Eve surface record was not available.");
var surface = _selection.Surface.Surface.LatestAsync().GetAwaiter().GetResult();
PublishSurface(surface);
}
@ -327,25 +231,9 @@ namespace GameCult.Eve.UnityScene
private void EnsureLiveSubscriptions()
{
if (_subscriptions != null) return;
var client = _documentSession?.OpenSchemaClient()
?? CultNetSchemaClients.CreateForEndpoint(_endpoint);
if (_documentSession == null)
{
var endpoint = new Uri(_endpoint);
client.Connect(endpoint.Host, endpoint.Port);
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5);
while (!client.Connected && DateTime.UtcNow < deadline)
System.Threading.Thread.Sleep(1);
if (!client.Connected)
{
client.Dispose();
throw new TimeoutException($"Timed out connecting live Eve state subscription to '{_endpoint}'.");
}
}
_subscriptions = new CultNetDatabaseSubscriptionClient(client, _node!.Database.Cache, _networkRegistry!);
_subscriptions.Changed += OnReplicatedDocumentChanged;
if (_liveSubscriptionsOpen) return;
_liveSubscriptionsOpen = true;
_liveWatches.Add(_selection.Surface.Surface.Watch(surface => _liveDocuments.Enqueue(surface)));
var lowered = new EveUnitySceneSurfaceLowerer()
.Lower(CurrentSurfaceDocument.SurfaceDocument, CurrentSurfaceDocument.AdvertisedSurface);
string entityViewPointer = lowered.PlayableWorld == null
@ -353,31 +241,16 @@ namespace GameCult.Eve.UnityScene
: lowered.PlayableWorld.EntityViewPointerId;
if (!string.IsNullOrWhiteSpace(entityViewPointer))
{
_subscriptions.SubscribeAsync(
"eve-unity-entity-view",
recordKeys: new[] { entityViewPointer },
schemaIds: new[] { EveEntitySoaViewDocument.SchemaId })
var entities = _mesh.DocumentAsync<EveEntitySoaViewDocument>(_endpointId, entityViewPointer)
.GetAwaiter().GetResult();
if (_node.Database.Cache.Get(new CultRecordKey(entityViewPointer)) is EveEntitySoaViewDocument current)
_liveDocuments.Enqueue(current);
_liveDocuments.Enqueue(entities.LatestAsync().GetAwaiter().GetResult());
_liveWatches.Add(entities.Watch(view => _liveDocuments.Enqueue(view)));
}
_subscriptions.SubscribeAsync(
"eve-unity-surface",
recordKeys: new[] { _advertisedSurface!.RecordRef },
schemaIds: new[] { EveSurfaceDocument.SchemaId },
includeSnapshot: false)
.GetAwaiter().GetResult();
_subscriptions.SubscribeAsync(
"eve-unity-receipts",
schemaIds: new[] { EveCommandReceiptDocument.SchemaId },
includeSnapshot: false)
.GetAwaiter().GetResult();
}
private void OnReplicatedDocumentChanged(CultNetReplicatedDocumentChange change)
{
if (change.Document is EveEntitySoaViewDocument or EveSurfaceDocument or EveCommandReceiptDocument)
_liveDocuments.Enqueue(change.Document);
var receipts = _mesh.CollectionAsync<EveCommandReceiptDocument>(_endpointId).GetAwaiter().GetResult();
_liveWatches.Add(receipts.WatchChanges(change =>
{
if (change.Document != null) _liveDocuments.Enqueue(change.Document);
}));
}
private void RefreshAssetCatalog()
@ -386,13 +259,8 @@ namespace GameCult.Eve.UnityScene
if (string.IsNullOrWhiteSpace(interaction.AssetManifestRecordRef))
return;
var catalog = _snapshot!
.FetchDocumentsAsync<EveAssetCatalogDocument>(
recordKeys: new[] { interaction.AssetManifestRecordRef },
schemaIds: new[] { EveAssetCatalogDocument.SchemaId })
.GetAwaiter()
.GetResult()
.FirstOrDefault();
var catalog = _mesh.ReadAsync<EveAssetCatalogDocument>(_endpointId, interaction.AssetManifestRecordRef)
.GetAwaiter().GetResult();
if (catalog == null || catalog.Version == CurrentAssetCatalogVersion)
return;
@ -415,14 +283,8 @@ namespace GameCult.Eve.UnityScene
foreach (var group in selected.GroupBy(selection => selection.Variant!.Uri, StringComparer.Ordinal))
{
var variant = group.First().Variant!;
var manifest = _snapshot!.FetchDocumentsAsync<CultMeshCdnArtifactManifest>(
recordKeys: new[] { variant.Uri },
schemaIds: new[] { CultMeshCdnSchemaVersions.ArtifactManifest })
.GetAwaiter()
.GetResult();
var descriptor = manifest.FirstOrDefault();
if (descriptor == null)
throw new InvalidOperationException($"Provider asset bundle manifest '{variant.Uri}' was not available.");
var descriptor = _mesh.ReadAsync<CultMeshCdnArtifactManifest>(_endpointId, variant.Uri)
.GetAwaiter().GetResult();
var bytes = ReadCachedBundle(descriptor, variant);
VerifyBundle(bytes, variant);
var bundle = AssetBundle.LoadFromMemory(bytes);
@ -489,12 +351,10 @@ namespace GameCult.Eve.UnityScene
for (var index = 0; index < references.Length; index += 2)
{
var batch = references.Skip(index).Take(2).ToArray();
var chunks = _snapshot!
.FetchDocumentsAsync<CultMeshCdnArtifactChunk>(
recordKeys: batch.Select(reference => reference.RecordKey).ToArray(),
schemaIds: new[] { CultMeshCdnSchemaVersions.ArtifactChunk })
.GetAwaiter()
.GetResult();
var chunks = batch.Select(reference => _mesh
.ReadAsync<CultMeshCdnArtifactChunk>(_endpointId, reference.RecordKey)
.GetAwaiter().GetResult())
.ToArray();
foreach (var reference in batch)
{
var chunk = chunks.FirstOrDefault(candidate =>

View file

@ -126,7 +126,7 @@ namespace GameCult.Eve.UnityScene
_transport = null;
Selection = null;
if (!transportOwnedConnection && _discovery?.Status == TaskStatus.RanToCompletion)
_discovery.Result.Connection.Dispose();
_discovery.Result.Mesh.Dispose();
_discovery = null;
}
@ -185,11 +185,8 @@ namespace GameCult.Eve.UnityScene
: replicaPath;
_transport = new EveUnityCultMeshLiveProviderTransport(
resolvedReplicaPath,
Selection.Endpoint,
Selection.ProviderId,
Selection.SurfaceId,
runtimeId,
Selection.Connection);
Selection,
runtimeId);
_transport.EntityViewAvailable += view => _pendingEntityViews.Enqueue(view);
_bridge = new EveUnitySceneLiveProviderBridge(_transport);
}

View file

@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using GameCult.Eve.Surface;
using GameCult.Mesh;
using GameCult.Networking;
#nullable enable
@ -13,46 +10,27 @@ namespace GameCult.Eve.UnityScene
{
public sealed class EveUnityCultMeshProviderSelection
{
public EveUnityCultMeshProviderSelection(
string endpoint,
string verseId,
string providerId,
string surfaceId,
string surfaceKind,
EveUnityCultMeshConnectionContext connection)
internal EveUnityCultMeshProviderSelection(
string endpointId,
CultMeshClient mesh,
EveSurfaceSelection surface)
{
Endpoint = endpoint ?? "";
VerseId = verseId ?? "";
ProviderId = providerId ?? "";
SurfaceId = surfaceId ?? "";
SurfaceKind = surfaceKind ?? "";
Connection = connection ?? throw new ArgumentNullException(nameof(connection));
EndpointId = endpointId;
Mesh = mesh;
Surface = surface;
}
public string Endpoint { get; }
public string VerseId { get; }
public string ProviderId { get; }
public string SurfaceId { get; }
public string SurfaceKind { get; }
public EveUnityCultMeshConnectionContext Connection { get; }
public string EndpointId { get; }
public string VerseId => Surface.Provider.VerseId;
public string ProviderId => Surface.Provider.ProviderId;
public string SurfaceId => Surface.Advertisement.SurfaceId;
public string SurfaceKind => Surface.Advertisement.SurfaceKind;
public CultMeshClient Mesh { get; }
public EveSurfaceSelection Surface { get; }
}
public sealed class EveUnityCultMeshProviderDiscovery
{
private static readonly Type[] AdvertisementDocumentTypes =
{
typeof(EveProviderAdvertisementDocument)
};
public EveUnityCultMeshProviderSelection Discover(
string rendezvousEndpoint,
string providerId = "",
string surfaceId = "",
string surfaceKind = "interactive-world",
string verseId = "")
=> DiscoverAsync(rendezvousEndpoint, providerId, surfaceId, surfaceKind, verseId)
.GetAwaiter().GetResult();
public async Task<EveUnityCultMeshProviderSelection> DiscoverAsync(
string rendezvousEndpoint,
string providerId = "",
@ -63,100 +41,33 @@ namespace GameCult.Eve.UnityScene
{
if (string.IsNullOrWhiteSpace(rendezvousEndpoint))
throw new ArgumentException("Rendezvous endpoint must be non-empty.", nameof(rendezvousEndpoint));
var endpointId = !string.IsNullOrWhiteSpace(verseId) ? verseId : providerId;
if (string.IsNullOrWhiteSpace(endpointId))
throw new ArgumentException("Provider or Verse identity must be supplied for CultMesh discovery.", nameof(providerId));
var response = await CultMesh.CreateVerseDiscoveryClient().FetchAsync(
rendezvousEndpoint,
new CultMeshVerseCatalogRequestMessage
{
VerseIds = string.IsNullOrWhiteSpace(verseId) ? null : new[] { verseId },
TransportVersion = "cultmesh.v0"
}).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
var candidates = response.Verses
.Where(verse => string.IsNullOrWhiteSpace(verseId) ||
string.Equals(verse.VerseId, verseId, StringComparison.Ordinal))
.SelectMany(verse => (verse.DiscoveryEndpoints ?? Array.Empty<string>())
.Select(endpoint => new { Verse = verse, Endpoint = endpoint }))
.Where(candidate => !string.IsNullOrWhiteSpace(candidate.Endpoint))
.ToArray();
if (candidates.Length == 0)
throw new InvalidOperationException("The rendezvous endpoint advertised no compatible Verse endpoints.");
var failures = new List<string>();
foreach (var candidate in candidates)
var mesh = new CultMeshClient(new CultMeshClientOptions
{
EveUnityCultMeshConnectionContext? connection = null;
try
{
connection = EveUnityCultMeshConnectionContext.Create(
candidate.Verse.VerseId,
new[] { candidate.Verse.ToVerseDescriptor() });
var advertisement = (await FetchAdvertisementsAsync(connection, cancellationToken).ConfigureAwait(false))
.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(
candidate.Endpoint,
candidate.Verse.VerseId,
advertisement.Document.ProviderId,
advertisement.Surface.SurfaceId,
advertisement.Surface.SurfaceKind,
connection);
}
catch (Exception error)
{
connection?.Dispose();
failures.Add($"{candidate.Endpoint}: {error.Message}");
}
}
var filter = $"provider='{providerId}', surface='{surfaceId}', kind='{surfaceKind}'";
var detail = failures.Count == 0 ? "" : $" Endpoint failures: {string.Join(" | ", failures)}";
throw new InvalidOperationException($"No advertised Eve surface matched {filter}.{detail}");
}
private static async Task<EveProviderAdvertisementDocument[]> FetchAdvertisementsAsync(
EveUnityCultMeshConnectionContext connection,
CancellationToken cancellationToken)
{
var cacheRegistry = CultMesh.CreateCultCacheDocumentRegistry(AdvertisementDocumentTypes);
var networkRegistry = CultMesh.CreateCultNetDocumentRegistry(AdvertisementDocumentTypes, cacheRegistry);
using var snapshot = await CultMeshSnapshotSession.ConnectAsync(
connection.Sessions,
connection.EndpointId,
new CultMeshSnapshotEndpointOptions
{
Context = CultMesh.Verse("eve.discovery", "eve-unity").Context,
DocumentRegistry = networkRegistry,
Request = new CultMeshSnapshotRequestOptions
RendezvousEndpoints = new[] { rendezvousEndpoint }
});
try
{
var surface = await mesh.SurfaceAsync(
endpointId,
new EveSurfaceRequest
{
ShardId = "provider",
ShardEpoch = 1,
ConnectTimeout = TimeSpan.FromSeconds(5),
ResponseTimeout = TimeSpan.FromSeconds(10),
MessageIdPrefix = "eve-unity-discovery",
RudpRuntimeId = "eve-unity.discovery",
RudpMaxFragmentBytes = 1024
}
}.Request,
networkRegistry,
cancellationToken).ConfigureAwait(false);
cancellationToken.ThrowIfCancellationRequested();
return (await snapshot.FetchDocumentsAsync<EveProviderAdvertisementDocument>()
.ConfigureAwait(false)).ToArray();
ProviderId = providerId,
SurfaceId = surfaceId,
SurfaceKind = surfaceKind,
LoweringTarget = "unity-scene"
},
cancellationToken).ConfigureAwait(false);
return new EveUnityCultMeshProviderSelection(endpointId, mesh, surface);
}
catch
{
mesh.Dispose();
throw;
}
}
}
}