331 lines
8.8 KiB
Go
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)
|
|
}
|
|
}
|