eq2go/internal/guilds/master.go
2025-08-08 13:08:15 -05:00

515 lines
12 KiB
Go

package guilds
import (
"sort"
"strings"
"sync"
"eq2emu/internal/database"
)
// MasterList provides a bespoke, performance-optimized guild collection with specialized indices
type MasterList struct {
// Core storage
guilds map[int32]*Guild
mutex sync.RWMutex
// Specialized indices for O(1) lookups
byName map[string]*Guild // name -> guild (case-insensitive)
byLevel map[int8][]*Guild // level -> guilds
recruiting []*Guild // guilds with recruiters
// Cached metadata with invalidation
levels []int8
recruitingMap map[int32]bool // guildID -> isRecruiting
metaStale bool
}
// NewMasterList creates a new bespoke master list optimized for guild operations
func NewMasterList() *MasterList {
return &MasterList{
guilds: make(map[int32]*Guild),
byName: make(map[string]*Guild),
byLevel: make(map[int8][]*Guild),
recruiting: make([]*Guild, 0),
recruitingMap: make(map[int32]bool),
}
}
// Add adds a guild to the master list with O(1) core operations
func (m *MasterList) Add(guild *Guild) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
guildID := guild.GetID()
// Check existence
if _, exists := m.guilds[guildID]; exists {
return false
}
// Add to core storage
m.guilds[guildID] = guild
// Update specialized indices
name := strings.ToLower(guild.GetName())
m.byName[name] = guild
level := guild.GetLevel()
m.byLevel[level] = append(m.byLevel[level], guild)
// Update recruiting index
isRecruiting := guild.GetNumRecruiters() > 0
m.recruitingMap[guildID] = isRecruiting
if isRecruiting {
m.recruiting = append(m.recruiting, guild)
}
// Invalidate metadata cache
m.metaStale = true
return true
}
// Get retrieves a guild by ID with O(1) performance
func (m *MasterList) Get(guildID int32) *Guild {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.guilds[guildID]
}
// GetByName retrieves a guild by name with O(1) performance
func (m *MasterList) GetByName(name string) *Guild {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.byName[strings.ToLower(name)]
}
// GetByLevel returns all guilds of a specific level with O(1) performance
func (m *MasterList) GetByLevel(level int8) []*Guild {
m.mutex.RLock()
defer m.mutex.RUnlock()
guilds := m.byLevel[level]
if guilds == nil {
return nil
}
// Return a copy to prevent external modification
result := make([]*Guild, len(guilds))
copy(result, guilds)
return result
}
// GetRecruiting returns all guilds that are recruiting with O(1) performance
func (m *MasterList) GetRecruiting() []*Guild {
m.mutex.RLock()
defer m.mutex.RUnlock()
// Return a copy to prevent external modification
result := make([]*Guild, len(m.recruiting))
copy(result, m.recruiting)
return result
}
// GetByLevelRange returns guilds within a level range using optimized range queries
func (m *MasterList) GetByLevelRange(minLevel, maxLevel int8) []*Guild {
m.mutex.RLock()
defer m.mutex.RUnlock()
var result []*Guild
for level := minLevel; level <= maxLevel; level++ {
if guilds := m.byLevel[level]; guilds != nil {
result = append(result, guilds...)
}
}
return result
}
// GetRecruitingByLevel returns recruiting guilds of a specific level using set intersection
func (m *MasterList) GetRecruitingByLevel(level int8) []*Guild {
m.mutex.RLock()
defer m.mutex.RUnlock()
levelGuilds := m.byLevel[level]
if levelGuilds == nil {
return nil
}
var result []*Guild
for _, guild := range levelGuilds {
if m.recruitingMap[guild.GetID()] {
result = append(result, guild)
}
}
return result
}
// Remove removes a guild from the master list
func (m *MasterList) Remove(guildID int32) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
guild := m.guilds[guildID]
if guild == nil {
return false
}
// Remove from core storage
delete(m.guilds, guildID)
// Remove from name index
name := strings.ToLower(guild.GetName())
delete(m.byName, name)
// Remove from level index
level := guild.GetLevel()
if levelGuilds := m.byLevel[level]; levelGuilds != nil {
for i, g := range levelGuilds {
if g.GetID() == guildID {
m.byLevel[level] = append(levelGuilds[:i], levelGuilds[i+1:]...)
break
}
}
// Clean up empty level arrays
if len(m.byLevel[level]) == 0 {
delete(m.byLevel, level)
}
}
// Remove from recruiting index
delete(m.recruitingMap, guildID)
for i, g := range m.recruiting {
if g.GetID() == guildID {
m.recruiting = append(m.recruiting[:i], m.recruiting[i+1:]...)
break
}
}
// Invalidate metadata cache
m.metaStale = true
return true
}
// Update updates a guild's indices when its properties change
func (m *MasterList) Update(guild *Guild) {
m.mutex.Lock()
defer m.mutex.Unlock()
guildID := guild.GetID()
oldGuild := m.guilds[guildID]
if oldGuild == nil {
return
}
// Update core storage
m.guilds[guildID] = guild
// Update name index if changed
oldName := strings.ToLower(oldGuild.GetName())
newName := strings.ToLower(guild.GetName())
if oldName != newName {
delete(m.byName, oldName)
m.byName[newName] = guild
}
// Update level index if changed
oldLevel := oldGuild.GetLevel()
newLevel := guild.GetLevel()
if oldLevel != newLevel {
// Remove from old level
if levelGuilds := m.byLevel[oldLevel]; levelGuilds != nil {
for i, g := range levelGuilds {
if g.GetID() == guildID {
m.byLevel[oldLevel] = append(levelGuilds[:i], levelGuilds[i+1:]...)
break
}
}
if len(m.byLevel[oldLevel]) == 0 {
delete(m.byLevel, oldLevel)
}
}
// Add to new level
m.byLevel[newLevel] = append(m.byLevel[newLevel], guild)
}
// Update recruiting index if changed
oldRecruiting := m.recruitingMap[guildID]
newRecruiting := guild.GetNumRecruiters() > 0
if oldRecruiting != newRecruiting {
m.recruitingMap[guildID] = newRecruiting
if oldRecruiting && !newRecruiting {
// Remove from recruiting list
for i, g := range m.recruiting {
if g.GetID() == guildID {
m.recruiting = append(m.recruiting[:i], m.recruiting[i+1:]...)
break
}
}
} else if !oldRecruiting && newRecruiting {
// Add to recruiting list
m.recruiting = append(m.recruiting, guild)
}
}
// Invalidate metadata cache
m.metaStale = true
}
// GetAll returns all guilds
func (m *MasterList) GetAll() []*Guild {
m.mutex.RLock()
defer m.mutex.RUnlock()
result := make([]*Guild, 0, len(m.guilds))
for _, guild := range m.guilds {
result = append(result, guild)
}
return result
}
// Count returns the total number of guilds
func (m *MasterList) Count() int {
m.mutex.RLock()
defer m.mutex.RUnlock()
return len(m.guilds)
}
// GetLevels returns all guild levels with lazy caching
func (m *MasterList) GetLevels() []int8 {
m.mutex.Lock()
defer m.mutex.Unlock()
if !m.metaStale && m.levels != nil {
return m.levels
}
// Rebuild level cache
levelSet := make(map[int8]bool)
for level := range m.byLevel {
levelSet[level] = true
}
m.levels = make([]int8, 0, len(levelSet))
for level := range levelSet {
m.levels = append(m.levels, level)
}
sort.Slice(m.levels, func(i, j int) bool {
return m.levels[i] < m.levels[j]
})
m.metaStale = false
return m.levels
}
// GetStatistics returns detailed statistics about the guild system
func (m *MasterList) GetStatistics() GuildStatistics {
m.mutex.RLock()
defer m.mutex.RUnlock()
stats := GuildStatistics{
TotalGuilds: len(m.guilds),
}
totalMembers := 0
activeGuilds := 0
totalRecruiters := len(m.recruiting)
uniqueAccounts := make(map[int32]bool)
highestLevel := int8(1)
for _, guild := range m.guilds {
members := guild.GetAllMembers()
memberCount := len(members)
totalMembers += memberCount
if memberCount > 0 {
activeGuilds++
}
// Track unique accounts
for _, member := range members {
uniqueAccounts[member.AccountID] = true
}
// Find highest level
if guild.GetLevel() > highestLevel {
highestLevel = guild.GetLevel()
}
}
stats.TotalMembers = totalMembers
stats.ActiveGuilds = activeGuilds
stats.TotalRecruiters = totalRecruiters
stats.UniqueAccounts = len(uniqueAccounts)
stats.HighestGuildLevel = highestLevel
if len(m.guilds) > 0 {
stats.AverageGuildSize = float64(totalMembers) / float64(len(m.guilds))
}
return stats
}
// Search performs efficient guild searches using specialized indices
func (m *MasterList) Search(criteria GuildSearchCriteria) []*Guild {
m.mutex.RLock()
defer m.mutex.RUnlock()
// Start with all guilds or use specialized indices for optimization
var candidates []*Guild
// Use level range optimization if specified
if criteria.MinLevel > 0 || criteria.MaxLevel > 0 {
minLevel := criteria.MinLevel
maxLevel := criteria.MaxLevel
if minLevel == 0 {
minLevel = 1
}
if maxLevel == 0 {
maxLevel = 100 // reasonable max
}
for level := minLevel; level <= maxLevel; level++ {
if guilds := m.byLevel[level]; guilds != nil {
candidates = append(candidates, guilds...)
}
}
} else if criteria.RecruitingOnly {
// Use recruiting index for optimization
candidates = make([]*Guild, len(m.recruiting))
copy(candidates, m.recruiting)
} else {
// Use all guilds
candidates = make([]*Guild, 0, len(m.guilds))
for _, guild := range m.guilds {
candidates = append(candidates, guild)
}
}
// Apply remaining filters
var results []*Guild
for _, guild := range candidates {
if m.matchesCriteria(guild, criteria) {
results = append(results, guild)
}
}
// Sort by name
sort.Slice(results, func(i, j int) bool {
return results[i].GetName() < results[j].GetName()
})
return results
}
// LoadFromDatabase loads all guilds from database into the master list
func (m *MasterList) LoadFromDatabase(db *database.Database) error {
// Clear existing data
m.mutex.Lock()
m.guilds = make(map[int32]*Guild)
m.byName = make(map[string]*Guild)
m.byLevel = make(map[int8][]*Guild)
m.recruiting = make([]*Guild, 0)
m.recruitingMap = make(map[int32]bool)
m.metaStale = true
m.mutex.Unlock()
// Load guild IDs first using MySQL-compatible approach
var guildIDs []int32
rows, err := db.Query(`SELECT id FROM guilds ORDER BY id`)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var id int32
if err := rows.Scan(&id); err != nil {
return err
}
guildIDs = append(guildIDs, id)
}
if err := rows.Err(); err != nil {
return err
}
// Load each guild and add to master list
for _, guildID := range guildIDs {
guild, err := Load(db, guildID)
if err != nil {
continue // Skip failed loads
}
m.Add(guild)
}
return nil
}
// Helper method for criteria matching
func (m *MasterList) matchesCriteria(guild *Guild, criteria GuildSearchCriteria) bool {
// Name pattern matching
if criteria.NamePattern != "" {
if !strings.Contains(strings.ToLower(guild.GetName()), strings.ToLower(criteria.NamePattern)) {
return false
}
}
// Member count range
memberCount := len(guild.GetAllMembers())
if criteria.MinMembers > 0 && memberCount < criteria.MinMembers {
return false
}
if criteria.MaxMembers > 0 && memberCount > criteria.MaxMembers {
return false
}
// Play style
if criteria.PlayStyle > 0 && guild.GetRecruitingPlayStyle() != criteria.PlayStyle {
return false
}
// Required flags
for _, flag := range criteria.RequiredFlags {
if guild.GetRecruitingFlag(flag) == 0 {
return false
}
}
// Required description tags
for _, tag := range criteria.RequiredDescTags {
found := false
for i := int8(0); i < 4; i++ {
if guild.GetRecruitingDescTag(i) == tag {
found = true
break
}
}
if !found {
return false
}
}
// Excluded description tags
for _, tag := range criteria.ExcludedDescTags {
for i := int8(0); i < 4; i++ {
if guild.GetRecruitingDescTag(i) == tag {
return false
}
}
}
return true
}