Fix WAV decode cache and spell lookup performance

- audio_engine.cpp: cache key was based on vector pointer address, not
  content — cache never hit since each asset load produces a new
  allocation. Replace with FNV-1a over head+tail bytes + size, giving
  correct content-based identity at O(1) cost. Add 256-entry cap with
  eviction to prevent unbounded growth over long sessions.

- game_handler.hpp/cpp, spellbook_screen, game_screen: knownSpells was
  a std::vector with O(n) std::find lookups scattered across packet
  handlers and UI code. Switch to std::unordered_set for O(1) insert,
  erase, and membership checks. Update all push_back/erase-remove/find
  call sites to use set operations.
This commit is contained in:
Kelsi 2026-02-17 15:13:54 -08:00
parent e29b67dad9
commit 827679579f
6 changed files with 47 additions and 36 deletions

View file

@ -25,9 +25,25 @@ struct DecodedWavCacheEntry {
static std::unordered_map<uint64_t, DecodedWavCacheEntry> gDecodedWavCache;
static uint64_t makeWavCacheKey(const std::vector<uint8_t>& wavData) {
uint64_t ptr = static_cast<uint64_t>(reinterpret_cast<uintptr_t>(wavData.data()));
uint64_t sz = static_cast<uint64_t>(wavData.size());
return (ptr * 11400714819323198485ull) ^ (sz + 0x9e3779b97f4a7c15ull + (ptr << 6) + (ptr >> 2));
// FNV-1a over the first 256 bytes + last 256 bytes + total size.
// Full-content hash would be correct but slow for large files; sampling the
// edges catches virtually all distinct files while keeping cost O(1).
constexpr uint64_t FNV_OFFSET = 14695981039346656037ull;
constexpr uint64_t FNV_PRIME = 1099511628211ull;
uint64_t h = FNV_OFFSET;
auto mix = [&](uint8_t b) { h ^= b; h *= FNV_PRIME; };
const size_t sz = wavData.size();
const size_t head = std::min(sz, size_t(256));
for (size_t i = 0; i < head; ++i) mix(wavData[i]);
if (sz > 256) {
const size_t tail_start = sz > 512 ? sz - 256 : 256;
for (size_t i = tail_start; i < sz; ++i) mix(wavData[i]);
}
// Mix in the total size so files with identical head/tail but different
// lengths still produce different keys.
for (int s = 0; s < 8; ++s) mix(static_cast<uint8_t>(sz >> (s * 8)));
return h;
}
static bool decodeWavCached(const std::vector<uint8_t>& wavData, DecodedWavCacheEntry& out) {
@ -80,6 +96,12 @@ static bool decodeWavCached(const std::vector<uint8_t>& wavData, DecodedWavCache
entry.sampleRate = sampleRate;
entry.frames = framesRead;
entry.pcmData = pcmData;
// Evict oldest half when cache grows too large (keeps ~128 most-recent sounds)
if (gDecodedWavCache.size() >= 256) {
auto it = gDecodedWavCache.begin();
for (size_t n = gDecodedWavCache.size() / 2; n > 0; --n, ++it) {}
gDecodedWavCache.erase(gDecodedWavCache.begin(), it);
}
gDecodedWavCache.emplace(key, entry);
out = entry;
return true;