From 054779251688dc307376466e1ca54d0e43e7fc7f Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Sun, 19 Jul 2026 04:07:02 +0100 Subject: [PATCH] Drive stable entity layouts from hot body publications --- .../EveUnityCultMeshLiveProviderTransport.cs | 41 +++++++++++++++++++ .../EveUnitySceneSurfaceLowererTests.cs | 34 +++++++++++++++ .../org.gamecult.eve.unity-scene/package.json | 2 +- 3 files changed, 76 insertions(+), 1 deletion(-) 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 d07f237..d359846 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs @@ -67,6 +67,7 @@ namespace GameCult.Eve.UnityScene private EveProviderAdvertisementDocument? _advertisement; private EveAdvertisedSurface? _advertisedSurface; private CultMeshBodyPublicationResolver? _bodyResolver; + private EveEntitySoaViewDocument? _latestEntityLayout; private long _lastQueuedEntityViewEpoch = -1; private long _lastQueuedEntityViewSequence = -1; private bool _disposed; @@ -200,6 +201,7 @@ namespace GameCult.Eve.UnityScene private void QueueEntityView(EveEntitySoaViewDocument document) { + _latestEntityLayout = document; if (document.ProducerEpoch < _lastQueuedEntityViewEpoch || (document.ProducerEpoch == _lastQueuedEntityViewEpoch && document.Sequence <= _lastQueuedEntityViewSequence)) @@ -209,6 +211,41 @@ namespace GameCult.Eve.UnityScene _liveDocuments.Enqueue(document); } + private void QueueBodyPublication(CultMeshBodyPublicationDocument publication) + { + var layout = _latestEntityLayout; + if (layout == null || layout.Buffers == null || layout.Buffers.Length == 0 || + !string.Equals(layout.Buffers[0].BufferId, publication.BodyId, StringComparison.Ordinal)) + return; + + QueueEntityView(new EveEntitySoaViewDocument + { + Schema = layout.Schema, + ProviderId = layout.ProviderId, + ViewId = layout.ViewId, + PublishedAtUtc = DateTimeOffset.UtcNow.ToString("O"), + BodySchemaId = layout.BodySchemaId, + LayoutVersion = layout.LayoutVersion, + ProducerEpoch = publication.ProducerEpoch, + Sequence = publication.Sequence, + Capacity = layout.Capacity, + Buffers = layout.Buffers, + Columns = layout.Columns, + DirtyRanges = (layout.DirtyRanges ?? Array.Empty()) + .Select(range => new EveEntitySoaDirtyRange + { + ColumnId = range.ColumnId, + StartIndex = range.StartIndex, + Count = range.Count, + Sequence = publication.Sequence + }) + .ToArray(), + RenderGroups = layout.RenderGroups, + Identities = layout.Identities, + FrameId = publication.Sequence + }); + } + public void SubmitCommand(EveSurfaceCommandRequest request) { if (request == null) throw new ArgumentNullException(nameof(request)); @@ -612,6 +649,8 @@ namespace GameCult.Eve.UnityScene { if (document is EveEntitySoaViewDocument current) QueueEntityView(current); + else if (document is CultMeshBodyPublicationDocument publication) + QueueBodyPublication(publication); else if (document is EveFieldsSplatsDocument fields) _liveDocuments.Enqueue(fields); } @@ -649,6 +688,8 @@ namespace GameCult.Eve.UnityScene { if (change.Document is EveEntitySoaViewDocument entityView) QueueEntityView(entityView); + else if (change.Document is CultMeshBodyPublicationDocument publication) + QueueBodyPublication(publication); else if (change.Document is EveSurfaceDocument or EveCommandReceiptDocument or EveFieldsSplatsDocument) _liveDocuments.Enqueue(change.Document); } 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 566a61f..a10dd96 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -53,6 +53,40 @@ namespace GameCult.Eve.UnityScene.Tests Assert.That(handle.RecordKey, Is.Not.EqualTo(generationNPlusOne.RecordKey)); } + [Test] + public void LiveTransportReusesStableLayoutForNewBodyPublications() + { + using var transport = new EveUnityCultMeshLiveProviderTransport( + "test-replica.cc", + "cultnet://127.0.0.1:3075", + "aetheria", + "aetheria.pilot"); + var view = EntityLeaseDocument(); + var publication = BodyPublication(view, view.Sequence + 1); + var queueView = typeof(EveUnityCultMeshLiveProviderTransport) + .GetMethod("QueueEntityView", BindingFlags.NonPublic | BindingFlags.Instance)!; + var queuePublication = typeof(EveUnityCultMeshLiveProviderTransport) + .GetMethod("QueueBodyPublication", BindingFlags.NonPublic | BindingFlags.Instance)!; + var queue = (System.Collections.Concurrent.ConcurrentQueue) + typeof(EveUnityCultMeshLiveProviderTransport) + .GetField("_liveDocuments", BindingFlags.NonPublic | BindingFlags.Instance)! + .GetValue(transport)!; + + queueView.Invoke(transport, new object[] { view }); + Assert.That(queue.TryDequeue(out _), Is.True); + queuePublication.Invoke(transport, new object[] { publication }); + + Assert.That(queue.TryDequeue(out var queued), Is.True); + var generation = queued as EveEntitySoaViewDocument; + Assert.That(generation, Is.Not.Null); + Assert.That(generation!.Sequence, Is.EqualTo(publication.Sequence)); + Assert.That(generation.ProducerEpoch, Is.EqualTo(publication.ProducerEpoch)); + Assert.That(generation.Buffers, Is.SameAs(view.Buffers)); + Assert.That(generation.Columns, Is.SameAs(view.Columns)); + Assert.That(generation.Identities, Is.SameAs(view.Identities)); + Assert.That(generation.DirtyRanges.All(range => range.Sequence == publication.Sequence), Is.True); + } + [Test] public void LiveTransportDropsExpiredHistoricalGenerationWithoutReinterpretingLatest() { diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index bde89f2..24f05f5 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.75", + "version": "0.3.76", "unity": "6000.0", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {