Issue #10: Remove too old pages from cache

This commit is contained in:
Jakub Melka 2022-02-05 18:18:21 +01:00
parent 49cab7937a
commit 8bf2913ded
5 changed files with 106 additions and 1 deletions

View File

@ -217,7 +217,8 @@ const PDFPrecompiledPage* PDFAsynchronousPageCompiler::getCompiledPage(PDFIntege
return nullptr;
}
const PDFPrecompiledPage* page = m_cache.object(pageIndex);
PDFPrecompiledPage* page = m_cache.object(pageIndex);
if (!page && compile)
{
QMutexLocker locker(&m_mutex);
@ -228,9 +229,43 @@ const PDFPrecompiledPage* PDFAsynchronousPageCompiler::getCompiledPage(PDFIntege
}
}
if (page)
{
page->markAccessed();
}
return page;
}
void PDFAsynchronousPageCompiler::smartClearCache(const int milisecondsLimit, const std::vector<PDFInteger>& activePages)
{
if (m_state != State::Active)
{
// Jakub Melka: Cache clearing can be done only in active state
return;
}
QMutexLocker locker(&m_mutex);
Q_ASSERT(std::is_sorted(activePages.cbegin(), activePages.cend()));
QList<PDFInteger> pageIndices = m_cache.keys();
for (const PDFInteger pageIndex : pageIndices)
{
if (std::binary_search(activePages.cbegin(), activePages.cend(), pageIndex))
{
// We do not remove active page
continue;
}
const PDFPrecompiledPage* page = m_cache.object(pageIndex);
if (page && page->hasExpired(milisecondsLimit))
{
m_cache.remove(pageIndex);
}
}
}
void PDFAsynchronousPageCompiler::onPageCompiled()
{
std::vector<PDFInteger> compiledPages;
@ -249,6 +284,7 @@ void PDFAsynchronousPageCompiler::onPageCompiled()
{
// If we are in active state, try to store precompiled page
PDFPrecompiledPage* page = new PDFPrecompiledPage(std::move(task.precompiledPage));
page->markAccessed();
qint64 memoryConsumptionEstimate = page->getMemoryConsumptionEstimate();
if (m_cache.insert(it->first, page, memoryConsumptionEstimate))
{

View File

@ -106,6 +106,13 @@ public:
/// \param compile Compile the page, if it is not found in the cache
const PDFPrecompiledPage* getCompiledPage(PDFInteger pageIndex, bool compile);
/// Performs smart cache clear. Too old pages are removed from the cache,
/// but only if these pages are not in active pages. Use this function to
/// clear cache to avoid huge memory consumption.
/// \param milisecondsLimit Pages with access time above this limit will be erased
/// \param activePages Sorted vector of active pages, which should remain in cache
void smartClearCache(const int milisecondsLimit, const std::vector<PDFInteger>& activePages);
/// Is operation being cancelled?
virtual bool isOperationCancelled() const override;

View File

@ -26,6 +26,7 @@
#include "pdfannotation.h"
#include "pdfdbgheap.h"
#include <QTimer>
#include <QPainter>
#include <QFontMetrics>
@ -464,6 +465,7 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
m_textLayoutCompiler(new PDFAsynchronousTextLayoutCompiler(this)),
m_rasterizer(new PDFRasterizer(this)),
m_progress(nullptr),
m_cacheClearTimer(new QTimer(this)),
m_useOpenGL(false)
{
m_controller = new PDFDrawSpaceController(this);
@ -473,6 +475,7 @@ PDFDrawWidgetProxy::PDFDrawWidgetProxy(QObject* parent) :
connect(m_compiler, &PDFAsynchronousPageCompiler::renderingError, this, &PDFDrawWidgetProxy::renderingError);
connect(m_compiler, &PDFAsynchronousPageCompiler::pageImageChanged, this, &PDFDrawWidgetProxy::pageImageChanged);
connect(m_textLayoutCompiler, &PDFAsynchronousTextLayoutCompiler::textLayoutChanged, this, &PDFDrawWidgetProxy::onTextLayoutChanged);
connect(m_cacheClearTimer, &QTimer::timeout, this, &PDFDrawWidgetProxy::performPageCacheClear);
}
PDFDrawWidgetProxy::~PDFDrawWidgetProxy()
@ -484,6 +487,7 @@ void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document)
{
if (getDocument() != document)
{
m_cacheClearTimer->stop();
m_compiler->stop(document.hasReset());
m_textLayoutCompiler->stop(document.hasReset());
m_controller->setDocument(document);
@ -495,6 +499,11 @@ void PDFDrawWidgetProxy::setDocument(const PDFModifiedDocument& document)
m_compiler->start();
m_textLayoutCompiler->start();
if (document)
{
m_cacheClearTimer->start(CACHE_CLEAR_TIMEOUT);
}
}
}
@ -951,6 +960,29 @@ std::vector<PDFInteger> PDFDrawWidgetProxy::getPagesIntersectingRect(QRect rect)
return pages;
}
std::vector<PDFInteger> PDFDrawWidgetProxy::getActivePages() const
{
std::vector<PDFInteger> activePages = getPagesIntersectingRect(m_widget->rect());
// Consider page prefetching - at least two pages after last current
// pages are treated as active.
if (!activePages.empty())
{
if (const PDFDocument* document = getDocument())
{
const PDFInteger pageIndex = activePages.back();
const PDFInteger pageCount = document->getCatalog()->getPageCount();
const PDFInteger pageEnd = qMin(pageCount, pageIndex + 3);
for (PDFInteger i = pageIndex + 1; i < pageEnd; ++i)
{
activePages.push_back(i);
}
}
}
return activePages;
}
PDFInteger PDFDrawWidgetProxy::getPageUnderPoint(QPoint point, QPointF* pagePoint) const
{
// Iterate trough pages, place them and test, if they intersects with rectangle
@ -1259,6 +1291,12 @@ QRectF PDFDrawWidgetProxy::fromDeviceSpace(const QRectF& rect) const
rect.height() * m_deviceSpaceUnitToPixel);
}
void PDFDrawWidgetProxy::performPageCacheClear()
{
std::vector<PDFInteger> activePage = getActivePages();
m_compiler->smartClearCache(CACHE_PAGE_EXPIRATION_TIMEOUT, activePage);
}
void PDFDrawWidgetProxy::onTextLayoutChanged()
{
emit repaintNeeded();

View File

@ -30,6 +30,7 @@
class QPainter;
class QScrollBar;
class QTimer;
namespace pdf
{
@ -307,6 +308,9 @@ public:
/// \param rect Rectangle to test
std::vector<PDFInteger> getPagesIntersectingRect(QRect rect) const;
/// Returns sorted vector of page indices, which should remain in the cache
std::vector<PDFInteger> getActivePages() const;
/// Returns page, under which is point. If no page is under the point,
/// then -1 is returned. Point is in widget coordinates. If \p pagePoint
/// is not nullptr, then point in page coordinate space is set here.
@ -431,9 +435,14 @@ private:
static constexpr PDFReal MIN_ZOOM = 8.0 / 100.0;
static constexpr PDFReal MAX_ZOOM = 6400.0 / 100.0;
static constexpr qint64 CACHE_CLEAR_TIMEOUT = 5000;
static constexpr qint64 CACHE_PAGE_EXPIRATION_TIMEOUT = 30000;
/// Converts rectangle from device space to the pixel space
QRectF fromDeviceSpace(const QRectF& rect) const;
void performPageCacheClear();
void onTextLayoutChanged();
void onOptionalContentGroupStateChanged();
void onColorManagementSystemChanged();
@ -529,6 +538,9 @@ private:
/// Progress
PDFProgress* m_progress;
/// Cache clear timer
QTimer* m_cacheClearTimer;
/// Additional drawing interfaces
std::set<IDocumentDrawInterface*> m_drawInterfaces;

View File

@ -27,6 +27,7 @@
#include <QPen>
#include <QBrush>
#include <QElapsedTimer>
namespace pdf
{
@ -239,6 +240,16 @@ public:
PDFSnapInfo* getSnapInfo() { return &m_snapInfo; }
const PDFSnapInfo* getSnapInfo() const { return &m_snapInfo; }
/// Mark this precompiled page as accessed at a current time
void markAccessed() { m_expirationTimer.start(); }
/// Has page content expired with given timeout? This function
/// is used together with function \p markAccessed to control
/// cached pages expiration policy. Pages can be marked as accessed,
/// and too old accessed pages can be removed.
/// \sa markAccessed
bool hasExpired(qint64 timeout) const { return m_expirationTimer.hasExpired(timeout); }
struct GraphicPieceInfo
{
enum class Type
@ -347,6 +358,7 @@ private:
std::vector<QPainter::CompositionMode> m_compositionModes;
QList<PDFRenderError> m_errors;
PDFSnapInfo m_snapInfo;
QElapsedTimer m_expirationTimer;
};
/// Processor, which processes PDF's page commands and writes them to the precompiled page.