eq2go/internal/ground_spawn/benchmark_test.go

430 lines
10 KiB
Go

package ground_spawn
import (
"fmt"
"math/rand"
"testing"
"eq2emu/internal/database"
)
// Mock implementations for benchmarking
// mockPlayer implements Player interface for benchmarks
type mockPlayer struct {
level int16
location int32
name string
}
func (p *mockPlayer) GetLevel() int16 { return p.level }
func (p *mockPlayer) GetLocation() int32 { return p.location }
func (p *mockPlayer) GetName() string { return p.name }
// mockSkill implements Skill interface for benchmarks
type mockSkill struct {
current int16
max int16
}
func (s *mockSkill) GetCurrentValue() int16 { return s.current }
func (s *mockSkill) GetMaxValue() int16 { return s.max }
// createTestGroundSpawn creates a ground spawn for benchmarking
func createTestGroundSpawn(b *testing.B, id int32) *GroundSpawn {
b.Helper()
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
b.Fatalf("Failed to create test database: %v", err)
}
gs := New(db)
gs.GroundSpawnID = id
gs.Name = fmt.Sprintf("Benchmark Node %d", id)
gs.CollectionSkill = "Mining"
gs.NumberHarvests = 5
gs.AttemptsPerHarvest = 1
gs.CurrentHarvests = 5
gs.X, gs.Y, gs.Z = float32(rand.Intn(1000)), float32(rand.Intn(1000)), float32(rand.Intn(1000))
gs.ZoneID = int32(rand.Intn(10) + 1)
gs.GridID = int32(rand.Intn(100))
// Add mock harvest entries for realistic benchmarking
gs.HarvestEntries = []*HarvestEntry{
{
GroundSpawnID: id,
MinSkillLevel: 10,
MinAdventureLevel: 1,
BonusTable: false,
Harvest1: 80.0,
Harvest3: 20.0,
Harvest5: 10.0,
HarvestImbue: 5.0,
HarvestRare: 2.0,
Harvest10: 1.0,
},
{
GroundSpawnID: id,
MinSkillLevel: 50,
MinAdventureLevel: 10,
BonusTable: true,
Harvest1: 90.0,
Harvest3: 30.0,
Harvest5: 15.0,
HarvestImbue: 8.0,
HarvestRare: 5.0,
Harvest10: 2.0,
},
}
// Add mock harvest items
gs.HarvestItems = []*HarvestEntryItem{
{GroundSpawnID: id, ItemID: 1001, IsRare: ItemRarityNormal, GridID: 0, Quantity: 1},
{GroundSpawnID: id, ItemID: 1002, IsRare: ItemRarityNormal, GridID: 0, Quantity: 1},
{GroundSpawnID: id, ItemID: 1003, IsRare: ItemRarityRare, GridID: 0, Quantity: 1},
{GroundSpawnID: id, ItemID: 1004, IsRare: ItemRarityImbue, GridID: 0, Quantity: 1},
}
return gs
}
// BenchmarkGroundSpawnCreation measures ground spawn creation performance
func BenchmarkGroundSpawnCreation(b *testing.B) {
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
b.Fatalf("Failed to create test database: %v", err)
}
defer db.Close()
b.ResetTimer()
b.Run("Sequential", func(b *testing.B) {
for i := 0; i < b.N; i++ {
gs := New(db)
gs.GroundSpawnID = int32(i)
gs.Name = fmt.Sprintf("Node %d", i)
_ = gs
}
})
b.Run("Parallel", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
id := int32(0)
for pb.Next() {
gs := New(db)
gs.GroundSpawnID = id
gs.Name = fmt.Sprintf("Node %d", id)
id++
_ = gs
}
})
})
}
// BenchmarkGroundSpawnState measures state operations
func BenchmarkGroundSpawnState(b *testing.B) {
gs := createTestGroundSpawn(b, 1001)
b.Run("IsAvailable", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = gs.IsAvailable()
}
})
})
b.Run("IsDepleted", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = gs.IsDepleted()
}
})
})
b.Run("GetHarvestMessageName", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = gs.GetHarvestMessageName(true, false)
}
})
})
b.Run("GetHarvestSpellType", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = gs.GetHarvestSpellType()
}
})
})
}
// BenchmarkHarvestAlgorithm measures the core harvest processing performance
func BenchmarkHarvestAlgorithm(b *testing.B) {
gs := createTestGroundSpawn(b, 1001)
player := &mockPlayer{level: 50, location: 1, name: "BenchmarkPlayer"}
skill := &mockSkill{current: 75, max: 100}
b.Run("ProcessHarvest", func(b *testing.B) {
for i := 0; i < b.N; i++ {
// Reset harvest count for consistent benchmarking
gs.CurrentHarvests = gs.NumberHarvests
_, err := gs.ProcessHarvest(player, skill, 75)
if err != nil {
b.Fatalf("Harvest failed: %v", err)
}
}
})
b.Run("FilterHarvestTables", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = gs.filterHarvestTables(player, 75)
}
})
b.Run("SelectHarvestTable", func(b *testing.B) {
tables := gs.filterHarvestTables(player, 75)
for i := 0; i < b.N; i++ {
_ = gs.selectHarvestTable(tables, 75)
}
})
b.Run("DetermineHarvestType", func(b *testing.B) {
tables := gs.filterHarvestTables(player, 75)
table := gs.selectHarvestTable(tables, 75)
for i := 0; i < b.N; i++ {
_ = gs.determineHarvestType(table, false)
}
})
b.Run("AwardHarvestItems", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = gs.awardHarvestItems(HarvestType3Items, player)
}
})
}
// BenchmarkMasterListOperations measures master list performance
func BenchmarkMasterListOperations(b *testing.B) {
ml := NewMasterList()
// Pre-populate with ground spawns for realistic testing
const numSpawns = 1000
spawns := make([]*GroundSpawn, numSpawns)
b.StopTimer()
for i := 0; i < numSpawns; i++ {
spawns[i] = createTestGroundSpawn(b, int32(i+1))
spawns[i].ZoneID = int32(i%10 + 1) // Distribute across 10 zones
spawns[i].CollectionSkill = []string{"Mining", "Gathering", "Fishing", "Trapping"}[i%4]
ml.AddGroundSpawn(spawns[i])
}
b.StartTimer()
b.Run("GetGroundSpawn", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
id := int32((rand.Intn(numSpawns) + 1))
_ = ml.GetGroundSpawn(id)
}
})
})
b.Run("AddGroundSpawn", func(b *testing.B) {
startID := int32(numSpawns + 1)
b.ResetTimer()
for i := 0; i < b.N; i++ {
gs := createTestGroundSpawn(b, startID+int32(i))
ml.AddGroundSpawn(gs)
}
})
b.Run("GetByZone", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
zoneID := int32(rand.Intn(10) + 1)
_ = ml.GetByZone(zoneID)
}
})
})
b.Run("GetBySkill", func(b *testing.B) {
skills := []string{"Mining", "Gathering", "Fishing", "Trapping"}
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
skill := skills[rand.Intn(len(skills))]
_ = ml.GetBySkill(skill)
}
})
})
b.Run("GetAvailableSpawns", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ml.GetAvailableSpawns()
}
})
b.Run("GetDepletedSpawns", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ml.GetDepletedSpawns()
}
})
b.Run("GetStatistics", func(b *testing.B) {
for i := 0; i < b.N; i++ {
_ = ml.GetStatistics()
}
})
}
// BenchmarkConcurrentHarvesting measures concurrent harvest performance
func BenchmarkConcurrentHarvesting(b *testing.B) {
const numSpawns = 100
spawns := make([]*GroundSpawn, numSpawns)
for i := 0; i < numSpawns; i++ {
spawns[i] = createTestGroundSpawn(b, int32(i+1))
}
player := &mockPlayer{level: 50, location: 1, name: "BenchmarkPlayer"}
skill := &mockSkill{current: 75, max: 100}
b.Run("ConcurrentHarvesting", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
spawnIdx := rand.Intn(numSpawns)
gs := spawns[spawnIdx]
// Reset if depleted
if gs.IsDepleted() {
gs.Respawn()
}
_, _ = gs.ProcessHarvest(player, skill, 75)
}
})
})
}
// BenchmarkMemoryAllocation measures memory allocation patterns
func BenchmarkMemoryAllocation(b *testing.B) {
db, err := database.NewSQLite("file::memory:?mode=memory&cache=shared")
if err != nil {
b.Fatalf("Failed to create test database: %v", err)
}
defer db.Close()
b.Run("GroundSpawnAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
gs := New(db)
gs.GroundSpawnID = int32(i)
gs.HarvestEntries = make([]*HarvestEntry, 2)
gs.HarvestItems = make([]*HarvestEntryItem, 4)
_ = gs
}
})
b.Run("MasterListAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
ml := NewMasterList()
_ = ml
}
})
b.Run("HarvestResultAllocation", func(b *testing.B) {
b.ReportAllocs()
for i := 0; i < b.N; i++ {
result := &HarvestResult{
Success: true,
HarvestType: HarvestType3Items,
ItemsAwarded: make([]*HarvestedItem, 3),
MessageText: "You harvested 3 items",
SkillGained: false,
}
_ = result
}
})
}
// BenchmarkRespawnOperations measures respawn performance
func BenchmarkRespawnOperations(b *testing.B) {
gs := createTestGroundSpawn(b, 1001)
b.Run("Respawn", func(b *testing.B) {
for i := 0; i < b.N; i++ {
gs.CurrentHarvests = 0 // Deplete
gs.Respawn()
}
})
b.Run("RespawnWithRandomHeading", func(b *testing.B) {
gs.RandomizeHeading = true
for i := 0; i < b.N; i++ {
gs.CurrentHarvests = 0 // Deplete
gs.Respawn()
}
})
}
// BenchmarkStringOperations measures string processing performance
func BenchmarkStringOperations(b *testing.B) {
skills := []string{"Mining", "Gathering", "Collecting", "Fishing", "Trapping", "Foresting", "Unknown"}
b.Run("HarvestMessageGeneration", func(b *testing.B) {
gs := createTestGroundSpawn(b, 1001)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
skill := skills[rand.Intn(len(skills))]
gs.CollectionSkill = skill
_ = gs.GetHarvestMessageName(rand.Intn(2) == 1, rand.Intn(2) == 1)
}
})
})
b.Run("SpellTypeGeneration", func(b *testing.B) {
gs := createTestGroundSpawn(b, 1001)
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
skill := skills[rand.Intn(len(skills))]
gs.CollectionSkill = skill
_ = gs.GetHarvestSpellType()
}
})
})
}
// BenchmarkComparisonWithOldSystem provides comparison benchmarks
// These would help measure the performance improvement from modernization
func BenchmarkComparisonWithOldSystem(b *testing.B) {
ml := NewMasterList()
const numSpawns = 1000
// Setup
b.StopTimer()
for i := 0; i < numSpawns; i++ {
gs := createTestGroundSpawn(b, int32(i+1))
ml.AddGroundSpawn(gs)
}
b.StartTimer()
b.Run("ModernizedLookup", func(b *testing.B) {
// Modern generic-based lookup
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
id := int32(rand.Intn(numSpawns) + 1)
_ = ml.GetGroundSpawn(id)
}
})
})
b.Run("ModernizedFiltering", func(b *testing.B) {
// Modern filter-based operations
for i := 0; i < b.N; i++ {
_ = ml.GetAvailableSpawns()
}
})
}