using System.Collections.Concurrent; namespace NeonSprawl.Server.Game.Encounters; /// Thread-safe in-memory encounter completion flags (NEO-104). public sealed class InMemoryEncounterCompletionStore : IEncounterCompletionStore { private readonly ConcurrentDictionary completedAtByKey = new(StringComparer.Ordinal); private readonly ConcurrentDictionary keyLocks = new(StringComparer.Ordinal); /// public bool IsCompleted(string playerId, string encounterId) { var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { return completedAtByKey.ContainsKey(key); } } /// public bool TryMarkCompleted(string playerId, string encounterId, DateTimeOffset completedAt) { var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { if (completedAtByKey.ContainsKey(key)) { return false; } completedAtByKey[key] = completedAt; return true; } } /// public bool TryGetCompletedAt(string playerId, string encounterId, out DateTimeOffset completedAt) { completedAt = default; var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { return completedAtByKey.TryGetValue(key, out completedAt); } } }