mirror of https://github.com/JakubMelka/PDF4QT.git
Issue #10: Remove too old pages from cache
This commit is contained in:
parent
49cab7937a
commit
8bf2913ded
|
@ -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))
|
||||
{
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
Loading…
Reference in New Issue