eq2go/internal/common/master_list.go

312 lines
8.1 KiB
Go

// Package common provides shared utilities and patterns used across multiple game systems.
//
// The MasterList type provides a generic, thread-safe collection management pattern
// that is used extensively throughout the EQ2Go server implementation for managing
// game entities like items, spells, spawns, achievements, etc.
package common
import (
"fmt"
"maps"
"sync"
)
// Identifiable represents any type that can be identified by a key
type Identifiable[K comparable] interface {
GetID() K
}
// MasterList provides a generic, thread-safe collection for managing game entities.
// It implements the common pattern used across all EQ2Go master lists with consistent
// CRUD operations, bulk operations, and thread safety.
//
// K is the key type (typically int32, uint32, or string)
// V is the value type (must implement Identifiable[K])
type MasterList[K comparable, V Identifiable[K]] struct {
items map[K]V
mutex sync.RWMutex
}
// NewMasterList creates a new master list instance
func NewMasterList[K comparable, V Identifiable[K]]() *MasterList[K, V] {
return &MasterList[K, V]{
items: make(map[K]V),
}
}
// Add adds an item to the master list. Returns true if added, false if it already exists.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Add(item V) bool {
ml.mutex.Lock()
defer ml.mutex.Unlock()
id := item.GetID()
if _, exists := ml.items[id]; exists {
return false
}
ml.items[id] = item
return true
}
// AddOrUpdate adds an item to the master list or updates it if it already exists.
// Always returns true. Thread-safe for concurrent access.
func (ml *MasterList[K, V]) AddOrUpdate(item V) bool {
ml.mutex.Lock()
defer ml.mutex.Unlock()
id := item.GetID()
ml.items[id] = item
return true
}
// Get retrieves an item by its ID. Returns the zero value of V if not found.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Get(id K) V {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
return ml.items[id]
}
// GetSafe retrieves an item by its ID with existence check.
// Returns the item and true if found, zero value and false if not found.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) GetSafe(id K) (V, bool) {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
item, exists := ml.items[id]
return item, exists
}
// Exists checks if an item with the given ID exists in the list.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Exists(id K) bool {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
_, exists := ml.items[id]
return exists
}
// Remove removes an item by its ID. Returns true if removed, false if not found.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Remove(id K) bool {
ml.mutex.Lock()
defer ml.mutex.Unlock()
if _, exists := ml.items[id]; !exists {
return false
}
delete(ml.items, id)
return true
}
// Update updates an existing item. Returns error if the item doesn't exist.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Update(item V) error {
ml.mutex.Lock()
defer ml.mutex.Unlock()
id := item.GetID()
if _, exists := ml.items[id]; !exists {
return fmt.Errorf("item with ID %v not found", id)
}
ml.items[id] = item
return nil
}
// Size returns the number of items in the list.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Size() int {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
return len(ml.items)
}
// IsEmpty returns true if the list contains no items.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) IsEmpty() bool {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
return len(ml.items) == 0
}
// Clear removes all items from the list.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Clear() {
ml.mutex.Lock()
defer ml.mutex.Unlock()
// Create new map to ensure memory is freed
ml.items = make(map[K]V)
}
// GetAll returns a copy of all items in the list.
// The returned map is safe to modify without affecting the master list.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) GetAll() map[K]V {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
result := make(map[K]V, len(ml.items))
maps.Copy(result, ml.items)
return result
}
// GetAllSlice returns a slice containing all items in the list.
// The returned slice is safe to modify without affecting the master list.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) GetAllSlice() []V {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
result := make([]V, 0, len(ml.items))
for _, v := range ml.items {
result = append(result, v)
}
return result
}
// GetAllIDs returns a slice containing all IDs in the list.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) GetAllIDs() []K {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
result := make([]K, 0, len(ml.items))
for k := range ml.items {
result = append(result, k)
}
return result
}
// ForEach executes a function for each item in the list.
// The function receives a copy of each item, so modifications won't affect the list.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) ForEach(fn func(K, V)) {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
for k, v := range ml.items {
fn(k, v)
}
}
// Filter returns a new slice containing items that match the predicate function.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Filter(predicate func(V) bool) []V {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
// Pre-allocate with estimated capacity to reduce allocations
result := make([]V, 0, len(ml.items)/4) // Assume ~25% match rate
for _, v := range ml.items {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// Find returns the first item that matches the predicate function.
// Returns zero value and false if no match is found.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Find(predicate func(V) bool) (V, bool) {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
for _, v := range ml.items {
if predicate(v) {
return v, true
}
}
var zero V
return zero, false
}
// Count returns the number of items that match the predicate function.
// Thread-safe for concurrent access.
func (ml *MasterList[K, V]) Count(predicate func(V) bool) int {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
count := 0
for _, v := range ml.items {
if predicate(v) {
count++
}
}
return count
}
// WithReadLock executes a function while holding a read lock on the list.
// Use this for complex operations that need consistent read access to multiple items.
func (ml *MasterList[K, V]) WithReadLock(fn func(map[K]V)) {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
fn(ml.items)
}
// WithWriteLock executes a function while holding a write lock on the list.
// Use this for complex operations that need to modify multiple items atomically.
func (ml *MasterList[K, V]) WithWriteLock(fn func(map[K]V)) {
ml.mutex.Lock()
defer ml.mutex.Unlock()
fn(ml.items)
}
// FilterWithCapacity returns items matching predicate with pre-allocated capacity.
// Use when you have a good estimate of result size to optimize allocations.
func (ml *MasterList[K, V]) FilterWithCapacity(predicate func(V) bool, expectedSize int) []V {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
result := make([]V, 0, expectedSize)
for _, v := range ml.items {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// FilterInto appends matching items to the provided slice, avoiding new allocations.
// Returns the updated slice. Use this for repeated filtering to reuse memory.
func (ml *MasterList[K, V]) FilterInto(predicate func(V) bool, result []V) []V {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
// Clear the slice but keep capacity
result = result[:0]
for _, v := range ml.items {
if predicate(v) {
result = append(result, v)
}
}
return result
}
// CountAndFilter performs both count and filter in a single pass.
// More efficient than calling Count() and Filter() separately.
func (ml *MasterList[K, V]) CountAndFilter(predicate func(V) bool) (int, []V) {
ml.mutex.RLock()
defer ml.mutex.RUnlock()
count := 0
result := make([]V, 0, len(ml.items)/4)
for _, v := range ml.items {
if predicate(v) {
count++
result = append(result, v)
}
}
return count, result
}