Drive stable entity layouts from hot body publications

This commit is contained in:
Meta Vens 2026-07-19 04:07:02 +01:00
commit 0547792516
3 changed files with 76 additions and 1 deletions

View file

@ -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<EveEntitySoaDirtyRange>())
.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);
}

View file

@ -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<object>)
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()
{

View file

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