neon-sprawl/server/NeonSprawl.Server/Game/Npc/InMemoryNpcRuntimeStateStor...

109 lines
3.5 KiB
C#

using System.Collections.Concurrent;
namespace NeonSprawl.Server.Game.Npc;
/// <summary>Thread-safe in-memory NPC runtime state for prototype instances (NEO-93).</summary>
public sealed class InMemoryNpcRuntimeStateStore : INpcRuntimeStateStore
{
private readonly ConcurrentDictionary<string, NpcRuntimeRow> rowsByNpcId = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, object> idLocks = new(StringComparer.Ordinal);
/// <inheritdoc />
public DateTimeOffset LastAdvancedUtc { get; set; } = DateTimeOffset.MinValue;
/// <inheritdoc />
public bool TryGet(string? npcInstanceId, out NpcRuntimeStateSnapshot snapshot)
{
var key = NormalizeNpcInstanceId(npcInstanceId);
if (key.Length == 0 || !PrototypeNpcRegistry.TryGet(key, out _))
{
snapshot = default;
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
var row = rowsByNpcId.GetOrAdd(key, static id => NpcRuntimeRow.CreateIdle(id));
snapshot = row.ToSnapshot();
return true;
}
}
/// <inheritdoc />
public bool TryWrite(string? npcInstanceId, in NpcRuntimeStateSnapshot snapshot)
{
var key = NormalizeNpcInstanceId(npcInstanceId);
if (key.Length == 0 || !PrototypeNpcRegistry.TryGet(key, out _))
{
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
rowsByNpcId[key] = NpcRuntimeRow.FromSnapshot(snapshot with { NpcInstanceId = key });
return true;
}
}
/// <inheritdoc />
public bool TryResetPrototypeRow(string? npcInstanceId)
{
var key = NormalizeNpcInstanceId(npcInstanceId);
if (key.Length == 0 || !PrototypeNpcRegistry.TryGet(key, out _))
{
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
rowsByNpcId[key] = NpcRuntimeRow.CreateIdle(key);
return true;
}
}
/// <inheritdoc />
public void ResetAllPrototypeRows()
{
foreach (var npcInstanceId in PrototypeNpcRegistry.GetInstanceIdsInOrder())
{
_ = TryResetPrototypeRow(npcInstanceId);
}
}
private static string NormalizeNpcInstanceId(string? npcInstanceId) =>
npcInstanceId?.Trim().ToLowerInvariant() ?? string.Empty;
private sealed class NpcRuntimeRow
{
public required string NpcInstanceId { get; init; }
public NpcBehaviorState BehaviorState { get; init; }
public DateTimeOffset PhaseStartedUtc { get; init; }
public ActiveTelegraphSnapshot? ActiveTelegraph { get; init; }
public static NpcRuntimeRow CreateIdle(string npcInstanceId) =>
new()
{
NpcInstanceId = npcInstanceId,
BehaviorState = NpcBehaviorState.Idle,
PhaseStartedUtc = DateTimeOffset.MinValue,
ActiveTelegraph = null,
};
public NpcRuntimeStateSnapshot ToSnapshot() =>
new(NpcInstanceId, BehaviorState, PhaseStartedUtc, ActiveTelegraph);
public static NpcRuntimeRow FromSnapshot(NpcRuntimeStateSnapshot snapshot) =>
new()
{
NpcInstanceId = snapshot.NpcInstanceId,
BehaviorState = snapshot.BehaviorState,
PhaseStartedUtc = snapshot.PhaseStartedUtc,
ActiveTelegraph = snapshot.ActiveTelegraph,
};
}
}