From a77cf88100c261ca593d51c50f43da6663386662 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 19 Jul 2026 14:06:25 +0100 Subject: [PATCH] Move daemon SoA frames off snapshot transport --- Aetheria.State.Daemon.Smoke/Program.cs | 8 ++-- Aetheria.State.Daemon/Program.cs | 9 ++-- Aetheria.State.Smoke/Program.cs | 41 +++++++++++++++---- Aetheria.Unity/Packages/manifest.json | 4 +- Aetheria.Unity/Packages/packages-lock.json | 6 +-- .../AetheriaRuntimeDaemonSoaFramePublisher.cs | 16 +++----- .../Runtime/AetheriaRuntimeVerseClient.cs | 1 - 7 files changed, 54 insertions(+), 31 deletions(-) diff --git a/Aetheria.State.Daemon.Smoke/Program.cs b/Aetheria.State.Daemon.Smoke/Program.cs index bcda19208..e4e368b9e 100644 --- a/Aetheria.State.Daemon.Smoke/Program.cs +++ b/Aetheria.State.Daemon.Smoke/Program.cs @@ -4439,8 +4439,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks RequireNear(100, Stat(target, "hull"), 0.000001, "trigger contact must not bypass the authored detonation delay"); - using var soaCache = new CultCache(); - using var soaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher(soaCache, producerEpoch: 1); + using var soaNetworkBodies = new CultMeshNetworkBodyStore(); + using var soaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher(soaNetworkBodies, producerEpoch: 1); using var soaFrame = soaPublisher.BuildCurrentZoneEntities( new AetheriaRuntimeDaemonFrameDocument { FrameId = 2, Run = run }); var mineIdentity = soaFrame.View.Identities.Single(identity => @@ -4625,8 +4625,8 @@ internal sealed class AetheriaDaemonYmirSmokeChecks node.Id == "aetheria.daemon.game.world.entity.0"); RequireEqual(rareHullAssetKey, rareSurfaceEntity.Props["assetRef"], "Eve scene projection must select the typed hull asset rather than the player Djinni fallback"); - using var rareSoaCache = new CultCache(); - using var rareSoaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher(rareSoaCache, producerEpoch: 1); + using var rareSoaNetworkBodies = new CultMeshNetworkBodyStore(); + using var rareSoaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher(rareSoaNetworkBodies, producerEpoch: 1); var rareSoa = rareSoaPublisher.BuildCurrentZoneEntities(rareFrame, catalog); RequireEqual(rareHullAssetKey, rareSoa.View.Identities.Single().AssetRef, "SoA identity and Eve scene projection must share the catalog-owned hull asset key"); diff --git a/Aetheria.State.Daemon/Program.cs b/Aetheria.State.Daemon/Program.cs index acdfef300..d635069ec 100644 --- a/Aetheria.State.Daemon/Program.cs +++ b/Aetheria.State.Daemon/Program.cs @@ -49,7 +49,8 @@ using var physicsPersistence = await AetheriaYmirPersistenceCoordinator.OpenAsyn node, worldPhysics, latestFrame).ConfigureAwait(false); -using var cultMeshRudpHost = StartClientCultMeshHost(node, options, () => latestFrame); +using var networkBodies = new CultMeshNetworkBodyStore(); +using var cultMeshRudpHost = StartClientCultMeshHost(node, options, () => latestFrame, networkBodies); using var clientSubscriptions = new CultNetDatabaseSubscriptionServer(cultMeshRudpHost, node.Database); var managedViewportDemand = new AetheriaManagedViewportDemandState(); clientSubscriptions.DemandChanged += managedViewportDemand.Observe; @@ -61,7 +62,7 @@ if (traceClientRudp) $"bodies=[{string.Join(",", demand.BodyIds)}] transports=[{string.Join(",", demand.SupportedBodyTransports)}]"); using var bodyDemand = new CultMeshBodyDemandTracker(clientSubscriptions); using var soaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher( - node.Cache, + networkBodies, DateTimeOffset.UtcNow.ToUnixTimeMilliseconds(), bodyDemand); using var clientPumpCancellation = new CancellationTokenSource(); @@ -640,7 +641,8 @@ static async Task ImportRemoteCommittedFactsAsy static RudpCultNetSchemaServer StartClientCultMeshHost( AetheriaStateNode node, AetheriaDaemonHostOptions options, - Func latestFrame) + Func latestFrame, + CultMeshNetworkBodyStore networkBodies) { var bundleCdnDocuments = BuildBundleCdnDocuments(node, options); var socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); @@ -655,6 +657,7 @@ static RudpCultNetSchemaServer StartClientCultMeshHost( MaxPendingReliablePackets = 8192 }); _ = new CultMeshContentServer(server, node.Cache); + _ = new CultMeshBodyServer(server, networkBodies); var advertisedEndpoint = $"rudp://{options.ClientCultMeshAdvertiseHost}:{((IPEndPoint)socket.LocalEndPoint!).Port}"; server.OnCultNet((request, peer) => { diff --git a/Aetheria.State.Smoke/Program.cs b/Aetheria.State.Smoke/Program.cs index 8d06d2059..5cba8875a 100644 --- a/Aetheria.State.Smoke/Program.cs +++ b/Aetheria.State.Smoke/Program.cs @@ -4,6 +4,7 @@ using Aetheria.State.Migration; using GameCult.Caching; using GameCult.Aetheria.State.Verse; using GameCult.Mesh; +using GameCult.Networking; using GameCult.Eve.Surface; using EveProviderAdvertisementState = GameCult.Eve.Surface.EveProviderAdvertisementDocument; using EveSurfaceDocument = GameCult.Eve.Surface.EveSurfaceDocument; @@ -1144,7 +1145,8 @@ static async Task ProveDirectSoaPublicationPipeline() } }; - using var publisher = new AetheriaRuntimeDaemonSoaFramePublisher(cache, producerEpoch: 9); + using var liveBodies = new CultMeshNetworkBodyStore(); + using var publisher = new AetheriaRuntimeDaemonSoaFramePublisher(liveBodies, producerEpoch: 9); var built = publisher.BuildCurrentZoneEntities(frame); var payloadIdentity = built.View.Identities.SingleOrDefault(identity => string.Equals(identity.EntityId, "soa-pipeline-smoke:zone:0:physical-payload:mine:soa-witness", StringComparison.Ordinal)); @@ -1170,6 +1172,8 @@ static async Task ProveDirectSoaPublicationPipeline() var published = await publisher.PublishAsync(built); if (cache.Get(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) != null) throw new InvalidOperationException("Building a CultMesh body publication made the Eve view visible before commit."); + if (cache.GetAll().Any() || cache.GetAll().Any()) + throw new InvalidOperationException("Hot SoA body bytes were retained as snapshot-addressable CDN state."); frame.FrameId++; var nextBuilt = publisher.BuildCurrentZoneEntities(frame); @@ -1215,11 +1219,13 @@ static async Task ProveDirectSoaPublicationPipeline() throw new InvalidOperationException("The view generation was substituted by the latest CultMesh body publication."); var view = cache.Get(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest) ?? throw new InvalidOperationException("Typed Eve SoA view did not become visible after body publication."); - if (body.PreferredLocal.TransportKind != CultMeshBodyTransportKind.SharedMemory || - body.NetworkFallback.TransportKind != CultMeshBodyTransportKind.Network || - body.PreferredLocal.TransportKind == CultMeshBodyTransportKind.SharedFileMapping || - body.NetworkFallback.TransportKind == CultMeshBodyTransportKind.SharedFileMapping) - throw new InvalidOperationException("Direct SoA publication used an Aetheria file-mapping convention."); + var localRepresentation = body.Representations.SingleOrDefault(candidate => + candidate.TransportKind == CultMeshBodyTransportKind.SharedMemory); + var networkRepresentation = body.Representations.SingleOrDefault(candidate => + candidate.TransportKind == CultMeshBodyTransportKind.Network); + if (localRepresentation == null || networkRepresentation == null || + body.Representations.Any(candidate => candidate.TransportKind == CultMeshBodyTransportKind.SharedFileMapping)) + throw new InvalidOperationException("Direct SoA publication did not expose shared memory plus direct network representations."); var request = new CultMeshBodyValidationRequest { @@ -1236,14 +1242,14 @@ static async Task ProveDirectSoaPublicationPipeline() new ICultMeshBodyTransportAdapter[] { new CultMeshSharedMemoryBodyAdapter(), - new CultMeshNetworkBodyAdapter(new CultMeshNetworkBodyResolver(cache).CreateFetchDelegate()) + new CultMeshNetworkBodyAdapter(descriptor => ReadLiveBody(liveBodies, descriptor)) }, (producerId, _) => producerId == AetheriaRuntimeDaemonSoaFramePublisher.ProducerId)) .ResolveReadOnly(body, request); using var networkLease = new CultMeshBodyPublicationResolver(new CultMeshBodyTransportService( new ICultMeshBodyTransportAdapter[] { - new CultMeshNetworkBodyAdapter(new CultMeshNetworkBodyResolver(cache).CreateFetchDelegate()) + new CultMeshNetworkBodyAdapter(descriptor => ReadLiveBody(liveBodies, descriptor)) }, (producerId, _) => producerId == AetheriaRuntimeDaemonSoaFramePublisher.ProducerId)) .ResolveReadOnly(body, request); @@ -1281,6 +1287,25 @@ static async Task ProveDirectSoaPublicationPipeline() throw new InvalidOperationException("Inactive physical payload remained in SoA or disturbed a surviving synthetic identity."); } +static byte[] ReadLiveBody(CultMeshNetworkBodyStore bodies, CultMeshBodyDescriptor descriptor) +{ + var request = new CultMeshBodyReadRequestMessage + { + MessageId = Guid.NewGuid().ToString("N"), + CapabilityToken = descriptor.CapabilityToken, + BodyId = descriptor.BodyId, + BodySchemaId = descriptor.SchemaId, + LayoutVersion = descriptor.LayoutVersion, + ProducerEpoch = descriptor.ProducerEpoch, + Sequence = descriptor.Sequence, + ExpectedSizeBytes = descriptor.ByteSize, + SemanticHash = descriptor.SemanticHash + }; + if (!bodies.TryRead(request, DateTimeOffset.UtcNow, out _, out var body)) + throw new InvalidOperationException("Ephemeral SoA network generation could not be resolved."); + return body; +} + static int ReadInt32(EveEntitySoaViewDocument view, ICultMeshBodyReadLease lease, string semantic, int row) { var column = view.Columns.Single(candidate => candidate.Semantic == semantic); diff --git a/Aetheria.Unity/Packages/manifest.json b/Aetheria.Unity/Packages/manifest.json index a27d70649..b5e8e378f 100644 --- a/Aetheria.Unity/Packages/manifest.json +++ b/Aetheria.Unity/Packages/manifest.json @@ -7,9 +7,9 @@ "com.unity.modules.physics": "1.0.0", "com.unity.modules.uielements": "1.0.0", "com.unity.render-pipelines.universal": "17.4.0", - "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.30", + "org.gamecult.cultlib": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.31", "org.gamecult.eve.plugin-fields": "https://github.com/GameCult/EvePlugins.git?path=/plugins/eve-plugin-fields/unity/org.gamecult.eve.plugin-fields#eve-plugin-fields-unity-v0.2.3", "org.gamecult.eve.surface": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.surface#eveunity-surface-v0.2.4", - "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.88" + "org.gamecult.eve.unity-scene": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.89" } } diff --git a/Aetheria.Unity/Packages/packages-lock.json b/Aetheria.Unity/Packages/packages-lock.json index ef9743027..aa687ee6e 100644 --- a/Aetheria.Unity/Packages/packages-lock.json +++ b/Aetheria.Unity/Packages/packages-lock.json @@ -127,7 +127,7 @@ } }, "org.gamecult.cultlib": { - "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.30", + "version": "https://github.com/GameCult/CultLib.git?path=/unity/org.gamecult.cultlib#cultlib-unity-v1.0.31", "depth": 0, "source": "git", "dependencies": {}, @@ -152,11 +152,11 @@ "hash": "e08fa08335f99e9edddeb706912eecfad07cb281" }, "org.gamecult.eve.unity-scene": { - "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.88", + "version": "https://github.com/GameCult/EveUnity.git?path=/packages/org.gamecult.eve.unity-scene#eveunity-scene-v0.3.89", "depth": 0, "source": "git", "dependencies": { - "org.gamecult.cultlib": "1.0.30", + "org.gamecult.cultlib": "1.0.31", "org.gamecult.eve.plugin-fields": "0.2.3", "org.gamecult.eve.surface": "0.2.4", "com.unity.mathematics": "1.3.2", diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaFramePublisher.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaFramePublisher.cs index aaba0072a..714042316 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaFramePublisher.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeDaemonSoaFramePublisher.cs @@ -3,7 +3,6 @@ using System.Buffers.Binary; using System.Collections.Generic; using System.Linq; using System.Threading.Tasks; -using GameCult.Caching; using GameCult.Eve.Surface; using GameCult.Mesh; @@ -82,25 +81,22 @@ namespace GameCult.Aetheria.State.Verse private const string EntityProxyMaterialUri = "cultmesh://aetheria/assets/daemon/entity_proxy/material"; private readonly CultMeshFrameBodyPublisher _localPublisher; - private readonly CultMeshNetworkBodyPublisher _networkPublisher; + private readonly CultMeshNetworkBodyStore _networkBodies; private readonly CultMeshBodyDemandTracker? _demand; private readonly Dictionary _syntheticEntityIndices = new Dictionary(StringComparer.Ordinal); private int _nextSyntheticEntityIndex = -2; public AetheriaRuntimeDaemonSoaFramePublisher( - CultCache cache, + CultMeshNetworkBodyStore networkBodies, long producerEpoch, CultMeshBodyDemandTracker? demand = null) { - if (cache == null) throw new ArgumentNullException(nameof(cache)); + _networkBodies = networkBodies ?? throw new ArgumentNullException(nameof(networkBodies)); _localPublisher = new CultMeshFrameBodyPublisher( BodyId, BodySchemaId, LayoutVersion, Capacity, producerEpoch, checked((int)EntityHotSlabLayout.Create(Capacity).TotalByteLength), TimeSpan.FromSeconds(30)); - _networkPublisher = new CultMeshNetworkBodyPublisher( - cache, - generation => string.Equals(generation.ProducerId, ProducerId, StringComparison.Ordinal)); _demand = demand; } @@ -299,7 +295,7 @@ namespace GameCult.Aetheria.State.Verse } } - public async Task PublishAsync(AetheriaRuntimeDaemonSoaFrame frame) + public Task PublishAsync(AetheriaRuntimeDaemonSoaFrame frame) { if (frame == null) throw new ArgumentNullException(nameof(frame)); var now = DateTimeOffset.UtcNow; @@ -320,7 +316,7 @@ namespace GameCult.Aetheria.State.Verse var representations = new List(); if (frame.PublishSharedMemory) representations.Add(local); if (frame.PublishNetwork) - representations.Add(await _networkPublisher.PublishAsync(generation, networkBytes!).ConfigureAwait(false)); + representations.Add(_networkBodies.PublishOwned(generation, networkBytes!)); var publication = new CultMeshBodyPublicationDocument { BodyId = BodyId, @@ -341,7 +337,7 @@ namespace GameCult.Aetheria.State.Verse if (view.Buffers.Length != 1 || !string.Equals(view.Buffers[0].BufferId, publication.BodyId, StringComparison.Ordinal) || view.Columns.Any(column => !string.Equals(column.BufferId, publication.BodyId, StringComparison.Ordinal))) throw new InvalidOperationException("Aetheria Eve SoA layout does not reference its published logical body identity."); - return new AetheriaRuntimeDaemonSoaPublication(publication, view); + return Task.FromResult(new AetheriaRuntimeDaemonSoaPublication(publication, view)); } public void Dispose() => _localPublisher.Dispose(); diff --git a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs index daa1ef97e..0aef76752 100644 --- a/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs +++ b/Packages/org.gamecult.aetheria.state/Runtime/AetheriaRuntimeVerseClient.cs @@ -189,7 +189,6 @@ namespace GameCult.Aetheria.State.Verse typeof(EveAssetCatalogDocument), typeof(EveEntitySoaViewDocument), typeof(CultMeshBodyPublicationDocument), - typeof(CultMeshNetworkBodyDocument), typeof(CultMeshCdnArtifactManifest), typeof(CultMeshCdnArtifactChunk), typeof(AetheriaRuntimeDaemonCommandDocument),