111 lines
3.5 KiB
C#
111 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);
|
|
}
|
|
|
|
LastAdvancedUtc = DateTimeOffset.MinValue;
|
|
}
|
|
|
|
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,
|
|
};
|
|
}
|
|
}
|