From 61a255a4d5f965c39cce01554a4ec4bce627d24e Mon Sep 17 00:00:00 2001 From: Meta Vens Date: Mon, 13 Jul 2026 20:27:00 +0100 Subject: [PATCH] Resolve CultMesh network body fallback --- .../EveUnityCultMeshLiveProviderTransport.cs | 49 ++++++++++++++++++- .../EveUnitySceneSurfaceLowererTests.cs | 32 ++++++++++++ .../org.gamecult.eve.unity-scene/package.json | 2 +- 3 files changed, 80 insertions(+), 3 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 45a14d7..de75b0c 100644 --- a/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs +++ b/packages/org.gamecult.eve.unity-scene/Runtime/CultMesh/EveUnityCultMeshLiveProviderTransport.cs @@ -31,6 +31,7 @@ namespace GameCult.Eve.UnityScene typeof(EveAssetCatalogDocument), typeof(EveEntitySoaViewDocument), typeof(CultMeshBodyPublicationDocument), + typeof(CultMeshNetworkBodyDocument), typeof(CultMeshCdnArtifactManifest), typeof(CultMeshCdnArtifactChunk) }; @@ -291,12 +292,56 @@ namespace GameCult.Eve.UnityScene { new CultMeshSharedMemoryBodyAdapter(), new CultMeshMappedBodyAdapter(mappedRoot), - new CultMeshNetworkBodyAdapter(_ => throw new NotSupportedException( - "The connected Verse does not expose a CultMesh network body byte reader.")) + new CultMeshNetworkBodyAdapter(ReadNetworkBody) }, (producerId, _) => string.Equals(producerId, _providerId, StringComparison.Ordinal))); } + private byte[] ReadNetworkBody(CultMeshBodyDescriptor descriptor) + { + var binding = _snapshot! + .FetchDocumentsAsync( + recordKeys: new[] { CultMeshNetworkBodyDocument.CreateRecordKey(descriptor.CapabilityToken).Value }, + schemaIds: new[] { CultMeshBodyPublicationSchemaVersions.NetworkBody }) + .GetAwaiter().GetResult().SingleOrDefault() + ?? throw new FileNotFoundException("Provider network body capability is missing.", descriptor.CapabilityToken); + var manifest = _snapshot + .FetchDocumentsAsync( + recordKeys: new[] { binding.ManifestRecordKey }, + schemaIds: new[] { CultMeshCdnSchemaVersions.ArtifactManifest }) + .GetAwaiter().GetResult().SingleOrDefault() + ?? throw new FileNotFoundException("Provider network body manifest is missing.", binding.ManifestRecordKey); + var chunks = _snapshot + .FetchDocumentsAsync( + recordKeys: manifest.Chunks.Select(chunk => chunk.RecordKey).ToArray(), + schemaIds: new[] { CultMeshCdnSchemaVersions.ArtifactChunk }) + .GetAwaiter().GetResult(); + return ResolveNetworkBody(descriptor, binding, manifest, chunks); + } + + private static byte[] ResolveNetworkBody( + CultMeshBodyDescriptor descriptor, + CultMeshNetworkBodyDocument binding, + CultMeshCdnArtifactManifest manifest, + IReadOnlyList chunks) + { + var registry = CultMesh.CreateCultCacheDocumentRegistry( + typeof(CultMeshNetworkBodyDocument), + typeof(CultMeshCdnArtifactManifest), + typeof(CultMeshCdnArtifactChunk)); + var cache = new CultCache(registry); + cache.UpsertAsync(binding, new CultRecordHandle(binding.RecordKey)) + .GetAwaiter().GetResult(); + cache.UpsertAsync(manifest, new CultRecordHandle( + CultMeshCdnArtifactManifest.CreateRecordKey(manifest))) + .GetAwaiter().GetResult(); + foreach (var chunk in chunks) + cache.UpsertAsync(chunk, new CultRecordHandle( + CultMeshCdnArtifactChunk.CreateRecordKey(chunk))) + .GetAwaiter().GetResult(); + return new CultMeshNetworkBodyResolver(cache).Fetch(descriptor); + } + private void PublishEntityView(EveEntitySoaViewDocument document) { ResolveAdvertisement(); 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 1ad7b11..185bfa9 100644 --- a/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs +++ b/packages/org.gamecult.eve.unity-scene/Tests/Editor/EveUnitySceneSurfaceLowererTests.cs @@ -57,6 +57,38 @@ namespace GameCult.Eve.UnityScene.Tests new CultMeshBodyPublicationHandle(view.Buffers[0].BufferId, view.ProducerEpoch, view.Sequence).RecordKey)); } + [Test] + public async System.Threading.Tasks.Task LiveTransportResolvesExactNetworkBodyThroughVerseDocuments() + { + var view = EntityLeaseDocument(); + var bytes = Enumerable.Range(0, 32).Select(value => (byte)value).ToArray(); + var source = new CultCache(); + var generation = new CultMeshBodyGeneration + { + BodyId = view.Buffers[0].BufferId, + ProducerId = view.ProviderId, + SchemaId = view.BodySchemaId, + LayoutVersion = view.LayoutVersion, + Capacity = view.Capacity, + ProducerEpoch = view.ProducerEpoch, + Sequence = view.Sequence, + Synchronization = CultMeshBodySynchronization.TripleBuffer, + LeaseExpiresAtUnixMs = DateTimeOffset.UtcNow.AddMinutes(1).ToUnixTimeMilliseconds() + }; + var descriptor = await new CultMeshNetworkBodyPublisher(source, _ => true).PublishAsync(generation, bytes); + var binding = source.Get( + CultMeshNetworkBodyDocument.CreateRecordKey(descriptor.CapabilityToken))!; + var manifest = source.Get(new CultRecordKey(binding.ManifestRecordKey))!; + var chunks = manifest.Chunks.Select(reference => + source.Get(new CultRecordKey(reference.RecordKey))!).ToArray(); + var method = typeof(EveUnityCultMeshLiveProviderTransport) + .GetMethod("ResolveNetworkBody", BindingFlags.NonPublic | BindingFlags.Static)!; + + var resolved = (byte[])method.Invoke(null, new object[] { descriptor, binding, manifest, chunks })!; + + Assert.That(resolved, Is.EqualTo(bytes)); + } + [Test] public void EntitySoaViewReadsGenericSemanticColumnsFromInjectedLease() { diff --git a/packages/org.gamecult.eve.unity-scene/package.json b/packages/org.gamecult.eve.unity-scene/package.json index f02b183..47e736b 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.2.2", + "version": "0.2.3", "unity": "2021.3", "description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.", "dependencies": {