using System.Collections.Concurrent;
namespace NeonSprawl.Server.Game.Encounters;
/// Thread-safe in-memory encounter completion flags (NEO-104).
public sealed class InMemoryEncounterCompletionStore : IEncounterCompletionStore
{
private readonly ConcurrentDictionary completedAtByKey = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary keyLocks = new(StringComparer.Ordinal);
///
public bool IsCompleted(string playerId, string encounterId)
{
var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId);
return key.Length > 0 && completedAtByKey.ContainsKey(key);
}
///
public bool TryMarkCompleted(string playerId, string encounterId, DateTimeOffset completedAt)
{
var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId);
if (key.Length == 0)
{
return false;
}
lock (keyLocks.GetOrAdd(key, _ => new object()))
{
if (completedAtByKey.ContainsKey(key))
{
return false;
}
completedAtByKey[key] = completedAt;
return true;
}
}
///
public bool TryGetCompletedAt(string playerId, string encounterId, out DateTimeOffset completedAt)
{
completedAt = default;
var key = EncounterProgressIds.MakeProgressKey(playerId, encounterId);
if (key.Length == 0)
{
return false;
}
lock (keyLocks.GetOrAdd(key, _ => new object()))
{
return completedAtByKey.TryGetValue(key, out completedAt);
}
}
}