You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
97 lines
2.6 KiB
Go
97 lines
2.6 KiB
Go
package storage
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"fmt"
|
|
"time"
|
|
|
|
"github.com/redis/go-redis/v9"
|
|
|
|
"github.com/denisovdennis/autohero/internal/model"
|
|
)
|
|
|
|
const heroTownSessionKeyFmt = "autohero:v1:hero:%d:town_session"
|
|
|
|
// HeroTownSessionRedis is the last persisted in-town NPC tour snapshot (reconnect / crash recovery).
|
|
type HeroTownSessionRedis struct {
|
|
SavedAtUnixNano int64 `json:"savedAtUnixNano"`
|
|
State model.GameState `json:"state"`
|
|
CurrentTownID int64 `json:"currentTownId,omitempty"`
|
|
PositionX float64 `json:"positionX"`
|
|
PositionY float64 `json:"positionY"`
|
|
TownPause *model.TownPausePersisted `json:"townPause,omitempty"`
|
|
}
|
|
|
|
// TownSessionStore mirrors in-town hero state to Redis for faster/stale-DB-safe reconnect.
|
|
type TownSessionStore struct {
|
|
rdb *redis.Client
|
|
}
|
|
|
|
// NewTownSessionStore returns a store backed by Redis, or nil if rdb is nil.
|
|
func NewTownSessionStore(rdb *redis.Client) *TownSessionStore {
|
|
if rdb == nil {
|
|
return nil
|
|
}
|
|
return &TownSessionStore{rdb: rdb}
|
|
}
|
|
|
|
func (s *TownSessionStore) key(heroID int64) string {
|
|
return fmt.Sprintf(heroTownSessionKeyFmt, heroID)
|
|
}
|
|
|
|
// Save stores the hero's in-town session. Caller must set hero.TownPause (e.g. after SyncToHero).
|
|
func (s *TownSessionStore) Save(ctx context.Context, heroID int64, h *model.Hero) error {
|
|
if s == nil || s.rdb == nil || h == nil {
|
|
return nil
|
|
}
|
|
if h.State != model.StateInTown {
|
|
return nil
|
|
}
|
|
var townID int64
|
|
if h.CurrentTownID != nil {
|
|
townID = *h.CurrentTownID
|
|
}
|
|
payload := HeroTownSessionRedis{
|
|
SavedAtUnixNano: time.Now().UnixNano(),
|
|
State: h.State,
|
|
CurrentTownID: townID,
|
|
PositionX: h.PositionX,
|
|
PositionY: h.PositionY,
|
|
TownPause: h.TownPause,
|
|
}
|
|
b, err := json.Marshal(payload)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return s.rdb.Set(ctx, s.key(heroID), b, 72*time.Hour).Err()
|
|
}
|
|
|
|
// Delete removes the in-town session key (hero left town or state no longer in_town).
|
|
func (s *TownSessionStore) Delete(ctx context.Context, heroID int64) error {
|
|
if s == nil || s.rdb == nil {
|
|
return nil
|
|
}
|
|
return s.rdb.Del(ctx, s.key(heroID)).Err()
|
|
}
|
|
|
|
// Load returns the stored session, or (nil, nil) if missing.
|
|
func (s *TownSessionStore) Load(ctx context.Context, heroID int64) (*HeroTownSessionRedis, error) {
|
|
if s == nil || s.rdb == nil {
|
|
return nil, nil
|
|
}
|
|
b, err := s.rdb.Get(ctx, s.key(heroID)).Bytes()
|
|
if err != nil {
|
|
if errors.Is(err, redis.Nil) {
|
|
return nil, nil
|
|
}
|
|
return nil, err
|
|
}
|
|
var p HeroTownSessionRedis
|
|
if err := json.Unmarshal(b, &p); err != nil {
|
|
return nil, err
|
|
}
|
|
return &p, nil
|
|
}
|