using System.Collections.Concurrent; namespace NeonSprawl.Server.Game.Encounters; /// Thread-safe in-memory encounter completion event records (NEO-107). public sealed class InMemoryEncounterCompleteEventStore : IEncounterCompleteEventStore { private readonly ConcurrentDictionary eventsByKey = new(StringComparer.Ordinal); private readonly ConcurrentDictionary keyLocks = new(StringComparer.Ordinal); /// public bool TryRecord(EncounterCompleteEvent completeEvent) { var key = EncounterProgressIds.MakeProgressKey(completeEvent.PlayerId, completeEvent.EncounterId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { if (eventsByKey.ContainsKey(key)) { return false; } eventsByKey[key] = completeEvent with { GrantedItems = completeEvent.GrantedItems.ToArray(), }; return true; } } /// public bool TryGet(string playerId, string encounterId, out EncounterCompleteEvent completeEvent) { completeEvent = default; var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { return eventsByKey.TryGetValue(key, out completeEvent); } } /// Dev fixture only — removes one player+encounter event row (NEO-108 Bruno reset). public bool TryClear(string playerId, string encounterId) { var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { var removed = eventsByKey.TryRemove(key, out _); keyLocks.TryRemove(key, out _); return removed; } } }