Publish gameplay state through hot entity bodies
This commit is contained in:
parent
668ca9d832
commit
8dc133d917
5 changed files with 152 additions and 54 deletions
|
|
@ -50,6 +50,11 @@ using var physicsPersistence = await AetheriaYmirPersistenceCoordinator.OpenAsyn
|
|||
latestFrame).ConfigureAwait(false);
|
||||
using var cultMeshRudpHost = StartClientCultMeshHost(node, options, () => latestFrame);
|
||||
using var clientSubscriptions = new CultNetDatabaseSubscriptionServer(cultMeshRudpHost, node.Database);
|
||||
if (traceClientRudp)
|
||||
clientSubscriptions.DemandChanged += demand => Console.WriteLine(
|
||||
$"CultMesh body demand consumer={demand.ConsumerRuntimeId} subscription={demand.SubscriptionId} " +
|
||||
$"active={demand.Active} sameMachine={demand.SameMachine} " +
|
||||
$"bodies=[{string.Join(",", demand.BodyIds)}] transports=[{string.Join(",", demand.SupportedBodyTransports)}]");
|
||||
using var bodyDemand = new CultMeshBodyDemandTracker(clientSubscriptions);
|
||||
using var soaPublisher = new AetheriaRuntimeDaemonSoaFramePublisher(
|
||||
node.Cache,
|
||||
|
|
@ -59,23 +64,23 @@ using var clientPumpCancellation = new CancellationTokenSource();
|
|||
var clientPump = RunClientCultMeshPumpAsync(cultMeshRudpHost, clientPumpCancellation.Token);
|
||||
var nextApiPublicationUtc = DateTimeOffset.UtcNow;
|
||||
var ingressState = new AetheriaDaemonIngressState();
|
||||
var firstTick = await TickAsync(node, options, worldPhysics, soaPublisher, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false);
|
||||
var hotState = new AetheriaHotEntityPublicationState();
|
||||
var firstTick = await TickAsync(node, options, worldPhysics, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false);
|
||||
ThrowIfClientPumpFaulted(clientPump);
|
||||
latestFrame = firstTick.Frame;
|
||||
var firstPublication = PreparePublication(
|
||||
node.StatePath,
|
||||
options,
|
||||
soaPublisher,
|
||||
firstTick,
|
||||
ingressState,
|
||||
physicsPersistence);
|
||||
await PublishPreparedDocumentsAsync(
|
||||
node,
|
||||
options,
|
||||
soaPublisher,
|
||||
physicsPersistence,
|
||||
firstPublication,
|
||||
publishTopology: true).ConfigureAwait(false);
|
||||
await PublishHotEntityStateAsync(node, soaPublisher, hotState, firstTick.Frame, ingressState.Catalog).ConfigureAwait(false);
|
||||
nextApiPublicationUtc = DateTimeOffset.UtcNow.Add(options.ApiPublicationInterval);
|
||||
Console.WriteLine($"Aetheria Verse daemon published frame {firstTick.Frame.FrameId}.");
|
||||
Console.WriteLine($"Aetheria client CultMesh endpoint: rudp://{options.ClientCultMeshAdvertiseHost}:{cultMeshRudpHost.LocalEndPoint.Port}");
|
||||
|
|
@ -111,9 +116,10 @@ while (!stopped.Task.IsCompleted)
|
|||
}
|
||||
|
||||
var buildPublications = DateTimeOffset.UtcNow >= nextApiPublicationUtc;
|
||||
var tick = await TickAsync(node, options, worldPhysics, soaPublisher, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false);
|
||||
var tick = await TickAsync(node, options, worldPhysics, latestFrame, ingressState, buildPublications: false).ConfigureAwait(false);
|
||||
ThrowIfClientPumpFaulted(clientPump);
|
||||
latestFrame = tick.Frame;
|
||||
await PublishHotEntityStateAsync(node, soaPublisher, hotState, tick.Frame, ingressState.Catalog).ConfigureAwait(false);
|
||||
nextTickUtc += options.TickInterval;
|
||||
if (nextTickUtc < DateTimeOffset.UtcNow - options.TickInterval)
|
||||
nextTickUtc = DateTimeOffset.UtcNow;
|
||||
|
|
@ -124,14 +130,12 @@ while (!stopped.Task.IsCompleted)
|
|||
var publication = PreparePublication(
|
||||
node.StatePath,
|
||||
options,
|
||||
soaPublisher,
|
||||
tick,
|
||||
ingressState,
|
||||
physicsPersistence);
|
||||
publicationTask = PublishPreparedDocumentsAsync(
|
||||
node,
|
||||
options,
|
||||
soaPublisher,
|
||||
physicsPersistence,
|
||||
publication,
|
||||
publishTopology: false);
|
||||
|
|
@ -167,7 +171,6 @@ static async Task<AetheriaRuntimeDaemonTickResult> TickAsync(
|
|||
AetheriaStateNode node,
|
||||
AetheriaDaemonHostOptions options,
|
||||
IAetheriaRuntimeWorldPhysics worldPhysics,
|
||||
AetheriaRuntimeDaemonSoaFramePublisher soaPublisher,
|
||||
AetheriaRuntimeDaemonFrameDocument? currentFrame,
|
||||
AetheriaDaemonIngressState ingressState,
|
||||
bool buildPublications)
|
||||
|
|
@ -286,7 +289,6 @@ static async Task<AetheriaRuntimeDaemonTickResult> TickAsync(
|
|||
StarbridgeScenario = starbridgeScenario,
|
||||
StarbridgeSession = starbridgeSession,
|
||||
BuildPublications = buildPublications,
|
||||
SoaFramePublisher = soaPublisher,
|
||||
OperationContext = new AetheriaRuntimeDaemonOperationContext
|
||||
{
|
||||
LoadoutTemplates = loadoutTemplates
|
||||
|
|
@ -341,7 +343,7 @@ static async Task<AetheriaRuntimeDaemonTickResult> TickAsync(
|
|||
|
||||
if (buildPublications)
|
||||
{
|
||||
await PublishDaemonApiDocumentsAsync(node, options, soaPublisher, result, publishTopology: true).ConfigureAwait(false);
|
||||
await PublishDaemonApiDocumentsAsync(node, options, result, publishTopology: true).ConfigureAwait(false);
|
||||
TracePhase("api-publication");
|
||||
await PublishStateSurfacesAsync(node, options, result.Frame.PublishedAtUtc).ConfigureAwait(false);
|
||||
TracePhase("state-surfaces");
|
||||
|
|
@ -355,7 +357,6 @@ static async Task<AetheriaRuntimeDaemonTickResult> TickAsync(
|
|||
static AetheriaPreparedPublication PreparePublication(
|
||||
string statePath,
|
||||
AetheriaDaemonHostOptions options,
|
||||
AetheriaRuntimeDaemonSoaFramePublisher soaPublisher,
|
||||
AetheriaRuntimeDaemonTickResult tick,
|
||||
AetheriaDaemonIngressState ingressState,
|
||||
AetheriaYmirPersistenceCoordinator physicsPersistence)
|
||||
|
|
@ -381,8 +382,7 @@ static AetheriaPreparedPublication PreparePublication(
|
|||
StarbridgeScenario = ingressState.StarbridgeScenario,
|
||||
StarbridgeSession = ingressState.StarbridgeSession,
|
||||
RenderSettings = options.RenderSettings,
|
||||
SimulationSettings = options.SimulationSettings,
|
||||
SoaFramePublisher = soaPublisher
|
||||
SimulationSettings = options.SimulationSettings
|
||||
},
|
||||
frame.AccountedCommandIds?.Count ?? 0);
|
||||
return new AetheriaPreparedPublication(publication, physics);
|
||||
|
|
@ -391,7 +391,6 @@ static AetheriaPreparedPublication PreparePublication(
|
|||
static async Task PublishPreparedDocumentsAsync(
|
||||
AetheriaStateNode node,
|
||||
AetheriaDaemonHostOptions options,
|
||||
AetheriaRuntimeDaemonSoaFramePublisher soaPublisher,
|
||||
AetheriaYmirPersistenceCoordinator physicsPersistence,
|
||||
AetheriaPreparedPublication prepared,
|
||||
bool publishTopology)
|
||||
|
|
@ -400,7 +399,6 @@ static async Task PublishPreparedDocumentsAsync(
|
|||
await PublishDaemonApiDocumentsAsync(
|
||||
node,
|
||||
options,
|
||||
soaPublisher,
|
||||
prepared.Publication,
|
||||
publishTopology).ConfigureAwait(false);
|
||||
if (publishTopology)
|
||||
|
|
@ -1510,10 +1508,44 @@ static bool TryGetDouble(
|
|||
double.TryParse(text, NumberStyles.Float, CultureInfo.InvariantCulture, out value);
|
||||
}
|
||||
|
||||
static async Task PublishHotEntityStateAsync(
|
||||
AetheriaStateNode node,
|
||||
AetheriaRuntimeDaemonSoaFramePublisher publisher,
|
||||
AetheriaHotEntityPublicationState state,
|
||||
AetheriaRuntimeDaemonFrameDocument frame,
|
||||
AetheriaRuntimeCatalogSnapshot? catalog)
|
||||
{
|
||||
using var hotFrame = publisher.BuildCurrentZoneEntities(frame, catalog);
|
||||
if (hotFrame == null)
|
||||
return;
|
||||
|
||||
var publication = await publisher.PublishAsync(hotFrame).ConfigureAwait(false);
|
||||
var layoutChanged = !state.Matches(publication.View);
|
||||
var publishesRemoteGeneration = publication.Body.Representations.Any(
|
||||
value => value.TransportKind == CultMeshBodyTransportKind.Network);
|
||||
if (layoutChanged || publishesRemoteGeneration)
|
||||
{
|
||||
if (layoutChanged && string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_CLIENT_RUDP"), "1", StringComparison.Ordinal))
|
||||
Console.WriteLine(
|
||||
$"CultMesh entity layout changed sequence={publication.View.Sequence} bytes={MessagePackSerializer.Serialize(publication.View).Length} " +
|
||||
$"identities={publication.View.Identities.Length} representations=[{string.Join(",", publication.Body.Representations.Select(value => value.TransportKind))}]");
|
||||
await node.MutableDocument<CultMeshBodyPublicationDocument>(
|
||||
CultMeshBodyPublicationDocument.CreateLatestRecordKey(publication.Body.BodyId))
|
||||
.ReplaceAsync(publication.Body)
|
||||
.ConfigureAwait(false);
|
||||
}
|
||||
if (layoutChanged)
|
||||
{
|
||||
await node.MutableDocument<EveEntitySoaViewDocument>(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest)
|
||||
.ReplaceAsync(publication.View)
|
||||
.ConfigureAwait(false);
|
||||
state.Set(publication.View);
|
||||
}
|
||||
}
|
||||
|
||||
static async Task PublishDaemonApiDocumentsAsync(
|
||||
AetheriaStateNode node,
|
||||
AetheriaDaemonHostOptions options,
|
||||
AetheriaRuntimeDaemonSoaFramePublisher soaPublisher,
|
||||
AetheriaRuntimeDaemonTickResult result,
|
||||
bool publishTopology)
|
||||
{
|
||||
|
|
@ -1567,32 +1599,21 @@ static async Task PublishDaemonApiDocumentsAsync(
|
|||
var activeMainMenuSurfaceId = string.IsNullOrWhiteSpace(mainMenuState?.ActiveSurfaceId)
|
||||
? AetheriaRuntimeMainMenuCommands.RootSurfaceId
|
||||
: mainMenuState.ActiveSurfaceId;
|
||||
var gameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
result.Frame,
|
||||
result.Health ?? new AetheriaRuntimeDaemonHealthDocument(),
|
||||
result.CommandBoundary ?? AetheriaRuntimeDaemonCommandBoundaryDocument.Create(options.DaemonId),
|
||||
activeMainMenuSurfaceId,
|
||||
inputCatalog);
|
||||
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)
|
||||
.ReplaceAsync(AetheriaRuntimeSurfaceDocuments.ToPortableSurface(gameSurface))
|
||||
.ConfigureAwait(false);
|
||||
await PublishDaemonSectorMapSurfaceAsync(node, result.Frame, inputCatalog).ConfigureAwait(false);
|
||||
// Keep low-bandwidth control state ahead of the bulk entity frame on the ordered
|
||||
// subscription stream. Commands and beam feedback must not wait behind SoA delivery.
|
||||
if (result.SoaFrame != null && result.SoaView != null &&
|
||||
string.Equals(result.SoaView.Schema, AetheriaRuntimeDaemonSchemas.SoaView, StringComparison.Ordinal))
|
||||
if (publishTopology)
|
||||
{
|
||||
await node.MutableDocument<AetheriaRuntimeDaemonSoaViewDocument>(AetheriaRuntimeVerseRecordKeys.DaemonSoaViewLatest)
|
||||
.ReplaceAsync(result.SoaView)
|
||||
.ConfigureAwait(false);
|
||||
var soaPublication = await soaPublisher.PublishAsync(result.SoaFrame).ConfigureAwait(false);
|
||||
await node.MutableDocument<CultMeshBodyPublicationDocument>(
|
||||
CultMeshBodyPublicationDocument.CreateLatestRecordKey(soaPublication.Body.BodyId))
|
||||
.ReplaceAsync(soaPublication.Body)
|
||||
.ConfigureAwait(false);
|
||||
await node.MutableDocument<EveEntitySoaViewDocument>(AetheriaRuntimeVerseRecordKeys.EveEntitySoaViewLatest)
|
||||
.ReplaceAsync(soaPublication.View)
|
||||
var gameSurface = AetheriaRuntimeDaemonGameSurfaceBuilder.Build(
|
||||
result.Frame,
|
||||
result.Health ?? new AetheriaRuntimeDaemonHealthDocument(),
|
||||
result.CommandBoundary ?? AetheriaRuntimeDaemonCommandBoundaryDocument.Create(options.DaemonId),
|
||||
activeMainMenuSurfaceId,
|
||||
inputCatalog);
|
||||
var portableGameSurface = AetheriaRuntimeSurfaceDocuments.ToPortableSurface(gameSurface);
|
||||
if (string.Equals(Environment.GetEnvironmentVariable("AETHERIA_TRACE_CLIENT_RUDP"), "1", StringComparison.Ordinal))
|
||||
Console.WriteLine($"Eve game topology bytes={MessagePackSerializer.Serialize(portableGameSurface).Length}");
|
||||
await node.MutableDocument<EveSurfaceDocument>(AetheriaRuntimeVerseRecordKeys.DaemonGameSurface)
|
||||
.ReplaceAsync(portableGameSurface)
|
||||
.ConfigureAwait(false);
|
||||
await PublishDaemonSectorMapSurfaceAsync(node, result.Frame, inputCatalog).ConfigureAwait(false);
|
||||
}
|
||||
if (!publishTopology)
|
||||
return;
|
||||
|
|
@ -3126,6 +3147,73 @@ internal sealed record AetheriaPreparedPublication(
|
|||
AetheriaRuntimeDaemonTickResult Publication,
|
||||
IReadOnlyList<AetheriaYmirZonePersistenceCapture> Physics);
|
||||
|
||||
internal sealed class AetheriaHotEntityPublicationState
|
||||
{
|
||||
private EveEntitySoaViewDocument? _layout;
|
||||
|
||||
public void Set(EveEntitySoaViewDocument layout) => _layout = layout;
|
||||
|
||||
public bool Matches(EveEntitySoaViewDocument next)
|
||||
{
|
||||
var current = _layout;
|
||||
if (current == null || current.ProviderId != next.ProviderId ||
|
||||
current.BodySchemaId != next.BodySchemaId || current.LayoutVersion != next.LayoutVersion ||
|
||||
current.ProducerEpoch != next.ProducerEpoch || current.Capacity != next.Capacity ||
|
||||
current.Buffers.Length != next.Buffers.Length ||
|
||||
current.Columns.Length != next.Columns.Length || current.DirtyRanges.Length != next.DirtyRanges.Length ||
|
||||
current.RenderGroups.Length != next.RenderGroups.Length || current.Identities.Length != next.Identities.Length)
|
||||
return false;
|
||||
|
||||
for (var i = 0; i < current.Buffers.Length; i++)
|
||||
{
|
||||
var a = current.Buffers[i];
|
||||
var b = next.Buffers[i];
|
||||
if (a.BufferId != b.BufferId || a.ByteOffset != b.ByteOffset || a.ByteLength != b.ByteLength)
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < current.Columns.Length; i++)
|
||||
{
|
||||
var a = current.Columns[i];
|
||||
var b = next.Columns[i];
|
||||
if (a.ColumnId != b.ColumnId || a.Semantic != b.Semantic || a.BufferId != b.BufferId ||
|
||||
a.ScalarType != b.ScalarType || a.ByteOffset != b.ByteOffset ||
|
||||
a.ElementStride != b.ElementStride || a.ElementCount != b.ElementCount ||
|
||||
a.Unit != b.Unit || a.CoordinateSpace != b.CoordinateSpace)
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < current.DirtyRanges.Length; i++)
|
||||
{
|
||||
var a = current.DirtyRanges[i];
|
||||
var b = next.DirtyRanges[i];
|
||||
if (a.ColumnId != b.ColumnId || a.StartIndex != b.StartIndex || a.Count != b.Count)
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < current.RenderGroups.Length; i++)
|
||||
{
|
||||
var a = current.RenderGroups[i];
|
||||
var b = next.RenderGroups[i];
|
||||
if (a.GroupId != b.GroupId || a.MeshAssetRef != b.MeshAssetRef ||
|
||||
a.MaterialAssetRef != b.MaterialAssetRef || a.SubMeshIndex != b.SubMeshIndex ||
|
||||
a.Layer != b.Layer || a.InstanceCount != b.InstanceCount || a.DefaultScale != b.DefaultScale ||
|
||||
a.BoundsCenterX != b.BoundsCenterX || a.BoundsCenterY != b.BoundsCenterY ||
|
||||
a.BoundsCenterZ != b.BoundsCenterZ || a.BoundsSizeX != b.BoundsSizeX ||
|
||||
a.BoundsSizeY != b.BoundsSizeY || a.BoundsSizeZ != b.BoundsSizeZ ||
|
||||
a.ShadowMode != b.ShadowMode || a.ReceiveShadows != b.ReceiveShadows || a.Lod != b.Lod)
|
||||
return false;
|
||||
}
|
||||
for (var i = 0; i < current.Identities.Length; i++)
|
||||
{
|
||||
var a = current.Identities[i];
|
||||
var b = next.Identities[i];
|
||||
if (a.Index != b.Index || a.EntityId != b.EntityId || a.EntityKind != b.EntityKind ||
|
||||
a.Label != b.Label || a.Faction != b.Faction || a.Selectable != b.Selectable ||
|
||||
a.Controllable != b.Controllable || a.AssetRef != b.AssetRef)
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
internal sealed class AetheriaDaemonIngressState
|
||||
{
|
||||
public int ObservedEveCommandCount { get; set; } = -1;
|
||||
|
|
|
|||
|
|
@ -1083,7 +1083,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
("directionMode", "source-forward.v1"),
|
||||
("renderChannel", "world.effects"),
|
||||
("activationActionId", "pilot.scoop"),
|
||||
("power", FormatNumber(player.TractorPower)),
|
||||
("powerStateSemantic", AetheriaRuntimeDaemonSoaColumnKinds.BeamPower),
|
||||
("activationThreshold", FormatNumber(AetheriaRuntimeTractorMechanics.ActivationThreshold)),
|
||||
("radius", FormatNumber(AetheriaRuntimeTractorMechanics.Radius)),
|
||||
("maximumDistance", FormatNumber(AetheriaRuntimeTractorMechanics.Distance))
|
||||
|
|
|
|||
|
|
@ -42,6 +42,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public const string RenderVisibility = "render.visibility";
|
||||
public const string RenderLod = "render.lod";
|
||||
public const string CargoQuantity = "inventory.cargo.quantity";
|
||||
public const string BeamPower = "effect.beam.power";
|
||||
}
|
||||
|
||||
public static class AetheriaRuntimeDaemonRenderShadowModes
|
||||
|
|
|
|||
|
|
@ -69,7 +69,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public const string BodyId = "eve:entity-soa:aetheria.daemon:pilot";
|
||||
public const string ProducerId = "aetheria.daemon";
|
||||
public const string BodySchemaId = "gamecult.eve.entity_soa.body.v2";
|
||||
public const int LayoutVersion = 2;
|
||||
public const int LayoutVersion = 3;
|
||||
private const int Capacity = 4096;
|
||||
private const int EntityRenderGroupId = 1;
|
||||
private const int FloatStride = 4;
|
||||
|
|
@ -366,7 +366,13 @@ namespace GameCult.Aetheria.State.Verse
|
|||
var maxX = positions.Max(position => (float)position.PositionX);
|
||||
var maxY = positions.Max(position => (float)position.PositionY);
|
||||
var maxZ = positions.Max(position => (float)position.PositionZ);
|
||||
const float padding = 16.0f;
|
||||
const float boundsCell = 1024.0f;
|
||||
var boundsMinX = MathF.Floor((minX - 16.0f) / boundsCell) * boundsCell;
|
||||
var boundsMinY = MathF.Floor((minY - 16.0f) / boundsCell) * boundsCell;
|
||||
var boundsMinZ = MathF.Floor((minZ - 16.0f) / boundsCell) * boundsCell;
|
||||
var boundsMaxX = MathF.Ceiling((maxX + 16.0f) / boundsCell) * boundsCell;
|
||||
var boundsMaxY = MathF.Ceiling((maxY + 16.0f) / boundsCell) * boundsCell;
|
||||
var boundsMaxZ = MathF.Ceiling((maxZ + 16.0f) / boundsCell) * boundsCell;
|
||||
|
||||
return new[]
|
||||
{
|
||||
|
|
@ -391,12 +397,12 @@ namespace GameCult.Aetheria.State.Verse
|
|||
DisplayName = "Daemon current-zone entities",
|
||||
InstanceCount = entities.Count + pickups.Count + payloads.Count +
|
||||
celestialBodies.Count + asteroidInstances.Count,
|
||||
BoundsCenterX = (minX + maxX) * 0.5f,
|
||||
BoundsCenterY = (minY + maxY) * 0.5f,
|
||||
BoundsCenterZ = (minZ + maxZ) * 0.5f,
|
||||
BoundsSizeX = Math.Max(maxX - minX + padding, padding),
|
||||
BoundsSizeY = Math.Max(maxY - minY + padding, padding),
|
||||
BoundsSizeZ = Math.Max(maxZ - minZ + padding, padding),
|
||||
BoundsCenterX = (boundsMinX + boundsMaxX) * 0.5f,
|
||||
BoundsCenterY = (boundsMinY + boundsMaxY) * 0.5f,
|
||||
BoundsCenterZ = (boundsMinZ + boundsMaxZ) * 0.5f,
|
||||
BoundsSizeX = Math.Max(boundsMaxX - boundsMinX, boundsCell),
|
||||
BoundsSizeY = Math.Max(boundsMaxY - boundsMinY, boundsCell),
|
||||
BoundsSizeZ = Math.Max(boundsMaxZ - boundsMinZ, boundsCell),
|
||||
ShadowMode = AetheriaRuntimeDaemonRenderShadowModes.On,
|
||||
ReceiveShadows = true,
|
||||
DefaultScale = 1.0f,
|
||||
|
|
@ -415,6 +421,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
var entity = entities[index];
|
||||
WriteInt32(bytes, layout.EntityIndex + index * IntStride, entity.EntityIndex);
|
||||
WriteInt32(bytes, layout.CargoQuantity + index * IntStride, AetheriaRuntimeCargoCapacityQueries.Quantity(entity));
|
||||
WriteFloat(bytes, layout.BeamPower, index, entity.TractorPower);
|
||||
WriteFloat3(bytes, layout.Position, index, entity.PositionX, entity.PositionY, entity.PositionZ);
|
||||
WriteFloat(bytes, layout.RotationRadians, index, Math.Atan2(entity.DirectionX, entity.DirectionY));
|
||||
WriteFloat3(bytes, layout.Velocity, index, entity.VelocityX, 0.0, entity.VelocityY);
|
||||
|
|
@ -767,6 +774,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
private EntityHotSlabLayout(
|
||||
long entityIndex,
|
||||
long cargoQuantity,
|
||||
long beamPower,
|
||||
long position,
|
||||
long rotationRadians,
|
||||
long velocity,
|
||||
|
|
@ -781,6 +789,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
{
|
||||
EntityIndex = entityIndex;
|
||||
CargoQuantity = cargoQuantity;
|
||||
BeamPower = beamPower;
|
||||
Position = position;
|
||||
RotationRadians = rotationRadians;
|
||||
Velocity = velocity;
|
||||
|
|
@ -796,6 +805,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
|
||||
public long EntityIndex { get; }
|
||||
public long CargoQuantity { get; }
|
||||
public long BeamPower { get; }
|
||||
public long Position { get; }
|
||||
public long RotationRadians { get; }
|
||||
public long Velocity { get; }
|
||||
|
|
@ -814,6 +824,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
var offset = 0L;
|
||||
var entityIndex = Take(ref offset, count, IntStride);
|
||||
var cargoQuantity = Take(ref offset, count, IntStride);
|
||||
var beamPower = Take(ref offset, count, FloatStride);
|
||||
var position = Take(ref offset, count, Float3Stride);
|
||||
var rotationRadians = Take(ref offset, count, FloatStride);
|
||||
var velocity = Take(ref offset, count, Float3Stride);
|
||||
|
|
@ -828,6 +839,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
return new EntityHotSlabLayout(
|
||||
entityIndex,
|
||||
cargoQuantity,
|
||||
beamPower,
|
||||
position,
|
||||
rotationRadians,
|
||||
velocity,
|
||||
|
|
@ -847,6 +859,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
{
|
||||
Column("entity-index", AetheriaRuntimeDaemonSoaColumnKinds.EntityIndex, "int32", EntityIndex, IntStride, count, "index", "world"),
|
||||
Column("cargo-quantity", AetheriaRuntimeDaemonSoaColumnKinds.CargoQuantity, "int32", CargoQuantity, IntStride, count, "items", "entity"),
|
||||
Column("beam-power", AetheriaRuntimeDaemonSoaColumnKinds.BeamPower, "float32", BeamPower, FloatStride, count, "normalized", "entity"),
|
||||
Column("position", AetheriaRuntimeDaemonSoaColumnKinds.Position, "float3", Position, Float3Stride, count, "world_units", "world"),
|
||||
Column("rotation-radians", AetheriaRuntimeDaemonSoaColumnKinds.RotationRadians, "float32", RotationRadians, FloatStride, count, "radians", "world"),
|
||||
Column("velocity", AetheriaRuntimeDaemonSoaColumnKinds.Velocity, "float3", Velocity, Float3Stride, count, "world_units_per_second", "world"),
|
||||
|
|
|
|||
|
|
@ -40,7 +40,6 @@ namespace GameCult.Aetheria.State.Verse
|
|||
public int SimulationStepCount { get; set; } = 1;
|
||||
public bool StopCompressedSimulationOnAttention { get; set; }
|
||||
public bool BuildPublications { get; set; } = true;
|
||||
public AetheriaRuntimeDaemonSoaFramePublisher? SoaFramePublisher { get; set; }
|
||||
}
|
||||
|
||||
public sealed class AetheriaRuntimeDaemonTickResult
|
||||
|
|
@ -373,13 +372,10 @@ namespace GameCult.Aetheria.State.Verse
|
|||
int simulationStepsExecuted = 0,
|
||||
AetheriaRuntimeSimulationInterruption? attentionInterruption = null)
|
||||
{
|
||||
var soaPublisher = options.SoaFramePublisher ??
|
||||
throw new InvalidOperationException("The daemon-lifetime Aetheria SoA publisher is required.");
|
||||
var catalog = options.Catalog ?? new AetheriaRuntimeCatalogSnapshot(
|
||||
Array.Empty<AetheriaRuntimeCatalogItem>(),
|
||||
Array.Empty<AetheriaRuntimeCorporation>(),
|
||||
Array.Empty<AetheriaRuntimeNameFile>());
|
||||
var soaFrame = soaPublisher.BuildCurrentZoneEntities(frame, catalog);
|
||||
var commandBoundary = AetheriaRuntimeDaemonCommandBoundaryDocument.Create(options.DaemonId);
|
||||
var providerAdvertisement = AetheriaRuntimeDaemonProviderAdvertisementDocument.Create(
|
||||
stateFilePath,
|
||||
|
|
@ -429,7 +425,7 @@ namespace GameCult.Aetheria.State.Verse
|
|||
operationResult.Run,
|
||||
operationResult,
|
||||
frame,
|
||||
soaFrame,
|
||||
soaFrame: null,
|
||||
providerAdvertisement,
|
||||
health,
|
||||
commandBoundary,
|
||||
|
|
|
|||
Loading…
Reference in a new issue