using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
using NeonSprawl.Server.Game.PositionState;
namespace NeonSprawl.Server.Game.Items;
/// Thread-safe in-memory inventory; seeds configured dev player with an empty snapshot (NEO-54).
public sealed class InMemoryPlayerInventoryStore(IOptions options) : IPlayerInventoryStore
{
private readonly ConcurrentDictionary byPlayer = CreateInitialMap(options.Value);
private readonly ConcurrentDictionary playerLocks = new(StringComparer.OrdinalIgnoreCase);
private static ConcurrentDictionary CreateInitialMap(GamePositionOptions o)
{
var id = NormalizePlayerId(o.DevPlayerId);
var map = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
map[id] = PlayerInventorySnapshot.Empty();
return map;
}
///
public bool TryGetSnapshot(string playerId, out PlayerInventorySnapshot snapshot)
{
var key = NormalizePlayerId(playerId);
if (key.Length == 0)
{
snapshot = PlayerInventorySnapshot.Empty();
return false;
}
lock (playerLocks.GetOrAdd(key, _ => new object()))
{
if (!byPlayer.TryGetValue(key, out var current))
{
snapshot = PlayerInventorySnapshot.Empty();
return false;
}
snapshot = Clone(current);
return true;
}
}
///
public bool TryReplaceSnapshot(string playerId, PlayerInventorySnapshot snapshot)
{
var key = NormalizePlayerId(playerId);
if (key.Length == 0)
{
return false;
}
lock (playerLocks.GetOrAdd(key, _ => new object()))
{
if (!byPlayer.ContainsKey(key))
{
return false;
}
byPlayer[key] = Clone(snapshot);
return true;
}
}
///
public bool TryMutateSnapshot(
string playerId,
Func mutator,
out PlayerInventorySnapshot result)
{
var key = NormalizePlayerId(playerId);
if (key.Length == 0)
{
result = PlayerInventorySnapshot.Empty();
return false;
}
lock (playerLocks.GetOrAdd(key, _ => new object()))
{
if (!byPlayer.TryGetValue(key, out var current))
{
result = PlayerInventorySnapshot.Empty();
return false;
}
var write = mutator(Clone(current));
result = Clone(write.Value);
if (write.Write)
{
byPlayer[key] = Clone(write.Value);
}
return true;
}
}
private static PlayerInventorySnapshot Clone(PlayerInventorySnapshot source) =>
new()
{
BagSlots = CloneSlots(source.BagSlots),
EquipmentSlots = CloneSlots(source.EquipmentSlots),
};
private static InventorySlotState[] CloneSlots(InventorySlotState[] slots)
{
var copy = new InventorySlotState[slots.Length];
for (var i = 0; i < slots.Length; i++)
{
var s = slots[i];
copy[i] = new InventorySlotState(s.SlotIndex, s.ItemId, s.Quantity);
}
return copy;
}
private static string NormalizePlayerId(string? playerId)
{
var t = playerId?.Trim();
if (string.IsNullOrEmpty(t))
{
return string.Empty;
}
return t.ToLowerInvariant();
}
}