107 lines
3.3 KiB
C#
107 lines
3.3 KiB
C#
using Microsoft.Extensions.Options;
|
|
|
|
namespace NeonSprawl.Server.Game.PositionState;
|
|
|
|
/// <summary>PostgreSQL-backed <see cref="IPositionStateStore"/> (NS-17). Player ids are stored normalized: invariant lower + trim for case-insensitive parity with <see cref="InMemoryPositionStateStore"/>.</summary>
|
|
public sealed class PostgresPositionStateStore : IPositionStateStore
|
|
{
|
|
private readonly Npgsql.NpgsqlDataSource _dataSource;
|
|
private readonly GamePositionOptions _options;
|
|
|
|
public PostgresPositionStateStore(Npgsql.NpgsqlDataSource dataSource, IOptions<GamePositionOptions> options)
|
|
{
|
|
_dataSource = dataSource;
|
|
_options = options.Value;
|
|
var id = _options.DevPlayerId.Trim();
|
|
if (id.Length == 0)
|
|
{
|
|
throw new InvalidOperationException("Game:DevPlayerId must be non-empty.");
|
|
}
|
|
}
|
|
|
|
public bool TryGetPosition(string playerId, out PositionSnapshot snapshot)
|
|
{
|
|
var norm = NormalizePlayerId(playerId);
|
|
if (norm.Length == 0)
|
|
{
|
|
snapshot = default;
|
|
return false;
|
|
}
|
|
|
|
PostgresPositionBootstrap.EnsureSchema(_dataSource);
|
|
|
|
using var conn = _dataSource.OpenConnection();
|
|
using var cmd = new Npgsql.NpgsqlCommand(
|
|
"SELECT pos_x, pos_y, pos_z, sequence FROM player_position WHERE player_id = @pid;",
|
|
conn);
|
|
cmd.Parameters.AddWithValue("pid", norm);
|
|
using var reader = cmd.ExecuteReader();
|
|
if (!reader.Read())
|
|
{
|
|
snapshot = default;
|
|
return false;
|
|
}
|
|
|
|
snapshot = ReadSnapshot(reader);
|
|
return true;
|
|
}
|
|
|
|
public bool TryApplyMoveTarget(string playerId, double x, double y, double z, out PositionSnapshot snapshot)
|
|
{
|
|
var norm = NormalizePlayerId(playerId);
|
|
if (norm.Length == 0)
|
|
{
|
|
snapshot = default;
|
|
return false;
|
|
}
|
|
|
|
PostgresPositionBootstrap.EnsureSchema(_dataSource);
|
|
|
|
using var conn = _dataSource.OpenConnection();
|
|
using var cmd = new Npgsql.NpgsqlCommand(
|
|
"""
|
|
UPDATE player_position
|
|
SET pos_x = @x, pos_y = @y, pos_z = @z,
|
|
sequence = sequence + 1,
|
|
updated_at = now()
|
|
WHERE player_id = @pid
|
|
RETURNING pos_x, pos_y, pos_z, sequence;
|
|
""",
|
|
conn);
|
|
cmd.Parameters.AddWithValue("pid", norm);
|
|
cmd.Parameters.AddWithValue("x", x);
|
|
cmd.Parameters.AddWithValue("y", y);
|
|
cmd.Parameters.AddWithValue("z", z);
|
|
using var reader = cmd.ExecuteReader();
|
|
if (!reader.Read())
|
|
{
|
|
snapshot = default;
|
|
return false;
|
|
}
|
|
|
|
snapshot = ReadSnapshot(reader);
|
|
return true;
|
|
}
|
|
|
|
private static PositionSnapshot ReadSnapshot(Npgsql.NpgsqlDataReader reader)
|
|
{
|
|
var seq = reader.GetInt64(3);
|
|
return new PositionSnapshot(
|
|
reader.GetDouble(0),
|
|
reader.GetDouble(1),
|
|
reader.GetDouble(2),
|
|
unchecked((ulong)seq));
|
|
}
|
|
|
|
private static string NormalizePlayerId(string? playerId)
|
|
{
|
|
var t = playerId?.Trim();
|
|
if (string.IsNullOrEmpty(t))
|
|
{
|
|
return string.Empty;
|
|
}
|
|
|
|
return t.ToLowerInvariant();
|
|
}
|
|
}
|