neon-sprawl/server/NeonSprawl.Server/Game/Npc/InMemoryThreatStateStore.cs

69 lines
2.1 KiB
C#

using System.Collections.Concurrent;
namespace NeonSprawl.Server.Game.Npc;
/// <summary>Thread-safe in-memory aggro holders for prototype NPC instances (NEO-92).</summary>
public sealed class InMemoryThreatStateStore : IThreatStateStore
{
private readonly ConcurrentDictionary<string, string?> holderByNpcId = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, object> idLocks = new(StringComparer.Ordinal);
/// <inheritdoc />
public bool TryGet(string? npcInstanceId, out ThreatStateSnapshot snapshot)
{
var key = NormalizeNpcInstanceId(npcInstanceId);
if (key.Length == 0 || !PrototypeNpcRegistry.TryGet(key, out _))
{
snapshot = default;
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
if (!holderByNpcId.TryGetValue(key, out var holder))
{
holder = null;
holderByNpcId[key] = holder;
}
snapshot = new ThreatStateSnapshot(key, holder);
return true;
}
}
/// <inheritdoc />
public bool TrySetHolder(string? npcInstanceId, string? playerIdLowercaseOrNull)
{
var key = NormalizeNpcInstanceId(npcInstanceId);
if (key.Length == 0 || !PrototypeNpcRegistry.TryGet(key, out _))
{
return false;
}
var normalizedHolder = NormalizePlayerId(playerIdLowercaseOrNull);
lock (idLocks.GetOrAdd(key, _ => new object()))
{
holderByNpcId[key] = normalizedHolder;
return true;
}
}
/// <inheritdoc />
public bool TryClearHolder(string? npcInstanceId) => TrySetHolder(npcInstanceId, null);
private static string NormalizeNpcInstanceId(string? npcInstanceId) =>
npcInstanceId?.Trim().ToLowerInvariant() ?? string.Empty;
private static string? NormalizePlayerId(string? playerId)
{
if (string.IsNullOrWhiteSpace(playerId))
{
return null;
}
return playerId.Trim().ToLowerInvariant();
}
}