fix groups
This commit is contained in:
parent
27e720e703
commit
b08de58336
@ -36,9 +36,9 @@ func createTestEntity(id int32, name string, isPlayer bool) *mockEntity {
|
|||||||
// createTestGroup creates a group with test data for benchmarking
|
// createTestGroup creates a group with test data for benchmarking
|
||||||
func createTestGroup(b *testing.B, groupID int32, memberCount int) *Group {
|
func createTestGroup(b *testing.B, groupID int32, memberCount int) *Group {
|
||||||
b.Helper()
|
b.Helper()
|
||||||
|
|
||||||
group := NewGroup(groupID, nil, nil)
|
group := NewGroup(groupID, nil, nil)
|
||||||
|
|
||||||
// Add test members
|
// Add test members
|
||||||
for i := 0; i < memberCount; i++ {
|
for i := 0; i < memberCount; i++ {
|
||||||
entity := createTestEntity(
|
entity := createTestEntity(
|
||||||
@ -46,14 +46,14 @@ func createTestGroup(b *testing.B, groupID int32, memberCount int) *Group {
|
|||||||
fmt.Sprintf("Player%d", i+1),
|
fmt.Sprintf("Player%d", i+1),
|
||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
|
|
||||||
isLeader := (i == 0)
|
isLeader := (i == 0)
|
||||||
err := group.AddMember(entity, isLeader)
|
err := group.AddMember(entity, isLeader)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
b.Fatalf("Failed to add member to group: %v", err)
|
b.Fatalf("Failed to add member to group: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,7 +65,7 @@ func BenchmarkGroupCreation(b *testing.B) {
|
|||||||
group.Disband() // Clean up background goroutine
|
group.Disband() // Clean up background goroutine
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("NewGroupWithOptions", func(b *testing.B) {
|
b.Run("NewGroupWithOptions", func(b *testing.B) {
|
||||||
options := DefaultGroupOptions()
|
options := DefaultGroupOptions()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@ -73,7 +73,7 @@ func BenchmarkGroupCreation(b *testing.B) {
|
|||||||
group.Disband() // Clean up background goroutine
|
group.Disband() // Clean up background goroutine
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("NewGroupParallel", func(b *testing.B) {
|
b.Run("NewGroupParallel", func(b *testing.B) {
|
||||||
var idCounter int64
|
var idCounter int64
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
@ -90,7 +90,7 @@ func BenchmarkGroupCreation(b *testing.B) {
|
|||||||
func BenchmarkGroupMemberOperations(b *testing.B) {
|
func BenchmarkGroupMemberOperations(b *testing.B) {
|
||||||
group := createTestGroup(b, 1001, 3)
|
group := createTestGroup(b, 1001, 3)
|
||||||
defer group.Disband() // Clean up background goroutine
|
defer group.Disband() // Clean up background goroutine
|
||||||
|
|
||||||
b.Run("AddMember", func(b *testing.B) {
|
b.Run("AddMember", func(b *testing.B) {
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@ -101,7 +101,7 @@ func BenchmarkGroupMemberOperations(b *testing.B) {
|
|||||||
testGroup.Disband() // Clean up background goroutine
|
testGroup.Disband() // Clean up background goroutine
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetSize", func(b *testing.B) {
|
b.Run("GetSize", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -109,13 +109,13 @@ func BenchmarkGroupMemberOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetMembers", func(b *testing.B) {
|
b.Run("GetMembers", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = group.GetMembers()
|
_ = group.GetMembers()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetLeaderName", func(b *testing.B) {
|
b.Run("GetLeaderName", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -123,13 +123,13 @@ func BenchmarkGroupMemberOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("UpdateGroupMemberInfo", func(b *testing.B) {
|
b.Run("UpdateGroupMemberInfo", func(b *testing.B) {
|
||||||
members := group.GetMembers()
|
members := group.GetMembers()
|
||||||
if len(members) == 0 {
|
if len(members) == 0 {
|
||||||
b.Skip("No members to update")
|
b.Skip("No members to update")
|
||||||
}
|
}
|
||||||
|
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
member := members[i%len(members)]
|
member := members[i%len(members)]
|
||||||
if member.Member != nil {
|
if member.Member != nil {
|
||||||
@ -143,7 +143,7 @@ func BenchmarkGroupMemberOperations(b *testing.B) {
|
|||||||
func BenchmarkGroupOptions(b *testing.B) {
|
func BenchmarkGroupOptions(b *testing.B) {
|
||||||
group := createTestGroup(b, 1001, 3)
|
group := createTestGroup(b, 1001, 3)
|
||||||
defer group.Disband() // Clean up background goroutine
|
defer group.Disband() // Clean up background goroutine
|
||||||
|
|
||||||
b.Run("GetGroupOptions", func(b *testing.B) {
|
b.Run("GetGroupOptions", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -151,7 +151,7 @@ func BenchmarkGroupOptions(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("SetGroupOptions", func(b *testing.B) {
|
b.Run("SetGroupOptions", func(b *testing.B) {
|
||||||
options := DefaultGroupOptions()
|
options := DefaultGroupOptions()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@ -159,7 +159,7 @@ func BenchmarkGroupOptions(b *testing.B) {
|
|||||||
group.SetGroupOptions(&options)
|
group.SetGroupOptions(&options)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetLastLooterIndex", func(b *testing.B) {
|
b.Run("GetLastLooterIndex", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -167,7 +167,7 @@ func BenchmarkGroupOptions(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("SetNextLooterIndex", func(b *testing.B) {
|
b.Run("SetNextLooterIndex", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
group.SetNextLooterIndex(int8(i % 6))
|
group.SetNextLooterIndex(int8(i % 6))
|
||||||
@ -179,11 +179,11 @@ func BenchmarkGroupOptions(b *testing.B) {
|
|||||||
func BenchmarkGroupRaidOperations(b *testing.B) {
|
func BenchmarkGroupRaidOperations(b *testing.B) {
|
||||||
group := createTestGroup(b, 1001, 6)
|
group := createTestGroup(b, 1001, 6)
|
||||||
defer group.Disband() // Clean up background goroutine
|
defer group.Disband() // Clean up background goroutine
|
||||||
|
|
||||||
// Setup some raid groups
|
// Setup some raid groups
|
||||||
raidGroups := []int32{1001, 1002, 1003, 1004}
|
raidGroups := []int32{1001, 1002, 1003, 1004}
|
||||||
group.ReplaceRaidGroups(raidGroups)
|
group.ReplaceRaidGroups(raidGroups)
|
||||||
|
|
||||||
b.Run("GetRaidGroups", func(b *testing.B) {
|
b.Run("GetRaidGroups", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -191,7 +191,7 @@ func BenchmarkGroupRaidOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("IsGroupRaid", func(b *testing.B) {
|
b.Run("IsGroupRaid", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -199,14 +199,14 @@ func BenchmarkGroupRaidOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("IsInRaidGroup", func(b *testing.B) {
|
b.Run("IsInRaidGroup", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
targetID := int32((i % 4) + 1001)
|
targetID := int32((i % 4) + 1001)
|
||||||
_ = group.IsInRaidGroup(targetID, false)
|
_ = group.IsInRaidGroup(targetID, false)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("AddGroupToRaid", func(b *testing.B) {
|
b.Run("AddGroupToRaid", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
// Cycle through a limited set of group IDs to avoid infinite growth
|
// Cycle through a limited set of group IDs to avoid infinite growth
|
||||||
@ -214,14 +214,14 @@ func BenchmarkGroupRaidOperations(b *testing.B) {
|
|||||||
group.AddGroupToRaid(groupID)
|
group.AddGroupToRaid(groupID)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("ReplaceRaidGroups", func(b *testing.B) {
|
b.Run("ReplaceRaidGroups", func(b *testing.B) {
|
||||||
newGroups := []int32{2001, 2002, 2003}
|
newGroups := []int32{2001, 2002, 2003}
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
group.ReplaceRaidGroups(newGroups)
|
group.ReplaceRaidGroups(newGroups)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("ClearGroupRaid", func(b *testing.B) {
|
b.Run("ClearGroupRaid", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
group.ClearGroupRaid()
|
group.ClearGroupRaid()
|
||||||
@ -235,25 +235,25 @@ func BenchmarkGroupRaidOperations(b *testing.B) {
|
|||||||
func BenchmarkGroupMessaging(b *testing.B) {
|
func BenchmarkGroupMessaging(b *testing.B) {
|
||||||
group := createTestGroup(b, 1001, 6)
|
group := createTestGroup(b, 1001, 6)
|
||||||
defer group.Disband() // Clean up background goroutine
|
defer group.Disband() // Clean up background goroutine
|
||||||
|
|
||||||
b.Run("SimpleGroupMessage", func(b *testing.B) {
|
b.Run("SimpleGroupMessage", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
group.SimpleGroupMessage(fmt.Sprintf("Benchmark message %d", i))
|
group.SimpleGroupMessage(fmt.Sprintf("Benchmark message %d", i))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("SendGroupMessage", func(b *testing.B) {
|
b.Run("SendGroupMessage", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
group.SendGroupMessage(GROUP_MESSAGE_TYPE_SYSTEM, fmt.Sprintf("System message %d", i))
|
group.SendGroupMessage(GROUP_MESSAGE_TYPE_SYSTEM, fmt.Sprintf("System message %d", i))
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GroupChatMessageFromName", func(b *testing.B) {
|
b.Run("GroupChatMessageFromName", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
group.GroupChatMessageFromName(
|
group.GroupChatMessageFromName(
|
||||||
fmt.Sprintf("Player%d", i%6+1),
|
fmt.Sprintf("Player%d", i%6+1),
|
||||||
0,
|
0,
|
||||||
fmt.Sprintf("Chat message %d", i),
|
fmt.Sprintf("Chat message %d", i),
|
||||||
CHANNEL_GROUP_CHAT,
|
CHANNEL_GROUP_CHAT,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -264,7 +264,7 @@ func BenchmarkGroupMessaging(b *testing.B) {
|
|||||||
func BenchmarkGroupState(b *testing.B) {
|
func BenchmarkGroupState(b *testing.B) {
|
||||||
group := createTestGroup(b, 1001, 6)
|
group := createTestGroup(b, 1001, 6)
|
||||||
defer group.Disband() // Clean up background goroutine
|
defer group.Disband() // Clean up background goroutine
|
||||||
|
|
||||||
b.Run("GetID", func(b *testing.B) {
|
b.Run("GetID", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -272,7 +272,7 @@ func BenchmarkGroupState(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("IsDisbanded", func(b *testing.B) {
|
b.Run("IsDisbanded", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -280,7 +280,7 @@ func BenchmarkGroupState(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetCreatedTime", func(b *testing.B) {
|
b.Run("GetCreatedTime", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -288,7 +288,7 @@ func BenchmarkGroupState(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetLastActivity", func(b *testing.B) {
|
b.Run("GetLastActivity", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -301,11 +301,11 @@ func BenchmarkGroupState(b *testing.B) {
|
|||||||
// BenchmarkMasterListOperations measures master list performance
|
// BenchmarkMasterListOperations measures master list performance
|
||||||
func BenchmarkMasterListOperations(b *testing.B) {
|
func BenchmarkMasterListOperations(b *testing.B) {
|
||||||
ml := NewMasterList()
|
ml := NewMasterList()
|
||||||
|
|
||||||
// Pre-populate with groups
|
// Pre-populate with groups
|
||||||
const numGroups = 1000
|
const numGroups = 1000
|
||||||
groups := make([]*Group, numGroups)
|
groups := make([]*Group, numGroups)
|
||||||
|
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
for i := 0; i < numGroups; i++ {
|
for i := 0; i < numGroups; i++ {
|
||||||
groups[i] = createTestGroup(b, int32(i+1), rand.Intn(6)+1)
|
groups[i] = createTestGroup(b, int32(i+1), rand.Intn(6)+1)
|
||||||
@ -320,7 +320,7 @@ func BenchmarkMasterListOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}()
|
}()
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
|
|
||||||
b.Run("GetGroup", func(b *testing.B) {
|
b.Run("GetGroup", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -329,7 +329,7 @@ func BenchmarkMasterListOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("AddGroup", func(b *testing.B) {
|
b.Run("AddGroup", func(b *testing.B) {
|
||||||
startID := int32(numGroups + 1)
|
startID := int32(numGroups + 1)
|
||||||
var addedGroups []*Group
|
var addedGroups []*Group
|
||||||
@ -344,39 +344,39 @@ func BenchmarkMasterListOperations(b *testing.B) {
|
|||||||
group.Disband()
|
group.Disband()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetAllGroups", func(b *testing.B) {
|
b.Run("GetAllGroups", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = ml.GetAllGroups()
|
_ = ml.GetAllGroups()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetActiveGroups", func(b *testing.B) {
|
b.Run("GetActiveGroups", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = ml.GetActiveGroups()
|
_ = ml.GetActiveGroups()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetGroupsByZone", func(b *testing.B) {
|
b.Run("GetGroupsByZone", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
zoneID := int32(rand.Intn(100) + 1)
|
zoneID := int32(rand.Intn(100) + 1)
|
||||||
_ = ml.GetGroupsByZone(zoneID)
|
_ = ml.GetGroupsByZone(zoneID)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetGroupsBySize", func(b *testing.B) {
|
b.Run("GetGroupsBySize", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
size := int32(rand.Intn(6) + 1)
|
size := int32(rand.Intn(6) + 1)
|
||||||
_ = ml.GetGroupsBySize(size)
|
_ = ml.GetGroupsBySize(size)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetRaidGroups", func(b *testing.B) {
|
b.Run("GetRaidGroups", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = ml.GetRaidGroups()
|
_ = ml.GetRaidGroups()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetGroupStatistics", func(b *testing.B) {
|
b.Run("GetGroupStatistics", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = ml.GetGroupStatistics()
|
_ = ml.GetGroupStatistics()
|
||||||
@ -393,9 +393,9 @@ func BenchmarkManagerOperations(b *testing.B) {
|
|||||||
BuffUpdateInterval: 5 * time.Second,
|
BuffUpdateInterval: 5 * time.Second,
|
||||||
EnableStatistics: true,
|
EnableStatistics: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := NewManager(config, nil)
|
manager := NewManager(config, nil)
|
||||||
|
|
||||||
// Pre-populate with groups
|
// Pre-populate with groups
|
||||||
b.StopTimer()
|
b.StopTimer()
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
@ -403,7 +403,7 @@ func BenchmarkManagerOperations(b *testing.B) {
|
|||||||
manager.NewGroup(leader, nil, 0)
|
manager.NewGroup(leader, nil, 0)
|
||||||
}
|
}
|
||||||
b.StartTimer()
|
b.StartTimer()
|
||||||
|
|
||||||
b.Run("NewGroup", func(b *testing.B) {
|
b.Run("NewGroup", func(b *testing.B) {
|
||||||
startID := int32(1000)
|
startID := int32(1000)
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@ -414,7 +414,7 @@ func BenchmarkManagerOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetGroup", func(b *testing.B) {
|
b.Run("GetGroup", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -423,7 +423,7 @@ func BenchmarkManagerOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("IsGroupIDValid", func(b *testing.B) {
|
b.Run("IsGroupIDValid", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -432,7 +432,7 @@ func BenchmarkManagerOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetGroupCount", func(b *testing.B) {
|
b.Run("GetGroupCount", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -440,13 +440,13 @@ func BenchmarkManagerOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetAllGroups", func(b *testing.B) {
|
b.Run("GetAllGroups", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
_ = manager.GetAllGroups()
|
_ = manager.GetAllGroups()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GetStats", func(b *testing.B) {
|
b.Run("GetStats", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -462,9 +462,9 @@ func BenchmarkInviteSystem(b *testing.B) {
|
|||||||
InviteTimeout: 30 * time.Second,
|
InviteTimeout: 30 * time.Second,
|
||||||
EnableStatistics: true,
|
EnableStatistics: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := NewManager(config, nil)
|
manager := NewManager(config, nil)
|
||||||
|
|
||||||
b.Run("AddInvite", func(b *testing.B) {
|
b.Run("AddInvite", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
leader := createTestEntity(int32(i+1), fmt.Sprintf("Leader%d", i+1), true)
|
leader := createTestEntity(int32(i+1), fmt.Sprintf("Leader%d", i+1), true)
|
||||||
@ -472,7 +472,7 @@ func BenchmarkInviteSystem(b *testing.B) {
|
|||||||
manager.AddInvite(leader, member)
|
manager.AddInvite(leader, member)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("HasPendingInvite", func(b *testing.B) {
|
b.Run("HasPendingInvite", func(b *testing.B) {
|
||||||
// Add some invites first
|
// Add some invites first
|
||||||
for i := 0; i < 100; i++ {
|
for i := 0; i < 100; i++ {
|
||||||
@ -480,7 +480,7 @@ func BenchmarkInviteSystem(b *testing.B) {
|
|||||||
member := createTestEntity(int32(i+2001), fmt.Sprintf("TestMember%d", i+1), true)
|
member := createTestEntity(int32(i+2001), fmt.Sprintf("TestMember%d", i+1), true)
|
||||||
manager.AddInvite(leader, member)
|
manager.AddInvite(leader, member)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.ResetTimer()
|
b.ResetTimer()
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
@ -489,7 +489,7 @@ func BenchmarkInviteSystem(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("Invite", func(b *testing.B) {
|
b.Run("Invite", func(b *testing.B) {
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
leader := createTestEntity(int32(i+3001), fmt.Sprintf("InviteLeader%d", i+1), true)
|
leader := createTestEntity(int32(i+3001), fmt.Sprintf("InviteLeader%d", i+1), true)
|
||||||
@ -497,7 +497,7 @@ func BenchmarkInviteSystem(b *testing.B) {
|
|||||||
_ = manager.Invite(leader, member)
|
_ = manager.Invite(leader, member)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("DeclineInvite", func(b *testing.B) {
|
b.Run("DeclineInvite", func(b *testing.B) {
|
||||||
// Add invites to decline
|
// Add invites to decline
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@ -515,20 +515,20 @@ func BenchmarkConcurrentOperations(b *testing.B) {
|
|||||||
MaxGroups: 1000,
|
MaxGroups: 1000,
|
||||||
EnableStatistics: true,
|
EnableStatistics: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
manager := NewManager(config, nil)
|
manager := NewManager(config, nil)
|
||||||
|
|
||||||
// Pre-populate
|
// Pre-populate
|
||||||
for i := 0; i < 50; i++ {
|
for i := 0; i < 50; i++ {
|
||||||
leader := createTestEntity(int32(i+1), fmt.Sprintf("ConcurrentLeader%d", i+1), true)
|
leader := createTestEntity(int32(i+1), fmt.Sprintf("ConcurrentLeader%d", i+1), true)
|
||||||
manager.NewGroup(leader, nil, 0)
|
manager.NewGroup(leader, nil, 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
b.Run("ConcurrentGroupAccess", func(b *testing.B) {
|
b.Run("ConcurrentGroupAccess", func(b *testing.B) {
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
for pb.Next() {
|
for pb.Next() {
|
||||||
groupID := int32(rand.Intn(50) + 1)
|
groupID := int32(rand.Intn(50) + 1)
|
||||||
|
|
||||||
switch rand.Intn(4) {
|
switch rand.Intn(4) {
|
||||||
case 0:
|
case 0:
|
||||||
_ = manager.GetGroup(groupID)
|
_ = manager.GetGroup(groupID)
|
||||||
@ -543,7 +543,7 @@ func BenchmarkConcurrentOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("ConcurrentInviteOperations", func(b *testing.B) {
|
b.Run("ConcurrentInviteOperations", func(b *testing.B) {
|
||||||
var counter int64
|
var counter int64
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
b.RunParallel(func(pb *testing.PB) {
|
||||||
@ -551,7 +551,7 @@ func BenchmarkConcurrentOperations(b *testing.B) {
|
|||||||
i := atomic.AddInt64(&counter, 1)
|
i := atomic.AddInt64(&counter, 1)
|
||||||
leader := createTestEntity(int32(i+20000), fmt.Sprintf("ConcurrentInviteLeader%d", i), true)
|
leader := createTestEntity(int32(i+20000), fmt.Sprintf("ConcurrentInviteLeader%d", i), true)
|
||||||
member := createTestEntity(int32(i+30000), fmt.Sprintf("ConcurrentInviteMember%d", i), true)
|
member := createTestEntity(int32(i+30000), fmt.Sprintf("ConcurrentInviteMember%d", i), true)
|
||||||
|
|
||||||
switch rand.Intn(3) {
|
switch rand.Intn(3) {
|
||||||
case 0:
|
case 0:
|
||||||
manager.AddInvite(leader, member)
|
manager.AddInvite(leader, member)
|
||||||
@ -574,7 +574,7 @@ func BenchmarkMemoryAllocation(b *testing.B) {
|
|||||||
group.Disband() // Clean up background goroutine
|
group.Disband() // Clean up background goroutine
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("MasterListAllocation", func(b *testing.B) {
|
b.Run("MasterListAllocation", func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@ -582,7 +582,7 @@ func BenchmarkMemoryAllocation(b *testing.B) {
|
|||||||
_ = ml
|
_ = ml
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("ManagerAllocation", func(b *testing.B) {
|
b.Run("ManagerAllocation", func(b *testing.B) {
|
||||||
config := GroupManagerConfig{}
|
config := GroupManagerConfig{}
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
@ -592,7 +592,7 @@ func BenchmarkMemoryAllocation(b *testing.B) {
|
|||||||
_ = manager
|
_ = manager
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
b.Run("GroupMemberInfoAllocation", func(b *testing.B) {
|
b.Run("GroupMemberInfoAllocation", func(b *testing.B) {
|
||||||
b.ReportAllocs()
|
b.ReportAllocs()
|
||||||
for i := 0; i < b.N; i++ {
|
for i := 0; i < b.N; i++ {
|
||||||
@ -617,50 +617,3 @@ func BenchmarkMemoryAllocation(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
// BenchmarkComparisonWithOldSystem provides comparison benchmarks
|
|
||||||
func BenchmarkComparisonWithOldSystem(b *testing.B) {
|
|
||||||
ml := NewMasterList()
|
|
||||||
const numGroups = 1000
|
|
||||||
|
|
||||||
// Setup
|
|
||||||
b.StopTimer()
|
|
||||||
groups := make([]*Group, numGroups)
|
|
||||||
for i := 0; i < numGroups; i++ {
|
|
||||||
groups[i] = createTestGroup(b, int32(i+1), rand.Intn(6)+1)
|
|
||||||
ml.AddGroup(groups[i])
|
|
||||||
}
|
|
||||||
// Cleanup all groups when benchmark is done
|
|
||||||
defer func() {
|
|
||||||
for _, group := range groups {
|
|
||||||
if group != nil {
|
|
||||||
group.Disband()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}()
|
|
||||||
b.StartTimer()
|
|
||||||
|
|
||||||
b.Run("ModernizedGroupLookup", func(b *testing.B) {
|
|
||||||
// Modern generic-based lookup
|
|
||||||
b.RunParallel(func(pb *testing.PB) {
|
|
||||||
for pb.Next() {
|
|
||||||
id := int32(rand.Intn(numGroups) + 1)
|
|
||||||
_ = ml.GetGroup(id)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
b.Run("ModernizedGroupFiltering", func(b *testing.B) {
|
|
||||||
// Modern filter-based operations
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = ml.GetActiveGroups()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
b.Run("ModernizedGroupStatistics", func(b *testing.B) {
|
|
||||||
// Modern statistics computation
|
|
||||||
for i := 0; i < b.N; i++ {
|
|
||||||
_ = ml.GetGroupStatistics()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
@ -8,19 +8,19 @@ type Entity interface {
|
|||||||
GetLevel() int8
|
GetLevel() int8
|
||||||
GetClass() int8
|
GetClass() int8
|
||||||
GetRace() int8
|
GetRace() int8
|
||||||
|
|
||||||
// Health and power
|
// Health and power
|
||||||
GetHP() int32
|
GetHP() int32
|
||||||
GetTotalHP() int32
|
GetTotalHP() int32
|
||||||
GetPower() int32
|
GetPower() int32
|
||||||
GetTotalPower() int32
|
GetTotalPower() int32
|
||||||
|
|
||||||
// Entity types
|
// Entity types
|
||||||
IsPlayer() bool
|
IsPlayer() bool
|
||||||
IsBot() bool
|
IsBot() bool
|
||||||
IsNPC() bool
|
IsNPC() bool
|
||||||
IsDead() bool
|
IsDead() bool
|
||||||
|
|
||||||
// World positioning
|
// World positioning
|
||||||
GetZone() Zone
|
GetZone() Zone
|
||||||
GetDistance(other Entity) float32
|
GetDistance(other Entity) float32
|
||||||
@ -31,4 +31,4 @@ type Zone interface {
|
|||||||
GetZoneID() int32
|
GetZoneID() int32
|
||||||
GetInstanceID() int32
|
GetInstanceID() int32
|
||||||
GetZoneName() string
|
GetZoneName() string
|
||||||
}
|
}
|
||||||
|
@ -9,29 +9,29 @@ import (
|
|||||||
// Group represents a player group with embedded database operations
|
// Group represents a player group with embedded database operations
|
||||||
type Group struct {
|
type Group struct {
|
||||||
// Core fields
|
// Core fields
|
||||||
GroupID int32 `json:"group_id" db:"group_id"`
|
GroupID int32 `json:"group_id" db:"group_id"`
|
||||||
Options GroupOptions `json:"options"`
|
Options GroupOptions `json:"options"`
|
||||||
Members []*GroupMemberInfo `json:"members"`
|
Members []*GroupMemberInfo `json:"members"`
|
||||||
RaidGroups []int32 `json:"raid_groups"`
|
RaidGroups []int32 `json:"raid_groups"`
|
||||||
CreatedTime time.Time `json:"created_time" db:"created_time"`
|
CreatedTime time.Time `json:"created_time" db:"created_time"`
|
||||||
LastActivity time.Time `json:"last_activity" db:"last_activity"`
|
LastActivity time.Time `json:"last_activity" db:"last_activity"`
|
||||||
Disbanded bool `json:"disbanded" db:"disbanded"`
|
Disbanded bool `json:"disbanded" db:"disbanded"`
|
||||||
|
|
||||||
// Internal fields
|
// Internal fields
|
||||||
membersMutex sync.RWMutex `json:"-"`
|
membersMutex sync.RWMutex `json:"-"`
|
||||||
raidGroupsMutex sync.RWMutex `json:"-"`
|
raidGroupsMutex sync.RWMutex `json:"-"`
|
||||||
optionsMutex sync.RWMutex `json:"-"`
|
optionsMutex sync.RWMutex `json:"-"`
|
||||||
activityMutex sync.RWMutex `json:"-"`
|
activityMutex sync.RWMutex `json:"-"`
|
||||||
disbandMutex sync.RWMutex `json:"-"`
|
disbandMutex sync.RWMutex `json:"-"`
|
||||||
|
|
||||||
// Communication channels
|
// Communication channels
|
||||||
messageQueue chan *GroupMessage `json:"-"`
|
messageQueue chan *GroupMessage `json:"-"`
|
||||||
updateQueue chan *GroupUpdate `json:"-"`
|
updateQueue chan *GroupUpdate `json:"-"`
|
||||||
|
|
||||||
// Background processing
|
// Background processing
|
||||||
stopChan chan struct{} `json:"-"`
|
stopChan chan struct{} `json:"-"`
|
||||||
wg sync.WaitGroup `json:"-"`
|
wg sync.WaitGroup `json:"-"`
|
||||||
|
|
||||||
// Database integration - embedded operations
|
// Database integration - embedded operations
|
||||||
db any `json:"-"` // Database connection
|
db any `json:"-"` // Database connection
|
||||||
isNew bool `json:"-"` // Flag for new groups
|
isNew bool `json:"-"` // Flag for new groups
|
||||||
@ -53,11 +53,11 @@ func New(db any) *Group {
|
|||||||
db: db,
|
db: db,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start background processing
|
// Start background processing
|
||||||
group.wg.Add(1)
|
group.wg.Add(1)
|
||||||
go group.processMessages()
|
go group.processMessages()
|
||||||
|
|
||||||
return group
|
return group
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -106,7 +106,7 @@ func (g *Group) Save() error {
|
|||||||
func (g *Group) Delete() error {
|
func (g *Group) Delete() error {
|
||||||
// Disband the group first
|
// Disband the group first
|
||||||
g.Disband()
|
g.Disband()
|
||||||
|
|
||||||
// TODO: Implement database delete logic
|
// TODO: Implement database delete logic
|
||||||
// This would require integration with the actual database system
|
// This would require integration with the actual database system
|
||||||
return nil
|
return nil
|
||||||
|
@ -9,46 +9,46 @@ import (
|
|||||||
|
|
||||||
// Mock entity implementation for testing
|
// Mock entity implementation for testing
|
||||||
type mockEntity struct {
|
type mockEntity struct {
|
||||||
id int32
|
id int32
|
||||||
name string
|
name string
|
||||||
level int8
|
level int8
|
||||||
class int8
|
class int8
|
||||||
race int8
|
race int8
|
||||||
hp int32
|
hp int32
|
||||||
maxHP int32
|
maxHP int32
|
||||||
power int32
|
power int32
|
||||||
maxPower int32
|
maxPower int32
|
||||||
isPlayer bool
|
isPlayer bool
|
||||||
isNPC bool
|
isNPC bool
|
||||||
isBot bool
|
isBot bool
|
||||||
isDead bool
|
isDead bool
|
||||||
zone *mockZone
|
zone *mockZone
|
||||||
groupID int32
|
groupID int32
|
||||||
groupInfo *GroupMemberInfo
|
groupInfo *GroupMemberInfo
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *mockEntity) GetID() int32 { return m.id }
|
func (m *mockEntity) GetID() int32 { return m.id }
|
||||||
func (m *mockEntity) GetName() string { return m.name }
|
func (m *mockEntity) GetName() string { return m.name }
|
||||||
func (m *mockEntity) GetLevel() int8 { return m.level }
|
func (m *mockEntity) GetLevel() int8 { return m.level }
|
||||||
func (m *mockEntity) GetClass() int8 { return m.class }
|
func (m *mockEntity) GetClass() int8 { return m.class }
|
||||||
func (m *mockEntity) GetRace() int8 { return m.race }
|
func (m *mockEntity) GetRace() int8 { return m.race }
|
||||||
func (m *mockEntity) GetHP() int32 { return m.hp }
|
func (m *mockEntity) GetHP() int32 { return m.hp }
|
||||||
func (m *mockEntity) GetTotalHP() int32 { return m.maxHP }
|
func (m *mockEntity) GetTotalHP() int32 { return m.maxHP }
|
||||||
func (m *mockEntity) GetPower() int32 { return m.power }
|
func (m *mockEntity) GetPower() int32 { return m.power }
|
||||||
func (m *mockEntity) GetTotalPower() int32 { return m.maxPower }
|
func (m *mockEntity) GetTotalPower() int32 { return m.maxPower }
|
||||||
func (m *mockEntity) IsPlayer() bool { return m.isPlayer }
|
func (m *mockEntity) IsPlayer() bool { return m.isPlayer }
|
||||||
func (m *mockEntity) IsNPC() bool { return m.isNPC }
|
func (m *mockEntity) IsNPC() bool { return m.isNPC }
|
||||||
func (m *mockEntity) IsBot() bool { return m.isBot }
|
func (m *mockEntity) IsBot() bool { return m.isBot }
|
||||||
func (m *mockEntity) IsDead() bool { return m.isDead }
|
func (m *mockEntity) IsDead() bool { return m.isDead }
|
||||||
func (m *mockEntity) GetZone() Zone { return m.zone }
|
func (m *mockEntity) GetZone() Zone { return m.zone }
|
||||||
func (m *mockEntity) GetDistance(other Entity) float32 { return 10.0 }
|
func (m *mockEntity) GetDistance(other Entity) float32 { return 10.0 }
|
||||||
|
|
||||||
// GroupAware implementation
|
// GroupAware implementation
|
||||||
func (m *mockEntity) GetGroupMemberInfo() *GroupMemberInfo { return m.groupInfo }
|
func (m *mockEntity) GetGroupMemberInfo() *GroupMemberInfo { return m.groupInfo }
|
||||||
func (m *mockEntity) SetGroupMemberInfo(info *GroupMemberInfo) { m.groupInfo = info }
|
func (m *mockEntity) SetGroupMemberInfo(info *GroupMemberInfo) { m.groupInfo = info }
|
||||||
func (m *mockEntity) GetGroupID() int32 { return m.groupID }
|
func (m *mockEntity) GetGroupID() int32 { return m.groupID }
|
||||||
func (m *mockEntity) SetGroupID(groupID int32) { m.groupID = groupID }
|
func (m *mockEntity) SetGroupID(groupID int32) { m.groupID = groupID }
|
||||||
func (m *mockEntity) IsInGroup() bool { return m.groupID > 0 }
|
func (m *mockEntity) IsInGroup() bool { return m.groupID > 0 }
|
||||||
|
|
||||||
// Mock zone implementation
|
// Mock zone implementation
|
||||||
type mockZone struct {
|
type mockZone struct {
|
||||||
@ -111,7 +111,7 @@ func TestGroupCreation(t *testing.T) {
|
|||||||
for _, tt := range tests {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
group := NewGroup(tt.groupID, tt.options, nil)
|
group := NewGroup(tt.groupID, tt.options, nil)
|
||||||
|
|
||||||
if (group == nil) != tt.expectNil {
|
if (group == nil) != tt.expectNil {
|
||||||
t.Errorf("NewGroup() returned nil = %v, want %v", group == nil, tt.expectNil)
|
t.Errorf("NewGroup() returned nil = %v, want %v", group == nil, tt.expectNil)
|
||||||
return
|
return
|
||||||
@ -245,7 +245,7 @@ func TestGroupOptions(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
group.SetGroupOptions(&newOptions)
|
group.SetGroupOptions(&newOptions)
|
||||||
|
|
||||||
options = group.GetGroupOptions()
|
options = group.GetGroupOptions()
|
||||||
if options.LootMethod != LOOT_METHOD_NEED_BEFORE_GREED {
|
if options.LootMethod != LOOT_METHOD_NEED_BEFORE_GREED {
|
||||||
t.Errorf("Updated loot method = %d, want %d", options.LootMethod, LOOT_METHOD_NEED_BEFORE_GREED)
|
t.Errorf("Updated loot method = %d, want %d", options.LootMethod, LOOT_METHOD_NEED_BEFORE_GREED)
|
||||||
@ -305,13 +305,13 @@ func TestGroupConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wg.Add(numGoroutines)
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
for i := range numGoroutines {
|
for i := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
member := createMockEntity(int32(100+goroutineID), fmt.Sprintf("Temp%d", goroutineID), true)
|
member := createMockEntity(int32(100+goroutineID), fmt.Sprintf("Temp%d", goroutineID), true)
|
||||||
|
|
||||||
for j := range operationsPerGoroutine {
|
for j := range operationsPerGoroutine {
|
||||||
if j%2 == 0 {
|
if j%2 == 0 {
|
||||||
// Try to add member (will mostly fail due to full group)
|
// Try to add member (will mostly fail due to full group)
|
||||||
@ -329,18 +329,18 @@ func TestGroupConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test concurrent option updates
|
// Test concurrent option updates
|
||||||
t.Run("ConcurrentOptionUpdates", func(t *testing.T) {
|
t.Run("ConcurrentOptionUpdates", func(t *testing.T) {
|
||||||
wg.Add(numGoroutines)
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
for i := range numGoroutines {
|
for i := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for j := range operationsPerGoroutine {
|
for j := range operationsPerGoroutine {
|
||||||
if j%2 == 0 {
|
if j%2 == 0 {
|
||||||
// Read options
|
// Read options
|
||||||
@ -357,18 +357,18 @@ func TestGroupConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test concurrent raid operations
|
// Test concurrent raid operations
|
||||||
t.Run("ConcurrentRaidOperations", func(t *testing.T) {
|
t.Run("ConcurrentRaidOperations", func(t *testing.T) {
|
||||||
wg.Add(numGoroutines)
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
for i := range numGoroutines {
|
for i := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for j := range operationsPerGoroutine {
|
for j := range operationsPerGoroutine {
|
||||||
switch j % 4 {
|
switch j % 4 {
|
||||||
case 0:
|
case 0:
|
||||||
@ -384,18 +384,18 @@ func TestGroupConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
})
|
})
|
||||||
|
|
||||||
// Test concurrent member info updates
|
// Test concurrent member info updates
|
||||||
t.Run("ConcurrentMemberInfoUpdates", func(t *testing.T) {
|
t.Run("ConcurrentMemberInfoUpdates", func(t *testing.T) {
|
||||||
wg.Add(numGoroutines)
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
for i := range numGoroutines {
|
for i := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for range operationsPerGoroutine {
|
for range operationsPerGoroutine {
|
||||||
members := group.GetMembers()
|
members := group.GetMembers()
|
||||||
if len(members) > 0 {
|
if len(members) > 0 {
|
||||||
@ -406,7 +406,7 @@ func TestGroupConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -521,8 +521,8 @@ func TestGroupManagerInvitations(t *testing.T) {
|
|||||||
MaxGroups: 1000,
|
MaxGroups: 1000,
|
||||||
MaxRaidGroups: 4,
|
MaxRaidGroups: 4,
|
||||||
InviteTimeout: 200 * time.Millisecond, // Very short timeout for testing
|
InviteTimeout: 200 * time.Millisecond, // Very short timeout for testing
|
||||||
UpdateInterval: 0, // Disable background updates for testing
|
UpdateInterval: 0, // Disable background updates for testing
|
||||||
BuffUpdateInterval: 0, // Disable background updates for testing
|
BuffUpdateInterval: 0, // Disable background updates for testing
|
||||||
EnableCrossServer: true,
|
EnableCrossServer: true,
|
||||||
EnableRaids: true,
|
EnableRaids: true,
|
||||||
EnableQuestSharing: true,
|
EnableQuestSharing: true,
|
||||||
@ -555,7 +555,7 @@ func TestGroupManagerInvitations(t *testing.T) {
|
|||||||
t.Logf("Accept invite result = %d (expected due to missing leader lookup in test)", acceptResult)
|
t.Logf("Accept invite result = %d (expected due to missing leader lookup in test)", acceptResult)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Since invite acceptance failed due to missing world integration,
|
// Since invite acceptance failed due to missing world integration,
|
||||||
// let's manually add the member to test the group functionality
|
// let's manually add the member to test the group functionality
|
||||||
err := manager.AddGroupMember(groupID, member, false)
|
err := manager.AddGroupMember(groupID, member, false)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -570,11 +570,11 @@ func TestGroupManagerInvitations(t *testing.T) {
|
|||||||
// Test invitation timeout
|
// Test invitation timeout
|
||||||
member2 := createMockEntity(3, "Member2", true)
|
member2 := createMockEntity(3, "Member2", true)
|
||||||
manager.Invite(leader, member2)
|
manager.Invite(leader, member2)
|
||||||
|
|
||||||
// Wait for timeout (now timeout is 200ms so wait 250ms)
|
// Wait for timeout (now timeout is 200ms so wait 250ms)
|
||||||
time.Sleep(250 * time.Millisecond)
|
time.Sleep(250 * time.Millisecond)
|
||||||
|
|
||||||
// Try to accept after timeout (will fail due to missing leader lookup,
|
// Try to accept after timeout (will fail due to missing leader lookup,
|
||||||
// but we're mainly testing that the invite was cleaned up)
|
// but we're mainly testing that the invite was cleaned up)
|
||||||
acceptResult = manager.AcceptInvite(member2, nil, true)
|
acceptResult = manager.AcceptInvite(member2, nil, true)
|
||||||
if acceptResult == GROUP_INVITE_SUCCESS {
|
if acceptResult == GROUP_INVITE_SUCCESS {
|
||||||
@ -611,26 +611,26 @@ func TestGroupManagerConcurrency(t *testing.T) {
|
|||||||
// Test concurrent group creation and removal
|
// Test concurrent group creation and removal
|
||||||
t.Run("ConcurrentGroupCreation", func(t *testing.T) {
|
t.Run("ConcurrentGroupCreation", func(t *testing.T) {
|
||||||
wg.Add(numGoroutines)
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
for i := range numGoroutines {
|
for i := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for j := range groupsPerGoroutine {
|
for j := range groupsPerGoroutine {
|
||||||
leader := createMockEntity(int32(goroutineID*1000+j), fmt.Sprintf("Leader%d_%d", goroutineID, j), true)
|
leader := createMockEntity(int32(goroutineID*1000+j), fmt.Sprintf("Leader%d_%d", goroutineID, j), true)
|
||||||
|
|
||||||
// Create group
|
// Create group
|
||||||
groupID, err := manager.NewGroup(leader, nil, 0)
|
groupID, err := manager.NewGroup(leader, nil, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add some members
|
// Add some members
|
||||||
for k := range 3 {
|
for k := range 3 {
|
||||||
member := createMockEntity(int32(goroutineID*1000+j*10+k), fmt.Sprintf("Member%d_%d_%d", goroutineID, j, k), true)
|
member := createMockEntity(int32(goroutineID*1000+j*10+k), fmt.Sprintf("Member%d_%d_%d", goroutineID, j, k), true)
|
||||||
_ = manager.AddGroupMember(groupID, member, false)
|
_ = manager.AddGroupMember(groupID, member, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sometimes remove the group
|
// Sometimes remove the group
|
||||||
if j%2 == 0 {
|
if j%2 == 0 {
|
||||||
_ = manager.RemoveGroup(groupID)
|
_ = manager.RemoveGroup(groupID)
|
||||||
@ -638,7 +638,7 @@ func TestGroupManagerConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
})
|
})
|
||||||
|
|
||||||
@ -647,7 +647,7 @@ func TestGroupManagerConcurrency(t *testing.T) {
|
|||||||
// Create some groups
|
// Create some groups
|
||||||
groups := make([]int32, 10)
|
groups := make([]int32, 10)
|
||||||
leaders := make([]*mockEntity, 10)
|
leaders := make([]*mockEntity, 10)
|
||||||
|
|
||||||
for i := range 10 {
|
for i := range 10 {
|
||||||
leader := createMockEntity(int32(10000+i), fmt.Sprintf("InviteLeader%d", i), true)
|
leader := createMockEntity(int32(10000+i), fmt.Sprintf("InviteLeader%d", i), true)
|
||||||
leaders[i] = leader
|
leaders[i] = leader
|
||||||
@ -656,20 +656,20 @@ func TestGroupManagerConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wg.Add(numGoroutines)
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
for i := range numGoroutines {
|
for i := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for j := range 100 {
|
for j := range 100 {
|
||||||
leaderIdx := goroutineID % len(leaders)
|
leaderIdx := goroutineID % len(leaders)
|
||||||
leader := leaders[leaderIdx]
|
leader := leaders[leaderIdx]
|
||||||
|
|
||||||
member := createMockEntity(int32(20000+goroutineID*100+j), fmt.Sprintf("InviteMember%d_%d", goroutineID, j), true)
|
member := createMockEntity(int32(20000+goroutineID*100+j), fmt.Sprintf("InviteMember%d_%d", goroutineID, j), true)
|
||||||
|
|
||||||
// Send invite
|
// Send invite
|
||||||
_ = manager.Invite(leader, member)
|
_ = manager.Invite(leader, member)
|
||||||
|
|
||||||
// Sometimes accept, sometimes decline
|
// Sometimes accept, sometimes decline
|
||||||
if j%3 == 0 {
|
if j%3 == 0 {
|
||||||
_ = manager.AcceptInvite(member, nil, false)
|
_ = manager.AcceptInvite(member, nil, false)
|
||||||
@ -680,9 +680,9 @@ func TestGroupManagerConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
|
|
||||||
// Cleanup groups
|
// Cleanup groups
|
||||||
for _, groupID := range groups {
|
for _, groupID := range groups {
|
||||||
_ = manager.RemoveGroup(groupID)
|
_ = manager.RemoveGroup(groupID)
|
||||||
@ -692,11 +692,11 @@ func TestGroupManagerConcurrency(t *testing.T) {
|
|||||||
// Test concurrent statistics updates
|
// Test concurrent statistics updates
|
||||||
t.Run("ConcurrentStatistics", func(t *testing.T) {
|
t.Run("ConcurrentStatistics", func(t *testing.T) {
|
||||||
wg.Add(numGoroutines)
|
wg.Add(numGoroutines)
|
||||||
|
|
||||||
for i := range numGoroutines {
|
for i := range numGoroutines {
|
||||||
go func(goroutineID int) {
|
go func(goroutineID int) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
|
|
||||||
for range 1000 {
|
for range 1000 {
|
||||||
_ = manager.GetStats()
|
_ = manager.GetStats()
|
||||||
_ = manager.GetGroupCount()
|
_ = manager.GetGroupCount()
|
||||||
@ -704,7 +704,7 @@ func TestGroupManagerConcurrency(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}(i)
|
}(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
wg.Wait()
|
wg.Wait()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@ -860,4 +860,363 @@ func BenchmarkGroupOperations(b *testing.B) {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestMasterListCreation tests master list creation
|
||||||
|
func TestMasterListCreation(t *testing.T) {
|
||||||
|
masterList := NewMasterList()
|
||||||
|
if masterList == nil {
|
||||||
|
t.Fatal("NewMasterList returned nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if masterList.GetGroupCount() != 0 {
|
||||||
|
t.Errorf("Expected count 0, got %d", masterList.GetGroupCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
if !masterList.IsEmpty() {
|
||||||
|
t.Error("New master list should be empty")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMasterListBasicOperations tests basic operations
|
||||||
|
func TestMasterListBasicOperations(t *testing.T) {
|
||||||
|
masterList := NewMasterList()
|
||||||
|
|
||||||
|
// Create test groups
|
||||||
|
group1 := NewGroup(1001, nil, nil)
|
||||||
|
group2 := NewGroup(1002, nil, nil)
|
||||||
|
|
||||||
|
// Add members to create different characteristics
|
||||||
|
leader1 := createMockEntity(1, "Leader1", true)
|
||||||
|
member1 := createMockEntity(2, "Member1", true)
|
||||||
|
group1.AddMember(leader1, true)
|
||||||
|
group1.AddMember(member1, false)
|
||||||
|
|
||||||
|
leader2 := createMockEntity(3, "Leader2", true)
|
||||||
|
group2.AddMember(leader2, true)
|
||||||
|
|
||||||
|
// Test adding
|
||||||
|
if !masterList.AddGroup(group1) {
|
||||||
|
t.Error("Should successfully add group1")
|
||||||
|
}
|
||||||
|
|
||||||
|
if !masterList.AddGroup(group2) {
|
||||||
|
t.Error("Should successfully add group2")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test duplicate add (should fail)
|
||||||
|
if masterList.AddGroup(group1) {
|
||||||
|
t.Error("Should not add duplicate group")
|
||||||
|
}
|
||||||
|
|
||||||
|
if masterList.GetGroupCount() != 2 {
|
||||||
|
t.Errorf("Expected count 2, got %d", masterList.GetGroupCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test retrieving
|
||||||
|
retrieved := masterList.GetGroup(1001)
|
||||||
|
if retrieved == nil {
|
||||||
|
t.Error("Should retrieve added group")
|
||||||
|
}
|
||||||
|
|
||||||
|
if retrieved.GetID() != 1001 {
|
||||||
|
t.Errorf("Expected ID 1001, got %d", retrieved.GetID())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test safe retrieval
|
||||||
|
retrieved, exists := masterList.GetGroupSafe(1001)
|
||||||
|
if !exists || retrieved == nil {
|
||||||
|
t.Error("GetGroupSafe should return group and true")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, exists = masterList.GetGroupSafe(9999)
|
||||||
|
if exists {
|
||||||
|
t.Error("GetGroupSafe should return false for non-existent ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test HasGroup
|
||||||
|
if !masterList.HasGroup(1001) {
|
||||||
|
t.Error("HasGroup should return true for existing ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
if masterList.HasGroup(9999) {
|
||||||
|
t.Error("HasGroup should return false for non-existent ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test removing
|
||||||
|
if !masterList.RemoveGroup(1001) {
|
||||||
|
t.Error("Should successfully remove group")
|
||||||
|
}
|
||||||
|
|
||||||
|
if masterList.GetGroupCount() != 1 {
|
||||||
|
t.Errorf("Expected count 1, got %d", masterList.GetGroupCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
if masterList.HasGroup(1001) {
|
||||||
|
t.Error("Group should be removed")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test clear
|
||||||
|
masterList.Clear()
|
||||||
|
if masterList.GetGroupCount() != 0 {
|
||||||
|
t.Errorf("Expected count 0 after clear, got %d", masterList.GetGroupCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
group1.Disband()
|
||||||
|
group2.Disband()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMasterListFeatures tests domain-specific features
|
||||||
|
func TestMasterListFeatures(t *testing.T) {
|
||||||
|
masterList := NewMasterList()
|
||||||
|
|
||||||
|
// Create groups with different characteristics
|
||||||
|
group1 := NewGroup(101, nil, nil)
|
||||||
|
group2 := NewGroup(102, nil, nil)
|
||||||
|
group3 := NewGroup(103, nil, nil)
|
||||||
|
group4 := NewGroup(104, nil, nil)
|
||||||
|
|
||||||
|
// Group 1: 2 members in zone 220
|
||||||
|
leader1 := createMockEntity(1, "Leader1", true)
|
||||||
|
member1 := createMockEntity(2, "Member1", true)
|
||||||
|
leader1.zone = &mockZone{zoneID: 220, instanceID: 1, zoneName: "commonlands"}
|
||||||
|
member1.zone = &mockZone{zoneID: 220, instanceID: 1, zoneName: "commonlands"}
|
||||||
|
group1.AddMember(leader1, true)
|
||||||
|
group1.AddMember(member1, false)
|
||||||
|
|
||||||
|
// Group 2: 1 member (solo) in zone 221
|
||||||
|
leader2 := createMockEntity(3, "Leader2", true)
|
||||||
|
leader2.zone = &mockZone{zoneID: 221, instanceID: 1, zoneName: "antonica"}
|
||||||
|
group2.AddMember(leader2, true)
|
||||||
|
|
||||||
|
// Group 3: Full group (6 members) in zone 220, make it a raid
|
||||||
|
leader3 := createMockEntity(4, "Leader3", true)
|
||||||
|
leader3.zone = &mockZone{zoneID: 220, instanceID: 1, zoneName: "commonlands"}
|
||||||
|
group3.AddMember(leader3, true)
|
||||||
|
for i := 1; i < MAX_GROUP_SIZE; i++ {
|
||||||
|
member := createMockEntity(int32(10+i), fmt.Sprintf("RaidMember%d", i), true)
|
||||||
|
member.zone = &mockZone{zoneID: 220, instanceID: 1, zoneName: "commonlands"}
|
||||||
|
group3.AddMember(member, false)
|
||||||
|
}
|
||||||
|
group3.ReplaceRaidGroups([]int32{103, 104})
|
||||||
|
|
||||||
|
// Group 4: Disbanded group
|
||||||
|
leader4 := createMockEntity(20, "Leader4", true)
|
||||||
|
group4.AddMember(leader4, true)
|
||||||
|
group4.Disband()
|
||||||
|
|
||||||
|
// Add groups to master list
|
||||||
|
masterList.AddGroup(group1)
|
||||||
|
masterList.AddGroup(group2)
|
||||||
|
masterList.AddGroup(group3)
|
||||||
|
masterList.AddGroup(group4)
|
||||||
|
|
||||||
|
// Test GetGroupByMember
|
||||||
|
found := masterList.GetGroupByMember("member1")
|
||||||
|
if found == nil || found.GetID() != 101 {
|
||||||
|
t.Error("GetGroupByMember should find group containing Member1")
|
||||||
|
}
|
||||||
|
|
||||||
|
found = masterList.GetGroupByMember("LEADER2")
|
||||||
|
if found == nil || found.GetID() != 102 {
|
||||||
|
t.Error("GetGroupByMember should find group containing Leader2 (case insensitive)")
|
||||||
|
}
|
||||||
|
|
||||||
|
found = masterList.GetGroupByMember("NonExistent")
|
||||||
|
if found != nil {
|
||||||
|
t.Error("GetGroupByMember should return nil for non-existent member")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetGroupByLeader
|
||||||
|
found = masterList.GetGroupByLeader("leader1")
|
||||||
|
if found == nil || found.GetID() != 101 {
|
||||||
|
t.Error("GetGroupByLeader should find group led by Leader1")
|
||||||
|
}
|
||||||
|
|
||||||
|
found = masterList.GetGroupByLeader("LEADER3")
|
||||||
|
if found == nil || found.GetID() != 103 {
|
||||||
|
t.Error("GetGroupByLeader should find group led by Leader3 (case insensitive)")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetGroupsBySize
|
||||||
|
soloGroups := masterList.GetGroupsBySize(1)
|
||||||
|
if len(soloGroups) != 1 {
|
||||||
|
t.Errorf("GetGroupsBySize(1) returned %v results, want 1", len(soloGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
twoMemberGroups := masterList.GetGroupsBySize(2)
|
||||||
|
if len(twoMemberGroups) != 1 {
|
||||||
|
t.Errorf("GetGroupsBySize(2) returned %v results, want 1", len(twoMemberGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
fullGroups := masterList.GetGroupsBySize(MAX_GROUP_SIZE)
|
||||||
|
if len(fullGroups) != 1 {
|
||||||
|
t.Errorf("GetGroupsBySize(%d) returned %v results, want 1", MAX_GROUP_SIZE, len(fullGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetGroupsByZone
|
||||||
|
zone220Groups := masterList.GetGroupsByZone(220)
|
||||||
|
if len(zone220Groups) != 2 { // group1 and group3
|
||||||
|
t.Errorf("GetGroupsByZone(220) returned %v results, want 2", len(zone220Groups))
|
||||||
|
}
|
||||||
|
|
||||||
|
zone221Groups := masterList.GetGroupsByZone(221)
|
||||||
|
if len(zone221Groups) != 1 { // group2
|
||||||
|
t.Errorf("GetGroupsByZone(221) returned %v results, want 1", len(zone221Groups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetActiveGroups
|
||||||
|
activeGroups := masterList.GetActiveGroups()
|
||||||
|
if len(activeGroups) != 3 { // group1, group2, group3 (group4 is disbanded)
|
||||||
|
t.Errorf("GetActiveGroups() returned %v results, want 3", len(activeGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetRaidGroups
|
||||||
|
raidGroups := masterList.GetRaidGroups()
|
||||||
|
if len(raidGroups) != 1 { // group3
|
||||||
|
t.Errorf("GetRaidGroups() returned %v results, want 1", len(raidGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetSoloGroups
|
||||||
|
soloGroups = masterList.GetSoloGroups()
|
||||||
|
if len(soloGroups) != 1 { // group2
|
||||||
|
t.Errorf("GetSoloGroups() returned %v results, want 1", len(soloGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetFullGroups
|
||||||
|
fullGroups = masterList.GetFullGroups()
|
||||||
|
if len(fullGroups) != 1 { // group3
|
||||||
|
t.Errorf("GetFullGroups() returned %v results, want 1", len(fullGroups))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetZones
|
||||||
|
zones := masterList.GetZones()
|
||||||
|
if len(zones) < 2 {
|
||||||
|
t.Errorf("GetZones() returned %v zones, want at least 2", len(zones))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetSizes
|
||||||
|
sizes := masterList.GetSizes()
|
||||||
|
if len(sizes) < 3 {
|
||||||
|
t.Errorf("GetSizes() returned %v sizes, want at least 3", len(sizes))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test GetTotalMembers
|
||||||
|
totalMembers := masterList.GetTotalMembers()
|
||||||
|
expectedTotal := int32(2 + 1 + MAX_GROUP_SIZE) // group1 + group2 + group3 (group4 is disbanded)
|
||||||
|
if totalMembers != expectedTotal {
|
||||||
|
t.Errorf("GetTotalMembers() returned %v, want %v", totalMembers, expectedTotal)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test UpdateGroup
|
||||||
|
group1.AddMember(createMockEntity(30, "NewMember", true), false)
|
||||||
|
err := masterList.UpdateGroup(group1)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("UpdateGroup failed: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test updating non-existent group
|
||||||
|
nonExistentGroup := NewGroup(9999, nil, nil)
|
||||||
|
err = masterList.UpdateGroup(nonExistentGroup)
|
||||||
|
if err == nil {
|
||||||
|
t.Error("UpdateGroup should fail for non-existent group")
|
||||||
|
}
|
||||||
|
nonExistentGroup.Disband()
|
||||||
|
|
||||||
|
// Test GetGroupStatistics
|
||||||
|
stats := masterList.GetGroupStatistics()
|
||||||
|
if stats.TotalGroups != 4 {
|
||||||
|
t.Errorf("Statistics TotalGroups = %v, want 4", stats.TotalGroups)
|
||||||
|
}
|
||||||
|
if stats.ActiveGroups != 3 {
|
||||||
|
t.Errorf("Statistics ActiveGroups = %v, want 3", stats.ActiveGroups)
|
||||||
|
}
|
||||||
|
if stats.RaidGroups != 1 {
|
||||||
|
t.Errorf("Statistics RaidGroups = %v, want 1", stats.RaidGroups)
|
||||||
|
}
|
||||||
|
if stats.SoloGroups != 1 {
|
||||||
|
t.Errorf("Statistics SoloGroups = %v, want 1", stats.SoloGroups)
|
||||||
|
}
|
||||||
|
if stats.FullGroups != 1 {
|
||||||
|
t.Errorf("Statistics FullGroups = %v, want 1", stats.FullGroups)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test Cleanup
|
||||||
|
removedCount := masterList.Cleanup()
|
||||||
|
if removedCount != 1 { // Should remove group4
|
||||||
|
t.Errorf("Cleanup() removed %v groups, want 1", removedCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
if masterList.GetGroupCount() != 3 {
|
||||||
|
t.Errorf("Group count after cleanup = %v, want 3", masterList.GetGroupCount())
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
group1.Disband()
|
||||||
|
group2.Disband()
|
||||||
|
group3.Disband()
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestMasterListConcurrency tests concurrent access
|
||||||
|
func TestMasterListConcurrency(t *testing.T) {
|
||||||
|
masterList := NewMasterList()
|
||||||
|
|
||||||
|
// Add initial groups
|
||||||
|
for i := 1; i <= 50; i++ {
|
||||||
|
group := NewGroup(int32(i+100), nil, nil)
|
||||||
|
leader := createMockEntity(int32(i), fmt.Sprintf("Leader%d", i), true)
|
||||||
|
group.AddMember(leader, true)
|
||||||
|
masterList.AddGroup(group)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Test concurrent access
|
||||||
|
done := make(chan bool, 10)
|
||||||
|
|
||||||
|
// Concurrent readers
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
go func() {
|
||||||
|
defer func() { done <- true }()
|
||||||
|
for j := 0; j < 100; j++ {
|
||||||
|
masterList.GetGroup(int32(j%50 + 101))
|
||||||
|
masterList.GetActiveGroups()
|
||||||
|
masterList.GetGroupByMember(fmt.Sprintf("leader%d", j%50+1))
|
||||||
|
masterList.GetGroupsBySize(1)
|
||||||
|
masterList.GetZones()
|
||||||
|
}
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Concurrent writers
|
||||||
|
for i := 0; i < 5; i++ {
|
||||||
|
go func(workerID int) {
|
||||||
|
defer func() { done <- true }()
|
||||||
|
for j := 0; j < 10; j++ {
|
||||||
|
groupID := int32(workerID*1000 + j + 1000)
|
||||||
|
group := NewGroup(groupID, nil, nil)
|
||||||
|
leader := createMockEntity(int32(workerID*1000+j), fmt.Sprintf("Worker%d-Leader%d", workerID, j), true)
|
||||||
|
group.AddMember(leader, true)
|
||||||
|
masterList.AddGroup(group) // Some may fail due to concurrent additions
|
||||||
|
}
|
||||||
|
}(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for all goroutines
|
||||||
|
for i := 0; i < 10; i++ {
|
||||||
|
<-done
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify final state - should have at least 50 initial groups
|
||||||
|
finalCount := masterList.GetGroupCount()
|
||||||
|
if finalCount < 50 {
|
||||||
|
t.Errorf("Expected at least 50 groups after concurrent operations, got %d", finalCount)
|
||||||
|
}
|
||||||
|
if finalCount > 100 {
|
||||||
|
t.Errorf("Expected at most 100 groups after concurrent operations, got %d", finalCount)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cleanup
|
||||||
|
masterList.ForEach(func(id int32, group *Group) {
|
||||||
|
group.Disband()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
@ -9,36 +9,36 @@ import (
|
|||||||
// Manager provides group management with embedded database operations
|
// Manager provides group management with embedded database operations
|
||||||
type Manager struct {
|
type Manager struct {
|
||||||
// Core fields with embedded database operations
|
// Core fields with embedded database operations
|
||||||
MasterList *MasterList `json:"master_list"`
|
MasterList *MasterList `json:"master_list"`
|
||||||
Config GroupManagerConfig `json:"config"`
|
Config GroupManagerConfig `json:"config"`
|
||||||
Stats GroupManagerStats `json:"stats"`
|
Stats GroupManagerStats `json:"stats"`
|
||||||
|
|
||||||
// Group ID generation
|
// Group ID generation
|
||||||
nextGroupID int32 `json:"-" db:"next_group_id"`
|
nextGroupID int32 `json:"-" db:"next_group_id"`
|
||||||
nextGroupIDMutex sync.Mutex `json:"-"`
|
nextGroupIDMutex sync.Mutex `json:"-"`
|
||||||
|
|
||||||
// Pending invitations
|
// Pending invitations
|
||||||
PendingInvites map[string]*GroupInvite `json:"pending_invites"`
|
PendingInvites map[string]*GroupInvite `json:"pending_invites"`
|
||||||
RaidPendingInvites map[string]*GroupInvite `json:"raid_pending_invites"`
|
RaidPendingInvites map[string]*GroupInvite `json:"raid_pending_invites"`
|
||||||
invitesMutex sync.RWMutex `json:"-"`
|
invitesMutex sync.RWMutex `json:"-"`
|
||||||
|
|
||||||
// Event handlers
|
// Event handlers
|
||||||
EventHandlers []GroupEventHandler `json:"-"`
|
EventHandlers []GroupEventHandler `json:"-"`
|
||||||
eventHandlersMutex sync.RWMutex `json:"-"`
|
eventHandlersMutex sync.RWMutex `json:"-"`
|
||||||
|
|
||||||
// Statistics
|
// Statistics
|
||||||
statsMutex sync.RWMutex `json:"-"`
|
statsMutex sync.RWMutex `json:"-"`
|
||||||
|
|
||||||
// Background processing
|
// Background processing
|
||||||
stopChan chan struct{} `json:"-"`
|
stopChan chan struct{} `json:"-"`
|
||||||
wg sync.WaitGroup `json:"-"`
|
wg sync.WaitGroup `json:"-"`
|
||||||
|
|
||||||
// Integration interfaces
|
// Integration interfaces
|
||||||
database GroupDatabase `json:"-"`
|
database GroupDatabase `json:"-"`
|
||||||
packetHandler GroupPacketHandler `json:"-"`
|
packetHandler GroupPacketHandler `json:"-"`
|
||||||
validator GroupValidator `json:"-"`
|
validator GroupValidator `json:"-"`
|
||||||
notifier GroupNotifier `json:"-"`
|
notifier GroupNotifier `json:"-"`
|
||||||
|
|
||||||
// Database integration - embedded operations
|
// Database integration - embedded operations
|
||||||
db any `json:"-"` // Database connection
|
db any `json:"-"` // Database connection
|
||||||
isNew bool `json:"-"` // Flag for new managers
|
isNew bool `json:"-"` // Flag for new managers
|
||||||
@ -58,7 +58,7 @@ func NewManager(config GroupManagerConfig, db any) *Manager {
|
|||||||
db: db,
|
db: db,
|
||||||
isNew: true,
|
isNew: true,
|
||||||
}
|
}
|
||||||
|
|
||||||
return manager
|
return manager
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -72,7 +72,7 @@ func (m *Manager) Save() error {
|
|||||||
func (m *Manager) Delete() error {
|
func (m *Manager) Delete() error {
|
||||||
// Stop the manager first
|
// Stop the manager first
|
||||||
m.Stop()
|
m.Stop()
|
||||||
|
|
||||||
// TODO: Implement database delete logic
|
// TODO: Implement database delete logic
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
@ -165,7 +165,7 @@ func (m *Manager) RemoveGroup(groupID int32) error {
|
|||||||
|
|
||||||
// Disband the group
|
// Disband the group
|
||||||
group.Disband()
|
group.Disband()
|
||||||
|
|
||||||
// Remove from master list
|
// Remove from master list
|
||||||
if !m.MasterList.RemoveGroup(groupID) {
|
if !m.MasterList.RemoveGroup(groupID) {
|
||||||
return fmt.Errorf("failed to remove group %d from master list", groupID)
|
return fmt.Errorf("failed to remove group %d from master list", groupID)
|
||||||
@ -507,4 +507,4 @@ func (m *Manager) GetStats() GroupManagerStats {
|
|||||||
defer m.statsMutex.RUnlock()
|
defer m.statsMutex.RUnlock()
|
||||||
|
|
||||||
return m.Stats
|
return m.Stats
|
||||||
}
|
}
|
||||||
|
@ -514,4 +514,4 @@ func (m *Manager) fireGroupInviteDeclinedEvent(leader, member Entity) {
|
|||||||
for _, handler := range m.EventHandlers {
|
for _, handler := range m.EventHandlers {
|
||||||
go handler.OnGroupInviteDeclined(leader, member)
|
go handler.OnGroupInviteDeclined(leader, member)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -290,7 +290,7 @@ func TestManagerConcurrentOperations(t *testing.T) {
|
|||||||
|
|
||||||
for j := range operationsPerGoroutine {
|
for j := range operationsPerGoroutine {
|
||||||
leader := createMockEntity(int32(id*1000+j), fmt.Sprintf("Leader%d_%d", id, j), true)
|
leader := createMockEntity(int32(id*1000+j), fmt.Sprintf("Leader%d_%d", id, j), true)
|
||||||
|
|
||||||
// Create group
|
// Create group
|
||||||
groupID, err := manager.NewGroup(leader, nil, 0)
|
groupID, err := manager.NewGroup(leader, nil, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@ -417,7 +417,7 @@ func TestManagerEventHandlers(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
manager := NewManager(config, nil)
|
manager := NewManager(config, nil)
|
||||||
|
|
||||||
// Track events
|
// Track events
|
||||||
events := make([]string, 0)
|
events := make([]string, 0)
|
||||||
var eventsMutex sync.Mutex
|
var eventsMutex sync.Mutex
|
||||||
@ -610,4 +610,4 @@ func (m *mockEventHandler) OnGroupOptionsChanged(group *Group, oldOptions, newOp
|
|||||||
|
|
||||||
func (m *mockEventHandler) OnGroupMemberUpdate(group *Group, member *GroupMemberInfo) error {
|
func (m *mockEventHandler) OnGroupMemberUpdate(group *Group, member *GroupMemberInfo) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
@ -2,212 +2,735 @@ package groups
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"maps"
|
||||||
"eq2emu/internal/common"
|
"strings"
|
||||||
|
"sync"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
// MasterList manages all groups using generic MasterList pattern
|
// 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 {
|
type MasterList struct {
|
||||||
*common.MasterList[int32, *Group]
|
// 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 master list for groups
|
// NewMasterList creates a new group master list
|
||||||
func NewMasterList() *MasterList {
|
func NewMasterList() *MasterList {
|
||||||
return &MasterList{
|
return &MasterList{
|
||||||
MasterList: common.NewMasterList[int32, *Group](),
|
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,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// AddGroup adds a group to the master list
|
// 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 {
|
func (ml *MasterList) AddGroup(group *Group) bool {
|
||||||
return ml.MasterList.Add(group)
|
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 a group by ID
|
// GetGroup retrieves by ID (O(1))
|
||||||
func (ml *MasterList) GetGroup(groupID int32) *Group {
|
func (ml *MasterList) GetGroup(groupID int32) *Group {
|
||||||
return ml.MasterList.Get(groupID)
|
ml.mutex.RLock()
|
||||||
|
defer ml.mutex.RUnlock()
|
||||||
|
return ml.groups[groupID]
|
||||||
}
|
}
|
||||||
|
|
||||||
// RemoveGroup removes a group by ID
|
// 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 {
|
func (ml *MasterList) RemoveGroup(groupID int32) bool {
|
||||||
return ml.MasterList.Remove(groupID)
|
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
|
// GetAllGroups returns all groups as a slice
|
||||||
func (ml *MasterList) GetAllGroups() []*Group {
|
func (ml *MasterList) GetAllGroups() []*Group {
|
||||||
return ml.MasterList.GetAllSlice()
|
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
|
// GetGroupsByFilter returns groups matching the filter function
|
||||||
func (ml *MasterList) GetGroupsByFilter(filter func(*Group) bool) []*Group {
|
func (ml *MasterList) GetGroupsByFilter(filter func(*Group) bool) []*Group {
|
||||||
return ml.MasterList.Filter(filter)
|
ml.mutex.RLock()
|
||||||
}
|
defer ml.mutex.RUnlock()
|
||||||
|
|
||||||
// GetActiveGroups returns all non-disbanded groups
|
var result []*Group
|
||||||
func (ml *MasterList) GetActiveGroups() []*Group {
|
for _, group := range ml.groups {
|
||||||
return ml.GetGroupsByFilter(func(group *Group) bool {
|
if filter(group) {
|
||||||
return !group.IsDisbanded()
|
result = append(result, group)
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetGroupsByZone returns groups with members in the specified zone
|
|
||||||
func (ml *MasterList) GetGroupsByZone(zoneID int32) []*Group {
|
|
||||||
return ml.GetGroupsByFilter(func(group *Group) bool {
|
|
||||||
members := group.GetMembers()
|
|
||||||
for _, member := range members {
|
|
||||||
if member.ZoneID == zoneID {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return false
|
}
|
||||||
})
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupsBySize returns groups of the specified size
|
// 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 {
|
func (ml *MasterList) GetGroupsBySize(size int32) []*Group {
|
||||||
return ml.GetGroupsByFilter(func(group *Group) bool {
|
ml.mutex.RLock()
|
||||||
return group.GetSize() == size
|
defer ml.mutex.RUnlock()
|
||||||
})
|
return ml.bySize[size]
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetRaidGroups returns all groups that are part of raids
|
// GetRaidGroups returns all groups that are part of raids (O(1))
|
||||||
func (ml *MasterList) GetRaidGroups() []*Group {
|
func (ml *MasterList) GetRaidGroups() []*Group {
|
||||||
return ml.GetGroupsByFilter(func(group *Group) bool {
|
ml.mutex.RLock()
|
||||||
return group.IsGroupRaid()
|
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
|
// GetSoloGroups returns all groups with only one member (O(1))
|
||||||
func (ml *MasterList) GetSoloGroups() []*Group {
|
func (ml *MasterList) GetSoloGroups() []*Group {
|
||||||
return ml.GetGroupsBySize(1)
|
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
|
// GetFullGroups returns all groups at maximum capacity (O(1))
|
||||||
func (ml *MasterList) GetFullGroups() []*Group {
|
func (ml *MasterList) GetFullGroups() []*Group {
|
||||||
return ml.GetGroupsBySize(MAX_GROUP_SIZE)
|
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
|
// GetGroupsByLeader returns groups led by entities with the specified name
|
||||||
func (ml *MasterList) GetGroupsByLeader(leaderName string) []*Group {
|
func (ml *MasterList) GetGroupsByLeader(leaderName string) []*Group {
|
||||||
return ml.GetGroupsByFilter(func(group *Group) bool {
|
group := ml.GetGroupByLeader(leaderName)
|
||||||
return group.GetLeaderName() == leaderName
|
if group != nil {
|
||||||
})
|
return []*Group{group}
|
||||||
|
}
|
||||||
|
return []*Group{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupsByMember returns groups containing a member with the specified name
|
// GetGroupsByMember returns groups containing a member with the specified name
|
||||||
func (ml *MasterList) GetGroupsByMember(memberName string) []*Group {
|
func (ml *MasterList) GetGroupsByMember(memberName string) []*Group {
|
||||||
return ml.GetGroupsByFilter(func(group *Group) bool {
|
group := ml.GetGroupByMember(memberName)
|
||||||
members := group.GetMembers()
|
if group != nil {
|
||||||
for _, member := range members {
|
return []*Group{group}
|
||||||
if member.Name == memberName {
|
}
|
||||||
return true
|
return []*Group{}
|
||||||
}
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetGroupStatistics returns statistics about the groups in the master list
|
// 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 {
|
func (ml *MasterList) GetGroupStatistics() *GroupMasterListStats {
|
||||||
allGroups := ml.GetAllGroups()
|
ml.mutex.Lock() // Need write lock to potentially update cache
|
||||||
activeGroups := ml.GetActiveGroups()
|
defer ml.mutex.Unlock()
|
||||||
raidGroups := ml.GetRaidGroups()
|
|
||||||
|
ml.refreshMetaCache()
|
||||||
var totalMembers int32
|
|
||||||
var totalRaidMembers int32
|
var totalRaidMembers int32
|
||||||
|
for _, group := range ml.raidGroups {
|
||||||
for _, group := range activeGroups {
|
totalRaidMembers += group.GetSize()
|
||||||
totalMembers += group.GetSize()
|
|
||||||
if group.IsGroupRaid() {
|
|
||||||
totalRaidMembers += group.GetSize()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var averageGroupSize float64
|
var averageGroupSize float64
|
||||||
if len(activeGroups) > 0 {
|
if len(ml.activeGroups) > 0 {
|
||||||
averageGroupSize = float64(totalMembers) / float64(len(activeGroups))
|
averageGroupSize = float64(ml.totalMembers) / float64(len(ml.activeGroups))
|
||||||
}
|
}
|
||||||
|
|
||||||
return &GroupMasterListStats{
|
return &GroupMasterListStats{
|
||||||
TotalGroups: int32(len(allGroups)),
|
TotalGroups: int32(len(ml.groups)),
|
||||||
ActiveGroups: int32(len(activeGroups)),
|
ActiveGroups: int32(len(ml.activeGroups)),
|
||||||
RaidGroups: int32(len(raidGroups)),
|
RaidGroups: int32(len(ml.raidGroups)),
|
||||||
TotalMembers: totalMembers,
|
TotalMembers: ml.totalMembers,
|
||||||
TotalRaidMembers: totalRaidMembers,
|
TotalRaidMembers: totalRaidMembers,
|
||||||
AverageGroupSize: averageGroupSize,
|
AverageGroupSize: averageGroupSize,
|
||||||
SoloGroups: int32(len(ml.GetSoloGroups())),
|
SoloGroups: int32(len(ml.soloGroups)),
|
||||||
FullGroups: int32(len(ml.GetFullGroups())),
|
FullGroups: int32(len(ml.fullGroups)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GroupMasterListStats holds statistics about the groups master list
|
// GroupMasterListStats holds statistics about the groups master list
|
||||||
type GroupMasterListStats struct {
|
type GroupMasterListStats struct {
|
||||||
TotalGroups int32 `json:"total_groups"`
|
TotalGroups int32 `json:"total_groups"`
|
||||||
ActiveGroups int32 `json:"active_groups"`
|
ActiveGroups int32 `json:"active_groups"`
|
||||||
RaidGroups int32 `json:"raid_groups"`
|
RaidGroups int32 `json:"raid_groups"`
|
||||||
TotalMembers int32 `json:"total_members"`
|
TotalMembers int32 `json:"total_members"`
|
||||||
TotalRaidMembers int32 `json:"total_raid_members"`
|
TotalRaidMembers int32 `json:"total_raid_members"`
|
||||||
AverageGroupSize float64 `json:"average_group_size"`
|
AverageGroupSize float64 `json:"average_group_size"`
|
||||||
SoloGroups int32 `json:"solo_groups"`
|
SoloGroups int32 `json:"solo_groups"`
|
||||||
FullGroups int32 `json:"full_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
|
// Cleanup removes disbanded groups from the master list
|
||||||
func (ml *MasterList) Cleanup() int32 {
|
func (ml *MasterList) Cleanup() int32 {
|
||||||
disbandedGroups := ml.GetGroupsByFilter(func(group *Group) bool {
|
ml.mutex.Lock()
|
||||||
return group.IsDisbanded()
|
defer ml.mutex.Unlock()
|
||||||
})
|
|
||||||
|
|
||||||
removed := int32(0)
|
removed := int32(0)
|
||||||
for _, group := range disbandedGroups {
|
for id, group := range ml.groups {
|
||||||
if ml.RemoveGroup(group.GetID()) {
|
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++
|
removed++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if removed > 0 {
|
||||||
|
// Invalidate metadata cache
|
||||||
|
ml.metaStale = true
|
||||||
|
}
|
||||||
|
|
||||||
return removed
|
return removed
|
||||||
}
|
}
|
||||||
|
|
||||||
// ValidateAll validates all groups in the master list
|
// ValidateAll validates all groups in the master list
|
||||||
func (ml *MasterList) ValidateAll() []error {
|
func (ml *MasterList) ValidateAll() []error {
|
||||||
|
ml.mutex.RLock()
|
||||||
|
defer ml.mutex.RUnlock()
|
||||||
|
|
||||||
var errors []error
|
var errors []error
|
||||||
|
|
||||||
allGroups := ml.GetAllGroups()
|
for id, group := range ml.groups {
|
||||||
for _, group := range allGroups {
|
if group == nil {
|
||||||
|
errors = append(errors, fmt.Errorf("group ID %d is nil", id))
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
// Check for basic validity
|
// 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 {
|
if group.GetID() <= 0 {
|
||||||
errors = append(errors, fmt.Errorf("group %d has invalid ID", group.GetID()))
|
errors = append(errors, fmt.Errorf("group %d has invalid ID", group.GetID()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.GetSize() == 0 && !group.IsDisbanded() {
|
if group.GetSize() == 0 && !group.IsDisbanded() {
|
||||||
errors = append(errors, fmt.Errorf("group %d is empty but not disbanded", group.GetID()))
|
errors = append(errors, fmt.Errorf("group %d is empty but not disbanded", group.GetID()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if group.GetSize() > MAX_GROUP_SIZE {
|
if group.GetSize() > MAX_GROUP_SIZE {
|
||||||
errors = append(errors, fmt.Errorf("group %d exceeds maximum size (%d > %d)",
|
errors = append(errors, fmt.Errorf("group %d exceeds maximum size (%d > %d)",
|
||||||
group.GetID(), group.GetSize(), MAX_GROUP_SIZE))
|
group.GetID(), group.GetSize(), MAX_GROUP_SIZE))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check for leader
|
// Check for leader
|
||||||
members := group.GetMembers()
|
members := group.GetMembers()
|
||||||
hasLeader := false
|
hasLeader := false
|
||||||
leaderCount := 0
|
leaderCount := 0
|
||||||
|
|
||||||
for _, member := range members {
|
for _, member := range members {
|
||||||
if member.Leader {
|
if member.Leader {
|
||||||
hasLeader = true
|
hasLeader = true
|
||||||
leaderCount++
|
leaderCount++
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if !hasLeader && !group.IsDisbanded() {
|
if !hasLeader && !group.IsDisbanded() {
|
||||||
errors = append(errors, fmt.Errorf("group %d has no leader", group.GetID()))
|
errors = append(errors, fmt.Errorf("group %d has no leader", group.GetID()))
|
||||||
}
|
}
|
||||||
|
|
||||||
if leaderCount > 1 {
|
if leaderCount > 1 {
|
||||||
errors = append(errors, fmt.Errorf("group %d has multiple leaders (%d)", group.GetID(), leaderCount))
|
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
|
return errors
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsValid returns true if all groups are valid
|
||||||
|
func (ml *MasterList) IsValid() bool {
|
||||||
|
errors := ml.ValidateAll()
|
||||||
|
return len(errors) == 0
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user