Keep embedded surfaces out of base finality
This commit is contained in:
parent
1b6701456b
commit
44923edc22
4 changed files with 63 additions and 15 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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<EveUnitySceneCommandReceipt>();
|
||||
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<InvalidOperationException>(() =>
|
||||
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<string, string>(),
|
||||
Array.Empty<EveSurfaceComponent>()),
|
||||
Array.Empty<EveStyleToken>()),
|
||||
Array.Empty<EveCommandTemplate>());
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -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": {
|
||||
|
|
|
|||
Reference in a new issue