Separate Eve control and entity subscriptions

This commit is contained in:
Meta Vens 2026-07-19 00:02:51 +01:00
commit edaf3b1e3b
4 changed files with 87 additions and 18 deletions

View file

@ -255,6 +255,29 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
surfaceId,
requiredSurfaceKind: "interactive-world",
clientRuntimeId: $"eve-unity-test-{Guid.NewGuid():N}");
var preparationDeadline = Time.realtimeSinceStartup + 30f;
while (true)
{
var preparation = provider.PrepareAsync();
while (!preparation.IsCompleted && Time.realtimeSinceStartup < preparationDeadline)
yield return new WaitForSecondsRealtime(0.1f);
if (!preparation.IsCompleted)
throw new TimeoutException("Timed out preparing the advertised Eve provider.");
var prepared = false;
try
{
preparation.GetAwaiter().GetResult();
prepared = true;
}
catch (InvalidOperationException) when (Time.realtimeSinceStartup < preparationDeadline)
{
}
catch (TimeoutException) when (Time.realtimeSinceStartup < preparationDeadline)
{
}
if (prepared) break;
yield return new WaitForSecondsRealtime(0.1f);
}
var publicationDeadline = Time.realtimeSinceStartup + 30f;
while (true)
{

View file

@ -255,6 +255,29 @@ namespace GameCult.EveUnity.GenericClient.PlayModeTests
surfaceId,
requiredSurfaceKind: "interactive-world",
clientRuntimeId: $"eve-unity-test-{Guid.NewGuid():N}");
var preparationDeadline = Time.realtimeSinceStartup + 30f;
while (true)
{
var preparation = provider.PrepareAsync();
while (!preparation.IsCompleted && Time.realtimeSinceStartup < preparationDeadline)
yield return new WaitForSecondsRealtime(0.1f);
if (!preparation.IsCompleted)
throw new TimeoutException("Timed out preparing the advertised Eve provider.");
var prepared = false;
try
{
preparation.GetAwaiter().GetResult();
prepared = true;
}
catch (InvalidOperationException) when (Time.realtimeSinceStartup < preparationDeadline)
{
}
catch (TimeoutException) when (Time.realtimeSinceStartup < preparationDeadline)
{
}
if (prepared) break;
yield return new WaitForSecondsRealtime(0.1f);
}
var publicationDeadline = Time.realtimeSinceStartup + 30f;
while (true)
{

View file

@ -60,6 +60,7 @@ namespace GameCult.Eve.UnityScene
private CultNetDocumentRegistry? _networkRegistry;
private CultNetShardDescriptor? _replicaShard;
private CultNetDatabaseSubscriptionClient? _subscriptions;
private CultNetDatabaseSubscriptionClient? _entitySubscriptions;
private CultMeshClient? _meshClient;
private CultMeshContentTransferService? _contentTransfer;
private CultMeshVerifiedBodyMappingBroker? _assetBodyMappings;
@ -364,13 +365,15 @@ namespace GameCult.Eve.UnityScene
_prefabs.Clear();
_nativeAssets.Clear();
_nativeAssetMetadata.Clear();
_subscriptions?.Dispose();
_entitySubscriptions?.Dispose();
_node?.Dispose();
_snapshot?.Dispose();
_subscriptions?.Dispose();
_meshClient?.Dispose();
_node = null;
_snapshot = null;
_subscriptions = null;
_entitySubscriptions = null;
_meshClient = null;
_contentTransfer = null;
_assetBodyMappings = null;
@ -689,21 +692,27 @@ namespace GameCult.Eve.UnityScene
private void EnsureLiveSubscriptions()
{
if (_subscriptions != null) return;
var endpoint = new Uri(_endpoint);
var client = CultNetSchemaClients.CreateForEndpoint(_endpoint);
client.Connect(endpoint.Host, endpoint.Port);
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5);
while (!client.Connected && DateTime.UtcNow < deadline)
System.Threading.Thread.Sleep(1);
if (!client.Connected)
if (_subscriptions != null && _entitySubscriptions != null) return;
_subscriptions?.Dispose();
_entitySubscriptions?.Dispose();
_subscriptions = null;
_entitySubscriptions = null;
try
{
client.Dispose();
throw new TimeoutException($"Timed out connecting live Eve state subscription to '{_endpoint}'.");
_subscriptions = CreateSubscriptionClient();
_entitySubscriptions = CreateSubscriptionClient();
}
catch
{
_subscriptions?.Dispose();
_entitySubscriptions?.Dispose();
_subscriptions = null;
_entitySubscriptions = null;
throw;
}
_subscriptions = new CultNetDatabaseSubscriptionClient(client, _node!.Database.Cache, _networkRegistry!);
_subscriptions.Changed += OnReplicatedDocumentChanged;
_entitySubscriptions.Changed += OnReplicatedDocumentChanged;
var lowered = new EveUnitySceneSurfaceLowerer()
.Lower(CurrentSurfaceDocument.SurfaceDocument, CurrentSurfaceDocument.AdvertisedSurface);
string entityViewPointer = lowered.PlayableWorld == null
@ -711,12 +720,12 @@ namespace GameCult.Eve.UnityScene
: lowered.PlayableWorld.EntityViewPointerId;
if (!string.IsNullOrWhiteSpace(entityViewPointer))
{
_subscriptions.SubscribeAsync(
_entitySubscriptions.SubscribeAsync(
"eve-unity-entity-view",
recordKeys: new[] { entityViewPointer },
schemaIds: new[] { EveEntitySoaViewDocument.SchemaId })
.GetAwaiter().GetResult();
var current = _node.Database.Cache.Get(new CultRecordKey(entityViewPointer)) as EveEntitySoaViewDocument
var current = _node!.Database.Cache.Get(new CultRecordKey(entityViewPointer)) as EveEntitySoaViewDocument
?? _snapshot!
.FetchDocumentsAsync<EveEntitySoaViewDocument>(
recordKeys: new[] { entityViewPointer },
@ -731,8 +740,6 @@ namespace GameCult.Eve.UnityScene
schemaIds: new[] { EveSurfaceDocument.SchemaId },
includeSnapshot: false)
.GetAwaiter().GetResult();
// Entity views name one immutable body generation. PublishEntityView fetches that
// exact publication; a schema-wide subscription would import every retained frame.
_subscriptions.SubscribeAsync(
"eve-unity-receipts",
schemaIds: new[] { EveCommandReceiptDocument.SchemaId },
@ -740,6 +747,22 @@ namespace GameCult.Eve.UnityScene
.GetAwaiter().GetResult();
}
private CultNetDatabaseSubscriptionClient CreateSubscriptionClient()
{
var endpoint = new Uri(_endpoint);
var client = CultNetSchemaClients.CreateForEndpoint(_endpoint);
client.Connect(endpoint.Host, endpoint.Port);
var deadline = DateTime.UtcNow + TimeSpan.FromSeconds(5);
while (!client.Connected && DateTime.UtcNow < deadline)
System.Threading.Thread.Sleep(1);
if (!client.Connected)
{
client.Dispose();
throw new TimeoutException($"Timed out connecting live Eve state subscription to '{_endpoint}'.");
}
return new CultNetDatabaseSubscriptionClient(client, _node!.Database.Cache, _networkRegistry!);
}
private void OnReplicatedDocumentChanged(CultNetReplicatedDocumentChange change)
{
if (change.Document is EveEntitySoaViewDocument entityView)

View file

@ -1,7 +1,7 @@
{
"name": "org.gamecult.eve.unity-scene",
"displayName": "Eve Unity Scene Lowering",
"version": "0.3.71",
"version": "0.3.72",
"unity": "6000.0",
"description": "Provider-agnostic Unity scene shell for gamecult.eve.surface.v1 interactive world surfaces.",
"dependencies": {