using System.Collections.Concurrent;
using Microsoft.Extensions.Options;
namespace NeonSprawl.Server.Game.PositionState;
/// Thread-safe in-memory positions; seeds the configured dev player on construction.
public sealed class InMemoryPositionStateStore(IOptions options) : IPositionStateStore
{
private readonly ConcurrentDictionary positions = CreateInitialMap(options.Value);
private static ConcurrentDictionary CreateInitialMap(GamePositionOptions o)
{
var ids = GamePlayerSeed.GetSeedPlayerIds(o);
if (ids.Count == 0)
{
throw new InvalidOperationException("Game:DevPlayerId must be non-empty.");
}
var map = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase);
var p = o.DefaultPosition ?? new DefaultPositionOptions();
foreach (var id in ids)
{
map[id] = new PositionSnapshot(p.X, p.Y, p.Z, 0);
}
return map;
}
public bool TryGetPosition(string playerId, out PositionSnapshot snapshot)
{
var key = playerId?.Trim();
if (!string.IsNullOrEmpty(key))
{
return positions.TryGetValue(key, out snapshot);
}
snapshot = default;
return false;
}
public bool TryApplyMoveTarget(string playerId, double x, double y, double z, out PositionSnapshot snapshot)
{
var key = playerId?.Trim();
if (string.IsNullOrEmpty(key))
{
snapshot = default;
return false;
}
while (true)
{
if (!positions.TryGetValue(key, out var previous))
{
snapshot = default;
return false;
}
var next = new PositionSnapshot(x, y, z, previous.Sequence + 1);
if (!positions.TryUpdate(key, next, previous))
{
continue;
}
snapshot = next;
return true;
}
}
}