63 lines
2.2 KiB
C#
63 lines
2.2 KiB
C#
using System.Collections.Concurrent;
|
|
|
|
namespace NeonSprawl.Server.Game.AbilityInput;
|
|
|
|
/// <summary>Thread-safe in-memory cooldown map (NEO-32); not persisted across process restarts.</summary>
|
|
public sealed class InMemoryPlayerAbilityCooldownStore : IPlayerAbilityCooldownStore
|
|
{
|
|
private readonly ConcurrentDictionary<string, ConcurrentDictionary<int, DateTimeOffset>> ends = new(StringComparer.Ordinal);
|
|
|
|
public bool IsOnCooldown(string playerId, int slotIndex, DateTimeOffset now)
|
|
{
|
|
return TryGetActiveCooldownEnd(playerId, slotIndex, now, out _);
|
|
}
|
|
|
|
public void StartCooldown(string playerId, int slotIndex, DateTimeOffset now, TimeSpan duration)
|
|
{
|
|
var perSlot = ends.GetOrAdd(playerId, _ => new ConcurrentDictionary<int, DateTimeOffset>());
|
|
perSlot[slotIndex] = now + duration;
|
|
}
|
|
|
|
public bool TryGetCooldownEndUtc(string playerId, int slotIndex, DateTimeOffset now, out DateTimeOffset endUtc)
|
|
{
|
|
return TryGetActiveCooldownEnd(playerId, slotIndex, now, out endUtc);
|
|
}
|
|
|
|
/// <summary>Shared read path: returns true with <paramref name="endUtc"/> when cooldown is active; removes slot entry when <paramref name="now"/> >= stored end.</summary>
|
|
private static bool TryGetActiveCooldownEnd(
|
|
ConcurrentDictionary<string, ConcurrentDictionary<int, DateTimeOffset>> ends,
|
|
string playerId,
|
|
int slotIndex,
|
|
DateTimeOffset now,
|
|
out DateTimeOffset endUtc)
|
|
{
|
|
endUtc = default;
|
|
if (!ends.TryGetValue(playerId, out var perSlot))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (!perSlot.TryGetValue(slotIndex, out var end))
|
|
{
|
|
return false;
|
|
}
|
|
|
|
if (now >= end)
|
|
{
|
|
perSlot.TryRemove(slotIndex, out _);
|
|
return false;
|
|
}
|
|
|
|
endUtc = end;
|
|
return true;
|
|
}
|
|
|
|
private bool TryGetActiveCooldownEnd(string playerId, int slotIndex, DateTimeOffset now, out DateTimeOffset endUtc)
|
|
{
|
|
return TryGetActiveCooldownEnd(ends, playerId, slotIndex, now, out endUtc);
|
|
}
|
|
|
|
/// <inheritdoc />
|
|
public void ClearPlayer(string playerId) => _ = ends.TryRemove(playerId, out _);
|
|
}
|