Lower exact hot body state in generic Unity clients
This commit is contained in:
parent
82558473bc
commit
78f4937670
11 changed files with 204 additions and 59 deletions
|
|
@ -68,10 +68,13 @@ namespace GameCult.Eve.UnityScene
|
|||
private EveAdvertisedSurface? _advertisedSurface;
|
||||
private CultMeshBodyPublicationResolver? _bodyResolver;
|
||||
private EveEntitySoaViewDocument? _latestEntityLayout;
|
||||
private CultMeshBodyPublicationDocument? _latestEntityPublication;
|
||||
private CultMeshMappedFrameBodyCursor? _mappedEntityFrameCursor;
|
||||
private CultMeshBodyDescriptor? _mappedEntityFrameContract;
|
||||
private long _lastQueuedEntityViewEpoch = -1;
|
||||
private long _lastQueuedEntityViewSequence = -1;
|
||||
private long _lastPresentedEntityViewEpoch = -1;
|
||||
private long _lastPresentedEntityViewSequence = -1;
|
||||
private bool _disposed;
|
||||
|
||||
public EveUnityCultMeshLiveProviderTransport(
|
||||
|
|
@ -192,8 +195,8 @@ namespace GameCult.Eve.UnityScene
|
|||
ThrowIfSubscriptionFailed(_subscriptions);
|
||||
while (_liveDocuments.TryDequeue(out var document))
|
||||
{
|
||||
if (document is EveEntitySoaViewDocument entityView)
|
||||
PublishEntityView(entityView);
|
||||
if (document is PendingEntityGeneration entityGeneration)
|
||||
PublishEntityView(entityGeneration.View, entityGeneration.Publication);
|
||||
else if (document is EveFieldsSplatsDocument fields)
|
||||
FieldsSplatsAvailable?.Invoke(fields);
|
||||
else if (document is EveSurfaceDocument surface)
|
||||
|
|
@ -217,17 +220,14 @@ namespace GameCult.Eve.UnityScene
|
|||
{
|
||||
TraceHotState($"layout epoch={document.ProducerEpoch} sequence={document.Sequence} identities={document.Identities?.Length ?? 0}");
|
||||
_latestEntityLayout = document;
|
||||
if (document.ProducerEpoch < _lastQueuedEntityViewEpoch ||
|
||||
(document.ProducerEpoch == _lastQueuedEntityViewEpoch &&
|
||||
document.Sequence <= _lastQueuedEntityViewSequence))
|
||||
return;
|
||||
_lastQueuedEntityViewEpoch = document.ProducerEpoch;
|
||||
_lastQueuedEntityViewSequence = document.Sequence;
|
||||
_liveDocuments.Enqueue(document);
|
||||
var publication = _latestEntityPublication;
|
||||
if (publication != null && PublicationMatchesLayout(publication, document))
|
||||
QueueEntityGeneration(document, publication);
|
||||
}
|
||||
|
||||
private void QueueBodyPublication(CultMeshBodyPublicationDocument publication)
|
||||
{
|
||||
_latestEntityPublication = publication;
|
||||
var layout = _latestEntityLayout;
|
||||
if (layout == null || layout.Buffers == null || layout.Buffers.Length == 0 ||
|
||||
!string.Equals(layout.Buffers[0].BufferId, publication.BodyId, StringComparison.Ordinal))
|
||||
|
|
@ -238,10 +238,43 @@ namespace GameCult.Eve.UnityScene
|
|||
|
||||
TraceHotState($"publication body={publication.BodyId} epoch={publication.ProducerEpoch} sequence={publication.Sequence} plane={string.Join(",", publication.Representations.Select(value => value.TransportKind))}");
|
||||
|
||||
QueueEntityView(MaterializeEntityGeneration(
|
||||
layout,
|
||||
publication.ProducerEpoch,
|
||||
publication.Sequence));
|
||||
var generation = PublicationMatchesLayout(publication, layout)
|
||||
? layout
|
||||
: MaterializeEntityGeneration(layout, publication.ProducerEpoch, publication.Sequence);
|
||||
QueueEntityGeneration(generation, publication);
|
||||
}
|
||||
|
||||
private void QueueEntityGeneration(
|
||||
EveEntitySoaViewDocument document,
|
||||
CultMeshBodyPublicationDocument publication)
|
||||
{
|
||||
if (document.ProducerEpoch < _lastQueuedEntityViewEpoch ||
|
||||
(document.ProducerEpoch == _lastQueuedEntityViewEpoch &&
|
||||
document.Sequence <= _lastQueuedEntityViewSequence))
|
||||
return;
|
||||
_lastQueuedEntityViewEpoch = document.ProducerEpoch;
|
||||
_lastQueuedEntityViewSequence = document.Sequence;
|
||||
_liveDocuments.Enqueue(new PendingEntityGeneration(document, publication));
|
||||
}
|
||||
|
||||
private static bool PublicationMatchesLayout(
|
||||
CultMeshBodyPublicationDocument publication,
|
||||
EveEntitySoaViewDocument layout) =>
|
||||
publication.ProducerEpoch == layout.ProducerEpoch &&
|
||||
publication.Sequence == layout.Sequence;
|
||||
|
||||
private sealed class PendingEntityGeneration
|
||||
{
|
||||
public PendingEntityGeneration(
|
||||
EveEntitySoaViewDocument view,
|
||||
CultMeshBodyPublicationDocument publication)
|
||||
{
|
||||
View = view;
|
||||
Publication = publication;
|
||||
}
|
||||
|
||||
public EveEntitySoaViewDocument View { get; }
|
||||
public CultMeshBodyPublicationDocument Publication { get; }
|
||||
}
|
||||
|
||||
private static EveEntitySoaViewDocument MaterializeEntityGeneration(
|
||||
|
|
@ -530,7 +563,9 @@ namespace GameCult.Eve.UnityScene
|
|||
return normalized.ToLowerInvariant();
|
||||
}
|
||||
|
||||
private void PublishEntityView(EveEntitySoaViewDocument document)
|
||||
private void PublishEntityView(
|
||||
EveEntitySoaViewDocument document,
|
||||
CultMeshBodyPublicationDocument publication)
|
||||
{
|
||||
ResolveAdvertisement();
|
||||
if (!string.Equals(document.ProviderId, _advertisement!.ProviderId, StringComparison.Ordinal))
|
||||
|
|
@ -539,15 +574,13 @@ namespace GameCult.Eve.UnityScene
|
|||
if (document.Buffers == null || document.Buffers.Length == 0)
|
||||
throw new InvalidOperationException("Entity layout does not name a primary logical buffer.");
|
||||
var handle = BodyPublicationHandle(document);
|
||||
var publicationKey = CultMeshBodyPublicationDocument.CreateLatestRecordKey(handle.BodyId);
|
||||
var publication = _node!.Database.Cache.Get<CultMeshBodyPublicationDocument>(publicationKey)
|
||||
?? throw new InvalidOperationException(
|
||||
$"Subscribed CultMesh body publication '{publicationKey.Value}' is missing for generation {handle.Sequence}.");
|
||||
handle.Validate(publication);
|
||||
var now = DateTimeOffset.UtcNow;
|
||||
if (!IsPublicationLive(publication, now))
|
||||
return;
|
||||
EnsureMappedEntityFrameCursor(publication);
|
||||
if (_mappedEntityFrameCursor != null)
|
||||
return;
|
||||
_bodyResolver ??= CreateBodyResolver();
|
||||
var lease = _bodyResolver.ResolveReadOnly(publication, new CultMeshBodyValidationRequest
|
||||
{
|
||||
|
|
@ -569,6 +602,8 @@ namespace GameCult.Eve.UnityScene
|
|||
try
|
||||
{
|
||||
handler(document, lease);
|
||||
_lastPresentedEntityViewEpoch = document.ProducerEpoch;
|
||||
_lastPresentedEntityViewSequence = document.Sequence;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -616,16 +651,14 @@ namespace GameCult.Eve.UnityScene
|
|||
return;
|
||||
|
||||
var descriptor = lease.Descriptor;
|
||||
if (descriptor.ProducerEpoch < _lastQueuedEntityViewEpoch ||
|
||||
(descriptor.ProducerEpoch == _lastQueuedEntityViewEpoch &&
|
||||
descriptor.Sequence <= _lastQueuedEntityViewSequence))
|
||||
if (descriptor.ProducerEpoch < _lastPresentedEntityViewEpoch ||
|
||||
(descriptor.ProducerEpoch == _lastPresentedEntityViewEpoch &&
|
||||
descriptor.Sequence <= _lastPresentedEntityViewSequence))
|
||||
{
|
||||
lease.Dispose();
|
||||
return;
|
||||
}
|
||||
|
||||
_lastQueuedEntityViewEpoch = descriptor.ProducerEpoch;
|
||||
_lastQueuedEntityViewSequence = descriptor.Sequence;
|
||||
var generation = MaterializeEntityGeneration(
|
||||
layout,
|
||||
descriptor.ProducerEpoch,
|
||||
|
|
@ -639,6 +672,8 @@ namespace GameCult.Eve.UnityScene
|
|||
try
|
||||
{
|
||||
handler(generation, lease);
|
||||
_lastPresentedEntityViewEpoch = descriptor.ProducerEpoch;
|
||||
_lastPresentedEntityViewSequence = descriptor.Sequence;
|
||||
}
|
||||
catch
|
||||
{
|
||||
|
|
@ -728,33 +763,22 @@ namespace GameCult.Eve.UnityScene
|
|||
.Where(value => !string.IsNullOrWhiteSpace(value))
|
||||
.Distinct(StringComparer.Ordinal)
|
||||
.ToArray();
|
||||
var liveRecordKeys = new List<string>(fieldRefs);
|
||||
var liveSchemaIds = new List<string>();
|
||||
if (fieldRefs.Length > 0)
|
||||
liveSchemaIds.Add(EveFieldsSchemas.Splats);
|
||||
if (!string.IsNullOrWhiteSpace(entityViewPointer))
|
||||
{
|
||||
if (string.IsNullOrWhiteSpace(entityBodyId))
|
||||
throw new InvalidOperationException(
|
||||
"The playable world advertises an entity-view pointer without its logical body id.");
|
||||
liveRecordKeys.Insert(0, entityViewPointer);
|
||||
liveRecordKeys.Insert(1, CultMeshBodyPublicationDocument.CreateLatestRecordKey(entityBodyId).Value);
|
||||
liveSchemaIds.Insert(0, EveEntitySoaViewDocument.SchemaId);
|
||||
liveSchemaIds.Insert(1, CultMeshBodyPublicationSchemaVersions.Publication);
|
||||
}
|
||||
if (liveRecordKeys.Count > 0)
|
||||
{
|
||||
var initial = _entitySubscriptions.SubscribeAsync(
|
||||
"eve-unity-entity-view",
|
||||
recordKeys: liveRecordKeys,
|
||||
schemaIds: liveSchemaIds,
|
||||
consumerRuntimeId: _runtimeId,
|
||||
bodyIds: string.IsNullOrWhiteSpace(entityBodyId) ? null : new[] { entityBodyId },
|
||||
supportedBodyTransports: string.IsNullOrWhiteSpace(entityBodyId) ? null : new[]
|
||||
{
|
||||
CultMeshBodyTransportKind.SharedMemory.ToString(),
|
||||
CultMeshBodyTransportKind.Network.ToString()
|
||||
})
|
||||
var initial = CultMesh.SubscribeHotBodyAsync(
|
||||
_entitySubscriptions,
|
||||
_bodyResolver!,
|
||||
new CultMeshHotBodySubscription(
|
||||
"eve-unity-entity-view",
|
||||
_runtimeId,
|
||||
entityViewPointer,
|
||||
EveEntitySoaViewDocument.SchemaId,
|
||||
entityBodyId,
|
||||
fieldRefs,
|
||||
fieldRefs.Length > 0 ? new[] { EveFieldsSchemas.Splats } : null))
|
||||
.GetAwaiter().GetResult();
|
||||
TraceHotState($"initial subscription documents=[{string.Join(",", initial.Select(value => value.GetType().Name))}]");
|
||||
foreach (var document in initial)
|
||||
|
|
@ -767,6 +791,16 @@ namespace GameCult.Eve.UnityScene
|
|||
_liveDocuments.Enqueue(fields);
|
||||
}
|
||||
}
|
||||
else if (fieldRefs.Length > 0)
|
||||
{
|
||||
var initial = _entitySubscriptions.SubscribeAsync(
|
||||
"eve-unity-fields",
|
||||
recordKeys: fieldRefs,
|
||||
schemaIds: new[] { EveFieldsSchemas.Splats })
|
||||
.GetAwaiter().GetResult();
|
||||
foreach (var fields in initial.OfType<EveFieldsSplatsDocument>())
|
||||
_liveDocuments.Enqueue(fields);
|
||||
}
|
||||
_subscriptions.SubscribeAsync(
|
||||
"eve-unity-surface",
|
||||
recordKeys: new[] { _advertisedSurface!.RecordRef },
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ namespace GameCult.Eve.UnityScene
|
|||
RenderChannel = Get(node, "renderChannel");
|
||||
ActivationActionId = Get(node, "activationActionId");
|
||||
Power = NonNegative(node, "power", 0f);
|
||||
PowerStateSemantic = Get(node, "powerStateSemantic");
|
||||
ActivationThreshold = NonNegative(node, "activationThreshold", 0.01f);
|
||||
Radius = NonNegative(node, "radius", 0f);
|
||||
MaximumDistance = NonNegative(node, "maximumDistance", 0f);
|
||||
|
|
@ -33,6 +34,7 @@ namespace GameCult.Eve.UnityScene
|
|||
public string RenderChannel { get; }
|
||||
public string ActivationActionId { get; }
|
||||
public float Power { get; }
|
||||
public string PowerStateSemantic { get; }
|
||||
public float ActivationThreshold { get; }
|
||||
public float Radius { get; }
|
||||
public float MaximumDistance { get; }
|
||||
|
|
|
|||
|
|
@ -76,8 +76,13 @@ namespace GameCult.Eve.UnityScene
|
|||
beam.Root.transform.SetParent(source.Transform, false);
|
||||
}
|
||||
|
||||
var visiblePower = presentation.Power > presentation.ActivationThreshold
|
||||
? presentation.Power
|
||||
var power = presentation.Power;
|
||||
if (!string.IsNullOrWhiteSpace(presentation.PowerStateSemantic))
|
||||
power = source.Entity.TryGetScalar(presentation.PowerStateSemantic, out var livePower)
|
||||
? Math.Max(0f, livePower)
|
||||
: 0f;
|
||||
var visiblePower = power > presentation.ActivationThreshold
|
||||
? power
|
||||
: 0f;
|
||||
foreach (var particles in beam.Root.GetComponentsInChildren<ParticleSystem>(true))
|
||||
{
|
||||
|
|
|
|||
|
|
@ -47,6 +47,10 @@ namespace GameCult.Eve.UnityScene
|
|||
var assetRef = string.IsNullOrWhiteSpace(identity?.AssetRef)
|
||||
? renderGroup?.MeshAssetRef ?? ""
|
||||
: identity!.AssetRef;
|
||||
var scalarState = new Dictionary<string, float>(StringComparer.Ordinal);
|
||||
foreach (var semantic in view.FloatSemantics)
|
||||
if (view.TryReadFloat(semantic, row, out var scalar))
|
||||
scalarState[semantic] = scalar;
|
||||
var fact = new EveUnityPresentedEntity(
|
||||
entityIndex,
|
||||
entityId,
|
||||
|
|
@ -62,7 +66,8 @@ namespace GameCult.Eve.UnityScene
|
|||
lod,
|
||||
identity?.Selectable ?? true,
|
||||
identity?.Controllable ?? false,
|
||||
assetRef);
|
||||
assetRef,
|
||||
scalarState);
|
||||
presented.Add(fact);
|
||||
if (_sink is IEveUnityEntityGenerationSink)
|
||||
continue;
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ namespace GameCult.Eve.UnityScene
|
|||
private readonly ICultMeshBodyReadLease _lease;
|
||||
private readonly Dictionary<string, EveEntitySoaBuffer> _buffers;
|
||||
private readonly Dictionary<string, EveEntitySoaColumn> _columns;
|
||||
private readonly string[] _floatSemantics;
|
||||
|
||||
private EveUnityEntitySoaView(EveEntitySoaViewDocument document, ICultMeshBodyReadLease lease)
|
||||
{
|
||||
|
|
@ -21,12 +22,18 @@ namespace GameCult.Eve.UnityScene
|
|||
_lease = lease;
|
||||
_buffers = document.Buffers.ToDictionary(buffer => buffer.BufferId, StringComparer.Ordinal);
|
||||
_columns = document.Columns.ToDictionary(column => column.Semantic, StringComparer.Ordinal);
|
||||
_floatSemantics = document.Columns
|
||||
.Where(column => string.Equals(column.ScalarType, "float32", StringComparison.Ordinal))
|
||||
.Select(column => column.Semantic)
|
||||
.Where(semantic => !string.IsNullOrWhiteSpace(semantic))
|
||||
.ToArray();
|
||||
}
|
||||
|
||||
public EveEntitySoaViewDocument Document { get; }
|
||||
public long Generation => Document.Sequence;
|
||||
public IReadOnlyList<EveEntityRenderGroup> RenderGroups => Document.RenderGroups;
|
||||
public int EntityCount => Document.Columns.Length == 0 ? 0 : Document.Columns.Min(column => column.ElementCount);
|
||||
public IReadOnlyList<string> FloatSemantics => _floatSemantics;
|
||||
|
||||
public static EveUnityEntitySoaView Open(EveEntitySoaViewDocument document, ICultMeshBodyReadLease lease)
|
||||
{
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ namespace GameCult.Eve.UnityScene
|
|||
private readonly EveUnityShotReceiptPresenter _shots = new EveUnityShotReceiptPresenter();
|
||||
private readonly List<EveUnitySceneCommandReceipt> _pendingReceipts =
|
||||
new List<EveUnitySceneCommandReceipt>();
|
||||
private long _activeStateVersion;
|
||||
private bool _connected;
|
||||
private bool _receiptConnected;
|
||||
|
||||
|
|
@ -36,7 +37,14 @@ namespace GameCult.Eve.UnityScene
|
|||
|
||||
public EveUnitySceneCommandReceipt? LastReceipt { get; private set; }
|
||||
|
||||
public long ActiveVersion => _connection.ActiveVersion;
|
||||
public long ActiveVersion => Math.Max(_connection.ActiveVersion, _activeStateVersion);
|
||||
|
||||
public void AdvanceStateVersion(long version)
|
||||
{
|
||||
if (version <= _activeStateVersion) return;
|
||||
_activeStateVersion = version;
|
||||
PublishReceiptsWhoseStateIsVisible();
|
||||
}
|
||||
|
||||
public string SourcePointer => _connection.SourcePointer;
|
||||
|
||||
|
|
|
|||
|
|
@ -230,8 +230,11 @@ namespace GameCult.Eve.UnityScene
|
|||
_entityViewsConnected = true;
|
||||
}
|
||||
|
||||
private void OnEntityViewAvailable(EveEntitySoaViewDocument document, GameCult.Mesh.ICultMeshBodyReadLease lease) =>
|
||||
private void OnEntityViewAvailable(EveEntitySoaViewDocument document, GameCult.Mesh.ICultMeshBodyReadLease lease)
|
||||
{
|
||||
_client.AdvanceStateVersion(document.FrameId);
|
||||
_entityPresenter.Apply(document, lease);
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class EveUnityLivePlayableWorldAssetProvider :
|
||||
|
|
|
|||
|
|
@ -24,7 +24,8 @@ namespace GameCult.Eve.UnityScene
|
|||
int lod,
|
||||
bool selectable,
|
||||
bool controllable,
|
||||
string assetRef)
|
||||
string assetRef,
|
||||
IReadOnlyDictionary<string, float>? scalarState = null)
|
||||
{
|
||||
SourceIndex = sourceIndex;
|
||||
EntityId = entityId ?? "";
|
||||
|
|
@ -41,6 +42,7 @@ namespace GameCult.Eve.UnityScene
|
|||
Selectable = selectable;
|
||||
Controllable = controllable;
|
||||
AssetRef = assetRef ?? "";
|
||||
ScalarState = scalarState ?? new Dictionary<string, float>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public int SourceIndex { get; }
|
||||
|
|
@ -58,6 +60,10 @@ namespace GameCult.Eve.UnityScene
|
|||
public bool Selectable { get; }
|
||||
public bool Controllable { get; }
|
||||
public string AssetRef { get; }
|
||||
public IReadOnlyDictionary<string, float> ScalarState { get; }
|
||||
|
||||
public bool TryGetScalar(string semantic, out float value) =>
|
||||
ScalarState.TryGetValue(semantic ?? "", out value);
|
||||
|
||||
public EveUnityPlayableWorldEntity ToPlayableWorldEntity() => new EveUnityPlayableWorldEntity(
|
||||
EntityId,
|
||||
|
|
|
|||
|
|
@ -76,6 +76,44 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
}
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void RendererReadsDaemonPowerFromThePresentedEntityGeneration()
|
||||
{
|
||||
var host = new GameObject("beam-state-test-host");
|
||||
var source = new GameObject("beam-state-test-source");
|
||||
var prefab = new GameObject("beam-state-test-prefab");
|
||||
prefab.AddComponent<ParticleSystem>();
|
||||
var renderer = host.AddComponent<EveUnityBeamPresentationRenderer>();
|
||||
var registry = new Registry(
|
||||
"source",
|
||||
source.transform,
|
||||
new Dictionary<string, float>(StringComparer.Ordinal) { ["effect.beam.power"] = 0.75f });
|
||||
try
|
||||
{
|
||||
var presentation = new EveUnityBeamPresentation(Node(
|
||||
"tractor", "beam.presentation", new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
["sourceEntityId"] = "source",
|
||||
["assetRole"] = "effect.beam.tractor",
|
||||
["directionMode"] = "source-forward.v1",
|
||||
["power"] = "0.125",
|
||||
["powerStateSemantic"] = "effect.beam.power",
|
||||
["activationThreshold"] = "0.01"
|
||||
}));
|
||||
|
||||
renderer.Apply(new[] { presentation }, registry, new Provider(prefab));
|
||||
|
||||
Assert.That(renderer.TryGetPower("tractor", out var power), Is.True);
|
||||
Assert.That(power, Is.EqualTo(0.75f).Within(0.0001f));
|
||||
}
|
||||
finally
|
||||
{
|
||||
UnityEngine.Object.DestroyImmediate(host);
|
||||
UnityEngine.Object.DestroyImmediate(source);
|
||||
UnityEngine.Object.DestroyImmediate(prefab);
|
||||
}
|
||||
}
|
||||
|
||||
private static EveUnityBeamPresentation Presentation(float power) => new EveUnityBeamPresentation(Node(
|
||||
"tractor", "beam.presentation", new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{
|
||||
|
|
@ -104,11 +142,14 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
private sealed class Registry : IEveUnityPresentedEntityRegistry
|
||||
{
|
||||
private readonly EveUnityPresentedEntityHandle _handle;
|
||||
public Registry(string entityId, Transform transform)
|
||||
public Registry(
|
||||
string entityId,
|
||||
Transform transform,
|
||||
IReadOnlyDictionary<string, float>? scalarState = null)
|
||||
{
|
||||
_handle = new EveUnityPresentedEntityHandle(
|
||||
new EveUnityPresentedEntity(0, entityId, "ship", "", "", Vector3.zero, 0, Vector3.zero,
|
||||
1, 1, 0, 0, true, true, ""),
|
||||
1, 1, 0, 0, true, true, "", scalarState),
|
||||
transform);
|
||||
}
|
||||
public EveUnityPresentedEntityGeneration? CurrentGeneration => null;
|
||||
|
|
|
|||
|
|
@ -73,11 +73,11 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
.GetValue(transport)!;
|
||||
|
||||
queueView.Invoke(transport, new object[] { view });
|
||||
Assert.That(queue.TryDequeue(out _), Is.True);
|
||||
Assert.That(queue, Is.Empty, "a layout without its body publication is not a complete generation");
|
||||
queuePublication.Invoke(transport, new object[] { publication });
|
||||
|
||||
Assert.That(queue.TryDequeue(out var queued), Is.True);
|
||||
var generation = queued as EveEntitySoaViewDocument;
|
||||
var generation = queued!.GetType().GetProperty("View")!.GetValue(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));
|
||||
|
|
@ -87,6 +87,34 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
Assert.That(generation.DirtyRanges.All(range => range.Sequence == publication.Sequence), Is.True);
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LiveTransportPairsBodyPublicationThatArrivesBeforeItsLayout()
|
||||
{
|
||||
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);
|
||||
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)!;
|
||||
|
||||
queuePublication.Invoke(transport, new object[] { publication });
|
||||
Assert.That(queue, Is.Empty, "a publication without its layout is not a complete generation");
|
||||
queueView.Invoke(transport, new object[] { view });
|
||||
|
||||
Assert.That(queue.TryDequeue(out var queued), Is.True);
|
||||
Assert.That(queued!.GetType().GetProperty("View")!.GetValue(queued), Is.SameAs(view));
|
||||
Assert.That(queued.GetType().GetProperty("Publication")!.GetValue(queued), Is.SameAs(publication));
|
||||
}
|
||||
|
||||
[Test]
|
||||
public void LiveTransportReadsLaterMappedFramesWithoutNewControlDocuments()
|
||||
{
|
||||
|
|
@ -1887,7 +1915,7 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
}
|
||||
|
||||
[Test]
|
||||
public void LivePlayableWorldClientWaitsForProviderSurfaceUpdateAfterReceipt()
|
||||
public void LivePlayableWorldClientReconcilesReceiptAfterReactiveStateVersionAdvances()
|
||||
{
|
||||
var source = new FakeProviderSurfaceSource(
|
||||
"aetheria",
|
||||
|
|
@ -1944,12 +1972,18 @@ namespace GameCult.Eve.UnityScene.Tests
|
|||
Assert.That(client.LastReceipt.State, Is.EqualTo("pending"));
|
||||
Assert.That(client.ActiveVersion, Is.EqualTo(1));
|
||||
|
||||
client.AdvanceStateVersion(2);
|
||||
|
||||
Assert.That(client.LastReceipt.State, Is.EqualTo("reconciled"));
|
||||
Assert.That(client.ActiveVersion, Is.EqualTo(2));
|
||||
Assert.That(sceneSink.ConfiguredWorlds.Count, Is.EqualTo(1),
|
||||
"reactive state visibility must not require republishing heavy surface topology");
|
||||
|
||||
source.Publish(new EveUnitySceneProviderSurfaceSnapshot(
|
||||
PlayableArpgDocument(includeRaider: false, playerPosition: "30,0,30"),
|
||||
Advertisement("aetheria.daemon.game"),
|
||||
"cultmesh://aetheria/eve/surfaces/aetheria.daemon.game",
|
||||
2));
|
||||
Assert.That(client.LastReceipt.State, Is.EqualTo("reconciled"));
|
||||
Assert.That(client.ActiveVersion, Is.EqualTo(2));
|
||||
Assert.That(client.LastPresentation, Is.Not.Null);
|
||||
Assert.That(client.LastPresentation!.ActiveEntities, Is.EqualTo(2));
|
||||
|
|
|
|||
|
|
@ -1,11 +1,11 @@
|
|||
{
|
||||
"name": "org.gamecult.eve.unity-scene",
|
||||
"displayName": "Eve Unity Scene Lowering",
|
||||
"version": "0.3.77",
|
||||
"version": "0.3.78",
|
||||
"unity": "6000.0",
|
||||
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
|
||||
"dependencies": {
|
||||
"org.gamecult.cultlib": "1.0.22",
|
||||
"org.gamecult.cultlib": "1.0.23",
|
||||
"org.gamecult.eve.plugin-fields": "0.2.3",
|
||||
"org.gamecult.eve.surface": "0.2.4",
|
||||
"com.unity.mathematics": "1.3.2",
|
||||
|
|
|
|||
Reference in a new issue