using System.Collections.Concurrent; namespace NeonSprawl.Server.Game.Rewards; /// Thread-safe in-memory quest completion reward delivery records (NEO-126). public sealed class InMemoryRewardDeliveryStore : IRewardDeliveryStore { private readonly ConcurrentDictionary eventsByKey = new(StringComparer.Ordinal); private readonly ConcurrentDictionary keyLocks = new(StringComparer.Ordinal); /// public bool TryRecord(RewardDeliveryEvent deliveryEvent) { var key = RewardDeliveryIds.MakeStoreKey(deliveryEvent.PlayerId, deliveryEvent.QuestId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { if (eventsByKey.ContainsKey(key)) { return false; } eventsByKey[key] = deliveryEvent with { GrantedItems = [..deliveryEvent.GrantedItems], GrantedSkillXp = [..deliveryEvent.GrantedSkillXp], GrantedReputation = [..deliveryEvent.GrantedReputation], }; return true; } } /// public bool TryGet(string playerId, string questId, out RewardDeliveryEvent deliveryEvent) { deliveryEvent = default; var key = RewardDeliveryIds.MakeStoreKey(playerId, questId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { return eventsByKey.TryGetValue(key, out deliveryEvent); } } /// Dev fixture only — removes one player+quest delivery row (Bruno / test reset). public bool TryClear(string playerId, string questId) { var key = RewardDeliveryIds.MakeStoreKey(playerId, questId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { _ = eventsByKey.TryRemove(key, out _); keyLocks.TryRemove(key, out _); return true; } } }