737 lines
19 KiB
Go
737 lines
19 KiB
Go
package groups
|
|
|
|
import (
|
|
"fmt"
|
|
"maps"
|
|
"strings"
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// MasterList manages groups with optimized lookups for:
|
|
// - Fast ID-based lookups (O(1))
|
|
// - Fast member-based lookups (indexed)
|
|
// - Fast zone-based filtering (indexed)
|
|
// - Fast size-based filtering (indexed)
|
|
// - Raid group management and lookups
|
|
// - Leader-based searching
|
|
// - Activity tracking and cleanup
|
|
type MasterList struct {
|
|
// Core storage
|
|
groups map[int32]*Group // ID -> Group
|
|
mutex sync.RWMutex
|
|
|
|
// Indices for O(1) lookups
|
|
byMember map[string]*Group // Member name -> group containing that member
|
|
byLeader map[string]*Group // Leader name -> group
|
|
byZone map[int32][]*Group // Zone ID -> groups with members in that zone
|
|
bySize map[int32][]*Group // Size -> groups of that size
|
|
activeGroups map[int32]*Group // Active (non-disbanded) groups
|
|
raidGroups map[int32]*Group // Groups that are part of raids
|
|
soloGroups map[int32]*Group // Single-member groups
|
|
fullGroups map[int32]*Group // Full groups (size = MAX_GROUP_SIZE)
|
|
|
|
// Activity tracking
|
|
byLastActivity map[time.Time][]*Group // Last activity time -> groups
|
|
|
|
// Cached metadata and slices
|
|
totalMembers int32 // Total active members across all groups
|
|
zones []int32 // Unique zones with group members
|
|
sizes []int32 // Unique group sizes
|
|
zoneStats map[int32]int // Zone ID -> group count
|
|
sizeStats map[int32]int // Size -> group count
|
|
allGroupsSlice []*Group // Cached slice of all groups
|
|
activeGroupsSlice []*Group // Cached slice of active groups
|
|
metaStale bool // Whether metadata cache needs refresh
|
|
}
|
|
|
|
// NewMasterList creates a new group master list
|
|
func NewMasterList() *MasterList {
|
|
return &MasterList{
|
|
groups: make(map[int32]*Group),
|
|
byMember: make(map[string]*Group),
|
|
byLeader: make(map[string]*Group),
|
|
byZone: make(map[int32][]*Group),
|
|
bySize: make(map[int32][]*Group),
|
|
activeGroups: make(map[int32]*Group),
|
|
raidGroups: make(map[int32]*Group),
|
|
soloGroups: make(map[int32]*Group),
|
|
fullGroups: make(map[int32]*Group),
|
|
byLastActivity: make(map[time.Time][]*Group),
|
|
zoneStats: make(map[int32]int),
|
|
sizeStats: make(map[int32]int),
|
|
allGroupsSlice: make([]*Group, 0),
|
|
activeGroupsSlice: make([]*Group, 0),
|
|
metaStale: true,
|
|
}
|
|
}
|
|
|
|
// refreshMetaCache updates the cached metadata
|
|
func (ml *MasterList) refreshMetaCache() {
|
|
if !ml.metaStale {
|
|
return
|
|
}
|
|
|
|
// Clear and rebuild zone and size stats
|
|
ml.zoneStats = make(map[int32]int)
|
|
ml.sizeStats = make(map[int32]int)
|
|
zoneSet := make(map[int32]struct{})
|
|
sizeSet := make(map[int32]struct{})
|
|
ml.totalMembers = 0
|
|
|
|
// Collect unique values and stats
|
|
for _, group := range ml.activeGroups {
|
|
size := group.GetSize()
|
|
ml.sizeStats[size]++
|
|
sizeSet[size] = struct{}{}
|
|
ml.totalMembers += size
|
|
|
|
// Collect zones from group members
|
|
members := group.GetMembers()
|
|
zoneMap := make(map[int32]struct{})
|
|
for _, member := range members {
|
|
if member.ZoneID > 0 {
|
|
zoneMap[member.ZoneID] = struct{}{}
|
|
}
|
|
}
|
|
for zoneID := range zoneMap {
|
|
ml.zoneStats[zoneID]++
|
|
zoneSet[zoneID] = struct{}{}
|
|
}
|
|
}
|
|
|
|
// Clear and rebuild cached slices
|
|
ml.zones = ml.zones[:0]
|
|
for zoneID := range zoneSet {
|
|
ml.zones = append(ml.zones, zoneID)
|
|
}
|
|
|
|
ml.sizes = ml.sizes[:0]
|
|
for size := range sizeSet {
|
|
ml.sizes = append(ml.sizes, size)
|
|
}
|
|
|
|
// Rebuild all groups slice
|
|
ml.allGroupsSlice = ml.allGroupsSlice[:0]
|
|
if cap(ml.allGroupsSlice) < len(ml.groups) {
|
|
ml.allGroupsSlice = make([]*Group, 0, len(ml.groups))
|
|
}
|
|
for _, group := range ml.groups {
|
|
ml.allGroupsSlice = append(ml.allGroupsSlice, group)
|
|
}
|
|
|
|
// Rebuild active groups slice
|
|
ml.activeGroupsSlice = ml.activeGroupsSlice[:0]
|
|
if cap(ml.activeGroupsSlice) < len(ml.activeGroups) {
|
|
ml.activeGroupsSlice = make([]*Group, 0, len(ml.activeGroups))
|
|
}
|
|
for _, group := range ml.activeGroups {
|
|
ml.activeGroupsSlice = append(ml.activeGroupsSlice, group)
|
|
}
|
|
|
|
ml.metaStale = false
|
|
}
|
|
|
|
// updateGroupIndices updates all indices for a group
|
|
func (ml *MasterList) updateGroupIndices(group *Group, add bool) {
|
|
groupID := group.GetID()
|
|
size := group.GetSize()
|
|
leaderName := group.GetLeaderName()
|
|
isRaid := group.IsGroupRaid()
|
|
isDisbanded := group.IsDisbanded()
|
|
members := group.GetMembers()
|
|
|
|
if add {
|
|
// Add to size index
|
|
ml.bySize[size] = append(ml.bySize[size], group)
|
|
|
|
// Add to leader index
|
|
if leaderName != "" {
|
|
ml.byLeader[strings.ToLower(leaderName)] = group
|
|
}
|
|
|
|
// Add to member index
|
|
for _, member := range members {
|
|
if member.Name != "" {
|
|
ml.byMember[strings.ToLower(member.Name)] = group
|
|
}
|
|
}
|
|
|
|
// Add to zone index
|
|
zoneMap := make(map[int32]struct{})
|
|
for _, member := range members {
|
|
if member.ZoneID > 0 {
|
|
zoneMap[member.ZoneID] = struct{}{}
|
|
}
|
|
}
|
|
for zoneID := range zoneMap {
|
|
ml.byZone[zoneID] = append(ml.byZone[zoneID], group)
|
|
}
|
|
|
|
// Add to specialized indices
|
|
if !isDisbanded {
|
|
ml.activeGroups[groupID] = group
|
|
}
|
|
if isRaid {
|
|
ml.raidGroups[groupID] = group
|
|
}
|
|
if size == 1 {
|
|
ml.soloGroups[groupID] = group
|
|
}
|
|
if size == MAX_GROUP_SIZE {
|
|
ml.fullGroups[groupID] = group
|
|
}
|
|
|
|
// Add to activity index
|
|
activity := group.GetLastActivity()
|
|
ml.byLastActivity[activity] = append(ml.byLastActivity[activity], group)
|
|
} else {
|
|
// Remove from size index
|
|
sizeGroups := ml.bySize[size]
|
|
for i, g := range sizeGroups {
|
|
if g.GetID() == groupID {
|
|
ml.bySize[size] = append(sizeGroups[:i], sizeGroups[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
|
|
// Remove from leader index
|
|
if leaderName != "" {
|
|
delete(ml.byLeader, strings.ToLower(leaderName))
|
|
}
|
|
|
|
// Remove from member index
|
|
for _, member := range members {
|
|
if member.Name != "" {
|
|
delete(ml.byMember, strings.ToLower(member.Name))
|
|
}
|
|
}
|
|
|
|
// Remove from zone index
|
|
zoneMap := make(map[int32]struct{})
|
|
for _, member := range members {
|
|
if member.ZoneID > 0 {
|
|
zoneMap[member.ZoneID] = struct{}{}
|
|
}
|
|
}
|
|
for zoneID := range zoneMap {
|
|
zoneGroups := ml.byZone[zoneID]
|
|
for i, g := range zoneGroups {
|
|
if g.GetID() == groupID {
|
|
ml.byZone[zoneID] = append(zoneGroups[:i], zoneGroups[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
// Remove from specialized indices
|
|
delete(ml.activeGroups, groupID)
|
|
delete(ml.raidGroups, groupID)
|
|
delete(ml.soloGroups, groupID)
|
|
delete(ml.fullGroups, groupID)
|
|
|
|
// Remove from activity index
|
|
activity := group.GetLastActivity()
|
|
activityGroups := ml.byLastActivity[activity]
|
|
for i, g := range activityGroups {
|
|
if g.GetID() == groupID {
|
|
ml.byLastActivity[activity] = append(activityGroups[:i], activityGroups[i+1:]...)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// AddGroup adds a group with full indexing
|
|
func (ml *MasterList) AddGroup(group *Group) bool {
|
|
if group == nil {
|
|
return false
|
|
}
|
|
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
// Check if exists
|
|
if _, exists := ml.groups[group.GetID()]; exists {
|
|
return false
|
|
}
|
|
|
|
// Add to core storage
|
|
ml.groups[group.GetID()] = group
|
|
|
|
// Update all indices
|
|
ml.updateGroupIndices(group, true)
|
|
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
|
|
return true
|
|
}
|
|
|
|
// GetGroup retrieves by ID (O(1))
|
|
func (ml *MasterList) GetGroup(groupID int32) *Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.groups[groupID]
|
|
}
|
|
|
|
// GetGroupSafe retrieves a group by ID with existence check
|
|
func (ml *MasterList) GetGroupSafe(groupID int32) (*Group, bool) {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
group, exists := ml.groups[groupID]
|
|
return group, exists
|
|
}
|
|
|
|
// HasGroup checks if a group exists by ID
|
|
func (ml *MasterList) HasGroup(groupID int32) bool {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
_, exists := ml.groups[groupID]
|
|
return exists
|
|
}
|
|
|
|
// RemoveGroup removes a group and updates all indices
|
|
func (ml *MasterList) RemoveGroup(groupID int32) bool {
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
group, exists := ml.groups[groupID]
|
|
if !exists {
|
|
return false
|
|
}
|
|
|
|
// Remove from core storage
|
|
delete(ml.groups, groupID)
|
|
|
|
// Update all indices
|
|
ml.updateGroupIndices(group, false)
|
|
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
|
|
return true
|
|
}
|
|
|
|
// GetAllGroups returns all groups as a slice
|
|
func (ml *MasterList) GetAllGroups() []*Group {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make([]*Group, len(ml.allGroupsSlice))
|
|
copy(result, ml.allGroupsSlice)
|
|
return result
|
|
}
|
|
|
|
// GetAllGroupsMap returns a copy of all groups map
|
|
func (ml *MasterList) GetAllGroupsMap() map[int32]*Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make(map[int32]*Group, len(ml.groups))
|
|
maps.Copy(result, ml.groups)
|
|
return result
|
|
}
|
|
|
|
// GetGroupCount returns the total number of groups
|
|
func (ml *MasterList) GetGroupCount() int32 {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return int32(len(ml.groups))
|
|
}
|
|
|
|
// Size returns the total number of groups
|
|
func (ml *MasterList) Size() int {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return len(ml.groups)
|
|
}
|
|
|
|
// IsEmpty returns true if the master list is empty
|
|
func (ml *MasterList) IsEmpty() bool {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return len(ml.groups) == 0
|
|
}
|
|
|
|
// Clear removes all groups from the list
|
|
func (ml *MasterList) Clear() {
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
// Clear all maps
|
|
ml.groups = make(map[int32]*Group)
|
|
ml.byMember = make(map[string]*Group)
|
|
ml.byLeader = make(map[string]*Group)
|
|
ml.byZone = make(map[int32][]*Group)
|
|
ml.bySize = make(map[int32][]*Group)
|
|
ml.activeGroups = make(map[int32]*Group)
|
|
ml.raidGroups = make(map[int32]*Group)
|
|
ml.soloGroups = make(map[int32]*Group)
|
|
ml.fullGroups = make(map[int32]*Group)
|
|
ml.byLastActivity = make(map[time.Time][]*Group)
|
|
|
|
// Clear cached metadata
|
|
ml.zones = ml.zones[:0]
|
|
ml.sizes = ml.sizes[:0]
|
|
ml.allGroupsSlice = ml.allGroupsSlice[:0]
|
|
ml.activeGroupsSlice = ml.activeGroupsSlice[:0]
|
|
ml.zoneStats = make(map[int32]int)
|
|
ml.sizeStats = make(map[int32]int)
|
|
ml.totalMembers = 0
|
|
ml.metaStale = true
|
|
}
|
|
|
|
// GetGroupsByFilter returns groups matching the filter function
|
|
func (ml *MasterList) GetGroupsByFilter(filter func(*Group) bool) []*Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
var result []*Group
|
|
for _, group := range ml.groups {
|
|
if filter(group) {
|
|
result = append(result, group)
|
|
}
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetGroupByMember returns the group containing the specified member (O(1))
|
|
func (ml *MasterList) GetGroupByMember(memberName string) *Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byMember[strings.ToLower(memberName)]
|
|
}
|
|
|
|
// GetGroupByLeader returns the group led by the specified leader (O(1))
|
|
func (ml *MasterList) GetGroupByLeader(leaderName string) *Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byLeader[strings.ToLower(leaderName)]
|
|
}
|
|
|
|
// GetActiveGroups returns all non-disbanded groups (O(1))
|
|
func (ml *MasterList) GetActiveGroups() []*Group {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make([]*Group, len(ml.activeGroupsSlice))
|
|
copy(result, ml.activeGroupsSlice)
|
|
return result
|
|
}
|
|
|
|
// GetGroupsByZone returns groups with members in the specified zone (O(1))
|
|
func (ml *MasterList) GetGroupsByZone(zoneID int32) []*Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.byZone[zoneID]
|
|
}
|
|
|
|
// GetGroupsBySize returns groups of the specified size (O(1))
|
|
func (ml *MasterList) GetGroupsBySize(size int32) []*Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
return ml.bySize[size]
|
|
}
|
|
|
|
// GetRaidGroups returns all groups that are part of raids (O(1))
|
|
func (ml *MasterList) GetRaidGroups() []*Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
result := make([]*Group, 0, len(ml.raidGroups))
|
|
for _, group := range ml.raidGroups {
|
|
result = append(result, group)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetSoloGroups returns all groups with only one member (O(1))
|
|
func (ml *MasterList) GetSoloGroups() []*Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
result := make([]*Group, 0, len(ml.soloGroups))
|
|
for _, group := range ml.soloGroups {
|
|
result = append(result, group)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetFullGroups returns all groups at maximum capacity (O(1))
|
|
func (ml *MasterList) GetFullGroups() []*Group {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
result := make([]*Group, 0, len(ml.fullGroups))
|
|
for _, group := range ml.fullGroups {
|
|
result = append(result, group)
|
|
}
|
|
return result
|
|
}
|
|
|
|
// GetGroupsByLeader returns groups led by entities with the specified name
|
|
func (ml *MasterList) GetGroupsByLeader(leaderName string) []*Group {
|
|
group := ml.GetGroupByLeader(leaderName)
|
|
if group != nil {
|
|
return []*Group{group}
|
|
}
|
|
return []*Group{}
|
|
}
|
|
|
|
// GetGroupsByMember returns groups containing a member with the specified name
|
|
func (ml *MasterList) GetGroupsByMember(memberName string) []*Group {
|
|
group := ml.GetGroupByMember(memberName)
|
|
if group != nil {
|
|
return []*Group{group}
|
|
}
|
|
return []*Group{}
|
|
}
|
|
|
|
// GetZones returns all unique zones with group members using cached results
|
|
func (ml *MasterList) GetZones() []int32 {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make([]int32, len(ml.zones))
|
|
copy(result, ml.zones)
|
|
return result
|
|
}
|
|
|
|
// GetSizes returns all unique group sizes using cached results
|
|
func (ml *MasterList) GetSizes() []int32 {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
// Return a copy to prevent external modification
|
|
result := make([]int32, len(ml.sizes))
|
|
copy(result, ml.sizes)
|
|
return result
|
|
}
|
|
|
|
// GetTotalMembers returns the total number of active members across all groups
|
|
func (ml *MasterList) GetTotalMembers() int32 {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
return ml.totalMembers
|
|
}
|
|
|
|
// GetGroupStatistics returns statistics about the groups in the master list using cached data
|
|
func (ml *MasterList) GetGroupStatistics() *GroupMasterListStats {
|
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
|
defer ml.mutex.Unlock()
|
|
|
|
ml.refreshMetaCache()
|
|
|
|
var totalRaidMembers int32
|
|
for _, group := range ml.raidGroups {
|
|
totalRaidMembers += group.GetSize()
|
|
}
|
|
|
|
var averageGroupSize float64
|
|
if len(ml.activeGroups) > 0 {
|
|
averageGroupSize = float64(ml.totalMembers) / float64(len(ml.activeGroups))
|
|
}
|
|
|
|
return &GroupMasterListStats{
|
|
TotalGroups: int32(len(ml.groups)),
|
|
ActiveGroups: int32(len(ml.activeGroups)),
|
|
RaidGroups: int32(len(ml.raidGroups)),
|
|
TotalMembers: ml.totalMembers,
|
|
TotalRaidMembers: totalRaidMembers,
|
|
AverageGroupSize: averageGroupSize,
|
|
SoloGroups: int32(len(ml.soloGroups)),
|
|
FullGroups: int32(len(ml.fullGroups)),
|
|
}
|
|
}
|
|
|
|
// GroupMasterListStats holds statistics about the groups master list
|
|
type GroupMasterListStats struct {
|
|
TotalGroups int32 `json:"total_groups"`
|
|
ActiveGroups int32 `json:"active_groups"`
|
|
RaidGroups int32 `json:"raid_groups"`
|
|
TotalMembers int32 `json:"total_members"`
|
|
TotalRaidMembers int32 `json:"total_raid_members"`
|
|
AverageGroupSize float64 `json:"average_group_size"`
|
|
SoloGroups int32 `json:"solo_groups"`
|
|
FullGroups int32 `json:"full_groups"`
|
|
}
|
|
|
|
// RefreshGroupIndices refreshes indices for a group (used when group state changes)
|
|
func (ml *MasterList) RefreshGroupIndices(group *Group) {
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
// Remove from old indices
|
|
ml.updateGroupIndices(group, false)
|
|
// Add to new indices
|
|
ml.updateGroupIndices(group, true)
|
|
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
}
|
|
|
|
// UpdateGroup updates an existing group and refreshes indices
|
|
func (ml *MasterList) UpdateGroup(group *Group) error {
|
|
if group == nil {
|
|
return fmt.Errorf("group cannot be nil")
|
|
}
|
|
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
// Check if exists
|
|
old, exists := ml.groups[group.GetID()]
|
|
if !exists {
|
|
return fmt.Errorf("group %d not found", group.GetID())
|
|
}
|
|
|
|
// Remove old group from indices (but not core storage yet)
|
|
ml.updateGroupIndices(old, false)
|
|
|
|
// Update core storage
|
|
ml.groups[group.GetID()] = group
|
|
|
|
// Add new group to indices
|
|
ml.updateGroupIndices(group, true)
|
|
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
|
|
return nil
|
|
}
|
|
|
|
// ForEach executes a function for each group
|
|
func (ml *MasterList) ForEach(fn func(int32, *Group)) {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
for id, group := range ml.groups {
|
|
fn(id, group)
|
|
}
|
|
}
|
|
|
|
// Cleanup removes disbanded groups from the master list
|
|
func (ml *MasterList) Cleanup() int32 {
|
|
ml.mutex.Lock()
|
|
defer ml.mutex.Unlock()
|
|
|
|
removed := int32(0)
|
|
for id, group := range ml.groups {
|
|
if group.IsDisbanded() {
|
|
// Remove from core storage
|
|
delete(ml.groups, id)
|
|
// Remove from indices (group is already disbanded, so activeGroups won't include it)
|
|
ml.updateGroupIndices(group, false)
|
|
removed++
|
|
}
|
|
}
|
|
|
|
if removed > 0 {
|
|
// Invalidate metadata cache
|
|
ml.metaStale = true
|
|
}
|
|
|
|
return removed
|
|
}
|
|
|
|
// ValidateAll validates all groups in the master list
|
|
func (ml *MasterList) ValidateAll() []error {
|
|
ml.mutex.RLock()
|
|
defer ml.mutex.RUnlock()
|
|
|
|
var errors []error
|
|
|
|
for id, group := range ml.groups {
|
|
if group == nil {
|
|
errors = append(errors, fmt.Errorf("group ID %d is nil", id))
|
|
continue
|
|
}
|
|
|
|
// Check for basic validity
|
|
if group.GetID() != id {
|
|
errors = append(errors, fmt.Errorf("group ID mismatch: map key %d != group ID %d", id, group.GetID()))
|
|
}
|
|
|
|
if group.GetID() <= 0 {
|
|
errors = append(errors, fmt.Errorf("group %d has invalid ID", group.GetID()))
|
|
}
|
|
|
|
if group.GetSize() == 0 && !group.IsDisbanded() {
|
|
errors = append(errors, fmt.Errorf("group %d is empty but not disbanded", group.GetID()))
|
|
}
|
|
|
|
if group.GetSize() > MAX_GROUP_SIZE {
|
|
errors = append(errors, fmt.Errorf("group %d exceeds maximum size (%d > %d)",
|
|
group.GetID(), group.GetSize(), MAX_GROUP_SIZE))
|
|
}
|
|
|
|
// Check for leader
|
|
members := group.GetMembers()
|
|
hasLeader := false
|
|
leaderCount := 0
|
|
|
|
for _, member := range members {
|
|
if member.Leader {
|
|
hasLeader = true
|
|
leaderCount++
|
|
}
|
|
}
|
|
|
|
if !hasLeader && !group.IsDisbanded() {
|
|
errors = append(errors, fmt.Errorf("group %d has no leader", group.GetID()))
|
|
}
|
|
|
|
if leaderCount > 1 {
|
|
errors = append(errors, fmt.Errorf("group %d has multiple leaders (%d)", group.GetID(), leaderCount))
|
|
}
|
|
|
|
// Validate index consistency
|
|
if !group.IsDisbanded() {
|
|
if _, exists := ml.activeGroups[id]; !exists {
|
|
errors = append(errors, fmt.Errorf("active group %d not found in activeGroups index", id))
|
|
}
|
|
}
|
|
|
|
if group.IsGroupRaid() {
|
|
if _, exists := ml.raidGroups[id]; !exists {
|
|
errors = append(errors, fmt.Errorf("raid group %d not found in raidGroups index", id))
|
|
}
|
|
}
|
|
|
|
if group.GetSize() == 1 {
|
|
if _, exists := ml.soloGroups[id]; !exists {
|
|
errors = append(errors, fmt.Errorf("solo group %d not found in soloGroups index", id))
|
|
}
|
|
}
|
|
|
|
if group.GetSize() == MAX_GROUP_SIZE {
|
|
if _, exists := ml.fullGroups[id]; !exists {
|
|
errors = append(errors, fmt.Errorf("full group %d not found in fullGroups index", id))
|
|
}
|
|
}
|
|
}
|
|
|
|
return errors
|
|
}
|
|
|
|
// IsValid returns true if all groups are valid
|
|
func (ml *MasterList) IsValid() bool {
|
|
errors := ml.ValidateAll()
|
|
return len(errors) == 0
|
|
}
|