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

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
}