using System; using System.Collections.Generic; using System.Linq; using System.Net.Sockets; namespace codelanguage { /// /// Represents a connected client on the server (networking) /// public class ServerClientConnection { public int ClientId { get; set; } public string Username { get; set; } public TcpClient Socket { get; set; } public bool IsAuthenticated { get; set; } public bool IsConnected { get; set; } public DateTime ConnectedAt { get; set; } public DateTime LastHeartbeat { get; set; } public int Latency { get; set; } // milliseconds // Client state public CraftValue PlayerMob { get; set; } // Player character public List OwnedObjects { get; set; } = new(); // Message queues public MessageQueue IncomingMessages { get; set; } = new(); public MessageQueue OutgoingMessages { get; set; } = new(); public ServerClientConnection(int clientId) { ClientId = clientId; ConnectedAt = DateTime.UtcNow; LastHeartbeat = DateTime.UtcNow; IsConnected = true; } /// /// Check if this connection has timed out /// public bool IsTimedOut() { return DateTime.UtcNow - LastHeartbeat > TimeSpan.FromMilliseconds(NetworkConfig.HeartbeatTimeoutMs); } /// /// Check if heartbeat is due /// public bool IsHeartbeatDue() { return DateTime.UtcNow - LastHeartbeat > TimeSpan.FromMilliseconds(NetworkConfig.HeartbeatIntervalMs); } /// /// Refresh client liveness only, preserving the previous latency value. /// public void TouchHeartbeat(int latency = -1) { LastHeartbeat = DateTime.UtcNow; if (latency >= 0) Latency = latency; } public override string ToString() { return $"ClientConnection(Id={ClientId}, User={Username ?? "unknown"}, Connected={ConnectedAt}, Auth={IsAuthenticated})"; } } /// /// Server-side connection manager /// Handles client lifecycle: connect, authenticate, disconnect /// public class ConnectionManager { private Dictionary _clients = new(); private int _nextClientId = 1; private object _clientLock = new object(); /// /// Create a new client connection /// public ServerClientConnection CreateConnection(TcpClient socket = null) { lock (_clientLock) { int clientId = _nextClientId++; var connection = new ServerClientConnection(clientId) { Socket = socket }; _clients[clientId] = connection; return connection; } } /// /// Get a connection by ID /// public ServerClientConnection? GetConnection(int clientId) { lock (_clientLock) { _clients.TryGetValue(clientId, out var conn); return conn; } } /// /// Authenticate a client /// public bool AuthenticateClient(int clientId, string username) { lock (_clientLock) { if (!_clients.TryGetValue(clientId, out var conn)) return false; conn.Username = username; conn.IsAuthenticated = true; return true; } } /// /// Disconnect a client /// public bool DisconnectClient(int clientId) { lock (_clientLock) { if (!_clients.TryGetValue(clientId, out var conn)) return false; conn.IsConnected = false; conn.Socket?.Close(); _clients.Remove(clientId); return true; } } /// /// Update heartbeat for a client /// public void UpdateHeartbeat(int clientId, int latency = 0) { lock (_clientLock) { if (_clients.TryGetValue(clientId, out var conn)) { conn.LastHeartbeat = DateTime.UtcNow; if (latency >= 0) conn.Latency = latency; } } } public List GetConnectedClients() { lock (_clientLock) { var result = new List(_clients.Count); foreach (var kvp in _clients) if (kvp.Value.IsConnected) result.Add(kvp.Value); return result; } } public List GetAuthenticatedClients() { lock (_clientLock) { var result = new List(_clients.Count); foreach (var kvp in _clients) if (kvp.Value.IsConnected && kvp.Value.IsAuthenticated) result.Add(kvp.Value); return result; } } public List DetectDeadConnections() { var deadClients = new List(); List toRemove; lock (_clientLock) { toRemove = new List(_clients.Count); foreach (var kvp in _clients) { if (kvp.Value.IsTimedOut()) { deadClients.Add(kvp.Key); toRemove.Add(kvp.Key); } } foreach (var id in toRemove) { if (_clients.TryGetValue(id, out var conn)) { conn.IsConnected = false; _clients.Remove(id); } } } return deadClients; } /// /// Touch a client's heartbeat without otherwise enqueueing any message. /// Active inbound traffic uses this to refresh liveness so legitimate /// busy clients are not killed by the heartbeat timeout. /// public void TouchHeartbeat(int clientId, int latency = -1) { lock (_clientLock) { if (_clients.TryGetValue(clientId, out var conn)) { conn.LastHeartbeat = DateTime.UtcNow; if (latency >= 0) conn.Latency = latency; } } } /// /// Send message to a specific client /// public void SendToClient(int clientId, NetworkMessage msg) { lock (_clientLock) { if (_clients.TryGetValue(clientId, out var conn)) { msg.ClientId = clientId; conn.OutgoingMessages.Enqueue(msg); } } } public void BroadcastToClients(NetworkMessage msg, int excludeClientId = -1) { List<(int id, ServerClientConnection conn)> targets; lock (_clientLock) { targets = new List<(int, ServerClientConnection)>(_clients.Count); foreach (var kvp in _clients) if (kvp.Value.IsConnected && kvp.Key != excludeClientId) targets.Add((kvp.Key, kvp.Value)); } // Enqueue outside lock to reduce contention. Each recipient gets its own // message copy so mutating ClientId for one recipient cannot leak into // messages already queued for other clients. foreach (var (id, conn) in targets) { var recipientMessage = msg.Clone(); recipientMessage.ClientId = id; conn.OutgoingMessages.Enqueue(recipientMessage); } } public void BroadcastToAuthenticatedClients(NetworkMessage msg, int excludeClientId = -1) { List<(int id, ServerClientConnection conn)> targets; lock (_clientLock) { targets = new List<(int, ServerClientConnection)>(_clients.Count); foreach (var kvp in _clients) if (kvp.Value.IsConnected && kvp.Value.IsAuthenticated && kvp.Key != excludeClientId) targets.Add((kvp.Key, kvp.Value)); } // Each recipient must observe a distinct message object. Reusing the same // instance and mutating ClientId after enqueueing would let a later // assignment leak into messages already queued for other clients. foreach (var (id, conn) in targets) { var recipientMessage = msg.Clone(); recipientMessage.ClientId = id; conn.OutgoingMessages.Enqueue(recipientMessage); } } public int GetConnectionCount() { lock (_clientLock) { int count = 0; foreach (var kvp in _clients) if (kvp.Value.IsConnected) count++; return count; } } public int GetTotalClientCount() { lock (_clientLock) { return _clients.Count; } } public bool IsAtCapacity() { return GetConnectionCount() >= NetworkConfig.MaxConnections; } /// /// Clear all connections (for testing) /// public void ClearAll() { lock (_clientLock) { foreach (var conn in _clients.Values) { conn.Socket?.Close(); } _clients.Clear(); } } } /// /// Client-side connection state /// public class ClientConnectionState { public int ClientId { get; set; } public string ServerAddress { get; set; } public int ServerPort { get; set; } public bool IsConnected { get; set; } public bool IsAuthenticated { get; set; } public string SessionToken { get; set; } public DateTime ConnectedAt { get; set; } public DateTime LastHeartbeat { get; set; } public int Latency { get; set; } // Message queues public MessageQueue IncomingMessages { get; set; } = new(); public MessageQueue OutgoingMessages { get; set; } = new(); public ClientConnectionState() { ConnectedAt = DateTime.UtcNow; LastHeartbeat = DateTime.UtcNow; } public bool IsTimedOut() { return DateTime.UtcNow - LastHeartbeat > TimeSpan.FromMilliseconds(NetworkConfig.HeartbeatTimeoutMs); } public void TouchHeartbeat(int latency = -1) { LastHeartbeat = DateTime.UtcNow; if (latency >= 0) Latency = latency; } public override string ToString() { return $"ClientConnectionState(Id={ClientId}, Server={ServerAddress}:{ServerPort}, Connected={IsConnected}, Auth={IsAuthenticated})"; } } }