331 lines
8.8 KiB
Go

package achievements
import (
"fmt"
"sync"
)
// MasterList is a specialized achievement master list optimized for:
// - Fast ID-based lookups (O(1))
// - Fast category-based lookups (O(1))
// - Fast expansion-based lookups (O(1))
// - Efficient filtering and iteration
type MasterList struct {
// Core storage
achievements map[uint32]*Achievement // ID -> Achievement
mutex sync.RWMutex
// Category indices for O(1) lookups
byCategory map[string][]*Achievement // Category -> achievements
byExpansion map[string][]*Achievement // Expansion -> achievements
// Cached metadata
categories []string // Unique categories (cached)
expansions []string // Unique expansions (cached)
metaStale bool // Whether metadata cache needs refresh
}
// NewMasterList creates a new specialized achievement master list
func NewMasterList() *MasterList {
return &MasterList{
achievements: make(map[uint32]*Achievement),
byCategory: make(map[string][]*Achievement),
byExpansion: make(map[string][]*Achievement),
metaStale: true,
}
}
// refreshMetaCache updates the categories and expansions cache
func (m *MasterList) refreshMetaCache() {
if !m.metaStale {
return
}
categorySet := make(map[string]struct{})
expansionSet := make(map[string]struct{})
// Collect unique categories and expansions
for _, achievement := range m.achievements {
if achievement.Category != "" {
categorySet[achievement.Category] = struct{}{}
}
if achievement.Expansion != "" {
expansionSet[achievement.Expansion] = struct{}{}
}
}
// Clear existing caches and rebuild
m.categories = m.categories[:0]
for category := range categorySet {
m.categories = append(m.categories, category)
}
m.expansions = m.expansions[:0]
for expansion := range expansionSet {
m.expansions = append(m.expansions, expansion)
}
m.metaStale = false
}
// AddAchievement adds an achievement with full indexing
func (m *MasterList) AddAchievement(achievement *Achievement) bool {
if achievement == nil {
return false
}
m.mutex.Lock()
defer m.mutex.Unlock()
// Check if exists
if _, exists := m.achievements[achievement.AchievementID]; exists {
return false
}
// Add to core storage
m.achievements[achievement.AchievementID] = achievement
// Update category index
if achievement.Category != "" {
m.byCategory[achievement.Category] = append(m.byCategory[achievement.Category], achievement)
}
// Update expansion index
if achievement.Expansion != "" {
m.byExpansion[achievement.Expansion] = append(m.byExpansion[achievement.Expansion], achievement)
}
// Invalidate metadata cache
m.metaStale = true
return true
}
// GetAchievement retrieves by ID (O(1))
func (m *MasterList) GetAchievement(id uint32) *Achievement {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.achievements[id]
}
// GetAchievementClone retrieves a cloned copy of an achievement by ID
func (m *MasterList) GetAchievementClone(id uint32) *Achievement {
m.mutex.RLock()
defer m.mutex.RUnlock()
achievement := m.achievements[id]
if achievement == nil {
return nil
}
return achievement.Clone()
}
// GetAllAchievements returns a copy of all achievements map
func (m *MasterList) GetAllAchievements() map[uint32]*Achievement {
m.mutex.RLock()
defer m.mutex.RUnlock()
// Return a copy to prevent external modification
result := make(map[uint32]*Achievement, len(m.achievements))
for id, achievement := range m.achievements {
result[id] = achievement
}
return result
}
// GetAchievementsByCategory returns all achievements in a category (O(1))
func (m *MasterList) GetAchievementsByCategory(category string) []*Achievement {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.byCategory[category]
}
// GetAchievementsByExpansion returns all achievements in an expansion (O(1))
func (m *MasterList) GetAchievementsByExpansion(expansion string) []*Achievement {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.byExpansion[expansion]
}
// GetAchievementsByCategoryAndExpansion returns achievements matching both category and expansion
func (m *MasterList) GetAchievementsByCategoryAndExpansion(category, expansion string) []*Achievement {
m.mutex.RLock()
defer m.mutex.RUnlock()
categoryAchievements := m.byCategory[category]
expansionAchievements := m.byExpansion[expansion]
// Use smaller set for iteration efficiency
if len(categoryAchievements) > len(expansionAchievements) {
categoryAchievements, expansionAchievements = expansionAchievements, categoryAchievements
}
// Set intersection using map lookup
expansionSet := make(map[*Achievement]struct{}, len(expansionAchievements))
for _, achievement := range expansionAchievements {
expansionSet[achievement] = struct{}{}
}
var result []*Achievement
for _, achievement := range categoryAchievements {
if _, exists := expansionSet[achievement]; exists {
result = append(result, achievement)
}
}
return result
}
// GetCategories returns all unique categories using cached results
func (m *MasterList) GetCategories() []string {
m.mutex.Lock() // Need write lock to potentially update cache
defer m.mutex.Unlock()
m.refreshMetaCache()
// Return a copy to prevent external modification
result := make([]string, len(m.categories))
copy(result, m.categories)
return result
}
// GetExpansions returns all unique expansions using cached results
func (m *MasterList) GetExpansions() []string {
m.mutex.Lock() // Need write lock to potentially update cache
defer m.mutex.Unlock()
m.refreshMetaCache()
// Return a copy to prevent external modification
result := make([]string, len(m.expansions))
copy(result, m.expansions)
return result
}
// RemoveAchievement removes an achievement and updates all indices
func (m *MasterList) RemoveAchievement(id uint32) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
achievement, exists := m.achievements[id]
if !exists {
return false
}
// Remove from core storage
delete(m.achievements, id)
// Remove from category index
if achievement.Category != "" {
categoryAchievements := m.byCategory[achievement.Category]
for i, a := range categoryAchievements {
if a.AchievementID == id {
m.byCategory[achievement.Category] = append(categoryAchievements[:i], categoryAchievements[i+1:]...)
break
}
}
}
// Remove from expansion index
if achievement.Expansion != "" {
expansionAchievements := m.byExpansion[achievement.Expansion]
for i, a := range expansionAchievements {
if a.AchievementID == id {
m.byExpansion[achievement.Expansion] = append(expansionAchievements[:i], expansionAchievements[i+1:]...)
break
}
}
}
// Invalidate metadata cache
m.metaStale = true
return true
}
// UpdateAchievement updates an existing achievement
func (m *MasterList) UpdateAchievement(achievement *Achievement) error {
if achievement == nil {
return fmt.Errorf("achievement cannot be nil")
}
m.mutex.Lock()
defer m.mutex.Unlock()
// Check if exists
old, exists := m.achievements[achievement.AchievementID]
if !exists {
return fmt.Errorf("achievement %d not found", achievement.AchievementID)
}
// Remove old achievement from indices (but not core storage yet)
if old.Category != "" {
categoryAchievements := m.byCategory[old.Category]
for i, a := range categoryAchievements {
if a.AchievementID == achievement.AchievementID {
m.byCategory[old.Category] = append(categoryAchievements[:i], categoryAchievements[i+1:]...)
break
}
}
}
if old.Expansion != "" {
expansionAchievements := m.byExpansion[old.Expansion]
for i, a := range expansionAchievements {
if a.AchievementID == achievement.AchievementID {
m.byExpansion[old.Expansion] = append(expansionAchievements[:i], expansionAchievements[i+1:]...)
break
}
}
}
// Update core storage
m.achievements[achievement.AchievementID] = achievement
// Add new achievement to indices
if achievement.Category != "" {
m.byCategory[achievement.Category] = append(m.byCategory[achievement.Category], achievement)
}
if achievement.Expansion != "" {
m.byExpansion[achievement.Expansion] = append(m.byExpansion[achievement.Expansion], achievement)
}
// Invalidate metadata cache
m.metaStale = true
return nil
}
// Size returns the total number of achievements
func (m *MasterList) Size() int {
m.mutex.RLock()
defer m.mutex.RUnlock()
return len(m.achievements)
}
// Clear removes all achievements from the master list
func (m *MasterList) Clear() {
m.mutex.Lock()
defer m.mutex.Unlock()
// Clear all maps
m.achievements = make(map[uint32]*Achievement)
m.byCategory = make(map[string][]*Achievement)
m.byExpansion = make(map[string][]*Achievement)
// Clear cached metadata
m.categories = m.categories[:0]
m.expansions = m.expansions[:0]
m.metaStale = true
}
// ForEach executes a function for each achievement
func (m *MasterList) ForEach(fn func(uint32, *Achievement)) {
m.mutex.RLock()
defer m.mutex.RUnlock()
for id, achievement := range m.achievements {
fn(id, achievement)
}
}