65 lines
2.0 KiB
C#
65 lines
2.0 KiB
C#
using System.Collections.Concurrent;
|
|
using Microsoft.Extensions.Options;
|
|
|
|
namespace NeonSprawl.Server.Game.PositionState;
|
|
|
|
/// <summary>Thread-safe in-memory positions; seeds the configured dev player on construction.</summary>
|
|
public sealed class InMemoryPositionStateStore(IOptions<GamePositionOptions> options) : IPositionStateStore
|
|
{
|
|
private readonly ConcurrentDictionary<string, PositionSnapshot> positions = CreateInitialMap(options.Value);
|
|
|
|
private static ConcurrentDictionary<string, PositionSnapshot> 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<string, PositionSnapshot>(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;
|
|
}
|
|
}
|
|
}
|