Resolve CultMesh bodies by view generation

This commit is contained in:
Meta Vens 2026-07-13 19:47:19 +01:00
commit d9c1de9130
4 changed files with 76 additions and 6 deletions

View file

@ -100,6 +100,17 @@ namespace GameCult.Eve.UnityScene
public event Action<EveEntitySoaViewDocument, ICultMeshBodyReadLease>? 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<CultMeshBodyPublicationDocument>(
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,

View file

@ -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<CultMeshBodyPublicationDocument>(generationN.RecordKey));
await cache.UpsertAsync(generationNPlusOne,
new CultRecordHandle<CultMeshBodyPublicationDocument>(generationNPlusOne.RecordKey));
var handle = (CultMeshBodyPublicationHandle)typeof(EveUnityCultMeshLiveProviderTransport)
.GetMethod("BodyPublicationHandle", BindingFlags.NonPublic | BindingFlags.Static)!
.Invoke(null, new object[] { view })!;
var resolved = cache.Get<CultMeshBodyPublicationDocument>(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;

View file

@ -4,6 +4,7 @@
"references": [
"GameCult.Eve.Surface",
"GameCult.Eve.UnityScene",
"GameCult.Eve.UnityScene.CultMesh",
"GameCult.Eve.PluginFields"
],
"includePlatforms": [

View file

@ -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"