mirror of
https://github.com/steredbits/pelagia.git
synced 2026-10-18 02:01:28 +02:00
117 lines
3.6 KiB
C++
117 lines
3.6 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
// Copyright (C) 2026 steredbits and Pelagia contributors
|
|
#ifndef PELAGIA_CORE_UI_POSTER_CACHE_H
|
|
#define PELAGIA_CORE_UI_POSTER_CACHE_H
|
|
|
|
// Two-level poster cache:
|
|
// - memory: textures ready to draw, budget in bytes (RGBA), LRU eviction
|
|
// of posters not drawn in the current frame;
|
|
// - disk (util::DiskCache): files as served by the server.
|
|
//
|
|
// get() is called during drawing (main thread): it never blocks.
|
|
// A missing poster is requested from a worker thread (disk, otherwise
|
|
// network, then decoding and downscaling); the decoded image comes back through the
|
|
// MainQueue and becomes a texture at the start of a later frame (a few
|
|
// uploads per frame at most, to avoid hitches). A request that has not
|
|
// been displayed for a while (fast scrolling) is abandoned before the
|
|
// download.
|
|
|
|
#include <cstdint>
|
|
#include <memory>
|
|
#include <string>
|
|
#include <unordered_map>
|
|
#include <vector>
|
|
|
|
#include "ui/canvas.h"
|
|
#include "ui/image_codec.h"
|
|
#include "ui/tasks.h"
|
|
#include "util/disk_cache.h"
|
|
|
|
namespace ui {
|
|
|
|
struct PosterKey {
|
|
std::string item_id;
|
|
std::string tag; // ImageTags.Primary: changes when the image changes
|
|
int width = 0; // width requested from the server and decoded width
|
|
std::string str() const;
|
|
};
|
|
|
|
// Download of the bytes of a poster (called from a worker thread).
|
|
class PosterFetcher {
|
|
public:
|
|
virtual ~PosterFetcher() {}
|
|
virtual bool fetch(const PosterKey& key, std::string* bytes) = 0;
|
|
};
|
|
|
|
struct Poster {
|
|
TextureId texture = 0;
|
|
int w = 0;
|
|
int h = 0;
|
|
};
|
|
|
|
class PosterCache {
|
|
public:
|
|
struct Options {
|
|
size_t memory_budget = 64u * 1024 * 1024; // ~190 posters of 240x360
|
|
int uploads_per_frame = 4;
|
|
int unwanted_after_frames = 30; // ~0.5 s without being drawn
|
|
uint64_t retry_failed_ms = 30000; // new attempt after a failure
|
|
};
|
|
|
|
// disk may be null (disk cache unavailable).
|
|
PosterCache(Canvas* canvas, TaskRunner* io, MainQueue* main, PosterFetcher* fetcher,
|
|
util::DiskCache* disk, const Options& options);
|
|
~PosterCache();
|
|
|
|
// Start of frame: textures to create, eviction, abandonment of forgotten requests.
|
|
void begin_frame(uint64_t now_ms);
|
|
|
|
// Poster ready, or nullptr (loading started if needed, or failed: the caller
|
|
// draws a placeholder frame).
|
|
const Poster* get(const PosterKey& key);
|
|
|
|
// Loads in progress or textures waiting to be created.
|
|
bool busy() const;
|
|
size_t memory_bytes() const { return memory_bytes_; }
|
|
size_t texture_count() const;
|
|
uint64_t load_count() const { return loads_; }
|
|
|
|
private:
|
|
enum class State : uint8_t { Loading, Decoded, Ready, Failed };
|
|
struct Entry {
|
|
State state = State::Loading;
|
|
uint64_t serial = 0;
|
|
uint64_t last_frame = 0; // last frame where get() asked for it
|
|
uint64_t failed_at_ms = 0;
|
|
std::shared_ptr<std::atomic<bool>> wanted;
|
|
std::shared_ptr<Image> image; // Decoded: waiting for a texture
|
|
Poster poster;
|
|
};
|
|
|
|
void start_load(const std::string& id, const PosterKey& key, Entry* entry);
|
|
void on_loaded(const std::string& id, uint64_t serial, std::shared_ptr<Image> image);
|
|
void upload_pending();
|
|
void evict();
|
|
void release(Entry* entry);
|
|
|
|
Canvas* canvas_;
|
|
TaskRunner* io_;
|
|
MainQueue* main_;
|
|
PosterFetcher* fetcher_;
|
|
util::DiskCache* disk_;
|
|
Options options_;
|
|
std::unordered_map<std::string, Entry> entries_;
|
|
std::vector<std::string> decoded_; // arrival order of the decoded images
|
|
uint64_t frame_ = 1;
|
|
uint64_t now_ms_ = 0;
|
|
uint64_t next_serial_ = 1;
|
|
size_t memory_bytes_ = 0;
|
|
size_t loading_ = 0;
|
|
uint64_t loads_ = 0; // loads started (disk or network)
|
|
Lifetime lifetime_;
|
|
};
|
|
|
|
} // namespace ui
|
|
|
|
#endif // PELAGIA_CORE_UI_POSTER_CACHE_H
|