mirror of
https://github.com/Mezeporta/Erupe.git
synced 2026-05-06 22:35:11 +02:00
Phase B of #188. Quest JSON title/description/text_main/text_sub_a/ text_sub_b/success_cond/fail_cond/contractor now accept either a plain string (existing behaviour) or a language-keyed object like "title": { "jp": "...", "en": "...", "fr": "..." } CompileQuestJSON takes the compiling session's language and resolves each field through a fallback chain (requested -> plain -> jp -> en -> any non-empty), so existing single-language quest JSONs keep working byte-for-byte unchanged. The quest cache is re-keyed on (questID, language) so compiled binaries for different languages never leak between sessions on a multi-language server. loadQuestBinary and loadQuestFile now pass s.Lang() into both the compiler and the cache. ParseQuestBinary emits plain-string LocalizedStrings, so the binary -> JSON -> binary round-trip still produces identical output. The new LocalizedString type lives in its own file and is reusable by phase C (scenarios, mail templates, shop text). Shift-JIS encoding still applies to the wire format, so localized values must use characters representable in Shift-JIS — ASCII, kana, CJK — which is documented on the type.
64 lines
1.7 KiB
Go
64 lines
1.7 KiB
Go
package channelserver
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
)
|
|
|
|
// questCacheKey identifies a cached quest variant. Phase B of #188 added the
|
|
// language dimension so localized quest text compiled for one session does
|
|
// not leak into another session's response.
|
|
type questCacheKey struct {
|
|
questID int
|
|
lang string
|
|
}
|
|
|
|
// QuestCache is a thread-safe, expiring cache for parsed quest file data,
|
|
// keyed by (questID, language). Entries for different languages are stored
|
|
// independently so a Japanese client and a French client on the same server
|
|
// never share compiled binaries.
|
|
type QuestCache struct {
|
|
mu sync.RWMutex
|
|
data map[questCacheKey][]byte
|
|
expiry map[questCacheKey]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[questCacheKey][]byte),
|
|
expiry: make(map[questCacheKey]time.Time),
|
|
ttl: time.Duration(ttlSeconds) * time.Second,
|
|
}
|
|
}
|
|
|
|
// Get returns cached quest data for the (questID, lang) variant if it exists
|
|
// and has not expired.
|
|
func (c *QuestCache) Get(questID int, lang string) ([]byte, bool) {
|
|
if c.ttl <= 0 {
|
|
return nil, false
|
|
}
|
|
k := questCacheKey{questID: questID, lang: lang}
|
|
c.mu.RLock()
|
|
defer c.mu.RUnlock()
|
|
b, ok := c.data[k]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
if time.Now().After(c.expiry[k]) {
|
|
return nil, false
|
|
}
|
|
return b, true
|
|
}
|
|
|
|
// Put stores quest data for the (questID, lang) variant with the configured TTL.
|
|
func (c *QuestCache) Put(questID int, lang string, b []byte) {
|
|
k := questCacheKey{questID: questID, lang: lang}
|
|
c.mu.Lock()
|
|
c.data[k] = b
|
|
c.expiry[k] = time.Now().Add(c.ttl)
|
|
c.mu.Unlock()
|
|
}
|