feat: support controller's cache
This commit is contained in:
@@ -0,0 +1,194 @@
|
||||
package controller
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"iter"
|
||||
"slices"
|
||||
|
||||
"github.com/google/uuid"
|
||||
"github.com/iliadenisov/galaxy/internal/model/game"
|
||||
)
|
||||
|
||||
type Cache struct {
|
||||
g *game.Game
|
||||
cacheRaceIndexByID map[uuid.UUID]int
|
||||
raceIndexByShipGroupIndex map[int]int
|
||||
shipClassByShipGroupIndex map[int]*game.ShipType
|
||||
planetByPlanetNumber map[uint]*game.Planet
|
||||
cacheRelation map[int]map[int]game.Relation
|
||||
}
|
||||
|
||||
func NewCache(g *game.Game) *Cache {
|
||||
if g == nil {
|
||||
panic("NewCache: nil Game passed")
|
||||
}
|
||||
c := &Cache{
|
||||
g: g,
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
func (c *Cache) Relation(r1, r2 int) game.Relation {
|
||||
if c.cacheRelation == nil {
|
||||
for r1 := range c.g.Race {
|
||||
for r2 := range c.g.Race {
|
||||
if r1 == r2 {
|
||||
continue
|
||||
}
|
||||
rel := slices.IndexFunc(c.g.Race[r1].Relations, func(r game.RaceRelation) bool { return r.RaceID == c.g.Race[r2].ID })
|
||||
if rel < 0 {
|
||||
panic(fmt.Sprintf("Relation: opponent not found idx=%d", r2))
|
||||
}
|
||||
if _, ok := c.cacheRelation[r1]; !ok {
|
||||
c.cacheRelation[r1] = make(map[int]game.Relation)
|
||||
}
|
||||
c.cacheRelation[r1][r2] = c.g.Race[r1].Relations[rel].Relation
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
if _, ok := c.cacheRelation[r1]; !ok {
|
||||
panic(fmt.Sprintf("Relation: no left race idx=%d", r1))
|
||||
}
|
||||
if v, ok := c.cacheRelation[r1][r2]; !ok {
|
||||
panic(fmt.Sprintf("Relation: no right race idx=%d", r2))
|
||||
} else {
|
||||
return v
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) Planet(planetNumber uint) *game.Planet {
|
||||
if c.planetByPlanetNumber == nil {
|
||||
c.planetByPlanetNumber = make(map[uint]*game.Planet)
|
||||
for p := range c.g.Map.Planet {
|
||||
c.planetByPlanetNumber[c.g.Map.Planet[p].Number] = &c.g.Map.Planet[p]
|
||||
}
|
||||
}
|
||||
if v, ok := c.planetByPlanetNumber[planetNumber]; ok {
|
||||
return v
|
||||
} else {
|
||||
panic(fmt.Sprintf("Planet: not found by number=%d", planetNumber))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupShipClass(groupIndex int) *game.ShipType {
|
||||
if c.shipClassByShipGroupIndex == nil {
|
||||
c.fillShipsAndGroups()
|
||||
}
|
||||
c.validateShipGroupIndex(groupIndex)
|
||||
if v, ok := c.shipClassByShipGroupIndex[groupIndex]; ok {
|
||||
return v
|
||||
} else {
|
||||
panic(fmt.Sprintf("ShipClassByShipGroupIndex: group not found by index=%v", groupIndex))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) RaceIndex(ID uuid.UUID) int {
|
||||
if c.cacheRaceIndexByID == nil {
|
||||
c.cacheRaceIndexByID = make(map[uuid.UUID]int)
|
||||
for i := range c.g.Race {
|
||||
c.cacheRaceIndexByID[c.g.Race[i].ID] = i
|
||||
}
|
||||
}
|
||||
if v, ok := c.cacheRaceIndexByID[ID]; ok {
|
||||
return v
|
||||
} else {
|
||||
panic(fmt.Sprintf("RaceIndex: race not found by ID=%v", ID))
|
||||
}
|
||||
}
|
||||
|
||||
// ShipGroup is a proxy func, nothing to cache
|
||||
func (c *Cache) ShipGroup(groupIndex int) *game.ShipGroup {
|
||||
c.validateShipGroupIndex(groupIndex)
|
||||
return &c.g.ShipGroups[groupIndex]
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupsIndex() iter.Seq[int] {
|
||||
return func(yield func(int) bool) {
|
||||
for i := range c.g.ShipGroups {
|
||||
if !yield(i) {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupOwnerRaceIndex(groupIndex int) int {
|
||||
if c.raceIndexByShipGroupIndex == nil {
|
||||
c.fillShipsAndGroups()
|
||||
}
|
||||
c.validateShipGroupIndex(groupIndex)
|
||||
if v, ok := c.raceIndexByShipGroupIndex[groupIndex]; ok {
|
||||
return v
|
||||
} else {
|
||||
panic(fmt.Sprintf("ShipGroupRace: group not found by index=%v", groupIndex))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupOwnerRace(groupIndex int) *game.Race {
|
||||
return &c.g.Race[c.ShipGroupOwnerRaceIndex(groupIndex)]
|
||||
}
|
||||
|
||||
func (c *Cache) ShipGroupNumber(i int, n uint) {
|
||||
c.validateShipGroupIndex(i)
|
||||
c.g.ShipGroups[i].Number = n
|
||||
}
|
||||
|
||||
func (c *Cache) DeleteShipGroup(i int) {
|
||||
c.validateShipGroupIndex(i)
|
||||
c.unsafeDeleteShipGroup(i)
|
||||
}
|
||||
|
||||
func (c *Cache) DeleteKilledShipGroups() {
|
||||
for i := len(c.g.ShipGroups) - 1; i >= 0; i-- {
|
||||
if c.g.ShipGroups[i].Number == 0 {
|
||||
c.unsafeDeleteShipGroup(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) unsafeDeleteShipGroup(i int) {
|
||||
c.g.ShipGroups = append(c.g.ShipGroups[:i], c.g.ShipGroups[i+1:]...)
|
||||
delete(c.raceIndexByShipGroupIndex, i)
|
||||
delete(c.shipClassByShipGroupIndex, i)
|
||||
}
|
||||
|
||||
// Internal
|
||||
|
||||
func (c *Cache) validateShipGroupIndex(i int) {
|
||||
if i >= len(c.g.ShipGroups) {
|
||||
panic(fmt.Sprintf("group index out of groups len: %d >= %d", i, len(c.g.ShipGroups)))
|
||||
}
|
||||
}
|
||||
|
||||
func (c *Cache) fillShipsAndGroups() {
|
||||
if c.raceIndexByShipGroupIndex != nil {
|
||||
clear(c.raceIndexByShipGroupIndex)
|
||||
} else {
|
||||
c.raceIndexByShipGroupIndex = make(map[int]int)
|
||||
}
|
||||
if c.shipClassByShipGroupIndex != nil {
|
||||
clear(c.shipClassByShipGroupIndex)
|
||||
} else {
|
||||
c.shipClassByShipGroupIndex = make(map[int]*game.ShipType)
|
||||
}
|
||||
for groupIndex := range c.g.ShipGroups {
|
||||
ri := c.RaceIndex(c.g.ShipGroups[groupIndex].OwnerID)
|
||||
c.raceIndexByShipGroupIndex[groupIndex] = ri
|
||||
sti, ok := ShipClassIndex(c.g, ri, c.g.ShipGroups[groupIndex].TypeID)
|
||||
if !ok {
|
||||
panic(fmt.Sprintf("CollectPlanetGroups: ship class not found for race=%q group=%v", c.g.Race[ri].Name, c.g.ShipGroups[groupIndex].Index))
|
||||
}
|
||||
c.shipClassByShipGroupIndex[groupIndex] = &c.g.Race[ri].ShipTypes[sti]
|
||||
}
|
||||
}
|
||||
|
||||
// Helpers
|
||||
|
||||
func ShipClassIndex(g *game.Game, ri int, classID uuid.UUID) (int, bool) {
|
||||
if len(g.Race) < ri+1 {
|
||||
panic(fmt.Sprintf("ShipClass: game race index %d invalid: len=%d", ri, len(g.Race)))
|
||||
}
|
||||
sti := slices.IndexFunc(g.Race[ri].ShipTypes, func(st game.ShipType) bool { return st.ID == classID })
|
||||
return sti, sti >= 0
|
||||
}
|
||||
@@ -33,6 +33,7 @@ type Repo interface {
|
||||
type Controller struct {
|
||||
param Param
|
||||
Repo Repo
|
||||
Cache *Cache
|
||||
}
|
||||
|
||||
type Param struct {
|
||||
@@ -74,6 +75,7 @@ func (c *Controller) ExecuteGame(consumer func(Repo, *game.Game)) error {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.Cache = NewCache(g)
|
||||
consumer(c.Repo, g)
|
||||
return c.Repo.Release()
|
||||
}
|
||||
|
||||
@@ -30,7 +30,7 @@ func TestNewGame(t *testing.T) {
|
||||
assert.NoError(t, err)
|
||||
|
||||
assert.FileExists(t, filepath.Join(root, "state.json"))
|
||||
assert.FileExists(t, filepath.Join(root, "000/state.json"))
|
||||
assert.FileExists(t, filepath.Join(root, "0000/state.json"))
|
||||
|
||||
g, err := r.LoadState()
|
||||
assert.NoError(t, err)
|
||||
|
||||
Reference in New Issue
Block a user