diff --git a/packages/org.gamecult.eve.unity-scene/README.md b/packages/org.gamecult.eve.unity-scene/README.md index 01fc8a4..52d0a60 100644 --- a/packages/org.gamecult.eve.unity-scene/README.md +++ b/packages/org.gamecult.eve.unity-scene/README.md @@ -183,4 +183,6 @@ its exact receipt lease alive, makes the obsolete surface read-only, and emits the terminal receipt only after that surface generation and its qualified asset catalog have committed together. Asset preparation failure therefore cannot leave an accepted Verse switch paired with actionable controls from the prior -Verse. +Verse. Embedded surfaces may recompose the visible tree, but their versions are +derived rendering inputs and cannot satisfy or clear the advertised base +surface's finality barrier. 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 f7f01a9..d4dc321 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs @@ -71,6 +71,7 @@ namespace GameCult.Eve.UnityScene private PendingAssetCatalogUpdate? _pendingAssetCatalog; private bool _assetCatalogUpdateRunning; private long _surfaceAssetGeneration; + private long _mountedBaseSurfaceVersion; private long _presentationBarrierVersion = -1; private long _lastQueuedEntityViewEpoch = -1; private long _lastQueuedEntityViewSequence = -1; @@ -905,11 +906,13 @@ namespace GameCult.Eve.UnityScene private void PublishBaseSurface(EveSurfaceDocument surface) { if (surface == null) throw new ArgumentNullException(nameof(surface)); + if (_baseSurface != null && surface.Version <= MountedBaseSurfaceVersion) + return; var generation = Interlocked.Increment(ref _surfaceAssetGeneration); if (!_bootstrapped) { _baseSurface = surface; - PublishComposedSurface(); + PublishComposedSurface(surface.Version); return; } @@ -917,7 +920,7 @@ namespace GameCult.Eve.UnityScene if (string.Equals(source.Identity, CurrentAssetSourceIdentity, StringComparison.Ordinal)) { _baseSurface = surface; - PublishComposedSurface(); + PublishComposedSurface(surface.Version); return; } @@ -937,10 +940,11 @@ namespace GameCult.Eve.UnityScene PublishComposedSurface(); } - private void PublishComposedSurface() + private void PublishComposedSurface(long? committedBaseVersion = null) { ApplyPreparedSurface(PrepareComposedSurface( - _baseSurface ?? throw new InvalidOperationException("The base Eve surface is unavailable."))); + _baseSurface ?? throw new InvalidOperationException("The base Eve surface is unavailable.")), + committedBaseVersion); } private PreparedSurface PrepareComposedSurface(EveSurfaceDocument baseSurface) @@ -969,14 +973,15 @@ namespace GameCult.Eve.UnityScene return new PreparedSurface(document, continuousCommands); } - private void ApplyPreparedSurface(PreparedSurface prepared) + private void ApplyPreparedSurface(PreparedSurface prepared, long? committedBaseVersion = null) { CurrentSurfaceDocument = prepared.Document; _continuousCommandIds.Clear(); foreach (var command in prepared.ContinuousCommands) _continuousCommandIds.Add(command); SurfaceDocumentAvailable?.Invoke(CurrentSurfaceDocument); - CompletePresentationTransition(CurrentSurfaceDocument.Version); + if (committedBaseVersion.HasValue) + CompletePresentationTransition(committedBaseVersion.Value); } internal static EveSurfaceDocument ComposeSurface( @@ -1463,7 +1468,7 @@ namespace GameCult.Eve.UnityScene _baseSurface = surface; try { - ApplyPreparedSurface(prepared); + ApplyPreparedSurface(prepared, surface?.Version); } finally { @@ -2000,28 +2005,30 @@ namespace GameCult.Eve.UnityScene { lock (_presentationFinalityGate) { - if (_presentationBarrierVersion > CurrentSurfaceDocument.Version) + if (_presentationBarrierVersion > _mountedBaseSurfaceVersion) throw new InvalidOperationException( $"The Eve presentation is awaiting provider surface version {_presentationBarrierVersion}; " + - $"mounted version {CurrentSurfaceDocument.Version} is read-only until that generation commits."); + $"mounted base version {_mountedBaseSurfaceVersion} is read-only until that generation commits."); } } private void BeginPresentationTransition(long sourceVersion) { - if (sourceVersion <= CurrentSurfaceDocument.Version) - return; lock (_presentationFinalityGate) + { + if (sourceVersion <= _mountedBaseSurfaceVersion) + return; _presentationBarrierVersion = Math.Max(_presentationBarrierVersion, sourceVersion); + } } private bool DeferTerminalReceiptUntilPresentation(EveCommandReceiptDocument receipt) { lock (_presentationFinalityGate) { - if (receipt.SourceVersion > CurrentSurfaceDocument.Version) + if (receipt.SourceVersion > _mountedBaseSurfaceVersion) _presentationBarrierVersion = Math.Max(_presentationBarrierVersion, receipt.SourceVersion); - if (_presentationBarrierVersion <= CurrentSurfaceDocument.Version) + if (_presentationBarrierVersion <= _mountedBaseSurfaceVersion) return false; _deferredTerminalReceipts[receipt.CommandId] = receipt; return true; @@ -2033,6 +2040,7 @@ namespace GameCult.Eve.UnityScene EveCommandReceiptDocument[] ready; lock (_presentationFinalityGate) { + _mountedBaseSurfaceVersion = Math.Max(_mountedBaseSurfaceVersion, committedVersion); if (_presentationBarrierVersion > committedVersion) return; _presentationBarrierVersion = -1; @@ -2046,6 +2054,15 @@ namespace GameCult.Eve.UnityScene FinalizeReceipt(receipt, terminal: true); } + private long MountedBaseSurfaceVersion + { + get + { + lock (_presentationFinalityGate) + return _mountedBaseSurfaceVersion; + } + } + private void FinalizeReceipt(EveCommandReceiptDocument receipt, bool terminal) { if (terminal) diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCultMeshAssetLookupTests.cs b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCultMeshAssetLookupTests.cs index d3bd735..bfcac30 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCultMeshAssetLookupTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnityCultMeshAssetLookupTests.cs @@ -208,6 +208,7 @@ namespace GameCult.Eve.UnityScene.Tests BindingFlags.NonPublic | BindingFlags.Instance)!; type.GetField("_bootstrapped", BindingFlags.NonPublic | BindingFlags.Instance)! .SetValue(transport, true); + complete.Invoke(transport, new object[] { 1L }); pending.Add(request.CommandId, request); var observed = new List(); transport.CommandReceiptAvailable += observed.Add; @@ -219,6 +220,7 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(pending.ContainsKey(request.CommandId), Is.True); Assert.That(observed, Is.Empty); + type.GetProperty("CurrentSurfaceDocument")!.SetValue(transport, SceneSurface(100)); var blocked = Assert.Throws(() => transport.SubmitCommand(Request("stale-launch", "launch"))); StringAssert.Contains("read-only", blocked!.Message); @@ -335,6 +337,33 @@ namespace GameCult.Eve.UnityScene.Tests sourceVersion, invocationHash: EveCommandInvocationHash.Compute(request)); + private static EveUnitySceneProviderSurfaceDocument SceneSurface(long version) + { + var surface = new EveSurfaceDocument( + "test.provider", + "", + "", + version, + "", + new EveSurfaceTree( + "test.surface", + new EveSurfaceComponent( + "root", + "surface", + new Dictionary(), + Array.Empty()), + Array.Empty()), + Array.Empty()); + return new EveUnitySceneProviderSurfaceDocument( + surface, + new EveUnitySceneProviderSurfaceAdvertisement( + "test.surface", + "interactive-world", + new EveUnitySceneWorldInteraction("", "", EveCommandReceiptDocument.SchemaId, "")), + "test:surface", + version); + } + private static async Task AwaitOrCancel(Task task, CancellationToken cancellationToken) { var cancelled = Task.Delay(Timeout.Infinite, cancellationToken); diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index 9e98294..6551218 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.129", + "version": "0.3.130", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {