diff --git a/src/ui/explorer/explorer_block_cache.cpp b/src/ui/explorer/explorer_block_cache.cpp index 678e41d..e5802f4 100644 --- a/src/ui/explorer/explorer_block_cache.cpp +++ b/src/ui/explorer/explorer_block_cache.cpp @@ -123,6 +123,7 @@ bool ExplorerBlockCache::ensureOpen() } db_ = openedDb; + ++revision_; // UI-thread-only handle: a short wait is enough; a miss falls back to the in-memory spill instead of // freezing the frame for 2 s. sqlite3_busy_timeout(db_, 250); @@ -176,6 +177,20 @@ std::map ExplorerBlockCache::loadRange(int minHeight, return blocks; } +const std::map& ExplorerBlockCache::loadRangeCached(int minHeight, int maxHeight) +{ + if (range_cache_revision_ != revision_) { range_cache_.clear(); range_cache_revision_ = revision_; } + if (range_cache_.size() >= 16) range_cache_.clear(); + const std::pair key(minHeight, maxHeight); + auto it = range_cache_.find(key); + if (it == range_cache_.end()) { + auto loaded = loadRange(minHeight, maxHeight); // may open the DB (bumps revision_) + if (range_cache_revision_ != revision_) { range_cache_.clear(); range_cache_revision_ = revision_; } + it = range_cache_.emplace(key, std::move(loaded)).first; + } + return it->second; +} + std::vector ExplorerBlockCache::searchBlocks(const std::string& query, int limit) { std::vector results; @@ -256,6 +271,7 @@ bool ExplorerBlockCache::storeBlockRow(const ExplorerBlockSummary& block) bool ExplorerBlockCache::storeBlock(const ExplorerBlockSummary& block) { if (block.height < 1 || block.hash.empty()) return false; + ++revision_; const bool stored = storeBlockRow(block); if (stored) { spill_.erase(block.height); @@ -297,6 +313,7 @@ int ExplorerBlockCache::cachedBlockCount() void ExplorerBlockCache::clearBlocks() { + ++revision_; spill_.clear(); if (!ensureOpen()) return; exec("DELETE FROM explorer_blocks"); @@ -305,6 +322,7 @@ void ExplorerBlockCache::clearBlocks() void ExplorerBlockCache::pruneTo(int maxRows) { if (maxRows < 1 || !db_) return; + ++revision_; // Keep the maxRows highest blocks. The OFFSET row is the (maxRows+1)-th highest; when the table is smaller the // subquery is NULL and nothing is deleted. Freed pages are reused, so the file stops growing at its high-water // mark (no VACUUM / auto_vacuum: changing auto_vacuum needs a rebuild of existing databases). @@ -333,8 +351,10 @@ ExplorerBlockCache::SavedTipValidation ExplorerBlockCache::prepareValidation( // No DB to validate against: keep the spill honest ourselves (tip regressed, or a different block at the same height). if (!spill_.empty() && !currentBestHash.empty() && (currentHeight < spill_tip_height_ || - (currentHeight == spill_tip_height_ && currentBestHash != spill_tip_hash_))) + (currentHeight == spill_tip_height_ && currentBestHash != spill_tip_hash_))) { spill_.clear(); + ++revision_; + } if (!currentBestHash.empty()) { spill_tip_height_ = currentHeight; spill_tip_hash_ = currentBestHash; } return validation; } @@ -526,6 +546,7 @@ void ExplorerBlockCache::close() { tip_known_ = false; if (!db_) return; + ++revision_; sqlite3_close(db_); db_ = nullptr; } diff --git a/src/ui/explorer/explorer_block_cache.h b/src/ui/explorer/explorer_block_cache.h index 56752fa..d9f88ee 100644 --- a/src/ui/explorer/explorer_block_cache.h +++ b/src/ui/explorer/explorer_block_cache.h @@ -5,6 +5,7 @@ #include #include #include +#include #include struct sqlite3; @@ -44,6 +45,11 @@ public: const std::string& databasePath() const { return database_path_; } std::map loadRange(int minHeight, int maxHeight); + // loadRange memoized per (min,max) and invalidated by revision() so the per-frame Explorer list only hits SQLite + // after the cache actually changed (store / clear / prune / spill reset / open / close). The reference is valid + // until the next call; copy it if you call again before using it. + const std::map& loadRangeCached(int minHeight, int maxHeight); + std::uint64_t revision() const { return revision_; } // Fuzzy search over cached blocks: matches when the query is a substring of the height (as text) // or the block hash (case-insensitive). Returns newest-first, capped at `limit`. std::vector searchBlocks(const std::string& query, int limit); @@ -102,6 +108,11 @@ private: int open_attempts_ = 0; int stores_since_prune_ = 0; + // Bumped by every mutation that can change what loadRange returns; keys the loadRangeCached memo. + std::uint64_t revision_ = 1; + std::uint64_t range_cache_revision_ = 0; + std::map, std::map> range_cache_; + // Blocks the DB refused, bounded and UI-thread only. Evicts the entry farthest from the last spilled height // (the Explorer fetches around the visible window). Cleared with clearBlocks() and when the chain tip regresses. static constexpr std::size_t kMaxSpillBlocks = 512;