340 lines
8.9 KiB
Go

package ground_spawn
import (
"sync"
)
// MasterList is a specialized ground spawn master list optimized for:
// - Fast zone-based lookups (O(1))
// - Fast skill-based lookups (O(1))
// - Spatial grid queries for proximity searches
// - Set intersection operations for complex queries
// - Fast state-based queries (available/depleted)
type MasterList struct {
// Core storage
spawns map[int32]*GroundSpawn // ID -> GroundSpawn
mutex sync.RWMutex
// Category indices for O(1) lookups
byZone map[int32][]*GroundSpawn // Zone ID -> spawns
bySkill map[string][]*GroundSpawn // Skill -> spawns
// State indices for O(1) filtering
availableSpawns []*GroundSpawn // Available spawns (cached)
depletedSpawns []*GroundSpawn // Depleted spawns (cached)
stateStale bool // Whether state caches need refresh
// Statistics cache
stats *Statistics
statsStale bool
// Spatial grid for proximity queries (grid size = 100 units)
spatialGrid map[gridKey][]*GroundSpawn
gridSize float32
}
// gridKey represents a spatial grid cell
type gridKey struct {
x, y int32
}
// NewMasterList creates a new specialized ground spawn master list
func NewMasterList() *MasterList {
return &MasterList{
spawns: make(map[int32]*GroundSpawn),
byZone: make(map[int32][]*GroundSpawn),
bySkill: make(map[string][]*GroundSpawn),
spatialGrid: make(map[gridKey][]*GroundSpawn),
gridSize: 100.0, // 100 unit grid cells
stateStale: true, // Initial state needs refresh
statsStale: true, // Initial stats need refresh
}
}
// getGridKey returns the grid cell for given coordinates
func (ml *MasterList) getGridKey(x, y float32) gridKey {
return gridKey{
x: int32(x / ml.gridSize),
y: int32(y / ml.gridSize),
}
}
// refreshStateCache updates the available/depleted spawn caches
func (ml *MasterList) refreshStateCache() {
if !ml.stateStale {
return
}
// Clear existing caches
ml.availableSpawns = ml.availableSpawns[:0]
ml.depletedSpawns = ml.depletedSpawns[:0]
// Rebuild caches
for _, gs := range ml.spawns {
if gs.IsAvailable() {
ml.availableSpawns = append(ml.availableSpawns, gs)
} else if gs.IsDepleted() {
ml.depletedSpawns = append(ml.depletedSpawns, gs)
}
}
ml.stateStale = false
}
// refreshStatsCache updates the statistics cache
func (ml *MasterList) refreshStatsCache() {
if !ml.statsStale {
return
}
var availableSpawns int
zoneMap := make(map[int32]int)
skillMap := make(map[string]int64)
// Single pass through all spawns
for _, gs := range ml.spawns {
if gs.IsAvailable() {
availableSpawns++
}
zoneMap[gs.ZoneID]++
skillMap[gs.CollectionSkill]++
}
ml.stats = &Statistics{
TotalHarvests: 0, // Would need to be tracked separately
SuccessfulHarvests: 0, // Would need to be tracked separately
RareItemsHarvested: 0, // Would need to be tracked separately
SkillUpsGenerated: 0, // Would need to be tracked separately
HarvestsBySkill: skillMap,
ActiveGroundSpawns: availableSpawns,
GroundSpawnsByZone: zoneMap,
}
ml.statsStale = false
}
// AddGroundSpawn adds a ground spawn with full indexing
func (ml *MasterList) AddGroundSpawn(gs *GroundSpawn) bool {
ml.mutex.Lock()
defer ml.mutex.Unlock()
// Check if exists
if _, exists := ml.spawns[gs.GroundSpawnID]; exists {
return false
}
// Add to core storage
ml.spawns[gs.GroundSpawnID] = gs
// Update zone index
ml.byZone[gs.ZoneID] = append(ml.byZone[gs.ZoneID], gs)
// Update skill index
ml.bySkill[gs.CollectionSkill] = append(ml.bySkill[gs.CollectionSkill], gs)
// Update spatial grid
gridKey := ml.getGridKey(gs.X, gs.Y)
ml.spatialGrid[gridKey] = append(ml.spatialGrid[gridKey], gs)
// Invalidate state and stats caches
ml.stateStale = true
ml.statsStale = true
return true
}
// GetGroundSpawn retrieves by ID (O(1))
func (ml *MasterList) GetGroundSpawn(id int32) *GroundSpawn {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
return ml.spawns[id]
}
// GetByZone returns all spawns in a zone (O(1))
func (ml *MasterList) GetByZone(zoneID int32) []*GroundSpawn {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
return ml.byZone[zoneID] // Return slice directly for performance
}
// GetBySkill returns all spawns for a skill (O(1))
func (ml *MasterList) GetBySkill(skill string) []*GroundSpawn {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
return ml.bySkill[skill] // Return slice directly for performance
}
// GetByZoneAndSkill returns spawns matching both zone and skill (set intersection)
func (ml *MasterList) GetByZoneAndSkill(zoneID int32, skill string) []*GroundSpawn {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
zoneSpawns := ml.byZone[zoneID]
skillSpawns := ml.bySkill[skill]
// Use smaller set for iteration efficiency
if len(zoneSpawns) > len(skillSpawns) {
zoneSpawns, skillSpawns = skillSpawns, zoneSpawns
}
// Set intersection using map lookup
skillSet := make(map[*GroundSpawn]struct{}, len(skillSpawns))
for _, gs := range skillSpawns {
skillSet[gs] = struct{}{}
}
var result []*GroundSpawn
for _, gs := range zoneSpawns {
if _, exists := skillSet[gs]; exists {
result = append(result, gs)
}
}
return result
}
// GetNearby returns spawns within radius of given coordinates using spatial grid
func (ml *MasterList) GetNearby(x, y float32, radius float32) []*GroundSpawn {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
// Calculate grid search bounds
minX := int32((x - radius) / ml.gridSize)
maxX := int32((x + radius) / ml.gridSize)
minY := int32((y - radius) / ml.gridSize)
maxY := int32((y + radius) / ml.gridSize)
var candidates []*GroundSpawn
// Check all grid cells in range
for gx := minX; gx <= maxX; gx++ {
for gy := minY; gy <= maxY; gy++ {
key := gridKey{x: gx, y: gy}
if spawns, exists := ml.spatialGrid[key]; exists {
candidates = append(candidates, spawns...)
}
}
}
// Filter by exact distance
radiusSquared := radius * radius
var result []*GroundSpawn
for _, gs := range candidates {
dx := gs.X - x
dy := gs.Y - y
if dx*dx+dy*dy <= radiusSquared {
result = append(result, gs)
}
}
return result
}
// GetAvailableSpawns returns harvestable spawns using cached results
func (ml *MasterList) GetAvailableSpawns() []*GroundSpawn {
ml.mutex.Lock() // Need write lock to potentially update cache
defer ml.mutex.Unlock()
ml.refreshStateCache()
// Return a copy to prevent external modification
result := make([]*GroundSpawn, len(ml.availableSpawns))
copy(result, ml.availableSpawns)
return result
}
// GetDepletedSpawns returns depleted spawns using cached results
func (ml *MasterList) GetDepletedSpawns() []*GroundSpawn {
ml.mutex.Lock() // Need write lock to potentially update cache
defer ml.mutex.Unlock()
ml.refreshStateCache()
// Return a copy to prevent external modification
result := make([]*GroundSpawn, len(ml.depletedSpawns))
copy(result, ml.depletedSpawns)
return result
}
// GetStatistics returns system statistics using cached results
func (ml *MasterList) GetStatistics() *Statistics {
ml.mutex.Lock() // Need write lock to potentially update cache
defer ml.mutex.Unlock()
ml.refreshStatsCache()
// Return a copy to prevent external modification
return &Statistics{
TotalHarvests: ml.stats.TotalHarvests,
SuccessfulHarvests: ml.stats.SuccessfulHarvests,
RareItemsHarvested: ml.stats.RareItemsHarvested,
SkillUpsGenerated: ml.stats.SkillUpsGenerated,
HarvestsBySkill: ml.stats.HarvestsBySkill,
ActiveGroundSpawns: ml.stats.ActiveGroundSpawns,
GroundSpawnsByZone: ml.stats.GroundSpawnsByZone,
}
}
// RemoveGroundSpawn removes a spawn and updates all indices
func (ml *MasterList) RemoveGroundSpawn(id int32) bool {
ml.mutex.Lock()
defer ml.mutex.Unlock()
gs, exists := ml.spawns[id]
if !exists {
return false
}
// Remove from core storage
delete(ml.spawns, id)
// Remove from zone index
zoneSpawns := ml.byZone[gs.ZoneID]
for i, spawn := range zoneSpawns {
if spawn.GroundSpawnID == id {
ml.byZone[gs.ZoneID] = append(zoneSpawns[:i], zoneSpawns[i+1:]...)
break
}
}
// Remove from skill index
skillSpawns := ml.bySkill[gs.CollectionSkill]
for i, spawn := range skillSpawns {
if spawn.GroundSpawnID == id {
ml.bySkill[gs.CollectionSkill] = append(skillSpawns[:i], skillSpawns[i+1:]...)
break
}
}
// Remove from spatial grid
gridKey := ml.getGridKey(gs.X, gs.Y)
gridSpawns := ml.spatialGrid[gridKey]
for i, spawn := range gridSpawns {
if spawn.GroundSpawnID == id {
ml.spatialGrid[gridKey] = append(gridSpawns[:i], gridSpawns[i+1:]...)
break
}
}
// Invalidate state and stats caches
ml.stateStale = true
ml.statsStale = true
return true
}
// Size returns the total number of spawns
func (ml *MasterList) Size() int {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
return len(ml.spawns)
}
// InvalidateStateCache marks the state and stats caches as stale
// Call this when spawn states change (e.g., after harvesting, respawning)
func (ml *MasterList) InvalidateStateCache() {
ml.mutex.Lock()
defer ml.mutex.Unlock()
ml.stateStale = true
ml.statsStale = true
}