neon-sprawl/server/NeonSprawl.Server/Game/Items/InMemoryPlayerInventoryStor...

85 lines
2.7 KiB
C#

using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
using NeonSprawl.Server.Game.PositionState;
namespace NeonSprawl.Server.Game.Items;
/// <summary>Thread-safe in-memory inventory; seeds configured dev player with an empty snapshot (NEO-54).</summary>
public sealed class InMemoryPlayerInventoryStore(IOptions<GamePositionOptions> options) : IPlayerInventoryStore
{
private readonly ConcurrentDictionary<string, PlayerInventorySnapshot> byPlayer = CreateInitialMap(options.Value);
private readonly ConcurrentDictionary<string, object> playerLocks = new(StringComparer.OrdinalIgnoreCase);
private static ConcurrentDictionary<string, PlayerInventorySnapshot> CreateInitialMap(GamePositionOptions o)
{
var id = NormalizePlayerId(o.DevPlayerId);
var map = new ConcurrentDictionary<string, PlayerInventorySnapshot>(StringComparer.OrdinalIgnoreCase);
map[id] = PlayerInventorySnapshot.Empty();
return map;
}
/// <inheritdoc />
public bool TryGetSnapshot(string playerId, out PlayerInventorySnapshot snapshot)
{
var key = NormalizePlayerId(playerId);
if (key.Length == 0 || !byPlayer.TryGetValue(key, out var existing))
{
snapshot = PlayerInventorySnapshot.Empty();
return false;
}
lock (playerLocks.GetOrAdd(key, _ => new object()))
{
snapshot = Clone(existing);
return true;
}
}
/// <inheritdoc />
public bool TryReplaceSnapshot(string playerId, PlayerInventorySnapshot snapshot)
{
var key = NormalizePlayerId(playerId);
if (key.Length == 0 || !byPlayer.ContainsKey(key))
{
return false;
}
lock (playerLocks.GetOrAdd(key, _ => new object()))
{
byPlayer[key] = Clone(snapshot);
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();
}
}