From d21cfecaf520f24940d29156e78e9cee46423c58 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Thu, 9 Jul 2026 23:13:17 +0100 Subject: [PATCH] Route EveUnity live providers through generic client --- docs/eve-migration-roadmap.md | 8 +++- .../EveUnitySceneLiveProviderBridge.cs | 4 +- .../EveUnitySceneProviderConnection.cs | 38 +++++++++++++++++++ .../EveUnitySceneSurfaceLowererTests.cs | 5 ++- scripts/run-aetheria-unity-package-smoke.ps1 | 37 ++++++++++++++++++ ...un-eveunity-scene-provider-shell-smoke.ps1 | 7 +++- 6 files changed, 93 insertions(+), 6 deletions(-) diff --git a/docs/eve-migration-roadmap.md b/docs/eve-migration-roadmap.md index efc0a6d..40a4758 100644 --- a/docs/eve-migration-roadmap.md +++ b/docs/eve-migration-roadmap.md @@ -1021,7 +1021,13 @@ Recently cut: `AetheriaRuntimeVerseRecordKeys.DaemonAssetManifest` record, submits movement through the advertised `gamecult.eve.command.v1` boundary, observes provider-owned receipts, and refreshes from daemon snapshots without - importing Aetheria product authority into EveUnity. Aetheria also proves the + importing Aetheria product authority into EveUnity. The generic Unity + document-source wrapper now connects live provider sources through + `IEveUnitySceneProviderSurfaceDocumentConnection`, delegates provider refresh + through `IEveUnityProviderRefreshSource`, `Connect()` is idempotent for the + current snapshot, and `EveUnitySceneLiveProviderBridge` forwards receipts + while `EveUnityPlayableWorldLiveClient` owns refresh-on-terminal receipt + policy. Aetheria also proves the generic Unity scene body, not only a recording sink: `GenericEveUnityClientHostInstantiatesAetheriaDaemonWorldThroughProviderComponent` mounts the public provider component through `EveUnityPlayableWorldClientHost`, diff --git a/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs b/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs index 134109d..44afc89 100644 --- a/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs +++ b/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneLiveProviderBridge.cs @@ -65,6 +65,7 @@ namespace GameCult.Eve.UnityScene public sealed class EveUnitySceneLiveProviderBridgeComponent : MonoBehaviour, IEveUnitySceneProviderSurfaceDocumentSource, + IEveUnitySceneProviderSurfaceDocumentConnection, IEveUnityPlayableWorldAssetManifestDocumentSource, IEveUnitySceneCommandSink, IEveUnitySceneCommandReceiptSource, @@ -179,6 +180,7 @@ namespace GameCult.Eve.UnityScene public sealed class EveUnitySceneLiveProviderBridge : IEveUnitySceneProviderSurfaceDocumentSource, + IEveUnitySceneProviderSurfaceDocumentConnection, IEveUnityPlayableWorldAssetManifestDocumentSource, IEveUnitySceneCommandSink, IEveUnitySceneCommandReceiptSource, @@ -282,8 +284,6 @@ namespace GameCult.Eve.UnityScene private void OnCommandReceiptAvailable(EveUnitySceneCommandReceipt receipt) { if (receipt == null) throw new ArgumentNullException(nameof(receipt)); - if (receipt.ShouldRefreshProviderSurface) - _transport.Refresh(); ReceiptAvailable?.Invoke(receipt); } } diff --git a/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs b/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs index 44266d0..e21292c 100644 --- a/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs +++ b/runtimes/incubating/eve-unity-scene/Runtime/EveUnitySceneProviderConnection.cs @@ -19,6 +19,8 @@ namespace GameCult.Eve.UnityScene void Connect(); + void Refresh(); + void Disconnect(); } @@ -36,6 +38,13 @@ namespace GameCult.Eve.UnityScene event Action DocumentAvailable; } + public interface IEveUnitySceneProviderSurfaceDocumentConnection + { + void Connect(); + + void Disconnect(); + } + public sealed class EveUnitySceneProviderSurfaceDocument { public EveUnitySceneProviderSurfaceDocument( @@ -71,12 +80,16 @@ namespace GameCult.Eve.UnityScene public sealed class EveUnitySceneProviderSurfaceDocumentSource : IEveUnitySceneProviderSurfaceSource, IDisposable { private readonly IEveUnitySceneProviderSurfaceDocumentSource _documentSource; + private readonly IEveUnitySceneProviderSurfaceDocumentConnection? _documentConnection; + private readonly IEveUnityProviderRefreshSource? _refreshSource; private bool _connected; public EveUnitySceneProviderSurfaceDocumentSource( IEveUnitySceneProviderSurfaceDocumentSource documentSource) { _documentSource = documentSource ?? throw new ArgumentNullException(nameof(documentSource)); + _documentConnection = documentSource as IEveUnitySceneProviderSurfaceDocumentConnection; + _refreshSource = documentSource as IEveUnityProviderRefreshSource; CurrentSnapshot = _documentSource.CurrentDocument.ToSnapshot(); } @@ -96,14 +109,21 @@ namespace GameCult.Eve.UnityScene return; _documentSource.DocumentAvailable += OnDocumentAvailable; + _documentConnection?.Connect(); _connected = true; } + public void Refresh() + { + _refreshSource?.Refresh(); + } + public void Disconnect() { if (!_connected) return; + _documentConnection?.Disconnect(); _documentSource.DocumentAvailable -= OnDocumentAvailable; _connected = false; } @@ -166,11 +186,18 @@ namespace GameCult.Eve.UnityScene _connected = true; } + if (HasAppliedCurrentSnapshot()) + return _session.ActiveProjection!; + return ApplySnapshot(_surfaceSource.CurrentSnapshot); } public EveUnitySceneProjection Refresh() { + _surfaceSource.Refresh(); + if (HasAppliedCurrentSnapshot()) + return _session.ActiveProjection!; + return ApplySnapshot(_surfaceSource.CurrentSnapshot); } @@ -245,6 +272,17 @@ namespace GameCult.Eve.UnityScene return projection; } + private bool HasAppliedCurrentSnapshot() + { + var projection = _session.ActiveProjection; + if (projection == null) + return false; + + var snapshot = _surfaceSource.CurrentSnapshot; + return _session.ActiveVersion == snapshot.Version && + string.Equals(_session.ActiveSourcePointer, snapshot.SourcePointer, StringComparison.Ordinal); + } + private void OnSnapshotAvailable(EveUnitySceneProviderSurfaceSnapshot snapshot) { ApplySnapshot(snapshot); diff --git a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs index 68c6ab3..65b01d3 100644 --- a/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/runtimes/incubating/eve-unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -372,7 +372,6 @@ namespace GameCult.Eve.UnityScene.Tests using var bridge = new EveUnitySceneLiveProviderBridge(transport); var sceneSink = new FakePlayableWorldSceneSink(); - bridge.Connect(); using var runtime = new EveUnityPlayableWorldRuntime( bridge, bridge, @@ -1407,6 +1406,10 @@ namespace GameCult.Eve.UnityScene.Tests { } + public void Refresh() + { + } + public void Disconnect() { } diff --git a/scripts/run-aetheria-unity-package-smoke.ps1 b/scripts/run-aetheria-unity-package-smoke.ps1 index 76c70f6..26cfe19 100644 --- a/scripts/run-aetheria-unity-package-smoke.ps1 +++ b/scripts/run-aetheria-unity-package-smoke.ps1 @@ -24,6 +24,8 @@ $aetheriaGameSurfaceBuilder = Join-Path $AetheriaRoot "Packages\org.gamecult.aet $aetheriaDaemonOperationsClient = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonOperationsClient.cs" $aetheriaDaemonDocuments = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeDaemonDocuments.cs" $aetheriaRuntimeVerseClient = Join-Path $AetheriaRoot "Packages\org.gamecult.aetheria.state\Runtime\AetheriaRuntimeVerseClient.cs" +$eveUnitySceneProviderConnection = Join-Path $eveRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneProviderConnection.cs" +$eveUnitySceneLiveProviderBridge = Join-Path $eveRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneLiveProviderBridge.cs" if (-not (Test-Path $manifestPath)) { throw "Aetheria Unity package manifest not found: $manifestPath" } @@ -69,6 +71,12 @@ if (-not (Test-Path $aetheriaDaemonDocuments)) { if (-not (Test-Path $aetheriaRuntimeVerseClient)) { throw "Aetheria runtime Verse client not found: $aetheriaRuntimeVerseClient" } +if (-not (Test-Path $eveUnitySceneProviderConnection)) { + throw "EveUnity scene provider connection not found: $eveUnitySceneProviderConnection" +} +if (-not (Test-Path $eveUnitySceneLiveProviderBridge)) { + throw "EveUnity scene live provider bridge not found: $eveUnitySceneLiveProviderBridge" +} $manifest = Get-Content -Raw -LiteralPath $manifestPath | ConvertFrom-Json $requiredDependencies = @{ @@ -120,6 +128,35 @@ foreach ($compileItem in $requiredSceneCompileItems) { } } +$sceneProviderConnection = Get-Content -Raw -LiteralPath $eveUnitySceneProviderConnection +foreach ($symbol in @( + "IEveUnitySceneProviderSurfaceDocumentConnection", + "_documentConnection?.Connect();", + "_documentConnection?.Disconnect();", + "_refreshSource?.Refresh();", + "HasAppliedCurrentSnapshot()", + "_session.ActiveSourcePointer" +)) { + if (-not $sceneProviderConnection.Contains($symbol)) { + throw "EveUnity scene provider connection missing live-source lifecycle symbol: $symbol" + } +} + +$sceneLiveProviderBridge = Get-Content -Raw -LiteralPath $eveUnitySceneLiveProviderBridge +foreach ($symbol in @( + "IEveUnitySceneProviderSurfaceDocumentConnection", + "IEveUnitySceneLiveProviderTransport", + "CommandReceiptAvailable += OnCommandReceiptAvailable", + "ReceiptAvailable?.Invoke(receipt);" +)) { + if (-not $sceneLiveProviderBridge.Contains($symbol)) { + throw "EveUnity scene live provider bridge missing transport receipt/lifecycle symbol: $symbol" + } +} +if ($sceneLiveProviderBridge.Contains("if (receipt.ShouldRefreshProviderSurface)")) { + throw "EveUnity scene live provider bridge must forward receipts; playable-world live client owns refresh policy" +} + $runtimePackage = Get-Content -Raw -LiteralPath $aetheriaEveRuntimePackage foreach ($dependency in @( '"org.gamecult.eve.unity-scene": "0.1.0"', diff --git a/scripts/run-eveunity-scene-provider-shell-smoke.ps1 b/scripts/run-eveunity-scene-provider-shell-smoke.ps1 index 49309fd..a81d034 100644 --- a/scripts/run-eveunity-scene-provider-shell-smoke.ps1 +++ b/scripts/run-eveunity-scene-provider-shell-smoke.ps1 @@ -106,14 +106,14 @@ foreach ($symbol in @("EveUnitySceneClientSession", "EveUnitySceneProviderSurfac } $providerConnectionSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneProviderConnection.cs") -Raw -foreach ($symbol in @("IEveUnitySceneProviderSurfaceSource", "IEveUnitySceneCommandSink", "IEveUnitySceneProviderSurfaceDocumentSource", "EveUnitySceneProviderSurfaceDocument", "EveUnitySceneProviderSurfaceDocumentSource", "ToSnapshot", "DocumentAvailable", "EveUnitySceneProviderConnection", "SnapshotAvailable", "CurrentSnapshot", "Connect", "Refresh", "SubmitMoveIntent", "SubmitMoveVectorIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Disconnect")) { +foreach ($symbol in @("IEveUnitySceneProviderSurfaceSource", "IEveUnitySceneCommandSink", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneProviderSurfaceDocumentConnection", "IEveUnityProviderRefreshSource", "EveUnitySceneProviderSurfaceDocument", "EveUnitySceneProviderSurfaceDocumentSource", "ToSnapshot", "DocumentAvailable", "EveUnitySceneProviderConnection", "SnapshotAvailable", "CurrentSnapshot", "Connect", "Refresh", "_refreshSource?.Refresh();", "SubmitMoveIntent", "SubmitMoveVectorIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "Disconnect", "HasAppliedCurrentSnapshot")) { if (-not $providerConnectionSource.Contains($symbol)) { throw "EveUnity scene provider connection missing symbol: $symbol" } } $liveProviderBridgeSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnitySceneLiveProviderBridge.cs") -Raw -foreach ($symbol in @("IEveUnitySceneLiveProviderTransport", "EveUnitySceneLiveProviderTransportBehaviour", "EveUnitySceneLiveProviderBridgeComponent", "EveUnitySceneLiveProviderBridge", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandSink", "IEveUnitySceneCommandReceiptSource", "IEveUnityProviderRefreshSource", "SubmitCommand", "CommandReceiptAvailable", "ShouldRefreshProviderSurface", "transportBehaviour")) { +foreach ($symbol in @("IEveUnitySceneLiveProviderTransport", "EveUnitySceneLiveProviderTransportBehaviour", "EveUnitySceneLiveProviderBridgeComponent", "EveUnitySceneLiveProviderBridge", "IEveUnitySceneProviderSurfaceDocumentSource", "IEveUnitySceneProviderSurfaceDocumentConnection", "IEveUnityPlayableWorldAssetManifestDocumentSource", "IEveUnitySceneCommandSink", "IEveUnitySceneCommandReceiptSource", "IEveUnityProviderRefreshSource", "SubmitCommand", "CommandReceiptAvailable", "ReceiptAvailable?.Invoke(receipt);", "transportBehaviour")) { if (-not $liveProviderBridgeSource.Contains($symbol)) { throw "EveUnity scene live provider bridge missing symbol: $symbol" } @@ -121,6 +121,9 @@ foreach ($symbol in @("IEveUnitySceneLiveProviderTransport", "EveUnitySceneLiveP if ($liveProviderBridgeSource.Contains("Aetheria")) { throw "EveUnity scene live provider bridge must remain provider-agnostic and cannot reference Aetheria" } +if ($liveProviderBridgeSource.Contains("if (receipt.ShouldRefreshProviderSurface)")) { + throw "EveUnity scene live provider bridge must forward receipts; playable-world live client owns refresh policy" +} $liveClientSource = Get-Content -LiteralPath (Join-Path $projectRoot "runtimes\incubating\eve-unity-scene\Runtime\EveUnityPlayableWorldLiveClient.cs") -Raw foreach ($symbol in @("EveUnityPlayableWorldLiveClient", "IEveUnitySceneCommandReceiptSource", "EveUnitySceneCommandReceipt", "EveUnitySceneProviderConnection", "EveUnityPlayableWorldPresenter", "ActiveWorld", "LastPresentation", "LastReceipt", "ReceiptAvailable", "Connect", "Refresh", "SubmitMoveIntent", "SubmitMoveVectorIntent", "SubmitFocusIntent", "SubmitTargetIntent", "SubmitActionIntent", "ProjectionUpdated", "ShouldRefreshProviderSurface", "IsProviderOwned", "Disconnect")) {