package main import ( "sync" "time" "github.com/panjf2000/gnet/v2" "zombiezen.com/go/sqlite/sqlitex" ) type ClientState int const ( StateConnected ClientState = iota StateAuthenticated StateCharacterSelect StateDisconnected ) type Client struct { conn gnet.Conn address string connected time.Time lastActive time.Time state ClientState accountID uint32 accountName string version int16 mu sync.RWMutex } type ClientManager struct { clients map[int]*Client db *sqlitex.Pool mu sync.RWMutex } func NewClientManager(db *sqlitex.Pool) *ClientManager { return &ClientManager{ clients: make(map[int]*Client), db: db, } } func (cm *ClientManager) AddClient(fd int, client *Client) { cm.mu.Lock() defer cm.mu.Unlock() cm.clients[fd] = client } func (cm *ClientManager) RemoveClient(fd int) { cm.mu.Lock() defer cm.mu.Unlock() delete(cm.clients, fd) } func (cm *ClientManager) GetClient(fd int) *Client { cm.mu.RLock() defer cm.mu.RUnlock() return cm.clients[fd] } func (cm *ClientManager) CleanupExpired() { cm.mu.Lock() defer cm.mu.Unlock() cutoff := time.Now().Add(-30 * time.Minute) for fd, client := range cm.clients { if client.lastActive.Before(cutoff) { client.conn.Close() delete(cm.clients, fd) } } } func (cm *ClientManager) GetStats() (int, int) { cm.mu.RLock() defer cm.mu.RUnlock() total := len(cm.clients) authenticated := 0 for _, client := range cm.clients { if client.state == StateAuthenticated { authenticated++ } } return total, authenticated }