11 KiB
Package Modernization Instructions
Goal
Transform legacy packages to use focused, performance-optimized bespoke master lists with simplified database operations. REMEMBER THAT THIS IS FOR A TOTAL REWRITE, NOT A VARIATION. If any step does not apply to a given package, it is okay to skip it and to focus on modernizing other areas of the package.
Steps
1. Create Bespoke MasterList Implementation
Replace generic implementations with specialized, high-performance master lists:
// Create domain-specific master list optimized for the use case
type MasterList struct {
// Core storage
items map[K]*Type // ID -> Type
mutex sync.RWMutex
// Specialized indices for O(1) lookups
byCategory map[string][]*Type // Category -> items
byProperty map[string][]*Type // Property -> items
// Cached metadata with invalidation
categories []string
metaStale bool
}
2. Consolidate Package Structure
Remove:
- Legacy wrapper functions (LoadAll, SaveAll, etc.)
- Duplicate database files (database.go, database_legacy.go)
- Generic MasterList dependencies
- README.md (use doc.go instead)
- Separate "active_record.go" files
Consolidate into:
{type}.go
- Main type with embedded database operationstypes.go
- Supporting types only (no main type)master.go
- Bespoke MasterList optimized for domainplayer.go
- Player-specific logic (if applicable)doc.go
- Primary documentation{type}_test.go
- Focused testsbenchmark_test.go
- Comprehensive performance tests
3. Refactor Main Type
Transform the main type to include database operations:
// Before: Separate type and database operations
type Achievement struct {
ID uint32
Title string
}
func LoadAchievement(db *database.Database, id uint32) (*Achievement, error)
func SaveAchievement(db *database.Database, a *Achievement) error
// After: Embedded database operations
type Achievement struct {
ID uint32
Title string
db *database.Database
isNew bool
}
func New(db *database.Database) *Achievement
func Load(db *database.Database, id uint32) (*Achievement, error)
func (a *Achievement) Save() error
func (a *Achievement) Delete() error
func (a *Achievement) Reload() error
4. Create Bespoke MasterList
Replace generic implementations with specialized, optimized master lists:
// Before: Generic base with limited optimization
type MasterList struct {
*common.MasterList[uint32, *Achievement]
}
// After: Bespoke implementation optimized for domain
type MasterList struct {
// Core storage
achievements map[uint32]*Achievement
mutex sync.RWMutex
// Domain-specific indices for O(1) lookups
byCategory map[string][]*Achievement
byExpansion map[string][]*Achievement
// Cached metadata with lazy loading
categories []string
expansions []string
metaStale bool
}
func (m *MasterList) AddAchievement(a *Achievement) bool {
m.mutex.Lock()
defer m.mutex.Unlock()
// Check existence
if _, exists := m.achievements[a.AchievementID]; exists {
return false
}
// Add to core storage
m.achievements[a.AchievementID] = a
// Update specialized indices
m.byCategory[a.Category] = append(m.byCategory[a.Category], a)
m.byExpansion[a.Expansion] = append(m.byExpansion[a.Expansion], a)
// Invalidate metadata cache
m.metaStale = true
return true
}
// O(1) category lookup
func (m *MasterList) GetByCategory(category string) []*Achievement {
m.mutex.RLock()
defer m.mutex.RUnlock()
return m.byCategory[category]
}
5. Optimize for Domain Use Cases
Focus on the specific operations your domain needs:
// Implement GetID() for consistency
func (a *Achievement) GetID() uint32 {
return a.AchievementID
}
// Add domain-specific optimized methods
func (m *MasterList) GetByCategoryAndExpansion(category, expansion string) []*Achievement {
// Use set intersection for efficient combined queries
categoryItems := m.byCategory[category]
expansionItems := m.byExpansion[expansion]
// Use smaller set for iteration efficiency
if len(categoryItems) > len(expansionItems) {
categoryItems, expansionItems = expansionItems, categoryItems
}
// Set intersection using map lookup
expansionSet := make(map[*Achievement]struct{}, len(expansionItems))
for _, item := range expansionItems {
expansionSet[item] = struct{}{}
}
var result []*Achievement
for _, item := range categoryItems {
if _, exists := expansionSet[item]; exists {
result = append(result, item)
}
}
return result
}
6. Simplify API
Remove:
- Generic MasterList dependencies
- Legacy type variants (Achievement vs AchievementRecord)
- Conversion methods (ToLegacy, FromLegacy)
- Duplicate CRUD operations
- Complex wrapper functions
- Slow O(n) filter operations
Keep:
- Single type definition
- Direct database methods on type
- Domain-specific optimized operations
- O(1) indexed lookups where possible
- Lazy caching for expensive operations
7. Update Documentation
Create concise doc.go
:
// Package achievements provides [brief description].
//
// Basic Usage:
//
// achievement := achievements.New(db)
// achievement.Title = "Dragon Slayer"
// achievement.Save()
//
// loaded, _ := achievements.Load(db, 1001)
// loaded.Delete()
//
// Bespoke Master List (optimized for performance):
//
// masterList := achievements.NewMasterList()
// masterList.AddAchievement(achievement)
//
// // O(1) category lookup
// combatAchievements := masterList.GetByCategory("Combat")
//
// // O(1) expansion lookup
// classicAchievements := masterList.GetByExpansion("Classic")
//
// // Optimized set intersection
// combined := masterList.GetByCategoryAndExpansion("Combat", "Classic")
package achievements
8. Testing
Create focused tests:
- Test core type operations (New, Save, Load, Delete)
- Test MasterList basic operations
- Remove legacy compatibility tests
- Keep tests simple and direct
Key Principles
- Single Source of Truth: One type definition, not multiple variants
- Embedded Operations: Database methods on the type itself
- Bespoke Master Lists: Domain-specific optimized implementations
- Performance First: O(1) lookups, lazy caching, efficient algorithms
- Thread Safety: Proper RWMutex usage with minimal lock contention
- No Legacy Baggage: Remove all "Legacy" types and converters
- Documentation in Code: Use doc.go, not README.md
- Comprehensive Benchmarking: Measure and optimize all operations
Migration Checklist
- Identify main type and supporting types
- Consolidate database operations into main type
- Add db field and methods to main type
- Create bespoke MasterList optimized for domain
- Add specialized indices for O(1) lookups
- Implement lazy caching for expensive operations
- Add set intersection algorithms for combined queries
- Implement GetID() for consistency
- Remove all generic MasterList dependencies
- Remove all legacy types and converters
- Update doc.go with performance-focused examples
- Simplify tests to cover core functionality
- Add concurrency tests for thread safety
- Create comprehensive benchmarks (benchmark_test.go)
- Verify performance meets targets (sub-microsecond operations)
- Run
go test -race
to verify thread safety - Run
go fmt
,go test
, andgo test -bench=.
Expected Results
- 80% less code in most packages
- Single type instead of multiple variants
- Thread-safe operations with optimized locking
- O(1) performance for common lookup operations
- Sub-microsecond response times for most operations
- Specialized algorithms for domain-specific queries
- Consistent API across all packages
- Better maintainability with focused, understandable code
- Performance predictability through comprehensive benchmarking
Benchmarking
After modernization, create comprehensive benchmarks to measure performance improvements and ensure no regressions:
Required Benchmarks
Core Operations:
func BenchmarkTypeCreation(b *testing.B) // New type creation
func BenchmarkTypeOperations(b *testing.B) // CRUD operations
func BenchmarkMasterListOperations(b *testing.B) // Collection operations
Performance Critical Paths:
func BenchmarkCoreAlgorithm(b *testing.B) // Main business logic
func BenchmarkConcurrentAccess(b *testing.B) // Thread safety
func BenchmarkMemoryAllocation(b *testing.B) // Memory patterns
Comparison Benchmarks:
func BenchmarkComparisonWithOldSystem(b *testing.B) // Before/after metrics
Benchmark Structure
Use sub-benchmarks for detailed measurements:
func BenchmarkMasterListOperations(b *testing.B) {
ml := NewMasterList()
// Setup data...
b.Run("Add", func(b *testing.B) {
for i := 0; i < b.N; i++ {
ml.Add(createTestItem(i))
}
})
b.Run("Get", func(b *testing.B) {
b.RunParallel(func(pb *testing.PB) {
for pb.Next() {
_ = ml.Get(randomID())
}
})
})
}
Mock Implementations
Create lightweight mocks for interfaces:
type mockPlayer struct {
level int16
location int32
}
func (p *mockPlayer) GetLevel() int16 { return p.level }
func (p *mockPlayer) GetLocation() int32 { return p.location }
Performance Expectations
Target performance after bespoke modernization:
- Creation operations: <100ns per operation
- ID lookup operations: <50ns per operation (O(1) map access)
- Indexed lookup operations: <100ns per operation (O(1) specialized indices)
- Combined queries: <5µs per operation (optimized set intersection)
- Cached metadata: <100ns per operation (lazy loading)
- Memory allocations: Zero allocations for read operations
- Concurrent access: Linear scaling with cores, minimal lock contention
- Specialized algorithms: Domain-optimized performance characteristics
Running Benchmarks
# Run all benchmarks
go test -bench=. ./internal/package_name
# Detailed benchmarks with memory stats
go test -bench=. -benchmem ./internal/package_name
# Compare performance over time
go test -bench=. -count=5 ./internal/package_name
# CPU profiling for optimization
go test -bench=BenchmarkCoreAlgorithm -cpuprofile=cpu.prof ./internal/package_name
Example Commands
# Remove legacy files and generic dependencies
rm README.md database_legacy.go active_record.go
# Remove generic MasterList imports
grep -r "eq2emu/internal/common" . --include="*.go" | cut -d: -f1 | sort -u | xargs sed -i '/eq2emu\/internal\/common/d'
# Rename if needed
mv active_record.go achievement.go
# Test the changes
go fmt ./...
go test ./...
go test -race ./... # Verify thread safety
go test -bench=. ./...
go build ./...
# Performance verification
go test -bench=BenchmarkMasterListOperations -benchtime=5s ./internal/package_name
go test -bench=. -benchmem ./internal/package_name