eq2go/internal/login/client_list.go

226 lines
4.7 KiB
Go

package login
import (
"sync"
"time"
"eq2emu/internal/udp"
)
// ClientList manages connected login clients
type ClientList struct {
clients map[*udp.Connection]*Client
byAccountID map[int32]*Client
mu sync.RWMutex
}
// NewClientList creates a new client list
func NewClientList() *ClientList {
return &ClientList{
clients: make(map[*udp.Connection]*Client),
byAccountID: make(map[int32]*Client),
}
}
// Add adds a client to the list
func (cl *ClientList) Add(client *Client) {
if client == nil || client.GetConnection() == nil {
return
}
cl.mu.Lock()
defer cl.mu.Unlock()
// Remove any existing client for this connection
if existing, exists := cl.clients[client.GetConnection()]; exists {
existing.Disconnect("Replaced by new connection")
if existing.GetAccountID() > 0 {
delete(cl.byAccountID, existing.GetAccountID())
}
}
cl.clients[client.GetConnection()] = client
}
// Remove removes a client from the list
func (cl *ClientList) Remove(client *Client) {
if client == nil {
return
}
cl.mu.Lock()
defer cl.mu.Unlock()
conn := client.GetConnection()
if conn != nil {
delete(cl.clients, conn)
}
if client.GetAccountID() > 0 {
delete(cl.byAccountID, client.GetAccountID())
}
}
// GetByConnection returns a client by connection
func (cl *ClientList) GetByConnection(conn *udp.Connection) *Client {
if conn == nil {
return nil
}
cl.mu.RLock()
defer cl.mu.RUnlock()
return cl.clients[conn]
}
// GetByAccountID returns a client by account ID
func (cl *ClientList) GetByAccountID(accountID int32) *Client {
if accountID <= 0 {
return nil
}
cl.mu.RLock()
defer cl.mu.RUnlock()
return cl.byAccountID[accountID]
}
// UpdateAccountMapping updates the account ID mapping for a client
func (cl *ClientList) UpdateAccountMapping(client *Client) {
if client == nil {
return
}
cl.mu.Lock()
defer cl.mu.Unlock()
accountID := client.GetAccountID()
if accountID > 0 {
// Remove any existing mapping for this account
if existing, exists := cl.byAccountID[accountID]; exists && existing != client {
existing.Disconnect("Account logged in elsewhere")
delete(cl.clients, existing.GetConnection())
}
cl.byAccountID[accountID] = client
}
}
// Process processes all clients
func (cl *ClientList) Process() {
cl.mu.Lock()
defer cl.mu.Unlock()
// Check for timed out clients
timeout := 5 * time.Minute
var toRemove []*Client
for _, client := range cl.clients {
if client.IsTimedOut(timeout) {
toRemove = append(toRemove, client)
}
}
// Remove timed out clients
for _, client := range toRemove {
client.Disconnect("Connection timeout")
delete(cl.clients, client.GetConnection())
if client.GetAccountID() > 0 {
delete(cl.byAccountID, client.GetAccountID())
}
}
}
// Count returns the number of connected clients
func (cl *ClientList) Count() int {
cl.mu.RLock()
defer cl.mu.RUnlock()
return len(cl.clients)
}
// GetClients returns a slice of all clients (snapshot)
func (cl *ClientList) GetClients() []*Client {
cl.mu.RLock()
defer cl.mu.RUnlock()
clients := make([]*Client, 0, len(cl.clients))
for _, client := range cl.clients {
clients = append(clients, client)
}
return clients
}
// GetClientsByState returns clients in a specific state
func (cl *ClientList) GetClientsByState(state ClientState) []*Client {
cl.mu.RLock()
defer cl.mu.RUnlock()
var clients []*Client
for _, client := range cl.clients {
if client.GetState() == state {
clients = append(clients, client)
}
}
return clients
}
// DisconnectAll disconnects all clients
func (cl *ClientList) DisconnectAll(reason string) {
cl.mu.Lock()
defer cl.mu.Unlock()
for _, client := range cl.clients {
client.Disconnect(reason)
}
// Clear maps
cl.clients = make(map[*udp.Connection]*Client)
cl.byAccountID = make(map[int32]*Client)
}
// DisconnectByAccountID disconnects a client by account ID
func (cl *ClientList) DisconnectByAccountID(accountID int32, reason string) bool {
client := cl.GetByAccountID(accountID)
if client == nil {
return false
}
client.Disconnect(reason)
cl.Remove(client)
return true
}
// GetStats returns client list statistics
func (cl *ClientList) GetStats() ClientListStats {
cl.mu.RLock()
defer cl.mu.RUnlock()
stats := ClientListStats{
Total: len(cl.clients),
States: make(map[ClientState]int),
}
for _, client := range cl.clients {
state := client.GetState()
stats.States[state]++
}
return stats
}
// ClientListStats represents statistics about the client list
type ClientListStats struct {
Total int `json:"total"`
States map[ClientState]int `json:"states"`
}
// ForEach executes a function for each client (thread-safe)
func (cl *ClientList) ForEach(fn func(*Client)) {
clients := cl.GetClients()
for _, client := range clients {
fn(client)
}
}