2025-08-08 10:28:46 -05:00

401 lines
10 KiB
Go

package alt_advancement
import (
"fmt"
"sync"
)
// MasterList is a specialized alternate advancement master list optimized for:
// - Fast ID-based lookups (O(1))
// - Fast group-based lookups (O(1))
// - Fast class-based lookups (O(1))
// - Fast level-based lookups (O(1))
// - Efficient filtering and prerequisite validation
type MasterList struct {
// Core storage
altAdvancements map[int32]*AltAdvancement // NodeID -> AltAdvancement
mutex sync.RWMutex
// Group indices for O(1) lookups
byGroup map[int8][]*AltAdvancement // Group -> AAs
byClass map[int8][]*AltAdvancement // ClassReq -> AAs
byLevel map[int8][]*AltAdvancement // MinLevel -> AAs
// Cached metadata
groups []int8 // Unique groups (cached)
classes []int8 // Unique classes (cached)
metaStale bool // Whether metadata cache needs refresh
}
// NewMasterList creates a new specialized alternate advancement master list
func NewMasterList() *MasterList {
return &MasterList{
altAdvancements: make(map[int32]*AltAdvancement),
byGroup: make(map[int8][]*AltAdvancement),
byClass: make(map[int8][]*AltAdvancement),
byLevel: make(map[int8][]*AltAdvancement),
metaStale: true,
}
}
// refreshMetaCache updates the groups and classes cache
func (m *MasterList) refreshMetaCache() {
if !m.metaStale {
return
}
groupSet := make(map[int8]struct{})
classSet := make(map[int8]struct{})
// Collect unique groups and classes
for _, aa := range m.altAdvancements {
groupSet[aa.Group] = struct{}{}
if aa.ClassReq > 0 {
classSet[aa.ClassReq] = struct{}{}
}
}
// Clear existing caches and rebuild
m.groups = m.groups[:0]
for group := range groupSet {
m.groups = append(m.groups, group)
}
m.classes = m.classes[:0]
for class := range classSet {
m.classes = append(m.classes, class)
}
m.metaStale = false
}
// AddAltAdvancement adds an alternate advancement with full indexing
func (m *MasterList) AddAltAdvancement(aa *AltAdvancement) bool {
if aa == nil {
return false
}
m.mutex.Lock()
defer m.mutex.Unlock()
// Check if exists
if _, exists := m.altAdvancements[aa.NodeID]; exists {
return false
}
// Add to core storage
m.altAdvancements[aa.NodeID] = aa
// Update group index
m.byGroup[aa.Group] = append(m.byGroup[aa.Group], aa)
// Update class index
m.byClass[aa.ClassReq] = append(m.byClass[aa.ClassReq], aa)
// Update level index
m.byLevel[aa.MinLevel] = append(m.byLevel[aa.MinLevel], aa)
// Invalidate metadata cache
m.metaStale = true
return true
}
// GetAltAdvancement retrieves by node ID (O(1))
func (m *MasterList) GetAltAdvancement(nodeID int32) *AltAdvancement {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.altAdvancements[nodeID]
}
// GetAltAdvancementClone retrieves a cloned copy of an alternate advancement by node ID
func (m *MasterList) GetAltAdvancementClone(nodeID int32) *AltAdvancement {
m.mutex.RLock()
defer m.mutex.RUnlock()
aa := m.altAdvancements[nodeID]
if aa == nil {
return nil
}
return aa.Clone()
}
// GetAllAltAdvancements returns a copy of all alternate advancements map
func (m *MasterList) GetAllAltAdvancements() map[int32]*AltAdvancement {
m.mutex.RLock()
defer m.mutex.RUnlock()
// Return a copy to prevent external modification
result := make(map[int32]*AltAdvancement, len(m.altAdvancements))
for id, aa := range m.altAdvancements {
result[id] = aa
}
return result
}
// GetAltAdvancementsByGroup returns all alternate advancements in a group (O(1))
func (m *MasterList) GetAltAdvancementsByGroup(group int8) []*AltAdvancement {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.byGroup[group]
}
// GetAltAdvancementsByClass returns all alternate advancements for a class (O(1))
func (m *MasterList) GetAltAdvancementsByClass(classID int8) []*AltAdvancement {
m.mutex.RLock()
defer m.mutex.RUnlock()
// Return class-specific AAs plus universal AAs (ClassReq == 0)
var result []*AltAdvancement
// Add class-specific AAs
if classAAs := m.byClass[classID]; classAAs != nil {
result = append(result, classAAs...)
}
// Add universal AAs (ClassReq == 0)
if universalAAs := m.byClass[0]; universalAAs != nil {
result = append(result, universalAAs...)
}
return result
}
// GetAltAdvancementsByLevel returns all alternate advancements available at a specific level
func (m *MasterList) GetAltAdvancementsByLevel(level int8) []*AltAdvancement {
m.mutex.RLock()
defer m.mutex.RUnlock()
var result []*AltAdvancement
// Collect all AAs with MinLevel <= level
for minLevel, aas := range m.byLevel {
if minLevel <= level {
result = append(result, aas...)
}
}
return result
}
// GetAltAdvancementsByGroupAndClass returns AAs matching both group and class
func (m *MasterList) GetAltAdvancementsByGroupAndClass(group, classID int8) []*AltAdvancement {
m.mutex.RLock()
defer m.mutex.RUnlock()
groupAAs := m.byGroup[group]
if groupAAs == nil {
return nil
}
var result []*AltAdvancement
for _, aa := range groupAAs {
if aa.ClassReq == 0 || aa.ClassReq == classID {
result = append(result, aa)
}
}
return result
}
// GetGroups returns all unique groups using cached results
func (m *MasterList) GetGroups() []int8 {
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([]int8, len(m.groups))
copy(result, m.groups)
return result
}
// GetClasses returns all unique classes using cached results
func (m *MasterList) GetClasses() []int8 {
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([]int8, len(m.classes))
copy(result, m.classes)
return result
}
// RemoveAltAdvancement removes an alternate advancement and updates all indices
func (m *MasterList) RemoveAltAdvancement(nodeID int32) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
aa, exists := m.altAdvancements[nodeID]
if !exists {
return false
}
// Remove from core storage
delete(m.altAdvancements, nodeID)
// Remove from group index
groupAAs := m.byGroup[aa.Group]
for i, a := range groupAAs {
if a.NodeID == nodeID {
m.byGroup[aa.Group] = append(groupAAs[:i], groupAAs[i+1:]...)
break
}
}
// Remove from class index
classAAs := m.byClass[aa.ClassReq]
for i, a := range classAAs {
if a.NodeID == nodeID {
m.byClass[aa.ClassReq] = append(classAAs[:i], classAAs[i+1:]...)
break
}
}
// Remove from level index
levelAAs := m.byLevel[aa.MinLevel]
for i, a := range levelAAs {
if a.NodeID == nodeID {
m.byLevel[aa.MinLevel] = append(levelAAs[:i], levelAAs[i+1:]...)
break
}
}
// Invalidate metadata cache
m.metaStale = true
return true
}
// UpdateAltAdvancement updates an existing alternate advancement
func (m *MasterList) UpdateAltAdvancement(aa *AltAdvancement) error {
if aa == nil {
return fmt.Errorf("alternate advancement cannot be nil")
}
m.mutex.Lock()
defer m.mutex.Unlock()
// Check if exists
old, exists := m.altAdvancements[aa.NodeID]
if !exists {
return fmt.Errorf("alternate advancement %d not found", aa.NodeID)
}
// Remove old AA from indices (but not core storage yet)
groupAAs := m.byGroup[old.Group]
for i, a := range groupAAs {
if a.NodeID == aa.NodeID {
m.byGroup[old.Group] = append(groupAAs[:i], groupAAs[i+1:]...)
break
}
}
classAAs := m.byClass[old.ClassReq]
for i, a := range classAAs {
if a.NodeID == aa.NodeID {
m.byClass[old.ClassReq] = append(classAAs[:i], classAAs[i+1:]...)
break
}
}
levelAAs := m.byLevel[old.MinLevel]
for i, a := range levelAAs {
if a.NodeID == aa.NodeID {
m.byLevel[old.MinLevel] = append(levelAAs[:i], levelAAs[i+1:]...)
break
}
}
// Update core storage
m.altAdvancements[aa.NodeID] = aa
// Add new AA to indices
m.byGroup[aa.Group] = append(m.byGroup[aa.Group], aa)
m.byClass[aa.ClassReq] = append(m.byClass[aa.ClassReq], aa)
m.byLevel[aa.MinLevel] = append(m.byLevel[aa.MinLevel], aa)
// Invalidate metadata cache
m.metaStale = true
return nil
}
// Size returns the total number of alternate advancements
func (m *MasterList) Size() int {
m.mutex.RLock()
defer m.mutex.RUnlock()
return len(m.altAdvancements)
}
// Clear removes all alternate advancements from the master list
func (m *MasterList) Clear() {
m.mutex.Lock()
defer m.mutex.Unlock()
// Clear all maps
m.altAdvancements = make(map[int32]*AltAdvancement)
m.byGroup = make(map[int8][]*AltAdvancement)
m.byClass = make(map[int8][]*AltAdvancement)
m.byLevel = make(map[int8][]*AltAdvancement)
// Clear cached metadata
m.groups = m.groups[:0]
m.classes = m.classes[:0]
m.metaStale = true
}
// ForEach executes a function for each alternate advancement
func (m *MasterList) ForEach(fn func(int32, *AltAdvancement)) {
m.mutex.RLock()
defer m.mutex.RUnlock()
for id, aa := range m.altAdvancements {
fn(id, aa)
}
}
// ValidateAll validates all alternate advancements in the master list
func (m *MasterList) ValidateAll() []error {
m.mutex.RLock()
defer m.mutex.RUnlock()
var errors []error
for nodeID, aa := range m.altAdvancements {
if !aa.IsValid() {
errors = append(errors, fmt.Errorf("invalid AA data: node_id=%d", nodeID))
}
// Validate prerequisites
if aa.RankPrereqID > 0 {
prereq := m.altAdvancements[aa.RankPrereqID]
if prereq == nil {
errors = append(errors, fmt.Errorf("AA %d has invalid prerequisite node ID %d", nodeID, aa.RankPrereqID))
}
}
// Validate positioning
if aa.Col < MIN_AA_COL || aa.Col > MAX_AA_COL {
errors = append(errors, fmt.Errorf("AA %d has invalid column %d", nodeID, aa.Col))
}
if aa.Row < MIN_AA_ROW || aa.Row > MAX_AA_ROW {
errors = append(errors, fmt.Errorf("AA %d has invalid row %d", nodeID, aa.Row))
}
// Validate costs and ranks
if aa.RankCost < MIN_RANK_COST || aa.RankCost > MAX_RANK_COST {
errors = append(errors, fmt.Errorf("AA %d has invalid rank cost %d", nodeID, aa.RankCost))
}
if aa.MaxRank < MIN_MAX_RANK || aa.MaxRank > MAX_MAX_RANK {
errors = append(errors, fmt.Errorf("AA %d has invalid max rank %d", nodeID, aa.MaxRank))
}
}
return errors
}