using System.Collections.Concurrent; namespace NeonSprawl.Server.Game.Encounters; /// Thread-safe in-memory encounter progress; empty at startup (NEO-104). public sealed class InMemoryEncounterProgressStore : IEncounterProgressStore { private sealed class ProgressRow(bool started, HashSet defeatedNpcInstanceIds) { public bool Started { get; } = started; public HashSet DefeatedNpcInstanceIds { get; } = defeatedNpcInstanceIds; } private readonly ConcurrentDictionary byKey = new(StringComparer.Ordinal); private readonly ConcurrentDictionary keyLocks = new(StringComparer.Ordinal); /// public bool TryGetProgress(string playerId, string encounterId, out EncounterProgressSnapshot snapshot) { snapshot = default!; var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { if (!byKey.TryGetValue(key, out var row)) { return false; } snapshot = new EncounterProgressSnapshot( EncounterProgressIds.NormalizePlayerId(playerId), EncounterProgressIds.NormalizeEncounterId(encounterId), row.Started, new HashSet(row.DefeatedNpcInstanceIds, StringComparer.Ordinal)); return true; } } /// public bool TryActivate(string playerId, string encounterId) { var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { if (byKey.TryGetValue(key, out var existing) && existing.Started) { return false; } byKey[key] = new ProgressRow( started: true, defeatedNpcInstanceIds: new HashSet(StringComparer.Ordinal)); return true; } } /// public bool TryAddDefeatedTarget(string playerId, string encounterId, string npcInstanceId) { var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId); var npcKey = EncounterProgressIds.NormalizeNpcInstanceId(npcInstanceId); if (key.Length == 0 || npcKey.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { if (!byKey.TryGetValue(key, out var row) || !row.Started) { return false; } return row.DefeatedNpcInstanceIds.Add(npcKey); } } }