using System.Collections.Concurrent;
using System.Net.WebSockets;
namespace HSchool.Server.Net;
/// Tracks live connections and hands out client ids.
internal sealed class ClientRegistry
{
private readonly ConcurrentDictionary _clients = new();
private readonly object _names = new();
private uint _nextPlayerId;
public int Count => _clients.Count;
/// Snapshot-free enumeration; safe because the dictionary is concurrent.
public IEnumerable All => _clients.Values;
public GameClient Add(WebSocket socket)
{
var playerId = Interlocked.Increment(ref _nextPlayerId);
var client = new GameClient(playerId, socket);
_clients[playerId] = client;
return client;
}
public GameClient? Find(uint playerId) => _clients.GetValueOrDefault(playerId);
public void Remove(uint playerId) => _clients.TryRemove(playerId, out _);
public bool IsUserNameOnline(string normalizedUserName)
{
lock (_names)
{
return HasUserName(normalizedUserName);
}
}
///
/// One live socket per name. Checked under the same lock as
/// so a second tab cannot sneak a Welcome in between the HTTP check and SetUserName.
///
public bool TryClaimUserName(GameClient client, string userName)
{
lock (_names)
{
if (HasUserName(userName))
{
return false;
}
client.SetUserName(userName);
return true;
}
}
private bool HasUserName(string normalizedUserName)
{
foreach (var existing in _clients.Values)
{
if (existing.UserName is null)
{
continue;
}
if (string.Equals(existing.NormalizedUserName, normalizedUserName, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
return false;
}
}