Files
lunar-tear/server/internal/runtime/holder.go
T
Ilya Groshev dc7c1df4fd
Build and Push Docker images to Docker Hub / build-and-push (push) Has been cancelled
Add campaign bonuses; fix parts variant/sub-stat grants and menu-pick quest resume state
2026-05-25 09:31:53 +03:00

94 lines
3.0 KiB
Go

// Package runtime owns the live, hot-swappable view of master data.
//
// The Holder atomically swaps a *Catalogs aggregate every time the operator
// asks the server to re-read assets/release/20240404193219.bin.e (typically via
// the admin webhook in cmd/lunar-tear/admin.go). gRPC services hold a *Holder
// and call Get() at the start of each RPC, so they always see a consistent
// snapshot.
package runtime
import (
"fmt"
"log"
"os"
"sync/atomic"
"time"
"lunar-tear/server/internal/campaign"
"lunar-tear/server/internal/gacha"
"lunar-tear/server/internal/masterdata"
"lunar-tear/server/internal/masterdata/memorydb"
"lunar-tear/server/internal/model"
"lunar-tear/server/internal/questflow"
"lunar-tear/server/internal/store"
)
// Catalogs is an immutable snapshot of every catalog and catalog-derived
// handler the server needs at runtime. A new *Catalogs is built from scratch
// on every reload and atomically published via Holder.
type Catalogs struct {
GameConfig *masterdata.GameConfig
Parts *masterdata.PartsCatalog
Quest *masterdata.QuestCatalog
GachaEntries []store.GachaCatalogEntry
GachaMedals map[int32]masterdata.GachaMedalInfo
GachaPool *masterdata.GachaCatalog
Shop *masterdata.ShopCatalog
DupExchange map[int32][]model.DupExchangeEntry
ConditionResolver *masterdata.ConditionResolver
CageOrnament *masterdata.CageOrnamentCatalog
LoginBonus *masterdata.LoginBonusCatalog
CharacterViewer *masterdata.CharacterViewerCatalog
Omikuji *masterdata.OmikujiCatalog
Material *masterdata.MaterialCatalog
ConsumableItem *masterdata.ConsumableItemCatalog
Costume *masterdata.CostumeCatalog
Weapon *masterdata.WeaponCatalog
Explore *masterdata.ExploreCatalog
Gimmick *masterdata.GimmickCatalog
CharacterBoard *masterdata.CharacterBoardCatalog
CharacterRebirth *masterdata.CharacterRebirthCatalog
Companion *masterdata.CompanionCatalog
SideStory *masterdata.SideStoryCatalog
BigHunt *masterdata.BigHuntCatalog
Tower *masterdata.TowerCatalog
Labyrinth *masterdata.LabyrinthCatalog
Campaign *campaign.Catalog
QuestHandler *questflow.QuestHandler
GachaHandler *gacha.GachaHandler
}
type Holder struct {
binPath string
cur atomic.Pointer[Catalogs]
}
func NewHolder(binPath string) (*Holder, error) {
h := &Holder{binPath: binPath}
if err := h.Reload(); err != nil {
return nil, err
}
return h, nil
}
func (h *Holder) Reload() error {
if err := memorydb.Init(h.binPath); err != nil {
return fmt.Errorf("memorydb.Init: %w", err)
}
c, err := buildCatalogs()
if err != nil {
return fmt.Errorf("buildCatalogs: %w", err)
}
h.cur.Store(c)
now := time.Now()
if err := os.Chtimes(h.binPath, now, now); err != nil {
log.Printf("[runtime] os.Chtimes(%s) failed (clients may not invalidate cache): %v", h.binPath, err)
}
return nil
}
func (h *Holder) Get() *Catalogs {
return h.cur.Load()
}