enhance basestore with common operations, simplify monsters
This commit is contained in:
parent
c2eeaa2f42
commit
21acb38157
@ -3,8 +3,6 @@ package monsters
|
|||||||
import (
|
import (
|
||||||
"dk/internal/store"
|
"dk/internal/store"
|
||||||
"fmt"
|
"fmt"
|
||||||
"sort"
|
|
||||||
"sync"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
// Monster represents a monster in the game
|
// Monster represents a monster in the game
|
||||||
@ -21,14 +19,11 @@ type Monster struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monster) Save() error {
|
func (m *Monster) Save() error {
|
||||||
monsterStore := GetStore()
|
return GetStore().UpdateWithRebuild(m.ID, m)
|
||||||
monsterStore.UpdateMonster(m)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *Monster) Delete() error {
|
func (m *Monster) Delete() error {
|
||||||
monsterStore := GetStore()
|
GetStore().RemoveWithRebuild(m.ID)
|
||||||
monsterStore.RemoveMonster(m.ID)
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -36,13 +31,13 @@ func (m *Monster) Delete() error {
|
|||||||
func New() *Monster {
|
func New() *Monster {
|
||||||
return &Monster{
|
return &Monster{
|
||||||
Name: "",
|
Name: "",
|
||||||
MaxHP: 10, // Default HP
|
MaxHP: 10,
|
||||||
MaxDmg: 5, // Default damage
|
MaxDmg: 5,
|
||||||
Armor: 0, // Default armor
|
Armor: 0,
|
||||||
Level: 1, // Default level
|
Level: 1,
|
||||||
MaxExp: 10, // Default exp reward
|
MaxExp: 10,
|
||||||
MaxGold: 5, // Default gold reward
|
MaxGold: 5,
|
||||||
Immune: ImmuneNone, // No immunity by default
|
Immune: ImmuneNone,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -63,279 +58,122 @@ func (m *Monster) Validate() error {
|
|||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Immunity constants for monster immunity types
|
// Immunity constants
|
||||||
const (
|
const (
|
||||||
ImmuneNone = 0
|
ImmuneNone = 0
|
||||||
ImmuneHurt = 1 // Immune to Hurt spells
|
ImmuneHurt = 1
|
||||||
ImmuneSleep = 2 // Immune to Sleep spells
|
ImmuneSleep = 2
|
||||||
)
|
)
|
||||||
|
|
||||||
// MonsterStore provides in-memory storage with O(1) lookups and monster-specific indices
|
// MonsterStore with enhanced BaseStore
|
||||||
type MonsterStore struct {
|
type MonsterStore struct {
|
||||||
*store.BaseStore[Monster] // Embedded generic store
|
*store.BaseStore[Monster]
|
||||||
byLevel map[int][]int // Level -> []ID
|
|
||||||
byImmunity map[int][]int // Immunity -> []ID
|
|
||||||
allByLevel []int // All IDs sorted by level, then ID
|
|
||||||
mu sync.RWMutex // Protects indices
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Global in-memory store
|
// Global store with singleton pattern
|
||||||
var monsterStore *MonsterStore
|
var GetStore = store.NewSingleton(func() *MonsterStore {
|
||||||
var storeOnce sync.Once
|
ms := &MonsterStore{BaseStore: store.NewBaseStore[Monster]()}
|
||||||
|
|
||||||
// Initialize the in-memory store
|
// Register indices
|
||||||
func initStore() {
|
ms.RegisterIndex("byLevel", store.BuildIntGroupIndex(func(m *Monster) int {
|
||||||
monsterStore = &MonsterStore{
|
return m.Level
|
||||||
BaseStore: store.NewBaseStore[Monster](),
|
}))
|
||||||
byLevel: make(map[int][]int),
|
|
||||||
byImmunity: make(map[int][]int),
|
ms.RegisterIndex("byImmunity", store.BuildIntGroupIndex(func(m *Monster) int {
|
||||||
allByLevel: make([]int, 0),
|
return m.Immune
|
||||||
}
|
}))
|
||||||
|
|
||||||
|
ms.RegisterIndex("allByLevel", store.BuildSortedListIndex(func(a, b *Monster) bool {
|
||||||
|
if a.Level == b.Level {
|
||||||
|
return a.ID < b.ID
|
||||||
|
}
|
||||||
|
return a.Level < b.Level
|
||||||
|
}))
|
||||||
|
|
||||||
|
return ms
|
||||||
|
})
|
||||||
|
|
||||||
|
// Enhanced CRUD operations
|
||||||
|
func (ms *MonsterStore) AddMonster(monster *Monster) error {
|
||||||
|
return ms.AddWithRebuild(monster.ID, monster)
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetStore returns the global monster store
|
|
||||||
func GetStore() *MonsterStore {
|
|
||||||
storeOnce.Do(initStore)
|
|
||||||
return monsterStore
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddMonster adds a monster to the in-memory store and updates all indices
|
|
||||||
func (ms *MonsterStore) AddMonster(monster *Monster) {
|
|
||||||
ms.mu.Lock()
|
|
||||||
defer ms.mu.Unlock()
|
|
||||||
|
|
||||||
// Validate monster
|
|
||||||
if err := monster.Validate(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add to base store
|
|
||||||
ms.Add(monster.ID, monster)
|
|
||||||
|
|
||||||
// Rebuild indices
|
|
||||||
ms.rebuildIndicesUnsafe()
|
|
||||||
}
|
|
||||||
|
|
||||||
// RemoveMonster removes a monster from the store and updates indices
|
|
||||||
func (ms *MonsterStore) RemoveMonster(id int) {
|
func (ms *MonsterStore) RemoveMonster(id int) {
|
||||||
ms.mu.Lock()
|
ms.RemoveWithRebuild(id)
|
||||||
defer ms.mu.Unlock()
|
|
||||||
|
|
||||||
// Remove from base store
|
|
||||||
ms.Remove(id)
|
|
||||||
|
|
||||||
// Rebuild indices
|
|
||||||
ms.rebuildIndicesUnsafe()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// UpdateMonster updates a monster efficiently
|
func (ms *MonsterStore) UpdateMonster(monster *Monster) error {
|
||||||
func (ms *MonsterStore) UpdateMonster(monster *Monster) {
|
return ms.UpdateWithRebuild(monster.ID, monster)
|
||||||
ms.mu.Lock()
|
|
||||||
defer ms.mu.Unlock()
|
|
||||||
|
|
||||||
// Validate monster
|
|
||||||
if err := monster.Validate(); err != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
// Update base store
|
|
||||||
ms.Add(monster.ID, monster)
|
|
||||||
|
|
||||||
// Rebuild indices
|
|
||||||
ms.rebuildIndicesUnsafe()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// LoadData loads monster data from JSON file, or starts with empty store
|
// Data persistence
|
||||||
func LoadData(dataPath string) error {
|
func LoadData(dataPath string) error {
|
||||||
ms := GetStore()
|
ms := GetStore()
|
||||||
|
return ms.BaseStore.LoadData(dataPath)
|
||||||
// Load from base store, which handles JSON loading
|
|
||||||
if err := ms.BaseStore.LoadData(dataPath); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Rebuild indices from loaded data
|
|
||||||
ms.rebuildIndices()
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// SaveData saves monster data to JSON file
|
|
||||||
func SaveData(dataPath string) error {
|
func SaveData(dataPath string) error {
|
||||||
ms := GetStore()
|
ms := GetStore()
|
||||||
return ms.BaseStore.SaveData(dataPath)
|
return ms.BaseStore.SaveData(dataPath)
|
||||||
}
|
}
|
||||||
|
|
||||||
// rebuildIndicesUnsafe rebuilds all indices from base store data (caller must hold lock)
|
// Query functions using enhanced store
|
||||||
func (ms *MonsterStore) rebuildIndicesUnsafe() {
|
|
||||||
// Clear indices
|
|
||||||
ms.byLevel = make(map[int][]int)
|
|
||||||
ms.byImmunity = make(map[int][]int)
|
|
||||||
ms.allByLevel = make([]int, 0)
|
|
||||||
|
|
||||||
// Collect all monsters and build indices
|
|
||||||
allMonsters := ms.GetAll()
|
|
||||||
|
|
||||||
// Build level and immunity indices
|
|
||||||
for id, monster := range allMonsters {
|
|
||||||
ms.byLevel[monster.Level] = append(ms.byLevel[monster.Level], id)
|
|
||||||
ms.byImmunity[monster.Immune] = append(ms.byImmunity[monster.Immune], id)
|
|
||||||
ms.allByLevel = append(ms.allByLevel, id)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort allByLevel by level first, then by ID
|
|
||||||
sort.Slice(ms.allByLevel, func(i, j int) bool {
|
|
||||||
monsterI, _ := ms.GetByID(ms.allByLevel[i])
|
|
||||||
monsterJ, _ := ms.GetByID(ms.allByLevel[j])
|
|
||||||
if monsterI.Level == monsterJ.Level {
|
|
||||||
return ms.allByLevel[i] < ms.allByLevel[j]
|
|
||||||
}
|
|
||||||
return monsterI.Level < monsterJ.Level
|
|
||||||
})
|
|
||||||
|
|
||||||
// Sort level indices by ID
|
|
||||||
for level := range ms.byLevel {
|
|
||||||
sort.Ints(ms.byLevel[level])
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sort immunity indices by level, then ID
|
|
||||||
for immunity := range ms.byImmunity {
|
|
||||||
sort.Slice(ms.byImmunity[immunity], func(i, j int) bool {
|
|
||||||
monsterI, _ := ms.GetByID(ms.byImmunity[immunity][i])
|
|
||||||
monsterJ, _ := ms.GetByID(ms.byImmunity[immunity][j])
|
|
||||||
if monsterI.Level == monsterJ.Level {
|
|
||||||
return ms.byImmunity[immunity][i] < ms.byImmunity[immunity][j]
|
|
||||||
}
|
|
||||||
return monsterI.Level < monsterJ.Level
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// rebuildIndices rebuilds all monster-specific indices from base store data
|
|
||||||
func (ms *MonsterStore) rebuildIndices() {
|
|
||||||
ms.mu.Lock()
|
|
||||||
defer ms.mu.Unlock()
|
|
||||||
ms.rebuildIndicesUnsafe()
|
|
||||||
}
|
|
||||||
|
|
||||||
// Retrieves a monster by ID - O(1) lookup
|
|
||||||
func Find(id int) (*Monster, error) {
|
func Find(id int) (*Monster, error) {
|
||||||
ms := GetStore()
|
ms := GetStore()
|
||||||
monster, exists := ms.GetByID(id)
|
monster, exists := ms.Find(id)
|
||||||
if !exists {
|
if !exists {
|
||||||
return nil, fmt.Errorf("monster with ID %d not found", id)
|
return nil, fmt.Errorf("monster with ID %d not found", id)
|
||||||
}
|
}
|
||||||
return monster, nil
|
return monster, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves all monsters - O(1) lookup (returns pre-sorted slice)
|
|
||||||
func All() ([]*Monster, error) {
|
func All() ([]*Monster, error) {
|
||||||
ms := GetStore()
|
ms := GetStore()
|
||||||
ms.mu.RLock()
|
return ms.AllSorted("allByLevel"), nil
|
||||||
defer ms.mu.RUnlock()
|
|
||||||
|
|
||||||
result := make([]*Monster, 0, len(ms.allByLevel))
|
|
||||||
for _, id := range ms.allByLevel {
|
|
||||||
if monster, exists := ms.GetByID(id); exists {
|
|
||||||
result = append(result, monster)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves monsters by level - O(1) lookup
|
|
||||||
func ByLevel(level int) ([]*Monster, error) {
|
func ByLevel(level int) ([]*Monster, error) {
|
||||||
ms := GetStore()
|
ms := GetStore()
|
||||||
ms.mu.RLock()
|
return ms.GroupByIndex("byLevel", level), nil
|
||||||
defer ms.mu.RUnlock()
|
|
||||||
|
|
||||||
ids, exists := ms.byLevel[level]
|
|
||||||
if !exists {
|
|
||||||
return []*Monster{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]*Monster, 0, len(ids))
|
|
||||||
for _, id := range ids {
|
|
||||||
if monster, exists := ms.GetByID(id); exists {
|
|
||||||
result = append(result, monster)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves monsters within a level range (inclusive) - O(k) where k is result size
|
|
||||||
func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) {
|
func ByLevelRange(minLevel, maxLevel int) ([]*Monster, error) {
|
||||||
ms := GetStore()
|
ms := GetStore()
|
||||||
ms.mu.RLock()
|
|
||||||
defer ms.mu.RUnlock()
|
|
||||||
|
|
||||||
var result []*Monster
|
var result []*Monster
|
||||||
for level := minLevel; level <= maxLevel; level++ {
|
for level := minLevel; level <= maxLevel; level++ {
|
||||||
if ids, exists := ms.byLevel[level]; exists {
|
monsters := ms.GroupByIndex("byLevel", level)
|
||||||
for _, id := range ids {
|
result = append(result, monsters...)
|
||||||
if monster, exists := ms.GetByID(id); exists {
|
|
||||||
result = append(result, monster)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Retrieves monsters by immunity type - O(1) lookup
|
|
||||||
func ByImmunity(immunityType int) ([]*Monster, error) {
|
func ByImmunity(immunityType int) ([]*Monster, error) {
|
||||||
ms := GetStore()
|
ms := GetStore()
|
||||||
ms.mu.RLock()
|
return ms.GroupByIndex("byImmunity", immunityType), nil
|
||||||
defer ms.mu.RUnlock()
|
|
||||||
|
|
||||||
ids, exists := ms.byImmunity[immunityType]
|
|
||||||
if !exists {
|
|
||||||
return []*Monster{}, nil
|
|
||||||
}
|
|
||||||
|
|
||||||
result := make([]*Monster, 0, len(ids))
|
|
||||||
for _, id := range ids {
|
|
||||||
if monster, exists := ms.GetByID(id); exists {
|
|
||||||
result = append(result, monster)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Saves a new monster to the in-memory store and sets the ID
|
// Insert with ID assignment
|
||||||
func (m *Monster) Insert() error {
|
func (m *Monster) Insert() error {
|
||||||
ms := GetStore()
|
ms := GetStore()
|
||||||
|
|
||||||
// Validate before insertion
|
|
||||||
if err := m.Validate(); err != nil {
|
|
||||||
return fmt.Errorf("validation failed: %w", err)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Assign new ID if not set
|
|
||||||
if m.ID == 0 {
|
if m.ID == 0 {
|
||||||
m.ID = ms.GetNextID()
|
m.ID = ms.GetNextID()
|
||||||
}
|
}
|
||||||
|
return ms.AddMonster(m)
|
||||||
// Add to store
|
|
||||||
ms.AddMonster(m)
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the monster is immune to Hurt spells
|
// Helper methods
|
||||||
func (m *Monster) IsHurtImmune() bool {
|
func (m *Monster) IsHurtImmune() bool {
|
||||||
return m.Immune == ImmuneHurt
|
return m.Immune == ImmuneHurt
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the monster is immune to Sleep spells
|
|
||||||
func (m *Monster) IsSleepImmune() bool {
|
func (m *Monster) IsSleepImmune() bool {
|
||||||
return m.Immune == ImmuneSleep
|
return m.Immune == ImmuneSleep
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns true if the monster has any immunity
|
|
||||||
func (m *Monster) HasImmunity() bool {
|
func (m *Monster) HasImmunity() bool {
|
||||||
return m.Immune != ImmuneNone
|
return m.Immune != ImmuneNone
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the string representation of the monster's immunity
|
|
||||||
func (m *Monster) ImmunityName() string {
|
func (m *Monster) ImmunityName() string {
|
||||||
switch m.Immune {
|
switch m.Immune {
|
||||||
case ImmuneNone:
|
case ImmuneNone:
|
||||||
@ -349,17 +187,13 @@ func (m *Monster) ImmunityName() string {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculates a simple difficulty rating based on stats
|
|
||||||
func (m *Monster) DifficultyRating() float64 {
|
func (m *Monster) DifficultyRating() float64 {
|
||||||
// Simple formula: (HP + Damage + Armor) / Level
|
|
||||||
// Higher values indicate tougher monsters relative to their level
|
|
||||||
if m.Level == 0 {
|
if m.Level == 0 {
|
||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
return float64(m.MaxHP+m.MaxDmg+m.Armor) / float64(m.Level)
|
return float64(m.MaxHP+m.MaxDmg+m.Armor) / float64(m.Level)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the experience reward per hit point (efficiency metric)
|
|
||||||
func (m *Monster) ExpPerHP() float64 {
|
func (m *Monster) ExpPerHP() float64 {
|
||||||
if m.MaxHP == 0 {
|
if m.MaxHP == 0 {
|
||||||
return 0
|
return 0
|
||||||
@ -367,7 +201,6 @@ func (m *Monster) ExpPerHP() float64 {
|
|||||||
return float64(m.MaxExp) / float64(m.MaxHP)
|
return float64(m.MaxExp) / float64(m.MaxHP)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns the gold reward per hit point (efficiency metric)
|
|
||||||
func (m *Monster) GoldPerHP() float64 {
|
func (m *Monster) GoldPerHP() float64 {
|
||||||
if m.MaxHP == 0 {
|
if m.MaxHP == 0 {
|
||||||
return 0
|
return 0
|
||||||
|
@ -6,35 +6,329 @@ import (
|
|||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"reflect"
|
"reflect"
|
||||||
|
"sort"
|
||||||
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Store provides generic storage operations
|
// Validatable interface for entities that can validate themselves
|
||||||
type Store[T any] interface {
|
type Validatable interface {
|
||||||
LoadFromJSON(filename string) error
|
Validate() error
|
||||||
SaveToJSON(filename string) error
|
|
||||||
LoadData(dataPath string) error
|
|
||||||
SaveData(dataPath string) error
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// BaseStore provides generic JSON persistence
|
// IndexBuilder function type for building custom indices
|
||||||
|
type IndexBuilder[T any] func(allItems map[int]*T) any
|
||||||
|
|
||||||
|
// BaseStore provides generic storage with index management
|
||||||
type BaseStore[T any] struct {
|
type BaseStore[T any] struct {
|
||||||
items map[int]*T
|
items map[int]*T
|
||||||
maxID int
|
maxID int
|
||||||
mu sync.RWMutex
|
mu sync.RWMutex
|
||||||
itemType reflect.Type
|
itemType reflect.Type
|
||||||
|
indices map[string]any
|
||||||
|
indexBuilders map[string]IndexBuilder[T]
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewBaseStore creates a new base store for type T
|
// NewBaseStore creates a new base store for type T
|
||||||
func NewBaseStore[T any]() *BaseStore[T] {
|
func NewBaseStore[T any]() *BaseStore[T] {
|
||||||
var zero T
|
var zero T
|
||||||
return &BaseStore[T]{
|
return &BaseStore[T]{
|
||||||
items: make(map[int]*T),
|
items: make(map[int]*T),
|
||||||
maxID: 0,
|
maxID: 0,
|
||||||
itemType: reflect.TypeOf(zero),
|
itemType: reflect.TypeOf(zero),
|
||||||
|
indices: make(map[string]any),
|
||||||
|
indexBuilders: make(map[string]IndexBuilder[T]),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Index Management
|
||||||
|
|
||||||
|
// RegisterIndex registers an index builder function
|
||||||
|
func (bs *BaseStore[T]) RegisterIndex(name string, builder IndexBuilder[T]) {
|
||||||
|
bs.mu.Lock()
|
||||||
|
defer bs.mu.Unlock()
|
||||||
|
bs.indexBuilders[name] = builder
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetIndex retrieves a named index
|
||||||
|
func (bs *BaseStore[T]) GetIndex(name string) (any, bool) {
|
||||||
|
bs.mu.RLock()
|
||||||
|
defer bs.mu.RUnlock()
|
||||||
|
index, exists := bs.indices[name]
|
||||||
|
return index, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// RebuildIndices rebuilds all registered indices
|
||||||
|
func (bs *BaseStore[T]) RebuildIndices() {
|
||||||
|
bs.mu.Lock()
|
||||||
|
defer bs.mu.Unlock()
|
||||||
|
bs.rebuildIndicesUnsafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (bs *BaseStore[T]) rebuildIndicesUnsafe() {
|
||||||
|
allItems := make(map[int]*T, len(bs.items))
|
||||||
|
for k, v := range bs.items {
|
||||||
|
allItems[k] = v
|
||||||
|
}
|
||||||
|
|
||||||
|
for name, builder := range bs.indexBuilders {
|
||||||
|
bs.indices[name] = builder(allItems)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enhanced CRUD Operations
|
||||||
|
|
||||||
|
// AddWithRebuild adds item with validation and index rebuild
|
||||||
|
func (bs *BaseStore[T]) AddWithRebuild(id int, item *T) error {
|
||||||
|
bs.mu.Lock()
|
||||||
|
defer bs.mu.Unlock()
|
||||||
|
|
||||||
|
if validatable, ok := any(item).(Validatable); ok {
|
||||||
|
if err := validatable.Validate(); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bs.items[id] = item
|
||||||
|
if id > bs.maxID {
|
||||||
|
bs.maxID = id
|
||||||
|
}
|
||||||
|
|
||||||
|
bs.rebuildIndicesUnsafe()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RemoveWithRebuild removes item and rebuilds indices
|
||||||
|
func (bs *BaseStore[T]) RemoveWithRebuild(id int) {
|
||||||
|
bs.mu.Lock()
|
||||||
|
defer bs.mu.Unlock()
|
||||||
|
delete(bs.items, id)
|
||||||
|
bs.rebuildIndicesUnsafe()
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateWithRebuild updates item with validation and index rebuild
|
||||||
|
func (bs *BaseStore[T]) UpdateWithRebuild(id int, item *T) error {
|
||||||
|
return bs.AddWithRebuild(id, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common Query Methods
|
||||||
|
|
||||||
|
// Find retrieves an item by ID
|
||||||
|
func (bs *BaseStore[T]) Find(id int) (*T, bool) {
|
||||||
|
bs.mu.RLock()
|
||||||
|
defer bs.mu.RUnlock()
|
||||||
|
item, exists := bs.items[id]
|
||||||
|
return item, exists
|
||||||
|
}
|
||||||
|
|
||||||
|
// AllSorted returns all items using named sorted index
|
||||||
|
func (bs *BaseStore[T]) AllSorted(indexName string) []*T {
|
||||||
|
bs.mu.RLock()
|
||||||
|
defer bs.mu.RUnlock()
|
||||||
|
|
||||||
|
if index, exists := bs.indices[indexName]; exists {
|
||||||
|
if sortedIDs, ok := index.([]int); ok {
|
||||||
|
result := make([]*T, 0, len(sortedIDs))
|
||||||
|
for _, id := range sortedIDs {
|
||||||
|
if item, exists := bs.items[id]; exists {
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback: return all items by ID order
|
||||||
|
ids := make([]int, 0, len(bs.items))
|
||||||
|
for id := range bs.items {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
sort.Ints(ids)
|
||||||
|
|
||||||
|
result := make([]*T, 0, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
result = append(result, bs.items[id])
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// LookupByIndex finds single item using string lookup index
|
||||||
|
func (bs *BaseStore[T]) LookupByIndex(indexName, key string) (*T, bool) {
|
||||||
|
bs.mu.RLock()
|
||||||
|
defer bs.mu.RUnlock()
|
||||||
|
|
||||||
|
if index, exists := bs.indices[indexName]; exists {
|
||||||
|
if lookupMap, ok := index.(map[string]int); ok {
|
||||||
|
if id, found := lookupMap[key]; found {
|
||||||
|
if item, exists := bs.items[id]; exists {
|
||||||
|
return item, true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, false
|
||||||
|
}
|
||||||
|
|
||||||
|
// GroupByIndex returns items grouped by key
|
||||||
|
func (bs *BaseStore[T]) GroupByIndex(indexName string, key any) []*T {
|
||||||
|
bs.mu.RLock()
|
||||||
|
defer bs.mu.RUnlock()
|
||||||
|
|
||||||
|
if index, exists := bs.indices[indexName]; exists {
|
||||||
|
switch groupMap := index.(type) {
|
||||||
|
case map[int][]int:
|
||||||
|
if intKey, ok := key.(int); ok {
|
||||||
|
if ids, found := groupMap[intKey]; found {
|
||||||
|
result := make([]*T, 0, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
if item, exists := bs.items[id]; exists {
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
case map[string][]int:
|
||||||
|
if strKey, ok := key.(string); ok {
|
||||||
|
if ids, found := groupMap[strKey]; found {
|
||||||
|
result := make([]*T, 0, len(ids))
|
||||||
|
for _, id := range ids {
|
||||||
|
if item, exists := bs.items[id]; exists {
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return []*T{}
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterByIndex returns items matching filter criteria
|
||||||
|
func (bs *BaseStore[T]) FilterByIndex(indexName string, filterFunc func(*T) bool) []*T {
|
||||||
|
bs.mu.RLock()
|
||||||
|
defer bs.mu.RUnlock()
|
||||||
|
|
||||||
|
var sourceIDs []int
|
||||||
|
|
||||||
|
if index, exists := bs.indices[indexName]; exists {
|
||||||
|
if sortedIDs, ok := index.([]int); ok {
|
||||||
|
sourceIDs = sortedIDs
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if sourceIDs == nil {
|
||||||
|
for id := range bs.items {
|
||||||
|
sourceIDs = append(sourceIDs, id)
|
||||||
|
}
|
||||||
|
sort.Ints(sourceIDs)
|
||||||
|
}
|
||||||
|
|
||||||
|
var result []*T
|
||||||
|
for _, id := range sourceIDs {
|
||||||
|
if item, exists := bs.items[id]; exists && filterFunc(item) {
|
||||||
|
result = append(result, item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Common Index Builders
|
||||||
|
|
||||||
|
// BuildStringLookupIndex creates string-to-ID mapping
|
||||||
|
func BuildStringLookupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] {
|
||||||
|
return func(allItems map[int]*T) any {
|
||||||
|
index := make(map[string]int)
|
||||||
|
for id, item := range allItems {
|
||||||
|
key := keyFunc(item)
|
||||||
|
index[key] = id
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildCaseInsensitiveLookupIndex creates lowercase string-to-ID mapping
|
||||||
|
func BuildCaseInsensitiveLookupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] {
|
||||||
|
return func(allItems map[int]*T) any {
|
||||||
|
index := make(map[string]int)
|
||||||
|
for id, item := range allItems {
|
||||||
|
key := strings.ToLower(keyFunc(item))
|
||||||
|
index[key] = id
|
||||||
|
}
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildIntGroupIndex creates int-to-[]ID mapping
|
||||||
|
func BuildIntGroupIndex[T any](keyFunc func(*T) int) IndexBuilder[T] {
|
||||||
|
return func(allItems map[int]*T) any {
|
||||||
|
index := make(map[int][]int)
|
||||||
|
for id, item := range allItems {
|
||||||
|
key := keyFunc(item)
|
||||||
|
index[key] = append(index[key], id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort each group by ID
|
||||||
|
for key := range index {
|
||||||
|
sort.Ints(index[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildStringGroupIndex creates string-to-[]ID mapping
|
||||||
|
func BuildStringGroupIndex[T any](keyFunc func(*T) string) IndexBuilder[T] {
|
||||||
|
return func(allItems map[int]*T) any {
|
||||||
|
index := make(map[string][]int)
|
||||||
|
for id, item := range allItems {
|
||||||
|
key := keyFunc(item)
|
||||||
|
index[key] = append(index[key], id)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sort each group by ID
|
||||||
|
for key := range index {
|
||||||
|
sort.Ints(index[key])
|
||||||
|
}
|
||||||
|
|
||||||
|
return index
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// BuildSortedListIndex creates sorted []ID list
|
||||||
|
func BuildSortedListIndex[T any](sortFunc func(*T, *T) bool) IndexBuilder[T] {
|
||||||
|
return func(allItems map[int]*T) any {
|
||||||
|
ids := make([]int, 0, len(allItems))
|
||||||
|
for id := range allItems {
|
||||||
|
ids = append(ids, id)
|
||||||
|
}
|
||||||
|
|
||||||
|
sort.Slice(ids, func(i, j int) bool {
|
||||||
|
return sortFunc(allItems[ids[i]], allItems[ids[j]])
|
||||||
|
})
|
||||||
|
|
||||||
|
return ids
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Singleton Management Helper
|
||||||
|
|
||||||
|
// NewSingleton creates singleton store pattern with sync.Once
|
||||||
|
func NewSingleton[S any](initFunc func() *S) func() *S {
|
||||||
|
var store *S
|
||||||
|
var once sync.Once
|
||||||
|
|
||||||
|
return func() *S {
|
||||||
|
once.Do(func() {
|
||||||
|
store = initFunc()
|
||||||
|
})
|
||||||
|
return store
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Legacy Methods (backward compatibility)
|
||||||
|
|
||||||
// GetNextID returns the next available ID atomically
|
// GetNextID returns the next available ID atomically
|
||||||
func (bs *BaseStore[T]) GetNextID() int {
|
func (bs *BaseStore[T]) GetNextID() int {
|
||||||
bs.mu.Lock()
|
bs.mu.Lock()
|
||||||
@ -45,10 +339,7 @@ func (bs *BaseStore[T]) GetNextID() int {
|
|||||||
|
|
||||||
// GetByID retrieves an item by ID
|
// GetByID retrieves an item by ID
|
||||||
func (bs *BaseStore[T]) GetByID(id int) (*T, bool) {
|
func (bs *BaseStore[T]) GetByID(id int) (*T, bool) {
|
||||||
bs.mu.RLock()
|
return bs.Find(id)
|
||||||
defer bs.mu.RUnlock()
|
|
||||||
item, exists := bs.items[id]
|
|
||||||
return item, exists
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add adds an item to the store
|
// Add adds an item to the store
|
||||||
@ -85,8 +376,11 @@ func (bs *BaseStore[T]) Clear() {
|
|||||||
defer bs.mu.Unlock()
|
defer bs.mu.Unlock()
|
||||||
bs.items = make(map[int]*T)
|
bs.items = make(map[int]*T)
|
||||||
bs.maxID = 0
|
bs.maxID = 0
|
||||||
|
bs.rebuildIndicesUnsafe()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// JSON Persistence
|
||||||
|
|
||||||
// LoadFromJSON loads items from JSON using reflection
|
// LoadFromJSON loads items from JSON using reflection
|
||||||
func (bs *BaseStore[T]) LoadFromJSON(filename string) error {
|
func (bs *BaseStore[T]) LoadFromJSON(filename string) error {
|
||||||
bs.mu.Lock()
|
bs.mu.Lock()
|
||||||
@ -148,7 +442,7 @@ func (bs *BaseStore[T]) SaveToJSON(filename string) error {
|
|||||||
items = append(items, item)
|
items = append(items, item)
|
||||||
}
|
}
|
||||||
|
|
||||||
data, err := json.MarshalIndent(items, "", " ")
|
data, err := json.MarshalIndent(items, "", "\t")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return fmt.Errorf("failed to marshal to JSON: %w", err)
|
return fmt.Errorf("failed to marshal to JSON: %w", err)
|
||||||
}
|
}
|
||||||
@ -178,6 +472,7 @@ func (bs *BaseStore[T]) LoadData(dataPath string) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fmt.Printf("Loaded %d items from JSON\n", len(bs.items))
|
fmt.Printf("Loaded %d items from JSON\n", len(bs.items))
|
||||||
|
bs.RebuildIndices() // Rebuild indices after loading
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user