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 id = o.DevPlayerId.Trim(); if (id.Length == 0) { throw new InvalidOperationException("Game:DevPlayerId must be non-empty."); } var map = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); var p = o.DefaultPosition ?? new DefaultPositionOptions(); 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; } } }