diff --git a/server/channelserver/handlers_quest.go b/server/channelserver/handlers_quest.go index 6721ecfd5..a5670ebb5 100644 --- a/server/channelserver/handlers_quest.go +++ b/server/channelserver/handlers_quest.go @@ -205,9 +205,8 @@ func handleMsgMhfSaveFavoriteQuest(s *Session, p mhfpacket.MHFPacket) { } func loadQuestFile(s *Session, questId int) []byte { - data, exists := s.server.questCacheData[questId] - if exists && s.server.questCacheTime[questId].Add(time.Duration(s.server.erupeConfig.QuestCacheExpiry)*time.Second).After(time.Now()) { - return data + if cached, ok := s.server.questCache.Get(questId); ok { + return cached } file, err := os.ReadFile(filepath.Join(s.server.erupeConfig.BinPath, fmt.Sprintf("quests/%05dd0.bin", questId))) @@ -260,11 +259,9 @@ func loadQuestFile(s *Session, questId int) []byte { } questBody.WriteBytes(newStrings.Data()) - s.server.questCacheLock.Lock() - s.server.questCacheData[questId] = questBody.Data() - s.server.questCacheTime[questId] = time.Now() - s.server.questCacheLock.Unlock() - return questBody.Data() + result := questBody.Data() + s.server.questCache.Put(questId, result) + return result } func makeEventQuest(s *Session, rows *sql.Rows) ([]byte, error) { diff --git a/server/channelserver/quest_cache.go b/server/channelserver/quest_cache.go new file mode 100644 index 000000000..c36e781be --- /dev/null +++ b/server/channelserver/quest_cache.go @@ -0,0 +1,49 @@ +package channelserver + +import ( + "sync" + "time" +) + +// QuestCache is a thread-safe, expiring cache for parsed quest file data. +type QuestCache struct { + mu sync.RWMutex + data map[int][]byte + expiry map[int]time.Time + ttl time.Duration +} + +// NewQuestCache creates a QuestCache with the given TTL in seconds. +// A TTL of 0 disables caching (Get always misses). +func NewQuestCache(ttlSeconds int) *QuestCache { + return &QuestCache{ + data: make(map[int][]byte), + expiry: make(map[int]time.Time), + ttl: time.Duration(ttlSeconds) * time.Second, + } +} + +// Get returns cached quest data if it exists and has not expired. +func (c *QuestCache) Get(questID int) ([]byte, bool) { + if c.ttl <= 0 { + return nil, false + } + c.mu.RLock() + defer c.mu.RUnlock() + b, ok := c.data[questID] + if !ok { + return nil, false + } + if time.Now().After(c.expiry[questID]) { + return nil, false + } + return b, true +} + +// Put stores quest data in the cache with the configured TTL. +func (c *QuestCache) Put(questID int, b []byte) { + c.mu.Lock() + c.data[questID] = b + c.expiry[questID] = time.Now().Add(c.ttl) + c.mu.Unlock() +} diff --git a/server/channelserver/sys_channel_server.go b/server/channelserver/sys_channel_server.go index 3583c40ad..c15182d4e 100644 --- a/server/channelserver/sys_channel_server.go +++ b/server/channelserver/sys_channel_server.go @@ -101,9 +101,7 @@ type Server struct { raviente *Raviente - questCacheLock sync.RWMutex - questCacheData map[int][]byte - questCacheTime map[int]time.Time + questCache *QuestCache handlerTable map[network.PacketID]handlerFunc } @@ -132,8 +130,7 @@ func NewServer(config *Config) *Server { state: make([]uint32, 30), support: make([]uint32, 30), }, - questCacheData: make(map[int][]byte), - questCacheTime: make(map[int]time.Time), + questCache: NewQuestCache(config.ErupeConfig.QuestCacheExpiry), handlerTable: buildHandlerTable(), } diff --git a/server/channelserver/sys_channel_server_test.go b/server/channelserver/sys_channel_server_test.go index 244bac8c9..18f539094 100644 --- a/server/channelserver/sys_channel_server_test.go +++ b/server/channelserver/sys_channel_server_test.go @@ -58,8 +58,7 @@ func createTestServer() *Server { sessions: make(map[net.Conn]*Session), stages: make(map[string]*Stage), semaphore: make(map[string]*Semaphore), - questCacheData: make(map[int][]byte), - questCacheTime: make(map[int]time.Time), + questCache: NewQuestCache(0), erupeConfig: &cfg.Config{ DebugOptions: cfg.DebugOptions{ LogOutboundMessages: false,