using System.Collections.Concurrent; namespace NeonSprawl.Server.Game.Rewards; /// Thread-safe in-memory 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); 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) => TryGet(playerId, RewardDeliverySourceKinds.QuestCompletion, questId, out deliveryEvent); /// public bool TryGet(string playerId, string sourceKind, string sourceId, out RewardDeliveryEvent deliveryEvent) { deliveryEvent = default; var key = RewardDeliveryIds.MakeStoreKey(playerId, sourceKind, sourceId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { return eventsByKey.TryGetValue(key, out deliveryEvent); } } /// public bool TryClear(string playerId, string questId) => TryClear(playerId, RewardDeliverySourceKinds.QuestCompletion, questId); /// Dev fixture only — removes one player+source delivery row (Bruno / test reset). public bool TryClear(string playerId, string sourceKind, string sourceId) { var key = RewardDeliveryIds.MakeStoreKey(playerId, sourceKind, sourceId); if (key.Length == 0) { return false; } lock (keyLocks.GetOrAdd(key, _ => new object())) { _ = eventsByKey.TryRemove(key, out _); keyLocks.TryRemove(key, out _); return true; } } }