neon-sprawl/server/NeonSprawl.Server/Game/Gathering/InMemoryResourceNodeInstanc...

89 lines
2.5 KiB
C#

using System.Collections.Concurrent;
namespace NeonSprawl.Server.Game.Gathering;
/// <summary>Thread-safe in-memory node instance capacity; empty at startup — lazy rows only (NEO-61).</summary>
public sealed class InMemoryResourceNodeInstanceStore : IResourceNodeInstanceStore
{
private readonly ConcurrentDictionary<string, int> remainingById = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, object> idLocks = new(StringComparer.Ordinal);
/// <inheritdoc />
public bool TryGetRemainingGathers(string interactableId, out ResourceNodeInstanceSnapshot snapshot)
{
var key = ResourceNodeInstanceIds.Normalize(interactableId);
if (key.Length == 0)
{
snapshot = default;
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
if (!remainingById.TryGetValue(key, out var remaining))
{
snapshot = default;
return false;
}
snapshot = new ResourceNodeInstanceSnapshot(key, remaining);
return true;
}
}
/// <inheritdoc />
public bool TryEnsureInitialized(string interactableId, int initialRemainingGathers)
{
var key = ResourceNodeInstanceIds.Normalize(interactableId);
if (key.Length == 0 || initialRemainingGathers < 0)
{
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
if (remainingById.ContainsKey(key))
{
return true;
}
remainingById[key] = initialRemainingGathers;
return true;
}
}
/// <inheritdoc />
public bool TryDecrementRemainingGathers(
string interactableId,
out int previousRemaining,
out int remainingGathers)
{
previousRemaining = 0;
remainingGathers = 0;
var key = ResourceNodeInstanceIds.Normalize(interactableId);
if (key.Length == 0)
{
return false;
}
lock (idLocks.GetOrAdd(key, _ => new object()))
{
if (!remainingById.TryGetValue(key, out var current) || current <= 0)
{
if (current == 0)
{
remainingGathers = 0;
}
return false;
}
previousRemaining = current;
remainingGathers = current - 1;
remainingById[key] = remainingGathers;
return true;
}
}
}