diff --git a/packages/org.gamecult.eve.unity-scene/README.md b/packages/org.gamecult.eve.unity-scene/README.md index c54381c..0844e3b 100644 --- a/packages/org.gamecult.eve.unity-scene/README.md +++ b/packages/org.gamecult.eve.unity-scene/README.md @@ -38,6 +38,11 @@ main thread applies completed typed documents and assets; it never waits for a CultMesh task, keeping Play, Pause, input, inspection, and rendering responsive while a provider is slow or temporarily unreachable. +Navigation keeps the outgoing presentation quiesced until the candidate route +is ready, then commits the provider route before activating the candidate. +Presentation connections are provider-owned leases: disposing the outgoing +host releases only its lease and cannot disconnect the newly committed Verse. + The package never imports provider product assemblies. Add `EveUnityRendererFeature` to the Universal Renderer used by Eve cameras. diff --git a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshPlayableWorldProvider.cs b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshPlayableWorldProvider.cs index 17ea29d..cee9cc2 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshPlayableWorldProvider.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshPlayableWorldProvider.cs @@ -49,6 +49,7 @@ namespace GameCult.Eve.UnityScene private EveUnitySceneLiveProviderBridge? _bridge; private PreviousProvider? _previousProvider; private bool _navigationRouteCommitted; + private int _connectionLeaseCount; private Task? _preparation; private CancellationTokenSource? _preparationLifetime; private readonly ConcurrentQueue _pendingEntityViews = new ConcurrentQueue(); @@ -114,7 +115,10 @@ namespace GameCult.Eve.UnityScene public void Connect() { + // During navigation the candidate presentation connects before the route + // becomes canonical, so every new lease must connect its selected bridge. Bridge.Connect(); + checked { _connectionLeaseCount++; } } public Task PrepareAsync() @@ -132,7 +136,11 @@ namespace GameCult.Eve.UnityScene public void Disconnect() { - _bridge?.Disconnect(); + if (_connectionLeaseCount <= 0) + return; + _connectionLeaseCount--; + if (_connectionLeaseCount == 0) + _bridge?.Disconnect(); } public async Task NavigateAsync(EveUnitySceneNavigationTarget target) @@ -289,6 +297,7 @@ namespace GameCult.Eve.UnityScene _previousProvider?.Transport?.Dispose(); _previousProvider = null; _navigationRouteCommitted = false; + _connectionLeaseCount = 0; while (_pendingEntityViews.TryDequeue(out var pending)) pending.Lease.Dispose(); while (_pendingFields.TryDequeue(out _)) { } _bridge = null; diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index 27cab25..041e901 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -1222,6 +1222,8 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(provider.CommitNavigationCount, Is.EqualTo(2)); Assert.That(provider.FinalizeNavigationCount, Is.EqualTo(2)); + Assert.That(provider.ActiveConnectionLeases, Is.EqualTo(1), + "disposing superseded presentations must leave the committed Verse connected"); Assert.That(hostObject.transform.Cast() .Count(child => child.name == "Eve Unity Candidate Presentation"), Is.EqualTo(1)); } @@ -3433,6 +3435,7 @@ namespace GameCult.Eve.UnityScene.Tests IEveUnitySceneCommandReceiptSource, IEveUnityProviderRefreshSource, IEveUnityInputCapabilitySource, + IEveUnitySceneProviderSurfaceDocumentConnection, IEveUnityNavigableProvider { public List Submitted { get; } = new List(); @@ -3451,6 +3454,8 @@ namespace GameCult.Eve.UnityScene.Tests public bool FailNextNavigationFinalize { get; set; } + public int ActiveConnectionLeases { get; private set; } + public string ActiveRoute { get; private set; } = "original"; public List RoutesObservedDuringReactivation { get; } = new List(); @@ -3529,6 +3534,17 @@ namespace GameCult.Eve.UnityScene.Tests return Task.CompletedTask; } + public void Connect() + { + ActiveConnectionLeases++; + } + + public void Disconnect() + { + if (ActiveConnectionLeases > 0) + ActiveConnectionLeases--; + } + public void CommitNavigation() { CommitNavigationCount++; diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index d1b717c..ea38ef2 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,11 +1,11 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.3.117", + "version": "0.3.118", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": { - "org.gamecult.cultlib": "1.0.54", + "org.gamecult.cultlib": "1.0.55", "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.3.3", "org.gamecult.eve.unity-uitoolkit": "0.1.6",