diff --git a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index 4d05854..2ec6eb6 100644 --- a/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/ReleaseConsumerProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -255,6 +255,29 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests surfaceId, requiredSurfaceKind: "interactive-world", clientRuntimeId: $"eve-unity-test-{Guid.NewGuid():N}"); + var preparationDeadline = Time.realtimeSinceStartup + 30f; + while (true) + { + var preparation = provider.PrepareAsync(); + while (!preparation.IsCompleted && Time.realtimeSinceStartup < preparationDeadline) + yield return new WaitForSecondsRealtime(0.1f); + if (!preparation.IsCompleted) + throw new TimeoutException("Timed out preparing the advertised Eve provider."); + var prepared = false; + try + { + preparation.GetAwaiter().GetResult(); + prepared = true; + } + catch (InvalidOperationException) when (Time.realtimeSinceStartup < preparationDeadline) + { + } + catch (TimeoutException) when (Time.realtimeSinceStartup < preparationDeadline) + { + } + if (prepared) break; + yield return new WaitForSecondsRealtime(0.1f); + } var publicationDeadline = Time.realtimeSinceStartup + 30f; while (true) { diff --git a/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs b/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs index 4d05854..2ec6eb6 100644 --- a/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs +++ b/TestProject/Assets/Tests/PlayMode/GenericWorldCaptureTests.cs @@ -255,6 +255,29 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests surfaceId, requiredSurfaceKind: "interactive-world", clientRuntimeId: $"eve-unity-test-{Guid.NewGuid():N}"); + var preparationDeadline = Time.realtimeSinceStartup + 30f; + while (true) + { + var preparation = provider.PrepareAsync(); + while (!preparation.IsCompleted && Time.realtimeSinceStartup < preparationDeadline) + yield return new WaitForSecondsRealtime(0.1f); + if (!preparation.IsCompleted) + throw new TimeoutException("Timed out preparing the advertised Eve provider."); + var prepared = false; + try + { + preparation.GetAwaiter().GetResult(); + prepared = true; + } + catch (InvalidOperationException) when (Time.realtimeSinceStartup < preparationDeadline) + { + } + catch (TimeoutException) when (Time.realtimeSinceStartup < preparationDeadline) + { + } + if (prepared) break; + yield return new WaitForSecondsRealtime(0.1f); + } var publicationDeadline = Time.realtimeSinceStartup + 30f; while (true) { diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs index c2389cb..e085b14 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs @@ -60,6 +60,7 @@ namespace GameCult.Eve.UnityScene private CultNetDocumentRegistry? _networkRegistry; private CultNetShardDescriptor? _replicaShard; private CultNetDatabaseSubscriptionClient? _subscriptions; + private CultNetDatabaseSubscriptionClient? _entitySubscriptions; private CultMeshClient? _meshClient; private CultMeshContentTransferService? _contentTransfer; private CultMeshVerifiedBodyMappingBroker? _assetBodyMappings; @@ -364,13 +365,15 @@ namespace GameCult.Eve.UnityScene _prefabs.Clear(); _nativeAssets.Clear(); _nativeAssetMetadata.Clear(); + _subscriptions?.Dispose(); + _entitySubscriptions?.Dispose(); _node?.Dispose(); _snapshot?.Dispose(); - _subscriptions?.Dispose(); _meshClient?.Dispose(); _node = null; _snapshot = null; _subscriptions = null; + _entitySubscriptions = null; _meshClient = null; _contentTransfer = null; _assetBodyMappings = null; @@ -689,21 +692,27 @@ namespace GameCult.Eve.UnityScene private void EnsureLiveSubscriptions() { - if (_subscriptions != null) return; - var endpoint = new Uri(_endpoint); - var client = CultNetSchemaClients.CreateForEndpoint(_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) + if (_subscriptions != null && _entitySubscriptions != null) return; + _subscriptions?.Dispose(); + _entitySubscriptions?.Dispose(); + _subscriptions = null; + _entitySubscriptions = null; + try { - client.Dispose(); - throw new TimeoutException($"Timed out connecting live Eve state subscription to '{_endpoint}'."); + _subscriptions = CreateSubscriptionClient(); + _entitySubscriptions = CreateSubscriptionClient(); + } + catch + { + _subscriptions?.Dispose(); + _entitySubscriptions?.Dispose(); + _subscriptions = null; + _entitySubscriptions = null; + throw; } - - _subscriptions = new CultNetDatabaseSubscriptionClient(client, _node!.Database.Cache, _networkRegistry!); _subscriptions.Changed += OnReplicatedDocumentChanged; + _entitySubscriptions.Changed += OnReplicatedDocumentChanged; + var lowered = new EveUnitySceneSurfaceLowerer() .Lower(CurrentSurfaceDocument.SurfaceDocument, CurrentSurfaceDocument.AdvertisedSurface); string entityViewPointer = lowered.PlayableWorld == null @@ -711,12 +720,12 @@ namespace GameCult.Eve.UnityScene : lowered.PlayableWorld.EntityViewPointerId; if (!string.IsNullOrWhiteSpace(entityViewPointer)) { - _subscriptions.SubscribeAsync( + _entitySubscriptions.SubscribeAsync( "eve-unity-entity-view", recordKeys: new[] { entityViewPointer }, schemaIds: new[] { EveEntitySoaViewDocument.SchemaId }) .GetAwaiter().GetResult(); - var current = _node.Database.Cache.Get(new CultRecordKey(entityViewPointer)) as EveEntitySoaViewDocument + var current = _node!.Database.Cache.Get(new CultRecordKey(entityViewPointer)) as EveEntitySoaViewDocument ?? _snapshot! .FetchDocumentsAsync( recordKeys: new[] { entityViewPointer }, @@ -731,8 +740,6 @@ namespace GameCult.Eve.UnityScene schemaIds: new[] { EveSurfaceDocument.SchemaId }, includeSnapshot: false) .GetAwaiter().GetResult(); - // Entity views name one immutable body generation. PublishEntityView fetches that - // exact publication; a schema-wide subscription would import every retained frame. _subscriptions.SubscribeAsync( "eve-unity-receipts", schemaIds: new[] { EveCommandReceiptDocument.SchemaId }, @@ -740,6 +747,22 @@ namespace GameCult.Eve.UnityScene .GetAwaiter().GetResult(); } + private CultNetDatabaseSubscriptionClient CreateSubscriptionClient() + { + var endpoint = new Uri(_endpoint); + var client = CultNetSchemaClients.CreateForEndpoint(_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}'."); + } + return new CultNetDatabaseSubscriptionClient(client, _node!.Database.Cache, _networkRegistry!); + } + private void OnReplicatedDocumentChanged(CultNetReplicatedDocumentChange change) { if (change.Document is EveEntitySoaViewDocument entityView) diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index d8d7c4e..2781bdf 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,7 +1,7 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.71", + "version": "0.3.72", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {