From d9c1de9130419103d7421d95fb2c6789a0921f46 Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 19:47:19 +0100 Subject: [PATCH] Resolve CultMesh bodies by view generation --- .../EveUnityCultMeshLiveProviderTransport.cs | 21 +++++-- .../EveUnitySceneSurfaceLowererTests.cs | 56 +++++++++++++++++++ .../GameCult.Eve.UnityScene.Tests.asmdef | 1 + .../org.gamecult.eve.unity-scene/package.json | 4 +- 4 files changed, 76 insertions(+), 6 deletions(-) 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 7b0d8ed..171ea4f 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs @@ -100,6 +100,17 @@ namespace GameCult.Eve.UnityScene public event Action? EntityViewAvailable; + private static CultMeshBodyPublicationHandle BodyPublicationHandle(EveEntitySoaViewDocument document) + { + if (document == null) throw new ArgumentNullException(nameof(document)); + if (document.Buffers == null || document.Buffers.Length == 0) + throw new InvalidOperationException("Entity layout does not name a primary logical buffer."); + return new CultMeshBodyPublicationHandle( + document.Buffers[0].BufferId, + document.ProducerEpoch, + document.Sequence); + } + public void Connect() { EnsureOpen(); @@ -293,16 +304,18 @@ namespace GameCult.Eve.UnityScene throw new UnauthorizedAccessException("Entity layout provider does not match the advertised Eve provider."); if (document.Buffers == null || document.Buffers.Length == 0) throw new InvalidOperationException("Entity layout does not name a primary logical buffer."); - var bodyId = document.Buffers[0].BufferId; + var handle = BodyPublicationHandle(document); var publication = _snapshot! .FetchDocumentsAsync( - recordKeys: new[] { CultMeshBodyPublicationDocument.CreateRecordKey(bodyId).Value }, + recordKeys: new[] { handle.RecordKey.Value }, schemaIds: new[] { CultMeshBodyPublicationSchemaVersions.Publication }) .GetAwaiter().GetResult().FirstOrDefault() - ?? throw new InvalidOperationException($"Provider did not publish CultMesh body '{bodyId}'."); + ?? throw new InvalidOperationException( + $"Provider did not publish CultMesh body generation '{handle.RecordKey.Value}'."); + handle.Validate(publication); var lease = _bodyResolver.ResolveReadOnly(publication, new CultMeshBodyValidationRequest { - BodyId = bodyId, + BodyId = handle.BodyId, SchemaId = document.BodySchemaId, LayoutVersion = document.LayoutVersion, ProducerEpoch = document.ProducerEpoch, 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 37203c4..3df7dfd 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -1,6 +1,8 @@ using System; using System.Collections.Generic; using System.Linq; +using System.Reflection; +using GameCult.Caching; using GameCult.Eve.Surface; using GameCult.Mesh; using NUnit.Framework; @@ -12,6 +14,29 @@ namespace GameCult.Eve.UnityScene.Tests { public sealed class EveUnitySceneSurfaceLowererTests { + [Test] + public async System.Threading.Tasks.Task LiveTransportResolvesViewGenerationWhenNextPublicationAlreadyExists() + { + var view = EntityLeaseDocument(); + var generationN = BodyPublication(view, sequence: view.Sequence); + var generationNPlusOne = BodyPublication(view, sequence: view.Sequence + 1); + var cache = new CultCache(); + await cache.UpsertAsync(generationN, + new CultRecordHandle(generationN.RecordKey)); + await cache.UpsertAsync(generationNPlusOne, + new CultRecordHandle(generationNPlusOne.RecordKey)); + + var handle = (CultMeshBodyPublicationHandle)typeof(EveUnityCultMeshLiveProviderTransport) + .GetMethod("BodyPublicationHandle", BindingFlags.NonPublic | BindingFlags.Static)! + .Invoke(null, new object[] { view })!; + var resolved = cache.Get(handle.RecordKey); + + Assert.That(resolved, Is.Not.Null); + handle.Validate(resolved!); + Assert.That(resolved!.Sequence, Is.EqualTo(view.Sequence)); + Assert.That(handle.RecordKey, Is.Not.EqualTo(generationNPlusOne.RecordKey)); + } + [Test] public void EntitySoaViewReadsGenericSemanticColumnsFromInjectedLease() { @@ -1827,6 +1852,37 @@ namespace GameCult.Eve.UnityScene.Tests } }; + private static CultMeshBodyPublicationDocument BodyPublication( + EveEntitySoaViewDocument view, + long sequence) + { + var descriptor = FakeBodyReadLease.DescriptorFor(view, view.Buffers[0].ByteLength); + descriptor.Sequence = sequence; + descriptor.Synchronization = CultMeshBodySynchronization.ImmutableSequence; + descriptor.LeaseExpiresAtUnixMs = DateTimeOffset.UtcNow.AddMinutes(1).ToUnixTimeMilliseconds(); + descriptor.TransportKind = CultMeshBodyTransportKind.SharedFileMapping; + var network = FakeBodyReadLease.DescriptorFor(view, view.Buffers[0].ByteLength); + network.Sequence = sequence; + network.Synchronization = descriptor.Synchronization; + network.LeaseExpiresAtUnixMs = descriptor.LeaseExpiresAtUnixMs; + network.TransportKind = CultMeshBodyTransportKind.Network; + return new CultMeshBodyPublicationDocument + { + BodyId = descriptor.BodyId, + ProducerId = view.ProviderId, + SchemaId = descriptor.SchemaId, + LayoutVersion = descriptor.LayoutVersion, + ByteSize = descriptor.ByteSize, + Capacity = descriptor.Capacity, + ProducerEpoch = descriptor.ProducerEpoch, + Sequence = sequence, + Synchronization = descriptor.Synchronization, + LivenessExpiresAtUnixMs = descriptor.LeaseExpiresAtUnixMs, + PreferredLocal = descriptor, + NetworkFallback = network + }; + } + private sealed class FakeBodyReadLease : ICultMeshBodyReadLease { private readonly byte[] _bytes; diff --git a/packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef b/packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef index 2f8a14e..4428a96 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/GameCult.Eve.UnityScene.Tests.asmdef @@ -4,6 +4,7 @@ "references": [ "GameCult.Eve.Surface", "GameCult.Eve.UnityScene", + "GameCult.Eve.UnityScene.CultMesh", "GameCult.Eve.PluginFields" ], "includePlatforms": [ diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index f4b02d9..ee51a03 100644 --- a/packages/org.gamecult.eve.unity-scene/package.json +++ b/packages/org.gamecult.eve.unity-scene/package.json @@ -1,11 +1,11 @@ { "name": "org.gamecult.eve.unity-scene", "displayName": "Eve Unity Scene Lowering", - "version": "0.2.0", + "version": "0.2.1", "unity": "2021.3", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": { - "org.gamecult.cultlib": "1.0.8", + "org.gamecult.cultlib": "1.0.9", "org.gamecult.eve.plugin-fields": "0.1.1", "org.gamecult.eve.surface": "0.2.0", "com.unity.mathematics": "1.3.2"