123 lines
3.8 KiB
C#
123 lines
3.8 KiB
C#
using System.Numerics;
|
|
using Nexus.Core;
|
|
using Nexus.Memory;
|
|
|
|
namespace Nexus.Data;
|
|
|
|
/// <summary>
|
|
/// Immutable snapshot of player position for lock-free cross-thread reads.
|
|
/// Class (reference type) so volatile assignment is atomic.
|
|
/// </summary>
|
|
public sealed class PlayerPositionData
|
|
{
|
|
public readonly float X, Y, Z;
|
|
public readonly bool HasPosition;
|
|
|
|
private PlayerPositionData() { }
|
|
|
|
public PlayerPositionData(float x, float y, float z)
|
|
{
|
|
X = x;
|
|
Y = y;
|
|
Z = z;
|
|
HasPosition = true;
|
|
}
|
|
|
|
public static readonly PlayerPositionData Empty = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Immutable snapshot of player vitals for lock-free cross-thread reads.
|
|
/// Class (reference type) so volatile assignment is atomic.
|
|
/// </summary>
|
|
public sealed class PlayerVitalsData
|
|
{
|
|
public readonly int LifeCurrent, LifeTotal;
|
|
public readonly int ManaCurrent, ManaTotal;
|
|
public readonly int EsCurrent, EsTotal;
|
|
public readonly bool HasVitals;
|
|
|
|
private PlayerVitalsData() { }
|
|
|
|
public PlayerVitalsData(int lifeCur, int lifeMax, int manaCur, int manaMax, int esCur, int esMax)
|
|
{
|
|
LifeCurrent = lifeCur;
|
|
LifeTotal = lifeMax;
|
|
ManaCurrent = manaCur;
|
|
ManaTotal = manaMax;
|
|
EsCurrent = esCur;
|
|
EsTotal = esMax;
|
|
HasVitals = true;
|
|
}
|
|
|
|
public static readonly PlayerVitalsData Empty = new();
|
|
}
|
|
|
|
/// <summary>
|
|
/// Immutable wrapper for Matrix4x4 so volatile reference assignment works.
|
|
/// </summary>
|
|
public sealed class CameraMatrixData
|
|
{
|
|
public readonly Matrix4x4 Matrix;
|
|
|
|
public CameraMatrixData(Matrix4x4 matrix)
|
|
{
|
|
Matrix = matrix;
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Centralized memory cache — single source of truth for all game data.
|
|
/// Hot fields updated at 60Hz (4 RPM calls), cold fields at 10Hz (full snapshot).
|
|
/// All fields use volatile reference types or atomic primitives for lock-free cross-thread reads.
|
|
/// </summary>
|
|
public sealed class GameDataCache
|
|
{
|
|
// ── Hot data (updated at 60Hz) ──
|
|
public volatile CameraMatrixData? CameraMatrix;
|
|
public volatile PlayerPositionData PlayerPosition = PlayerPositionData.Empty;
|
|
public volatile PlayerVitalsData PlayerVitals = PlayerVitalsData.Empty;
|
|
public volatile bool IsLoading;
|
|
public volatile bool IsEscapeOpen;
|
|
|
|
// ── Cold data (updated at 10Hz) ──
|
|
public volatile IReadOnlyList<EntitySnapshot> Entities = [];
|
|
public volatile IReadOnlyList<EntitySnapshot> HostileMonsters = [];
|
|
public volatile IReadOnlyList<EntitySnapshot> NearbyLoot = [];
|
|
public volatile WalkabilitySnapshot? Terrain;
|
|
public volatile uint AreaHash;
|
|
public volatile int AreaLevel;
|
|
public volatile string? CurrentAreaName;
|
|
public volatile string? CharacterName;
|
|
|
|
// ── UI tree root pointer (updated at 10Hz) — tree is read on-demand ──
|
|
public volatile nint GameUiPtr;
|
|
|
|
// ── Quest groups from UI element tree (updated at 10Hz) ──
|
|
public volatile IReadOnlyList<UiQuestGroup>? UiQuestGroups;
|
|
|
|
// ── Quest linked lists from GameUi (updated at 10Hz) ──
|
|
public volatile IReadOnlyList<QuestLinkedEntry>? QuestLinkedList;
|
|
|
|
// ── Quest states from AreaInstance sub-object (updated at 10Hz) ──
|
|
public volatile IReadOnlyList<QuestStateEntry>? QuestStates;
|
|
|
|
// ── Full GameState (updated at 10Hz) — for systems that need the complete object ──
|
|
public volatile GameState? LatestState;
|
|
|
|
// ── Timestamps ──
|
|
private long _hotTickTimestamp;
|
|
private long _coldTickTimestamp;
|
|
|
|
public long HotTickTimestamp
|
|
{
|
|
get => Interlocked.Read(ref _hotTickTimestamp);
|
|
set => Interlocked.Exchange(ref _hotTickTimestamp, value);
|
|
}
|
|
|
|
public long ColdTickTimestamp
|
|
{
|
|
get => Interlocked.Read(ref _coldTickTimestamp);
|
|
set => Interlocked.Exchange(ref _coldTickTimestamp, value);
|
|
}
|
|
}
|