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;
}
}
}